/* 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;
}