From 5161305cccde03b8dccf25b6f53c13cbf2f6a858 Mon Sep 17 00:00:00 2001 From: frosso Date: Thu, 20 Nov 2025 22:02:41 +0100 Subject: [PATCH 01/11] refactor: add Apple Pay and Google Pay definitions --- .../feat-add-apple-pay-google-pay-definitions | 4 + client/constants/payment-method.ts | 2 + client/disable-confirmation-modal/index.js | 21 +- .../transformers/stripe-to-wc.js | 4 - .../utils/__tests__/normalize.test.js | 3 - client/express-checkout/utils/normalize.js | 1 - client/payment-methods-icons.tsx | 10 - .../express-checkout-settings/index.js | 51 ++-- .../apple-google-pay-item.tsx | 137 +++++----- .../settings/payment-methods-section/index.js | 6 +- includes/admin/class-wc-payments-admin.php | 2 +- includes/class-wc-payment-gateway-wcpay.php | 14 +- .../class-wc-payments-order-success-page.php | 44 ++- includes/class-wc-payments-token-service.php | 45 +++- includes/constants/class-payment-method.php | 2 + ...payments-express-checkout-ajax-handler.php | 31 ++- ...yments-express-checkout-button-handler.php | 1 - ...ayments-express-checkout-button-helper.php | 33 --- .../Definitions/ApplePayDefinition.php | 250 ++++++++++++++++++ .../Definitions/GooglePayDefinition.php | 250 ++++++++++++++++++ .../PaymentMethodDefinitionRegistry.php | 4 + .../services/class-checkout-service.php | 2 +- tests/js/jest-test-file-setup.js | 2 + .../test-class-upe-payment-gateway.php | 2 +- .../test-class-wc-payment-gateway-wcpay.php | 2 +- .../test-class-wc-payments-token-service.php | 215 +++++++++++++++ 26 files changed, 958 insertions(+), 180 deletions(-) create mode 100644 changelog/feat-add-apple-pay-google-pay-definitions create mode 100644 includes/payment-methods/Configs/Definitions/ApplePayDefinition.php create mode 100644 includes/payment-methods/Configs/Definitions/GooglePayDefinition.php diff --git a/changelog/feat-add-apple-pay-google-pay-definitions b/changelog/feat-add-apple-pay-google-pay-definitions new file mode 100644 index 00000000000..2283ab27757 --- /dev/null +++ b/changelog/feat-add-apple-pay-google-pay-definitions @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +refactor: add Apple Pay and Google Pay payment definitions diff --git a/client/constants/payment-method.ts b/client/constants/payment-method.ts index e5c778e0e26..58e2a07ef1c 100644 --- a/client/constants/payment-method.ts +++ b/client/constants/payment-method.ts @@ -11,12 +11,14 @@ enum PAYMENT_METHOD_IDS { AFFIRM = 'affirm', AFTERPAY_CLEARPAY = 'afterpay_clearpay', ALIPAY = 'alipay', + APPLE_PAY = 'apple_pay', AU_BECS_DEBIT = 'au_becs_debit', BANCONTACT = 'bancontact', CARD = 'card', CARD_PRESENT = 'card_present', EPS = 'eps', GIROPAY = 'giropay', + GOOGLE_PAY = 'google_pay', GRABPAY = 'grabpay', IDEAL = 'ideal', KLARNA = 'klarna', diff --git a/client/disable-confirmation-modal/index.js b/client/disable-confirmation-modal/index.js index a42c824451f..ab1122a9ab1 100644 --- a/client/disable-confirmation-modal/index.js +++ b/client/disable-confirmation-modal/index.js @@ -20,12 +20,7 @@ import PaymentDeleteIllustration from '../components/payment-delete-illustration import WooCardIcon from 'assets/images/cards/woo-card.svg?asset'; import ConfirmationModal from '../components/confirmation-modal'; import paymentMethodsMap from 'wcpay/payment-methods-map'; -import { - ApplePayIcon, - GooglePayIcon, - LinkIcon, - WooIconShort, -} from 'wcpay/payment-methods-icons'; +import { LinkIcon, WooIconShort } from 'wcpay/payment-methods-icons'; const DisableConfirmationModal = ( { onClose, onConfirm } ) => { const [ enabledMethodIds ] = useEnabledPaymentMethodIds(); @@ -109,20 +104,14 @@ const DisableConfirmationModal = ( { onClose, onConfirm } ) => { <>
  • diff --git a/client/express-checkout/transformers/stripe-to-wc.js b/client/express-checkout/transformers/stripe-to-wc.js index 82534e259b8..bc57f3929dc 100644 --- a/client/express-checkout/transformers/stripe-to-wc.js +++ b/client/express-checkout/transformers/stripe-to-wc.js @@ -77,10 +77,6 @@ export const transformStripePaymentMethodForStoreApi = ( key: 'payment_method', value: 'card', }, - { - key: 'payment_request_type', - value: paymentData.expressPaymentType, - }, { key: 'wcpay-fraud-prevention-token', value: window.wcpayFraudPreventionToken ?? '', diff --git a/client/express-checkout/utils/__tests__/normalize.test.js b/client/express-checkout/utils/__tests__/normalize.test.js index e963a356911..1da01070178 100644 --- a/client/express-checkout/utils/__tests__/normalize.test.js +++ b/client/express-checkout/utils/__tests__/normalize.test.js @@ -200,7 +200,6 @@ describe( 'Express checkout normalization', () => { ship_to_different_address: 1, terms: 1, 'wcpay-payment-method': paymentMethodId, - payment_request_type: 'express', express_payment_type: 'express', 'wcpay-fraud-prevention-token': 'token123', }; @@ -242,7 +241,6 @@ describe( 'Express checkout normalization', () => { ship_to_different_address: 1, terms: 1, 'wcpay-payment-method': paymentMethodId, - payment_request_type: undefined, express_payment_type: undefined, 'wcpay-fraud-prevention-token': '', }; @@ -288,7 +286,6 @@ describe( 'Express checkout normalization', () => { ship_to_different_address: 1, terms: 1, 'wcpay-payment-method': paymentMethodId, - payment_request_type: undefined, express_payment_type: undefined, 'wcpay-fraud-prevention-token': '', }; diff --git a/client/express-checkout/utils/normalize.js b/client/express-checkout/utils/normalize.js index e55a44bfee9..b8fde3aad5b 100644 --- a/client/express-checkout/utils/normalize.js +++ b/client/express-checkout/utils/normalize.js @@ -71,7 +71,6 @@ export const normalizeOrderData = ( event, paymentMethodId ) => { ship_to_different_address: 1, terms: 1, 'wcpay-payment-method': paymentMethodId, - payment_request_type: event?.expressPaymentType, express_payment_type: event?.expressPaymentType, 'wcpay-fraud-prevention-token': fraudPreventionTokenValue, }; diff --git a/client/payment-methods-icons.tsx b/client/payment-methods-icons.tsx index 8d505dbf9e2..a73eaeb0d76 100644 --- a/client/payment-methods-icons.tsx +++ b/client/payment-methods-icons.tsx @@ -14,8 +14,6 @@ import MasterCardAsset from 'assets/images/cards/mastercard.svg?asset'; import AmexAsset from 'assets/images/cards/amex.svg?asset'; import WooAsset from 'assets/images/payment-methods/woo.svg?asset'; import WooAssetShort from 'assets/images/payment-methods/woo-short.svg?asset'; -import ApplePayAsset from 'assets/images/cards/apple-pay.svg?asset'; -import GooglePayAsset from 'assets/images/cards/google-pay.svg?asset'; import DinersClubAsset from 'assets/images/cards/diners.svg?asset'; import DiscoverAsset from 'assets/images/cards/discover.svg?asset'; import CBAsset from 'assets/images/cards/cb.svg?asset'; @@ -49,10 +47,6 @@ export const AmericanExpressIcon = iconComponent( AmexAsset, __( 'American Express', 'woocommerce-payments' ) ); -export const ApplePayIcon = iconComponent( - ApplePayAsset, - __( 'Apple Pay', 'woocommerce-payments' ) -); export const CBIcon = iconComponent( CBAsset, __( 'Cartes Bancaires', 'woocommerce-payments' ) @@ -65,10 +59,6 @@ export const DiscoverIcon = iconComponent( DiscoverAsset, __( 'Discover', 'woocommerce-payments' ) ); -export const GooglePayIcon = iconComponent( - GooglePayAsset, - __( 'Google Pay', 'woocommerce-payments' ) -); export const JCBIcon = iconComponent( JCBAsset, __( 'JCB', 'woocommerce-payments' ) diff --git a/client/settings/express-checkout-settings/index.js b/client/settings/express-checkout-settings/index.js index ff7889e5751..f0197a35904 100644 --- a/client/settings/express-checkout-settings/index.js +++ b/client/settings/express-checkout-settings/index.js @@ -17,12 +17,8 @@ import SettingsLayout from '../settings-layout'; import LoadableSettingsSection from '../loadable-settings-section'; import SaveSettingsSection from '../save-settings-section'; import ErrorBoundary from '../../components/error-boundary'; -import { - AmazonPayIcon, - ApplePayIcon, - GooglePayIcon, - WooIcon, -} from 'wcpay/payment-methods-icons'; +import { AmazonPayIcon, WooIcon } from 'wcpay/payment-methods-icons'; +import methodsConfiguration from 'wcpay/payment-methods-map'; const methods = { woopay: { @@ -76,24 +72,33 @@ const methods = { sections: [ { section: 'enable', - description: () => ( - <> -
    -
    - -
    -
    - + description: () => { + const { + icon: ApplePayIcon, + } = methodsConfiguration.apple_pay; + const { + icon: GooglePayIcon, + } = methodsConfiguration.google_pay; + + return ( + <> +
    +
    + +
    +
    + +
    -
    -

    - { __( - 'Allow your customers to collect payments via Apple Pay and Google Pay.', - 'woocommerce-payments' - ) } -

    - - ), +

    + { __( + 'Allow your customers to collect payments via Apple Pay and Google Pay.', + 'woocommerce-payments' + ) } +

    + + ); + }, }, { section: 'general', diff --git a/client/settings/express-checkout/apple-google-pay-item.tsx b/client/settings/express-checkout/apple-google-pay-item.tsx index 7f1ebeefa57..dac6bf6db60 100644 --- a/client/settings/express-checkout/apple-google-pay-item.tsx +++ b/client/settings/express-checkout/apple-google-pay-item.tsx @@ -11,10 +11,10 @@ import { Button, CheckboxControl } from '@wordpress/components'; */ import { getPaymentMethodSettingsUrl } from '../../utils'; import { usePaymentRequestEnabledSettings } from 'wcpay/data'; -import { ApplePayIcon, GooglePayIcon } from 'wcpay/payment-methods-icons'; import DuplicateNotice from 'wcpay/components/duplicate-notice'; import DuplicatedPaymentMethodsContext from '../settings-manager/duplicated-payment-methods-context'; import GooglePayTestModeCompatibilityNotice from '../google-pay-test-mode-compatibility-notice'; +import methodsConfiguration from '../../payment-methods-map'; const AppleGooglePayExpressCheckoutItem = (): React.ReactElement => { const id = 'apple_pay_google_pay'; @@ -31,6 +31,9 @@ const AppleGooglePayExpressCheckoutItem = (): React.ReactElement => { } = useContext( DuplicatedPaymentMethodsContext ); const isDuplicate = Object.keys( duplicates ).includes( id ); + const ApplePayIcon = methodsConfiguration.apple_pay.icon; + const GooglePayIcon = methodsConfiguration.google_pay.icon; + return (
  • {
  • - { __( 'Apple Pay', 'woocommerce-payments' ) } + { methodsConfiguration.apple_pay.label }
    - { __( - 'Apple Pay', - 'woocommerce-payments' - ) } + { methodsConfiguration.apple_pay.label }
    + { + methodsConfiguration.apple_pay + .description + } + { isPaymentRequestEnabled && ' ' } { /* eslint-disable jsx-a11y/anchor-has-content */ - isPaymentRequestEnabled - ? __( - 'Apple Pay is an easy and secure way for customers to pay on your store.', + isPaymentRequestEnabled && + interpolateComponents( { + mixedString: __( + /* eslint-disable-next-line max-len */ + 'By enabling this feature, you agree to {{stripeLink}}Stripe{{/stripeLink}} and' + + "{{appleLink}} Apple{{/appleLink}}'s terms of use.", 'woocommerce-payments' - ) - : interpolateComponents( { - mixedString: __( - /* eslint-disable-next-line max-len */ - 'Apple Pay is an easy and secure way for customers to pay on your store. ' + + ), + components: { + stripeLink: ( + + ), + appleLink: ( + ), - components: { - stripeLink: ( - - ), - appleLink: ( - - ), - br:
    , - }, - } ) + br:
    , + }, + } ) /* eslint-enable jsx-a11y/anchor-has-content */ }
    @@ -110,50 +109,46 @@ const AppleGooglePayExpressCheckoutItem = (): React.ReactElement => {
    - { __( 'Google Pay', 'woocommerce-payments' ) } + { methodsConfiguration.google_pay.label }
    - { __( - 'Google Pay', - 'woocommerce-payments' - ) } + { methodsConfiguration.google_pay.label }
    + { + methodsConfiguration.google_pay + .description + } + { isPaymentRequestEnabled && ' ' } { /* eslint-disable jsx-a11y/anchor-has-content */ - isPaymentRequestEnabled - ? __( - 'Offer customers a fast, secure checkout experience with Google Pay.', + isPaymentRequestEnabled && + interpolateComponents( { + mixedString: __( + /* eslint-disable-next-line max-len */ + 'By enabling this feature, you agree to {{stripeLink}}Stripe{{/stripeLink}}, ' + + "and {{googleLink}}Google{{/googleLink}}'s terms of use.", 'woocommerce-payments' - ) - : interpolateComponents( { - mixedString: __( - /* eslint-disable-next-line max-len */ - 'Offer customers a fast, secure checkout experience with Google Pay. ' + - /* eslint-disable-next-line max-len */ - 'By enabling this feature, you agree to {{stripeLink}}Stripe{{/stripeLink}}, ' + - "and {{googleLink}}Google{{/googleLink}}'s terms of use.", - 'woocommerce-payments' + ), + components: { + stripeLink: ( + + ), + googleLink: ( + ), - components: { - stripeLink: ( - - ), - googleLink: ( - - ), - br:
    , - }, - } ) + br:
    , + }, + } ) /* eslint-enable jsx-a11y/anchor-has-content */ }
    diff --git a/client/settings/payment-methods-section/index.js b/client/settings/payment-methods-section/index.js index 5dab67d7f12..e01b00d0226 100644 --- a/client/settings/payment-methods-section/index.js +++ b/client/settings/payment-methods-section/index.js @@ -42,8 +42,10 @@ const PaymentMethodsSection = () => { ( id ) => methodsConfiguration[ id ] && ! methodsConfiguration[ id ].allows_pay_later && - // Stripe Link is displayed in the Express Checkout section - PAYMENT_METHOD_IDS.LINK !== id + // methods displayed in the Express Checkout section + PAYMENT_METHOD_IDS.LINK !== id && + PAYMENT_METHOD_IDS.APPLE_PAY !== id && + PAYMENT_METHOD_IDS.GOOGLE_PAY !== id ) .reduce( ( acc, methodId ) => { // Ensuring that card is at the top of the list diff --git a/includes/admin/class-wc-payments-admin.php b/includes/admin/class-wc-payments-admin.php index 6c47eb093f2..65c5da6986e 100644 --- a/includes/admin/class-wc-payments-admin.php +++ b/includes/admin/class-wc-payments-admin.php @@ -957,7 +957,7 @@ private function get_js_settings(): array { 'showUpdateDetailsTask' => $this->get_should_show_update_business_details_task( $account_status_data ), 'wpcomReconnectUrl' => $this->payments_api_client->is_server_connected() && ! $this->payments_api_client->has_server_connection_owner() ? WC_Payments_Account::get_wpcom_reconnect_url() : null, 'multiCurrencySetup' => [ - 'isSetupCompleted' => filter_var( get_option( 'wcpay_multi_currency_setup_completed' ), FILTER_VALIDATE_BOOLEAN ) ? 'yes' : 'no,', + 'isSetupCompleted' => filter_var( get_option( 'wcpay_multi_currency_setup_completed' ), FILTER_VALIDATE_BOOLEAN ) ? 'yes' : 'no', ], 'isMultiCurrencyEnabled' => WC_Payments_Features::is_customer_multi_currency_enabled(), 'shouldUseExplicitPrice' => WC_Payments_Explicit_Price_Formatter::should_output_explicit_price(), diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index 23d9c3ef388..1554dd1be4a 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -342,8 +342,10 @@ public function __construct( // to have a map for it instead, just in case the pattern changes. $this->payment_method_capability_key_map = [ 'alipay' => 'alipay_payments', + 'apple_pay' => 'card_payments', 'sofort' => 'sofort_payments', 'giropay' => 'giropay_payments', + 'google_pay' => 'card_payments', 'bancontact' => 'bancontact_payments', 'eps' => 'eps_payments', 'ideal' => 'ideal_payments', @@ -1814,7 +1816,7 @@ public function process_payment_for_order( $cart, $payment_information, $schedul $this->maybe_add_customer_notification_note( $order, $processing ); // ensuring the payment method title is set before any early return paths to avoid incomplete order data. - if ( empty( $_POST['payment_request_type'] ) && empty( $_POST['express_payment_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + if ( empty( $_POST['express_payment_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification // Extract payment method details for setting the payment method title. if ( $payment_needed ) { $charge = $intent ? $intent->get_charge() : null; @@ -4116,6 +4118,16 @@ public function get_upe_available_payment_methods() { $methods_with_fees = array_keys( $this->account->get_fees() ); + // Google Pay and Apple Pay use card payment fees, so if card is available, they should be too. + if ( in_array( 'card', $methods_with_fees, true ) ) { + if ( in_array( 'google_pay', $available_methods, true ) ) { + $methods_with_fees[] = 'google_pay'; + } + if ( in_array( 'apple_pay', $available_methods, true ) ) { + $methods_with_fees[] = 'apple_pay'; + } + } + return array_values( array_intersect( $available_methods, $methods_with_fees ) ); } diff --git a/includes/class-wc-payments-order-success-page.php b/includes/class-wc-payments-order-success-page.php index a5ed0b31695..ddd38711e4b 100644 --- a/includes/class-wc-payments-order-success-page.php +++ b/includes/class-wc-payments-order-success-page.php @@ -188,6 +188,12 @@ public function show_woocommerce_payments_payment_method_name( $payment_method_t return $this->show_woopay_payment_method_name( $order ); } + // Check if this is an Express Checkout payment (Google Pay, Apple Pay, etc.). + $express_checkout_payment_method = $order->get_meta( '_wcpay_express_checkout_payment_method' ); + if ( ! empty( $express_checkout_payment_method ) ) { + return $this->show_express_checkout_payment_method_name( $order, $express_checkout_payment_method ); + } + $gateway = WC()->payment_gateways()->payment_gateways()[ $payment_method_id ]; if ( ! is_object( $gateway ) || ! method_exists( $gateway, 'get_payment_method' ) ) { @@ -195,8 +201,9 @@ public function show_woocommerce_payments_payment_method_name( $payment_method_t } $payment_method = $gateway->get_payment_method( $order ); - // GooglePay/ApplePay/Link/Card to be supported later. - if ( $payment_method->get_id() === Payment_Method::CARD ) { + + // Handle card-based payments (Card, Link). + if ( in_array( $payment_method->get_id(), [ Payment_Method::CARD ], true ) ) { return $this->show_card_payment_method_name( $order, $payment_method ); } @@ -210,6 +217,39 @@ public function show_woocommerce_payments_payment_method_name( $payment_method_t return $payment_method_title; } + /** + * Returns the HTML to add the Express Checkout payment method logo and last 4 digits + * of the card used to the payment method name on the order received page. + * + * @param WC_Order $order the order being shown. + * @param string $express_checkout_payment_method the express checkout payment method (e.g., 'google_pay', 'apple_pay'). + * + * @return string + */ + public function show_express_checkout_payment_method_name( $order, $express_checkout_payment_method ) { + $payment_method = WC_Payments::get_payment_method_by_id( $express_checkout_payment_method ); + + if ( ! $payment_method ) { + return 'Payment Request'; + } + + $payment_method_title = $payment_method->get_title(); + $last4 = $order->get_meta( 'last4' ); + + // If we have the last 4 digits, show "Google Pay ending in 1234". + if ( $last4 ) { + return sprintf( + /* translators: 1: payment method name (e.g. Google Pay), 2: last 4 digits of card */ + __( '%1$s ending in %2$s', 'woocommerce-payments' ), + esc_html( $payment_method_title ), + esc_html( $last4 ) + ); + } + + // Otherwise just show "Google Pay". + return esc_html( $payment_method_title ); + } + /** * Returns the HTML to add the card brand logo and the last 4 digits of the card used to the * payment method name on the order received page. diff --git a/includes/class-wc-payments-token-service.php b/includes/class-wc-payments-token-service.php index 283a0d7851a..01edb21dcef 100644 --- a/includes/class-wc-payments-token-service.php +++ b/includes/class-wc-payments-token-service.php @@ -12,6 +12,8 @@ use WCPay\Logger; use WCPay\Payment_Methods\CC_Payment_Gateway; use WCPay\Constants\Payment_Method; +use WCPay\PaymentMethods\Configs\Definitions\ApplePayDefinition; +use WCPay\PaymentMethods\Configs\Definitions\GooglePayDefinition; /** * Handles and process WC payment tokens API. @@ -58,12 +60,13 @@ public function init_hooks() { add_filter( 'woocommerce_get_customer_payment_tokens', [ $this, 'woocommerce_get_customer_payment_tokens' ], 10, 3 ); add_filter( 'woocommerce_payment_methods_list_item', [ $this, 'get_account_saved_payment_methods_list_item_sepa' ], 10, 2 ); add_filter( 'woocommerce_payment_methods_list_item', [ $this, 'get_account_saved_payment_methods_list_item_link' ], 10, 2 ); + add_filter( 'woocommerce_payment_methods_list_item', [ $this, 'get_account_saved_payment_methods_list_item_wallet' ], 10, 2 ); add_filter( 'woocommerce_get_credit_card_type_label', [ $this, 'normalize_sepa_label' ] ); add_filter( 'woocommerce_get_credit_card_type_label', [ $this, 'normalize_stripe_link_label' ] ); } /** - * Creates and add a token to an user, based on the payment_method object + * Creates and add a token to a user, based on the payment_method object * * @param array $payment_method Payment method to be added. * @param WP_User $user User to attach payment method to. @@ -101,7 +104,10 @@ public function add_token_to_user( $payment_method, $user ) { $token->set_expiry_year( $payment_method[ Payment_Method::CARD ]['exp_year'] ); $token->set_card_type( strtolower( $payment_method[ Payment_Method::CARD ]['display_brand'] ?? $payment_method[ Payment_Method::CARD ]['networks']['preferred'] ?? $payment_method[ Payment_Method::CARD ]['brand'] ) ); $token->set_last4( $payment_method[ Payment_Method::CARD ]['last4'] ); - + if ( ! empty( $payment_method[ Payment_Method::CARD ]['wallet']['type'] ) ) { + $wallet_type = $payment_method[ Payment_Method::CARD ]['wallet']['type'] ?? null; + $token->add_meta_data( '_wcpay_wallet_type', $wallet_type, true ); + } } $token->set_token( $payment_method['id'] ); $token->set_user_id( $user->ID ); @@ -365,6 +371,41 @@ public function get_account_saved_payment_methods_list_item_link( $item, $paymen return $item; } + /** + * Controls the output for Wallet tokens on the My account page. + * + * @param array $item Individual list item from woocommerce_saved_payment_methods_list. + * @param WC_Payment_Token|WC_Payment_Token_WCPay_Link $payment_token The payment token associated with this method entry. + * @return array Filtered item + */ + public function get_account_saved_payment_methods_list_item_wallet( $item, $payment_token ) { + if ( 'cc' !== strtolower( $payment_token->get_type() ) ) { + return $item; + } + + $wallet_type = $payment_token->get_meta( '_wcpay_wallet_type', true ); + + if ( empty( $wallet_type ) ) { + return $item; + } + + // Google Pay and Apple Pay are separate payment methods, so we can retrieve them from the registered payment methods class. + $payment_method = WC_Payments::get_payment_method_by_id( $wallet_type ); + if ( ! $payment_method || ! method_exists( $payment_method, 'get_title' ) ) { + return $item; + } + + $original_brand = $item['method']['brand'] ?? ''; + $item['method']['brand'] = sprintf( + /* translators: 1: card brand, 2: wallet name */ + _x( '%1$s %2$s', 'Payment token with wallet', 'woocommerce-payments' ), + $payment_method->get_title(), + $original_brand + ); + + return $item; + } + /** * Normalizes the SEPA IBAN label on My Account page. * diff --git a/includes/constants/class-payment-method.php b/includes/constants/class-payment-method.php index cd354fbdc3d..680216d2a60 100644 --- a/includes/constants/class-payment-method.php +++ b/includes/constants/class-payment-method.php @@ -43,6 +43,8 @@ class Payment_Method extends Base_Constant { const MULTIBANCO = 'multibanco'; const GRABPAY = 'grabpay'; const WECHAT_PAY = 'wechat_pay'; + const GOOGLE_PAY = 'google_pay'; + const APPLE_PAY = 'apple_pay'; const IPP_ALLOWED_PAYMENT_METHODS = [ self::CARD_PRESENT, diff --git a/includes/express-checkout/class-wc-payments-express-checkout-ajax-handler.php b/includes/express-checkout/class-wc-payments-express-checkout-ajax-handler.php index 7e03deb057b..b405a0ed6ec 100644 --- a/includes/express-checkout/class-wc-payments-express-checkout-ajax-handler.php +++ b/includes/express-checkout/class-wc-payments-express-checkout-ajax-handler.php @@ -175,24 +175,41 @@ public function tokenized_cart_set_payment_method_type( \WC_Order $order, \WP_RE $payment_data[ sanitize_key( $data['key'] ) ] = wc_clean( $data['value'] ); } - if ( empty( $payment_data['payment_request_type'] ) ) { + if ( empty( $payment_data['express_payment_type'] ) ) { return; } - $payment_request_type = wc_clean( wp_unslash( $payment_data['payment_request_type'] ) ); + $express_payment_type = wc_clean( wp_unslash( $payment_data['express_payment_type'] ) ); - $payment_method_titles = [ - 'apple_pay' => 'Apple Pay', - 'google_pay' => 'Google Pay', - ]; + $payment_method_title = $this->get_payment_method_title_from_definition( $express_payment_type ); + // fallback, just in case. + if ( ! $payment_method_title ) { + $payment_method_title = 'Payment Request'; + } $suffix = apply_filters( 'wcpay_payment_request_payment_method_title_suffix', 'WooPayments' ); if ( ! empty( $suffix ) ) { $suffix = " ($suffix)"; } - $payment_method_title = isset( $payment_method_titles[ $payment_request_type ] ) ? $payment_method_titles[ $payment_request_type ] : 'Payment Request'; $order->set_payment_method_title( $payment_method_title . $suffix ); + $order->update_meta_data( '_wcpay_express_checkout_payment_method', $express_payment_type ); + } + + /** + * Get the payment method title from the definition. + * + * @param string $payment_method_id The payment method ID (e.g., 'apple_pay', 'google_pay'). + * @return string|null The payment method title or null if not found. + */ + private function get_payment_method_title_from_definition( $payment_method_id ) { + $payment_method = WC_Payments::get_payment_method_by_id( $payment_method_id ); + + if ( $payment_method && method_exists( $payment_method, 'get_title' ) ) { + return $payment_method->get_title(); + } + + return null; } /** diff --git a/includes/express-checkout/class-wc-payments-express-checkout-button-handler.php b/includes/express-checkout/class-wc-payments-express-checkout-button-handler.php index 247c31b6005..2004e5e6261 100644 --- a/includes/express-checkout/class-wc-payments-express-checkout-button-handler.php +++ b/includes/express-checkout/class-wc-payments-express-checkout-button-handler.php @@ -93,7 +93,6 @@ public function init() { add_filter( 'woocommerce_cart_needs_shipping_address', [ $this, 'filter_cart_needs_shipping_address' ], 11, 1 ); add_action( 'wp_enqueue_scripts', [ $this, 'scripts' ] ); add_filter( 'woocommerce_gateway_title', [ $this, 'filter_gateway_title' ], 10, 2 ); - add_action( 'woocommerce_checkout_order_processed', [ $this->express_checkout_helper, 'add_order_payment_method_title' ], 10, 2 ); $this->express_checkout_ajax_handler->init(); diff --git a/includes/express-checkout/class-wc-payments-express-checkout-button-helper.php b/includes/express-checkout/class-wc-payments-express-checkout-button-helper.php index 2cb442d08b2..b81b5524af9 100644 --- a/includes/express-checkout/class-wc-payments-express-checkout-button-helper.php +++ b/includes/express-checkout/class-wc-payments-express-checkout-button-helper.php @@ -803,37 +803,4 @@ public function get_taxes_like_cart( $product, $price ) { public function sanitize_string( $string ) { return trim( wc_strtolower( remove_accents( $string ) ) ); } - - /** - * Add express checkout payment method title to the order. - * - * @param integer $order_id The order ID. - * - * @return void - */ - public function add_order_payment_method_title( $order_id ) { - if ( empty( $_POST['express_payment_type'] ) || ! isset( $_POST['payment_method'] ) || 'woocommerce_payments' !== $_POST['payment_method'] ) { // phpcs:ignore WordPress.Security.NonceVerification - return; - } - - $express_payment_type = wc_clean( wp_unslash( $_POST['express_payment_type'] ) ); // phpcs:ignore WordPress.Security.NonceVerification - $express_payment_titles = [ - 'apple_pay' => 'Apple Pay', - 'google_pay' => 'Google Pay', - ]; - $payment_method_title = $express_payment_titles[ $express_payment_type ] ?? false; - - if ( ! $payment_method_title ) { - return; - } - - $suffix = apply_filters( 'wcpay_payment_request_payment_method_title_suffix', 'WooPayments' ); - if ( ! empty( $suffix ) ) { - $suffix = " ($suffix)"; - } - - $order = wc_get_order( $order_id ); - $order->set_payment_method_title( $payment_method_title . $suffix ); - $order->save(); - } } diff --git a/includes/payment-methods/Configs/Definitions/ApplePayDefinition.php b/includes/payment-methods/Configs/Definitions/ApplePayDefinition.php new file mode 100644 index 00000000000..c4c43bbc0cc --- /dev/null +++ b/includes/payment-methods/Configs/Definitions/ApplePayDefinition.php @@ -0,0 +1,250 @@ +> + */ + public static function get_limits_per_currency(): array { + return []; + } + + /** + * Whether this payment method is available for the given currency and country + * + * @param string $currency The currency code to check. + * @param string $account_country The merchant's account country. + * + * @return bool + */ + public static function is_available_for( string $currency, string $account_country ): bool { + if ( ! PaymentMethodUtils::is_available_for( self::get_supported_currencies(), self::get_supported_countries(), $currency, $account_country ) ) { + return false; + } + + return true; + } + + /** + * Whether this payment method should be enabled by default + * + * @return bool + */ + public static function is_enabled_by_default(): bool { + return false; + } + + /** + * Get the minimum amount for this payment method for a given currency and country + * + * @param string $currency The currency code. + * @param string $country The country code. + * + * @return int|null The minimum amount or null if no minimum. + */ + public static function get_minimum_amount( string $currency, string $country ): ?int { + return null; + } + + /** + * Get the maximum amount for this payment method for a given currency and country + * + * @param string $currency The currency code. + * @param string $country The country code. + * + * @return int|null The maximum amount or null if no maximum. + */ + public static function get_maximum_amount( string $currency, string $country ): ?int { + return null; + } +} diff --git a/includes/payment-methods/Configs/Definitions/GooglePayDefinition.php b/includes/payment-methods/Configs/Definitions/GooglePayDefinition.php new file mode 100644 index 00000000000..4f53070bd81 --- /dev/null +++ b/includes/payment-methods/Configs/Definitions/GooglePayDefinition.php @@ -0,0 +1,250 @@ +> + */ + public static function get_limits_per_currency(): array { + return []; + } + + /** + * Whether this payment method is available for the given currency and country + * + * @param string $currency The currency code to check. + * @param string $account_country The merchant's account country. + * + * @return bool + */ + public static function is_available_for( string $currency, string $account_country ): bool { + if ( ! PaymentMethodUtils::is_available_for( self::get_supported_currencies(), self::get_supported_countries(), $currency, $account_country ) ) { + return false; + } + + return true; + } + + /** + * Whether this payment method should be enabled by default + * + * @return bool + */ + public static function is_enabled_by_default(): bool { + return false; + } + + /** + * Get the minimum amount for this payment method for a given currency and country + * + * @param string $currency The currency code. + * @param string $country The country code. + * + * @return int|null The minimum amount or null if no minimum. + */ + public static function get_minimum_amount( string $currency, string $country ): ?int { + return null; + } + + /** + * Get the maximum amount for this payment method for a given currency and country + * + * @param string $currency The currency code. + * @param string $country The country code. + * + * @return int|null The maximum amount or null if no maximum. + */ + public static function get_maximum_amount( string $currency, string $country ): ?int { + return null; + } +} diff --git a/includes/payment-methods/Configs/Registry/PaymentMethodDefinitionRegistry.php b/includes/payment-methods/Configs/Registry/PaymentMethodDefinitionRegistry.php index 8ed7ba7504b..f0f947616dd 100644 --- a/includes/payment-methods/Configs/Registry/PaymentMethodDefinitionRegistry.php +++ b/includes/payment-methods/Configs/Registry/PaymentMethodDefinitionRegistry.php @@ -8,6 +8,8 @@ namespace WCPay\PaymentMethods\Configs\Registry; use WCPay\PaymentMethods\Configs\Definitions\AlipayDefinition; +use WCPay\PaymentMethods\Configs\Definitions\ApplePayDefinition; +use WCPay\PaymentMethods\Configs\Definitions\GooglePayDefinition; use WCPay\PaymentMethods\Configs\Definitions\WechatPayDefinition; use WCPay\PaymentMethods\Configs\Interfaces\PaymentMethodDefinitionInterface; @@ -32,6 +34,8 @@ class PaymentMethodDefinitionRegistry { // Add new payment method definitions here. AlipayDefinition::class, WechatPayDefinition::class, + ApplePayDefinition::class, + GooglePayDefinition::class, ]; /** diff --git a/includes/woopay/services/class-checkout-service.php b/includes/woopay/services/class-checkout-service.php index b776d505f27..cf900f7d4b4 100644 --- a/includes/woopay/services/class-checkout-service.php +++ b/includes/woopay/services/class-checkout-service.php @@ -64,7 +64,7 @@ public function create_and_confirm_setup_intention_request( Request $base_reques */ public function is_platform_payment_method( Payment_Information $payment_information ) { // Return false for express checkout method. - if ( isset( $_POST['payment_request_type'] ) || isset( $_POST['express_payment_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification + if ( isset( $_POST['express_payment_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification return false; } diff --git a/tests/js/jest-test-file-setup.js b/tests/js/jest-test-file-setup.js index b9646f92464..5c26b76467e 100644 --- a/tests/js/jest-test-file-setup.js +++ b/tests/js/jest-test-file-setup.js @@ -185,4 +185,6 @@ global.wooPaymentsPaymentMethodDefinitions = { } ), alipay: buildMockDefinition( 'alipay', 'Alipay', [ 'USD' ] ), + google_pay: buildMockDefinition( 'google_pay', 'Google Pay' ), + apple_pay: buildMockDefinition( 'apple_pay', 'Apple Pay' ), }; diff --git a/tests/unit/payment-methods/test-class-upe-payment-gateway.php b/tests/unit/payment-methods/test-class-upe-payment-gateway.php index 6148ba67adf..98042961e65 100644 --- a/tests/unit/payment-methods/test-class-upe-payment-gateway.php +++ b/tests/unit/payment-methods/test-class-upe-payment-gateway.php @@ -390,7 +390,7 @@ public function test_process_payment_returns_correct_redirect_when_using_saved_p public function test_process_payment_returns_correct_redirect_when_using_payment_request() { $order = WC_Helper_Order::create_order(); $intent = WC_Helper_Intention::create_intention(); - $_POST['payment_request_type'] = 'google_pay'; + $_POST['express_payment_type'] = 'google_pay'; $this->mock_gateway->expects( $this->once() ) ->method( 'manage_customer_details_for_order' ) diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay.php b/tests/unit/test-class-wc-payment-gateway-wcpay.php index 2b5f43d6824..1d495c03f20 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay.php @@ -3915,7 +3915,7 @@ public function test_process_payment_returns_correct_redirect() { public function test_process_payment_returns_correct_redirect_when_using_payment_request() { $order = WC_Helper_Order::create_order(); - $_POST['payment_request_type'] = 'google_pay'; + $_POST['express_payment_type'] = 'google_pay'; $_POST = [ 'wcpay-payment-method' => 'pm_mock', 'payment_method' => 'woocommerce_payments', diff --git a/tests/unit/test-class-wc-payments-token-service.php b/tests/unit/test-class-wc-payments-token-service.php index 3345964a002..f8d5846d6d0 100644 --- a/tests/unit/test-class-wc-payments-token-service.php +++ b/tests/unit/test-class-wc-payments-token-service.php @@ -799,4 +799,219 @@ private function generate_link_token( $stripe_id, $wp_id = 0 ) { $token->save(); return $token; } + + /** + * Ensures token added with Google Pay wallet metadata. + */ + public function test_add_token_to_user_with_google_pay_wallet() { + $expiry_year = intval( gmdate( 'Y' ) ) + 1; + $mock_payment_method = [ + 'id' => 'pm_mock', + 'card' => [ + 'brand' => 'visa', + 'last4' => '4242', + 'exp_month' => 6, + 'exp_year' => $expiry_year, + 'wallet' => [ + 'type' => 'google_pay', + ], + ], + 'type' => Payment_Method::CARD, + ]; + + $token = $this->token_service->add_token_to_user( $mock_payment_method, wp_get_current_user() ); + + $this->assertEquals( 'woocommerce_payments', $token->get_gateway_id() ); + $this->assertEquals( 1, $token->get_user_id() ); + $this->assertEquals( 'pm_mock', $token->get_token() ); + $this->assertEquals( 'visa', $token->get_card_type() ); + $this->assertEquals( '4242', $token->get_last4() ); + $this->assertEquals( '06', $token->get_expiry_month() ); + $this->assertEquals( $expiry_year, $token->get_expiry_year() ); + $this->assertEquals( 'google_pay', $token->get_meta( '_wcpay_wallet_type', true ) ); + } + + /** + * Ensures token added without wallet metadata. + */ + public function test_add_token_to_user_without_wallet() { + $expiry_year = intval( gmdate( 'Y' ) ) + 1; + $mock_payment_method = [ + 'id' => 'pm_mock', + 'card' => [ + 'brand' => 'visa', + 'last4' => '4242', + 'exp_month' => 6, + 'exp_year' => $expiry_year, + ], + 'type' => Payment_Method::CARD, + ]; + + $token = $this->token_service->add_token_to_user( $mock_payment_method, wp_get_current_user() ); + + $this->assertEquals( 'woocommerce_payments', $token->get_gateway_id() ); + $this->assertEquals( '', $token->get_meta( '_wcpay_wallet_type', true ) ); + } + + /** + * Ensures get_account_saved_payment_methods_list_item_wallet returns wallet display name. + */ + public function test_get_account_saved_payment_methods_list_item_wallet_google_pay() { + // Create a mock payment method that returns the title. + $mock_payment_method = $this->createMock( WCPay\Payment_Methods\UPE_Payment_Method::class ); + $mock_payment_method->method( 'get_title' )->willReturn( 'Google Pay' ); + + // Use Reflection to inject the mock into WC_Payments' payment method map. + $reflection = new ReflectionClass( WC_Payments::class ); + $property = $reflection->getProperty( 'payment_method_map' ); + $property->setAccessible( true ); + $original_map = $property->getValue(); + $new_map = $original_map; + $new_map['google_pay'] = $mock_payment_method; + $property->setValue( null, $new_map ); + + $token = new WC_Payment_Token_CC(); + $token->set_gateway_id( 'woocommerce_payments' ); + $token->set_token( 'pm_mock' ); + $token->set_card_type( 'visa' ); + $token->set_last4( '4242' ); + $token->set_user_id( 1 ); + $token->set_expiry_month( '12' ); + $token->set_expiry_year( '2030' ); + $token->add_meta_data( '_wcpay_wallet_type', 'google_pay', true ); + $token->save(); + + $item = [ + 'method' => [ + 'brand' => 'Visa', + 'last4' => '4242', + ], + ]; + + $result = $this->token_service->get_account_saved_payment_methods_list_item_wallet( $item, $token ); + + $this->assertEquals( 'Google Pay Visa', $result['method']['brand'] ); + + // Restore original payment method map. + $property->setValue( null, $original_map ); + } + + /** + * Ensures get_account_saved_payment_methods_list_item_wallet returns wallet display name for Apple Pay. + */ + public function test_get_account_saved_payment_methods_list_item_wallet_apple_pay() { + // Create a mock payment method that returns the title. + $mock_payment_method = $this->createMock( WCPay\Payment_Methods\UPE_Payment_Method::class ); + $mock_payment_method->method( 'get_title' )->willReturn( 'Apple Pay' ); + + // Use Reflection to inject the mock into WC_Payments' payment method map. + $reflection = new ReflectionClass( WC_Payments::class ); + $property = $reflection->getProperty( 'payment_method_map' ); + $property->setAccessible( true ); + $original_map = $property->getValue(); + $new_map = $original_map; + $new_map['apple_pay'] = $mock_payment_method; + $property->setValue( null, $new_map ); + + $token = new WC_Payment_Token_CC(); + $token->set_gateway_id( 'woocommerce_payments' ); + $token->set_token( 'pm_mock' ); + $token->set_card_type( 'mastercard' ); + $token->set_last4( '5555' ); + $token->set_user_id( 1 ); + $token->set_expiry_month( '12' ); + $token->set_expiry_year( '2030' ); + $token->add_meta_data( '_wcpay_wallet_type', 'apple_pay', true ); + $token->save(); + + $item = [ + 'method' => [ + 'brand' => 'Mastercard', + 'last4' => '5555', + ], + ]; + + $result = $this->token_service->get_account_saved_payment_methods_list_item_wallet( $item, $token ); + + $this->assertEquals( 'Apple Pay Mastercard', $result['method']['brand'] ); + + // Restore original payment method map. + $property->setValue( null, $original_map ); + } + + /** + * Ensures get_account_saved_payment_methods_list_item_wallet doesn't modify non-wallet tokens. + */ + public function test_get_account_saved_payment_methods_list_item_wallet_without_wallet() { + $token = new WC_Payment_Token_CC(); + $token->set_gateway_id( 'woocommerce_payments' ); + $token->set_token( 'pm_mock' ); + $token->set_card_type( 'visa' ); + $token->set_last4( '4242' ); + $token->set_user_id( 1 ); + $token->set_expiry_month( '12' ); + $token->set_expiry_year( '2030' ); + $token->save(); + + $item = [ + 'method' => [ + 'brand' => 'Visa', + 'last4' => '4242', + ], + ]; + + $result = $this->token_service->get_account_saved_payment_methods_list_item_wallet( $item, $token ); + + $this->assertEquals( 'Visa', $result['method']['brand'] ); + } + + /** + * Ensures get_account_saved_payment_methods_list_item_wallet doesn't modify SEPA tokens. + */ + public function test_get_account_saved_payment_methods_list_item_wallet_ignores_sepa() { + $token = new WC_Payment_Token_WCPay_SEPA(); + $token->set_gateway_id( 'woocommerce_payments_sepa_debit' ); + $token->set_token( 'pm_mock' ); + $token->set_last4( '3000' ); + $token->save(); + + $item = [ + 'method' => [ + 'brand' => 'SEPA IBAN', + 'last4' => '3000', + ], + ]; + + $result = $this->token_service->get_account_saved_payment_methods_list_item_wallet( $item, $token ); + + $this->assertEquals( 'SEPA IBAN', $result['method']['brand'] ); + } + + /** + * Ensures get_account_saved_payment_methods_list_item_wallet handles missing payment method gracefully. + */ + public function test_get_account_saved_payment_methods_list_item_wallet_missing_payment_method() { + $token = new WC_Payment_Token_CC(); + $token->set_gateway_id( 'woocommerce_payments' ); + $token->set_token( 'pm_mock' ); + $token->set_card_type( 'visa' ); + $token->set_last4( '4242' ); + $token->set_user_id( 1 ); + $token->set_expiry_month( '12' ); + $token->set_expiry_year( '2030' ); + $token->add_meta_data( '_wcpay_wallet_type', 'unknown_wallet', true ); + $token->save(); + + $item = [ + 'method' => [ + 'brand' => 'Visa', + 'last4' => '4242', + ], + ]; + + $result = $this->token_service->get_account_saved_payment_methods_list_item_wallet( $item, $token ); + + // Should return unchanged when wallet type not found. + $this->assertEquals( 'Visa', $result['method']['brand'] ); + } } From 000dac3aecfc7c12927a02e80c2e26138e399d60 Mon Sep 17 00:00:00 2001 From: frosso Date: Fri, 21 Nov 2025 08:16:08 +0100 Subject: [PATCH 02/11] WIP --- includes/class-wc-payments-token-service.php | 5 ++--- .../Configs/Definitions/ApplePayDefinition.php | 2 -- .../Configs/Definitions/GooglePayDefinition.php | 2 -- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/includes/class-wc-payments-token-service.php b/includes/class-wc-payments-token-service.php index 01edb21dcef..8e9cdd3a3f4 100644 --- a/includes/class-wc-payments-token-service.php +++ b/includes/class-wc-payments-token-service.php @@ -105,8 +105,7 @@ public function add_token_to_user( $payment_method, $user ) { $token->set_card_type( strtolower( $payment_method[ Payment_Method::CARD ]['display_brand'] ?? $payment_method[ Payment_Method::CARD ]['networks']['preferred'] ?? $payment_method[ Payment_Method::CARD ]['brand'] ) ); $token->set_last4( $payment_method[ Payment_Method::CARD ]['last4'] ); if ( ! empty( $payment_method[ Payment_Method::CARD ]['wallet']['type'] ) ) { - $wallet_type = $payment_method[ Payment_Method::CARD ]['wallet']['type'] ?? null; - $token->add_meta_data( '_wcpay_wallet_type', $wallet_type, true ); + $token->add_meta_data( '_wcpay_wallet_type', $payment_method[ Payment_Method::CARD ]['wallet']['type'], true ); } } $token->set_token( $payment_method['id'] ); @@ -397,7 +396,7 @@ public function get_account_saved_payment_methods_list_item_wallet( $item, $paym $original_brand = $item['method']['brand'] ?? ''; $item['method']['brand'] = sprintf( - /* translators: 1: card brand, 2: wallet name */ + /* translators: 1: wallet name, 2: card brand */ _x( '%1$s %2$s', 'Payment token with wallet', 'woocommerce-payments' ), $payment_method->get_title(), $original_brand diff --git a/includes/payment-methods/Configs/Definitions/ApplePayDefinition.php b/includes/payment-methods/Configs/Definitions/ApplePayDefinition.php index c4c43bbc0cc..070748e12d7 100644 --- a/includes/payment-methods/Configs/Definitions/ApplePayDefinition.php +++ b/includes/payment-methods/Configs/Definitions/ApplePayDefinition.php @@ -9,8 +9,6 @@ use WCPay\PaymentMethods\Configs\Interfaces\PaymentMethodDefinitionInterface; use WCPay\PaymentMethods\Configs\Constants\PaymentMethodCapability; -use WCPay\Constants\Country_Code; -use WCPay\Constants\Currency_Code; use WCPay\PaymentMethods\Configs\Utils\PaymentMethodUtils; /** diff --git a/includes/payment-methods/Configs/Definitions/GooglePayDefinition.php b/includes/payment-methods/Configs/Definitions/GooglePayDefinition.php index 4f53070bd81..1a0e840ee04 100644 --- a/includes/payment-methods/Configs/Definitions/GooglePayDefinition.php +++ b/includes/payment-methods/Configs/Definitions/GooglePayDefinition.php @@ -9,8 +9,6 @@ use WCPay\PaymentMethods\Configs\Interfaces\PaymentMethodDefinitionInterface; use WCPay\PaymentMethods\Configs\Constants\PaymentMethodCapability; -use WCPay\Constants\Country_Code; -use WCPay\Constants\Currency_Code; use WCPay\PaymentMethods\Configs\Utils\PaymentMethodUtils; /** From d4f89172ae418828d5891cbf1c592b98005fcf80 Mon Sep 17 00:00:00 2001 From: frosso Date: Fri, 21 Nov 2025 09:41:00 +0100 Subject: [PATCH 03/11] WIP --- .../admin/test-class-wc-rest-payments-settings-controller.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php b/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php index a24e1c62d98..93e83e233c1 100644 --- a/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php +++ b/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php @@ -265,6 +265,8 @@ public function test_get_settings_returns_available_payment_method_ids() { Payment_Method::SEPA, Payment_Method::P24, Payment_Method::LINK, + Payment_Method::APPLE_PAY, + Payment_Method::GOOGLE_PAY, ], $enabled_method_ids ); From 1416d53081867b1252a7c32f2759fbe53120179b Mon Sep 17 00:00:00 2001 From: frosso Date: Fri, 21 Nov 2025 10:18:23 +0100 Subject: [PATCH 04/11] WIP --- includes/class-wc-payment-gateway-wcpay.php | 42 ++++++++++----------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index 1554dd1be4a..91e6f3e8389 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -1815,31 +1815,31 @@ public function process_payment_for_order( $cart, $payment_information, $schedul $this->maybe_add_customer_notification_note( $order, $processing ); - // ensuring the payment method title is set before any early return paths to avoid incomplete order data. - if ( empty( $_POST['express_payment_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification - // Extract payment method details for setting the payment method title. - if ( $payment_needed ) { - $charge = $intent ? $intent->get_charge() : null; - $payment_method_details = $charge ? $charge->get_payment_method_details() : []; - // For payment intents, get payment method type from the intent itself, fallback to charge details. - $payment_method_type = $intent ? $intent->get_payment_method_type() : null; - if ( ! $payment_method_type && $payment_method_details ) { - $payment_method_type = $payment_method_details['type'] ?? null; - } + // Extract payment method details for setting the payment method title. + if ( $payment_needed ) { + $charge = $intent ? $intent->get_charge() : null; + $payment_method_details = $charge ? $charge->get_payment_method_details() : []; + // For payment intents, get payment method type from the intent itself, fallback to charge details. + $payment_method_type = $intent ? $intent->get_payment_method_type() : null; + if ( ! $payment_method_type && $payment_method_details ) { + $payment_method_type = $payment_method_details['type'] ?? null; + } - if ( 'card' === $payment_method_type && isset( $payment_method_details['card']['last4'] ) ) { - $order->add_meta_data( 'last4', $payment_method_details['card']['last4'], true ); - if ( isset( $payment_method_details['card']['brand'] ) ) { - $order->add_meta_data( '_card_brand', $payment_method_details['card']['brand'], true ); - } - $order->save_meta_data(); + if ( 'card' === $payment_method_type && isset( $payment_method_details['card']['last4'] ) ) { + $order->add_meta_data( 'last4', $payment_method_details['card']['last4'], true ); + if ( isset( $payment_method_details['card']['brand'] ) ) { + $order->add_meta_data( '_card_brand', $payment_method_details['card']['brand'], true ); } - } else { - $payment_method_details = false; - $token = $payment_information->is_using_saved_payment_method() ? $payment_information->get_payment_token() : null; - $payment_method_type = $token ? $this->get_payment_method_type_for_setup_intent( $intent, $token ) : null; + $order->save_meta_data(); } + } else { + $payment_method_details = false; + $token = $payment_information->is_using_saved_payment_method() ? $payment_information->get_payment_token() : null; + $payment_method_type = $token ? $this->get_payment_method_type_for_setup_intent( $intent, $token ) : null; + } + // ensuring the payment method title is set before any early return paths to avoid incomplete order data. + if ( empty( $_POST['express_payment_type'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification $this->set_payment_method_title_for_order( $order, $payment_method_type, $payment_method_details ); } From 0044722d3cc7a2db8e90bd65f1dce22e0f262e9f Mon Sep 17 00:00:00 2001 From: frosso Date: Fri, 21 Nov 2025 10:20:20 +0100 Subject: [PATCH 05/11] WIP --- client/settings/express-checkout/apple-google-pay-item.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/settings/express-checkout/apple-google-pay-item.tsx b/client/settings/express-checkout/apple-google-pay-item.tsx index dac6bf6db60..4f104a9a382 100644 --- a/client/settings/express-checkout/apple-google-pay-item.tsx +++ b/client/settings/express-checkout/apple-google-pay-item.tsx @@ -31,8 +31,8 @@ const AppleGooglePayExpressCheckoutItem = (): React.ReactElement => { } = useContext( DuplicatedPaymentMethodsContext ); const isDuplicate = Object.keys( duplicates ).includes( id ); - const ApplePayIcon = methodsConfiguration.apple_pay.icon; - const GooglePayIcon = methodsConfiguration.google_pay.icon; + const { icon: ApplePayIcon } = methodsConfiguration.apple_pay; + const { icon: GooglePayIcon } = methodsConfiguration.google_pay; return (
  • Date: Fri, 21 Nov 2025 14:23:38 +0100 Subject: [PATCH 06/11] WIP --- .../payment-method-details/index.tsx | 52 ++++++++++++++++--- .../payment-method-details/style.scss | 7 +++ client/transactions/list/index.tsx | 2 +- 3 files changed, 52 insertions(+), 9 deletions(-) diff --git a/client/components/payment-method-details/index.tsx b/client/components/payment-method-details/index.tsx index d228316d22d..856c8d46065 100755 --- a/client/components/payment-method-details/index.tsx +++ b/client/components/payment-method-details/index.tsx @@ -12,6 +12,7 @@ import './style.scss'; import p24BankList from '../../payment-details/payment-method/p24/bank-list'; import { HoverTooltip } from '../tooltip'; import { getTransactionPaymentMethodTitle } from 'wcpay/transactions/utils/getTransactionPaymentMethodTitle'; +import paymentMethodsMap from 'wcpay/payment-methods-map'; interface Payment { type: string; @@ -67,6 +68,46 @@ const formatDetails = ( payment: Payment ): ReactNode => { } }; +const WalletIcon = ( { payment }: PaymentMethodDetailsProps ) => { + const { wallet } = payment[ payment.type ]; + if ( ! wallet ) return null; + + if ( ! wallet.type ) return null; + + const paymentMethod = paymentMethodsMap[ wallet.type ]; + if ( ! paymentMethod ) return null; + + const { icon: Icon, label } = paymentMethod; + + return ( + + + + ); +}; +const PaymentMethodIcon = ( { payment }: PaymentMethodDetailsProps ) => { + if ( ! payment?.type ) return null; + + const paymentMethod = paymentMethodsMap[ payment.type ]; + if ( ! paymentMethod ) return null; + + const { icon: Icon, label } = paymentMethod; + + return ( + + + + ); +}; + interface PaymentMethodDetailsProps { payment: Payment; } @@ -78,20 +119,15 @@ const PaymentMethodDetails = ( { payment }: PaymentMethodDetailsProps ) => { return ; } - let brand = payment.type; - if ( paymentMethod && paymentMethod.brand ) { - brand = paymentMethod.brand; - } - if ( paymentMethod && paymentMethod.network ) { - brand = paymentMethod.network; - } - const details = formatDetails( payment ); const accountCountry = wcpaySettings?.accountStatus?.country || 'US'; + const brand = + paymentMethod?.brand || paymentMethod?.network || payment?.type; return ( + ) ) : ( - + ), }, order: { From 911f789344961d6cf1ccd30da78386aa761ff56d Mon Sep 17 00:00:00 2001 From: frosso Date: Fri, 21 Nov 2025 14:41:17 +0100 Subject: [PATCH 07/11] ope cleanup --- .../payment-method-details/index.tsx | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/client/components/payment-method-details/index.tsx b/client/components/payment-method-details/index.tsx index 856c8d46065..37ae85dee8a 100755 --- a/client/components/payment-method-details/index.tsx +++ b/client/components/payment-method-details/index.tsx @@ -89,25 +89,6 @@ const WalletIcon = ( { payment }: PaymentMethodDetailsProps ) => { ); }; -const PaymentMethodIcon = ( { payment }: PaymentMethodDetailsProps ) => { - if ( ! payment?.type ) return null; - - const paymentMethod = paymentMethodsMap[ payment.type ]; - if ( ! paymentMethod ) return null; - - const { icon: Icon, label } = paymentMethod; - - return ( - - - - ); -}; - interface PaymentMethodDetailsProps { payment: Payment; } From 1e727d7a3951efcdaae3117ad853f9e29482e371 Mon Sep 17 00:00:00 2001 From: frosso Date: Fri, 21 Nov 2025 14:47:56 +0100 Subject: [PATCH 08/11] slight refactor on the icon --- .../class-wc-payments-order-success-page.php | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/includes/class-wc-payments-order-success-page.php b/includes/class-wc-payments-order-success-page.php index ddd38711e4b..41138e5e0cc 100644 --- a/includes/class-wc-payments-order-success-page.php +++ b/includes/class-wc-payments-order-success-page.php @@ -233,21 +233,19 @@ public function show_express_checkout_payment_method_name( $order, $express_chec return 'Payment Request'; } - $payment_method_title = $payment_method->get_title(); - $last4 = $order->get_meta( 'last4' ); - - // If we have the last 4 digits, show "Google Pay ending in 1234". - if ( $last4 ) { - return sprintf( - /* translators: 1: payment method name (e.g. Google Pay), 2: last 4 digits of card */ - __( '%1$s ending in %2$s', 'woocommerce-payments' ), - esc_html( $payment_method_title ), - esc_html( $last4 ) - ); - } - - // Otherwise just show "Google Pay". - return esc_html( $payment_method_title ); + ob_start(); + ?> + + Date: Fri, 21 Nov 2025 18:17:28 +0100 Subject: [PATCH 09/11] fix post-merge issues --- .../test-class-wc-rest-payments-settings-controller.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php b/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php index 5abefcbf7b2..0dacc70c509 100644 --- a/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php +++ b/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php @@ -20,9 +20,11 @@ use WCPay\Payment_Methods\Sepa_Payment_Method; use WCPay\Payment_Methods\Link_Payment_Method; use WCPay\PaymentMethods\Configs\Definitions\AffirmDefinition; +use WCPay\PaymentMethods\Configs\Definitions\ApplePayDefinition; use WCPay\PaymentMethods\Configs\Definitions\BancontactDefinition; use WCPay\PaymentMethods\Configs\Definitions\EpsDefinition; use WCPay\PaymentMethods\Configs\Definitions\GiropayDefinition; +use WCPay\PaymentMethods\Configs\Definitions\GooglePayDefinition; use WCPay\PaymentMethods\Configs\Definitions\IdealDefinition; use WCPay\PaymentMethods\Configs\Definitions\P24Definition; use WCPay\PaymentMethods\Configs\Definitions\SofortDefinition; @@ -145,9 +147,11 @@ public function set_up() { $mock_payment_methods = []; $payment_method_definitions = [ + ApplePayDefinition::class, BancontactDefinition::class, EpsDefinition::class, GiropayDefinition::class, + GooglePayDefinition::class, IdealDefinition::class, P24Definition::class, SofortDefinition::class, @@ -297,8 +301,8 @@ public function test_get_settings_returns_available_payment_method_ids() { Payment_Method::P24, Payment_Method::SOFORT, Payment_Method::LINK, - Payment_Method::APPLE_PAY, - Payment_Method::GOOGLE_PAY, + Payment_Method::APPLE_PAY, + Payment_Method::GOOGLE_PAY, ]; sort( $expected_method_ids ); From 91afd58fd28268caa29135fe6c5e5fe58d496cb1 Mon Sep 17 00:00:00 2001 From: frosso Date: Mon, 24 Nov 2025 12:57:30 +0100 Subject: [PATCH 10/11] fix after payment definition refactor --- .../Definitions/ApplePayDefinition.php | 9 ---- .../Definitions/GooglePayDefinition.php | 45 ------------------- 2 files changed, 54 deletions(-) diff --git a/includes/payment-methods/Configs/Definitions/ApplePayDefinition.php b/includes/payment-methods/Configs/Definitions/ApplePayDefinition.php index 070748e12d7..85774a2d817 100644 --- a/includes/payment-methods/Configs/Definitions/ApplePayDefinition.php +++ b/includes/payment-methods/Configs/Definitions/ApplePayDefinition.php @@ -75,15 +75,6 @@ public static function get_description( ?string $account_country = null ): strin return __( 'Apple Pay is an easy and secure way for customers to pay on your store.', 'woocommerce-payments' ); } - /** - * Is the payment method a BNPL (Buy Now Pay Later) payment method? - * - * @return boolean - */ - public static function is_bnpl(): bool { - return PaymentMethodUtils::is_bnpl( self::get_capabilities() ); - } - /** * Is the payment method a reusable payment method? * diff --git a/includes/payment-methods/Configs/Definitions/GooglePayDefinition.php b/includes/payment-methods/Configs/Definitions/GooglePayDefinition.php index 1a0e840ee04..f90c3fa8eb2 100644 --- a/includes/payment-methods/Configs/Definitions/GooglePayDefinition.php +++ b/includes/payment-methods/Configs/Definitions/GooglePayDefinition.php @@ -75,42 +75,6 @@ public static function get_description( ?string $account_country = null ): strin return __( 'Offer customers a fast, secure checkout experience with Google Pay.', 'woocommerce-payments' ); } - /** - * Is the payment method a BNPL (Buy Now Pay Later) payment method? - * - * @return boolean - */ - public static function is_bnpl(): bool { - return PaymentMethodUtils::is_bnpl( self::get_capabilities() ); - } - - /** - * Is the payment method a reusable payment method? - * - * @return boolean - */ - public static function is_reusable(): bool { - return PaymentMethodUtils::is_reusable( self::get_capabilities() ); - } - - /** - * Does the payment method accept only domestic payments? - * - * @return boolean - */ - public static function accepts_only_domestic_payments(): bool { - return PaymentMethodUtils::accepts_only_domestic_payments( self::get_capabilities() ); - } - - /** - * Does the payment method allow manual capture? - * - * @return boolean - */ - public static function allows_manual_capture(): bool { - return PaymentMethodUtils::allows_manual_capture( self::get_capabilities() ); - } - /** * Get the list of supported currencies * @@ -213,15 +177,6 @@ public static function is_available_for( string $currency, string $account_count return true; } - /** - * Whether this payment method should be enabled by default - * - * @return bool - */ - public static function is_enabled_by_default(): bool { - return false; - } - /** * Get the minimum amount for this payment method for a given currency and country * From dbf4f034ab52b94843be52529344208b4f7afdb8 Mon Sep 17 00:00:00 2001 From: frosso Date: Mon, 1 Dec 2025 17:06:10 +0100 Subject: [PATCH 11/11] post-merge cleanup --- .../Definitions/ApplePayDefinition.php | 27 ------------------- 1 file changed, 27 deletions(-) diff --git a/includes/payment-methods/Configs/Definitions/ApplePayDefinition.php b/includes/payment-methods/Configs/Definitions/ApplePayDefinition.php index 85774a2d817..6a70d410c63 100644 --- a/includes/payment-methods/Configs/Definitions/ApplePayDefinition.php +++ b/includes/payment-methods/Configs/Definitions/ApplePayDefinition.php @@ -75,33 +75,6 @@ public static function get_description( ?string $account_country = null ): strin return __( 'Apple Pay is an easy and secure way for customers to pay on your store.', 'woocommerce-payments' ); } - /** - * Is the payment method a reusable payment method? - * - * @return boolean - */ - public static function is_reusable(): bool { - return PaymentMethodUtils::is_reusable( self::get_capabilities() ); - } - - /** - * Does the payment method accept only domestic payments? - * - * @return boolean - */ - public static function accepts_only_domestic_payments(): bool { - return PaymentMethodUtils::accepts_only_domestic_payments( self::get_capabilities() ); - } - - /** - * Does the payment method allow manual capture? - * - * @return boolean - */ - public static function allows_manual_capture(): bool { - return PaymentMethodUtils::allows_manual_capture( self::get_capabilities() ); - } - /** * Get the list of supported currencies *