PageSourceSearch

https://exiledrecords.com/templates/js/cart2.js

js exiledrecords.com collected 2026-09-26 16:27:39 UTC 56,909 bytes, 1,201 lines download raw bytes

1/*
2 * async cart processing code
3 */
4
5// fix older IE barfing on console.log code
6if (typeof console == "undefined" || typeof console.log == "undefined") var console = { log: function() {} };
7
8function Cart(use_stripe) {
9    // properties
10    this.cart = {};
11    this.cart_message = '';
12    this.stripe_finished = false;
13
14    // detect coming from bfcache and re-get cart if on iOS
15    var mios = /(ipad|ipod|iphone)/i.test( navigator.userAgent );
16    var mandroid = /android/i.test( navigator.userAgent );
17    if( mios ){
18        window.onpageshow = function( event ) {
19            if( event.persisted ){
20                cart.get_cart();
21            }
22        };
23    }
24
25    // methods
26
27
28    this.show_notification = function(text){
29        this.cart_message = text;
30        this.show_cart();
31        $('#show_cart_full').show();
32
33        // commenting out cart notification message to only show cart display
34        //$("#cart_notification").show();
35        //$('#cart_notification').html('' + text + '<div style="text-align:center"><button id="cart_notification_button">OK</button></div>');
36        //$('#cart_notification_button').click(function(){$("#cart_notification").fadeOut('slow');});
37
38        // commenting out automatic fade
39        //setTimeout(function(){$("#cart_notification").fadeOut('slow');}, 1500);
40    };
41
42    this.get_cart = function() {
43        var verify = '';
44        var location = document.location.href;
45        if (location.match(/SecureCartCheckout/)) {
46            verify = '1';
47        }
48        var params = new URLSearchParams({
49            verify: verify,
50            time_stamp: new Date().getTime()
51        });
52        AwsWafIntegration.fetch('/SecureCartList.json?' + params.toString(), {
53            credentials: 'same-origin'
54        })
55        .then(function(response) { return response.json(); })
56        .then(function(data) {
57            cart.cart = data;
58            cart.show_cart();
59            var location = document.location.href;
60            if (!location.match(/SecureCartCheckoutResults/) && location.match(/SecureCartCheckout/) ) {
61                // Stripe enforces a minimum charge, so a cart landing between
62                // $0.01 and $0.50 cannot be paid for and is held at the cart.
63                // A cart a coupon discounted all the way to zero is a different
64                // case: there is nothing to charge at all, and checkout settles
65                // it without contacting Stripe (see Checkout::Submit's
66                // dispatch_payment). Note prices.total arrives as null rather
67                // than "0.00" when it is zero — the shared price formatter
68                // drops zeros — so compare the parsed number, not the raw value.
69                var cart_total = parseFloat(data.prices.total) || 0;
70                var comped     = (parseFloat(data.prices.discount) || 0) > 0
71                                 && cart_total === 0;
72                if(cart_total > 0.5 || comped){
73                    cart.show_checkout();
74                }else if(cart_total > 0){
75                    // A cart with real value but under the payment processor's
76                    // minimum cannot be charged. Explain it here, in the
77                    // checkout page's own form slot, rather than bouncing to
78                    // the cart view — that redirect is silent, and the cart
79                    // view's message area lives inside #cartmodal, which stays
80                    // hidden on that page, so the shopper would learn nothing.
81                    $("#checkout_form_target").html(
82                        '<div class="alert alert-warning" id="cart_minimum_notice">'
83                        + '<strong>This order is under the $0.50 minimum we can charge.</strong> '
84                        + 'Your total comes to $' + cart_total.toFixed(2) + '. '
85                        + 'Please <a href="/SecureCartView">add another item to your cart</a>, '
86                        + 'or contact the store to complete this order.'
87                        + '</div>'
88                    );
89                }else{
90                    // Nothing of value in the cart at all — back to the cart.
91                    window.location.replace("/SecureCartView");
92                }
93            }
94        })
95        .catch(function(error) {
96            cart.show_notification("<p>Could not retreive cart, please try again!</p>");
97            console.log("cart dataload error - " + error);
98        });
99    };
100
101    this.update_cart = function(type, id, qty) {
102        // If iPad|iPhone|iPod
103        var base_url = '/';
104        if( mios || mandroid ){
105            window.location.assign(base_url+"SecureCartAdd2?type="+type+"&id="+id
106                                   +"&qty="+qty);
107        }else{
108            $('html, body').animate({scrollTop:0}, 'slow');
109            $.ajax({
110                url: base_url+'SecureCartAdd.json',
111                type: 'POST',
112                //dataType: 'jsonp',
113                data: {
114                    'type': type,
115                    'id' : id,
116                    'qty' : qty
117                },
118                success: function(data) {
119                    var results = data;
120                    if (results.errors) {
121                        var error_msg = "<p>Errors in updating cart:</p><ul><li>";
122                        error_msg += results.errors.join("</li>\n<li>");
123                        error_msg += "</li></ul>";
124                        cart.show_notification(error_msg);
125                    } else {
126                        cart.show_notification("<p>"+results.message+"</p>");
127                        cart.get_cart();
128                    }
129
130                    var cart_modal = $("#cartmodal");
131                    if(cart_modal){
132                        var fancybox = $(".fancybox-close");
133                        if(fancybox){
134                            fancybox.click()
135                        }
136                        $("#cartlink").click();
137                        setTimeout(function(){$("#cartviewCheckoutButton").focus(); console.log('focused')}, 0);
138                    }
139                },
140                error: function(jqXHR, textStatus, errorThrown) {
141                    console.log({jqXHR: jqXHR, ts: textStatus, et: errorThrown});
142                    cart.show_notification("<p>Could not add to cart, please try again!</p>");
143                }
144            });
145        }
146    };
147
148    this.delete_from_cart = function(type, id) {
149        $.ajax({
150            url: '/SecureCartAdd.json',
151            type: 'POST',
152            //dataType: 'jsonp',
153            data: {
154                'type': type,
155                'id' : id,
156                'delete' : 1
157            },
158            success: function(data) {
159                var results = data;
160                if (results.errors) {
161                    var error_msg = "<p>Errors in removing item from cart:</p><ul><li>";
162                    error_msg += results.errors.join("</li>\n<li>");
163                    error_msg += "</li></ul>";
164                    cart.show_notification(error_msg);
165                } else {
166                    cart.show_notification("<p>"+results.message+"</p>");
167                    cart.get_cart();
168                }
169            },
170            error: function(jqXHR, textStatus, errorThrown) {
171                cart.show_notification("<p>Could not remove from cart, please try again!</p>");
172            }
173        });
174    };
175
176    this.clear_cart = function() {
177        $.ajax({
178            url: '/SecureCartAdd.json',
179            type: 'POST',
180            //dataType: 'jsonp',
181            data: {
182                'clear': 1
183            },
184            success: function(data) {
185                var results = data;
186                if (results.errors) {
187                    var error_msg = "<p>Errors in adding to cart:</p><ul><li>";
188                    error_msg += results.errors.join("</li>\n<li>");
189                    error_msg += "</li></ul>";
190                    cart.show_notification(error_msg);
191                } else {
192                    cart.show_notification("<p>"+results.message+"</p>");
193                    cart.get_cart();
194                }
195            },
196            error: function(jqXHR, textStatus, errorThrown) {
197                cart.show_notification("<p>Could not clear cart, please try again!</p>");
198            }
199        });
200    };
201
202    this.show_cart_summary = function() {
203        var tmpl = "<span id='cart_summary'> <i class='bi bi-cart2'></i></span><span id='cart_summary_count'>{{count}}</span>";
204        var template = Handlebars.compile(tmpl);
205        var price = 0;
206        var count = 0;
207        if (this.cart) {
208            if (this.cart.prices && this.cart.prices.total) {
209                price = this.cart.prices.total;
210            }
211            if (this.cart.cart_data) {
212                count = this.cart.cart_data.map(item => Number(item.cart_qty)).reduce( (prev, next) => prev + next, 0);
213            }
214        }
215        var data = {
216            count: count,
217            price: Number(price).toFixed(2)
218        };
219        $("#cartplace").html(template(data));
220    };
221
222    this.show_cart_full = function() {
223        //console.log(this.cart_message);
224        var tmpl = $("#cartview_template").html();
225        var template = Handlebars.compile(tmpl);
226        var data = {
227            cart_data: this.cart.cart_data,
228            taxes:     (this.cart.prices && this.cart.prices.taxes) ? Number(this.cart.prices.taxes).toFixed(2) : 0,
229            fees:     (this.cart.prices && this.cart.prices.fees) ? Number(this.cart.prices.fees).toFixed(2) : 0,
230            shipping:  (this.cart.prices && this.cart.prices.shipping) ? Number(this.cart.prices.shipping).toFixed(2) : 0,
231            total:     (this.cart.prices && this.cart.prices.total) ? Number(this.cart.prices.total).toFixed(2) : 0,
232            message:   this.cart_message ? this.cart_message : ''
233        }
234        var text = template(data);
235        //console.log(text);
236        $("#show_cart_full").html(text);
237        $("#show_cart_full").css('position', 'relative').css('z-index', '10000');
238    };
239
240    this.show_cart = function() {
241        this.show_cart_full();
242        this.show_cart_summary();
243        $("#cartviewCheckoutButton").focus();
244    };
245
246    this.toggle_ccform = function() {
247        var payment_form = $('input:radio[name=payment_option]:checked').val();
248        if (payment_form == "creditcard") {
249            var cctmpl = $("#cc_form_template").html();
250            $("#paymentplace").html(cctmpl);
251        } else {
252            $("#paymentplace").html("");
253        }
254    };
255
256    this.toggle_checking_out = function(checking_out){
257        cart.submitting_checkout = checking_out;
258        $("#checkout_submit").attr("disabled", checking_out);
259        //console.log("Checking out? "+checking_out);
260        //console.log("Button disabled? "+$("#checkout_submit").attr("disabled"));
261    };
262
263    this.init_stripe_widget = function() {
264        $('#error_display').attr('style','display:none;');
265        $('#error_display').html('');
266        var form = document.getElementById('stripe_payment_form');
267        if (!form) return;
268        $(form).show();
269
270        // Build our Stripe instance from the per-site publishable key.
271        // Sourced from the #tp-stripe-pk hidden input inside the Stripe
272        // payment form (which is itself inside the payment_options_template
273        // script-clone block — a hidden input is safe there; a nested
274        // <script> would close the outer template prematurely, leaking
275        // the form to live page-render).
276        var pk = $('#tp-stripe-pk').val();
277        if (!pk || typeof Stripe === 'undefined') {
278            $('#checkout_message_display').attr('style','display:block;');
279            $('#checkout_message_display').html(
280                "Payment configuration error — please refresh or contact support."
281            );
282            return;
283        }
284        if (!cart.stripe) {
285            // Connect-aware: pass stripeAccount so client-side Stripe.js
286            // routes through the connected account the server creates PIs
287            // on. Without this, confirmCardPayment errors with
288            // "No such payment_intent" because Stripe.js looks under the
289            // platform account by default but the PI lives on acct_X.
290            var acct = $('#tp-stripe-acct').val();
291            cart.stripe = acct ? Stripe(pk, { stripeAccount: acct }) : Stripe(pk);
292        }
293
294        // Rollout-gated path: async_pi (server creates PI, client confirms)
295        // vs legacy (client createToken, server creates Charge). Branch sets
296        // both the mount target and the do_checkout submit choreography.
297        cart.payment_mode = ($('#tp-payment-mode').val() === 'legacy') ? 'legacy' : 'async_pi';
298        var mount_target = (cart.payment_mode === 'legacy') ? '#card-element' : '#payment-element';
299
300        try {
301            // CardElement either way. CardElement doesn't need to know
302            // about the PI to mount — clientSecret (PI path) or a tok_xxx
303            // (legacy path) is only produced at submit-time. Single-row
304            // Stripe-rendered card UI, zero extra Stripe.js calls before
305            // submit, in both modes.
306            cart.payment_elements = cart.stripe.elements();
307            cart.payment_element = cart.payment_elements.create('card', {
308                hidePostalCode: true, // we collect billing zip from our own form
309            });
310            cart.payment_element.mount(mount_target);
311        } catch (e) {
312            console.error('CardElement mount failed:', e);
313            $('#checkout_message_display').attr('style','display:block;');
314            $('#checkout_message_display').html(
315                'Payment widget failed to load: ' + (e && e.message) + ' — please refresh.'
316            );
317            return;
318        }
319
320        // Single-click submit: validate -> create PI -> confirm.
321        form.addEventListener('submit', function(event){
322            event.preventDefault();
323            if (cart.submitting_checkout) {
324                console.log('returning early from submit, preventing duplicate clicks');
325                return;
326            }
327            cart.do_checkout();
328        });
329    };
330
331    this.get_updated_cart_data = function(has_shipping_selected) {
332        var shipping_option_id = $('input[name="shipping_option_id"]:checked').val();
333        var shipping_is_billing = $('input[name="billing_is_shipping"]:checked').val();
334        var state = shipping_is_billing? $('select[name="billing_state"]').val():
335                $('select[name="shipping_state"]').val();
336        var city = shipping_is_billing? $('input[name="billing_city"]').val():
337                $('input[name="shipping_city"]').val();
338        // zip + country let live weight-based options quote a real rate;
339        // ignored entirely by flat-rate sites.
340        var zip = shipping_is_billing? $('input[name="billing_zip"]').val():
341                $('input[name="shipping_zip"]').val();
342        var country = shipping_is_billing? $('select[name="billing_country"] option:selected').val():
343                $('select[name="shipping_country"] option:selected').val();
344        var shipping_address_id = $('input[name="shipping_address_id"]:checked').val();
345        var d = {
346            'ig': 1,
347            'state': state,
348            'city': city,
349            'zip': zip,
350            'country': country,
351            'address_id': shipping_address_id,
352            'shipping_option_id': shipping_option_id
353        };
354
355        if(cart.cart.giftcard_amount && cart.cart.giftcard_code){
356            d["giftcard_amount"] = cart.cart.giftcard_amount;
357            d["giftcard_code"] = cart.cart.giftcard_code;
358        }
359
360        $.ajax({
361            url: '/SecureCartTotals.json',
362            dataType: 'jsonp',
363            data: d,
364            success: function(data) {
365                cart.cart.prices = data.prices;
366                cart.cart.giftcard_covers_all = typeof(data.final_total) !== 'undefined' && data.final_total == "$0.00";
367                // A coupon that discounts the cart to exactly zero leaves
368                // nothing to charge either — same shape as a gift card
369                // covering the whole total. prices.total arrives as null
370                // rather than "0.00" when it is zero (the shared price
371                // formatter drops zeros), so compare the parsed number.
372                cart.cart.discounted_to_zero = (parseFloat(data.prices.discount) || 0) > 0
373                                               && (parseFloat(data.prices.total) || 0) === 0;
374                if(has_shipping_selected){
375                    cart.init_totals_html();
376                    $("#payment_options").show();
377                    $(".subtotal").text(data.subtotal);
378                    $(".adjusted_subtotal").text(data.adjusted_subtotal);
379                    $(".shipping").text(data.shipping || "$0");
380                    $(".taxes").text(data.taxes || "$0");
381                    $(".total").text(data.total || "$0");
382                    if(data.prices.fees && data.prices.fees > 0){
383                        $(".fees_display").show();
384                        $(".fees").text(data.fees || "$0");
385                    }
386                    if(data.final_total && data.giftcard_amount){
387                        $(".giftcard_totals").show();
388                        $(".giftcard_value").text(data.giftcard_amount);
389                        $(".final_total").text(data.final_total);
390                    }
391                    cart.set_payment_html(cart.cart.giftcard_covers_all, cart.cart.discounted_to_zero);
392                    if(!use_stripe){
393                        cart.toggle_ccform();
394                    }
395                }
396
397            },
398            error: function(jqXHR, textStatus, errorThrown) {
399                console.log("Error in getting updated pricing: " + errorThrown);
400            }
401        });
402        //if(has_shipping_selected){
403            //this.toggle_ccform();
404        //}
405    }
406
407    this.toggle_shipping = function() {
408        var target_checked = $('input[name="billing_is_shipping"]:checked').val();
409        if (target_checked) {
410            $('.shipping_data_item').attr('disabled', 'disabled');
411            $('.shipping_legend').css('color', '#cccccc');
412        } else {
413            $('.shipping_data_item').removeAttr('disabled');
414            $('.shipping_legend').css('color', '#000000');
415        }
416    }
417
418    this.toggle_address_form = function() {
419        var address_checked = $('input[name="billing_address_id"]:checked').val();
420        if (cart.cart && cart.cart.username && cart.cart.user_data && cart.cart.user_data.good_addresses) {
421            if (address_checked) {
422                $('input:radio[name=billing_address_id][disabled=false]').removeAttr('checked');
423                $('input:radio[name=shipping_address_id][disabled=false]').removeAttr('checked');
424            } else {
425                $('input:radio[name=billing_address_id]:first').attr('checked', true);
426                $('input:radio[name=shipping_address_id]:first').attr('checked', true);
427            }
428        }
429    }
430
431    this.render_cart_address_form = function() {
432        var tmpl = $("#checkout_address_template").html();
433        if (tmpl) {
434            var template = Handlebars.compile(tmpl);
435            var data = {
436                username: cart.cart.username,
437                user_data: cart.cart.user_data,
438                has_physical: cart.cart.has_physical,
439                addresses: user.addresses
440            };
441            $("#checkout_address_target").html(template(data));
442            this.toggle_address_form();
443            $("select[name$=_country]").each(function(){
444                var name       = $(this).attr("name").replace("country","state");
445                var target     = $("[name="+name+"]").parent();
446                var us_vals    = target.html();
447                var other_vals = "<input name='"+name+"' type='text' value=''/>";
448                $(this).change(function(){
449                    if($(this).val() == 'US') {
450                        target.html(us_vals);
451                        target.parent().find(".legend").html("State");
452                    }else{
453                        target.html(other_vals);
454                        target.parent().find(".legend").html("State/Province/...");
455                    }
456                })});
457
458        }
459
460        if(cart.cart.shipping_exempt){
461            cart.get_updated_cart_data(1);
462            /*
463            var data = {
464                username: cart.cart.username,
465                show_international_digital_warning: cart.cart.show_international_digital_warning,
466                cart_data: cart.cart.cart_data,
467                prices: cart.cart.prices,
468                user_data: cart.cart.user_data,
469                has_digital: cart.cart.has_digital,
470                has_physical: cart.cart.has_physical,
471                google: cart.cart.google,
472                paypal: cart.cart.paypal,
473                has_giftcards: cart.cart.has_giftcards,
474
475            };
476             */
477            //var totals_template = Handlebars.compile($("#totals_template").html());
478            //var totals_html = totals_template(data);
479            //$("#totals_target").html(totals_html);
480            //cart.set_payment_html(false);
481            //$("#totals").show();
482            //$("#payment_options").show();
483            // TODO or validate?
484            //cart.do_checkout();
485        }
486    };
487
488    this.init_totals_html = function(){
489        if(!this.totals_initialized){
490            var totals_template = Handlebars.compile($("#totals_template").html());
491            $("#totals_target").html(totals_template(cart.cart));
492            $("#totals").show();
493            this.totals_initialized = true;
494        }
495    };
496
497    this.set_payment_html = function(giftcard_covers_all, discounted_to_zero){
498        var payment_html;
499        // Nothing left to charge — either a gift card covers the whole total
500        // or a coupon discounted it to zero. Render the no-card panel instead
501        // of a card form the customer has nothing to pay with.
502        if(giftcard_covers_all || discounted_to_zero){
503            var payment_template = Handlebars.compile($("#paying_all_with_giftcard").html());
504            payment_html = payment_template({ giftcard_covers_all: giftcard_covers_all });
505            $("#payment_target").html(payment_html);
506        }else{
507            var payment_template = Handlebars.compile($("#payment_options_template").html());
508            payment_html = payment_template({
509                username: cart.cart.username,
510                show_international_digital_warning: cart.cart.show_international_digital_warning,
511                cart_data: cart.cart.cart_data,
512                prices: cart.cart.prices,
513                user_data: cart.cart.user_data,
514                has_digital: cart.cart.has_digital,
515                has_physical: cart.cart.has_physical,
516                google: cart.cart.google,
517                paypal: cart.cart.paypal,
518                has_giftcards: cart.cart.has_giftcards,
519            });
520            $("#payment_target").html(payment_html);
521            if(use_stripe){
522                cart.init_stripe_widget();
523            }
524        }
525        $("#payment_options").show();
526    };
527
528    this.check_before_checkout = function(){
529        var validator = $("#cart_form").validate();
530        if(!validator.form()){
531            var err0 = $(validator.errorList[0].element);
532            $('html, body').animate({scrollTop: err0.offset().top},
533                                    300, function(){err0.focus();} );
534            return;
535        }else{
536            cart.do_checkout();
537        }
538    };
539
540    this.set_coupon_message = function(msg){
541        $("#coupon_message").html(msg);
542    };
543
544    this.try_coupon_code = function(evt) {
545        var code = $("#coupon_code").val();
546        if(!code){
547            cart.set_coupon_message("Please Enter A Coupon Code");
548            return;
549        }
550        $.ajax({
551            url: '/SecureCartAdd.json',
552            type: 'POST',
553            //dataType: 'jsonp',
554            data: {
555                'type': 'coupon',
556                'id' : code,
557            },
558            success: function(data) {
559                var results = data;
560                if (results.errors) {
561                    var error_msg = "";
562                    error_msg += results.errors.join("<br/>");
563                    cart.set_coupon_message(error_msg);
564                } else {
565                    cart.set_coupon_message(results.message);
566                    cart.get_cart();
567                }
568            },
569            error: function(jqXHR, textStatus, errorThrown) {
570                cart.set_coupon_message("Could not add coupon, please try again later!");
571            }
572        });
573        evt.preventDefault();
574        evt.stopPropagation();
575    };
576
577    this.show_checkout = function() {
578        var tmpl = $("#checkout_form_template").html();
579        if(!tmpl){
580            return;
581        }
582        var template = Handlebars.compile(tmpl);
583        var data = {
584            username: cart.cart.username,
585            show_international_digital_warning: cart.cart.show_international_digital_warning,
586            cart_data: cart.cart.cart_data,
587            prices: cart.cart.prices,
588            user_data: cart.cart.user_data,
589            has_digital: cart.cart.has_digital?true:false,
590            has_physical: cart.cart.has_physical?true:false,
591            shipping_exempt: cart.cart.shipping_exempt?true:false,
592            google: cart.cart.google,
593            paypal: cart.cart.paypal
594
595        };
596        //console.log(cart.cart.cart_data);
597        $("#checkout_form_target").html(template(data));
598        $('input:radio[name=shipping_option_id][disabled=false]:first').attr('checked', true);
599        $("#cart_form").validate({
600            submitHandler: function(form,event) {
601                if(event){
602                    event.preventDefault();
603                }
604                cart.do_checkout();
605                return false;
606            }
607        });
608        user.loadAddressesForCheckout();
609        this.render_cart_address_form();
610        this.toggle_shipping();
611        this.toggle_ccform();
612        this.prepopulate_cc_name();
613        $('input[name=email]').change(function(){cart.check_email()});
614        $('input:radio[name=billing_address_id]').change(function(){cart.prepopulate_cc_name()});
615        setTimeout(cart.get_updated_cart_data, 0);
616    }
617
618    this.prepopulate_cc_name = function() {
619        var billing_address_id = $('input:radio[name=billing_address_id]:checked').val();
620        if (billing_address_id && cart.cart.user_data && cart.cart.user_data.address_list) {
621            var address;
622            for (count in cart.cart.user_data.address_list) {
623                var test_address = cart.cart.user_data.address_list[count];
624                //console.log("testing billing address " + count + " against " + billing_address_id + " == " + test_address.id);
625                if (test_address.id == billing_address_id) {
626                    //console.log("yay, found it");
627                    address = test_address;
628                    //break;
629                }
630            }
631            if (address) {
632                $('input[name=cc_firstname]').val(address.first_name);
633                $('input[name=cc_lastname]').val(address.last_name);
634
635            }
636        }
637    }
638
639    this.check_email = function() {
640        var email = $('input[name=email]').val();
641        if (user.user && user.user.fully_logged_in) {
642            // existing customer, let them pass
643        } else {
644            if (email) {
645                $.ajax({
646                    url: '/SecureCartUserCheck.json',
647                    dataType: 'jsonp',
648                    data: {
649                        'email': email
650                    },
651                    success: function(data) {
652                        if (data.force_login) {
653                            console.log("existing user, not logged in");
654                            var prepopulated_data = {
655                                'username': email
656                            };
657                            user.cartSplash(prepopulated_data);
658
659                        } else if (data.incomplete_user) {
660                            console.log("mailing list user");
661                            user.showPasswordRecovery(email);
662
663                        } else {
664                            // new customer, let them pass
665                        }
666
667                    },
668                    error: function(jqXHR, textStatus, errorThrown) {
669                        console.log("Error in doing email check: " + errorThrown);
670                    }
671                });
672            }
673        }
674        return false;
675    }
676
677    this.get_addresses = function(){
678        var billing_is_shipping = $('input[name=billing_is_shipping]:checked').val();
679        var shipping_id =         $('input:radio[name=shipping_address_id]:checked').val();
680        var shipping_address_1 =  $('input[name=shipping_address_1]').val();
681        var shipping_address_2 =  $('input[name=shipping_address_2]').val();
682        var shipping_first_name = $('input[name=shipping_first_name]').val();
683        var shipping_last_name =  $('input[name=shipping_last_name]').val();
684        var shipping_city =       $('input[name=shipping_city]').val();
685        var shipping_state =      $('select[name=shipping_state] option:selected').val()
686            || $('input[name=shipping_state]').val();
687        var shipping_zip =        $('input[name=shipping_zip]').val();
688        var shipping_phone =      $('input[name=shipping_phone]').val();
689        var shipping_country =    $('select[name=shipping_country] option:selected').val();
690        var billing_id =          $('input:radio[name=billing_address_id]:checked').val();
691        var billing_address_1 =   $('input[name=billing_address_1]').val();
692        var billing_address_2 =   $('input[name=billing_address_2]').val();
693        var billing_first_name =  $('input[name=first_name]').val();
694        var billing_last_name =   $('input[name=last_name]').val();
695        var billing_city =        $('input[name=billing_city]').val();
696        var billing_state =       $('select[name=billing_state] option:selected').val()
697                || $('input[name=billing_state]').val();
698        var billing_zip =         $('input[name=billing_zip]').val();
699        var billing_phone =       $('input[name=billing_phone]').val();
700        var billing_country =     $('select[name=billing_country] option:selected').val();
701        var errors = [];
702        var a      = {
703            shipping: {
704                id:         billing_is_shipping? billing_id : shipping_id,
705                address_1:  billing_is_shipping? billing_address_1 : shipping_address_1,
706                address_2:  billing_is_shipping? billing_address_2 : shipping_address_2,
707                first_name: billing_is_shipping? billing_first_name : shipping_first_name,
708                last_name:  billing_is_shipping? billing_last_name : shipping_last_name,
709                city:       billing_is_shipping? billing_city : shipping_city,
710                state:      billing_is_shipping? billing_state : shipping_state,
711                zip:        billing_is_shipping? billing_zip : shipping_zip,
712                phone:      billing_is_shipping? billing_phone : shipping_phone,
713                country:    billing_is_shipping? billing_country : shipping_country,
714            },
715            billing: {
716                is_shipping: billing_is_shipping,
717                id:          billing_id,
718                address_1:   billing_address_1,
719                address_2:   billing_address_2,
720                first_name:  billing_first_name,
721                last_name:   billing_last_name,
722                city:        billing_city,
723                state:       billing_state,
724                zip:         billing_zip,
725                phone:       billing_phone,
726                country:     billing_country,
727            }
728        };
729
730
731        if (! ( a.billing.id || (a.billing.address_1 && a.billing.first_name && a.billing.last_name && a.billing.city && a.billing.country && (a.billing.state || a.billing.country != 'US' ) && a.billing.phone && a.billing.zip) ) ) {
732            if ( a.billing.address_1 || a.billing.first_name || a.billing.last_name || a.billing.city || a.billing.state || a.billing.country || a.billing.phone || a.billing.zip) {
733                if (! a.billing.address_1) {
734                    errors.push("Need to provide a billing address");
735                }
736                if (! a.billing.first_name) {
737                    errors.push("Need to provide a first name for billing");
738                }
739                if (! a.billing.last_name) {
740                    errors.push("Need to provide a last name for billing");
741                }
742                if (! a.billing.city) {
743                    errors.push("Need to provide a billing city");
744                }
745                if (! a.billing.country) {
746                    errors.push("Need to provide a billing country");
747                }
748                if ((!a.billing.state || !a.billing.state.length) && a.billing.country == 'US') {
749                    errors.push("Need to provide a billing state");
750                }
751                if (! a.billing.phone) {
752                    errors.push("Need to provide a billing phone number");
753                }
754                if (! a.billing.zip) {
755                    errors.push("Need to provide a billing zip");
756                }
757            } else {
758                errors.push("Need to specify a billing address");
759            }
760        }
761
762        var block_digital = true;
763        if(  a.billing.country != "US" && cart.cart.has_digital){
764            if(a.billing.id){
765                var adds = cart.cart.user_data.address_list.concat(user.addresses);
766                if(adds){
767                    adds.forEach(function(el){
768                        if(el.id == a.billing.id && el.country == "US"){
769                            block_digital = false;
770                        }
771                    });
772                }
773            }
774        }else{
775            block_digital = false;
776        }
777
778        if(block_digital){
779            errors.push("We&rsquo;re sorry, we can only sell MP3 downloads to customers in the United States. We are not able to provide you with your downloads.");
780        }
781
782        if (! ( a.shipping.id || a.billing.is_shipping || (a.shipping.address_1 && a.shipping.first_name && a.shipping.last_name && a.shipping.city && a.shipping.country && (a.shipping.state || a.shipping.country != 'US') && a.shipping.phone && a.shipping.zip) ) ) {
783            if ( a.shipping.address_1 || a.shipping.first_name || a.shipping.last_name || a.shipping.city || a.shipping.state || a.shipping.country || a.shipping.phone || a.shipping.zip) {
784                if (! a.shipping.address_1) {
785                    errors.push("Need to provide a shipping address");
786                }
787                if (! a.shipping.first_name) {
788                    errors.push("Need to provide a first name for shipping");
789                }
790                if (! a.shipping.last_name) {
791                    errors.push("Need to provide a last name for shipping");
792                }
793                if (! a.shipping.city) {
794                    errors.push("Need to provide a shipping city");
795                }
796                if (! a.shipping.country) {
797                    errors.push("Need to provide a shipping country");
798                }
799                if (! a.shipping.state && a.shipping.country == 'US') {
800                    errors.push("Need to provide a shipping state");
801                }
802                if (! a.shipping.phone) {
803                    errors.push("Need to provide a shipping phone number");
804                }
805                if (! a.shipping.zip) {
806                    errors.push("Need to provide a shipping zip");
807                }
808            } else {
809                errors.push("Need to specify a shipping address");
810            }
811        }
812
813        a.errors = errors;
814        return a;
815    }
816
817    this.get_shipping_options = function(){
818        var validator = $("#cart_form").validate();
819        if(!validator.form()){
820            var err0 = $(validator.errorList[0].element);
821            $('html, body').animate({scrollTop: err0.offset().top},
822                                    300, function(){err0.focus();} );
823            return;
824        }
825        var addresses = this.get_addresses();
826        var shipping_error = function(msg){
827            $("#pre_shipping_messages").html(msg);
828        };
829        if(addresses.errors.length){
830            shipping_error("<br/>Errors: <br />" + addresses.errors.join("<br /></br>"));
831        }else{
832            $.ajax({
833                url: '/SecureCartShippingOptions.json',
834                type: 'GET',
835                dataType: 'json',
836                data: {
837                    total: this.cart.prices.total,
838                    shipping_address_id: addresses.shipping.id,
839                    shipping_country:    addresses.shipping.country,
840                    // destination detail for live weight-based rating;
841                    // flat-rate sites ignore these.
842                    shipping_zip:        addresses.shipping.zip,
843                    shipping_state:      addresses.shipping.state,
844                    shipping_city:       addresses.shipping.city,
845                },
846                success: function(shipping_data, textStatus, jqXHR) {
847                    if(shipping_data.errors.length){
848                        shipping_error("Errors: <br />" + shipping_data.errors.join("<br />"));
849                    }else{
850                        /*
851                        var payment_data = {
852                            username: cart.cart.username,
853                            show_international_digital_warning: cart.cart.show_international_digital_warning,
854                            cart_data: cart.cart.cart_data,
855                            prices: cart.cart.prices,
856                            user_data: cart.cart.user_data,
857                            has_digital: cart.cart.has_digital,
858                            has_physical: cart.cart.has_physical,
859                            has_giftcards: cart.cart.has_giftcards,
860                            google: cart.cart.google,
861                            paypal: cart.cart.paypal
862
863                        };*/
864                        //var data = $.extend(shipping_data, payment_data);
865                        //var shipping_html = shipping_template(data);
866                        //var totals_template = Handlebars.compile($("#totals_template").html());
867                        //var totals_html = totals_template(data);
868                        //var payment_template = Handlebars.compile($("#payment_options_template").html());
869                        //var payment_html = payment_template(data);
870                        var shipping_template = Handlebars.compile($("#shipping_options_template").html());
871                        var shipping_html = shipping_template(shipping_data);
872                        $("#shipping_target").html(shipping_html);
873
874                        $("[name*=_country]").attr("disabled", true);
875                        $("#billcheck").attr("disabled", true);
876                    }
877                },
878                error: function(jqXHR, textStatus, errorThrown) {
879                    shipping_error("It seems we had problems connecting to the server to retrieve the available shipping options. Please try again.");
880                }
881            });
882        }
883    };
884
885
886    this.apply_giftcard = function(){
887        cart.cart.giftcard_amount = $('#giftcard_redeem_amount').val();
888        cart.cart.giftcard_code = $.trim($('#giftcard_code').val());
889        cart.get_updated_cart_data(1);
890        cart.set_payment_html(cart.cart.giftcard_covers_all);
891    };
892
893    this.check_giftcard = function(){
894        var code = $.trim($('#giftcard_code').val());
895        var status_message = function(giftcard_data){
896            var color, message;
897            if(giftcard_data.success){
898                color =  "green";
899                message = "You have $"+giftcard_data.balance+" left on your card.";
900            }else{
901                color =  "red";
902                message = giftcard_data.message;
903            }
904            $('#giftcard_status_message').text(message);
905            $('#giftcard_status_message').attr('style', 'display:block; color:'+color);
906        };
907        $.ajax({
908            url: '/SecureCartCheckGiftcard.json',
909            type: 'GET',
910            dataType: 'json',
911            data: {
912                code: code
913            },
914            success: function(giftcard_data, textStatus, jqXHR) {
915                status_message(giftcard_data);
916                // console.log(giftcard_data.debug);
917                if(giftcard_data.success){
918                    var max_redeem    = Math.min(giftcard_data.balance, cart.cart.prices.total).toFixed(2);
919                    var min_charge    = 1;
920                    var secondary_max = (cart.cart.prices.total - min_charge).toFixed(2);
921
922                    var update_amount = function(){
923                        var num_val = Number($(this).val());
924                        var diff    = cart.cart.prices.total - num_val;
925                        if(num_val > Number(max_redeem)){
926                            $(this).val(max_redeem);
927                        }else if( diff > 0 && diff < min_charge ){
928                            $(this).val(secondary_max);
929                            $('#giftcard_status_message').html("Giftcard amount $"+num_val.toFixed(2)+" does not cover full order amount:<br/>
929 Need to charge at least $1 due to processing minimum.<br/>Changed giftcard amount to $"+secondary_max);
930                            cart.apply_giftcard();
931                        }else if(num_val < 0.01){
932                            $(this).val(0.01);
933                        }
934                    };
935                    $('#giftcard_redeem_amount').val(max_redeem);
936                    $('#giftcard_redeem_amount').change(update_amount).change();
937                    $('#giftcard_redeem_amount').attr('max', max_redeem);
938                    $('#giftcard_redeem_input').show();
939
940                }
941            },
942            error: function(jqXHR, textStatus, errorThrown) {
943                console.log(textStatus);
944                console.log(errorThrown);
945            }
946        });
947    };
948
949    this.do_checkout = function() {
950        if(cart.submitting_checkout){
951            console.log("returning early from checkout, preventing duplicate clicks");
952            return;
953        }
954        cart.toggle_checking_out(true);
955
956        var email = $('form#cart_form input[name=email]').val();
957        var password = $('input[name=password]').val();
958        var password_confirm = $('input[name=password_confirm]').val();
959        var subscribe = $('input:checkbox[name=subscribe]:checked').val();
960        var po_number = $('input[name=po_number]').val();
961        var comments = $('textarea[name=comments]').val();
962        var mobile_phone = $('input[name=mobile_phone]').val();
963        var shipping_option_id = $('input[name=shipping_option_id]:checked').val();
964        var payment_option = $('input:hidden[name=payment_option]').val() ||  $('input:radio[name=payment_option]:checked').val();
965        var cc_firstname = $('input[name=cc_firstname]').val();
966        var cc_lastname = $('input[name=cc_lastname]').val();
967        var cc_num = $('input[name=cc_num]').val();
968        var cc_type = $('input:radio[name=cc_type]:checked').val();
969        var cc_expmonth = $('select[name=cc_expmonth] option:selected').val();
970        var cc_expyear = $('select[name=cc_expyear] option:selected').val();
971        var cc_cvc = $('input[name=cc_cvc]').val();
972
973
974        var errors = [];
975        var addresses = this.get_addresses();
976        if(addresses.errors.length){
977            errors.push(addresses.errors);
978        }
979
980
981        if (! shipping_option_id && cart.cart && cart.cart.has_physical) {
982            errors.push("Need to specify a shipping method");
983        }
984
985        if (payment_option == "creditcard" && ! (cc_firstname && cc_lastname && cc_num && cc_type && cc_expmonth && cc_expyear && cc_cvc)) {
986            errors.push("Need to fill out all fields for credit card");
987        }
988
989        // sorry, no logical XOR in Javascript. This kludge makes do.
990        if ((password && ! password_confirm) || (!password && password_confirm)) {
991            errors.push("You need to enter both a password and a password confirmation");
992        }
993
994        if (password && password_confirm && password != password_confirm) {
995            errors.push("Passwords did not match");
996        }
997
998        if (errors.length > 0) {
999            cart.toggle_checking_out(false);
1000            var errorstring = "Errors: <br />" + errors.join("<br />");
1001            console.log("Showing errors: " + errorstring);
1002            $('#checkout_message_display').attr('style','display:block;');
1003            $("#checkout_message_display").html(errorstring);
1004            $('#error_display').attr('style','display:block;');
1005            $("#error_display").html(errorstring);
1006        } else if (!use_stripe && !cart.cart.has_physical && !cart.totals_initialized){
1007            cart.toggle_checking_out(false);
1008            cart.get_updated_cart_data(true);
1009            //cart.init_totals_html();
1010            //cart.set_payment_html(false);
1011            //return false;
1012        } else {
1013            $('#checkout_message_display').attr('style','display:block;');
1014            $("#checkout_message_display").html("Submitting order - please wait&hellip;");
1015            var d = {
1016                shipping_address_id: addresses.shipping.id,
1017                billing_address_id:  addresses.billing.id,
1018                email:               email,
1019                password:            password,
1020                subscribe:           subscribe,
1021                billing_is_shipping: addresses.billing.is_shipping,
1022                shipping_address_1:  addresses.shipping.address_1,
1023                shipping_address_2:  addresses.shipping.address_2,
1024                shipping_first_name: addresses.shipping.first_name,
1025                shipping_last_name:  addresses.shipping.last_name,
1026                shipping_city:       addresses.shipping.city,
1027                shipping_state:      addresses.shipping.state,
1028                shipping_country:    addresses.shipping.country,
1029                shipping_zip:        addresses.shipping.zip,
1030                shipping_phone:      addresses.shipping.phone,
1031                billing_address_1:   addresses.billing.address_1,
1032                billing_address_2:   addresses.billing.address_2,
1033                billing_first_name:  addresses.billing.first_name,
1034                billing_last_name:   addresses.billing.last_name,
1035                billing_city:        addresses.billing.city,
1036                billing_state:       addresses.billing.state,
1037                billing_country:     addresses.billing.country,
1038                billing_zip:         addresses.billing.zip,
1039                billing_phone:       addresses.billing.phone,
1040                po_number:           po_number,
1041                comments:            comments,
1042                mobile_phone:        mobile_phone,
1043                shipping_option_id:  shipping_option_id,
1044                payment_option:      payment_option,
1045                cc_firstname:        cc_firstname,
1046                cc_lastname:         cc_lastname,
1047                cc_num:              cc_num,
1048                cc_type:             cc_type,
1049                cc_expmonth:         cc_expmonth,
1050                cc_expyear:          cc_expyear,
1051                cc_cvc:              cc_cvc
1052            };
1053
1054            if(cart.cart.giftcard_amount && cart.cart.giftcard_code){
1055                d["giftcard_amount"] = cart.cart.giftcard_amount;
1056                d["giftcard_code"] = cart.cart.giftcard_code;
1057            }
1058
1059            var postOrder = function() {
1060                $.ajax({
1061                    url: '/SecureCartCheckoutSubmit.json',
1062                    // important to be POST because nginx doesn't retry requests w/ non-idempotent methods
1063                    // want to avoid duplicate orders, even if request times out or errors initially
1064                    type: 'POST',
1065                    //dataType: 'jsonp',
1066                    data: d,
1067                    success: function(data, textStatus, jqXHR) {
1068                        console.log("cart submit complete");
1069                        if (data.force_login) {
1070                            cart.toggle_checking_out(false);
1071                            console.log("user not logged in");
1072                            var prepopulated_data = {
1073                                'half_logged_in': 1,
1074                                'username': email
1075                            };
1076                            user.cartSplash(prepopulated_data);
1077
1078                        } else if (data.incomplete_user) {
1079                            cart.toggle_checking_out(false);
1080                            console.log("mailing list user");
1081                            user.showPasswordRecovery(email);
1082
1083                        } else if (data.errors.length > 0) {
1084                            cart.toggle_checking_out(false);
1085                            var errorstring = "Errors from server: <br />" + data.errors.join("<br />");
1086                            if (data.force_cart_refresh){
1087                                // TODO reload cart, dropping unavailable items
1088                                setTimeout(function(){location.reload();}, 5000);
1089                                errorstring += "<span class='fa fa-spinner fa-spin'></span>";
1090                            }
1091                            console.log("errors from server - " + errorstring);
1092                            $('#checkout_message_display').attr('style','display:block;');
1093                            $("#checkout_message_display").html(errorstring);
1094
1095                        } else if (data.status === 'pending_confirmation'
1096                                   && data.client_action
1097                                   && data.client_action.type === 'stripe_payment_intent') {
1098                            // Backend created the PI; confirm it client-side with
1099                            // CardElement + confirmCardPayment. 3DS challenges open
1100                            // a Stripe-managed modal in-browser; on success we
1101                            // navigate to the results page ourselves (CardElement
1102                            // path doesn't auto-redirect like deferred-intent).
1103                            var clientSecret = data.client_action.data.client_secret;
1104                            var bn = (addresses.billing.first_name || '') + ' ' +
1105                                     (addresses.billing.last_name  || '');
1106                            cart.stripe.confirmCardPayment(clientSecret, {
1107                                payment_method: {
1108                                    card: cart.payment_element,
1109                                    billing_details: {
1110                                        name:  bn.trim() || undefined,
1111                                        email: email || undefined,
1112                                        phone: addresses.billing.phone || undefined,
1113                                        address: {
1114                                            line1:       addresses.billing.address_1 || undefined,
1115                                            line2:       addresses.billing.address_2 || undefined,
1116                                            city:        addresses.billing.city || undefined,
1117                                            state:       addresses.billing.state || undefined,
1118                                            postal_code: addresses.billing.zip || undefined,
1119                                            country:     addresses.billing.country || undefined,
1120                                        },
1121                                    },
1122                                },
1123                            }).then(function(result) {
1124                                if (result.error) {
1125                                    // Inline error display; submit button stays
1126                                    // enabled so the customer can retry. The PI is
1127                                    // still attached to the order — a retry will
1128                                    // create a fresh order/PI server-side, which
1129                                    // is fine: the orphaned PI auto-expires.
1130                                    cart.toggle_checking_out(false);
1131                                    $('#checkout_message_display').attr('style','display:block;');
1132                                    $('#checkout_message_display').html(result.error.message || 'Payment failed.');
1133                                    return;
1134                                }
1135                                // Stripe confirmed the PI. The webhook → Lambda
1136                                // will flip payment_state asynchronously; results
1137                                // page polls until it lands.
1138                                window.location = '/SecureCartCheckoutRe
1138sults';
1139                            });
1140
1141                        } else if (data.redirect_url) {
1142                            $('#checkout_message_display').attr('style','display:block;');
1143                            $("#checkout_message_display").html("Order submitted successfully! Please wait while we finish setting things up&hellip;");
1144                            window.location = data.redirect_url;
1145
1146                        } else {
1147                            cart.toggle_checking_out(false);
1148                            $('#checkout_message_display').attr('style','display:block;');
1149                            $("#checkout_message_display").html("Hm, it seems like there was a problem with the order. Try submitting it again.");
1150                        }
1151                    },
1152                    error: function(jqXHR, textStatus, errorThrown) {
1153                        cart.toggle_checking_out(false);
1154                        console.log("cart submit error - " + errorThrown);
1155                        $('#checkout_message_display').attr('style','display:block;');
1156                        $("#checkout_message_display").html("It seems we had problems connecting to the server to finish processing your order. Please try again.");
1157                    }
1158                });
1159            };
1160
1161            // CardElement does its own inline validation as the customer
1162            // types; no pre-flight elements.submit() round-trip needed
1163            // (that's a deferred-intent-only handshake). The server-side
1164            // confirmCardPayment call will surface any remaining errors.
1165            //
1166            // Legacy Charge path: tokenize the card client-side first and
1167            // carry tok_xxx in the POST. The server's _stripe_payment
1168            // reads $q->param('stripe_token') and creates the Stripe::Charge
1169            // inline. PI path skips this — server creates the PI on submit
1170            // and client confirms with the returned client_secret instead.
1171            // Nothing to charge (gift card covers it, or a coupon discounted
1172            // the cart to zero) means there is no card to tokenize — and no
1173            // card element on the page to read one from. Tokenizing anyway
1174            // fails with "Your card number is incomplete." and the order is
1175            // never posted. The server settles a zero balance without
1176            // contacting Stripe (Checkout::Submit's dispatch_payment).
1177            var nothing_to_charge = cart.cart.giftcard_covers_all || cart.cart.discounted_to_zero;
1178            if (use_stripe && cart.payment_mode === 'legacy' && !nothing_to_charge) {
1179                cart.stripe.createToken(cart.payment_element).then(function(result) {
1180                    if (result.error) {
1181                        cart.toggle_checking_out(false);
1182                        $('#checkout_message_display').attr('style','display:block;');
1183                        $('#checkout_message_display').html(result.error.message || 'Card details could not be tokenized.');
1184                        return;
1185                    }
1186                    d.stripe_token = result.token.id;
1187                    postOrder();
1188                });
1189            } else {
1190                postOrder();
1191            }
1192
1193
1194        }
1195
1196        // prevent click action from propagating
1197        return false;
1198    };
1199
1200    return this;
1201};

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.