Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC - Centralized payment method definitions #10217

Draft
wants to merge 47 commits into
base: develop
Choose a base branch
from

Conversation

brettshumaker
Copy link
Contributor

@brettshumaker brettshumaker commented Jan 22, 2025

This PR introduces a centralized system for defining payment methods in WooCommerce Payments, significantly reducing the complexity and maintenance burden of implementing new payment methods. Previously, adding or modifying payment methods required changes across multiple files with duplicated logic. This new architecture provides a single source of truth for payment method definitions.

This PR was born out of a POC for centralizing payment method definitions to reduce the amount of changes that need to be made while implementing (or removing support for) a new payment method. It's resulting from #10098

Once I've added the appropriate unit tests, I'll mark the PR as ready but, given it's size I thought it'd be good to get an initial review.


@botwoo
Copy link
Collaborator

botwoo commented Jan 22, 2025

Test the build

Option 1. Jetpack Beta

  • Install and activate Jetpack Beta.
  • Use this build by searching for PR number 10217 or branch name poc/centralized-payment-method-definitions 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: 7be446b
  • Build time: 2025-02-19 21:55:44 UTC

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

Copy link
Contributor

github-actions bot commented Jan 22, 2025

Size Change: +713 kB (+55%) 🆘

Total Size: 2 MB

Filename Size Change
release/woocommerce-payments/dist/index.js 411 kB +177 kB (+76%) 🆘
release/woocommerce-payments/dist/multi-currency.js 237 kB +178 kB (+301%) 🆘
release/woocommerce-payments/dist/payment-gateways.js 218 kB +178 kB (+443%) 🆘
release/woocommerce-payments/dist/settings.js 402 kB +178 kB (+80%) 🆘
release/woocommerce-payments/includes/payment-methods/Configs/scripts/generate-payment-method-types.js 823 B +823 B (new file) 🆕
ℹ️ View Unchanged
Filename Size
release/woocommerce-payments/assets/css/admin.css 1.4 kB
release/woocommerce-payments/assets/css/admin.rtl.css 1.4 kB
release/woocommerce-payments/assets/css/success.css 189 B
release/woocommerce-payments/assets/css/success.rtl.css 190 B
release/woocommerce-payments/dist/blocks-checkout-rtl.css 2.67 kB
release/woocommerce-payments/dist/blocks-checkout.css 2.67 kB
release/woocommerce-payments/dist/blocks-checkout.js 55.3 kB
release/woocommerce-payments/dist/cart-block.js 17.2 kB
release/woocommerce-payments/dist/cart.js 5.73 kB
release/woocommerce-payments/dist/checkout-rtl.css 1.28 kB
release/woocommerce-payments/dist/checkout.css 1.28 kB
release/woocommerce-payments/dist/checkout.js 34.6 kB
release/woocommerce-payments/dist/express-checkout-rtl.css 236 B
release/woocommerce-payments/dist/express-checkout.css 236 B
release/woocommerce-payments/dist/express-checkout.js 15.7 kB
release/woocommerce-payments/dist/frontend-tracks.js 854 B
release/woocommerce-payments/dist/index-rtl.css 35.5 kB
release/woocommerce-payments/dist/index.css 35.6 kB
release/woocommerce-payments/dist/multi-currency-analytics.js 1.08 kB
release/woocommerce-payments/dist/multi-currency-rtl.css 4.29 kB
release/woocommerce-payments/dist/multi-currency-switcher-block.js 61.1 kB
release/woocommerce-payments/dist/multi-currency.css 4.29 kB
release/woocommerce-payments/dist/order-rtl.css 740 B
release/woocommerce-payments/dist/order.css 740 B
release/woocommerce-payments/dist/order.js 42.6 kB
release/woocommerce-payments/dist/payment-gateways-rtl.css 1.34 kB
release/woocommerce-payments/dist/payment-gateways.css 1.34 kB
release/woocommerce-payments/dist/plugins-page-rtl.css 386 B
release/woocommerce-payments/dist/plugins-page.css 386 B
release/woocommerce-payments/dist/plugins-page.js 20.1 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.5 kB
release/woocommerce-payments/dist/settings-rtl.css 11.4 kB
release/woocommerce-payments/dist/settings.css 11.4 kB
release/woocommerce-payments/dist/subscription-edit-page.js 703 B
release/woocommerce-payments/dist/subscription-product-onboarding-modal-rtl.css 524 B
release/woocommerce-payments/dist/subscription-product-onboarding-modal.css 524 B
release/woocommerce-payments/dist/subscription-product-onboarding-modal.js 20.2 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 19.3 kB
release/woocommerce-payments/dist/tokenized-express-checkout-rtl.css 236 B
release/woocommerce-payments/dist/tokenized-express-checkout.css 236 B
release/woocommerce-payments/dist/tokenized-express-checkout.js 16.7 kB
release/woocommerce-payments/dist/tos-rtl.css 235 B
release/woocommerce-payments/dist/tos.css 235 B
release/woocommerce-payments/dist/tos.js 21.8 kB
release/woocommerce-payments/dist/woopay-direct-checkout.js 6.13 kB
release/woocommerce-payments/dist/woopay-express-button.js 23.3 kB
release/woocommerce-payments/dist/woopay-rtl.css 4.31 kB
release/woocommerce-payments/dist/woopay.css 4.28 kB
release/woocommerce-payments/dist/woopay.js 71 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 772 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.2 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 kB
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-connection.js 28.4 kB
release/woocommerce-payments/vendor/automattic/jetpack-connection/dist/jetpack-connection.rtl.css 10 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 424 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 632 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

I also got rid of the payment method class constants that were referring to another constant to get the ID. That required changes in a few other places to address errors. I added a new get_keywords() method to be able to implement these into the duplicate detection service.
…tion and move that functionality to a util class since it should largely be the same for all payment method definitions
@brettshumaker
Copy link
Contributor Author

@marcinbot I really appreciate you reviewing this! My apologies for it being on my back burner for a little while, but I've made several updates based on your feedback:

  1. Converted all methods in the definitions to be static
  2. Removed the payment method constants in favor of direct string returns
  3. Simplified the icon handling by removing the trait and adding direct getter methods
  4. Implemented PSR-4 naming consistency throughout the Configs directory
  5. Moved shared payment method functionality to utility classes
  6. Converted all BNPL payment methods (Affirm, Afterpay/Clearpay, and Klarna) to use the new definition system

I still need to go through and add tests where appropriate for the new functionality (hence this is still a draft), but wanted to get your eyes on these architectural changes first to make sure I'm heading in the right direction.

Could you please take another look when you have a chance? Thanks!

Copy link
Contributor

@marcinbot marcinbot left a comment

Choose a reason for hiding this comment

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

Looks good, a few suggestions below, but I think we're simplifying the approach here a lot :)

$this->limits_per_currency = WC_Payments_Utils::get_bnpl_limits_per_currency( self::PAYMENT_METHOD_STRIPE_ID );
$this->countries = [ Country_Code::UNITED_STATES, Country_Code::CANADA ];

$capabilities = AffirmDefinition::get_capabilities();
Copy link
Contributor

Choose a reason for hiding this comment

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

With definitions in place now, we can have one UPE_Payment_Method class whose constructor could look like this:

__construct( $token_service, string $payment_method_definition_class ) {
    $this->definition = $payment_method_definition_class;

    $capabilities = $this->definition::get_capabilities();
    // other initialization goes here
}

Then, whatever instantiates the above, could just instantiate multiple instances of UPE_Payment_Method but with different definitions passed in.

I think there's even a way to make Psalm enforce that the definition implements the interface like (note that this would require a phpcs rule to be disabled to allow the class-name as type):

/**
 * @template T of PaymentMethodDefinitionInterface
 *
 * @param class-name<T> $payment_method_definition_class
 */

So we'd basically end up with one UPE_Payment_Method class and multiple definitions. This hinges on one of my questions below of whether we can move all the methods to this approach, but even if not, we can add some conditions here, like making payment_method_definition_class nullable and allowing inheriting classes to do their own thing for methods without a definition class.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I had the thought that once all payment methods are converted to use definitions there was a potential for even more simplification. I can look at this a bit more to see what that would mean for payment methods that need additional logic for methods like get_countries(). We could put that in the definition, but I think we were trying to keep them pretty lean.

Copy link
Contributor

Choose a reason for hiding this comment

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

We could put that in the definition, but I think we were trying to keep them pretty lean.

We are, but, depending on the case, we could either hardcode stuff as plain data or maybe bend the leanness a bit. I'm looking at the existing Klarna logic for countries and it doesn't seem too bad to keep in the definition if need be.


export type PaymentMethodCapabilityType = typeof PaymentMethodCapability[ keyof typeof PaymentMethodCapability ];

export const PaymentMethodDefinitions: PaymentMethodConfigurations = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Since PHP enforces the values, and PaymentMethodDefinition interface enforces the shape of the objects, perhaps there's no need for this?

It would be really good to avoid duplicating this code and especially auto-generating stuff, which is a lesson learned the hard way in the V2 server.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that exported type was accidentally left in the generation script. It's not being used anywhere so I'll remove it.

It would be really good to avoid duplicating this code and especially auto-generating stuff, which is a lesson learned the hard way in the V2 server.

Are you advocating for not automatically generating the client-side types based on the PHP code?

Copy link
Contributor

Choose a reason for hiding this comment

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

Are you advocating for not automatically generating the client-side types based on the PHP code?

If there's a pre-existing mechanism to do that, then we can continue. But we should also keep it on a simpler side - there should be a way to feed any class to such a generator which would output a type.

I'm specifically not sure about declaring constants and complex types as constants (PaymentMethodDefinitions here) - it feels like there might be a more intuitive way of passing these from PHP to JS without resorting to "compiling" the code (maybe just serialize an object to JSON?). It would come at a cost of performance but could be easier to maintain.

/**
* Generates payment method configurations for the frontend.
*/
class GeneratePaymentMethodConfigsCommand {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think another argument against this is that we could end up accidentally shipping this.

It's not the end of the world if we do, and we can address it by moving this class to the cli folder, but still.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think another argument against this is that we could end up accidentally shipping this.

I'm happy to do whatever you think is best in this regard.

/**
* Internal dependencies
*/
import type { PaymentMethodMapEntry } from '../payment-methods-map';
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 exactly sure how this is compiling (maybe because it's just importing the TS type?), but there seem to be a circular dependency, here. client/payment-methods/generated-map.ts is importing client/payment-methods-map.tsx. But client/payment-methods-map.tsx is also importing client/payment-methods/generated-map.ts.

Maybe PaymentMethodMapEntry should be separated in a different file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, good catch! Resolved this in a16c8f3

Copy link
Contributor

@frosso frosso left a comment

Choose a reason for hiding this comment

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

The approach you have here is really good and smart - I bet it wasn't easy to create the abstraction to support the use cases we have now.

I think you proved the viability pretty well by having Affirm/Afterpay/Klarna.

Tomorrow I'll take a closer look at better understanding the build steps and the complexity/extendability needs in case we need more use cases for the other "special" payment methods (like Alipay and the different supported currencies at checkout, which are based on the account's country).

Great job so far!

This reverts commit dccdc2d.
There was an issue with `require.context` not being available in Jest since it's a webpack thing. We're now mocking the CreatePaymentMethodIconComponent instead.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants