PageSourceSearch

https://glas2021.com/content/themes/exhibition/assets/js/checkout.js?ver=9a8b449f

js glas2021.com collected 2026-09-28 09:42:50 UTC 32,079 bytes, 1,033 lines download raw bytes

1'use strict';
2
3(function ( $ ) {
4	$.themeCheckout = {
5
6		stripe: null,
7		card: null,
8		elements: null,
9		order_id: null,
10		checkoutSubmitted: false,
11		submitCheckout: () => {},
12
13		/**
14		 * Initializes the checkout process.
15		 */
16		init: function () {
17			$.themeCheckout.initProps();
18			$.themeCheckout.attachEvents();
19			$.themeCheckout.initStripeCardElement();
20		},
21
22		/**
23		 * Initializes properties for the checkout process.
24		 */
25		initProps: function () {
26			$.themeCheckout.$pmSelector                  = $( '#pmt-method' );
27			$.themeCheckout.$pmMethodSelect              = $( '#payment-method-select' );
28			$.themeCheckout.$ccPaymentMethodSelector     = $( '.payment-method-type-selector' );
29			$.themeCheckout.$ccPaymentMethodSelectorItem = $( '.payment-method-option' );
30			$.themeCheckout.$ccField                     = $( '#payment-form' );
31			$.themeCheckout.$paymentEmail                = $( '#email' );
32			$.themeCheckout.$terms                       = $( '#terms' );
33			$.themeCheckout.$submitButton                = $( '#submit-purchase' );
34			$.themeCheckout.$cardErrors                  = $( '#card-errors' );
35			$.themeCheckout.hasAdditionalpmTypeS         = false;
36			$.themeCheckout.ccMethod                     = 'manual-cc';
37
38			$.themeCheckout.stripePaymentRequestEvent = null;
39		},
40
41		/**
42		 * Triggers a Sentry message with the given message and extras.
43		 *
44		 * @param message
45		 * @param extras
46		 */
47		triggerSentryWithExtras: function ( message, extras ) {
48			// Log a Sentry error
49			if ( typeof Sentry !== 'undefined' ) {
50				Sentry.withScope( function ( scope ) {
51					scope.setLevel( 'error' );
52
53					if ( extras ) {
54						scope.setExtras( extras );
55					}
56					Sentry.captureMessage( message );
57				} );
58			}
59		},
60
61		/**
62		 * Attaches event listeners for payment method selection.
63		 */
64		attachEvents: function () {
65			$.themeCheckout.$ccPaymentMethodSelectorItem.on( 'click', function ( e ) {
66				e.preventDefault();
67
68				// Do not allow to change payment method if checkout is already submitted
69				if ( $.themeCheckout.checkoutSubmitted ) {
70					return;
71				}
72
73				$.themeCheckout.selectPaymentMethodType( $( this ) );
74			} );
75		},
76
77		/**
78		 * Escapes HTML special characters in the given text.
79		 *
80		 * @returns {string}
81		 */
82		escHtml: function ( text ) {
83			return $( '<div>' ).text( text ).html();
84		},
85
86		/**
87		 * Checks if the selected payment method is a manual credit card payment.
88		 *
89		 * @returns {boolean}
90		 */
91		isManualCreditCardPayment: function () {
92			return $.themeCheckout.ccMethod === 'manual-cc';
93		},
94
95		/**
96		 * Initializes Stripe Card Element on the given element selector.
97		 * Requires STRIPE_PUBLIC_KEY and optionally STRIPE_CONNECTED_ACCOUNT_ID to be defined globally.
98		 * Adds listeners for card element changes to display validation errors.
99		 */
100		initStripeCardElement: function () {
101
102			if ( $( '#card-element' ).length === 0 ) {
103				return;
104			}
105
106			const options  = STRIPE_CONNECTED_ACCOUNT_ID ? { stripeAccount: STRIPE_CONNECTED_ACCOUNT_ID } : {};
107			const stripe   = Stripe( STRIPE_PUBLIC_KEY, options );
108			const elements = stripe.elements();
109
110			// Create an instance of the card Element
111			const card = elements.create( 'card' );
112
113			// Add an instance of the card Element into the `card-element` <div>
114			card.mount( '#card-element' );
115
116			// Handle real-time validation errors from the card Element.
117			card.addEventListener( 'change', function ( event ) {
118				const displayError       = document.getElementById( 'card-errors' );
119				displayError.textContent = event.error ? event.error.message : '';
120			} );
121
122			$.themeCheckout.stripe   = stripe;
123			$.themeCheckout.card     = card;
124			$.themeCheckout.elements = elements;
125
126			$.themeCheckout.initPaymentRequest();
127		},
128
129		/**
130		 * Initializes the Payment Request API for additional payment methods like Apple Pay and Google Pay.
131		 */
132		initPaymentRequest: function () {
133			// Skip extra Payment Methods initialization if there is no element for them.
134			$.themeCheckout.paymentRequest = $.themeCheckout.stripe.paymentRequest( {
135				country: window.themeSettings.country_code || 'US',
136				currency: (window.themeSettings.currency_code || 'usd').toLowerCase(),
137				total: {
138					label: 'Total',
139					amount: 0,
140				},
141			} );
142
143			// Check availability of the Payment Request API
144			$.themeCheckout.paymentRequest.canMakePayment().then( function ( enabledMethods ) {
145				$.themeCheckout.displayAvailablePaymentMethods( enabledMethods );
146			}, function () {
147				$.themeCheckout.$ccPaymentMethodSelector.slideUp();
148			} );
149
150			// Handle payment
151			$.themeCheckout.paymentRequest.on( 'paymentmethod', async ( ev ) => {
152				$.themeCheckout.stripePaymentRequestEvent = ev;
153				$.themeCheckout.submitCheckout( ev );
154			} );
155
156			// Handle closing of the payment request UI
157			$.themeCheckout.paymentRequest.on( 'cancel', async ( ev ) => {
158
159				// Do not display cancellation message if checkout is already submitted
160				if ( $.themeCheckout.checkoutSubmitted ) {
161					return;
162				}
163
164				$.themeCheckout.displayCheckoutError( { error: { message: 'Payment cancelled.' } } );
165			} );
166		},
167
168		/**
169		 * Updates the order total in the Payment Request API.
170		 *
171		 * @param newTotal
172		 */
173		updateOrderTotal: function ( newTotal ) {
174			if ( !$.themeCheckout.paymentRequest ) {
175				return;
176			}
177
178			if ( typeof newTotal === 'string' ) {
179				try {
180					newTotal = parseFloat( newTotal.replace( /[^0-9.-]+/g, '' ) );
181				} catch ( e ) {
182					newTotal = 0;
183				}
184			}
185
186			// Round to avoid floating point precision issues with Stripe amounts
187			newTotal = Math.round( Math.max( newTotal, 0 ) * 100 );
188
189			$.themeCheckout.paymentRequest.update( {
190				total: {
191					label: 'Total',
192					amount: newTotal
193				}
194			} );
195		},
196
197		/**
198		 * Displays available additional payment methods in the UI.
199		 *
200		 * @param enabledMethods
201		 */
202		displayAvailablePaymentMethods: function ( enabledMethods ) {
203			if ( !enabledMethods ) {
204				return;
205			}
206
207			// Show Payment Method selector
208			if ( enabledMethods.applePay ) {
209
210				// Show Apple Pay option
211				$.themeCheckout.$ccPaymentMethodSelector.find( '.apple-pay' ).removeClass( 'hidden' );
212			}
213			if ( enabledMethods.googlePay ) {
214
215				// Show Google Pay option
216				$.themeCheckout.$ccPaymentMethodSelector.find( '.google-pay' ).removeClass( 'hidden' );
217			}
218
219			$.themeCheckout.hasAdditionalPMs = enabledMethods && (enabledMethods.googlePay || enabledMethods.applePay);
220			if ( $.themeCheckout.hasAdditionalPMs && !$.themeCheckout.orderIsFreeCheckout() ) {
221				$.themeCheckout.$ccPaymentMethodSelector.slideDown();
222			}
223		},
224
225		/**
226		 * Handles selection of payment method type in the UI.
227		 *
228		 * @param $ccPaymentMethodItem
229		 */
230		selectPaymentMethodType: function ( $ccPaymentMethodItem ) {
231
232			const shouldChangeCCFields = !!$ccPaymentMethodItem;
233
234			$ccPaymentMethodItem = $ccPaymentMethodItem || $.themeCheckout.$ccPaymentMethodSelectorItem.first();
235
236			// Check selected payment method type
237			$.themeCheckout.ccMethod = $ccPaymentMethodItem.data( 'pm-type' );
238
239			// Set current payment method as selected
240			$.themeCheckout.$ccPaymentMethodSelectorItem.removeClass( 'selected' );
241			$ccPaymentMethodItem.addClass( 'selected' );
242
243			// Hide or show credit card fields based on selected payment method
244			if ( $ccPaymentMethodItem.hasClass( 'credit-card' ) ) {
245				if ( shouldChangeCCFields ) {
246					$.themeCheckout.$ccField.slideDown();
247					$.themeCheckout.$pmMethodSelect.slideDown();
248				}
249			} else {
250				if ( shouldChangeCCFields ) {
251					$.themeCheckout.$ccField.slideUp();
252					$.themeCheckout.$pmMethodSelect.slideUp();
253				}
254			}
255		},
256
257		/**
258		 * Returns true when the current order total is zero.
259		 */
260		orderIsFreeCheckout: function () {
261			var total = parseFloat( $( '.total_purchase_price' ).text().replace( /[^0-9.-]+/g, '' ) ) || 0;
262			return total === 0;
263		},
264
265		/**
266		 * Shows credit card fields and payment UI for a non-free order.
267		 */
268		showCreditCardFields: function () {
269			if ( $.themeCheckout.isManualCreditCardPayment() ) {
270				$.themeCheckout.$ccField.slideDown();
271			}
272			if ( $.themeCheckout.hasAdditionalPMs ) {
273				$.themeCheckout.$ccPaymentMethodSelector.slideDown();
274			}
275		},
276
277		/**
278		 * Hides credit card fields and payment UI for a free order.
279		 */
280		hideCreditCardFields: function () {
281			$.themeCheckout.$ccField.slideUp();
282			if ( $.themeCheckout.hasAdditionalPMs ) {
283				$.themeCheckout.$ccPaymentMethodSelector.slideUp();
284			}
285			$.themeCheckout.$pmMethodSelect.hide();
286			$.themeCheckout.selectPaymentMethodType();
287		},
288
289		/**
290		 * Shows or hides payment fields based on whether the order is free.
291		 */
292		maybeToggleCreditCardFields: function () {
293			if ( $.themeCheckout.orderIsFreeCheckout() ) {
294				$.themeCheckout.hideCreditCardFields();
295				$.themeCheckout.$pmSelector.val( 'cash' );
296				return;
297			}
298
299			// Snapshot before show() — an admin may have explicitly chosen cash on a paid order.
300			var keepCash = $.themeCheckout.$pmMethodSelect.is( ':visible' )
301				&& $.themeCheckout.$pmSelector.val() === 'cash';
302
303			if ( !keepCash ) {
304				$.themeCheckout.$pmSelector.val( 'cc' );
305				$.themeCheckout.showCreditCardFields();
306			}
307
308			// Keep hidden when a wallet method (Apple/Google Pay) is active.
309			if ( keepCash || $.themeCheckout.isManualCreditCardPayment() ) {
310				$.themeCheckout.$pmMethodSelect.show();
311			}
312
313			$( '.payment-section-title' ).show();
314		},
315
316		/**
317		 * Completes the Payment Method request based on the success parameter.
318		 *
319		 * @param success
320		 */
321		completePaymentMethodRequest: function ( success ) {
322			if ( !$.themeCheckout.stripePaymentRequestEvent ) {
323				return;
324			}
325
326			if ( success ) {
327				$.themeCheckout.stripePaymentRequestEvent.complete( 'success' );
328			} else {
329				$.themeCheckout.stripePaymentRequestEvent.complete( 'fail' );
330			}
331			$.themeCheckout.stripePaymentRequestEvent = null;
332		},
333
334		/**
335		 * Validates the required fields in the checkout form.
336		 *
337		 * @param $requiredFields
338		 */
339		validateRequiredFields: function ( $requiredFields ) {
340			if ( typeof $requiredFields.each === 'function' ) {
341				$requiredFields.each( function () {
342					$.themeCheckout.validateCheckoutInputs( $( this ) );
343				} );
344			} else {
345				$.each( $requiredFields, function ( i, $field ) {
346					$.themeCheckout.validateCheckoutInputs( $field );
347				} );
348			}
349
350			$.themeCheckout.validateCheckoutEmail( $.themeCheckout.$paymentEmail );
351
352			if ( $.themeCheckout.$terms.length ) {
353				$.themeCheckout.validateCheckoutTermsCheckbox( $.themeCheckout.$terms );
354			}
355		},
356
357		/**
358		 * Displays checkout error messages to the user.
359		 *
360		 * @param result
361		 */
362		displayCheckoutError: function ( result ) {
363			// Inform the user if there was an error
364			$.themeCheckout.$cardErrors.html( result.error.message || 'Unknown error' );
365			$.themeCheckout.$submitButton.prop( 'disabled', false ).val( 'Purchase' );
366		},
367
368		/**
369		 * Submits the purchase request if the form is valid.
370		 *
371		 * @param $order
372		 * @param submitOrderCallback
373		 */
374		submitPurchaseRequestIfValidForm: function ( $order, submitOrderCallback ) {
375			submitOrderCallback = submitOrderCallback || function ( order, result ) {};
376			var $error          = $( '.error' );
377			$error.next().hide();
378
379			// Check for validation errors, display them and stop submission
380			if ( $error.length ) {
381				$error.next().show();
382				$( '.error-message.submit-error' ).text( 'Please fix the errors before continuing.' ).show();
383				$.themeCheckout.$submitButton.prop( 'disabled', false ).val( 'Purchase' );
384				return;
385			}
386
387			$( '.error-message.submit-error' ).hide();
388
389			const isCreditCardPayment = $order.total > 0 && ($.themeCheckout.$pmSelector.length === 0 || $.themeCheckout.$pmSelector.val() === 'cc');
390
391			// Submit order without payment method if payment method is not credit card
392			if ( !isCreditCardPayment ) {
393				submitOrderCallback( $order, {} );
394				return;
395			}
396
397			// Submit order with payment method if payment method is credit card
398			if ( $.themeCheckout.isManualCreditCardPayment() ) {
399				$.themeCheckout.stripe.createPaymentMethod( {
400					type: 'card',
401					card: $.themeCheckout.card,
402					billing_details: {
403						name: $( '#first_name' ).val() + ' ' + $( '#last_name' ).val(),
404						email: $( '#email' ).val(),
405						address: {
406							postal_code: $( '#mailing_postal_code' ).val()
407						}
408					}
409				} ).then( function ( result ) {
410					// Submit the checkout and pass the PaymentMethod.
411					submitOrderCallback( $order, result );
412				} );
413			} else {
414
415				// Request payment method and then submit checkout for alternative payment methods (Apple Pay, Google Pay)
416				$.themeCheckout.submitCheckout = function ( result ) {
417					submitOrderCallback( $order, result || {} );
418				};
419				$.themeCheckout.requestPaymentMethod();
420			}
421		},
422
423		/**
424		 * Initiates the Payment Method request.
425		 */
426		requestPaymentMethod: function () {
427			if ( $.themeCheckout.paymentRequest ) {
428				$.themeCheckout.paymentRequest.show();
429			}
430		},
431
432		/**
433		 * Retrieves a payment intent with up to 3 retries (1s, 2s, 4s backoff) on connection errors.
434		 *
435		 * @param clientSecret
436		 * @returns {PaymentIntent|null}
437		 */
438		retrievePaymentIntentWithRetry: async function ( clientSecret ) {
439			const retryDelays = [1000, 2000, 4000];
440			const maxAttempts = 3;
441
442			for ( let attempt = 0; attempt < maxAttempts; attempt++ ) {
443				await new Promise( resolve => setTimeout( resolve, retryDelays[attempt] ) );
444
445				const result = await $.themeCheckout.stripe.retrievePaymentIntent( clientSecret );
446
447				if ( result.paymentIntent ) {
448					return result.paymentIntent;
449				}
450
451				// Only retry on connection errors; any other error won't resolve on retry.
452				if ( !result.error || result.error.type !== 'api_connection_error' ) {
453					return null;
454				}
455			}
456
457			return null;
458		},
459
460		/**
461		 * Handles an api_connection_error from confirmCheckoutCardPayment.
462		 * Checks if confirmation went through by retrieving the intent; proceeds to finalization if so,
463		 * otherwise re-enables the form for retry.
464		 *
465		 * @param stripeError
466		 * @param clientSecret
467		 * @param callback
468		 * @param errorCallback
469		 */
470		onCardConfirmationConnectionError: async function ( stripeError, clientSecret, callback, errorCallback ) {
471			const paymentIntent = await $.themeCheckout.retrievePaymentIntentWithRetry( clientSecret );
472
473			if ( paymentIntent && paymentIntent.status === 'requires_capture' ) {
474				callback();
475				return;
476			}
477
478			// Could not confirm intent status — re-enable so customer can retry.
479			const message = 'We are experiencing issues connecting to our payments provider. Please check your internet connection.';
480			$.themeCheckout.$cardErrors.html( message );
481			$.themeCheckout.setIsCheckoutSubmitted( false );
482			$( document ).trigger( 'checkout-error', { type: 'error', details: message } );
483			$.themeCheckout.triggerSentryWithExtras( '[ Payment confirmation connection error ] ' + stripeError.message, {
484				error: stripeError,
485				orderId: $.themeCheckout.order_id || null,
486			} );
487			if ( errorCallback ) {
488				errorCallback( message );
489			}
490		},
491
492		/**
493		 * Handles a Stripe error from confirmCheckoutCardPayment (e.g. card declined).
494		 * Re-enables the form with the error message.
495		 *
496		 * @param stripeError
497		 * @param errorCallback
498		 */
499		onCardConfirmationError: function ( stripeError, errorCallback ) {
500			$.themeCheckout.triggerSentryWithExtras( '[ Payment confirmation error ] ' + stripeError.message, {
501				error: stripeError,
502				orderId: $.themeCheckout.order_id || null,
503			} );
504			$( document ).trigger( 'checkout-error', { type: 'error', details: stripeError.message } );
505			console.error( '[ Payment Confirmation error ] ' + stripeError.message, stripeError );
506			if ( errorCallback ) {
507				errorCallback( stripeError.message );
508			}
509		},
510
511		/**
512		 * Confirms a Stripe card payment using the provided client secret.
513		 * Calls the provided callback on success or errorCallback on failure.
514		 *
515		 * @param clientSecret
516		 * @param callback
517		 * @param errorCallback
518		 */
519		confirmCheckoutCardPayment: async function ( clientSecret, callback, errorCallback ) {
520			const paymentMethod = $.themeCheckout.stripePaymentRequestEvent?.paymentMethod?.id || {
521				card: $.themeCheckout.card,
522				billing_details: {
523					name: $( '#first_name' ).val() + ' ' + $( '#last_name' ).val(),
524				},
525			};
526			$.themeCheckout.stripe.confirmCardPayment( clientSecret, { payment_method: paymentMethod } )
527				.then( async resp => {
528					if ( resp.error ) {
529						if ( resp.error.type === 'api_connection_error' ) {
530							await $.themeCheckout.onCardConfirmationConnectionError( resp.error, clientSecret, callback, errorCallback );
531						} else {
532							$.themeCheckout.onCardConfirmationError( resp.error, errorCallback );
533						}
534					} else {
535						callback();
536					}
537				} );
538		},
539
540		/**
541		 * Finalizes an authenticated payment for the given order ID.
542		 * Sends an AJAX request to the server to complete the payment process.
543		 * Calls the provided callback on success or errorCallback on failure.
544		 *
545		 * @param orderId
546		 * @param callback
547		 * @param errorCallback
548		 */
549		capturePaymentAndFinalizePurchase: async function ( data, callback, errorCallback ) {
550
551			data.action = data.actions?.finalize || 'filmbot_finalize_purchase';
552
553			$.ajax( {
554				url: themeSettings.ajaxUrl,
555				method: 'POST',
556				timeout: 60000,
557				data: data,
558				success: function ( response ) {
559					if ( response.success ) {
560						if ( typeof callback === 'function' ) {
561							callback( response.orderDetails );
562						}
563					} else {
564						// some error...
565						var error = response.data[0] || 'Unknown Error';
566
567						$.themeCheckout.triggerSentryWithExtras( '[ Finalize purchase error ] ' + error, {
568							error: error,
569							orderId: data.order_id || null,
570							response: response
571						} );
572
573						if ( typeof errorCallback === 'function' ) {
574							errorCallback( error );
575						}
576					}
577				},
578				error: function ( xhr, status, error ) {
579					$.themeCheckout.triggerSentryWithExtras( '[ Finalize purchase AJAX error ]', {
580						xhr: xhr,
581						status: status,
582						error: error,
583						orderId: data.order_id || null
584					} );
585
586					if ( typeof errorCallback === 'function' ) {
587						errorCallback( "Your order may have been successful, please check your email for confirmation. If you don't see it within a few minutes, contact us." );
588						$( '#submit-purchase' ).prop( 'disabled', true ).val( 'CONNECTION ISSUE' );
589					}
590				}
591			} );
592		},
593
594
595		/**
596		 * Checks the payment method for the given order data
597		 * and updates it to 'cash' if the total amount is zero.
598		 *
599		 * @param data
600		 *
601		 * @returns object
602		 */
603		checkPaymentMethod: function ( data ) {
604
605			let total = data.order.total;
606
607			total = typeof total === 'string' ? parseFloat( total ) : total;
608
609			if ( total === 0 ) {
610				data.pm = 'cash';
611			}
612
613			return data;
614		},
615
616		/**
617		 * Submits the checkout data to the server via AJAX.
618		 * Handles payment confirmation if required.
619		 * Calls the provided callback on success or errorCallback on failure.
620		 *
621		 * @param data
622		 * @param callback
623		 * @param errorCallback
624		 */
625		checkoutSubmitRequest: function ( data, callback, errorCallback ) {
626
627			// Do not allow multiple submissions of the checkout form
628			if ( $.themeCheckout.checkoutSubmitted ) {
629				return;
630			}
631
632			data = $.themeCheckout.checkPaymentMethod( data );
633
634			if ( data.pm === 'cc' ) {
635				$.themeCheckout.initializePurchaseRequest( data, callback, errorCallback );
636				return;
637			}
638
639			// Disable the submit button to prevent multiple submissions while processing the order.
640			$.themeCheckout.setIsCheckoutSubmitted( true );
641
642			$.ajax( {
643				url: themeSettings.ajaxUrl,
644				method: 'POST',
645				timeout: 60000,
646				data: data,
647				success: function ( response ) {
648
649					if ( response.success && response.orderDetails ) {
650						if ( typeof callback === 'function' ) {
651							callback( response.orderDetails );
652						}
653					} else {
654						// some error...
655						$.themeCheckout.triggerSentryWithExtras( '[ Create Purchase error ]', {
656							response: response,
657							orderId: $.themeCheckout.order_id || null
658						} );
659
660						if ( typeof errorCallback === 'function' ) {
661							errorCallback( response );
662						}
663					}
664				},
665				error: $.themeCheckout.orderInitializationError
666			} );
667		},
668
669		onPaymentAuthorized: function ( initResponse, data, callback, errorCallback ) {
670
671			$.themeCheckout.completePaymentMethodRequest( true );
672
673			$.themeCheckout.capturePaymentAndFinalizePurchase( { ...data, order_id: initResponse.order_id }, () => {
674				if ( typeof callback === 'function' ) {
675					callback( initResponse.orderDetails );
676				}
677			}, ( error ) => {
678
679				$.themeCheckout.completePaymentMethodRequest( false );
680
681				if ( typeof errorCallback === 'function' ) {
682					errorCallback( { data: error } );
683				}
684			} );
685		},
686
687		/**
688		 * Submits the checkout data to the server via AJAX.
689		 * Handles payment confirmation if required.
690		 * Calls the provided callback on success or errorCallback on failure.
691		 *
692		 * @param data
693		 * @param callback
694		 * @param errorCallback
695		 */
696		initializePurchaseRequest: function ( data, callback, errorCallback ) {
697			data.action = data.actions?.initialize || 'filmbot_initialize_purchase';
698
699			if ( $.themeCheckout.order_id ) {
700				data.order_id = $.themeCheckout.order_id;
701			}
702
703			// Disable the submit button to prevent multiple submissions while processing the order.
704			$.themeCheckout.setIsCheckoutSubmitted( true );
705
706			$.ajax( {
707				url: themeSettings.ajaxUrl,
708				method: 'POST',
709				timeout: 60000,
710				data: data,
711				success: function ( response ) {
712					if ( response.success && response.orderDetails ) {
713
714						$.themeCheckout.order_id = response.order_id;
715
716						// Display error if initialization failed,
717						if ( response.payment_status === 'requires_capture' ) {
718							// Finalize the payment method as payment is already authorized but requires capture.
719							$.themeCheckout.onPaymentAuthorized( response, data, callback, errorCallback );
720							return;
721						}
722
723						// If this is a one-time payment that requires confirmation, confirm the card payment first and proceed to order finalization.
724						$.themeCheckout.confirmCheckoutCardPayment( response.client_secret, () => {
725
726							// If the payment is confirmed, complete the authenticated payment.
727							$.themeCheckout.onPaymentAuthorized( response, data, callback, errorCallback );
728
729						}, ( error ) => {
730
731							$.themeCheckout.completePaymentMethodRequest( false );
732
733							if ( typeof errorCallback === 'function' ) {
734								errorCallback( { data: error } );
735							}
736						} );
737					} else {
738
739						$.themeCheckout.completePaymentMethodRequest( false );
740
741						// some error...
742						$.themeCheckout.triggerSentryWithExtras( '[ Initialize purchase error ]', {
743							response: response,
744							orderId: $.themeCheckout.order_id || null
745						} );
746
747						if ( typeof errorCallback === 'function' ) {
748							errorCallback( response );
749						}
750					}
751				},
752				error: function ( xhr, status, error ) {
753					// No charge attempted yet — re-enable so customer can retry.
754					$.themeCheckout.completePaymentMethodRequest( false );
755					$.themeCheckout.setIsCheckoutSubmitted( false );
756					$( document ).trigger( 'checkout-error', { type: 'error', details: error } );
757					$( '.error-message.submit-error' ).show().html( 'There was a connection error. Your order is incomplete, please check your internet connection and try again.' );
758					$.themeCheckout.triggerSentryWithExtras( '[ Initialize Purchase connection error ] ' + error, { xhr, status } );
759				}
760			} );
761		},
762
763		/**
764		 * Handles a network error during non-card order submission.
765		 * Keeps the form disabled since the order may have completed on the server.
766		 *
767		 * @param xhr
768		 * @param status
769		 * @param error
770		 */
771		orderInitializationError: function ( xhr, status, error ) {
772			// timeout or server error on our end... don't re-enable the form, just message it
773			// as we can't actually say if the charge went through or not.
774			$( '.error-message.submit-error' ).text( 'There was an error completing your transaction, please contact us for support.' );
775			$( '#submit-purchase' ).prop( 'value', 'ORDER FAILED' );
776			$( document ).trigger( 'checkout-error', { type: 'error', details: error } );
777			$.themeCheckout.triggerSentryWithExtras( '[ Checkout error ] ' + error, { xhr, status } );
778			$.themeCheckout.completePaymentMethodRequest( false );
779		},
780
781		/**
782		 * Displays a confirmation dialog for the given donation amount.
783		 * Calls the provided callback if user confirms.
784		 *
785		 * @param donationAmount
786		 * @param callback
787		 */
788		confirmDonationAmount: function ( donationAmount, callback ) {
789			bootbox.confirm( {
790				title: "Confirm donation amount",
791				message: "You've chosen to donate $" + donationAmount + ". Ok to continue?",
792				buttons: {
793					confirm: { label: 'Yes' },
794					cancel: { label: 'No' }
795				},
796				callback: function ( confirmed ) {
797					if ( confirmed === true && typeof callback === 'function' ) {
798						callback();
799					}
800				}
801			} );
802		},
803
804		/**
805		 * Validates the given email address format.
806		 *
807		 * @param emailAddress
808		 */
809		isValidCheckoutEmailAddress: function ( emailAddress ) {
810			var pattern = new RegExp( /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ );
811			return pattern.test( emailAddress );
812		},
813
814		/**
815		 * Validates the checkout email input field.
816		 *
817		 * @param $paymentEmail
818		 */
819		validateCheckoutEmail: function ( $paymentEmail ) {
820			if ( !$.themeCheckout.isValidCheckoutEmailAddress( $paymentEmail.val() ) ) {
821				$paymentEmail.addClass( 'error' );
822			} else {
823				$paymentEmail.removeClass( 'error' );
824				$paymentEmail.next().hide();
825			}
826		},
827
828		/**
829		 * Validates the given checkout input field for non-empty value.
830		 *
831		 * @param item
832		 */
833		validateCheckoutInputs: function ( item ) {
834			// Skip disabled or hidden inputs — they are not reachable by the customer.
835			if ( item.prop( 'disabled' ) === true || !item.is( ':visible' ) ) {
836				return;
837			}
838
839			var val = item.val();
840			item.toggleClass( 'error', $.trim( val ) === '' || val === null );
841			if ( item.hasClass( 'error' ) ) {
842				item.next().show();
843			} else {
844				item.next().hide();
845			}
846		},
847
848		/**
849		 * Validates the terms and conditions checkbox.
850		 *
851		 * @param $terms
852		 */
853		validateCheckoutTermsCheckbox: function ( $terms ) {
854			var checked = $( 'input[name=terms]' ).prop( 'checked' );
855			$terms.toggleClass( 'error', checked !== true );
856		},
857
858		/**
859		 * Displays checkout error messages to the user.
860		 * Accepts either a string or an array of error messages.
861		 *
862		 * @param error
863		 */
864		displayCheckoutErrorsList: function ( error ) {
865			if ( 'string' === typeof error ) {
866				$( '.error-message.submit-error' ).html( error ).show();
867			} else if ( error.length === 1 ) {
868				$( '.error-message.submit-error' ).html( error[0] ).show();
869			} else {
870				var messages = `<ul>`;
871
872				$.each( error, function ( i, msg ) {
873					messages += `<li>${msg}</li>`;
874				} );
875
876				messages += `</ul>`;
877
878				$( '.error-message.submit-error' ).html( messages ).show();
879			}
880		},
881
882		/**
883		 * Triggers a Sentry error event with the given failed response details.
884		 *
885		 * @param failedResponse
886		 * @param error
887		 */
888		triggerSentryError: function ( failedResponse, error ) {
889			if ( typeof Sentry !== 'undefined' ) {
890				Sentry.withScope( function ( scope ) {
891					scope.setLevel( 'error' );
892
893					scope.setExtras( {
894						response: failedResponse
895					} );
896
897					Sentry.captureMessage( '[ Checkout error ] ' + error );
898				} );
899			}
900		},
901
902		/**
903		 * Handles order failure by displaying error messages and logging the error details.
904		 *
905		 * @param failedResponse
906		 */
907		orderFailed: function ( failedResponse ) {
908			// some error...
909			var error = failedResponse.data || 'Unknown Error';
910			$( document ).trigger( 'checkout-error', { type: 'error', details: error } );
911
912			console.error( '[ Checkout error ] ' + error, failedResponse );
913
914			$.themeCheckout.triggerSentryError( failedResponse, error );
915
916			$.themeCheckout.displayCheckoutErrorsList( error );
917
918			$.themeCheckout.setIsCheckoutSubmitted( false );
919		},
920
921		/**
922		 * Toggles the availability of checkout form elements based on the availability parameter.
923		 *
924		 * @param isAvailable
925		 */
926		toggleCheckoutFormElementsAvailability: function ( isAvailable = true ) {
927
928			// Toggle availability of quantity selectors
929			const $quantitySelectors = $( '.ticket-quantity select' );
930			if ( $quantitySelectors.length > 0 ) {
931				$quantitySelectors.prop( 'disabled', !isAvailable );
932			}
933
934			// Toggle availability of payment method select
935			const $pmSelect = $( '#payment-method-select select' );
936			if ( $pmSelect.length > 0 ) {
937				$pmSelect.prop( 'disabled', !isAvailable );
938			}
939
940			// Toggle availability of discount code input
941			const $discountCodeInput = $( '#discount-code-input' );
942			if ( $discountCodeInput.length > 0 ) {
943				$discountCodeInput.prop( 'disabled', !isAvailable );
944			}
945
946			// Toggle availability of discount code button
947			const $discountCodeButton = $( '.discount-code button' );
948			if ( $discountCodeButton.length > 0 ) {
949				$discountCodeButton.prop( 'disabled', !isAvailable );
950			}
951
952			// Toggle availability of "Comp?" checkbox
953			const $isCompCheckbox = $( '#is-comp' );
954			if ( $isCompCheckbox.length > 0 ) {
955				$isCompCheckbox.prop( 'disabled', !isAvailable );
956			}
957
958			// Toggle availability of "Donate" checkbox
959			const $donateCheckbox = $( '#donate-me' );
960			if ( $donateCheckbox.length > 0 ) {
961				$donateCheckbox.prop( 'disabled', !isAvailable );
962			}
963
964			// Toggle availability of terms checkbox
965			const $termsCheckbox = $( '#terms' );
966			if ( $termsCheckbox.length > 0 ) {
967				$termsCheckbox.prop( 'disabled', !isAvailable );
968			}
969
970			// Toggle availability of customer information inputs
971			const $customerInputs = $( '.payment-address input, .payment-address select' );
972			if ( $customerInputs.length > 0 ) {
973				if ( !isAvailable ) {
974					$customerInputs.prop( 'disabled', true );
975				} else {
976					$customerInputs.filter( ':not(:hidden)' ).prop( 'disabled', false );
977				}
978			}
979
980			// Toggle availability of customer information inputs
981			const $membershipInfoInputs = $( '.cart-membership-details input, .cart-membership-details select' );
982			if ( $membershipInfoInputs.length > 0 ) {
983				$membershipInfoInputs.prop( 'disabled', !isAvailable );
984			}
985
986			// Toggle availability of customer information inputs
987			const $customDonationAmountInputs = $( '.donation-variants-container input' );
988			if ( $customDonationAmountInputs.length > 0 ) {
989				$customDonationAmountInputs.prop( 'disabled', !isAvailable );
990			}
991		},
992
993		/**
994		 * Toggles the availability of the submit button in the checkout form
995		 * and resets its text if it is being enabled.
996		 *
997		 * @param isAvailable
998		 */
999		toggleSubmitButtonAvailability: function ( isAvailable = true ) {
1000			if ( isAvailable ) {
1001				$( '#submit-purchase' ).prop( 'disabled', false ).val( 'Purchase' );
1002			} else {
1003				$( '#submit-purchase' ).prop( 'disabled', true ).val( 'Please wait...' );
1004				$( '.error-message.submit-error' ).hide();
1005			}
1006		},
1007
1008		/**
1009		 * Toggles the availability of Stripe Card Elements
1010		 *
1011		 * @param isAvailable
1012		 */
1013		toggleCardElementsAvailability: function ( isAvailable = true ) {
1014			if ( $.themeCheckout.card ) {
1015				$.themeCheckout.card.update( { disabled: !isAvailable } );
1016			}
1017		},
1018
1019		/**
1020		 * Sets the checkout submission status and toggles the availability
1021		 * of the form fields accordingly to prevent changes while the checkout is being processed.
1022		 *
1023		 * @param isSubmitted
1024		 */
1025		setIsCheckoutSubmitted: function ( isSubmitted = true ) {
1026			$.themeCheckout.checkoutSubmitted = isSubmitted;
1027
1028			$.themeCheckout.toggleSubmitButtonAvailability( !isSubmitted );
1029			$.themeCheckout.toggleCheckoutFormElementsAvailability( !isSubmitted );
1030			$.themeCheckout.toggleCardElementsAvailability( !isSubmitted );
1031		},
1032	};
1033})( jQuery );

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.