Skip to content

Instantly share code, notes, and snippets.

@Mozu-CS
Last active December 28, 2015 22:13
Show Gist options
  • Select an option

  • Save Mozu-CS/bb8175f7fbea94fac8ee to your computer and use it in GitHub Desktop.

Select an option

Save Mozu-CS/bb8175f7fbea94fac8ee to your computer and use it in GitHub Desktop.
Updated portions of models-checkout.js
FulfillmentInfo = CheckoutStep.extend({
initialize: function () {
var me = this;
this.on('change:availableShippingMethods', function (me, value) {
me.updateShippingMethod(me.get('shippingMethodCode'));
});
_.defer(function () {
// This adds the price and other metadata off the chosen
// method to the info object itself.
// This can only be called after the order is loaded
// because the order data will impact the shipping costs.
me.updateShippingMethod(me.get('shippingMethodCode'));
me.orderAvailableShippingMethods();
});
},
orderAvailableShippingMethods: function() {
var shippingMethods = this.get('availableShippingMethods');
if(shippingMethods) {
shippingMethods.sort(function(a, b) {
return a.price - b.price;
});
this.set('availableShippingMethods', shippingMethods);
}
},
var completeStep = function () {
order.messages.reset();
order.syncApiModel();
me.isLoading(true);
console.log(order.apiModel);
order.apiModel.getShippingMethodsFromContact().then(function (methods) {
methods.sort(function(a, b) {
return a.price - b.price;
});
return parent.refreshShippingMethods(methods);
}).ensure(function () {
addr.set('candidateValidatedAddresses', null);
me.isLoading(false);
parent.isLoading(false);
me.calculateStepStatus();
parent.calculateStepStatus();
});
};
define([
'modules/jquery-mozu',
'underscore',
'hyprlive',
'modules/backbone-mozu',
'modules/api',
'modules/models-customer',
'modules/models-address',
'modules/models-paymentmethods',
'hyprlivecontext'
],
function ($, _, Hypr, Backbone, api, CustomerModels, AddressModels, PaymentMethods, HyprLiveContext) {
var CheckoutStep = Backbone.MozuModel.extend({
helpers: ['stepStatus', 'requiresFulfillmentInfo', 'requiresDigitalFulfillmentContact'], //
// instead of overriding constructor, we are creating
// a method that only the CheckoutStepView knows to
// run, so it can run late enough for the parent
// reference in .getOrder to exist;
initStep: function () {
var me = this;
this.next = (function(next) {
return _.debounce(function() {
if (!me.isLoading()) next.call(me);
}, 750, true);
})(this.next);
var order = me.getOrder();
me.calculateStepStatus();
me.listenTo(order, 'error', function () {
if (me.isLoading()) {
me.isLoading(false);
}
});
me.set('orderId', order.id);
if (me.apiModel) me.apiModel.on('action', function (name, data) {
if (data) {
data.orderId = order.id;
} else {
me.apiModel.prop('orderId', order.id);
}
});
},
calculateStepStatus: function () {
// override this!
var newStepStatus = this.isValid(!this.stepStatus()) ? 'complete' : 'invalid';
return this.stepStatus(newStepStatus);
},
getOrder: function () {
return this.parent;
},
stepStatus: function (newStatus) {
if (arguments.length > 0) {
this._stepStatus = newStatus;
this.trigger('stepstatuschange', newStatus);
}
return this._stepStatus;
},
requiresFulfillmentInfo: function () {
return this.getOrder().get('requiresFulfillmentInfo');
},
requiresDigitalFulfillmentContact: function () {
return this.getOrder().get('requiresDigitalFulfillmentContact');
},
edit: function () {
this.stepStatus('incomplete');
},
next: function () {
if (this.submit()) this.isLoading(true);
}
}),
FulfillmentContact = CheckoutStep.extend({
relations: CustomerModels.Contact.prototype.relations,
validation: CustomerModels.Contact.prototype.validation,
digitalOnlyValidation: {
'email': {
pattern: 'email',
msg: Hypr.getLabel('emailMissing')
}
},
dataTypes: {
contactId: function(val) {
return (val === 'new') ? val : Backbone.MozuModel.DataTypes.Int(val);
}
},
helpers: ['contacts'],
contacts: function () {
var contacts = this.getOrder().get('customer').get('contacts').toJSON();
return contacts && contacts.length > 0 && contacts;
},
initialize: function () {
var self = this;
this.on('change:contactId', function (model, newContactId) {
if (!newContactId || newContactId === 'new') {
model.get('address').clear();
model.get('phoneNumbers').clear();
model.unset('id');
model.unset('firstName');
model.unset('lastNameOrSurname');
} else {
model.set(model.getOrder().get('customer').get('contacts').get(newContactId).toJSON(), {silent: true});
}
});
},
calculateStepStatus: function () {
if (!this.requiresFulfillmentInfo() && this.requiresDigitalFulfillmentContact()) {
this.validation = this.digitalOnlyValidation;
}
if (!this.requiresFulfillmentInfo() && !this.requiresDigitalFulfillmentContact()) return this.stepStatus('complete');
return CheckoutStep.prototype.calculateStepStatus.apply(this);
},
getOrder: function () {
// since this is one step further away from the order, it has to be accessed differently
return this.parent.parent;
},
choose: function (e) {
var idx = parseInt($(e.currentTarget).val(), 10);
if (idx !== -1) {
var addr = this.get('address');
var valAddr = addr.get('candidateValidatedAddresses')[idx];
for (var k in valAddr) {
addr.set(k, valAddr[k]);
}
}
},
toJSON: function () {
if (this.requiresFulfillmentInfo() || this.requiresDigitalFulfillmentContact()) {
return CheckoutStep.prototype.toJSON.apply(this, arguments);
}
},
isDigitalValid: function() {
var email = this.get('email');
return (!email) ? false : true;
},
nextDigitalOnly: function () {
var order = this.getOrder(),
me = this;
if (this.validate()) return false;
this.getOrder().apiModel.update({ fulfillmentInfo: me.toJSON() }).ensure(function () {
me.isLoading(false);
order.messages.reset();
order.syncApiModel();
me.calculateStepStatus();
return order.get('billingInfo').calculateStepStatus();
});
},
next: function () {
if (!this.requiresFulfillmentInfo() && this.requiresDigitalFulfillmentContact()) {
return this.nextDigitalOnly();
}
var validationObj = this.validate();
if (validationObj) {
Object.keys(validationObj).forEach(function(key){
this.trigger('error', {message: validationObj[key]});
}, this);
return false;
}
var parent = this.parent,
order = this.getOrder(),
me = this,
isAddressValidationEnabled = HyprLiveContext.locals.siteContext.generalSettings.isAddressValidationEnabled,
allowInvalidAddresses = HyprLiveContext.locals.siteContext.generalSettings.allowInvalidAddresses;
this.isLoading(true);
var addr = this.get('address');
var completeStep = function () {
order.messages.reset();
order.syncApiModel();
me.isLoading(true);
console.log(order.apiModel);
order.apiModel.getShippingMethodsFromContact().then(function (methods) {
methods.sort(function(a, b) {
return a.price - b.price;
});
return parent.refreshShippingMethods(methods);
}).ensure(function () {
addr.set('candidateValidatedAddresses', null);
me.isLoading(false);
parent.isLoading(false);
me.calculateStepStatus();
parent.calculateStepStatus();
});
};
var promptValidatedAddress = function () {
order.syncApiModel();
me.isLoading(false);
parent.isLoading(false);
me.stepStatus('invalid');
};
if (!isAddressValidationEnabled) {
completeStep();
} else {
if (!addr.get('candidateValidatedAddresses')) {
var methodToUse = allowInvalidAddresses ? 'validateAddressLenient' : 'validateAddress';
addr.apiModel[methodToUse]().then(function (resp) {
if (resp.data && resp.data.addressCandidates && resp.data.addressCandidates.length) {
if (_.find(resp.data.addressCandidates, addr.is, addr)) {
addr.set('isValidated', true);
completeStep();
return;
}
addr.set('candidateValidatedAddresses', resp.data.addressCandidates);
promptValidatedAddress();
} else {
completeStep();
}
}, function (e) {
if (allowInvalidAddresses) {
// TODO: sink the exception.in a better way.
order.messages.reset();
completeStep();
} else {
order.messages.reset({ message: Hypr.getLabel('addressValidationError') });
}
});
} else {
completeStep();
}
}
}
}),
FulfillmentInfo = CheckoutStep.extend({
initialize: function () {
var me = this;
this.on('change:availableShippingMethods', function (me, value) {
me.updateShippingMethod(me.get('shippingMethodCode'));
});
_.defer(function () {
// This adds the price and other metadata off the chosen
// method to the info object itself.
// This can only be called after the order is loaded
// because the order data will impact the shipping costs.
me.updateShippingMethod(me.get('shippingMethodCode'));
me.orderAvailableShippingMethods();
});
},
orderAvailableShippingMethods: function() {
var shippingMethods = this.get('availableShippingMethods');
if(shippingMethods) {
shippingMethods.sort(function(a, b) {
return a.price - b.price;
});
this.set('availableShippingMethods', shippingMethods);
}
},
relations: {
fulfillmentContact: FulfillmentContact
},
validation: {
shippingMethodCode: {
required: true,
msg: Hypr.getLabel('chooseShippingMethod')
}
},
refreshShippingMethods: function (methods) {
this.set({
availableShippingMethods: methods
});
// always make them choose again
_.each(['shippingMethodCode', 'shippingMethodName'], this.unset, this);
// after unset we need to select the cheapest option
this.updateShippingMethod();
},
calculateStepStatus: function () {
// If no shipping required, we're done.
if (!this.requiresFulfillmentInfo()) return this.stepStatus('complete');
// If there's no shipping address yet, go blank.
if (this.get('fulfillmentContact').stepStatus() !== 'complete') {
return this.stepStatus('new');
}
// Incomplete status for shipping is basically only used to show the Shipping Method's Next button,
// which does nothing but show the Payment Info step.
var billingInfo = this.parent.get('billingInfo');
if (!billingInfo || billingInfo.stepStatus() === 'new') return this.stepStatus('incomplete');
// Payment Info step has been initialized. Complete status hides the Shipping Method's Next button.
return this.stepStatus('complete');
},
updateShippingMethod: function (code) {
var available = this.get('availableShippingMethods'),
newMethod = _.findWhere(available, { shippingMethodCode: code }),
lowestValue = _.min(available, function(ob) { return ob.price; }); // Returns Infinity if no items in collection.
if (!newMethod && available && available.length && lowestValue) {
newMethod = lowestValue;
}
if (newMethod) {
this.set(newMethod);
this.applyShipping();
}
},
applyShipping: function() {
if (this.validate()) return false;
var me = this;
this.isLoading(true);
var order = this.getOrder();
if (order) {
order.apiModel.update({ fulfillmentInfo: me.toJSON() })
.then(function (o) {
var billingInfo = me.parent.get('billingInfo');
if (billingInfo) {
billingInfo.loadCustomerDigitalCredits();
}
})
.ensure(function() {
me.isLoading(false);
me.calculateStepStatus();
me.parent.get('billingInfo').calculateStepStatus();
});
}
},
next: function () {
this.stepStatus('complete');
this.parent.get('billingInfo').calculateStepStatus();
}
}),
BillingInfo = CheckoutStep.extend({
mozuType: 'payment',
validation: {
paymentType: {
fn: "validatePaymentType"
},
savedPaymentMethodId: {
fn: "validateSavedPaymentMethodId"
},
'billingContact.email': {
pattern: 'email',
msg: Hypr.getLabel('emailMissing')
}
},
dataTypes: {
'isSameBillingShippingAddress': Backbone.MozuModel.DataTypes.Boolean,
'creditAmountToApply': Backbone.MozuModel.DataTypes.Float
},
relations: {
billingContact: CustomerModels.Contact,
card: PaymentMethods.CreditCardWithCVV,
check: PaymentMethods.Check
},
validatePaymentType: function(value, attr) {
var order = this.getOrder();
var payment = order.apiModel.getCurrentPayment();
var errorMessage = Hypr.getLabel('paymentTypeMissing');
if (!value) return errorMessage;
if ((value === "StoreCredit" || value === "GiftCard") && this.nonStoreCreditTotal() > 0 && !payment) return errorMessage;
},
validateSavedPaymentMethodId: function (value, attr, computedState) {
if (this.get('usingSavedCard')) {
var isValid = this.get('savedPaymentMethodId');
if (!isValid) return Hypr.getLabel('selectASavedCard');
}
},
helpers: ['acceptsMarketing', 'savedPaymentMethods', 'availableStoreCredits', 'applyingCredit', 'maxCreditAmountToApply',
'activeStoreCredits', 'nonStoreCreditTotal', 'activePayments', 'hasSavedCardPayment', 'availableDigitalCredits', 'digitalCreditPaymentTotal', 'isAnonymousShopper', 'visaCheckoutFlowComplete'],
acceptsMarketing: function () {
return this.getOrder().get('acceptsMarketing');
},
visaCheckoutFlowComplete: function() {
return this.get('paymentWorkflow') === 'VisaCheckout';
},
cancelVisaCheckout: function() {
var self = this;
var order = this.getOrder();
var currentPayment = order.apiModel.getCurrentPayment();
return order.apiVoidPayment(currentPayment.id).then(function() {
self.clear();
self.stepStatus('incomplete');
self.setDefaultPaymentType(self);
});
},
activePayments: function () {
return this.getOrder().apiModel.getActivePayments();
},
hasSavedCardPayment: function() {
var currentPayment = this.getOrder().apiModel.getCurrentPayment();
return !!(currentPayment && currentPayment.billingInfo.card && currentPayment.billingInfo.card.paymentServiceCardId);
},
nonStoreCreditTotal: function () {
var me = this,
order = this.getOrder(),
total = order.get('total'),
result,
activeCredits = this.activeStoreCredits();
if (!activeCredits) return total;
result = total - _.reduce(activeCredits, function (sum, credit) {
return sum + credit.amountRequested;
}, 0);
return me.roundToPlaces(result, 2);
},
resetAddressDefaults: function () {
var billingAddress = this.get('billingContact').get('address');
var addressDefaults = billingAddress.defaults;
billingAddress.set('countryCode', addressDefaults.countryCode);
billingAddress.set('addressType', addressDefaults.addressType);
billingAddress.set('candidateValidatedAddresses', addressDefaults.candidateValidatedAddresses);
},
savedPaymentMethods: function () {
var cards = this.getOrder().get('customer').get('cards').toJSON();
return cards && cards.length > 0 && cards;
},
activeStoreCredits: function () {
var active = this.getOrder().apiModel.getActiveStoreCredits();
return active && active.length > 0 && active;
},
availableStoreCredits: function () {
var order = this.getOrder(),
customer = order.get('customer'),
credits = customer && customer.get('credits'),
usedCredits = this.activeStoreCredits(),
availableCredits = credits && _.compact(_.map(credits.models, function (credit) {
if (!(credit.creditType === 'StoreCredit' || credit.creditType === 'GiftCard'))
return false;
credit = _.clone(credit);
if (usedCredits) _.each(usedCredits, function (uc) {
if (uc.billingInfo.storeCreditCode === credit.code) {
credit.currentBalance -= uc.amountRequested;
}
});
return credit.currentBalance > 0 ? credit : false;
}));
return availableCredits && availableCredits.length > 0 && availableCredits;
},
applyingCredit: function () {
return this._applyingCredit;
},
maxCreditAmountToApply: function () {
var order = this.getOrder(),
total = order.get('amountRemainingForPayment'),
applyingCredit = this.applyingCredit();
if (applyingCredit) return Math.min(applyingCredit.currentBalance, total).toFixed(2);
},
beginApplyCredit: function () {
var selectedCredit = this.get('selectedCredit');
this._oldPaymentType = this.get('paymentType');
if (selectedCredit) {
var applyingCredit = _.findWhere(this.availableStoreCredits(), { code: selectedCredit });
if (applyingCredit) {
this._applyingCredit = applyingCredit;
this.set('creditAmountToApply', this.maxCreditAmountToApply());
}
}
},
closeApplyCredit: function () {
delete this._applyingCredit;
this.unset('selectedCredit');
this.set('paymentType', this._oldPaymentType);
},
finishApplyCredit: function () {
var self = this,
order = self.getOrder();
return order.apiAddStoreCredit({
storeCreditCode: this.get('selectedCredit'),
amount: this.get('creditAmountToApply')
}).then(function (o) {
order.set(o.data);
self.closeApplyCredit();
return order.update();
});
},
// digital
onCreditAmountChanged: function(digCredit, amt) {
this.applyDigitalCredit(digCredit.get('code'), amt);
},
loadCustomerDigitalCredits: function () {
var self = this,
order = this.getOrder(),
customer = order.get('customer'),
activeCredits = this.activeStoreCredits();
var customerCredits = customer.get('credits');
if (customerCredits && customerCredits.length > 0) {
var currentDate = new Date(),
unexpiredDate = new Date(2076, 6, 4);
// todo: refactor so conversion & get can re-use - Greg Murray on 2014-07-01
var invalidCredits = customerCredits.filter(function(cred) {
var credBalance = cred.get('currentBalance'),
credExpDate = cred.get('expirationDate');
var expDate = (credExpDate) ? new Date(credExpDate) : unexpiredDate;
return (!credBalance || credBalance <= 0 || expDate < currentDate);
});
_.each(invalidCredits, function(inv) {
customerCredits.remove(inv);
});
}
self._cachedDigitalCredits = customerCredits;
if (activeCredits) {
var userEnteredCredits = _.filter(activeCredits, function(activeCred) {
var existingCustomerCredit = self._cachedDigitalCredits.find(function(cred) {
return cred.get('code').toLowerCase() === activeCred.billingInfo.storeCreditCode.toLowerCase();
});
if (!existingCustomerCredit) {
return true;
}
//apply pricing update.
existingCustomerCredit.set('isEnabled', true);
existingCustomerCredit.set('creditAmountApplied', activeCred.amountRequested);
existingCustomerCredit.set('remainingBalance', existingCustomerCredit.calculateRemainingBalance());
return false;
});
if (userEnteredCredits) {
this.convertPaymentsToDigitalCredits(userEnteredCredits, customer);
}
}
},
convertPaymentsToDigitalCredits: function(activeCredits, customer) {
var me = this;
_.each(activeCredits, function (activeCred) {
var currentCred = activeCred;
return me.retrieveDigitalCredit(customer, currentCred.billingInfo.storeCreditCode, me, currentCred.amountRequested).then(function(digCredit) {
me.trigger('orderPayment', me.getOrder().data, me);
return digCredit;
});
});
},
availableDigitalCredits: function () {
if (! this._cachedDigitalCredits) {
this.loadCustomerDigitalCredits();
}
return this._cachedDigitalCredits && this._cachedDigitalCredits.length > 0 && this._cachedDigitalCredits;
},
refreshBillingInfoAfterAddingStoreCredit: function (order, updatedOrder) {
//clearing existing order billing info because information may have been removed (payment info) #68583
// #73389 only refresh if the payment requirement has changed after adding a store credit.
var activePayments = this.activePayments();
var hasNonStoreCreditPayment = (_.filter(activePayments, function (item) { return item.paymentType !== 'StoreCredit'; })).length > 0;
if ((order.get('amountRemainingForPayment') >= 0 && !hasNonStoreCreditPayment) ||
(order.get('amountRemainingForPayment') < 0 && hasNonStoreCreditPayment)) {
order.get('billingInfo').clear();
order.set(updatedOrder, { silent: true });
}
this.trigger('orderPayment', updatedOrder, this);
},
applyDigitalCredit: function (creditCode, creditAmountToApply, isEnabled) {
var self = this,
order = self.getOrder(),
maxCreditAvailable = null;
this._oldPaymentType = this.get('paymentType');
var digitalCredit = this._cachedDigitalCredits.filter(function(cred) {
return cred.get('code').toLowerCase() === creditCode.toLowerCase();
});
if (! digitalCredit || digitalCredit.length === 0) {
return self.deferredError(Hypr.getLabel('digitalCodeAlreadyUsed', creditCode), self);
}
digitalCredit = digitalCredit[0];
var previousAmount = digitalCredit.get('creditAmountApplied');
var previousEnabledState = digitalCredit.get('isEnabled');
if (!creditAmountToApply && creditAmountToApply !== 0) {
creditAmountToApply = self.getMaxCreditToApply(digitalCredit, self);
}
digitalCredit.set('creditAmountApplied', creditAmountToApply);
digitalCredit.set('remainingBalance', digitalCredit.calculateRemainingBalance());
digitalCredit.set('isEnabled', isEnabled);
//need to round to prevent being over total by .01
if (creditAmountToApply > 0) {
creditAmountToApply = self.roundToPlaces(creditAmountToApply, 2);
}
var activeCreditPayments = this.activeStoreCredits();
if (activeCreditPayments) {
//check if payment applied with this code, remove
var sameCreditPayment = _.find(activeCreditPayments, function (cred) {
return cred.status !== 'Voided' && cred.billingInfo && cred.billingInfo.storeCreditCode.toLowerCase() === creditCode.toLowerCase();
});
if (sameCreditPayment) {
if (this.areNumbersEqual(sameCreditPayment.amountRequested, creditAmountToApply)) {
var deferredSameCredit = api.defer();
deferredSameCredit.reject();
return deferredSameCredit.promise;
}
if (creditAmountToApply === 0) {
return order.apiVoidPayment(sameCreditPayment.id).then(function(o) {
order.set(o.data);
self.trigger('orderPayment', o.data, self);
return o;
});
} else {
maxCreditAvailable = self.getMaxCreditToApply(digitalCredit, self, sameCreditPayment.amountRequested);
if (creditAmountToApply > maxCreditAvailable) {
digitalCredit.set('creditAmountApplied', previousAmount);
digitalCredit.set('isEnabled', previousEnabledState);
digitalCredit.set('remainingBalance', digitalCredit.calculateRemainingBalance());
return self.deferredError(Hypr.getLabel('digitalCreditExceedsBalance'), self);
}
return order.apiVoidPayment(sameCreditPayment.id).then(function (o) {
order.set(o.data);
return order.apiAddStoreCredit({
storeCreditCode: creditCode,
amount: creditAmountToApply,
email: self.get('billingContact').get('email')
}).then(function (o) {
self.refreshBillingInfoAfterAddingStoreCredit(order, o.data);
return o;
});
});
}
}
}
if (creditAmountToApply === 0) {
return this.getOrder();
}
maxCreditAvailable = self.getMaxCreditToApply(digitalCredit, self);
if (creditAmountToApply > maxCreditAvailable) {
digitalCredit.set('creditAmountApplied', previousAmount);
digitalCredit.set('remainingBalance', digitalCredit.calculateRemainingBalance());
digitalCredit.set('isEnabled', previousEnabledState);
return self.deferredError(Hypr.getLabel('digitalCreditExceedsBalance'), self);
}
return order.apiAddStoreCredit({
storeCreditCode: creditCode,
amount: creditAmountToApply,
email: self.get('billingContact').get('email')
}).then(function (o) {
self.refreshBillingInfoAfterAddingStoreCredit(order, o.data);
return o;
});
},
deferredError: function deferredError(msg, scope) {
scope.trigger('error', {
message: msg
});
var deferred = api.defer();
deferred.reject();
return deferred.promise;
},
areNumbersEqual: function(f1, f2) {
var epsilon = 0.01;
return (Math.abs(f1 - f2)) < epsilon;
},
retrieveDigitalCredit: function (customer, creditCode, me, amountRequested) {
var self = this;
return customer.apiGetDigitalCredit(creditCode).then(function (credit) {
var creditModel = new PaymentMethods.DigitalCredit(credit.data);
creditModel.set('isTiedToCustomer', false);
var validateCredit = function() {
var now = new Date(),
activationDate = creditModel.get('activationDate') ? new Date(creditModel.get('activationDate')) : null,
expDate = creditModel.get('expirationDate') ? new Date(creditModel.get('expirationDate')) : null;
if (expDate && expDate < now) {
return self.deferredError(Hypr.getLabel('expiredCredit', expDate.toLocaleDateString()), self);
}
if (activationDate && activationDate > now) {
return self.deferredError(Hypr.getLabel('digitalCreditNotYetActive', activationDate.toLocaleDateString()), self);
}
if (!creditModel.get('currentBalance') || creditModel.get('currentBalance') <= 0) {
return self.deferredError(Hypr.getLabel('digitalCreditNoRemainingFunds'), self);
}
return null;
};
var validate = validateCredit();
if (validate !== null) {
return null;
}
var maxAmt = me.getMaxCreditToApply(creditModel, me, amountRequested);
if (!!amountRequested && amountRequested < maxAmt) {
maxAmt = amountRequested;
}
creditModel.set('creditAmountApplied', maxAmt);
creditModel.set('remainingBalance', creditModel.calculateRemainingBalance());
creditModel.set('isEnabled', true);
me._cachedDigitalCredits.push(creditModel);
me.applyDigitalCredit(creditCode, maxAmt, true);
me.trigger('sync', creditModel);
return creditModel;
});
},
getDigitalCredit: function () {
var me = this,
order = me.getOrder(),
customer = order.get('customer');
var creditCode = this.get('digitalCreditCode');
var existingDigitalCredit = this._cachedDigitalCredits.filter(function (cred) {
return cred.get('code').toLowerCase() === creditCode.toLowerCase();
});
if (existingDigitalCredit && existingDigitalCredit.length > 0){
me.trigger('error', {
message: Hypr.getLabel('digitalCodeAlreadyUsed', creditCode)
});
// to maintain promise api
var deferred = api.defer();
deferred.reject();
return deferred.promise;
}
me.isLoading(true);
return me.retrieveDigitalCredit(customer, creditCode, me).then(function() {
me.isLoading(false);
return me;
});
},
getMaxCreditToApply: function(creditModel, scope, toBeVoidedPayment) {
var remainingTotal = scope.nonStoreCreditTotal();
if (!!toBeVoidedPayment) {
remainingTotal += toBeVoidedPayment;
}
var maxAmt = remainingTotal < creditModel.get('currentBalance') ? remainingTotal : creditModel.get('currentBalance');
return scope.roundToPlaces(maxAmt, 2);
},
roundToPlaces: function(amt, numberOfDecimalPlaces) {
var transmogrifier = Math.pow(10, numberOfDecimalPlaces);
return Math.round(amt * transmogrifier) / transmogrifier;
},
digitalCreditPaymentTotal: function () {
var activeCreditPayments = this.activeStoreCredits();
if (!activeCreditPayments)
return null;
return _.reduce(activeCreditPayments, function (sum, credit) {
return sum + credit.amountRequested;
}, 0);
},
addRemainingCreditToCustomerAccount: function(creditCode, isEnabled) {
var self = this;
var digitalCredit = self._cachedDigitalCredits.find(function(credit) {
return credit.code.toLowerCase() === creditCode.toLowerCase();
});
if (!digitalCredit) {
return self.deferredError(Hypr.getLabel('genericNotFound'), self);
}
digitalCredit.set('addRemainderToCustomer', isEnabled);
return digitalCredit;
},
getDigitalCreditsToAddToCustomerAccount: function() {
return this._cachedDigitalCredits.where({ isEnabled: true, addRemainderToCustomer: true, isTiedToCustomer: false });
},
isAnonymousShopper: function() {
var order = this.getOrder(),
customer = order.get('customer');
return (!customer || !customer.id || customer.id <= 1);
},
removeCredit: function(id) {
var order = this.getOrder();
return order.apiVoidPayment(id).then(order.update);
},
syncPaymentMethod: function (me, newId) {
if (!newId || newId === 'new') {
me.get('billingContact').clear();
me.get('card').clear();
me.get('check').clear();
me.unset('paymentType');
me.set('usingSavedCard', false);
} else {
me.setSavedPaymentMethod(newId);
me.set('usingSavedCard', true);
}
},
setSavedPaymentMethod: function (newId, manualCard) {
var me = this,
customer = me.getOrder().get('customer'),
card = manualCard || customer.get('cards').get(newId),
cardBillingContact = card && customer.get('contacts').get(card.get('contactId'));
if (card) {
me.get('billingContact').set(cardBillingContact.toJSON(), { silent: true });
me.get('card').set(card.toJSON());
me.set('paymentType', 'CreditCard');
me.set('usingSavedCard', true);
if (Hypr.getThemeSetting('isCvvSuppressed')) {
me.get('card').set('isCvvOptional', true);
if (me.parent.get('amountRemainingForPayment') > 0) {
return me.applyPayment();
}
}
}
},
getPaymentTypeFromCurrentPayment: function () {
var billingInfoPaymentType = this.get('paymentType'),
billingInfoPaymentWorkflow = this.get('paymentWorkflow'),
currentPayment = this.getOrder().apiModel.getCurrentPayment(),
currentPaymentType = currentPayment && currentPayment.billingInfo.paymentType,
currentPaymentWorkflow = currentPayment && currentPayment.billingInfo.paymentWorkflow,
currentBillingContact = currentPayment && currentPayment.billingInfo.billingContact,
currentCard = currentPayment && currentPayment.billingInfo.card;
if (currentPaymentType && (currentPaymentType !== billingInfoPaymentType || currentPaymentWorkflow !== billingInfoPaymentWorkflow)) {
this.set('paymentType', currentPaymentType, { silent: true });
this.set('paymentWorkflow', currentPaymentWorkflow, { silent: true });
this.set('card', currentCard, { silent: true });
this.set('billingContact', currentBillingContact, { silent: true });
}
},
edit: function () {
this.getPaymentTypeFromCurrentPayment();
CheckoutStep.prototype.edit.apply(this, arguments);
},
initialize: function () {
var me = this;
_.defer(function () {
me.getPaymentTypeFromCurrentPayment();
var savedCardId = me.get('card.paymentServiceCardId');
me.set('savedPaymentMethodId', savedCardId, { silent: true });
me.setSavedPaymentMethod(savedCardId);
if (!savedCardId) {
me.setDefaultPaymentType(me);
}
me.on('change:usingSavedCard', function (me, yes) {
if (!yes) {
me.get('card').clear();
me.set('usingSavedCard', false);
}
else {
me.set('isSameBillingShippingAddress', false);
me.setSavedPaymentMethod(me.get('savedPaymentMethodId'));
}
});
});
var billingContact = this.get('billingContact');
this.on('change:paymentType', this.selectPaymentType);
this.selectPaymentType(this, this.get('paymentType'));
this.on('change:isSameBillingShippingAddress', function (model, wellIsIt) {
if (wellIsIt) {
billingContact.set(this.parent.get('fulfillmentInfo').get('fulfillmentContact').toJSON(), { silent: true });
}
});
this.on('change:savedPaymentMethodId', this.syncPaymentMethod);
this._cachedDigitalCredits = null;
_.bindAll(this, 'applyPayment', 'markComplete');
},
selectPaymentType: function(me, newPaymentType) {
if (!me.changed || !me.changed.paymentWorkflow) {
me.set('paymentWorkflow', 'Mozu');
}
me.get('check').selected = newPaymentType === 'Check';
me.get('card').selected = newPaymentType === 'CreditCard';
},
setDefaultPaymentType: function(me) {
me.set('paymentType', 'CreditCard');
if (me.savedPaymentMethods() && me.savedPaymentMethods().length > 0) {
me.set('usingSavedCard', true);
}
},
calculateStepStatus: function () {
var fulfillmentComplete = this.parent.get('fulfillmentInfo').stepStatus() === 'complete',
activePayments = this.activePayments(),
thereAreActivePayments = activePayments.length > 0,
paymentTypeIsCard = activePayments && !!_.findWhere(activePayments, { paymentType: 'CreditCard' }),
balanceNotPositive = this.parent.get('amountRemainingForPayment') <= 0;
if (paymentTypeIsCard && !Hypr.getThemeSetting('isCvvSuppressed')) return this.stepStatus('incomplete'); // initial state for CVV entry
if (!fulfillmentComplete) return this.stepStatus('new');
if (thereAreActivePayments && (balanceNotPositive || (this.get('paymentType') === 'PaypalExpress' && window.location.href.indexOf('PaypalExpress=complete') !== -1))) return this.stepStatus('complete');
return this.stepStatus('incomplete');
},
hasPaymentChanged: function(payment) {
function normalizeBillingInfos(obj) {
return {
paymentType: obj.paymentType,
billingContact: _.extend(_.pick(obj.billingContact,
'email',
'firstName',
'lastNameOrSurname',
'phoneNumbers'),
{
address: obj.billingContact.address ? _.pick(obj.billingContact.address,
'address1',
'address2',
'addressType',
'cityOrTown',
'countryCode',
'postalOrZipCode',
'stateOrProvince') : {}
}),
card: _.extend(_.pick(obj.card,
'expireMonth',
'expireYear',
'nameOnCard',
'isSavedCardInfo'),
{
cardType: obj.card.paymentOrCardType || obj.card.cardType,
cardNumber: obj.card.cardNumberPartOrMask || obj.card.cardNumberPart || obj.card.cardNumber,
id: obj.card.paymentServiceCardId || obj.card.id,
isCardInfoSaved: obj.card.isCardInfoSaved || false
}),
check: obj.check || {}
};
}
var normalizedSavedPaymentInfo = normalizeBillingInfos(payment.billingInfo);
var normalizedLiveBillingInfo = normalizeBillingInfos(this.toJSON());
if (payment.paymentWorkflow === 'VisaCheckout') {
normalizedLiveBillingInfo.billingContact.address.addressType = normalizedSavedPaymentInfo.billingContact.address.addressType;
}
return !_.isEqual(normalizedSavedPaymentInfo, normalizedLiveBillingInfo);
},
submit: function () {
var order = this.getOrder();
// just can't sync these emails right
order.syncBillingAndCustomerEmail();
// This needs to be ahead of validation so we can check if visa checkout is being used.
var currentPayment = order.apiModel.getCurrentPayment();
// the card needs to know if this is a saved card or not.
this.get('card').set('isSavedCard', order.get('billingInfo.usingSavedCard'));
// the card needs to know if this is Visa checkout (or Amazon? TBD)
if (currentPayment) {
this.get('card').set('isVisaCheckout', currentPayment.paymentWorkflow.toLowerCase() === 'visacheckout');
}
var val = this.validate();
if (this.nonStoreCreditTotal() > 0 && val) {
// display errors:
var error = {"items":[]};
for (var key in val) {
if (val.hasOwnProperty(key)) {
var errorItem = {};
errorItem.name = key;
errorItem.message = key.substring(0, ".") + val[key];
error.items.push(errorItem);
}
}
if (error.items.length > 0) {
order.onCheckoutError(error);
}
return false;
}
var card = this.get('card');
if (!currentPayment) {
return this.applyPayment();
} else if (this.hasPaymentChanged(currentPayment)) {
return order.apiVoidPayment(currentPayment.id).then(this.applyPayment);
} else if (card.get('cvv') && card.get('paymentServiceCardId')) {
return card.apiSave().then(this.markComplete, order.onCheckoutError);
} else {
this.markComplete();
}
},
applyPayment: function () {
var self = this, order = this.getOrder();
this.syncApiModel();
if (this.nonStoreCreditTotal() > 0) {
return order.apiAddPayment().then(function() {
var payment = order.apiModel.getCurrentPayment();
var modelCard, modelCvv;
var activePayments = order.apiModel.getActivePayments();
var creditCardPayment = activePayments && _.findWhere(activePayments, { paymentType: 'CreditCard' });
//Clear card if no credit card payments exists
if (!creditCardPayment && self.get('card')) {
self.get('card').clear();
}
if (payment) {
switch (payment.paymentType) {
case 'CreditCard':
modelCard = self.get('card');
modelCvv = modelCard.get('cvv');
if (
modelCvv && modelCvv.indexOf('*') === -1 // CVV exists and is not masked
) {
modelCard.set('cvv', '***');
// to hide CVV once it has been sent to the paymentservice
}
self.markComplete();
break;
default:
self.markComplete();
}
}
});
} else {
this.markComplete();
}
},
markComplete: function () {
this.stepStatus('complete');
this.isLoading(false);
var order = this.getOrder();
_.defer(function() {
order.isReady(true);
});
},
toJSON: function(options) {
var j = CheckoutStep.prototype.toJSON.apply(this, arguments), loggedInEmail;
if (this.nonStoreCreditTotal() === 0 && j.billingContact) {
delete j.billingContact.address;
}
if (j.billingContact && !j.billingContact.email) {
j.billingContact.email = this.getOrder().get('customer.emailAddress');
}
return j;
}
});
var ShopperNotes = Backbone.MozuModel.extend(),
checkoutPageValidation = {
'emailAddress': {
fn: function (value) {
if (this.attributes.createAccount && (!value || !value.match(Backbone.Validation.patterns.email))) return Hypr.getLabel('emailMissing');
}
},
'password': {
fn: function (value) {
if (this.attributes.createAccount && !value) return Hypr.getLabel('passwordMissing');
}
},
'confirmPassword': {
fn: function (value) {
if (this.attributes.createAccount && value !== this.get('password')) return Hypr.getLabel('passwordsDoNotMatch');
}
}
};
if (Hypr.getThemeSetting('requireCheckoutAgreeToTerms')) {
checkoutPageValidation.agreeToTerms = {
acceptance: true,
msg: Hypr.getLabel('didNotAgreeToTerms')
};
}
var CheckoutPage = Backbone.MozuModel.extend({
mozuType: 'order',
handlesMessages: true,
relations: {
fulfillmentInfo: FulfillmentInfo,
billingInfo: BillingInfo,
shopperNotes: ShopperNotes,
customer: CustomerModels.Customer
},
validation: checkoutPageValidation,
dataTypes: {
createAccount: Backbone.MozuModel.DataTypes.Boolean,
acceptsMarketing: Backbone.MozuModel.DataTypes.Boolean,
amountRemainingForPayment: Backbone.MozuModel.DataTypes.Float
},
initialize: function (data) {
var self = this,
user = require.mozuData('user');
_.defer(function() {
var latestPayment = self.apiModel.getCurrentPayment(),
activePayments = self.apiModel.getActivePayments(),
fulfillmentInfo = self.get('fulfillmentInfo'),
fulfillmentContact = fulfillmentInfo.get('fulfillmentContact'),
billingInfo = self.get('billingInfo'),
steps = [fulfillmentInfo, fulfillmentContact, billingInfo],
paymentWorkflow = latestPayment && latestPayment.paymentWorkflow,
visaCheckoutPayment = activePayments && _.findWhere(activePayments, { paymentWorkflow: 'VisaCheckout' }),
allStepsComplete = function () {
return _.reduce(steps, function(m, i) { return m + i.stepStatus(); }, '') === 'completecompletecomplete';
},
isReady = allStepsComplete();
//Visa checkout payments can be added to order without UIs knowledge. This evaluates and voids the required payments.
if (visaCheckoutPayment) {
_.each(_.filter(self.apiModel.getActivePayments(), function (payment) {
return payment.paymentType !== 'StoreCredit' && payment.paymentType !== 'GiftCard' && payment.paymentWorkflow != 'VisaCheckout';
}), function (payment) {
self.apiVoidPayment(payment.id);
});
paymentWorkflow = visaCheckoutPayment.paymentWorkflow;
billingInfo.unset('billingContact');
billingInfo.set('card', visaCheckoutPayment.billingInfo.card);
billingInfo.set('billingContact', visaCheckoutPayment.billingInfo.billingContact, { silent:true });
}
if (paymentWorkflow) {
billingInfo.set('paymentWorkflow', paymentWorkflow);
billingInfo.get('card').set({
isCvvOptional: Hypr.getThemeSetting('isCvvSuppressed'),
paymentWorkflow: paymentWorkflow
});
billingInfo.trigger('stepstatuschange'); // trigger a rerender
}
self.isReady(isReady);
_.each(steps, function(step) {
self.listenTo(step, 'stepstatuschange', function() {
_.defer(function() {
self.isReady(allStepsComplete());
});
});
});
if (!self.get('requiresFulfillmentInfo')) {
self.validation = _.pick(self.constructor.prototype.validation, _.filter(_.keys(self.constructor.prototype.validation), function(k) { return k.indexOf('fulfillment') === -1; }));
}
self.get('billingInfo.billingContact').on('change:email', function(model, newVal) {
self.set('email', newVal);
});
var billingEmail = billingInfo.get('billingContact.email');
if (!billingEmail && user.email) billingInfo.set('billingContact.email', user.email);
});
if (user.isAuthenticated) {
this.set('customer', { id: user.accountId });
}
// preloaded JSON has this as null if it's unset, which defeats the defaults collection in backbone
if (data.acceptsMarketing === null) {
self.set('acceptsMarketing', true);
}
_.bindAll(this, 'update', 'onCheckoutSuccess', 'onCheckoutError', 'addNewCustomer', 'saveCustomerCard',/* 'finalPaymentReconcile', */'apiCheckout', 'addDigitalCreditToCustomerAccount', 'addCustomerContact', 'addBillingContact', 'addShippingContact', 'addShippingAndBillingContact');
},
processDigitalWallet: function(digitalWalletType, payment) {
var me = this;
me.runForAllSteps(function() {
this.isLoading(true);
});
me.trigger('beforerefresh');
// void active payments; if there are none then the promise will resolve immediately
return api.all.apply(api, _.map(_.filter(me.apiModel.getActivePayments(), function(payment) {
return payment.paymentType !== 'StoreCredit' && payment.paymentType !== 'GiftCard';
}), function(payment) {
return me.apiVoidPayment(payment.id);
})).then(function() {
return me.apiProcessDigitalWallet({
digitalWalletData: JSON.stringify(payment)
}).then(function () {
me.updateVisaCheckoutBillingInfo();
me.runForAllSteps(function() {
this.trigger('sync');
this.isLoading(false);
});
me.updateShippingInfo();
});
});
},
updateShippingInfo: function() {
var me = this;
this.apiModel.getShippingMethods().then(function (methods) {
me.get('fulfillmentInfo').refreshShippingMethods(methods);
});
},
updateVisaCheckoutBillingInfo: function() {
//Update the billing info with visa checkout payment
var billingInfo = this.get('billingInfo');
var activePayments = this.apiModel.getActivePayments();
var visaCheckoutPayment = activePayments && _.findWhere(activePayments, { paymentWorkflow: 'VisaCheckout' });
if (visaCheckoutPayment) {
billingInfo.set('card', visaCheckoutPayment.billingInfo.card);
billingInfo.unset('billingContact');
billingInfo.set('billingContact', visaCheckoutPayment.billingInfo.billingContact, { silent:true });
billingInfo.set('paymentWorkflow', visaCheckoutPayment.paymentWorkflow);
billingInfo.set('paymentType', visaCheckoutPayment.paymentType);
this.refresh();
}
},
addCoupon: function () {
var me = this;
var code = this.get('couponCode');
var orderDiscounts = me.get('orderDiscounts');
if (orderDiscounts && _.findWhere(orderDiscounts, { couponCode: code })) {
// to maintain promise api
var deferred = api.defer();
deferred.reject();
deferred.promise.otherwise(function () {
me.trigger('error', {
message: Hypr.getLabel('promoCodeAlreadyUsed', code)
});
});
return deferred.promise;
}
this.isLoading(true);
return this.apiAddCoupon(this.get('couponCode')).then(function () {
me.get('billingInfo').trigger('sync');
me.set('couponCode', '');
var productDiscounts = _.flatten(_.pluck(me.get('items'), 'productDiscounts'));
var shippingDiscounts = _.flatten(_.pluck(_.flatten(_.pluck(me.get('items'), 'shippingDiscounts')), 'discount'));
var orderShippingDiscounts = _.flatten(_.pluck(me.get('shippingDiscounts'), 'discount'));
var allDiscounts = me.get('orderDiscounts').concat(productDiscounts).concat(shippingDiscounts).concat(orderShippingDiscounts);
var lowerCode = code.toLowerCase();
var matchesCode = function (d) {
// there are discounts that have no coupon code that we should not blow up on.
return (d.couponCode || "").toLowerCase() === lowerCode;
};
if (!allDiscounts || !_.find(allDiscounts, matchesCode))
{
me.trigger('error', {
message: Hypr.getLabel('promoCodeError', code)
});
}
else if (me.get('total') === 0) {
me.trigger('complete');
}
me.isLoading(false);
});
},
onCheckoutSuccess: function () {
this.isLoading(true);
this.trigger('complete');
},
onCheckoutError: function (error) {
var order = this,
errorHandled = false;
order.isLoading(false);
if (!error || !error.items || error.items.length === 0) {
error = {
items: [
{
message: error.message || Hypr.getLabel('unknownError')
}
]
};
}
$.each(error.items, function (ix, errorItem) {
if (errorItem.name === 'ADD_CUSTOMER_FAILED' && errorItem.message.toLowerCase().indexOf('invalid parameter: password')) {
errorHandled = true;
order.trigger('passwordinvalid', errorItem.message.substring(errorItem.message.indexOf('Password')));
}
if (errorItem.errorCode === 'ADD_CUSTOMER_FAILED' && errorItem.message.toLowerCase().indexOf('invalid parameter: emailaddress')) {
errorHandled = true;
order.trigger('userexists', order.get('emailAddress'));
}
});
//on an error, if the card is declined -- and the service returns no card data, lets unset the model.card
this.apiGet().then(function(res) {
if (res.data.billingInfo && !res.data.billingInfo.card) {
order.unset('billingInfo.card', {silent: true});
}
order.trigger('error', error);
});
if (!errorHandled) order.messages.reset(error.items);
order.isSubmitting = false;
throw error;
},
addNewCustomer: function () {
var self = this,
billingInfo = this.get('billingInfo'),
billingContact = billingInfo.get('billingContact'),
email = this.get('emailAddress'),
captureCustomer = function (customer) {
if (!customer || (customer.type !== 'customer' && customer.type !== 'login')) return;
var newCustomer;
if (customer.type === 'customer') newCustomer = customer.data;
if (customer.type === 'login') newCustomer = customer.data.customerAccount;
if (newCustomer && newCustomer.id) {
self.set('customer', newCustomer);
api.off('sync', captureCustomer);
api.off('spawn', captureCustomer);
}
};
api.on('sync', captureCustomer);
api.on('spawn', captureCustomer);
return this.apiAddNewCustomer({
account: {
emailAddress: email,
userName: email,
firstName: billingContact.get('firstName') || this.get('fulfillmentInfo.fulfillmentContact.firstName'),
lastName: billingContact.get('lastNameOrSurname') || this.get('fulfillmentInfo.fulfillmentContact.lastNameOrSurname'),
acceptsMarketing: self.get('acceptsMarketing')
},
password: this.get('password')
}).then(function (customer) {
self.customerCreated = true;
return customer;
}, function (error) {
self.customerCreated = false;
self.isSubmitting = false;
throw error;
});
},
addBillingContact: function () {
return this.addCustomerContact('billingInfo', 'billingContact', [{ name: 'Billing' }]);
},
addShippingContact: function () {
return this.addCustomerContact('fulfillmentInfo', 'fulfillmentContact', [{ name: 'Shipping' }]);
},
addShippingAndBillingContact: function () {
return this.addCustomerContact('fulfillmentInfo', 'fulfillmentContact', [{ name: 'Shipping' }, { name: 'Billing' }]);
},
addCustomerContact: function (infoName, contactName, contactTypes) {
var customer = this.get('customer'),
contactInfo = this.get(infoName),
process = [function () {
// Update contact if a valid contact ID exists
if (orderContact.id && orderContact.id > 0) {
return customer.apiModel.updateContact(orderContact);
}
if (orderContact.id === -1 || orderContact.id === 1 || orderContact.id === 'new') {
delete orderContact.id;
}
return customer.apiModel.addContact(orderContact).then(function(contactResult) {
orderContact.id = contactResult.data.id;
return contactResult;
});
}];
var contactInfoContactName = contactInfo.get(contactName);
var customerContacts = this.get('customer').get('contacts');
if (!contactInfoContactName.get('accountId')) {
contactInfoContactName.set('accountId', customer.id);
}
var orderContact = contactInfoContactName.toJSON();
// if customer doesn't have a primary of any of the contact types we're setting, then set primary for those types
if (!this.isSavingNewCustomer()) {
process.unshift(function() {
return customer.apiModel.getContacts().then(function(contacts) {
_.each(contactTypes, function(newType) {
var primaryExistsAlready = _.find(contacts.data.items, function(existingContact) {
return _.find(existingContact.types || [], function(existingContactType) {
return existingContactType.name === newType.name && existingContactType.isPrimary;
});
});
newType.isPrimary = !primaryExistsAlready;
});
});
});
} else {
_.each(contactTypes, function(type) {
type.isPrimary = true;
});
}
// handle email
if (!orderContact.email) orderContact.email = this.get('emailAddress') || customer.get('emailAddress') || require.mozuData('user').email;
var contactId = orderContact.contactId;
if (contactId) orderContact.id = contactId;
if (!orderContact.id || orderContact.id === -1 || orderContact.id === 1 || orderContact.id === 'new') {
orderContact.types = contactTypes;
return api.steps(process);
} else {
var customerContact = customerContacts.get(orderContact.id).toJSON();
if (this.isContactModified(orderContact, customerContact)) {
//keep the current types on edit
orderContact.types = orderContact.types ? orderContact.types : customerContact.types;
return api.steps(process);
} else {
var deferred = api.defer();
deferred.resolve();
return deferred.promise;
}
}
},
isContactModified: function(orderContact, customerContact) {
var validContact = orderContact && customerContact && orderContact.id === customerContact.id;
var addressChanged = validContact && !_.isEqual(orderContact.address, customerContact.address);
//Note: Only home phone is used on the checkout page
var phoneChanged = validContact && orderContact.phoneNumbers.home &&
(!customerContact.phoneNumbers.home || orderContact.phoneNumbers.home !== customerContact.phoneNumbers.home);
//Check whether any of the fields available in the contact UI on checkout page is modified
return validContact &&
(addressChanged || phoneChanged ||
orderContact.email !== customerContact.email || orderContact.firstName !== customerContact.firstName ||
orderContact.lastNameOrSurname !== customerContact.lastNameOrSurname);
},
saveCustomerCard: function () {
var order = this,
customer = this.get('customer'), //new CustomerModels.EditableCustomer(this.get('customer').toJSON()),
billingInfo = this.get('billingInfo'),
isSameBillingShippingAddress = billingInfo.get('isSameBillingShippingAddress'),
isPrimaryAddress = this.isSavingNewCustomer(),
billingContact = billingInfo.get('billingContact').toJSON(),
card = billingInfo.get('card'),
doSaveCard = function () {
order.cardsSaved = order.cardsSaved || customer.get('cards').reduce(function(saved, card) {
saved[card.id] = true;
return saved;
}, {});
var method = order.cardsSaved[card.get('id') || card.get('paymentServiceCardId')] ? 'updateCard' : 'addCard';
card.set('contactId', billingContact.id);
return customer.apiModel[method](card.toJSON()).then(function (card) {
order.cardsSaved[card.data.id] = true;
return card;
});
},
saveBillingContactFirst = function () {
if (billingContact.id === -1 || billingContact.id === 1) delete billingContact.id;
return customer.apiModel.addContact(billingContact).then(function (contact) {
billingContact.id = contact.data.id;
return contact;
});
};
var contactId = billingContact.contactId;
if (contactId) billingContact.id = contactId;
if (!billingContact.id || billingContact.id === -1 || billingContact.id === 1 || billingContact.id === 'new') {
billingContact.types = !isSameBillingShippingAddress ? [{ name: 'Billing', isPrimary: isPrimaryAddress }] : [{ name: 'Shipping', isPrimary: isPrimaryAddress }, { name: 'Billing', isPrimary: isPrimaryAddress }];
return saveBillingContactFirst().then(doSaveCard);
} else {
return doSaveCard();
}
},
setFulfillmentContactEmail: function () {
var fulfillmentEmail = this.get('fulfillmentInfo.fulfillmentContact.email'),
orderEmail = this.get('email');
if (!fulfillmentEmail) {
this.set('fulfillmentInfo.fulfillmentContact.email', orderEmail);
}
},
syncBillingAndCustomerEmail: function () {
var billingEmail = this.get('billingInfo.billingContact.email'),
customerEmail = this.get('emailAddress') || require.mozuData('user').email;
if (!customerEmail) {
this.set('emailAddress', billingEmail);
}
if (!billingEmail) {
this.set('billingInfo.billingContact.email', customerEmail);
}
},
addDigitalCreditToCustomerAccount: function () {
var billingInfo = this.get('billingInfo'),
customer = this.get('customer');
var digitalCredits = billingInfo.getDigitalCreditsToAddToCustomerAccount();
if (!digitalCredits)
return;
return _.each(digitalCredits, function (cred) {
return customer.apiAddStoreCredit(cred.get('code'));
});
},
isSavingNewCustomer: function() {
return this.get('createAccount') && !this.customerCreated;
},
submit: function () {
var order = this,
billingInfo = this.get('billingInfo'),
billingContact = billingInfo.get('billingContact'),
isSameBillingShippingAddress = billingInfo.get('isSameBillingShippingAddress'),
isSavingCreditCard = false,
isSavingNewCustomer = this.isSavingNewCustomer(),
isAuthenticated = require.mozuData('user').isAuthenticated,
nonStoreCreditTotal = billingInfo.nonStoreCreditTotal(),
requiresFulfillmentInfo = this.get('requiresFulfillmentInfo'),
requiresBillingInfo = nonStoreCreditTotal > 0,
process = [function() {
return order.update({
ipAddress: order.get('ipAddress'),
shopperNotes: order.get('shopperNotes').toJSON()
});
}];
if (this.isSubmitting) return;
this.isSubmitting = true;
if (requiresBillingInfo && !billingContact.isValid()) {
// reconcile the empty address after we got back from paypal and possibly other situations.
// also happens with visacheckout ..
var billingInfoFromPayment = this.apiModel.getCurrentPayment().billingInfo;
billingInfo.set(billingInfoFromPayment, { silent: true });
}
this.syncBillingAndCustomerEmail();
this.setFulfillmentContactEmail();
if (nonStoreCreditTotal > 0 && this.validate()) {
this.isSubmitting = false;
return false;
}
this.isLoading(true);
if (isSavingNewCustomer) {
process.unshift(this.addNewCustomer);
}
var activePayments = this.apiModel.getActivePayments();
var saveCreditCard = false;
if (activePayments !== null && activePayments.length > 0) {
var creditCard = _.findWhere(activePayments, { paymentType: 'CreditCard' });
if (creditCard && creditCard.billingInfo && creditCard.billingInfo.card) {
saveCreditCard = creditCard.billingInfo.card.isCardInfoSaved;
billingInfo.set('card', creditCard.billingInfo.card);
}
}
if (saveCreditCard && (this.get('createAccount') || isAuthenticated)) {
isSavingCreditCard = true;
process.push(this.saveCustomerCard);
}
if ((this.get('createAccount') || isAuthenticated) && billingInfo.getDigitalCreditsToAddToCustomerAccount().length > 0) {
process.push(this.addDigitalCreditToCustomerAccount);
}
//save contacts
if (isAuthenticated || isSavingNewCustomer) {
if (!isSameBillingShippingAddress && !isSavingCreditCard) {
if (requiresFulfillmentInfo) process.push(this.addShippingContact);
if (requiresBillingInfo) process.push(this.addBillingContact);
} else if (isSameBillingShippingAddress && !isSavingCreditCard) {
process.push(this.addShippingAndBillingContact);
} else if (!isSameBillingShippingAddress && isSavingCreditCard && requiresFulfillmentInfo) {
process.push(this.addShippingContact);
}
}
process.push(/*this.finalPaymentReconcile, */this.apiCheckout);
api.steps(process).then(this.onCheckoutSuccess, this.onCheckoutError);
},
update: function() {
var j = this.toJSON();
return this.apiModel.update(j);
},
refresh: function() {
var me = this;
this.trigger('beforerefresh');
return this.apiGet().then(function() {
me.trigger('refresh');
// me.runForAllSteps(function() {
// this.trigger("sync");
// });
});
},
runForAllSteps: function(cb) {
var me = this;
_.each([
'fulfillmentInfo.fulfillmentContact',
'fulfillmentInfo',
'billingInfo'
], function(name) {
cb.call(me.get(name));
});
},
isReady: function (val) {
this.set('isReady', val);
},
toJSON: function (options) {
var j = Backbone.MozuModel.prototype.toJSON.apply(this, arguments);
if (!options || !options.helpers) {
delete j.password;
delete j.confirmPassword;
}
return j;
}
});
return {
CheckoutPage: CheckoutPage
};
}
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment