PageSourceSearch

https://mapr.org/wp-content/plugins/woocommerce/assets/js/frontend/checkout.js?ver=11.1.2

js mapr.org collected 2026-10-02 05:12:52 UTC 42,449 bytes, 1,474 lines download raw bytes

1/* global wc_checkout_params */
2jQuery( function ( $ ) {
3	// wc_checkout_params is required to continue, ensure the object exists
4	if ( typeof wc_checkout_params === 'undefined' ) {
5		return false;
6	}
7
8	$.blockUI.defaults.overlayCSS.cursor = 'default';
9
10	/**
11	 * Create the API object passed to custom place order button render callbacks.
12	 * This is checkout-specific and includes form validation.
13	 *
14	 * @return {Object} API object with validate and submit methods
15	 */
16	function createCheckoutPlaceOrderApi() {
17		var $form = window.wc.customPlaceOrderButton.__getForm();
18
19		return {
20			/**
21			 * Validate the checkout form.
22			 * This gets a little tricky.
23			 * The existing checkout.js does NOT have a "validate everything before submit" function - it's not needed.
24			 * Validation is done:
25			 *  - Field-by-field via `validate_field` on blur/change
26			 *  - Server-side on form submission (errors are returned in AJAX response).
27			 * This function tries to mimic client-side validation, but WooCommerce's real validation happens server-side.
28			 *
29			 * @return {Promise<{hasError: boolean}>} Promise resolving to validation result
30			 */
31			validate: function () {
32				return new Promise( function ( resolve ) {
33					var hasError = false;
34
35					// On a "normal" shortcode checkout page, the page validates this server-side only (not via validate_field).
36					// We do client-side validation here for a better UX with custom place order buttons.
37					// Clearing any stale invalid state before re-validating, to ensure a clean slate.
38					var $termsCheckbox = $form.find( 'input[name="terms"]:visible' );
39					if ( $termsCheckbox.length ) {
40						$termsCheckbox.closest( '.form-row' ).removeClass( 'woocommerce-invalid' );
41					}
42
43					// Trigger field-level validation (which adds `.woocommerce-invalid` to invalid fields)
44					$form.find( '.input-text, select, input:checkbox' ).trigger( 'validate' );
45
46					// Check for validation errors (from validate_field handler).
47					// Only consider visible fields: `validate_field` flags any empty
48					// required field regardless of visibility, so hidden fields (e.g.
49					// the collapsed "Ship to a different address?" shipping fields)
50					// would otherwise block submission even when the visible form is
51					// valid. The `.woocommerce-invalid` class lives on the `.form-row`,
52					// which is hidden when its section is collapsed.
53					if ( $form.find( '.woocommerce-invalid:visible' ).length > 0 ) {
54						hasError = true;
55					}
56
57					// Check required fields (adds .woocommerce-invalid if not already set)
58					$form.find( '.validate-required:visible' ).each( function () {
59						var $field = $( this );
60						var $input = $field.find( 'input.input-text, select, input:checkbox' );
61
62						if ( $input.length === 0 ) {
63							return;
64						}
65
66						var isEmpty;
67						if ( $input.is( ':checkbox' ) ) {
68							isEmpty = ! $input.is( ':checked' );
69						} else {
70							isEmpty = $input.val() === '' || $input.val() === null;
71						}
72
73						if ( isEmpty ) {
74							hasError = true;
75							$field.addClass( 'woocommerce-invalid woocommerce-invalid-required-field' );
76						}
77					} );
78
79					// Check terms checkbox - this is our client-side validation for better UX
80					// (WC Core only validates terms server-side)
81					if ( $termsCheckbox.length && ! $termsCheckbox.is( ':checked' ) ) {
82						hasError = true;
83						$termsCheckbox.closest( '.form-row' ).addClass( 'woocommerce-invalid' );
84					}
85
86					// Scroll to the first invalid field in DOM order
87					if ( hasError ) {
88						var $firstInvalidField = $form.find( '.woocommerce-invalid:visible' ).first();
89						if ( $firstInvalidField.length ) {
90							$( 'html, body' ).animate(
91								{
92									scrollTop: $firstInvalidField.offset().top - 100,
93								},
94								500
95							);
96						}
97					}
98
99					resolve( { hasError: hasError } );
100				} );
101			},
102
103			/**
104			 * Submit the checkout form.
105			 * Triggers the same logic as clicking the default place order button.
106			 */
107			submit: function () {
108				$form.trigger( 'submit' );
109			},
110		};
111	}
112
113	// Clean up custom place order button before checkout update destroys the DOM.
114	// After the update, init_payment_methods() will trigger payment method selection,
115	// which will call render() again for the active gateway.
116	$( document.body ).on( 'update_checkout', function () {
117		window.wc.customPlaceOrderButton.__cleanup();
118	} );
119
120	// When a gateway registers after a page load, render its button if it's selected.
121	$( document.body ).on( 'wc_custom_place_order_button_registered', function ( e, gatewayId ) {
122		window.wc.customPlaceOrderButton.__maybeShow( gatewayId, createCheckoutPlaceOrderApi() );
123	} );
124
125	var wc_checkout_form = {
126		updateTimer: false,
127		dirtyInput: false,
128		selectedPaymentMethod: false,
129		xhr: false,
130		$order_review: $( '#order_review' ),
131		$checkout_form: $( 'form.checkout' ),
132		init: function () {
133			$( document.body ).on( 'update_checkout', this.update_checkout );
134			$( document.body ).on( 'init_checkout', this.init_checkout );
135
136			// Payment methods
137			this.$checkout_form.on(
138				'click',
139				'input[name="payment_method"]',
140				this.payment_method_selected
141			);
142
143			if ( $( document.body ).hasClass( 'woocommerce-order-pay' ) ) {
144				this.$order_review.on(
145					'click',
146					'input[name="payment_method"]',
147					this.payment_method_selected
148				);
149				this.$order_review.on( 'submit', this.submitOrder );
150				this.$order_review.attr( 'novalidate', 'novalidate' );
151
152				// Initialize the custom place order button for the "order-pay" page
153				var $orderPayMethod = this.$order_review.find( 'input[name="payment_method"]:checked' );
154				if ( $orderPayMethod.length ) {
155					window.wc.customPlaceOrderButton.__maybeHideDefaultButtonOnInit( $orderPayMethod.val() );
156					$orderPayMethod.trigger( 'click' );
157				}
158			}
159
160			// Prevent HTML5 validation which can conflict.
161			this.$checkout_form.attr( 'novalidate', 'novalidate' );
162
163			// Form submission
164			this.$checkout_form.on( 'submit', this.submit );
165
166			// Inline validation
167			this.$checkout_form.on(
168				'input validate change focusout',
169				'.input-text, select, input:checkbox',
170				this.validate_field
171			);
172
173			// Manual trigger
174			this.$checkout_form.on( 'update', this.trigger_update_checkout );
175
176			// Inputs/selects which update totals
177			this.$checkout_form.on(
178				'change',
179				// eslint-disable-next-line max-len
180				'select.shipping_method, input[name^="shipping_method"], #ship-to-different-address input, .update_totals_on_change select, .update_totals_on_change input[type="radio"], .update_totals_on_change input[type="checkbox"]',
181				this.trigger_update_checkout
182			);
183			this.$checkout_form.on(
184				'change',
185				'.address-field select',
186				this.input_changed
187			);
188			this.$checkout_form.on(
189				'change',
190				'.address-field input.input-text, .update_totals_on_change input.input-text',
191				this.maybe_input_changed
192			);
193			this.$checkout_form.on(
194				'keydown',
195				'.address-field input.input-text, .update_totals_on_change input.input-text',
196				this.queue_update_checkout
197			);
198
199			// Handle blur on address_1 fields when autocomplete provider is available
200			this.$checkout_form.on(
201				'blur',
202				'#billing_address_1, #shipping_address_1',
203				this.address_field_blur
204			);
205
206			// Address fields
207			this.$checkout_form.on(
208				'change',
209				'#ship-to-different-address input',
210				this.ship_to_different_address
211			);
212
213			// Trigger events
214			this.$checkout_form
215				.find( '#ship-to-different-address input' )
216				.trigger( 'change' );
217			this.init_payment_methods();
218
219			// Update on page load
220			if ( wc_checkout_params.is_checkout === '1' ) {
221				$( document.body ).trigger( 'init_checkout' );
222			}
223			if ( wc_checkout_params.option_guest_checkout === 'yes' ) {
224				$( 'input#createaccount' )
225					.on( 'change', this.toggle_create_account )
226					.trigger( 'change' );
227			}
228		},
229		init_payment_methods: function () {
230			var $payment_methods = $( '.woocommerce-checkout' ).find(
231				'input[name="payment_method"]'
232			);
233
234			// If there is one method, we can hide the radio input
235			if ( 1 === $payment_methods.length ) {
236				$payment_methods.eq( 0 ).hide();
237			}
238
239			// If there was a previously selected method, check that one.
240			if ( wc_checkout_form.selectedPaymentMethod ) {
241				$( '#' + wc_checkout_form.selectedPaymentMethod ).prop(
242					'checked',
243					true
244				);
245			}
246
247			// If there are none selected, select the first.
248			if ( 0 === $payment_methods.filter( ':checked' ).length ) {
249				$payment_methods.eq( 0 ).prop( 'checked', true );
250			}
251
252			// Get name of new selected method.
253			var checkedPaymentMethod = $payment_methods
254				.filter( ':checked' )
255				.eq( 0 )
256				.prop( 'id' );
257
258			if ( $payment_methods.length > 1 ) {
259				// Hide open descriptions.
260				$( 'div.payment_box:not(".' + checkedPaymentMethod + '")' )
261					.filter( ':visible' )
262					.slideUp( 0 );
263			}
264
265			// Check if initially selected gateway has custom place order button (via server-side flag)
266			// This hides the default button immediately to prevent flash while the gateway JS loads
267			var $selectedMethod = $payment_methods.filter( ':checked' ).eq( 0 );
268			if ( $selectedMethod.length ) {
269				window.wc.customPlaceOrderButton.__maybeHideDefaultButtonOnInit( $selectedMethod.val() );
270			}
271
272			// Trigger click event for selected method
273			$payment_methods.filter( ':checked' ).eq( 0 ).trigger( 'click' );
274		},
275		get_payment_method: function () {
276			return wc_checkout_form.$checkout_form
277				.find( 'input[name="payment_method"]:checked' )
278				.val();
279		},
280		payment_method_selected: function ( e ) {
281			e.stopPropagation();
282
283			if ( $( '.payment_methods input.input-radio' ).length > 1 ) {
284				var target_payment_box = $(
285						'div.payment_box.' + $( this ).attr( 'ID' )
286					),
287					is_checked = $( this ).is( ':checked' );
288
289				if ( is_checked && ! target_payment_box.is( ':visible' ) ) {
290					$( 'div.payment_box' ).filter( ':visible' ).slideUp( 230 );
291
292					if ( is_checked ) {
293						target_payment_box.slideDown( 230 );
294					}
295				}
296			} else {
297				$( 'div.payment_box' ).show();
298			}
299
300			if ( $( this ).data( 'order_button_text' ) ) {
301				$( '#place_order' ).text(
302					$( this ).data( 'order_button_text' )
303				);
304			} else {
305				$( '#place_order' ).text( $( '#place_order' ).data( 'value' ) );
306			}
307
308			var selectedPaymentMethod = $(
309				'.woocommerce-checkout input[name="payment_method"]:checked'
310			).attr( 'id' );
311
312			if (
313				selectedPaymentMethod !== wc_checkout_form.selectedPaymentMethod
314			) {
315				$( document.body ).trigger( 'payment_method_selected' );
316			}
317
318			// Handle custom place order button
319			var gatewayId = $( this ).val();
320			window.wc.customPlaceOrderButton.__maybeShow( gatewayId, createCheckoutPlaceOrderApi() );
321
322			wc_checkout_form.selectedPaymentMethod = selectedPaymentMethod;
323		},
324		toggle_create_account: function () {
325			$( 'div.create-account' ).hide();
326
327			if ( $( this ).is( ':checked' ) ) {
328				// Ensure password is not pre-populated.
329				$( '#account_password' ).val( '' );
330				$( 'div.create-account' ).slideDown();
331			}
332		},
333		init_checkout: function () {
334			$( document.body ).trigger( 'update_checkout' );
335		},
336		/**
337		 * Check if an address_1 field has an active autocomplete provider and should skip checkout updates
338		 * @param {Event} e - The event object
339		 * @return {boolean} true if updates should be skipped, false otherwise
340		 */
341		should_skip_address_update: function ( e ) {
342			var target = e.target || e.srcElement;
343			if (
344				target &&
345				( target.id === 'billing_address_1' ||
346					target.id === 'shipping_address_1' )
347			) {
348				// Skip if we're manipulating the DOM for autocomplete
349				if (
350					target.getAttribute( 'data-autocomplete-manipulating' ) ===
351					'true'
352				) {
353					return true;
354				}
355
356				var type = target.id.replace( '_address_1', '' );
357
358				// Check if window.wc.addressAutocomplete exists and has an active provider
359				if (
360					window.wc &&
361					window.wc.addressAutocomplete &&
362					window.wc.addressAutocomplete.activeProvider
363				) {
364					if (
365						window.wc.addressAutocomplete.activeProvider[ type ]
366					) {
367						return true;
368					}
369				}
370			}
371			return false;
372		},
373		/**
374		 * Check if an address_1 field has an active autocomplete provider and should trigger checkout updates on blur
375		 * @param {Event} e - The event object
376		 * @return {boolean} true if updates should be triggered, false otherwise
377		 */
378		should_trigger_address_blur_update: function ( e ) {
379			var target = e.target || e.srcElement;
380			if (
381				target &&
382				( target.id === 'billing_address_1' ||
383					target.id === 'shipping_address_1' )
384			) {
385				// Skip if we're manipulating the DOM for autocomplete
386				if (
387					target.getAttribute( 'data-autocomplete-manipulating' ) ===
388					'true'
389				) {
390					return false;
391				}
392
393				var type = target.id.replace( '_address_1', '' );
394
395				// Check if window.wc.addressAutocomplete exists and has an active provider
396				if (
397					window.wc &&
398					window.wc.addressAutocomplete &&
399					window.wc.addressAutocomplete.activeProvider
400				) {
401					if (
402						window.wc.addressAutocomplete.activeProvider[ type ]
403					) {
404						return true;
405					}
406				}
407			}
408			return false;
409		},
410		maybe_input_changed: function ( e ) {
411			if ( wc_checkout_form.should_skip_address_update( e ) ) {
412				return;
413			}
414
415			if ( wc_checkout_form.dirtyInput ) {
416				wc_checkout_form.input_changed( e );
417			}
418		},
419		input_changed: function ( e ) {
420			if ( wc_checkout_form.should_skip_address_update( e ) ) {
421				return;
422			}
423
424			wc_checkout_form.dirtyInput = e.target;
425			wc_checkout_form.maybe_update_checkout();
426		},
427		address_field_blur: function ( e ) {
428			if ( wc_checkout_form.should_trigger_address_blur_update( e ) ) {
429				wc_checkout_form.dirtyInput = e.target;
430				wc_checkout_form.maybe_update_checkout();
431			}
432		},
433		queue_update_checkout: function ( e ) {
434			var code = e.keyCode || e.which || 0;
435
436			if ( code === 9 ) {
437				return true;
438			}
439
440			// Check if we're in an address_1 field with an active provider
441			var target = e.target || e.srcElement;
442			if (
443				target &&
444				( target.id === 'billing_address_1' ||
445					target.id === 'shipping_address_1' )
446			) {
447				// Check if an autocomplete provider is available for this field
448				var type = target.id.replace( '_address_1', '' );
449
450				// Check if window.wc.addressAutocomplete exists and has an active provider
451				if (
452					window.wc &&
453					window.wc.addressAutocomplete &&
454					window.wc.addressAutocomplete.activeProvider
455				) {
456					if (
457						window.wc.addressAutocomplete.activeProvider[ type ]
458					) {
459						// Provider is available - don't queue updates while typing
460						// Updates will be triggered on blur instead
461						return true;
462					}
463				}
464			}
465
466			wc_checkout_form.dirtyInput = this;
467			wc_checkout_form.reset_update_checkout_timer();
468			wc_checkout_form.updateTimer = setTimeout(
469				wc_checkout_form.maybe_update_checkout,
470				'1000'
471			);
472		},
473		trigger_update_checkout: function ( event ) {
474			wc_checkout_form.reset_update_checkout_timer();
475			wc_checkout_form.dirtyInput = false;
476			$( document.body ).trigger( 'update_checkout', {
477				current_target: event ? event.currentTarget : null,
478			} );
479		},
480		maybe_update_checkout: function () {
481			var update_totals = true;
482
483			if ( $( wc_checkout_form.dirtyInput ).length ) {
484				var $required_inputs = $( wc_checkout_form.dirtyInput )
485					.closest( 'div' )
486					.find( '.address-field.validate-required' );
487
488				if ( $required_inputs.length ) {
489					$required_inputs.each( function () {
490						if (
491							$( this ).find( 'input.input-text' ).val() === ''
492						) {
493							update_totals = false;
494						}
495					} );
496				}
497			}
498			if ( update_totals ) {
499				wc_checkout_form.trigger_update_checkout();
500			}
501		},
502		ship_to_different_address: function () {
503			$( 'div.shipping_address' ).hide();
504			if ( $( this ).is( ':checked' ) ) {
505				$( 'div.shipping_address' ).slideDown();
506			}
507		},
508		reset_update_checkout_timer: function () {
509			clearTimeout( wc_checkout_form.updateTimer );
510		},
511		is_valid_json: function ( raw_json ) {
512			try {
513				var json = JSON.parse( raw_json );
514
515				return json && 'object' === typeof json;
516			} catch ( e ) {
517				return false;
518			}
519		},
520		validate_field: function ( e ) {
521			var $this = $( this ),
522				$parent = $this.closest( '.form-row' ),
523				validated = true,
524				validate_required = $parent.is( '.validate-required' ),
525				validate_email = $parent.is( '.validate-email' ),
526				validate_phone = $parent.is( '.validate-phone' ),
527				pattern = '',
528				event_type = e.type;
529
530			if ( 'input' === event_type ) {
531				$this
532					.removeAttr( 'aria-invalid' )
533					.removeAttr( 'aria-describedby' );
534				$parent.find( '.checkout-inline-error-message' ).remove();
535				$parent.removeClass(
536					// eslint-disable-next-line max-len
537					'woocommerce-invalid woocommerce-invalid-required-field woocommerce-invalid-email woocommerce-invalid-phone woocommerce-validated'
538				);
539			}
540
541			if (
542				'validate' === event_type ||
543				'change' === event_type ||
544				'focusout' === event_type
545			) {
546				if ( validate_required ) {
547					if (
548						( 'checkbox' === $this.attr( 'type' ) &&
549							! $this.is( ':checked' ) ) ||
550						$this.val() === ''
551					) {
552						$this.attr( 'aria-invalid', 'true' );
553						$parent
554							.removeClass( 'woocommerce-validated' )
555							.addClass(
556								'woocommerce-invalid woocommerce-invalid-required-field'
557							);
558						validated = false;
559					}
560				}
561
562				if ( validate_email ) {
563					if ( $this.val() ) {
564						/* https://stackoverflow.com/questions/2855865/jquery-validate-e-mail-address-regex */
565						pattern = new RegExp(
566							// eslint-disable-next-line max-len
567							/^([a-z\d!#$%&'*+\-\/=?^_`{|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+(\.[a-z\d!#$%&'*+\-\/=?^_`{|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)*|"((([ \t]*\r\n)?[ \t]+)?([\x01-\x08\x0b\x0c\x0e-\x1f\x7f\x21\x23-\x5b\x5d-\x7e\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|\\[\x01-\x09\x0b\x0c\x0d-\x7f\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))*(([ \t]*\r\n)?[ \t]+)?")@(([a-z\d\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|[a-z\d\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF][a-z\d\-._~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]*[a-z\d\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])\.)+([a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|[a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF][a-z\d\-._~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]*[0-9a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])\.?$/i
568						); // eslint-disable-line max-len
569
570						if ( ! pattern.test( $this.val() ) ) {
571							$this.attr( 'aria-invalid', 'true' );
572							$parent
573								.removeClass( 'woocommerce-validated' )
574								.addClass(
575									'woocommerce-invalid woocommerce-invalid-email'
576								); // eslint-disable-line max-len
577							validated = false;
578						}
579					}
580				}
581
582				if ( validate_phone ) {
583					pattern = new RegExp( /[\s\#0-9_\-\+\/\(\)\.]/g );
584
585					if ( 0 < $this.val().replace( pattern, '' ).length ) {
586						$this.attr( 'aria-invalid', 'true' );
587						$parent
588							.removeClass( 'woocommerce-validated' )
589							.addClass(
590								'woocommerce-invalid woocommerce-invalid-phone'
591							);
592						validated = false;
593					}
594				}
595
596				if ( validated ) {
597					$this
598						.removeAttr( 'aria-invalid' )
599						.removeAttr( 'aria-describedby' );
600					$parent.find( '.checkout-inline-error-message' ).remove();
601					$parent
602						.removeClass(
603							'woocommerce-invalid woocommerce-invalid-required-field woocommerce-invalid-email woocommerce-invalid-phone'
604						)
605						.addClass( 'woocommerce-validated' ); // eslint-disable-line max-len
606				}
607			}
608		},
609		update_checkout: function ( event, args ) {
610			// Small timeout to prevent multiple requests when several fields update at the same time
611			wc_checkout_form.reset_update_checkout_timer();
612			wc_checkout_form.updateTimer = setTimeout(
613				wc_checkout_form.update_checkout_action,
614				'5',
615				args
616			);
617		},
618		update_checkout_action: function ( args ) {
619			if ( wc_checkout_form.xhr ) {
620				wc_checkout_form.xhr.abort();
621			}
622
623			if ( $( 'form.checkout' ).length === 0 ) {
624				return;
625			}
626
627			args =
628				typeof args !== 'undefined'
629					? args
630					: {
631							update_shipping_method: true,
632					  };
633
634			var country = $( '#billing_country' ).val(),
635				state = $( '#billing_state' ).val(),
636				postcode = $( ':input#billing_postcode' ).val(),
637				city = $( '#billing_city' ).val(),
638				address = $( ':input#billing_address_1' ).val(),
639				address_2 = $( ':input#billing_address_2' ).val(),
640				s_country = country,
641				s_state = state,
642				s_postcode = postcode,
643				s_city = city,
644				s_address = address,
645				s_address_2 = address_2,
646				$required_inputs = $( wc_checkout_form.$checkout_form ).find(
647					'.address-field.validate-required:visible'
648				),
649				has_full_address = true;
650
651			if ( $required_inputs.length ) {
652				$required_inputs.each( function () {
653					if ( $( this ).find( ':input' ).val() === '' ) {
654						has_full_address = false;
655					}
656				} );
657			}
658
659			if (
660				$( '#ship-to-different-address' )
661					.find( 'input' )
662					.is( ':checked' )
663			) {
664				s_country = $( '#shipping_country' ).val();
665				s_state = $( '#shipping_state' ).val();
666				s_postcode = $( ':input#shipping_postcode' ).val();
667				s_city = $( '#shipping_city' ).val();
668				s_address = $( ':input#shipping_address_1' ).val();
669				s_address_2 = $( ':input#shipping_address_2' ).val();
670			}
671
672			var data = {
673				security: wc_checkout_params.update_order_review_nonce,
674				payment_method: wc_checkout_form.get_payment_method(),
675				country: country,
676				state: state,
677				postcode: postcode,
678				city: city,
679				address: address,
680				address_2: address_2,
681				s_country: s_country,
682				s_state: s_state,
683				s_postcode: s_postcode,
684				s_city: s_city,
685				s_address: s_address,
686				s_address_2: s_address_2,
687				has_full_address: has_full_address,
688				post_data: $( 'form.checkout' ).serialize(),
689			};
690
691			if ( false !== args.update_shipping_method ) {
692				var shipping_methods = {};
693
694				$(
695					// eslint-disable-next-line max-len
696					'select.shipping_method, input[name^="shipping_method"][type="radio"]:checked, input[name^="shipping_method"][type="hidden"]'
697				).each( function () {
698					shipping_methods[ $( this ).data( 'index' ) ] =
699						$( this ).val();
700				} );
701
702				data.shipping_method = shipping_methods;
703			}
704
705			$(
706				'.woocommerce-checkout-payment, .woocommerce-checkout-review-order-table'
707			).block( {
708				message: null,
709				overlayCSS: {
710					background: '#fff',
711					opacity: 0.6,
712				},
713			} );
714
715			wc_checkout_form.xhr = $.ajax( {
716				type: 'POST',
717				url: wc_checkout_params.wc_ajax_url
718					.toString()
719					.replace( '%%endpoint%%', 'update_order_review' ),
720				data: data,
721				success: function ( data ) {
722					// Reload the page if requested
723					if ( data && true === data.reload ) {
724						window.location.reload();
725						return;
726					}
727
728					// Remove any notices added previously
729					$( '.woocommerce-NoticeGroup-updateOrderReview' ).remove();
730
731					var termsCheckBoxChecked = $( '#terms' ).prop( 'checked' );
732
733					// Save payment details to a temporary object
734					var paymentDetails = {};
735					$( '.payment_box :input' ).each( function () {
736						var ID = $( this ).attr( 'id' );
737
738						if ( ID ) {
739							if (
740								$.inArray( $( this ).attr( 'type' ), [
741									'checkbox',
742									'radio',
743								] ) !== -1
744							) {
745								paymentDetails[ ID ] =
746									$( this ).prop( 'checked' );
747							} else {
748								paymentDetails[ ID ] = $( this ).val();
749							}
750						}
751					} );
752
753					// Always update the fragments
754					if ( data && data.fragments ) {
755						$.each( data.fragments, function ( key, value ) {
756							if (
757								! wc_checkout_form.fragments ||
758								wc_checkout_form.fragments[ key ] !== value
759							) {
760								$( key ).replaceWith( value );
761							}
762							$( key ).unblock();
763						} );
764						wc_checkout_form.fragments = data.fragments;
765					}
766
767					// Recheck the terms and conditions box, if needed
768					if ( termsCheckBoxChecked ) {
769						$( '#terms' ).prop( 'checked', true );
770					}
771
772					// Fill in the payment details if possible without overwriting data if set.
773					if ( ! $.isEmptyObject( paymentDetails ) ) {
774						$( '.payment_box :input' ).each( function () {
775							var ID = $( this ).attr( 'id' );
776							if ( ID ) {
777								if (
778									$.inArray( $( this ).attr( 'type' ), [
779										'checkbox',
780										'radio',
781									] ) !== -1
782								) {
783									$( this )
784										.prop( 'checked', paymentDetails[ ID ] )
785										.trigger( 'change' );
786								} else if (
787									$.inArray( $( this ).attr( 'type' ), [
788										'select',
789									] ) !== -1
790								) {
791									$( this )
792										.val( paymentDetails[ ID ] )
793										.trigger( 'change' );
794								} else if (
795									null !== $( this ).val() &&
796									0 === $( this ).val().length
797								) {
798									$( this )
799										.val( paymentDetails[ ID ] )
800										.trigger( 'change' );
801								}
802							}
803						} );
804					}
805
806					// Check for error
807					if ( data && 'failure' === data.result ) {
808						var $form = $( 'form.checkout' );
809
810						// Remove notices from all sources
811						$(
812							'.woocommerce-error, .woocommerce-message, .is-error, .is-success'
813						).remove();
814
815						// Add new errors returned by this event
816						if ( data.messages ) {
817							$form.prepend(
818								'<div class="woocommerce-NoticeGroup woocommerce-NoticeGroup-updateOrderReview">' +
819									data.messages +
820									'</div>'
821							); // eslint-disable-line max-len
822						} else {
823							$form.prepend( data );
824						}
825
826						// Lose focus for all fields
827						$form
828							.find( '.input-text, select, input:checkbox' )
829							.trigger( 'validate' )
830							.trigger( 'blur' );
831
832						wc_checkout_form.scroll_to_notices();
833					}
834
835					// Re-init methods
836					wc_checkout_form.init_payment_methods();
837
838					// If there is no errors and the checkout update was triggered by changing the shipping method, focus its radio input.
839					if (
840						data &&
841						'success' === data.result &&
842						args.current_target &&
843						args.current_target.id.indexOf( 'shipping_method' ) !==
844							-1
845					) {
846						document
847							.getElementById( args.current_target.id )
848							.focus();
849					}
850
851					// Fire updated_checkout event.
852					$( document.body ).trigger( 'updated_checkout', [ data ] );
853				},
854			} );
855		},
856		handleUnloadEvent: function ( e ) {
857			// Modern browsers have their own standard generic messages that they will display.
858			// Confirm, alert, prompt or custom message are not allowed during the unload event
859			// Browsers will display their own standard messages
860
861			// Check if the browser is Internet Explorer
862			if (
863				navigator.userAgent.indexOf( 'MSIE' ) !== -1 ||
864				!! document.documentMode
865			) {
866				// IE handles unload events differently than modern browsers
867				e.preventDefault();
868				return undefined;
869			}
870
871			return true;
872		},
873		attachUnloadEventsOnSubmit: function () {
874			$( window ).on( 'beforeunload', this.handleUnloadEvent );
875		},
876		detachUnloadEventsOnSubmit: function () {
877			$( window ).off( 'beforeunload', this.handleUnloadEvent );
878		},
879		blockOnSubmit: function ( $form ) {
880			var isBlocked = $form.data( 'blockUI.isBlocked' );
881
882			if ( 1 !== isBlocked ) {
883				$form.block( {
884					message: null,
885					overlayCSS: {
886						background: '#fff',
887						opacity: 0.6,
888					},
889				} );
890			}
891		},
892		submitOrder: function () {
893			wc_checkout_form.blockOnSubmit( $( this ) );
894		},
895		submit: function () {
896			wc_checkout_form.reset_update_checkout_timer();
897			var $form = $( this );
898
899			if ( $form.is( '.processing' ) ) {
900				return false;
901			}
902
903			// Trigger a handler to let gateways manipulate the checkout if needed
904			if (
905				$form.triggerHandler( 'checkout_place_order', [
906					wc_checkout_form,
907				] ) !== false &&
908				$form.triggerHandler(
909					'checkout_place_order_' +
910						wc_checkout_form.get_payment_method(),
911					[ wc_checkout_form ]
912				) !== false
913			) {
914				$form.addClass( 'processing' );
915
916				wc_checkout_form.blockOnSubmit( $form );
917
918				// Attach event to block reloading the page when the form has been submitted
919				wc_checkout_form.attachUnloadEventsOnSubmit();
920
921				// ajaxSetup is global, but we use it to ensure JSON is valid once returned.
922				$.ajaxSetup( {
923					dataFilter: function ( raw_response, dataType ) {
924						// We only want to work with JSON
925						if ( 'json' !== dataType ) {
926							return raw_response;
927						}
928
929						if ( wc_checkout_form.is_valid_json( raw_response ) ) {
930							return raw_response;
931						} else {
932							// Attempt to fix the malformed JSON
933							var maybe_valid_json =
934								raw_response.match( /{"result.*}/ );
935
936							if ( null === maybe_valid_json ) {
937								console.log(
938									'Unable to fix malformed JSON #1'
939								);
940							} else if (
941								wc_checkout_form.is_valid_json(
942									maybe_valid_json[ 0 ]
943								)
944							) {
945								console.log(
946									'Fixed malformed JSON. Original:'
947								);
948								console.log( raw_response );
949								raw_response = maybe_valid_json[ 0 ];
950							} else {
951								console.log(
952									'Unable to fix malformed JSON #2'
953								);
954							}
955						}
956
957						return raw_response;
958					},
959				} );
960
961				$.ajax( {
962					type: 'POST',
963					url: wc_checkout_params.checkout_url,
964					data: $form.serialize(),
965					dataType: 'json',
966					success: function ( result ) {
967						// Detach the unload handler that prevents a reload / redirect
968						wc_checkout_form.detachUnloadEventsOnSubmit();
969
970						$( '.checkout-inline-error-message' ).remove();
971
972						try {
973							if (
974								'success' === result.result &&
975								$form.triggerHandler(
976									'checkout_place_order_success',
977									[ result, wc_checkout_form ]
978								) !== false
979							) {
980								if (
981									-1 ===
982										result.redirect.indexOf( 'https://' ) ||
983									-1 === result.redirect.indexOf( 'http://' )
984								) {
985									window.location = result.redirect;
986								} else {
987									window.location = decodeURI(
988										result.redirect
989									);
990								}
991							} else if ( 'failure' === result.result ) {
992								throw 'Result failure';
993							} else {
994								throw 'Invalid response';
995							}
996						} catch ( err ) {
997							// Reload page
998							if ( true === result.reload ) {
999								window.location.reload();
1000								return;
1001							}
1002
1003							// Trigger update in case we need a fresh nonce
1004							if ( true === result.refresh ) {
1005								$( document.body ).trigger( 'update_checkout' );
1006							}
1007
1008							// Add new errors
1009							if ( result.messages ) {
1010								var $msgs = $( result.messages )
1011									// The error notice template (plugins/woocommerce/templates/notices/error.php)
1012									// adds the role="alert" to a list HTML element. This becomes a problem in this context
1013									// because screen readers won't read the list content correctly if its role is not "list".
1014									.removeAttr( 'role' )
1015									.attr( 'tabindex', '-1' );
1016								var $msgsWithLink =
1017									wc_checkout_form.wrapMessagesInsideLink(
1018										$msgs
1019									);
1020								var $msgsWrapper = $(
1021									'<div role="alert"></div>'
1022								).append( $msgsWithLink );
1023
1024								wc_checkout_form.submit_error(
1025									$msgsWrapper.prop( 'outerHTML' )
1026								);
1027								wc_checkout_form.show_inline_errors( $msgs );
1028							} else {
1029								wc_checkout_form.submit_error(
1030									'<div class="woocommerce-error">' +
1031										wc_checkout_params.i18n_checkout_error +
1032										'</div>'
1033								); // eslint-disable-line max-len
1034							}
1035						}
1036					},
1037					error: function ( jqXHR, textStatus, errorThrown ) {
1038						// Detach the unload handler that prevents a reload / redirect
1039						wc_checkout_form.detachUnloadEventsOnSubmit();
1040
1041						// This is just a technical error fallback. i18_checkout_error is expected to be al
1041ways defined and localized.
1042						var errorMessage = errorThrown;
1043
1044						if (
1045							typeof wc_checkout_params === 'object' &&
1046							wc_checkout_params !== null &&
1047							wc_checkout_params.hasOwnProperty(
1048								'i18n_checkout_error'
1049							) &&
1050							typeof wc_checkout_params.i18n_checkout_error ===
1051								'string' &&
1052							wc_checkout_params.i18n_checkout_error.trim() !== ''
1053						) {
1054							errorMessage =
1055								wc_checkout_params.i18n_checkout_error;
1056						}
1057
1058						wc_checkout_form.submit_error(
1059							'<div class="woocommerce-error">' +
1060								errorMessage +
1061								'</div>'
1062						);
1063					},
1064				} );
1065			}
1066
1067			return false;
1068		},
1069		submit_error: function ( error_message ) {
1070			$(
1071				'.woocommerce-NoticeGroup-checkout, .woocommerce-error, .woocommerce-message, .is-error, .is-success'
1072			).remove();
1073			wc_checkout_form.$checkout_form.prepend(
1074				'<div class="woocommerce-NoticeGroup woocommerce-NoticeGroup-checkout">' +
1075					error_message +
1076					'</div>'
1077			); // eslint-disable-line max-len
1078			wc_checkout_form.$checkout_form
1079				.removeClass( 'processing' )
1080				.unblock();
1081			wc_checkout_form.$checkout_form
1082				.find( '.input-text, select, input:checkbox' )
1083				.trigger( 'validate' )
1084				.trigger( 'blur' );
1085			wc_checkout_form.scroll_to_notices();
1086			wc_checkout_form.$checkout_form
1087				.find(
1088					'.woocommerce-error[tabindex="-1"], .wc-block-components-notice-banner.is-error[tabindex="-1"]'
1089				)
1090				.trigger( 'focus' );
1091			$( document.body ).trigger( 'checkout_error', [ error_message ] );
1092		},
1093		wrapMessagesInsideLink: function ( $msgs ) {
1094			$msgs.find( 'li[data-id]' ).each( function () {
1095				const $this = $( this );
1096				const dataId = $this.attr( 'data-id' );
1097				if ( dataId ) {
1098					const $link = $( '<a>', {
1099						href: '#' + dataId,
1100						html: $this.html(),
1101					} );
1102					$this.empty().append( $link );
1103				}
1104			} );
1105
1106			return $msgs;
1107		},
1108		show_inline_errors: function ( $messages ) {
1109			$messages.find( 'li[data-id]' ).each( function () {
1110				const $this = $( this );
1111				const dataId = $this.attr( 'data-id' );
1112				const $field = $( '#' + dataId );
1113
1114				if ( $field.length === 1 ) {
1115					const descriptionId = dataId + '_description';
1116					const msg = $this.text().trim();
1117					const $formRow = $field.closest( '.form-row' );
1118
1119					const errorMessage = document.createElement( 'p' );
1120					errorMessage.id = descriptionId;
1121					errorMessage.className = 'checkout-inline-error-message';
1122					errorMessage.textContent = msg;
1123
1124					if ( $formRow && errorMessage.textContent.length > 0 ) {
1125						$formRow.append( errorMessage );
1126					}
1127
1128					$field.attr( 'aria-describedby', descriptionId );
1129					$field.attr( 'aria-invalid', 'true' );
1130				}
1131			} );
1132		},
1133		scroll_to_notices: function () {
1134			var scrollElement = $(
1135				'.woocommerce-NoticeGroup-updateOrderReview, .woocommerce-NoticeGroup-checkout'
1136			);
1137
1138			if ( ! scrollElement.length ) {
1139				scrollElement = $( 'form.checkout' );
1140			}
1141			$.scroll_to_notices( scrollElement );
1142		},
1143	};
1144
1145	var wc_checkout_coupons = {
1146		init: function () {
1147			$( document.body ).on(
1148				'click',
1149				'a.showcoupon',
1150				this.show_coupon_form
1151			);
1152			$( document.body ).on(
1153				'click',
1154				'.woocommerce-remove-coupon',
1155				this.remove_coupon
1156			);
1157			$( document.body ).on(
1158				'keydown',
1159				'.woocommerce-remove-coupon',
1160				this.on_keydown_remove_coupon
1161			);
1162			$( document.body ).on(
1163				'change input',
1164				'#coupon_code',
1165				this.remove_coupon_error
1166			);
1167			$( 'form.checkout_coupon' )
1168				.hide()
1169				.on( 'submit', this.submit.bind( this ) );
1170		},
1171		show_coupon_form: function () {
1172			var $showcoupon = $( this );
1173
1174			$( '.checkout_coupon' ).slideToggle( 400, function () {
1175				var $coupon_form = $( this );
1176
1177				if ( $coupon_form.is( ':visible' ) ) {
1178					$showcoupon.attr( 'aria-expanded', 'true' );
1179					$coupon_form.find( ':input:eq(0)' ).trigger( 'focus' );
1180				} else {
1181					$showcoupon.attr( 'aria-expanded', 'false' );
1182				}
1183			} );
1184			return false;
1185		},
1186		show_coupon_error: function ( html_element, $target ) {
1187			if ( $target.length === 0 ) {
1188				return;
1189			}
1190
1191			this.remove_coupon_error();
1192
1193			var msg = $( $.parseHTML( html_element ) ).text().trim();
1194
1195			if ( msg === '' ) {
1196				return;
1197			}
1198
1199			$target
1200				.find( '#coupon_code' )
1201				.trigger( 'focus' )
1202				.addClass( 'has-error' )
1203				.attr( 'aria-invalid', 'true' )
1204				.attr( 'aria-describedby', 'coupon-error-notice' );
1205
1206			$( '<span>', {
1207				class: 'coupon-error-notice',
1208				id: 'coupon-error-notice',
1209				role: 'alert',
1210				text: msg,
1211			} ).appendTo( $target );
1212		},
1213		remove_coupon_error: function () {
1214			var $coupon_field = $( '#coupon_code' );
1215
1216			if ( $coupon_field.length === 0 ) {
1217				return;
1218			}
1219
1220			$coupon_field
1221				.removeClass( 'has-error' )
1222				.removeAttr( 'aria-invalid' )
1223				.removeAttr( 'aria-describedby' )
1224				.next( '.coupon-error-notice' )
1225				.remove();
1226		},
1227
1228		clear_coupon_input: function () {
1229			const $coupon_field = $( '#coupon_code' );
1230			$coupon_field
1231				.val( '' )
1232				.removeClass( 'has-error' )
1233				.removeAttr( 'aria-invalid' )
1234				.removeAttr( 'aria-describedby' )
1235				.next( '.coupon-error-notice' )
1236				.remove();
1237		},
1238		submit: function ( evt ) {
1239			var $form = $( evt.currentTarget );
1240			var $coupon_field = $form.find( '#coupon_code' );
1241			var self = this;
1242
1243			self.remove_coupon_error();
1244
1245			if ( $form.is( '.processing' ) ) {
1246				return false;
1247			}
1248
1249			$form.addClass( 'processing' ).block( {
1250				message: null,
1251				overlayCSS: {
1252					background: '#fff',
1253					opacity: 0.6,
1254				},
1255			} );
1256
1257			var data = {
1258				security: wc_checkout_params.apply_coupon_nonce,
1259				coupon_code: $form.find( 'input[name="coupon_code"]' ).val(),
1260				billing_email: wc_checkout_form.$checkout_form
1261					.find( 'input[name="billing_email"]' )
1262					.val(),
1263			};
1264
1265			$.ajax( {
1266				type: 'POST',
1267				url: wc_checkout_params.wc_ajax_url
1268					.toString()
1269					.replace( '%%endpoint%%', 'apply_coupon' ),
1270				data: data,
1271				success: function ( response ) {
1272					$(
1273						'.woocommerce-error, .woocommerce-message, .is-error, .is-success, .checkout-inline-error-message'
1274					).remove();
1275					$form.removeClass( 'processing' ).unblock();
1276
1277					if ( response ) {
1278						// We only want to show coupon notices if they are no errors.
1279						// Coupon errors are shown under the input.
1280						if (
1281							response.indexOf( 'woocommerce-error' ) === -1 &&
1282							response.indexOf( 'is-error' ) === -1
1283						) {
1284							$form.slideUp( 400, function () {
1285								$( 'a.showcoupon' ).attr(
1286									'aria-expanded',
1287									'false'
1288								);
1289								$form.before( response );
1290							} );
1291							self.clear_coupon_input();
1292						} else {
1293							self.show_coupon_error(
1294								response,
1295								$coupon_field.parent()
1296							);
1297						}
1298
1299						$( document.body ).trigger(
1300							'applied_coupon_in_checkout',
1301							[ data.coupon_code ]
1302						);
1303						$( document.body ).trigger( 'update_checkout', {
1304							update_shipping_method: false,
1305						} );
1306					}
1307				},
1308				dataType: 'html',
1309			} );
1310
1311			return false;
1312		},
1313		remove_coupon: function ( e ) {
1314			e.preventDefault();
1315
1316			var container = $( this ).parents(
1317					'.woocommerce-checkout-review-order'
1318				),
1319				coupon = $( this ).data( 'coupon' );
1320
1321			container.addClass( 'processing' ).block( {
1322				message: null,
1323				overlayCSS: {
1324					background: '#fff',
1325					opacity: 0.6,
1326				},
1327			} );
1328
1329			var data = {
1330				security: wc_checkout_params.remove_coupon_nonce,
1331				coupon: coupon,
1332			};
1333
1334			$.ajax( {
1335				type: 'POST',
1336				url: wc_checkout_params.wc_ajax_url
1337					.toString()
1338					.replace( '%%endpoint%%', 'remove_coupon' ),
1339				data: data,
1340				success: function ( code ) {
1341					$(
1342						'.woocommerce-error, .woocommerce-message, .is-error, .is-success'
1343					).remove();
1344					container.removeClass( 'processing' ).unblock();
1345
1346					if ( code ) {
1347						$( 'form.woocommerce-checkout' ).before( code );
1348
1349						$( document.body ).trigger(
1350							'removed_coupon_in_checkout',
1351							[ data.coupon ]
1352						);
1353						$( document.body ).trigger( 'update_checkout', {
1354							update_shipping_method: false,
1355						} );
1356
1357						// Remove coupon code from coupon field
1358						wc_checkout_coupons.clear_coupon_input();
1359						$( 'form.checkout_coupon' ).slideUp( 400, function () {
1360							$( 'a.showcoupon' ).attr(
1361								'aria-expanded',
1362								'false'
1363							);
1364						} );
1365					}
1366				},
1367				error: function ( jqXHR ) {
1368					if ( wc_checkout_params.debug_mode ) {
1369						/* jshint devel: true */
1370						console.log( jqXHR.responseText );
1371					}
1372				},
1373				dataType: 'html',
1374			} );
1375		},
1376		/**
1377		 * Handle when pressing the Space key on the remove coupon link.
1378		 * This is necessary because the link got the role="button" attribute
1379		 * and needs to act like a button.
1380		 *
1381		 * @param {Object} e The JQuery event
1382		 */
1383		on_keydown_remove_coupon: function ( e ) {
1384			if ( e.key === ' ' ) {
1385				e.preventDefault();
1386				$( this ).trigger( 'click' );
1387			}
1388		},
1389	};
1390
1391	var wc_checkout_login_form = {
1392		init: function () {
1393			$( document.body ).on(
1394				'click',
1395				'a.showlogin',
1396				this.show_login_form
1397			);
1398		},
1399		show_login_form: function () {
1400			var $form = $( 'form.login, form.woocommerce-form--login' );
1401			if ( $form.is( ':visible' ) ) {
1402				// If already visible, hide it.
1403				$form.slideToggle( {
1404					duration: 400,
1405				} );
1406			} else {
1407				// If not visible, show it and then scroll
1408				$form.slideToggle( {
1409					duration: 400,
1410					complete: function () {
1411						if ( $form.is( ':visible' ) ) {
1412							$( 'html, body' ).animate(
1413								{
1414									scrollTop: $form.offset().top - 50,
1415								},
1416								300
1417							);
1418						}
1419					},
1420				} );
1421			}
1422			return false;
1423		},
1424	};
1425
1426	var wc_terms_toggle = {
1427		init: function () {
1428			$( document.body ).on(
1429				'click',
1430				'a.woocommerce-terms-and-conditions-link',
1431				this.toggle_terms
1432			);
1433		},
1434
1435		toggle_terms: function () {
1436			if ( $( '.woocommerce-terms-and-conditions' ).length ) {
1437				$( '.woocommerce-terms-and-conditions' ).slideToggle(
1438					function () {
1439						var link_toggle = $(
1440							'.woocommerce-terms-and-conditions-link'
1441						);
1442
1443						if (
1444							$( '.woocommerce-terms-and-conditions' ).is(
1445								':visible'
1446							)
1447						) {
1448							link_toggle.addClass(
1449								'woocommerce-terms-and-conditions-link--open'
1450							);
1451							link_toggle.removeClass(
1452								'woocommerce-terms-and-conditions-link--closed'
1453							);
1454						} else {
1455							link_toggle.removeClass(
1456								'woocommerce-terms-and-conditions-link--open'
1457							);
1458							link_toggle.addClass(
1459								'woocommerce-terms-and-conditions-link--closed'
1460							);
1461						}
1462					}
1463				);
1464
1465				return false;
1466			}
1467		},
1468	};
1469
1470	wc_checkout_form.init();
1471	wc_checkout_coupons.init();
1472	wc_checkout_login_form.init();
1473	wc_terms_toggle.init();
1474} );

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.