From f7fcac4d116d82236edff2fd77cb391843128ede Mon Sep 17 00:00:00 2001 From: Ramon Corrales Date: Tue, 6 Jan 2026 22:27:56 -0500 Subject: [PATCH 1/3] feat: improve express checkout handling by surfacing errors --- src/modal-checkout/checkout.scss | 4 +++ src/modal-checkout/index.js | 51 +++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/modal-checkout/checkout.scss b/src/modal-checkout/checkout.scss index 01b88255b..53090590e 100644 --- a/src/modal-checkout/checkout.scss +++ b/src/modal-checkout/checkout.scss @@ -985,6 +985,10 @@ margin-top: 0; } + .woocommerce .woocommerce-notices-wrapper .woocommerce-error.newspack-ui__notice--error { + padding: var(--newspack-ui-spacer-2, 12px); + } + .modal-processing { opacity: 0.5; } diff --git a/src/modal-checkout/index.js b/src/modal-checkout/index.js index 5238186d5..5b75d9d05 100644 --- a/src/modal-checkout/index.js +++ b/src/modal-checkout/index.js @@ -40,8 +40,9 @@ import { domReady, onCheckoutPlaceOrderProcessing } from './utils'; } function clearNotices() { + $( '.woocommerce-notices-wrapper' ).empty(); $( - `.woocommerce-NoticeGroup-checkout, .${ CLASS_PREFIX }__inline-error, .woocommerce-error, .woocommerce-message, .wc-block-components-notice-banner, .woocommerce-notices-wrapper` + `.woocommerce-NoticeGroup-checkout, .${ CLASS_PREFIX }__inline-error, .woocommerce-error, .woocommerce-message, .wc-block-components-notice-banner` ).remove(); } @@ -786,6 +787,54 @@ import { domReady, onCheckoutPlaceOrderProcessing } from './utils'; form.removeClass( 'modal-processing' ); return true; } + + /** + * Watch for Express Checkout errors added to the checkout container. + * Express Checkout (GPay/Apple Pay) uses the Store API which doesn't trigger + * the standard WooCommerce checkout_error event. + */ + function observeExpressCheckoutErrors() { + const ERROR_HANDLED_ATTR = 'data-newspack-error-handled'; + + const handleExpressCheckoutError = errorNode => { + if ( errorNode.hasAttribute( ERROR_HANDLED_ATTR ) ) { + return; + } + errorNode.setAttribute( ERROR_HANDLED_ATTR, 'true' ); + + $( errorNode ).addClass( `${ CLASS_PREFIX }__notice ${ CLASS_PREFIX }__notice--error` ); + container.dispatchEvent( placeOrderErrorEvent ); + errorNode.scrollIntoView( { behavior: 'smooth', block: 'center' } ); + }; + + const isErrorNode = node => { + return node.classList?.contains( 'woocommerce-error' ) || node.classList?.contains( 'wc-block-components-notice-banner' ); + }; + + const observer = new MutationObserver( mutations => { + for ( const mutation of mutations ) { + for ( const node of mutation.addedNodes ) { + if ( node.nodeType !== Node.ELEMENT_NODE ) { + continue; + } + if ( isErrorNode( node ) ) { + handleExpressCheckoutError( node ); + return; + } + const nestedError = node.querySelector?.( '.woocommerce-error, .wc-block-components-notice-banner' ); + if ( nestedError ) { + handleExpressCheckoutError( nestedError ); + return; + } + } + } + } ); + + const observeTarget = container || document.body; + observer.observe( observeTarget, { childList: true, subtree: true } ); + } + + observeExpressCheckoutErrors(); } init(); } From b6701b79cb903bfd0f5d9b9fb093debeb304d9cc Mon Sep 17 00:00:00 2001 From: Ramon Corrales Date: Tue, 6 Jan 2026 22:35:29 -0500 Subject: [PATCH 2/3] refactor: refine modal checkout error observer to target only error notices --- src/modal-checkout/index.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/modal-checkout/index.js b/src/modal-checkout/index.js index 5b75d9d05..0d58ed0dd 100644 --- a/src/modal-checkout/index.js +++ b/src/modal-checkout/index.js @@ -794,6 +794,10 @@ import { domReady, onCheckoutPlaceOrderProcessing } from './utils'; * the standard WooCommerce checkout_error event. */ function observeExpressCheckoutErrors() { + if ( ! container ) { + return; + } + const ERROR_HANDLED_ATTR = 'data-newspack-error-handled'; const handleExpressCheckoutError = errorNode => { @@ -808,7 +812,13 @@ import { domReady, onCheckoutPlaceOrderProcessing } from './utils'; }; const isErrorNode = node => { - return node.classList?.contains( 'woocommerce-error' ) || node.classList?.contains( 'wc-block-components-notice-banner' ); + if ( node.classList?.contains( 'woocommerce-error' ) ) { + return true; + } + if ( node.classList?.contains( 'wc-block-components-notice-banner' ) && node.classList?.contains( 'is-error' ) ) { + return true; + } + return false; }; const observer = new MutationObserver( mutations => { @@ -821,7 +831,7 @@ import { domReady, onCheckoutPlaceOrderProcessing } from './utils'; handleExpressCheckoutError( node ); return; } - const nestedError = node.querySelector?.( '.woocommerce-error, .wc-block-components-notice-banner' ); + const nestedError = node.querySelector?.( '.woocommerce-error, .wc-block-components-notice-banner.is-error' ); if ( nestedError ) { handleExpressCheckoutError( nestedError ); return; @@ -830,8 +840,7 @@ import { domReady, onCheckoutPlaceOrderProcessing } from './utils'; } } ); - const observeTarget = container || document.body; - observer.observe( observeTarget, { childList: true, subtree: true } ); + observer.observe( container, { childList: true, subtree: true } ); } observeExpressCheckoutErrors(); From 8e71d83acf7ad301d7dc5ad2d9af4fb37c8bd2b5 Mon Sep 17 00:00:00 2001 From: Ramon Corrales Date: Tue, 6 Jan 2026 23:01:43 -0500 Subject: [PATCH 3/3] refactor: disconnect express checkout error observer on modal close --- src/modal-checkout/index.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/modal-checkout/index.js b/src/modal-checkout/index.js index 0d58ed0dd..a327c90ba 100644 --- a/src/modal-checkout/index.js +++ b/src/modal-checkout/index.js @@ -841,9 +841,16 @@ import { domReady, onCheckoutPlaceOrderProcessing } from './utils'; } ); observer.observe( container, { childList: true, subtree: true } ); + return observer; } - observeExpressCheckoutErrors(); + const expressCheckoutErrorObserver = observeExpressCheckoutErrors(); + if ( expressCheckoutErrorObserver ) { + const disconnect = () => expressCheckoutErrorObserver.disconnect(); + container.addEventListener( 'checkout-complete', disconnect ); + container.addEventListener( 'checkout-cancel', disconnect ); + window.addEventListener( 'beforeunload', disconnect ); + } } init(); }