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..a327c90ba 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,70 @@ 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() { + if ( ! container ) { + return; + } + + 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 => { + 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 => { + 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.is-error' ); + if ( nestedError ) { + handleExpressCheckoutError( nestedError ); + return; + } + } + } + } ); + + observer.observe( container, { childList: true, subtree: true } ); + return observer; + } + + const expressCheckoutErrorObserver = observeExpressCheckoutErrors(); + if ( expressCheckoutErrorObserver ) { + const disconnect = () => expressCheckoutErrorObserver.disconnect(); + container.addEventListener( 'checkout-complete', disconnect ); + container.addEventListener( 'checkout-cancel', disconnect ); + window.addEventListener( 'beforeunload', disconnect ); + } } init(); }