@nuskin/ns-checkout
Version:
Ecomm3 Checkout module
1,252 lines (1,108 loc) • 76.6 kB
JavaScript
'use strict';
import '../app.js';
import {UserService, AccountManager, AuthenticationService} from '@nuskin/ns-account';
import {ConfigService, RunConfigService, StringService, storage, events, StickyHeaderStack, StickyHeader, StickyHeaderBoundary, util, SponsorStorageService, StoreFrontSponsorStorageService, PersonalOfferStorageService} from '@nuskin/ns-util';
import {
AccertifyFraudService,
CartService,
ShopContextService,
AdrConstants,
AdrService,
PaymentType,
PaymentService,
TransBankPaymentAdapter,
CurrencyCodeToNumber,
PersonalOfferService,
CheckoutModeService,
OrderManager,
PickupUtil,
ExternalPaymentService, Cart
} from '@nuskin/ns-shop';
import {AdrUtil,CartUtil,ModalUtil,OrderUtil,PaymentUtil,SectionUtil,ShippingAddressUtil,StringUtil,ThreatMatrixUtil,PromoCodeOffers} from '@nuskin/ns-checkout-common';
import {STATE_CONFIG} from "@nuskin/ns-form-elements";
import {retrieveProductionToggles, isEnabled} from '@nuskin/ns-feature-flags';
angular.module('checkout').controller('OrderSummaryCtrl', [
'$window',
'$scope',
'$rootScope',
'$filter',
'$q',
'$http',
'$location',
'$timeout',
'nsAdrOptions',
'nsShipping',
'nsTimeDelivery',
'nsPayment',
'nsUtil',
'nsSection',
'nsSections',
'nsDowntime',
'tdcService',
'nsADRType',
'nsAdrUtil',
'googleMapsApi',
'NgMap',
function(
$window,
$scope,
$rootScope,
$filter,
$q,
$http,
$location,
$timeout,
nsAdrOptions,
nsShipping,
nsTimeDelivery,
nsPayment,
nsUtil,
nsSection,
nsSections,
nsDowntime,
tdcService,
nsADRType,
nsAdrUtil,
googleMapsApi,
NgMap) {
// If the route or page changes publish any analytics changes
$rootScope.$on("$locationChangeStart", function(event, next, current) {
// make sure the function is defined before calling it
if (typeof $scope.publishShippingMethodChangeCount === 'function') {
$scope.publishShippingMethodChangeCount();
}
});
window.addEventListener('beforeunload', function() {
// make sure the function is defined before calling it
if (typeof $scope.publishShippingMethodChangeCount === 'function') {
$scope.publishShippingMethodChangeCount();
}
});
$scope.getItemCnt = () => {
return $scope.order ? $scope.order.sapItemQty : 0;
};
$scope.getAddressSummary = (address) => {
let result = "";
if (address) {
result = address.shippingAddress1;
if (address.shippingAddress2) {
result += `, ${address.shippingAddress2}`;
}
if (address.shippingCounty) {
result += `, ${address.shippingCounty}`;
}
if (address.addressType != '7-11') {
result += `, ${address.shippingCity}`;
}
if (address.shippingState) {
result += `, ${$filter('stateName')(address.shippingState)}`;
}
if (!$scope.config.checkout.hideShippingPostalCode) {
result += `, ${address.shippingPostalCode}`;
}
if (address.shippingCountry) {
result += `, ${address.shippingCountry}`;
}
}
return result;
};
$scope.shippingAccordionClicked = () => {
// Shipping section is open
// If we are busy don't let them do anything
if (!$scope.isBusy && !$scope.sections.shipping.pristine) {
if ($scope.sections.shipping.edit && $scope.order.selectedAddress) {
// If we are id edit mode don't let them try to continue if we are adding or have errors
if (!$scope.sections.shipping.adding && !$scope.sections.shipping.editing && !$scope.sections.shipping.error.hasErrorMessages()) {
// If they changed the address then we need to simulate and force the opening of the shipmethod section
if (!$scope.currentSelectedAddress || $scope.currentSelectedAddress.beShippingAddressId !== $scope.order.selectedAddress.beShippingAddressId) {
$scope.sections.shipmethod.pristine = true;
$scope.changeShippingAddressAndSimulate();
}
$scope.continueEdit('shipping');
}
} else if ($scope.enableChangeShippingAddress) {
// When the section is opened set the currentSelectedAddress so we can compare when it is closed to force a simulate
$scope.currentSelectedAddress = $scope.order.selectedAddress;
$scope.updateEdit('shipping');
}
}
};
$scope.shipMethodAccordionClicked = () => {
if (!$scope.isBusy && !$scope.sections.shipmethod.pristine) {
if ($scope.sections.shipmethod.edit) {
if (!$scope.shouldSelectPickupPoint() && !$scope.sections.shipmethod.error.hasErrorMessages()) {
//Collapse accordion
$scope.continueEdit('shipmethod');
}
} else {
$scope.updateEdit('shipmethod');
}
}
};
$scope.billingAccordionClicked = () => {
if (!$scope.isBusy && !$scope.sections.payment.pristine) {
if ($scope.sections.payment.edit) {
if (!$scope.isPaymentContinueBtnDisabled()) {
$scope.continueEdit('payment');
}
} else {
$scope.updateEdit('payment');
}
}
};
$scope.adrAccordionClicked = () => {
if (!$scope.isBusy && !$scope.sections.adrOptions.pristine) {
if ($scope.sections.adrOptions.edit) {
$scope.continueEdit('adrOptions');
} else {
$scope.updateEdit('adrOptions');
}
}
};
$scope.toggleFlag = (flag) => {
this[flag] = !(!!flag);
};
// Returns true if the product's price list includes a points price and the item is not ADR, false otherwise
$scope.hasPointsPrice = item => (Number.isFinite(item.points) && !item.isAdr);
// Returns the total points price for this item
// (e.g. item that costs 4.5 pts each, user is purchasing 3 with points -> item points subtotal = 4.5 * 3 = 13.5 pts)
$scope.getItemSubtotalPoints = item => item.qtyRedeemWithPoints * item.points;
$scope.getItemUnitPrice = sapItem => {
let unitPrice = 0;
if (sapItem.price > 0) {
// calculate based off of simulate. Should not be a divide by 0 since lineItem price > 0. (qty > qtyRedeemWithPoints
unitPrice = sapItem.price / (sapItem.qty - sapItem.qtyRedeemWithPoints);
} else {
// can't base it off of simulate, use cart unit price.
CartService.getItemData({cartItems: true})
.filter(item => item.sku === sapItem.sku)
.forEach(item => {
unitPrice = item.price;
});
}
return unitPrice;
};
// Handle points allocation for line items with quantity = 1
$scope.syncSingleItemRedeem = item => {
if ($scope.showPointsPaymentOptions && validatePointsChange(item, item.redeem ? item.qty : 0)) {
updateDomItemPricing(item, item.redeem ? item.qty : 0);
$scope.updateEdit("cart");
} else {
item.redeem = false;
}
};
// Handle points allocation for line items with quantity > 1
$scope.updatePtsForMultipleQtyItem = (item, usePoints) => {
if ($scope.showPointsPaymentOptions && validatePointsChange(item, usePoints ? 1 : 0)) {
if (usePoints) {
item.redeem = true;
updateDomItemPricing(item, 1);
} else {
item.redeem = false;
updateDomItemPricing(item, 0);
}
$scope.updateEdit("cart");
} else {
item.redeem = false;
}
};
$scope.decreaseQtyRedeem = item => {
if ($scope.showPointsPaymentOptions && validatePointsChange(item, item.qtyRedeemWithPoints - 1)) {
if (item.qtyRedeemWithPoints > 1) {
updateDomItemPricing(item, item.qtyRedeemWithPoints - 1);
}
}
};
$scope.increaseQtyRedeem = item => {
if ($scope.showPointsPaymentOptions && validatePointsChange(item, item.qtyRedeemWithPoints + 1)) {
if (item.qtyRedeemWithPoints < item.qty) {
updateDomItemPricing(item, item.qtyRedeemWithPoints + 1);
}
}
};
/**
* This is the ng-click event function for the points Continue button.
* Simulate order then reset (un-pristine) the points fields.
*/
$scope.pointsContinueClicked = (disabled = false) => {
if (disabled) return;
if ($scope.pointsEdited) {
$scope.simulateOrder().then(function () {
$scope.continueEdit("cart");
$scope.pointsEdited = false;
});
} else {
$scope.continueEdit("cart");
}
};
$scope.updateQtyRedeem = item => {
if (!$scope.showPointsPaymentOptions || !item.redeem) {
return;
}
let newQtyRedeem = Math.floor(item.qtyRedeemWithPoints || 0);
newQtyRedeem = Math.floor(newQtyRedeem || 0);
newQtyRedeem = newQtyRedeem > item.qty ? item.qty: newQtyRedeem;
newQtyRedeem = newQtyRedeem < 1 ? 1 : newQtyRedeem;
if (validatePointsChange(item, newQtyRedeem)) {
updateDomItemPricing(item, newQtyRedeem);
}
};
// Sets the quantity to redeem with points on the item, and updates the item
// price, cart subtotal price, and grand total price accordingly.
function updateDomItemPricing(sapItem, qtyRedeem) {
CartService.getItemData({cartItems: true})
.filter(cartItem => cartItem.sku === sapItem.sku)
.forEach(cartItem => {
let {deltaItemTotal, sapItemData} = CartService.updateItemPointsPrice(sapItem.key, cartItem.key, qtyRedeem);
// update local sapItem with new information
Object.assign(sapItem, sapItemData);
$scope.order.orderTotals.itemSubtotal += deltaItemTotal;
$scope.order.orderTotals.grandTotal += deltaItemTotal;
$scope.cartPointsSubtotal = getPointsPriceTotal(false);
$scope.pointsTotal = getPointsPriceTotal();
});
$scope.order.orderTotals.psvTotal = getPsvTotal();
}
function getPsvTotal() {
let psvRunningTotal = 0;
CartService.getItemData({sapItems: true})
.forEach(sapItem => {
// update psv
psvRunningTotal += (sapItem.qty - sapItem.qtyRedeemWithPoints) * CartService.getItemUnitPsv(sapItem.key);
});
return psvRunningTotal;
}
function validatePointsChange(sapItem, newQtyRedeem) {
let pointsDiff = (sapItem.points * newQtyRedeem) - (sapItem.points * (sapItem.qtyRedeemWithPoints || 0)),
singlePsv = CartService.getItemUnitPsv(sapItem.key),
psvDiff = ((singlePsv * newQtyRedeem) - (singlePsv * sapItem.qtyRedeemWithPoints || 0)).toFixed(2),
retVal = false;
$scope.pointsEdited = true;
// Skip validation if user's points balance not yet initialized
if (!$scope.showPointsPaymentOptions || $scope.userAvailablePoints === null) {
return;
}
if ($scope.pointsTotal + pointsDiff > $scope.userAvailablePoints) {
addValidationMessage($scope.tdc.userPointsExceededError);
} else if (!AdrService.isAdrOverride() && getOrderTotalPsv() - psvDiff <= 0) {
addValidationMessage($scope.tdc.zeroPsvError);
} else {
removeValidationMessage();
retVal = true;
}
function addValidationMessage(message) {
let domHasMessage = $scope.sections.cart.error.infoMessages
.some(msg => msg.text === message);
if (!domHasMessage) {
$scope.sections.cart.error.addErrorMessage('info', {text: message});
}
}
function removeValidationMessage() {
let leftOverMessages = $scope.sections.cart.error.infoMessages
.filter(item => item.text !== $scope.tdc.userPointsExceededError && item.text !== $scope.tdc.zeroPsvError);
$scope.sections.cart.error.infoMessages = [];
leftOverMessages.forEach(message => $scope.sections.cart.error.addErrorMessage('info', {text: message}));
}
return retVal;
}
function getOrderTotalPsv() {
return parseFloat(CartService.getItemData({cartItems: true})
.filter(item => (item.isAdr && $scope.adr.shipImmediate) || (!item.isAdr))
.map(item => {
return item.singlePv * (item.qty - (item.qtyRedeemWithPoints || 0));
})
.reduce((a, b) => a + b, 0))
.toFixed(2);
}
// Because the checkout DOM is built from SAP items list, and order requests are built from cart items list,
// this function copies changes to points allocation from SAP item to cart item
function syncCartItemToSapItem(sapItem) {
CartService.syncCartItemToSapItem(sapItem.key);
}
/***
* Acquire the total points.
*
* @param applyShipping{boolean} to the total point price. True by default.
* @returns {number} the point price total
*/
function getPointsPriceTotal(applyShipping = true) {
if (!$scope.order) {
return 0.0;
}
let productsTotal = CartService.getItemData({sapItems: true})
.filter(item => !!item.points)
.map(item => {
let qtyRedeem = item.qtyRedeemWithPoints > 0 ? item.qtyRedeemWithPoints : item.redeem ? item.qty : 0;
return item.points * qtyRedeem;
})
.reduce((a, b) => a + b, 0);
let pointsShippingCost = 0;
if (nuskin.configuration.adpManage && nuskin.configuration.adpManage.PayWithPointsShippingCost) {
pointsShippingCost = Number.parseInt(nuskin.configuration.adpManage.PayWithPointsShippingCost);
}
return productsTotal + (($scope.order.shippingPayWithPoints && applyShipping) ? pointsShippingCost : 0);
}
function resetPointsAllocations() {
if ($scope.showPointsPaymentOptions) {
CartService.getItemData({sapItems: true})
.filter(item => item.redeem)
.forEach(item => {
updateDomItemPricing(item, 0);
});
$scope.order.shippingPayWithPoints = false;
}
}
$scope.isPWPShippingMethod = selectedMethod => {
let adpManage = nuskin.configuration.adpManage;
if (!adpManage || !adpManage.enablePayWithPoints || !adpManage.PayWithPointsShippingMethods) return false;
if (!selectedMethod || !selectedMethod.Code) return false;
return adpManage.PayWithPointsShippingMethods.indexOf(selectedMethod.Code) > -1;
};
$scope.onTogglePWPShipping = () => {
$scope.pointsTotal = getPointsPriceTotal();
if ($scope.pointsTotal > $scope.userAvailablePoints) {
$scope.order.shippingPayWithPoints = false;
$scope.pointsTotal = getPointsPriceTotal();
// Add user error message if not already present
let hasPtsExceededError = $scope.sections.review.error.errorMessages
.some(msg => msg.text === $scope.tdc.userPointsExceededError);
if (!hasPtsExceededError) {
$scope.sections.review.error.addErrorMessage("error", {text: $scope.tdc.userPointsExceededError});
}
}
};
$scope.gmap = {
googleMapsApiUrl: googleMapsApi,
markers: null,
selectedMarker: null,
instance: null
};
$scope.showCheckout = storage.getItem(storage.metadata.RECEIVING_ITEMS) !== true;
var EXTERNAL_PAYMENT_NEXT_ORDER = "externalPaymentNextOrder";
if (!$scope.showCheckout) {
return;
}
$scope.forms = {};
$scope.runConfig = RunConfigService.getRunConfig();
// Provide a default landing page if needed.
if (!$scope.runConfig.landing){
$scope.runConfig.landing = "checkout.html#/orderSummary";
}
$scope.user = UserService.getUser();
try {
$scope.editMode = checkoutSetup.editMode;
$scope.bruneiName = checkoutSetup.bruneiName;
$scope.indonesiaName = checkoutSetup.indonesiaName;
$scope.malaysiaName = checkoutSetup.malaysiaName;
$scope.philippinesName = checkoutSetup.philippinesName;
$scope.singaporeName = checkoutSetup.singaporeName;
$scope.thailandName = checkoutSetup.thailandName;
}
// If checkoutSetup is not set up:
catch(err) {
console.error(err.message);
}
$scope.saveSAPAddresstoProfile = false;
$scope.transBankInstallmentsBusy = false;
$scope.transBankErrorMessage = "";
$scope.isSummerPromo = false;
$scope.getCartParams = function(params = {}) {
params.isMySite = StoreFrontSponsorStorageService.getStoreFrontSponsor() &&
SponsorStorageService.getSponsor();
return params;
};
if (nsAdrUtil.showAdrShipWhen()) {
CartService.goToCheckoutPage({route: 'adrShipWhen'})
return;
}
// ---------------------------------------------
//
// Private Variables
//
// ---------------------------------------------
var user = $scope.user;
// ---------------------------------------------
//
// Public Variables
//
// ---------------------------------------------
$scope.config = ConfigService.getMarketConfig();
ConfigService.loadCustomJSStylesOnPage('Checkout');
StringService.getStrings([{key:'downtimeText', dflt:'Shopping is going down for maintenance soon'},{key:'cardEditText',dflt:'Edit'}]).then((resp) => {
$scope.commonStrings = resp;
});
if (nuskin.util.MobileUtils.isMobileLike() || window.innerWidth < 1337) {
$scope.pickupMapSelected = true;
}
$scope.tdc = tdcService.getTdc('checkout');
$scope.PaymentType = PaymentType;
$scope.hasEventItems = CartService.hasEventItems();
$scope.isPitchCart = !!CartService.getCartProperty("isPitchCart");
if (StoreFrontSponsorStorageService.getStoreFrontSponsor() && SponsorStorageService.getSponsor() && !$scope.isPitchCart) {
$scope.isStorefrontCart = true;
} else {
$scope.isStorefrontCart = false;
}
$scope.isGuestCheckout = CheckoutModeService.isGuestCheckout();
$scope.hidePointValue = false;
$scope.isGift = !!CartService.getCartProperty("isGift");
$scope.chosen = {};
$scope.chosen.element = null;
$scope.chosen.isValid = false;
$scope.termsConditionsCheckbox = false;
$scope.globalHeader = null;
$scope.errorHeader = null;
$scope.paymentIsWire = false;
$scope.deleteShippingDialogId = null;
$scope.deletePaymentDialogId = null;
$scope.emailOptIn = false;
$scope.stickySideBar = { value: false };
$scope.slideUpOrderReview = false;
$scope.isIOS = nuskin.util.MobileUtils.isIOS();
$scope.googleMapsChanged = false;
// The following properties on scope should not be modified in a isolate scope directive
$scope.contentLoaded = false;
$scope.showSpinner = true;
$scope.buyForOther = false;
$scope.isBusy = false;
$scope.forceDisableOrderButton = false;
$scope.backdatingToggle = false;
$scope.showExtraPaymentInfo = false;
$scope.usedPoints = CartService.getCartInfo().totalOrderPoints;
$scope.showPsv = ShopContextService.showPsv($scope.user ? $scope.user.accountType : 0) && !$scope.isGuestCheckout;
$scope.showPointsPaymentOptions = false;
$scope.detailsIcon = 'nuskin:icon-smallcart-empty';
$scope.userAvailablePoints = null;
$scope.pointsTotal = 0;
$scope.blockPointsPromoCombo = false;
// Health warnings required by California for orders containing applicable SKUs
$scope.californiaWarnings = null;
$scope.translations = {
loyalty: {
enjoyCreditsLabel: '',
summaryEarningsLabel: '',
summaryRedeemedLabel: ''
}
};
$scope.getTranslation = () => {
angular.forEach($scope.translations, (object, applicationKey) => {
StringService.getApplicationStrings(applicationKey).then((response) => {
$scope.translations[applicationKey] = response;
$scope.$apply();
})
})
};
$scope.getTranslation();
if ($scope.isPitchCart) {
$scope.personalOffer = PersonalOfferStorageService.getPersonalOffer();
}
SectionUtil.initSections($scope, nsSections, nsAdrUtil, $timeout, PersonalOfferService);
var hideProcessingIndicators = function() {
$('#mobile-processing').hide();
$scope.isBusy = false;
};
// If authentication is required and the user is not authenticated we redirect back to the cart with a force login.
if (ConfigService.getMarketConfig().authenticationRequired) {
var redirectToCartPage = false;
if (UserService.isLTOSite()){
if (!AuthenticationService.isLoggedIn()){
redirectToCartPage = true;
}
}
else if (!AccountManager.isLoggedIn() && !$scope.isGuestCheckout){
redirectToCartPage = true;
}
if (redirectToCartPage){
CartService.goToCartPage($scope.getCartParams({hashParams: 'forceLogin'}));
return;
}
}
// Redirect to order confirmation if we have received a successful externalPayment
var needToHandleExternalPayment = ExternalPaymentService.needToHandleExternalPayment();
if (needToHandleExternalPayment) {
hideProcessingIndicators();
ExternalPaymentService.handlePayment($scope.tdc, $scope.sections, $scope.adr).then(doingRedirect => {
if (doingRedirect) {
hideProcessingIndicators();
} else {
initWaitingRoom();
}
});
} else {
initWaitingRoom();
}
// ---------------------------------------------
//
// Public Methods
//
// ---------------------------------------------
$scope.initAndGo = function () {
$scope.isBusy = true;
// IMPORTANT NOTE: This call MSUT be the isEnabledAsync and not the sychronous isEnabled
// because the loading of the accertify flags has not happened yet and we need to make
// sure the feature flag toggles are loaded first before initing the accertify beacon script
retrieveProductionToggles().then((flags) => {
$scope.blockPointsPromoCombo = isEnabled('cx16-2785_points_promo');
if (isEnabled('cx8_380_accertify')) {
if (AccertifyFraudService.accertifyInMarketConfig(ConfigService.getMarketConfig())) {
AccertifyFraudService.initAccertify(ConfigService.getMarketConfig());
if (isEnabled('cx8_380_debugging')) {
console.log("CX8:\n\t", AccertifyFraudService.getTransactionID())
}
}
}
});
var simulatePromiseDependencies = [];
OrderUtil.initOrders($scope, $rootScope, $location, $timeout, $q, $http, nsUtil, simulatePromiseDependencies);
CartUtil.initCart($scope, $timeout);
PaymentUtil.initPayments($scope, $http, nsUtil, nsPayment, nsAdrUtil, simulatePromiseDependencies, PersonalOfferService);
PaymentUtil.injectPaymentComponent();
// Special case for JP with Business Portfolio.... or for LIVE events where they do pickup only orders
if ($scope.order.simulateWithoutShipping || $scope.config.checkout.skipShippingAddress) {
CartUtil.initializeCart();
// For fixing the P1 production issue
$('#orderSummaryRoot').css('display', 'block');
} else {
ShippingAddressUtil.initShippingAddresses($scope, nsUtil, nsShipping, nsTimeDelivery, $timeout, simulatePromiseDependencies, PersonalOfferService);
loadCheckout();
}
$scope.cuotas = $scope.order.selectedPayment && $scope.order.selectedPayment.useInstallments ? $scope.order.selectedPayment.getInstallmentSelectedNumber() : 1;
setTimeout(function () {
if (nuskin && ConfigService.getMarketConfig().enableThreatMetrics) {
ThreatMatrixUtil.initThreatMatrixScriptUrl($scope, nsUtil);
}
}, 2000);
};
$scope.initShippingAddresses = function() {
ShippingAddressUtil.initShippingAddresses($scope, nsUtil, nsShipping, nsTimeDelivery, $timeout, []);
};
$scope.setCaliforniaWarnings = function() {
let address = $scope.order.selectedAddress;
if (!address) return; // exit early if there is no shipping address
if ($scope.runConfig.country === 'US' && address.shippingState === 'CA') {
// Order is shipping to California; add warnings if order contains applicable SKUs;
let skuWarnings = window.californiaSkuWarnings || [];
let orderItems = CartService.getItemData({sapItems: true});
$scope.californiaWarnings = skuWarnings
.filter(s => orderItems.some(item => item.sku === s.sku))
.map(s => s.warning)
.join('\n');
} else {
$scope.californiaWarnings = null;
}
};
/**
* Load sticky headers and hide processing indicators once content has been loaded into DOM
*/
$scope.$watch("contentLoaded", function(newValue) {
if(newValue) {
$scope.buildStickyHeaders();
hideProcessingIndicators();
$scope.pointsEdited = false;
showPointsPaymentOptions();
$scope.orderHasAdr = CartService.getItemCnt({cartAdrItems: true}) > 0;
$scope.setCaliforniaWarnings();
$scope.sidebar = document.getElementById("checkoutSideBar");
if (nuskin.util.MobileUtils.isMobileLike() && !$scope.config.checkout.turnOffStickySideBar) {
$window.requestAnimationFrame(stickyScroll);
}
}
});
function simulateWithoutShippingForJapan() {
$scope.order.simulateWithoutShipping = true;
$scope.saveSAPAddresstoProfile = true;
CartUtil.initializeCart();
// For fixing the P1 production issue
$('#orderSummaryRoot').css('display', 'block');
}
$scope.buildStickyHeaders = function () {
setTimeout(function () {
if (nuskin && StickyHeader && !$scope.busyHeader) {
// If we're inside AgeLoc, we need to watch a different scroll element
if(document.querySelector('.ageloc-wrap')) {
StickyHeaderStack.scrollWatch= '.ageloc-wrap';
}
StickyHeaderStack.reinitialize();
var headerBoundary = new StickyHeaderBoundary('#header');
StickyHeaderStack.stackOffsetMobile = '#header';
$scope.errorHeader = new StickyHeader('#checkoutErrorSummary .errorDialogue', {
customLeftAlignment: true,
onClick: 'scrollToOrigin'
});
$scope.shippingError = new StickyHeader('#checkout-shipping-address .errorDialogue', {
customLeftAlignment: true,
onClick: 'scrollToOrigin'
});
$scope.paymentError = new StickyHeader('#checkout-edit-payment .errorDialogue', {
customLeftAlignment: true,
onClick: 'scrollToOrigin'
});
$scope.busyHeader = new StickyHeader('#busyHeader', {
customLeftAlignment: true,
onClick: 'scrollToOrigin'
});
$scope.reviewError = new StickyHeader('#checkout-review-order .errorDialogue', {
customLeftAlignment: true,
onClick: 'scrollToOrigin'
});
StickyHeaderStack.$stackProxy.addClass('ns-atomic');
}
}, 5000);
};
function stickyScroll() {
let sidebar = document.getElementById("checkoutSideBar");
if (sidebar) {
let sideBarTop = sidebar.getBoundingClientRect().top;
let paymentSection = document.getElementById("checkout-edit-payment");
let paymentBottom = paymentSection.getBoundingClientRect().bottom;
let adrSection = document.getElementById("checkout-adrOptions");
let adrBottom = adrSection.getBoundingClientRect().bottom;
let lastSectionBottom = paymentBottom;
let body = document.getElementsByTagName('body')[0];
let viewWidth = body.offsetWidth;
let viewHeight = window.innerHeight;
if ($scope.order.adr || $scope.adr) {
lastSectionBottom = adrBottom;
}
if (nuskin.util.MobileUtils.isMobileLike()) {
sidebar.removeAttribute('style');
if (viewHeight < viewWidth && viewWidth >= 767) {
$scope.stickySideBar.value = false;
} else if ((viewHeight - 60) < sideBarTop && !$scope.stickySideBar.value) {
$scope.stickySideBar.value = true;
} else if (lastSectionBottom < (viewHeight - 70)) {
$scope.stickySideBar.value = false;
$scope.slideUpOrderReview = false;
}
if ($scope.$$phase != '$apply' && $scope.$$phase != '$digest') {
$scope.$apply();
}
}
}
}
if (!$scope.config.checkout.turnOffStickySideBar) {
$window.addEventListener('scroll', (e) => {
stickyScroll();
});
$window.addEventListener('touchmove', (e) => {
stickyScroll();
});
$window.addEventListener('resize', () => {
stickyScroll();
});
$window.addEventListener('orientationchange', () => {
setTimeout(() => {
stickyScroll();
}, 500);
});
$scope.$watch('sections.payment.edit', () => {
// This is waiting for an animation to complete
setTimeout(() => {
stickyScroll();
}, 700);
});
}
$scope.$watch('slideUpOrderReview', () => {
let body = document.getElementsByTagName("body")[0];
if ($scope.slideUpOrderReview) {
body.classList.add('overflow-none');
} else {
body.classList.remove('overflow-none');
}
});
$scope.processBackToCart = function () {
$('#mobile-processing').show();
if(StickyHeaderStack) {
StickyHeaderStack.remove();
}
sessionStorage.setItem('appChangeSpinner', true);
if ($scope.showPointsPaymentOptions) {
resetPointsAllocations();
}
if (isSummerPromo()) {
window.location = document.referrer;
} else {
CartService.goToCartPage($scope.getCartParams());
}
};
$scope.onLoadTerms = function () {
util.sanitizeEmbeddedPage();
};
$scope.updateUserEmailOptInStatus = function () {
//console.log("Email Opt In = " + $scope.emailOptIn);
var userId = user.id;
var countryCode = $scope.runConfig.country;
try {
var dataObject = {"ID":userId, "EmailStatus": "normal", "CountryCode": countryCode};
var extendedAccountUrl = util.getValue("ConfigService.getMarketConfig().nsurl_extendedAccount", "/account-service/api/v1/account/extended/");
$.ajax({
url: extendedAccountUrl + userId + "?eid=" + user.eid,
type: "PUT",
data: JSON.stringify(dataObject),
crossDomain: true,
withCredentials: true,
contentType: "application/json",
headers: {
"client_id": ConfigService.getMarketConfig().checkout.clientId,
"client_secret": ConfigService.getMarketConfig().checkout.clientSecret
},
success: function () {
console.log("Email Opt In flag updated successfully.");
},
error: function () {
console.log("Error updating email opt in flag.");
}
});
} catch (err) {
}
};
//stuff for payWithPoints
$scope.showPointsBalance = function () {
return (CartService.getCartInfo().totalOrderPoints > 0);
};
$scope.pointsBalance = function () {
var usedPoints = CartService.getCartInfo().totalOrderPoints;
return $scope.remainingUserPoints(usedPoints);
};
$scope.remainingUserPoints = function (usedPoints) {
var remainingPoints = 0;
if (!$scope.isGuestCheckout && user) {
var userAvailablePoints = user.availablePoints;
remainingPoints = userAvailablePoints - usedPoints;
}
return remainingPoints;
};
// If the selected payment type changes then we need to update installments
$scope.$watch('order.selectedPayment.ofsId', (newValue, oldValue) => {
if (newValue !== oldValue) {
updateAvailableInstallments();
}
});
// If the order price changes then we need to update installments
$scope.$watch('order.orderTotals.itemSubtotal', (newValue, oldValue) => {
if (newValue != oldValue) {
updateAvailableInstallments();
}
});
async function updateAvailableInstallments() {
if ($scope.config.checkout.enableZoozInstallments && $scope.order.selectedPayment && $scope.order.selectedPayment.useInstallments) {
$scope.transBankInstallmentsBusy = true;
try {
let installments = await PaymentService.getInstallmentsForPaymentType($scope.order.orderTotals.itemSubtotal, $scope.order.selectedPayment.getPaymentTypeId());
if (installments) {
$scope.availableInstallments = installments;
// If no installment selected or selected installment not in options, default to first option
if (installments.length > 0 &&
!installments.includes($scope.order.selectedPayment.installmentSelectedNumber)) {
$scope.order.selectedPayment.installmentSelectedNumber = installments[0];
}
$scope.transBankInstallmentsBusy = false;
await updateInstallmentRates();
apply();
} else {
apply(() => {
$scope.transBankErrorMessage = $scope.tdc.transBankServiceError;
$scope.order.selectedPayment.useInstallments = false;
$scope.transBankInstallmentsBusy = false;
});
}
} catch(err) {
console.error(err);
apply(() => {
$scope.transBankErrorMessage = $scope.tdc.transBankServiceError;
$scope.order.selectedPayment.useInstallments = false;
$scope.transBankInstallmentsBusy = false;
});
}
}
}
async function updateInstallmentRates() {
if ($scope.config.checkout.enableZoozInstallments && $scope.order.selectedPayment && $scope.order.selectedPayment.useInstallments) {
let amount = $scope.order.orderTotals.itemSubtotal;
let paymentTypeId = $scope.order.selectedPayment.getPaymentTypeId();
let installmentNumber = $scope.order.selectedPayment.installmentSelectedNumber;
let pricing = await PaymentService.getPricingForInstallmentCount(amount, paymentTypeId, installmentNumber);
if (pricing) {
apply(() => {
$scope.order.orderTotals.tea = pricing.additionalInfo.tea;
$scope.order.orderTotals.cft = pricing.additionalInfo.cft;
$scope.installmentPaymentsOfLabel = $scope.tdc.installmentPaymentsOfLabel ? $scope.tdc.installmentPaymentsOfLabel.replace('{numPayments}', $scope.order.selectedPayment.installmentSelectedNumber) : "";
});
}
}
}
$scope.clearInstallments = function () {
try {
if ($scope.runConfig.country == 'AR' || $scope.order.adr) {
$scope.order.selectedPayment.setInstallmentSelectedNumber(0);
$scope.order.selectedPayment.useInstallments = false;
}
} catch(err) {}
};
$scope.useInstallmentsChanged = function() {
if ($scope.order.selectedPayment.getUseInstallments()) {
if ($scope.config.checkout.enableTransBankInstallments) {
getTransBankInstallments();
} else if ($scope.config.checkout.enableZoozInstallments) {
//clear out the previous installment if SAP doesn't return installment then we shouldn't keep the previous installmentFee
$scope.order.orderTotals.installmentFee = null;
$scope.order.orderTotals.tem = null;
updateAvailableInstallments().then(() => {
$scope.simulateOrder();
}).catch((err) => {
console.log(err)
});
} else {
$scope.order.selectedPayment.installmentSelectedNumber = 0;
$scope.order.orderTotals.installmentFee = null;
$scope.order.orderTotals.installmentTem = null;
$scope.order.orderTotals.installmentCft = null;
$scope.order.orderTotals.installmentTea = null;
}
}
};
$scope.installmentNumChanged = function () {
try {
if ($scope.config.checkout.enableTransBankInstallments) {
if ($.isEmptyObject($scope.order.selectedPayment.availableInstallmentPeriods)) {
getTransBankInstallments();
}
} else if ($scope.config.checkout.enableZoozInstallments) {
//clear out the previous installment if SAP doesn't return installment then we shouldn't keep the previous installmentFee
$scope.order.orderTotals.installmentFee = null;
$scope.order.orderTotals.installmentTem = null;
$scope.order.orderTotals.installmentCft = null;
$scope.order.orderTotals.installmentTea = null;
$scope.simulateOrder();
updateInstallmentRates();
} else if ($scope.config.checkout.simulateWithInstallments) {
$scope.order.orderTotals.installmentFee = null;
$scope.order.orderTotals.installmentTem = null;
$scope.order.orderTotals.installmentCft = null;
$scope.order.orderTotals.installmentTea = null;
$scope.simulateOrder();
if ($scope.runConfig.country === "AR" && !$scope.order.orderTotals.installmentTem) {
updateTEM();
}
}
} catch(err) {}
};
// Delete this function when we get the tem from sap this is just a set of default values for now
function updateTEM() {
switch ($scope.order.selectedPayment.installmentSelectedNumber) {
case 3:
$scope.order.orderTotals.installmentTem = 6.6553;
break;
case 6:
$scope.order.orderTotals.installmentTem = 8.0170;
break;
case 9:
$scope.order.orderTotals.installmentTem = 7.2857;
break;
case 12:
$scope.order.orderTotals.installmentTem = 6.2262;
break;
case 18:
$scope.order.orderTotals.installmentTem = 5.1047;
break;
case 24:
$scope.order.orderTotals.installmentTem = 4.3591;
break;
}
}
$scope.showingSpinner = function() {
return $scope.isBusy || ($scope.showSpinner && !$scope.contentLoaded);
};
function getTransBankInstallments() {
$scope.transBankErrorMessage = "";
if ($scope.order.selectedPayment.useInstallments && $scope.order.selectedPayment && $scope.order.paymentSecurityNumber) {
$scope.transBankInstallmentsBusy = true;
var selectedPayment = $scope.order.selectedPayment.toJSON();
var country = $scope.runConfig.country === "CL" ? "CHL" : $scope.runConfig.country;
var config = ConfigService.getMarketConfig();
var transBankUrl = "/external-payment/api/v1/pspInstallments";
var dataObject = {
cardNumber: selectedPayment.paymentNumber,
cvv: $scope.order.paymentSecurityNumber,
expirationDate: selectedPayment.expYear + selectedPayment.expMonth,
productNumber: TransBankPaymentAdapter.adaptTransBankPayment(selectedPayment.paymentTypeId),
amount: parseInt($scope.order.orderTotals.grandTotal),
currency: CurrencyCodeToNumber.getNumber($scope.order.currencyCode),
country: country
};
$.ajax({
type: "PUT",
url: transBankUrl,
data: JSON.stringify(dataObject),
headers: {
"client_id": config.checkout.clientId,
"client_secret": config.checkout.clientSecret
},
contentType: "application/json",
success: setupTransBankInstallments,
error: function(xhr) {
console.error("Trans Bank Installment Error: " , xhr);
$scope.transBankErrorMessage = $scope.tdc.transBankServiceError;
$scope.order.selectedPayment.useInstallments = false;
$scope.transBankInstallmentsBusy = false;
apply();
}
});
} else {
$scope.order.selectedPayment.useInstallments = false;
// $scope.forms.ccvform.ccv.$setDirty();
// $scope.forms.ccvform.ccv.$validate();
}
}
function setupTransBankInstallments(installmentData) {
if ($.isEmptyObject(installmentData)){
$scope.transBankErrorMessage = $scope.tdc.transBankNoApprovedInstallments;
} else {
$scope.transBankErrorMessage = "";
$scope.order.selectedPayment.availableInstallmentPeriods = installmentData;
$scope.order.selectedPayment.installmentSelectedNumber = installmentData[0];
}
$scope.transBankInstallmentsBusy = false;
apply();
}
$scope.hideBackToCartButton = function() {
return ($scope.sections.shipping.adding || $scope.sections.shipping.editing ||
$scope.sections.shipping.selecting || $scope.sections.payment.adding ||
$scope.sections.payment.editing);
};
$scope.isInstallmentPaymentType = function (p) {
if (p) {
switch (p.paymentTypeId) {
case PaymentType.DBS:
case PaymentType.UOB:
case PaymentType.MBB:
case PaymentType.PBB:
case PaymentType.CIMB:
case PaymentType.KBANK:
case PaymentType.BCA:
case PaymentType.BDO:
case PaymentType.APVN:
case PaymentType.CITI:
case PaymentType.HSBC:
return true;
}
}
return false;
};
// Pickup points and google maps related functionality.
$scope.showMarkerInfo = function(event, id) {
$scope.gmap.selectedMarker = $scope.gmap.markers[id];
$scope.gmap.instance.showInfoWindow('info', this);
};
$scope.updateMap = function() {
NgMap.getMap().then(function(map) {
$scope.gmap.instance = map;
$scope.gmap.markers = PickupUtil.markers($scope.order.pickupPoints);
let bounds = PickupUtil.bounds($scope.order.pickupPoints);
let center = bounds.getCenter();
$scope.gmap.instance.setCenter(center);
$scope.gmap.instance.fitBounds(bounds);
});
};
$scope.$watch(function() {
return $scope.showAvailablePickupPoints();
}, function(newValue, oldValue) {
if (newValue && !oldValue) {
$scope.updateMap();
}
});
$scope.showAvailablePickupPoints = function () {
let result = false;
if (nsUtil.has($scope.order, 'selectedShippingMethod.Code')) {
result = PickupUtil.supports($scope.order.selectedShippingMethod.Code)
&& Array.isArray($scope.order.pickupPoints)
&& $scope.order.pickupPoints.length > 0
&& !$scope.order.selectedPickupPoint
&& !$scope.sections.shipping.selecting
&& !$scope.sections.shipping.editing
&& !$scope.sections.shipping.adding
&& !$scope.isBusy
&& !$scope.editMode;
}
return result;
};
$scope.showNoPickupPointsFoundMessage = function () {
let result = false;
if (nsUtil.has($scope.order, 'selectedShippingMethod.Code')) {
result = PickupUtil.supports($scope.order.selectedShippingMethod.Code)
&& Array.isArray($scope.order.pickupPoints) && $scope.order.pickupPoints.length === 0
&& !$scope.order.selectedPickupPoint
&& !$scope.sections.shipping.selecting
&& !$scope.sections.shipping.editing
&& !$scope.sections.shipping.adding
&& !$scope.isBusy
&& !$scope.editMode;
}
return result;
};
$scope.showSelectedPickupPoint = function () {
let result = false;
if (nsUtil.has($scope.order, 'selectedShippingMethod.Code')) {
result = PickupUtil.supports($scope.order.selectedShippingMethod.Code)
&& $scope.order.selectedPickupPoint
&& !$scope.sections.shipping.selecting
&& !$scope.sections.shipping.selecting
&& !$scope.sections.shipping.editing
&& !$scope.sections.shipping.adding
&& !$scope.isBusy
&& !$scope.editMode;
}
return result;
};
$scope.clearSelectedPickupPoint = function () {
$scope.order.selectedPickupPoint = null;
OrderManager.saveOrders();
};
$scope.selectPickupPoint = function (index) {
$scope.order.selectedPickupPoint = $scope.order.pickupPoints[index];
OrderManager.saveOrders();
};
$scope.shouldSelectPickupPoint = function () {
let result = false;
if (nsUtil.has($scope.order, 'selectedShippingMethod.Code')) {
result = PickupUtil.supports($scope.order.selectedShippingMethod.Code)
&& !$scope.order.selectedPickupPoint;
}
return result;
};
$scope.showPickupList = function() {
$scope.pickupListSelected = true;
$scope.pickupMapSelected = false;
};
$scope.showPickupMap = function() {
$scope.pickupListSelected = false;
$scope.pickupMapSelected = true;
};
// COM-2043: GA Event to fire when shipping section is completed
$scope.$watch('sections.payment.pristine', () => {
// If both shipping and billing sections are dirty - then we know the shipping is complete
if (!$scope.sections.payment.pristine && !$scope.sections.shipping.pristine) {
$scope.gtmProducts = convertCartItemsToGtmProducts(CartService.getItemData({cartItems: true}));
events.publish(events.shop.CART_CHECKOUT, {
'ecommerce': {
'currencyCode': $scope.order.currencyCode,
'checkout': {
'actionField': {'step': 3, 'option': ''},
'products': $scope.gtmProducts
}
}
});
}
});
// COM-2043: GA Event to fire when payment section is completed
$scope.$watch('sections.review.pristine', () => {
// If both shipping and billing sections are dirty - then we know the shipping is complete
if (!$scope.sections.review.pristine && !$scope.sections.payment.pristine) {
events.publish(events.shop.CART_CHECKOUT, {
'ecommerce': {
'currencyCode': $scope.order.currencyCode,
'checkout': {
'actionField': {'step': 4, 'option': $scope.order.selectedPayment.paymentTypeDescription},
'products': $scope.gtmProducts
}
}