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