/* Order Form / Checkout Features */ var errorObj = {}; var orderValid = false; var partialSaved = false; var requiredFields = [ '#emailAddress', '#firstName', '#lastName', '#postalCode', '#city', '#address1', '#address2', '#state', '#country', '#phoneNumber', '#shipCity', '#shipAddress1', '#shipAddress2', '#shipState', '#shipCountry', '#shipPostalCode', '#cardMonth', '#cardNumber', '#cardSecurityCode', '#terms' ]; var savePartialQueue = false; var saveCartQueue = false; var savedCart = false; const regexPOBox = /\b(?:\d+\s*)?(?:p(\.|ost)?\s?o(ffice)?.?(\s?box)?|[a-z]+\s?post\s?office|[a-z]+\s?po\s?box)(?:\s?\d+)?\b/i; $(document).ready(function() { changeCardTypeIcon(); savedCart = serializedCart(); var cleave1 = new Cleave('#cardNumber', { creditCard: true, onCreditCardTypeChanged: function (type) {} }); var cleave2 = new Cleave('#phoneNumber', { phone: true, phoneRegionCode: 'US' }); /* Whenever a form element with the saveCart class is changed, the Cart Abandonment and Save Partial events should trigger */ $('form').on('change', '.saveCart', function(event) { saveCartAbandonment(); savePartial(); }); /* Disable scroll when focused on a number input */ $('form').on('focus', 'input[type=number]', function(e) { $(this).on('wheel', function(e2) { e2.preventDefault(); }); }); /* Clicking the checkout button should submit the order form */ $('#checkoutButton').click(function(e) { $('#orderForm').submit(); }); /* Card icon hovers over the cardNumber field and should focus that field when clicked */ $('.cardIcon').click(function(e) { $('#cardNumber').focus(); }); /* Card type icon in field needs to be changed when the card number changed */ $('#cardNumber').on('change keyup paste', function(e) { changeCardTypeIcon(); }); /* Show CVV instructions modal when button is clicked */ $("#cvvInstructionButton").click(function() { lightbox('#cvvLightbox'); }); /* Show a modal that displays the reason we collect phone numbers (SMS terms) */ $("#phoneWhy").click(function() { lightbox('#phoneLightbox'); }); /* Validate fields on input or blur */ $('body').on('input blur change','input, select',function(e) { // Remove error state from the modified input $(this).siblings('.inlineError').hide(); $(this).removeClass('inputError'); if ($(this).attr('id') == "cardNumber" && $(this).val() == "") $(this).addClass('type_allcards'); validateField($(this)); }); // Prevent typing numbers into firstname/lastname fields */ $('#firstName, #lastName').on('keypress', function (e) { var regex = new RegExp(/[0-9]/); var key = String.fromCharCode(!e.charCode ? e.which : e.charCode); if (regex.test(key)) { e.preventDefault(); return false; } }); /* Prevent pasting numbers into some firstname/lastname $('#firstName, #lastName').on('paste', function (e) { var element = this; setTimeout(function (e2) { var text = $(element).val(); $(element).val(text.replace(/[0-9]/g,'')); }, 0); }); /* * Form Submit Is Blocked Unless orderValid=true * Once form is validated successfully, it is re-submitted */ $('#orderForm').submit(function(event) { /* Reset Errors */ errorObj = {}; $('.inlineError').hide(); $('.inputError').removeClass('inputError'); processingModalShow(); $('#submitButton').blur(); // prevent multiple submissions by users who focus the element and hit "enter" rapidly if (!orderValid) { event.preventDefault(); if (validateFields(requiredFields)) { unsetExitPop(); orderValid = true; if (typeof amplitude !== "undefined") amplitude.getInstance().logEvent("complete - checkout",{}); // re-submit after a small timeout so overlay will show on iOS devices setTimeout(function(){ $('#orderForm').submit(); // amplitude.getInstance().logEvent("sale - main product",{ // "test":"test" // }); }, 1000); } else { popErrors(); processingModalHide(); } } }); /* prevent "enter" form submit from input/select fields */ $('input,select').keydown(function(e){ if (e.keyCode == 13) { $(this).blur(); } }); /* Toggle Address 2 value when requested by user */ $('#addr2Toggle').click(function(e) { $('#addr2Container').fadeIn(); $('#address2').focus(); $('#addr2Toggle').hide(); }); /* Toggle shipping fields when the checkbox is checked */ $('#shippingDifferent').change(function() { toggleShippingFields(); // The shipping fields can change eligibility for validation based on whether or not a secondary shipping address is being provided validateFields(['#address1', '#address2', '#shipAddress1', '#shipAddress2',]); }); /* Click to return to the checkout form */ $('.goToCheckout').click(function(e) { e.preventDefault(); $('html,body').animate({scrollTop: $("a[name='orderAnchor']").offset().top},'slow'); }); }); function changeCardTypeIcon() { $("#cardNumber").siblings('.cardIcon').removeClass('type_visa type_mastercard type_amex type_discover'); if (!$('#cardNumber').val()) { $('#cardNumber').siblings('.cardIcon').addClass('type_allcards'); $('#cardNumber').addClass('allCards'); return; } else { $('#cardNumber').siblings('.cardIcon').removeClass('type_allcards'); $('#cardNumber').removeClass('allCards'); } var cardType = getCardType($('#cardNumber').val()); if (cardType) { $('#cardNumber').siblings('.cardIcon').addClass('type_'+cardType); } } function fieldError(id,errorString) { var ele = $('#'+id); var errorTarget = ele.data('errortarget'); ele.addClass('inputError').removeClass('inputSuccess'); $('.inlineError[data-input="'+errorTarget+'"]').show(); $('.inlineError[data-input="'+errorTarget+'"]').html(''); // cardMonth and cardYear should always be linked if (id == "cardMonth" || id == "cardYear") { $('#cardMonth,#cardYear').removeClass('inputSuccess').addClass('inputError'); } } function fieldValid(id) { var ele = $('#'+id); var errorTarget = ele.data('errortarget'); ele.removeClass('inputError').addClass('inputSuccess'); $('.inlineError[data-input="'+errorTarget+'"]').hide(); $('.inlineError[data-input="'+errorTarget+'"]').html(''); // cardMonth and cardYear should always be linked if (id == "cardMonth" || id == "cardYear") { $('#cardMonth,#cardYear').addClass('inputSuccess'); } } function getCardType(cardNum) { var payCardType = ""; var regexMap = [ {regEx: /^4[0-9\s]{1,}/ig, cardType: "visa"}, {regEx: /^5[1-5\s][0-9\s]{3,}/ig, cardType: "mastercard"}, {regEx: /^3[47][0-9\s]{2,}/ig, cardType: "amex"}, {regEx: /^6011[0-9\s]{4,}/ig, cardType: "discover"} ]; for (var j = 0; j < regexMap.length; j++) { if (cardNum.match(regexMap[j].regEx)) { payCardType = regexMap[j].cardType; break; } } return payCardType; } function luhnCheck(value) { if (/[^0-9-\s]+/.test(value)) return false; let nCheck = 0, bEven = false; value = value.replace(/\D/g, ""); for (var n = value.length - 1; n >= 0; n--) { var cDigit = value.charAt(n), nDigit = parseInt(cDigit, 10); if (bEven && (nDigit *= 2) > 9) nDigit -= 9; nCheck += nDigit; bEven = !bEven; } return (nCheck % 10) == 0; } function popErrors() { $('button').blur(); // prevent user from hitting enter and triggering modal multiple times // Parse errors object into a pipe-delimited list (for logging/analytics), and html elements (for display) var errorHTML = ""; var errorList = ""; $.each(errorObj,function(k,v) { errorHTML += v; errorList += "|"+k+"|"; }); processingModalHide(); // Setup and show errors in lightbox $("#errorLightbox .orderErrors .errorDesc").html("There are problems with your order form."); $("#errorLightbox .orderErrors .errorList").html(errorHTML); lightbox("#errorLightbox"); } function processingModalHide() { $('#placeOrderShade').fadeOut(200); } function processingModalShow() { $('#placeOrderShade').fadeIn(200); } function saveCartAbandonment() { var formVals = serializedCart(); if (formVals === savedCart) return; // by setting a timeout each time this event is triggered, we insure only the last one fires. // this prevents an issue whereby an entire form worth of fields is filled out quickly (like an autofill) // and that could trigger a dozen simultaneous requests clearTimeout(saveCartQueue); saveCartQueue = setTimeout(function() { jQuery.post($('#cartAbandonURI').val(), formVals, function(data) {}, 'json'); savedCart = formVals; },3000); } function savePartial() { if (partialSaved) return; var fields = ['#emailAddress','#firstName','#lastName','#address1','#city','#state','#country','#postalCode','#phoneNumber']; // only allow the partial to be saved if all required fields are completed var doSave = true; $.each(fields, function(k,field) { var fval = $(field).val(); if (!fval) { doSave = false; return; } }); if (!doSave) return; var data = serializedCart(); clearTimeout(savePartialQueue); savePartialQueue = setTimeout(function() { jQuery.post("/order/partial", data, function(data) { if (data.status == "saved") { if (typeof addPushEvent == 'function') addPushEvent('Order_Partial',false,false); if (typeof amplitude !== "undefined") amplitude.getInstance().logEvent("save - checkout - partial",{}); partialSaved = true; } },'json'); },1500); } function serializedCart() { var ser = $("#orderForm").find('.saveCart').serialize(); return ser; } function toggleShippingFields() { if (!$('#shippingDifferent').prop('checked')) { $('#shippingGroup').hide(); } else { $('#shippingGroup').show(); } } function validateCC() { var returnArray = []; // Each value is a key of an invalid component of the CC# if (testIP && $('#cardNumber').val() === '0000 0000 0000 0000') return false; // Allow for testing (this is still validated on the back-end) var cardType = getCardType($('#cardNumber').val()); var luhnValid = luhnCheck($('#cardNumber').val()); if (cardType == "amex") returnArray.push("amex"); // we do not accept amex if (!luhnValid) returnArray.push("luhn"); if (!$('#cardNumber').val()) returnArray.push("length"); if (returnArray.length > 0) { return returnArray; } else { return false; } } function validateField(ele) { var id = ele.attr('id'); var errorString = ""; var val = ele.val(); var shippingDifferent = $('#shippingDifferent').prop('checked'); if (id == 'emailAddress') { if (!validEmail(val,false)) errorString += "
  • You Must Enter A Valid Email
  • "; } else if (id == 'firstName') { if (val.length < 2) errorString += "
  • First Name Must Be At Least 2 Characters
  • "; } else if (id == 'lastName') { if (val.length < 2) errorString += "
  • Last Name Must Be At Least 2 Characters
  • "; } else if (id == 'postalCode') { if (!(/^\d{5}(-\d{4})?$/.test(val))) errorString += "
  • Zip Code Must Be A Valid US Postal Code
  • "; else if (val.length < 3 || val.length > 20) errorString += "
  • Zip Code Must Be At Between 3-20 Characters
  • "; } else if (id == 'state') { if (!val || val.length < 1) errorString += "
  • Select A State
  • "; } else if (id == 'city') { if (val.length < 2) errorString += "
  • City Must Be At Least 2 Characters
  • "; } else if (id == 'address1') { if (val.length < 2) errorString += "
  • Address Must Be At Least 2 Characters
  • "; else if ((regexPOBox.test(val)) && !shippingDifferent) { errorString += "
  • Product cannot ship to PO Boxes
  • "; } } else if (id == 'address2') { if (val.length > 1 && !shippingDifferent) { if (regexPOBox.test(val)) { errorString += "
  • Product cannot ship to PO Boxes
  • "; } } if (val.length < 1) { $('div[data-input="address2"]').hide(); } } else if (id == 'country') { if (val.length != 2) errorString += "
  • Select A Country
  • "; } else if (id == 'shipPostalCode' && shippingDifferent) { if (!(/^\d{5}(-\d{4})?$/.test(val))) errorString += "
  • Shipping Postal Code Must Be A Valid US Postal Code
  • "; else if (val.length < 3 || val.length > 20) errorString += "
  • Shipping Postal Code Must Be At Between 3-20 Characters
  • "; } else if (id == 'shipState' && shippingDifferent) { if (!val || val.length < 1) errorString += "
  • Select A Shipping State
  • "; } else if (id == 'shipCity' && shippingDifferent) { if (val.length < 2) errorString += "
  • Shipping City Must Be At Least 2 Characters
  • "; } else if (id == 'shipAddress1' && shippingDifferent) { if (val.length < 2) errorString += "
  • Shipping Street Address Must Be At Least 2 Characters
  • "; else if (regexPOBox.test(val)) errorString += "
  • Product cannot ship to PO Boxes
  • "; } else if (id == 'shipAddress2' && shippingDifferent) { if (val.length > 1) { if (regexPOBox.test(val)) { errorString += "
  • Product cannot ship to PO Boxes
  • "; } } if (val.length < 1) { $('div[data-input="shipAddress2"]').hide(); } } else if (id == 'shipCountry' && shippingDifferent) { if (val.length != 2) errorString += "
  • Select A Shipping Country
  • "; } else if (id == 'phoneNumber') { val = val.replace(/[_ \)\(-]/g,''); if (!val) errorString += "
  • Phone Number Must Be Numeric
  • "; else if (!$.isNumeric(val)) errorString += "
  • Phone Number Must Be Numeric
  • "; else if (val.length < 7) errorString += "
  • Phone Number Too Short
  • "; } else if (id == 'cardYear' || id == 'cardMonth') { $('#cardYear, #cardMonth').removeClass('inputError'); if ($('#cardYear').val() < 1) errorString += "
  • Missing Expiration Year
  • "; if ($('#cardMonth').val() < 1) errorString += "
  • Missing Expiration Month
  • "; /* Validate month/date combo is not in the past */ if (parseInt($('#cardMonth').val()) != 0 && parseInt($('#cardYear').val()) != 0) { var d = new Date(); var currentMonth = d.getMonth()+1; var currentYear = d.getFullYear().toString().substr(-2); var cardYear = parseInt($('#cardYear').val()); var cardMonth = parseInt($('#cardMonth').val()) if (currentYear > cardYear) errorString += "
  • Card Expiration Must Be A Future Date
  • "; else if (currentMonth > cardMonth && currentYear >= cardYear) errorString += "
  • Card Expiration Must Be A Future Date
  • "; } } else if (id == 'cardNumber') { var ccInvalid = validateCC(); if (ccInvalid) { if ($.inArray('amex',ccInvalid) > -1) { errorString += "
  • Sorry, We Do Not Accept American Express
  • "; } else if ($.inArray('length',ccInvalid) > -1) { errorString += "
  • Card Number Length is Invalid
  • "; } else if ($.inArray('luhn',ccInvalid) > -1) { errorString += "
  • Card Number Is Invalid
  • "; } } } else if (id == 'cardSecurityCode') { if (val.length != 3 && val.length != 4) errorString += "
  • CVV Must Be 3 or 4 Digits
  • "; if (!$.isNumeric(val)) errorString += "
  • CVV Must Be Numeric
  • "; } else if (id == 'terms') { if (!$('#terms').prop('checked')) errorString += "
  • You must accept the terms to complete your order
  • "; } if (errorString == "") { if (val) fieldValid(id); return true; } else { fieldError(id,errorString); errorObj[id] = errorString; return false; } } function validateFields(fields) { var valid = true; errorObj = {}; // reset previous validation errors $.each(fields, function(k,field) { $(field).val($.trim($(field).val())); if (!validateField($(field))) valid = false; }); if (!valid) { var errorList = ""; $.each(errorObj,function(k,v) { errorList += "|"+k+"|"; }); if (typeof amplitude !== "undefined") amplitude.getInstance().logEvent("fail validation - checkout",{'error message':errorList}); } return valid; }