$('[name="ShippingCountryCode"], [name="BillingCountryCode"]').change(function(){ var countrycode = $(this).val(); if(ScnStore.IsCISCountry(countrycode) && !ScnStore.IsCISWebssite()) { $('[name="ShippingCountryCode"]').val(''); $('[name="BillingCountryCode"]').val(''); ScnStore.ShowRUMessage(countrycode); } }); /**/ ;(function(factory){ if (typeof define === 'function' && define.amd) { define(['jquery'], factory); } else if (typeof exports !== 'undefined') { module.exports = factory(require('jquery')); } else { factory(jQuery); } })(function($){ // Prevent console.log() from breaking IE and other old browsers if ( ! window.console ) console = { log: function(){} }; /* * The constructor function for ScnStore */ function ScnStore(element, settings) { this.defaults = { logLevel: 'error', locale: 'es_ES', prodLang: 'en', currency: 'USD', miniCart: { Container: '', AjaxUrl: '', Enabled: false }, checkoutUrl: 'checkout.action', successUrl: 'thank-you.action', orderForm: {}, // Defaults to empty cardTypes: { visa: '^4', amex: '^3[47]', mastercard: '^5[1-5]\d{2}|^2(?:2(?:2[1-9]|[3-9]\d)|[3-6]\d\d|7(?:[01]\d|20))', discover: '^6(?:011|5)', jcb: '^(?:2131|1800)', dinersclub: '^3(?:0[0-5]|[68])' } }; // We create a new property to hold our default settings after they // have been merged with user supplied settings this.settings = $.extend({},this,this.defaults,settings); // This object holds values that will change as the plugin operates this.initials = { lastCart: {}, orderForm: {}, isPageLoad: true, countryList: {}, currency: this.settings.currency }; // Attaches the properties of this.initials as properties of ScnStore $.extend(this,this.initials); // Here we'll hold a reference to the html tag of the document this.$el = $('html'); // We'll call our initiator function to get things rolling! this.init(); } /** * Called once per instance * Calls starter methods and associate the '.zippy-carousel' class * @params void * @returns void */ ScnStore.prototype.init = function(){ // Add a class to the body so we can add styles this.$el.addClass('scnstore'); // Pull in the logger library so we can have good logging that doesn't // break browsers and preserves stack traces (loglevel.js) this.log = window.log.noConflict(); this.log.setLevel(this.settings.logLevel); // Bind any events to make stuff happen on the real this.activate(); // Register the events. Let the user click stuff to make things happen this.events(); // Activate the mini cart if it's enabled if (this.settings.miniCart.Enabled) { this.MiniCart.init(); } // Tell the app that the page loaded (should be last) this._pageLoaded(); }; /** * Activates the initial store stuff when the page loads * @params void * @returns void * */ ScnStore.prototype.activate = function(){ // Update the cart contents as soon as we get to the page. this.CartContents(); // Load in the order form data if any is set this._loadOrderForm(); // Set any defaults for page load this._initPaymentForm(); }; /** * Associate event handlers to events * For all events we'll add them in here. * @params void * @returns void * */ ScnStore.prototype.events = function(){ // It's like that and like this and like that and uh... var that = this; // Find all the AddToCart buttons and attach a listener to each one $('body').on('click', '.scnstore-atc', function(){ var $el = $(this); var prodId = $el.data('store-product-id'); var qty = ($el.data('store-qty'))? $(this).data('store-qty'): 1; that._failIfUndefined(prodId, 'data-store-product-id not set on the element!'); that._logger('Add To Cart click detected', 'info'); that._logger('Data on click: '+prodId+' with qty:'+qty); // Add a loading class to the element // (Remove it when the response comes back) $el.addClass('loading'); that.AddToCart(prodId, qty, $el); }); // Remove item from the cart by uniqueId $('.scnstore-remove').on('click', function(e){ e.preventDefault(); var uniqueId = $(this).data('store-unique-id'); that._failIfUndefined(uniqueId, 'data-store-unique-id not set on the element!'); that._logger('Remove line item click detected', 'info'); that._logger('Data on click: ' + uniqueId); that.RemoveLineItem(uniqueId); }); // External Payment Trigger $('.external-payment-button').on('click', function(e){ e.preventDefault(); var paymentMethod = $(this).data('payment-method'); if(paymentMethod == "service_sberbank") { var form = $('#order_form').validate(); if(!form.checkForm()) { form.showErrors(); return; } } that._failIfUndefined(paymentMethod, 'data-payment-method not set on the element!'); $(this).addClass('disabled').addClass('wait'); that._logger('External payment click detected', 'info'); that._logger('Data on click: ' + paymentMethod); that.BeginExternalPayment(paymentMethod); }); // I WISH THE BELOW WASN'T SO HORRIBLE LOOKING. BUT IT WORKS /** * ATTACH LISTENERS FOR COUNTRY AND STATE FIELDS * * These listen for changes on the country and state fields which * will fire all the updates on the lists themselves. */ if (! $.isEmptyObject(this.orderForm)) { that = this; var form = this.orderForm; // One for billing address if (this._isset(form.BillingAddressData)) { $('#'+form.BillingAddressData.CountryCode.Id).change(function() { that.UpdateStateList( form.BillingAddressData.StateProvince.Id, form.BillingAddressData.CountryCode.Id ); }); // When the state changes we should check if "other" was selected // and handle appropriately $('#'+form.BillingAddressData.StateProvince.Id).change(function() { that._checkForOtherStateValAndHandle( that.orderForm.BillingAddressData.StateProvince.Id ); }); } // if (this._isset(form.ShippingAddressData)) { $('#'+form.ShippingAddressData.CountryCode.Id).change(function() { that.UpdateStateList( form.ShippingAddressData.StateProvince.Id, form.ShippingAddressData.CountryCode.Id ); }); // Shipping one, too $('#'+form.ShippingAddressData.StateProvince.Id).change(function() { that._checkForOtherStateValAndHandle( that.orderForm.ShippingAddressData.StateProvince.Id ); }); } /** * ATTACH LISTENERS FOR ALL FORM FIELDS * * These listen for changes on the defined fields which will update * the order form via ajax calls */ // Elements to for change/blur to call UpdateCart var onblur = { ShippingAddressData: [ 'Email', 'FirstName', 'LastName', 'Address1', 'Address2', 'City', 'PostalCode', 'Phone' ], BillingAddressData: [ 'FirstName', 'LastName', 'Address1', 'Address2', 'City', 'PostalCode', 'Phone' ], }; var onchange = { ShippingAddressData: ['StateProvince', 'CountryCode'], BillingAddressData: ['StateProvince', 'CountryCode'] }; // Use with extreme caution! Will chew up resources if you're not careful var oninput = { ShippingAddressData: ['PostalCode'], BillingAddressData: ['PostalCode'] }; // Loop through onblur elements and attach listeners to them. $.each(onblur, function(section, fields) { for (var i = fields.length - 1; i >= 0; i--) { if (that._isset(form[section][fields[i]])) { var shipElemId = form[section][fields[i]].Id; $(document).on('blur', '#'+shipElemId, function() { that.UpdateOrderForm(); }); } } }); // Loop through onchange elements in the same way $.each(onchange, function(section, fields) { for (var i = fields.length - 1; i >= 0; i--) { if (that._isset(form[section][fields[i]])) { var shipElemId = form[section][fields[i]].Id; $('#'+shipElemId).change(function() { that.UpdateOrderForm(); }); } } }); // And the same with the oninput elements $.each(oninput, function(section, fields) { for (var i = fields.length - 1; i >= 0; i--) { if (that._isset(form[section][fields[i]])) { var field = fields[i]; var shipElemId = form[section][field].Id; $('#'+shipElemId).on('input', function() { // Special handling for postcodes. We don't want to send a call on every // keystroke if we don't need to. This will only check ones that are on the // list of postcode types to check if (field == 'PostalCode') { var countryCode = $('#'+form[section].CountryCode.Id).val(); if ($.inArray(countryCode, that._shipQuoteValidationList.PostCodeRequired) !== -1) { that.UpdateOrderForm(); } } }); } } }); // Attach the credit card number handler here so it handles input // standardly. This has been piloted on other payment forms with // success. if (form.hasOwnProperty('PaymentMethods')) { if (form.PaymentMethods.hasOwnProperty('creditcard')) { var cardId = form.PaymentMethods.creditcard.AccountNumber.Id; // Attach input listener $(document).on('input', '#'+cardId, function() { // take the input by keystroke and format the digits // in groups of four with no non-digit characters. var initVal = $(this).val(); $(this).val(that._cardFormat(initVal)); // Auto-select the card type icons based on first digits // of the card number var cardType = that._getCardType($(this).val()); if (cardType) { that._setCardType(cardType); } else { that._unsetAllCardTypes(); } }); } } $(document).on('click', '.payment-card', function() { var types = that.settings.cardTypes; var $el = $(this); $.each(types, function(card, regex) { if ($el.hasClass(card)) { that._unsetAllCardTypes(); $el.addClass('active'); } }); }); // LANGUAGE UPDATE $('.cart-item .language').change(function() { var itemCode = $(this).val(); var uniqueId = $(this).closest('.cart-item') .data('scnstore-uniqueid'); that._logger('Updating language', 'info'); that._logger('Unique ID: ' + uniqueId); that._logger('New item code: ' + itemCode); $(this).attr('disabled', true); that.ChangeLanguage(uniqueId, itemCode); }); // QUANTITY UPDATE $('.cart-item .item-qty').change(function() { var qty = $(this).val(); var uniqueId = $(this).closest('.cart-item') .data('scnstore-uniqueid'); that._logger('Updating quantity', 'info'); that._logger('Unique ID: ' + uniqueId); that._logger('New qunatity: ' + qty); $(this).attr('disabled', true); that.ChangeQuantity(uniqueId, qty); }); } }; /** * AddToCart * Triggers a set of subfunctions to add a specified item to the cart with * a specified quantity * @params string productId * @returns object cart contents * */ ScnStore.prototype.AddToCart = function(productId, qty, $el, callback){ // If no qty was passed in, then set it to 1. if ( ! this._isset(qty) || qty == 0) { qty = 1; } this._logger('Called AddToCart with: ', 'table'); this._logger({ProductID:productId,Quantity:qty}); var req = this._jsonRpcRequest("CartApi.AddToCart", [{ LineItem: { ProductId: productId, Quantity: qty, }, }]); $.ajaxq('scnstoreq', $.extend(this._jsonBp, { context: this, data: {r:JSON.stringify(req)}, success: function(data){ // If the call returns an error, handle it appropriately this._displayError(data); if (data.hasOwnProperty('result')) { this._updateCart(data.result); } // Remove the loading class if it's set on the calling element // Then add the confirmation popover if warranted if (this._isset($el)) { $el.removeClass('loading'); if (this._isset('callback') && callback == 'inline') { this._atcInline($el); return; } // Otheriwse fire the feedback effect // this._atcPopover($el); this._atcBtnFeedback($el); } }, failure: function(errMsg) { this._logger('AddToCart wasn\'t successful', 'warn'); this._throwError(errMsg); $el.removeClass('loading'); }, })); }; /** * CartContents * Get an object with the full cart in it * @params none * @returns object cart contents * */ ScnStore.prototype.CartContents = function(callback){ this._logger('Called CartContents', 'info'); var req = this._jsonRpcRequest("CartApi.CartContents"); $.ajaxq('scnstoreq', $.extend(this._jsonBp, { context: this, data: {r:JSON.stringify(req)}, success: function(data){ // If the call returns an error, handle it appropriately if (data.hasOwnProperty('result')) { this._updateCart(data.result); } this._displayError(data); callback && callback(); }, failure: function(errMsg) { this._logger('CartContents wasn\'t successful', 'warn'); this._throwError(errMsg); }, })); }; /** * LineItemCount * Return the number of items in the shopping cart * @params none * @returns int * */ ScnStore.prototype.LineItemCount = function(){ if (this.hasOwnProperty('lastCart')) { if ($.isEmptyObject(this.lastCart) || this.lastCart == false || this.lastCart.LineItems == null) { return 0; } } else { return 0; } return this.lastCart.LineItems.length; }; /** * GenerateShareLink * Return the link to share on checkout page to share cart items * @params none * @returns int * */ ScnStore.prototype.GenerateShareLink = function(){ // var urlPath = "/store/checkout.action"; var url = new URL(window.location.href); url.search = ""; var orgId = $('[name="call-center-cos"]').val(); if(orgId && orgId != "") { url.searchParams.set("orgid", orgId); } var ref = $('[name="staff-member"]').val(); if(ref && ref != "") { url.searchParams.set("ref", ref); } var promoCode = $('[name="promo-code"]').val(); if(promoCode && promoCode != "") { url.searchParams.set("promocode", promoCode); } if (this.hasOwnProperty('lastCart')) { if ($.isEmptyObject(this.lastCart) || this.lastCart == false || this.lastCart.LineItems == null) { return url.toString(); } } else { return url.toString(); } for (var i = 0; i < this.lastCart.LineItems.length; i++) { var item = this.lastCart.LineItems[i]; var id = i + 1; url.searchParams.set("item" + id, item.ProductId); url.searchParams.set("qty" + id, item.Quantity); } return url.toString(); }; /** * CHANGE THE LANGUAGE OF AN ITEM * * Updating a line item langugae consists of passing in a new item code which * contains the language and the unique product ID. * * Return the number of items in the shopping cart * @params uniqueId * @params newItemCode * @returns new item * */ ScnStore.prototype.ChangeLanguage = function(uniqueId, newItemCode) { var that = this; // Do some checks if ( ! this._isset(uniqueId)) { this._logger('The item\'s unique ID is required.', 'error'); return; } if ( ! this._isset(newItemCode)) { this._logger('The new item code with proper language is required.', 'error'); return; } var newItem = { UniqueId: uniqueId, ProductId: newItemCode }; // On success, lets try to slap the new item data on there this.UpdateLineItem(newItem); }; /** * CHANGE THE QUANTITY OF AN ITEM * * Updating a line item quantity consists of passing in a new int(quantity) * and calling the ajax gods from above * * Return the number of items in the shopping cart * @params uniqueId * @params newItemCode * @returns new item * */ ScnStore.prototype.ChangeQuantity = function(uniqueId, qty) { if ( ! this._isset(uniqueId)) { this._logger('The item\'s unique ID is required.', 'error'); return; } if ( ! this._isset(qty) && /^\d+$/.test(qty)) { this._logger('The new item quantity is required and must be a number.', 'error'); return; } // If it was set to 0, remove the line item from the cart if (qty == 0) { if ($('li[data-scnstore-uniqueid='+uniqueId+']').find('.delete').length) { // Simulate a click on this trigger as it's set up with css and such, keeping it simple $('li[data-scnstore-uniqueid='+uniqueId+']').find('.delete').click(); return; } this.RemoveLineItem(uniqueId); } var newItem = { UniqueId: uniqueId, Quantity: qty }; // On success, lets try to smack the new item data on there this.UpdateLineItem(newItem); }; /** * UpdateLineItem * Update an item in the cart * * Looks like this: * {UniqueId: "b1g70ng5tr1ng", ProductId: "dmsmh.en.hc", etc.} * * You don't have to put all the params on there if you don't want to but * you certainly could if you wanted to. The key is the unique id * * @params none * @returns object cart contents * */ ScnStore.prototype.UpdateLineItem = function(newItem){ this._logger('Called UpdateLineItem...', 'info'); var that = this; var li = {}; $.each(this.lastCart.LineItems, function(item, attrs) { if (attrs.UniqueId == newItem.UniqueId) { li = that.lastCart.LineItems[item]; } }); if (li.length == 0) { this._logger('Item not found by unique id', 'error'); return; } // Roll them all together li = $.extend({}, li, newItem); var req = this._jsonRpcRequest("CartApi.UpdateLineItem", [{ LineItem: li, }]); this._logger('New item updating to:'); this._logger(li); $.ajaxq('scnstoreq', $.extend(this._jsonBp, { context: this, data: {r:JSON.stringify(req)}, success: function(data){ this._logger('UpdateLineItem successful:', 'info'); if (data.hasOwnProperty('result')) { this._updateCart(data.result); $.each(data.result.LineItems, function(i, item) { if (item.UniqueId == newItem.UniqueId) { that._updateProductAttrs(item); } }); } this._displayError(data); }, failure: function(errMsg) { this._logger('CartContents wasn\'t successful', 'warn'); this._throwError(errMsg); }, })); this.UpdateOrderForm(); return true; }; /** * RemoveLineItem * Removes a line item with the specified unique line item id * @params string line item id * @returns object cart contents * */ ScnStore.prototype.RemoveLineItem = function(uniqueId){ this._logger('Called RemoveLineItem with: ', 'info'); this._logger({uniqueId:uniqueId}); var req = this._jsonRpcRequest("CartApi.RemoveLineItem", [{ LineItem: { UniqueId: uniqueId }, }]); $.ajaxq('scnstoreq', $.extend(this._jsonBp, { context: this, data: {r:JSON.stringify(req)}, success: function(data){ // If the call returns an error, handle it appropriately this._displayError(data); if (data.hasOwnProperty('result')) { this._updateCart(data.result); } }, failure: function(errMsg) { this._logger('RemoveLineItem call failed', 'warn'); this._throwError(errMsg); }, })); if ($.isEmptyObject(this.orderForm)) { this.UpdateOrderForm(); } }; /** * SetReferrerName * Sets ReferrerName * @params referrerName * @returns --- * */ ScnStore.prototype.SetReferrerName = function(referrerName) { var attrs = { referrer_name: referrerName, }; var that = this; var req = this._jsonRpcRequest("CartApi.SetReferrerName", [attrs]); $.ajaxq('scnstoreq', $.extend(this._jsonBp, { context: ScnStore, data: {r:JSON.stringify(req)}, success: function(data){ // If the call returns an error, handle it appropriately that._displayError(data); if (data.hasOwnProperty('result')) { that._updateCart(data.result); } }, failure: function(errMsg) { that._logger('SetReferrerName call failed', 'error'); that._throwError(errMsg); }, })); }; /** * SetPromoCode * Sets ReferrerName * @params promoCode * @returns --- * */ ScnStore.prototype.SetPromoCode = function(promoCode) { var attrs = { promo_code: promoCode, }; var that = this; var req = this._jsonRpcRequest("CartApi.SetPromoCode", [attrs]); $.ajaxq('scnstoreq', $.extend(this._jsonBp, { context: ScnStore, data: {r:JSON.stringify(req)}, success: function(data){ // If the call returns an error, handle it appropriately that._displayError(data); if (data.hasOwnProperty('result')) { that._updateCart(data.result); } }, failure: function(errMsg) { that._logger('SetPromoCode call failed', 'error'); that._throwError(errMsg); }, })); }; /** * SetMembershipNumber * Sets ReferrerName * @params promoCode * @returns --- * */ ScnStore.prototype.SetMembershipNumber = function(membershipNumber) { var attrs = { membership_number: membershipNumber, }; var that = this; var req = this._jsonRpcRequest("CartApi.SetMembershipNumber", [attrs]); $.ajaxq('scnstoreq', $.extend(this._jsonBp, { context: ScnStore, data: {r:JSON.stringify(req)}, success: function(data){ // If the call returns an error, handle it appropriately that._displayError(data); if (data.hasOwnProperty('result') && data.result) { that._updateCart(data.result); $('.ias-error').addClass('hidden'); $('#ias_success').removeClass('hidden'); } else { $('#ias_success').addClass('hidden'); $('#ias_error_invalid').removeClass('hidden'); } }, failure: function(errMsg) { that._logger('SetMembershipNumber call failed', 'error'); that._throwError(errMsg); }, })); }; /** * GetCartShareLink * * @params --- * @returns promise with link to share Shipping Address, Billing Address, IAS Num & Referrer name * */ ScnStore.prototype.GetCartShareLink = function() { var that = this; var req = this._jsonRpcRequest("CartApi.EncodeShareCartLink", []); return new Promise(function (resolve, reject) { $.ajaxq('scnstoreq', $.extend(that._jsonBp, { context: ScnStore, data: {r:JSON.stringify(req)}, success: function(data){ // If the call returns an error, handle it appropriately that._displayError(data); console.log(data); resolve(data.result.ResponseInfoList[0].Data); }, failure: function(errMsg) { that._logger('GetCartShareLink call failed', 'error'); that._throwError(errMsg); }, })); }); }; /** * DecodeShareCartLink * * @params data -> string to decode * @returns updates cart and checkout information * */ ScnStore.prototype.DecodeShareCartLink = function(data) { var attrs = { data: data }; var that = this; var req = this._jsonRpcRequest("CartApi.DecodeShareCartLink", [attrs]); $.ajaxq('scnstoreq', $.extend(this._jsonBp, { context: ScnStore, data: {r:JSON.stringify(req)}, success: function(data){ // If the call returns an error, handle it appropriately that._displayError(data); console.log(data); that.UpdateCheckoutInfo(data.result); }, failure: function(errMsg) { that._logger('DecodeShareCartLink call failed', 'error'); that._throwError(errMsg); }, })); }; /** * UpdateCheckoutInfo * * @params order -> order information * @returns updates cart and checkout information * */ ScnStore.prototype.UpdateCheckoutInfo = function(order) { var form = this.orderForm; var shipAddr = form.ShippingAddressData; var billAddr = form.BillingAddressData; if (typeof shipAddr !== "undefined") { $('[name="' + shipAddr.Email.Id + '"]').val(order.ShippingAddressData.Email); $('[name="' + shipAddr.FirstName.Id + '"]').val(order.ShippingAddressData.FirstName); $('[name="' + shipAddr.LastName.Id + '"]').val(order.ShippingAddressData.LastName); $('[name="' + shipAddr.Address1.Id + '"]').val(order.ShippingAddressData.Address1); $('[name="' + shipAddr.Address2.Id + '"]').val(order.ShippingAddressData.Address2); $('[name="' + shipAddr.CountryCode.Id + '"]').val(order.ShippingAddressData.CountryCode); $('[name="' + shipAddr.Phone.Id + '"]').val(order.ShippingAddressData.Phone); $('[name="' + shipAddr.City.Id + '"]').val(order.ShippingAddressData.City); $('[name="' + shipAddr.StateProvince.Id + '"]').val(order.ShippingAddressData.StateProvince); $('[name="' + shipAddr.PostalCode.Id + '"]').val(order.ShippingAddressData.PostalCode); } if (typeof billAddr !== "undefined") { $('[name="' + billAddr.FirstName.Id + '"]').val(order.BillingAddressData.FirstName); $('[name="' + billAddr.LastName.Id + '"]').val(order.BillingAddressData.LastName); $('[name="' + billAddr.Address1.Id + '"]').val(order.BillingAddressData.Address1); $('[name="' + billAddr.Address2.Id + '"]').val(order.BillingAddressData.Address2); $('[name="' + billAddr.Phone.Id + '"]').val(order.BillingAddressData.Phone); $('[name="' + billAddr.CountryCode.Id + '"]').val(order.BillingAddressData.CountryCode); $('[name="' + billAddr.City.Id + '"]').val(order.BillingAddressData.City); $('[name="' + billAddr.StateProvince.Id + '"]').val(order.BillingAddressData.StateProvince); $('[name="' + billAddr.PostalCode.Id + '"]').val(order.BillingAddressData.PostalCode); } $('[name="promo-code"]').val(order.PromoCode); $('[name="IasNumber"]').val(order.MembershipNumber); $('[name="staff-member"]').val(order.ReferrerName); // apply discount if(order.MembershipNumber != "" && order.PromoCode != "") { $('#IasApplyBtn').click(); } }; /** * UPDATE UI * General callback function that you can tie things to if you want to * update them in the UI after the cart gets updated in any way. * @params --- * @returns --- * */ ScnStore.prototype.UpdateUI = function() { this._logger('Updating the UI...', 'info'); this._logger('The last cart is'); this._logger(this.lastCart); var $counter = $('.store-cart-items-count'); // Check if there is an store-cart-items-count and update if ($counter.length) { if (this.LineItemCount() != 0) { $counter.text(this.LineItemCount()).removeClass('invisible'); } else { $counter.text('').addClass('invisible'); } } if (this.settings.miniCart.Enabled) { // Update the minicart (it will check if its enabled or not) this.UpdateMiniCart(); } // If this is a respnse from a full recalculate, better get to updating // all the shipping options and such this._setShippingOptions(); this.UpdateCartTotals(); this.UpdateItemListing(); }; /** * UPDATE CART TOTALS * * Updates the cart items in the checkout page via ajax and replaces the * li in the DOM with the response. The template does all the PT * cart accessing. * @params --- * @return bool */ ScnStore.prototype.UpdateCartTotals = function() { this._logger('Updating Cart Totals...', 'info'); // Keep this as a pet named that var cart = this.lastCart, shipping = cart.Shipping, subtotal = cart.Subtotal, tax = cart.Tax, discount = cart.Discount, customs = cart.Customs, total = cart.Total, locale = this.settings.locale.split('_'), currency = this.settings.currency, lang = locale[0], fmtc = window.FormatCurrency, // Currency formatter function that = this; // update totals and stuff $totals = $('#totals_box'); $totals.find('.subtotal .totals-data bdi').html(fmtc(lang, currency, subtotal, false)); $totals.find('.shipping .totals-data bdi').html(fmtc(lang, currency, shipping, false)); $totals.find('.discount .totals-data bdi').html("-"+fmtc(lang, currency, discount, false)); $totals.find('.customs .totals-data bdi').html(fmtc(lang, currency, customs, false)); $totals.find('.tax .totals-data bdi').html(fmtc(lang, currency, tax, false)); $totals.find('.grand-total .totals-data bdi').html(fmtc(lang, currency, total, false)); // Conditional values. var cond = ['tax', 'discount', 'customs']; $('#totals_box .discount').attr('data-amount', discount); $('#totals_box .customs').attr('data-amount', customs); $('#totals_box .tax').attr('data-amount', tax); for (var i = cond.length - 1; i >= 0; i--) { if ($totals.find('.'+cond[i]).attr('data-amount') > 0 || cart[cond[i]] > 0) { $totals.find('.'+cond[i]).removeClass('hidden'); } else { $totals.find('.'+cond[i]).addClass('hidden'); } } }; /** * UpdateItemListing * Updated page html and adds classes to the items which are added to the cart. * Then CSS makes page look different (such as "Added to Card" button) */ ScnStore.prototype.UpdateItemListing = function() { $('[data-listing-product-id]').removeClass('listing__item--added-to-cart'); if (this.lastCart.LineItems) { $.each(this.lastCart.LineItems, function(index, item) { $('[data-listing-product-id="'+item.ProductId+'"]').addClass('listing__item--added-to-cart'); }); } }; /** * UpdateMiniCart * * Updates the minicart via ajax and replaces it in the DOM with the * response. The template does all the PT cart accessing. * @params --- * @return bool */ ScnStore.prototype.UpdateMiniCart = function() { this._logger('Updating Mini Cart...', 'info'); // Preserve this var that = this; // Update the mini_cart if it's enabled $.get( this.settings.miniCart.AjaxUrl, function( data ) { $( that.settings.miniCart.Container ).html( data ); // If this is not a pageload then show the cart for a few seconds // then hide it again. if (that.MiniCart.skipMiniCart == false) { that.MiniCart.showMiniCart(3000); } // Now that it's after the first time (pageload), allow the minicart to show that.MiniCart.skipMiniCart = false; // Bind all the minicart DOM stuff to the JS that controls it that._logger('Updated the Mini Cart', 'info'); return true; }); }; ScnStore.prototype.MiniCart = { timeoutId: 0, skipMiniCart: true, init: function() { // Bind the events to the object this.events(); }, // Minicart module manipulation hideMiniCart: function() { $('#mini_cart').removeClass('active'); setTimeout(function(){$('#mini_cart').addClass('hidden');}, 250); }, showMiniCart: function(duration) { var that = this; // Only do this if there is something in the cart $('#mini_cart').delay(200).removeClass('hidden').addClass('active'); if (typeof that.timeoutId !== "undefined") { clearTimeout(that.timeoutId); } // If there was a duration set, then set the timeout here so the // minicart will auto-hide. if (typeof duration !== "undefined") { that.timeoutId = setTimeout(function(){ that.hideMiniCart(); }, duration); } }, // Individual item manipulation removeItem: function($el) { var obj = window.ScnStore; // Trigger the removal animation $el.addClass('deleted'); // Timeout and remove the item from the DOM after the CSS removes it. setTimeout(function(){ $el.remove(); obj.RemoveLineItem($el.data('scnstore-uniqueid')); }, 300); }, events: function() { // Maintain this var that = this; // Mouse enters the cart button banner, we add active class // to make the minicart appear $('#banner_shopping_cart').on('mouseenter', function() { if (window.ScnStore.LineItemCount() > 0) { that.showMiniCart(); } }); // Mouse leaves cart button banner, set timer to remove // class "active" in a half of a second $('#banner_shopping_cart').on('mouseleave', function() { that.timeoutId = setTimeout(function(){that.hideMiniCart();}, 250); }); // However, of the mouse then enters the minicart itself, // clear the destruction timer and leave the class intact $( "#mini_cart_outer" ).on('mouseenter', '#mini_cart', function() { if (typeof that.timeoutId !== "undefined") { clearTimeout(that.timeoutId); } }); // Leaves minicart, start the timer $("#mini_cart_outer").on('mouseleave', '#mini_cart', function() { that.timeoutId = setTimeout(function(){that.hideMiniCart();}, 500); }); // ScnStore functionality Remove Line Item from cart $('#mini_cart_outer').on('click','.minicart-item .delete',function() { that.removeItem($(this).closest('.minicart-item')); }); // Click on a close minicart button and comply $("#mini_cart_outer").on('click', 'button.minicart-close', function(e) { e.preventDefault(); that.hideMiniCart(); }); } }; /** * UpdateOrderForm * * The idea on the this is to take whatever data has been filled out on the * order form and send it to the server. This takes care of all the logic * that determines if we're doing a full recalculate or not. * * @params --- * @return bool */ ScnStore.prototype.UpdateOrderForm = function() { this._logger('Updating order form...', 'info'); if ( ! this._isset(this.orderForm)) { this._logger('The order form is not set. Readme tells how to set it.', 'warn'); return; } // Conjur up a recepticle of the coveted input var OrderFormData = { FullRecalculate: false, ShippingAddressData: {}, BillingAddressData: {}, ShippingOptionData: {} }; // It's like that and like this and like that and uh... var that = this; // Capture the data in all the fields. $.each(that.orderForm, function(section, fields) { // Capture the shipping address data if (section == "ShippingAddressData") { // Stuff the address details into the object $.each(fields, function(field, attr) { var val = $('#' + attr.Id).val(); /** * The "other" state field is an option for the user in case * his region is not in the dropdown. If it's there we copy * it into the normal state field's data object so it gets * used by the backend when we send all the data over. */ if (field == 'StateProvince' && val == "other") { val = $('#ShippingStatePorvince_other').val(); } OrderFormData.ShippingAddressData[field] = val; }); that._logger("Captured shipping address as:", 'info'); that._logger(OrderFormData.ShippingAddressData); // continue to the next section... } // Capture the billing address fields if (section == "BillingAddressData") { // Stuff the address details into the object $.each(fields, function(field, attr) { var val = $('#' + attr.Id).val(); if (field == 'StateProvince' && val == "other") { val = $('#BillingStatePorvince_other').val(); } OrderFormData.BillingAddressData[field] = val; }); that._logger("Captured billing address as:", 'info'); that._logger(OrderFormData.BillingAddressData); } // Capture the shipping method selected if (section == "ShippingOptions") { var val = $('input[name=ShipOption]').val(); OrderFormData.ShippingOptionData.ShipOption = val; that._logger("Captured shipping option as:", 'info'); that._logger(OrderFormData.ShippingOptionData); } }); /** * MERGE SHIPPING INTO BILLING IF BILLING IS BLANK * * Check if we have a billing address set by looking at address1 * we just copy over the shipping to the billing and hope for the best. * Maybe should look at making this more intelligent */ if ( ! OrderFormData.BillingAddressData.Address1) { this._logger('No bill addr set, copy from ship addr...', 'info'); // Copy happy shipping data to billaddr, Bob Ross style... var sad = JSON.parse(JSON.stringify(OrderFormData.ShippingAddressData)); OrderFormData.BillingAddressData = sad; this._logger('Merged ship addr into bill addr:', 'info'); this._logger(OrderFormData.BillingAddressData); } /** * SET THE BILLING EMAIL ADDRESS ALWAYS BECAUSE ONLY THE SHIPPING ONE GETS INPUT * */ OrderFormData.BillingAddressData.Email = OrderFormData.ShippingAddressData.Email; /** * DO A SEPERATE CALL TO SET THE SHIPPING OPTION * * This is how it's set up, I didn't write it that way. Anyhow... * currently if we do another recalc and this is not set it will error. */ if (OrderFormData.ShippingOptionData.ShipOption) { var shipOption = OrderFormData.ShippingOptionData.ShipOption; this.SelectShippingOption(shipOption); } /** * FULL RECALCULATE CHECK * * Check the required fields on shipping address to determine if * we should do a full recalculate or not. The full reclac basically * says to recalculate eveything including shipping options and * all the customs, tax, etc. * * The fields required depend on the city and postal code. If the country * doesn't require postal codes per shippers as defined in the postalcode * map then we don't need it and only use the city. * * State/region is NOT considered for shipping rates. And theoretically, * if we can calculate shipping we can also calculate taxes/customs. <----- IMPORTANT */ var ship = OrderFormData.ShippingAddressData; var pcr = this._shipQuoteValidationList.PostCodeRequired; var rr = this._shipQuoteValidationList.RegionRequired; // FIRST CHECK IF THE POSTAL CODE IS REQUIRED FOR "FULL REFRESH" // This means we flag the call to return totals, shipping data, etc. if ($.inArray(ship.CountryCode, pcr) !== -1) { this._logger('The post code is required', 'warn'); if (ship.PostalCode.length > 0) { this._logger('And it\'s set to '+ship.PostalCode+' so set flag to "true"'); OrderFormData.FullRecalculate = true; } // NEXT CHECK IF THE REGION IS REQUIRED } else if ($.inArray(ship.CountryCode, rr) !== -1) { this._logger('The region is required', 'warn'); if (ship.StateProvince.length > 0) { this._logger('And it\'s set to '+ship.StateProvince+' so set flag to "true"'); OrderFormData.FullRecalculate = true; } // OTHERWISE WE MINIMALLY NEED THE CITY } else { this._logger('The city is required', 'warn'); if (ship.City && ship.City.length > 0 && ship.CountryCode) { this._logger('And it\'s set to '+ship.City+' so set flag to "true"'); OrderFormData.FullRecalculate = true; } } // Check if taxes should be recalcualted because there are some taxes on the order already if (this.lastCart.Tax || this.lastCart.Customs) { OrderFormData.FullRecalculate = true; } // Send it to the server if the page didn't *just* load if ( ! this.isPageLoad) { var fr = OrderFormData.FullRecalculate; this._logger('Calling bill and ship update. FullRecalc: '+fr); this.SetBillingShippingAndRecalculate(OrderFormData, fr); } // ORG ORDER INFO ROUTING HANDLER if (ship.City && ship.City.length > 0 && ship.CountryCode) { this.GetLocalOrgData(); } }; // Set the billing and shipping address and recalculate ScnStore.prototype.SetBillingShippingAndRecalculate = function(address, fullRecalc) { this._logger('Called SetBillingShippingAndRecalculate with: ', 'info'); this._logger(address); // A HACK to prevent making cart empty when use lands to the confirmation page if(window.location.pathname == "/store/review.action" && !address.ShippingAddressData.Email) { console.log("Dont update the cart, empty data"); return; } var req = this._jsonRpcRequest("CartApi.SetBillingShippingAndRecalculate", [{ BillingAddressData: address.BillingAddressData, ShippingAddressData: address.ShippingAddressData, FullRecalculate: fullRecalc }]); return $.ajaxq('scnstoreq', $.extend(this._jsonBp, { context: this, data: {r:JSON.stringify(req)}, success: function(data){ // If the call returns an error, handle it appropriately this._displayError(data); if (data.hasOwnProperty('result')) { return this._updateCart(data.result); } }, failure: function(errMsg) { this._logger('SetBillingShippingAndRecalculate call failed', 'error'); this._throwError(errMsg); }, })); }; // Set the shipping option ScnStore.prototype.SelectShippingOption = function(shipOption) { this._logger('Called SelectShippingOption with: ', 'info'); this._logger(shipOption); var req = this._jsonRpcRequest("CartApi.SelectShippingOption", [{ ShippingOptionUniqueId: shipOption, }]); $.ajaxq('scnstoreq', $.extend(this._jsonBp, { context: this, data: {r:JSON.stringify(req)}, success: function(data){ // If the call returns an error, handle it appropriately this._displayError(data); }, failure: function(errMsg) { this._logger('SelectShippingOption call failed', 'warn'); this._throwError(errMsg); }, })); }; /** * UPDATE STATE LIST * * When the state list needs to be updated, pass in the id's of the * state list and corresponding country list ID for a good time. */ ScnStore.prototype.UpdateStateList = function(listId, cntyId) { this._logger('Updating the state list for #' + listId, 'info'); $('#'+listId).addClass('disabled').attr('disabled', 'disabled'); $('#'+listId).parent().addClass('updating'); $('#'+listId+' option').remove(); this._setStateSelect(listId, $('#'+cntyId).val()); }; ScnStore.prototype.ApplyMemberDiscount = function() { this._logger('Applying member discount.'); var fields = this.orderForm.IasMembership; var member_info = { number: $('#'+fields.IasNumber.Id).val(), lifetime: $('#'+fields.IasLifetime.Id).is(':checked'), exp_date: null }; // If lifetime is not checked, add expiration data if ( ! member_info.lifetime) { member_info.exp_year = $('#'+fields.IasExpYear.Id).val(); member_info.exp_month = $('#'+fields.IasExpMonth.Id).val(); member_info.exp_date = member_info.exp_year+"-"+member_info.exp_month; } this._logger(member_info); var this_year = new Date().getFullYear(); var this_month = new Date().getMonth(); // Validate the number if (member_info.number.length > 19 || member_info.number.length < 16) { $('#ias_error_number_length').removeClass('hidden'); return; } $('#ias_error_number_length').addClass('hidden'); // Validate the expiration if it's not a lifetime if ( ! member_info.lifetime) { var valid = false; if (member_info.exp_year == this_year) { if (member_info.exp_month >= this_month) { valid = true; } } else if(member_info.exp_year > this_year) { valid = true; } // Validate the expiration date if ( ! valid) { $('#ias_error_expired').removeClass('hidden'); return; } else { $('#ias_error_expired').addClass('hidden'); } } // Hide all errors once past $('.ias-error').addClass('hidden'); // SetPublicAttrs var attrs = {}; attrs.public_ias_number = (member_info.number) ? member_info.number : null; var req = this._jsonRpcRequest("CartApi.SetPublicAttrs", [{ OrderAttrs: attrs, FullRecalculate: true }]); $.ajaxq('scnstoreq', $.extend(this._jsonBp, { context: this, data: {r:JSON.stringify(req)}, success: function(data){ // If the call returns an error, handle it appropriately this._displayError(data); if (data.hasOwnProperty('result')) { $('.ias-error').addClass('hidden'); $('#ias_success').removeClass('hidden'); this._updateCart(data.result); } }, failure: function(errMsg) { this._logger('SetBillingShippingAndRecalculate call failed', 'error'); this._throwError(errMsg); }, })); }; /** * LOCAL ORG INFO NOTIFICATION * * This is one of those recursive callback data call things. */ ScnStore.prototype.GetLocalOrgData = function(orgData) { var ship_address = this.lastCart.ShippingAddressData; // Minimum criteria to fire the _loadOrgData() method if ( ! ship_address.Address1 && ! ship_address.City && ! ship_address.CountryCode ) { return false; } if ($('.hide-until-ship').hasClass('data-transfer-permission')) { this._loadOrgData(); } }; /** * LOAD ORG DATA * * We use this to call the gelocator to get the local org to the public * based on the shipping address information. * */ ScnStore.prototype._loadOrgData = function(orgData) { this._logger('Getting nearest org based on shipping address...', 'info'); if ( ! this._isset(orgData)) { var url = "https://gd2.edit.firechrome.org/gcui-globaldata/get-org-for-book-order.action"; // Testing //var url = "https://gd2.ondemandhosting.info/gcui-globaldata/get-org-for-book-order.action"; // Live var sa = this.lastCart.ShippingAddressData; var query = "?street=" + sa.Address1 + "&city=" + sa.City + "&state=" + sa.StateProvince + "&postcode=" + sa.PostalCode + "&country=" + sa.CountryCode + "&callback=ScnStore._loadOrgData"; $.ajaxq('scnstoreq', { context: this, url: url+query, dataType: "script", failure: function(errMsg) { this._throwError(errMsg);} }); } // Handle the callback response from the above request else { this._logger('Handling org data response', 'info'); this._logger(orgData, 'info'); var od = orgData.result.items[0]; // Edit the html nodes on the page $('#nearest_church p.order-info-notice').html( 'Your order information will be sent to '+od.full_name.trim()+', your nearest Scientology Church.'); // Image and Org Data // // ================== // if ($('#local_org_card').attr('data-org-id') !== od.id) { // Add the org ID to the wrapper $('#local_org_card').attr('data-org-id', od.id); $('.org-image').remove(); 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'; if (od.csiorgstatus === 'ideal') { // Get the image filename from the org data response ideal_img_url = 'https://files.ondemandhosting.info/imagecache/scalecrop-400x267-auto/data/shared/web/assets/videos/master_thumbs/org-'+od.id.trim()+'_tour.jpg'; // Test if image exists var imghttp = $.ajax({ type:"HEAD", url: ideal_img_url, async: false }); if (imghttp.status === 200) { img_url = ideal_img_url; } } // Make the image element var oi = document.createElement('img'); oi.setAttribute('class', 'org-image img-fluid'); oi.setAttribute('alt', od.full_name); oi.setAttribute('title', od.full_name); oi.setAttribute('id', 'org_img_'+od.id); oi.setAttribute('src', img_url); oi.setAttribute('style', 'display: block; border-radius: 5px; max-height: 100px'); var oi_wrapper = document.getElementById('org_image_wrapper'); oi_wrapper.appendChild(oi); // Address $('#nearest_church p.org-address .org-street-address').html(od.address1); $('#nearest_church p.org-address .org-city').html(od.city); $('#nearest_church p.org-address .org-state-province').html(od.state_province); $('#nearest_church p.org-address .org-postal-code').html(od.postal_code); $('#nearest_church p.org-address .org-phone').html(od.phone); } // Reveal the thing $('.hide-until-ship').removeClass('hidden'); // SetPublicAttrs var attrs = { public_NearestChurch: od.id, public_ConsentType: 42 }; var req = this._jsonRpcRequest("CartApi.SetPublicAttrs", [{ OrderAttrs: attrs, FullRecalculate: false }]); $.ajaxq('scnstoreq', $.extend(this._jsonBp, { context: this, data: {r:JSON.stringify(req)}, success: function(data){ // If the call returns an error, handle it appropriately this._displayError(data); }, failure: function(errMsg) { this._logger('SetPublicAttrs call failed', 'error'); this._throwError(errMsg); } })); } }; /** * BeginExternalPayment * * We have different payment methods which need to be initiated at some point * they get started here. */ ScnStore.prototype.BeginExternalPayment = function(method) { this._logger('Beginning External Payment using: ' + method, 'info'); // Find out what payment method is selected and handle accordingly payMethods = window.ScnStore.orderForm.PaymentMethods; if ( ! payMethods.hasOwnProperty(method)) { this._logger('Payment method "'+method+'" is not defined in the settings. Aborting.', 'warn'); return; } var itemTitles = []; $('.cart-item .title').each( function(index, val) { itemTitles.push($(this).text().split("\n")); }); var itemDescs = []; $('.cart-item .description').each( function(index, val) { itemDescs.push($(this).text().split("\n")); }); var itemLangs = []; $('.cart-item .language option').each( function(index, val) { if ($(this).prop('selected') == true) { itemLangs.push($(this).text()); } }); this._logger("ExternalPayment languages", "info"); this._logger(itemLangs, "info"); var lineItems = []; if (this.lastCart.LineItems) { $.each(this.lastCart.LineItems, function(index, item) { var itemName = itemTitles[index][1].trim(), itemDesc = itemDescs[index][1].trim(), itemLang = itemLangs[index].trim(); lineItems[index] = { Name: itemName, Desc: itemDesc+", "+itemLang, Amt: item.UnitPrice, Quantity: parseFloat(item.Quantity), ItemCode: item.ProductId, Index: index }; }); } // Double check and make sure its supported here. var req = null; switch (method) { case 'service_paypal' : var PaymentData = this._preparePayPalData(); this._logger("Beginning External Payment with: ", 'info'); this._logger(PaymentData); // Build the request params for PayPal req = this._jsonRpcRequest("CartApi.BeginExternalPayment", [{ Attrs: { 'Method': method, 'ReturnURL': PaymentData.ReturnURL, 'CancelURL': PaymentData.CancelURL, 'Locale': PaymentData.Locale, 'Items': lineItems, } }]); break; case 'service_sberbank': var PaymentData = this._prepareSberbankData(); this._logger("Beginning External Payment with: ", 'info'); this._logger(PaymentData); // Build the request params for PayPal req = this._jsonRpcRequest("CartApi.BeginExternalPayment", [{ Attrs: { 'Method': method, 'ReturnURL': PaymentData.ReturnURL, 'CancelURL': PaymentData.CancelURL, 'Locale': PaymentData.Locale, 'Items': lineItems, } }]); break; default : this._logger('Payment method "'+method+'" is not supported. Aborting', 'warn'); return; } // Do the call for External Payment $.ajaxq('scnstoreq', $.extend(this._jsonBp, { context: this, data: {r:JSON.stringify(req)}, success: function(data){ // If the call returns an error, handle it appropriately this._displayError(data); // If the call has no error, behold the result if (data.hasOwnProperty('result')) { if (data.result.hasOwnProperty('RedirectURL')) { this._redirect(data.result.RedirectURL); } else { var errdata = { error: {code: 1000, message: 'There was an error beginning your PayPal payment.'} }; this._displayError(errdata); } } }, failure: function(errMsg) { this._logger('BeginExternalPayment call failed', 'error'); this._throwError(errMsg); }, })); }; /** * ExternalPaymentDetails * * So now we have the issue of * they get started here. */ ScnStore.prototype.ExternalPaymentDetails = function(ppobject, method) { this._logger('Fetching External Payment Details using: ' + ppobject.token, 'info'); var req = null; // Find out what payment method is selected and handle accordingly payMethods = window.ScnStore.orderForm.PaymentMethods; if ( ! payMethods.hasOwnProperty(method)) { this._logger('Payment method "'+method+'" is not defined in the settings. Aborting.', 'warn'); return; } // Double check and make sure its supported here. switch (method) { case 'service_paypal' : this._logger("Doing ExternalPaymentDetails call with token: " + ppobject.token, 'info'); // Build the request params for PayPal req = this._jsonRpcRequest("CartApi.ExternalPaymentDetails", [{ Attrs: { 'Method': method, 'PayPalToken': ppobject.token } }]); break; case 'service_sberbank' : this._logger("Doing ExternalPaymentDetails call to sberbank", 'info'); // Build the request params for PayPal req = this._jsonRpcRequest("CartApi.ExternalPaymentDetails", [{ Attrs: { 'Method': method, } }]); break; default : this._logger('Payment method "'+method+'" is not supported. Aborting', 'warn'); return; } // Do the call for External Payment $.ajaxq('scnstoreq', $.extend(this._jsonBp, { context: this, data: {r:JSON.stringify(req)}, success: function(data){ // If the call returns an error, handle it appropriately this._displayError(data); // If the call has no error, behold the result if (data.hasOwnProperty('result')) { if (data.result.hasOwnProperty('ShippingAddressData')) { this._logger('ExternalPaymentDetails successful:','info'); this._logger(data.result,'info'); // if (method == "service_paypal") { var name = data.result.ShippingAddressData.FirstName.split(" "); if (name.length == 4) { data.result.ShippingAddressData.FirstName = name[0]+" "+name[1]; data.result.ShippingAddressData.LastName = name[2]+" "+name[3]; } else if (name.length == 3) { data.result.ShippingAddressData.FirstName = name[0]+" "+name[1]; data.result.ShippingAddressData.LastName = name[2]; } else { data.result.ShippingAddressData.FirstName = name[0]; data.result.ShippingAddressData.LastName = name[1]; } var bfname = data.result.BillingAddressData.FirstName; var blname = data.result.BillingAddressData.LastName; data.result.BillingAddressData = data.result.ShippingAddressData; data.result.BillingAddressData.FirstName = bfname; data.result.BillingAddressData.LastName = blname; } if (this._populateOrderForm(data.result)) { this.UpdateDataDisplayBoxes(); $('#review_overlay').remove(); } } else { var errdata = { error: {code: 1000, message: 'There was an error processing your PayPal payment.'}}; this._displayError(errdata); } } }, failure: function(errMsg) { this._logger('ExternalPaymentDetails call failed', 'error'); this._throwError(errMsg, 'error'); }, })); }; /** * ResetExternalPayment * * So now we have the issue of * they get started here. */ ScnStore.prototype.ResetExternalPayment = function(method) { this._logger('Resetting external payment: ' + method, 'info'); // Find out what payment method is selected and handle accordingly payMethods = window.ScnStore.orderForm.PaymentMethods; if ( ! payMethods.hasOwnProperty(method)) { this._logger('Payment method "'+method+'" is not defined in the settings. Aborting.', 'warn'); return; } var req = this._jsonRpcRequest("CartApi.ResetExternalPayment", [{ Attrs: { 'Method': method } }]); // Do the call for External Payment $.ajaxq('scnstoreq', $.extend(this._jsonBp, { context: this, data: {r:JSON.stringify(req)}, success: function(data){ // If the call returns an error, handle it appropriately this._displayError(data); // If the call has no error, behold the result if (data.hasOwnProperty('result')) { if (data.result.hasOwnProperty('ShippingAddressData')) { this._redirect('/store/checkout.action'); } else { var errdata = { error: {code: 1000, message: 'There was an error processing your PayPal payment.'}}; this._displayError(errdata); } } }, failure: function(errMsg) { this._logger('ExternalPaymentDetails call failed', 'error'); this._throwError(errMsg); }, })); }; /** * CHECKOUT * * Run all the actions necessary to prepare the order for checkout * and any last-minuite validation. Throw it in here. */ ScnStore.prototype.Checkout = function(method) { this._logger('Doing Checkout...', 'info'); // Find out what payment method is selected and handle accordingly payMethods = window.ScnStore.orderForm.PaymentMethods; if ( ! payMethods.hasOwnProperty(method)) { this._logger( 'Payment method "'+method+'" is not defined in the settings. Aborting.', 'warn'); return; } var PaymentData = null; // Double check and make sure its supported here. switch (method) { case 'creditcard' : PaymentData = this._captureCreditCardForm(); break; case 'service_paypal' : PaymentData = this._paypalVars(); break; case 'service_sberbank' : PaymentData = this._sberbankVars(); break; default : this._logger('Payment method is not supported. Aborting', 'warn'); return; } this._logger("Captured payment data as: ", 'info'); this._logger(PaymentData); // Fire ze missiles. this.ExecuteOrder(PaymentData); }; /** * EXECUTE ORDER * * This is the end of the line, honcho. */ ScnStore.prototype.ExecuteOrder = function(payment) { if (payment == 'test') { payment = { Method: "creditcard", AccountType: "visa", AccountNumber: "4111111111111111", AccountName: "Joe Test", AccountExpiration: "2020-10", AccountVerifCode: "123" }; } this._logger('Called ExecuteOrder with: ', 'info'); this._logger(payment); // Double check and make sure its supported here. var req = null; switch (payment.Method) { case 'creditcard' : req = this._jsonRpcRequest("CartApi.ExecuteOrder", [{ PaymentAttrs: { Method: payment.Method, AccountType: payment.AccountType, AccountNumber: payment.AccountNumber, AccountName: payment.AccountName, AccountExpiration: payment.AccountExpiration, AccountVerifCode: payment.AccountVerifCode, }, Currency: this.defaults.currency, Total: this.lastCart.Total }]); break; case 'service_paypal' : req = this._jsonRpcRequest("CartApi.ExecuteOrder", [{ PaymentAttrs: { Method: payment.Method, PayPalToken: payment.Token, PayPalPayerId: payment.PayerID, AccountName: payment.AccountName, }, Currency: this.defaults.currency, Total: this.lastCart.Total }]); break; case 'service_sberbank' : req = this._jsonRpcRequest("CartApi.ExecuteOrder", [{ PaymentAttrs: { Method: payment.Method, OrderId: payment.OrderId }, Currency: this.defaults.currency, Total: this.lastCart.Total }]); break; default : this._logger('Payment method is not supported. Aborting', 'warn'); return; } this._logger(req); $.ajaxq('scnstoreq', $.extend(this._jsonBp, { context: this, data: {r:JSON.stringify(req)}, success: function(data){ this._logger('ExecuteOrder call succeeded', 'info'); this._logger(data.result); if (data.result) { this._redirect(this.settings.successUrl); return; } // If the call returns an error, handle it appropriately this._displayError(data); $('#checkout_btn').prop('disabled', false).removeClass('wait') .html('Colocar Pedido »'); }, failure: function(errMsg) { this._logger('ExecuteOrder call failed', 'warn'); this._throwError(errMsg); }, })); }; ScnStore.prototype._captureCreditCardForm = function() { fields = window.ScnStore.orderForm.PaymentMethods.creditcard; var PaymentData = { Method: 'creditcard', AccountType: $('input[name='+fields.AccountType.Id+']:checked').val(), AccountNumber: $('#'+fields.AccountNumber.Id).val().replace(/\D/g,''), AccountName: $('#'+fields.AccountName.Id).val(), AccountExpiration: $('#'+fields.AccountExpiration.Year).val()+'-'+$('#'+fields.AccountExpiration.Month).val(), AccountVerifCode: $('#'+fields.AccountVerifCode.Id).val() }; return PaymentData; }; ScnStore.prototype._paypalVars = function() { var bd = this.orderForm.BillingAddressData, PaymentData = { Method: 'service_paypal', Token: this._getQueryVariable('token'), PayerID: this._getQueryVariable('PayerID'), AccountName: bd.FirstName+" "+bd.LastName }; return PaymentData; }; ScnStore.prototype._preparePayPalData = function() { params = window.ScnStore.orderForm.PaymentMethods.service_paypal; var PaymentData = { Method: 'service_paypal', ReturnURL: params.ReturnURL, CancelURL: params.CancelURL, Locale: params.Locale, }; return PaymentData; }; ScnStore.prototype._sberbankVars = function() { var bd = this.orderForm.BillingAddressData, PaymentData = { Method: 'service_sberbank', AccountName: bd.FirstName+" "+bd.LastName, OrderId: this._getQueryVariable('orderId') }; return PaymentData; }; ScnStore.prototype._prepareSberbankData = function() { params = window.ScnStore.orderForm.PaymentMethods.service_sberbank; var PaymentData = { Method: 'service_sberbank', ReturnURL: params.ReturnURL, CancelURL: params.CancelURL, Locale: params.Locale, }; return PaymentData; }; /** * Formats a cc number to include spaces and no non-alnum chars * Try to read at own risk. */ ScnStore.prototype._cardFormat = function(str) { var delimeter = ' '; var numbers = str.substr(0,19).replace(/[^\d]/gi,''); var full_segment = numbers.match(/(\d){4}/g); if (full_segment) { var block = ''; for (i = 0; i < full_segment.length; i++) { block += (i != full_segment.length - 1 ? full_segment[i] + ((i < 3) ? delimeter : '') : full_segment[i]); } return (numbers.length % 4 != 0 ? block + ' ' + numbers.substr((full_segment.length * 4),numbers.length) : block); } else { return numbers; } }; // Update the cart and all the things. Make sure you pass it a newCart // or it's going to get very angry. ScnStore.prototype._updateCart = function(newCart) { if (newCart !== null) { this._logger('Updating lastCart with newCart: ', 'info'); this._logger(newCart); // Update the lastcart contents with the new stuff this.lastCart = newCart; // Tack on the currency we got from the cart update response; if (newCart.Currency) { this.settings.currency = newCart.Currency; } this.UpdateUI(); } }; /** * Load the order form into the orderForm object */ ScnStore.prototype._loadOrderForm = function() { // Test if the order form is set on the page if ( ! $.isEmptyObject(this.settings.orderForm)) { // Transfer the order form from settings to its own place this.orderForm = this.settings.orderForm; // Since we have an order form and there is an address set on it // we should call in the countries list to be loaded on the // select option. if (this.orderForm.ShippingAddressData.CountryCode.Id || this.orderForm.BillingAddressData.CountryCode.Id) { this._loadCountries(); } } }; /* * Check if the coutries are actually set, if not pull them down from the * CDN and re-call this function with the countries inside. That will * then skip the ajax step and move on to stuffing the ScnStore.countryList * object with the data we got back. */ ScnStore.prototype._loadCountries = function(countries) { var that = this; if ( ! this._isset(countries)) { this._logger('Loading countries...', 'info'); var url = "https://sd.ondemandhosting.info/lookups/country_list.html"; var locale = that.settings.locale.split('_'); var lang = locale[0]; $.ajaxq('scnstoreq', { context: this, url: url+"?locale="+lang+"&callback=ScnStore._loadCountries", dataType: "script", failure: function(errMsg) { this._throwError(errMsg);} }); // If the countries argument is defined we're assuming the country // list is being passed in via the callback in the URL, so we throw it // on the form. } else { // for CPLOCIS keep only CIS countries if(this.IsCISWebssite()) { for (var countryCode in countries) { if (countries.hasOwnProperty(countryCode)) { if(!this.IsCISCountry(countryCode)) { delete countries[countryCode]; } } } } this.countryList = countries; this._logger('Countries loaded successfully:', 'info'); this._logger(countries); var form = this.orderForm; // Set for Shipping Country var shipCntyId = form.ShippingAddressData.CountryCode.Id; if (shipCntyId) { this._logger('Setting ShippingCountryCode...'); var shipStateId = form.ShippingAddressData.StateProvince.Id; this._setGeoSelectElementPair(shipCntyId, shipStateId); $('[name="ShippingCountryCode"]').change(); } // Set for Billing Country var billCntyId = form.BillingAddressData.CountryCode.Id; if (billCntyId) { this._logger('Setting BillingCountryCode...'); $('[name="BillingCountryCode"]').change(); var billStateId = form.BillingAddressData.StateProvince.Id; this._setGeoSelectElementPair(billCntyId, billStateId); } } }; /** * SET GEO SELECT ELEMENT PAIR * * I know this has a crazy name but at least it hints as what it's for. * Basically we are going to call this on multiple elements on the same * page in succession so I wanted to keep it DRY by putting this * into a function. It's for country and state dropdowns (pairs) */ ScnStore.prototype._setGeoSelectElementPair = function(cntyId, stateId) { country = this._setCountrySelect(cntyId); // Now trigger the state list update if (this._isset(stateId) && stateId != false) { this._setStateSelect(stateId, country); } }; /** * Since we're doing this on both billing and shipping, I put this into * a function. Basically build the country option set and append it to the * select element by ID as pased in. */ ScnStore.prototype._setCountrySelect = function(cntyId) { var cntySet = this._createOptionSet(this.countryList, true); var $list = $('#' + cntyId); // Add the option set of countries to the select element $list.append(cntySet); // Now select the country that was last set on the list var country = $list.data('last-country-code'); if (country) { this._logger('Last country was '+country+', setting it.'); $list.val(country.toUpperCase()); } else { this._logger('No last country set, falling back to locale...'); var ll = "es_ES".split("_"); if (ll.length > 1) { country = ll[1].toUpperCase(); this._logger('Locale found: ' + ll[1]); $list.val(ll[1].toUpperCase()); } else { country = "US"; this._logger('Locale not found, defaulting to "US".'); $list.val("US"); } } // Finally, we should update the last-country-set data attr so if we // update this again on the same pageload, we won't reset user input $list.attr('data-last-country-code', country); $list.removeAttr('disabled'); return country; }; // This function should be called any time the country changes ScnStore.prototype._setStateSelect = function(listId, country) { var that = this; var rand = parseInt(Math.random()*(99999999-10000000)+10000000); var callback = '_stateList_' + rand; this._logger('Name of the callback is: '+callback); // This is the callback that will then update the correct state list // I'm setting this FIRST to make sure it's there when the call comes back window[callback] = function(stateList) { that._logger('Got the states for #'+listId); // Since the state list comes back with country prefixes on the state // abbreviations, we need to strip them off, with this doohickey var o = {}; $.each(stateList, function(code, name) { var parts = code.split('-'); var abbr = parts[1]; o[abbr] = name; }); stateList = o; var stateSet = that._createOptionSet(stateList, true); var $list = $('#' + listId); // Add the option set of countries to the select element $list.append(stateSet); // Now select the state that was last set on the list if present // and also make sure it's an option on the new list. var last_state = $list.data('last-state'); var last_state_in_list = false; if (last_state) { that._logger('Last state was '+last_state+', checking if it\'s here.'); $.each(stateList, function(code, name) { if (code == last_state.toUpperCase()) { $list.val(last_state.toUpperCase()); last_state_in_list = true; return; } }); } // @TODO Make this somehow more elegant... // Now let's add a little localization. We should set the first // item in the list as a label for the state/region and use the // local name for it (State, Territory, Region, Province, etc.) var label = null; var c = country; if (c == "US" || c == "MX" || c == "BR" || c == "DE") { label = 'Estado...'; } else if (c == "AU") { label = 'Territorio/Estado...'; } else if (c == "ZA" || c == "IT" || c == "TW" || c == "NZ") { label = 'Provincia...'; } else if (c == "CA") { label = 'Provincia/Territorio...'; } else if (c == "GB") { label = 'Condado...'; } else { label = 'Región...'; } if (last_state || last_state_in_list) { $list.prepend(''); } else { $list.prepend(''); } $list.append(''); // Finally, we should update the last-state data attr so if we // update this again on the same pageload, we won't reset user input $list.attr('data-last-state', last_state); $list.removeClass('disabled').removeAttr("disabled"); $list.parent().removeClass('updating'); }; // Make the call to get the states list by country id and let the callback // do all the heavy lifting this._loadStateProvinces(country, callback); }; /** * LOAD STATE/PROVINCES * * This is similar to _loadCountires() but for states on those countries. * Right now it's called by _setStateSelect as that function attaches a * unique callback to window that will take the state list it gets and * makes the select dropdown for that form element specifically. * */ ScnStore.prototype._loadStateProvinces = function(data, callback) { this._logger('Getting states for '+ data +'...', 'info'); var url = "https://sd.ondemandhosting.info/lookups/state_list.html"; this._logger("URL: "+url+"?countryCode=" + data + "&callback=" + callback); $.ajaxq('scnstoreq', { context: this, url: url+"?countryCode=" + data + "&callback=" + callback, dataType: "script", failure: function(errMsg) { this._throwError(errMsg);} }); }; /** * CREATE OPTION SET * Since we're doing this on multiple dropdowns in the form we shoud just put * the iterator here in one place. * * Takes a key-value pair of the data you want to make options with. * Example: {IN:"Indiana"} */ ScnStore.prototype._createOptionSet = function(map, sort) { this._logger(map); // Sort the text values because they come as sorted by keys. Lame. if (sort) { map = this._sortProperties(map); } // We make a documentFragment instead of using tons of jQuery.append's // as this was proven to be 3-4x faster for large lists per John Resig: // http://ejohn.org/blog/dom-documentfragments/ var optSet = document.createDocumentFragment(); $.each(map, function(code, name) { var option = document.createElement("option"); option.textContent = name; option.value = code; optSet.appendChild(option); }); return optSet; }; // This is just quick and dirty. This is called by a listener on the state // field ang checks if the value is "other". If so, do some magical stuff // to create an input field to specify what is meant by "other". ScnStore.prototype._checkForOtherStateValAndHandle = function(listId) { this._logger('Calling _checkForOtherStateValAndHandle', 'info'); var $list = $('#'+listId); var listVal = $list.val(); if (listVal == "other") { // Check if there is already an "other" input field there if ( ! $('#'+listId+'_other').length) { this._logger('There\'s no "other" input and it is selected. Making one...'); var $listParent = $list.parent(); // Create a new input for alt var input = document.createElement("input"); input.type = "text"; input.setAttribute("id", listId + "_other"); input.setAttribute("style", "display: inline-block; width: 68%; float: left; margin-left: 2.5%" ); input.setAttribute("class", "form-control"); // Make room for the new input by shrinking the existing list $list.css({ width: '29.5%', float: 'left' }).addClass('otherState'); // Slam it into the little container for the stae field $listParent.append(input); $('#'+listId + "_other").focus(); this._logger('done.'); } } else { this._logger('The selected state is not "other"'); // So now we know that the user selected something other than // "other" if ($list.hasClass('otherState')) { this._logger('Removing "other" text input'); $list.removeClass('otherState pull-left').css({width:'',float:''}); $('#'+listId+'_other').remove(); } } }; /** * SET SHIPPING OPTIONS * * Pretty self explanatory */ ScnStore.prototype._setShippingOptions = function() { if ( ! this.hasOwnProperty('orderForm')) { return; } if ( ! this.orderForm.hasOwnProperty('ShippingOptions')) { return; } this._logger('Setting shipping options...', 'info'); var cart = this.lastCart; var shipOptWrapperId = this.orderForm.ShippingOptions.Id; // So there are no shipping options in the *last* cart if ( ! cart.ShippingOptions) { this._logger('No ShippingOptions available yet.', 'warn'); // Lop through any stuff in the options list and $('#' + shipOptWrapperId + ' li').each(function(index, val) { if ($(this).attr('id') == 'ship_option_notice') { $(this).removeClass('hidden'); } else { $(this).remove(); } }); return; } var that = this; var options = cart.ShippingOptions; var $optionContainer = $('#'+that.orderForm.ShippingOptions.Id); this._logger('Found some shipping options, here they are:'); this._logger(options); // Remove the old options and de-register them from this.orderForm $('#' + shipOptWrapperId + ' li').each(function(index, val) { if ($(this).attr('id') == 'ship_option_notice') { $(this).addClass('hidden'); } else { $(this).remove(); } }); delete this.orderForm.ShippingOptions.Options; this.orderForm.ShippingOptions.Options = {}; for (var i = options.length - 1; i >= 0; i--) { $optionContainer.append(that._buildShippingOption(options[i])); } $('#' + shipOptWrapperId + ' input[type=radio]:first-child').attr('checked', 'checked'); }; ScnStore.prototype._buildShippingOption = function(option) { this._logger('Building option for ' + option.Name); // Some randomness for unique IDs var rand = parseInt(Math.random()*(99999-10000)+10000); var elId = 'ship_option_' + rand; var currency = this.settings.currency; var locale = this.settings.locale.split('_'); var lang = locale[0]; var form = this.orderForm; // Build a list item for this option var wrapper = document.createElement('li'); wrapper.setAttribute('class', 'shipping-option col'); // Make the input var input = document.createElement('input'); input.setAttribute('type', 'radio'); input.setAttribute('name', 'ShipOption'); input.setAttribute('value', option.UniqueId); input.setAttribute('id', elId); wrapper.appendChild(input); // Make the label var label = document.createElement('label'); label.setAttribute('for', elId); label.setAttribute('class', 'd-flex flex-row align-content-center align-items-center'); // Make the contents of the label var fauxRdoBtn = document.createElement('div'); fauxRdoBtn.setAttribute('class', 'faux-radio-btn'); label.appendChild(fauxRdoBtn); // Make a container for title and description var contentWrapper = document.createElement('div'); contentWrapper.setAttribute('class', 'ship-opt-content d-flex flex-column'); // The title var title = document.createElement('div'); title.setAttribute('class', 'title'); title.appendChild(document.createTextNode(option.Name)); contentWrapper.appendChild(title); // The body var body = document.createElement('div'); body.setAttribute('class', 'body'); body.appendChild(document.createTextNode( 'Se enviará en un día hábil')); contentWrapper.appendChild(body); // Stick the contentWrapper inside the label label.appendChild(contentWrapper); // The rate var rate = document.createElement('div'); rate.setAttribute('class', 'rate ml-auto'); rate.appendChild(document.createTextNode( window.FormatCurrency(lang, currency, option.Amount, true) )); label.appendChild(rate); // Now stick the label on the wrapper wrapper.appendChild(label); // Finally, register this shipping option with the orderForm form.ShippingOptions.Options[option.UniqueId] = {id: elId}; this._logger('Done. Element ID is ' + elId); return wrapper; }; ScnStore.prototype._populateOrderForm = function(data) { var form = this.orderForm; $.each(data.BillingAddressData, function(i,v) { if (form.BillingAddressData.hasOwnProperty(i)) { form.BillingAddressData[i] = v; } }); $.each(data.ShippingAddressData, function(i,v) { if (form.ShippingAddressData.hasOwnProperty(i)) { form.ShippingAddressData[i] = v; } }); this.orderForm = form; if (this.SetBillingShippingAndRecalculate(this.orderForm, true)) { return true; } return false; }; ScnStore.prototype.UpdateDataDisplayBoxes = function() { var form = this.orderForm; var shipAddr = form.ShippingAddressData; var billAddr = form.BillingAddressData; if (typeof shipAddr !== "undefined") { if ($(".shipping-address-box")) { $(".shipping-address-box .first-name").text(shipAddr.FirstName); $(".shipping-address-box .last-name").text(shipAddr.LastName); $(".shipping-address-box .address1").text(shipAddr.Address1); if (shipAddr.Address2) { $(".shipping-address-box .address2").html("
"+shipAddr.Address2); } $(".shipping-address-box .city").text(shipAddr.City); $(".shipping-address-box .state-province").text(shipAddr.StateProvince); $(".shipping-address-box .zip-postal").text(shipAddr.PostalCode); $(".shipping-address-box .country").text(shipAddr.CountryCode); } } if (typeof billAddr !== "undefined") { if ($(".billing-address-box")) { $(".billing-address-box .email").text(billAddr.Email); $(".billing-address-box .first-name").text(billAddr.FirstName); $(".billing-address-box .last-name").text(billAddr.LastName); $(".billing-address-box .address1").text(billAddr.Address1); if (billAddr.Address2) { $(".billing-address-box .address2").html("
"+billAddr.Address2); } $(".billing-address-box .city").text(billAddr.City); $(".billing-address-box .state-province").text(billAddr.StateProvince); $(".billing-address-box .zip-postal").text(billAddr.PostalCode); $(".billing-address-box .country").text(billAddr.CountryCode); } } return true; }; // PAYMENT FORM STUFF ScnStore.prototype._initPaymentForm = function() { if ( ! this.orderForm) { return false; } var form = this.orderForm; // For all things on this level if (form.hasOwnProperty('PaymentMethods')) { // For all credit card related initilizations if (form.PaymentMethods.hasOwnProperty('creditcard')) { // set expiration month to PT month var mid = form.PaymentMethods.creditcard.AccountExpiration.Month; var yid = form.PaymentMethods.creditcard.AccountExpiration.Year; var date = new Date(); $('#'+mid).val(date.getMonth() + 1); $('#'+yid).val(date.getFullYear()); } } return true; }; ScnStore.prototype._getCardType = function(val) { // Auto-select the card type icons based on first digits // of the card number. Regex from regular-expressions.info var types = this.settings.cardTypes; var ret = false; $.each(types, function(card, expr) { var re = new RegExp(expr, 'g'); if (val.match(re)) { ret = card; } }); return ret; }; ScnStore.prototype._setCardType = function(type) { $('.payment-card').each(function(i,el) { if ($(el).hasClass(type)) { $(el).addClass('active'); $(el).siblings().prop('checked', true); } }); }; ScnStore.prototype._unsetAllCardTypes = function() { $('.payment-card').removeClass('active'); $('input[name=CardType]').prop('checked', false); }; // Add to Cart Popover. So this is the little popover element that will appear over the normal add-to-cart // buttons when they get clicked. We want to provide some feedback for clicking the link and some 8-C on // getting them to the cart or to keep looking. ScnStore.prototype._atcPopover = function($el) { if ($el.hasClass('no-popover')) { return; } var rand = Math.floor(Math.random()*90000) + 10000; var rmid = 'rm_' + rand; var txt = document.createElement('p'); txt.setAttribute('class', 'text-success'); txt.setAttribute('style', 'margin: 0; font-weight: bold;'); txt.appendChild(document.createTextNode('Añadido a tu carrito de compras')); var btn1 = document.createElement('a'); btn1.setAttribute('class', 'btn'); btn1.setAttribute('class', 'btn-primary'); btn1.setAttribute('class', 'btn-sm'); btn1.setAttribute('href', '/store/checkout.action'); btn1.setAttribute('style', ''); btn1.appendChild(document.createTextNode('Continúa para Verificar')); var btn2 = document.createElement('button'); btn2.setAttribute('class', 'btn btn-sm btn-light'); btn2.setAttribute('id', rmid); btn2.setAttribute('style', ''); btn2.appendChild(document.createTextNode('×')); var buttons = document.createElement('div'); buttons.setAttribute('style', 'white-space: nowrap'); buttons.appendChild(btn1); buttons.appendChild(btn2); var content = document.createElement('div'); content.appendChild(txt); content.appendChild(buttons); var options = { placement: 'top', trigger: 'focus', html: true, template: '', content: content, }; $el.popover(options).popover('show'); $('body').on('click', '#' + rmid, function(event) { event.preventDefault(); $el.popover('hide'); }); setTimeout(function(){ $el.popover('hide'); },5000); }; ScnStore.prototype._atcBtnFeedback = function($el) { if ($el.hasClass('no-feedback')) { return; } // Cache the stuff already in the button var cache = $el.html(); var original_width = $el.outerWidth(); var original_height = $el.outerHeight(); $el.addClass('proceed-to-checkout'); if ($el.hasClass('btn-icon')) { $el.find('span[class^=iconer-]').removeClass().addClass('iconer-tick-circle'); } else { $el.css({'width':original_width,'height':original_height}); var icon = document.createElement('div'); icon.setAttribute('class', 'iconer-tick-circle'); $el.html(icon); } // Revert after 5 seconds setTimeout(function(){ $el.html(cache); $el.removeClass('proceed-to-checkout'); }, 3000); }; // Add to Cart Inline. Here we want to provide some way to alert the user to the fact that // his item was indeed added to his shopping cart and that he may even proceed to the // cart if he so wishes. ScnStore.prototype._atcInline = function($el) { // Cache the stuff already in the button var cache = $el.html(); // Add this class to apply any styling needed $el.addClass('proceed-to-checkout'); $el.html('Añadido a tu carrito de compras'); // Revert after 5 seconds setTimeout(function(){ $el.html(cache); $el.removeClass('proceed-to-checkout'); }, 3000); }; /** * UTILITIES * ============================== */ // JSON-RPC boilerplate ScnStore.prototype._jsonRpcRequest = function(methodName, params) { if ( ! this._isset(params)) { params = [{}]; } return { "jsonrpc": "2.0", "method": methodName, "id": parseInt(Math.random() * 1000000000000), "params": params }; }; // JSON boilerplate for ajax requests ScnStore.prototype._jsonBp = { type: "POST", url: "/store-api.action", // contentType: "application/json; charset=utf-8", dataType: "json" }; /** * Console.log shortcut * This function utilizes a library called loglevel.js. It's a small tool * that handles console.* way better than a lot of other things. */ ScnStore.prototype._logger = function(data, level) { // Don't remove this comment, template thing // return; // this.log.table = function(data) { if (typeof console.table !== "undefined") { console.table(data); } else { this.log.info(data); } }; var prefix = "ScnStore:"; data = prefix+" "+data; switch (level) { case 'info': this.log.info(data); break; case 'warn': this.log.warn(data); break; case 'trace': this.log.info(trace); break; case 'table': this.log.table(data); break; case 'error': this.log.error(data); break; default: this.log.debug(data); } }; // Test if a variable is undefined, display error and quit ScnStore.prototype._failIfUndefined = function(v, msg) { if ( ! this._isset(v)) { throw new this._throwError(msg); } }; // Make a custom error type ScnStore.prototype._throwError = function(msg) { var err = Error.call(this, msg); err.name = "ScnStoreError"; return err; }; ScnStore.prototype._displayError = function(data) { if (data.error) { var err = data.error; var alertElement; if(err === 'link_expired') { err = { code: 1000, data: [{ Code: 1000, Message: err }] } } console.error('err', err); // ErrCode 1000 if (err.code == 1000) { for (var i = err.data.length - 1; i >= 0; i--) { var message = err.data[i].Message; if(message == "invalid_referrer_email") { message = 'Invalid Consultant Email.'; } else if(message == "invalid_promo_code") { message = 'Invalid Consultant Email.'; } else if(message == "link_expired") { message = 'Link has expired. Please request a new link from your material consulatant.'; } this._buildErrorMessage( err.data[i].Code, message ); 1 // Log the fact in the console this._logger('ScnStore Error '+ err.data[i].Code +': '+ err.data[i].Message, 'error'); } } else if(err.indexOf("JSON:") == 0) { var resp = JSON.parse(err.substr("JSON:".length, err.length)); var message = ""; // Sberbank error handlings if(resp.sberbank_error_code) { message = window.sberbankResponseErrorCode && sberbankResponseErrorCode[resp.sberbank_error_code.toString()]; $('.button--sberbank-cancel').removeClass('d-none'); } this._buildErrorMessage( 0, message ); // Log the fact in the console this._logger('ScnStore Error '+ message, 'error'); } if(err && err.indexOf && err.indexOf("UI:") == 0) { this._buildErrorMessage( 0, err.substr("UI:".length, err.length) ); // Log the fact in the console this._logger('ScnStore Error '+ err.data[i].Message, 'error'); } else { // This is probably a lower level error that would make no // sense to the end-user. We can log this in the console and // set a generic error message for the user. // Log the fact in the console this._logger('ScnStore Error: '+ err, 'error'); this._buildErrorMessage( 'generic_err', // 'An error occured. Please refresh the page or come back later.' 'mensaje de error' ); } } }; ScnStore.prototype._buildErrorMessage = function(code, message) { var alert = document.createElement('li'); alert.setAttribute('class', 'alert alert-danger alert-dismissible'); alert.setAttribute('id', code); alert.setAttribute('role', 'alert'); alert.appendChild(document.createTextNode(message)); var btn = document.createElement('button'); btn.setAttribute('type', 'button'); btn.setAttribute('class', 'close'); btn.setAttribute('data-dismiss', 'alert'); btn.setAttribute('aria-label', 'Close'); var x = document.createElement('span'); x.setAttribute('aria-hidden', 'true'); x.appendChild(document.createTextNode('×')); btn.appendChild(x); alert.appendChild(btn); if (code == 'jsvalidate_msg') { $('#error_box #jsvalidate_msg').remove(); $('#error_box').append(alert); } else if ( ! $('#error_box #'+code).length) { $('#error_box').append(alert); } // Animate scroll to error box on page var smscroll = new SmoothScroll(); var smanchor = document.querySelector('#error_box'); smscroll.animateScroll(smanchor, false, {speed: 500, offset: 50, easing: 'easeOutQuint',updateURL:false}); }; // Check if a variable is defined in any way ScnStore.prototype._isset = function(v) { return (typeof v !== 'undefined') ? true : false; }; ScnStore.prototype._pageLoaded = function() { var that = this; if(document.readyState === "complete") { that._pageLoadCompleted(); } else { // Use the timeout to really try to make sure this is going to // fire well after all the init stuff. $(document).ready(function() { that._pageLoadCompleted(); }); } }; ScnStore.prototype._pageLoadCompleted = function() { var that = this; that.isPageLoad = false; if ( ! $.isEmptyObject(that.settings.orderForm)) { that.UpdateOrderForm(); } }; ScnStore.prototype._isChrome = function() { if (navigator.userAgent.toLowerCase().indexOf('chrome') > -1) { return true; } return false; }; ScnStore.prototype._updateProductAttrs = function(newItem) { var uniqueId = newItem.UniqueId, itemCode = newItem.ProductId, quantity = newItem.Quantity, subtotal = newItem.SubtotalPrice, unitPrice= newItem.UnitPrice, locale = this.settings.locale.split('_'), currency = this.settings.currency, lang = locale[0], that = this; // body... var $item = $('.cart-item[data-scnstore-uniqueid='+uniqueId+']'); $item.attr('id', 'cart_item_'+itemCode); $item.find('label[for^=language_]').attr('for', 'language_'+itemCode); $item.find('select[name^=language_]').attr('name', 'language_'+itemCode); $item.find('select[id^=language_]').attr('id', 'language_'+itemCode); $item.find('label[for^=quantity_]').attr('for', 'quantity_'+itemCode); $item.find('input[name^=quantity_]').attr('name', 'quantity_'+itemCode); $item.find('input[id^=quantity_]').attr('id', 'quantity_'+itemCode); // @TODO @FIXME // Add the new image URL to the product if different // $.get('/store/ajax/product-images.action', function(data) { // $.each(data, function(key, prod) { // if (uniqueId == key) { // if ($item.find('.image img').attr('src') != prod.Image) { // // We want to prepend the path with the image proxy stuff so we're not // // making the user download massive images. // var proxy = '/imagecache/scalefit-170x1000@as=1@qa=65@fm=jpeg@bg=ffffff'; // var parseUrl = function(href) { // var l = document.createElement("a"); // l.href = href; // return l; // }; // var url = parseUrl(prod.Image), // host = "/_alias/f.edgesuite.net", // path = url.pathname.replace(host, ""), // link = host + proxy + path; // that._logger('Updating image url with: ' + link); // $item.find('.image img').attr('src', link); // }; // }; // }); // }); // update prices on the line item itself. Not to be confused with the // cart totals section $item.find('.unit-price-data bdi').html( window.FormatCurrency(lang, currency, unitPrice, true)); $item.find('.total-price bdi').html( window.FormatCurrency(lang, currency, subtotal, true)); $item.find('select').removeAttr('disabled'); $item.find('input').removeAttr('disabled'); }; /** * Sort object properties (only own properties will be sorted). * param object obj object to sort properties * param bool isNumericSort true - sort object properties as numeric value * returns object */ ScnStore.prototype._sortProperties = function(obj, isNumericSort) { isNumericSort=isNumericSort || false; // by default text sort var sortable=[]; for(var key in obj) { if(obj.hasOwnProperty(key)) sortable.push([key, obj[key]]); if(isNumericSort) { sortable.sort(function(a, b) { return a[1]-b[1]; }); } else { sortable.sort(function(a, b) { var x=a[1].toLowerCase(), y=b[1].toLowerCase(); return xy ? 1 : 0; }); } } // Convert back to object obj = {}; $.each(sortable, function(i, arr) { obj[arr[0]] = arr[1]; }); sorted = obj; return sorted; }; ScnStore.prototype._redirect = function(url) { var ua = navigator.userAgent.toLowerCase(), isIE = ua.indexOf('msie') !== -1, version = parseInt(ua.substr(4, 2), 10); // Internet Explorer 8 and lower if (isIE && version < 9) { var link = document.createElement('a'); link.href = url; document.body.appendChild(link); link.click(); } // All other browsers else { window.location.href = url; } }; $.fn.serializeObject = function() { var o = {}; var a = this.serializeArray(); $.each(a, function() { if (o[this.name] !== undefined) { if (!o[this.name].push) { o[this.name] = [o[this.name]]; } o[this.name].push(this.value || ''); } else { o[this.name] = this.value || ''; } }); return o; }; ScnStore.prototype._click = function(selector) { var linkEl = $( selector ); if ( linkEl.attr ( 'onclick' ) === undefined ) { document.location = linkEl.attr ( 'href' ); } else { linkEl.click (); } }; ScnStore.prototype._getQueryVariable = function(variable) { var query = window.location.search.substring(1); var vars = query.split('&'); for (var i = 0; i < vars.length; i++) { var pair = vars[i].split('='); if (decodeURIComponent(pair[0]) == variable) { return decodeURIComponent(pair[1]); } } console.log('Query variable %s not found', variable); return false; }; /** * SHIPPING QUOTE VALIDATION MAP * * This is a list of country codes that which require certain criteria be * met in order to supply a shipping quote. The first list are cntys that * require a postal code of some kind and the second list are ones that * require some kind of state/region. The rest require city name to be * specified Got all of this data from ups.com website */ ScnStore.prototype._shipQuoteValidationList = { PostCodeRequired: [ "DZ", // Algeria "AD", // Andorra "AR", // Argentina "AM", // Armenia "AU", // Australia "AT", // Austria "AZ", // Azerbaijan "BD", // Bangladesh "BY", // Belarus "BE", // Belgium "BA", // Bosnia-Herzegovina "BR", // Brazil "BG", // Bulgaria "CA", // Canada "CN", // China "CO", // Colombia "HR", // Croatia "CY", // Cyprus "CZ", // Czech Republic "DK", // Denmark "EC", // Ecuador "EE", // Estonia "FI", // Finland "FR", // France "GE", // Georgia "DE", // Germany "GR", // Greece "GL", // Greenland "HU", // Hungary "IS", // Iceland "IN", // India "ID", // Indonesia "IL", // Israel "IT", // Italy "JP", // Japan "KZ", // Kazakhstan "KG", // Kyrgyzstan "LV", // Latvia "LI", // Liechtenstein "LT", // Lithuania "LU", // Luxembourg "MK", // Macedonia "MY", // Malaysia "MH", // Marshall Islands "MX", // Mexico "FM", // Micronesia "MD", // Moldova "MN", // Mongolia "ME", // Montenegro "NL", // Netherlands "NZ", // New Zealand "NO", // Norway "PK", // Pakistan "PW", // Palau "PH", // Philippines "PL", // Poland "PT", // Portugal "RO", // Romania "RU", // Russia "SM", // San Marino "SA", // Saudi Arabia "RS", // Serbia "SG", // Singapore "SK", // Slovakia "SI", // Slovenia "ZA", // South Africa "KR", // South Korea "ES", // Spain "LK", // Sri Lanka "SE", // Sweden "CH", // Switzerland "TW", // Taiwan "TJ", // Tajikistan "TH", // Thailand "TN", // Tunisia "TR", // Turkey "TM", // Turkmenistan "UA", // Ukraine "GB", // United Kingdom "US", // United States "UY", // Uruguay "UZ", // Uzbekistan "VU", // Vanuatu "VN" // Vietnam ], RegionRequired: [ "AL", // Albania "AG", // Antigua and Barbuda "KY", // Cayman Islands "CL", // Chile "JM", // Jamaica "KW", // Kuwait "NG", // Nigeria "PA", // Panama "PE", // Peru "TT", // Trinidad and Tobago "VE", // Venezuela "ZW" // Zimbabwe ] }; // show message to go to the CPLO website if you are try to buy from cis not on the CPLOCIS website ScnStore.prototype.ShowRUMessage = function (countryCode) { var country = countryCode || globalGeolocationData.estimated_location.country_code; $('[data-countrycode]').addClass('hidden'); $('#russia_store_message [data-countrycode="'+country+'"]').removeClass('hidden'); $('#russia_store_message').modal(); // put back when we have cplo site // this.GenerateShareLink().then(function(link){ // var url = new URL($('#russia_store_message').data('url')); // var newURL = new URL(link); // url.searchParams.set("order", newURL.searchParams.get("order")); // $('#russia_store_message a').attr('href', url.toString()); // }); window.ga && ga('send', 'event', "CIS Message", "Shown", countryCode); } // returns true if countrycode is CIS ScnStore.prototype.IsCISCountry = function(countrycode) { return countrycode == "RU" || countrycode == "KZ" || countrycode == "KG" || countrycode == "BY" || countrycode == "MD"; } // returns true if this is CIS website ScnStore.prototype.IsCISWebssite = function() { return $('body').data('orgid') == "cplocis"; } /** * Initialize the plugin once for each DOM object passed to jQuery * @params object options object * @returns void * */ $.fn.ScnStore = function(options){ return this.each(function(index,el){ el.ScnStore = new ScnStore(el,options); }); }; });