Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Fix flaw with event emitters and complete checkout flow for the cheque payment method. #2108

Merged
merged 17 commits into from
Apr 3, 2020

Conversation

nerrad
Copy link
Contributor

@nerrad nerrad commented Apr 3, 2020

With master having the necessary pieces in place to theoretically complete a payment using the cheque payment method and fulfill the checkout flow (including a redirect to order receipt page), I took some time to test things out and fix bugs that popped up.

In the process I discovered a serious flaw with the event emitters that I overlooked when designing the system. The flaw is that all the registered observers on the event emitters cannot react to any state changes because they are all invoked in one render process. This also means that any state changes in observers can be problematic because subsequent observers would not know of those state changes.

The solution involves recognizing that we are using the event emitters for tracking flow through the checkout and to limit status and state changes to after the event has emitted. By adding event emitters to the PaymentMethodData context, and adding another Checkout status it actually ended up simplifying the flow logic because now there is a stable point for payment methods to hook in their behaviour and for the CheckoutProcessor component to hook in it's logic.

Sidenote: I realize "simplifying" seems to be an oxymoron here because it's still relatively complex. But the complexity is absorbed by the providers instead of passed on to extension components to manage (which can increase complexity exponentially if extensions have to check constantly for status changes among all the elements in the checkout flow).

With the changes in this pull:

  • Payment method extensions will not be responsible for updating the payment status. The payment method provider context handles that. Payment method components will register observers on the events they need to react to and return appropriate values for communicating with checkout.
  • Convention (not enforced) will be that observers must not set any state in checkout (they can set their own local state if they want). Provider state (payment, checkout, shipping) is handled by provider's emitters after all observers for an event have fired.

Here's a rough diagram of the flow (not including shipping events):

CheckoutFlow@2x

In this pull:

  • new payment method event emitters are added,
  • new actions/status to checkout state context added (checkoutProcessingComplete)
  • refactor checkout state provider.
  • refactor checkout processor.
  • wire up cheque payment method so it's hooked into the onPaymentProcessing event emitter.

To Test

It is expected that Cart/Checkout behaviour up to the point of clicking the related submit button should continue to work as expected outside what was done in this branch.

You should be able to finish a complete checkout flow using the cheque payment method without errors.

There should be no errors in the console while testing.

Stripe is not expected to work.

Validation errors after submit button is clicked may have regressed. Please do test, but we can fix in follow-ups unless the cause is clear.

Followups:

@nerrad nerrad requested a review from a team as a code owner April 3, 2020 03:25
@nerrad nerrad requested review from Aljullu and mikejolley and removed request for a team April 3, 2020 03:25
@nerrad nerrad self-assigned this Apr 3, 2020
@nerrad nerrad added skip-changelog PRs that you don't want to appear in the changelog. type: enhancement The issue is a request for an enhancement. type: refactor The issue/PR is related to refactoring. labels Apr 3, 2020
@nerrad nerrad added this to the Future Release milestone Apr 3, 2020
@github-actions
Copy link
Contributor

github-actions bot commented Apr 3, 2020

Size Change: -108 kB (5%) ✅

Total Size: 2.06 MB

Filename Size Change
build/active-filters-frontend.js 7.52 kB -4 B (0%)
build/active-filters.js 8.29 kB -5 B (0%)
build/all-products-frontend.js 17.3 kB -3 B (0%)
build/all-products.js 66.2 kB -3 B (0%)
build/all-reviews.js 11 kB +2 B (0%)
build/attribute-filter-frontend.js 17 kB +8 B (0%)
build/attribute-filter.js 11.8 kB -7 B (0%)
build/blocks.js 2.92 kB +1 B
build/cart-frontend.js 162 kB +516 B (0%)
build/cart.js 79.4 kB +178 B (0%)
build/checkout-frontend.js 166 kB +1.19 kB (0%)
build/checkout.js 80 kB +816 B (1%)
build/custom-select-control-style.js 782 B -1 B
build/featured-category.js 146 kB -1 B
build/featured-product.js 59.4 kB +5 B (0%)
build/handpicked-products.js 7.51 kB +1 B
build/price-filter-frontend.js 14.1 kB +4 B (0%)
build/price-filter.js 10.4 kB -4 B (0%)
build/product-category.js 8.53 kB -6 B (0%)
build/product-on-sale.js 8.18 kB -2 B (0%)
build/product-search.js 3.86 kB +2 B (0%)
build/product-tag.js 6.68 kB -1 B
build/products-by-attribute.js 8.68 kB -7 B (0%)
build/reviews-by-category.js 13 kB +2 B (0%)
build/reviews-by-product.js 14.5 kB +3 B (0%)
build/reviews-frontend.js 9.08 kB -1 B
build/vendors.js 367 kB +2 B (0%)
build/wc-payment-method-cheque.js 0 B -815 B (0%)
build/wc-payment-method-stripe.js 0 B -110 kB (0%)
ℹ️ View Unchanged
Filename Size Change
build/all-reviews-legacy.js 10.8 kB 0 B
build/block-error-boundary-legacy.js 774 B 0 B
build/block-error-boundary.js 774 B 0 B
build/blocks-legacy.js 2.92 kB 0 B
build/checkbox-control-style-legacy.js 779 B 0 B
build/checkbox-control-style.js 781 B 0 B
build/custom-select-control-style-legacy.js 782 B 0 B
build/editor-legacy-rtl.css 12.6 kB 0 B
build/editor-legacy.css 12.6 kB 0 B
build/editor-rtl.css 13.5 kB 0 B
build/editor.css 13.5 kB 0 B
build/featured-category-legacy.js 146 kB 0 B
build/featured-product-legacy.js 59.5 kB 0 B
build/handpicked-products-legacy.js 7.31 kB 0 B
build/panel-style-legacy.js 773 B 0 B
build/panel-style.js 773 B 0 B
build/product-best-sellers-legacy.js 7.41 kB 0 B
build/product-best-sellers.js 7.62 kB 0 B
build/product-categories-legacy.js 3.2 kB 0 B
build/product-categories.js 3.19 kB 0 B
build/product-category-legacy.js 8.32 kB 0 B
build/product-list-style-legacy.js 776 B 0 B
build/product-new-legacy.js 7.58 kB 0 B
build/product-new.js 7.78 kB 0 B
build/product-on-sale-legacy.js 7.92 kB 0 B
build/product-search-legacy.js 3.64 kB 0 B
build/product-tag-legacy.js 6.48 kB 0 B
build/product-top-rated-legacy.js 7.55 kB 0 B
build/product-top-rated.js 7.75 kB 0 B
build/products-by-attribute-legacy.js 8.48 kB 0 B
build/reviews-by-category-legacy.js 12.7 kB 0 B
build/reviews-by-product-legacy.js 14.2 kB 0 B
build/reviews-frontend-legacy.js 8.4 kB 0 B
build/snackbar-notice-style-legacy.js 778 B 0 B
build/snackbar-notice-style.js 779 B 0 B
build/spinner-style-legacy.js 774 B 0 B
build/spinner-style.js 772 B 0 B
build/style-legacy-rtl.css 5.3 kB 0 B
build/style-legacy.css 5.31 kB 0 B
build/style-rtl.css 14.4 kB 0 B
build/style.css 14.4 kB 0 B
build/vendors-legacy.js 280 kB 0 B
build/vendors-style-legacy-rtl.css 1.97 kB 0 B
build/vendors-style-legacy.css 1.97 kB 0 B
build/vendors-style-legacy.js 110 B 0 B
build/vendors-style-rtl.css 1.97 kB 0 B
build/vendors-style.css 1.97 kB 0 B
build/vendors-style.js 108 B 0 B
build/wc-blocks-data.js 6.71 kB 0 B
build/wc-blocks-registry.js 1.51 kB 0 B
build/wc-settings.js 2.14 kB 0 B

compressed-size-action

@nerrad
Copy link
Contributor Author

nerrad commented Apr 3, 2020

I have no idea why bundlesize is reporting 0B for the payment method builds. I tried a production build of them locally and didn't have any issue.

Copy link
Contributor

@Aljullu Aljullu left a comment

Choose a reason for hiding this comment

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

Changes look good to me and Check payment works like a charm! 🎉 I left some minor comments below but I don't think they should be blocking.

Validation errors after submit button is clicked may have regressed. Please do test, but we can fix in follow-ups unless the cause is clear.

I could verify this. Validation errors do not allow the checkout process to complete, but they don't show up.

const getObserversByPriority = ( observers, eventType ) => {
return observers[ eventType ]
? Array.from( observers[ eventType ].values() ).sort( ( a, b ) => {
return a.priority > b.priority ? -1 : 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

This could be rewritten as a subtraction, if you want:

Suggested change
return a.priority > b.priority ? -1 : 1;
return b.priority - a.priority;

@@ -56,4 +62,20 @@ describe( 'Testing emitters', () => {
}
);
} );
describe( 'Test Priority', () => {
it( 'Executes observers in expected order by priority', async () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: we usually write test descriptions in lowercase:

Suggested change
it( 'Executes observers in expected order by priority', async () => {
it( 'executes observers in expected order by priority', async () => {

)
);
};
},
Copy link
Contributor

Choose a reason for hiding this comment

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

No need to do it in this pull, but this looks like something that could be abstracted. These four methods are the same just changing the type. Same for checkout-state/event-emit.js and shipping/event-emit.js.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ya this is a good idea. Done in 01eb74c

*/
const Content = ( { activePaymentMethod, eventRegistration } ) => {
// hook into payment processing event.
useEffect( () => {
Copy link
Member

Choose a reason for hiding this comment

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

Can you help me understand what this does exactly?

Can we abstract/import it so gateways have less to worry about?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Basically every gateway will have to at a minimum register an observer on the onPaymentProcessing event. This event fires when checkout has initiated payment processing as a part of the flow. What gateways return from this callback when it's invoked determines whether the gateway was succesful, failed, or errored for it's processing. The possible shapes of the return value are:

Success

Can return:

  • true
  • OR an object: { paymentMethodData, billingData }

Fail:

Return an object: { fail: { errorMessage, paymentMethodData } }

Error:

Return an object: { errorMessage, validationErrors }

  • errorMessage is used as just a general error message to display in the payment method step.
  • validationErrors is an object where keys are field ids and values are the error message for that field (to help with displaying field validation errors for for the 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.

Can we abstract/import it so gateways have less to worry about?

So one thing I was thinking we could do is have a function for registering really simple gateways that are similar to cheque in that they all the processing happens server side. So it'd have a fairly simple signature (gateway name, maybe icon, and text for the content window). Then this function would take care of the payment method registration (with Config etc) using components common to all these payment methods. We could export this as a helper on the block-registry global.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
skip-changelog PRs that you don't want to appear in the changelog. type: enhancement The issue is a request for an enhancement. type: refactor The issue/PR is related to refactoring.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants