PageSourceSearch

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

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

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.