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

Show Express Checkout button previews in editor #10141

Merged

Conversation

danielmx-dev
Copy link
Contributor

@danielmx-dev danielmx-dev commented Jan 13, 2025

Fixes #10043

Changes proposed in this Pull Request

When visiting the Page: Cart template, the button for express payment is not visible in the Cart block. When debugging this further, I noticed that when using the template editor, the contents were rendered inside an iframe, which Gutenberg does by design: WordPress/gutenberg#25775

When the Stripe iframe is located inside the FSE iframe, no callbacks are executed and the payment buttons are never loaded (even if the conditions to render the buttons are met). This is not a problem with the page editor since, in my testing, that editor does not use an iframe to render the contents of the page, so the buttons load as expected.

In this PR I'm proposing to establish a timeout, if the elementsReady callback is never called, we render fallback buttons using the APIs provided by Google Pay (the Pay javascript library and https://developers.google.com/pay/api/web/guides/brand-guidelines ) and ApplePay (proprietary CSS rules only available in Safari https://developer.apple.com/documentation/apple_pay_on_the_web/displaying_apple_pay_buttons_using_css).

The downside of this approach is that, if more express payment methods are added, we'd need to implement a way to render the buttons ourselves. An alternative would be to just show placeholder buttons and don't worry too much about the styling, but please let me know what do you think.

Testing instructions

  • Make sure you are using the Cart block in your designated Cart page (The designated Cart page is configured in WooCommerce > Settings > Advanced > Page setup).
  • Edit the cart page in the Pages editor.
  • In a new tab, using the twenty-twenty four theme, go to Appearance > Editor > Templates and then edit the Page: Cart template.

Previous Result:
In the page editor, the supported Express Checkout buttons are rendered
image

In the template editor no buttons are rendered, and there's only a blank space:
image

Current Result (using this branch):
The Page editor remains the same, and buttons are rendered:
image

The Template editor now renders the express payment buttons:
image

Regression Testing:

Express Payment method should still work as expected for making purchases in the Product, Cart, and Checkout pages.


  • 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

@botwoo
Copy link
Collaborator

botwoo commented Jan 13, 2025

Test the build

Option 1. Jetpack Beta

  • Install and activate Jetpack Beta.
  • Use this build by searching for PR number 10141 or branch name fix/show-preview-for-express-checkout-buttons-in-editor 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: 8426805
  • Build time: 2025-02-28 09:46:50 UTC

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

Copy link
Contributor

github-actions bot commented Jan 13, 2025

Size Change: +741 B (0%)

Total Size: 1.29 MB

Filename Size Change
release/woocommerce-payments/dist/blocks-checkout-rtl.css 2.74 kB +73 B (+3%)
release/woocommerce-payments/dist/blocks-checkout.css 2.74 kB +73 B (+3%)
release/woocommerce-payments/dist/blocks-checkout.js 56.1 kB +595 B (+1%)
ℹ️ 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/cart-block.js 17.3 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.8 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/index.js 234 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/multi-currency.js 59.1 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.5 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/payment-gateways.js 40.1 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.6 kB
release/woocommerce-payments/dist/settings-rtl.css 11.4 kB
release/woocommerce-payments/dist/settings.css 11.4 kB
release/woocommerce-payments/dist/settings.js 224 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.8 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.4 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

@danielmx-dev danielmx-dev marked this pull request as ready for review January 16, 2025 19:07
@danielmx-dev danielmx-dev requested review from a team and frosso and removed request for a team January 16, 2025 19:47
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.

A few comments about memoization and dependencies I thought would be worth pointing out.

The fallback works well 👍 which makes me wonder - should we just use this fallback in preview mode, without worrying about the Stripe "preview" (which is not really a "preview")?

}, FALLBACK_BUTTON_WAIT_TIME );

return () => clearTimeout( handle );
}, [ isPreview, onElementsReadyCalled ] );
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
}, [ isPreview, onElementsReadyCalled ] );
}, [ isPreview ] );

The value returned by useRef shouldn't be a hook dependency.

if ( typeof buttonAttributes !== 'undefined' ) {
override.buttonHeight = Number( buttonAttributes.height );
}
const checkoutElementOptions = useMemo( () => {
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 sure useMemo is too useful, in this scenario. It seems that the memoized value would be discarded at each re-render, regardless.

buttonOptions comes from the useExpressCheckout hook. And, in turn, it is computed by the getExpressCheckoutButtonStyleSettings() utility.

The getExpressCheckoutButtonStyleSettings utility returns a new object each time it is invoked.

So, buttonOptions will have a new in-memory reference each time the hook/utility is invoked, making useMemo also execute each time the ExpressCheckoutComponent component re-renders.

I noticed that buttonAttributes prop is also re-computed at each re-render: https://github.com/woocommerce/woocommerce/blob/3fced787bf20112dae420cb97b3da617a929b7de/plugins/woocommerce-blocks/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment-methods.tsx#L33-L38

Is there a specific scenario you were thinking useMemo would have been useful in this component?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was leftover from when why was trying another approach. I'll remove the useMemo usage.

/**
* Internal dependencies
*/
import { getExpressCheckoutButtonAppearance } from 'wcpay/express-checkout/utils';
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this file is within the tokenized-express-checkout, it's best to include the functions from that directory, rather than the express-checkout directory (which doesn't include "tokenized" cart changes).

Otherwise the bundles are going to get mixed, and there might be modifications in the "tokenized" functions that wouldn't be considered.

Suggested change
import { getExpressCheckoutButtonAppearance } from 'wcpay/express-checkout/utils';
import { getExpressCheckoutButtonAppearance } from '../../utils';

Comment on lines 16 to 19
const appearance = useMemo(
() => getExpressCheckoutButtonAppearance( buttonAttributes ),
[ buttonAttributes ]
);
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar observation as the one I made in ExpressCheckoutComponent - is there a scenario where this useMemo would be useful?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this scenario I did it to avoid recalculating the border radius with every render, but the savings are probably negligible, I'll just remove it and see how it performs.

() => getExpressCheckoutButtonAppearance( buttonAttributes ),
[ buttonAttributes ]
);
const ref = useRef( null );
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be possible to use a less generic ref name for this variable?
It looks like it's used for the GooglePlay's container - maybe googlePlayContainerRef or googlePlayWrapperRef?

ref.current.appendChild( button );
} )();
}
}, [ ref, theme, expressPaymentMethod, borderRadius ] );
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
}, [ ref, theme, expressPaymentMethod, borderRadius ] );
}, [ theme, expressPaymentMethod, borderRadius ] );

Return values from useRefs shouldn't be used as part of hook dependencies.

ref.current
?.querySelector( 'button' )
?.style?.setProperty( 'border-radius', borderRadius );
}, [ ref, borderRadius ] );
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
}, [ ref, borderRadius ] );
}, [ borderRadius ] );

expressPaymentMethod === 'googlePay' &&
! renderGooglePayButtonPromise.current
) {
renderGooglePayButtonPromise.current = ( async () => {
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 curious about the assignment of this function to renderGooglePayButtonPromise.current - could we have gotten away with just assigning a flag to indicate if the loading had been called?

i.e.: something like this:

diff --git a/client/tokenized-express-checkout/blocks/components/express-checkout-button-preview.js b/client/tokenized-express-checkout/blocks/components/express-checkout-button-preview.js
index b612e0202..1fce7c0d4 100644
--- a/client/tokenized-express-checkout/blocks/components/express-checkout-button-preview.js
+++ b/client/tokenized-express-checkout/blocks/components/express-checkout-button-preview.js
@@ -18,7 +18,7 @@ const ExpressCheckoutButtonPreview = ( {
 		[ buttonAttributes ]
 	);
 	const ref = useRef( null );
-	const renderGooglePayButtonPromise = useRef( null );
+	const hasStartedLoadingGooglePlayButton = useRef( false );
 
 	const theme = options.buttonTheme[ expressPaymentMethod ];
 	const borderRadius = appearance.variables.borderRadius;
@@ -27,9 +27,10 @@ const ExpressCheckoutButtonPreview = ( {
 		if (
 			ref.current &&
 			expressPaymentMethod === 'googlePay' &&
-			! renderGooglePayButtonPromise.current
+			! hasStartedLoadingGooglePlayButton.current
 		) {
-			renderGooglePayButtonPromise.current = ( async () => {
+			hasStartedLoadingGooglePlayButton.current = true;
+			( async () => {
 				const targetDocument = ref.current.ownerDocument;
 				const targetWindow = targetDocument.defaultView;
 				if ( ! targetWindow.google?.payments?.api?.PaymentsClient ) {

@danielmx-dev
Copy link
Contributor Author

The fallback works well 👍 which makes me wonder - should we just use this fallback in preview mode, without worrying about the Stripe "preview" (which is not really a "preview")?

My initial reasoning was to always try using Stripe first to provide a Preview as close as possible to the live experience; however, at least from my testing, the appearance in both are really close, so it's probably fine to use the new component in all previews.

@danielmx-dev danielmx-dev requested a review from frosso January 22, 2025 18:26
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.

Excellent work! Thank you for entertaining my suggestions.
I added a very optional suggestion to simplify the body of the ExpressCheckoutButtonPreview component.

Now that we have ExpressCheckoutButtonPreview, I am even questioning whether ExpressCheckoutButtonPreview should be rendered within ExpressCheckoutComponent. In the edit property of tokenizedExpressCheckoutElementApplePay/tokenizedExpressCheckoutElementGooglePay we could just render ExpressCheckoutButtonPreview and get rid of the whole isPreview prop 🤷 but I'm guessing that would be too many changes in this PR.

Regardless, this already fixes the main issue 👍

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.

Still looking good, thank you for all the changes! :shipit:

@danielmx-dev
Copy link
Contributor Author

danielmx-dev commented Jan 30, 2025

@frosso Based in this comment, I added one more commit to avoid using the Preview component if a new express payment method gets added in the future and doesn't have an implementation yet. f26c0ad

@frosso
Copy link
Contributor

frosso commented Jan 30, 2025

Sounds good, thank you @danielmx-dev . Looks like a minor change 👍 it would have been fine to also add that in the future, if we needed to implement those methods :D

@danielmx-dev
Copy link
Contributor Author

@frosso It looks like we'll finally move forward with this solution. I have re-requested your review since I copied the solution to non-tokenized ECE as well.

@danielmx-dev danielmx-dev requested a review from frosso February 27, 2025 18:10
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.

Changes in the tokenized express checkout seem in line 👍

Checked cart & checkout pages in the editor, still working well:
Screenshot 2025-02-28 at 10 49 25 AM
Screenshot 2025-02-28 at 10 49 13 AM

As opposed to develop:
Screenshot 2025-02-28 at 10 48 50 AM
Screenshot 2025-02-28 at 10 48 46 AM

Placing the order from block-based cart/checkout still works.

:shipit:

@danielmx-dev danielmx-dev added this pull request to the merge queue Feb 28, 2025
Merged via the queue into develop with commit e4b3f99 Feb 28, 2025
28 checks passed
@danielmx-dev danielmx-dev deleted the fix/show-preview-for-express-checkout-buttons-in-editor branch February 28, 2025 15:31
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.

Express Payment button not visibile on the Cart template
3 participants