Skip to content

Conversation

@RadoslavGeorgiev
Copy link
Contributor

@RadoslavGeorgiev RadoslavGeorgiev commented Oct 15, 2025

Fixes #10617

Changes proposed in this Pull Request

  • Remove the dynamic keys from WC_Payments_Onboarding_Service in favor of storing the locale (& country code) within the array. This is done under the assumption that those change, but not frequently.
  • As in the original issue, change where customer payment methods are stored to storage that has cleanup / garbage collection mechanisms. I choose to only cache payment methods for logged-in users, and to use a single and easily traceable user meta entry for cache.

The latter is the bigger part of this PR, and replaces the current storage of payment methods as options with dynamic keys to user meta under a single key (_wcpay_payment_methods). The stored value looks like this:

{
	"customer_id": "cus_TEwTgNq3kg25ff",
	"payment_methods_woocommerce_payments": [
		{ "payment_method" },
		{ "payment_method" }
	]
}

Upon follow-up calls, we validate that the customer ID is the same as expected and if so, use the cached value.

Considerations:

  1. There are some APIs (REST & extensibility) having methods that can fetch payment method details, and those will not be cached. However, I am not aware of anything that uses those APIs, we built them for extensibility, but have no feedback of them being used.
  2. There will be no caching for non-logged-in customers. However, there are no calls to fetch stored payment methods for them either.
  3. The gateway ID is included in the cache to support legacy functionality like SEPA, which is a separate gateway. I doubt that's available anymore, but I wanted to preserve backward compatibility.

Testing instructions

  1. Onboard a new merchant, make sure that everything looks well (available payment methods, etc.)
  2. Check out and store your payment method, either as a logged-in or logged-out user.
  3. Repeat a couple of times to have a few payment methods.
  4. Navigate to My Account and delete either payment method. It should disappear as expected.

I recommend connecting to xDebug within get_payment_methods_from_stripe() to confirm whether the cache is hit, busted, or updated properly.


  • Run npm run changelog to add a changelog file, choose patch to leave it empty if the change is not significant. You can add multiple changelog files in one PR by running this command a few times.
  • Covered with tests (or have a good reason not to test in description ☝️)
  • Tested on mobile (or does not apply)

@github-actions
Copy link
Contributor

github-actions bot commented Oct 15, 2025

Test the build

Option 1. Jetpack Beta

  • Install and activate Jetpack Beta.
  • Use this build by searching for PR number 11092 or branch name WOOPMNT-4879 in your-test.site/wp-admin/admin.php?page=jetpack-beta&plugin=woocommerce-payments

Option 2. Jurassic Ninja - available for logged-in A12s

🚀 Launch a JN site with this branch 🚀

ℹ️ Install this Tampermonkey script to get more options.


Build info:

  • Latest commit: 97d1540
  • Build time: 2025-10-17 19:44:03 UTC

Note: the build is updated when a new commit is pushed to this PR.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 15, 2025

Size Change: 0 B

Total Size: 873 kB

ℹ️ View Unchanged
Filename Size
release/woocommerce-payments/assets/css/admin.css 1.45 kB
release/woocommerce-payments/assets/css/admin.rtl.css 1.45 kB
release/woocommerce-payments/assets/css/success.css 1.06 kB
release/woocommerce-payments/assets/css/success.rtl.css 1.06 kB
release/woocommerce-payments/dist/blocks-checkout-rtl.css 2.79 kB
release/woocommerce-payments/dist/blocks-checkout.css 2.79 kB
release/woocommerce-payments/dist/blocks-checkout.js 54.3 kB
release/woocommerce-payments/dist/cart-block-rtl.css 113 B
release/woocommerce-payments/dist/cart-block.css 112 B
release/woocommerce-payments/dist/cart-block.js 16.7 kB
release/woocommerce-payments/dist/cart.js 5.27 kB
release/woocommerce-payments/dist/checkout-rtl.css 1.08 kB
release/woocommerce-payments/dist/checkout.css 1.08 kB
release/woocommerce-payments/dist/checkout.js 34.4 kB
release/woocommerce-payments/dist/express-checkout-rtl.css 367 B
release/woocommerce-payments/dist/express-checkout.css 367 B
release/woocommerce-payments/dist/express-checkout.js 16.9 kB
release/woocommerce-payments/dist/frontend-tracks.js 833 B
release/woocommerce-payments/dist/index-rtl.css 21.2 kB
release/woocommerce-payments/dist/index.css 21.2 kB
release/woocommerce-payments/dist/index.js 153 kB
release/woocommerce-payments/dist/multi-currency-analytics.js 1.08 kB
release/woocommerce-payments/dist/multi-currency-rtl.css 3.82 kB
release/woocommerce-payments/dist/multi-currency-switcher-block.js 18.2 kB
release/woocommerce-payments/dist/multi-currency.css 3.83 kB
release/woocommerce-payments/dist/multi-currency.js 24.6 kB
release/woocommerce-payments/dist/order-rtl.css 740 B
release/woocommerce-payments/dist/order.css 740 B
release/woocommerce-payments/dist/order.js 21.2 kB
release/woocommerce-payments/dist/plugins-page-rtl.css 484 B
release/woocommerce-payments/dist/plugins-page.css 484 B
release/woocommerce-payments/dist/plugins-page.js 2.64 kB
release/woocommerce-payments/dist/product-details-rtl.css 433 B
release/woocommerce-payments/dist/product-details.css 436 B
release/woocommerce-payments/dist/product-details.js 12.3 kB
release/woocommerce-payments/dist/settings-rtl.css 11.8 kB
release/woocommerce-payments/dist/settings.css 11.7 kB
release/woocommerce-payments/dist/settings.js 140 kB
release/woocommerce-payments/dist/subscription-edit-page.js 703 B
release/woocommerce-payments/dist/subscription-product-onboarding-modal-rtl.css 527 B
release/woocommerce-payments/dist/subscription-product-onboarding-modal.css 527 B
release/woocommerce-payments/dist/subscription-product-onboarding-modal.js 1.98 kB
release/woocommerce-payments/dist/subscription-product-onboarding-toast.js 730 B
release/woocommerce-payments/dist/subscriptions-empty-state-rtl.css 120 B
release/woocommerce-payments/dist/subscriptions-empty-state.css 120 B
release/woocommerce-payments/dist/subscriptions-empty-state.js 1.9 kB
release/woocommerce-payments/dist/success.js 6.03 kB
release/woocommerce-payments/dist/tos-rtl.css 235 B
release/woocommerce-payments/dist/tos.css 235 B
release/woocommerce-payments/dist/tos.js 3 kB
release/woocommerce-payments/dist/woopay-direct-checkout.js 5.68 kB
release/woocommerce-payments/dist/woopay-express-button.js 22.8 kB
release/woocommerce-payments/dist/woopay-rtl.css 4.29 kB
release/woocommerce-payments/dist/woopay.css 4.27 kB
release/woocommerce-payments/dist/woopay.js 70.8 kB
release/woocommerce-payments/includes/subscriptions/assets/css/plugin-page.css 625 B
release/woocommerce-payments/includes/subscriptions/assets/js/plugin-page.js 814 B
release/woocommerce-payments/vendor/automattic/jetpack-assets/build/i18n-loader.js 2.46 kB
release/woocommerce-payments/vendor/automattic/jetpack-assets/build/jetpack-script-data.js 957 B
release/woocommerce-payments/vendor/automattic/jetpack-assets/src/js/i18n-loader.js 1.02 kB
release/woocommerce-payments/vendor/automattic/jetpack-assets/src/js/script-data.js 69 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/babel.config.js 163 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/identity-crisis.css 2.47 kB
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/identity-crisis.js 14.3 kB
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/identity-crisis.rtl.css 2.47 kB
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-connection.css 10.1 kB
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-connection.js 29.7 kB
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-connection.rtl.css 10.1 kB
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-admin-create-user.css 198 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-admin-create-user.js 280 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-admin-create-user.rtl.css 198 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-login.css 625 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-login.js 333 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-login.rtl.css 626 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-sso-users.js 417 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-users-connection.js 161 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/tracks-ajax.js 521 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/tracks-callables.js 585 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-admin-create-user.css 215 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-admin-create-user.js 521 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-login.css 721 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-login.js 412 B
release/woocommerce-payments/vendor/automattic/jetpack-connection/src/sso/jetpack-sso-users.js 625 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/about.css 1.04 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/admin-empty-state.css 294 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/admin-order-statuses.css 408 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/admin.css 3.59 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/checkout.css 301 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/modal.css 746 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/view-subscription.css 574 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/wcs-upgrade.css 414 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/admin-pointers.js 543 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/admin.js 9.4 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/jstz.js 6.78 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/jstz.min.js 3.84 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/meta-boxes-coupon.js 545 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/meta-boxes-subscription.js 2.52 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/moment.js 22.2 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/moment.min.js 11.7 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/payment-method-restrictions.js 1.29 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/wcs-meta-boxes-order.js 507 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/payment-methods.js 358 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/single-product.js 428 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/view-subscription.js 1.38 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/wcs-cart.js 782 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/modal.js 1.09 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/wcs-upgrade.js 1.26 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/build/index.css 391 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/build/index.js 3.04 kB

compressed-size-action

@RadoslavGeorgiev RadoslavGeorgiev marked this pull request as ready for review October 15, 2025 14:05
Copy link
Contributor

@leonardola leonardola left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that we only remove cached payment methods when there is an merchant account update, the customer deletes a payment method, the customer adds a new payment method or the customer sets the default payment method.

Do you think it would be beneficial to add a scheduled action to proactively remove stale cached methods? reducing the size of the wp_usermeta table?

@RadoslavGeorgiev
Copy link
Contributor Author

Thanks for the review @leonardola!

My understanding is that we only remove cached payment methods when there is an merchant account update, the customer deletes a payment method, the customer adds a new payment method or the customer sets the default payment method.

Do you think it would be beneficial to add a scheduled action to proactively remove stale cached methods? reducing the size of the wp_usermeta table?

Yes and no. It could potentially be beneficial, but I'd say that the effect would be rather negligible. The way metadata is loaded (altogether at the first load of the object, user in this case), up to a single entry per user does not constitute a significant slow-down in any case.

On the other hand, even with just Card and Link enabled, not having the data handy, slows down the initial checkout page by more than a second for me. I think this is worth the sacrifice.


Regarding tests, I might have decided to over-utilize Cursor at the end of my work day. I'll optimize them first thing tomorrow.

Copy link
Contributor

@leonardola leonardola left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried different methods to trigger the removal of the wp_options data but it never happened. See comments below

if (
is_array( $cached_data )
&& isset( $cached_data[ $cache_key ] )
&& is_array( $cached_data[ $cache_key ] )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I visit /my-account > Payment Methods the $cache_key is set to payment_methods_ instead of payment_methods_woocommerce_payments so the cache is not used for the first page load.

Steps followed:

  • Remove the _wcpay_payment_methods meta from the database
  • Add breakpoint here
  • Visit /my-account > Payment Methods
  • First time the function is called the cache does not exist
  • Second time it does but uses the wrong key so it has to recreate the cache again
  • Same happens when I change the default payment method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch! We are using an undocumented filter from core and assuming that the gateway ID is always a string, even though that is not the case.

Seems like this will require me to rewrite the get_payment_methods_from_stripe method and particularly the format of the cached data. Right now, it follows this format:

{
	"customer_id": "cus_XYZ",
	"payment_methods_{gateway_id}": [
		// payment methods here
	]
}

However, with the gateway ID possibly missing, I can't use it. I'll switch back to using the $type from $retrievable_payment_method_types instead of the gateway ID. The data would look pretty similar with {type} instead of {gateway_id} in the key, but instead of checking the cache and returning it at once, I'll always go through the foreach loop for each payment method and check the cache there.

I'll implement this on Monday, but I'm curious what you think in the meantime.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure which option is the fastest but caching something with the wrong name is prone to other problems. Let's try the loop version.

* Clear all cached payment methods.
* Used when account data is updated and all payment method caches need to be cleared.
*/
public function clear_all_cached_payment_methods() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the merchant account via stripe dashboard. The account.updated event got to the transact server but this function was never called. What's the proper way to trigger this flow?

Steps taken:

  • Visit Stripe account dashboard
  • Make sure your transact server is listening to Stripe webhooks
  • Add a breakpoint here
  • Update the Personal details
  • Notice the function is never called

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The account.updated webhook for the client seems not to be a mirror image of Stripe's account.updated, nor sent at the same time.

I assume that you were expecting this to happen through the changed webhook processing service. However, after this PR (very recent), the account.updated seems to be sent upon specific conditions, ex. hitting some account-based limit.

I added 97d1540 to make sure that we use the new account.deleted webhook to clean up payment methods.

Now, you should be able to hit the breakpoint shortly after starting the account reset process, even with a test drive account.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the account.deleted webhook being triggered after reseting and re-onboarding the test account and it didn't hit the breakpoint either.

Screenshot 2025-10-22 at 15 58 12

@vbelolapotkov vbelolapotkov removed their request for review October 21, 2025 11:15
Copy link
Contributor

@leonardola leonardola left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall the changes look good. Left a few comments about backward compatibility and the old options still not being deleted

* @return ?array Fields data, or NULL if failed to retrieve.
*/
public function get_fields_data( string $locale = '', bool $force_refresh = false ): ?array {
public function get_fields_data( string $locale = '' ): ?array {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is public method. I'm not sure about the backward compatibility policy for WooPayments but any other 3rd party plugin using this method will break after this change

$payment_methods = $this->payments_api_client->get_recommended_payment_methods( $country_code, $locale );

// Indicate that the cached value is specific for the given locale and country code.
return [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the returned data also breaks backward compatibility

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stop using options for caching customer data

3 participants