Skip to content

Commit 9beb64e

Browse files
authored
fix: handle checkout form inline errors and expired sessions (#2255)
* fix: handle inline checkout form validation errors * fix: remove unwanted change * fix(modal-checkout): load new modal checkout session if session expires * fix: copilot feedback * fix: display initial error on unrecoverable error reload * fix: typo
1 parent 71380fb commit 9beb64e

File tree

4 files changed

+74
-9
lines changed

4 files changed

+74
-9
lines changed

includes/class-modal-checkout.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,7 @@ public static function enqueue_scripts() {
759759
'billing_details' => self::get_modal_checkout_labels( 'billing_details' ),
760760
'shipping_details' => self::get_modal_checkout_labels( 'shipping_details' ),
761761
'gift_recipient' => self::get_modal_checkout_labels( 'gift_recipient' ),
762+
'critical_error' => self::get_modal_checkout_labels( 'checkout_critical_error' ),
762763
'complete_button' => self::order_button_text_with_price(),
763764
],
764765
]
@@ -970,13 +971,18 @@ public static function enqueue_modal( $product_id = null ) {
970971
'register_modal_title' => self::get_modal_checkout_labels( 'register_modal_title' ),
971972
'signin_modal_title' => self::get_modal_checkout_labels( 'signin_modal_title' ),
972973
'thankyou_modal_title' => self::get_modal_checkout_labels( 'checkout_success' ),
974+
'critical_error' => self::get_modal_checkout_labels( 'checkout_critical_error' ),
973975
],
974976

975977
'processing_payment_messages' => [
976978
[
977979
'text' => __( 'Processing payment...', 'newspack-blocks' ),
978980
'delay' => 0,
979981
],
982+
[
983+
'text' => __( 'Processing payment...', 'newspack-blocks' ),
984+
'delay' => 250,
985+
],
980986
[
981987
'text' => __( 'Verifying details...', 'newspack-blocks' ),
982988
'delay' => 8000,
@@ -1551,7 +1557,7 @@ public static function woocommerce_cart_product_cannot_be_purchased_message( $me
15511557
*/
15521558
public static function hide_expiry_message_shop_link( $message ) {
15531559
if ( self::is_modal_checkout() && strpos( $message, 'Sorry, your session has expired' ) !== false ) {
1554-
return __( 'Could not complete this transaction. Please contact us for assistance.', 'newspack-blocks' );
1560+
return self::get_modal_checkout_labels( 'checkout_critical_error' );
15551561
}
15561562
return $message;
15571563
}
@@ -2029,6 +2035,7 @@ public static function get_modal_checkout_labels( $key = null ) {
20292035
'checkout_nyp_thankyou' => __( "Thank you for your generosity! We couldn't do this without you!", 'newspack-blocks' ),
20302036
'checkout_nyp_title' => __( 'Increase your support', 'newspack-blocks' ),
20312037
'checkout_nyp_apply' => __( 'Apply', 'newspack-blocks' ),
2038+
'checkout_critical_error' => __( 'We ran into a problem processing this request. Please try again.', 'newspack-blocks' ),
20322039
];
20332040

20342041
/**

src/modal-checkout/index.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import './checkout.scss';
88
* Internal dependencies
99
*/
1010
import { manageCheckoutAttempt, manageCheckoutSuccess, manageLoaded, managePagination } from './analytics';
11-
import { domReady } from './utils';
11+
import { domReady, onCheckoutPlaceOrderProcessing } from './utils';
1212

1313
( $ => {
1414
domReady( () => {
@@ -26,6 +26,7 @@ import { domReady } from './utils';
2626
const placeOrderStartEvent = new CustomEvent( 'checkout-place-order-start' );
2727
const placeOrderSuccessEvent = new CustomEvent( 'checkout-place-order-success' );
2828
const placeOrderErrorEvent = new CustomEvent( 'checkout-place-order-error' );
29+
const placeOrderCriticalErrorEvent = new CustomEvent( 'checkout-place-order-critical-error' );
2930

3031
function getEventHandlers( element, event ) {
3132
const events = $._data( element, 'events' );
@@ -121,16 +122,30 @@ import { domReady } from './utils';
121122
placedOrder = true;
122123
container.dispatchEvent( placeOrderStartEvent );
123124
} );
125+
onCheckoutPlaceOrderProcessing( container, function () {
126+
if ( ! placedOrder ) {
127+
return;
128+
}
129+
// If the form stops processing before the `checkout_place_order_success` event is fired, dispatch an error event.
130+
if ( ! $form.is( '.processing' ) ) {
131+
placedOrder = false;
132+
container.dispatchEvent( placeOrderErrorEvent );
133+
}
134+
} );
124135
$form.on( 'checkout_place_order_success', function () {
125136
placedOrder = false;
126137
container.dispatchEvent( placeOrderSuccessEvent );
127138
} );
128139

129-
$( document.body ).on( 'checkout_error', function () {
140+
$( document.body ).on( 'checkout_error', function ( event, errors ) {
130141
if ( ! placedOrder ) {
131142
return;
132143
}
133144
placedOrder = false;
145+
if ( errors && errors.indexOf( newspackBlocksModalCheckout.labels.critical_error ) >= 0 ) {
146+
container.dispatchEvent( placeOrderCriticalErrorEvent );
147+
return;
148+
}
134149
container.dispatchEvent( placeOrderErrorEvent );
135150
} );
136151
$form.on( 'update_checkout', function () {

src/modal-checkout/modal.js

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
onCheckoutCancel,
1919
onCheckoutPlaceOrderStart,
2020
onCheckoutPlaceOrderError,
21+
onCheckoutPlaceOrderCriticalError,
2122
createHiddenInput,
2223
triggerFormSubmit,
2324
getCheckoutData,
@@ -39,6 +40,9 @@ let inCheckoutIntent = false;
3940
// Checkout title.
4041
let checkoutTitle = newspackBlocksModal.labels.checkout_modal_title;
4142

43+
// Last-submitted checkout form.
44+
let activeCheckoutForm = null;
45+
4246
// Close the modal.
4347
const closeModal = el => {
4448
if ( el.overlayId && window.newspackReaderActivation?.overlays ) {
@@ -129,13 +133,14 @@ domReady( () => {
129133
processingPaymentTimeouts = [];
130134
};
131135

132-
const renderProcessingPaymentScreen = () => {
136+
const renderProcessingPaymentScreen = event => {
133137
spinner.querySelectorAll( `.${ PROCESSING_PAYMENT_TEXT_CLASS }` ).forEach( node => node.remove() );
134138
spinner.style.display = 'flex';
135139
clearProcessingPaymentTimeouts();
136140
processingPaymentText.textContent = PROCESSING_PAYMENT_MESSAGES[ 0 ]?.text ?? '';
137141
PROCESSING_PAYMENT_MESSAGES.slice( 1 ).forEach( ( { text, delay } ) => {
138142
const timeoutId = setTimeout( () => {
143+
event.target.dispatchEvent( new CustomEvent( 'checkout-place-order-processing' ) );
139144
processingPaymentText.textContent = text;
140145
}, delay );
141146
processingPaymentTimeouts.push( timeoutId );
@@ -164,6 +169,15 @@ domReady( () => {
164169
summaryTextNode.textContent = checkoutData.price_summary;
165170
}
166171

172+
// Display initial errors if any.
173+
if ( modalCheckout.initialErrors ) {
174+
const errorContainer = document.createElement( 'div' );
175+
errorContainer.classList.add( 'woocommerce-error' );
176+
errorContainer.textContent = modalCheckout.initialErrors;
177+
container.prepend( errorContainer );
178+
delete modalCheckout.initialErrors;
179+
}
180+
167181
// Revert modal title and width default value.
168182
setModalSize();
169183
setModalTitle( checkoutTitle );
@@ -192,6 +206,20 @@ domReady( () => {
192206

193207
hideProcessingPaymentScreen();
194208
} );
209+
210+
// Resubmit modal checkout form if an unrecoverable error is encountered.
211+
const refreshCheckout = form => {
212+
if ( ! form ) {
213+
return;
214+
}
215+
closeCheckout();
216+
spinner.style.display = 'none';
217+
modalCheckout.initialErrors = newspackBlocksModal.labels.critical_error;
218+
form.requestSubmit( form.querySelector( 'button[type="submit"]' ) );
219+
hideProcessingPaymentScreen();
220+
};
221+
222+
onCheckoutPlaceOrderCriticalError( container, () => refreshCheckout( activeCheckoutForm ) );
195223
}
196224

197225
iframeReady( iframe, handleIframeReady, () => {
@@ -468,6 +496,7 @@ domReady( () => {
468496
// Append product data info to the modal, so we can grab it for GA4 events outside of the iframe.
469497
document.getElementById( 'newspack_modal_checkout' ).setAttribute( 'data-checkout', JSON.stringify( checkoutData ) );
470498
}
499+
activeCheckoutForm = form;
471500
};
472501

473502
/**

src/modal-checkout/utils.js

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -180,16 +180,16 @@ export function onCheckoutPlaceOrderStart( container, callback ) {
180180
}
181181

182182
/**
183-
* Run a callback when the checkout place order succeeds.
183+
* Run a callback when the checkout place order is processing.
184184
*
185185
* @param {Object} container The container element inside the iframe document.
186-
* @param {Function} callback The callback to execute when the checkout place order succeeds.
186+
* @param {Function} callback The callback to execute when the checkout place order is processing.
187187
*/
188-
export function onCheckoutPlaceOrderSuccess( container, callback ) {
189-
if ( container.checkoutPlaceOrderSuccess ) {
188+
export function onCheckoutPlaceOrderProcessing( container, callback ) {
189+
if ( container.checkoutPlaceOrderProcessing ) {
190190
callback();
191191
} else {
192-
container.addEventListener( 'checkout-place-order-success', callback );
192+
container.addEventListener( 'checkout-place-order-processing', callback );
193193
}
194194
}
195195

@@ -207,6 +207,20 @@ export function onCheckoutPlaceOrderError( container, callback ) {
207207
}
208208
}
209209

210+
/**
211+
* Run a callback when the checkout place order fails in an unrecoverable state.
212+
*
213+
* @param {Object} container The container element inside the iframe document.
214+
* @param {Function} callback The callback to execute when the checkout place order fails in an unrecoverable state.
215+
*/
216+
export function onCheckoutPlaceOrderCriticalError( container, callback ) {
217+
if ( container.checkoutPlaceOrderCriticalError ) {
218+
callback();
219+
} else {
220+
container.addEventListener( 'checkout-place-order-critical-error', callback );
221+
}
222+
}
223+
210224
/**
211225
* Run a callback when the checkout is complete.
212226
*

0 commit comments

Comments
 (0)