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