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.