woocommerce / woocommerce-gateway-stripe

The official Stripe Payment Gateway for WooCommerce
https://wordpress.org/plugins/woocommerce-gateway-stripe/
236 stars 206 forks source link

SPIKE - Sources API and Intentions API #1666

Open bborman22 opened 3 years ago

bborman22 commented 3 years ago

With the work to add UPE to the plugin, we will need to explore the current implementations of the Sources API and Payment Intents API. UPE currently is dependent on using the Payment Intents API.

The goal of this spike will be to provide details and a recommendation for how the two APIs can co-exist or if it would be more beneficial to move over to only using Payment Intents (keeping in mind the scope and timing needs). The secondary goal to this would be creating issues to address any next steps for that process.

Some possible starting points:

FangedParakeet commented 3 years ago

May the Source be with you: what does it all mean?

The Sources/Charges API is the payments flow that Stripe eventually replaced with the Payment Method/Intent flow. A Source is a secure object used to represent a customer's payment information. It is created from a mounted payment element (not UPE) and maintained--for security and privacy reasons--on the client; a Charge can be created using this Source and this is how a transaction can be completed. In the Payment Intent flow, the Charge object is no longer created manually, but instead is returned attached to a successfully confirmed Payment Intent.

The Sources API can support most payment methods, but notably not card payments. The Payment Intents API can also support most payment methods, but unfortunately not yet Multibanco, which has planned but not yet fully implemented support (see this table for a complete list of supported payment methods). For this reason, the Stripe plugin currently uses a blend of the Sources, Charges, and Intents API. Since neither API contains universal coverage for all necessary payment methods, this state of affairs will have to remain for now. However, since Intents are required for card payment flows, there is some existing functionality that we will be able to leverage. Specifically, there are API handlers to create and update payments, as well as functions to fill required API fields--level 3 data, for example--although, these may require a little refurbishment to appease the UPE.

Existing payment flows

Ignoring Apple/Google pay and instead focussing on the existing payment gateways, there are three payment flows currently implemented within the Stripe plugin.

  1. Card payment flow (Sources + Intents)
  2. SEPA payment flow (Sources + Charges)
  3. Redirection payment flow (Sources + Charges)

The first two are flows without redirection, where payment is confirmed or charges are created immediately upon completing a checkout inside the payment gateway's process_payment function, before being redirected to the Order Complete page. The final flow is the flow used by every other payment gateway, where a Source is created during checkout, the user is redirected towards Stripe for payment authorisation, redirected back to the Order Complete page if authorisation succeeds, and at this point the Source is retrieved and used to create a Charge. The main difference between direct and redirected flows is that in the former the order is completed before redirection, whereas in the latter the order can only be completed after redirection.

Anyways, to wade a little deeper into the details, let me walk you through these three flows for successful transactions.

Card payments

  1. User submits checkout form.
  2. Client intercepts form submit. Creates Source containing payment details, using Stripe client JS. Source ID added as additional field to form.
  3. Checkout form resubmitted.
  4. Regular WC checkout flow proceeds. Eventually, WC_Gateway_Stripe::process_payment is called with order details.
  5. Payment Intent created for order from Source, via request to Stripe API.
  6. Payment Intent confirmed, via separate request to Stripe API.
  7. Complete order. Return redirect URL to WC for Order Complete page.
  8. WC redirects user to Order Complete page.

SEPA payments

  1. User submits checkout form.
  2. Client intercepts form submit. Creates Source containing payment details, as well as SEPA mandates, using Stripe client JS. Source ID added as additional field to form.
  3. Checkout form resubmitted.
  4. Regular WC checkout flow proceeds. Eventually, WC_Gateway_Stripe_Sepa::process_payment is called with order details.
  5. Charge created for order from Source, via request to Stripe API.
  6. Complete order. Return redirect URL to WC for Order Complete page.
  7. WC redirects user to Order Complete page.

Redirection payment flow

  1. User submits checkout form.
  2. Regular WC checkout flow proceeds. Eventually, process_payment function is called for relevant payment gateway with order details.
  3. Create Source and add Source ID as metadata to order.
  4. Return redirect URL to WC for Stripe authorization (this URL is found within the previous Source response).
  5. WC redirects user to Stripe for authorization.
  6. Stripe redirects user to Order Complete page.
  7. Hook in WC_Stripe_Order_Handler fires to process redirect payment. Source is retrieved and Charge created for order from Source, via request to Stripe API.
  8. Complete order.

So what about us?

The UPE, as we are hopefully aware, works with Payment Intents and requires one to be created prior to the payment element being mounted. In the existing implementation, the UPE's client JS is kept entirely separate from existing checkout JS code. I think we should keep it this way, since the checkout experience will be identical to the WCPay checkout experience and we will not need to draw from any of the Stripe gateway's checkout client logic.

The gateway, by virtue of the nature of the beast, will also be kept separate from existing code; however, since WC_Gateway_Stripe uses Payment Intents, we do have the option of inheriting from and falling back to parent functionality, if required--for example, in the case of checkouts with a saved payment method. Moreover handler functions to make API requests to Stripe to create and update Payment Intents are both already present, though we may need to update these request bodies to satisfy the UPE's inclinations.

I would not recommend converting any existing Source API code or flows to use Intents, as, without existing comprehensive test coverage, it will be very difficult to ensure that functionality is maintained and moreover all use cases cannot be covered by Payment Intents. I do think we can convert existing Payment Intent API handlers to be able to be used with the UPE. I think I have enough information to proceed with a PoC/MVP implementation for a successful happy checkout flow using the UPE and that'll likely be my next compulsion from here on out.

ricardo commented 3 years ago

in the case of checkouts with a saved payment method

@FangedParakeet Additionally, scheduled subscription payments rely on saved sources. So we still need a flexible approach to process a payment when UPE is enabled.

FangedParakeet commented 3 years ago

In my testing of backwards compatability with saved payment methods in #1780, I discovered that if you have saved sources to your account, they are still returned by requests to Stripe's /payment_methods endpoint. Moreoever you can attach the saved source returned to a payment intent as a payment method and Stripe will let you continue to confirm the intent and complete a checkout. However, if you try to do this the other way around--using the ID of a payment method in lieu of a source to complete a checkout--there are errors galore.

I think most of the existing sources code will probably need to remain in some form anyways, but just a point worth considering.