UNPKG

@nuskin/ns-checkout

Version:

Ecomm3 Checkout module

1,252 lines (1,108 loc) • 76.6 kB
'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 } }