Skip to content
Draft
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
91d638b
Basic get_all_payment_methods() implementation
daledupreez Aug 7, 2025
35837db
Ensure we clear the new _all payment methods cache
daledupreez Aug 8, 2025
9cfccaf
Always send expand flags for SEPA
daledupreez Aug 8, 2025
68bee81
Refactor logic to get customer UPE tokens
daledupreez Aug 8, 2025
c622d76
Refactor WC_Stripe_Subscriptions_Trait->maybe_render_subscription_pay…
daledupreez Aug 8, 2025
2e20c28
Revert "Refactor WC_Stripe_Subscriptions_Trait->maybe_render_subscrip…
daledupreez Aug 8, 2025
b733cd3
Reinstate SEPA logic
daledupreez Aug 8, 2025
243d766
Merge branch 'develop' into refactor/fetching-all-customer-payment-me…
daledupreez Aug 8, 2025
341f885
Remove unnecessary array_merge() call
daledupreez Aug 8, 2025
20ade01
Changelog
daledupreez Aug 8, 2025
6c9eb2f
Use Payment Method Configuration to identify active payment methods
daledupreez Aug 8, 2025
0b12464
Merge branch 'develop' into refactor/fetching-all-customer-payment-me…
daledupreez Aug 11, 2025
b6e1e68
Add link to Stripe docs for limit argument
daledupreez Aug 11, 2025
aaa7ba7
Use one-line isset() for missing customer checks
daledupreez Aug 11, 2025
ef842a2
Use database cache for all payment methods cache
daledupreez Aug 11, 2025
247b333
Changelog
daledupreez Aug 11, 2025
4dc2b3c
Remove double-period
daledupreez Aug 11, 2025
6028240
Merge branch 'develop' into add/database-caching-for-all-payment-methods
daledupreez Aug 13, 2025
5e314fa
Merge branch 'develop' into add/database-caching-for-all-payment-methods
daledupreez Aug 18, 2025
d0c884a
Merge branch 'develop' into add/database-caching-for-all-payment-methods
daledupreez Aug 29, 2025
9032012
Fix merge of readme and changelog
daledupreez Aug 29, 2025
f79da6b
Refactor customer cache cleanup
daledupreez Aug 29, 2025
d7e4785
Flush customer data after attaching a payment method
daledupreez Aug 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
* 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
* Tweak - Improve how we cache saved payment methods for customers

= 9.7.1 - 2025-07-28 =
* Add - Add state mapping for Lithuania in express checkout
Expand Down
102 changes: 101 additions & 1 deletion includes/class-wc-stripe-customer.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ class WC_Stripe_Customer {
*/
const PAYMENT_METHODS_TRANSIENT_KEY = 'stripe_payment_methods_';

/**
* Cache prefix for all saved payment methods per customer..
*/
protected const ALL_PAYMENT_METHODS_CACHE_PREFIX = 'all_payment_methods_';

/**
* Queryable Stripe payment method types.
*/
Expand All @@ -30,6 +35,13 @@ 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.
*
* @see https://docs.stripe.com/api/payment_methods/customer_list#list_customer_payment_methods-limit
*/
protected const PAYMENT_METHODS_API_LIMIT = 100;

/**
* Stripe customer ID
*
Expand Down Expand Up @@ -712,7 +724,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'
Expand Down Expand Up @@ -741,6 +753,93 @@ 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::ALL_PAYMENT_METHODS_CACHE_PREFIX . $this->get_id();
$all_payment_methods = WC_Stripe_Database_Cache::get( $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;
}

$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 (
isset( $response->error->param, $response->error->code )
&& 'customer' === $response->error->param
&& 'resource_missing' === $response->error->code
) {
// If the customer doesn't exist, cache an empty array.
WC_Stripe_Database_Cache::set( $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.
WC_Stripe_Database_Cache::set( $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.
*
Expand Down Expand Up @@ -846,6 +945,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() );
}
WC_Stripe_Database_Cache::delete( self::ALL_PAYMENT_METHODS_CACHE_PREFIX . $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 );
Expand Down
44 changes: 12 additions & 32 deletions includes/payment-tokens/class-wc-stripe-payment-tokens.php
Original file line number Diff line number Diff line change
Expand Up @@ -301,44 +301,24 @@ public function woocommerce_get_customer_upe_payment_tokens( $tokens, $user_id,

// Retrieve the payment methods for the enabled reusable gateways.
$payment_methods = [];
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 ) {
$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;
}
}
}
} else {
foreach ( self::UPE_REUSABLE_GATEWAYS_BY_PAYMENT_METHOD as $payment_method_type => $reausable_gateway_id ) {
// 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() ) {
$payment_methods[] = $customer->get_payment_methods( $payment_method_type );
}
}
}
$reusable_payment_method_types = array_keys( self::UPE_REUSABLE_GATEWAYS_BY_PAYMENT_METHOD );

$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() ) {
$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 );
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 = array_merge( ...$payment_methods );
$payment_methods = $customer->get_all_payment_methods( $active_reusable_payment_method_types );

$payment_method_ids = array_map(
function ( $payment_method ) {
return $payment_method->id;
Expand Down
2 changes: 2 additions & 0 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -145,5 +145,7 @@ 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
* Tweak - Improve how we cache saved payment methods for customers

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