diff --git a/modules/ppcp-api-client/src/Factory/FraudProcessorResponseFactory.php b/modules/ppcp-api-client/src/Factory/FraudProcessorResponseFactory.php index e48d7fd3e..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; - $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 ); } diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 4484bba21..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; @@ -214,6 +210,8 @@ class ApplepayButton { 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-applepay/resources/js/Context/BaseHandler.js b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js index 61d614ab9..69745082e 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 { @@ -9,9 +10,15 @@ 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 + && this.ppcpConfig?.can_save_vault_token; // vault is enabled + } + 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..b1d29c9c9 100644 --- a/modules/ppcp-applepay/resources/js/boot-block.js +++ b/modules/ppcp-applepay/resources/js/boot-block.js @@ -1,8 +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} 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; @@ -50,6 +52,13 @@ const ApplePayComponent = () => { const features = ['products']; +if ( + cartHasSubscriptionProducts(ppcpConfig) + && (new CheckoutHandler(buttonConfig, ppcpConfig)).isVaultV3Mode() +) { + features.push('subscriptions'); +} + registerExpressPaymentMethod({ name: buttonData.id, label:
, diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index 654868447..5e6450045 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -87,6 +87,7 @@ const PayPalComponent = ({ bn_code: '', context: config.scriptData.context, payment_method: 'ppcp-gateway', + funding_source: window.ppcpFundingSource ?? 'paypal', createaccount: false }), }); @@ -325,8 +326,6 @@ const PayPalComponent = ({ }; handleSubscriptionShippingChange = async (data, actions) => { - console.log('--- handleSubscriptionShippingChange', data, actions); - try { const shippingOptionId = data.selected_shipping_option?.id; if (shippingOptionId) { @@ -476,6 +475,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) 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 5137a2b68..6613360f3 100644 --- a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php +++ b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php @@ -148,6 +148,21 @@ class SavePaymentMethodsModule implements ModuleInterface { 'vault' => array( 'store_in_vault' => 'ON_SUCCESS', 'usage_type' => 'MERCHANT', + 'permit_multiple_payment_tokens' => true, + ), + ), + ), + ); + } 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( + 'vault' => array( + 'store_in_vault' => 'ON_SUCCESS', ), ), ), @@ -159,6 +174,7 @@ class SavePaymentMethodsModule implements ModuleInterface { 'vault' => array( 'store_in_vault' => 'ON_SUCCESS', 'usage_type' => 'MERCHANT', + 'permit_multiple_payment_tokens' => true, ), ), ), @@ -207,11 +223,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 ?? '' - ); + 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 520099994..b0f37a449 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; @@ -66,9 +68,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. * * @return int */ @@ -79,11 +81,17 @@ class WooCommercePaymentTokens { ): 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; } - $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 ); @@ -105,6 +113,96 @@ 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; + } + + // 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 ); + $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; + } + + // 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 ); + $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; + } } } 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-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 7ba73386e..5216b82c3 100644 --- a/modules/ppcp-vaulting/src/VaultingModule.php +++ b/modules/ppcp-vaulting/src/VaultingModule.php @@ -81,11 +81,50 @@ 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; } ); + 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; + } + + $is_post = isset( $_SERVER['REQUEST_METHOD'] ) && $_SERVER['REQUEST_METHOD'] === 'POST'; + + // Exclude ApplePay tokens from payment pages. + 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 ] ); + } + } + } + + return $tokens; + }, + 10, + 3 + ); + add_filter( 'woocommerce_payment_methods_list_item', /** @@ -98,10 +137,18 @@ class VaultingModule implements ModuleInterface { return $item; } - if ( strtolower( $payment_token->get_type() ) === 'paypal' ) { - assert( $payment_token instanceof PaymentTokenPayPal ); - $item['method']['brand'] = $payment_token->get_email(); + if ( $payment_token instanceof PaymentTokenPayPal ) { + $item['method']['brand'] = 'PayPal / ' . $payment_token->get_email(); + return $item; + } + if ( $payment_token instanceof PaymentTokenVenmo ) { + $item['method']['brand'] = 'Venmo / ' . $payment_token->get_email(); + return $item; + } + + if ( $payment_token instanceof PaymentTokenApplePay ) { + $item['method']['brand'] = 'ApplePay #' . ( (string) $payment_token->get_id() ); return $item; } 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 ) ); + } } 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 636de372d..b559c18c6 100644 --- a/modules/ppcp-wc-subscriptions/src/RenewalHandler.php +++ b/modules/ppcp-wc-subscriptions/src/RenewalHandler.php @@ -22,9 +22,13 @@ 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\FundingSource\FundingSourceRenderer; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; @@ -105,6 +109,13 @@ class RenewalHandler { */ protected $authorized_payments_processor; + /** + * The funding source renderer. + * + * @var FundingSourceRenderer + */ + protected $funding_source_renderer; + /** * RenewalHandler constructor. * @@ -117,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, @@ -127,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; @@ -139,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; } /** @@ -202,11 +216,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 ) { + $name = 'paypal'; + } + + if ( $token instanceof PaymentTokenVenmo ) { + $name = 'venmo'; + } + + if ( $token instanceof PaymentTokenApplePay ) { + $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; @@ -387,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 ); @@ -440,4 +479,29 @@ class RenewalHandler { (object) $properties ); } + + /** + * Updates the payment source name to the one really used for the payment. + * + * @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 { + 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() + ) + ); + } + } } diff --git a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php index 51fe3e2fe..861ca55c1 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(); + } } } } @@ -311,7 +322,7 @@ class WcSubscriptionsModule implements ModuleInterface { foreach ( $tokens as $token ) { $output .= '