PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 11.0.1
WooCommerce v11.0.1
11.1.0 11.1.0-rc.2 11.1.0-rc.1 11.1.0-beta.2 11.1.0-beta.1 11.0.1 11.0.0 11.0.0-rc.3 11.0.0-rc.2 11.0.0-rc.1 11.0.0-beta.2 11.0.0-beta.1 10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / assets / js / frontend / checkout.js
woocommerce / assets / js / frontend Last commit date
test 1 month ago utils 6 months ago a8c-address-autocomplete-service.js 1 month ago a8c-address-autocomplete-service.min.js 1 month ago account-i18n.js 2 years ago account-i18n.min.js 2 years ago add-payment-method.js 6 months ago add-payment-method.min.js 6 months ago add-to-cart-variation.js 2 months ago add-to-cart-variation.min.js 2 months ago add-to-cart.js 9 months ago add-to-cart.min.js 9 months ago address-autocomplete.js 3 months ago address-autocomplete.min.js 3 months ago address-i18n.js 3 months ago address-i18n.min.js 3 months ago back-in-stock-form.js 1 year ago back-in-stock-form.min.js 1 year ago cart-fragments.js 3 years ago cart-fragments.min.js 3 years ago cart.js 1 year ago cart.min.js 1 year ago checkout.js 1 month ago checkout.min.js 1 month ago country-select.js 2 years ago country-select.min.js 2 years ago credit-card-form.js 8 years ago credit-card-form.min.js 8 years ago geolocation.js 2 years ago geolocation.min.js 2 years ago lost-password.js 8 years ago lost-password.min.js 8 years ago order-attribution.js 7 months ago order-attribution.min.js 7 months ago order-review.js 2 months ago order-review.min.js 2 months ago password-strength-meter.js 1 year ago password-strength-meter.min.js 1 year ago price-slider.js 5 years ago price-slider.min.js 3 years ago single-product.js 9 months ago single-product.min.js 9 months ago tokenization-form.js 5 years ago tokenization-form.min.js 3 years ago woocommerce.js 3 months ago woocommerce.min.js 3 months ago wp-consent-api-integration.js 2 years ago wp-consent-api-integration.min.js 2 years ago
checkout.js
1475 lines
1 /* global wc_checkout_params */
2 jQuery( 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 = 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 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 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 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 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 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 always 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 .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 .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 } );
1475