This repository has been archived by the owner on Feb 23, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 219
Cart Checkout Flow Architectural Update (reference pull). Includes Apple Pay Integration POC #1780
Closed
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
nerrad
added
focus: components
Work that introduces new or updates existing components.
category: extensibility
Work involving adding or updating extensibility. Useful to combine with other scopes impacted.
type: enhancement
The issue is a request for an enhancement.
labels
Feb 19, 2020
Plan (jolly
|
nerrad
force-pushed
the
add/stripe-payment-method-integration
branch
8 times, most recently
from
February 27, 2020 19:51
d56ad77
to
216e261
Compare
nerrad
force-pushed
the
add/stripe-payment-method-integration
branch
from
February 28, 2020 18:27
964236a
to
731fcc5
Compare
nerrad
force-pushed
the
add/stripe-payment-method-integration
branch
4 times, most recently
from
March 5, 2020 16:09
c7fb315
to
83038dc
Compare
- includes updates to registering - includes in progress skeleton for stripe-credit implementation
nerrad
force-pushed
the
add/stripe-payment-method-integration
branch
from
March 5, 2020 17:04
83038dc
to
96635b4
Compare
nerrad
changed the title
Implement stripe applePay express payment integration.
Cart Checkout Flow Architectural Update (reference pull). Includes Apple Pay Integration POC
Mar 5, 2020
nerrad
requested review from
a team and
senadir
and removed request for
a team and
senadir
March 5, 2020 21:46
This was referenced Mar 6, 2020
Closed
Now that all the extraction is done and the todo lists above have associated issues I'm going to close this pull. I'm not deleting the branch yet though until I've extracted the apple pay stuff into its own pull for working on. |
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Labels
category: extensibility
Work involving adding or updating extensibility. Useful to combine with other scopes impacted.
focus: components
Work that introduces new or updates existing components.
type: enhancement
The issue is a request for an enhancement.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Cart Checkout Flow Architectural Update
This pull is a reference pull for some proposed changes to checkout block architecture structure and flow. I reference Cart block as well because I think much of this can apply to the cart context as well considering express payment methods might get implemented in the cart at some point. So what is being proposed here could be adapted for the cart relatively trivially. However, the perspective of this pull is orientated at least initially, towards the checkout block implementation. It is derived out of the following guiding principles:
Ultimately, this is a proposal, however it does hold up finishing off the cart and checkout blocks. Since we've already had some sign off from #1349 with regard to payment method integration, I don't think it's a requirement to get this reviewed by payment method developers before moving ahead with this, but it'd still be worthwhile having this reviewed at some point to catch any potential red flags (I'll be posting an internal post to help gather this feedback).
However, I do want feedback from this repos maintainers and team:
Bringing it all together
Many of the component parts of the checkout flow have already been developed and implemented over the last few weeks of cart and checkout block work. For the most part, this proposal does not replace any work that has already been done. It simply integrates these disparate parts together into a cohesive flow that provides consistent status state and extensibility points (which focus initially on payment method extensions) to support iteration one of the blocks.
As a part of working out this architecture proposal, I built an example integration of Apple Pay. I was able to get it to the point of actually triggering the payment interface and asserting it received totals as expected. Essentially, this validated that the approach was workable. However, there are still a number of pieces needing complete before I can do a complete transaction. Still, the validation was enough for me to get this reference pull up for review and comments. This should help us with the final push to finishing off the blocks.
Before getting into some of the concrete details, I've created a flow chart that gives a high level overview of the flow:
I've rebased the pull into discrete commits to aid with review of work. Keep in mind that there's still a number of
@todos
to accomplish (which will be summarized in the plan section). As a reference pull, this will not be merged as is but instead I expect we will open separate pulls for implementing the separate pieces.usePaymentMethodInterfaceHook
and update the payment method registration api (25c4631). This new hook exposes the primary interface that will be provided to registered payment methods using the payment method registration api. This hook is implemented in 0a67165 for the rendering of registered payment methods.useBillingData
anduseStoreOrder
hooks (b86ab77). These are currently placeholders.General concepts
Tracking flow through status.
At any point in the checkout lifecycle, components (and extensions hooked in) should be able to accurately detect the state of the checkout flow. This includes things like:
Using simple booleans can be fine in some cases, but in others it can lead to complicated conditionals and bug prone code (especially as we start writing behaviour that reacts to various flow state).
I decided to extract various elements of flow to specific status types that are tracked in the various contexts. As much as possible these statues are set internally in reaction to various actions so there's no implementation needed in children components. So while payment method extensions are expected to update the active payment method state to help communicate to checkout what their flow is at, the checkout status is tightly controlled via the contexts.
An example where this is beneficial is the
isCalculating
boolean exposed by the checkout context. This is actually a derived boolean that reflects at any moment if there is anything occurring which will affect totals. It considers a number of different in flight requests that might affect this and (hopefully) reliably only isfalse
when there are no in flight requests waiting to resolve. Managing something like this at the component level (or giving components the ability to set this boolean) would lead to unpredictable results (for exampleone component setting this totrue
and another component immediately setting it tofalse
).So in general, whenever this is some sort of flow status needing tracked, it should be something set and exposed at the context level.
Emitting Events
Another tricky thing for extensibility, is providing opinionated, yet flexible interfaces for extensions to act on and react to specific events in the flow. For stability, it's important that the core checkout flow controls all communication to and from the server specific to checkout processing leaving extension requirements to the extension to handle. This allows for extensions to predictably interact with the checkout data as needed without impacting other extensions hooking into it.
One of the most reliably ways to implement this type of extensibility is via the usage of an events system. So in this proposal, the contexts expose a variety of event registration apis for extensions (beginning with payment methods) to provide callbacks that will get invoked with relevant data at the time of the event firing. This is especially useful for payment methods like Apple Pay (or eventually Chrome Pay etc) that have a very self-contained interface for processing the payment, yet need to be able to communicate with checkout and react to various events from the checkout when it's done something.
An example of this is express payment methods that have their own billing data/shipping data capture that needs to update based on rates provided by checkout. You can see this implemented with the ApplePay payment method.
Details
In this section I'm going to go into a bit more detail about some of the api for the proposed architectural flow.
Contexts
In the structure, I'm proposing that we use
React.Context
to expose various data for tracking state and flow in the checkout. In some cases the context maintains the "tree" state within the context itself (viauseReducer
) and in others in a globalwp.data
store (for data that communicates with the server).Notices Context
This system was implemented by Mike in #1843. It essentially does three things:
@wordpress/notices
internally).useStoreNotices
) hook an api for getting and setting notices.This system is exposed on components by wrapping them in a
<StoreNoticesProvider>
component.Shipping Method Data context
The shipping method data context exposes the api interfaces for the following things (typedef
ShippingMethodDataContext
) via theuseShippingMethodData
hook:shippingErrorStatus
: The current error status for shipping rates if present.dispatchErrorStatus
: A function for dispatching a shipping error status. Used in combination with...shippingErrorTypes
: An object with the various error statuses that can be dispatched (NONE
,INVALID_ADDRESS
,UNKNOWN
)shippingRates
: This is retrieved using theuseShippingRates
hook.shippingRatesLoading
: True when shipping rates are loading.selectedRates
: Will expose the selected rates (@todo
will implementuseSelectedRates
hook from Update and select shipping rates dynamically #1794)setSelectedRates
: Function for setting new selected rates (also exposed from hook in Update and select shipping rates dynamically #1794)shippingAddress
: The current set shipping address.setShippingAddress
: A function for setting the shipping address. This will trigger shipping rates updates.onShippingRateSuccess
: This is a function for registering a callback to be invoked when shipping rates are retrieved successfully. Callbacks will receive the new rates as an argument.onShippingRateFail
: This is a function for registering a callback to be invoked when shipping rates fail to be retrieved. Callbacks will receive the error status as an argument.onShippingRateSelectSuccess
: This is a function for registering a callback to be invoked when shipping rate selection is successful.onShippingRateSelectFail
: This is a function for registering a callback to be invoked when shipping rates selection is unsuccessful.needsShipping
: Boolean indicating whether the contents of the order needs shipping (true
) or not (false
).Payment Method Data Context
The payment method data context exposes the api interfaces for the following things (typedef
PaymentMethodDataContext
) via theusePaymentMethodData
hook.setPaymentStatus
: used to set the current active payment method flow status.Calling it returns an object of dispatcher methods with the following methods:started()
,processing()
,completed()
,error()
,failed( errorMessage, CartBillingAddress, extraPaymentMethodData)
,success(CartBillingAddress, extraPaymentMethodData)
. Note extra payment data would get attached to the order processing request so payment methods could hook in server side to do any processing of the payment as a part of the checkout processing.currentStatus
: This is an object that returns helper methods for determining the current status of the active payment method flow. Includes:isPristine()
,isStarted()
,isProcessing()
,isFinished()
,hasError()
,hasFailed()
,isSuccessful()
.paymentStatuses
: This is an object of payment method status constants (this is likely unnecessary).billingData
: This is the current billing data tracked in the context state.setBillingData
: This is used to set the billing data in the state.paymentMethodData
: This is the current extra data tracked in the context state. This is arbitrary data provided by the payment method extension after it has completed payment for checkout to include in the processing request. Typically this would contain things like paymenttoken
andpayment_method
name.errorMessage
: This exposes the current set error message provided by the active payment method (if present).activePaymentMethod
: This is the current active payment method in the checkout.setActivePaymentMethod
: This is used to set the active payment method (typically triggered by user selecting a payment method). Any statuses in this context will be relevant to the active payment method. When a new payment method is selected the payment status is reset toPRISTINE
.Checkout Context
This context is the main one. Internally via the
<CheckoutContextProvider>
it handles wrapping children in<ShippingMethodDataProvider>
and<PaymentMethodDataProvider>
. So the checkout components just need to be wrapped by<CheckoutContextProvider>
.The provider receives three props:
activePaymentMethod
: A string, this allows for initializing with a specific payment method as the active payment method. Potentially something that could be an option for the merchant.redirectUrl
: A string, this is used to indicate where the checkout redirects to when it is complete. There is also a setter for updating this internally if needed.submitLabel
: This allows for customizing the checkout submit button label.Via the
useCheckoutContext
, the following are exposed:submitLabel
: This is whatever label was passed via the provider.onSubmit
: This is a callback to be invoked either by submitting the checkout button or by express payment methods to start checkout processing after they have finished their processing.isComplete
: True when checkout has finished processing and the subscribed checkout processing callbacks have all been invoked along with a successful processing of the checkout by the server.isIdle
: True when the checkout state has changed and there currently is no activity.isProcessing
: True when checkout has been submitted and is processing all the subscribed callbacks for the processing event and then the server side processing.isCalculating
: True when something in the checkout is/could result in totals being updated.hasError
: True when the checkout has an error (this is a separate status from the previous).redirectUrl
: The current set url that the checkout will redirect to when it is complete.onCheckoutCompleteSuccess
: Function used to register a callback that will fire when the checkout has processed successfully and is marked complete.onCheckoutCompleteError
: Function used to register a callback that will fire when something in the checkout processing has caused an error.onCheckoutProcessing
: Function used to register a callback that will be invoked when checkout has been submitted but before it sends the request to the server to process. This would be the place to register a callback that does some sort of validation. If any callback returns an error object, then the processing will bail (see flow chart for details) and set the checkout to idle status with hasError totrue
.dispatchActions
: This is an object with various functions for dispatching status in the checkout. It is not exposed to extensions but is for internal use only.Hooks
I'm not going to go into detail for all the hooks as that is fairly straightforward from existing implementations. However I do want to highlight an important extension interface hook,
usePaymentMethodInterface
.usePaymentMethodInterface
This hook is used to expose all the interfaces for the registered payment method components to utilize. Essentially the result from this hook is fed in as props on the registered payment components when they are setup by checkout. You can use the typedef (
RegisteredPaymentMethodProps
) to see what is fed to payment methods as props from this hook.Why don't payment methods just implement this hook?
The contract is established through props fed to the payment method components via props. This allows us to avoid having to expose the hook publicly and experiment with how the props are retrieved, exposed in the future.
Payment Method Integration changes
One major impetus for this architectural flow proposal is the work in implementing an actual payment method! I picked apple pay first because I'm a sucker for pain. Ha! No actually, I picked it because I thought it'd be a good example of something with a relatively straightforward third party api that is self-contained as way of validating the strategy of checkout handling all order processing and the two way communication with the payment method component for it's processing.
It's important to acknowledge that for the most part, the registration of payment methods and the general principles from the work in #1349 has not changed. Consider this an evolution of that work which resulted in a few changes needed that you can see implemented in this pull:
activeContent
element from registered payment methods will now receive props from the object exposed byusePaymentMethodInterface
.edit
property value (that is a React Element). This property will be used in the editor context. I don't think we want to have any payment libraries etc loading in the editor context. So this provides a way for payment methods to provide whatever is needed for the preview in the editor context.stepContent
from the configuration objects. I think checkout itself should be responsible for the billing form. Any extra fields the payment method requires can be a part of it'sactiveContent
element output in the designated ui in checkout (when the payment methods tab is active).PaymentMethodConfig
(notExpressPaymentMethodConfig
), includes aneedsBilling
boolean config option (optional and defaults to true. This can be used to signal to checkout whether the payment method needs the billing data or not (and possibly used by checkout to determine whether to show the billing form or not - but that may be something controlled by other means as well, so just a signal).Note: I've moved the Plan section to the first comment after this description to make it easier for editing