PageSourceSearch

https://www.bridgepub.com/store/assets/js/jquery.scnstore.js?_=0628a

js bridgepub.com collected 2026-10-02 10:18:27 UTC 128,451 bytes, 3,661 lines download raw bytes

1$('[name="ShippingCountryCode"], [name="BillingCountryCode"]').change(function(){
2    var countrycode = $(this).val();
3
4    if(ScnStore.IsCISCountry(countrycode) && !ScnStore.IsCISWebssite()) {
5        $('[name="ShippingCountryCode"]').val('');
6        $('[name="BillingCountryCode"]').val('');
7        ScnStore.ShowRUMessage(countrycode);
8    }
9});
10
11
12/**/
13
14;(function(factory){
15  
16    if (typeof define === 'function' && define.amd) {
17        define(['jquery'], factory);
18    } else if (typeof exports !== 'undefined') {
19        module.exports = factory(require('jquery'));
20    } else {
21        factory(jQuery);
22    }
23
24})(function($){
25
26    // Prevent console.log() from breaking IE and other old browsers
27    if ( ! window.console ) console = { log: function(){} };
28
29    /*
30     * The constructor function for ScnStore
31     */
32    function ScnStore(element, settings) {
33        this.defaults = {
34            logLevel:    'error',
35            locale:      'en',
36            prodLang:    'en',
37            currency:    'USD',
38            miniCart: {
39                Container: '',
40                AjaxUrl:   '',
41                Enabled:   false
42            },
43            checkoutUrl: 'checkout.action',
44            successUrl: 'thank-you.action',
45            orderForm: {}, // Defaults to empty
46            cardTypes: {
47                visa:       '^4',
48                amex:       '^3[47]',
49                mastercard: '^5[1-5]\d{2}|^2(?:2(?:2[1-9]|[3-9]\d)|[3-6]\d\d|7(?:[01]\d|20))',
50                discover:   '^6(?:011|5)',
51                jcb:        '^(?:2131|1800)',
52                dinersclub: '^3(?:0[0-5]|[68])'
53            }
54        };
55
56        // We create a new property to hold our default settings after they
57        // have been merged with user supplied settings
58        this.settings = $.extend({},this,this.defaults,settings);
59
60        // This object holds values that will change as the plugin operates
61        this.initials = {
62            lastCart:    {},
63            orderForm:   {},
64            isPageLoad:  true,
65            countryList: {},
66            currency:    this.settings.currency     
67        };
68
69        // Attaches the properties of this.initials as properties of ScnStore
70        $.extend(this,this.initials);
71
72        // Here we'll hold a reference to the html tag of the document
73        this.$el = $('html');
74
75        // We'll call our initiator function to get things rolling!
76        this.init();
77    }
78
79    /**
80     * Called once per instance
81     * Calls starter methods and associate the '.zippy-carousel' class
82     * @params void
83     * @returns void
84     */
85    ScnStore.prototype.init = function(){
86        // Add a class to the body so we can add styles
87        this.$el.addClass('scnstore');
88
89        // Pull in the logger library so we can have good logging that doesn't
90        // break browsers and preserves stack traces (loglevel.js)
91        this.log = window.log.noConflict();
92        this.log.setLevel(this.settings.logLevel);
93
94        // Bind any events to make stuff happen on the real
95        this.activate();
96
97        // Register the events. Let the user click stuff to make things happen
98        this.events();
99
100        // Activate the mini cart if it's enabled
101        if (this.settings.miniCart.Enabled) {
102            this.MiniCart.init();
103        }
104
105        // Tell the app that the page loaded (should be last)
106        this._pageLoaded();
107    };
108    
109    
110    /**
111     * Activates the initial store stuff when the page loads
112     * @params void
113     * @returns void
114     *
115     */ 
116    ScnStore.prototype.activate = function(){
117        
118        // Update the cart contents as soon as we get to the page.
119        this.CartContents();
120
121        // Load in the order form data if any is set
122        this._loadOrderForm();
123
124        // Set any defaults for page load
125        this._initPaymentForm();
126
127    };
128    
129    /**
130     * Associate event handlers to events
131     * For all events we'll add them in here.
132     * @params void
133     * @returns void
134     *
135     */
136    ScnStore.prototype.events = function(){
137
138        // It's like that and like this and like that and uh...
139        var that = this;
140
141        // Find all the AddToCart buttons and attach a listener to each one
142        $('body').on('click', '.scnstore-atc', function(){
143
144            console.log('Add to cart click detected');
145
146            var $el = $(this);
147            var prodId = $el.data('store-product-id');
148            var qty = ($el.data('store-qty'))? $(this).data('store-qty'): 1;
149
150            that._failIfUndefined(prodId,
151                'data-store-product-id not set on the element!');
152
153            that._logger('Add To Cart click detected', 'info');
154            that._logger('Data on click: '+prodId+' with qty:'+qty);
155
156            // Add a loading class to the element
157            // (Remove it when the response comes back)
158            $el.addClass('loading');
159
160            that.AddToCart(prodId, qty, $el);
161        });
162
163
164        // Remove item from the cart by uniqueId
165        $('.scnstore-remove').on('click', function(e){
166            e.preventDefault();
167            var uniqueId = $(this).data('store-unique-id');
168            
169            that._failIfUndefined(uniqueId,
170                'data-store-unique-id not set on the element!');
171
172            that._logger('Remove line item click detected', 'info');
173            that._logger('Data on click: ' + uniqueId);
174            that.RemoveLineItem(uniqueId);        
175        });
176
177
178        // External Payment Trigger
179        $('.external-payment-button').on('click', function(e){
180            e.preventDefault();
181            var paymentMethod = $(this).data('payment-method');
182
183            if(paymentMethod == "service_sberbank") {
184                var form = $('#order_form').validate();
185                if(!form.checkForm()) {
186                    form.showErrors();
187                    return;
188                }
189            }
190            
191            that._failIfUndefined(paymentMethod,
192                'data-payment-method not set on the element!');
193                
194            $(this).addClass('disabled').addClass('wait');
195
196            that._logger('External payment click detected', 'info');
197            that._logger('Data on click: ' + paymentMethod);
198            that.BeginExternalPayment(paymentMethod);
199
200        });
201
202
203        // I WISH THE BELOW WASN'T SO HORRIBLE LOOKING. BUT IT WORKS
204
205        /**
206         * ATTACH LISTENERS FOR COUNTRY AND STATE FIELDS
207         *
208         * These listen for changes on the country and state fields which
209         * will fire all the updates on the lists themselves.
210         */
211        if (! $.isEmptyObject(this.orderForm)) {
212
213            that = this;
214            var form = this.orderForm;
215
216            // One for billing address
217            if (this._isset(form.BillingAddressData)) {
218                $('#'+form.BillingAddressData.CountryCode.Id).change(function() {
219                    that.UpdateStateList(
220                        form.BillingAddressData.StateProvince.Id,
221                        form.BillingAddressData.CountryCode.Id
222                    );
223                });
224
225                // When the state changes we should check if "other" was selected
226                // and handle appropriately
227                $('#'+form.BillingAddressData.StateProvince.Id).change(function() {
228                    that._checkForOtherStateValAndHandle(
229                        that.orderForm.BillingAddressData.StateProvince.Id
230                    );
231                });
232            }
233
234            // 
235            if (this._isset(form.ShippingAddressData)) {
236                $('#'+form.ShippingAddressData.CountryCode.Id).change(function() {
237                    that.UpdateStateList(
238                        form.ShippingAddressData.StateProvince.Id,
239                        form.ShippingAddressData.CountryCode.Id
240                    );
241                });
242
243                // Shipping one, too
244                $('#'+form.ShippingAddressData.StateProvince.Id).change(function() {
245                    that._checkForOtherStateValAndHandle(
246                        that.orderForm.ShippingAddressData.StateProvince.Id
247                    );
248                });
249            }
250
251
252            /**
253             * ATTACH LISTENERS FOR ALL FORM FIELDS
254             *
255             * These listen for changes on the defined fields which will update
256             * the order form via ajax calls
257             */
258
259            // Elements to for change/blur to call UpdateCart
260            var onblur = {
261                ShippingAddressData: [
262                    'Email', 'FirstName', 'LastName', 'Address1', 'Address2', 'City',
263                    'PostalCode', 'Phone'
264                ],
265                BillingAddressData: [
266                    'FirstName', 'LastName', 'Address1', 'Address2', 'City',
267                    'PostalCode', 'Phone'
268                ],
269            };
270            var onchange = {
271                ShippingAddressData: ['StateProvince', 'CountryCode'],
272                BillingAddressData:  ['StateProvince', 'CountryCode']
273            };
274
275            // Use with extreme caution! Will chew up resources if you're not careful
276            var oninput = {
277                ShippingAddressData: ['PostalCode'],
278                BillingAddressData:  ['PostalCode']
279            };
280
281            // Loop through onblur elements and attach listeners to them.
282            $.each(onblur, function(section, fields) {
283                for (var i = fields.length - 1; i >= 0; i--) {
284                    if (that._isset(form[section][fields[i]])) {
285                        var shipElemId = form[section][fields[i]].Id;
286                        $(document).on('blur', '#'+shipElemId, function() {
287                            that.UpdateOrderForm();
288                        });
289                    }
290                }
291            });
292
293            // Loop through onchange elements in the same way
294            $.each(onchange, function(section, fields) {
295                for (var i = fields.length - 1; i >= 0; i--) {
296                    if (that._isset(form[section][fields[i]])) {
297                        var shipElemId = form[section][fields[i]].Id;
298                        $('#'+shipElemId).change(function() {
299                            that.UpdateOrderForm();
300                        });
301                    }
302                }
303            });
304
305
306            // And the same with the oninput elements
307            $.each(oninput, function(section, fields) {
308                for (var i = fields.length - 1; i >= 0; i--) {
309                    if (that._isset(form[section][fields[i]])) {
310                        var field = fields[i];
311                        var shipElemId = form[section][field].Id;
312                        $('#'+shipElemId).on('input', function() {
313    
314                            // Special handling for postcodes. We don't want to send a call on every
315                            // keystroke if we don't need to. This will only check ones that are on the
316                            // list of postcode types to check
317                            if (field == 'PostalCode') {
318                                var countryCode = $('#'+form[section].CountryCode.Id).val();
319                                if ($.inArray(countryCode, that._shipQuoteValidationList.PostCodeRequired) !== -1) {
320                                    that.UpdateOrderForm();
321                                }
322                            }
323                        });
324                    }
325                }
326            });
327
328
329
330            // Attach the credit card number handler here so it handles input
331            // standardly. This has been piloted on other payment forms with
332            // success.
333            if (form.hasOwnProperty('PaymentMethods')) {
334                if (form.PaymentMethods.hasOwnProperty('creditcard')) {
335                    var cardId = form.PaymentMethods.creditcard.AccountNumber.Id;
336                    
337                    // Attach input listener
338                    $(document).on('input', '#'+cardId, function() {
339
340                        // take the input by keystroke and format the digits
341                        // in groups of four with no non-digit characters.
342                        var initVal = $(this).val();
343                        $(this).val(that._cardFormat(initVal));
344
345                        // Auto-select the card type icons based on first digits
346                        // of the card number
347                        var cardType = that._getCardType($(this).val());
348                        if (cardType) {
349                            that._setCardType(cardType);
350                        } else {
351                            that._unsetAllCardTypes();
352                        }
353                    });
354                }
355            }
356
357            $(document).on('click', '.payment-card', function() {
358                var types = that.settings.cardTypes;
359                var $el = $(this);
360
361                $.each(types, function(card, regex) {
362                    if ($el.hasClass(card)) {
363                        that._unsetAllCardTypes();
364                        $el.addClass('active');
365                    }
366                });
367            });
368
369
370            // LANGUAGE UPDATE
371            $('.cart-item .language').change(function() {
372                var itemCode = $(this).val();
373                var uniqueId = $(this).closest('.cart-item')
374                    .data('scnstore-uniqueid');
375
376                that._logger('Updating language', 'info');
377                that._logger('Unique ID: ' + uniqueId);
378                that._logger('New item code: ' + itemCode);
379
380                $(this).attr('disabled', true);
381                that.ChangeLanguage(uniqueId, itemCode);
382            });
383
384
385            // QUANTITY UPDATE
386            $('.cart-item .item-qty').change(function() {
387                var qty = $(this).val();
388                var uniqueId = $(this).closest('.cart-item')
389                    .data('scnstore-uniqueid');
390
391                that._logger('Updating quantity', 'info');
392                that._logger('Unique ID: ' + uniqueId);
393                that._logger('New qunatity: ' + qty);
394
395                $(this).attr('disabled', true);
396                that.ChangeQuantity(uniqueId, qty);
397            });
398
399        }
400    };
401
402    
403    /**
404     * AddToCart
405     * Triggers a set of subfunctions to add a specified item to the cart with
406     * a specified quantity
407     * @params  string  productId
408     * @returns object  cart contents
409     *
410     */
411    ScnStore.prototype.AddToCart = function(productId, qty, $el, callback){
412        
413        if ( ! this._isset(qty) || qty == 0) {
414            qty = 1;
415        }
416
417        productId = this._resolveBonusReplacement(productId);
418
419        window.trackEvent && trackEvent('Store', 'Add to Cart', productId);
420
421        this._logger('Called AddToCart with: ', 'table');
422        this._logger({ProductID:productId,Quantity:qty});
423
424        var req = this._jsonRpcRequest("CartApi.AddToCart", [{
425            LineItem: {
426                ProductId: productId,
427                Quantity: qty,
428            },
429        }]);
430
431        $.ajaxq('scnstoreq', $.extend(this._jsonBp, {
432            context: this,
433            data: {r:JSON.stringify(req)},
434            success: function(data){
435
436                this._displayError(data);
437                if (data.hasOwnProperty('result')) {
438                    this._updateCart(data.result);
439                }
440
441                if (this._isset($el)) {
442                    $el.removeClass('loading');
443
444                    if (this._isset('callback') && callback == 'inline') {
445                        this._atcInline($el);
446                        return;
447                    }
448
449                    this._atcBtnFeedback($el);
450                }
451            },
452            failure: function(errMsg) {
453                this._logger('AddToCart wasn\'t successful', 'warn');
454                this._throwError(errMsg);
455                $el && $el.removeClass('loading');
456            },
457        }));
458    };
459
460
461    ScnStore.prototype._resolveBonusReplacement = function(productId) {
462        if (!window.BonusEbookAddon ||
463            typeof window.BonusEbookAddon.getReplacementProductId !== 'function') {
464            return productId;
465        }
466
467        return window.BonusEbookAddon.getReplacementProductId(productId) || productId;
468    };
469
470
471    /**
472     * CartContents
473     * Get an object with the full cart in 
473it
474     * @params  none
475     * @returns object  cart contents
476     *
477     */
478    ScnStore.prototype.CartContents = function(callback){
479
480        this._logger('Called CartContents', 'info');
481
482        var req = this._jsonRpcRequest("CartApi.CartContents");
483
484        $.ajaxq('scnstoreq', $.extend(this._jsonBp, {
485            context: this,
486            data: {r:JSON.stringify(req)},
487            success: function(data){
488                // If the call returns an error, handle it appropriately
489                if (data.hasOwnProperty('result')) {
490                    this._updateCart(data.result);
491                }
492                
493                this._displayError(data);
494
495                callback && callback();
496            },
497            failure: function(errMsg) {
498                this._logger('CartContents wasn\'t successful', 'warn');
499                this._throwError(errMsg);
500            },
501        }));
502    };
503
504
505    /**
506     * LineItemCount
507     * Return the number of items in the shopping cart
508     * @params  none
509     * @returns int
510     *
511     */
512    ScnStore.prototype.LineItemCount = function(){
513
514        if (this.hasOwnProperty('lastCart')) {
515            if ($.isEmptyObject(this.lastCart) ||
516                this.lastCart == false ||
517                this.lastCart.LineItems == null) {
518                return 0;
519            }
520        } else {
521            return 0;
522        }
523
524        return this.lastCart.LineItems.length;
525    };
526
527    /**
528     * GenerateShareLink
529     * Return the link to share on checkout page to share cart items
530     * @params  none
531     * @returns int
532     *
533     */
534    ScnStore.prototype.GenerateShareLink = function(){
535
536        // var urlPath = "/store/checkout.action";
537        var url = new URL(window.location.href);
538        url.search = "";
539
540        var orgId = $('[name="call-center-cos"]').val();
541        if(orgId && orgId != "") {
542            url.searchParams.set("orgid", orgId);
543        }
544
545        var ref = $('[name="staff-member"]').val();
546        if(ref && ref != "") {
547            url.searchParams.set("ref", ref);
548        }
549
550        var promoCode = $('[name="promo-code"]').val();
551        if(promoCode && promoCode != "") {
552            url.searchParams.set("promocode", promoCode);
553        }
554
555        if (this.hasOwnProperty('lastCart')) {
556            if ($.isEmptyObject(this.lastCart) ||
557                this.lastCart == false ||
558                this.lastCart.LineItems == null) {
559                return url.toString();
560            }
561        } else {
562            return url.toString();
563        }
564
565        for (var i = 0; i < this.lastCart.LineItems.length; i++) {
566            var item = this.lastCart.LineItems[i];
567            var id = i + 1;
568            url.searchParams.set("item" + id, item.ProductId);
569            url.searchParams.set("qty" + id, item.Quantity);
570        }
571
572        return url.toString();
573    };
574
575
576    /**
577     * CHANGE THE LANGUAGE OF AN ITEM
578     * 
579     * Updating a line item langugae consists of passing in a new item code which
580     * contains the language and the unique product ID.
581     * 
582     * Return the number of items in the shopping cart
583     * @params  uniqueId
584     * @params  newItemCode
585     * @returns new item
586     *
587     */
588    ScnStore.prototype.ChangeLanguage = function(uniqueId, newItemCode) {
589
590        var that = this;
591
592        // Do some checks
593        if ( ! this._isset(uniqueId)) {
594            this._logger('The item\'s unique ID is required.', 'error');
595            return;
596        }
597        if ( ! this._isset(newItemCode)) {
598            this._logger('The new item code with proper language is required.', 'error');
599            return;
600        }
601
602        var newItem = {
603            UniqueId: uniqueId,
604            ProductId: newItemCode
605        };
606
607        // On success, lets try to slap the new item data on there
608        this.UpdateLineItem(newItem);
609    };
610
611
612
613    /**
614     * CHANGE THE QUANTITY OF AN ITEM
615     * 
616     * Updating a line item quantity consists of passing in a new int(quantity)
617     * and calling the ajax gods from above
618     * 
619     * Return the number of items in the shopping cart
620     * @params  uniqueId
621     * @params  newItemCode
622     * @returns new item
623     *
624     */
625    ScnStore.prototype.ChangeQuantity = function(uniqueId, qty) {
626
627        if ( ! this._isset(uniqueId)) {
628            this._logger('The item\'s unique ID is required.', 'error');
629            return;
630        }
631        if ( ! this._isset(qty) && /^\d+$/.test(qty)) {
632            this._logger('The new item quantity is required and must be a number.', 'error');
633            return;
634        }
635
636        // If it was set to 0, remove the line item from the cart 
637        if (qty == 0) {
638            if ($('li[data-scnstore-uniqueid='+uniqueId+']').find('.delete').length) {
639
640                // Simulate a click on this trigger as it's set up with css and such, keeping it simple
641                $('li[data-scnstore-uniqueid='+uniqueId+']').find('.delete').click();
642                return;
643            }
644
645            this.RemoveLineItem(uniqueId);
646        }
647
648        var newItem = {
649            UniqueId: uniqueId,
650            Quantity: qty
651        };
652
653        // On success, lets try to smack the new item data on there
654        this.UpdateLineItem(newItem);
655    };
656
657
658    /**
659     * UpdateLineItem
660     * Update an item in the cart
661     *
662     * Looks like this:
663     * {UniqueId: "b1g70ng5tr1ng", ProductId: "dmsmh.en.hc", etc.} 
664     *
665     * You don't have to put all the params on there if you don't want to but
666     * you certainly could if you wanted to. The key is the unique id
667     *
668     * @params  none
669     * @returns object  cart contents
670     *
671     */
672    ScnStore.prototype.UpdateLineItem = function(newItem){
673        this._logger('Called UpdateLineItem...', 'info');
674        var that = this;
675
676        var li = {};
677        $.each(this.lastCart.LineItems, function(item, attrs) {
678            if (attrs.UniqueId == newItem.UniqueId) {
679                li = that.lastCart.LineItems[item];
680            }
681        });
682
683        if (li.length == 0) {
684            this._logger('Item not found by unique id', 'error');
685            return;
686        }
687
688
689        // Roll them all together
690        li = $.extend({}, li, newItem);
691        var req = this._jsonRpcRequest("CartApi.UpdateLineItem", [{
692            LineItem: li,
693        }]);
694
695        this._logger('New item updating to:');
696        this._logger(li);
697
698        $.ajaxq('scnstoreq', $.extend(this._jsonBp, {
699            context: this,
700            data: {r:JSON.stringify(req)},
701            success: function(data){
702                this._logger('UpdateLineItem successful:', 'info');
703
704                if (data.hasOwnProperty('result')) {
705                    this._updateCart(data.result);
706                    
707                    $.each(data.result.LineItems, function(i, item) {
708                        if (item.UniqueId == newItem.UniqueId) {
709                            that._updateProductAttrs(item);
710                        }
711                    });
712                }
713
714                this._displayError(data);
715            },
716            failure: function(errMsg) {
717                this._logger('CartContents wasn\'t successful', 'warn');
718                this._throwError(errMsg);
719            },
720        }));
721
722        this.UpdateOrderForm();
723
724        return true;
725    };
726
727
728    /**
729     * RemoveLineItem
730     * Removes a line item with the specified unique line item id
731     * @params  string  line item id
732     * @returns object  cart contents
733     *
734     */
735    ScnStore.prototype.RemoveLineItem = function(uniqueId){
736
737        this._logger('Called RemoveLineItem with: ', 'info');
738        this._logger({uniqueId:uniqueId});
739
740        var req = this._jsonRpcRequest("CartApi.RemoveLineItem", [{
741            LineItem: {
742                UniqueId: uniqueId
743            },
744        }]);
745
746        $.ajaxq('scnstoreq', $.extend(this._jsonBp, {
747            context: this,
748            data: {r:JSON.stringify(req)},
749            success: function(data){
750
751                // If the call returns an error, handle it appropriately
752                this._displayError(data);
753                if (data.hasOwnProperty('result')) {
754                    this._updateCart(data.result);
755                }
756            },
757            failure: function(errMsg) {
758                this._logger('RemoveLineItem call failed', 'warn');
759                this._throwError(errMsg);
760            },
761        }));
762
763        if ($.isEmptyObject(this.orderForm)) {
764            this.UpdateOrderForm();
765        }
766    };
767
768
769    /**
770     * SetReferrerName
771     * Sets ReferrerName
772     * @params  referrerName
773     * @returns ---
774     *
775     */
776    ScnStore.prototype.SetReferrerName = function(referrerName) {
777        var attrs = {
778            referrer_name: referrerName,
779        };
780
781        var that = this;
782        var req = this._jsonRpcRequest("CartApi.SetReferrerName", [attrs]);
783
784        $.ajaxq('scnstoreq', $.extend(this._jsonBp, {
785            context: ScnStore,
786            data: {r:JSON.stringify(req)},
787            success: function(data){
788
789                // If the call returns an error, handle it appropriately
790                that._displayError(data);
791                if (data.hasOwnProperty('result')) {
792                    that._updateCart(data.result);
793                }
794            },
795            failure: function(errMsg) {
796                that._logger('SetReferrerName call failed', 'error');
797                that._throwError(errMsg);
798            },
799        }));
800
801     };
802
803    /**
804     * SetPromoCode
805     * Sets ReferrerName
806     * @params  promoCode
807     * @returns ---
808     *
809     */
810    ScnStore.prototype.SetPromoCode = function(promoCode) {
811        var attrs = {
812            promo_code: promoCode,
813        };
814
815        var that = this;
816        var req = this._jsonRpcRequest("CartApi.SetPromoCode", [attrs]);
817
818        $.ajaxq('scnstoreq', $.extend(this._jsonBp, {
819            context: ScnStore,
820            data: {r:JSON.stringify(req)},
821            success: function(data){
822
823                // If the call returns an error, handle it appropriately
824                that._displayError(data);
825                if (data.hasOwnProperty('result')) {
826                    that._updateCart(data.result);
827                }
828            },
829            failure: function(errMsg) {
830                that._logger('SetPromoCode call failed', 'error');
831                that._throwError(errMsg);
832            },
833        }));
834
835     };
836
837    /**
838     * SetMembershipNumber
839     * Sets ReferrerName
840     * @params  promoCode
841     * @returns ---
842     *
843     */
844    ScnStore.prototype.SetMembershipNumber = function(membershipNumber) {
845        var attrs = {
846            membership_number: membershipNumber,
847        };
848
849        var that = this;
850        var req = this._jsonRpcRequest("CartApi.SetMembershipNumber", [attrs]);
851
852        $.ajaxq('scnstoreq', $.extend(this._jsonBp, {
853            context: ScnStore,
854            data: {r:JSON.stringify(req)},
855            success: function(data){
856
857                // If the call returns an error, handle it appropriately
858                that._displayError(data);
859                if (data.hasOwnProperty('result') && data.result) {
860                    that._updateCart(data.result);
861                    $('.ias-error').addClass('hidden');
862                    $('#ias_success').removeClass('hidden');
863                } else {
864                    $('#ias_success').addClass('hidden');
865                    $('#ias_error_invalid').removeClass('hidden');
866                }
867            },
868            failure: function(errMsg) {
869                that._logger('SetMembershipNumber call failed', 'error');
870                that._throwError(errMsg);
871            },
872        }));
873
874     };
875
876    /**
877     * GetCartShareLink
878     * 
879     * @params  ---
880     * @returns promise with link to share Shipping Address, Billing Address, IAS Num & Referrer name
881     *
882     */
883    ScnStore.prototype.GetCartShareLink = function() {
884        var that = this;
885        var req = this._jsonRpcRequest("CartApi.EncodeShareCartLink", []);
886
887        return new Promise(function (resolve, reject) {
888            $.ajaxq('scnstoreq', $.extend(that._jsonBp, {
889                context: ScnStore,
890                data: {r:JSON.stringify(req)},
891                success: function(data){
892                    // If the call returns an error, handle it appropriately
893                    that._displayError(data);
894                    console.log(data);
895                    resolve(data.result.ResponseInfoList[0].Data);
896                },
897                failure: function(errMsg) {
898                    that._logger('GetCartShareLink call failed', 'error');
899                    that._throwError(errMsg);
900                },
901            }));
902        });
903
904     };
905
906    /**
907     * DecodeShareCartLink
908     * 
909     * @params  data -> string to decode
910     * @returns updates cart and checkout information
911     *
912     */
913    ScnStore.prototype.DecodeShareCartLink = function(data) {
914        var attrs = {
915            data: data
916        };
917
918        var that = this;
919        var req = this._jsonRpcRequest("CartApi.DecodeShareCartLink", [attrs]);
920
921        $.ajaxq('scnstoreq', $.extend(this._jsonBp, {
922            context: ScnStore,
923            data: {r:JSON.stringify(req)},
924            success: function(data){
925
926                // If the call returns an error, handle it appropriately
927                that._displayError(data);
928                console.log(data);
929                that.UpdateCheckoutInfo(data.result);
930            },
931            failure: function(errMsg) {
932                that._logger('DecodeShareCartLink call failed', 'error');
933                that._throwError(errMsg);
934            },
935        }));
936
937     };
938     
939    /**
940     * UpdateCheckoutInfo
941     * 
942     * @params  order -> order information
943     * @returns updates cart and checkout information
944     *
945     */
946    ScnStore.prototype.UpdateCheckoutInfo = function(order) {
947        var form = this.orderForm;
948        var shipAddr = form.ShippingAddressData;
949        var billAddr = form.BillingAddressData;
950
951        if (typeof shipAddr !== "undefined") {
952            $('[name="' + shipAddr.Email.Id + '"]').val(order.ShippingAddressData.Email);
953            $('[name="' + shipAddr.FirstName.Id + '"]').val(order.ShippingAddressData.FirstName);
954            $('[name="' + shipAddr.LastName.Id + '"]').val(order.ShippingAddressData.LastName);
955            $('[name="' + shipAddr.Address1.Id + '"]').val(order.ShippingAddressData.Address1);
956            $('[name="' + shipAddr.Address2.Id + '"]').val(order.ShippingAddressData.Address2);
957            $('[name="' + shipAddr.CountryCode.Id + '"]').val(order.ShippingAddressData.CountryCode);
958            $('[name="' + shipAddr.Phone.Id + '"]').val(order.ShippingAddressData.Phone);
959            $('[name="' + shipAddr.City.Id + '"]').val(order.ShippingAddressData.City);
960            $('[name="' + shipAddr.StateProvince.Id + '"]').val(order.ShippingAddressData.StateProvince);
961            $('[name="' + shipAddr.PostalCode.Id + '"]').val(order.ShippingAddressData.PostalCode);
962        }
963
964        if (typeof billAddr !== "undefined") {
965            $('[name="' + billAddr.FirstName.Id + '"]').val(order.BillingAddressData.FirstName);
966            $('[name="' + billAddr.LastName.Id + '"]').val(order.BillingAddressData.LastName);
967            $('[name="' + billAddr.Address1.Id + '"]').val(order.BillingAddressData.Address1);
968            $('[name="' + billAddr.Address2.Id + '"]').val(order.BillingAddressData.Address2);
969            $('[name="' + billAddr.Phone.Id + '"]').val(order.BillingAddressData.Phone);
970            $('[name="' + billAddr.CountryCode.Id + '"]').val(order.BillingAddressData.CountryCode);
971            $('[name="' + billAddr.City.Id + '"]').val(order.BillingAddressData.City);
972            $('[name="' + billAddr.StateProvince.Id + '"]').val(order.BillingAddressData.StateProvince);
973            $('[name="' + billAddr.PostalCode.Id + '"]').val(order.BillingAddressData.PostalCode);
974        }
975        $('[name="promo-code"]').val(order.PromoCode);
976        $('[name="IasNumber"]').val(order.MembershipNumber);
977        $('[name="staff-member"]').val(order.ReferrerName);
978
979        // apply discount
980        if(order.MembershipNumber != "" && order.PromoCode != "") {
981            $('#IasApplyBtn').click();
982        }
983    };
984
985    /**
986     * UPDATE UI
987     * General callback function that you can tie things to if you want to
988     * update them in the UI after the cart gets updated in any way.
989     * @params  ---
990     * @returns ---
991     *
992     */
993    ScnStore.prototype.UpdateUI = function() {
994
995        this._logger('Updating the UI...', 'info');
996        this._logger('The last cart is');
997        this._logger(this.lastCart);
998
999        var $counter = $('.store-cart-items-count');
1000
1001        // Check if there is an store-cart-items-count and update
1002        if ($counter.length) {
1003            if (this.LineItemCount() != 0) {
1004                $counter.text(this.LineItemCount()).removeClass('invisible');
1005            } else {
1006                $counter.text('').addClass('invisible');
1007            }
1008        }
1009        
1010        if (this.settings.miniCart.Enabled) {
1011            // Update the minicart (it will check if its enabled or not)
1012            this.UpdateMiniCart();
1013        }
1014
1015        // If this is a respnse from a full recalculate, better get to updating
1016        // all the shipping options and such
1017        this._setShippingOptions();
1018
1019        this.UpdateCartTotals();
1020
1021        this.UpdateItemListing();
1022    };
1023
1024
1025    /** 
1026     * UPDATE CART TOTALS
1027     *
1028     * Updates the cart items in the checkout page via ajax and replaces the
1029     * li in the DOM with the response. The template does all the PT
1030     * cart accessing.
1031     * @params ---
1032     * @return bool
1033     */
1034    ScnStore.prototype.UpdateCartTotals = function() {
1035        this._logger('Updating Cart Totals...', 'info');
1036
1037        // Keep this as a pet named that
1038        var cart     = this.lastCart,
1039            shipping = cart.Shipping,
1040            subtotal = cart.Subtotal,
1041            tax      = cart.Tax,
1042            discount = cart.Discount,
1043            customs  = cart.Customs,
1044            total    = cart.Total,
1045            locale   = this.settings.locale.split('_'),
1046            currency = this.settings.currency,
1047            lang     = locale[0],
1048            fmtc     = window.FormatCurrency, // Currency formatter function
1049            that     = this;
1050
1051        // update totals and stuff
1052        $totals = $('#totals_box');
1053        $totals.find('.subtotal .totals-data bdi').html(fmtc(lang, currency, subtotal, false));
1054        $totals.find('.shipping .totals-data bdi').html(fmtc(lang, currency, shipping, false));
1055        $totals.find('.discount .totals-data bdi').html("-"+fmtc(lang, currency, discount, false));
1056        $totals.find('.customs .totals-data bdi').html(fmtc(lang, currency, customs, false));
1057        $totals.find('.tax .totals-data bdi').html(fmtc(lang, currency, tax, false));
1058        $totals.find('.grand-total .totals-data bdi').html(fmtc(lang, currency, total, false));
1059        
1060        // Conditional values.
1061        var cond = ['tax', 'discount', 'customs'];
1062        $('#totals_box .discount').attr('data-amount', discount);
1063        $('#totals_box .customs').attr('data-amount', customs);
1064        $('#totals_box .tax').attr('data-amount', tax);
1065
1066        for (var i = cond.length - 1; i >= 0; i--) {
1067            if ($totals.find('.'+cond[i]).attr('data-amount') > 0 || cart[cond[i]] > 0) {
1068                $totals.find('.'+cond[i]).removeClass('hidden');
1069            } else {
1070                $totals.find('.'+cond[i]).addClass('hidden');
1071            }
1072        }
1073    };
1074
1075    /**
1076     * UpdateItemListing
1077     * Updated page html and adds classes to the items which are added to the cart.
1078     * Then CSS makes page look different (such as "Added to Card" button)
1079     */
1080    ScnStore.prototype.UpdateItemListing = function() {
1081        $('[data-listing-product-id]').removeClass('listing__item--added-to-cart');
1082        if (this.lastCart.LineItems) {
1083            $.each(this.lastCart.LineItems, function(index, item) {
1084                $('[data-listing-product-id="'+item.ProductId+'"]').addClass('listing__item--added-to-cart');
1085            });
1086        }
1087    };
1088
1089    /**
1090     * UpdateMiniCart
1091     *
1092     * Updates the minicart via ajax and replaces it in the DOM with the
1093     * response. The template does all the PT cart accessing.
1094     * @params ---
1095     * @return bool
1096     */
1097    ScnStore.prototype.UpdateMiniCart = function() {
1098
1099        this._logger('Updating Mini Cart...', 'info');
1100
1101        if (this.MiniCart.syncFromCart(this.lastCart)) {
1102            if (this.MiniCart.skipMiniCart == false) {
1103                this.MiniCart.showMiniCart(3000);
1104            }
1105
1106            this.MiniCart.skipMiniCart = false;
1107            this._logger('Updated the Mini Cart inline', 'info');
1108
1109            return true;
1110        }
1111
1112        // Preserve this
1113        var that = this;
1114        var preservedImages = {};
1115        var $miniCartContainer = $(that.settings.miniCart.Container);
1116        var $itemsConstraint = $miniCartContainer.find('.items-constraint');
1117        var scrollTop = $itemsConstraint.scrollTop();
1118
1119        $miniCartContainer.find('.minicart-item').each(function() {
1120            var $item = $(this);
1121            var uniqueId = $item.data('scnstore-uniqueid');
1122            var $image = $item.find('.image').children().detach();
1123
1124            if (uniqueId && $image.length) {
1125                preservedImages[uniqueId] = $image;
1126            }
1127        });
1128
1129        // Update the mini_cart if it's enabled
1130        $.get( this.settings.miniCart.AjaxUrl, function( data ) {
1131            $miniCartContainer.html( data );
1132
1133            $miniCartContainer.find('.minicart-item').each(function() {
1134                var $item = $(this);
1135                var uniqueId = $item.data('scnstore-uniqueid');
1136
1137                if (uniqueId && preservedImages[uniqueId]) {
1138                    $item.find('.image').empty().append(preservedImages[uniqueId]);
1139                }
1140            });
1141
1142            $miniCartContainer.find('.items-constraint').scrollTop(scrollTop);
1143
1144            // If this is not a pageload then show the cart for a few seconds
1145            // then hide it again.
1146            if (that.MiniCart.skipMiniCart == false) {
1147                that.MiniCart.showMiniCart(3000);
1148            }
1149
1150            // Now that it's after the first time (pageload), allow the minicart to show
1151            that.MiniCart.skipMiniCart = false;
1152
1153            // Bind all the minicart DOM stuff to the JS that controls it
1154            that._logger('Updated the Mini Cart', 'info');
1155
1156            return true;
1157        });
1158    }; 
1159
1160    ScnStore.prototype.MiniCart = {
1161
1162        timeoutId: 0,
1163        skipMiniCart: true,
1164
1165        init: function() {
1166            // Bind the events to the object
1167            this.events();
1168        },
1169
1170        // Minicart module manipulation
1171        hideMiniCart: function() {
1172            $('#mini_cart').removeClass('active');
1173            setTimeout(function(){$('#mini_cart').addClass('hidden');}, 250);
1174        },
1175
1176        showMiniCart: function(duration) {
1177            var that = this;
1178
1179            // Only do this if there is something in the cart
1180            $('#mini_cart').delay(200).removeClass('hidden').addClass('active');
1181
1182            if (typeof that.timeoutId !== "undefined") {
1183                clearTimeout(that.timeoutId);
1184            }
1185
1186            // If there was a duration set, then set the timeout here so the
1187            // minicart will auto-hide.
1188            if (typeof duration !== "undefined") {
1189                that.timeoutId = setTimeout(function(){
1190                    that.hideMiniCart();
1191                }, duration);
1192            }
1193        },
1194
1195        // Individual item manipulation
1196        removeItem: function($el) {
1197            var obj = window.ScnStore;
1198
1199            // Trigger the removal animation
1200            $el.addClass('deleted');
1201
1202            // Timeout and remove the item from the DOM after the CSS removes it.
1203            setTimeout(function(){
1204                $el.remove();
1205                obj.RemoveLineItem($el.data('scnstore-uniqueid'));
1206            }, 300);
1207        },
1208
1209        syncFromCart: function(cart) {
1210            var obj = window.ScnStore;
1211            var $miniCart = $('#mini_cart');
1212            var $items = $miniCart.find('.minicart-item');
1213            var lineItems = cart && cart.LineItems ? cart.LineItems : [];
1214            var locale;
1215            var lang;
1216            var currency;
1217
1218            if (!$miniCart.length || !$items.length || !$miniCart.find('.minicart-subtotal bdi').length) {
1219                return false;
1220            }
1221
1222            if ($items.length !== lineItems.length) {
1223                return false;
1224            }
1225
1226            locale = obj.settings.locale.split('_');
1227            lang = locale[0];
1228            currency = obj.settings.currency;
1229
1230            var canSync = true;
1231            $items.each(function(index) {
1232                var $item = $(this);
1233                var lineItem = lineItems[index];
1234
1235                if (!lineItem || $item.data('scnstore-uniqueid') != lineItem.UniqueId) {
1236                    canSync = false;
1237                    return false;
1238                }
1239            });
1240
1241            if (!canSync) {
1242                return false;
1243            }
1244
1245            $items.each(function(index) {
1246                var $item = $(this);
1247                var lineItem = lineItems[index];
1248                var lineTotal = lineItem.SubtotalPrice;
1249
1250                if (typeof lineTotal === 'undefined') {
1251                    lineTotal = lineItem.LineTotal;
1252                }
1253
1254                if (typeof lineTotal === 'undefined') {
1255                    lineTotal = lineItem.UnitPrice * lineItem.Quantity;
1256                }
1257
1258                $item.find('.minicart-qty-value').text(lineItem.Quantity);
1259                $item.find('.minicart-qty-change').removeAttr('disabled');
1260                $item.find('.price bdi').html(
1261                    window.FormatCurrency(lang, currency, lineTotal, true)
1262                );
1263            });
1264
1265            $miniCart.find('.minicart-subtotal bdi').html(
1266                window.FormatCurrency(lang, currency, cart.Subtotal, true)
1267            );
1268
1269            return true;
1270        },
1271
1272        changeItemQuantity: function($trigger, delta) {
1273            var obj = window.ScnStore;
1274            var $item = $trigger.closest('.minicart-item');
1275            var uniqueId = $item.data('scnstore-uniqueid');
1276            var currentQty = parseInt($item.find('.minicart-qty-value').text(), 10);
1277
1278            if (!uniqueId || isNaN(currentQty)) {
1279                return;
1280            }
1281
1282            $item.find('.minicart-qty-change').attr('disabled', true);
1283            obj.ChangeQuantity(uniqueId, Math.max(currentQty + delta, 0));
1284        },
1285
1286        events: function() {
1287
1288            // Maintain this
1289            var that = this;
1290
1291            // Mouse enters the cart button banner, we add active class
1292            // to make the minicart appear
1293            $('#banner_shopping_cart').on('mouseenter', function() {
1294                if (window.ScnStore.LineItemCount() > 0) {
1295                    that.showMiniCart();
1296                }
1297            });
1298
1299            // Mouse leaves cart button banner, set timer to remove
1300            // class "active" in a half of a second
1301            $('#banner_shopping_cart').on('mouseleave', function() {
1302                that.timeoutId = setTimeout(function(){that.hideMiniCart();}, 250);
1303            });
1304
1305            // However, of the mouse then enters the minicart itself,
1306            // clear the destruction timer and leave the class intact
1307            $( ".mini_cart_outer" ).on('mouseenter', '#mini_cart', function() {
1308                if (typeof that.timeoutId !== "undefined") {
1309                    clearTimeout(that.timeoutId);
1310                }
1311            });
1312
1313            // Leaves minicart, start the timer
1314            $(".mini_cart_outer").on('mouseleave', '#mini_cart', function() {
1315                that.timeoutId = setTimeout(function(){that.hideMiniCart();}, 500);
1316            });
1317
1318
1319            // ScnStore functionality Remove Line Item from cart
1320            $('.mini_cart_outer').on('click','.minicart-item .delete',function() {
1321                that.removeItem($(this).closest('.minicart-item'));
1322            });
1323
1324            $('.mini_cart_outer').on('click', '.minicart-item .minicart-qty-decrease', function(e) {
1325                e.preventDefault();
1326                that.changeItemQuantity($(this), -1);
1327            });
1328
1329            $('.mini_cart_outer').on('click', '.minicart-item .minicart-qty-increase', function(e) {
1330                e.preventDefault();
1331                that.changeItemQuantity($(this), 1);
1332            });
1333
1334
1335            // Click on a close minicart button and comply
1336            $(".mini_cart_outer").on('click', 'button.minicart-close', function(e) {
1337                e.preventDefault();
1338                that.hideMiniCart();
1339            });
1340        }
1341    };
1342
1343
1344    /**
1345     * UpdateOrderForm
1346     *
1347     * The idea on the this is to take whatever data has been filled out on the
1348     * order form and send it to the server. This takes care of all the logic
1349     * that determines if we're doing a full recalculate or not.
1350     *
1351     * @params ---
1352     * @return bool
1353     */
1354     ScnStore.prototype.UpdateOrderForm = function() {
1355
1356
1357        this._logger('Updating order form...', 'info');
1358
1359        if ( ! this._isset(this.orderForm)) {
1360            this._logger('The order form is not set. Readme tells how to set it.', 'w
1360arn');
1361            return;
1362        }
1363
1364        // Conjur up a recepticle of the coveted input
1365        var OrderFormData = {
1366            FullRecalculate: false,
1367            ShippingAddressData: {},
1368            BillingAddressData:  {},
1369            ShippingOptionData:  {}
1370        };
1371
1372        // It's like that and like this and like that and uh...
1373        var that = this;
1374
1375        // Capture the data in all the fields.
1376        $.each(that.orderForm, function(section, fields) {
1377
1378            // Capture the shipping address data
1379            if (section == "ShippingAddressData") {
1380
1381                // Stuff the address details into the object
1382                $.each(fields, function(field, attr) {
1383                    var val = $('#' + attr.Id).val();
1384
1385                    /**
1386                     * The "other" state field is an option for the user in case
1387                     * his region is not in the dropdown. If it's there we copy
1388                     * it into the normal state field's data object so it gets
1389                     * used by the backend when we send all the data over.
1390                     */
1391                    if (field == 'StateProvince' && val == "other") {
1392                        val = $('#ShippingStatePorvince_other').val();
1393                    }
1394                    OrderFormData.ShippingAddressData[field] = val;
1395                });
1396
1397                that._logger("Captured shipping address as:", 'info');
1398                that._logger(OrderFormData.ShippingAddressData);
1399
1400                // continue to the next section...
1401            }
1402
1403            // Capture the billing address fields
1404            if (section == "BillingAddressData") {
1405
1406                // Stuff the address details into the object
1407                $.each(fields, function(field, attr) {
1408                    var val = $('#' + attr.Id).val();
1409                    if (field == 'StateProvince' && val == "other") {
1410                        val = $('#BillingStatePorvince_other').val();
1411                    }
1412                    OrderFormData.BillingAddressData[field] = val;
1413                });
1414
1415                that._logger("Captured billing address as:", 'info');
1416                that._logger(OrderFormData.BillingAddressData);
1417
1418            }
1419
1420
1421            // Capture the shipping method selected
1422            if (section == "ShippingOptions") {
1423
1424                var val = $('input[name=ShipOption]').val();
1425                OrderFormData.ShippingOptionData.ShipOption = val;
1426
1427                that._logger("Captured shipping option as:", 'info');
1428                that._logger(OrderFormData.ShippingOptionData);
1429            }
1430
1431        });
1432
1433
1434        /**
1435         * MERGE SHIPPING INTO BILLING IF BILLING IS BLANK
1436         * 
1437         * Check if we have a billing address set by looking at address1
1438         * we just copy over the shipping to the billing and hope for the best.
1439         * Maybe should look at making this more intelligent
1440         */
1441        if ( ! OrderFormData.BillingAddressData.Address1) {
1442
1443            this._logger('No bill addr set, copy from ship addr...', 'info');
1444
1445            // Copy happy shipping data to billaddr, Bob Ross style...
1446            var sad = JSON.parse(JSON.stringify(OrderFormData.ShippingAddressData));
1447            OrderFormData.BillingAddressData = sad;
1448
1449            this._logger('Merged ship addr into bill addr:', 'info');
1450            this._logger(OrderFormData.BillingAddressData);
1451
1452        }
1453
1454        /**
1455         * SET THE BILLING EMAIL ADDRESS ALWAYS BECAUSE ONLY THE SHIPPING ONE GETS INPUT
1456         * 
1457         */
1458         OrderFormData.BillingAddressData.Email = OrderFormData.ShippingAddressData.Email;
1459
1460        /**
1461         * DO A SEPERATE CALL TO SET THE SHIPPING OPTION  
1462         *
1463         * This is how it's set up, I didn't write it that way. Anyhow...
1464         * currently if we do another recalc and this is not set it will error.
1465         */
1466        if (OrderFormData.ShippingOptionData.ShipOption) {
1467            var shipOption = OrderFormData.ShippingOptionData.ShipOption;
1468            this.SelectShippingOption(shipOption);
1469        }
1470
1471
1472        /**
1473         * FULL RECALCULATE CHECK
1474         * 
1475         * Check the required fields on shipping address to determine if
1476         * we should do a full recalculate or not. The full reclac basically
1477         * says to recalculate eveything including shipping options and
1478         * all the customs, tax, etc.
1479         * 
1480         * The fields required depend on the city and postal code. If the country
1481         * doesn't require postal codes per shippers as defined in the postalcode
1482         * map then we don't need it and only use the city.
1483         * 
1484         * State/region is NOT considered for shipping rates. And theoretically,
1485         * if we can calculate shipping we can also calculate taxes/customs.  <----- IMPORTANT
1486         */
1487        var ship = OrderFormData.ShippingAddressData;
1488        var pcr  = this._shipQuoteValidationList.PostCodeRequired;
1489        var rr   = this._shipQuoteValidationList.RegionRequired;
1490
1491        // FIRST CHECK IF THE POSTAL CODE IS REQUIRED FOR "FULL REFRESH"
1492        // This means we flag the call to return totals, shipping data, etc.
1493        if ($.inArray(ship.CountryCode, pcr) !== -1) {
1494
1495            this._logger('The post code is required', 'warn');
1496            if (ship.PostalCode.length > 0) {
1497                this._logger('And it\'s set to '+ship.PostalCode+' so set flag to "true"');
1498                OrderFormData.FullRecalculate = true;
1499            }
1500
1501        // NEXT CHECK IF THE REGION IS REQUIRED
1502        } else if ($.inArray(ship.CountryCode, rr) !== -1) {
1503
1504            this._logger('The region is required', 'warn');
1505            if (ship.StateProvince.length > 0) {
1506                this._logger('And it\'s set to '+ship.StateProvince+' so set flag to "true"');
1507                OrderFormData.FullRecalculate = true;
1508            }
1509
1510        // OTHERWISE WE MINIMALLY NEED THE CITY
1511        } else {
1512            this._logger('The city is required', 'warn');
1513            if (ship.City && ship.City.length > 0 && ship.CountryCode) {
1514                this._logger('And it\'s set to '+ship.City+' so set flag to "true"');
1515                OrderFormData.FullRecalculate = true;
1516            }        
1517        }
1518
1519
1520        // Check if taxes should be recalcualted because there are some taxes on the order already
1521        if (this.lastCart.Tax || this.lastCart.Customs) {
1522            OrderFormData.FullRecalculate = true;
1523        }
1524
1525
1526        // Send it to the server if the page didn't *just* load
1527        if ( ! this.isPageLoad) {
1528            var fr = OrderFormData.FullRecalculate;
1529            this._logger('Calling bill and ship update. FullRecalc: '+fr);
1530            this.SetBillingShippingAndRecalculate(OrderFormData, fr);
1531        }
1532
1533
1534        // ORG ORDER INFO ROUTING HANDLER
1535        if (ship.City && ship.City.length > 0 && ship.CountryCode) {
1536            this.GetLocalOrgData();
1537        }
1538    };
1539
1540
1541    // Set the billing and shipping address and recalculate
1542    ScnStore.prototype.SetBillingShippingAndRecalculate = function(address, fullRecalc) {
1543        this._logger('Called SetBillingShippingAndRecalculate with: ', 'info');
1544        this._logger(address);
1545
1546        // A HACK to prevent making cart empty when use lands to the confirmation page
1547        if(window.location.pathname == "/store/review.action" && !address.ShippingAddressData.Email) { 
1548            console.log("Dont update the cart, empty data");
1549            return;
1550        }
1551
1552        var req = this._jsonRpcRequest("CartApi.SetBillingShippingAndRecalculate", [{
1553            BillingAddressData: address.BillingAddressData,
1554            ShippingAddressData: address.ShippingAddressData,
1555            FullRecalculate: fullRecalc
1556        }]);
1557
1558        return $.ajaxq('scnstoreq', $.extend(this._jsonBp, {
1559            context: this,
1560            data: {r:JSON.stringify(req)},
1561            success: function(data){
1562
1563                // If the call returns an error, handle it appropriately
1564                this._displayError(data);
1565                if (data.hasOwnProperty('result')) {
1566                    return this._updateCart(data.result);
1567                }
1568            },
1569            failure: function(errMsg) {
1570                this._logger('SetBillingShippingAndRecalculate call failed', 'error');
1571                this._throwError(errMsg);
1572            },
1573        }));
1574    };
1575
1576
1577    // Set the shipping option
1578    ScnStore.prototype.SelectShippingOption = function(shipOption) {
1579        this._logger('Called SelectShippingOption with: ', 'info');
1580        this._logger(shipOption);
1581
1582        var req = this._jsonRpcRequest("CartApi.SelectShippingOption", [{
1583            ShippingOptionUniqueId: shipOption,
1584        }]);
1585
1586        $.ajaxq('scnstoreq', $.extend(this._jsonBp, {
1587            context: this,
1588            data: {r:JSON.stringify(req)},
1589            success: function(data){
1590
1591                // If the call returns an error, handle it appropriately
1592                this._displayError(data);
1593            },
1594            failure: function(errMsg) {
1595                this._logger('SelectShippingOption call failed', 'warn');
1596                this._throwError(errMsg);
1597            },
1598        }));
1599    };
1600
1601
1602    /**
1603     * UPDATE STATE LIST
1604     *
1605     * When the state list needs to be updated, pass in the id's of the
1606     * state list and corresponding country list ID for a good time.
1607     */
1608    ScnStore.prototype.UpdateStateList = function(listId, cntyId) {
1609        this._logger('Updating the state list for #' + listId, 'info');
1610
1611        $('#'+listId).addClass('disabled').attr('disabled', 'disabled');
1612        $('#'+listId).parent().addClass('updating');
1613        $('#'+listId+' option').remove();
1614
1615        this._setStateSelect(listId, $('#'+cntyId).val());
1616    };
1617
1618
1619
1620    ScnStore.prototype.ApplyMemberDiscount = function() {
1621        this._logger('Applying member discount.');
1622
1623        var fields = this.orderForm.IasMembership;
1624        var member_info = {
1625            number:    $('#'+fields.IasNumber.Id).val(),
1626            lifetime:  $('#'+fields.IasLifetime.Id).is(':checked'),
1627            exp_date:  null
1628        };
1629
1630        // If lifetime is not checked, add expiration data
1631        if ( ! member_info.lifetime) {
1632            member_info.exp_year = $('#'+fields.IasExpYear.Id).val();
1633            member_info.exp_month = $('#'+fields.IasExpMonth.Id).val();
1634            member_info.exp_date = member_info.exp_year+"-"+member_info.exp_month;
1635        }
1636
1637        this._logger(member_info);
1638
1639        var this_year = new Date().getFullYear();
1640        var this_month = new Date().getMonth();
1641
1642
1643        // Validate the number
1644        if (member_info.number.length > 19 || member_info.number.length < 16) {
1645            $('#ias_error_number_length').removeClass('hidden');
1646            return;
1647        }
1648
1649        $('#ias_error_number_length').addClass('hidden');
1650
1651        // Validate the expiration if it's not a lifetime
1652        if ( ! member_info.lifetime) {
1653            var valid = false;
1654
1655            if (member_info.exp_year == this_year) {
1656                if (member_info.exp_month >= this_month) {
1657                    valid = true;
1658                }
1659            }
1660            else if(member_info.exp_year > this_year) {
1661                valid = true;
1662            }
1663
1664            // Validate the expiration date
1665            if ( ! valid) {
1666                $('#ias_error_expired').removeClass('hidden');
1667                return;
1668            } else {
1669                $('#ias_error_expired').addClass('hidden');
1670            }
1671        }
1672
1673        // Hide all errors once past
1674        $('.ias-error').addClass('hidden');
1675
1676        // SetPublicAttrs
1677        var attrs = {};
1678        attrs.public_ias_number = (member_info.number) ? member_info.number : null;
1679
1680        var req = this._jsonRpcRequest("CartApi.SetPublicAttrs", [{
1681            OrderAttrs: attrs,
1682            FullRecalculate: true
1683        }]);
1684
1685        $.ajaxq('scnstoreq', $.extend(this._jsonBp, {
1686            context: this,
1687            data: {r:JSON.stringify(req)},
1688            success: function(data){
1689
1690                // If the call returns an error, handle it appropriately
1691                this._displayError(data);
1692                if (data.hasOwnProperty('result')) {
1693                    $('.ias-error').addClass('hidden');
1694                    $('#ias_success').removeClass('hidden');
1695                    this._updateCart(data.result);
1696                }
1697            },
1698            failure: function(errMsg) {
1699                this._logger('SetBillingShippingAndRecalculate call failed', 'error');
1700                this._throwError(errMsg);
1701            },
1702        }));
1703
1704    };
1705
1706
1707    /**
1708     * LOCAL ORG INFO NOTIFICATION
1709     *
1710     * This is one of those recursive callback data call things.
1711     */
1712     ScnStore.prototype.GetLocalOrgData = function(orgData) {
1713        
1714        var ship_address = this.lastCart.ShippingAddressData;
1715
1716        // Minimum criteria to fire the _loadOrgData() method
1717        if ( ! ship_address.Address1 && ! ship_address.City && ! ship_address.CountryCode ) {
1718            return false;
1719        }
1720        
1721        if ($('.hide-until-ship').hasClass('data-transfer-permission')) {
1722            this._loadOrgData();
1723        }
1724     };
1725
1726
1727     /**
1728     * LOAD ORG DATA
1729     *
1730     * We use this to call the gelocator to get the local org to the public
1731     * based on the shipping address information.
1732     *
1733     */
1734    ScnStore.prototype._loadOrgData = function(orgData) {
1735
1736        this._logger('Getting nearest org based on shipping address...', 'info');
1737
1738        if ( ! this._isset(orgData)) {
1739            var url = "https://gd2.edit.firechrome.org/gcui-globaldata/get-org-for-book-order.action"; // Testing
1740            //var url = "https://gd2.ondemandhosting.info/gcui-globaldata/get-org-for-book-order.action"; // Live
1741            var sa = this.lastCart.ShippingAddressData;
1742
1743            var query = 
1744              "?street="   + sa.Address1 +
1745              "&city="     + sa.City +
1746              "&state="    + sa.StateProvince +
1747              "&postcode=" + sa.PostalCode +
1748              "&country="  + sa.CountryCode +
1749              "&callback=ScnStore._loadOrgData";
1750            
1751            $.ajaxq('scnstoreq', {
1752                context: this,
1753                url: url+query,
1754                dataType: "script",
1755                failure: function(errMsg) { this._throwError(errMsg);}
1756            });
1757        }
1758
1759        // Handle the callback response from the above request
1760        else {
1761            this._logger('Handling org data response', 'info');
1762            this._logger(orgData, 'info');
1763
1764            var od = orgData.result.items[0];
1765            
1766            // Edit the html nodes on the page
1767            $('#nearest_church p.order-info-notice').html(
1768                'Your order information will be sent to <b>'+od.full_name.trim()+'</b>, your nearest Scientology Church.');
1769
1770
1771            // Image and Org Data //
1772            // ================== //
1773
1774            if ($('#local_org_card').attr('data-org-id') !== od.id) {
1775
1776                // Add the org ID to the wrapper
1777                $('#local_org_card').attr('data-org-id', od.id);
1778
1779                $('.org-image').remove();
1780                var img_url = 'https://files.ondemandhosting.info/imagecache/cropfit@w=180@cr=0,5,499,281@qa=85/data/www.scientology.org/themes/www_scientology_org2/images/generic-org02.jpg';
1781
1782
1783                if (od.csiorgstatus === 'ideal') {
1784
1785                    // Get the image filename from the org data response
1786                    ideal_img_url = 'https://files.ondemandhosting.info/imagecache/scalecrop-400x267-auto/data/shared/web/assets/videos/master_thumbs/org-'+od.id.trim()+'_tour.jpg';
1787
1788                    // Test if image exists
1789                    var imghttp = $.ajax({
1790                        type:"HEAD",
1791                        url: ideal_img_url,
1792                        async: false
1793                    });
1794
1795                    if (imghttp.status === 200) {
1796                        img_url = ideal_img_url;
1797                    }
1798                }
1799
1800                // Make the image element
1801                var oi = document.createElement('img');
1802                oi.setAttribute('class', 'org-image img-fluid');
1803                oi.setAttribute('alt', od.full_name);
1804                oi.setAttribute('title', od.full_name);
1805                oi.setAttribute('id', 'org_img_'+od.id);
1806                oi.setAttribute('src', img_url);
1807                oi.setAttribute('style', 'display: block; border-radius: 5px; max-height: 100px');
1808
1809                var oi_wrapper = document.getElementById('org_image_wrapper');
1810                oi_wrapper.appendChild(oi);
1811
1812                // Address
1813                $('#nearest_church p.org-address .org-street-address').html(od.address1);
1814                $('#nearest_church p.org-address .org-city').html(od.city);
1815                $('#nearest_church p.org-address .org-state-province').html(od.state_province);
1816                $('#nearest_church p.org-address .org-postal-code').html(od.postal_code);
1817                $('#nearest_church p.org-address .org-phone').html(od.phone);
1818            }
1819
1820
1821            // Reveal the thing
1822            $('.hide-until-ship').removeClass('hidden');
1823
1824            // SetPublicAttrs
1825            var attrs = {
1826                public_NearestChurch: od.id,
1827                public_ConsentType: 42
1828            };
1829            
1830            var req = this._jsonRpcRequest("CartApi.SetPublicAttrs", [{
1831                OrderAttrs: attrs,
1832                FullRecalculate: false
1833            }]);
1834
1835            $.ajaxq('scnstoreq', $.extend(this._jsonBp, {
1836                context: this,
1837                data: {r:JSON.stringify(req)},
1838                success: function(data){
1839                    // If the call returns an error, handle it appropriately
1840                    this._displayError(data);
1841                },
1842                failure: function(errMsg) {
1843                    this._logger('SetPublicAttrs call failed', 'error');
1844                    this._throwError(errMsg);
1845                }
1846            }));        
1847        }
1848    };
1849
1850
1851
1852    /**
1853     * BeginExternalPayment
1854     *
1855     * We have different payment methods which need to be initiated at some point
1856     * they get started here.
1857     */
1858    ScnStore.prototype.BeginExternalPayment = function(method) {
1859        this._logger('Beginning External Payment using: ' + method, 'info');
1860
1861        // Find out what payment method is selected and handle accordingly
1862        payMethods = window.ScnStore.orderForm.PaymentMethods;
1863
1864        if ( ! payMethods.hasOwnProperty(method)) {
1865            this._logger('Payment method "'+method+'" is not defined in the settings. Aborting.', 'warn');
1866            return;
1867        }
1868
1869        var itemTitles = [];
1870        $('.cart-item .title').each( function(index, val) {
1871            itemTitles.push($(this).text().split("\n"));
1872        });
1873        var itemDescs = [];
1874        $('.cart-item .description').each( function(index, val) {
1875            itemDescs.push($(this).text().split("\n"));
1876        });
1877        var itemLangs = [];
1878        $('.cart-item .language option').each( function(index, val) {
1879            if ($(this).prop('selected') == true) {
1880                itemLangs.push($(this).text());
1881            }
1882        });
1883
1884        this._logger("ExternalPayment languages", "info");
1885        this._logger(itemLangs, "info");
1886
1887        var lineItems = [];
1888        if (this.lastCart.LineItems) {
1889            $.each(this.lastCart.LineItems, function(index, item) {
1890
1891                var itemName = itemTitles[index][1].trim(),
1892                    itemDesc = itemDescs[index][1].trim(),
1893                    itemLang = itemLangs[index].trim();
1894
1895                lineItems[index] = {
1896                    Name:     itemName,
1897                    Desc:     itemDesc+", "+itemLang,
1898                    Amt:      item.UnitPrice,
1899                    Quantity: parseFloat(item.Quantity),
1900                    ItemCode: item.ProductId,
1901                    Index:    index
1902                };
1903            });
1904        }
1905
1906
1907        // Double check and make sure its supported here.
1908        var req = null;
1909        switch (method) {
1910            case 'service_paypal' :
1911                var PaymentData = this._preparePayPalData();
1912                this._logger("Beginning External Payment with: ", 'info');
1913                this._logger(PaymentData);
1914
1915                // Build the request params for PayPal
1916                req = this._jsonRpcRequest("CartApi.BeginExternalPayment", [{
1917                    Attrs: {
1918                        'Method': method,
1919                        'ReturnURL': PaymentData.ReturnURL,
1920                        'CancelURL': PaymentData.CancelURL,
1921                        'Locale': PaymentData.Locale,
1922                        'Items': lineItems,
1923                    }
1924                }]);
1925                break;
1926            case 'service_sberbank':
1927                    var PaymentData = this._prepareSberbankData();
1928                    this._logger("Beginning External Payment with: ", 'info');
1929                    this._logger(PaymentData);
1930    
1931                    // Build the request params for PayPal
1932                    req = this._jsonRpcRequest("CartApi.BeginExternalPayment", [{
1933                        Attrs: {
1934                            'Method': method,
1935                            'ReturnURL': PaymentData.ReturnURL,
1936                            'CancelURL': PaymentData.CancelURL,
1937                            'Locale': PaymentData.Locale,
1938                            'Items': lineItems,
1939                        }
1940                    }]);
1941                    break;
1942            default :
1943                this._logger('Payment method "'+method+'" is not supported. Aborting', 'warn');
1944                return;
1945        }
1946
1947
1948        // Do the call for External Payment
1949        $.ajaxq('scnstoreq', $.extend(this._jsonBp, {
1950            context: this,
1951            data: {r:JSON.stringify(req)},
1952            success: function(data){
1953
1954                // If the call returns an error, handle it appropriately
1955                this._displayError(data);
1956
1957                // If the call has no error, behold the result
1958                if (data.hasOwnProperty('result')) {
1959                    if (data.result.hasOwnProperty('RedirectURL')) {
1960                        this._redirect(data.result.RedirectURL);
1961                    } else {
1962                        var errdata = {
1963                            error: {code: 1000, message: 'There was an error beginning your PayPal payment.'}
1964                        };
1965                        this._displayError(errdata);
1966                    }
1967                }
1968            },
1969            failure: function(errMsg) {
1970                this._logger('BeginExternalPayment call failed', 'error');
1971                this._throwError(errMsg);
1972            },
1973        }));
1974
1975    };
1976
1977
1978    /**
1979     * ExternalPaymentDetails
1980     *
1981     * So now we have the issue of 
1982     * they get started here.
1983     */
1984     ScnStore.prototype.ExternalPaymentDetails = function(ppobject, method) {
1985        this._logger('Fetching External Payment Details using: ' + ppobject.token, 'info');
1986        var req = null;
1987
1988        // Find out what payment method is selected and handle accordingly
1989        payMethods = window.ScnStore.orderForm.PaymentMethods;
1990        if ( ! payMethods.hasOwnProperty(method)) {
1991            this._logger('Payment method "'+method+'" is not defined in the settings. Aborting.', 'warn');
1992            return;
1993        }
1994
1995        // Double check and make sure its supported here.
1996        switch (method) {
1997            case 'service_paypal' :
1998                this._logger("Doing ExternalPaymentDetails call with token: " + ppobject.token, 'info');
1999
2000                // Build the request params for PayPal
2001                req = this._jsonRpcRequest("CartApi.ExternalPaymentDetails", [{
2002                    Attrs: {
2003                        'Method': method,
2004                        'PayPalToken': ppobject.token
2005                    }
2006                }]);
2007                break;
2008            case 'service_sberbank' :
2009                this._logger("Doing ExternalPaymentDetails call to sberbank", 'info');
2010
2011                // Build the request params for PayPal
2012                req = this._jsonRpcRequest("CartApi.ExternalPaymentDetails", [{
2013                    Attrs: {
2014                        'Method': method,
2015                    }
2016                }]);
2017                break;
2018            default :
2019                this._logger('Payment method "'+method+'" is not supported. Aborting', 'warn');
2020                return;
2021        }
2022
2023
2024        // Do the call for External Payment
2025        $.ajaxq('scnstoreq', $.extend(this._jsonBp, {
2026            context: this,
2027            data: {r:JSON.stringify(req)},
2028            success: function(data){
2029
2030                // If the call returns an error, handle it appropriately
2031                this._displayError(data);
2032
2033                // If the call has no error, behold the result
2034                if (data.hasOwnProperty('result')) {
2035                    if (data.result.hasOwnProperty('ShippingAddressData')) {
2036                        this._logger('ExternalPaymentDetails successful:','info');
2037                        this._logger(data.result,'info');
2038
2039                        // 
2040                        if (method == "service_paypal") {
2041                            var name = data.result.ShippingAddressData.FirstName.split(" ");
2042                            if (name.length == 4) {
2043                                data.result.ShippingAddressData.FirstName = name[0]+" "+name[1];
2044                                data.result.ShippingAddressData.LastName = name[2]+" "+name[3];
2045                            } else if (name.length == 3) {
2046                                data.result.ShippingAddressData.FirstName = name[0]+" "+name[1];
2047                                data.result.ShippingAddressData.LastName = name[2];
2048                            } else {
2049                                data.result.ShippingAddressData.FirstName = name[0];
2050                                data.result.ShippingAddressData.LastName = name[1];
2051                            }
2052
2053                            var bfname = data.result.BillingAddressData.FirstName;
2054                            var blname = data.result.BillingAddressData.LastName;
2055                            data.result.BillingAddressData = data.result.ShippingAddressData;
2056                            data.result.BillingAddressData.FirstName = bfname;
2057                            data.result.BillingAddressData.LastName = blname;
2058                        }
2059
2060                        if (this._populateOrderForm(data.result)) {
2061                            this.UpdateDataDisplayBoxes();
2062                            $('#review_overlay').remove();
2063                        }
2064                    } else {
2065                        var errdata = {
2066                            error: {code: 1000, message: 'There was an error processing your PayPal payment.'}};
2067                        this._displayError(errdata);                        
2068                    }
2069                }
2070            },
2071            failure: function(errMsg) {
2072                this._logger('ExternalPaymentDetails call failed', 'error');
2073                this._throwError(errMsg, 'error');
2074            },
2075        }));
2076    };
2077
2078
2079    /**
2080     * ResetExternalPayment
2081     *
2082     * So now we have the issue of 
2083     * they get started here.
2084     */
2085     ScnStore.prototype.ResetExternalPayment = function(method) {
2086        this._logger('Resetting external payment: ' + method, 'info');
2087
2088        // Find out what payment method is selected and handle accordingly
2089        payMethods = window.ScnStore.orderForm.PaymentMethods;
2090
2091        if ( ! payMethods.hasOwnProperty(method)) {
2092            this._logger('Payment method "'+method+'" is not defined in the settings. Aborting.', 'warn');
2093            return;
2094        }
2095
2096        var req = this._jsonRpcRequest("CartApi.ResetExternalPayment", [{
2097            Attrs: { 'Method': method }
2098        }]);
2099
2100
2101        // Do the call for External Payment
2102        $.ajaxq('scnstoreq', $.extend(this._jsonBp, {
2103            context: this,
2104            data: {r:JSON.stringify(req)},
2105            success: function(data){
2106
2107                // If the call returns an error, handle it appropriately
2108                this._displayError(data);
2109
2110                // If the call has no error, behold the result
2111                if (data.hasOwnProperty('result')) {
2112                    if (data.result.hasOwnProperty('ShippingAddressData')) {
2113                        this._redirect('/store/checkout.action');
2114                    } else {
2115                        var errdata = {
2116                            error: {code: 1000, message: 'There was an error processing your PayPal payment.'}};
2117                        this._displayError(errdata);
2118                    }
2119                }
2120            },
2121            failure: function(errMsg) {
2122                this._logger('ExternalPaymentDetails call failed', 'error');
2123                this._throwError(errMsg);
2124            },
2125        }));
2126    };
2127
2128
2129
2130    /**
2131     * CHECKOUT
2132     *
2133     * Run all the actions necessary to prepare the order for checkout
2134     * and any last-minuite validation. Throw it in here.
2135     */
2136
2137    ScnStore.prototype.Checkout = function(method) {
2138        this._logger('Doing Checkout...', 'info');
2139
2140        // Find out what payment method is selected and handle accordingly
2141        payMethods = window.ScnStore.orderForm.PaymentMethods;
2142
2143        if ( ! payMethods.hasOwnProperty(method)) {
2144            this._logger(
2145                'Payment method "'+method+'" is not defined in the settings. Aborting.', 'warn');
2146            return;
2147        }
2148
2149        var PaymentData = null;
2150
2151        // Double check and make sure its supported here.
2152        switch (method) {
2153            case 'creditcard' :
2154                PaymentData = this._captureCreditCardForm();
2155                break;
2156            case 'service_paypal' :
2157                PaymentData = this._paypalVars();
2158                break;
2159            case 'service_sberbank' :
2160                PaymentData = this._sberbankVars();
2161                break;
2162            default :
2163                this._logger('Payment method is not supported. Aborting', 'warn');
2164                return;
2165        }
2166
2167        this._logger("Captured payment data as: ", 'info');
2168        this._logger(PaymentData);
2169
2170        // Fire ze missiles.
2171        this.ExecuteOrder(PaymentData);
2172
2173    };
2174
2175
2176
2177    /**
2178     * EXECUTE ORDER
2179     *
2180     * This is the end of the line, honcho.
2181     */
2182    ScnStore.prototype.ExecuteOrder = function(payment) {
2183        
2184        if (payment == 'test') {
2185            payment = {
2186                Method:            "creditcard",
2187                AccountType:       "visa",
2188                AccountNumber:     "4111111111111111",
2189                AccountName:       "Joe Test",
2190                AccountExpiration: "2020-10",
2191                AccountVerifCode:  "123"
2192            };
2193        }
2194
2195        this._logger('Called ExecuteOrder with: ', 'info');
2196        this._logger(payment);
2197
2198        // Double check and make sure its supported here.
2199        var req = null;
2200        switch (payment.Method) {
2201            case 'creditcard' :
2202                req = this._jsonRpcRequest("CartApi.ExecuteOrder", [{
2203                    PaymentAttrs: {
2204                        Method:            payment.Method,
2205                        AccountType:       payment.AccountType,
2206                        AccountNumber:     payment.AccountNumber,
2207                        AccountName:       payment.AccountName,
2208                        AccountExpiration: payment.AccountExpiration,
2209                        AccountVerifCode:  payment.AccountVerifCode,
2210                    },
2211                    Currency:          this.defaults.currency,
2212                    Total:             this.lastCart.Total
2213                }]);
2214                break;
2215            case 'service_paypal' :
2216                req = this._jsonRpcRequest("CartApi.ExecuteOrder", [{
2217                    PaymentAttrs: {
2218                        Method:   payment.Method,
2219                        PayPalToken:    payment.Token,
2220                        PayPalPayerId:  payment.PayerID,
2221                        AccountName:    payment.AccountName,
2222                    },
2223                    Currency:          this.defaults.currency,
2224                    Total:             this.lastCart.Total
2225                }]);
2226                break;
2227            case 'service_sberbank' :
2228                req = this._jsonRpcRequest("CartApi.ExecuteOrder", [{
2229                    PaymentAttrs: {
2230                        Method:   payment.Method,
2231                        OrderId: payment.OrderId
2232                    },
2233                    Currency:          this.defaults.currency,
2234                    Total:             this.lastCart.Total
2235                }]);
2236                break;
2237            default :
2238                this._logger('Payment method is not supported. Aborting', 'warn');
2239                return;
2240        }
2241
2242        this._logger(req);
2243
2244        $.ajaxq('scnstoreq', $.extend(this._jsonBp, {
2245            context: this,
2246            data: {r:JSON.stringify(req)},
2247            success: function(data){
2248                this._logger('ExecuteOrder call succeeded', 'info');
2249                this._logger(data.result);
2250                if (data.result) {
2251                    this._redirect(this.settings.successUrl);
2252                    return;
2253                }
2254
2255                // If the call returns an error, handle it appropriately
2256                this._displayError(data);
2257                $('#checkout_btn').prop('disabled', false).removeClass('wait')
2258                    .html('Place Order »');
2259            },
2260            failure: function(errMsg) {
2261                this._logger('ExecuteOrder call failed', 'warn');
2262                this._throwError(errMsg);
2263            },
2264        }));
2265    };
2266
2267
2268
2269    ScnStore.prototype._captureCreditCardForm = function() {
2270        
2271        fields = window.ScnStore.orderForm.PaymentMethods.creditcard;
2272
2273        var PaymentData = {
2274            Method: 'creditcard',
2275            AccountType:       $('input[name='+fields.AccountType.Id+']:checked').val(),
2276            AccountNumber:     $('#'+fields.AccountNumber.Id).val().replace(/\D/g,''),
2277            AccountName:       $('#'+fields.AccountName.Id).val(),
2278            AccountExpiration: $('#'+fields.AccountExpiration.Year).val()+'-'+$('#'+fields.AccountExpiration.Month).val(),
2279            AccountVerifCode:  $('#'+fields.AccountVerifCode.Id).val()
2280        };
2281
2282        return PaymentData;
2283    };
2284
2285
2286    ScnStore.prototype._paypalVars = function() {
2287
2288        var bd = this.orderForm.BillingAddressData,
2289        PaymentData = {
2290            Method: 'service_paypal',
2291            Token: this._getQueryVariable('token'),
2292            PayerID: this._getQueryVariable('PayerID'),
2293            AccountName: bd.FirstName+" "+bd.LastName
2294        };
2295
2296        return PaymentData;
2297    };
2298
2299
2300    ScnStore.prototype._preparePayPalData = function() {
2301        
2302        params = window.ScnStore.orderForm.PaymentMethods.service_paypal;
2303
2304        var PaymentData = {
2305            Method: 'service_paypal',
2306            ReturnURL: params.ReturnURL,
2307            CancelURL: params.CancelURL,
2308            Locale: params.Locale,
2309        };
2310
2311        return PaymentData;
2312    };
2313
2314
2315    ScnStore.prototype._sberbankVars = function() {
2316
2317        var bd = this.orderForm.BillingAddressData,
2318        PaymentData = {
2319            Method: 'service_sberbank',
2320            AccountName: bd.FirstName+" "+bd.LastName,
2321            OrderId: this._getQueryVariable('orderId')
2322        };
2323
2324        return PaymentData;
2325    };
2326
2327
2328
2329    ScnStore.prototype._prepareSberbankData = function() {
2330        
2331        params = window.ScnStore.orderForm.PaymentMethods.service_sberbank;
2332
2333        var PaymentData = {
2334            Method: 'service_sberbank',
2335            ReturnURL: params.ReturnURL,
2336            CancelURL: params.CancelURL,
2337            Locale: params.Locale,
2338        };
2339
2340        return PaymentData;
2341    };
2342
2343
2344    /**
2345     * Formats a cc number to include spaces and no non-alnum chars
2346     * Try to read at own risk.
2347     */
2348    ScnStore.prototype._cardFormat = function(str) {
2349        var delimeter    = ' ';
2350        var numbers      = str.substr(0,19).replace(/[^\d]/gi,'');
2351        var full_segment = numbers.match(/(\d){4}/g);
2352
2353        if (full_segment) { 
2354            var block = '';
2355            for (i = 0; i < full_segment.length; i++) {
2356                block += (i != full_segment.length - 1 ?
2357                    full_segment[i] + ((i < 3) ? delimeter : '') :
2358                    full_segment[i]);
2359            }
2360            return (numbers.length % 4 != 0 ?
2361                block + ' ' + numbers.substr((full_segment.length * 4),numbers.length) :
2362                block);
2363        } else {
2364            return numbers;
2365        }
2366    };
2367
2368
2369    // Update the cart and all the things. Make sure you pass it a newCart
2370    // or it's going to get very angry.
2371    ScnStore.prototype._updateCart = function(newCart) {
2372
2373        if (newCart !== null) {
2374
2375            this._logger('Updating lastCart with newCart: ', 'info');
2376            this._logger(newCart);
2377
2378            // Update the lastcart contents with the new stuff
2379            this.lastCart = newCart;
2380
2381            // Tack on the currency we got from the cart update response;
2382            if (newCart.Currency) {
2383                this.settings.currency = newCart.Currency;
2384            }
2385
2386            this.UpdateUI();
2387        }
2388    };
2389
2390
2391    /**
2392     * Load the order form into the orderForm object
2393     */
2394    ScnStore.prototype._loadOrderForm = function() {
2395
2396        // Test if the order form is set on the page
2397        if ( ! $.isEmptyObject(this.settings.orderForm)) {
2398
2399            // Transfer the order form from settings to its own place
2400            this.orderForm = this.settings.orderForm;
2401
2402            // Since we have an order form and there is an address set on it
2403            // we should call in the countries list to be loaded on the
2404            // select option.
2405            if (this.orderForm.ShippingAddressData.CountryCode.Id ||
2406                this.orderForm.BillingAddressData.CountryCode.Id) {
2407
2408                this._loadCountries();
2409            }
2410        }
2411    };
2412
2413
2414    /*
2415     * Check if the coutries are actually set, if not pull them down from the
2416     * CDN and re-call this function with the countries inside. That will
2417     * then skip the ajax step and move on to stuffing the ScnStore.countryList
2418     * object with the data we got back.
2419     */
2420    ScnStore.prototype._loadCountries = function(countries) {
2421
2422        var that = this;
2423
2424        if ( ! this._isset(countries)) {
2425            this._logger('Loading countries...', 'info');
2426            var url = "https://sd.ondemandhosting.info/lookups/country_list.html";
2427            var locale = that.settings.locale.split('_');
2428            var lang = locale[0];
2429
2430            $.ajaxq('scnstoreq', {
2431                context: this,
2432                url: url+"?locale="+lang+"&callback=ScnStore._loadCountries",
2433                dataType: "script",
2434                failure: function(errMsg) { this._throwError(errMsg);}
2435            });
2436
2437        // If the countries argument is defined we're assuming the country
2438        // list is being passed in via the callback in the URL, so we throw it
2439        // on the form.
2440        } else {
2441            // for CPLOCIS keep only CIS countries
2442            if(this.IsCISWebssite()) {
2443                for (var countryCode in countries) {
2444                    if (countries.hasOwnProperty(countryCode)) {
2445                        if(!this.IsCISCountry(countryCode)) {
2446                            delete countries[countryCode];
2447                        }
2448                    }
2449                }
2450            }
2451
2452            this.countryList = countries;
2453            this._logger('Countries loaded successfully:', 'info');
2454            this._logger(countries);
2455
2456            var form = this.orderForm;
2457
2458            // Set for Shipping Country
2459            var shipCntyId = form.ShippingAddressData.CountryCode.Id;
2460
2461            if (shipCntyId) {
2462                this._logger('Setting ShippingCountryCode...');
2463                
2464                var shipStateId = form.ShippingAddressData.StateProvince.Id;
2465                this._setGeoSelectElementPair(shipCntyId, shipStateId);
2466                $('[name="ShippingCountryCode"]').change();
2467            }
2468
2469            // Set for Billing Country
2470            var billCntyId = form.BillingAddressData.CountryCode.Id;
2471            if (billCntyId) {
2472                this._logger('Setting BillingCountryCode...');
2473                $('[name="BillingCountryCode"]').change();
2474                
2475                var billStateId = form.BillingAddressData.StateProvince.Id;
2476                this._setGeoSelectElementPair(billCntyId, billStateId);
2477            }
2478        }
2479    };
2480
2481
2482
2483    /**
2484     * SET GEO SELECT ELEMENT PAIR
2485     *
2486     * I know this has a crazy name but at least it hints as what it's for.
2487     * Basically we are going to call this on multiple elements on the same
2488     * page in succession so I wanted to keep it DRY by putting this
2489     * into a function. It's for country and state dropdowns (pairs)
2490     */
2491    ScnStore.prototype._setGeoSelectElementPair = function(cntyId, stateId) {
2492        country = this._setCountrySelect(cntyId);
2493
2494        // Now trigger the state list update
2495        if (this._isset(stateId) && stateId != false) {
2496            this._setStateSelect(stateId, country);
2497        }
2498    };
2499
2500
2501
2502    /**
2503     * Since we're doing this on both billing and shipping, I put this into
2504     * a function. Basically build the country option set and append it to the
2505     * select element by ID as pased in.
2506     */
2507    ScnStore.prototype._setCountrySelect = function(cntyId) {
2508
2509        var cntySet = this._createOptionSet(this.countryList, true);
2510        var $list = $('#' + cntyId);
2511
2512        // Add the option set of countries to the select element
2513        $list.append(cntySet);
2514
2515        // Now select the country that was last set on the list
2516        var country = $list.data('last-country-code');
2517        if (country) {
2518            this._logger('Last country was '+country+', setting it.');
2519            $list.val(country.toUpperCase());
2520        } else {
2521            this._logger('No last country set, falling back to locale...');
2522            var ll = "en".split("_");
2523
2524            if (ll.length > 1) {
2525                country = ll[1].toUpperCase();
2526                this._logger('Locale found: ' + ll[1]);
2527                $list.val(ll[1].toUpperCase());
2528            } else {
2529                country = "US";
2530                this._logger('Locale not found, defaulting to "US".');
2531                $list.val("US");
2532            }
2533        }
2534
2535        // Finally, we should update the last-country-set data attr so if we
2536        // update this again on the same pageload, we won't reset user input
2537        $list.attr('data-last-country-code', country);
2538        $list.removeAttr('disabled');
2539
2540        return country;
2541    };
2542
2543
2544
2545    // This function should be called any time the country changes
2546    ScnStore.prototype._setStateSelect = function(listId, country) {
2547
2548        var that = this;
2549
2550        var rand = parseInt(Math.random()*(99999999-10000000)+10000000);
2551        var callback = '_stateList_' + rand;
2552        this._logger('Name of the callback is: '+callback);
2553
2554        // This is the callback that will then update the correct state list
2555        // I'm setting this FIRST to make sure it's there when the call comes back
2556        window[callback] = function(stateList) {
2557            that._logger('Got the states for #'+listId);
2558
2559            // Since the state list comes back with country prefixes on the state
2560            // abbreviations, we need to strip them off, with this doohickey
2561            var o = {};
2562            $.each(stateList, function(code, name) {
2563                var parts = code.split('-');
2564                var abbr = parts[1];
2565                o[abbr] = name;
2566            });
2567            stateList = o;
2568
2569            var stateSet = that._createOptionSet(stateList, true);
2570            var $list = $('#' + listId);
2571
2572            // Add the option set of countries to the select element
2573            $list.append(stateSet);
2574
2575            // Now select the state that was last set on the list if present
2576            // and also make sure it's an option on the new list.
2577            var last_state = $list.data('last-state');
2578            var last_state_in_list = false;
2579
2580            if (last_state) {
2581                that._logger('Last state was '+last_state+', checking if it\'s here.');
2582
2583                $.each(stateList, function(code, name) {
2584                    if (code == last_state.toUpperCase()) {
2585                        $list.val(last_state.toUpperCase());
2586                        last_state_in_list = true;
2587                        return;
2588                    }
2589                });
2590            }
2591
2592            // @TODO Make this somehow more elegant...
2593
2594            // Now let's add a little localization. We should set the first
2595            // item in the list as a label for the state/region and use the
2596            // local name for it (State, Territory, Region, Province, etc.)
2597            var label = null;
2598            var c = country;
2599            if (c == "US" || c == "MX" || c == "BR" || c == "DE") {
2600                label = 'State...';
2601            } else if (c == "AU") {
2602                label = 'Territory/State...';
2603            } else if (c == "ZA" || c == "IT" || c == "TW" || c == "NZ") {
2604                label = 'Province...';
2605            } else if (c == "CA") {
2606                label = 'Province/Territory...';
2607            } else if (c == "GB") {
2608                label = 'County...';
2609            } else {
2610                label = 'Region...';
2611            }
2612
2613            if (last_state || last_state_in_list) {
2614                $list.prepend('<option value="">'+label+'</option>');
2615            } else {
2616                $list.prepend('<option value="" selected>'+label+'</option>');
2617            }
2618            $list.append('<option value="other">Other...</option>');
2619
2620
2621            // Finally, we should update the last-state data attr so if we
2622            // update this again on the same pageload, we won't reset user input
2623            $list.attr('data-last-state', last_state);
2624            $list.removeClass('disabled').removeAttr("disabled");
2625            $list.parent().removeClass('updating');
2626        };
2627
2628        // Make the call to get the states list by country id and let the callback
2629        // do all the heavy lifting
2630        this._loadStateProvinces(country, callback);
2631
2632    };
2633
2634
2635
2636    /**
2637     * LOAD STATE/PROVINCES
2638     *
2639     * This is similar to _loadCountires() but for states on those countries.
2640     * Right now it's called by _setStateSelect as that function attaches a
2641     * unique callback to window that will take the state list it gets and
2642     * makes the select dropdown for that form element specifically.
2643     *
2644     */
2645    ScnStore.prototype._loadStateProvinces = function(data, callback) {
2646
2647        this._logger('Getting states for '+ data +'...', 'info');
2648        var url = "https://sd.ondemandhosting.info/lookups/state_list.html";
2649
2650        this._logger("URL: "+url+"?countryCode=" + data + "&callback=" + callback);
2651
2652        $.ajaxq('scnstoreq', {
2653            context: this,
2654            url: url+"?countryCode=" + data + "&callback=" + callback,
2655            dataType: "script",
2656            failure: function(errMsg) { this._throwError(errMsg);}
2657        });
2658    };
2659
2660
2661
2662    /**
2663     * CREATE OPTION SET
2664     * Since we're doing this on multiple dropdowns in the form we shoud just put
2665     * the iterator here in one place.
2666     *
2667     * Takes a key-value pair of the data you want to make options with.
2668     * Example: {IN:"Indiana"}
2669     */
2670    ScnStore.prototype._createOptionSet = function(map, sort) {
2671        
2672        this._logger(map);
2673
2674        // Sort the text values because they come as sorted by keys. Lame.
2675        if (sort) {
2676            map = this._sortProperties(map);
2677        }
2678
2679        // We make a documentFragment instead of using tons of jQuery.append's
2680        // as this was proven to be 3-4x faster for large lists per John Resig:
2681        // http://ejohn.org/blog/dom-documentfragments/
2682        var optSet = document.createDocumentFragment();
2683
2684        $.each(map, function(code, name) {
2685            var option = document.createElement("option");
2686            option.textContent = name;
2687            option.value = code;
2688            optSet.appendChild(option);
2689        });
2690
2691        return optSet;
2692    };
2693
2694
2695    // This is just quick and dirty. This is called by a listener on the state
2696    // field ang checks if the value is "other". If so, do some magical stuff
2697    // to create an input field to specify what is meant by "other".
2698    ScnStore.prototype._checkForOtherStateValAndHandle = function(listId) {
2699        this._logger('Calling _checkForOtherStateValAndHandle', 'info');
2700
2701        var $list = $('#'+listId);
2702        var listVal = $list.val();
2703        
2704        if (listVal == "other") {
2705
2706            // Check if there is already an "other" input field there
2707            if ( ! $('#'+listId+'_other').length) {
2708
2709                this._logger('There\'s no "other" input and it is selected. Making one...');
2710
2711                var $listParent = $list.parent();
2712
2713                // Create a new input for alt
2714                var input = document.createElement("input");
2715                input.type = "text";
2716                input.setAttribute("id", listId + "_other");
2717                input.setAttribute("style",
2718                    "display: inline-block; width: 68%; float: left; margin-left: 2.5%"
2719                );
2720                input.setAttribute("class", "form-control");
2721
2722                // Make room for the new input by shrinking the existing list
2723                $list.css({
2724                    width: '29.5%',
2725                    float: 'left'
2726                }).addClass('otherState'); 
2727
2728                // Slam it into the little container for the stae field
2729                $listParent.append(input);
2730                $('#'+listId + "_other").focus();
2731
2732                this._logger('done.');
2733            }
2734
2735        } else {
2736            this._logger('The selected state is not "other"');
2737
2738            // So now we know that the user selected something other than
2739            // "other" 
2740            if ($list.hasClass('otherState')) {
2741                this._logger('Removing "other" text input');
2742                $list.removeClass('otherState pull-left').css({width:'',float:''});
2743                $('#'+listId+'_other').remove();
2744            }
2745        }
2746    };
2747
2748
2749
2750    /**
2751     * SET SHIPPING OPTIONS
2752     *
2753     * Pretty self explanatory
2754     */
2755    ScnStore.prototype._setShippingOptions = function() {
2756
2757        if ( ! this.hasOwnProperty('orderForm')) {
2758            return;
2759        }
2760        if ( ! this.orderForm.hasOwnProperty('ShippingOptions')) {
2761            return;
2762        }
2763
2764        this._logger('Setting shipping options...', 'info');
2765        var cart = this.lastCart;
2766        var shipOptWrapperId = this.orderForm.ShippingOptions.Id;
2767
2768        // So there are no shipping options in the *last* cart
2769        if ( ! cart.ShippingOptions) {
2770            this._logger('No ShippingOptions available yet.', 'warn');
2771
2772            // Lop through any stuff in the options list and 
2773            $('#' + shipOptWrapperId + ' li').each(function(index, val) {
2774                if ($(this).attr('id') == 'ship_option_notice') {
2775                    $(this).removeClass('hidden');
2776                } else {
2777                    $(this).remove();
2778                }
2779            });
2780            return;
2781        }
2782        
2783        var that = this;
2784        var options = cart.ShippingOptions;
2785        var $optionContainer = $('#'+that.orderForm.ShippingOptions.Id);
2786
2787        this._logger('Found some shipping options, here they are:');
2788        this._logger(options);
2789
2790        // Remove the old options and de-register them from this.orderForm
2791        $('#' + shipOptWrapperId + ' li').each(function(index, val) {
2792            if ($(this).attr('id') == 'ship_option_notice') {
2793                $(this).addClass('hidden');
2794            } else {
2795                $(this).remove();
2796            }
2797        });
2798
2799        delete this.orderForm.ShippingOptions.Options;
2800        this.orderForm.ShippingOptions.Options = {};
2801
2802        for (var i = options.length - 1; i >= 0; i--) {
2803            $optionContainer.append(that._buildShippingOption(options[i]));
2804        }
2805        
2806        $('#' + shipOptWrapperId + ' input[type=radio]:first-child').attr('checked', 'checked');
2807    };
2808
2809
2810    ScnStore.prototype._buildShippingOption = function(option) {
2811        this._logger('Building option for ' + option.Name);
2812
2813        // Some randomness for unique IDs
2814        var rand = parseInt(Math.random()*(99999-10000)+10000);
2815        var elId = 'ship_option_' + rand;
2816        var currency = this.settings.currency;
2817        var locale = this.settings.locale.split('_');
2818        var lang = locale[0];
2819        var form = this.orderForm;
2820
2821        // Build a list item for this option
2822        var wrapper = document.createElement('li');
2823        wrapper.setAttribute('class', 'shipping-option col');
2824
2825            // Make the input
2826            var input = document.createElement('input');
2827            input.setAttribute('type', 'radio');
2828            input.setAttribute('name', 'ShipOption');
2829            input.setAttribute('value', option.UniqueId);
2830            input.setAttribute('id', elId);
2831            wrapper.appendChild(input);
2832
2833            // Make the label
2834            var label = document.createElement('label');
2835            label.setAttribute('for', elId);
2836            label.setAttribute('class', 'd-flex flex-row align-content-center align-items-center');
2837
2838                // Make the contents of the label
2839                var fauxRdoBtn = document.createElement('div');
2840                fauxRdoBtn.setAttribute('class', 'faux-radio-btn');
2841                label.appendChild(fauxRdoBtn);
2842
2843                // Make a container for title and description
2844                var contentWrapper = document.createElement('div');
2845                contentWrapper.setAttribute('class', 'ship-opt-content d-flex flex-column');
2846
2847                    // The title
2848                    var title = document.createElement('div');
2849                    title.setAttribute('class', 'title');
2850                    title.appendChild(document.createTextNode(option.Name));
2851                    contentWrapper.appendChild(title);
2852
2853                    // The body
2854                    var body = document.createElement('div');
2855                    body.setAttribute('class', 'body');
2856                    body.appendChild(document.createTextNode(
2857                        'Will ship within one business day'));
2858                    contentWrapper.appendChild(body);
2859                
2860                // Stick the contentWrapper inside the label
2861                label.appendChild(contentWrapper);
2862
2863            // The rate
2864            var rate = document.createElement('div');
2865            rate.setAttribute('class', 'rate ml-auto');
2866            rate.appendChild(document.createTextNode(
2867                window.FormatCurrency(lang, currency, option.Amount, true)
2868            ));
2869            label.appendChild(rate);
2870
2871        // Now stick the label on the wrapper
2872        wrapper.appendChild(label);
2873
2874        // Finally, register this shipping option with the orderForm
2875        form.ShippingOptions.Options[option.UniqueId] = {id: elId};
2876        this._logger('Done. Element ID is ' + elId);
2877
2878        return wrapper;
2879    };
2880
2881
2882    ScnStore.prototype._populateOrderForm = function(data) {
2883        var form = this.orderForm;
2884
2885        $.each(data.BillingAddressData, function(i,v) {
2886            if (form.BillingAddressData.hasOwnProperty(i)) {
2887                form.BillingAddressData[i] = v;
2888            }    
2889        });
2890
2891        $.each(data.ShippingAddressData, function(i,v) {
2892            if (form.ShippingAddressData.hasOwnProperty(i)) {
2893                form.ShippingAddressData[i] = v;
2894            }    
2895        });
2896
2897        this.orderForm = form;
2898        if (this.SetBillingShippingAndRecalculate(this.orderForm, true)) {
2899            return true;
2900        }
2901
2902        return false;
2903    };
2904
2905
2906    ScnStore.prototype.UpdateDataDisplayBoxes = function() {
2907        
2908        var form = this.orderForm;
2909        var shipAddr = form.ShippingAddressData;
2910        var billAddr = form.BillingAddressData;
2911
2912        if (typeof shipAddr !== "undefined") {
2913            if ($(".shipping-address-box")) {
2914                $(".shipping-address-box .first-name").text(shipAddr.FirstName);
2915                $(".shipping-address-box .last-name").text(shipAddr.LastName);
2916                $(".shipping-address-box .address1").text(shipAddr.Address1);
2917                if (shipAddr.Address2) { $(".shipping-address-box .address2").html("<br>"+shipAddr.Address2); }
2918                $(".shipping-address-box .city").text(shipAddr.City);
2919                $(".shipping-address-box .state-province").text(shipAddr.StateProvince);
2920                $(".shipping-address-box .zip-postal").text(shipAddr.PostalCode);
2921                $(".shipping-address-box .country").text(shipAddr.CountryCode);
2922            }
2923        }
2924
2925        if (typeof billAddr !== "undefined") {
2926            if ($(".billing-address-box")) {
2927                $(".billing-address-box .email").text(billAddr.Email);
2928                $(".billing-address-box .first-name").text(billAddr.FirstName);
2929                $(".billing-address-box .last-name").text(billAddr.LastName);
2930                $(".billing-address-box .address1").text(billAddr.Address1);
2931                if (billAddr.Address2) { $(".billing-address-box .address2").html("<br>"+billAddr.Address2); }
2932                $(".billing-address-box .city").text(billAddr.City);
2933                $(".billing-address-box .state-province").text(billAddr.StateProvince);
2934                $(".billing-address-box .zip-postal").text(billAddr.PostalCode);
2935                $(".billing-address-box .country").text(billAddr.CountryCode);
2936            }
2937        }
2938
2939        return true;
2940    };
2941
2942
2943    // PAYMENT FORM STUFF
2944    ScnStore.prototype._initPaymentForm = function() {
2945        
2946        if ( ! this.orderForm) {
2947            return false;
2948        }
2949        var form = this.orderForm;
2950
2951        // For all things on this level
2952        if (form.hasOwnProperty('PaymentMethods')) {
2953            
2954            // For all credit card related initilizations
2955            if (form.PaymentMethods.hasOwnProperty('creditcard')) {
2956                // set expiration month to PT month
2957                var mid = form.PaymentMethods.creditcard.AccountExpiration.Month;
2958                var yid = form.PaymentMethods.creditcard.AccountExpiration.Year;
2959                var date = new Date();
2960                $('#'+mid).val(date.getMonth() + 1);
2961                $('#'+yid).val(date.getFullYear());
2962            }
2963        }
2964
2965        return true;
2966    };
2967
2968
2969    ScnStore.prototype._getCardType = function(val) {
2970        // Auto-select the card type icons based on first digits
2971        // of the card number. Regex from regular-expressions.info
2972        var types = this.settings.cardTypes;
2973        var ret = false;
2974        $.each(types, function(card, expr) {
2975            var re = new RegExp(expr, 'g');
2976            if (val.match(re)) {
2977                ret = card;
2978            }
2979        });
2980
2981        return ret;
2982    };
2983
2984
2985    ScnStore.prototype._setCardType = function(type) {
2986        
2987        $('.payment-card').each(function(i,el) {
2988            if ($(el).hasClass(type)) {
2989                $(el).addClass('active');
2990                $(el).siblings().prop('checked', true);
2991            }
2992        });
2993    };
2994
2995
2996    ScnStore.prototype._unsetAllCardTypes = function() {
2997        
2998        $('.payment-card').removeClass('active');
2999        $('input[name=CardType]').prop('checked', false);
3000    };
3001
3002
3003    // Add to Cart Popover. So this is the little popover element that will appear over the normal add-to-cart
3004    // buttons when they get clicked. We want to provide some feedback for clicking the link and some 8-C on
3005    // getting them to the cart or to keep looking.
3006    ScnStore.prototype._atcPopover = function($el) {
3007
3008        if ($el.hasClass('no-popover')) {
3009            return;
3010        }
3011
3012        var rand = Math.floor(Math.random()*90000) + 10000;
3013        var rmid = 'rm_' + rand;
3014
3015        var txt = document.createElement('p');
3016        txt.setAttribute('class', 'text-success');
3017        txt.setAttribute('style', 'margin: 0; font-weight: bold;');
3018        txt.appendChild(document.createTextNode('Added to your cart'));
3019
3020        var btn1 = document.createElement('a');
3021        btn1.setAttribute('class', 'btn');
3022        btn1.setAttribute('class', 'btn-primary');
3023        btn1.setAttribute('class', 'btn-sm');
3024        btn1.setAttribute('href', '/store/checkout.action');
3025        btn1.setAttribute('style', '');
3026        btn1.appendChild(document.createTextNode('Proceed to Checkout'));
3027
3028        var btn2 = document.createElement('button');
3029        btn2.setAttribute('class', 'btn btn-sm btn-light');
3030        btn2.setAttribute('id', rmid);
3031        btn2.setAttribute('style', '');
3032        btn2.appendChild(document.createTextNode('×'));
3033
3034        var buttons = document.createElement('div');
3035        buttons.setAttribute('style', 'white-space: nowrap');
3036        buttons.appendChild(btn1);
3037        buttons.appendChild(btn2);
3038
3039        var content = document.createElement('div');
3040        content.appendChild(txt);
3041        content.appendChild(buttons);
3042
3043        var options = {
3044            placement: 'top',
3045            trigger: 'focus',
3046            html: true,
3047            template: '<div class="popover" role="tooltip"><div class="arrow"></div><div class="popover-content"></div></div>',
3048            content: content,
3049        };
3050
3051        $el.popover(options).popover('show');
3052
3053        $('body').on('click', '#' + rmid, function(event) {
3054            event.preventDefault();
3055            $el.popover('hide');
3056        });
3057
3058        setTimeout(function(){
3059           $el.popover('hide'); 
3060        },5000);
3061
3062    };
3063
3064
3065    ScnStore.prototype._atcBtnFeedback = function($el) {
3066
3067        if ($el.hasClass('no-feedback')) {
3068            return;
3069        }
3070
3071        // Cache the stuff already in the button
3072        var cache = $el.html();
3073        var original_width = $el.outerWidth();
3074        var original_height = $el.outerHeight();
3075
3076        $el.addClass('proceed-to-checkout');
3077
3078        if ($el.hasClass('btn-icon')) {
3079            $el.find('span[class^=iconer-]').removeClass().addClass('iconer-tick-circle');
3080        }
3081        else {
3082            $el.css({'width':original_width,'height':original_height});
3083            var icon = document.createElement('div');
3084            icon.setAttribute('class', 'iconer-tick-circle');
3085            $el.html(icon);
3086        }
3087
3088        // Revert after 5 seconds
3089        setTimeout(function(){
3090            $el.html(cache);
3091            $el.removeClass('proceed-to-checkout');
3092        }, 3000);
3093    };
3094
3095    // Add to Cart Inline. Here we want to provide some way to alert the user to the fact that
3096    // his item was indeed added to his shopping cart and that he may even proceed to the
3097    // cart if he so wishes.
3098    ScnStore.prototype._atcInline = function($el) {
3099
3100        // Cache the stuff already in the button
3101        var cache = $el.html();
3102        
3103        // Add this class to apply any styling needed
3104        $el.addClass('proceed-to-checkout');
3105        $el.html('Added to your cart');
3106
3107        // Revert after 5 seconds
3108        setTimeout(function(){
3109            $el.html(cache);
3110            $el.removeClass('proceed-to-checkout');
3111        }, 3000);
3112    };
3113
3114
3115    /**
3116     * UTILITIES
3117     * ==============================
3118     */
3119
3120    // JSON-RPC boilerplate
3121    ScnStore.prototype._jsonRpcRequest = function(methodName, params) {
3122        if ( ! this._isset(params)) {
3123            params = [{}];
3124        }
3125
3126        return {
3127            "jsonrpc": "2.0",
3128            "method": methodName,
3129            "id": parseInt(Math.random() * 1000000000000),
3130            "params": params
3131        };
3132    };
3133
3134    // JSON boilerplate for ajax requests
3135    ScnStore.prototype._jsonBp = {
3136        type: "POST",
3137        url: "/store-api.action",
3138        // contentType: "application/json; charset=utf-8",
3139        dataType: "json"
3140    };
3141
3142    /**
3143     * Console.log shortcut
3144     * This function utilizes a library called loglevel.js. It's a small tool
3145     * that handles console.* way better than a lot of other things.
3146     */
3147     ScnStore.prototype._logger = function(data, level) {
3148
3149        // Don't remove this comment, template thing
3150        // 
3151        return;
3152        // 
3153        
3154
3155        this.log.table = function(data) {
3156            if (typeof console.table !== "undefined") {
3157                console.table(data);
3158            }
3159            else {
3160                this.log.info(data);
3161            }
3162        };
3163
3164        var prefix = "ScnStore:";
3165        data = prefix+" "+data;
3166
3167        switch (level) {
3168            case 'info':  this.log.info(data);  break;
3169            case 'warn':  this.log.warn(data);  break;
3170            case 'trace': this.log.info(trace); break;
3171            case 'table': this.log.table(data); break;
3172            case 'error': this.log.error(data); break;
3173            default: this.log.debug(data);
3174        }
3175    };
3176
3177
3178    // Test if a variable is undefined, display error and quit
3179    ScnStore.prototype._failIfUndefined = function(v, msg) {
3180        if ( ! this._isset(v)) {
3181            throw new this._throwError(msg);
3182        }
3183    };
3184
3185
3186    // Make a custom error type
3187    ScnStore.prototype._throwError = function(msg) {
3188        var err = Error.call(this, msg);
3189        err.name = "ScnStoreError";
3190        return err;
3191    };
3192
3193
3194    ScnStore.prototype._displayError = function(data) {
3195        if (data.error) {
3196
3197            var err = data.error;
3198            var alertElement;
3199
3200            if(err === 'link_expired') {
3201                err = {
3202                    code: 1000,
3203                    data: [{
3204                        Code: 1000,
3205                        Message: err
3206                   }]
3207                }
3208            }
3209
3210            console.error('err', err);
3211
3212            // ErrCode 1000 
3213            if (err.code == 1000) {
3214                for (var i = err.data.length - 1; i >= 0; i--) {
3215
3216                    var message = err.data[i].Message;
3217                    if(message == "invalid_referrer_email") {
3218                        message = 'Invalid Consultant Email.';
3219                    } else if(message == "invalid_promo_code") {
3220                        message = 'Invalid Consultant Email.';
3221                    } else if(message == "link_expired") {
3222                        message = 'Link has expired. Please request a new link from your material consulatant.';
3223                    }
3224
3225                    this._buildErrorMessage(
3226                        err.data[i].Code,
3227                        message
3228                    );
32291
3230                    // Log the fact in the console
3231                    this._logger('ScnStore Error '+
3232                        err.data[i].Code +': '+
3233                        err.data[i].Message, 'error');
3234                }
3235            } else if(err.indexOf("JSON:") == 0)  {
3236                var resp = JSON.parse(err.substr("JSON:".length, err.length));
3237                var message = "";
3238
3239                // Sberbank error handlings
3240                if(resp.sberbank_error_code) {
3241                    message = window.sberbankResponseErrorCode && sberbankResponseErrorCode[resp.sberbank_error_code.toString()];
3242                    $('.button--sberbank-cancel').removeClass('d-none');
3243                }
3244
3245                this._buildErrorMessage(
3246                    0,
3247                    message
3248                );
3249
3250                // Log the fact in the console
3251                this._logger('ScnStore Error '+
3252                    message, 'error');
3253            } if(err && err.indexOf && err.indexOf("UI:") == 0)  {
3254                this._buildErrorMessage(
3255                    0,
3256                    err.substr("UI:".length, err.length)
3257                );
3258
3259                // Log the fact in the console
3260                this._logger('ScnStore Error '+
3261                    err.data[i].Message, 'error');
3262            } else {
3263                // This is probably a lower level error that would make no
3264                // sense to the end-user. We can log this in the console and
3265                // set a generic error message for the user.
3266
3267                // Log the fact in the console
3268                this._logger('ScnStore Error: '+
3269                    err, 'error');
3270
3271                this._buildErrorMessage(
3272                    'generic_err',
3273                    // 'An error occured. Please refresh the page or come back later.'
3274                    'error message'
3275                );
3276            }
3277        }
3278    };
3279
3280    ScnStore.prototype._buildErrorMessage = function(code, message) {
3281        var alert = document.createElement('li');
3282        alert.setAttribute('class', 'alert alert-danger alert-dismissible');
3283        alert.setAttribute('id', code);
3284        alert.setAttribute('role', 'alert');
3285        alert.appendChild(document.createTextNode(message));
3286            var btn = document.createElement('button');
3287            btn.setAttribute('type', 'button');
3288            btn.setAttribute('class', 'close');
3289            btn.setAttribute('data-dismiss', 'alert');
3290            btn.setAttribute('aria-label', 'Close');
3291                var x = document.createElement('span');
3292                x.setAttribute('aria-hidden', 'true');
3293                x.appendChild(document.createTextNode('×'));
3294                btn.appendChild(x);
3295        alert.appendChild(btn);
3296
3297        if (code == 'jsvalidate_msg') {
3298            $('#error_box #jsvalidate_msg').remove();
3299            $('#error_box').append(alert);
3300        } else if ( ! $('#error_box #'+code).length) {
3301            $('#error_box').append(alert);
3302        }
3303
3304        // Animate scroll to error box on page
3305        var smscroll = new SmoothScroll();
3306        var smanchor = document.querySelector('#error_box');
3307        smscroll.animateScroll(smanchor, false, {speed: 500, offset: 50, easing: 'easeOutQuint',updateURL:false});
3308    };
3309
3310
3311    // Check if a variable is defined in any way
3312    ScnStore.prototype._isset = function(v) {
3313        return (typeof v !== 'undefined') ? true : false;
3314    };
3315
3316    ScnStore.prototype._pageLoaded = function() {
3317        var that = this;
3318
3319        if(document.readyState === "complete") {
3320            that._pageLoadCompleted();
3321        } else {
3322            // Use the timeout to really try to make sure this is going to
3323            // fire well after all the init stuff.
3324            $(document).ready(function() {
3325                that._pageLoadCompleted();
3326            });
3327        }
3328
3329    };
3330    
3331    ScnStore.prototype._pageLoadCompleted = function() {
3332        var that = this;
3333
3334        that.isPageLoad = false;
3335
3336        if ( ! $.isEmptyObject(that.settings.orderForm)) {
3337            that.UpdateOrderForm();
3338        }
3339    };
3340
3341    ScnStore.prototype._isChrome = function() {
3342        if (navigator.userAgent.toLowerCase().indexOf('chrome') > -1) {
3343            return true;
3344        }
3345
3346        return false;
3347    };
3348
3349
3350    ScnStore.prototype._updateProductAttrs = function(newItem) {
3351
3352
3353        var uniqueId = newItem.UniqueId,
3354            itemCode = newItem.ProductId,
3355            quantity = newItem.Quantity,
3356            subtotal = newItem.SubtotalPrice,
3357            unitPrice= newItem.UnitPrice,
3358            locale   = this.settings.locale.split('_'),
3359            currency = this.settings.currency,
3360            lang     = locale[0],
3361            that     = this;
3362
3363        // body...
3364        var $item = $('.cart-item[data-scnstore-uniqueid='+uniqueId+']');
3365        $item.attr('id', 'cart_item_'+itemCode);
3366        $item.find('label[for^=language_]').attr('for', 'language_'+itemCode);
3367        $item.find('select[name^=language_]').attr('name', 'language_'+itemCode);
3368        $item.find('select[id^=language_]').attr('id', 'language_'+itemCode);
3369        $item.find('label[for^=quantity_]').attr('for', 'quantity_'+itemCode);
3370        $item.find('input[name^=quantity_]').attr('name', 'quantity_'+itemCode);
3371        $item.find('input[id^=quantity_]').attr('id', 'quantity_'+itemCode);
3372
3373
3374        // @TODO @FIXME
3375        // Add the new image URL to the product if different
3376        // $.get('/store/ajax/product-images.action', function(data) {
3377        //     $.each(data, function(key, prod) {
3378        //         if (uniqueId == key) {
3379        //             if ($item.find('.image img').attr('src') != prod.Image) {
3380
3381        //                 // We want to prepend the path with the image proxy stuff so we're not
3382        //                 // making the user download massive images.
3383        //                 var proxy = '/imagecache/scalefit-170x1000@as=1@qa=65';
3384
3385        //                 var parseUrl = function(href) {
3386        //                     var l = document.createElement("a");
3387        //                     l.href = href;
3388        //                     return l;
3389        //                 };
3390        //                 var url = parseUrl(prod.Image),
3391        //                     host = "/_alias/f.edgesuite.net",
3392        //                     path = url.pathname.replace(host, ""),
3393        //                     link = host + proxy + path;
3394        //                 that._logger('Updating image url with: ' + link);
3395        //                 $item.find('.image img').attr('src', link);
3396        //             };
3397        //         };
3398        //     });
3399        // });
3400
3401        // update prices on the line item itself. Not to be confused with the
3402        // cart totals section
3403        $item.find('.unit-price-data bdi').html(
3404            window.FormatCurrency(lang, currency, unitPrice, true));
3405        $item.find('.total-price bdi').html(
3406            window.FormatCurrency(lang, currency, subtotal, true));
3407
3408        $item.find('select').removeAttr('disabled');
3409        $item.find('input').removeAttr('disabled');
3410
3411    };
3412
3413    /**
3414     * Sort object properties (only own properties will be sorted).
3415     * param   object obj object to sort properties
3416     * param   bool isNumericSort true - sort object properties as numeric value
3417     * returns object
3418     */
3419    ScnStore.prototype._sortProperties = function(obj, isNumericSort)
3420    {
3421        isNumericSort=isNumericSort || false; // by default text sort
3422        var sortable=[];
3423        for(var key in obj) {
3424            if(obj.hasOwnProperty(key))
3425                sortable.push([key, obj[key]]);
3426            if(isNumericSort) {
3427                sortable.sort(function(a, b) {
3428                    return a[1]-b[1];
3429                });
3430            } else {
3431                sortable.sort(function(a, b) {
3432                    var x=a[1].toLowerCase(),
3433                        y=b[1].toLowerCase();
3434                    return x<y ? -1 : x>y ? 1 : 0;
3435                });
3436            }
3437        }
3438
3439        // Convert back to object
3440        obj = {};
3441        $.each(sortable, function(i, arr) {
3442            obj[arr[0]] = arr[1];
3443        });
3444
3445        sorted = obj;
3446        return sorted;
3447    };
3448
3449    ScnStore.prototype._redirect = function(url) {
3450        var ua        = navigator.userAgent.toLowerCase(),
3451            isIE      = ua.indexOf('msie') !== -1,
3452            version   = parseInt(ua.substr(4, 2), 10);
3453
3454        // Internet Explorer 8 and lower
3455        if (isIE && version < 9) {
3456            var link = document.createElement('a');
3457            link.href = url;
3458            document.body.appendChild(link);
3459            link.click();
3460        }
3461
3462        // All other browsers
3463        else { window.location.href = url; }
3464    };
3465
3466
3467    $.fn.serializeObject = function() {
3468        var o = {};
3469        var a = this.serializeArray();
3470        $.each(a, function() {
3471            if (o[this.name] !== undefined) {
3472                if (!o[this.name].push) {
3473                    o[this.name] = [o[this.name]];
3474                }
3475                o[this.name].push(this.value || '');
3476            } else {
3477                o[this.name] = this.value || '';
3478            }
3479        });
3480
3481        return o;
3482    };
3483
3484    ScnStore.prototype._click = function(selector) {
3485        var linkEl = $( selector );
3486        if ( linkEl.attr ( 'onclick' ) === undefined ) {
3487            document.location = linkEl.attr ( 'href' );
3488        } else {
3489            linkEl.click ();
3490        }
3491    };
3492
3493
3494    ScnStore.prototype._getQueryVariable = function(variable) {		
3495        var query = window.location.search.substring(1);		
3496        var vars = query.split('&');		
3497        for (var i = 0; i < vars.length; i++) {		
3498            var pair = vars[i].split('=');		
3499            if (decodeURIComponent(pair[0]) == variable) {		
3500                return decodeURIComponent(pair[1]);		
3501            }		
3502        }		
3503        console.log('Query variable %s not found', variable);		
3504        return false;		
3505    };
3506
3507    /**
3508     * SHIPPING QUOTE VALIDATION MAP
3509     *
3510     * This is a list of country codes that which require certain criteria be
3511     * met in order to supply a shipping quote. The first list are cntys that
3512     * require a postal code of some kind and the second list are ones that
3513     * require some kind of state/region. The rest require city name to be
3514     * specified Got all of this data from ups.com website
3515     */
3516    ScnStore.prototype._shipQuoteValidationList = {
3517        PostCodeRequired: [
3518            "DZ", // Algeria
3519            "AD", // Andorra
3520            "AR", // Argentina
3521            "AM", // Armenia
3522            "AU", // Australia
3523            "AT", // Austria
3524            "AZ", // Azerbaijan
3525            "BD", // Bangladesh
3526            "BY", // Belarus
3527            "BE", // Belgium
3528            "BA", // Bosnia-Herzegovina
3529            "BR", // Brazil
3530            "BG", // Bulgaria
3531            "CA", // Canada
3532            "CN", // China
3533            "CO", // Colombia
3534            "HR", // Croatia
3535            "CY", // Cyprus
3536            "CZ", // Czech Republic
3537            "DK", // Denmark
3538            "EC", // Ecuador
3539            "EE", // Estonia
3540            "FI", // Finland
3541            "FR", // France
3542            "GE", // Georgia
3543            "DE", // Germany
3544            "GR", // Greece
3545            "GL", // Greenland
3546            "HU", // Hungary
3547            "IS", // Iceland
3548            "IN", // India
3549            "ID", // Indonesia
3550            "IL", // Israel
3551            "IT", // Italy
3552            "JP", // Japan
3553            "KZ", // Kazakhstan
3554            "KG", // Kyrgyzstan
3555            "LV", // Latvia
3556            "LI", // Liechtenstein
3557            "LT", // Lithuania
3558            "LU", // Luxembourg
3559            "MK", // Macedonia
3560            "MY", // Malaysia
3561            "MH", // Marshall Islands
3562            "MX", // Mexico
3563            "FM", // Micronesia
3564            "MD", // Moldova
3565            "MN", // Mongolia
3566            "ME", // Montenegro
3567            "NL", // Netherlands
3568            "NZ", // New Zealand
3569            "NO", // Norway
3570            "PK", // Pakistan
3571            "PW", // Palau
3572            "PH", // Philippines
3573            "PL", // Poland
3574            "PT", // Portugal
3575            "RO", // Romania
3576            "RU", // Russia
3577            "SM", // San Marino
3578            "SA", // Saudi Arabia
3579            "RS", // Serbia
3580            "SG", // Singapore
3581            "SK", // Slovakia
3582            "SI", // Slovenia
3583            "ZA", // South Africa
3584            "KR", // South Korea
3585            "ES", // Spain
3586            "LK", // Sri Lanka
3587            "SE", // Sweden
3588            "CH", // Switzerland
3589            "TW", // Taiwan
3590            "TJ", // Tajikistan
3591            "TH", // Thailand
3592            "TN", // Tunisia
3593            "TR", // Turkey
3594            "TM", // Turkmenistan
3595            "UA", // Ukraine
3596            "GB", // United Kingdom
3597            "US", // United States
3598            "UY", // Uruguay
3599            "UZ", // Uzbekistan
3600            "VU", // Vanuatu
3601            "VN"  // Vietnam
3602        ],
3603        RegionRequired: [
3604            "AL", // Albania
3605            "AG", // Antigua and Barbuda
3606            "KY", // Cayman Islands
3607            "CL", // Chile
3608            "JM", // Jamaica
3609            "KW", // Kuwait
3610            "NG", // Nigeria
3611            "PA", // Panama
3612            "PE", // Peru
3613            "TT", // Trinidad and Tobago
3614            "VE", // Venezuela
3615            "ZW"  // Zimbabwe
3616        ]
3617    };
3618
3619    // show message to go to the CPLO website if you are try to buy from cis not on the CPLOCIS website
3620    ScnStore.prototype.ShowRUMessage = function (countryCode) {
3621        var country = countryCode || globalGeolocationData.estimated_location.country_code;
3622        $('[data-countrycode]').addClass('hidden');
3623        $('#russia_store_message [data-countrycode="'+country+'"]').removeClass('hidden');
3624        $('#russia_store_message').modal();
3625        // put back when we have cplo site
3626        // this.GenerateShareLink().then(function(link){
3627        //     var url = new URL($('#russia_store_message').data('url'));
3628
3629        //     var newURL = new URL(link);
3630
3631        //     url.searchParams.set("order", newURL.searchParams.get("order"));
3632            
3633        //     $('#russia_store_message a').attr('href', url.toString());
3634        // });
3635        window.ga && ga('send', 'event', "CIS Message", "Shown", countryCode);
3636    }
3637    
3638    
3639    // returns true if countrycode is CIS
3640    ScnStore.prototype.IsCISCountry = function(countrycode) {
3641        return countrycode == "RU" || countrycode == "KZ" || countrycode == "KG" || countrycode == "BY" || countrycode == "MD";
3642    }
3643    
3644    // returns true if this is CIS website
3645    ScnStore.prototype.IsCISWebssite = function() {
3646        return $('body').data('orgid') == "cplocis";
3647    }
3648
3649
3650    /**
3651     * Initialize the plugin once for each DOM object passed to 
3651jQuery
3652     * @params  object  options object
3653     * @returns void
3654     *
3655     */
3656    $.fn.ScnStore = function(options){
3657        return this.each(function(index,el){
3658            el.ScnStore = new ScnStore(el,options);
3659        });
3660    };
3661});

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.