1jQuery(function () { 2 // Multi-currency: changing the checkout currency reloads the page in that 3 // currency (?scp-currency + cookie), so every price/symbol updates together. 4 // Delegated so it survives scp_refresh_checkout re-rendering the summary. 5 jQuery(document).on('change', '#scp-checkout-currency-select', function () { 6 var c = this.value; 7 document.cookie = 'scp_checkout_currency=' + encodeURIComponent(c) + ';path=/;max-age=86400'; 8 // Two-step checkout: the currency change reloads the page to refresh every 9 // price/symbol, which would otherwise drop the buyer back to step 1. Remember 10 // the current step so the two-step init can restore it after the reload. 11 try { 12 var $ts = jQuery('.scp-checkout-two-step'); 13 if ($ts.length && $ts.hasClass('scp-on-step-2')) { 14 sessionStorage.setItem('scp_checkout_step', '2'); 15 } 16 } catch (e) {} 17 try { 18 var u = new URL(window.location.href); 19 u.searchParams.set('scp-currency', c); 20 window.location.href = u.toString(); 21 } catch (e) { 22 window.location.href = window.location.pathname + '?scp-currency=' + encodeURIComponent(c); 23 } 24 }); 25}); 26 27function scp_refresh_checkout() { 28 var form_id = jQuery('input[name="scp_checkout_form_id"]').val(); // Get the hidden input value 29 30 var data = { 31 action: 'scp_refresh_checkout', 32 scp_checkout_form_id: form_id // Include the new param here 33 }; 34 35 // Send the currency the page is showing so the re-rendered summary matches it. 36 // Without this the handler resolves cookie -> base, and a checkout opened via a 37 // ?scp-currency= link (no cookie yet) came back in the base currency, mixing 38 // symbols with the rest of the page. scp_get_checkout_currency() reads $_REQUEST, 39 // so posting it here is enough. 40 var cur = jQuery('#scp-checkout-currency-select').val(); 41 if (cur) { 42 data['scp-currency'] = cur; 43 } 44 45 jQuery.ajax({ 46 url: scp_checkout.ajax_url, 47 type: 'POST', 48 data: data, 49 success: function(response) { 50 if (response.success) { 51 jQuery('#scp-checkout-cart-summary').html(response.data.html.cart_summary); 52 53 if (response.data.total == 0 && response.data.require_payment == false) { 54 jQuery('input[name="scp_payment_gateway"][value="nopayment"]').prop('checked', true); 55 jQuery('.scp-payment-form').hide(); 56 } else { 57 jQuery('.scp-payment-form').show(); 58 if(response.data.has_multiple == 1){ 59 60 if(jQuery('input[name="scp_payment_gateway"][value="paypal"]').length > 0){ 61 jQuery('input[name="scp_payment_gateway"][value="paypal"]').closest('.scp-payment-method').hide(); 62 } 63 64 jQuery('input[name="scp_payment_gateway"][value="stripe"]').prop('checked', true); 65 }else{ 66 if(jQuery('input[name="scp_payment_gateway"][value="paypal"]').length > 0){ 67 jQuery('input[name="scp_payment_gateway"][value="paypal"]').closest('.scp-payment-method').show(); 68 } 69 } 70 71 if (jQuery('input[name="scp_payment_gateway"]:checked').val() === 'nopayment') { 72 jQuery('input[name="scp_payment_gateway"][value="stripe"]').prop('checked', true); 73 } 74 75 // Lazy-init Stripe if total went from $0 to > $0 (e.g. coupon removed) 76 // and Stripe is now the selected gateway. 77 if (typeof scp_lazy_init_stripe === 'function' && jQuery('input[name="scp_payment_gateway"][value="stripe"]').is(':checked')) { 78 scp_lazy_init_stripe(); 79 } 80 81 } 82 } else { 83 // handle error 84 } 85 }, 86 error: function() { 87 alert(scp_success_error_translations.scp_error_occured); 88 } 89 }); 90} 91 92 93 94 95function scp_process_payment(){ 96 97 console.log('Submit payment'); 98 99} 100 101 102 103function scp_add_order_bump(bump_id){ 104 105 jQuery.ajax({ 106 107 url: scp_checkout.ajax_url, // AJAX URL 108 109 type: 'POST', 110 111 data: { 112 113 action: 'scp_add_order_bump', 114 115 bump_id : bump_id, 116 117 }, 118 119 success: function(response) { 120 121 if(response.success) { 122 123 // Update checkout with new data 124 125 scp_refresh_checkout(); 126 127 } else { 128 129 130 131 } 132 133 }, 134 135 error: function() { 136 137 alert(scp_success_error_translations.scp_error_occured); 138 139 } 140 141 }); 142 143} 144 145 146 147function scp_process_nopayment($form){ 148 149 150 151 var formData = jQuery('#scp-checkout-form').serialize(); 152 153 154 155 var data = { 156 157 'action': 'scp_confirm_payment', 158 159 'payment_method': 'nopayment', 160 161 'data' : formData 162 163 }; 164 165 166 167 jQuery.post(scp_checkout.ajax_url, data, function(response) { 168 169 if (response.error) { 170 171 paymentErrors.textContent = '<div class="scp-pay-payment-error">'+response.error+'</div>'; 172 173 jQuery('.scp-payment-process-button').removeClass('scp-action-btn').removeClass('scp-loading'); 174 175 jQuery('.scp-submit-button').removeClass('scp-show'); 176 177 } else { 178 179 jQuery('.scp-submit-button').removeClass('scp-show'); 180 181 window.location.href = response.redirect; 182 183 184 185 console.log(response); 186 187 //window.location.href = '/payment-success/'; 188 189 } 190 191 }); 192 193} 194 195 196 197function scp_validate_billing_address_fields() { 198 199 var isValid = true; // Flag to track if the billing address fields are valid 200 201 202 203 // Billing address fields to validate (by name) 204 205 var billingFields = { 206 207 address_line1: { 208 209 selector: '[name="address_line1"]', 210 211 errorMessage: scp_success_error_translations.scp_address_line_is_required 212 213 }, 214 215 country: { 216 217 selector: '[name="country"]', 218 219 errorMessage: scp_success_error_translations.scp_country_is_required 220 221 }, 222 223 zipcode: { 224 225 selector: '[name="zipcode"]', 226 227 errorMessage: scp_success_error_translations.scp_zipcode_is_required 228 229 }, 230 231 city: { 232 233 selector: '[name="city"]', 234 235 errorMessage: scp_success_error_translations.scp_city_is_required 236 237 }, 238 239 state: { 240 241 selector: '[name="state"]', 242 243 errorMessage: scp_success_error_translations.scp_state_is_required 244 245 } 246 247 }; 248 249 250 251 // Loop through each billing address field and validate 252 253 jQuery.each(billingFields, function(field, config) { 254 255 var input = jQuery(config.selector); // Get the input element by name 256 257 258 259 // Check if the field exists and is visible 260 261 if (input.length > 0 && input.is(':visible')) { 262 263 var value = input.val().trim(); // Get the value and trim it 264 265 266 267 // Remove any previous error messages 268 269 input.next('.scp-inline-error-message').remove(); 270 271 272 273 // Check if the field is empty (required fields) 274 275 if (value === '') { 276 277 isValid = false;
278 279 var errorSpan = jQuery('<span class="scp-inline-error-message" style="color:red; font-size:14px; margin-top:5px;">' + config.errorMessage + '</span>'); 280 281 input.after(errorSpan); // Append error message after the field 282 283 } 284 285 } 286 287 }); 288 289 290 291 return isValid; 292 293} 294 295 296 297function scp_validate_core_fields() { 298 299 var isValid = true; // Flag to track if the core fields are valid 300 301 302 303 // Core fields to validate (First Name, Last Name, Email) 304 305 var coreFields = { 306 307 firstname: { 308 309 selector: '#firstname', 310 311 errorMessage: scp_success_error_translations.scp_firstname_is_required 312 313 }, 314 315 lastname: { 316 317 selector: '#lastname', 318 319 errorMessage: scp_success_error_translations.scp_lastname_is_required 320 321 }, 322 323 email: { 324 325 selector: '#email', 326 327 errorMessage: scp_success_error_translations.scp_email_is_required 328 329 } 330 331 }; 332 333 334 335 // Loop through each core field and validate 336 337 jQuery.each(coreFields, function(field, config) { 338 339 var input = jQuery(config.selector); // Get the input element 340 341 var value = input.val().trim(); // Get the value and trim it 342 343 344 345 // Remove any previous error messages 346 347 input.next('.scp-inline-error-message').remove(); 348 349 350 351 // Check if the field is empty (required fields) 352 353 if (value === '') { 354 355 isValid = false; 356 357 var errorSpan = jQuery('<span class="scp-inline-error-message" style="color:red; font-size:14px; margin-top:5px;">' + config.errorMessage + '</span>'); 358 359 input.after(errorSpan); // Append error message after the field 360 361 } 362 363 364 365 // Additional email validation for email field 366 367 if (field === 'email' && value !== '') { 368 369 // Simple email regex check 370 371 var emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 372 373 if (!emailPattern.test(value)) { 374 375 isValid = false; 376 377 var errorSpan = jQuery('<span class="scp-inline-error-message" style="color:red; font-size:14px; margin-top:5px;">'+scp_success_error_translations.scp_enter_valid_email_address+'</span>'); 378 379 input.after(errorSpan); 380 381 } 382 383 } 384 385 }); 386 387 388 389 return isValid; 390 391} 392 393 394 395function scp_validate_custom_fields() { 396 397 var isValid = true; // Flag to track if the form is valid 398 399 400 401 // Select all required fields with the class 'scp-require-field' 402 403 var requiredFields = jQuery('.scp-require-field'); 404 405 406 407 // Loop through each required field 408 409 requiredFields.each(function() { 410 411 var input = jQuery(this).find('input, select, textarea'); // Select input, select, or textarea 412 413 var errorMessage = scp_success_error_translations.scp_field_required; 414 415 var inputType = input.prop('type'); 416 417 418 419 // Remove any previous error messages 420 421 jQuery(this).find('.scp-inline-error-message').remove(); 422 423 424 425 // For text, textarea, and select elements 426 427 if (inputType === 'text' || inputType === 'textarea') { 428 429 if (input.val().trim() === '') { 430 431 isValid = false; 432 433 var errorSpan = jQuery('<span class="scp-inline-error-message" style="color:red; font-size:14px; margin-top:5px;">' + errorMessage + '</span>'); 434 435 jQuery(this).append(errorSpan); 436 437 } 438 439 } 440 441 442 443 // For radio and checkbox inputs 444 445 else if (inputType === 'radio' || inputType === 'checkbox') { 446 447 if (!jQuery(this).find('input:checked').length) { 448 449 isValid = false; 450 451 var errorSpan = jQuery('<span class="scp-inline-error-message" style="color:red; font-size:14px; margin-top:5px;">' + errorMessage + '</span>'); 452 453 jQuery(this).append(errorSpan); 454 455 } 456 457 } 458 459 460 461 // For select elements 462 463 else if (input.is('select')) { 464 465 if (input.val() === '' || input.val() === null) { 466 467 isValid = false; 468 469 var errorSpan = jQuery('<span class="scp-inline-error-message" style="color:red; font-size:12px; margin-top:5px;">' + errorMessage + '</span>'); 470 471 jQuery(this).append(errorSpan); 472 473 } 474 475 } 476 477 }); 478 479 var requiredFields = jQuery('.scp-require-field'); 480 481 var errorMessage = scp_success_error_translations.scp_field_required; 482 // Loop through each required field 483 requiredFields.each(function() { 484 var $field = jQuery(this); 485 486 // Check if it has the 'scp-fields-type-phone' class 487 if ($field.hasClass('scp-fields-type-phone')) { 488 // Find input of type tel inside it 489 var telInput = $field.find('input[type="tel"]'); 490 491 // Check if such an input exists 492 if (telInput.length > 0) { 493 var telId = telInput.attr('id'); 494 if(!isPhoneNumberValid(telId)){ 495 496 isValid = false;
497 var errorSpan = jQuery('<span class="scp-inline-error-message" style="color:red; font-size:12px; margin-top:5px;">' + errorMessage + '</span>'); 498 jQuery(this).append(errorSpan); 499 500 } 501 502 503 } 504 } 505 }); 506 507 508 509 510 return isValid; 511 512} 513 514 515 516function showStatsofCountry(country_code){ 517 518 519 520 // Reference to the select and input fields 521 522 var $selectField = jQuery('#scp_billing_state'); 523 524 var $inputField = jQuery('#state_disabled'); 525 526 527 528 if (country_code === 'US' || country_code === 'CA') { 529 530 // Show the select field 531 532 $selectField.show().attr('name', 'state'); 533 534 $inputField.hide().attr('name', 'state_disabled'); 535 536 jQuery('.scp-billing-state-field').addClass('scp-form-selectbox'); 537 538 } else { 539 540 // Show the input field 541 542 $inputField.show().attr('name', 'state'); 543 544 $selectField.hide().attr('name', 'state_disabled'); 545 546 jQuery('.scp-billing-state-field').removeClass('scp-form-selectbox'); 547 548 } 549 550 551 552} 553 554 555 556 557 558function showShippingStatsofCountry(country_code){ 559 560 561 562 // Reference to the select and input fields 563 564 var $selectField = jQuery('#scp_shipping_state'); 565 566 var $inputField = jQuery('#shipping_state_disabled'); 567 568 569 570 if (country_code === 'US' || country_code === 'CA') { 571 572 // Show the select field 573 574 $selectField.show().attr('name', 'shipping_state'); 575 576 $inputField.hide().attr('name', 'shipping_state_disabled'); 577 578 } else { 579 580 // Show the input field 581 582 $inputField.show().attr('name', 'shipping_state'); 583 584 $selectField.hide().attr('name', 'shipping_state_disabled'); 585 586 } 587 588 589 590} 591 592 593 594var itiInstances = {}; 595 596 597 598function isPhoneNumberValid(inputId) { 599 600 if (itiInstances[inputId]) { 601 602 return itiInstances[inputId].isValidNumber(); 603 604 } 605 606 return false; 607 608} 609 610 611 612 613function scp_print_checkout_error(message){ 614 615 //jQuery('.scp-checkout-error-main').show(); 616 617 //jQuery('.scp-checkout-error-main').html(message); 618 619 620 jQuery('.scp-checkout-errro-all').show(); 621 622 jQuery('.scp-checkout-errro-all').html(message); 623 624 625} 626 627 628 629function scp_clear_checkout_error(){ 630 631 jQuery('.scp-checkout-error-main').hide(); 632 633 jQuery('.scp-checkout-error-main').html(''); 634 635 jQuery('.scp-checkout-errro-all').hide(); 636 jQuery('.scp-checkout-errro-all').html(''); 637 638} 639 640jQuery(document).ready(function() { 641 642 643 var $gateways = jQuery('input[name="scp_payment_gateway"]:first'); 644 645 if ($gateways.length === 1) { 646 $gateways.prop('checked', true).trigger('change'); 647 } 648 649 650 jQuery('.scp-fields-type-phone').each(function () { 651 652 var $input = jQuery(this).find('.scp-form-field__input'); 653 654 var defaultCountry = $input.attr('data-country') || 'us'; 655 var inputId = $input.attr('id'); 656 657 var hiddenInputFull = jQuery('<input>', { 658 type: 'hidden', 659 name: $input.attr('name') + '_full', 660 class: 'full_phone_number' 661 }); 662 663 var hiddenInputCountry = jQuery('<input>', { 664 type: 'hidden', 665 name: $input.attr('name') + '_country', 666 class: 'phone_country_code' 667 }); 668 669 $input.after(hiddenInputFull).after(hiddenInputCountry); 670 671 if (typeof itiInstances === 'undefined') { 672 itiInstances = {}; 673 } 674 675 var itiInstance = window.intlTelInput($input[0], { 676 autoPlaceholder: 'polite', 677 formatOnDisplay: true, 678 initialCountry: defaultCountry, 679 utilsScript: 680 'https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/11.0.14/js/utils.js' 681 }); 682 683 itiInstances[inputId] = itiInstance; 684 685 function updatePhoneNumber() { 686 687 var selectedCountryData = itiInstance.getSelectedCountryData(); 688 if (!selectedCountryData) return; 689 690 var iso = selectedCountryData.iso2.toLowerCase(); 691 692 var newPlaceholder = intlTelInputUtils.getExampleNumber( 693 iso, 694 true, 695 intlTelInputUtils.numberFormat.INTERNATIONAL 696 ); 697 698 if (!newPlaceholder) return; 699 700 // Base mask 701 var maskPattern = newPlaceholder.replace(/[0-9]/g, '0'); 702 703 // ð®ð© Indonesia & ð©ðª Germany â allow variable length 704 if (iso === 'id' || iso === 'de') { 705 maskPattern += ' 99999'; 706 } 707 708 // Remove old mask 709 $input.unmask(); 710 711 // Apply new mask safely 712 $input.mask(maskPattern, { 713 placeholder: newPlaceholder, 714 clearIfNotMatch: false, 715 translation: { 716 '0': { pattern: /[0-9]/ }, 717 '9': { pattern: /[0-9]/, optional: true } 718 } 719 }); 720 721 // ð¥ prevent digit cut 722 $input.removeAttr('maxlength'); 723 $input.attr('placeholder', newPlaceholder); 724 } 725 726 $input.on('countrychange', function () { 727 var selectedCountryData = itiInstance.getSelectedCountryData(); 728 if (!selectedCountryData) return; 729 730 hiddenInputCountry.val(selectedCountryData.iso2); 731 732 itiInstance.setNumber(''); 733 updatePhoneNumber(); 734 }); 735 736 $input.on('blur', function () { 737 if (!itiInstance.isValidNumber()) { 738 $input.addClass('error'); 739 } else { 740 $input.removeClass('error'); 741 hiddenInputFull.val(itiInstance.getNumber()); // E.164 742 } 743 }); 744 745 itiInstance.promise.then(function () { 746 $input.trigger('countrychange'); 747 }); 748 749}); 750 751 752 753 754
755 jQuery(document).on('click','#scp-checkout-credit-login', function() { 756 jQuery('.smart-forum-login-register-left-side .scp-tempplate-preview-box').addClass('scp-popup-active'); 757 jQuery('#scp_checkout_redirect').val('checkout'); 758 }); 759 760 jQuery('.scp-pp-checkbox').on('change', function() { 761 762 jQuery('.scp-pp-pricing-option').removeClass('selected'); 763 764 jQuery(this).closest('.scp-pp-pricing-option').addClass('selected'); 765 766 }); 767 768 769 770 if(jQuery('#scp-checkout').length > 0){ 771 772 scp_refresh_checkout(); 773 774 var country = jQuery('#scp_billing_country').val(); 775 776 showStatsofCountry(country); 777 778 779 780 var s_country = jQuery('#scp_shipping_country').val(); 781 782 showShippingStatsofCountry(s_country); 783 784 } 785 786 787 788 function scp_process_checkout(form){ 789 790 791 792 var $form = jQuery('#scp-checkout-form'); 793 794 gateway = $form.find('input[name=scp_payment_gateway]:checked').val(); 795 796 797 798 if(gateway == 'stripe'){ 799 800 scp_process_stripe($form); 801 802 }else if(gateway == 'nopayment'){ 803 804 scp_process_nopayment($form); 805 806 }else if(gateway == 'paypal'){ 807 808 scp_process_paypal($form); 809 810 } 811 812 813 814 } 815 816 817 818 819 820 821 822 function scp_scrollto_error(){ 823 824 825 826 jQuery('html, body').animate({ 827 828 scrollTop: jQuery('.scp-inline-error-message:first').closest('.scp-form-field').offset().top - 60 829 830 }, 500); 831 832 833 834 } 835 836 837 838 function scp_validate_checkout(){ 839 840 841 842 jQuery('.scp-inline-error-message').remove(); 843 jQuery('.scp-checkout-errro-all').hide(); 844 845 846 847 if(!scp_validate_core_fields()){ 848 849 scp_scrollto_error(); 850 851 return false; 852 853 } 854 855 856 857 if(!scp_validate_billing_address_fields()){ 858 859 scp_scrollto_error(); 860 861 return false; 862 863 } 864 865 866 867 if(!scp_validate_custom_fields()){ 868 869 scp_scrollto_error(); 870 871 return false; 872 873 } 874 875 876 877 878 var is_pass = true; 879 880 if(jQuery('.scp-terms-box').length > 0){ 881 882 jQuery('.scp-terms-box').each(function() { 883 884 var errorMessage = scp_success_error_translations.scp_terms_required_to_complete; 885 886 var $termsBox = jQuery(this); 887 888 var $checkbox = $termsBox.find('.scp-terms-checkbox'); 889 890 if ($termsBox.hasClass('require-terms') && !$checkbox.is(':checked')) { 891 892 var errorSpan = jQuery('<span class="scp-inline-error-message" style="color:red; font-size:14px; margin-top:5px;">'+errorMessage+'</span>'); 893 894 $termsBox.after(errorSpan); 895 896 is_pass = false; 897 898 } 899 900 }); 901 902 903 904 if(!is_pass){ 905 906 return false; 907 908 } 909 910 } 911 912 913 914 return true; 915 916 } 917 918 919 920 jQuery(document).on('click', '.scp-close-btn', function(event) { 921 922 923 924 jQuery('#scp-change-priceplan-popup').hide(); 925 926 927 928 }); 929 930 931 932 jQuery(document).on('submit', '#scp-checkout-form', function(event) { 933 934 935 936 scp_clear_checkout_error(); 937 938 if(scp_validate_checkout()){ 939 940 jQuery('.scp-submit-button').addClass('scp-loader-active'); 941 942 scp_process_checkout(); 943 944 }else{ 945 jQuery('.scp-checkout-errro-all').show(); 946 } 947 948 949 950 return false; 951 952 }); 953 954 955 956 jQuery(document).on('change', '.scp-bump-checkbox', function() { 957 958 var bump_id = jQuery(this).attr('data-bumpid'); 959 960 scp_add_order_bump(bump_id); 961 962 }); 963 964 965 966 967 968 969 970 jQuery(document).on('input', '#scp-apply-coupon', function() { 971 972 jQuery('.scp-coupon-apply-wrapper').toggleClass('scp-show-apply', jQuery(this).val().trim() !== ''); 973 974 }); 975 976 977 jQuery(document).on('change', '#scp-apply-credit-discount', function () { 978 979 var isChecked = jQuery(this).is(':checked'); 980 981 jQuery.ajax({ 982 url: scp_checkout.ajax_url, 983 method: 'POST', 984 data: { 985 action: 'scp_apply_credit_discount', 986 apply_credit: isChecked ? 1 : 0 987 }, 988 beforeSend: function () { 989 // You can show loader if needed 990 }, 991 success: function (response) { 992 if (response.success) { 993 if (jQuery('#scp-cart-v2-sidebar').length){ 994 updateCartSidebar(); 995 } else { 996 scp_refresh_checkout(); 997 } 998 } else { 999 alert(response.data.message || scp_success_error_translations.scp_error_occured); 1000 } 1001 }, 1002 error: function () { 1003 alert(scp_success_error_translations.scp_error_occured); 1004 } 1005 }); 1006 1007 }); 1008 1009 1010 jQuery(document).on('click', '#scp-apply-coupon-button', function() { 1011 1012 1013 jQuery('.scp-coupen-error-msg').html('').hide(); 1014 1015 var promotionCode = jQuery('#scp-apply-coupon').val(); 1016 1017 1018 1019 if (!promotionCode) { 1020 1021 alert(scp_success_error_translations.scp_please_enter_coupon_code); 1022 1023 return; 1024 1025 } 1026 1027 1028 1029 jQuery.ajax({ 1030 1031 url: scp_checkout.ajax_url, 1032 1033 method: 'POST', 1034 1035 data: { 1036 1037 action: 'scp_apply_coupon', 1038 1039 promotion_code: promotionCode 1040 1041 }, 1042 1043 beforeSend: function () { 1044 1045 //$('#apply-coupon-btn').prop('disabled', true).text('Applying...'); 1046 1047 }, 1048 1049 success: function (response) { 1050 1051 if (response.success) { 1052 if (jQuery('#scp-cart-v2-sidebar').length){ 1053 updateCartSidebar(); 1054 }else{ 1055 scp_refresh_checkout(); 1056 } 1057 1058 } else { 1059 1060 jQuery('.scp-coupen-error-msg').html('<p>'+response.data.message+'</p>').show(); 1061 setTimeout(function() { 1062 jQuery('.scp-coupen-error-msg').fadeOut(); // or use .hide() if no animation needed 1063 }, 5000); 1064 } 1065 1066 }, 1067 1068 error: function () { 1069 1070 alert(scp_success_error_translations.scp_error_occured); 1071 1072 }, 1073 1074 complete: function () { 1075 1076 //$('#apply-coupon-btn').prop('disabled', false).text('Apply Coupon'); 1077 1078 }, 1079 1080 }); 1081 1082 1083 1084 }); 1085 1086 1087 1088 jQuery(document).on('click', '.scp-coupon-remove-button', function() { 1089 1090 1091 1092 1093 1094 jQuery.ajax({ 1095 1096 url: scp_checkout.ajax_url, 1097 1098 method: 'POST', 1099 1100 data: { 1101 1102 action: 'scp_remove_coupon' 1103 1104 }, 1105 1106 beforeSend: function () { 1107 1108 //$('#apply-coupon-btn').prop('disabled', true).text('Applying...'); 1109 1110 }, 1111 1112 success: function (response) { 1113 1114 if (response.success) { 1115 1116 if (jQuery('#scp-cart-v2-sidebar').length){ 1117 updateCartSidebar(); 1118 }else{ 1119 scp_refresh_checkout(); 1120 } 1121 1122 jQuery('.scp-coupon-apply-wrapper').show(); 1123 1124 jQuery('.scp-coupon-applied-wrapper').hide(); 1125 1126 } else { 1127 1128 alert(response.data.message); 1129 1130 } 1131 1132 }, 1133 1134 error: function () { 1135 1136 alert(scp_success_error_translations.scp_error_occured); 1137 1138 }, 1139 1140 complete: function () { 1141 1142 //jQuery('#apply-coupon-btn').prop('disabled', false).text('Apply Coupon'); 1143 1144 }, 1145 1146 }); 1147 1148 1149 1150 }); 1151 1152 1153 1154 jQuery(document).on('change', '#billingaddresson', function() { 1155 1156 if (jQuery(this).is(':checked')) { 1157 1158 jQuery('.scp-shipping-wrapper').show(); // Show the shipping form when the checkbox is checked 1159 1160 } else { 1161 1162 jQuery('.scp-shipping-wrapper').hide(); // Hide the shipping form when the checkbox is unchecked 1163 1164 } 1165 1166 }); 1167 1168 1169 1170 jQuery(document).on('change', '#scp_billing_country', function() { 1171 1172 var country = jQuery(this).val(); 1173 1174 showStatsofCountry(country); 1175 1176 jQuery.ajax({ 1177 1178 url: scp_checkout.ajax_url, 1179 1180 type: 'POST', 1181 1182 data: { 1183 1184 action: 'scp_country_change', 1185 1186 country : country, 1187 1188 1189 1190 }, 1191 1192 success: function(response) { 1193 1194 if(response.is_tax) { 1195 1196 scp_refresh_checkout(); 1197 1198 }else{ 1199 1200 jQuery('#scp_billing_state').html(response.states); 1201 1202 } 1203 1204 1205 1206 /*if(response.state_required){ 1207 1208 jQuery('.scp-billing-state-field').show(); 1209 1210 }else{ 1211 1212 jQuery('.scp-billing-state-field').hide(); 1213 1214 }*/ 1215 1216 1217 1218 }, 1219 1220 error: function(error) { 1221 1222 console.error(error); 1223 1224 } 1225 1226 }); 1227 1228 }); 1229 1230 1231 1232 jQuery(document).on('change', '#scp_shipping_country', function() { 1233 1234 var country = jQuery(this).val(); 1235 1236 showShippingStatsofCountry(country); 1237 1238 jQuery.ajax({ 1239 1240 url: scp_checkout.ajax_url, 1241 1242 type: 'POST', 1243 1244 data: { 1245 1246 action: 'scp_country_change', 1247 1248 country : country, 1249 1250 1251 1252 }, 1253 1254 success: function(response) { 1255 1256 1257 1258 jQuery('#scp_shipping_state').html(response.states); 1259 1260 1261 1262 /*if(response.state_required){ 1263 1264 jQuery('.scp-billing-state-field').show(); 1265 1266 }else{ 1267 1268 jQuery('.scp-billing-state-field').hide(); 1269 1270 }*/ 1271 1272 1273 1274 }, 1275 1276 error: function(error) { 1277 1278 console.error(error); 1279 1280 } 1281 1282 }); 1283 1284 }); 1285 1286 1287 1288 jQuery(document).on('change', 'select#scp_billing_state', function() { 1289 1290 var country = jQuery('#scp_billing_country').val(); 1291 1292 1293 1294 if(country == 'US' || country == 'CA') { 1295 1296 1297 1298 var state = jQuery(this).val(); 1299 1300 jQuery.ajax({ 1301 1302 url: scp_checkout.ajax_url, 1303 1304 type: 'POST', 1305 1306 data: { 1307 1308 action: 'scp_country_change', 1309 1310 country : country, 1311 1312 state : state, 1313 1314 1315 1316 }, 1317 1318 success: function(response) { 1319 1320 scp_refresh_checkout(); 1321 1322 }, 1323 1324 error: function(error) { 1325 1326 console.error(error); 1327 1328 } 1329 1330 }); 1331 1332 }else{ 1333 1334 1335 1336 } 1337 1338 }); 1339 1340 1341 1342 jQuery(document).on('click', '.scp-checkout-order-item-quantity', function() { 1343 1344 var productid = jQuery(this).attr('data-productid'); 1345 1346 jQuery('#scp-productid').val(productid); 1347 1348 jQuery('#scp-qty-popup').fadeIn(200); 1349 1350 }); 1351 1352 1353 1354 1355 1356 jQuery(document).on('change', 'input[name="scp_payment_gateway"]', function() { 1357 1358 var selectedValue = jQuery('input[name="scp_payment_gateway"]:checked').val(); 1359 1360 jQuery('.scp-payment-gateway-form').hide(); 1361 1362 jQuery('.scp-payment-gateway-form.scp-gateway-type-'+selectedValue).show(); 1363 1364 }); 1365 1366 1367 1368 // Inline plan templates: selecting a plan updates the cart via the SAME 1369 // handler used by the popup (scp_update_pricing_plan). No popup involved. 1370 jQuery(document).on('change', '.scp-inline-plan-radio', function() { 1371 1372 var $radio = jQuery(this); 1373 var $wrap = $radio.closest('.scp-inline-plans'); 1374 var productid = $radio.attr('data-productid') || $wrap.attr('data-productid'); 1375 var planid = $radio.val(); 1376 1377 // Reflect the selection immediately for responsiveness. 1378 $wrap.find('.scp-inline-plan').removeClass('selected'); 1379 $radio.closest('.scp-inline-plan').addClass('selected'); 1380 1381 jQuery.ajax({ 1382 url: scp_checkout.ajax_url, 1383 type: 'POST', 1384 data: { 1385 action: 'scp_update_pricing_plan', 1386 product_id: productid, 1387 'scp-pricing': planid 1388 }, 1389 success: function(response) { 1390 if (response.success) { 1391 jQuery('#scp-checkout-cart-summary').html(response.data.html.cart_summary); 1392 } 1393 }, 1394 error: function(error) { 1395 console.error(error); 1396 } 1397 }); 1398 1399 }); 1400 1401 1402 1403 jQuery(document).on('click', '.scp-change-price-plan', function() { 1404 1405 var productid = jQuery(this).attr('data-productid'); 1406 1407 var planid = jQuery(this).attr('data-planid'); 1408 1409 jQuery('#scp-change-priceplan-popup').show(); 1410 1411 jQuery('.scp-price-plan-list').html(''); 1412 1413 jQuery.ajax({ 1414 1415 url: scp_checkout.ajax_url, // Use the localized ajax_url 1416 1417 type: 'POST', 1418 1419 data: { 1420 1421 action: 'scp_get_pricing_plan', 1422 1423 productid : productid, 1424 1425 planid : planid 1426 1427 }, 1428 1429 success: function(response) { 1430 1431 jQuery('.scp-price-plan-list').html(response); 1432 1433 1434 1435 console.log(response); 1436 1437 }, 1438 1439 error: function(error) { 1440 1441 console.error(error); 1442 1443 } 1444 1445 }); 1446 1447 1448 1449 }); 1450 1451 1452 1453 jQuery(document).on('submit', '#scp-form-change-qty', function(event) { 1454 1455 event.preventDefault(); // Prevent the default form submission 1456 1457 1458 1459 // Serialize the form data 1460 1461 var formData = jQuery(this).serialize(); 1462 1463 1464 1465 // Append the product ID to the serialized data 1466 1467 formData += '&action=scp_update_qty'; 1468 1469 1470 1471 jQuery.ajax({ 1472 1473 url: scp_checkout.ajax_url, // Use the localized ajax_url 1474 1475 type: 'POST', 1476 1477 data: formData, 1478 1479 success: function(response) { 1480 1481 if(response.success) { 1482 1483 jQuery('#scp-checkout-cart-summary').html(response.data.html.cart_summary); 1484 1485 } 1486 1487 }, 1488 1489 error: function(error) { 1490 1491 console.error(error); 1492 1493 } 1494 1495 }); 1496 1497 1498 1499 return false;
1500 1501 }); 1502 1503 1504 1505 jQuery(document).on('submit', '#scp-form-change-price-plan', function(event) { 1506 1507 event.preventDefault(); // Prevent the default form submission 1508 1509 1510 1511 // Serialize the form data 1512 1513 var formData = jQuery(this).serialize(); 1514 1515 1516 1517 // Append the product ID to the serialized data 1518 1519 formData += '&action=scp_update_pricing_plan'; 1520 1521 1522 1523 jQuery.ajax({ 1524 1525 url: scp_checkout.ajax_url, // Use the localized ajax_url 1526 1527 type: 'POST', 1528 1529 data: formData, 1530 1531 success: function(response) { 1532 1533 1534 1535 if(response.success) { 1536 1537 jQuery('#scp-checkout-cart-summary').html(response.data.html.cart_summary); 1538 1539 } 1540 1541 jQuery('#scp-change-priceplan-popup').hide(); 1542 1543 console.log(response); 1544 1545 }, 1546 1547 error: function(error) { 1548 1549 console.error(error); 1550 1551 } 1552 1553 }); 1554 1555 1556 1557 return false; 1558 1559 }); 1560 1561 1562 1563 1564 1565 /*jQuery('body').on('change', '.quantity input, .product-variation select', function() { 1566 1567 var cartData = { 1568 1569 action: 'update_cart', 1570 1571 cart_item_key: $(this).data('cart-item-key'), 1572 1573 quantity: $(this).val(), 1574 1575 // Other data like selected pricing plan can be added here 1576 1577 }; 1578 1579 1580 1581 $.post(wc_cart_params.ajax_url, cartData, function(response) { 1582 1583 if (response.success) { 1584 1585 // Update cart summary 1586 1587 $('.cart-total').html(response.data.total); 1588 1589 $('.cart-items').html(response.data.items); 1590 1591 } 1592 1593 }); 1594 1595 });*/ 1596 1597 1598 1599}); 1600 1601(function ($) { 1602 1603 var scp_abandonment_sent = false; 1604 1605 function scp_is_checkout_form() { 1606 return $('input[name="is_scp_checkout_form"]').val() === '1'; 1607 } 1608 1609 /** 1610 * Get product IDs from hidden field (supports array) 1611 */ 1612 function scp_get_product_ids() { 1613 var raw = $('input[name="scp_cart_product_ids"]').val(); 1614 1615 if (!raw) return []; 1616 1617 try { 1618 var products = JSON.parse(raw); 1619 1620 if (Array.isArray(products)) { 1621 return products.map(String).sort(); 1622 } 1623 } catch (e) {} 1624 1625 return []; 1626 } 1627 1628 /** 1629 * Build sessionStorage key per product set 1630 */ 1631 function scp_get_session_key() { 1632 var products = scp_get_product_ids(); 1633 1634 if (!products.length) { 1635 return 'scp_cart_abandonment_fired'; 1636 } 1637 1638 return 'scp_cart_abandonment_fired_' + products.join('_'); 1639 } 1640 1641 /** 1642 * Get checkout email safely 1643 */ 1644 function scp_get_checkout_email() { 1645 var email = $('#email').val(); 1646 return email ? email.trim() : ''; 1647 } 1648 1649 function scp_get_firstname() { 1650 var firstname = $('#firstname, input[name="firstname"]').val(); 1651 return firstname ? firstname.trim() : ''; 1652 } 1653 1654 function scp_get_lastname() { 1655 var lastname = $('#lastname, input[name="lastname"]').val(); 1656 return lastname ? lastname.trim() : ''; 1657 } 1658 1659 /** 1660 * 2-step checkout (templates 4 & 5, marked with .scp-checkout-two-step) 1661 * handles its own abandonment via the Step-1 register + 30-min cron flow, so 1662 * the 1-step exit-intent ('scp_cart_abandonment') must NOT fire there. 1663 */ 1664 function scp_is_two_step_checkout() { 1665 return $('.scp-checkout-two-step').length > 0; 1666 } 1667 1668 /** 1669 * Fire cart abandonment AJAX 1670 */ 1671 function scp_trigger_cart_abandonment(reason) { 1672 1673 if (scp_is_two_step_checkout()) return; 1674 1675 var sessionKey = scp_get_session_key(); 1676 1677 if (scp_abandonment_sent) return; 1678 if (sessionStorage.getItem(sessionKey) === '1') return; 1679 1680 var email = scp_get_checkout_email(); 1681 if (!email) return; 1682 1683 var form_id = $('input[name="scp_checkout_form_id"]').val(); 1684 if (!form_id) return; 1685 1686 var firstname = scp_get_firstname(); 1687 var lastname = scp_get_lastname(); 1688 1689 scp_abandonment_sent = true; 1690 sessionStorage.setItem(sessionKey, '1'); 1691 1692 console.log('Cart ab....'); 1693 1694 $.ajax({ 1695 url: scp_checkout.ajax_url, 1696 type: 'POST', 1697 data: { 1698 action: 'scp_cart_abandonment', 1699 nonce: scp_checkout.abandon_nonce, 1700 scp_checkout_form_id: form_id, 1701 email: email, 1702 firstname: firstname, 1703 lastname: lastname, 1704 reason: reason 1705 } 1706 }); 1707 } 1708 1709 /** 1710 * Fire cart abandonment using sendBeacon 1711 */ 1712 function scp_trigger_cart_abandonment_beacon(reason) { 1713 1714 if (scp_is_two_step_checkout()) return; 1715 1716 if (!scp_is_checkout_form()) return; 1717 1718 var sessionKey = scp_get_session_key(); 1719 1720 if (scp_abandonment_sent) return; 1721 if (sessionStorage.getItem(sessionKey) === '1') return; 1722 1723 var email = scp_get_checkout_email(); 1724 if (!email) return; 1725 1726 var form_id = $('input[name="scp_checkout_form_id"]').val(); 1727 if (!form_id) return; 1728 1729 scp_abandonment_sent = true; 1730 sessionStorage.setItem(sessionKey, '1'); 1731 1732 console.log('Cart ab....'); 1733 1734 var data = new FormData(); 1735 data.append('action', 'scp_cart_abandonment'); 1736 data.append('nonce', scp_checkout.abandon_nonce); 1737 data.append('scp_checkout_form_id', form_id); 1738 data.append('email', email); 1739 data.append('firstname', scp_get_firstname()); 1740 data.append('lastname', scp_get_lastname()); 1741 data.append('reason', reason); 1742 1743 navigator.sendBeacon(scp_checkout.ajax_url, data); 1744 } 1745 1746 /** 1747 * Detect tab switch / window change 1748 */ 1749 document.addEventListener('visibilitychange', function () { 1750 scp_trigger_cart_abandonment_beacon('tab_switch'); 1751 }); 1752 1753 /** 1754 * Detect page unload (refresh / close / back) 1755 */ 1756 window.addEventListener('beforeunload', function () { 1757 scp_trigger_cart_abandonment_beacon('page_leave'); 1758 }); 1759 1760})(jQuery); 1761 1762 1763/* ============================================================ 1764 * Two-Step Checkout (Templates 4 & 5 â shared engine) 1765 * Layout state ONLY. Shows one step at a time via the root step-state class 1766 * (.scp-on-step-1 / .scp-on-step-2) â the SAME mechanism the block-editor 1767 * preview uses, so the two stay in sync. That class also drives whether the 1768 * order summary shows in step 1 (see .scp-summary-step1-no in the shared 1769 * stylesheets). Reuses scp_validate_core_fields() to gate step 1; the
1770 * "Continue" control never submits. Cart, order-bump, coupon and submit 1771 * handlers are untouched (document-delegated). 1772 * 1773 * Scoped to the shared .scp-checkout-two-step marker (emitted for template4 AND 1774 * template5 by the render callback), so templates 1-3 are unaffected. Template 4 1775 * is two-column (summary in a sticky sidebar); Template 5 is one-column with the 1776 * summary as a collapsible bar on top â the ONLY template-specific bit here is 1777 * that collapsible bar, guarded by the .scp-checkout-template-5 class. 1778 * ============================================================ */ 1779jQuery(function ($) { 1780 1781 var $root = $('.scp-checkout-two-step'); 1782 if (!$root.length) { return; } 1783 1784 var $left = $root.find('.scp-left-part-checkout').first(); 1785 var $pane1 = $left.find('.scp-step-pane-1').first(); 1786 var $pane2 = $left.find('.scp-step-pane-2').first(); 1787 if (!$pane1.length || !$pane2.length) { return; } 1788 1789 var L = { 1790 info: 'Your info', 1791 payment: 'Payment', 1792 // Labels are configurable per checkout (block setting) and passed through 1793 // as data attributes by the render callback; fall back to the defaults. 1794 cont: $root.attr('data-scp-continue-label') || 'Continue to payment', 1795 back: $root.attr('data-scp-back-label') || 'Back to your info' 1796 }; 1797 1798 // Reference-design structural pieces (topbar, step indicator, sub/trust, 1799 // step-2 panels, guarantee) are injected once â the checkout blocks don't 1800 // provide them. All are cosmetic wrappers; the underlying fields/handlers 1801 // are untouched. scp_t4_decorate() is shared with the editor preview logic 1802 // conceptually (mirrored in scp-checkout-editor.js). 1803 var $cols = $root.find('.wp-block-columns').first(); 1804 1805 // Step indicator, above the two-column grid. 1806 if ($cols.length && !$root.find('.scp-step-indicator').length) { 1807 $cols.before( 1808 '<div class="scp-step-indicator">' + 1809 '<button type="button" class="scp-step-node scp-step-node-1 is-active"><span class="scp-step-dot">1</span><span class="scp-step-name">' + L.info + '</span></button>' + 1810 '<div class="scp-step-line"></div>' + 1811 '<button type="button" class="scp-step-node scp-step-node-2"><span class="scp-step-dot">2</span><span class="scp-step-name">' + L.payment + '</span></button>' + 1812 '</div>' 1813 ); 1814 } 1815 1816 // Step-2 greeting banner (full-width, below the step indicator; shown only on 1817 // step 2 via the shared stylesheet). Configurable/toggleable via the block 1818 // setting, passed through as data attributes by the render callback. The 1819 // [name] token is filled from the first-name field â refreshed each time the 1820 // buyer reaches step 2 (see setStep), so it reflects what they typed. 1821 var greetShow = ($root.attr('data-scp-greeting-show') || '1') !== '0'; 1822 var greetRaw = $root.attr('data-scp-greeting') || ''; 1823 1824 // Resolve [name]: substitute the buyer's first name, or drop the token plus an 1825 // adjacent comma/space so the sentence still reads ("Thanks, [name], for" -> 1826 // "Thanks for"). Only spaces/tabs are eaten, never the heading/sub-text newline. 1827 function scpResolveGreeting(raw, name) { 1828 name = (name || '').trim(); 1829 if (name) { return raw.replace(/\[name\]/g, name); } 1830 return raw 1831 .replace(/[ \t]*,?[ \t]*\[name\][ \t]*,?[ \t]*/g, ' ') 1832 .replace(/[ \t]{2,}/g, ' ') 1833 .replace(/ +([,.!?])/g, '$1') 1834 .replace(/[ \t]+\n/g, '\n') 1835 .replace(/\n[ \t]+/g, '\n') 1836 .trim(); 1837 } 1838 1839 // Render the resolved message into the banner: first line = heading, the rest 1840 // = sub-text (line breaks preserved). Text nodes only â never inject HTML. 1841 function scpFillGreeting($banner, raw, name) { 1842 if (!$banner.length) { return; } 1843 var lines = scpResolveGreeting(raw, name).split('\n'); 1844 $banner.find('.scp-step2-greeting-title').text(lines.shift() || ''); 1845 var $sub = $banner.find('.scp-step2-greeting-sub').empty();
1846 lines.forEach(function (ln, i) { 1847 if (i > 0) { $sub.append(document.createElement('br')); } 1848 $sub.append(document.createTextNode(ln)); 1849 }); 1850 $sub.toggle(lines.join('').trim() !== ''); 1851 } 1852 1853 if (greetShow && greetRaw && $cols.length && !$root.find('.scp-step2-greeting').length) { 1854 $cols.before( 1855 '<div class="scp-step2-greeting" role="status">' + 1856 '<p class="scp-step2-greeting-title"></p>' + 1857 '<p class="scp-step2-greeting-sub"></p>' + 1858 '</div>' 1859 ); 1860 scpFillGreeting($root.find('.scp-step2-greeting'), greetRaw, $root.find('#firstname').val()); 1861 } 1862 1863 // Step 1: subtitle (after heading) + Continue button + trust row. 1864 if (!$pane1.find('.scp-ref-sub').length) { 1865 var $h1 = $pane1.find('.scp-checkout-section-heading').first(); 1866 if ($h1.length) { $h1.after('<p class="scp-ref-sub">Your login and receipt go to this email.</p>'); } 1867 } 1868 if (!$pane1.find('.scp-step-continue').length) { 1869 // scp-loader-btn reuses the same in-button spinner as the Place Order button. 1870 $pane1.append('<button type="button" class="scp-step-continue scp-loader-btn">' + L.cont + '</button>'); 1871 } 1872 // Trust-row icons are inline SVG (stroke: currentColor, sized by the shared 1873 // stylesheet) rather than emoji, so they render identically on every platform 1874 // instead of inheriting the OS emoji font. 1875 var SCP_T4_ICON = { 1876 lock: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="10.5" width="17" height="11" rx="2.5"/><path d="M7.5 10.5V7a4.5 4.5 0 0 1 9 0v3.5"/></svg>', 1877 check: '<svg class="scp-trust-ok" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="m8 12.5 2.6 2.6L16 9.5"/></svg>', 1878 shield: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3.2 19 6v5.8c0 4.1-2.8 7.7-7 8.9-4.2-1.2-7-4.8-7-8.9V6z"/><path d="m9.2 12.2 2.1 2.1 3.9-4"/></svg>', 1879 bolt: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M13.5 2.5 5 14h6l-.8 7.5L19 10h-6.2z"/></svg>', 1880 // Step-2 panel heads (were ð³ / ð). 1881 card: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2.5" y="5" width="19" height="14" rx="2.5"/><path d="M2.5 9.5h19"/></svg>', 1882 pin: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 21.5s7-5.6 7-11a7 7 0 1 0-14 0c0 5.4 7 11 7 11z"/><circle cx="12" cy="10.5" r="2.5"/></svg>' 1883 }; 1884 1885 if (!$pane1.find('.scp-ref-trust').length) { 1886 $pane1.append( 1887 '<div class="scp-ref-trust">' + 1888 '<span>' + SCP_T4_ICON.lock + 'SSL encrypted</span>' + 1889 '<span>' + SCP_T4_ICON.check + 'Money-back guarantee</span>' + 1890 '</div>' 1891 ); 1892 } 1893 1894 // Summary card: its own trust row under the total (reference design). Appended 1895 // to the card itself, not the summary block, so it sits below the total once 1896 // the total is pushed to the bottom of the equal-height card. Template 4 only â 1897 // template 5's summary is a collapsible bar where this would not read as a 1898 // card footer. 1899 // 1900 // The cart summary is re-rendered by AJAX (once on load, then on bump/coupon/ 1901 // currency changes) and a re-render discards injected children, so a one-shot 1902 // append here silently disappears. Re-run it on each re-render with the same 1903 // MutationObserver approach the template-5 total mirror uses below. The 1904 // idempotent guard means the observer's own append settles after one no-op. 1905 if ($root.hasClass('scp-checkout-template-4')) { 1906 var $sumWrap = $root.find('#scp-checkout-cart-summary').first(); 1907 1908 function scpT4SummaryTrust() { 1909 var $card = $sumWrap.find('.scp-checkout-order-summary').first(); 1910 if (!$card.length || $card.find('.scp-summary-trust').length) { return; } 1911 $card.append( 1912 '<div class="scp-ref-trust scp-summary-trust">' + 1913 '<span>' + SCP_T4_ICON.shield + 'Secure</span>' + 1914 '<span>' + SCP_T4_ICON.bolt + 'Instant access</span>' + 1915 '</div>' 1916 ); 1917 } 1918 scpT4SummaryTrust(); 1919 1920 var sumWrapNode = $sumWrap.get(0); 1921 if (window.MutationObserver && sumWrapNode) { 1922 new MutationObserver(scpT4SummaryTrust).observe(sumWrapNode, { childList: true, subtree: true }); 1923 } 1924 } 1925 1926 // Step 2: a "Payment method" head before the payment form (leaf insert, no 1927 // DOM wrapping), then Back link + guarantee. Payment and billing are wrapped 1928 // into separate .scp-step2-panel cards further below. 1929 if (!$pane2.find('.scp-ref-panel-head').length) { 1930 var $pay = $pane2.find('.scp-payment-form').first(); 1931 if ($pay.length) { 1932 $pay.before('<div class="scp-ref-panel-head"><span class="scp-ref-ico">' + SCP_T4_ICON.card + '</span> Payment method</div>'); 1933 } 1934 } 1935 // Billing (and any other step-2) heading: same .scp-ref-ico treatment as the 1936 // payment head. The icon used to come from a CSS ::before emoji; it is a real 1937 // SVG now, so it has to be inserted here for the front end to have one. 1938 $pane2.find('.scp-checkout-section-heading').each(function () { 1939 var $h = $(this); 1940 if (!$h.find('.scp-ref-ico').length) { 1941 $h.prepend('<span class="scp-ref-ico">' + SCP_T4_ICON.pin + '</span>'); 1942 } 1943 }); 1944 if (!$pane2.find('.scp-step-back').length) { 1945 $pane2.append('<button type="button" class="scp-step-back">' + L.back + '</button>'); 1946 } 1947 if (!$pane2.find('.scp-ref-guarantee').length) { 1948 $pane2.append('<div class="scp-ref-guarantee">Backed by our money-back guarantee. Cancel anytime from your dashboard.</div>'); 1949 } 1950 1951 // Step 2 panels: wrap the payment section and the billing section each in a 1952 // bordered card so billing reads as its OWN panel (reference design), instead 1953 // of sharing one card separated by a divider line. Shared by both two-step 1954 // templates. Front-end only (static DOM â safe to wrap, unlike the editor); 1955 // idempotent. wrapAll groups a contiguous run of direct children into one box. 1956 if (!$pane2.find('.scp-pay-panel').length) { 1957 var $payParts = $pane2.children('.scp-ref-panel-head, .scp-payment-form'); 1958 if ($payParts.length) { 1959 $payParts.wrapAll('<div class="scp-step2-panel scp-pay-panel"></div>'); 1960 } 1961 } 1962 if (!$pane2.find('.scp-bill-panel').length) { 1963 var $billHead = $pane2.children('.scp-checkout-section-heading').first(); 1964 if ($billHead.length) { 1965 // From the "Billing Address" heading through the last billing/shipping 1966 // field, stopping before the pay button / back / guarantee. 1967 $billHead.add($billHead.nextUntil('.scp-checkout-pay-now-btn, .scp-step-back, .scp-ref-guarantee')) 1968 .wrapAll('<div class="scp-step2-panel scp-bill-panel"></div>'); 1969 } 1970 } 1971 1972 // Template 5 only: the order summary is a collapsible bar on top. Wrap the 1973 // summary blocks (order bump + cart summary) in a body, prepend a clickable 1974 // header with a mirrored total, and open it by default. Wrapping is safe here 1975 // (static front-end DOM â unlike the block editor's React tree). The header 1976 // total only MIRRORS the cart total; cart/bump/coupon logic is never touched. 1977 if ($root.hasClass('scp-checkout-template-5')) { 1978 var $summary = $root.find('.scp-summary-top').first(); 1979 if ($summary.length && !$summary.find('.scp-osum-bar').length) { 1980 // Newer checkouts ship the .scp-osum-body wrapper in the block markup 1981 // (so the editor preview matches); only wrap here if it's absent. 1982 if (!$summary.find('.scp-osum-body').length) { 1983 $summary.wrapInner('<div class="scp-osum-body"></div>'); 1984 } 1985 $summary.prepend( 1986 '<div class="scp-osum-bar">' + 1987 '<span class="scp-osum-title">Order summary <span class="scp-osum-chev">â¼</span></span>' + 1988 '<span class="scp-osum-total"></span>' + 1989 '</div>' 1990 ); 1991 $summary.addClass('is-open'); // open by default 1992 var $barTotal = $summary.find('.scp-osum-total'); 1993 1994 // Copy the cart's grand total into the collapsed bar so it reads even 1995 // when the body is closed. Read-only; kept in sync by the observer below. 1996 function scpT5SyncTotal() { 1997 var $t = $summary.find('.scp-order-summary-v2-total').first(); 1998 if (!$t.length) { $barTotal.text(''); return; } 1999 var $amt = $t.find('.scp-total-right').first(); 2000 $barTotal.text((($amt.length ? $amt.text() : $t.text()) || '').trim()); 2001 } 2002 scpT5SyncTotal(); 2003 2004 // Toggle open/closed on the bar (not the body, so clicks inside the 2005 // summary â coupon field, bump â don't collapse it). 2006 $summary.find('.scp-osum-bar').on('click', function () { 2007 $summary.toggleClass('is-open'); 2008 }); 2009 2010 // Keep the mirrored total current as the cart changes (bump, c
2010oupon, 2011 // currency). Observe ONLY the cart summary, never the bar we write into, 2012 // so updating the mirror can't feed back into the observer. 2013 var summaryNode = $summary.get(0); 2014 var cartNode = summaryNode && ( 2015 summaryNode.querySelector('#scp-checkout-cart-summary') || 2016 summaryNode.querySelector('.scp-order-summary-v2-container') 2017 ); 2018 if (window.MutationObserver && cartNode) { 2019 new MutationObserver(scpT5SyncTotal).observe(cartNode, { 2020 childList: true, characterData: true, subtree: true 2021 }); 2022 } 2023 } 2024 } 2025 2026 // Reflect the active step on the root element (drives step + summary 2027 // visibility via the shared stylesheet) and on the indicator. 2028 function setStep(n) { 2029 $root.removeClass('scp-on-step-1 scp-on-step-2').addClass('scp-on-step-' + n); 2030 2031 var $n1 = $root.find('.scp-step-node-1'); 2032 var $n2 = $root.find('.scp-step-node-2'); 2033 var $line = $root.find('.scp-step-line'); 2034 2035 // Step 1 keeps its active (brand) look and its "1" the whole way through â 2036 // no green "done" state and no checkmark once Step 2 is reached (by design). 2037 // The connector line also stays neutral (never turns green). 2038 $n1.addClass('is-active').removeClass('is-done'); 2039 $n1.find('.scp-step-dot').html('1'); 2040 $n2.toggleClass('is-active', n === 2); 2041 $line.removeClass('is-done'); 2042 2043 // Step 2 holds #card-element; (re)initialise Stripe now that it is 2044 // revealed. scp_lazy_init_stripe() is idempotent. 2045 if (n === 2 && typeof scp_lazy_init_stripe === 'function') { 2046 try { scp_lazy_init_stripe(); } catch (e) {} 2047 } 2048 2049 // Refresh the greeting's [name] with whatever the buyer typed in step 1. 2050 if (n === 2 && greetShow) { 2051 scpFillGreeting($root.find('.scp-step2-greeting'), greetRaw, $root.find('#firstname').val()); 2052 } 2053 2054 try { window.scrollTo({ top: 0, behavior: 'smooth' }); } 2055 catch (e) { window.scrollTo(0, 0); } 2056 } 2057 2058 // Advance to step 2 only when the contact fields are valid (reuses the 2059 // existing validation); never submits. Before advancing, register (create, 2060 // without logging in) the buyer when the email is new so a Step-1 abandoner 2061 // can be recovered. An existing email, an already-logged-in buyer, or any 2062 // failure simply proceeds â the Place Order flow remains the safety net and 2063 // signs the buyer in when they complete payment. 2064 function goStep2() { 2065 var ok = (typeof scp_validate_core_fields === 'function') ? scp_validate_core_fields() : true; 2066 if (!ok) { 2067 var $err = $pane1.find('.scp-inline-error-message').first(); 2068 if ($err.length) { 2069 $('html, body').animate({ scrollTop: $err.offset().top - 120 }, 200); 2070 } 2071 return; 2072 } 2073 2074 var ajaxUrl = (window.scp_checkout && scp_checkout.ajax_url) ? scp_checkout.ajax_url : (window.ajaxurl || ''); 2075 var nonce = $root.attr('data-scp-reg-nonce') || ''; 2076 2077 // No endpoint/nonce available â just advance (Place Order handles the account). 2078 if (!ajaxUrl || !nonce) { setStep(2); return; } 2079 2080 var $cont = $pane1.find('.scp-step-continue'); 2081 // Same in-button loader as the Place Order button (scp-loader-active). 2082 $cont.addClass('scp-loader-active').prop('disabled', true); 2083 2084 $.post(ajaxUrl, { 2085 action: 'scp_two_step_register', 2086 nonce: nonce, 2087 email: $root.find('#email').val(), 2088 firstname: $root.find('#firstname').val(), 2089 lastname: $root.find('#lastname').val() 2090 }).always(function () { 2091 // Created / existing / already-logged-in / failed all proceed to step 2. 2092 // Waiting for completion ensures the auth cookie is set first when created. 2093 $cont.removeClass('scp-loader-active').prop('disabled', false); 2094 setStep(2); 2095 }); 2096 } 2097 2098 // Ensure a defined initial step (the render callback also sets scp-on-step-1). 2099 if (!$root.hasClass('scp-on-step-2')) { $root.addClass('scp-on-step-1'); } 2100 2101 // Restore the payment step after a currency-change page reload so switching 2102 // currency on step 2 keeps the buyer on step 2 instead of bouncing to step 1. 2103 try { 2104 if (sessionStorage.getItem('scp_checkout_step') === '2') { 2105 sessionStorage.removeItem('scp_checkout_step'); 2106 setStep(2); 2107 } 2108 } catch (e) {} 2109 2110 $root.on('click', '.scp-step-continue', function (e) { e.preventDefault(); goStep2(); }); 2111 $root.on('click', '.scp-step-node-2', function (e) { e.preventDefault(); goStep2(); }); 2112 $root.on('click', '.scp-step-back', function (e) { e.preventDefault(); setStep(1); }); 2113 $root.on('click', '.scp-step-node-1', function (e) { e.preventDefault(); setStep(1); }); 2114 2115 // All step chrome is injected and the panels are wrapped â reveal the checkout 2116 // (removes scp-two-step-init, which the stylesheet used to hide the raw layout). 2117 $root.removeClass('scp-two-step-init'); 2118 2119});
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.