From 7f3d74c8c8dd196f7cde4def6a872de1e585889c Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Thu, 18 Jan 2024 10:39:49 +0000 Subject: [PATCH 01/20] Fix warning --- .../src/SavePaymentMethodsModule.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php index 1e4f70d9e..3529247a2 100644 --- a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php +++ b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php @@ -106,8 +106,9 @@ class SavePaymentMethodsModule implements ModuleInterface { } if ( $payment_method === PayPalGateway::ID ) { + $funding_source = $request_data['funding_source'] ?? null; - if ( $request_data['funding_source'] === 'venmo' ) { + if ( $funding_source === 'venmo' ) { $data['payment_source'] = array( 'venmo' => array( 'attributes' => array( @@ -118,6 +119,20 @@ class SavePaymentMethodsModule implements ModuleInterface { ), ), ); + } else if ( $funding_source === 'apple_pay' ) { + $data['payment_source'] = array( + 'apple_pay' => array( + 'stored_credential' => array( + 'payment_initiator' => 'CUSTOMER', + 'payment_type' => 'RECURRING', + ), + 'attributes' => array( + 'vault' => array( + 'store_in_vault' => 'ON_SUCCESS', + ), + ), + ), + ); } else { $data['payment_source'] = array( 'paypal' => array( From 827bd2568db908e7f5afff0c2377ebb9d27c336b Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Fri, 19 Jan 2024 09:52:59 +0000 Subject: [PATCH 02/20] Fix passing funding_source on block pages. --- modules/ppcp-blocks/resources/js/checkout-block.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index 654868447..1662d7d89 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -77,7 +77,7 @@ const PayPalComponent = ({ window.ppcpContinuationFilled = true; }, []) - const createOrder = async () => { + const createOrder = async (data, actions) => { try { const res = await fetch(config.scriptData.ajax.create_order.endpoint, { method: 'POST', @@ -87,6 +87,7 @@ const PayPalComponent = ({ bn_code: '', context: config.scriptData.context, payment_method: 'ppcp-gateway', + funding_source: data.paymentSource, createaccount: false }), }); From 96b83c9d0d607f5efacab0392729520f24594235 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Wed, 24 Jan 2024 08:47:48 +0000 Subject: [PATCH 03/20] ApplePay Vaulting Integration --- .../resources/js/ApplepayButton.js | 6 ++- .../resources/js/checkout-block.js | 2 +- .../js/modules/ButtonModuleWatcher.js | 1 - .../src/SavePaymentMethodsModule.php | 7 +-- .../src/WooCommercePaymentTokens.php | 14 ++++-- .../ppcp-vaulting/src/PaymentTokenPayPal.php | 21 ++++++++- modules/ppcp-vaulting/src/VaultingModule.php | 47 ++++++++++++++++++- 7 files changed, 86 insertions(+), 12 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 4484bba21..e9ec47bde 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -209,11 +209,15 @@ class ApplepayButton { /** * Show Apple Pay payment sheet when Apple Pay payment button is clicked */ - async onButtonClick() { + async onButtonClick(data, actions) { + console.log('data, actions', data, actions); + this.log('onButtonClick', this.context); const paymentRequest = this.paymentRequest(); + window.ppcpFundingSource = 'apple_pay'; // Do this on another place like on create order endpoint handler. + // Trigger woocommerce validation if we are in the checkout page. if (this.context === 'checkout') { const checkoutFormSelector = 'form.woocommerce-checkout'; diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index 1662d7d89..d999efa80 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -87,7 +87,7 @@ const PayPalComponent = ({ bn_code: '', context: config.scriptData.context, payment_method: 'ppcp-gateway', - funding_source: data.paymentSource, + funding_source: window.ppcpFundingSource ?? 'paypal', createaccount: false }), }); diff --git a/modules/ppcp-button/resources/js/modules/ButtonModuleWatcher.js b/modules/ppcp-button/resources/js/modules/ButtonModuleWatcher.js index c6165674a..3fccca178 100644 --- a/modules/ppcp-button/resources/js/modules/ButtonModuleWatcher.js +++ b/modules/ppcp-button/resources/js/modules/ButtonModuleWatcher.js @@ -7,7 +7,6 @@ class ButtonModuleWatcher { } watchContextBootstrap(callable) { - console.log('ButtonModuleWatcher.js: watchContextBootstrap', this.contextBootstrapRegistry) this.contextBootstrapWatchers.push(callable); Object.values(this.contextBootstrapRegistry).forEach(callable); } diff --git a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php index 71441f562..e79ec11f6 100644 --- a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php +++ b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php @@ -119,14 +119,14 @@ class SavePaymentMethodsModule implements ModuleInterface { ), ), ); - } else if ( $funding_source === 'apple_pay' ) { + } elseif ( $funding_source && $funding_source === 'apple_pay' ) { $data['payment_source'] = array( 'apple_pay' => array( 'stored_credential' => array( 'payment_initiator' => 'CUSTOMER', 'payment_type' => 'RECURRING', ), - 'attributes' => array( + 'attributes' => array( 'vault' => array( 'store_in_vault' => 'ON_SUCCESS', ), @@ -191,7 +191,8 @@ class SavePaymentMethodsModule implements ModuleInterface { $wc_payment_tokens->create_payment_token_paypal( $wc_order->get_customer_id(), $token_id, - $payment_source->properties()->email_address ?? '' + $payment_source->properties()->email_address ?? '', + $payment_source->name() ); } } diff --git a/modules/ppcp-save-payment-methods/src/WooCommercePaymentTokens.php b/modules/ppcp-save-payment-methods/src/WooCommercePaymentTokens.php index 520099994..3e5f99b04 100644 --- a/modules/ppcp-save-payment-methods/src/WooCommercePaymentTokens.php +++ b/modules/ppcp-save-payment-methods/src/WooCommercePaymentTokens.php @@ -66,16 +66,18 @@ class WooCommercePaymentTokens { /** * Creates a WC Payment Token for PayPal payment. * - * @param int $customer_id The WC customer ID. - * @param string $token The PayPal payment token. - * @param string $email The PayPal customer email. + * @param int $customer_id The WC customer ID. + * @param string $token The PayPal payment token. + * @param string $email The PayPal customer email. + * @param string $payment_source The funding source. * * @return int */ public function create_payment_token_paypal( int $customer_id, string $token, - string $email + string $email, + string $payment_source = '' ): int { $wc_tokens = WC_Payment_Tokens::get_customer_tokens( $customer_id, PayPalGateway::ID ); @@ -94,6 +96,10 @@ class WooCommercePaymentTokens { $payment_token_paypal->set_email( $email ); } + if ( $payment_source ) { + $payment_token_paypal->set_payment_source( $payment_source ); + } + try { $payment_token_paypal->save(); } catch ( Exception $exception ) { diff --git a/modules/ppcp-vaulting/src/PaymentTokenPayPal.php b/modules/ppcp-vaulting/src/PaymentTokenPayPal.php index 1a5858118..3b8eac849 100644 --- a/modules/ppcp-vaulting/src/PaymentTokenPayPal.php +++ b/modules/ppcp-vaulting/src/PaymentTokenPayPal.php @@ -28,7 +28,8 @@ class PaymentTokenPayPal extends WC_Payment_Token { * @var string[] */ protected $extra_data = array( - 'email' => '', + 'email' => '', + 'payment_source' => '', ); /** @@ -48,4 +49,22 @@ class PaymentTokenPayPal extends WC_Payment_Token { public function set_email( $email ) { $this->add_meta_data( 'email', $email, true ); } + + /** + * Get the payment source. + * + * @return string The payment source. + */ + public function get_payment_source() { + return $this->get_meta( 'payment_source' ); + } + + /** + * Set the payment source. + * + * @param string $payment_source The payment source. + */ + public function set_payment_source( string $payment_source ) { + $this->add_meta_data( 'payment_source', $payment_source, true ); + } } diff --git a/modules/ppcp-vaulting/src/VaultingModule.php b/modules/ppcp-vaulting/src/VaultingModule.php index bba8e5464..1860a889d 100644 --- a/modules/ppcp-vaulting/src/VaultingModule.php +++ b/modules/ppcp-vaulting/src/VaultingModule.php @@ -86,6 +86,37 @@ class VaultingModule implements ModuleInterface { } ); + add_filter( + 'woocommerce_get_customer_payment_tokens', + /** + * Filter available payment tokens depending on context. + * + * @psalm-suppress MissingClosureParamType + * @psalm-suppress MissingClosureReturnType + */ + function( $tokens, $customer_id, $gateway_id ) { + if ( ! is_array( $tokens ) ) { + return $tokens; + } + + // Exclude ApplePay tokens from payment pages. + if ( is_checkout() || is_cart() || is_product() ) { + foreach ( $tokens as $index => $token ) { + if ( + $token instanceof PaymentTokenPayPal + && $token->get_payment_source() === 'apple_pay' + ) { + unset( $tokens[ $index ] ); + } + } + } + + return $tokens; + }, + 10, + 3 + ); + add_filter( 'woocommerce_payment_methods_list_item', /** @@ -100,8 +131,22 @@ class VaultingModule implements ModuleInterface { if ( strtolower( $payment_token->get_type() ) === 'paypal' ) { assert( $payment_token instanceof PaymentTokenPayPal ); - $item['method']['brand'] = $payment_token->get_email(); + $email = $payment_token->get_email(); + $payment_source = $payment_token->get_payment_source(); + $brand_parts = array(); + + if ( $payment_source !== 'paypal' ) { + $brand_parts[] = ucwords( $payment_source ); + } + + if ( $email ) { + $brand_parts[] = $email; + } else { + $brand_parts[] = '#' . ( (string) $payment_token->get_id() ); + } + + $item['method']['brand'] = implode( ' / ', array_filter( $brand_parts ) ); return $item; } From 80df0c9b2752076435f18f68cacfe4744adbf58a Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Wed, 24 Jan 2024 15:13:57 +0000 Subject: [PATCH 04/20] Fix lint --- modules/ppcp-applepay/resources/js/ApplepayButton.js | 2 -- .../src/WooCommercePaymentTokens.php | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index e9ec47bde..9786805a4 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -210,8 +210,6 @@ class ApplepayButton { * Show Apple Pay payment sheet when Apple Pay payment button is clicked */ async onButtonClick(data, actions) { - console.log('data, actions', data, actions); - this.log('onButtonClick', this.context); const paymentRequest = this.paymentRequest(); diff --git a/modules/ppcp-save-payment-methods/src/WooCommercePaymentTokens.php b/modules/ppcp-save-payment-methods/src/WooCommercePaymentTokens.php index 3e5f99b04..e57198a0d 100644 --- a/modules/ppcp-save-payment-methods/src/WooCommercePaymentTokens.php +++ b/modules/ppcp-save-payment-methods/src/WooCommercePaymentTokens.php @@ -66,9 +66,9 @@ class WooCommercePaymentTokens { /** * Creates a WC Payment Token for PayPal payment. * - * @param int $customer_id The WC customer ID. - * @param string $token The PayPal payment token. - * @param string $email The PayPal customer email. + * @param int $customer_id The WC customer ID. + * @param string $token The PayPal payment token. + * @param string $email The PayPal customer email. * @param string $payment_source The funding source. * * @return int From 3b4bab512fd8d6849230ef2a265391e88aeafc5b Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Wed, 24 Jan 2024 18:05:00 +0000 Subject: [PATCH 05/20] Add process merchant payments for ApplePay vaulted tokens --- .../src/RenewalHandler.php | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-wc-subscriptions/src/RenewalHandler.php b/modules/ppcp-wc-subscriptions/src/RenewalHandler.php index 636de372d..9d0f2f691 100644 --- a/modules/ppcp-wc-subscriptions/src/RenewalHandler.php +++ b/modules/ppcp-wc-subscriptions/src/RenewalHandler.php @@ -22,6 +22,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; use WooCommerce\PayPalCommerce\Onboarding\Environment; +use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenPayPal; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; @@ -202,11 +203,31 @@ class RenewalHandler { if ( $wc_order->get_payment_method() === PayPalGateway::ID ) { $wc_tokens = WC_Payment_Tokens::get_customer_tokens( $wc_order->get_customer_id(), PayPalGateway::ID ); foreach ( $wc_tokens as $token ) { + $name = 'paypal'; + $properties = array( + 'vault_id' => $token->get_token(), + ); + + if ( $token instanceof PaymentTokenPayPal ) { + $payment_source_name = $token->get_payment_source(); + + if ( $payment_source_name ) { + $name = $payment_source_name; + } + + // Add required stored_credentials for apple_pay + if ( $payment_source_name === 'apple_pay' ) { + $properties['stored_credential'] = array( + 'payment_initiator' => 'MERCHANT', + 'payment_type' => 'RECURRING', + 'usage' => 'SUBSEQUENT', + ); + } + } + $payment_source = new PaymentSource( - 'paypal', - (object) array( - 'vault_id' => $token->get_token(), - ) + $name, + (object) $properties ); break; From 94b92d84345f319aecf1d54d1b0be9e6ae08217f Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Wed, 24 Jan 2024 18:15:50 +0000 Subject: [PATCH 06/20] Remove not needed code. --- modules/ppcp-applepay/resources/js/ApplepayButton.js | 2 +- modules/ppcp-blocks/resources/js/checkout-block.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 9786805a4..89015d673 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -209,7 +209,7 @@ class ApplepayButton { /** * Show Apple Pay payment sheet when Apple Pay payment button is clicked */ - async onButtonClick(data, actions) { + async onButtonClick() { this.log('onButtonClick', this.context); const paymentRequest = this.paymentRequest(); diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index d999efa80..0c01962cf 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -77,7 +77,7 @@ const PayPalComponent = ({ window.ppcpContinuationFilled = true; }, []) - const createOrder = async (data, actions) => { + const createOrder = async () => { try { const res = await fetch(config.scriptData.ajax.create_order.endpoint, { method: 'POST', From b3b6693aa270f762d9130e3c33f059b36d5fb7b2 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Wed, 24 Jan 2024 18:25:17 +0000 Subject: [PATCH 07/20] Fix lint --- modules/ppcp-wc-subscriptions/src/RenewalHandler.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-wc-subscriptions/src/RenewalHandler.php b/modules/ppcp-wc-subscriptions/src/RenewalHandler.php index 9d0f2f691..e3ce5bbfd 100644 --- a/modules/ppcp-wc-subscriptions/src/RenewalHandler.php +++ b/modules/ppcp-wc-subscriptions/src/RenewalHandler.php @@ -203,7 +203,7 @@ class RenewalHandler { if ( $wc_order->get_payment_method() === PayPalGateway::ID ) { $wc_tokens = WC_Payment_Tokens::get_customer_tokens( $wc_order->get_customer_id(), PayPalGateway::ID ); foreach ( $wc_tokens as $token ) { - $name = 'paypal'; + $name = 'paypal'; $properties = array( 'vault_id' => $token->get_token(), ); @@ -215,7 +215,7 @@ class RenewalHandler { $name = $payment_source_name; } - // Add required stored_credentials for apple_pay + // Add required stored_credentials for apple_pay. if ( $payment_source_name === 'apple_pay' ) { $properties['stored_credential'] = array( 'payment_initiator' => 'MERCHANT', From 0d4fbc4c0b3d1212139dbf43ca82441daeff85aa Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Fri, 26 Jan 2024 14:22:20 +0000 Subject: [PATCH 08/20] Refactor Venmo and ApplePay payment tokens. --- .../src/SavePaymentMethodsModule.php | 29 ++++-- .../src/WooCommercePaymentTokens.php | 90 +++++++++++++++++-- .../src/PaymentTokenApplePay.php | 31 +++++++ .../ppcp-vaulting/src/PaymentTokenFactory.php | 6 +- .../ppcp-vaulting/src/PaymentTokenHelper.php | 11 ++- .../ppcp-vaulting/src/PaymentTokenPayPal.php | 21 +---- .../ppcp-vaulting/src/PaymentTokenVenmo.php | 51 +++++++++++ .../src/PaymentTokensMigration.php | 2 +- modules/ppcp-vaulting/src/VaultingModule.php | 37 ++++---- .../src/RenewalHandler.php | 26 +++--- 10 files changed, 234 insertions(+), 70 deletions(-) create mode 100644 modules/ppcp-vaulting/src/PaymentTokenApplePay.php create mode 100644 modules/ppcp-vaulting/src/PaymentTokenVenmo.php diff --git a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php index e79ec11f6..5ea2a78dd 100644 --- a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php +++ b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php @@ -188,12 +188,29 @@ class SavePaymentMethodsModule implements ModuleInterface { } if ( $wc_order->get_payment_method() === PayPalGateway::ID ) { - $wc_payment_tokens->create_payment_token_paypal( - $wc_order->get_customer_id(), - $token_id, - $payment_source->properties()->email_address ?? '', - $payment_source->name() - ); + switch ( $payment_source->name() ) { + case 'venmo': + $wc_payment_tokens->create_payment_token_venmo( + $wc_order->get_customer_id(), + $token_id, + $payment_source->properties()->email_address ?? '' + ); + break; + case 'apple_pay': + $wc_payment_tokens->create_payment_token_applepay( + $wc_order->get_customer_id(), + $token_id + ); + break; + case 'paypal': + default: + $wc_payment_tokens->create_payment_token_paypal( + $wc_order->get_customer_id(), + $token_id, + $payment_source->properties()->email_address ?? '' + ); + break; + } } } }, diff --git a/modules/ppcp-save-payment-methods/src/WooCommercePaymentTokens.php b/modules/ppcp-save-payment-methods/src/WooCommercePaymentTokens.php index e57198a0d..c878f10d1 100644 --- a/modules/ppcp-save-payment-methods/src/WooCommercePaymentTokens.php +++ b/modules/ppcp-save-payment-methods/src/WooCommercePaymentTokens.php @@ -14,9 +14,11 @@ use Psr\Log\LoggerInterface; use stdClass; use WC_Payment_Token_CC; use WC_Payment_Tokens; +use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenApplePay; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenFactory; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenHelper; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenPayPal; +use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenVenmo; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; @@ -69,19 +71,17 @@ class WooCommercePaymentTokens { * @param int $customer_id The WC customer ID. * @param string $token The PayPal payment token. * @param string $email The PayPal customer email. - * @param string $payment_source The funding source. * * @return int */ public function create_payment_token_paypal( int $customer_id, string $token, - string $email, - string $payment_source = '' + string $email ): int { $wc_tokens = WC_Payment_Tokens::get_customer_tokens( $customer_id, PayPalGateway::ID ); - if ( $this->payment_token_helper->token_exist( $wc_tokens, $token ) ) { + if ( $this->payment_token_helper->token_exist( $wc_tokens, $token, PaymentTokenPayPal::class ) ) { return 0; } @@ -96,10 +96,6 @@ class WooCommercePaymentTokens { $payment_token_paypal->set_email( $email ); } - if ( $payment_source ) { - $payment_token_paypal->set_payment_source( $payment_source ); - } - try { $payment_token_paypal->save(); } catch ( Exception $exception ) { @@ -111,6 +107,84 @@ class WooCommercePaymentTokens { return $payment_token_paypal->get_id(); } + /** + * Creates a WC Payment Token for Venmo payment. + * + * @param int $customer_id The WC customer ID. + * @param string $token The Venmo payment token. + * @param string $email The Venmo customer email. + * + * @return int + */ + public function create_payment_token_venmo( + int $customer_id, + string $token, + string $email + ): int { + + $wc_tokens = WC_Payment_Tokens::get_customer_tokens( $customer_id, PayPalGateway::ID ); + if ( $this->payment_token_helper->token_exist( $wc_tokens, $token, PaymentTokenVenmo::class ) ) { + return 0; + } + + $payment_token_venmo = $this->payment_token_factory->create( 'venmo' ); + assert( $payment_token_venmo instanceof PaymentTokenVenmo ); + + $payment_token_venmo->set_token( $token ); + $payment_token_venmo->set_user_id( $customer_id ); + $payment_token_venmo->set_gateway_id( PayPalGateway::ID ); + + if ( $email && is_email( $email ) ) { + $payment_token_venmo->set_email( $email ); + } + + try { + $payment_token_venmo->save(); + } catch ( Exception $exception ) { + $this->logger->error( + "Could not create WC payment token Venmo for customer {$customer_id}. " . $exception->getMessage() + ); + } + + return $payment_token_venmo->get_id(); + } + + /** + * Creates a WC Payment Token for ApplePay payment. + * + * @param int $customer_id The WC customer ID. + * @param string $token The ApplePay payment token. + * + * @return int + */ + public function create_payment_token_applepay( + int $customer_id, + string $token + ): int { + + $wc_tokens = WC_Payment_Tokens::get_customer_tokens( $customer_id, PayPalGateway::ID ); + if ( $this->payment_token_helper->token_exist( $wc_tokens, $token, PaymentTokenApplePay::class ) ) { + return 0; + } + + $payment_token_applepay = $this->payment_token_factory->create( 'apple_pay' ); + assert( $payment_token_applepay instanceof PaymentTokenApplePay ); + + $payment_token_applepay->set_token( $token ); + $payment_token_applepay->set_user_id( $customer_id ); + $payment_token_applepay->set_gateway_id( PayPalGateway::ID ); + + try { + $payment_token_applepay->save(); + } catch ( Exception $exception ) { + $this->logger->error( + "Could not create WC payment token ApplePay for customer {$customer_id}. " . $exception->getMessage() + ); + } + + return $payment_token_applepay->get_id(); + } + /** * Creates a WC Payment Token for Credit Card payment. * diff --git a/modules/ppcp-vaulting/src/PaymentTokenApplePay.php b/modules/ppcp-vaulting/src/PaymentTokenApplePay.php new file mode 100644 index 000000000..a53aa254a --- /dev/null +++ b/modules/ppcp-vaulting/src/PaymentTokenApplePay.php @@ -0,0 +1,31 @@ +get_token() === $token_id ) { - return true; + if ( null !== $class_name ) { + if ( $wc_token instanceof $class_name ) { + return true; + } + } else { + return true; + } } } diff --git a/modules/ppcp-vaulting/src/PaymentTokenPayPal.php b/modules/ppcp-vaulting/src/PaymentTokenPayPal.php index 3b8eac849..1a5858118 100644 --- a/modules/ppcp-vaulting/src/PaymentTokenPayPal.php +++ b/modules/ppcp-vaulting/src/PaymentTokenPayPal.php @@ -28,8 +28,7 @@ class PaymentTokenPayPal extends WC_Payment_Token { * @var string[] */ protected $extra_data = array( - 'email' => '', - 'payment_source' => '', + 'email' => '', ); /** @@ -49,22 +48,4 @@ class PaymentTokenPayPal extends WC_Payment_Token { public function set_email( $email ) { $this->add_meta_data( 'email', $email, true ); } - - /** - * Get the payment source. - * - * @return string The payment source. - */ - public function get_payment_source() { - return $this->get_meta( 'payment_source' ); - } - - /** - * Set the payment source. - * - * @param string $payment_source The payment source. - */ - public function set_payment_source( string $payment_source ) { - $this->add_meta_data( 'payment_source', $payment_source, true ); - } } diff --git a/modules/ppcp-vaulting/src/PaymentTokenVenmo.php b/modules/ppcp-vaulting/src/PaymentTokenVenmo.php new file mode 100644 index 000000000..d53a4b4fb --- /dev/null +++ b/modules/ppcp-vaulting/src/PaymentTokenVenmo.php @@ -0,0 +1,51 @@ + '', + ); + + /** + * Get PayPal account email. + * + * @return string PayPal account email. + */ + public function get_email() { + return $this->get_meta( 'email' ); + } + + /** + * Set PayPal account email. + * + * @param string $email PayPal account email. + */ + public function set_email( $email ) { + $this->add_meta_data( 'email', $email, true ); + } +} diff --git a/modules/ppcp-vaulting/src/PaymentTokensMigration.php b/modules/ppcp-vaulting/src/PaymentTokensMigration.php index a7d3511cc..1ef41df4a 100644 --- a/modules/ppcp-vaulting/src/PaymentTokensMigration.php +++ b/modules/ppcp-vaulting/src/PaymentTokensMigration.php @@ -107,7 +107,7 @@ class PaymentTokensMigration { } } elseif ( $token->source()->paypal ) { $wc_tokens = WC_Payment_Tokens::get_customer_tokens( $id, PayPalGateway::ID ); - if ( $this->payment_token_helper->token_exist( $wc_tokens, $token->id() ) ) { + if ( $this->payment_token_helper->token_exist( $wc_tokens, $token->id(), PaymentTokenPayPal::class ) ) { $this->logger->info( 'Token already exist for user ' . (string) $id ); continue; } diff --git a/modules/ppcp-vaulting/src/VaultingModule.php b/modules/ppcp-vaulting/src/VaultingModule.php index 1860a889d..ff8acdf3b 100644 --- a/modules/ppcp-vaulting/src/VaultingModule.php +++ b/modules/ppcp-vaulting/src/VaultingModule.php @@ -81,6 +81,12 @@ class VaultingModule implements ModuleInterface { if ( $type === 'WC_Payment_Token_PayPal' ) { return PaymentTokenPayPal::class; } + if ( $type === 'WC_Payment_Token_Venmo' ) { + return PaymentTokenVenmo::class; + } + if ( $type === 'WC_Payment_Token_ApplePay' ) { + return PaymentTokenApplePay::class; + } return $type; } @@ -102,10 +108,7 @@ class VaultingModule implements ModuleInterface { // Exclude ApplePay tokens from payment pages. if ( is_checkout() || is_cart() || is_product() ) { foreach ( $tokens as $index => $token ) { - if ( - $token instanceof PaymentTokenPayPal - && $token->get_payment_source() === 'apple_pay' - ) { + if ( $token instanceof PaymentTokenApplePay ) { unset( $tokens[ $index ] ); } } @@ -129,24 +132,18 @@ class VaultingModule implements ModuleInterface { return $item; } - if ( strtolower( $payment_token->get_type() ) === 'paypal' ) { - assert( $payment_token instanceof PaymentTokenPayPal ); + if ( $payment_token instanceof PaymentTokenPayPal ) { + $item['method']['brand'] = 'PayPal / ' . $payment_token->get_email(); + return $item; + } - $email = $payment_token->get_email(); - $payment_source = $payment_token->get_payment_source(); - $brand_parts = array(); + if ( $payment_token instanceof PaymentTokenVenmo ) { + $item['method']['brand'] = 'Venmo / ' . $payment_token->get_email(); + return $item; + } - if ( $payment_source !== 'paypal' ) { - $brand_parts[] = ucwords( $payment_source ); - } - - if ( $email ) { - $brand_parts[] = $email; - } else { - $brand_parts[] = '#' . ( (string) $payment_token->get_id() ); - } - - $item['method']['brand'] = implode( ' / ', array_filter( $brand_parts ) ); + if ( $payment_token instanceof PaymentTokenApplePay ) { + $item['method']['brand'] = 'ApplePay #' . ( (string) $payment_token->get_id() ); return $item; } diff --git a/modules/ppcp-wc-subscriptions/src/RenewalHandler.php b/modules/ppcp-wc-subscriptions/src/RenewalHandler.php index e3ce5bbfd..a4ce028ee 100644 --- a/modules/ppcp-wc-subscriptions/src/RenewalHandler.php +++ b/modules/ppcp-wc-subscriptions/src/RenewalHandler.php @@ -22,9 +22,11 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; use WooCommerce\PayPalCommerce\Onboarding\Environment; +use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenApplePay; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenPayPal; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository; use Psr\Log\LoggerInterface; +use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenVenmo; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; @@ -209,20 +211,20 @@ class RenewalHandler { ); if ( $token instanceof PaymentTokenPayPal ) { - $payment_source_name = $token->get_payment_source(); + $name = 'paypal'; + } - if ( $payment_source_name ) { - $name = $payment_source_name; - } + if ( $token instanceof PaymentTokenVenmo ) { + $name = 'venmo'; + } - // Add required stored_credentials for apple_pay. - if ( $payment_source_name === 'apple_pay' ) { - $properties['stored_credential'] = array( - 'payment_initiator' => 'MERCHANT', - 'payment_type' => 'RECURRING', - 'usage' => 'SUBSEQUENT', - ); - } + if ( $token instanceof PaymentTokenApplePay ) { + $name = 'apple_pay'; + $properties['stored_credential'] = array( + 'payment_initiator' => 'MERCHANT', + 'payment_type' => 'RECURRING', + 'usage' => 'SUBSEQUENT', + ); } $payment_source = new PaymentSource( From 2167c7c78b4215ce0c40a112799482f097492b3f Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Fri, 26 Jan 2024 17:57:59 +0000 Subject: [PATCH 09/20] Add permit multiple tokens so Venmo can work alongside PayPal --- .../src/SavePaymentMethodsModule.php | 10 ++++++---- .../src/WcSubscriptionsModule.php | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php index 5ea2a78dd..2d4fd8ec6 100644 --- a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php +++ b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php @@ -113,8 +113,9 @@ class SavePaymentMethodsModule implements ModuleInterface { 'venmo' => array( 'attributes' => array( 'vault' => array( - 'store_in_vault' => 'ON_SUCCESS', - 'usage_type' => 'MERCHANT', + 'store_in_vault' => 'ON_SUCCESS', + 'usage_type' => 'MERCHANT', + 'permit_multiple_payment_tokens' => true, ), ), ), @@ -138,8 +139,9 @@ class SavePaymentMethodsModule implements ModuleInterface { 'paypal' => array( 'attributes' => array( 'vault' => array( - 'store_in_vault' => 'ON_SUCCESS', - 'usage_type' => 'MERCHANT', + 'store_in_vault' => 'ON_SUCCESS', + 'usage_type' => 'MERCHANT', + 'permit_multiple_payment_tokens' => true, ), ), ), diff --git a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php index ea20d5c69..573711a3d 100644 --- a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php +++ b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php @@ -301,7 +301,7 @@ class WcSubscriptionsModule implements ModuleInterface { foreach ( $tokens as $token ) { $output .= '
  • '; $output .= sprintf( '', $token->get_id() ); - $output .= sprintf( '', $token->get_meta( 'email' ) ?? '' ); + $output .= sprintf( '', $token->get_type(), $token->get_meta( 'email' ) ?? '' ); $output .= '
  • '; } $output .= ''; From be7ece91da8dd37f5c4b2218f7012bb2bf5579cd Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Fri, 26 Jan 2024 18:01:39 +0000 Subject: [PATCH 10/20] Fix lint --- .../src/SavePaymentMethodsModule.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php index 2d4fd8ec6..f9032e681 100644 --- a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php +++ b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php @@ -113,8 +113,8 @@ class SavePaymentMethodsModule implements ModuleInterface { 'venmo' => array( 'attributes' => array( 'vault' => array( - 'store_in_vault' => 'ON_SUCCESS', - 'usage_type' => 'MERCHANT', + 'store_in_vault' => 'ON_SUCCESS', + 'usage_type' => 'MERCHANT', 'permit_multiple_payment_tokens' => true, ), ), @@ -139,8 +139,8 @@ class SavePaymentMethodsModule implements ModuleInterface { 'paypal' => array( 'attributes' => array( 'vault' => array( - 'store_in_vault' => 'ON_SUCCESS', - 'usage_type' => 'MERCHANT', + 'store_in_vault' => 'ON_SUCCESS', + 'usage_type' => 'MERCHANT', 'permit_multiple_payment_tokens' => true, ), ), From c342a4a0d8551962e1fad717b070b81569c67659 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Tue, 30 Jan 2024 17:46:28 +0000 Subject: [PATCH 11/20] Add ApplePay subscription validations in block pages. --- modules/ppcp-applepay/resources/js/ApplepayButton.js | 4 ---- modules/ppcp-applepay/resources/js/Context/BaseHandler.js | 7 ++++++- .../ppcp-applepay/resources/js/Context/PayNowHandler.js | 2 +- .../resources/js/Context/SingleProductHandler.js | 2 +- modules/ppcp-applepay/resources/js/boot-block.js | 5 +++++ 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 89015d673..3526a0f03 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -70,10 +70,6 @@ class ApplepayButton { if (this.isEligible) { this.fetchTransactionInfo().then(() => { - const isSubscriptionProduct = this.ppcpConfig?.data_client_id?.has_subscriptions === true; - if (isSubscriptionProduct) { - return; - } this.addButton(); const id_minicart = "#apple-" + this.buttonConfig.button.mini_cart_wrapper; const id = "#apple-" + this.buttonConfig.button.wrapper; diff --git a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js index 61d614ab9..02d8ca0b2 100644 --- a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js @@ -9,9 +9,14 @@ class BaseHandler { this.ppcpConfig = ppcpConfig; } + isVaultV3Mode() { + return this.ppcpConfig?.save_payment_methods?.id_token // vault v3 + && ! this.ppcpConfig.data_client_id.paypal_subscriptions_enabled; // not PayPal Subscriptions mode + } + validateContext() { if ( this.ppcpConfig?.locations_with_subscription_product?.cart ) { - return false; + return this.isVaultV3Mode(); } return true; } diff --git a/modules/ppcp-applepay/resources/js/Context/PayNowHandler.js b/modules/ppcp-applepay/resources/js/Context/PayNowHandler.js index a580c4084..a12fd3636 100644 --- a/modules/ppcp-applepay/resources/js/Context/PayNowHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/PayNowHandler.js @@ -7,7 +7,7 @@ class PayNowHandler extends BaseHandler { validateContext() { if ( this.ppcpConfig?.locations_with_subscription_product?.payorder ) { - return false; + return this.isVaultV3Mode(); } return true; } diff --git a/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js b/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js index 5825a1f2c..5ad5857be 100644 --- a/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js @@ -9,7 +9,7 @@ class SingleProductHandler extends BaseHandler { validateContext() { if ( this.ppcpConfig?.locations_with_subscription_product?.product ) { - return false; + return this.isVaultV3Mode(); } return true; } diff --git a/modules/ppcp-applepay/resources/js/boot-block.js b/modules/ppcp-applepay/resources/js/boot-block.js index b8f905b6d..df20e67f5 100644 --- a/modules/ppcp-applepay/resources/js/boot-block.js +++ b/modules/ppcp-applepay/resources/js/boot-block.js @@ -1,6 +1,7 @@ import {useEffect, useState} from '@wordpress/element'; import {registerExpressPaymentMethod, registerPaymentMethod} from '@woocommerce/blocks-registry'; import {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading' +import {cartHasSubscriptionProducts} from '../../../ppcp-blocks/resources/js/Helper/Subscription' import ApplepayManager from "./ApplepayManager"; import {loadCustomScript} from "@paypal/paypal-js"; @@ -50,6 +51,10 @@ const ApplePayComponent = () => { const features = ['products']; +if (cartHasSubscriptionProducts(ppcpConfig)) { + features.push('subscriptions'); +} + registerExpressPaymentMethod({ name: buttonData.id, label:
    , From ebcc2ba3f9a77f6c07296291ce7f22f4382effe2 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Tue, 30 Jan 2024 18:22:53 +0000 Subject: [PATCH 12/20] Fix subscription validations --- modules/ppcp-applepay/resources/js/boot-block.js | 10 ++++++++-- modules/ppcp-blocks/resources/js/checkout-block.js | 8 ++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/boot-block.js b/modules/ppcp-applepay/resources/js/boot-block.js index df20e67f5..5dfb62b1f 100644 --- a/modules/ppcp-applepay/resources/js/boot-block.js +++ b/modules/ppcp-applepay/resources/js/boot-block.js @@ -1,7 +1,10 @@ import {useEffect, useState} from '@wordpress/element'; import {registerExpressPaymentMethod, registerPaymentMethod} from '@woocommerce/blocks-registry'; import {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading' -import {cartHasSubscriptionProducts} from '../../../ppcp-blocks/resources/js/Helper/Subscription' +import { + cartHasSubscriptionProducts, + isPayPalSubscription +} from '../../../ppcp-blocks/resources/js/Helper/Subscription' import ApplepayManager from "./ApplepayManager"; import {loadCustomScript} from "@paypal/paypal-js"; @@ -51,7 +54,10 @@ const ApplePayComponent = () => { const features = ['products']; -if (cartHasSubscriptionProducts(ppcpConfig)) { +if (cartHasSubscriptionProducts(ppcpConfig) + && ! isPayPalSubscription(ppcpConfig) + && ppcpConfig.can_save_vault_token +) { features.push('subscriptions'); } diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index 0c01962cf..f14417e1c 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -477,6 +477,14 @@ if(cartHasSubscriptionProducts(config.scriptData)) { block_enabled = false; } + // Don't render if vaulting disabled and is in vault subscription mode + if( + ! isPayPalSubscription(config.scriptData) + && ! config.scriptData.can_save_vault_token + ) { + block_enabled = false; + } + // Don't render buttons if in subscription mode and product not associated with a PayPal subscription if( isPayPalSubscription(config.scriptData) From 8d82e515068174c387b16473bcda5ba0bbccbb54 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Wed, 31 Jan 2024 10:32:21 +0000 Subject: [PATCH 13/20] Fix subscription validations --- .../resources/js/Context/BaseHandler.js | 4 +++- .../ppcp-applepay/resources/js/boot-block.js | 14 +++++------ .../src/WooCommercePaymentTokens.php | 24 ++++++++++++++++--- .../ppcp-vaulting/src/PaymentTokenHelper.php | 17 +++++++++++++ .../src/Settings/SettingsListener.php | 3 ++- 5 files changed, 49 insertions(+), 13 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js index 02d8ca0b2..ba3214c34 100644 --- a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js @@ -1,6 +1,7 @@ import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler"; import CartActionHandler from "../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler"; +import {isPayPalSubscription} from "../../../../ppcp-blocks/resources/js/Helper/Subscription"; class BaseHandler { @@ -11,7 +12,8 @@ class BaseHandler { isVaultV3Mode() { return this.ppcpConfig?.save_payment_methods?.id_token // vault v3 - && ! this.ppcpConfig.data_client_id.paypal_subscriptions_enabled; // not PayPal Subscriptions mode + && ! this.ppcpConfig?.data_client_id?.paypal_subscriptions_enabled // not PayPal Subscriptions mode + && this.ppcpConfig?.ppcpConfig.can_save_vault_token; // vault is enabled } validateContext() { diff --git a/modules/ppcp-applepay/resources/js/boot-block.js b/modules/ppcp-applepay/resources/js/boot-block.js index 5dfb62b1f..b1d29c9c9 100644 --- a/modules/ppcp-applepay/resources/js/boot-block.js +++ b/modules/ppcp-applepay/resources/js/boot-block.js @@ -1,12 +1,10 @@ import {useEffect, useState} from '@wordpress/element'; -import {registerExpressPaymentMethod, registerPaymentMethod} from '@woocommerce/blocks-registry'; +import {registerExpressPaymentMethod} from '@woocommerce/blocks-registry'; import {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading' -import { - cartHasSubscriptionProducts, - isPayPalSubscription -} from '../../../ppcp-blocks/resources/js/Helper/Subscription' +import {cartHasSubscriptionProducts} from '../../../ppcp-blocks/resources/js/Helper/Subscription' import ApplepayManager from "./ApplepayManager"; import {loadCustomScript} from "@paypal/paypal-js"; +import CheckoutHandler from "./Context/CheckoutHandler"; const ppcpData = wc.wcSettings.getSetting('ppcp-gateway_data'); const ppcpConfig = ppcpData.scriptData; @@ -54,9 +52,9 @@ const ApplePayComponent = () => { const features = ['products']; -if (cartHasSubscriptionProducts(ppcpConfig) - && ! isPayPalSubscription(ppcpConfig) - && ppcpConfig.can_save_vault_token +if ( + cartHasSubscriptionProducts(ppcpConfig) + && (new CheckoutHandler(buttonConfig, ppcpConfig)).isVaultV3Mode() ) { features.push('subscriptions'); } diff --git a/modules/ppcp-save-payment-methods/src/WooCommercePaymentTokens.php b/modules/ppcp-save-payment-methods/src/WooCommercePaymentTokens.php index c878f10d1..b0f37a449 100644 --- a/modules/ppcp-save-payment-methods/src/WooCommercePaymentTokens.php +++ b/modules/ppcp-save-payment-methods/src/WooCommercePaymentTokens.php @@ -85,7 +85,13 @@ class WooCommercePaymentTokens { return 0; } - $payment_token_paypal = $this->payment_token_factory->create( 'paypal' ); + // Try to update existing token of type before creating a new one. + $payment_token_paypal = $this->payment_token_helper->first_token_of_type( $wc_tokens, PaymentTokenPayPal::class ); + + if ( ! $payment_token_paypal ) { + $payment_token_paypal = $this->payment_token_factory->create( 'paypal' ); + } + assert( $payment_token_paypal instanceof PaymentTokenPayPal ); $payment_token_paypal->set_token( $token ); @@ -127,7 +133,13 @@ class WooCommercePaymentTokens { return 0; } - $payment_token_venmo = $this->payment_token_factory->create( 'venmo' ); + // Try to update existing token of type before creating a new one. + $payment_token_venmo = $this->payment_token_helper->first_token_of_type( $wc_tokens, PaymentTokenVenmo::class ); + + if ( ! $payment_token_venmo ) { + $payment_token_venmo = $this->payment_token_factory->create( 'venmo' ); + } + assert( $payment_token_venmo instanceof PaymentTokenVenmo ); $payment_token_venmo->set_token( $token ); @@ -167,7 +179,13 @@ class WooCommercePaymentTokens { return 0; } - $payment_token_applepay = $this->payment_token_factory->create( 'apple_pay' ); + // Try to update existing token of type before creating a new one. + $payment_token_applepay = $this->payment_token_helper->first_token_of_type( $wc_tokens, PaymentTokenApplePay::class ); + + if ( ! $payment_token_applepay ) { + $payment_token_applepay = $this->payment_token_factory->create( 'apple_pay' ); + } + assert( $payment_token_applepay instanceof PaymentTokenApplePay ); $payment_token_applepay->set_token( $token ); diff --git a/modules/ppcp-vaulting/src/PaymentTokenHelper.php b/modules/ppcp-vaulting/src/PaymentTokenHelper.php index 9edac18a9..368e146b1 100644 --- a/modules/ppcp-vaulting/src/PaymentTokenHelper.php +++ b/modules/ppcp-vaulting/src/PaymentTokenHelper.php @@ -39,4 +39,21 @@ class PaymentTokenHelper { return false; } + + /** + * Checks if given token exist as WC Payment Token. + * + * @param array $wc_tokens WC Payment Tokens. + * @param string $class_name Class name of the token. + * @return null|WC_Payment_Token + */ + public function first_token_of_type( array $wc_tokens, string $class_name ) { + foreach ( $wc_tokens as $wc_token ) { + if ( $wc_token instanceof $class_name ) { + return $wc_token; + } + } + + return null; + } } diff --git a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php index 5f1bd38ad..35ff96fff 100644 --- a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php +++ b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php @@ -346,9 +346,10 @@ class SettingsListener { /** * Prevent enabling both Pay Later messaging and PayPal vaulting * + * @return void * @throws RuntimeException When API request fails. */ - public function listen_for_vaulting_enabled() { + public function listen_for_vaulting_enabled(): void { if ( ! $this->is_valid_site_request() || State::STATE_ONBOARDED !== $this->state->current_state() ) { return; } From 0cf3f946c6373531bfeb1b67eb505c9d708933de Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Wed, 31 Jan 2024 14:59:47 +0000 Subject: [PATCH 14/20] Fix ApplePay token update --- modules/ppcp-applepay/resources/js/Context/BaseHandler.js | 2 +- modules/ppcp-vaulting/src/VaultingModule.php | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js index ba3214c34..69745082e 100644 --- a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js @@ -13,7 +13,7 @@ class BaseHandler { isVaultV3Mode() { return this.ppcpConfig?.save_payment_methods?.id_token // vault v3 && ! this.ppcpConfig?.data_client_id?.paypal_subscriptions_enabled // not PayPal Subscriptions mode - && this.ppcpConfig?.ppcpConfig.can_save_vault_token; // vault is enabled + && this.ppcpConfig?.can_save_vault_token; // vault is enabled } validateContext() { diff --git a/modules/ppcp-vaulting/src/VaultingModule.php b/modules/ppcp-vaulting/src/VaultingModule.php index 497ebecbe..5216b82c3 100644 --- a/modules/ppcp-vaulting/src/VaultingModule.php +++ b/modules/ppcp-vaulting/src/VaultingModule.php @@ -105,8 +105,13 @@ class VaultingModule implements ModuleInterface { return $tokens; } + $is_post = isset( $_SERVER['REQUEST_METHOD'] ) && $_SERVER['REQUEST_METHOD'] === 'POST'; + // Exclude ApplePay tokens from payment pages. - if ( is_checkout() || is_cart() || is_product() ) { + if ( + ( is_checkout() || is_cart() || is_product() ) + && ! $is_post // Don't check on POST so we have all payment methods on form submissions. + ) { foreach ( $tokens as $index => $token ) { if ( $token instanceof PaymentTokenApplePay ) { unset( $tokens[ $index ] ); From 77741f584371e0a2c9b50b537bc87b66c8e4affb Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Thu, 1 Feb 2024 09:43:50 +0000 Subject: [PATCH 15/20] Fix unset property warning --- .../src/Factory/FraudProcessorResponseFactory.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-api-client/src/Factory/FraudProcessorResponseFactory.php b/modules/ppcp-api-client/src/Factory/FraudProcessorResponseFactory.php index e48d7fd3e..86ac2d7ee 100644 --- a/modules/ppcp-api-client/src/Factory/FraudProcessorResponseFactory.php +++ b/modules/ppcp-api-client/src/Factory/FraudProcessorResponseFactory.php @@ -25,8 +25,8 @@ class FraudProcessorResponseFactory { * @return FraudProcessorResponse */ public function from_paypal_response( stdClass $data ): FraudProcessorResponse { - $avs_code = $data->avs_code ?: null; - $cvv_code = $data->cvv_code ?: null; + $avs_code = ($data->avs_code ?? null) ?: null; + $cvv_code = ($data->cvv_code ?? null) ?: null; return new FraudProcessorResponse( $avs_code, $cvv_code ); } From 977f9836e295442bb5e6a1341038780856fd6697 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Thu, 1 Feb 2024 09:53:26 +0000 Subject: [PATCH 16/20] Fix lint --- .../src/Factory/FraudProcessorResponseFactory.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-api-client/src/Factory/FraudProcessorResponseFactory.php b/modules/ppcp-api-client/src/Factory/FraudProcessorResponseFactory.php index 86ac2d7ee..2a2e0b68c 100644 --- a/modules/ppcp-api-client/src/Factory/FraudProcessorResponseFactory.php +++ b/modules/ppcp-api-client/src/Factory/FraudProcessorResponseFactory.php @@ -25,8 +25,8 @@ class FraudProcessorResponseFactory { * @return FraudProcessorResponse */ public function from_paypal_response( stdClass $data ): FraudProcessorResponse { - $avs_code = ($data->avs_code ?? null) ?: null; - $cvv_code = ($data->cvv_code ?? null) ?: null; + $avs_code = ( $data->avs_code ?? null ) ?: null; + $cvv_code = ( $data->cvv_code ?? null ) ?: null; return new FraudProcessorResponse( $avs_code, $cvv_code ); } From 6a99a9befcd0e5cd1133b0701b645d02e8940e12 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Mon, 5 Feb 2024 16:30:19 +0000 Subject: [PATCH 17/20] Fix update payment source on WC Order subscription renewals. --- modules/ppcp-wc-subscriptions/services.php | 4 +- .../src/RenewalHandler.php | 43 ++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-wc-subscriptions/services.php b/modules/ppcp-wc-subscriptions/services.php index 86599b3f2..e9b324b07 100644 --- a/modules/ppcp-wc-subscriptions/services.php +++ b/modules/ppcp-wc-subscriptions/services.php @@ -27,6 +27,7 @@ return array( $environment = $container->get( 'onboarding.environment' ); $settings = $container->get( 'wcgateway.settings' ); $authorized_payments_processor = $container->get( 'wcgateway.processor.authorized-payments' ); + $funding_source_renderer = $container->get( 'wcgateway.funding-source.renderer' ); return new RenewalHandler( $logger, $repository, @@ -36,7 +37,8 @@ return array( $payer_factory, $environment, $settings, - $authorized_payments_processor + $authorized_payments_processor, + $funding_source_renderer ); }, 'wc-subscriptions.repository.payment-token' => static function ( ContainerInterface $container ): PaymentTokenRepository { diff --git a/modules/ppcp-wc-subscriptions/src/RenewalHandler.php b/modules/ppcp-wc-subscriptions/src/RenewalHandler.php index a4ce028ee..06d223c35 100644 --- a/modules/ppcp-wc-subscriptions/src/RenewalHandler.php +++ b/modules/ppcp-wc-subscriptions/src/RenewalHandler.php @@ -28,6 +28,7 @@ use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenVenmo; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; +use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; @@ -108,6 +109,13 @@ class RenewalHandler { */ protected $authorized_payments_processor; + /** + * The funding source renderer. + * + * @var FundingSourceRenderer + */ + protected $funding_source_renderer; + /** * RenewalHandler constructor. * @@ -120,6 +128,7 @@ class RenewalHandler { * @param Environment $environment The environment. * @param Settings $settings The Settings. * @param AuthorizedPaymentsProcessor $authorized_payments_processor The Authorized Payments Processor. + * @param FundingSourceRenderer $funding_source_renderer The funding source renderer. */ public function __construct( LoggerInterface $logger, @@ -130,7 +139,8 @@ class RenewalHandler { PayerFactory $payer_factory, Environment $environment, Settings $settings, - AuthorizedPaymentsProcessor $authorized_payments_processor + AuthorizedPaymentsProcessor $authorized_payments_processor, + FundingSourceRenderer $funding_source_renderer ) { $this->logger = $logger; @@ -142,6 +152,7 @@ class RenewalHandler { $this->environment = $environment; $this->settings = $settings; $this->authorized_payments_processor = $authorized_payments_processor; + $this->funding_source_renderer = $funding_source_renderer; } /** @@ -410,6 +421,11 @@ class RenewalHandler { if ( $transaction_id ) { $this->update_transaction_id( $transaction_id, $wc_order ); + $payment_source = $order->payment_source(); + if ( $payment_source instanceof PaymentSource ) { + $this->update_payment_source( $payment_source, $wc_order ); + } + $subscriptions = wcs_get_subscriptions_for_order( $wc_order->get_id(), array( 'order_type' => 'any' ) ); foreach ( $subscriptions as $id => $subscription ) { $subscription->update_meta_data( 'ppcp_previous_transaction_reference', $transaction_id ); @@ -463,4 +479,29 @@ class RenewalHandler { (object) $properties ); } + + /** + * Updates the payment source name to the one really used for the payment. + * + * @param PaymentSource $payment_source + * @param \WC_Order $wc_order + * @return void + */ + private function update_payment_source( PaymentSource $payment_source, \WC_Order $wc_order ): void { + if ( ! $payment_source->name() ) { + return; + } + try { + $wc_order->set_payment_method_title( $this->funding_source_renderer->render_name( $payment_source->name() ) ); + $wc_order->save(); + } catch ( \Exception $e ) { + $this->logger->error( + sprintf( + 'Failed to update payment source to "%1$s" on order %2$d', + $payment_source->name(), + $wc_order->get_id() + ) + ); + } + } } From 9f859da8cb662d05277a186c99544fe772b52f90 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Mon, 5 Feb 2024 16:43:22 +0000 Subject: [PATCH 18/20] Fix lint --- modules/ppcp-wc-subscriptions/src/RenewalHandler.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-wc-subscriptions/src/RenewalHandler.php b/modules/ppcp-wc-subscriptions/src/RenewalHandler.php index 06d223c35..b559c18c6 100644 --- a/modules/ppcp-wc-subscriptions/src/RenewalHandler.php +++ b/modules/ppcp-wc-subscriptions/src/RenewalHandler.php @@ -483,8 +483,8 @@ class RenewalHandler { /** * Updates the payment source name to the one really used for the payment. * - * @param PaymentSource $payment_source - * @param \WC_Order $wc_order + * @param PaymentSource $payment_source The Payment Source. + * @param \WC_Order $wc_order WC order. * @return void */ private function update_payment_source( PaymentSource $payment_source, \WC_Order $wc_order ): void { From 8266e1bce7c7c9bb33301f573db0e3ff6cf0cd8c Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Mon, 5 Feb 2024 18:26:41 +0000 Subject: [PATCH 19/20] Fix subscription initial payment method name --- modules/ppcp-blocks/resources/js/checkout-block.js | 2 -- .../src/WcSubscriptionsModule.php | 11 +++++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index f14417e1c..5e6450045 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -326,8 +326,6 @@ const PayPalComponent = ({ }; handleSubscriptionShippingChange = async (data, actions) => { - console.log('--- handleSubscriptionShippingChange', data, actions); - try { const shippingOptionId = data.selected_shipping_option?.id; if (shippingOptionId) { diff --git a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php index 3335d24e9..994e139b7 100644 --- a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php +++ b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php @@ -111,6 +111,17 @@ class WcSubscriptionsModule implements ModuleInterface { $subscription->update_meta_data( 'ppcp_previous_transaction_reference', $transaction_id ); $subscription->save(); } + + // Update the initial payment method title if not the same as the first order. + $payment_method_title = $parent_order->get_payment_method_title(); + if ( + $payment_method_title + && $subscription instanceof \WC_Subscription + && $subscription->get_payment_method_title() !== $payment_method_title + ) { + $subscription->set_payment_method_title( $payment_method_title ); + $subscription->save(); + } } } } From 52720872de7f92254c42467257faa5c4e868bc7d Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Tue, 6 Feb 2024 17:40:39 +0000 Subject: [PATCH 20/20] Fix apple pay funding source renderer. --- .../src/FundingSource/FundingSourceRenderer.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/modules/ppcp-wc-gateway/src/FundingSource/FundingSourceRenderer.php b/modules/ppcp-wc-gateway/src/FundingSource/FundingSourceRenderer.php index a465db721..071410615 100644 --- a/modules/ppcp-wc-gateway/src/FundingSource/FundingSourceRenderer.php +++ b/modules/ppcp-wc-gateway/src/FundingSource/FundingSourceRenderer.php @@ -56,6 +56,8 @@ class FundingSourceRenderer { * @param string $id The ID of the funding source, such as 'venmo'. */ public function render_name( string $id ): string { + $id = $this->sanitize_id( $id ); + if ( array_key_exists( $id, $this->funding_sources ) ) { if ( in_array( $id, $this->own_funding_sources, true ) ) { return $this->funding_sources[ $id ]; @@ -78,6 +80,8 @@ class FundingSourceRenderer { * @param string $id The ID of the funding source, such as 'venmo'. */ public function render_description( string $id ): string { + $id = $this->sanitize_id( $id ); + if ( array_key_exists( $id, $this->funding_sources ) ) { return sprintf( /* translators: %s - Sofort, BLIK, iDeal, Mercado Pago, etc. */ @@ -90,4 +94,14 @@ class FundingSourceRenderer { $this->settings->get( 'description' ) : __( 'Pay via PayPal.', 'woocommerce-paypal-payments' ); } + + /** + * Sanitizes the id to a standard format. + * + * @param string $id The funding source id. + * @return string + */ + private function sanitize_id( string $id ): string { + return str_replace( '_', '', strtolower( $id ) ); + } }