PageSourceSearch

https://selfcaringco.com/wp-content/plugins/smart-creator-press/public/js/scp-checkout.js?ver=26.3.1790790449

js selfcaringco.com collected 2026-10-04 20:54:23 UTC 57,615 bytes, 2,119 lines download raw bytes

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.