From 91d638bf0fcaa5110d928e30e4536cf8c8ba1501 Mon Sep 17 00:00:00 2001 From: Dale du Preez Date: Thu, 7 Aug 2025 13:40:49 +0200 Subject: [PATCH 01/12] Basic get_all_payment_methods() implementation --- includes/class-wc-stripe-customer.php | 96 ++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/includes/class-wc-stripe-customer.php b/includes/class-wc-stripe-customer.php index 11c658c437..261eaa6054 100644 --- a/includes/class-wc-stripe-customer.php +++ b/includes/class-wc-stripe-customer.php @@ -30,6 +30,11 @@ class WC_Stripe_Customer { WC_Stripe_UPE_Payment_Method_Becs_Debit::STRIPE_ID, ]; + /** + * The maximum value for the `limit` argument in the Stripe payment_methods API. + */ + protected const PAYMENT_METHODS_API_LIMIT = 100; + /** * Stripe customer ID * @@ -712,7 +717,7 @@ public function get_payment_methods( $payment_method_type ) { [ 'customer' => $this->get_id(), 'type' => $payment_method_type, - 'limit' => 100, // Maximum allowed value. + 'limit' => self::PAYMENT_METHODS_API_LIMIT, ], 'payment_methods' . $params, 'GET' @@ -741,6 +746,95 @@ public function get_payment_methods( $payment_method_type ) { return empty( $payment_methods ) ? [] : $payment_methods; } + /** + * Get all payment methods for a customer. + * + * @param string[] $payment_method_types The payment method types to look for using Stripe method IDs. If the array is empty, it implies all payment method types. + * @param int $limit The maximum number of payment methods to return. If the value is -1, no limit is applied. + * @return array + */ + public function get_all_payment_methods( array $payment_method_types = [], int $limit = -1 ) { + if ( ! $this->get_id() ) { + return []; + } + + $cache_key = self::PAYMENT_METHODS_TRANSIENT_KEY . '__all_' . $this->get_id(); + $all_payment_methods = get_transient( $cache_key ); + + if ( false === $all_payment_methods || ! is_array( $all_payment_methods ) ) { + $all_payment_methods = []; + $last_payment_method_id = null; + + do { + $request_params = [ + 'customer' => $this->get_id(), + 'limit' => self::PAYMENT_METHODS_API_LIMIT, + ]; + + if ( $last_payment_method_id ) { + $request_params['starting_after'] = $last_payment_method_id; + } + + // TODO: work out how we can add the SEPA `expand[]` parameters to the request. Will that work when all types are being returned? + $response = WC_Stripe_API::request( $request_params, 'payment_methods', 'GET' ); + + if ( ! empty( $response->error ) ) { + if ( + isset( $response->error->code ) + && isset( $response->error->param ) + && 'customer' === $response->error->param + && 'resource_missing' === $response->error->code + ) { + // If the customer doesn't exist, cache an empty array. + set_transient( $cache_key, [], DAY_IN_SECONDS ); + } + return []; + } + + if ( ! is_array( $response->data ) || [] === $response->data ) { + break; + } + + $all_payment_methods = array_merge( $all_payment_methods, $response->data ); + + // Reset the last payment method ID so we can paginate correctly. + $last_payment_method_id = null; + if ( isset( $response->has_more ) && $response->has_more ) { + $last_payment_method = end( $response->data ); + + if ( $last_payment_method && ! empty( $last_payment_method->id ) ) { + $last_payment_method_id = $last_payment_method->id; + } + } + } while ( null !== $last_payment_method_id ); + + // Always cache the result without any filters applied. + set_transient( $cache_key, $all_payment_methods, DAY_IN_SECONDS ); + } + + // If there are no payment methods, no need to apply any filters below. + if ( [] === $all_payment_methods ) { + return $all_payment_methods; + } + + // Only apply the limit and type filters after fetching and caching all payment methods. + $filtered_payment_methods = $all_payment_methods; + if ( [] !== $payment_method_types ) { + $filtered_payment_methods = array_filter( + $filtered_payment_methods, + function ( $payment_method ) use ( $payment_method_types ) { + return in_array( $payment_method->type, $payment_method_types, true ); + } + ); + } + + if ( $limit > 0 ) { + return array_slice( $filtered_payment_methods, 0, $limit ); + } + + return $filtered_payment_methods; + } + /** * Delete a source from stripe. * From 35837dbf0ec9816526da573ddbe5b279d6f623bb Mon Sep 17 00:00:00 2001 From: Dale du Preez Date: Fri, 8 Aug 2025 16:34:29 +0200 Subject: [PATCH 02/12] Ensure we clear the new _all payment methods cache --- includes/class-wc-stripe-customer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/includes/class-wc-stripe-customer.php b/includes/class-wc-stripe-customer.php index 261eaa6054..ab85d8dea9 100644 --- a/includes/class-wc-stripe-customer.php +++ b/includes/class-wc-stripe-customer.php @@ -940,6 +940,7 @@ public function clear_cache( $payment_method_id = null ) { foreach ( self::STRIPE_PAYMENT_METHODS as $payment_method_type ) { delete_transient( self::PAYMENT_METHODS_TRANSIENT_KEY . $payment_method_type . $this->get_id() ); } + delete_transient( self::PAYMENT_METHODS_TRANSIENT_KEY . '__all_' . $this->get_id() ); // Clear cache for the specific payment method if provided. if ( $payment_method_id ) { WC_Stripe_Database_Cache::delete( 'payment_method_for_source_' . $payment_method_id ); From 9cfccaf7b716604c0d9e319bf4d7e3eba9cd51fa Mon Sep 17 00:00:00 2001 From: Dale du Preez Date: Fri, 8 Aug 2025 16:34:58 +0200 Subject: [PATCH 03/12] Always send expand flags for SEPA --- includes/class-wc-stripe-customer.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/includes/class-wc-stripe-customer.php b/includes/class-wc-stripe-customer.php index ab85d8dea9..cb4111c13b 100644 --- a/includes/class-wc-stripe-customer.php +++ b/includes/class-wc-stripe-customer.php @@ -775,8 +775,7 @@ public function get_all_payment_methods( array $payment_method_types = [], int $ $request_params['starting_after'] = $last_payment_method_id; } - // TODO: work out how we can add the SEPA `expand[]` parameters to the request. Will that work when all types are being returned? - $response = WC_Stripe_API::request( $request_params, 'payment_methods', 'GET' ); + $response = WC_Stripe_API::request( $request_params, 'payment_methods?expand[]=data.sepa_debit.generated_from.charge&expand[]=data.sepa_debit.generated_from.setup_attempt', 'GET' ); if ( ! empty( $response->error ) ) { if ( From 68bee8102f8426441cea0f5f8e4d0e6279f3bd44 Mon Sep 17 00:00:00 2001 From: Dale du Preez Date: Fri, 8 Aug 2025 16:37:50 +0200 Subject: [PATCH 04/12] Refactor logic to get customer UPE tokens --- .../class-wc-stripe-payment-tokens.php | 29 +++++++------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/includes/payment-tokens/class-wc-stripe-payment-tokens.php b/includes/payment-tokens/class-wc-stripe-payment-tokens.php index 839d9b9654..efa9198b77 100644 --- a/includes/payment-tokens/class-wc-stripe-payment-tokens.php +++ b/includes/payment-tokens/class-wc-stripe-payment-tokens.php @@ -301,19 +301,20 @@ public function woocommerce_get_customer_upe_payment_tokens( $tokens, $user_id, // Retrieve the payment methods for the enabled reusable gateways. $payment_methods = []; + + $reusable_payment_method_types = array_keys( self::UPE_REUSABLE_GATEWAYS_BY_PAYMENT_METHOD ); + $active_payment_method_types = []; + if ( $gateway->is_oc_enabled() ) { - // For OC, get all available payment method types - foreach ( self::UPE_REUSABLE_GATEWAYS_BY_PAYMENT_METHOD as $payment_method_type => $reausable_gateway_id ) { + // For Optimized Checkout, get all available payment method types. + foreach ( $reusable_payment_method_types as $payment_method_type ) { $payment_method_instance = WC_Stripe_UPE_Payment_Gateway::get_payment_method_instance( $payment_method_type ); if ( $payment_method_instance ) { - $retrieved_methods = $customer->get_payment_methods( $payment_method_type ); - if ( ! empty( $retrieved_methods ) ) { - $payment_methods[] = $retrieved_methods; - } + $active_payment_method_types[] = $payment_method_type; } } } else { - foreach ( self::UPE_REUSABLE_GATEWAYS_BY_PAYMENT_METHOD as $payment_method_type => $reausable_gateway_id ) { + foreach ( $reusable_payment_method_types as $payment_method_type ) { // The payment method type doesn't match the ones we use. Nothing to do here. if ( ! isset( $gateway->payment_methods[ $payment_method_type ] ) ) { continue; @@ -321,22 +322,12 @@ public function woocommerce_get_customer_upe_payment_tokens( $tokens, $user_id, $payment_method_instance = $gateway->payment_methods[ $payment_method_type ]; if ( $payment_method_instance->is_enabled() ) { - $payment_methods[] = $customer->get_payment_methods( $payment_method_type ); + $active_payment_method_types[] = $payment_method_type; } } } - // Add SEPA if it is disabled and iDEAL or Bancontact are enabled. iDEAL and Bancontact tokens are saved as SEPA tokens. - if ( $gateway->is_sepa_tokens_for_other_methods_enabled() ) { - if ( $gateway->is_oc_enabled() ) { - $payment_methods[] = $customer->get_payment_methods( WC_Stripe_UPE_Payment_Method_Sepa::STRIPE_ID ); - } elseif ( ! $gateway->payment_methods[ WC_Stripe_UPE_Payment_Method_Sepa::STRIPE_ID ]->is_enabled() - && ( $gateway->payment_methods[ WC_Stripe_UPE_Payment_Method_Ideal::STRIPE_ID ]->is_enabled() - || $gateway->payment_methods[ WC_Stripe_UPE_Payment_Method_Bancontact::STRIPE_ID ]->is_enabled() ) ) { - - $payment_methods[] = $customer->get_payment_methods( WC_Stripe_UPE_Payment_Method_Sepa::STRIPE_ID ); - } - } + $payment_methods = $customer->get_all_payment_methods( $active_payment_method_types ); $payment_methods = array_merge( ...$payment_methods ); $payment_method_ids = array_map( From c622d7627d13866d779251272de7dc14a10ad44c Mon Sep 17 00:00:00 2001 From: Dale du Preez Date: Fri, 8 Aug 2025 16:55:04 +0200 Subject: [PATCH 05/12] Refactor WC_Stripe_Subscriptions_Trait->maybe_render_subscription_payment_method() to use get_all_payment_methods() --- .../compat/trait-wc-stripe-subscriptions.php | 135 +++++++++--------- 1 file changed, 70 insertions(+), 65 deletions(-) diff --git a/includes/compat/trait-wc-stripe-subscriptions.php b/includes/compat/trait-wc-stripe-subscriptions.php index 3078684c53..204c4434cc 100644 --- a/includes/compat/trait-wc-stripe-subscriptions.php +++ b/includes/compat/trait-wc-stripe-subscriptions.php @@ -1013,72 +1013,12 @@ public function maybe_render_subscription_payment_method( $payment_method_to_dis $stripe_customer->set_id( $stripe_customer_id ); - $payment_method_to_display = __( 'N/A', 'woocommerce-gateway-stripe' ); - try { - // Retrieve all possible payment methods for subscriptions. - foreach ( WC_Stripe_Customer::STRIPE_PAYMENT_METHODS as $payment_method_type ) { - foreach ( $stripe_customer->get_payment_methods( $payment_method_type ) as $source ) { - if ( $source->id !== $stripe_source_id ) { - continue; - } + $customer_payment_methods = $stripe_customer->get_all_payment_methods(); - // Legacy handling for Stripe Card objects. ref: https://docs.stripe.com/api/cards/object - if ( isset( $source->object ) && WC_Stripe_Payment_Methods::CARD === $source->object ) { - /* translators: 1) card brand 2) last 4 digits */ - $payment_method_to_display = sprintf( __( 'Via %1$s card ending in %2$s', 'woocommerce-gateway-stripe' ), ( isset( $source->brand ) ? wc_get_credit_card_type_label( $source->brand ) : __( 'N/A', 'woocommerce-gateway-stripe' ) ), $source->last4 ); - break 2; - } - - switch ( $source->type ) { - case WC_Stripe_Payment_Methods::CARD: - /* translators: 1) card brand 2) last 4 digits */ - $payment_method_to_display = sprintf( __( 'Via %1$s card ending in %2$s', 'woocommerce-gateway-stripe' ), ( isset( $source->card->brand ) ? wc_get_credit_card_type_label( $source->card->brand ) : __( 'N/A', 'woocommerce-gateway-stripe' ) ), $source->card->last4 ); - break 3; - case WC_Stripe_Payment_Methods::SEPA_DEBIT: - /* translators: 1) last 4 digits of SEPA Direct Debit */ - $payment_method_to_display = sprintf( __( 'Via SEPA Direct Debit ending in %1$s', 'woocommerce-gateway-stripe' ), $source->sepa_debit->last4 ); - break 3; - case WC_Stripe_Payment_Methods::CASHAPP_PAY: - /* translators: 1) Cash App Cashtag */ - $payment_method_to_display = sprintf( __( 'Via Cash App Pay (%1$s)', 'woocommerce-gateway-stripe' ), $source->cashapp->cashtag ); - break 3; - case WC_Stripe_Payment_Methods::LINK: - /* translators: 1) email address associated with the Stripe Link payment method */ - $payment_method_to_display = sprintf( __( 'Via Stripe Link (%1$s)', 'woocommerce-gateway-stripe' ), $source->link->email ); - break 3; - case WC_Stripe_Payment_Methods::ACH: - $payment_method_to_display = sprintf( - /* translators: 1) account type (checking, savings), 2) last 4 digits of account. */ - __( 'Via %1$s Account ending in %2$s', 'woocommerce-gateway-stripe' ), - ucfirst( $source->us_bank_account->account_type ), - $source->us_bank_account->last4 - ); - break 3; - case WC_Stripe_Payment_Methods::BECS_DEBIT: - $payment_method_to_display = sprintf( - /* translators: last 4 digits of account. */ - __( 'BECS Direct Debit ending in %s', 'woocommerce-gateway-stripe' ), - $source->au_becs_debit->last4 - ); - break 3; - case WC_Stripe_Payment_Methods::ACSS_DEBIT: - $payment_method_to_display = sprintf( - /* translators: 1) bank name, 2) last 4 digits of account. */ - __( 'Via %1$s ending in %2$s', 'woocommerce-gateway-stripe' ), - $source->acss_debit->bank_name, - $source->acss_debit->last4 - ); - break 3; - case WC_Stripe_Payment_Methods::BACS_DEBIT: - /* translators: 1) the Bacs Direct Debit payment method's last 4 numbers */ - $payment_method_to_display = sprintf( __( 'Via Bacs Direct Debit ending in (%1$s)', 'woocommerce-gateway-stripe' ), $source->bacs_debit->last4 ); - break 3; - case WC_Stripe_Payment_Methods::AMAZON_PAY: - /* translators: 1) the Amazon Pay payment method's email */ - $payment_method_to_display = sprintf( __( 'Via Amazon Pay (%1$s)', 'woocommerce-gateway-stripe' ), $source->billing_details->email ?? '' ); - break 3; - } + foreach ( $customer_payment_methods as $payment_method ) { + if ( $payment_method->id === $stripe_source_id ) { + return $this->get_payment_method_to_display_for_payment_method( $payment_method ); } } } catch ( WC_Stripe_Exception $e ) { @@ -1086,7 +1026,72 @@ public function maybe_render_subscription_payment_method( $payment_method_to_dis WC_Stripe_Logger::log( 'Error: ' . $e->getMessage() ); } - return $payment_method_to_display; + return __( 'N/A', 'woocommerce-gateway-stripe' ); + } + + /** + * Helper function to get the descriptive text for a payment method or source. + * + * @param object $source The payment method or source object. + * @return string The descriptive text for the payment method or source. + */ + protected function get_payment_method_to_display_for_payment_method( object $source ): string { + // Legacy handling for Stripe Card objects. ref: https://docs.stripe.com/api/cards/object + if ( isset( $source->object ) && WC_Stripe_Payment_Methods::CARD === $source->object ) { + return sprintf( + /* translators: 1) card brand 2) last 4 digits */ + __( 'Via %1$s card ending in %2$s', 'woocommerce-gateway-stripe' ), + ( isset( $source->brand ) ? wc_get_credit_card_type_label( $source->brand ) : __( 'N/A', 'woocommerce-gateway-stripe' ) ), + $source->last4 + ); + } + + switch ( $source->type ) { + case WC_Stripe_Payment_Methods::CARD: + return sprintf( + /* translators: 1) card brand 2) last 4 digits */ + __( 'Via %1$s card ending in %2$s', 'woocommerce-gateway-stripe' ), + ( isset( $source->card->brand ) ? wc_get_credit_card_type_label( $source->card->brand ) : __( 'N/A', 'woocommerce-gateway-stripe' ) ), + $source->card->last4 + ); + case WC_Stripe_Payment_Methods::SEPA_DEBIT: + /* translators: 1) last 4 digits of SEPA Direct Debit */ + return sprintf( __( 'Via SEPA Direct Debit ending in %1$s', 'woocommerce-gateway-stripe' ), $source->sepa_debit->last4 ); + case WC_Stripe_Payment_Methods::CASHAPP_PAY: + /* translators: 1) Cash App Cashtag */ + return sprintf( __( 'Via Cash App Pay (%1$s)', 'woocommerce-gateway-stripe' ), $source->cashapp->cashtag ); + case WC_Stripe_Payment_Methods::LINK: + /* translators: 1) email address associated with the Stripe Link payment method */ + return sprintf( __( 'Via Stripe Link (%1$s)', 'woocommerce-gateway-stripe' ), $source->link->email ); + case WC_Stripe_Payment_Methods::ACH: + return sprintf( + /* translators: 1) account type (checking, savings), 2) last 4 digits of account. */ + __( 'Via %1$s Account ending in %2$s', 'woocommerce-gateway-stripe' ), + ucfirst( $source->us_bank_account->account_type ), + $source->us_bank_account->last4 + ); + case WC_Stripe_Payment_Methods::BECS_DEBIT: + return sprintf( + /* translators: last 4 digits of account. */ + __( 'BECS Direct Debit ending in %s', 'woocommerce-gateway-stripe' ), + $source->au_becs_debit->last4 + ); + case WC_Stripe_Payment_Methods::ACSS_DEBIT: + return sprintf( + /* translators: 1) bank name, 2) last 4 digits of account. */ + __( 'Via %1$s ending in %2$s', 'woocommerce-gateway-stripe' ), + $source->acss_debit->bank_name, + $source->acss_debit->last4 + ); + case WC_Stripe_Payment_Methods::BACS_DEBIT: + /* translators: 1) the Bacs Direct Debit payment method's last 4 numbers */ + return sprintf( __( 'Via Bacs Direct Debit ending in (%1$s)', 'woocommerce-gateway-stripe' ), $source->bacs_debit->last4 ); + case WC_Stripe_Payment_Methods::AMAZON_PAY: + /* translators: 1) the Amazon Pay payment method's email */ + return sprintf( __( 'Via Amazon Pay (%1$s)', 'woocommerce-gateway-stripe' ), $source->billing_details->email ?? '' ); + } + + return __( 'N/A', 'woocommerce-gateway-stripe' ); } /** From 2e20c28d2bb296119dcfa59c323aae561c56a809 Mon Sep 17 00:00:00 2001 From: Dale du Preez Date: Fri, 8 Aug 2025 17:29:05 +0200 Subject: [PATCH 06/12] Revert "Refactor WC_Stripe_Subscriptions_Trait->maybe_render_subscription_payment_method() to use get_all_payment_methods()" This reverts commit c622d7627d13866d779251272de7dc14a10ad44c. --- .../compat/trait-wc-stripe-subscriptions.php | 135 +++++++++--------- 1 file changed, 65 insertions(+), 70 deletions(-) diff --git a/includes/compat/trait-wc-stripe-subscriptions.php b/includes/compat/trait-wc-stripe-subscriptions.php index 204c4434cc..3078684c53 100644 --- a/includes/compat/trait-wc-stripe-subscriptions.php +++ b/includes/compat/trait-wc-stripe-subscriptions.php @@ -1013,12 +1013,72 @@ public function maybe_render_subscription_payment_method( $payment_method_to_dis $stripe_customer->set_id( $stripe_customer_id ); + $payment_method_to_display = __( 'N/A', 'woocommerce-gateway-stripe' ); + try { - $customer_payment_methods = $stripe_customer->get_all_payment_methods(); + // Retrieve all possible payment methods for subscriptions. + foreach ( WC_Stripe_Customer::STRIPE_PAYMENT_METHODS as $payment_method_type ) { + foreach ( $stripe_customer->get_payment_methods( $payment_method_type ) as $source ) { + if ( $source->id !== $stripe_source_id ) { + continue; + } - foreach ( $customer_payment_methods as $payment_method ) { - if ( $payment_method->id === $stripe_source_id ) { - return $this->get_payment_method_to_display_for_payment_method( $payment_method ); + // Legacy handling for Stripe Card objects. ref: https://docs.stripe.com/api/cards/object + if ( isset( $source->object ) && WC_Stripe_Payment_Methods::CARD === $source->object ) { + /* translators: 1) card brand 2) last 4 digits */ + $payment_method_to_display = sprintf( __( 'Via %1$s card ending in %2$s', 'woocommerce-gateway-stripe' ), ( isset( $source->brand ) ? wc_get_credit_card_type_label( $source->brand ) : __( 'N/A', 'woocommerce-gateway-stripe' ) ), $source->last4 ); + break 2; + } + + switch ( $source->type ) { + case WC_Stripe_Payment_Methods::CARD: + /* translators: 1) card brand 2) last 4 digits */ + $payment_method_to_display = sprintf( __( 'Via %1$s card ending in %2$s', 'woocommerce-gateway-stripe' ), ( isset( $source->card->brand ) ? wc_get_credit_card_type_label( $source->card->brand ) : __( 'N/A', 'woocommerce-gateway-stripe' ) ), $source->card->last4 ); + break 3; + case WC_Stripe_Payment_Methods::SEPA_DEBIT: + /* translators: 1) last 4 digits of SEPA Direct Debit */ + $payment_method_to_display = sprintf( __( 'Via SEPA Direct Debit ending in %1$s', 'woocommerce-gateway-stripe' ), $source->sepa_debit->last4 ); + break 3; + case WC_Stripe_Payment_Methods::CASHAPP_PAY: + /* translators: 1) Cash App Cashtag */ + $payment_method_to_display = sprintf( __( 'Via Cash App Pay (%1$s)', 'woocommerce-gateway-stripe' ), $source->cashapp->cashtag ); + break 3; + case WC_Stripe_Payment_Methods::LINK: + /* translators: 1) email address associated with the Stripe Link payment method */ + $payment_method_to_display = sprintf( __( 'Via Stripe Link (%1$s)', 'woocommerce-gateway-stripe' ), $source->link->email ); + break 3; + case WC_Stripe_Payment_Methods::ACH: + $payment_method_to_display = sprintf( + /* translators: 1) account type (checking, savings), 2) last 4 digits of account. */ + __( 'Via %1$s Account ending in %2$s', 'woocommerce-gateway-stripe' ), + ucfirst( $source->us_bank_account->account_type ), + $source->us_bank_account->last4 + ); + break 3; + case WC_Stripe_Payment_Methods::BECS_DEBIT: + $payment_method_to_display = sprintf( + /* translators: last 4 digits of account. */ + __( 'BECS Direct Debit ending in %s', 'woocommerce-gateway-stripe' ), + $source->au_becs_debit->last4 + ); + break 3; + case WC_Stripe_Payment_Methods::ACSS_DEBIT: + $payment_method_to_display = sprintf( + /* translators: 1) bank name, 2) last 4 digits of account. */ + __( 'Via %1$s ending in %2$s', 'woocommerce-gateway-stripe' ), + $source->acss_debit->bank_name, + $source->acss_debit->last4 + ); + break 3; + case WC_Stripe_Payment_Methods::BACS_DEBIT: + /* translators: 1) the Bacs Direct Debit payment method's last 4 numbers */ + $payment_method_to_display = sprintf( __( 'Via Bacs Direct Debit ending in (%1$s)', 'woocommerce-gateway-stripe' ), $source->bacs_debit->last4 ); + break 3; + case WC_Stripe_Payment_Methods::AMAZON_PAY: + /* translators: 1) the Amazon Pay payment method's email */ + $payment_method_to_display = sprintf( __( 'Via Amazon Pay (%1$s)', 'woocommerce-gateway-stripe' ), $source->billing_details->email ?? '' ); + break 3; + } } } } catch ( WC_Stripe_Exception $e ) { @@ -1026,72 +1086,7 @@ public function maybe_render_subscription_payment_method( $payment_method_to_dis WC_Stripe_Logger::log( 'Error: ' . $e->getMessage() ); } - return __( 'N/A', 'woocommerce-gateway-stripe' ); - } - - /** - * Helper function to get the descriptive text for a payment method or source. - * - * @param object $source The payment method or source object. - * @return string The descriptive text for the payment method or source. - */ - protected function get_payment_method_to_display_for_payment_method( object $source ): string { - // Legacy handling for Stripe Card objects. ref: https://docs.stripe.com/api/cards/object - if ( isset( $source->object ) && WC_Stripe_Payment_Methods::CARD === $source->object ) { - return sprintf( - /* translators: 1) card brand 2) last 4 digits */ - __( 'Via %1$s card ending in %2$s', 'woocommerce-gateway-stripe' ), - ( isset( $source->brand ) ? wc_get_credit_card_type_label( $source->brand ) : __( 'N/A', 'woocommerce-gateway-stripe' ) ), - $source->last4 - ); - } - - switch ( $source->type ) { - case WC_Stripe_Payment_Methods::CARD: - return sprintf( - /* translators: 1) card brand 2) last 4 digits */ - __( 'Via %1$s card ending in %2$s', 'woocommerce-gateway-stripe' ), - ( isset( $source->card->brand ) ? wc_get_credit_card_type_label( $source->card->brand ) : __( 'N/A', 'woocommerce-gateway-stripe' ) ), - $source->card->last4 - ); - case WC_Stripe_Payment_Methods::SEPA_DEBIT: - /* translators: 1) last 4 digits of SEPA Direct Debit */ - return sprintf( __( 'Via SEPA Direct Debit ending in %1$s', 'woocommerce-gateway-stripe' ), $source->sepa_debit->last4 ); - case WC_Stripe_Payment_Methods::CASHAPP_PAY: - /* translators: 1) Cash App Cashtag */ - return sprintf( __( 'Via Cash App Pay (%1$s)', 'woocommerce-gateway-stripe' ), $source->cashapp->cashtag ); - case WC_Stripe_Payment_Methods::LINK: - /* translators: 1) email address associated with the Stripe Link payment method */ - return sprintf( __( 'Via Stripe Link (%1$s)', 'woocommerce-gateway-stripe' ), $source->link->email ); - case WC_Stripe_Payment_Methods::ACH: - return sprintf( - /* translators: 1) account type (checking, savings), 2) last 4 digits of account. */ - __( 'Via %1$s Account ending in %2$s', 'woocommerce-gateway-stripe' ), - ucfirst( $source->us_bank_account->account_type ), - $source->us_bank_account->last4 - ); - case WC_Stripe_Payment_Methods::BECS_DEBIT: - return sprintf( - /* translators: last 4 digits of account. */ - __( 'BECS Direct Debit ending in %s', 'woocommerce-gateway-stripe' ), - $source->au_becs_debit->last4 - ); - case WC_Stripe_Payment_Methods::ACSS_DEBIT: - return sprintf( - /* translators: 1) bank name, 2) last 4 digits of account. */ - __( 'Via %1$s ending in %2$s', 'woocommerce-gateway-stripe' ), - $source->acss_debit->bank_name, - $source->acss_debit->last4 - ); - case WC_Stripe_Payment_Methods::BACS_DEBIT: - /* translators: 1) the Bacs Direct Debit payment method's last 4 numbers */ - return sprintf( __( 'Via Bacs Direct Debit ending in (%1$s)', 'woocommerce-gateway-stripe' ), $source->bacs_debit->last4 ); - case WC_Stripe_Payment_Methods::AMAZON_PAY: - /* translators: 1) the Amazon Pay payment method's email */ - return sprintf( __( 'Via Amazon Pay (%1$s)', 'woocommerce-gateway-stripe' ), $source->billing_details->email ?? '' ); - } - - return __( 'N/A', 'woocommerce-gateway-stripe' ); + return $payment_method_to_display; } /** From b733cd3fee5a460379db08cf7d46c163669becf6 Mon Sep 17 00:00:00 2001 From: Dale du Preez Date: Fri, 8 Aug 2025 17:46:31 +0200 Subject: [PATCH 07/12] Reinstate SEPA logic --- .../class-wc-stripe-payment-tokens.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/includes/payment-tokens/class-wc-stripe-payment-tokens.php b/includes/payment-tokens/class-wc-stripe-payment-tokens.php index efa9198b77..2abfe7d94e 100644 --- a/includes/payment-tokens/class-wc-stripe-payment-tokens.php +++ b/includes/payment-tokens/class-wc-stripe-payment-tokens.php @@ -327,6 +327,20 @@ public function woocommerce_get_customer_upe_payment_tokens( $tokens, $user_id, } } + // Add SEPA if it is disabled and iDEAL or Bancontact are enabled. iDEAL and Bancontact tokens are saved as SEPA tokens. + if ( $gateway->is_sepa_tokens_for_other_methods_enabled() ) { + if ( $gateway->is_oc_enabled() ) { + if ( ! in_array( WC_Stripe_UPE_Payment_Method_Sepa::STRIPE_ID, $active_payment_method_types, true ) ) { + $active_payment_method_types[] = WC_Stripe_UPE_Payment_Method_Sepa::STRIPE_ID; + } + } elseif ( ! $gateway->payment_methods[ WC_Stripe_UPE_Payment_Method_Sepa::STRIPE_ID ]->is_enabled() + && ( $gateway->payment_methods[ WC_Stripe_UPE_Payment_Method_Ideal::STRIPE_ID ]->is_enabled() + || $gateway->payment_methods[ WC_Stripe_UPE_Payment_Method_Bancontact::STRIPE_ID ]->is_enabled() ) ) { + + $active_payment_method_types[] = WC_Stripe_UPE_Payment_Method_Sepa::STRIPE_ID; + } + } + $payment_methods = $customer->get_all_payment_methods( $active_payment_method_types ); $payment_methods = array_merge( ...$payment_methods ); From 341f885cb4e7dce16c3df8485ce6d989b31c3e5d Mon Sep 17 00:00:00 2001 From: Dale du Preez Date: Fri, 8 Aug 2025 20:58:26 +0200 Subject: [PATCH 08/12] Remove unnecessary array_merge() call --- includes/payment-tokens/class-wc-stripe-payment-tokens.php | 1 - 1 file changed, 1 deletion(-) diff --git a/includes/payment-tokens/class-wc-stripe-payment-tokens.php b/includes/payment-tokens/class-wc-stripe-payment-tokens.php index 2abfe7d94e..3cd8b19074 100644 --- a/includes/payment-tokens/class-wc-stripe-payment-tokens.php +++ b/includes/payment-tokens/class-wc-stripe-payment-tokens.php @@ -343,7 +343,6 @@ public function woocommerce_get_customer_upe_payment_tokens( $tokens, $user_id, $payment_methods = $customer->get_all_payment_methods( $active_payment_method_types ); - $payment_methods = array_merge( ...$payment_methods ); $payment_method_ids = array_map( function ( $payment_method ) { return $payment_method->id; From 20ade016d8a426996cdd11b404de384f462d54f7 Mon Sep 17 00:00:00 2001 From: Dale du Preez Date: Fri, 8 Aug 2025 21:03:59 +0200 Subject: [PATCH 09/12] Changelog --- changelog.txt | 1 + readme.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/changelog.txt b/changelog.txt index bcd31e2242..35d49b1fbd 100644 --- a/changelog.txt +++ b/changelog.txt @@ -35,6 +35,7 @@ * Dev - Update SCSS to replace @import with @use and @forward * Fix - Handle missing customer when calling payment_methods API * Dev - Fix some e2e issues: timing, optional flows, and WooCommerce RC support +* Fix - Reduce number of calls to Stripe payment_methods API = 9.7.1 - 2025-07-28 = * Add - Add state mapping for Lithuania in express checkout diff --git a/readme.txt b/readme.txt index 391d6bf62f..c77bf077a6 100644 --- a/readme.txt +++ b/readme.txt @@ -144,5 +144,6 @@ If you get stuck, you can ask for help in the [Plugin Forum](https://wordpress.o * Dev - Update SCSS to replace @import with @use and @forward * Fix - Handle missing customer when calling payment_methods API * Dev - Fix some e2e issues: timing, optional flows, and WooCommerce RC support +* Fix - Reduce number of calls to Stripe payment_methods API [See changelog for full details across versions](https://raw.githubusercontent.com/woocommerce/woocommerce-gateway-stripe/trunk/changelog.txt). From 6c9eb2f3e1d522b0aa70c74a458f4b1e6618f6a4 Mon Sep 17 00:00:00 2001 From: Dale du Preez Date: Fri, 8 Aug 2025 22:05:33 +0200 Subject: [PATCH 10/12] Use Payment Method Configuration to identify active payment methods --- .../class-wc-stripe-payment-tokens.php | 42 ++++--------------- 1 file changed, 9 insertions(+), 33 deletions(-) diff --git a/includes/payment-tokens/class-wc-stripe-payment-tokens.php b/includes/payment-tokens/class-wc-stripe-payment-tokens.php index 3cd8b19074..1aeed475dc 100644 --- a/includes/payment-tokens/class-wc-stripe-payment-tokens.php +++ b/includes/payment-tokens/class-wc-stripe-payment-tokens.php @@ -303,45 +303,21 @@ public function woocommerce_get_customer_upe_payment_tokens( $tokens, $user_id, $payment_methods = []; $reusable_payment_method_types = array_keys( self::UPE_REUSABLE_GATEWAYS_BY_PAYMENT_METHOD ); - $active_payment_method_types = []; - - if ( $gateway->is_oc_enabled() ) { - // For Optimized Checkout, get all available payment method types. - foreach ( $reusable_payment_method_types as $payment_method_type ) { - $payment_method_instance = WC_Stripe_UPE_Payment_Gateway::get_payment_method_instance( $payment_method_type ); - if ( $payment_method_instance ) { - $active_payment_method_types[] = $payment_method_type; - } - } - } else { - foreach ( $reusable_payment_method_types as $payment_method_type ) { - // The payment method type doesn't match the ones we use. Nothing to do here. - if ( ! isset( $gateway->payment_methods[ $payment_method_type ] ) ) { - continue; - } - $payment_method_instance = $gateway->payment_methods[ $payment_method_type ]; - if ( $payment_method_instance->is_enabled() ) { - $active_payment_method_types[] = $payment_method_type; - } - } - } + $enabled_payment_methods = $gateway->get_upe_enabled_payment_method_ids(); + $active_reusable_payment_method_types = array_intersect( $enabled_payment_methods, $reusable_payment_method_types ); // Add SEPA if it is disabled and iDEAL or Bancontact are enabled. iDEAL and Bancontact tokens are saved as SEPA tokens. - if ( $gateway->is_sepa_tokens_for_other_methods_enabled() ) { - if ( $gateway->is_oc_enabled() ) { - if ( ! in_array( WC_Stripe_UPE_Payment_Method_Sepa::STRIPE_ID, $active_payment_method_types, true ) ) { - $active_payment_method_types[] = WC_Stripe_UPE_Payment_Method_Sepa::STRIPE_ID; - } - } elseif ( ! $gateway->payment_methods[ WC_Stripe_UPE_Payment_Method_Sepa::STRIPE_ID ]->is_enabled() - && ( $gateway->payment_methods[ WC_Stripe_UPE_Payment_Method_Ideal::STRIPE_ID ]->is_enabled() - || $gateway->payment_methods[ WC_Stripe_UPE_Payment_Method_Bancontact::STRIPE_ID ]->is_enabled() ) ) { - - $active_payment_method_types[] = WC_Stripe_UPE_Payment_Method_Sepa::STRIPE_ID; + if ( $gateway->is_sepa_tokens_for_other_methods_enabled() && ! in_array( WC_Stripe_UPE_Payment_Method_Sepa::STRIPE_ID, $active_reusable_payment_method_types, true ) ) { + if ( + in_array( WC_Stripe_UPE_Payment_Method_Ideal::STRIPE_ID, $active_reusable_payment_method_types, true ) + || in_array( WC_Stripe_UPE_Payment_Method_Bancontact::STRIPE_ID, $active_reusable_payment_method_types, true ) + ) { + $active_reusable_payment_method_types[] = WC_Stripe_UPE_Payment_Method_Sepa::STRIPE_ID; } } - $payment_methods = $customer->get_all_payment_methods( $active_payment_method_types ); + $payment_methods = $customer->get_all_payment_methods( $active_reusable_payment_method_types ); $payment_method_ids = array_map( function ( $payment_method ) { From b6e1e68f068f0c89d2dad1fada5bd2f82f45d30c Mon Sep 17 00:00:00 2001 From: Dale du Preez Date: Mon, 11 Aug 2025 10:00:50 +0200 Subject: [PATCH 11/12] Add link to Stripe docs for limit argument --- includes/class-wc-stripe-customer.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/includes/class-wc-stripe-customer.php b/includes/class-wc-stripe-customer.php index cb4111c13b..308e2b9351 100644 --- a/includes/class-wc-stripe-customer.php +++ b/includes/class-wc-stripe-customer.php @@ -32,6 +32,8 @@ class WC_Stripe_Customer { /** * The maximum value for the `limit` argument in the Stripe payment_methods API. + * + * @see https://docs.stripe.com/api/payment_methods/customer_list#list_customer_payment_methods-limit */ protected const PAYMENT_METHODS_API_LIMIT = 100; From aaa7ba7e08bd0d2b59bbdfb2cf4e7ac20498d071 Mon Sep 17 00:00:00 2001 From: Dale du Preez Date: Mon, 11 Aug 2025 10:04:25 +0200 Subject: [PATCH 12/12] Use one-line isset() for missing customer checks --- includes/class-wc-stripe-customer.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/includes/class-wc-stripe-customer.php b/includes/class-wc-stripe-customer.php index 308e2b9351..4b5c244c4b 100644 --- a/includes/class-wc-stripe-customer.php +++ b/includes/class-wc-stripe-customer.php @@ -781,8 +781,7 @@ public function get_all_payment_methods( array $payment_method_types = [], int $ if ( ! empty( $response->error ) ) { if ( - isset( $response->error->code ) - && isset( $response->error->param ) + isset( $response->error->param, $response->error->code ) && 'customer' === $response->error->param && 'resource_missing' === $response->error->code ) {