Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
f54a2d6
Remove unused force refresh flags
RadoslavGeorgiev Oct 15, 2025
229a931
For recommended payment methods & onboarding data, store country code…
RadoslavGeorgiev Oct 15, 2025
2b49bf7
Changelog entry
RadoslavGeorgiev Oct 15, 2025
409be11
Remove the onboarding and payment method keys as prefixes
RadoslavGeorgiev Oct 15, 2025
a00144d
Remove a left-over key for payment processing factors
RadoslavGeorgiev Oct 15, 2025
9719757
Transfer functionality to the token service, delete the rest
RadoslavGeorgiev Oct 15, 2025
ad6c994
Remove the webhook processing service's dependency
RadoslavGeorgiev Oct 15, 2025
3058d5e
Add user-based caching to the token service, fix the methods that cle…
RadoslavGeorgiev Oct 15, 2025
05d27d4
Fix most existing tests
RadoslavGeorgiev Oct 15, 2025
e272a5f
Include gateway IDs in payment method cache. Remove calls to delete f…
RadoslavGeorgiev Oct 15, 2025
25998d5
Add tests for the new methods of the token service
RadoslavGeorgiev Oct 15, 2025
1b735b9
Clean up properly after token service tests
RadoslavGeorgiev Oct 15, 2025
374dd97
Merge branch 'develop' into WOOPMNT-4879
RadoslavGeorgiev Oct 15, 2025
53fea0a
Remove todo comment, caching is now in place
RadoslavGeorgiev Oct 15, 2025
6eb011e
Remove redundant conditions from tests, fix the call for internal met…
RadoslavGeorgiev Oct 16, 2025
8c6cc6f
Update the changelog entry
RadoslavGeorgiev Oct 16, 2025
c58952a
Move a never() mock before the method call
RadoslavGeorgiev Oct 16, 2025
4dcd3e0
Combine tests for network cards
RadoslavGeorgiev Oct 16, 2025
97d1540
Clear cached payment methods upon the account.deleted webhook
RadoslavGeorgiev Oct 17, 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
4 changes: 4 additions & 0 deletions changelog/WOOPMNT-4879
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: dev

Stop using dynamic keys for the database cache and move cached payment methods to user meta.
102 changes: 22 additions & 80 deletions includes/class-database-cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,26 +19,10 @@ class Database_Cache implements MultiCurrencyCacheInterface {
const ACCOUNT_KEY = 'wcpay_account_data';
const ONBOARDING_FIELDS_DATA_KEY = 'wcpay_onboarding_fields_data';
const BUSINESS_TYPES_KEY = 'wcpay_business_types_data';
const PAYMENT_PROCESS_FACTORS_KEY = 'wcpay_payment_process_factors';
const FRAUD_SERVICES_KEY = 'wcpay_fraud_services_data';
const RECOMMENDED_PAYMENT_METHODS = 'wcpay_recommended_payment_methods';
const ADDRESS_AUTOCOMPLETE_JWT_KEY = 'wcpay_address_autocomplete_jwt';

/**
* Refresh during AJAX calls is avoided, but white-listing
* a key here will allow the refresh to happen.
*
* @var string[]
*/
const AJAX_ALLOWED_KEYS = [
self::PAYMENT_PROCESS_FACTORS_KEY,
];

/**
* Payment methods cache key prefix. Used in conjunction with the customer_id to cache a customer's payment methods.
*/
const PAYMENT_METHODS_KEY_PREFIX = 'wcpay_pm_';

/**
* Dispute status counts cache key.
*
Expand Down Expand Up @@ -86,6 +70,26 @@ class Database_Cache implements MultiCurrencyCacheInterface {
*/
const TRACKING_INFO_KEY = 'wcpay_tracking_info_cache';

/**
* All cache keys.
*
* @var string[]
*/
const ALL_KEYS = [
self::ACCOUNT_KEY,
self::ONBOARDING_FIELDS_DATA_KEY,
self::BUSINESS_TYPES_KEY,
self::FRAUD_SERVICES_KEY,
self::RECOMMENDED_PAYMENT_METHODS,
self::DISPUTE_STATUS_COUNTS_KEY,
self::DISPUTE_STATUS_COUNTS_KEY_TEST_MODE,
self::ACTIVE_DISPUTES_KEY,
self::AUTHORIZATION_SUMMARY_KEY,
self::AUTHORIZATION_SUMMARY_KEY_TEST_MODE,
self::CONNECT_INCENTIVE_KEY,
self::TRACKING_INFO_KEY,
];

/**
* Refresh disabled flag, controlling the behaviour of the get_or_add function.
*
Expand Down Expand Up @@ -214,72 +218,13 @@ public function delete( string $key ) {
}
}

/**
* Deletes all cache entries that are keyed with a certain prefix.
*
* This is useful when you use dynamic cache keys.
*
* Note: Only key prefixes with known, static prefixes are allowed, for protection purposes.
*
* @param string $key_prefix The cache key prefix.
*
* @return void
*/
public function delete_by_prefix( string $key_prefix ) {
// Protection against accidentally deleting all options or options that are not related to WCPay caching.
// Feel free to update this statement as more prefix cache keys are used.
$allowed_base_prefixes = [
self::PAYMENT_METHODS_KEY_PREFIX,
self::ONBOARDING_FIELDS_DATA_KEY,
self::RECOMMENDED_PAYMENT_METHODS,
];
$is_allowed = false;
foreach ( $allowed_base_prefixes as $allowed_base_prefix ) {
if ( strncmp( $key_prefix, $allowed_base_prefix, strlen( $allowed_base_prefix ) ) === 0 ) {
$is_allowed = true;
break;
}
}
if ( ! $is_allowed ) {
return; // Maybe throw exception here...
}

global $wpdb;

$options = $wpdb->get_results( $wpdb->prepare( "SELECT option_name FROM $wpdb->options WHERE option_name LIKE %s", $key_prefix . '%' ) );
foreach ( $options as $option ) {
$this->delete( $option->option_name );
}
}

/**
* Delete all known cache entries.
*/
public function delete_all() {
$keys = [
self::ACCOUNT_KEY,
self::ONBOARDING_FIELDS_DATA_KEY,
self::BUSINESS_TYPES_KEY,
self::PAYMENT_PROCESS_FACTORS_KEY,
self::FRAUD_SERVICES_KEY,
self::RECOMMENDED_PAYMENT_METHODS,
self::DISPUTE_STATUS_COUNTS_KEY,
self::DISPUTE_STATUS_COUNTS_KEY_TEST_MODE,
self::ACTIVE_DISPUTES_KEY,
self::AUTHORIZATION_SUMMARY_KEY,
self::AUTHORIZATION_SUMMARY_KEY_TEST_MODE,
self::CONNECT_INCENTIVE_KEY,
self::TRACKING_INFO_KEY,
];

foreach ( $keys as $key ) {
foreach ( self::ALL_KEYS as $key ) {
$this->delete( $key );
}

// Delete prefix-based keys.
$this->delete_by_prefix( self::PAYMENT_METHODS_KEY_PREFIX );
$this->delete_by_prefix( self::ONBOARDING_FIELDS_DATA_KEY ); // It can be prefixed with the locale.
$this->delete_by_prefix( self::RECOMMENDED_PAYMENT_METHODS ); // It can be prefixed with the locale.
}

/**
Expand Down Expand Up @@ -325,7 +270,7 @@ private function should_refresh_cache( string $key, $cache_contents, callable $v
// Do not refresh if doing ajax or the refresh has been disabled (running an AS job).
if (
defined( 'DOING_CRON' )
|| ( wp_doing_ajax() && ! in_array( $key, self::AJAX_ALLOWED_KEYS, true ) )
|| ( wp_doing_ajax() )
|| $this->refresh_disabled ) {
return false;
}
Expand Down Expand Up @@ -484,9 +429,6 @@ private function get_ttl( string $key, array $cache_contents ): int {
// If no orders, cache for an hour to check again soon.
$ttl = $cache_contents['data'] ? DAY_IN_SECONDS * 90 : HOUR_IN_SECONDS;
break;
case self::PAYMENT_PROCESS_FACTORS_KEY:
$ttl = 2 * HOUR_IN_SECONDS;
break;
case self::TRACKING_INFO_KEY:
$ttl = $cache_contents['errored'] ? 2 * MINUTE_IN_SECONDS : MONTH_IN_SECONDS;
break;
Expand Down
2 changes: 1 addition & 1 deletion includes/class-wc-payment-gateway-wcpay.php
Original file line number Diff line number Diff line change
Expand Up @@ -1872,7 +1872,7 @@ public function maybe_process_upe_redirect() {
if ( $this->is_setup_intent_success_creation_redirection() ) {
wc_add_notice( __( 'Payment method successfully added.', 'woocommerce-payments' ) );
$user = wp_get_current_user();
$this->customer_service->clear_cached_payment_methods_for_user( $user->ID );
$this->token_service->clear_cached_payment_methods_for_user( $user->ID );
}
return;
}
Expand Down
54 changes: 1 addition & 53 deletions includes/class-wc-payments-customer-service.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* @package WooCommerce\Payments
*/

use WCPay\Database_Cache;
use WCPay\Exceptions\API_Exception;
use WCPay\Logger;
use WCPay\Constants\Payment_Method;
Expand Down Expand Up @@ -57,13 +56,6 @@ class WC_Payments_Customer_Service {
*/
private $account;

/**
* Database_Cache instance to get information about the account
*
* @var Database_Cache
*/
private $database_cache;

/**
* WC_Payments_Session_Service instance for working with session information
*
Expand All @@ -83,20 +75,17 @@ class WC_Payments_Customer_Service {
*
* @param WC_Payments_API_Client $payments_api_client Payments API client.
* @param WC_Payments_Account $account WC_Payments_Account instance.
* @param Database_Cache $database_cache Database_Cache instance.
* @param WC_Payments_Session_Service $session_service Session Service class instance.
* @param WC_Payments_Order_Service $order_service Order Service class instance.
*/
public function __construct(
WC_Payments_API_Client $payments_api_client,
WC_Payments_Account $account,
Database_Cache $database_cache,
WC_Payments_Session_Service $session_service,
WC_Payments_Order_Service $order_service
) {
$this->payments_api_client = $payments_api_client;
$this->account = $account;
$this->database_cache = $database_cache;
$this->session_service = $session_service;
$this->order_service = $order_service;
}
Expand Down Expand Up @@ -262,23 +251,8 @@ public function get_payment_methods_for_customer( $customer_id, $type = 'card' )
return [];
}

$cache_payment_methods = ! WC_Payments::is_network_saved_cards_enabled();
$cache_key = Database_Cache::PAYMENT_METHODS_KEY_PREFIX . $customer_id . '_' . $type;

if ( $cache_payment_methods ) {
$payment_methods = $this->database_cache->get( $cache_key );
if ( is_array( $payment_methods ) ) {
return $payment_methods;
}
}

try {
$payment_methods = $this->payments_api_client->get_payment_methods( $customer_id, $type )['data'];
if ( $cache_payment_methods ) {
$this->database_cache->add( $cache_key, $payment_methods );
}
return $payment_methods;

return $this->payments_api_client->get_payment_methods( $customer_id, $type )['data'];
} catch ( API_Exception $e ) {
// If we failed to find the payment methods, we can simply return empty payment methods as this customer
// will be recreated when the user successfully adds a payment method.
Expand Down Expand Up @@ -310,23 +284,6 @@ public function update_payment_method_with_billing_details_from_order( $payment_
}
}

/**
* Clear payment methods cache for a user.
*
* @param int $user_id WC user ID.
*/
public function clear_cached_payment_methods_for_user( $user_id ) {
if ( WC_Payments::is_network_saved_cards_enabled() ) {
return; // No need to do anything, payment methods will never be cached in this case.
}

$retrievable_payment_method_types = [ Payment_Method::CARD, Payment_Method::LINK, Payment_Method::SEPA ];
$customer_id = $this->get_customer_id_by_user_id( $user_id );
foreach ( $retrievable_payment_method_types as $type ) {
$this->database_cache->delete( Database_Cache::PAYMENT_METHODS_KEY_PREFIX . $customer_id . '_' . $type );
}
}

/**
* Given a WC_Order or WC_Customer, returns an array representing a Stripe customer object.
* At least one parameter has to not be null.
Expand Down Expand Up @@ -387,15 +344,6 @@ public static function map_customer_data( ?WC_Order $wc_order = null, ?WC_Custom
return $data;
}

/**
* Delete all saved payment methods that are stored inside the database cache driver.
*
* @return void
*/
public function delete_cached_payment_methods() {
$this->database_cache->delete_by_prefix( Database_Cache::PAYMENT_METHODS_KEY_PREFIX );
}

/**
* Recreates the customer for this user.
*
Expand Down
Loading
Loading