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

Integrate split UPE payments with WooCommerce Blocks #4681

Merged
merged 23 commits into from
Nov 9, 2022

Conversation

harriswong
Copy link
Contributor

@harriswong harriswong commented Sep 1, 2022

Fixes #4603

Changes proposed in this Pull Request

Block checkout registers UPE as a payment method (Introduced by #2429). What this PR attempts to do is to register multiple payment methods instead.

Difference in payment methods management in classic checkout vs Blocks 🧐

Classic checkout renders and manages individual payment methods via WC Core and woocommerce_payment_gateways filter.

WooCommerce Blocks provide the PaymentMethodRegistry for the method to be registered. During the payment method registration, we rollout js scripts and styles, and this time the side responsible for the methods rendering is the front-end where blocks give us an assist in the form of registerPaymentMethod.

Problem solution 👨🏽‍💻

Having in mind the above, I took the opportunity and used the paymentMethodsConfig field, which comes from the back-end and is exposed in JavaScript. This field represents all the enabled payment methods from WCPay settings. On the front end, I take all the enabled payment methods and register a payment method for each of them. For each payment method, its name is being provided to the component in order to be used later on in the effect hook to create a payment intent, right after opening the payment method on the UI.

I also provide testingInstructions for each particular payment method to be displayed on the UI, if we are in the Test mode.

How the basic Card Element replaced the card Stripe payment element

The idea of replacing the card payment method with the legacy card is pretty straightforward - ignore the card payment method while attaching all the enabled UPE payment methods and attach the legacy card separately. The same idea is applied in the classic split UPE checkout. My first thought was to update the UPE implementation to somehow be able to inject a legacy card there. But then I thought - we already have the fully working separate component with the working legacy card, which is used when UPE aren't enabled.

Finally, what I did is I rolled out two components here - the legacy card and UPE payment elements without the card. This approach helped us to reduce the amount of global refactoring and allowed us to roll back the solution very quickly in case we will ever need to take a new path.

Testing instructions

  1. Go to the wp-admin console and create a new page. Search for the checkout block and add it to the page.
    image
  2. Go add some items to your cart.
  3. Go to your new block checkout page (ie. http://localhost:8082/block-checkout/)
  4. You should see all the enabled UPE payment methods render.

image

  1. Go to Payments -> Settings and disable some of the payment methods from the list. Then, go to the blocks checkout page and confirm, that the rendered methods list reflects only the enabled payment methods.
  2. Confirm, that there are test messages appearing for Card and SEPA payment.
  3. Make sure the confirmed order is on the Orders list in your WooCommerce console in the admin account. Check transaction details.

image

image

  1. Perform regression tests with non-UPE checkout and confirm, that card payment works as expected.

  • 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)

Post merge

@harriswong harriswong changed the base branch from develop to poc/upe-instances-multiplied September 1, 2022 23:09
@harriswong
Copy link
Contributor Author

harriswong commented Oct 4, 2022

Update

  • Nonce for these individual UPE payment method may need work

    $is_nonce_valid = check_ajax_referer( 'wcpay_create_payment_intent_nonce', false, false );
    . Otherwise, you may see this error:
    image

  • The payment method title (screenshot from description) needs to reflect the actual payment method.

  • I only created 4 payment methods to test the idea (card, bancontact, SEPA, Giropay). If creating classes to extends WC_Payments_UPE_Blocks_Payment_Method is the right approach, then we will need to create all the payment method classes.

  • Test broke. The UPE block checkout E2E tests rely on 1 UPE instance. We'll need to fix that since we now have multiple UPE instances.

  • If SEPA is disabled from UPE payment settings, then it shouldn't show here. Right now, I loop and display all of them.

@timur27
Copy link
Contributor

timur27 commented Oct 7, 2022

@harriswong I noticed one more thing - currently, with blocks, we are rendering the UPE credit card (at the bottom in the first screenshot) instead of the legacy credit card that uses Stripe Elements (the top one):

image

image

Whereas during the classic checkout with split UPE turned on, we're rendering the legacy credit card. There is some background of why it looks like that on the classic checkout in the description of this PR. My first thought was that we should align them and make blocks checkout behave the same as classic checkout. Do you agree with this? If yes, I'll cover this within this issue as well.

@harriswong
Copy link
Contributor Author

@timur27 Thanks for the note! Yes, I think making both behaviours the same would be good. This way, both will use the classic checkout for cards, and UPE elements for other payment methods. That said, the card classic checkout doesn't seem to work with Link, so whatever fix we decided upon will have to be applied to both in the future.

Please check with @FangedParakeet though.

@FangedParakeet
Copy link
Contributor

@timur27, yeah, the Blocks element should use the legacy payment element for the card payment method, but the UPE element for all other payment methods. This should align with how the classic checkout functions in the split-UPE sanctum.

The purpose for doing this is because the UPE is currently incompatible with WooPay, so this split-UPE approach will allow us to hurdle this obstacle by using the compatible legacy element for the WooPay card element alone, while using the UPE for all other payment methods.

I am honestly not sure how Link will eventually play into this (sorry lol), but there's more discussion on this on #4720.

@timur27 timur27 force-pushed the fix/4603-block-split-upe branch from 4a4e312 to e85f6cf Compare October 13, 2022 12:00
@github-actions
Copy link
Contributor

github-actions bot commented Oct 13, 2022

Size Change: +571 B (0%)

Total Size: 1.7 MB

Filename Size Change
release/woocommerce-payments/dist/blocks-checkout.js 34.4 kB +190 B (+1%)
release/woocommerce-payments/dist/checkout.js 27.1 kB +1 B (0%)
release/woocommerce-payments/dist/payment-request.js 23.1 kB +1 B (0%)
release/woocommerce-payments/dist/upe_checkout.js 38.5 kB +17 B (0%)
release/woocommerce-payments/dist/upe-blocks-checkout.js 45.3 kB +362 B (+1%)
ℹ️ View Unchanged
Filename Size
release/woocommerce-payments/assets/css/admin.css 913 B
release/woocommerce-payments/assets/css/success.css 401 B
release/woocommerce-payments/dist/blocks-checkout.css 1.77 kB
release/woocommerce-payments/dist/checkout.css 1.85 kB
release/woocommerce-payments/dist/index.css 35.8 kB
release/woocommerce-payments/dist/index.js 640 kB
release/woocommerce-payments/dist/multi-currency-analytics.js 8.88 kB
release/woocommerce-payments/dist/multi-currency-switcher-block.js 79 kB
release/woocommerce-payments/dist/multi-currency.css 14.7 kB
release/woocommerce-payments/dist/multi-currency.js 117 kB
release/woocommerce-payments/dist/onboarding.css 1.12 kB
release/woocommerce-payments/dist/onboarding.js 38.1 kB
release/woocommerce-payments/dist/order.js 299 B
release/woocommerce-payments/dist/payment-gateways.css 1.2 kB
release/woocommerce-payments/dist/payment-gateways.js 91.1 kB
release/woocommerce-payments/dist/platform-checkout.css 3.73 kB
release/woocommerce-payments/dist/platform-checkout.js 76.5 kB
release/woocommerce-payments/dist/settings.css 47.3 kB
release/woocommerce-payments/dist/settings.js 157 kB
release/woocommerce-payments/dist/subscription-edit-page.js 3.66 kB
release/woocommerce-payments/dist/subscription-product-onboarding-modal.css 7.18 kB
release/woocommerce-payments/dist/subscription-product-onboarding-modal.js 16.2 kB
release/woocommerce-payments/dist/subscription-product-onboarding-toast.js 428 B
release/woocommerce-payments/dist/subscriptions-empty-state.css 298 B
release/woocommerce-payments/dist/subscriptions-empty-state.js 48.4 kB
release/woocommerce-payments/dist/tos.css 236 B
release/woocommerce-payments/dist/tos.js 31.5 kB
release/woocommerce-payments/dist/upe_checkout.css 1.85 kB
release/woocommerce-payments/dist/upe-blocks-checkout.css 1.77 kB
release/woocommerce-payments/includes/subscriptions/assets/css/plugin-page.css 633 B
release/woocommerce-payments/includes/subscriptions/assets/js/plugin-page.js 355 B
release/woocommerce-payments/vendor/automattic/jetpack-admin-ui/src/css/jetpack-icon.css 224 B
release/woocommerce-payments/vendor/automattic/jetpack-assets/build/i18n-loader.js 2.43 kB
release/woocommerce-payments/vendor/automattic/jetpack-assets/src/js/i18n-loader.js 1.01 kB
release/woocommerce-payments/vendor/automattic/jetpack-identity-crisis/babel.config.js 160 B
release/woocommerce-payments/vendor/automattic/jetpack-identity-crisis/build/index.css 2.27 kB
release/woocommerce-payments/vendor/automattic/jetpack-identity-crisis/build/index.js 13.3 kB
release/woocommerce-payments/vendor/automattic/jetpack-identity-crisis/build/index.rtl.css 2.27 kB
release/woocommerce-payments/vendor/automattic/jetpack-tracking/src/js/tracks-ajax.js 789 B
release/woocommerce-payments/vendor/automattic/jetpack-tracking/src/js/tracks-callables.js 925 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/about.css 1.2 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/admin-order-statuses.css 403 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/admin.css 3.43 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/checkout.css 299 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/modal.css 742 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/view-subscription.css 572 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/css/wcs-upgrade.css 411 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/admin-pointers.js 544 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/admin.js 8.97 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/jstz.js 6.8 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/jstz.min.js 3.83 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/meta-boxes-coupon.js 544 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/meta-boxes-subscription.js 2.35 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/moment.js 22.1 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/admin/moment.min.js 11.6 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 502 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/payment-methods.js 355 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/frontend/single-product.js 429 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 387 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/modal.js 1.1 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/assets/js/wcs-upgrade.js 1.27 kB
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/build/index.css 392 B
release/woocommerce-payments/vendor/woocommerce/subscriptions-core/build/index.js 3.06 kB

compressed-size-action

@timur27 timur27 self-assigned this Oct 13, 2022
@timur27 timur27 force-pushed the fix/4603-block-split-upe branch from d6af1e6 to 2b40054 Compare October 14, 2022 15:04
@timur27 timur27 force-pushed the fix/4603-block-split-upe branch from 54e5ed2 to 6a10a65 Compare October 16, 2022 22:26
@timur27
Copy link
Contributor

timur27 commented Oct 27, 2022

@FangedParakeet today, I found one inconsistency between classic UPE checkout vs. the blocks one, and with this message, I'd like to clarify the performance expectations and if there is a need for alignment between these two.

How the classic split UPE checkout creates payment intents

As we know, the classic split UPE checkout is implemented in a way that wcpay_create_payment_intent_{payment_method} async AJAX calls are sent all at once during the page load when mounting UPE elements, providing our users a seamless experience when using payment methods since each method is already loaded and opens immediately whenever used.

How the blocks split UPE checkout creates payment intents

With woocommerce-blocks, things are implemented a bit differently, and I noticed a dissimilarity in the strategy of sending the requests to create a payment intent. We have the effect hook, which is responsible for creating a payment intent after rendering the component (unless paymentIntentId, hasRequestedIntent, API, errorMessage, appearance fields didn't change, of course).

At the same time, the registerPaymentMethod function from woocommerce-blocks creates a payment method config within blocks but does not render the payment method yet. The rendering occurs when users open a particular payment method. Effectively, a payment intent is created only for a specific form of payment when its block component is chosen from the list.

What I'd like to clarify

While there should be a way for Blocks implementation to create payment intents on the page load, I wanted to confirm the expectations. Should each payment method with blocks open immediately like in the classic checkout, or the loading time due to the intent creation API call for each method is acceptable in Blocks? Alternatively, maybe at this point, the most important thing for us is to make the Blocks checkout work, and we will get back to the performance later. These are the questions I don't have an answer to yet. 😊

Thank you for clarifying all this!

@FangedParakeet
Copy link
Contributor

Should each payment method with blocks open immediately like in the classic checkout, or the loading time due to the intent creation API call for each method is acceptable in Blocks?

This is actually quite a dense philosophical answer and one I don't entirely have an answer for. You are correct in that the classic checkout creates payment intents for all payment method types immediately on page load. This is "seamless", such that the end user will not have to wait for a new element to load while changing payment methods at checkout; however, this isn't tremendously efficient. We spend background resources to make these requests and moreover we create lots of additional perhaps superfluous requests to Stripe.

We create a new payment intent for every enabled payment method, irregardless of whether every payment method is even selected at checkout. An end user might never select a payment method and never see its payment element, but we will still trouble Stripe to create an intent and the front-end resources required to support this unseen element. These additional requests and unused payment intents might look a little suspicious from Stripe's perspective and is usually a symptom of some spammy sickness on a site's checkout.

It is designed this way not by some enlightened decision making, but simply because I understandably chose the simplest path when writing the initial PoC. Personally, I think the latency to mount a new payment element (create intent, create element using intent, and mount it to DOM) is generally a relatively negligible, constant-time operation, whereas the negatives of avoiding this scenario by preloading all payment elements is rather noticeable (multiplying our requests to Stripe, multiplying unused payment intents, etc.). So I would lean towards refactoring our existing code to only prepare and mount UPE elements when they are required, as opposed to loading them all on page load.

Alternatively, maybe at this point, the most important thing for us is to make the Blocks checkout work, and we will get back to the performance later.

That being said, I'd still hold this goal in higher regard, so for now I think following the shortest path is the most sensible option. It's worth remaining aware that once Stripe finally drops their new UPE implementation, this entire debate will be nullified, since we will no longer require a payment intent in order to present a UPE payment element to our customers. So I feel that perhaps expending too much effort on the most optimal solution in this case will be somewhat in vain, since this will not be a long-term concern for us.

Anyways, I'd vote for now on finding the simplest solution for Blocks right now and since that also happens to involve delaying payment intent creation until the moment is unavoidable, I'm even happier with choosing this path forwards. @mdmoore, we've chatted about this a few times here and there: let me know if you have any contradictory thoughts on this subject--or even better, if you happen to agree with me! 😁

@mdmoore
Copy link
Member

mdmoore commented Oct 27, 2022

I'd vote for now on finding the simplest solution for Blocks right now and since that also happens to involve delaying payment intent creation until the moment is unavoidable, I'm even happier with choosing this path forwards.

I agree completely. It might be worth sharing your findings in #4672 here. Hopefully we'll ultimately move to the same approach in classic checkout as well and keep the intent history tidy. If blocks checkout is giving us an assist on that, I'd say go with it!

@timur27 timur27 changed the title WIP: Fix/4603 block split upe Integrate split UPE payments with WooCommerce Blocks Oct 28, 2022
Timur Karimov and others added 2 commits October 28, 2022 12:07
@timur27 timur27 marked this pull request as ready for review October 28, 2022 14:15
@timur27 timur27 force-pushed the fix/4603-block-split-upe branch from cdfd88a to 6304292 Compare October 28, 2022 14:18
Copy link
Contributor

@FangedParakeet FangedParakeet left a comment

Choose a reason for hiding this comment

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

This issue is probably the largest one we have remaining and the one I've been keeping the most distance from, so awesome work, @timur27, for tackling it without breaking a sweat!

Generally everything here is working even better than expected! I was able to do pretty muche everything, except for completing a checkout with a saved UPE payment method. However, I think this may be related to #4530, so I'm happy to ignore this issue.

Here are a few oddities that I discovered.

Invisible notices

I'm not sure why, but every time I managed to coax an error out of the checkout, I found a blank notice at the top of the page.

Mysterious invisible error notice
Mysterious invisible error notice example

By using the Redux debug tools, I could find each missing error notice stored correctly in the state, but for some reason this is not being communicated to the front-end. Let me know if you're able to replicate this as well!

Payment gateway re-renders

I left another comment related to this issue, but essentially, whenever we select a new UPE payment gateway at checkout, the entire element re-renders and we remount a new payment element. I see that we discussed this earlier, but I didn't realise that the entire element still re-renders even if the select payment gateway has been selected before.

This is a little irritating, but not a goliath gaffe. However, I would maybe suggest that we might want to block the checkout--or at least the Place Order button--while the element is rendering, if possible. Several of the redirection UPE payment gateways do not require any additional payment information and when one of these gateways is selected, it isn't necessarily clear that anything is loading.

If you submit the form while the payment element is mounting, you are greeted with a rather strange error message. I had to sift through the Redux state to find it, due to the former issue, but I believe the error message states, "Invalid payment method. Please input a new card number" when the currently selected payment gateway is not the card gateway. I'm not entirely sure what's going on here, but if it's possible to block the "Place Order" button during this loading time, I think that would negate this unkindness.

Those are the only issues I could consistently replicate with these changes, so top work once more! Let me know your thoughts, as I wouldn't be opposed to opening new issues to address these challenges, if you'd like to move on from this current issue, as I do think you have accomplished the core heart of this task successfully.

client/checkout/blocks/upe.js Show resolved Hide resolved
client/checkout/blocks/upe-fields.js Show resolved Hide resolved
@timur27
Copy link
Contributor

timur27 commented Nov 1, 2022

I'm not sure why, but every time I managed to coax an error out of the checkout, I found a blank notice at the top of the page.

@FangedParakeet, this is a valid observation, thank you for bringing this up! 🙌🏼 I spent some time trying to find the root cause since I could reproduce it on the non-UPE blocks checkout and on the develop branch.

Finally, I discovered that the woocommerce-blocks plugin unexpectedly handles the error, probably in this area. I reached out Team Rubik for a confidence check, if my assumptions are correct or not. It turned out that there was the issue resolved some time ago, which fixes the mysterious invisible error notice starting from version 8.7.0 of Woocommerce Blocks.

After updating the plugin on my local WCPay environment, I confirmed that the issue is not appearing anymore.

@FangedParakeet
Copy link
Contributor

It turned out that there was woocommerce/woocommerce-blocks#7231 resolved some time ago, which fixes the mysterious invisible error notice starting from version 8.7.0 of Woocommerce Blocks.

Nice one! Can confirm that upgrading the Blocks plugin has now resolved this issue for me. 💪 Ma b.

Copy link
Contributor

@FangedParakeet FangedParakeet left a comment

Choose a reason for hiding this comment

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

Thanks for responding to all of my feedback from the last time! Don't worry, I only have a little more this time. 😅

Basically, everything is working as expected! The regular Blocks checkout flow, excluding saved payment methods, is working happily. Moreover the loading time while switching between payment methods at checkout is much healthier now that we persist the payment intent for the entire duration of a checkout. Great work!

However, sadly I discovered that the regular shortcode UPE checkout flow is now unfortunately broken and returns the following error consistently.

paymentMethodsConfig[paymentMethodType] is undefined

However, I've left another comment that explains why I'm very confident this is happening (you need to fix the parameters used on this function call), which should hopefully be a very easy amend. Please ping me again once you've fixed that and I don't think I'll have any other reason not to approve this PR. Cheers!

changelog/fix-4603-block-split-upe Outdated Show resolved Hide resolved
client/checkout/utils/upe.js Show resolved Hide resolved
@timur27
Copy link
Contributor

timur27 commented Nov 8, 2022

@FangedParakeet today, I found one more thing to align - paymentIntents weren't removed from the session after an order gets processed successfully. Because of this, a user was getting an error while trying to make two purchases with one payment method, one after the other.

The reason for that was that the payment gateway is considered separate when using Blocks, and adding actions in the else statement in class-upe-payment-gateway was never done because none of the payment methods a user chooses is a cart.

Somehow this worked with the classic UPE checkout, and I guess that hooks' callbacks from $card_gateway were somehow re-used in other payment methods.

I now fixed this behavior to work everywhere, and the problem disappeared. I tend to think that the solution is not ideal, but it seems to be bulletproof within our area of interest.

Copy link
Contributor

@FangedParakeet FangedParakeet left a comment

Choose a reason for hiding this comment

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

Thank you so much for outlasting me and my endless feedback in this marathon PR. Code looks great and works like a dream.

I found one more thing to align - paymentIntents weren't removed from the session after an order gets processed successfully.

Fantastic find here as well! I agree that the solution looks a little ugly (sorry 😁 ), but I can't think of a better way of ensuring that these actions definitely fire when the UPE is enabled, but only fire once. In fact, in the long term, I think this is a much more robust solution that allows us to potentially disable the card payment method entirely, without affecting these hooks.

I ran through a few regular checkout flows, using both the Blocks and shortcode checkout, and everything appears to be working as expected! I hereby offer the entirety of my grattitude and blessings.

L. G. T. M. :shipit:

@timur27 timur27 merged commit 6444432 into poc/upe-instances-multiplied Nov 9, 2022
@timur27 timur27 deleted the fix/4603-block-split-upe branch November 9, 2022 08:10
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.

Multiple UPE - Payment methods in blocks
4 participants