Skip to content

Commit e0d8375

Browse files
authored
Add method to fetch and cache all saved payment methods for a customer (#4567)
* Basic get_all_payment_methods() implementation * Ensure we clear the new _all payment methods cache * Always send expand flags for SEPA * Refactor logic to get customer UPE tokens * Refactor WC_Stripe_Subscriptions_Trait->maybe_render_subscription_payment_method() to use get_all_payment_methods() * Revert "Refactor WC_Stripe_Subscriptions_Trait->maybe_render_subscription_payment_method() to use get_all_payment_methods()" This reverts commit c622d76. * Reinstate SEPA logic * Remove unnecessary array_merge() call * Changelog * Use Payment Method Configuration to identify active payment methods * Add link to Stripe docs for limit argument * Use one-line isset() for missing customer checks
1 parent 53233bc commit e0d8375

File tree

4 files changed

+110
-33
lines changed

4 files changed

+110
-33
lines changed

changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
* Dev - Update SCSS to replace @import with @use and @forward
3838
* Fix - Handle missing customer when calling payment_methods API
3939
* Dev - Fix some e2e issues: timing, optional flows, and WooCommerce RC support
40+
* Fix - Reduce number of calls to Stripe payment_methods API
4041

4142
= 9.7.1 - 2025-07-28 =
4243
* Add - Add state mapping for Lithuania in express checkout

includes/class-wc-stripe-customer.php

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ class WC_Stripe_Customer {
3030
WC_Stripe_UPE_Payment_Method_Becs_Debit::STRIPE_ID,
3131
];
3232

33+
/**
34+
* The maximum value for the `limit` argument in the Stripe payment_methods API.
35+
*
36+
* @see https://docs.stripe.com/api/payment_methods/customer_list#list_customer_payment_methods-limit
37+
*/
38+
protected const PAYMENT_METHODS_API_LIMIT = 100;
39+
3340
/**
3441
* Stripe customer ID
3542
*
@@ -712,7 +719,7 @@ public function get_payment_methods( $payment_method_type ) {
712719
[
713720
'customer' => $this->get_id(),
714721
'type' => $payment_method_type,
715-
'limit' => 100, // Maximum allowed value.
722+
'limit' => self::PAYMENT_METHODS_API_LIMIT,
716723
],
717724
'payment_methods' . $params,
718725
'GET'
@@ -741,6 +748,93 @@ public function get_payment_methods( $payment_method_type ) {
741748
return empty( $payment_methods ) ? [] : $payment_methods;
742749
}
743750

751+
/**
752+
* Get all payment methods for a customer.
753+
*
754+
* @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.
755+
* @param int $limit The maximum number of payment methods to return. If the value is -1, no limit is applied.
756+
* @return array
757+
*/
758+
public function get_all_payment_methods( array $payment_method_types = [], int $limit = -1 ) {
759+
if ( ! $this->get_id() ) {
760+
return [];
761+
}
762+
763+
$cache_key = self::PAYMENT_METHODS_TRANSIENT_KEY . '__all_' . $this->get_id();
764+
$all_payment_methods = get_transient( $cache_key );
765+
766+
if ( false === $all_payment_methods || ! is_array( $all_payment_methods ) ) {
767+
$all_payment_methods = [];
768+
$last_payment_method_id = null;
769+
770+
do {
771+
$request_params = [
772+
'customer' => $this->get_id(),
773+
'limit' => self::PAYMENT_METHODS_API_LIMIT,
774+
];
775+
776+
if ( $last_payment_method_id ) {
777+
$request_params['starting_after'] = $last_payment_method_id;
778+
}
779+
780+
$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' );
781+
782+
if ( ! empty( $response->error ) ) {
783+
if (
784+
isset( $response->error->param, $response->error->code )
785+
&& 'customer' === $response->error->param
786+
&& 'resource_missing' === $response->error->code
787+
) {
788+
// If the customer doesn't exist, cache an empty array.
789+
set_transient( $cache_key, [], DAY_IN_SECONDS );
790+
}
791+
return [];
792+
}
793+
794+
if ( ! is_array( $response->data ) || [] === $response->data ) {
795+
break;
796+
}
797+
798+
$all_payment_methods = array_merge( $all_payment_methods, $response->data );
799+
800+
// Reset the last payment method ID so we can paginate correctly.
801+
$last_payment_method_id = null;
802+
if ( isset( $response->has_more ) && $response->has_more ) {
803+
$last_payment_method = end( $response->data );
804+
805+
if ( $last_payment_method && ! empty( $last_payment_method->id ) ) {
806+
$last_payment_method_id = $last_payment_method->id;
807+
}
808+
}
809+
} while ( null !== $last_payment_method_id );
810+
811+
// Always cache the result without any filters applied.
812+
set_transient( $cache_key, $all_payment_methods, DAY_IN_SECONDS );
813+
}
814+
815+
// If there are no payment methods, no need to apply any filters below.
816+
if ( [] === $all_payment_methods ) {
817+
return $all_payment_methods;
818+
}
819+
820+
// Only apply the limit and type filters after fetching and caching all payment methods.
821+
$filtered_payment_methods = $all_payment_methods;
822+
if ( [] !== $payment_method_types ) {
823+
$filtered_payment_methods = array_filter(
824+
$filtered_payment_methods,
825+
function ( $payment_method ) use ( $payment_method_types ) {
826+
return in_array( $payment_method->type, $payment_method_types, true );
827+
}
828+
);
829+
}
830+
831+
if ( $limit > 0 ) {
832+
return array_slice( $filtered_payment_methods, 0, $limit );
833+
}
834+
835+
return $filtered_payment_methods;
836+
}
837+
744838
/**
745839
* Delete a source from stripe.
746840
*
@@ -846,6 +940,7 @@ public function clear_cache( $payment_method_id = null ) {
846940
foreach ( self::STRIPE_PAYMENT_METHODS as $payment_method_type ) {
847941
delete_transient( self::PAYMENT_METHODS_TRANSIENT_KEY . $payment_method_type . $this->get_id() );
848942
}
943+
delete_transient( self::PAYMENT_METHODS_TRANSIENT_KEY . '__all_' . $this->get_id() );
849944
// Clear cache for the specific payment method if provided.
850945
if ( $payment_method_id ) {
851946
WC_Stripe_Database_Cache::delete( 'payment_method_for_source_' . $payment_method_id );

includes/payment-tokens/class-wc-stripe-payment-tokens.php

Lines changed: 12 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -301,44 +301,24 @@ public function woocommerce_get_customer_upe_payment_tokens( $tokens, $user_id,
301301

302302
// Retrieve the payment methods for the enabled reusable gateways.
303303
$payment_methods = [];
304-
if ( $gateway->is_oc_enabled() ) {
305-
// For OC, get all available payment method types
306-
foreach ( self::UPE_REUSABLE_GATEWAYS_BY_PAYMENT_METHOD as $payment_method_type => $reausable_gateway_id ) {
307-
$payment_method_instance = WC_Stripe_UPE_Payment_Gateway::get_payment_method_instance( $payment_method_type );
308-
if ( $payment_method_instance ) {
309-
$retrieved_methods = $customer->get_payment_methods( $payment_method_type );
310-
if ( ! empty( $retrieved_methods ) ) {
311-
$payment_methods[] = $retrieved_methods;
312-
}
313-
}
314-
}
315-
} else {
316-
foreach ( self::UPE_REUSABLE_GATEWAYS_BY_PAYMENT_METHOD as $payment_method_type => $reausable_gateway_id ) {
317-
// The payment method type doesn't match the ones we use. Nothing to do here.
318-
if ( ! isset( $gateway->payment_methods[ $payment_method_type ] ) ) {
319-
continue;
320-
}
321304

322-
$payment_method_instance = $gateway->payment_methods[ $payment_method_type ];
323-
if ( $payment_method_instance->is_enabled() ) {
324-
$payment_methods[] = $customer->get_payment_methods( $payment_method_type );
325-
}
326-
}
327-
}
305+
$reusable_payment_method_types = array_keys( self::UPE_REUSABLE_GATEWAYS_BY_PAYMENT_METHOD );
306+
307+
$enabled_payment_methods = $gateway->get_upe_enabled_payment_method_ids();
308+
$active_reusable_payment_method_types = array_intersect( $enabled_payment_methods, $reusable_payment_method_types );
328309

329310
// Add SEPA if it is disabled and iDEAL or Bancontact are enabled. iDEAL and Bancontact tokens are saved as SEPA tokens.
330-
if ( $gateway->is_sepa_tokens_for_other_methods_enabled() ) {
331-
if ( $gateway->is_oc_enabled() ) {
332-
$payment_methods[] = $customer->get_payment_methods( WC_Stripe_UPE_Payment_Method_Sepa::STRIPE_ID );
333-
} elseif ( ! $gateway->payment_methods[ WC_Stripe_UPE_Payment_Method_Sepa::STRIPE_ID ]->is_enabled()
334-
&& ( $gateway->payment_methods[ WC_Stripe_UPE_Payment_Method_Ideal::STRIPE_ID ]->is_enabled()
335-
|| $gateway->payment_methods[ WC_Stripe_UPE_Payment_Method_Bancontact::STRIPE_ID ]->is_enabled() ) ) {
336-
337-
$payment_methods[] = $customer->get_payment_methods( WC_Stripe_UPE_Payment_Method_Sepa::STRIPE_ID );
311+
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 ) ) {
312+
if (
313+
in_array( WC_Stripe_UPE_Payment_Method_Ideal::STRIPE_ID, $active_reusable_payment_method_types, true )
314+
|| in_array( WC_Stripe_UPE_Payment_Method_Bancontact::STRIPE_ID, $active_reusable_payment_method_types, true )
315+
) {
316+
$active_reusable_payment_method_types[] = WC_Stripe_UPE_Payment_Method_Sepa::STRIPE_ID;
338317
}
339318
}
340319

341-
$payment_methods = array_merge( ...$payment_methods );
320+
$payment_methods = $customer->get_all_payment_methods( $active_reusable_payment_method_types );
321+
342322
$payment_method_ids = array_map(
343323
function ( $payment_method ) {
344324
return $payment_method->id;

readme.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,5 +146,6 @@ If you get stuck, you can ask for help in the [Plugin Forum](https://wordpress.o
146146
* Dev - Update SCSS to replace @import with @use and @forward
147147
* Fix - Handle missing customer when calling payment_methods API
148148
* Dev - Fix some e2e issues: timing, optional flows, and WooCommerce RC support
149+
* Fix - Reduce number of calls to Stripe payment_methods API
149150

150151
[See changelog for full details across versions](https://raw.githubusercontent.com/woocommerce/woocommerce-gateway-stripe/trunk/changelog.txt).

0 commit comments

Comments
 (0)