1'use strict'; 2 3var checkoutApp = angular.module('checkoutApp', ['rain.form.validate', 'ngSanitize']).config(function ($sceDelegateProvider) { 4 $sceDelegateProvider.resourceUrlWhitelist([ 5 'self', 6 "https://secure.networkmerchants.com**", 7 "https://**.paypal.com/cgi-bin/webscr", 8 "https://www.my-davelocal.com/**", 9 "https://www.my-quiltstorewebsites.com/**", 10 "https://certtransaction.hostedpayments.com/**", 11 "https://transaction.hostedpayments.com/**" 12 ]); 13}); 14 15checkoutApp.controller('checkoutCtrl', ['$scope', '$http', '$window', '$timeout', '$interval', '$q', function ($scope, $http, $window, $timeout, $interval, $q) { 16 $scope.DATA_STATE_NONE = 0; 17 $scope.DATA_STATE_LOADING = 1; 18 $scope.DATA_STATE_NOT_SUPPORTED = 2; 19 $scope.DATA_STATE_ERROR = 3; 20 $scope.DATA_STATE_LOADED = 4; 21 $scope.geoLocationZipCode = ''; 22 23 $scope.PROCESSOR_NMI = 'NMI3S'; 24 $scope.PROCESSOR_VANTIV = 'VantivHosted'; 25 $scope.PROCESSOR_RAIN = 'RAIN_PAYMENTS'; 26 $scope.PROCESSOR_QUILT_ADYEN = 'Q_ADYEN'; 27 $scope.PROCESSOR_PAYPAL = 'paypal'; 28 $scope.PROCESSOR_PAY_AT_PICKUP = 'pay_at_pickup'; 29 $scope.PROCESSOR_FREE = 'free'; 30 $scope.PROCESSOR_GIFT_CARD = 'gc'; 31 $scope.STRIPE_MIN = .5; 32 $scope.ITEM_TYPE_CARE_PLAN = 'care_plan'; 33 $scope.RETAIL_DELIVERY_FEE = typeof COLORADO_RETAIL_DELIVERY_FEE !== 'undefined' ? COLORADO_RETAIL_DELIVERY_FEE : 0; 34 35 // Flags 36 37 $scope.enhancedShippingAndStorePickupOptions = false; 38 $scope.shippingCalcState = ''; 39 $scope.shippingCalcZipCode = ''; 40 $scope.enhancedShippingInStorePickup = false; 41 $scope.enhancedShippingCurbsidePickup = false; 42 $scope.enhancedShippingInfo = {}; 43 $scope.enhancedShippingInfo.shippingOptions = {}; 44 $scope.enhancedShippingInfo.storePickupOptions = {}; 45 $scope.enhancedShippingInfo.curbsidePickupOptions = {}; 46 $scope.enhancedShippingInfo.schoolPickupOptions = {}; 47 $scope.enhancedShippingInfo.localDeliveryOptions = {}; 48 $scope.Class = {}; 49 $scope.InitInfo = { 50 InternationalEnabled: false, 51 }; 52 $scope.forms = {}; 53 $scope.StatusInfo = {}; 54 $scope.StatusInfo.Initializing = true; 55 $scope.StatusInfo.CouponShow = false; 56 $scope.StatusInfo.GiftCardShow = false; 57 $scope.StatusInfo.IsCouponEntered = false; 58 $scope.StatusInfo.processing = false; 59 60 $scope.Processing = false; 61 62 $scope.CartData = {}; 63 $scope.CartStatus = {}; 64 $scope.CartStatus.Valid = true; 65 $scope.InventoryMessages = {}; 66 $scope.AvailabilityMessages = {}; 67 68 $scope.ContactInfo = {}; 69 $scope.ContactStatus = {}; 70 71 $scope.ShippingInfo = {}; 72 $scope.HideBillingSameAsShipping = false; 73 $scope.ShippingInfoPrefilledFlags = { 74 Address1: false, 75 Address2: false, 76 City: false, 77 State: false, 78 Zip: false, 79 Country: false 80 }; 81 $scope.ShippingInfo.State = ""; 82 $scope.ShippingStatus = {}; 83 $scope.ShippingStatus.Required = true; 84 $scope.ShippingStatus.Local = true; 85 $scope.ShippingStatus.OptionsUpdating = 0; 86 $scope.ShippingDiscount = 0; 87 $scope.ShippingInfo.shipToSchool = false; 88 $scope.ShippingInfo.selectedDistrict = null; 89 $scope.ShippingInfo.selectedSchool = null; 90 $scope.districts = []; 91 $scope.schools = []; 92 $scope.allSchools = []; 93 94 $scope.CardInfo = {}; 95 $scope.CardStatus = {}; 96 $scope.Rain = {}; 97 $scope.Adyen = {}; 98 99 $scope.BillingInfo = {}; 100 $scope.BillingInfo.AddressSameAsShipping = true; 101 $scope.BillingStatus = {}; 102 $scope.BillingStatus.Required = true; 103 104 $scope.BillingAddress = {}; 105 $scope.BillingAddress.Required = false; 106 107 $scope.giftCardOrCouponDataState = $scope.DATA_STATE_NONE; 108 $scope.giftCardOrCouponCode = ''; 109 $scope.giftCardOrCoupon = []; 110 $scope.giftCardCouponMsg = ''; 111 $scope.giftCardCouponMsgTimeout = null; 112 $scope.giftCardOrCouponRemovalMap = {}; 113 114 $scope.CheckoutInfo = {}; 115 $scope.CheckoutInfo.sendTextNotifications = false; 116 117 $scope.GiftMessage = {}; 118 $scope.orderNotes = {}; 119 $scope.JurisdictionalTax = ""; 120 $scope.JurisdictionalTaxes = {}; 121 $scope.shipping_tax_jurisdictions = {}; 122 $scope.taxable_sales = 0; 123 $scope.non_taxable_sales = 0; 124 $scope.non_sales = 0; 125 $scope.HasDynamicTaxes = false;
126 $scope.dynamicRateCache = null; 127 $scope.dynamicRateCacheKey = null; 128 $scope.status = {}; 129 $scope.showCaptcha = null; 130 $scope.GST_Taxes = 0; 131 $scope.GST_SubTotal = 0; 132 $scope.GST_Due = 0; 133 $scope.isTestStore = false; 134 $scope.ShippingSubtotal = 0.00; 135 $scope.defaultOptions = {}; 136 $scope.options = {}; 137 $scope.dynamicTotalTax = 0; 138 $scope.knobby = {}; 139 $scope.tax_service = 0; 140 $scope.couponStatusMessage = ''; 141 $scope.domainDisabled = false; 142 $scope.subscribeToMarketingEmails = false; 143 $scope.isMobileBrowser = 0; 144 $scope.scheduledPaymentId = 0; 145 $scope.subscriptionId = 0; 146 $scope.invoiceId = 0; 147 $scope.invoicesCartId = 0; 148 $scope.rtoCartId = null; 149 $scope.customerId = 0; 150 $scope.paymentToken = null; 151 $scope.paymentType = null; 152 $scope.paymentDueDate = null; 153 $scope.mmsAmount = null; 154 $scope.dynamicTaxesPromiseAccumulator = null; 155 $scope.dynamicTaxesReady = true; 156 $scope.jmInsurance = {}; 157 $scope.confirmedJMInsurance = false; 158 $scope.contactJMInsuranceInfo = {}; 159 $scope.retailDeliveryFeeTaxJurisdiction = {}; 160 $scope.digitalOrder = false; 161 162 $scope.retailDeliveryFee = 0.00; 163 164 $scope.isInsuranceSwitchedOn = false; 165 $scope.isLuxsuranceSwitchedOn = false; 166 $scope.recaptchaToken = null; 167 $scope.customerCards = []; 168 $scope.formData = { 169 selectedCustomerCardId: null, 170 }; 171 $scope.paymentMode = 'manual'; 172 $scope.isShippingOptionsProvided = true; 173 $scope.isLocalDeliveryOptionsProvided = true; 174 175 $scope.safeApply = function() { 176 var phase = this.$root.$$phase; 177 178 if (phase !== '$apply' && phase !== '$digest') { 179 this.$apply(); 180 } 181 }; 182 183 /** 184 * Model for registering stripe payment request(apple/google pay) 185 * @type {?PaymentRequestModel} 186 */ 187 $scope.paymentRequestModel = null; 188 189 $scope.isRequestPaymentSupported = false; 190 191 $scope.getPaymentRequestModel = function () { 192 if (!$scope.paymentRequestModel) { 193 if (typeof PaymentRequestModel !== 'undefined' && typeof CheckoutContextWrapper !== 'undefined') { 194 $scope.paymentRequestModel = new PaymentRequestModel(new CheckoutContextWrapper($scope)); 195 } 196 } 197 198 return $scope.paymentRequestModel; 199 }; 200 201 $scope.showPaypalButtonOnCart = function () { 202 if (!$scope.CartData.Items) { 203 return false; 204 } 205 206 if ($scope.isCardProcessorRain() && $scope.isBNPLEnabled()) { 207 // Show paypal button in express checkout block 208 return false; 209 } 210 211 var isSupportedProcessor = $scope.allowCreditCardCheckout() || $scope.isCardProcessorVantiv() || $scope.isCardProcessorRain() || $scope.isCardProcessorQuiltAdyen(); 212 213 return isSupportedProcessor && $scope.allowPayPalCheckout() && !$scope.isCardOnFileNeeded(); 214 }; 215 216 $scope.showExpressCheckout = function () { 217 return !$scope.checkFlagDisableExpressCheckout() && $scope.showMainCheckout() && $scope.isRequestPaymentSupported; 218 }; 219 220 $scope.paymentMethod = 'cc'; 221 222 $scope.onPaymentMethodChanged = function (paymentMethod) { 223 $scope.paymentMethod = paymentMethod; 224 225 if ($scope.paymentMethod === 'pickup') { 226 $scope.payAtPickupSelected(); 227 } else { 228 $scope.payWithCardSelected() 229 } 230 }; 231 232 /** 233 * @type {StripeBNPLModel|null} 234 */ 235 $scope.stripeBNPLModel = null; 236 237 $scope.isAffirmEnabled = function() { 238 return $scope.isBNPLEnabled('affirm'); 239 }; 240 241 $scope.isBNPLEnabled = function (type) { 242 if (!$scope.CartData || !$scope.CartData.Items || $scope.isWebPayment()) { 243 return false; 244 } 245 246 var hasNotSupportedItems = $scope.getItemsWithPackageItems().some(function (item) { 247 if (['class', 'rto', 'subscription', 'rental', 'gc'].indexOf(item.type) !== -1) { 248 return true; 249 } 250 251 if ($scope.digitalOrder) { 252 return false 253 } 254 255 if (item.type === 'pslr' && item.downloadable) { 256 return true; 257 } 258 259 return false;
260 }); 261 262 if (!$scope.isCardProcessorRain() || $scope.isWebPayment() || hasNotSupportedItems) { 263 return false; 264 } 265 266 var bnplSettings = null; 267 if ($scope.InitInfo && $scope.InitInfo.CheckoutSettings && $scope.InitInfo.CheckoutSettings.BNPL) { 268 bnplSettings = $scope.InitInfo.CheckoutSettings.BNPL; 269 } 270 271 if (!type) { 272 return Object.entries(bnplSettings).some(function (entry) { 273 return entry[1]; 274 }); 275 } 276 277 if (!bnplSettings || !$scope.stripeBNPLModel) { 278 return false; 279 } 280 281 return !!bnplSettings[type]; 282 }; 283 284 $scope.getBNPLAmount = function () { 285 var amountDue = $scope.getAmountDue(); 286 287 if (typeof amountDue !== 'number' || isNaN(amountDue)) { 288 return 0; 289 } 290 291 return parseInt((amountDue * 100).toFixed(0)); 292 }; 293 294 $scope.getBNPLCurrency = function () { 295 var currency = 'usd'; 296 297 if ($scope.InitInfo && $scope.InitInfo.Currency && typeof $scope.InitInfo.Currency === 'string') { 298 currency = $scope.InitInfo.Currency.toLowerCase(); 299 } 300 301 return currency; 302 }; 303 304 $scope.getBNPLCountry = function () { 305 var country = 'US'; 306 307 if ($scope.BillingInfo && $scope.BillingInfo.CountryIso2Key) { 308 country = $scope.BillingInfo.CountryIso2Key || country; 309 } 310 311 return country.toUpperCase(); 312 }; 313 314 $scope.getBNPLMerchantCountry = function () { 315 var merchantCountryIso = $scope.CountryIso2KeyGet($scope.InitInfo.DomesticCountry); 316 317 var merchantCountry = 'US'; 318 if (merchantCountryIso && typeof merchantCountryIso === 'string') { 319 merchantCountry = merchantCountryIso.toUpperCase(); 320 } 321 322 return merchantCountry; 323 }; 324 325 $scope.onBNPL = function (type, checkoutOptions) { 326 checkoutOptions = checkoutOptions || {}; 327 var checkCaptcha = function () { 328 if (!$scope.isMinimumAmountMet()) { 329 return Promise.reject('Minimum Amount'); 330 } 331 332 if ($scope.showCaptcha) { 333 if ($scope.knobby && $scope.knobby.module_update_to_captcha_v3) { 334 var authToken = $scope.InitInfo.store_token; 335 var url = $scope.RainApiUrl + 'store/captcha/validate'; 336 var headers = {}; 337 if (authToken) { 338 headers.Authorization = 'Bearer ' + authToken; 339 } 340 341 return new Promise(function(resolve, reject) { 342 grecaptcha.ready(function() { 343 grecaptcha.execute(window.recaptchaSiteKey, {action: 'checkout'}) 344 .then(function(token) { 345 $http.post(url, {token: token}, {headers: headers}) 346 .then(function() { 347 $scope.showCaptcha = false; 348 349 resolve(token); 350 }) 351 .catch(function(response) { 352 $scope.showCaptcha = true; 353 354 $.growlUI('Error <i class="fa fa-exclamation text-danger"></i>', response.data.error); 355 356 reject(response.data.error); 357 }); 358 }); 359 }); 360 }); 361 } 362 363 return $http.get($scope.BuildUrl('/captcha/check/' + window.captchaResponse), {withCredentials: true}).then(function (result) { 364 var status = result.data.success === true; 365 366 $scope.StatusInfo.ErrorMessageBottom = status ? null : "Please click the button that says, \"I'm not a robot\", to continue."; 367 $scope.showCaptcha = !status; 368 369 if (!status) { 370 recaptchaOnload(); 371 } 372 373 return status ? Promise.resolve() : Promise.reject('Wrong Captcha'); 374 }); 375 } 376 377 return Promise.resolve(); 378 }; 379 380 var vaidate = function () { 381 $scope.resetPayAtPickup(); 382 383 if ($scope.forms.contact) { 384 $scope.$broadcast('form-check-validity'); 385
386 if ($scope.forms.contact.$invalid) { 387 return Promise.reject('Invalid Contact Form'); 388 } 389 } 390 391 if ($scope.contactForm && $scope.showContactInfo()) { 392 $scope.contactForm.$setSubmitted(); 393 if ($scope.contactForm.$invalid) { 394 return Promise.reject('Invalid Contact Form'); 395 } 396 } 397 398 if ($scope.shippingForm && $scope.isShippingRequired() && !$scope.isPickup()) { 399 $scope.ShippingStatus.Submitted = true; 400 $scope.shippingForm.$setSubmitted(); 401 402 if ($scope.shippingForm.$invalid) { 403 return Promise.reject('Invalid Valid Form'); 404 } 405 } 406 407 $scope.BillingAddressVerify(); 408 409 if ($scope.billingForm && $scope.isBillingRequired() && !$scope.isPayAtPickup()) { 410 $scope.billingForm.$setSubmitted(); 411 if ($scope.billingForm.$invalid) { 412 return Promise.reject('Invalid Billing Address'); 413 } 414 } else if ($scope.billingForm && $scope.isTermsRequired()) { 415 if (!$scope.BillingInfo.TermsAgree) { 416 $scope.billingForm.$setSubmitted(); 417 return Promise.reject('Invalid Terms Agree'); 418 } 419 } 420 421 $scope.Validate(); 422 423 $scope.ShippingStatus.MessagesShow = false; 424 425 if (!$scope.ShippingStatus.Valid) { 426 $scope.ShippingStatus.MessagesShow = true; 427 428 return Promise.reject('ShippingStatus is not valid'); 429 } 430 431 var contactValid = !$scope.skipBilling() ? $scope.ContactStatus.Valid : true; 432 433 if (!contactValid) { 434 $scope.ContactStatus.MessagesShow = true; 435 436 return Promise.reject('Contact Status is no valid'); 437 } 438 439 $scope.SessionUpdate(); 440 441 if (!$scope.skipBilling() && ($scope.isResponsive() || $scope.isNewUIFlagSet())) { 442 $scope.ShippingStatus.Entered = true; 443 $scope.BillingEdit(); 444 445 // return Promise.reject('Billing Edit'); 446 } 447 448 return Promise.resolve(); 449 }; 450 451 var payment_method_types = { 452 afterpay: 'afterpay_clearpay', 453 }; 454 455 if ($scope.checkFlagBlockCheckoutOnDynamicTaxFailure()) { 456 var prepareCheckout = function () { 457 if (checkoutOptions.taxesVerified) { 458 if ($scope.HasDynamicTaxes && $scope.isDynamicTaxServiceEnabled()) { 459 $scope.TotalsCalculate(); 460 $scope.CartVerify(); 461 $scope.BillingVerify(); 462 } 463 464 return Promise.resolve(); 465 } 466 467 return checkCaptcha().then(function () { 468 return vaidate(); 469 }); 470 }; 471 472 return prepareCheckout() 473 .then(function () { 474 if (checkoutOptions.taxesVerified) { 475 return Promise.resolve(); 476 } 477 478 var freshTaxesPromise = $scope.ensureFreshDynamicTaxesForCheckout(); 479 480 if (!freshTaxesPromise) { 481 return Promise.resolve(); 482 } 483 484 $scope.setProcessingCheckout(true); 485 486 return freshTaxesPromise.then(function () { 487 if ($scope.hasActiveDynamicTaxFailure()) { 488 $scope.setProcessingCheckout(false); 489 return Promise.reject(new Error($scope.StatusInfo.DynamicTaxError || $scope.StatusInfo.DynamicTaxAddressError)); 490 } 491 492 checkoutOptions.taxesVerified = true; 493 return prepareCheckout(); 494 }); 495 }) 496 .then(function () { 497 $scope.setProcessingCheckout(true); 498 $scope.makeTaxInclusive(); 499 500 if ($scope.hasActiveDynamicTaxFailure()) { 501 $scope.setProcessingCheckout(false); 502 return Promise.reject(new Error($scope.StatusInfo.DynamicTaxError || $scope.StatusInfo.DynamicTaxAddressError)); 503 } 504 505 return Promise.resolve(); 506 }) 507 .then(function () { 508 return $scope.PreProcess($scope.buildCheckoutPreProcessData()).then(function(id) { 509 if (!id) { 510 throw new Error('Unable to create transaction'); 511 } 512 }); 513 }) 514 .then(function () { 515 return $scope.SubmitInfoGet($scope.getSubmitInfoGetData(null, [payment_method_types[type] || type])); 516 }) 517 .then(function () { 518 var shippingAddress; 519 520 if ($scope.isPickup()) { 521 var optionAddress = $scope.ShippingInfo.Option.address; 522 523 shippingAddress = {
524 country: $scope.CountryIso2KeyGet($scope.InitInfo.Countries[optionAddress.country]), 525 state: optionAddress.state, 526 city: optionAddress.city, 527 line1: optionAddress.street1, 528 line2: optionAddress.street2, 529 postal_code: optionAddress.postalCode, 530 }; 531 } else { 532 shippingAddress = { 533 country: $scope.CountryIso2KeyGet($scope.ShippingInfo.Country), 534 state: $scope.ShippingInfo.State, 535 city: $scope.ShippingInfo.City, 536 line1: $scope.ShippingInfo.Address1, 537 line2: $scope.ShippingInfo.Address2, 538 postal_code: $scope.ShippingInfo.Zip, 539 }; 540 } 541 542 var billingName = [$scope.BillingInfo.FirstName, $scope.BillingInfo.LastName].join(' '); 543 var shippingName = $scope.isPickup() ? billingName : [$scope.ShippingInfo.FirstName, $scope.ShippingInfo.LastName].join(' '); 544 545 return $scope.stripeBNPLModel.getModel(type).confirmPayment( 546 $scope.TransactionId, 547 $scope.Rain.processorToken, 548 { 549 name: billingName, 550 email: $scope.BillingInfo.Email, 551 phone: $scope.BillingInfo.Phone, 552 address: { 553 country: $scope.CountryIso2KeyGet($scope.BillingInfo.Country), 554 state: $scope.BillingInfo.State, 555 city: $scope.BillingInfo.City, 556 line1: $scope.BillingInfo.Address1, 557 line2: $scope.BillingInfo.Address2, 558 postal_code: $scope.BillingInfo.Zip, 559 }, 560 }, 561 { 562 name: shippingName, 563 address: shippingAddress, 564 } 565 ); 566 }) 567 .catch(function (e) { 568 $scope.setProcessingCheckout(false); 569 $scope.setErrorMessageBottom(e.message || 'Error'); 570 571 $scope.safeApply(); 572 573 console.error(e); 574 }); 575 } 576 577 return checkCaptcha() 578 .then(function () { 579 return vaidate(); 580 }) 581 .then(function () { 582 $scope.setProcessingCheckout(true); 583 $scope.makeTaxInclusive(); 584 585 if (!$scope.dynamicTaxesReady) { 586 $scope.setErrorMessageBottom(null); 587 588 return $scope.updateDynamicTaxesOnce().then(function () { 589 if (!$scope.dynamicTaxesReady) { 590 return Promise.reject(new Error('Tax service is not responding, please wait a while and retry your order again.')); 591 } 592 593 return Promise.resolve(); 594 }); 595 } 596 597 return Promise.resolve(); 598 }) 599 .then(function () { 600 return $scope.PreProcess().then(function(id) { 601 if (!id) { 602 throw new Error('Unable to create transaction'); 603 } 604 }); 605 }) 606 .then(function () { 607 return $scope.SubmitInfoGet($scope.getSubmitInfoGetData(null, [payment_method_types[type] || type])); 608 }) 609 .then(function () { 610 var shippingAddress; 611 612 if ($scope.isPickup()) { 613 var optionAddress = $scope.ShippingInfo.Option.address; 614 615 shippingAddress = {
616 country: $scope.CountryIso2KeyGet($scope.InitInfo.Countries[optionAddress.country]), 617 state: optionAddress.state, 618 city: optionAddress.city, 619 line1: optionAddress.street1, 620 line2: optionAddress.street2, 621 postal_code: optionAddress.postalCode, 622 }; 623 } else { 624 shippingAddress = { 625 country: $scope.CountryIso2KeyGet($scope.ShippingInfo.Country), 626 state: $scope.ShippingInfo.State, 627 city: $scope.ShippingInfo.City, 628 line1: $scope.ShippingInfo.Address1, 629 line2: $scope.ShippingInfo.Address2, 630 postal_code: $scope.ShippingInfo.Zip, 631 }; 632 } 633 634 var billingName = [$scope.BillingInfo.FirstName, $scope.BillingInfo.LastName].join(' '); 635 var shippingName = $scope.isPickup() ? billingName : [$scope.ShippingInfo.FirstName, $scope.ShippingInfo.LastName].join(' '); 636 637 return $scope.stripeBNPLModel.getModel(type).confirmPayment( 638 $scope.TransactionId, 639 $scope.Rain.processorToken, 640 { 641 name: billingName, 642 email: $scope.BillingInfo.Email, 643 phone: $scope.BillingInfo.Phone, 644 address: { 645 country: $scope.CountryIso2KeyGet($scope.BillingInfo.Country), 646 state: $scope.BillingInfo.State, 647 city: $scope.BillingInfo.City, 648 line1: $scope.BillingInfo.Address1, 649 line2: $scope.BillingInfo.Address2, 650 postal_code: $scope.BillingInfo.Zip, 651 }, 652 }, 653 { 654 name: shippingName, 655 address: shippingAddress, 656 } 657 ); 658 }) 659 .catch(function (e) { 660 $scope.setProcessingCheckout(false); 661 $scope.setErrorMessageBottom(e.message || 'Error'); 662 663 $scope.safeApply(); 664 665 console.error(e); 666 }); 667 }; 668 669 // ****************************************************************************************** 670 // **** Initialization 671 // ****************************************************************************************** 672 673 $scope.NoOpPromise = function () { 674 var deferred = $q.defer(); 675 deferred.resolve(); 676 return deferred.promise; 677 }; 678 679 /** 680 * @return {number} 681 */ 682 $scope.RoundTo2Dec = function (value) { 683 684 return $scope.roundNumber(value, 2); 685 }; 686 687 $scope.roundNumber = function (value, precision) { 688 if (!value) { 689 return 0; 690 } 691 692 if (precision <= 0) { 693 precision = 0; 694 } else if (!precision) { 695 precision = 2; 696 } 697 698 if (precision < 10) { 699 var fixDecimalTailMultiplier = Math.pow(10, 10); 700 value = Math.round(value * fixDecimalTailMultiplier) / fixDecimalTailMultiplier; 701 } 702 703 /** 704 This one works correct: 705 > +(Math.round(2.175 + "e+" + 2) + "e-" + 2) 706 2.18 707 */ 708 return +(Math.round(value + "e+" + precision) + "e-" + precision); 709 }; 710 711 $scope.Init = function ( 712 wsUrl, 713 id, 714 geoLocationCountry, 715 geoLocationRegion, 716 geoLocationZipCode, 717 enhancedShippingAndStorePickupOptions, 718 options 719 ) { 720 $scope.WsUrl = wsUrl; 721 $scope.geoLocationZipCode = geoLocationZipCode; 722 723 if (window.domainDisabled && window.domainDisabled === true) { 724 $scope.domainDisabled = true; 725 } 726 if (window.scheduledPaymentId && +window.scheduledPaymentId > 0) { 727 $scope.scheduledPaymentId = +window.scheduledPaymentId; 728 } 729 if (window.invoiceId && +window.invoiceId > 0) { 730 $scope.invoiceId = +window.invoiceId; 731 } 732 if (window.invoicesCartId) { 733 $scope.invoicesCartId = window.invoicesCartId; 734 } 735 if (window.rtoCartId) { 736 $scope.rtoCartId = window.rtoCartId; 737 } 738 if (window.invoiceAmount && +window.invoiceAmount > 0) { 739 $scope.invoiceAmount = +window.invoiceAmount; 740 } 741 if (window.customerId && +window.customerId > 0) { 742 $scope.customerId = +window.customerId; 743 } 744 if (window.paymentToken) { 745 $scope.paymentToken = window.paymentToken; 746 } 747 if (window.paymentNonce) { 748 $scope.paymentNonce = window.paymentNonce; 749 } 750 $scope.BillingInfo.enrollAutoPay = false;
751 $scope.BillingInfo.addCardOnFile = false; 752 if (window.enrollAutoPay && window.enrollAutoPay == 1) { 753 $scope.BillingInfo.enrollAutoPay = true; 754 } 755 if (window.subscriptionId && +window.subscriptionId > 0) { 756 $scope.subscriptionId = +window.subscriptionId; 757 $scope.BillingInfo.enrollAutoPay = true; 758 } 759 if (window.paymentType) { 760 $scope.paymentType = window.paymentType; 761 } 762 if (window.paymentDueDate) { 763 $scope.paymentDueDate = window.paymentDueDate; 764 } 765 if (window.mmsAmount) { 766 $scope.mmsAmount = window.mmsAmount; 767 } 768 if (window.districts) { 769 $scope.districts = window.districts; 770 } 771 if (window.schools) { 772 $scope.allSchools = window.schools; 773 } 774 775 $scope.isMobileBrowser = window.isMobileBrowser; 776 777 // Internet Explorer: complicating JS since ever. Used this solution for determining isIE. 778 // https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser 779 let isIE = /*@cc_on!@*/false || !!document.documentMode; 780 if (isIE) { 781 for (let option in $scope.defaultOptions) { 782 if ($scope.defaultOptions.hasOwnProperty(option)) { 783 $scope.options[option] = $scope.defaultOptions[option]; 784 } 785 } 786 787 for (let option in options){ 788 if(options.hasOwnProperty(option)) { 789 $scope.options[option] = options[option]; 790 } 791 } 792 } else { 793 $scope.options = Object.assign({}, $scope.defaultOptions, options); 794 } 795 $scope.CartId = ''; 796 if (id) { 797 $scope.CartId = id; 798 } 799 800 $scope.enhancedShippingAndStorePickupOptions = enhancedShippingAndStorePickupOptions; 801 802 $scope.Class['visa'] = 'card-default'; 803 $scope.Class['mastercard'] = 'card-default'; 804 $scope.Class['amex'] = 'card-default'; 805 $scope.Class['discover'] = 'card-default'; 806 807 // Workaround for Safari bug causing selection of shipping calc zip code on focus to fail 808 try { 809 $("#shipping_calc_zip").mouseup(function (e) { 810 e.preventDefault(); 811 }); 812 } catch (err) { 813 // Consume and continue 814 } 815 816 $scope.ApiUrl = '/public/api'; 817 if (wsUrl) { 818 $scope.ApiUrl = wsUrl + $scope.ApiUrl; 819 } 820 $scope.StatusInfo.ErrorMessage = getQueryString('error_message'); 821 $scope.ExpirationOptionsGen(); 822 823 return $scope.InitInfoGet().then(function () { 824 if (geoLocationCountry && geoLocationRegion && (geoLocationCountry === 'US') && ($scope.InitInfo.DomesticCountry == geoLocationCountry || $scope.InitInfo.InternationalEnabled == true)) { 825 $scope.shippingCalcState = $scope.stateNameToAbbr(geoLocationRegion); 826 } 827 if (geoLocationZipCode) { 828 $scope.shippingCalcZipCode = geoLocationZipCode; 829 } 830 831 if ($scope.checkFlagRetailDeliveryFee() && !$scope.COLORADO_RETAIL_DELIVERY_FEE && $scope.InitInfo.COLORADO_RETAIL_DELIVERY_FEE) { 832 $scope.COLORADO_RETAIL_DELIVERY_FEE = $scope.InitInfo.COLORADO_RETAIL_DELIVERY_FEE; 833 } 834 835 $scope.setCustomerInfo(); 836 $scope.setWholesaleUserInfo(); 837 838 if ($scope.checkFlagCarePlans()) { 839 $scope.registerCarePlanHandleEvent(); 840 } 841 842 return $scope.CartDataGet().then(function () { 843 // Prefill shipping and customer information if we have subscription in cart 844 if ($scope.CartData && $scope.CartData.Items && $scope.CartData.Items.length > 0) { 845 var subscriptionItem = null; 846 $scope.updateDigitalOrder(); 847 $scope.CartData.Items.forEach(function (item) { 848 if (!subscriptionItem && 'subscription' === item.type) { 849 subscriptionItem = item; 850 } 851 }); 852 853 if (subscriptionItem && subscriptionItem.notes) { 854 $scope.ShippingInfo.FirstName = subscriptionItem.notes.firstName; 855 $scope.ShippingInfo.LastName = subscriptionItem.notes.lastName; 856 $scope.ShippingInfo.Address1 = subscriptionItem.notes.streetAddress1; 857 $scope.ShippingInfo.Address2 = subscriptionItem.notes.streetAddress2; 858 $scope.ShippingInfo.City = subscriptionItem.notes.city; 859 $scope.ShippingInfo.State = subscriptionItem.notes.stateCode 860 ? subscriptionItem.notes.stateCode 861 : subscriptionItem.notes.state; 862 $scope.ShippingInfo.Zip = subscriptionItem.notes.postalCode; 863 $scope.ShippingInfo.Country = subscriptionItem.notes.countryCode; 864 $scope.BillingInfo.Email = subscriptionItem.notes.email; 865 $scope.BillingInfo.Phone = subscriptionItem.notes.phone; 866 if (!subscriptionItem.notes.state.length) { 867 if (!$scope.ShippingInfo.Option) { 868 $scope.ShippingInfo.Option = {}; 869 } 870 if (!$scope.ShippingInfo.Options) { 871 $scope.ShippingInfo.Options = []; 872 } 873 $scope.ShippingInfo.Option.name = 'Store Pickup'; 874 if ($scope.ShippingInfo.Option.id) { 875 $scope.ShippingInfo.Options[$scope.ShippingInfo.Option.id] = $scope.ShippingInfo.Option; 876 } 877 } 878 879 $scope.BillingAddressVerify(); 880 } 881 } 882 $scope.StatusInfo.Initializing = false;
883 884 var shippingOptionsInit = function () { 885 return $scope.ShippingOptionsGet().then(function () { 886 $scope.setShippingOption(); 887 }).catch(function (e) { 888 // Ignore dynamic tax errors on init step 889 console.error(e); 890 }); 891 }; 892 893 return shippingOptionsInit(); 894 }); 895 }).then(function () { 896 var paymentRequestModel = $scope.getPaymentRequestModel(); 897 if (!paymentRequestModel) { 898 return Promise.resolve(); 899 } 900 901 return paymentRequestModel.init(); 902 }).then(function () { 903 if ($scope.isCardProcessorRain() && window.StripeBNPLModel && $scope.Rain.stripe && $scope.InitInfo && $scope.InitInfo.payLaterRedirectURL) { 904 $scope.stripeBNPLModel = new StripeBNPLModel( 905 $scope.Rain.stripe, 906 $scope.InitInfo.payLaterRedirectURL 907 ); 908 $scope.updateBNPLMessage(); 909 } 910 }).then(function () { 911 if (typeof gtag === 'undefined') { 912 $($scope.InitInfo.googleAnalyticsCode).appendTo(document.head); 913 } 914 }); 915 }; 916 917 $scope.BuildUrl = function (path, noCart, query) { 918 if (typeof noCart === 'undefined') { 919 noCart = false; 920 } 921 922 path = $scope.ApiUrl + path + (!noCart ? '/' : ''); 923 924 if (!noCart) { 925 path += $scope.CartId; 926 } 927 928 if (query) { 929 path += "?" + query; 930 } 931 932 return path; 933 }; 934 935 $scope.getDataWithDelay = function (callback) { 936 var timeToWaitForLoadData = 100; 937 938 window.localStorage.removeItem('loading-interrupted'); 939 940 return new Promise((resolve, reject) => { 941 setTimeout(() => { 942 resolve(callback()); 943 }, timeToWaitForLoadData); 944 }); 945 }; 946 947 $scope.isCarePlanEnable = function () { 948 return $scope.knobby && $scope.knobby.module_care_plans; 949 }; 950 951 $scope.InitInfoGet = function () { 952 var query = null; 953 var noCart = false; 954 955 if ($scope.isScheduledPayment()) { 956 query = 'spid=' + $scope.scheduledPaymentId + '&token=' + $scope.paymentToken; 957 } 958 959 if ($scope.isSubscriptionPayment()) { 960 query = 'sid=' + $scope.subscriptionId + '&token=' + $scope.paymentToken; 961 } 962 963 if ($scope.isInvoicePayment()) { 964 query = 'iid=' + $scope.invoiceId + '&token=' + $scope.paymentToken; 965 966 noCart = true; 967 } 968 969 if ($scope.isInvoicesPayment()) { 970 query = 'icid=' + $scope.invoicesCartId + '&token=' + $scope.paymentToken; 971 } 972 973 if ($scope.isRtoPayment()) { 974 query = 'rpid=' + $scope.rtoCartId + '&token=' + $scope.paymentToken; 975 } 976 977 if ($scope.isAddCardOnly()) { 978 query = 'cid=' + $scope.customerId + '&token=' + $scope.paymentToken; 979 } 980 981 if ($scope.isMmsPayment()) { 982 query = 'cid=' + $scope.customerId + '&date=' + $scope.paymentDueDate + '&amount=' + $scope.mmsAmount + '&token=' + $scope.paymentToken; 983 } 984 985 var options = {withCredentials: true}; 986 987 if (window.cartJWT) { 988 options.headers = { 989 Authorization: 'Bearer ' + window.cartJWT 990 }; 991 } 992 993 var requestCard = () => { 994 return $http.get($scope.BuildUrl('/checkout/initinfo', noCart, query), options); 995 }; 996 997 if (window.localStorage.getItem('loading-interrupted')) { 998 requestCard = () => { 999 return $scope.getDataWithDelay(() => { 1000 return $http.get($scope.BuildUrl('/checkout/initinfo', noCart, query), options); 1001 }); 1002 }; 1003 } 1004 1005 return requestCard().then(function (result) { 1006 if (result.data.initinfo) { 1007 $scope.InitInfo = result.data.initinfo; 1008 1009 $scope.RainApiUrl = $scope.InitInfo['RainApiUrl']; 1010 $scope.isTestStore = $scope.InitInfo['isTestStore']; 1011 1012 // Default Country 1013 var defaultCountry = $scope.ShippingInfo.Country; 1014 $scope.ShippingCountrySet(defaultCountry); 1015 1016 // Custom header 1017 if ($scope.InitInfo.CheckoutSettings.HeaderType === 'Custom' && $scope.InitInfo.CheckoutSettings.LogoHtml) { 1018 $('#customheader').html($scope.InitInfo.CheckoutSettings.LogoHtml); 1019 } 1020 1021 if ($scope.InitInfo.CurrencySymbol3) { 1022 $scope.InitInfo.CurrencySymbol2 = JSON.parse($scope.InitInfo.CurrencySymbol3); 1023 } 1024 1025 $scope.knobby = $scope.InitInfo.knobby; 1026 $scope.tax_service = $scope.InitInfo.tax_service; 1027 1028 if ($scope.isSavedCardsFlagEnabled() && $scope.customerId) { 1029 $scope.customerCards = Object.values($scope.InitInfo.customerCards || {}); 1030 } 1031 1032 if ($scope.isWebPayment()) { 1033 var processor = $scope.getCCProcessor(); 1034 if (processor) { 1035 $scope.setProcessor(processor); 1036 } 1037 if (!$scope.isWebPaymentDue() && !$scope.isAddCardOnly()) { 1038 $scope.setErrorMessageTop("This payment is paid - nothing due."); 1039 } 1040 } 1041 1042 $scope.initRainProcessing(); 1043 $scope.initAdyen(); 1044 $scope.updateProcessor(); 1045 } 1046 }); 1047 }; 1048 1049 $scope.getStoreUuid = function() { 1050 if ($scope.InitInfo && $scope.InitInfo.Store && $scope.InitInfo.Store.uuid) { 1051 return $scope.InitInfo && $scope.InitInfo.Store.uuid; 1052 } 1053 return ''; 1054 }; 1055 1056 $scope.getStoreCustomerServicePhone = function() { 1057 if ($scope.InitInfo && $scope.InitInfo.Store && $scope.InitInfo.Store) { 1058 return $scope.InitInfo && $scope.InitInfo.Store.phone; 1059 } 1060 return ''; 1061 }; 1062 1063 $scope.clearSavedCardSelection = function () { 1064 if (!$scope.formData?.selectedCustomerCardId) { 1065 return; 1066 } 1067 1068 $scope.formData.selectedCustomerCardId = null; 1069
1070 $scope.safeApply(); 1071 }; 1072 1073 $scope.initRainProcessing = function() { 1074 if ($scope.isCardProcessorRain() && $scope.InitInfo.Processors.cc_pk && typeof Stripe === 'function') { 1075 var stripeOptions = {betas: ['affirm_pilot_beta_1']}; 1076 $scope.Rain.stripe = Stripe($scope.InitInfo.Processors.cc_pk, stripeOptions); 1077 var elements = $scope.Rain.stripe.elements(); 1078 1079 $scope.Rain.card = elements.create('cardNumber', $scope.getRainCardStyles('card')); 1080 $scope.Rain.card.mount('#card-number'); 1081 $scope.Rain.card.on('change', $scope.checkRainPaymentError); 1082 1083 if ($scope.isSavedCardsFlagEnabled()) { 1084 $scope.Rain.card.on('focus', $scope.clearSavedCardSelection); 1085 } 1086 1087 $scope.Rain.exp = elements.create('cardExpiry', $scope.getRainCardStyles()); 1088 $scope.Rain.exp.mount('#card-exp'); 1089 $scope.Rain.exp.on('change', $scope.checkRainPaymentError); 1090 1091 if ($scope.isSavedCardsFlagEnabled()) { 1092 $scope.Rain.exp.on('focus', $scope.clearSavedCardSelection); 1093 } 1094 1095 $scope.Rain.cvv = elements.create('cardCvc', $scope.getRainCardStyles()); 1096 $scope.Rain.cvv.mount('#card-cvv'); 1097 $scope.Rain.cvv.on('change', $scope.checkRainPaymentError); 1098 1099 if ($scope.isSavedCardsFlagEnabled()) { 1100 $scope.Rain.cvv.on('focus', $scope.clearSavedCardSelection); 1101 } 1102 } 1103 }; 1104 1105 $scope.initAdyen = function() { 1106 let that = this; 1107 if (!$scope.checkFlagAdyenRegionFailover()) { 1108 if ($scope.isCardProcessorQuiltAdyen() && typeof AdyenCheckout === 'function') { 1109 let currency = window.currency_code; 1110 let clientKey = window.adyen_env ? 'live_C6KR4G2VQRAZPBUNDS7F7RTAZEWWRW27' : 'test_NLY6QP2DNZCW7BI3SNKBTJU6LUWEXBRP'; 1111 if (currency === 'CAD') { 1112 clientKey = window.adyen_env ? 'live_HWN53RBGFVASTJKFT2WPBJYKOARRXWTX' : 'test_ZV6G5SOS6JFNRAAAGNJ65JDDFUREPTAA'; 1113 } 1114 1115 const configuration = { 1116 environment: window.adyen_env ? 'live-us' : 'test', 1117 clientKey: clientKey, 1118 locale: 'en_US', 1119 onChange: that.handleAdyenChange, 1120 onError: (error, component) => { 1121 console.error(error.name, error.message, error.stack, component); 1122 $scope.logInvalidOriginError(error, 'initAdyen()'); 1123 } 1124 }; 1125 1126 AdyenCheckout(configuration).then(checkout => { 1127 that.checkout = checkout.create('securedfields', { 1128 // Optional configuration 1129 type: 'card',
1130 brands: ['mc', 'visa', 'amex', 'discover'], 1131 styles: { 1132 error: { 1133 color: 'red' 1134 }, 1135 validated: { 1136 color: 'green' 1137 } 1138 }, 1139 onBrand: data => { 1140 that.cardBrand = data.brand !== 'card' ? data.brand : ''; 1141 that.cardImage = data.brandImageUrl; 1142 }, 1143 onFocus: () => { 1144 if (!$scope.isSavedCardsFlagEnabled()) { 1145 return; 1146 } 1147 1148 if ($scope.paymentMode !== 'manual') { 1149 $scope.paymentMode = 'manual'; 1150 $scope.formData.selectedCustomerCardId = null; 1151 1152 $scope.safeApply(); 1153 } 1154 }, 1155 onError: data => { 1156 console.log('error'); 1157 console.log(data); 1158 }, 1159 }).mount('#adyen-container'); 1160 }); 1161 } 1162 } else { 1163 if ($scope.isCardProcessorQuiltAdyen()) { 1164 let currency = window.currency_code; 1165 let clientKey = window.adyen_env ? 'live_C6KR4G2VQRAZPBUNDS7F7RTAZEWWRW27' : 'test_NLY6QP2DNZCW7BI3SNKBTJU6LUWEXBRP'; 1166 if (currency === 'CAD') { 1167 clientKey = window.adyen_env ? 'live_HWN53RBGFVASTJKFT2WPBJYKOARRXWTX' : 'test_ZV6G5SOS6JFNRAAAGNJ65JDDFUREPTAA'; 1168 } 1169 1170 let testEnv = !window.adyen_env; 1171 let paymentEnv = testEnv ? 'test' : window.adyenRegion || 'live-us'; 1172 1173 const configuration = { 1174 environment: paymentEnv, 1175 clientKey: clientKey, 1176 locale: 'en_US', 1177 onChange: that.handleAdyenChange, 1178 onError: (error, component) => { 1179 console.error(error.name, error.message, error.stack, component); 1180 $scope.logInvalidOriginError(error, 'initAdyen()'); 1181 } 1182 }; 1183 1184 var intervalId = setInterval(waitAdyen, 20); 1185 function waitAdyen() { 1186 if (window.AdyenCheckout && window.adyenStyleLoaded) { 1187 clearInterval(intervalId); 1188 initalializeAdyen(); 1189 } 1190 } 1191 1192 function initalializeAdyen() { 1193 AdyenCheckout(configuration).then(checkout => { 1194 that.checkout = checkout.create('securedfields', { 1195 // Optional configuration 1196 type: 'card',
1197 brands: ['mc', 'visa', 'amex', 'discover'], 1198 styles: { 1199 error: { 1200 color: 'red' 1201 }, 1202 validated: { 1203 color: 'green' 1204 } 1205 }, 1206 onBrand: data => { 1207 that.cardBrand = data.brand !== 'card' ? data.brand : ''; 1208 that.cardImage = data.brandImageUrl; 1209 }, 1210 onFocus: () => { 1211 if (!$scope.isSavedCardsFlagEnabled()) { 1212 return; 1213 } 1214 1215 if ($scope.paymentMode !== 'manual') { 1216 $scope.paymentMode = 'manual'; 1217 $scope.formData.selectedCustomerCardId = null; 1218 1219 $scope.safeApply(); 1220 } 1221 }, 1222 onError: data => { 1223 console.log('error'); 1224 console.log(data); 1225 }, 1226 }).mount('#adyen-container'); 1227 }); 1228 } 1229 } 1230 } 1231 }; 1232 1233 $scope.handleAdyenChange = function(state) { 1234 console.log('Adyen State: ', state); 1235 $scope.Adyen.state = state; 1236 }; 1237 1238 $scope.isAdyenPaymentValid = function() { 1239 if ($scope.isSavedCardsFlagEnabled() && $scope.paymentMode === 'saved' && $scope.formData?.selectedCustomerCardId) { 1240 return true; 1241 } 1242 1243 return !!($scope.Adyen.state && $scope.Adyen.state.isValid); 1244 }; 1245 1246 $scope.getAdyenError = function() { 1247 if ($scope.isSavedCardsFlagEnabled() && $scope.paymentMode === 'saved') { 1248 if (!$scope.formData?.selectedCustomerCardId) { 1249 return 'Please select a saved card.'; 1250 } 1251 1252 return null; 1253 } 1254 1255 if (!$scope.isAdyenPaymentValid()) { 1256 if ($scope.Adyen.state && $scope.Adyen.state.valid) { 1257 if (!$scope.Adyen.state.valid.encryptedCardNumber) { 1258 return "Please enter a valid credit card number."; 1259 } 1260 if (!$scope.Adyen.state.valid.encryptedExpiryMonth || !$scope.Adyen.state.valid.encryptedExpiryYear) { 1261 return "Please enter a valid credit card expiration date."; 1262 } 1263 if (!$scope.Adyen.state.valid.encryptedSecurityCode) { 1264 return "Please enter a valid credit card CVV code."; 1265 } 1266 } 1267 return "Please complete the credit information before placing your order."; 1268 } 1269 }; 1270 1271 $scope.showOnlyHighestDiscountWarning = function() { 1272 let allCartItemsAreOnSale = false; 1273 if ($scope.CartData.Items && $scope.CartData.Items.length) { 1274 allCartItemsAreOnSale = $scope.CartData.Items.every(function(element) { 1275 return element?.on_sale === true; 1276 }); 1277 } 1278 1279 return $scope.InitInfo?.CheckoutSettings?.CouponStacking === 'applyHighestSaving' && 1280 !($scope.InitInfo?.CheckoutSettings?.DisableCouponsForSalePriceItems === true && allCartItemsAreOnSale); 1281 }; 1282 1283 $scope.showDontApplyOnSaleItemsWarning = function() { 1284 return $scope.InitInfo?.CheckoutSettings?.DisableCouponsForSalePriceItems === true; 1285 }; 1286 1287 $scope.isApplyHighestSavingCouponsMode = function() { 1288 return $scope.InitInfo?.CheckoutSettings?.CouponStacking === 'applyHighestSaving'; 1289 }; 1290 1291 $scope.updateBNPLMessage = function() { 1292 if (!$scope.isAffirmEnabled()) { 1293 return; 1294 } 1295 1296 var amountDue = $scope.getAmountDue(); 1297 var amountDueInCents = 0; 1298 if (typeof amountDue === 'number' && !isNaN(amountDue)) { 1299 amountDueInCents = parseInt((amountDue * 100).toFixed(0)); 1300 } 1301 1302 if (amountDueInCents < 5000) { 1303 return; 1304 } 1305 1306 var currency = 'USD'; 1307 if ($scope.InitInfo && $scope.InitInfo.Currency && typeof $scope.InitInfo.Currency === 'string') { 1308 currency = $scope.InitInfo.Currency.toUpperCase(); 1309 } 1310 1311 if (currency !== 'USD') { 1312 return; 1313 } 1314 1315 var elements = $scope.Rain.stripe.elements(); 1316 $scope.Rain.affirmMessage = elements.create('affirmMessage', { 1317 amount: amountDueInCents, 1318 currency: currency 1319 }); 1320 1321 $scope.Rain.affirmMessage.mount('#affirm-message'); 1322 }; 1323 1324 $scope.rainProcessingPrepareShippingAddress = function () { 1325 var shippingAddress = $scope.isShippingRequired() ? $scope.ShippingInfo : $scope.BillingInfo; 1326 1327 var shipping = { 1328 name: shippingAddress.FirstName + ' ' + shippingAddress.LastName, 1329 address: { 1330 line1: shippingAddress.Address1, 1331 line2: shippingAddress.Address2, 1332 city: shippingAddress.City, 1333 state: shippingAddress.State, 1334 postal_code: shippingAddress.Zip, 1335 } 1336 }; 1337 1338 var shippingCountry = $scope.getISO2digitCountryCode(shippingAddress.Country); 1339 1340 if (shippingCountry) { 1341 shipping.address.country = shippingCountry; 1342 } 1343 1344 return shipping; 1345 }; 1346 1347 $scope.rainProcessingPrepareBillingAddress = function () { 1348 if (!$scope.checkFlagAvsEcommerce()) { 1349 var billingCountry = $scope.getISO2digitCountryCode($scope.BillingInfo.Country); 1350 } else { 1351 var billingCountry = $scope.getIso2CountryCodeFromIso3($scope.BillingInfo.CountryKey) || null; 1352 } 1353 1354 var billing = { 1355 name: $scope.BillingInfo.FirstName + ' ' + $scope.BillingInfo.LastName, 1356 address: { 1357 line1: $scope.BillingInfo.Address1, 1358 line2: $scope.BillingInfo.Address2, 1359 city: $scope.BillingInfo.City, 1360 state: $scope.BillingInfo.State, 1361 postal_code: $scope.BillingInfo.Zip, 1362 } 1363 }; 1364 1365 if ($scope.checkFlagAvsEcommerce()) { 1366 billing.email = $scope.BillingInfo.Email; 1367 billing.phone = $scope.BillingInfo.Phone; 1368 } 1369 1370 // Country must be ISO 2 digit 1371 if (billingCountry) { 1372 billing.address.country = billingCountry; 1373 } 1374 1375 return billing; 1376 1377 }; 1378 1379 $scope.quiltAdyenPrepareBillingAddress = function () { 1380 let billing = { 1381 zip: $scope.BillingInfo.Zip, 1382 first_name: $scope.BillingInfo.FirstName, 1383 last_name: $scope.BillingInfo.LastName, 1384 address_1: $scope.BillingInfo.Address1, 1385 address_2: $scope.BillingInfo.Address2, 1386 city: $scope.BillingInfo.City, 1387 phone: $scope.BillingInfo.Phone, 1388 email: $scope.BillingInfo.Email 1389 }; 1390 1391 let two_letter_country = $scope.getIso2CountryCodeFromIso3($scope.BillingInfo.CountryKey); 1392 1393 if (two_letter_country) { 1394 billing.two_letter_country = two_letter_country; 1395 1396 if (two_letter_country == "US") { 1397 billing.two_letter_state = $scope.BillingInfo.State; 1398 } 1399 } 1400 1401 return billing; 1402 }; 1403 1404 $scope.quiltAdyenPrepareShippingAddress = function () { 1405 var shippingInfo = $scope.getEffectiveShippingInfo(); 1406 1407 let shipping = { 1408 zip: shippingInfo.Zip, 1409 first_name: shippingInfo.FirstName, 1410 last_name: shippingInfo.LastName, 1411 address_1: shippingInfo.Address1, 1412 address_2: shippingInfo.Address2, 1413 city: shippingInfo.City 1414 }; 1415 1416 let two_letter_country = $scope.getIso2CountryCodeFromIso3(shippingInfo.CountryKey); 1417 1418 if (two_letter_country) { 1419 shipping.two_letter_country = two_letter_country; 1420 1421 if (two_letter_country == "US") { 1422 shipping.two_letter_state = shippingInfo.State; 1423 } 1424 } 1425 1426 return shipping; 1427 }; 1428 1429 $scope.quiltAdyenPrepareShopperDetails = function () { 1430 return { 1431 //device_type: navigator.platform, 1432 browser_info: { 1433 userAgent: navigator.userAgent, 1434 language: navigator.language || navigator.userLanguage, 1435 timeZoneOffset: new Date().getTimezoneOffset() 1436 } 1437 } 1438 }; 1439 1440 $scope.getProcessRainPaymentData = function () { 1441 const isSavedCard = 1442 $scope.isSavedCardsFlagEnabled() && 1443 $scope.paymentMode === 'saved' && 1444 $scope.formData?.selectedCustomerCardId; 1445 1446 const data = {}; 1447 1448 if (isSavedCard) { 1449 const card = $scope.getCard(); 1450 1451 if (card?.gateway_token) { 1452 data.payment_method = card.gateway_token; 1453 } 1454 } else { 1455 data.payment_method = { 1456 card: $scope.Rain.card, 1457 billing_details: $scope.rainProcessingPrepareBillingAddress(), 1458 }; 1459 } 1460 1461 if ($scope.getAmountDue() !== 0 && !$scope.isCardOnFileNeeded() && !$scope.isPickup()) { 1462 data.shipping = $scope.rainProcessingPrepareShippingAddress(); 1463 } 1464 1465 return data; 1466 }; 1467 1468 $scope.processRainPayment = function(data) { 1469 if (typeof data === 'undefined') { 1470 data = $scope.getProcessRainPaymentData(); 1471 } 1472 1473 if ($scope.getAmountDue() === 0 && $scope.isCardOnFileNeeded()) { 1474 return $scope.Rain.stripe.confirmCardSetup($scope.Rain.processorToken, data).then(function(result) { 1475 return $scope.handleIntentResult(result); 1476 }); 1477 } else { 1478 return $scope.Rain.stripe.confirmCardPayment($scope.Rain.processorToken, data).then(function(result) { 1479 return $scope.handleIntentResult(result); 1480 }); 1481 } 1482 }; 1483 1484 $scope.handleIntentResult = function(result) { 1485 var context = {}; 1486 context.source = 'checkoutCtrl.js'; 1487 context.details = {}; 1488 context.store_uuid = $scope.InitInfo.Store.uuid; 1489 context.store_name = $scope.InitInfo.Store.name; 1490 context.details.function = 'confirmCardPayment()'; 1491 Logger.setApiUrl($scope.InitInfo.RainApiUrl); 1492 Logger.setToken($scope.InitInfo.store_token); 1493 1494 if (result.error) { 1495 $scope.StatusInfo.ErrorMessageBottom = result.error.message; 1496 Logger.log('Info', 'Rain Method Error', context); 1497 return 'error'; 1498 } else if (result.paymentIntent && (result.paymentIntent.status === 'succeeded' || result.paymentIntent.status === 'requires_capture')) { 1499 $scope.Rain.PaymentMethodId = result.paymentIntent.payment_method; 1500 Logger.log('Info', 'Rain Method Success', context); 1501 return 'success'; 1502 } else if (result.setupIntent && (result.setupIntent.status === 'succeeded' || result.setupIntent.status === 'requires_capture')) { 1503 $scope.Rain.PaymentMethodId = result.setupIntent.payment_method; 1504 Logger.log('Info', 'Rain Method Success', context); 1505 return 'success'; 1506 } else { 1507 // Not sure if this is even possible 1508 $scope.StatusInfo.ErrorMessageBottom = "(CH105) An unexpected error occured, please try again."; 1509 Logger.log('Info', 'Rain Method Failed', context); 1510 return 'failed'; 1511 } 1512 }; 1513 1514 $scope.checkRainPaymentError = function(event) { 1515 if (event.error) { 1516 $scope.Rain.error = event.error; 1517 console.log({type:'Rain Error', message:event.error.message}); 1518 } else { 1519 $scope.Rain.error = null; 1520 $scope.Rain[event.elementType] = event.complete; 1521 } 1522 }; 1523 1524 $scope.isRainPaymentValid = function() { 1525 if ($scope.isSavedCardsFlagEnabled() && $scope.formData?.selectedCustomerCardId) { 1526 return true; 1527 } 1528 1529 if ($scope.Rain.cardNumber && $scope.Rain.cardExpiry && $scope.Rain.cardCvc) { 1530 return true; 1531 } 1532 return false;
1533 }; 1534 1535 $scope.getRainCardStyles = function(elementType) { 1536 var styles = { 1537 style: { 1538 base: { 1539 color: "#565656", 1540 fontFamily: '"Roboto", sans-serif', 1541 fontSmoothing: "antialiased", 1542 fontSize: "16px", 1543 "::placeholder": { 1544 color: "#bebebe", 1545 fontWeight: 300 1546 } 1547 }, 1548 invalid: { 1549 color: "#fa755a", 1550 iconColor: "#fa755a" 1551 } 1552 }, 1553 }; 1554 if (elementType === 'card') { 1555 styles.showIcon = true; 1556 } 1557 return styles; 1558 }; 1559 1560 $scope.setProcessingCheckout = function(processing) { 1561 $scope.StatusInfo.processingCheckout = processing; 1562 $scope.setProcessing(processing); 1563 }; 1564 1565 $scope.setProcessing = function(processing) { 1566 $scope.StatusInfo.processing = processing; 1567 $scope.Processing = processing; 1568 }; 1569 1570 $scope.isProcessing = function() { 1571 return $scope.StatusInfo.processing || $scope.Processing || $scope.StatusInfo.processingCheckout; 1572 }; 1573 1574 $scope.clearErrorMessage = function() 1575 { 1576 $scope.StatusInfo.ErrorMessage = null; 1577 $scope.StatusInfo.ErrorMessageBottom = null; 1578 }; 1579 1580 $scope.setErrorMessageTop = function(message) { 1581 $scope.StatusInfo.ErrorMessage = message; 1582 }; 1583 1584 $scope.setErrorMessageBottom = function(message) { 1585 if (!$scope.StatusInfo.ErrorMessageBottom && message) { 1586 if ($scope.isResponsive()) { 1587 $scope.StatusInfo.ErrorMessageBottom = message; 1588 } else { 1589 $scope.StatusInfo.ErrorMessage = message; 1590 } 1591 } 1592 // TODO: Stack error messages 1593 } 1594 1595 $scope.stateNameToAbbr = function (stateName) { 1596 try { 1597 var stateData = $scope.InitInfo.Regions['USA']; 1598 1599 var names = Object.values(stateData); 1600 var index = names.indexOf(stateName); 1601 if (index >= 0) { 1602 var abbrs = Object.keys(stateData); 1603 return abbrs[index]; 1604 } 1605 return ''; 1606 } catch (err) { 1607 // Consume, continue 1608 return ''; 1609 } 1610 }; 1611 1612 $scope.isScheduledPayment = function() { 1613 return ($scope.scheduledPaymentId > 0 && $scope.paymentToken && $scope.paymentToken.length > 0); 1614 }; 1615 1616 $scope.isEnrollAutoPay = function() { 1617 return ($scope.isScheduledPayment() && $scope.BillingInfo && $scope.BillingInfo.enrollAutoPay); 1618 }; 1619 1620 $scope.isSubscriptionPayment = function() { 1621 return ($scope.subscriptionId > 0 && $scope.paymentToken && $scope.paymentToken.length > 0); 1622 }; 1623 1624 $scope.isInvoicePayment = function() { 1625 return ($scope.invoiceId > 0 && $scope.paymentToken && $scope.paymentToken.length > 0); 1626 }; 1627 1628 $scope.isInvoicesPayment = function() { 1629 return ($scope.invoicesCartId && $scope.paymentToken && $scope.paymentToken.length > 0); 1630 }; 1631 1632 $scope.isRtoPayment = function () { 1633 return $scope.rtoCartId && $scope.paymentToken && $scope.paymentToken.length > 0; 1634 }; 1635 1636 $scope.isMmsPayment = function() { 1637 return $scope.customerId && $scope.paymentDueDate && $scope.mmsAmount && $scope.paymentToken && $scope.paymentToken.length > 0 && $scope.paymentType === 'MMS'; 1638 }; 1639 1640 $scope.isAddCardOnly = function() { 1641 return ($scope.customerId > 0 && $scope.paymentToken && $scope.paymentToken.length > 0 && $scope.paymentType === 'AddCard'); 1642 }; 1643 1644 $scope.isWebPayment = function() { 1645 return ($scope.isScheduledPayment() || $scope.isSubscriptionPayment() || $scope.isInvoicePayment() || $scope.isInvoicesPayment() || $scope.isAddCardOnly() || $scope.isMmsPayment() || $scope.isRtoPayment()); 1646 }; 1647 1648 $scope.isWebPaymentDue = function() { 1649 if ($scope.isWebPayment() && !$scope.isAddCardOnly()) { 1650 return ($scope.InitInfo && $scope.InitInfo.payment_info && +$scope.InitInfo.payment_info.total_due > 0); 1651 } 1652 return false;
1653 }; 1654 1655 $scope.checkFlagBugClassesCantCheckOutEcommerce = function () { 1656 return $scope.knobby && $scope.knobby.module_bug_classes_cant_check_out_ecommerce; 1657 } 1658 1659 $scope.checkFlagShowCouponErrorMessageOnCheckoutPage = function () { 1660 return $scope.knobby && $scope.knobby.module_show_coupon_error_message_on_checkout_page; 1661 } 1662 1663 $scope.checkFlagAdyenRegionFailover = function() { 1664 return $scope.knobby && $scope.knobby.module_adyen_region_failover; 1665 } 1666 1667 $scope.checkFlagLogInvalidOriginError = function() { 1668 return $scope.knobby && $scope.knobby.module_log_invalid_origin_error; 1669 } 1670 1671 $scope.logInvalidOriginError = function(error, methodName) { 1672 if (!$scope.checkFlagLogInvalidOriginError()) { 1673 return; 1674 } 1675 1676 var name = (error && error.name) || ''; 1677 var message = (error && error.message) || ''; 1678 1679 if ((name + ' ' + message).toLowerCase().indexOf('origin') === -1) { 1680 return; 1681 } 1682 1683 if (typeof Logger === 'undefined') { 1684 return; 1685 } 1686 1687 var context = {}; 1688 context.source = 'checkoutCtrl.js'; 1689 context.details = {}; 1690 context.store_uuid = $scope.InitInfo.Store.uuid; 1691 context.store_name = $scope.InitInfo.Store.name; 1692 context.details.function = methodName; 1693 context.details.origin = window.location.origin; 1694 context.details.hostname = window.location.hostname; 1695 context.details.errorName = name; 1696 context.details.errorMessage = message; 1697 Logger.setApiUrl($scope.InitInfo.RainApiUrl); 1698 Logger.setToken($scope.InitInfo.store_token); 1699 Logger.log('Error', 'Manual card entry invalid origin', context); 1700 } 1701 1702 $scope.checkFlagBlockCheckoutOnDynamicTaxFailure = function () { 1703 return $scope.knobby && $scope.knobby.module_block_checkout_on_dynamic_tax_failure; 1704 }; 1705 1706 $scope.checkFlagBlockCheckoutButtonWhileProcessing = function () { 1707 return $scope.knobby && $scope.knobby.module_block_checkout_button_while_processing; 1708 }; 1709 1710 $scope.hasActiveDynamicTaxFailure = function () { 1711 return !!$scope.StatusInfo.DynamicTaxError || !!$scope.StatusInfo.DynamicTaxAddressError; 1712 }; 1713 1714 $scope.buildCheckoutPreProcessData = function () { 1715 if ($scope.checkFlagBlockCheckoutOnDynamicTaxFailure() 1716 && $scope.HasDynamicTaxes 1717 && $scope.isDynamicTaxServiceEnabled()) { 1718 $scope.TotalsCalculate(); 1719 } 1720 1721 return $scope.getPreProcessData(); 1722 }; 1723 1724 $scope.ensureFreshDynamicTaxesForCheckout = function () { 1725 if (!$scope.checkFlagBlockCheckoutOnDynamicTaxFailure() 1726 || !$scope.HasDynamicTaxes 1727 || !$scope.isDynamicTaxServiceEnabled()) { 1728 return null; 1729 } 1730 1731 var startFreshRequest = function () { 1732 $scope.dynamicTaxRequestId = ($scope.dynamicTaxRequestId || 0) + 1; 1733 $scope.invalidateDynamicTaxCache(); 1734 $scope.dynamicTaxesPromiseAccumulator = null; 1735 1736 return $scope.updateDynamicTaxesOnce(); 1737 }; 1738 1739 var inFlightPromise = $scope.dynamicTaxesInFlightPromise; 1740 1741 if (!inFlightPromise) { 1742 return startFreshRequest(); 1743 } 1744 1745 return inFlightPromise.catch(function () {}).then(function () { 1746 return startFreshRequest(); 1747 }); 1748 }; 1749 1750 $scope.calculateWaTaxesBasedOnStoreLocationAddress = function () { 1751 if ( 1752 !$scope.InitInfo?.useWashingtonTax 1753 || ($scope.ShippingInfo && $scope.ShippingInfo.Zip) 1754 ) { 1755 return false; 1756 } 1757 1758 var items = ($scope.CartData && $scope.CartData.Items) || []; 1759 var hasOnlyClassItem = items.every(function (item) { 1760 return item && item.type === 'class'; 1761 }); 1762 1763 return !!hasOnlyClassItem; 1764 } 1765 1766 $scope.tryRequestDynamicTaxesForClasses = function () { 1767 if (!$scope.calculateWaTaxesBasedOnStoreLocationAddress()) { 1768 return; 1769 } 1770 1771 $scope.updateDynamicTaxesOnce(); 1772 }; 1773 1774 $scope.isSubscribeToMarketingEmailsEnabled = function() { 1775 return $scope.InitInfo.CheckoutSettings && $scope.InitInfo.CheckoutSettings.subscribeToMarketingEmailsAtCheckout; 1776 }; 1777 1778 $scope.isNewUIFlagSet = function() { 1779 return ($scope.InitInfo && $scope.InitInfo.checkout_ux_2020_04_23_flag); 1780 }; 1781 1782 // ****************************************************************************************** 1783 // region Cart Data Methods 1784 // ****************************************************************************************** 1785 1786 $scope.getAmountDue = function() { 1787 if ($scope.isWebPayment()) { 1788 var $totalDue = 0; 1789 if ($scope.InitInfo && $scope.InitInfo.payment_info && $scope.InitInfo.payment_info.total_due > 0) {
1790 $totalDue = $scope.InitInfo.payment_info.total_due; 1791 } 1792 if ($scope.isInvoicePayment() && $scope.invoiceAmount && $scope.invoiceAmount < $totalDue) { 1793 $totalDue = $scope.invoiceAmount; 1794 } 1795 return $totalDue; 1796 } 1797 return $scope.Due; 1798 }; 1799 1800 $scope.CartDataGet = function () { 1801 if ($scope.isWebPayment()) { 1802 $scope.showCaptcha = $scope.isCaptchaEnabled(); 1803 return $scope.NoOpPromise(); 1804 } 1805 1806 var options = {withCredentials: true}; 1807 1808 if ($scope.InitInfo.store_token) { 1809 options.headers = { 1810 Authorization: 'Bearer ' + $scope.InitInfo.store_token 1811 }; 1812 } 1813 1814 return $http.get($scope.BuildUrl('/checkout/cartdata'), options).then(function (result) { 1815 $scope.CartDataAdapt(result.data); 1816 if ($scope.checkFlagCarePlans()) { 1817 $scope.updateCarePlanPrice(); 1818 } 1819 }) 1820 .then(function () { 1821 if ($scope.knobby['module_better_coupons']) { 1822 $scope.updateCouponsByCustomerGroups(); 1823 } 1824 }); 1825 }; 1826 1827 $scope.MakeNewCartId = function() { 1828 var uuid = $scope.create_UUID(); 1829 var name = 'cart_id'; 1830 var days = 31; 1831 var expires = ""; 1832 var date = new Date(); 1833 date.setTime(date.getTime() + (days*24*60*60*1000)); 1834 expires = "; expires=" + date.toUTCString(); 1835 document.cookie = name + "=" + (uuid || "") + expires + "; path=/"; 1836 }; 1837 1838 $scope.CartDataAdapt = function (data) { 1839 var tmpCaptcha = data.imnar; 1840 if (tmpCaptcha === true || tmpCaptcha === false) { 1841 $scope.showCaptcha = tmpCaptcha; 1842 } 1843 1844 $scope.CartData = data.cartdata || {}; 1845 1846 if ($scope.knobby['module_better_coupons']) { 1847 $scope.CartData.Coupons = $scope.CartData.Coupons.map(function (coupon) { 1848 var createdCoupon = new BetterCouponFactory(coupon).createCoupon(); 1849 1850 if (!$scope.couponWithCustomerGroupsIsAvailable(createdCoupon)) { 1851 createdCoupon.notEligible = true; 1852 1853 return createdCoupon; 1854 } 1855 1856 if (typeof createdCoupon.setAmountsBasedOnTiers === 'function' || 'setAmountsBasedOnTiers' in createdCoupon) { 1857 if (createdCoupon.couponBaseType === CouponConstants.coupon_bogo) { 1858 createdCoupon.setAmountsBasedOnTiers({lines: $scope.CartData.Items, disableCouponsForSalePriceItems: $scope.InitInfo?.CheckoutSettings?.DisableCouponsForSalePriceItems}); 1859 } else { 1860 createdCoupon.setAmountsBasedOnTiers($scope.SubTotal); 1861 } 1862 } 1863 1864 return createdCoupon; 1865 }); 1866 } 1867 1868 if ($scope.CartData.CartIsPurchased) { 1869 $scope.CartStatus.Valid = false; 1870 1871 return; 1872 } 1873 if ($scope.CartData.CartIsEmpty) { 1874 if(data.cartdata && data.cartdata.resetCartId && data.cartdata.resetCartId === true) { 1875 $scope.MakeNewCartId(); 1876 } 1877 1878 $scope.CartStatus.Valid = false; 1879 return; 1880 } 1881 1882 // Retrieve from session Storage and merge results 1883 var stored = $scope.SessionStorageGet(); 1884 1885 if (stored) { 1886 if (stored.ShippingInfo) { 1887 if ($scope.InitInfo.InternationalEnabled === false) { 1888 1889 if(stored.ShippingInfo.Country != $scope.InitInfo.DomesticCountry) { 1890 stored.ShippingInfo.Country = $scope.InitInfo.DomesticCountry; 1891 stored.ShippingInfo.Country = $scope.InitInfo.DomesticCountry; 1892 stored.ShippingInfo.CountryIso2Key = $scope.CountryIso2KeyGet($scope.InitInfo.DomesticCountry); 1893 stored.ShippingInfo.CountryKey = $scope.CountryKeyGet($scope.InitInfo.DomesticCountry); 1894 } 1895 1896 if($scope.shippingCountryHasRegions() && stored.ShippingInfo.State !='' && typeof $scope.InitInfo.Regions[$scope.getShippingCountryKey()][stored.ShippingInfo.State] == 'undefined'){ 1897 stored.ShippingInfo.State = '' ; 1898 $scope.shippingCalcState = ''; 1899 } 1900 1901 $scope.ShippingInfo = $.extend($scope.ShippingInfo, stored.ShippingInfo); 1902 if ($scope.ShippingInfo.Country) { 1903 $scope.ShippingCountrySet($scope.ShippingInfo.Country); 1904 } 1905 $scope.setShippingOption(); 1906 1907 } 1908 1909 $scope.ShippingInfo = $.extend($scope.ShippingInfo, stored.ShippingInfo); 1910 if ($scope.ShippingInfo.Country) { 1911 $scope.ShippingCountrySet($scope.ShippingInfo.Country); 1912 } 1913 $scope.setShippingOption(); 1914 } 1915 if (stored.BillingInfo) { 1916 $scope.BillingInfo = $.extend($scope.BillingInfo, stored.BillingInfo); 1917 } 1918 } 1919 1920 if ( 1921 $scope.isFlagSaveLuxsuranceInsuranceInfoAfterReloadingEnabled() 1922 && ($scope.isFlagInsuranceEnabled() || $scope.isFlagLuxsuranceEnabled()) 1923 ) { 1924 $scope.setInsuranceInfo(); 1925 } 1926 1927 // Items 1928 if ($scope.CartData.Items) { 1929 for (var i = 0, ilen = $scope.CartData.Items.length; i < ilen; i++) { 1930 var item = $scope.CartData.Items[i]; 1931 1932 var qty = item.qty; 1933 1934 // Fat Quarter 1935 if (item.qty_min === 'fat quarter') { 1936 item.FatQuarter = true; 1937 item.qty_min = .25; 1938 } 1939 1940 if (qty === 'fat quarter') { 1941 item.qty = .25; 1942 } 1943 var measureUnit = item.measure_unit; 1944 if ((measureUnit === 'Meter') && (qty === 0.25)) { 1945 item.FatQuarter = true; 1946 } 1947 1948 // minimum inventory 1949 if (qty < item.qty_min) { 1950 item.qty = item.qty_min; 1951 } 1952 1953 item.total = $scope.RoundTo2Dec(fractionalMultiplication(item.qty, $scope.getItemPrice(item))); 1954 item.qty_integer = (Math.floor(qty)).toString(); 1955 1956 // fractional inventory 1957 item.qty_remainder = $scope.getItemQtyRemainder(item); 1958 1959 1960 item.integer_options = $scope.IntegerOptions(item); 1961 item.remainder_options = $scope.RemainderOptions(item); 1962 } 1963 } 1964 1965 if ($scope.CartData.message) { 1966 $scope.StatusInfo.ErrorMessage = $scope.CartData.message; 1967 } 1968 1969 if ($scope.CartData.MessageArray) { 1970 if ($scope.CartData.MessageArray.RemovedItems) { 1971 $scope.InventoryMessages.RemovedItems = $scope.CartData.MessageArray.RemovedItems; 1972 } 1973 1974 if ($scope.CartData.MessageArray.InventoryChanges) { 1975 $scope.InventoryMessages.InventoryChanges = $scope.CartData.MessageArray.InventoryChanges; 1976 } 1977 1978 if ($scope.CartData.MessageArray.StoreAvailableItems) { 1979 $scope.AvailabilityMessages.StoreAvailableItems = $scope.CartData.MessageArray.StoreAvailableItems; 1980 } 1981 } 1982 1983 $scope.Validate(); 1984 $scope.updateProcessor(); 1985 console.log($scope.CartData); 1986 if ($scope.isSubscriptionOnly()) { 1987 $scope.autoFillBillingDataWithSubscription(); 1988 } 1989 1990 $scope.tryRequestDynamicTaxesForClasses(); 1991 }; 1992 1993 $scope.getItemQtyRemainder = function(item) { 1994 var qtyRemainder = null; 1995 1996 var qty = item.qty; 1997 var measureUnit = item.measure_unit; 1998 1999 if (measureUnit === 'Meter') { 2000 qtyRemainder = ((Math.round(qty * 100) % 100) + '').toString(); 2001 } else { 2002 qtyRemainder = ((Math.round(qty * 100) % 100) + '').toString(); 2003 qtyRemainder = ((Math.round(qtyRemainder / 25) * 25) + '').toString(); 2004 } 2005 2006 return qtyRemainder; 2007 }; 2008 2009 $scope.create_UUID = function () { 2010 var dt = new Date().getTime(); 2011 var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 2012 var r = (dt + Math.random()*16)%16 | 0; 2013 dt = Math.floor(dt/16); 2014 return (c=='x' ? r :(r&0x3|0x8)).toString(16); 2015 }); 2016 return uuid; 2017 } 2018 2019 $scope.autoFillBillingDataWithSubscription = function () {
2020 if ($scope.CartStatus.Valid && $scope.CartData && $scope.CartData.Items && $scope.CartData.Items.length > 0) { 2021 2022 var subscriptionItems = $scope.CartData.Items.filter(function (cartItem) { 2023 return cartItem.type === 'subscription' 2024 }); 2025 2026 if (subscriptionItems.length > 0) { 2027 var subscriptionItem = subscriptionItems.shift(); 2028 if (subscriptionItem && subscriptionItem.notes) { 2029 $scope.BillingInfo.FirstName = subscriptionItem.notes.firstName; 2030 $scope.BillingInfo.LastName = subscriptionItem.notes.lastName; 2031 $scope.BillingInfo.Address1 = subscriptionItem.notes.streetAddress1; 2032 $scope.BillingInfo.Address2 = subscriptionItem.notes.streetAddress2; 2033 $scope.BillingInfo.City = subscriptionItem.notes.city; 2034 $scope.BillingInfo.State = subscriptionItem.notes.stateCode; 2035 $scope.BillingInfo.Zip = subscriptionItem.notes.postalCode; 2036 $scope.BillingInfo.Country = subscriptionItem.notes.country; 2037 $scope.BillingInfo.CountryKey = subscriptionItem.notes.countryCode; 2038 } 2039 } 2040 } 2041 }; 2042 2043 2044 $scope.SessionStorageGet = function () { 2045 var stored = null; 2046 if (typeof sessionStorage !== 'undefined' && sessionStorage.checkout_info) { 2047 var temp = JSON.parse(sessionStorage.checkout_info); 2048 if (temp) { 2049 if (temp.cartId && temp.cartId == $scope.CartId) { 2050 stored = temp; 2051 } else { 2052 sessionStorage.removeItem('checkout_info') 2053 } 2054 } 2055 } 2056 return stored; 2057 }; 2058 2059 $scope.SessionStorageSet = function () { 2060 if (typeof sessionStorage !== 'undefined') { 2061 var temp = {}; 2062 temp.cartId = $scope.CartId; 2063 temp.ShippingInfo = $scope.ShippingInfo; 2064 temp.BillingInfo = $scope.BillingInfo; 2065 sessionStorage.checkout_info = JSON.stringify(temp); 2066 } 2067 }; 2068 2069 //endregion 2070 2071 // ****************************************************************************************** 2072 // region Calculators 2073 // ****************************************************************************************** 2074 2075 $scope.resetTaxes = function() { 2076 $scope.Taxes = 0; 2077 $scope.GST_Taxes = 0; 2078 $scope.dynamicTotalTax = 0; 2079 $scope.taxable_sales = 0; 2080 $scope.non_taxable_sales = 0; 2081 $scope.non_sales = 0; 2082 $scope.JurisdictionalTax = ""; 2083 $scope.JurisdictionalTaxes = {}; 2084 2085 var items = $scope.getItemsWithPackageItems(); 2086 var nonTaxable = 0; 2087 if (items) { 2088 for (var i = 0; i < items.length; i++) { 2089 var item = items[i]; 2090 item.tax = 0; 2091 item.taxable = 0; 2092 item.nonsales = 0; 2093 item.nontaxable = item.total; 2094 if (item.total) { 2095 nonTaxable += item.total; 2096 } 2097 item.jurisdictional_taxes = {}; 2098 } 2099 } 2100 2101 $scope.non_taxable_sales = nonTaxable; 2102 if ($scope.Discounts) { 2103 $scope.non_taxable_sales -= $scope.Discounts; 2104 } 2105 }; 2106 2107 // Keep in sync with Cart.model.php::TransJurisdictionTaxUpdate 2108 $scope.TransJurisdictionTaxUpdate = function (transaction_jurisdictional_taxes, jurisdiction_name, jurisdiction_tax, total) { 2109 if (transaction_jurisdictional_taxes) { 2110 var temp_tax = {}; 2111 var moneyPrecision = $scope.checkFlagFixCheckoutJurisdictionTaxPrecision() ? 2 : 5; 2112 if (transaction_jurisdictional_taxes.hasOwnProperty(jurisdiction_name)) { 2113 var prior_tax_value = Number(transaction_jurisdictional_taxes[jurisdiction_name]['tax_paid']); 2114 var prior_taxable_amount = Number(transaction_jurisdictional_taxes[jurisdiction_name]['taxable_sales']); 2115 prior_tax_value += $scope.roundNumber(jurisdiction_tax, moneyPrecision); 2116 if (jurisdiction_name !== 'Shipping' && !jurisdiction_name.match('Shipping')) { 2117 prior_taxable_amount += $scope.roundNumber(total, moneyPrecision); 2118 } 2119 temp_tax['tax_paid'] = $scope.roundNumber(prior_tax_value, moneyPrecision); 2120 temp_tax['taxable_sales'] = $scope.roundNumber(prior_taxable_amount, moneyPrecision); 2121 } else { 2122 temp_tax['tax_paid'] = $scope.roundNumber(jurisdiction_tax, moneyPrecision); 2123 temp_tax['taxable_sales'] = $scope.roundNumber(total, moneyPrecision); 2124 } 2125 2126 transaction_jurisdictional_taxes[jurisdiction_name] = temp_tax; 2127 } 2128 }; 2129 2130 $scope.calculateRtoTaxes = function (item, transaction_jurisdictional_taxes, useStoreTaxTable) { 2131 var info = { 2132 'line_jurisdictional_taxes': {}, 2133 'line_tax': 0, 2134 'taxable_amount': 0, 2135 'non_taxable_amount': 0, 2136 'non_sales_amount': 0, 2137 'transaction_jurisdictional_taxes': {} 2138 }; 2139 2140 var isCanadaTax = $scope.useCanadaTax(); 2141 2142 // Product 2143 var rtoProductPayment = parseFloat(item.rtoItems.rto_principle_payment || 0) * parseInt(item.rtoItems.rto_initial_payments || 0) + parseFloat(item.rtoItems.rto_down_payment || 0); 2144 if (rtoProductPayment) { 2145 var rtoProductPaymentTaxBitmap = isCanadaTax ? item.tax_bitmap : item.rtoItems.rto_product_tax_bitmap;
2146 var taxInfoProductPayment = $scope.calculateBitmapTax(rtoProductPaymentTaxBitmap, rtoProductPayment, transaction_jurisdictional_taxes, useStoreTaxTable, false, null, true); 2147 for (var nameProduct in taxInfoProductPayment.line_jurisdictional_taxes) { 2148 info['line_jurisdictional_taxes'][nameProduct] = taxInfoProductPayment['line_jurisdictional_taxes'][nameProduct]; 2149 } 2150 if (taxInfoProductPayment['line_tax']) { 2151 info['line_tax'] += $scope.roundNumber(taxInfoProductPayment['line_tax'], 2); 2152 info['taxable_amount'] += taxInfoProductPayment['taxable_amount']; 2153 info['non_taxable_amount'] += taxInfoProductPayment['non_taxable_amount']; 2154 } 2155 } 2156 2157 // Maintenance 2158 var rtoMaintenancePayment = parseFloat(item.rtoItems.rto_maintenance_payment || 0) * parseInt(item.rtoItems.rto_initial_payments || 0); 2159 if (rtoMaintenancePayment) { 2160 var rtoMaintenanceTaxBitmap = isCanadaTax ? item.tax_bitmap : item.rtoItems.rto_maintenance_tax_bitmap; 2161 var taxInfoMaintenancePayment = $scope.calculateBitmapTax(rtoMaintenanceTaxBitmap, rtoMaintenancePayment, transaction_jurisdictional_taxes, useStoreTaxTable, false, null, true); 2162 for (var name in taxInfoMaintenancePayment.line_jurisdictional_taxes) { 2163 if (info['line_jurisdictional_taxes'][name] === undefined) { 2164 info['line_jurisdictional_taxes'][name] = taxInfoMaintenancePayment['line_jurisdictional_taxes'][name]; 2165 } else { 2166 info['line_jurisdictional_taxes'][name]['tax_collected'] += taxInfoMaintenancePayment['line_jurisdictional_taxes'][name]['tax_collected']; 2167 info['line_jurisdictional_taxes'][name]['tax_paid'] += taxInfoMaintenancePayment['line_jurisdictional_taxes'][name]['tax_paid']; 2168 info['line_jurisdictional_taxes'][name]['taxable_amount'] += taxInfoMaintenancePayment['line_jurisdictional_taxes'][name]['taxable_amount']; 2169 } 2170 } 2171 if (taxInfoMaintenancePayment['line_tax']) { 2172 info['line_tax'] += $scope.roundNumber(taxInfoMaintenancePayment['line_tax'], 2); 2173 info['taxable_amount'] += taxInfoMaintenancePayment['taxable_amount']; 2174 info['non_taxable_amount'] += taxInfoMaintenancePayment['non_taxable_amount']; 2175 } 2176 } 2177 2178 // Insurance 2179 var rtoInsurancePayment = parseFloat(item.rtoItems.rto_insurance_payment) * parseInt(item.rtoItems.rto_initial_payments); 2180 if (rtoInsurancePayment) { 2181 var rtoInsuranceTaxBitmap = isCanadaTax ? item.tax_bitmap : item.rtoItems.rto_insurance_tax_bitmap; 2182 var taxInfoInsurancePayment = $scope.calculateBitmapTax(rtoInsuranceTaxBitmap, rtoInsurancePayment, transaction_jurisdictional_taxes, useStoreTaxTable, false, null, true); 2183 for (var name in taxInfoInsurancePayment.line_jurisdictional_taxes) { 2184 if (info['line_jurisdictional_taxes'][name] === undefined) { 2185 info['line_jurisdictional_taxes'][name] = taxInfoInsurancePayment['line_jurisdictional_taxes'][name]; 2186 } else { 2187 info['line_jurisdictional_taxes'][name]['tax_collected'] += taxInfoInsurancePayment['line_jurisdictional_taxes'][name]['tax_collected']; 2188 info['line_jurisdictional_taxes'][name]['tax_paid'] += taxInfoInsurancePayment['line_jurisdictional_taxes'][name]['tax_paid']; 2189 info['line_jurisdictional_taxes'][name]['taxable_amount'] += taxInfoInsurancePayment['line_jurisdictional_taxes'][name]['taxable_amount']; 2190 } 2191 } 2192 if (taxInfoInsurancePayment['line_tax']) { 2193 info['line_tax'] += $scope.roundNumber(taxInfoInsurancePayment['line_tax'], 2); 2194 info['taxable_amount'] += taxInfoInsurancePayment['taxable_amount']; 2195 info['non_taxable_amount'] += taxInfoInsurancePayment['non_taxable_amount']; 2196 } 2197 } 2198 2199 // Deposit 2200 var rtoDeposit = parseFloat(item.rtoItems.rto_security_deposit); 2201 if (rtoDeposit > 0) { 2202 info.non_sales_amount += rtoDeposit; 2203 } 2204 2205 // Cleanup rounding 2206 info.taxable_amount = $scope.roundNumber(info.taxable_amount, 2); 2207 info.non_taxable_amount = $scope.roundNumber(info.non_taxable_amount, 2); 2208 info.non_sales_amount = $scope.roundNumber(info.non_sales_amount, 2);
2209 var moneyPrecision = $scope.checkFlagFixCheckoutJurisdictionTaxPrecision() ? 2 : 5; 2210 2211 for (var name in info.line_jurisdictional_taxes) { 2212 info.line_jurisdictional_taxes[name]['tax_collected'] = $scope.roundNumber(info.line_jurisdictional_taxes[name]['tax_collected'], 2); 2213 info.line_jurisdictional_taxes[name]['tax_paid'] = $scope.roundNumber(info.line_jurisdictional_taxes[name]['tax_paid'], moneyPrecision); 2214 info.line_jurisdictional_taxes[name]['taxable_amount'] = $scope.roundNumber(info.line_jurisdictional_taxes[name]['taxable_amount'], moneyPrecision); 2215 } 2216 return info; 2217 } 2218 2219 $scope.calculateSubscriptionTaxes = function (item, transaction_jurisdictional_taxes, useStoreTaxTable, alternativeState) { 2220 var info = { 2221 'line_jurisdictional_taxes': {}, 2222 'line_tax': 0, 2223 'taxable_amount': 0, 2224 'non_taxable_amount': 0, 2225 'transaction_jurisdictional_taxes': {} 2226 }; 2227 2228 var registrationFee = parseFloat(item.subscriptionData.registration_fee); 2229 var subscriptionTaxInfo = $scope.calculateBitmapTax(item.tax_bitmap, registrationFee, transaction_jurisdictional_taxes, useStoreTaxTable, false, alternativeState); 2230 for (var nameProduct in subscriptionTaxInfo.line_jurisdictional_taxes) { 2231 info['line_jurisdictional_taxes'][nameProduct] = subscriptionTaxInfo['line_jurisdictional_taxes'][nameProduct]; 2232 info['transaction_jurisdictional_taxes'] = subscriptionTaxInfo['transaction_jurisdictional_taxes']; 2233 } 2234 2235 info['line_tax'] += subscriptionTaxInfo['line_tax']; 2236 info['taxable_amount'] += subscriptionTaxInfo['taxable_amount']; 2237 info['non_taxable_amount'] += subscriptionTaxInfo['non_taxable_amount']; 2238 info.subscription_details = { 2239 'line_jurisdictional_taxes': JSON.stringify(Object.assign({}, info.transaction_jurisdictional_taxes)), 2240 'line_tax': info['line_tax'], 2241 'taxable_amount': info['taxable_amount'], 2242 'non_taxable_amount': info['non_taxable_amount'], 2243 'kit_taxes': [], 2244 }; 2245 2246 if (item.subscriptionData.previous_items.length > 0) { 2247 var taxAtCountryLevel = $scope.taxAtCountryLevel(); 2248 var showAsGST = $scope.showAsGST(); 2249 var useStoreTaxTableForProduct = taxAtCountryLevel || showAsGST; 2250 var pricePerKit = parseFloat(item.subscriptionData.price_per_kit); 2251 2252 if ($scope.isPickup() || useStoreTaxTable) { 2253 useStoreTaxTableForProduct = true; 2254 } 2255 2256 for (var i = 0; i < item.subscriptionData.previous_items.length; i++) { 2257 var prevItem = item.subscriptionData.previous_items[i]; 2258 prevItem.tax_paid = 0; 2259 var productBitmap = prevItem.bitmap; 2260 2261 if (!productBitmap) { 2262 continue; 2263 } 2264 var productTaxInfo = $scope.calculateBitmapTax(productBitmap, pricePerKit, transaction_jurisdictional_taxes, useStoreTaxTableForProduct, false); 2265 info['line_tax'] += productTaxInfo['line_tax']; 2266 info['taxable_amount'] += productTaxInfo['taxable_amount']; 2267 info['non_taxable_amount'] += productTaxInfo['non_taxable_amount']; 2268 prevItem.tax_paid = productTaxInfo['line_tax']; 2269 for (var name in productTaxInfo.line_jurisdictional_taxes) { 2270 if (info['line_jurisdictional_taxes'][name] === undefined) { 2271 info['line_jurisdictional_taxes'][name] = productTaxInfo['line_jurisdictional_taxes'][name]; 2272 } else { 2273 info['line_jurisdictional_taxes'][name]['tax_collected'] += productTaxInfo['line_jurisdictional_taxes'][name]['tax_collected']; 2274 info['line_jurisdictional_taxes'][name]['tax_paid'] += productTaxInfo['line_jurisdictional_taxes'][name]['tax_paid']; 2275 info['line_jurisdictional_taxes'][name]['taxable_amount'] += productTaxInfo['line_jurisdictional_taxes'][name]['taxable_amount']; 2276 } 2277 2278 info['transaction_jurisdictional_taxes'] = productTaxInfo['transaction_jurisdictional_taxes']; 2279 } 2280 info.subscription_details.kit_taxes.push({ 2281 'tax_paid': prevItem.tax_paid, 2282 'subscription_offering_item_id': prevItem.subscription_offering_item_id, 2283 'line_jurisdictional_taxes': JSON.stringify(Object.assign({}, productTaxInfo['line_jurisdictional_taxes'])) 2284 }); 2285 } 2286 } 2287 return info; 2288 }; 2289 2290 $scope.CalculateItemTaxes = function () { 2291 var items = $scope.getItemsWithPackageItems(); 2292 2293 /* if we have GC or coupon and discount exceeds total price we need skip al taxes */ 2294 var isGcOrCouponExceeds = false;
2295 if ($scope.Discounts === $scope.SubTotal) { 2296 isGcOrCouponExceeds = true; 2297 } 2298 if (!items || items.length === 0 || $scope.isWholesaleTaxExempt() || isGcOrCouponExceeds) { 2299 $scope.resetTaxes(); 2300 return; 2301 } 2302 2303 var class_bitmap = $scope.getClassTaxBitmap(); 2304 var product_bitmap = $scope.getProductTaxBitmap(); 2305 var taxAtCountryLevel = $scope.taxAtCountryLevel(); 2306 var showAsGST = $scope.showAsGST(); 2307 var taxable_sales_total = 0; 2308 var non_taxable_sales_total = 0; 2309 var non_sales_total = 0; 2310 var transaction_jurisdictional_taxes = {}; 2311 var transaction_line_tax = 0; 2312 var total_gst_tax = 0; 2313 var stateCode = null; 2314 var totalAmount = 0; 2315 var moneyPrecision = $scope.checkFlagFixCheckoutJurisdictionTaxPrecision() ? 2 : 5; 2316 2317 for (var i = 0; i < items.length; i++) { 2318 var item = items[i]; 2319 var total = item.discounted_total ? item.discounted_total : item.total; 2320 totalAmount += total; 2321 2322 // Each item could use a different tax table 2323 var useStoreTaxTable = false; 2324 if (item.type == 'pitem' || item.type == 'pslr' || item.type == 'rto' || item.type == 'product') { 2325 if (item.tax_bitmap > 0 && product_bitmap && $scope.InitInfo.useCanadaTax) { 2326 item.tax_bitmap = product_bitmap; 2327 } 2328 2329 useStoreTaxTable = taxAtCountryLevel || showAsGST; 2330 2331 if ($scope.isPickup()) { 2332 useStoreTaxTable = true; 2333 } 2334 2335 // NOTE: Don't do this for everything because PayPal only stores will have shipping off so PayPal can calculate the shipping 2336 // But we still need to calculate the taxes correctly 2337 if ($scope.InitInfo.ShippingEnabled == false && item.type == 'rto') { 2338 useStoreTaxTable = true; 2339 } 2340 } 2341 if (item.type == 'class') { 2342 if (class_bitmap && (!item.tax_bitmap || item.tax_bitmap == 0 || $scope.InitInfo.useCanadaTax)) { 2343 item.tax_bitmap = class_bitmap; 2344 } 2345 2346 useStoreTaxTable = true; 2347 } 2348 if (item.type == 'pitem' || item.type == 'rto' || item.type == 'product') { 2349 if (product_bitmap && (!item.tax_bitmap || item.tax_bitmap == 0)) { 2350 item.tax_bitmap = product_bitmap; 2351 } 2352 if (item.parent_type == 'class') { 2353 useStoreTaxTable = true; 2354 } 2355 } 2356 if (item.type == 'subscription') { 2357 if (item.notes && item.notes.stateCode) { 2358 stateCode = item.notes.stateCode; 2359 } else if (item.subscriptionData && item.subscriptionData.notes && item.subscriptionData.notes.stateCode) { 2360 stateCode = item.subscriptionData.notes.stateCode; 2361 } 2362 2363 if (product_bitmap && (!item.tax_bitmap || item.tax_bitmap == 0 || $scope.InitInfo.useCanadaTax)) { 2364 item.tax_bitmap = product_bitmap; 2365 } 2366 useStoreTaxTable = taxAtCountryLevel || showAsGST; 2367 if ($scope.isPickup() || !item.notes || !item.notes.type || item.notes.type.toLowerCase() !== 'ship') { 2368 useStoreTaxTable = true; 2369 } 2370 2371 var taxTable = $scope.getTaxTable(true, stateCode, item.tax_bitmap); 2372 2373 if (stateCode !== null && taxTable) {
2374 if (taxTable.region !== stateCode) { 2375 item.tax_bitmap = 0; 2376 } else { 2377 useStoreTaxTable = true; 2378 } 2379 } 2380 } 2381 2382 if (item.notes && item.notes.taxable_amount) { 2383 item.taxable_amount = item.notes.taxable_amount; 2384 item.notes = ''; 2385 } 2386 2387 if (!item.taxable_amount) { 2388 item.taxable_amount = total; 2389 } 2390 2391 var isClassWithDynamicTaxes = item.type === 'class' && $scope.HasDynamicTaxes; 2392 2393 // Calculate taxes 2394 if (item.type === 'rto' && item.rtoItems !== undefined) { 2395 var info = $scope.calculateRtoTaxes(item, transaction_jurisdictional_taxes, useStoreTaxTable); 2396 } else if (item.type === 'subscription') { 2397 var info = $scope.calculateSubscriptionTaxes(item, transaction_jurisdictional_taxes, useStoreTaxTable, stateCode); 2398 } else { 2399 if (isClassWithDynamicTaxes && item.tax_bitmap == 0) { 2400 //We don't want to calculate taxes for classes with dynamic taxes 2401 var info = $scope.calculateBitmapTax(item.tax_bitmap, 0, transaction_jurisdictional_taxes, useStoreTaxTable, false); 2402 } else if ($scope.InitInfo.customer?.tax_exempt === 'yes' && $scope.InitInfo.customer?.tax_exempt_products && (item.type == 'pitem' || item.type == 'product' || item.type == 'pslr')) { 2403 var info = $scope.calculateBitmapTax(item.tax_bitmap, 0, transaction_jurisdictional_taxes, useStoreTaxTable, false); 2404 } else if (($scope.InitInfo.customer?.tax_exempt === 'yes' && $scope.InitInfo.customer?.tax_exempt_classes && (item.type == 'class' || item.parent_type == 'class'))) { 2405 var info = $scope.calculateBitmapTax(item.tax_bitmap, 0, transaction_jurisdictional_taxes, useStoreTaxTable, false); 2406 } else { 2407 if ($scope.checkFlagAvalaraTaxCodeIssue()) { 2408 if (item.nonTaxableItem) { 2409 var info = $scope.calculateBitmapTax(item.tax_bitmap, 0, transaction_jurisdictional_taxes, useStoreTaxTable, false); 2410 } else { 2411 var info = $scope.calculateBitmapTax(item.tax_bitmap, item.taxable_amount, transaction_jurisdictional_taxes, useStoreTaxTable, false); 2412 } 2413 } else { 2414 var info = $scope.calculateBitmapTax(item.tax_bitmap, item.taxable_amount, transaction_jurisdictional_taxes, useStoreTaxTable, false); 2415 } 2416 } 2417 } 2418 2419 if (!item.gst_tax || !item.gst_price) { 2420 var gst_info = $scope.calculateBitmapTax(item.tax_bitmap, item.price, null, useStoreTaxTable, false); 2421 item.gst_tax = $scope.roundNumber(gst_info.line_tax, 5); 2422 item.gst_price = $scope.roundNumber(+item.price + item.gst_tax, 2); 2423 } 2424 2425 total_gst_tax += (item.gst_tax * item.qty); 2426 2427 // Update item tax details 2428 item.jurisdictional_taxes = info.line_jurisdictional_taxes; 2429 2430 item.tax = $scope.roundNumber(info.line_tax, moneyPrecision); 2431 transaction_line_tax += info.line_tax; 2432 if (info['subscription_details']) { 2433 item.subscription_details = info['subscription_details']; 2434 } 2435 item.taxable = 0; 2436 if (info.taxable_amount > 0) { 2437 item.taxable = info.taxable_amount; 2438 taxable_sales_total += info.taxable_amount; 2439 } 2440 item.nontaxable = 0; 2441 if (info.non_taxable_amount) { 2442 item.nontaxable = info.non_taxable_amount; 2443 non_taxable_sales_total += info.non_taxable_amount; 2444 } 2445 item.nonsales = 0; 2446 if (info.non_sales_amount) { 2447 item.nonsales = info.non_sales_amount; 2448 non_sales_total += info.non_sales_amount; 2449 } 2450 } 2451 2452 // sum taxes by jurisdictional value 2453 var total_transaction_tax = 0; 2454 for (var tax_jurisdiction in transaction_jurisdictional_taxes) { 2455 if (transaction_jurisdictional_taxes.hasOwnProperty(tax_jurisdiction)) { 2456 total_transaction_tax += transaction_jurisdictional_taxes[tax_jurisdiction]['tax_paid']; 2457 transaction_jurisdictional_taxes[tax_jurisdiction]['tax_paid'] = $scope.roundNumber(transaction_jurisdictional_taxes[tax_jurisdiction]['tax_paid'], moneyPrecision); 2458 } 2459 } 2460 2461 var diff = subFloats(subFloats(subFloats(totalAmount, taxable_sales_total), non_sales_total), non_taxable_sales_total); 2462 if (diff > 0.005) { 2463 non_taxable_sales_total += diff; 2464 } else if (diff < -0.005) { 2465 console.error('We have a negative difference on the tax totals'); 2466 console.debug('diff is ' + diff); 2467 } 2468 2469 if (total_transaction_tax < 0.005) { 2470 $scope.resetTaxes(); 2471 return; 2472 } 2473 2474 var transaction_tax = items.reduce(function (tax, item) { 2475 return tax + item.tax; 2476 }, 0); 2477 2478 if ($scope.useCanadaTax()) { 2479 // Fix: Ensure consistency between individual item taxes and total transaction tax 2480 let jurisdictional_tax_total = $scope.roundNumber(total_transaction_tax, 2); 2481 2482 let tax_difference = jurisdictional_tax_total - transaction_tax; 2483 if (Math.abs(tax_difference) >= 0.01) { 2484 // Use the higher precision jurisdictional total if there's a significant difference 2485 transaction_tax = jurisdictional_tax_total; 2486 // sum taxes by line value & take the difference and apply it to the first taxable line item. (*magic penny*) 2487 for (let i = 0; i < items.length; i++) { 2488 let line_item = items[i]; 2489 if (line_item.taxable !== 0 && line_item.tax && (line_item.tax + tax_difference) > 0) { 2490 let item_tax_value = line_item.tax; 2491 line_item.tax = $scope.roundNumber(item_tax_value + tax_difference, 2); 2492 let jurisdictionIndexOfLargestTax = null; 2493 let jurisdictionLargestTaxCollected = 0; 2494 for (let jurisdictionIndex in line_item.jurisdictional_taxes) { 2495 let jTaxCollected = line_item.jurisdictional_taxes[jurisdictionIndex].tax_collected; 2496 if (!jTaxCollected) { 2497 continue; 2498 } 2499 if (jTaxCollected > jurisdictionLargestTaxCollected) { 2500 jurisdictionLargestTaxCollected = jTaxCollected; 2501 jurisdictionIndexOfLargestTax = jurisdictionIndex; 2502 } 2503 } 2504 if (jurisdictionIndexOfLargestTax) { 2505 let taxCollected = line_item.jurisdictional_taxes[jurisdictionIndexOfLargestTax].tax_collected; 2506 taxCollected = $scope.roundNumber(taxCollected + tax_difference, 2); 2507 line_item.jurisdictional_taxes[jurisdictionIndexOfLargestTax].tax_collected = taxCollected; 2508 } 2509 break; 2510 } 2511 } 2512 } 2513 } 2514 2515 $scope.taxable_sales = $scope.RoundTo2Dec(taxable_sales_total); 2516 $scope.non_taxable_sales = $scope.RoundTo2Dec(non_taxable_sales_total); 2517 $scope.non_sales = $scope.RoundTo2Dec(non_sales_total); 2518 $scope.setTransactionJurisdictionTaxes(transaction_jurisdictional_taxes); 2519 2520 $scope.GST_Taxes = $scope.RoundTo2Dec(total_gst_tax); 2521 $scope.Taxes = $scope.RoundTo2Dec(transaction_tax); 2522 }; 2523 2524 $scope.calculateBitmapTax = function (taxBitmap, total, jurisdictionTaxes, useStoreTaxTable, isShippingTax, alternativeState, ignoreFixedTax) { 2525 var lineTaxes = {}; 2526 var line_tax = 0; 2527 var taxable_amount = 0; 2528 var total_amount = $scope.roundNumber(total, 2);
2529 var originalTaxBitmap = +taxBitmap; 2530 2531 if ($scope.checkFlagFixCheckoutJurisdictionTaxPrecision()) { 2532 total = $scope.roundNumber(total, 2); 2533 } else { 2534 total = total || 0; // Make sure it's a number and not something like NaN 2535 } 2536 2537 if (originalTaxBitmap === 0) { 2538 return { 2539 line_jurisdictional_taxes: {}, 2540 line_tax: 0, 2541 taxable_amount: 0, 2542 non_taxable_amount: total_amount, 2543 transaction_jurisdictional_taxes: jurisdictionTaxes, 2544 }; 2545 } 2546 2547 if ($scope.InitInfo.CheckoutSettings.FixedTax === true && !ignoreFixedTax) { 2548 var fixedRate = Number($scope.InitInfo.CheckoutSettings.FixedTaxRate / 100); 2549 if (fixedRate > 0) { 2550 var jurisdiction = 'Fixed'; 2551 if (isShippingTax) { 2552 jurisdiction = 'Shipping' 2553 } 2554 2555 if (taxBitmap) { 2556 if ($scope.checkFlagFixCheckoutJurisdictionTaxPrecision()) { 2557 line_tax = $scope.roundNumber(total * fixedRate, 2); 2558 taxable_amount = total; 2559 lineTaxes[taxBitmap] = { 2560 taxable_amount: total, 2561 tax_paid: line_tax, 2562 rate: $scope.roundNumber(fixedRate * 100, 5), 2563 jurisdiction: jurisdiction, 2564 jurisdiction_id: 'web_fixed' 2565 }; 2566 } else { 2567 line_tax = total * fixedRate; 2568 taxable_amount = total; 2569 lineTaxes[taxBitmap] = { 2570 taxable_amount: $scope.roundNumber(total, 5), 2571 tax_paid: $scope.roundNumber(line_tax, 5), 2572 rate: $scope.roundNumber(fixedRate * 100, 5), 2573 jurisdiction: jurisdiction, 2574 jurisdiction_id: 'web_fixed' 2575 }; 2576 } 2577 2578 lineTaxes[taxBitmap].tax_collected = $scope.roundNumber(+lineTaxes[taxBitmap].tax_paid, 2); 2579 $scope.TransJurisdictionTaxUpdate(jurisdictionTaxes, jurisdiction, line_tax, total); 2580 } 2581 } 2582 } else { 2583 var taxTable = $scope.getTaxTable(useStoreTaxTable, alternativeState, taxBitmap); 2584 if (taxTable) { 2585 for (var bitmap_id in taxTable.rates) { 2586 var isTaxMatch = (+bitmap_id & +taxBitmap) || $scope.HasDynamicTaxes; 2587 2588 if (isTaxMatch) { 2589 var type = taxTable.rates[bitmap_id].tax_type; 2590 if (type === 'dynamic rate') { 2591 if ($scope.HasDynamicTaxes) { 2592 var result = $scope.dynamicRateCache; 2593 if (result !== null && typeof result !== 'undefined') { 2594 for (var dynamic_bitmap in result) { 2595 var dynamicRate = result[dynamic_bitmap]; 2596 var dynamic_name = dynamicRate.title; 2597 2598 if (!$scope.isDynamicTaxServiceEnabled()) { 2599 if (isShippingTax) { 2600 dynamic_name += ' Shipping'; 2601 } 2602 } 2603 2604 var dynamic_rate = Number(dynamicRate.rate); 2605 2606 if ($scope.isDynamicTaxServiceEnabled()) { 2607 if (isShippingTax && !dynamic_name.match('Shipping')) { 2608 continue; 2609 } 2610 2611 if (!isShippingTax && dynamic_name.match('Shipping')) { 2612 continue; 2613 } 2614 } 2615 2616 var dynamic_tax = total * Number(dynamic_rate); 2617 if ($scope.checkFlagFixC
2617heckoutJurisdictionTaxPrecision()) { 2618 dynamic_tax = $scope.roundNumber(total * Number(dynamic_rate), 2); 2619 } 2620 2621 if (dynamic_tax) { 2622 if ($scope.checkFlagFixCheckoutJurisdictionTaxPrecision()) { 2623 line_tax = $scope.roundNumber(line_tax + dynamic_tax, 2); 2624 2625 lineTaxes[dynamic_bitmap] = { 2626 taxable_amount: total, 2627 tax_paid: dynamic_tax, 2628 rate: $scope.roundNumber(dynamic_rate * 100, 5), 2629 jurisdiction: dynamic_name, 2630 jurisdiction_id: dynamic_bitmap, 2631 }; 2632 } else { 2633 line_tax += dynamic_tax; 2634 2635 lineTaxes[dynamic_bitmap] = { 2636 taxable_amount: $scope.roundNumber(total, 5), 2637 tax_paid: $scope.roundNumber(dynamic_tax, 5), 2638 rate: $scope.roundNumber(dynamic_rate * 100, 5), 2639 jurisdiction: dynamic_name, 2640 jurisdiction_id: dynamic_bitmap, 2641 }; 2642 } 2643 2644 lineTaxes[dynamic_bitmap].tax_collected = $scope.roundNumber(+lineTaxes[dynamic_bitmap].tax_paid, 2); 2645 $scope.TransJurisdictionTaxUpdate(jurisdictionTaxes, dynamic_name, dynamic_tax, total); 2646 } 2647 } 2648 } 2649 } 2650 } else { 2651 if (!$scope.HasDynamicTaxes) { 2652 var rate_obj = taxTable.rates[bitmap_id]; 2653 var useCanadaTax = $scope.useCanadaTax(); 2654 2655 // Special case for Canada, PST shipping tax is only applied if shipping to or from BC 2656 if (taxTable && isShippingTax && useCanadaTax && rate_obj.pos_tax_id === 'PST') { 2657 var shipToState = $scope.getShippingState(); 2658 var shipFromState = $scope.getStoreState(); 2659 if (shipToState !== 'BC' && shipFromState !== 'BC') { 2660 // Shipping tax not applicable 2661 continue; 2662 } 2663 } 2664 2665 // Calculate tax for this rate 2666 var total_to_tax = $scope.calculateTaxableAmountForJurisdiction(total, rate_obj); 2667 2668 // Set shipping jurisdiction name if shipping 2669 var jurisdiction_name = rate_obj.title; 2670 if (isShippingTax) { 2671 if (useCanadaTax) { 2672 jurisdiction_name += ' - Shipping'; 2673 } 2674 } 2675 2676 // Adjust Canada jurisdiction name to include title 2677 if (useCanadaTax) { 2678 jurisdiction_name = taxTable.title + ' - ' + jurisdiction_name; 2679 } 2680 2681 // No rate for Canada if no bitmap 2682 var rate = Number(rate_obj.rate); 2683 if (useCanadaTax && !originalTax
2683Bitmap) { 2684 rate = 0; 2685 } 2686 2687 // Calculate tax 2688 var jurisdiction_tax = total_to_tax * rate; 2689 if ($scope.checkFlagFixCheckoutJurisdictionTaxPrecision()) { 2690 jurisdiction_tax = $scope.roundNumber(total_to_tax * rate, 2); 2691 } 2692 var pos_tax_id = rate_obj.pos_tax_id; 2693 var tax_profile_id = taxTable.rates[bitmap_id].pos_tax_profile_id !== 'undefined' 2694 ? taxTable.rates[bitmap_id].pos_tax_profile_id : null; 2695 2696 if (jurisdiction_tax) { 2697 if ($scope.checkFlagFixCheckoutJurisdictionTaxPrecision()) { 2698 line_tax = $scope.roundNumber(line_tax + jurisdiction_tax, 2); 2699 lineTaxes[bitmap_id] = { 2700 taxable_amount: total_to_tax, 2701 tax_paid: jurisdiction_tax, 2702 rate: $scope.roundNumber(rate * 100, 5), 2703 jurisdiction: jurisdiction_name, 2704 jurisdiction_id: pos_tax_id, 2705 tax_profile_id: tax_profile_id 2706 }; 2707 lineTaxes[bitmap_id].tax_collected = $scope.roundNumber(+lineTaxes[bitmap_id].tax_paid, 2); 2708 $scope.TransJurisdictionTaxUpdate(jurisdictionTaxes, jurisdiction_name, jurisdiction_tax, total_to_tax); 2709 } else { 2710 line_tax += jurisdiction_tax; 2711 lineTaxes[bitmap_id] = { 2712 taxable_amount: $scope.roundNumber(total_to_tax, 5), 2713 tax_paid: $scope.roundNumber(jurisdiction_tax, 5), 2714 rate: $scope.roundNumber(rate * 100, 5), 2715 jurisdiction: jurisdiction_name, 2716 jurisdiction_id: pos_tax_id, 2717 tax_profile_id: tax_profile_id 2718 }; 2719 lineTaxes[bitmap_id].tax_collected = $scope.roundNumber(+lineTaxes[bitmap_id].tax_paid, 2); 2720 $scope.TransJurisdictionTaxUpdate(jurisdictionTaxes, jurisdiction_name, jurisdiction_tax, total_to_tax); 2721 } 2722 } 2723 } 2724 } 2725 } 2726 } 2727 } 2728 } 2729 2730 if (line_tax) { 2731 taxable_amount = total; 2732 } 2733 taxable_amount = $scope.roundNumber(taxable_amount, 2); 2734 var non_taxable_amount = 0; 2735 if (total_amount - taxable_amount > .005) { 2736 non_taxable_amount = total_amount - taxable_amount; 2737 } 2738 2739 return { 2740 line_jurisdictional_taxes: $scope.recalculateTaxJurisdictionApplied(line_tax, lineTaxes), 2741 line_tax: line_tax, 2742 taxable_amount: taxable_amount, 2743 non_taxable_amount: non_taxable_amount, 2744 transaction_jurisdictional_taxes: jurisdictionTaxes 2745 }; 2746 }; 2747 2748 $scope.recalculateTaxJurisdictionApplied = function (tax, taxJurisdictionsApplied) { 2749 if (Object.keys(taxJurisdictionsApplied).length) { 2750 var sumOfFixedTaxPaid = 0; 2751 2752 for (var key in taxJurisdictionsApplied) { 2753 sumOfFixedTaxPaid += $scope.roundNumber(+taxJurisdictionsApplied[key]['tax_paid'], 2); 2754 } 2755 2756 var taxDifference = $scope.roundNumber(tax, 2) - sumOfFixedTaxPaid; 2757 2758 if (taxDifference !== 0) { 2759 var lastKey = Object.keys(taxJurisdictionsApplied)[Object.keys(taxJurisdictionsApplied).length - 1]; 2760 var tax_collected = taxJurisdictionsApplied[lastKey]['tax_collected'] + taxDifference; 2761 taxJurisdictionsApplied[lastKey]['tax_collected'] = $scope.roundNumber(tax_collected, 2); 2762 } 2763 } 2764 2765 return taxJurisdictionsApplied; 2766 }; 2767 2768 $scope.calculateTaxableAmountForJurisdiction = function (total, jurisdiction) { 2769 total = +total; 2770 var taxableAmount = total; 2771 if (jurisdiction.tax_type === 'tiered rate') { 2772 var min_price = +jurisdiction.min_price || 0; 2773 var max_price = +jurisdiction.max_price || Number.MAX_VALUE; 2774 if (total < min_price) { 2775 taxableAmount = 0; 2776 } else { 2777 taxableAmount = total - min_price; 2778 if (total > max_price) { 2779 taxableAmount = max_price - min_price; 2780 } 2781 } 2782 } 2783
2784 if ($scope.checkFlagFixCheckoutJurisdictionTaxPrecision()) { 2785 return $scope.roundNumber(taxableAmount, 2); 2786 } 2787 2788 return taxableAmount; 2789 }; 2790 2791 /** * @return {number} */ 2792 $scope.calculateShippingTax = function (shipping) { 2793 var shippingTaxes = 0; 2794 shipping = +shipping; 2795 $scope.shipping_tax_jurisdictions = {}; 2796 2797 if ($scope.getShippingDiscount() > 0) { 2798 shipping -= $scope.getShippingDiscount(); 2799 } 2800 2801 if (!shipping || shipping < 0 || $scope.isWholesaleTaxExempt()) { 2802 return shippingTaxes; 2803 } 2804 2805 if ($scope.InitInfo.TaxShipping != 'yes' && !$scope.useCanadaTax() && !($scope.HasDynamicTaxes && $scope.isDynamicTaxServiceEnabled())) { 2806 return shippingTaxes; 2807 } 2808 2809 var transaction_jurisdictional_taxes = {}; 2810 if ($scope.JurisdictionalTaxes) { 2811 transaction_jurisdictional_taxes = $scope.JurisdictionalTaxes; 2812 if (transaction_jurisdictional_taxes['Shipping']) { 2813 delete transaction_jurisdictional_taxes['Shipping']; 2814 } 2815 } 2816 2817 // Hard coding Shipping tax bases on Product tax for now until we know something more 2818 var tax_bitmap = $scope.getProductTaxBitmap(); 2819 2820 var useStoreTaxTable = $scope.taxAtCountryLevel(); 2821 var info = $scope.calculateBitmapTax(tax_bitmap, shipping, transaction_jurisdictional_taxes, useStoreTaxTable, true); 2822 $scope.setTransactionJurisdictionTaxes(info.transaction_jurisdictional_taxes); 2823 $scope.shipping_tax_jurisdictions = info.line_jurisdictional_taxes; 2824 return $scope.RoundTo2Dec(info.line_tax); 2825 }; 2826 2827 $scope.calculateShippingTaxAmount = function (shipping) { 2828 var shippingTaxes = 0; 2829 shipping = +shipping; 2830 if (!shipping || shipping < 0 || $scope.isWholesaleTaxExempt()) { 2831 return shippingTaxes; 2832 } 2833 2834 if ($scope.InitInfo.TaxShipping != 'yes' && !$scope.useCanadaTax()) { 2835 return shippingTaxes; 2836 } 2837 2838 var transaction_jurisdictional_taxes = {}; 2839 if ($scope.JurisdictionalTaxes && $scope.JurisdictionalTaxes && transaction_jurisdictional_taxes.Shipping) { 2840 return $scope.RoundTo2Dec(transaction_jurisdictional_taxes.Shipping.tax_paid); 2841 } 2842 2843 // Hard coding Shipping tax bases on Product tax for now until we know something more 2844 var tax_bitmap = $scope.getProductTaxBitmap(); 2845 2846 var useStoreTaxTable = $scope.taxAtCountryLevel(); 2847 var info = $scope.calculateBitmapTax(tax_bitmap, shipping, transaction_jurisdictional_taxes, useStoreTaxTable, true); 2848 return $scope.RoundTo2Dec(info.line_tax); 2849 }; 2850 2851 $scope.taxAtCountryLevel = function () { 2852 var storeCountry = $scope.getStoreCountryKey(); 2853 var shippingCountry = $scope.getShippingCountryKey(); 2854 if (storeCountry == shippingCountry) { 2855 switch (storeCountry) { 2856 case 'CAN': 2857 return (!$scope.InitInfo.useCanadaTax); 2858 case 'AUS': 2859 return true; 2860 } 2861 } 2862 return false; 2863 }; 2864 2865 // alternativeState can be used for example for subscription taxes calculation 2866 // where we need to calculate taxes separately for subscription with some delivery method 2867 // and for previous products with another delivery method 2868 $scope.getTaxTable = function (useStoreTaxTable, alternativeState, taxBitmap) { 2869 var taxTable = null; 2870 var state = null; 2871 if ($scope.isPickup()) { 2872 useStoreTaxTable = true; 2873 } 2874 if ($scope.InitInfo.TaxableRegions) { 2875 if (alternativeState !== null && typeof alternativeState !== 'undefined') { 2876 state = alternativeState; 2877 } else if ($scope.digitalOrder && $scope.BillingInfo.State) { 2878 state = $scope.BillingInfo.State; 2879 } else if (useStoreTaxTable) { 2880 if ($scope.isPickup() && $scope.ShippingInfo && $scope.ShippingInfo.Option && $scope.ShippingInfo.Option.address && $scope.ShippingInfo.Option.address.state) { 2881 state = $scope.ShippingInfo.Option.address.state; 2882 } else if ($scope.InitInfo.Store) { 2883 state = $scope.InitInfo.Store.state; 2884 } 2885 } else { 2886 state = $scope.ShippingInfo.State; 2887 } 2888 if ($scope.getStoreCountryKey() === $scope.getShippingCountryKey() 2889 || $scope.taxAtCountryLevel() 2890 || $scope.digitalOrder 2891 ) { 2892 if (state && $scope.InitInfo.TaxableRegions[state]) { 2893 if ($scope.knobby['module_ignore_empty_tax_rates']) { 2894 taxTable = angular.copy($scope.InitInfo.TaxableRegions[state]); 2895 for (let bitmap in taxTable.rates) { 2896 let jurisdiction = taxTable.rates[bitmap]; 2897 if (jurisdiction.rate <= 0 && jurisdiction.tax_type !== 'dynamic rate') { 2898 delete taxTable.rates[bitmap]; 2899 } 2900 } 2901 } else { 2902 taxTable = $scope.InitInfo.TaxableRegions[state]; 2903 } 2904 } 2905 } 2906 } 2907 2908 if (taxTable === null && $scope.ShippingInfo.State && !$scope.digitalOrder) { 2909 taxTable = $scope.InitInfo.TaxableRegions[$scope.ShippingInfo.State]; 2910 } 2911 2912 $scope.HasDynamicTaxes = false;
2913 2914 if (taxTable) { 2915 var rates = taxTable.rates; 2916 for (var bitmap in rates) { 2917 var rate = rates[bitmap]; 2918 if (rate.tax_type === 'dynamic rate') { 2919 $scope.HasDynamicTaxes = true; 2920 break; 2921 } 2922 } 2923 } 2924 2925 if (taxTable && useStoreTaxTable) { 2926 var hasNonDynamicTax = false; 2927 var rates = taxTable.rates; 2928 for (var bitmap in rates) { 2929 var rate = rates[bitmap]; 2930 if (+bitmap & +taxBitmap) { 2931 if (rate.tax_type !== 'dynamic rate') { 2932 hasNonDynamicTax = true; 2933 break; 2934 } 2935 } 2936 } 2937 2938 // Ignore store taxes when dynamic taxes are presented 2939 if (hasNonDynamicTax && !$scope.HasDynamicTaxes) { 2940 return taxTable; 2941 } 2942 } 2943 2944 if (!$scope.InitInfo.TaxableRegions) { 2945 return taxTable; 2946 } 2947 2948 //dynamic_id == 2 Cali 2949 if (typeof $scope.InitInfo.TaxableRegions['California'] !== 'undefined') { 2950 if (state && state == 'CA') { 2951 var caData = $scope.InitInfo.TaxableRegions['California']; 2952 var rates = caData['rates']; 2953 for (var bitmap in rates) { 2954 var taxInfo = rates[bitmap]; 2955 if (typeof taxInfo['dynamic_id'] !== 'undefined' && taxInfo['dynamic_id'] == '2') { 2956 taxTable = caData; 2957 $scope.HasDynamicTaxes = true; 2958 break; 2959 } 2960 } 2961 } 2962 } 2963 2964 //dynamic_id == 3 Taxjar 2965 if (typeof $scope.InitInfo.TaxableRegions['All'] !== 'undefined') { 2966 var taxJarData = $scope.InitInfo.TaxableRegions['All']; 2967 var rates = taxJarData['rates']; 2968 for (var bitmap in rates) { 2969 var taxInfo = rates[bitmap]; 2970 if (typeof taxInfo['dynamic_id'] !== 'undefined' && taxInfo['dynamic_id'] == '3') { 2971 taxTable = taxJarData; 2972 $scope.HasDynamicTaxes = true; 2973 break; 2974 } 2975 } 2976 } 2977 2978 //dynamic_id == 4 Avalara 2979 if (typeof $scope.InitInfo.TaxableRegions['All'] !== 'undefined') { 2980 var avalaraData = $scope.InitInfo.TaxableRegions['All']; 2981 var rates = avalaraData['rates']; 2982 2983 for (var bitmap in rates) { 2984 var taxInfo = rates[bitmap]; 2985 if (typeof taxInfo['dynamic_id'] !== 'undefined' && taxInfo['dynamic_id'] == '4') { 2986 taxTable = avalaraData; 2987 $scope.HasDynamicTaxes = true; 2988 2989 break; 2990 } 2991 } 2992 } 2993 2994 return taxTable; 2995 }; 2996 2997 $scope.setTransactionJurisdictionTaxes = function (tjtArray) { 2998 if (!tjtArray) { 2999 tjtArray = {}; 3000 } 3001 $scope.JurisdictionalTax = JSON.stringify(tjtArray); 3002 $scope.JurisdictionalTaxes = tjtArray; 3003 var jurisdictional_taxes = $scope.JurisdictionalTaxes; 3004 if (jurisdictional_taxes['Shipping']) { 3005 delete jurisdictional_taxes['Shipping']; 3006 } 3007 }; 3008 3009 $scope.updateDynamicTaxes = function () { 3010 // Update dynamic taxes flag 3011 var taxTable = $scope.getTaxTable($scope.calculateWaTaxesBasedOnStoreLocationAddress()); 3012 3013 if (!$scope.HasDynamicTaxes) { 3014 return $scope.NoOpPromise(); 3015 } 3016 3017 if (!taxTable || !taxTable.rates) { 3018 return $scope.NoOpPromise(); 3019 } 3020 3021 var requestDynamicTaxes = function (rate) { 3022 $scope.setProcessing(true); 3023 3024 var dynamicTaxRequest = $scope.isDynamicTaxServiceEnabled() ? $scope.getDynamicRateForOrder(rate) : $scope.getDynamicRate(rate); 3025 3026 return dynamicTaxRequest.then(function () { 3027 $scope.TotalsCalculate(); 3028 3029 $scope.safeApply(); 3030 }).finally(function () { 3031 $scope.setProcessing(false); 3032 }); 3033 }; 3034 3035 var dynamicRates = Object.entries(taxTable.rates) 3036 .map(function (entry) { 3037 var bitmap = entry[0]; 3038 var rate = entry[1]; 3039 3040 return Object.assign({}, rate, { 3041 bitmap: bitmap 3042 }); 3043 }) 3044 .filter(function (rate) { 3045 return rate.tax_type === 'dynamic rate'; 3046 }); 3047 3048 if (dynamicRates.length) { 3049 return requestDynamicTaxes(dynamicRates[0]); 3050 } 3051
3052 return $scope.NoOpPromise(); 3053 }; 3054 3055 $scope.updateDynamicTaxesOnce = function () { 3056 if (!window.PromiseAccumulator) { 3057 return $scope.updateDynamicTaxes(); 3058 } 3059 3060 if (!$scope.dynamicTaxesPromiseAccumulator) { 3061 $scope.dynamicTaxesPromiseAccumulator = new PromiseAccumulator(function () { 3062 return $scope.updateDynamicTaxes(); 3063 }); 3064 } 3065 3066 if (!$scope.checkFlagBlockCheckoutOnDynamicTaxFailure()) { 3067 return $scope.dynamicTaxesPromiseAccumulator.getPromise(); 3068 } 3069 3070 var promise = $scope.dynamicTaxesPromiseAccumulator.getPromise(); 3071 $scope.dynamicTaxesInFlightPromise = promise; 3072 3073 promise.finally(function () { 3074 3075 if ($scope.dynamicTaxesInFlightPromise === promise) { 3076 $scope.dynamicTaxesInFlightPromise = null; 3077 } 3078 }); 3079 3080 return promise; 3081 }; 3082 3083 $scope.getDynamicRate = function (rate) { 3084 var token = $scope.InitInfo.store_token; 3085 var url = $scope.RainApiUrl + 'store/taxes/dynamic-jurisdiction'; 3086 var headers = {}; 3087 if (token) { 3088 headers.Authorization = 'Bearer ' + token; 3089 } 3090 3091 var destination = $scope.digitalOrder ? $scope.BillingInfo : $scope.ShippingInfo; 3092 var street1 = null; 3093 if (destination.Address1) { 3094 street1 = destination.Address1; 3095 } 3096 3097 var street2 = null; 3098 if (destination.Address2) { 3099 street2 = destination.Address2; 3100 } 3101 3102 var city = null; 3103 if (destination.City) { 3104 city = destination.City; 3105 } 3106 3107 var state = null; 3108 if (destination.State) { 3109 state = destination.State; 3110 } 3111 3112 var zip = null; 3113 if (destination.Zip) { 3114 zip = destination.Zip; 3115 } 3116 3117 var country = null; 3118 if (destination.Country) { 3119 country = destination.Country; 3120 } 3121 3122 var shouldUseStoreAddress = $scope.calculateWaTaxesBasedOnStoreLocationAddress() && (!zip || zip === ''); 3123 3124 if (shouldUseStoreAddress && $scope.InitInfo && $scope.InitInfo.Store) { 3125 var store = $scope.InitInfo.Store; 3126 street1 = store.address1 || street1; 3127 street2 = store.address2 || street2; 3128 city = store.city || city; 3129 state = store.state || state; 3130 zip = store.zip || zip; 3131 country = store.country || $scope.InitInfo.DomesticCountry || country; 3132 } 3133 3134 if ($scope.isPickup()) { 3135 var option = $scope.ShippingInfo.Option; 3136 if (option.address) { 3137 street1 = option.address.street1; 3138 street2 = option.address.street2; 3139 city = option.address.city; 3140 state = option.address.state; 3141 zip = option.address.postalCode; 3142 country = option.address.country; 3143 } 3144 } 3145 3146 // no zip, nothing to do 3147 if (typeof zip === 'undefined' || zip === null || zip === '') { 3148 return $scope.NoOpPromise(); 3149 } 3150 3151 // string together the URL params, will also be used as the cache key 3152 var params = ''; 3153 params += '?zip=' + zip; 3154 params += '&tax_type_id=' + rate.pos_tax_type_id; 3155 if (city) { 3156 params += '&city=' + city; 3157 } 3158 if (street1) { 3159 params += '&addr=' + street1; 3160 if (street2) { 3161 params += ' ' + street2; 3162 } 3163 } 3164 if (state) { 3165 params += '&state=' + state; 3166 } 3167 if (country) { 3168 params += '&country=' + country; 3169 } 3170 3171 // have we made this request before? 3172 var key = params; 3173 if ($scope.useDynamicTaxesCache() && key === $scope.dynamicRateCacheKey && $scope.hasDynamicRateCache()) { 3174 console.log("Use cached rate for key " + $scope.dynamicRateCacheKey); 3175 return $scope.NoOpPromise($scope.dynamicRateCache); 3176 } 3177 3178 // slap the params on the url 3179 url += params; 3180 3181 var request = $http({ 3182 method: 'GET', 3183 url: url, 3184 headers: headers 3185 }); 3186 3187 return request.then(function (result) { 3188 if (result.data.error) { 3189 $scope.dynamicTaxesReady = false;
3190 3191 return Promise.reject(new Error(result.data.error)); 3192 } else { 3193 $scope.dynamicRateCacheKey = key; 3194 $scope.dynamicRateCache = result.data; 3195 3196 return result.data; 3197 } 3198 }, function (error) { 3199 $scope.dynamicTaxesReady = false; 3200 3201 throw new Error(error.data.error); 3202 }); 3203 }; 3204 3205 $scope.invalidateDynamicTaxCache = function() { 3206 $scope.dynamicRateCache = null; 3207 $scope.dynamicRateCacheKey = null; 3208 }; 3209 3210 $scope.hasDynamicRateCache = function () { 3211 if (!$scope.dynamicRateCache) { 3212 return false; 3213 } 3214 3215 if (Array.isArray($scope.dynamicRateCache)) { 3216 return $scope.dynamicRateCache.length > 0; 3217 } 3218 3219 if (typeof $scope.dynamicRateCache === 'object') { 3220 return Object.keys($scope.dynamicRateCache).length > 0; 3221 } 3222 3223 return true; 3224 }; 3225 3226 $scope.useDynamicTaxesCache = function() { 3227 var allowCacheForDynamicTaxes = ['WA State']; 3228 3229 if ($scope.HasDynamicTaxes && $scope.isDynamicTaxServiceEnabled() && !allowCacheForDynamicTaxes.includes($scope.InitInfo.dynamic_tax_service)) { 3230 return false; 3231 } 3232 3233 return true; 3234 }; 3235 3236 $scope.getDynamicRateForOrder = function (rate) { 3237 if (!$scope.checkFlagBlockCheckoutOnDynamicTaxFailure()) { 3238 $scope.StatusInfo.DynamicTaxError = ''; //we need to reset vars like that before each request 3239 $scope.StatusInfo.DynamicTaxAddressError = ''; 3240 } 3241 3242 var isStaleDynamicTaxResponse = function () { 3243 return false; 3244 }; 3245 3246 if ($scope.checkFlagBlockCheckoutOnDynamicTaxFailure()) { 3247 var dynamicTaxRequestId = $scope.dynamicTaxRequestId || 0; 3248 3249 isStaleDynamicTaxResponse = function () { 3250 return dynamicTaxRequestId !== ($scope.dynamicTaxRequestId || 0); 3251 }; 3252 } 3253 3254 $scope.status.error = ''; 3255 var token = $scope.InitInfo.store_token; 3256 var url = $scope.RainApiUrl + 'store/taxes/dynamic-jurisdiction'; 3257 var headers = {}; 3258 if (token) { 3259 headers.Authorization = 'Bearer ' + token; 3260 } 3261 3262 $scope.invalidateDynamicTaxCache(); 3263 3264 var destination = $scope.digitalOrder ? $scope.BillingInfo : $scope.ShippingInfo; 3265 3266 var zip = destination.Zip; 3267 var state = destination.State; 3268 var city = destination.City; 3269 var country = destination.Country; 3270 3271 if ($scope.isPickup() && $scope.ShippingInfo && $scope.ShippingInfo.Option && $scope.ShippingInfo.Option.address) { 3272 zip = $scope.ShippingInfo.Option.address.postalCode; 3273 state = $scope.ShippingInfo.Option.address.state; 3274 city = $scope.ShippingInfo.Option.address.city; 3275 country = $scope.InitInfo.Countries[$scope.ShippingInfo.Option.address.country] || $scope.ShippingInfo.Option.address.country; 3276 } 3277 3278 if (!zip || !state || !city) { 3279 return $scope.NoOpPromise(); 3280 } 3281 3282 // When the WA taxes glitch flag is enabled and dynamic tax caching is allowed, 3283 // only invalidate the dynamic tax cache when the relevant address inputs or tax type change. 3284 if ($scope.useDynamicTaxesCache) { 3285 var address1 = (destination.Address1 || destination.address1 || destination.Street || destination.street || ''); 3286 var address2 = (destination.Address2 || destination.address2 || destination.street2 || ''); 3287 3288 if ($scope.isPickup() && $scope.ShippingInfo && $scope.ShippingInfo.Option && $scope.ShippingInfo.Option.address) { 3289 var pickupAddress = $scope.ShippingInfo.Option.address; 3290 address1 = (pickupAddress.Address1 || pickupAddress.address1 || pickupAddress.line1 || pickupAddress.street || address1); 3291 address2 = (pickupAddress.Address2 || pickupAddress.address2 || pickupAddress.line2 || pickupAddress.street2 || address2); 3292 } 3293 3294 var taxTypeId = null; 3295 if (rate && rate.taxTypeId) { 3296 taxTypeId = rate.taxTypeId; 3297 } else if ($scope.InitInfo && $scope.InitInfo.dynamic_tax_type_id ) { 3298 taxTypeId = $scope.InitInfo.dynamic_tax_type_id; 3299 } 3300 3301 var dynamicTaxCacheKey = [ 3302 zip || '', 3303 state || '', 3304 city || '', 3305 country || '', 3306 address1 || '', 3307 address2 || '', 3308 taxTypeId || '' 3309 ].join('|'); 3310 3311 if ($scope.lastDynamicTaxCacheKey !== dynamicTaxCacheKey) { 3312 $scope.invalidateDynamicTaxCache(); 3313 $scope.lastDynamicTaxCacheKey = dynamicTaxCacheKey; 3314 } 3315 } 3316 var shippingPrice = 0; 3317 3318 if (parseFloat(destination.OptionId) && destination.Options !== null && (destination.Options[destination.OptionId] !== undefined && destination.Options[destination.OptionId] !== null)) { 3319 shippingPrice = parseFloat(destination.Options[destination.OptionId].price) 3320 } 3321 3322 // have we made this request before?
3323 var key = zip + ':' + state + ':' + city; 3324 if (destination.OptionId) { 3325 key += destination.OptionId; 3326 } 3327 3328 if ($scope.useDynamicTaxesCache() && key === $scope.dynamicRateCacheKey && $scope.hasDynamicRateCache()) { 3329 console.log("Use cached rate for key " + $scope.dynamicRateCacheKey); 3330 return $scope.NoOpPromise($scope.dynamicRateCache); 3331 } 3332 3333 var street = ''; 3334 if (destination.Address1) { 3335 street += destination.Address1; 3336 } 3337 if (destination.Address2) { 3338 street += ' ' + destination.Address2; 3339 } 3340 3341 var items = []; 3342 var shipping = { 3343 street: street, 3344 city: city, 3345 state: state, 3346 zip: zip, 3347 country: country, 3348 price: shippingPrice 3349 }; 3350 3351 //Create line for sending it to tax service 3352 for (var i = 0; i < $scope.CartData.Items.length; i++) { 3353 var line = $scope.CartData.Items[i]; 3354 var type = line.type.toLowerCase(); 3355 if (type == 'pslr') { 3356 type = 'product'; 3357 } 3358 3359 // Class taxes handled in a different way 3360 if (type === 'class') { 3361 continue; 3362 } 3363 3364 var lineObj = { 3365 title: line.title, 3366 qty: parseFloat(line.qty), 3367 price: parseFloat(line.price), 3368 type: type, 3369 productId: parseInt(line.product_id), 3370 pslrId: line.pslr_id ? parseInt(line.pslr_id) : null, 3371 }; 3372 3373 items.push(lineObj); 3374 } 3375 3376 if ($scope.checkFlagRetailDeliveryFee() && shipping.state === 'CO') { 3377 var lineRetailDeliveryFee = { 3378 title: 'RetailDeliveryFee', 3379 qty: 1, 3380 productId: null, 3381 price: 0, 3382 type: 'shipping_fee', 3383 }; 3384 items.push(lineRetailDeliveryFee); 3385 } 3386 3387 var orderData = { 3388 shipping: shipping, 3389 items: items, 3390 taxTypeId: parseInt(rate.pos_tax_type_id) 3391 } 3392 3393 return $http.post(url, orderData, {headers: headers}).then(function (result) { 3394 if ($scope.checkFlagBlockCheckoutOnDynamicTaxFailure() && isStaleDynamicTaxResponse()) { 3395 return; 3396 } 3397 3398 if (result.data.error) { 3399 if ($scope.checkFlagBlockCheckoutOnDynamicTaxFailure()) { 3400 $scope.StatusInfo.DynamicTaxError = result.data.error.message || result.data.error; 3401 $scope.dynamicTaxesReady = false; 3402 } else { 3403 $scope.status.error = result.data.error; 3404 } 3405 } else { 3406 if ($scope.checkFlagBlockCheckoutOnDynamicTaxFailure()) { 3407 $scope.StatusInfo.DynamicTaxError = ''; 3408 $scope.StatusInfo.DynamicTaxAddressError = ''; 3409 $scope.dynamicTaxesReady = true; 3410 } 3411 3412 $scope.dynamicRateCacheKey = key; 3413 $scope.dynamicTotalTax = parseFloat(result.data.totalTax); 3414 $scope.dynamicRateCache = result.data.rates; 3415 if ($scope.checkFlagAvalaraTaxCodeIssue()) { 3416 for(var i = 0; i < result.data.lines_applied_jurisdictions?.length; i++) { 3417 if (result.data.lines_applied_jurisdictions[i][0].taxable_amount === 0){ 3418 $scope.CartData.Items[i].nonTaxableItem = true; 3419 } else { 3420 $scope.CartData.Items[i].nonTaxableItem = false; 3421 } 3422 } 3423 } 3424 if ($scope.checkFlagRetailDeliveryFee()) { 3425 Object.values(result.data.lines_applied_jurisdictions).forEach(function (items) { 3426 items.forEach(function (item) { 3427 if (item.jurisdiction.title === 'Retail Delivery Fee') { 3428 $scope.retailDeliveryFeeTaxJurisdiction = item.jurisdiction; 3429 } 3430 }); 3431 }); 3432 } 3433 return result.data.rates; 3434 } 3435 }, function (result) { 3436 if ($scope.checkFlagBlockCheckoutOnDynamicTaxFailure() && isStaleDynamicTaxResponse()) { 3437 return; 3438 } 3439 3440 if ($scope.checkFlagBlockCheckoutOnDynamicTaxFailure()) { 3441 $scope.dynamicTaxesReady = false;
3442 } 3443 3444 if (result.data) { 3445 if (result.data.error && result.data.error.error_code && result.data.error.error_code === 'address_error') { 3446 $scope.StatusInfo.DynamicTaxAddressError = 'We were unable to match your address, please check address'; 3447 } else { 3448 if (typeof result.data.error !== "undefined" && typeof result.data.error.message !== "undefined") { 3449 $scope.StatusInfo.DynamicTaxError = result.data.error.message; 3450 } else { 3451 $scope.StatusInfo.DynamicTaxError = result.data; 3452 } 3453 } 3454 } else { 3455 $scope.StatusInfo.DynamicTaxError = "An unhandled error has occurred: Dynamic Tax"; 3456 } 3457 $scope.dynamicTotalTax = 0; 3458 $scope.dynamicRateCache = null; 3459 $scope.dynamicRateCacheKey = null; 3460 }); 3461 }; 3462 3463 $scope.useCanadaTax = function () { 3464 return ($scope.InitInfo.useCanadaTax && $scope.getShippingCountryKey() === 'CAN'); 3465 }; 3466 3467 $scope.useWashingtonTax = function () { 3468 return $scope.InitInfo.useWashingtonTax; 3469 }; 3470 3471 // keep in sync with Cart.model 3472 $scope.getDiscountableTotalForPslrLimitedCoupon = function (coupon) { 3473 var discountable = 0; 3474 var discountableQty = 0; 3475 3476 for (var i = 0; i < $scope.CartData.Items.length; i++) { 3477 var item = $scope.CartData.Items[i]; 3478 3479 if (item.type != 'pslr' || item.discount_eligible == 'false') { 3480 continue; 3481 } 3482 var pslr = item.id; 3483 for (var p = 0; p < coupon.pslrIdArray.length; p++) { 3484 var valid = coupon.pslrIdArray[p]; 3485 console.log('compare ' + valid + ' to ' + pslr); 3486 if (valid == pslr) { 3487 if ($scope.InitInfo?.CheckoutSettings?.DisableCouponsForSalePriceItems === true && item.on_sale) { 3488 coupon.disabledForSalePriceItem = true; 3489 continue; 3490 } 3491 3492 item.discount_applied = true; 3493 discountable += item.total; 3494 3495 if ($scope.knobby['module_better_coupons']) { 3496 if (coupon.appliedLineUuid.indexOf(item.uuid) === -1) { 3497 coupon.appliedLineUuid.push(item.uuid); 3498 } 3499 } 3500 3501 break; 3502 } 3503 } 3504 } 3505 3506 if ($scope.isDollarOffFiltersAllowed()) { 3507 return {discountable: discountable, discountableQty: discountableQty}; 3508 } 3509 3510 return discountable; 3511 }; 3512 3513 $scope.getDiscountableTotalForClassLimitedCoupon = function (coupon) { 3514 var discountable = 0; 3515 var discountableQty = 0; 3516 3517 for (var i = 0; i < $scope.CartData.Items.length; i++) { 3518 var item = $scope.CartData.Items[i]; 3519 3520 if (item.type != 'class' || item.discount_eligible == 'false') { 3521 continue; 3522 } 3523 var classId = item.quiltClassId; 3524 for (var p = 0; p < coupon.classIdArray.length; p++) { 3525 var valid = coupon.classIdArray[p]; 3526 if (valid == classId) { 3527 if ($scope.InitInfo?.CheckoutSettings?.DisableCouponsForSalePriceItems === true && item.on_sale) { 3528 coupon.disabledForSalePriceItem = true; 3529 continue; 3530 } 3531 3532 item.discount_applied = true; 3533 discountable += item.total; 3534 3535 if ($scope.knobby['module_better_coupons']) { 3536 if (coupon.appliedLineUuid.indexOf(item.uuid) === -1) { 3537 coupon.appliedLineUuid.push(item.uuid); 3538 } 3539 } 3540 3541 break; 3542 } 3543 } 3544 } 3545 3546 if ($scope.isDollarOffFiltersAllowed()) { 3547 return {discountable: discountable, discountableQty: discountableQty}; 3548 } 3549 3550 return discountable; 3551 }; 3552 3553 // keep in sync with Cart.model 3554 $scope.getDiscountableTotalForDeptLimitedCoupon = function (coupon) { 3555 var discountable = 0; 3556 var discountableQty = 0; 3557 3558 console.info("Applying Department Coupon"); 3559 console.group(); 3560 console.info({s: 'Dep Array', o: coupon.departmentIdArray}
3560); 3561 for (var i = 0; i < $scope.CartData.Items.length; i++) { 3562 var item = $scope.CartData.Items[i]; 3563 3564 if (item.type !== 'pslr' || item.discount_eligible == 'false' || typeof item.departmentId === 'undefined') { 3565 console.info({s: "Item Not discountable", o: item}); 3566 continue; 3567 } 3568 var deptId = parseInt(item.departmentId); 3569 for (var d = 0; d < coupon.departmentIdArray.length; d++) { 3570 var valid = coupon.departmentIdArray[d]; 3571 if (valid == deptId) { 3572 if ($scope.InitInfo?.CheckoutSettings?.DisableCouponsForSalePriceItems === true && item.on_sale) { 3573 coupon.disabledForSalePriceItem = true; 3574 continue; 3575 } 3576 3577 discountable += item.total; 3578 item.discount_applied = true; 3579 3580 if ($scope.knobby['module_better_coupons']) { 3581 if (coupon.appliedLineUuid.indexOf(item.uuid) === -1) { 3582 coupon.appliedLineUuid.push(item.uuid); 3583 } 3584 } 3585 3586 if ($scope.isDollarOffFiltersAllowed()) { 3587 discountableQty++; 3588 } 3589 3590 console.info({s: "item discounted", o: item}); 3591 break; 3592 } 3593 } 3594 } 3595 console.groupEnd(); 3596 3597 if ($scope.isDollarOffFiltersAllowed()) { 3598 return {discountable: discountable, discountableQty: discountableQty}; 3599 } 3600 3601 return discountable; 3602 }; 3603 3604 $scope.getDiscountableTotalForItemTypesLimitedCoupon = function (coupon) { 3605 var discountable = 0; 3606 var discountableQty = 0; 3607 var itemType; 3608 3609 for (var i = 0; i < $scope.CartData.Items.length; i++) { 3610 var item = $scope.CartData.Items[i]; 3611 3612 if ($scope.knobby['module_better_coupons']) { 3613 if (item.discount_eligible == 'false' || typeof item.type === 'undefined') { 3614 continue; 3615 } 3616 } else { 3617 if (item.package_id || item.discount_eligible == 'false' || typeof item.type === 'undefined') { 3618 continue; 3619 } 3620 } 3621 3622 if (!is_set(item.type)) { 3623 continue; 3624 } 3625 3626 if (item.product_id) { 3627 itemType = 'products'; 3628 } else if (item.type === 'class' && item.package_id && item.package_id > 0) { 3629 itemType = 'packages'; 3630 } else if (item.type === 'class') { 3631 itemType = 'classes'; 3632 } 3633 3634 var itemDiscountable = 0; 3635 3636 if (coupon.itemTypesArray.indexOf(itemType) !== -1) { 3637 if ($scope.InitInfo?.CheckoutSettings?.DisableCouponsForSalePriceItems === true && item.on_sale) { 3638 coupon.disabledForSalePriceItem = true; 3639 continue; 3640 } 3641 3642 item.discount_applied = true; 3643 3644 if ($scope.knobby['module_better_coupons']) { 3645 itemDiscountable += item.qty * $scope.getItemPriceForWeb(item); 3646 discountable += item.qty * $scope.getItemPriceForWeb(item); 3647 3648 if (coupon.appliedLineUuid.indexOf(item.uuid) === -1) { 3649 coupon.appliedLineUuid.push(item.uuid); 3650 } 3651 } else { 3652 itemDiscountable += item.total; 3653 discountable += item.total; 3654 } 3655 3656 if ($scope.isDollarOffFiltersAllowed()) { 3657 discountableQty++; 3658 } 3659 } 3660 } 3661 3662 if ($scope.isDollarOffFiltersAllowed()) { 3663 return {discountable: discountable, discountableQty: discountableQty}; 3664 } 3665 3666 return discountable; 3667 }; 3668 3669 // keep in sync with Cart.model 3670 $scope.getDiscountableTotalForCategoryLimitedCoupon = function (coupon) { 3671 var discountable = 0; 3672 var discountableQty = 0; 3673 3674 console.log(coupon.categoryIdArray); 3675 3676 // Each item in the cart 3677 for (var i = 0; i < $scope.CartData.Items.length; i++) { 3678 var item = $scope.CartData.Items[i]; 3679 if (item.type != 'pslr' || item.discount_eligible == 'false' || typeof item.categoryIdArray === 'undefined') { 3680 continue; 3681 } 3682 console.log(item); 3683 var itemCategoryIds = item.categoryIdArray; 3684 3685 // this item doesn't need checked 3686 if (itemCategoryIds === null) { 3687 continue; 3688 } 3689 3690 // Each coupon 3691 var itemDiscountable = 0; 3692 for (var c = 0; c < coupon.categoryIdArray.length; c++) { 3693 var couponCategoryId = parseInt(coupon.categoryIdArray[c]); 3694 3695 // each category this item is in - does it match the coupon category? 3696 for (var ic = 0; ic < itemCategoryIds.length; ic++) { 3697 var catId = parseInt(itemCategoryIds[ic]); 3698 console.log('compare ' + couponCategoryId + ' to ' + catId); 3699 if (couponCategoryId == catId) { 3700 if ($scope.InitInfo?.CheckoutSettings?.DisableCouponsForSalePriceItems === true && item.on_sale) { 3701 coupon.disabledForSalePriceItem = true; 3702 continue; 3703 } 3704 3705 item.discount_applied = true; 3706 itemDiscountable += item.total; 3707 discountable += item.total; 3708 3709 if ($scope.knobby['module_better_coupons']) { 3710 if (coupon.appliedLineUuid.indexOf(item.uuid) === -1) { 3711 coupon.appliedLineUuid.push(item.uuid); 3712 } 3713 } 3714 3715 if ($scope.isDollarOffFiltersAllowed()) { 3716 discountableQty++; 3717 } 3718 3719 break; 3720 } 3721 } 3722 if (itemDiscountable > 0) { 3723 break; 3724 } 3725 } 3726 } 3727 3728 if ($scope.isDollarOffFiltersAllowed()) { 3729 return {discountable: discountable, discountableQty: discountableQty}; 3730 } 3731 3732 return discountable; 3733 }; 3734 3735 $scope.getDiscountableTotalForVendorLimitedCoupon = function (coupon) { 3736 var discountable = 0; 3737 var discountableQty = 0; 3738
3739 $scope.CartData.Items.forEach(function (item) { 3740 if (item.type !== 'pslr' || item.discount_eligible === 'false' || typeof item.pos_vendor_id === 'undefined' || item.pos_vendor_id === null) { 3741 return; 3742 } 3743 3744 coupon.vendorIdArray = coupon.vendorIdArray.map(function (vendorId) { 3745 return parseInt(vendorId); 3746 }); 3747 3748 if (coupon.vendorIdArray.indexOf(item.pos_vendor_id) !== -1) { 3749 if ($scope.InitInfo?.CheckoutSettings?.DisableCouponsForSalePriceItems === true && item.on_sale) { 3750 coupon.disabledForSalePriceItem = true; 3751 return; 3752 } 3753 3754 item.discount_applied = true; 3755 discountable += item.total; 3756 3757 if ($scope.knobby['module_better_coupons']) { 3758 if (coupon.appliedLineUuid.indexOf(item.uuid) === -1) { 3759 coupon.appliedLineUuid.push(item.uuid); 3760 } 3761 } 3762 3763 if ($scope.isDollarOffFiltersAllowed()) { 3764 discountableQty++; 3765 } 3766 } 3767 }); 3768 3769 if ($scope.isDollarOffFiltersAllowed()) { 3770 return {discountable: discountable, discountableQty: discountableQty}; 3771 } 3772 3773 return discountable; 3774 }; 3775 3776 $scope.getDiscountableTotalForMnfLimitedCoupon = function (coupon) { 3777 var discountable = 0; 3778 var discountableQty = 0; 3779 3780 $scope.CartData.Items.forEach(function (item) { 3781 if (item.type !== 'pslr' || item.discount_eligible === 'false' || typeof item.manufacturer_name === 'undefined' || item.manufacturer_name === null) { 3782 return; 3783 } 3784 3785 if (coupon.manufacturerNameArray.indexOf(item.manufacturer_name) !== -1) { 3786 if ($scope.InitInfo?.CheckoutSettings?.DisableCouponsForSalePriceItems === true && item.on_sale) { 3787 coupon.disabledForSalePriceItem = true; 3788 return; 3789 } 3790 3791 item.discount_applied = true; 3792 discountable += item.total; 3793 3794 if ($scope.knobby['module_better_coupons']) { 3795 if (coupon.appliedLineUuid.indexOf(item.uuid) === -1) { 3796 coupon.appliedLineUuid.push(item.uuid); 3797 } 3798 } 3799 3800 if ($scope.isDollarOffFiltersAllowed()) { 3801 discountableQty++; 3802 } 3803 } 3804 }); 3805 3806 if ($scope.isDollarOffFiltersAllowed()) { 3807 return {discountable: discountable, discountableQty: discountableQty}; 3808 } 3809 3810 return discountable; 3811 }; 3812 3813 function isDiscountable(item) { 3814 return item.type != 'gc' && (item.discount_eligible === true || item.discount_eligible === 'true'); 3815 } 3816 3817//help solve the magic penny issue with discounts 3818 $scope.MagicPennyDiscount = function(discount_left) { 3819 discount_left = $scope.RoundTo2Dec(discount_left); 3820 if (discount_left != 0) { 3821 //we are ignoring a few pennies from the discount so lets put them into the first available discountable item 3822 for (var j = 0; j < $scope.CartData.Items.length; j++) { 3823 var item = $scope.CartData.Items[j]; 3824 if(discount_left < 0 ){ 3825 var disc = -.01; 3826 } else { 3827 var disc = .01; 3828 } 3829 if (isDiscountable(item) && item.discount_applied && (item.total - item.discount - disc >= 0)) { 3830 //subtract one penny at a time add it to each discount until we are out of discount 3831 3832 discount_left -= disc; 3833 discount_left = Math.round(discount_left * 100) / 100; 3834 disc = Math.round(disc * 100) / 100; 3835 item.discount += disc; 3836 item.discount = Math.round(item.discount * 100) / 100; 3837 item.discounted_total = $scope.roundNumber(item.total - item.discount, 3); 3838 } 3839 if (Math.abs(discount_left) < .01) { 3840 break; 3841 } 3842 3843 } 3844 3845 } 3846 3847 } 3848 3849 $scope.getDiscountableTotalForDiscountableLines = function() { 3850 var discountable = 0; 3851 3852 for (var i = 0; i < $scope.CartData.Items.length; i++) { 3853 var item = $scope.CartData.Items[i]; 3854 if ($scope.InitInfo?.CheckoutSettings?.DisableCouponsForSalePriceItems === true && item.on_sale) { 3855 continue; 3856 } 3857 3858 if (isDiscountable(item)) { 3859 discountable += item.total; 3860 if (item.type === 'class' && item.package_items && Array.isArray(item.package_items) && item.package_items.length > 0) { 3861 for (var j = 0; j < item.package_items.length; j++) { 3862 if ($scope.knobby['module_fix_coupon_min_purchase_class_addon']) { 3863 discountable += item.package_items[j]['price'] * item.qty; 3864 } else { 3865 discountable += item.package_items[j]['price']; 3866 } 3867 } 3868 } 3869 } 3870 } 3871 3872 return discountable; 3873 } 3874 3875 $scope.getCouponEligibilityType = function (coupon) { 3876 if (coupon.pslrIdArray && coupon.pslrIdArray.length) { 3877 return 'item'; 3878 } else if (coupon.classIdArray && coupon.classIdArray.length) { 3879 return 'item'; 3880 } else if (coupon.departmentIdArray && coupon.departmentIdArray.length) { 3881 return 'department'; 3882 } else if (coupon.categoryIdArray && coupon.categoryIdArray.length) { 3883 return 'category'; 3884 } else if (coupon.itemTypesArray && coupon.itemTypesArray.length) { 3885 return 'itemType'; 3886 } else if (coupon.vendorIdArray && coupon.vendorIdArray.length) { 3887 return 'vendor'; 3888 } else if (coupon.manufacturerNameArray && coupon.manufacturerNameArray.length) { 3889 return 'manufacturer'; 3890 } 3891 3892 return null; 3893 } 3894 3895 $scope.isEligibleByItems = function (coupon) { 3896 const itemsIds = $scope.CartData.Items.map(function (item) { 3897 return parseInt(item.id); 3898 }); 3899 var hasEligiblePslrs = coupon.pslrIdArray.some(function (pslrId) { 3900 return itemsIds.includes(parseInt(pslrId)); 3901 }); 3902 3903 var hasEligibleClasses = coupon.classIdArray.some(function (classId) { 3904 if (!!$scope.knobby && $scope.knobby['module_fix_web_class_coupon_id_matching']) { 3905 return $scope.CartData.Items.some(function (item) { 3906 return item.type === 'class' && parseInt(item.quiltClassId) === parseInt(classId); 3907 }); 3908 } 3909 return itemsIds.includes(parseInt(classId)); 3910 }); 3911 3912 return hasEligiblePslrs || hasEligibleClasses; 3913 } 3914 3915 $scope.isEligibleByDepartments = function (coupon) { 3916 const departmentsIds = $scope.CartData.Items.map(function (item) { 3917 return item.departmentId; 3918 }); 3919 return coupon.departmentIdArray.some(function (departmentId) { 3920 return departmentsIds.includes(departmentId); 3921 }); 3922 } 3923 3924 $scope.isEligibleByCategories = function (coupon) { 3925 const categoriesIds = $scope.CartData.Items.reduce(function (acc, item) { 3926 return acc.concat(item.categoryIdArray); 3927 }, []); 3928 return coupon.categoryIdArray.some(function (categoryId) { 3929 return categoriesIds.includes(categoryId); 3930 }); 3931 } 3932 3933 $scope.isEligibleByItemTypes = function (coupon) { 3934 const itemsTypes = $scope.CartData.Items.map(function (item) { 3935 if (item.product_id) { 3936 return 'products'; 3937 } else if (item.type === 'class') { 3938 return 'classes'; 3939 } 3940 3941 return item.type; 3942 }); 3943 return coupon.itemTypesArray.some(function (itemType) { 3944 return itemsTypes.includes(itemType); 3945 }); 3946 } 3947 3948 $scope.isEligibleByVendors = function (coupon) { 3949 const vendorsIds = $scope.CartData.Items.map(function (item) { 3950 return item.pos_vendor_id; 3951 }); 3952 return coupon.vendorIdArray.some(function (vendorId) { 3953 return vendorsIds.includes(vendorId); 3954 }); 3955 } 3956 3957 $scope.isEligibleByManufacturers = function (coupon) { 3958 const manufacturersNames = $scope.CartData.Items.map(function (item) { 3959 return item.manufacturer_name; 3960 }); 3961 return coupon.manufacturerNameArray.some(function (manufacturerName) { 3962 return manufacturersNames.includes(manufacturerName); 3963 }); 3964 } 3965 3966 $scope.isEligibleCoupon = function (coupon) { 3967 switch($scope.getCouponEligibilityType(coupon)) { 3968 case 'item': 3969 return $scope.isEligibleByItems(coupon); 3970 case 'department': 3971 return $scope.isEligibleByDepartments(coupon); 3972 case 'category': 3973 return $scope.isEligibleByCategories(coupon); 3974 case 'itemType': 3975 return $scope.isEligibleByItemTypes(coupon); 3976 case 'vendor': 3977 return $scope.isEligibleByVendors(coupon); 3978 case 'manufacturer': 3979 return $scope.isEligibleByManufacturers(coupon); 3980 default: 3981 return true; 3982 } 3983 } 3984 3985 $scope.applyHighestSavingCouponsMark = function() { 3986 if ($scope.CartData?.Coupons?.length > 0) { 3987 var getCouponDiscountAbsValue = function (coupon) { 3988 var discountable = 0; 3989 3990 if ($scope.knobby['module_better_coupons']) { 3991 discountable = $scope.getDiscountableTotalForDiscountableLines(); 3992 } 3993 3994 if (coupon.couponType === 'percent_off') { 3995 if ($scope.knobby['module_better_coupons'] && coupon.couponBaseType === CouponConstants.coupon_bogo) { 3996 return coupon.dollarOffAmount; 3997 } 3998 3999 var discountableItem; 4000 4001 if (!$scope.knobby['module_better_coupons']) { 4002 discountable = $scope.SubTotal; 4003 } 4004 4005 if (coupon.pslrIdArray && coupon.pslrIdArray.length) { 4006 if ($scope.isDollarOffFiltersAllowed()) { 4007 discountableItem = $scope.getDiscountableTotalForPslrLimitedCoupon(coupon); 4008 discountable = discountableItem.discountable; 4009 } else { 4010 discountable = $scope.getDiscountableTotalForPslrLimitedCoupon(coupon); 4011 } 4012 } else if (coupon.classIdArray && coupon.classIdArray.length) { 4013 if ($scope.isDollarOffFiltersAllowed()) { 4014 discountableItem = $scope.getDiscountableTotalForClassLimitedCoupon(coupon); 4015 discountable = discountableItem.discountable; 4016 } else { 4017 discountable = $scope.getDiscountableTotalForClassLimitedCoupon(coupon); 4018 } 4019 } else if (coupon.departmentIdArray && coupon.departmentIdArray.length) { 4020 if ($scope.isDollarOffFiltersAllowed()) { 4021 discountableItem = $scope.getDiscountableTotalForDeptLimitedCoupon(coupon); 4022 discountable = discountableItem.discountable; 4023 } else { 4024 discountable = $scope.getDiscountableTotalForDeptLimitedCoupon(coupon); 4025 } 4026 } else if (coupon.categoryIdArray && coupon.categoryIdArray.length) { 4027 if ($scope.isDollarOffFiltersAllowed()) { 4028 discountableItem = $scope.getDiscountableTotalForCategoryLimitedCoupon(coupon); 4029 discountable = discountableItem.discountable; 4030 } else { 4031 discountable = $scope.getDiscountableTotalForCategoryLimitedCoupon(coupon); 4032 } 4033 } else if ($scope.knobby['module_better_coupons'] && coupon.itemTypesArray && coupon.itemTypesArray.length) { 4034 if ($scope.isDollarOffFiltersAllowed()) { 4035 discountableItem = $scope.getDiscountableTotalForItemTypesLimitedCoupon(coupon); 4036 discountable = discountableItem.discountable; 4037 } else { 4038 discountable = $scope.getDiscountableTotalForItemTypesLimitedCoupon(coupon); 4039 } 4040 } else if (coupon.vendorIdArray && coupon.vendorIdArray.length) { 4041 if ($scope.isDollarOffFiltersAllowed()) { 4042 discountable = discountableItem.discountable; 4043 } else { 4044 discountable = $scope.getDiscountableTotalForVendorLimitedCoupon(coupon); 4045 } 4046 } else if (coupon.manufacturerNameArray && coupon.manufacturerNameArray.length) { 4047 if ($scope.isDollarOffFiltersAllowed()) { 4048 discountableItem = $scope.getDiscountableTotalForMnfLimitedCoupon(coupon); 4049 discountable = discountableItem.discountable; 4050 } else { 4051 discountable = $scope.getDiscountableTotalForMnfLimitedCoupon(coupon); 4052 } 4053 } 4054 4055 return $scope.RoundTo2Dec(discountable * coupon.percentOff / 100); 4056 } else if (coupon.couponType === 'dollar_off') { 4057 if ($scope.knobby['module_better_coupons']) { 4058 if ($scope.isEligibleCoupon(coupon) && $scope.SubTotal >= coupon.minDollarAmount) { 4059 return coupon.dollarOffAmount; 4060 } 4061 } else { 4062 if ($scope.SubTotal >= coupon.minDollarAmount) { 4063 return coupon.dollarOffAmount; 4064 } 4065 } 4066 } else if (coupon.couponType === 'free_shipping_off') { 4067 if ($scope.ShippingInfo.Option) { 4068 return $scope.getShippingDiscount(); 4069 } 4070 } 4071 4072 return 0; 4073 } 4074 4075 if ($scope.knobby['module_better_coupons']) { 4076 var couponsForProcessing = $scope.CartData.Coupons; 4077 4078 if ($scope.InitInfo?.CheckoutSettings?.CouponStacking === 'applyAll') { 4079 var couponsForProcessing = $scope.CartData.Coupons.filter(function (coupon) { 4080 return coupon.couponBaseType === CouponConstants.coupon_bogo; 4081 }); 4082 } 4083 4084 couponsForProcessing = couponsForProcessing.filter(function(coupon) { 4085 return $scope.couponWithCustomerGroupsIsAvailable(coupon); 4086 }); 4087 4088 if (couponsForProcessing.length <= 0) { 4089 return; 4090 } 4091 4092 let maxCoupon = couponsForProcessing.reduce((max, coupon) => { 4093 return (Number(getCouponDiscountAbsValue(coupon)) > Number(getCouponDiscountAbsValue(max))) ? coupon : max; 4094 }, couponsForProcessing[0]); 4095 4096 var maxCouponValue = Number(getCouponDiscountAbsValue(maxCoupon)); 4097 var customerDiscountValue = Number($scope.CustomerDiscounts) || 0; 4098 var isApplyHighestSaving = $scope.InitInfo?.CheckoutSettings?.CouponStacking === 'applyHighestSaving'; 4099 var customerDiscountWins = isApplyHighestSaving && customerDiscountValue >= maxCouponValue && customerDiscountValue > 0; 4100 4101 if (customerDiscountWins) { 4102 couponsForProcessing.forEach(coupon => { 4103 if (coupon.couponType === 'free_shipping_off') { 4104 coupon.notEligible = true; 4105 } else {
4106 coupon.notApplied = true; 4107 if (coupon.couponBaseType === CouponConstants.coupon_bogo) { 4108 coupon.resetBogoLinesAndSuitableTiers(); 4109 } 4110 } 4111 }); 4112 } else { 4113 maxCoupon.notApplied = false; 4114 maxCoupon.notEligible = false; 4115 4116 couponsForProcessing.forEach(coupon => { 4117 if (coupon !== maxCoupon) { 4118 if (coupon.couponType === 'free_shipping_off') { 4119 coupon.notEligible = true; 4120 } else { 4121 coupon.notApplied = true; 4122 if (coupon.couponBaseType === CouponConstants.coupon_bogo) { 4123 coupon.resetBogoLinesAndSuitableTiers(); 4124 } 4125 } 4126 } 4127 }); 4128 if (isApplyHighestSaving && customerDiscountValue > 0 && maxCouponValue > 0) { 4129 $scope.CartData.Items.forEach(function (item) { 4130 if (isDiscountable(item)) { 4131 item.customer_discount = 0; 4132 item.discount = 0; 4133 item.discounted_total = $scope.getItemTotal(item); 4134 } 4135 }); 4136 $scope.CustomerDiscounts = 0; 4137 } 4138 } 4139 } else { 4140 let maxCoupon = $scope.CartData.Coupons.reduce((max, coupon) => { 4141 return (Number(getCouponDiscountAbsValue(coupon)) > Number(getCouponDiscountAbsValue(max))) ? coupon : max; 4142 }, $scope.CartData.Coupons[0]); 4143 4144 maxCoupon.notApplied = false; 4145 maxCoupon.notEligible = false; 4146 4147 $scope.CartData.Coupons.forEach(coupon => { 4148 if (coupon !== maxCoupon) { 4149 if (coupon.couponType === 'free_shipping_off') { 4150 coupon.notEligible = true; 4151 } else { 4152 coupon.notApplied = true; 4153 } 4154 } 4155 }); 4156 } 4157 } 4158 } 4159 4160 $scope.isCouponAvailable = function (coupon) { 4161 return !coupon.notApplied && !coupon.notEligible && coupon.discount > 0; 4162 } 4163 4164 $scope.isShippingCoupon = function (coupon) { 4165 return coupon.couponType === 'free_shipping_off'; 4166 } 4167 4168 $scope.couponUnavailabilityMessage = function (coupon) { 4169 if (coupon.notEligible) { 4170 return 'Not Eligible'; 4171 } else if (coupon.notApplied || coupon.discount === 0) { 4172 return 'Not Applied'; 4173 } 4174 4175 return ''; 4176 } 4177 4178 $scope.CalculateItemBetterDiscounts = function () { 4179 if (!$scope.CartData.Items || $scope.CartData.Items.length == 0) { 4180 $scope.CartData.Coupons = []; 4181 return 0.00; 4182 } 4183 4184 var allowStacking = $scope.InitInfo?.CheckoutSettings?.AllowDiscountStacking; 4185 var hasCoupons = $scope.CartData.Coupons && $scope.CartData.Coupons.length > 0; 4186 var shouldResetItems = $scope.CustomerDiscounts <= 0 || (!allowStacking && $scope.CustomerDiscounts > 0 && hasCoupons); 4187 4188 $scope.CartData.Items.forEach(function (item) { 4189 if (isDiscountable(item) && shouldResetItems) { 4190 item.discount = 0; 4191 item.discounted_total = $scope.getItemTotal(item); 4192 item.customer_discount = 0; 4193 } 4194 }); 4195 4196 $scope.CartData.Coupons.forEach(function (coupon) { 4197 coupon.discount = 0; 4198 }); 4199 4200 var discountable = 0; 4201 var itemTotal = 0; 4202 var discountableQty = 0; 4203 var discountableItem = {}; 4204 4205 for (var i = 0; i < $scope.CartData.Items.length; i++) { 4206 var item = $scope.CartData.Items[i]; 4207 if ($scope.InitInfo?.CheckoutSettings?.DisableCouponsForSalePriceItems === true && item.on_sale) { 4208 continue; 4209 } 4210 if (item.type != 'gc') { 4211 if ($scope.knobby['module_fix_coupon_min_purchase_class_addon'] && item.type === 'class' && item.package_items && item.package_items.length) { 4212 itemTotal += $scope.RoundTo2Dec($scope.getItemTotalForWeb(item)); 4213 } else { 4214 itemTotal += item.total; 4215 } 4216 } 4217 if (isDiscountable(item)) { 4218 discountable += item.total; 4219 if (item.type === 'class' && item.package_items && Array.isArray(item.package_items) && item.package_items.length > 0) { 4220 for (var j = 0; j < item.package_items.length; j++) { 4221 if ($scope.knobby['module_fix_coupon_min_purchase_class_addon']) { 4222 discountable += item.package_items[j]['price'] * item.qty; 4223 } else { 4224 discountable += item.package_items[j]['price']; 4225 } 4226 } 4227 } 4228 item.discount_applied = true; 4229 } 4230 } 4231 var discountableTotalAllItems = discountable; 4232 4233 $scope.CartData.Coupons.sort(function(a, b) { 4234 function getPriority(coupon) { 4235 if (coupon.couponType === CouponConstants.coupon_dollar_off && coupon.couponBaseType !== 'bogo') { 4236 return 0; 4237 } 4238 if (coupon.couponBaseType === 'bogo') { 4239 return 1; 4240 } 4241 if (coupon.couponType === CouponConstants.coupon_percent && coupon.couponBaseType !== 'bogo') { 4242 return 2; 4243 } 4244 return 3; 4245 } 4246 4247 if (a.couponBaseType === 'bogo' && b.couponBaseType === 'bogo') { 4248 if (a.notApplied === b.notApplied) { 4249 return getPriority(a) - getPriority(b); 4250 } 4251 return a.notApplied ? 1 : -1; 4252 } 4253 4254 return getPriority(a) - getPriority(b); 4255 }); 4256 4257 // Calculate the discounts off the total 4258 var customerDiscountWonInHighestSaving = $scope.InitInfo?.CheckoutSettings?.CouponStacking === 'applyHighestSaving' && 4259 $scope.CustomerDiscounts > 0 && $scope.CartData.Coupons && $scope.CartData.Coupons.length > 0 && 4260 $scope.CartData.Coupons.every(function (c) { return c.notApplied || c.notEligible; }); 4261 4262 if ($scope.CustomerDiscounts > 0 && (allowStacking || !$scope.CartData.Coupons || $scope.CartData.Coupons.length === 0 || customerDiscountWonInHighestSaving)) { 4263 $scope.Discounts = $scope.CustomerDiscounts; 4264 } else { 4265 $scope.Discounts = 0; 4266 } 4267 4268 if ($scope.CartData.Coupons) { 4269 $scope.StatusInfo.IsCouponEntered = $scope.StatusInfo.IsCouponEntered || $scope.CartData.Coupons.length > 0; 4270 4271 for (var c = 0; c < $scope.CartData.Coupons.length; c++) { 4272 var coupon = $scope.CartData.Coupons[c]; 4273 4274 if (coupon.couponBaseType === 'bogo') { 4275 continue; 4276 } 4277 4278 if (coupon.itemTypesArray.length === 1 && coupon.itemTypesArray.indexOf('packages') !== -1) { 4279 continue; 4280 } else if (coupon.itemTypesArray.indexOf('packages') !== -1) { 4281 coupon.itemTypesArray.splice(coupon.itemTypesArray.indexOf('packages'), 1); 4282 } 4283 4284 // this coupon only applies to some items 4285 if (coupon.pslrIdArray && coupon.pslrIdArray.length) { 4286 if ($scope.isDollarOffFiltersAllowed()) { 4287 discountableItem = $scope.getDiscountableTotalForPslrLimitedCoupon(coupon); 4288 4289 discountable = discountableItem.discountable; 4290 discountableQty = discountableItem.discountableQty; 4291 } else { 4292 discountable = $scope.getDiscountableTotalForPslrLimitedCoupon(coupon); 4293 } 4294 4295 if (!discountable) { 4296 discountable = discountableTotalAllItems; 4297 if (!coupon.disabledForSalePriceItem) { 4298 $scope.showGiftCardCouponMessage('Coupon Not Applied. No Eligible Items in Cart'); 4299 coupon.notEligible = true; 4300 } else {
4301 coupon.notApplied = true; 4302 } 4303 continue; 4304 } 4305 } else if (coupon.classIdArray && coupon.classIdArray.length) { 4306 if ($scope.isDollarOffFiltersAllowed()) { 4307 discountableItem = $scope.getDiscountableTotalForClassLimitedCoupon(coupon); 4308 4309 discountable = discountableItem.discountable; 4310 discountableQty = discountableItem.discountableQty; 4311 } else { 4312 discountable = $scope.getDiscountableTotalForClassLimitedCoupon(coupon); 4313 } 4314 4315 if (!discountable) { 4316 discountable = discountableTotalAllItems; 4317 if (!coupon.disabledForSalePriceItem) { 4318 $scope.showGiftCardCouponMessage('Coupon Not Applied. No Eligible Items in Cart'); 4319 coupon.notEligible = true; 4320 } else { 4321 coupon.notApplied = true; 4322 } 4323 continue; 4324 } 4325 } else if (coupon.departmentIdArray && coupon.departmentIdArray.length) { 4326 if ($scope.isDollarOffFiltersAllowed()) { 4327 discountableItem = $scope.getDiscountableTotalForDeptLimitedCoupon(coupon); 4328 discountable = discountableItem.discountable; 4329 discountableQty = discountableItem.discountableQty; 4330 } else { 4331 discountable = $scope.getDiscountableTotalForDeptLimitedCoupon(coupon); 4332 } 4333 4334 if (!discountable) { 4335 discountable = discountableTotalAllItems; 4336 if (!coupon.disabledForSalePriceItem) { 4337 $scope.showGiftCardCouponMessage('Coupon Not Applied. No Eligible Items in Cart'); 4338 coupon.notEligible = true; 4339 } else { 4340 coupon.notApplied = true; 4341 } 4342 continue; 4343 } 4344 } else if (coupon.categoryIdArray && coupon.categoryIdArray.length) { 4345 if ($scope.isDollarOffFiltersAllowed()) { 4346 discountableItem = $scope.getDiscountableTotalForCategoryLimitedCoupon(coupon); 4347 discountable = discountableItem.discountable; 4348 discountableQty = discountableItem.discountableQty; 4349 } else { 4350 discountable = $scope.getDiscountableTotalForCategoryLimitedCoupon(coupon); 4351 } 4352 4353 if (!discountable) { 4354 discountable = discountableTotalAllItems; 4355 if (!coupon.disabledForSalePriceItem) { 4356 $scope.showGiftCardCouponMessage('Coupon Not Applied. No Eligible Items in Cart'); 4357 coupon.notEligible = true; 4358 } else { 4359 coupon.notApplied = true; 4360 } 4361 continue; 4362 } 4363 } else if ( 4364 $scope.knobby['module_better_coupons'] && 4365 coupon.itemTypesArray && coupon.itemTypesArray.length 4366 ) { 4367 if ($scope.isDollarOffFiltersAllowed()) { 4368 discountableItem = $scope.getDiscountableTotalForItemTypesLimitedCoupon(coupon); 4369 discountable = discountableItem.discountable; 4370 discountableQty = discountableItem.discountableQty; 4371 } else { 4372 discountable = $scope.getDiscountableTotalForItemTypesLimitedCoupon(coupon); 4373 } 4374 4375 if (!discountable) { 4376 discountable = discountableTotalAllItems; 4377 if (!coupon.disabledForSalePriceItem) { 4378 $scope.showGiftCardCouponMessage('Coupon Not Applied. No Eligible Items in Cart'); 4379 coupon.notEligible = true; 4380 } else {
4381 coupon.notApplied = true; 4382 } 4383 continue; 4384 } 4385 } else if (coupon.vendorIdArray && coupon.vendorIdArray.length) { 4386 if ($scope.isDollarOffFiltersAllowed()) { 4387 discountableItem = $scope.getDiscountableTotalForVendorLimitedCoupon(coupon); 4388 discountable = discountableItem.discountable; 4389 discountableQty = discountableItem.discountableQty; 4390 } else { 4391 discountable = $scope.getDiscountableTotalForVendorLimitedCoupon(coupon); 4392 } 4393 4394 if (!discountable) { 4395 discountable = discountableTotalAllItems; 4396 if (!coupon.disabledForSalePriceItem) { 4397 $scope.showGiftCardCouponMessage('Coupon Not Applied. No Eligible Items in Cart'); 4398 coupon.notEligible = true; 4399 } else { 4400 coupon.notApplied = true; 4401 } 4402 continue; 4403 } 4404 } else if (coupon.manufacturerNameArray && coupon.manufacturerNameArray.length) { 4405 if ($scope.isDollarOffFiltersAllowed()) { 4406 discountableItem = $scope.getDiscountableTotalForMnfLimitedCoupon(coupon); 4407 discountable = discountableItem.discountable; 4408 discountableQty = discountableItem.discountableQty; 4409 } else { 4410 discountable = $scope.getDiscountableTotalForMnfLimitedCoupon(coupon); 4411 } 4412 4413 if (!discountable) { 4414 discountable = discountableTotalAllItems; 4415 if (!coupon.disabledForSalePriceItem) { 4416 $scope.showGiftCardCouponMessage('Coupon Not Applied. No Eligible Items in Cart'); 4417 coupon.notEligible = true; 4418 } else { 4419 coupon.notApplied = true; 4420 } 4421 continue; 4422 } 4423 } 4424 4425 coupon.totalDiscountableItemsValue = discountable; 4426 } 4427 } 4428 4429 for (var c = 0; c < $scope.CartData.Coupons.length; c++) { 4430 var coupon = $scope.CartData.Coupons[c]; 4431 4432 var disc = 0.00; 4433 var maxDiscount = null; 4434 var minDollarAmount = null; 4435 4436 if ($scope.knobby['module_fix_web_coupon_customer_group_restriction'] && !$scope.couponWithCustomerGroupsIsAvailable(coupon)) { 4437 coupon.discount = 0; 4438 coupon.notEligible = true; 4439 continue; 4440 } 4441 4442 if ( 4443 ( 4444 $scope.InitInfo?.CheckoutSettings?.CouponStacking === 'applyHighestSaving' || 4445 ($scope.knobby['module_better_coupons'] && $scope.InitInfo?.CheckoutSettings?.CouponStacking === 'applyAll' && coupon.couponBaseType === CouponConstants.coupon_bogo) 4446 ) && 4447 coupon.notApplied 4448 ) { 4449 coupon.discount = 0; 4450 continue; 4451 } 4452 4453 if (coupon.maxDiscount > 0) { 4454 maxDiscount = $scope.RoundTo2Dec(coupon.maxDiscount); 4455 } 4456 4457 if (coupon.minDollarAmount > 0) { 4458 minDollarAmount = $scope.RoundTo2Dec(coupon.minDollarAmount); 4459 if (minDollarAmount > itemTotal) { 4460 $scope.removeCoupon(coupon.couponId, "Min. Purchase of " + $scope.InitInfo.CurrencySymbol2 + minDollarAmount + ' Required. Excludes Gift Card Purchases.'); 4461 continue; 4462 } 4463 } 4464 4465 if ( 4466 (coupon.couponBaseType !== 'bogo' && !coupon.totalDiscountableItemsValue) || 4467 (coupon.couponBaseType === 'bogo' && !coupon.dollarOffAmount) 4468 ) { 4469 if (!coupon.disabledForSalePriceItem) { 4470 $scope.showGiftCardCouponMessage('Coupon Not Applied. No Eligible Items in Cart'); 4471 coupon.notEligible = true; 4472 } else {
4473 coupon.notApplied = true; 4474 } 4475 4476 continue; 4477 } 4478 4479 if (coupon.couponBaseType === 'bogo') { 4480 for (var uuid in coupon.limitedBogoLines) { 4481 if (coupon.limitedBogoLines.hasOwnProperty(uuid)) { 4482 var limitedBogoLine = coupon.limitedBogoLines[uuid]; 4483 4484 var lineItem = $scope.CartData.Items.find(function (item) { 4485 return item.uuid === uuid; 4486 }) || null; 4487 4488 if (lineItem) { 4489 var baseAmountForDiscount = lineItem.discounted_total / lineItem.qty; 4490 4491 if (coupon.couponType === CouponConstants.coupon_percent) { 4492 disc = baseAmountForDiscount * limitedBogoLine.discountableItemsAmount * (limitedBogoLine.bogoLinePercentOff / 100); 4493 } else if (coupon.couponType === CouponConstants.coupon_dollar_off) { 4494 if (baseAmountForDiscount > limitedBogoLine.bogoLineAmountOff) { 4495 disc = limitedBogoLine.bogoLineAmountOff * limitedBogoLine.discountableItemsAmount; 4496 } else { 4497 disc = baseAmountForDiscount * limitedBogoLine.discountableItemsAmount; 4498 } 4499 } 4500 4501 if (maxDiscount !== null && maxDiscount < disc) { 4502 disc = maxDiscount; 4503 } 4504 4505 disc = Math.round(disc * 100) / 100; 4506 if ((coupon.discount + disc) > maxDiscount && maxDiscount > 0) { 4507 disc = maxDiscount - coupon.discount; 4508 } 4509 4510 lineItem.discount += disc; 4511 lineItem.discounted_total = $scope.roundNumber($scope.getItemTotal(lineItem) - lineItem.discount, 3); 4512 coupon.discount += disc; 4513 $scope.Discounts += disc; 4514 } 4515 } 4516 } 4517 } else { 4518 var percentOffDiscountableTotalBeforeApply = 0; 4519 if ( 4520 coupon.couponType === 'percent_off' && 4521 !!window.knobby && window.knobby['module_fix_checkout_apply_all_coupon_stacking'] && 4522 $scope.InitInfo?.CheckoutSettings?.CouponStacking === 'applyAll' 4523 ) { 4524 coupon.appliedLineUuid.forEach(function (appliedLineUuid) { 4525 var appliedLineItem = $scope.CartData.Items.find(function (item) { 4526 return item.uuid === appliedLineUuid; 4527 }); 4528 4529 if (appliedLineItem) { 4530 percentOffDiscountableTotalBeforeApply += appliedLineItem.discounted_total; 4531 } 4532 }); 4533 } 4534 4535 coupon.appliedLineUuid.forEach(function (lineUuid, index, array) { 4536 var lineItem = $scope.CartData.Items.find(function (item) { 4537 return item.uuid === lineUuid; 4538 }); 4539 4540 if (!lineItem) { 4541 return; 4542 } 4543 4544 if (coupon.couponType === 'dollar_off') { 4545 if (discountable >= coupon.minDollarAmount) { 4546 if (index === array.length - 1) { 4547 disc = coupon.dollarOffAmount - coupon.discount; 4548 if (disc < 0) { 4549 disc = 0; 4550 } 4551 } else { 4552 var itemPercent = lineItem.total / coupon.totalDiscountableItemsValue; 4553 disc = (coupon.dollarOffAmount * itemPercent); 4554 } 4555 4556 if ((lineItem.discount + disc) > $scope.getItemTotal(lineItem)) { 4557 disc = ($scope.getItemTotal(lineItem) + disc) - (lineItem.discount + disc); 4558 } 4559 } 4560 } else if (coupon.couponType === 'percent_off') { 4561 if (index === array.length - 1) { 4562 var totalPercentOffDiscount; 4563 if ( 4564 !!window.knobby && window.knobby['module_fix_checkout_apply_all_c
4564oupon_stacking'] && 4565 $scope.InitInfo?.CheckoutSettings?.CouponStacking === 'applyAll' 4566 ) { 4567 totalPercentOffDiscount = Math.round(percentOffDiscountableTotalBeforeApply * (coupon.percentOff / 100) * 100) / 100; 4568 } else { 4569 totalPercentOffDiscount = Math.round(coupon.totalDiscountableItemsValue * (coupon.percentOff / 100) * 100) / 100; 4570 } 4571 disc = totalPercentOffDiscount - coupon.discount; 4572 if (disc < 0) { disc = 0; } 4573 } else { 4574 disc = lineItem.discounted_total * (coupon.percentOff / 100); 4575 } 4576 } 4577 4578 if (maxDiscount !== null && maxDiscount < disc) { 4579 disc = maxDiscount; 4580 } 4581 4582 disc = Math.round(disc * 100) / 100; 4583 4584 if ((coupon.discount + disc) > maxDiscount && maxDiscount > 0) { 4585 disc = maxDiscount - coupon.discount; 4586 } 4587 4588 if (disc < 0) { 4589 disc = 0; 4590 } 4591 4592 lineItem.discount += disc; 4593 lineItem.discounted_total = $scope.roundNumber($scope.getItemTotal(lineItem) - lineItem.discount, 3); 4594 coupon.discount += disc; 4595 $scope.Discounts += disc; 4596 }); 4597 } 4598 } 4599 4600 if (!allowStacking && $scope.CustomerDiscounts > 0 && $scope.Discounts > 0) { 4601 if ($scope.CustomerDiscounts > $scope.Discounts) { 4602 $scope.CartData.Items.forEach(function (item) { 4603 if (isDiscountable(item)) { 4604 item.discount = 0; 4605 item.discounted_total = $scope.getItemTotal(item); 4606 } 4607 }); 4608 if ($scope.CartData.Coupons) { 4609 for (var k = 0; k < $scope.CartData.Coupons.length; k++) { 4610 $scope.CartData.Coupons[k].discount = 0; 4611 $scope.CartData.Coupons[k].notApplied = true; 4612 } 4613 } 4614 $scope.Discounts = $scope.CustomerDiscounts; 4615 var customerDiscountPercent = $scope.InitInfo.customer.discount; 4616 $scope.CartData.Items.forEach(function (item) { 4617 if ($scope.InitInfo.customer?.discount > 0 && isDiscountable(item)) { 4618 var extPrice = item.price * item.qty; 4619 var itemDiscount = (extPrice * customerDiscountPercent) / 100; 4620 if (itemDiscount > extPrice) { 4621 itemDiscount = extPrice; 4622 } 4623 item.discount = itemDiscount; 4624 item.discounted_total = extPrice - itemDiscount; 4625 } 4626 }); 4627 } 4628 } 4629 4630 if ($scope.Discounts > $scope.SubTotal) { 4631 $scope.Discounts = $scope.SubTotal; 4632 } 4633 }; 4634 4635 // We need to divide the discount between all discountable entities 4636 // So we can then calculate the taxes 4637 // keep in sync with Cart.model CalculateItemDiscounts 4638 $scope.CalculateItemDiscounts = function () { 4639 var allowStacking = $scope.InitInfo && $scope.InitInfo.CheckoutSettings && $scope.InitInfo.CheckoutSettings.AllowDiscountStacking; 4640 4641 if (!$scope.CartData.Items || $scope.CartData.Items.length == 0) { 4642 // no items - clear the coupons 4643 $scope.CartData.Coupons = []; 4644 return 0.00; 4645 } 4646 4647 // Calculate the discountable total 4648 var discountable = 0; 4649 var itemTotal = 0; 4650 var discountableQty = 0; 4651 var discountableItem = {}; 4652 4653 for (var i = 0; i < $scope.CartData.Items.length; i++) { 4654 var item = $scope.CartData.Items[i]; 4655 if ($scope.InitInfo?.CheckoutSettings?.DisableCouponsForSalePriceItems === true && item.on_sale) { 4656 continue; 4657 } 4658 if (item.type != 'gc') { 4659 if ($scope.knobby['module_fix_coupon_min_purchase_class_addon'] && item.type === 'class' && item.package_items && item.package_items.length) { 4660 itemTotal += $scope.RoundTo2Dec($scope.getItemTotalForWeb(item)); 4661 } else { 4662 itemTotal += item.total; 4663 } 4664 } 4665 if (isDiscountable(item)) { 4666 discountable += item.total; 4667 if (item.type === 'class' && item.package_items && Array.isArray(item.package_items) && item.package_items.length > 0) { 4668 for (var j = 0; j < item.package_items.length; j++) { 4669 if ($scope.knobby['module_fix_coupon_min_purchase_class_addon']) { 4670 discountable += item.package_items[j]['price'] * item.qty; 4671 } else { 4672 discountable += item.package_items[j]['price']; 4673 } 4674 } 4675 } 4676 item.discount_applied = true; 4677 } 4678 } 4679 var discountableTotalAllItems = discountable; 4680 4681 // Calculate the discounts off the total 4682 var discounts = 0; 4683 if (allowStacking || !$scope.CartData.Coupons || $scope.CartData.Coupons.length === 0) { 4684 if ($scope.CustomerDiscounts > 0) { 4685 discounts = $scope.CustomerDiscounts; 4686 } 4687 } 4688 4689 if ($scope.CartData.Coupons) { 4690 $scope.StatusInfo.IsCouponEntered = $scope.StatusInfo.IsCouponEntered || $scope.CartData.Coupons.length > 0; 4691 4692 for (var c = 0; c < $scope.CartData.Coupons.length; c++) { 4693 var coupon = $scope.CartData.Coupons[c]; 4694 4695 if ($scope.knobby['module_better_coupons']) { 4696 if (coupon.itemTypesArray.length === 1 && coupon.itemTypesArray.indexOf('packages') !== -1) { 4697 continue; 4698 } else if (coupon.itemTypesArray.indexOf('packages') !== -1) { 4699 coupon.itemTypesArray.splice(coupon.itemTypesArray.indexOf('packages'), 1); 4700 } 4701 } 4702 4703 // reset - a pslr specific coupon may have changed discountable 4704 discountable = discountableTotalAllItems; 4705 coupon.discount = 0; 4706 var maxDiscount = null; 4707 var minDollarAmount = null; 4708 var discount = 0; 4709 var newDiscount = 0; 4710 4711 // this coupon only applies to some items 4712 if (coupon.pslrIdArray && coupon.pslrIdArray.length) { 4713 if ($scope.isDollarOffFiltersAllowed()) { 4714 discountableItem = $scope.getDiscountableTotalForPslrLimitedCoupon(coupon); 4715 discountable = discountableItem.discountable; 4716 discountableQty = discountableItem.discountableQty; 4717 } else { 4718 discountable = $scope.getDiscountableTotalForPslrLimitedCoupon(coupon); 4719 } 4720 4721 if (!discountable) { 4722 discountable = discountableTotalAllItems; 4723 if (!coupon.disabledForSalePriceItem) { 4724 $scope.showGiftCardCouponMessage('Coupon Not Applied. No Eligible Items in Cart'); 4725 coupon.notEligible = true; 4726 } else {
4727 coupon.notApplied = true; 4728 } 4729 continue; 4730 } 4731 } else if (coupon.classIdArray && coupon.classIdArray.length) { 4732 if ($scope.isDollarOffFiltersAllowed()) { 4733 discountableItem = $scope.getDiscountableTotalForClassLimitedCoupon(coupon); 4734 discountable = discountableItem.discountable; 4735 discountableQty = discountableItem.discountableQty; 4736 } else { 4737 discountable = $scope.getDiscountableTotalForClassLimitedCoupon(coupon); 4738 } 4739 4740 if (!discountable) { 4741 discountable = discountableTotalAllItems; 4742 if (!coupon.disabledForSalePriceItem) { 4743 $scope.showGiftCardCouponMessage('Coupon Not Applied. No Eligible Items in Cart'); 4744 coupon.notEligible = true; 4745 } else { 4746 coupon.notApplied = true; 4747 } 4748 continue; 4749 } 4750 } else if (coupon.departmentIdArray && coupon.departmentIdArray.length) { 4751 if ($scope.isDollarOffFiltersAllowed()) { 4752 discountableItem = $scope.getDiscountableTotalForDeptLimitedCoupon(coupon); 4753 discountable = discountableItem.discountable; 4754 discountableQty = discountableItem.discountableQty; 4755 } else { 4756 discountable = $scope.getDiscountableTotalForDeptLimitedCoupon(coupon); 4757 } 4758 4759 if (!discountable) { 4760 discountable = discountableTotalAllItems; 4761 if (!coupon.disabledForSalePriceItem) { 4762 $scope.showGiftCardCouponMessage('Coupon Not Applied. No Eligible Items in Cart'); 4763 coupon.notEligible = true; 4764 } else { 4765 coupon.notApplied = true; 4766 } 4767 continue; 4768 } 4769 } else if (coupon.categoryIdArray && coupon.categoryIdArray.length) { 4770 if ($scope.isDollarOffFiltersAllowed()) { 4771 discountableItem = $scope.getDiscountableTotalForCategoryLimitedCoupon(coupon); 4772 discountable = discountableItem.discountable; 4773 discountableQty = discountableItem.discountableQty; 4774 } else { 4775 discountable = $scope.getDiscountableTotalForCategoryLimitedCoupon(coupon); 4776 } 4777 4778 4779 if (!discountable) { 4780 discountable = discountableTotalAllItems; 4781 if (!coupon.disabledForSalePriceItem) { 4782 $scope.showGiftCardCouponMessage('Coupon Not Applied. No Eligible Items in Cart'); 4783 coupon.notEligible = true; 4784 } else { 4785 coupon.notApplied = true; 4786 } 4787 continue; 4788 } 4789 } else if ( 4790 $scope.knobby['module_better_coupons'] && 4791 coupon.itemTypesArray && coupon.itemTypesArray.length 4792 ) { 4793 if ($scope.isDollarOffFiltersAllowed()) { 4794 discountableItem = $scope.getDiscountableTotalForItemTypesLimitedCoupon(coupon); 4795 discountable = discountableItem.discountable; 4796 discountableQty = discountableItem.discountableQty; 4797 } else { 4798 discountable = $scope.getDiscountableTotalForItemTypesLimitedCoupon(coupon); 4799 } 4800 4801 if (!discountable) { 4802 discountable = discountableTotalAllItems; 4803 if (!coupon.disabledForSalePriceItem) { 4804 $scope.showGiftCardCouponMessage('Coupon Not Applied. No Eligible Items in Cart'); 4805 coupon.notEligible = true; 4806 } else {
4807 coupon.notApplied = true; 4808 } 4809 continue; 4810 } 4811 } else if (coupon.vendorIdArray && coupon.vendorIdArray.length) { 4812 if ($scope.isDollarOffFiltersAllowed()) { 4813 discountableItem = $scope.getDiscountableTotalForVendorLimitedCoupon(coupon); 4814 discountable = discountableItem.discountable; 4815 discountableQty = discountableItem.discountableQty; 4816 } else { 4817 discountable = $scope.getDiscountableTotalForVendorLimitedCoupon(coupon); 4818 } 4819 4820 if (!discountable) { 4821 discountable = discountableTotalAllItems; 4822 if (!coupon.disabledForSalePriceItem) { 4823 $scope.showGiftCardCouponMessage('Coupon Not Applied. No Eligible Items in Cart'); 4824 coupon.notEligible = true; 4825 } else { 4826 coupon.notApplied = true; 4827 } 4828 continue; 4829 } 4830 } else if (coupon.manufacturerNameArray && coupon.manufacturerNameArray.length) { 4831 if ($scope.isDollarOffFiltersAllowed()) { 4832 discountableItem = $scope.getDiscountableTotalForMnfLimitedCoupon(coupon); 4833 discountable = discountableItem.discountable; 4834 discountableQty = discountableItem.discountableQty; 4835 } else { 4836 discountable = $scope.getDiscountableTotalForMnfLimitedCoupon(coupon); 4837 } 4838 4839 if (!discountable) { 4840 discountable = discountableTotalAllItems; 4841 if (!coupon.disabledForSalePriceItem) { 4842 $scope.showGiftCardCouponMessage('Coupon Not Applied. No Eligible Items in Cart'); 4843 coupon.notEligible = true; 4844 } else { 4845 coupon.notApplied = true; 4846 } 4847 continue; 4848 } 4849 } 4850 4851 if (coupon.maxDiscount > 0) { 4852 maxDiscount = $scope.RoundTo2Dec(coupon.maxDiscount); 4853 } 4854 4855 if (coupon.minDollarAmount > 0) { 4856 minDollarAmount = $scope.RoundTo2Dec(coupon.minDollarAmount); 4857 if (minDollarAmount > itemTotal) { 4858 // this coupon can't be used - the total is too low. 4859 $scope.removeCoupon(coupon.couponId, "Min. Purchase of " + $scope.InitInfo.CurrencySymbol2 + minDollarAmount + ' Required. Excludes Gift Card Purchases.'); 4860 continue; 4861 } 4862 } 4863 4864 if (discountable > 0) { 4865 discountable -= (allowStacking || !$scope.CartData.Coupons || $scope.CartData.Coupons.length === 0) ? (Number($scope.CustomerDiscounts) || 0) : 0; 4866 4867 if (coupon.couponType === 'percent_off') { 4868 if ($scope.knobby['module_better_coupons'] && coupon.couponBaseType === CouponConstants.coupon_bogo) { 4869 discount = coupon.dollarOffAmount; 4870 } else { 4871 discount = $scope.RoundTo2Dec(discountable * coupon.percentOff / 100); 4872 } 4873 4874 if (maxDiscount !== null && maxDiscount < discount) { 4875 discount = maxDiscount; 4876 } 4877 4878 if ( 4879 ( 4880 $scope.InitInfo?.CheckoutSettings?.CouponStacking === 'applyHighestSaving' || 4881 ($scope.knobby['module_better_coupons'] && $scope.InitInfo?.CheckoutSettings?.CouponStacking === 'applyAll' && coupon.couponBaseType === CouponConstants.coupon_bogo) 4882 ) &&
4883 coupon.notApplied 4884 ) { 4885 coupon.discount = 0; 4886 } else { 4887 coupon.discount = discount; 4888 discounts += discount; 4889 } 4890 } else if (coupon.couponType === 'dollar_off') { 4891 if (discountable >= coupon.minDollarAmount) { 4892 newDiscount = coupon.dollarOffAmount; 4893 4894 if ($scope.knobby['module_better_coupons']) { 4895 if ($scope.isDollarOffFiltersAllowed() && discountableQty > 0 && coupon.couponBaseType !== CouponConstants.coupon_bogo) { 4896 newDiscount *= discountableQty; 4897 } 4898 } else { 4899 if ($scope.isDollarOffFiltersAllowed() && discountableQty > 0) { 4900 newDiscount *= discountableQty; 4901 } 4902 } 4903 4904 discount = $scope.RoundTo2Dec(newDiscount); 4905 4906 if (maxDiscount !== null && maxDiscount < discount) { 4907 discount = maxDiscount; 4908 } 4909 4910 if (discount > $scope.SubTotal) { 4911 discount = $scope.SubTotal; 4912 } 4913 4914 if ( 4915 ( 4916 $scope.InitInfo?.CheckoutSettings?.CouponStacking === 'applyHighestSaving' || 4917 ($scope.knobby['module_better_coupons'] && $scope.InitInfo?.CheckoutSettings?.CouponStacking === 'applyAll' && coupon.couponBaseType === CouponConstants.coupon_bogo) 4918 ) && 4919 coupon.notApplied 4920 ) { 4921 coupon.discount = 0; 4922 } else { 4923 coupon.discount = discount; 4924 discounts += discount; 4925 } 4926 } 4927 } 4928 } else { 4929 if (!coupon.disabledForSalePriceItem) { 4930 $scope.showGiftCardCouponMessage('Coupon Not Applied. No Eligible Items in Cart'); 4931 coupon.notEligible = true; 4932 } else { 4933 coupon.notApplied = true; 4934 } 4935 } 4936 } 4937 } 4938 4939 if (!allowStacking && $scope.CustomerDiscounts > 0 && discounts > 0) { 4940 if ($scope.CustomerDiscounts > discounts) { 4941 if ($scope.CartData.Coupons) { 4942 for (var k = 0; k < $scope.CartData.Coupons.length; k++) { 4943 $scope.CartData.Coupons[k].discount = 0; 4944 $scope.CartData.Coupons[k].notApplied = true; 4945 } 4946 } 4947 discounts = $scope.CustomerDiscounts; 4948 } 4949 } 4950 4951 $scope.Discounts = $scope.RoundTo2Dec(discounts); 4952 4953 // How much of total discounts is from customer discount (for transactionLineDiscount in line_totals) 4954 var useCustomerDiscount = $scope.CustomerDiscounts > 0 && discounts > 0 && 4955 (allowStacking || !$scope.CartData.Coupons?.length || discounts === $scope.CustomerDiscounts); 4956 var customerDiscountAmount = useCustomerDiscount ? $scope.CustomerDiscounts : 0; 4957 4958 // Assign each item it's share of the discount 4959 var discount_left = discounts; 4960 for (var i = 0; i < $scope.CartData.Items.length; i++) { 4961 var disc = 0.00; 4962 var item = $scope.CartData.Items[i]; 4963 4964 if (isDiscountable(item) && item.discount_applied) { 4965 var itemPercent = item.total / discountableTotalAllItems; 4966 disc = (discounts * itemPercent); 4967 disc = Math.round(disc * 100) / 100; 4968 discount_left -= disc; 4969 disc = Math.round(disc * 100) / 100; 4970 item.discount = disc; 4971 item.discounted_total = $scope.roundNumber($scope.getItemTotal(item) - item.discount, 3); 4972 4973 if (customerDiscountAmount > 0 && discounts > 0) { 4974 item.customer_discount = $scope.roundNumber(disc * (customerDiscountAmount / discounts), 2); 4975 } else { 4976 item.customer_discount = 0; 4977 } 4978 4979 //Calculate discount for each package item 4980 if (item.type === 'class' && item.package_items && Array.isArray(item.package_items) && item.package_items.length > 0) { 4981 for (var j = 0; j < item.package_items.length; j++) { 4982 var pItem = item.package_items[j]; 4983 var piTotal = pItem.price * item.qty; 4984 4985 //calculation for total discount; 4986 var pItemPercent = piTotal / discountableTotalAllItems; 4987 var piDisc = (discounts * pItemPercent); 4988 piDisc = Math.round(piDisc * 100) / 100; 4989 discount_left -= piDisc; 4990 piDisc = Math.round(piDisc * 100) / 100; 4991 4992 item.package_items[j]['discount'] = piDisc; 4993 item.package_items[j]['discounted_total'] = $scope.roundNumber(piTotal - piDisc, 3); 4994 if (customerDiscountAmount > 0 && discounts > 0) { 4995 item.package_items[j]['customer_discount'] = $scope.roundNumber(piDisc * (customerDiscountAmount / discounts), 2); 4996 } else { 4997 item.package_items[j]['customer_discount'] = 0; 4998 } 4999 } 5000 } 5001 } else if (!isDiscountable(item)) { 5002 item.customer_discount = 0; 5003 } 5004 } 5005 discount_left = $scope.RoundTo2Dec(discount_left); 5006 if (discount_left != 0) { 5007 $scope.MagicPennyDiscount(discount_left); 5008 } 5009 5010 }; 5011 5012 // Keep in sync with CartModel TotalsCalculate 5013 $scope.TotalsCalculate = function () { 5014 // Total items 5015 var total = 0; 5016 var totalBeingShipped = 0; 5017 var gst_total = 0; 5018 var requiresShipping = false;
5019 var qty = 0; 5020 var weight = 0; 5021 if ($scope.CartData.Items && $scope.CartData.Items.length > 0) { 5022 for (var i = 0; i < $scope.CartData.Items.length; i++) { 5023 var item = $scope.CartData.Items[i]; 5024 var item_total = $scope.getItemTotal(item); 5025 var item_gst_total = $scope.getItemGstTotal(item); 5026 5027 if (item.type === 'class') { 5028 total += $scope.RoundTo2Dec(fractionalMultiplication(item.qty, $scope.getItemPriceForWeb(item))); 5029 } else { 5030 total += $scope.RoundTo2Dec(item_total); 5031 } 5032 5033 gst_total += $scope.RoundTo2Dec(item_gst_total); 5034 5035 // these types are not shipped, or don't count towards shipping costs 5036 5037 //Rto items should have shipping options 5038 if (item.type != 'class' && item.type != 'gc' && item.type != 'subscription' && !item.downloadable) { 5039 totalBeingShipped += $scope.RoundTo2Dec(item_total); 5040 weight += Number(item.weight) * item.qty; 5041 } 5042 5043 qty += Number(item.qty); 5044 5045 // Shipping Required 5046 if (item.type == 'pslr' || item.type == 'product' || item.type == 'rto') { 5047 if (!item.downloadable) { 5048 requiresShipping = true; 5049 } 5050 } 5051 5052 if ( 5053 item.type === 'subscription' && 5054 typeof item.subscriptionData !== "undefined" && 5055 typeof item.subscriptionData.previous_items_cnt !== "undefined" && 5056 item.subscriptionData.previous_items_cnt > 0 && 5057 item.subscriptionData.delivery_type === 'ship' 5058 ) { 5059 requiresShipping = true; 5060 weight += Number(item.subscriptionData.catch_up_total_weight || 0); 5061 qty += Number(item.subscriptionData.catch_up_total_qty || 0); 5062 totalBeingShipped += Number(item.subscriptionData.catch_up_sub_total || 0); 5063 } 5064 } 5065 } else { 5066 requiresShipping = true; 5067 } 5068 5069 if ($scope.checkFlagBugClassesCantCheckOutEcommerce()) { 5070 // If cart contains only classes, gift cards, subscriptions, and/or digital downloads, shipping is not required 5071 if ($scope.isGCClassSubscriptionCombinationOnly()) { 5072 requiresShipping = false; 5073 } 5074 } 5075 5076 $scope.updateDigitalOrder(); 5077 5078 $scope.ShippingStatus.Required = requiresShipping; 5079 5080 //If we don't show shipping form anymore, let's clean shipping data. Because they will be used during dynamic taxes calculation
5081 //And dynamic taxes depend on shipping data that can't be filled from this point 5082 if ($scope.HasDynamicTaxes && !$scope.showShippingAddressForm()) { 5083 //Need to clean shipping data as well 5084 $scope.ShippingInfo.Address1 = null; 5085 $scope.ShippingInfo.Address2 = null; 5086 $scope.ShippingInfo.City = null; 5087 $scope.ShippingInfo.State = null; 5088 $scope.ShippingInfo.Zip = null; 5089 if (!$scope.digitalOrder && !$scope.calculateWaTaxesBasedOnStoreLocationAddress()) { 5090 $scope.dynamicRateCache = null; 5091 $scope.dynamicRateCacheKey = null; 5092 } 5093 $scope.SessionStorageSet(); 5094 } 5095 5096 $scope.Weight = weight; 5097 $scope.SubTotal = $scope.RoundTo2Dec(total); 5098 $scope.ShippingSubtotal = $scope.RoundTo2Dec(totalBeingShipped); 5099 $scope.GST_SubTotal = $scope.RoundTo2Dec(gst_total); 5100 5101 if ($scope.knobby['module_fix_tiered_coupon_on_website'] && $scope.knobby['module_better_coupons'] && $scope.CartData.Coupons) { 5102 $scope.CartData.Coupons.forEach(function (coupon) { 5103 if (typeof coupon.setAmountsBasedOnTiers !== 'function') { 5104 return; 5105 } 5106 if (coupon.couponBaseType === CouponConstants.coupon_bogo) { 5107 coupon.setAmountsBasedOnTiers({lines: $scope.CartData.Items, disableCouponsForSalePriceItems: $scope.InitInfo?.CheckoutSettings?.DisableCouponsForSalePriceItems}); 5108 } else { 5109 coupon.setAmountsBasedOnTiers($scope.SubTotal); 5110 } 5111 }); 5112 } 5113 5114 if ($scope.knobby['module_better_coupons']) { 5115 // Shipping 5116 var shipping = 0; 5117 if (requiresShipping && $scope.ShippingInfo.Option) { 5118 var t = Number($scope.ShippingInfo.Option.price) || 0; 5119 5120 shipping += Number(t); 5121 total += shipping; 5122 gst_total += shipping; 5123 } 5124 $scope.ShippingTotal = shipping; 5125 } 5126 5127 if ($scope.CartData.Items) { 5128 var customerDiscountTotal = 0; 5129 for (var i = 0, ilen = $scope.CartData.Items.length; i < ilen; i++) { 5130 var item = $scope.CartData.Items[i]; 5131 if ($scope.InitInfo.customer?.discount > 0 && isDiscountable(item)) { 5132 var customerDiscountPercent = $scope.InitInfo.customer.discount; 5133 var extPrice = item.price * item.qty; 5134 var itemDiscount = (extPrice * customerDiscountPercent) / 100 5135 if (itemDiscount > extPrice) { 5136 itemDiscount = extPrice; 5137 } 5138 5139 var discountedTotal = extPrice - itemDiscount; 5140 item.discounted_total = discountedTotal; 5141 item.discount_applied = true; 5142 item.discount = itemDiscount; 5143 item.customer_discount = itemDiscount; 5144 customerDiscountTotal += itemDiscount 5145 } else { 5146 item.customer_discount = 0; 5147 } 5148 } 5149 5150 $scope.CustomerDiscounts = customerDiscountTotal; 5151 } 5152 5153 if ($scope.knobby['module_better_coupons']) { 5154 $scope.applyHighestSavingCouponsMark(); 5155 } else { 5156 if ($scope.InitInfo?.CheckoutSettings?.CouponStacking === 'applyHighestSaving') { 5157 $scope.applyHighestSavingCouponsMark(); 5158 } 5159 } 5160 5161 if ($scope.knobby['module_better_coupons']) { 5162 $scope.CalculateItemBetterDiscounts(); 5163 } else { 5164 $scope.CalculateItemDiscounts(); 5165 } 5166 5167 // Taxes (must be after discounts) 5168 $scope.CalculateItemTaxes(); 5169 5170 // Discounts 5171 total -= $scope.Discounts; 5172 gst_total -= $scope.Discounts; 5173 5174 if (!$scope.showAsGST() || $scope.isShippingCountryGST()) { 5175 total += $scope.Taxes; 5176 } 5177 5178 if (!$scope.knobby['module_better_coupons']) { 5179 // Shipping 5180 var shipping = 0; 5181 if (requiresShipping && $scope.ShippingInfo.Option) { 5182 var t = Number($scope.ShippingInfo.Option.price) || 0; 5183 5184 shipping += Number(t); 5185 total += shipping; 5186 gst_total += shipping; 5187 } 5188 $scope.ShippingTotal = shipping; 5189 } 5190 5191 $scope.ShippingDiscount = $scope.getShippingDiscount(); 5192 5193 $scope.checkShippingCoupon(); 5194 5195 // Shipping Taxes 5196 if ($scope.gstToNon()) { 5197 $scope.ShippingTaxes = 0; 5198 } else { 5199 $scope.ShippingTaxes = $scope.calculateShippingTax(shipping); 5200 } 5201 5202 total += $scope.ShippingTaxes; 5203 gst_total += $scope.ShippingTaxes; 5204 5205 total -= $scope.ShippingDiscount; 5206 gst_total -= $scope.ShippingDiscount; 5207 5208 $scope.CartQty = qty; 5209 5210 if ($scope.checkFlagRetailDeliveryFee()) { 5211 //Retail delivery Fee 5212 if ($scope.ShippingInfo.State === 'CO' && $scope.hasNonSubscriptionItemsInCart() && $scope.tax_service !== 'avalara') {
5213 if ($scope.isRetailDeliveryFeeApplied()) { 5214 $scope.retailDeliveryFee = $scope.RETAIL_DELIVERY_FEE; 5215 } else { 5216 $scope.retailDeliveryFee = 0.00; 5217 } 5218 total += $scope.retailDeliveryFee; 5219 } else { 5220 $scope.retailDeliveryFee = 0.00; 5221 } 5222 } 5223 5224 // Gift Cards 5225 $scope.GCTotal = 0; 5226 if ($scope.CartData.GiftCards && !$scope.isPayAtPickup()) { 5227 for (var g = 0; g < $scope.CartData.GiftCards.length; g++) { 5228 var giftCard = $scope.CartData.GiftCards[g]; 5229 if (giftCard.maxGiftCardAmount > total) { 5230 giftCard.Amount = total; 5231 $scope.GCTotal += total; 5232 total = 0; 5233 gst_total = 0; 5234 } else { 5235 var max_amount = Number(giftCard.maxGiftCardAmount); 5236 giftCard.Amount = max_amount; 5237 $scope.GCTotal += max_amount; 5238 total -= max_amount; 5239 gst_total -= max_amount; 5240 } 5241 } 5242 } 5243 5244 if ($scope.showAsGST()) { 5245 $scope.CartTotal = $scope.RoundTo2Dec(gst_total); 5246 } else { 5247 $scope.CartTotal = $scope.RoundTo2Dec(total); 5248 } 5249 $scope.BillingStatus.Required = (total != 0 || $scope.isCardOnFileNeeded()); 5250 5251 let newTotal = $scope.RoundTo2Dec(total); 5252 let totalIsDirty = (newTotal != $scope.Due); 5253 $scope.Due = newTotal; 5254 5255 if (totalIsDirty) { 5256 // The BNPL message element needs to be update if the total changes 5257 $scope.updateBNPLMessage(); 5258 } 5259 5260 $scope.GST_Due = $scope.RoundTo2Dec(gst_total); 5261 $scope.updateProcessor(); 5262 $scope.checkMinAmountMet(); 5263 $scope.makeTaxInclusive(); 5264 }; 5265 5266 $scope.hasNonSubscriptionItemsInCart = function () { 5267 return $scope.CartData.Items.some(function (item) { 5268 return item.type !== 'subscription'; 5269 }); 5270 } 5271 5272 $scope.isRetailDeliveryFeeApplied = function () { 5273 return $scope.isStoreHasLocationForRetailFee() && $scope.Taxes > 0 && ((!$scope.ShippingInfo.Option && $scope.isWholesaleEnabled()) || ($scope.ShippingInfo.Option && $scope.ShippingInfo.Option.name !== 'Store Pickup' && $scope.ShippingInfo.Option.name !== 'Curbside Pickup')); 5274 } 5275 5276 $scope.InitToolTip = function (event) { 5277 var id = event.target.id; 5278 $('#' + id).tooltip(); 5279 }; 5280 5281 $scope.couponWithCustomerGroupsIsAvailable = function(coupon) { 5282 if (coupon.limitedCustomerGroupList.length) { 5283 if ($scope.BillingInfo && $scope.BillingInfo.customerGroups && $scope.BillingInfo.customerGroups.length) { 5284 var intersection = $scope.BillingInfo.customerGroups.filter(function (customerGroup) { 5285 return coupon.limitedCustomerGroupList.some(function (couponGroup) { 5286 return parseInt(customerGroup) === parseInt(couponGroup); 5287 }); 5288 } 5289 ); 5290 } 5291 5292 if (!$scope.BillingInfo || !intersection || (intersection && !intersection.length)) { 5293 return false; 5294 } 5295 } 5296 5297 return true; 5298 }; 5299 5300 $scope.updateCouponsByCustomerGroups = function () { 5301 if ($scope.knobby['module_better_coupons']) { 5302 var pattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; 5303 5304 if (!pattern.test($scope.BillingInfo.Email)) { 5305 return; 5306 } 5307 5308 var params = { 5309 customerEmail: $scope.BillingInfo.Email, 5310 }; 5311 5312 $http.get($scope.BuildUrl('/checkout/update-coupons-by-customer-groups'), {params: params, withCredentials: true}) 5313 .then(function (result) { 5314 $scope.BillingInfo.customerGroups = result.data.customerGroups || []; 5315 $scope.SessionStorageSet(); 5316 5317 $scope.CartDataAdapt(result.data); 5318 }); 5319 } 5320 }; 5321 5322 // endregion 5323 5324 // ****************************************************************************************** 5325 // region Change and Validation 5326 // ****************************************************************************************** 5327 5328 $scope.FormFieldErrorClass = function (formName, fieldId, form) { 5329 if ($scope.FormFieldError(formName, fieldId, form)) { 5330 return 'has-error'; 5331 } 5332 return ''; 5333 }; 5334 5335 $scope.FormFieldError = function (formName, fieldId, form) { 5336 if (!form) { 5337 form = $scope[formName]; 5338 } else { 5339 if (formName === 'shippingForm') { 5340 $scope.setShippingForm(form); 5341 } 5342 if (formName === 'billingForm') { 5343 $scope.setBillingForm(form); 5344 } 5345 if (formName === 'contactForm') { 5346 $scope.setContactForm(form); 5347 } 5348 } 5349 5350 if (form) { 5351 if (form[fieldId] && form[fieldId].$invalid) { 5352 if (form[fieldId].$dirty || form[fieldId].$touched || form.$submitted) { 5353 return true; 5354 } 5355 } 5356 return false;
5357 } 5358 return true; 5359 }; 5360 5361 $scope.setShippingForm = function(shippingForm) { 5362 if (!$scope.shippingForm && shippingForm) { 5363 $scope.shippingForm = shippingForm; 5364 } 5365 }; 5366 5367 $scope.setBillingForm = function(billingForm) { 5368 if (!$scope.billingForm && billingForm) { 5369 $scope.billingForm = billingForm; 5370 } 5371 }; 5372 5373 $scope.setContactForm = function(contactForm) { 5374 if (!$scope.contactForm && contactForm) { 5375 $scope.contactForm = contactForm; 5376 } 5377 }; 5378 5379 // Dead code - remove 5380 $scope.TextBlur = function (id, value) { 5381 if (!id) { 5382 return; 5383 } 5384 $scope.Class[id] = value ? 'text-success' : 'text-danger'; 5385 }; 5386 5387 $scope.ExpBlur = function (id) { 5388 if (!$scope.CardInfo.ExpMonth || !$scope.CardInfo.ExpYear) { 5389 $scope.Class[id] = 'text-danger'; 5390 } else { 5391 $scope.Class[id] = 'text-success'; 5392 } 5393 $scope.Validate(); 5394 }; 5395 5396 $scope.CCBlur = function (id) { 5397 if ($scope.CCValidate() === null) { 5398 $scope.Class[id] = 'text-success'; 5399 } else { 5400 $scope.Class[id] = 'text-danger'; 5401 } 5402 $scope.Class['visa'] = $scope.CardInfo.CCType == 'VISA' ? 'card-visa' : 'card-default'; 5403 $scope.Class['mastercard'] = $scope.CardInfo.CCType == 'MC' ? 'card-mastercard' : 'card-default'; 5404 $scope.Class['amex'] = $scope.CardInfo.CCType == 'AMEX' ? 'card-amex' : 'card-default'; 5405 $scope.Class['discover'] = $scope.CardInfo.CCType == 'DISC' ? 'card-discover' : 'card-default'; 5406 5407 $scope.Validate(); 5408 }; 5409 5410 $scope.AddressBlur = function () { 5411 // If we have a dynamic tax, it may need to be updated too 5412 return $scope.updateDynamicTaxesOnce(); 5413 }; 5414 5415 $scope.EmailValidate = function (id, email) { 5416 var reg = /^([A-Za-z0-9_\-\.\+])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/; 5417 if (reg.test(email) == false) { 5418 $scope.Class[id] = 'text-success'; 5419 } else { 5420 $scope.Class[id] = 'text-danger'; 5421 } 5422 $scope.Validate(); 5423 }; 5424 5425 $scope.Validate = function () { 5426 $scope.TotalsCalculate(); 5427 $scope.CartVerify(); 5428 $scope.ContactVerify(); 5429 $scope.ShippingVerify(); 5430 $scope.BillingVerify(); 5431 $scope.TotalsCalculate(); 5432 }; 5433 5434 // endregion 5435 5436 // ****************************************************************************************** 5437 // region Cart 5438 // ****************************************************************************************** 5439 5440 $scope.getCCProcessor = function() { 5441 if ($scope.InitInfo.Processors && $scope.InitInfo.Processors.cc) { 5442 return $scope.InitInfo.Processors.cc; 5443 } 5444 return null; 5445 }; 5446 5447 $scope.isProcessingEnabled = function() { 5448 return $scope.hasCardProcessor() || $scope.isPayPalEnabled(); 5449 }; 5450 5451 $scope.isCardProcessorVantiv = function() { 5452 return ($scope.InitInfo.Processors && $scope.InitInfo.Processors.cc === $scope.PROCESSOR_VANTIV); 5453 }; 5454 5455 $scope.isCardProcessorNMI = function() { 5456 return ($scope.InitInfo.Processors && $scope.InitInfo.Processors.cc === $scope.PROCESSOR_NMI); 5457 }; 5458 5459 $scope.isCardProcessorRain = function() { 5460 return ($scope.InitInfo.Processors && $scope.InitInfo.Processors.cc === $scope.PROCESSOR_RAIN); 5461 }; 5462 5463 $scope.isCardProcessorQuiltAdyen = function() { 5464 return ($scope.InitInfo.Processors && $scope.InitInfo.Processors.cc === $scope.PROCESSOR_QUILT_ADYEN); 5465 }; 5466 5467 $scope.isResponsive = function() { 5468 if (window.isResponsive === false) { 5469 return false; 5470 } 5471 return true; 5472 } 5473 5474 $scope.hasCardProcessor = function() { 5475 return ($scope.isCardProcessorVantiv() || $scope.isCardProcessorNMI() || $scope.isCardProcessorRain() || $scope.isCardProcessorQuiltAdyen()); 5476 }; 5477 5478 $scope.isPayPalEnabled = function() { 5479 return ($scope.InitInfo.Processors && $scope.InitInfo.Processors.paypal === 'yes'); 5480 }; 5481 5482 $scope.isVantivHosted = function() { 5483 return $scope.StatusInfo.Processor === $scope.PROCESSOR_VANTIV; 5484 }; 5485 5486 $scope.isRainProcessor = function() { 5487 return $scope.StatusInfo.Processor === $scope.PROCESSOR_RAIN; 5488 }; 5489
5490 $scope.isQuiltAdyen = function() { 5491 return $scope.StatusInfo.Processor === $scope.PROCESSOR_QUILT_ADYEN; 5492 }; 5493 5494 $scope.isPayPal = function() { 5495 return $scope.StatusInfo.Processor === $scope.PROCESSOR_PAYPAL; 5496 }; 5497 5498 $scope.isExtendPayPalShippingInfo = function () { 5499 return $scope.isShippingRequired(); 5500 }; 5501 5502 $scope.isFree = function() { 5503 return $scope.StatusInfo.Processor === $scope.PROCESSOR_FREE; 5504 }; 5505 5506 $scope.isCaptchaEnabled = function() { 5507 return ($scope.InitInfo && $scope.InitInfo.captcha); 5508 }; 5509 5510 $scope.isFullyPaidByGiftCard = function() { 5511 return $scope.Due === 0 && $scope.GCTotal > 0; 5512 }; 5513 5514 $scope.checkMinAmountMet = function() { 5515 if ($scope.StatusInfo.minAmountTriggered) { 5516 $scope.isMinimumAmountMet(); 5517 } 5518 }; 5519 5520 $scope.isMinimumAmountMet = function() { 5521 if ($scope.isCardProcessorRain() && $scope.isRainProcessor() && !$scope.isCardOnFileNeeded()) { 5522 if ($scope.getAmountDue() < $scope.STRIPE_MIN) { 5523 $scope.StatusInfo.ErrorMessageBottom = "The total purchase amount must be at least $.50 USD, please add more items to continue and checkout."; 5524 $scope.StatusInfo.minAmountTriggered = true; 5525 return false; 5526 } else { 5527 $scope.StatusInfo.ErrorMessageBottom = null; 5528 $scope.StatusInfo.minAmountTriggered = false; 5529 } 5530 } 5531 return true; 5532 } 5533 5534 $scope.isPayAtPickupEnabled = function() { 5535 return ($scope.isResponsive() 5536 && $scope.InitInfo 5537 && $scope.InitInfo.CheckoutSettings 5538 && $scope.InitInfo.CheckoutSettings.PayAtPickup 5539 ); 5540 }; 5541 5542 $scope.skipBilling = function() { 5543 return (($scope.isPayPal() || $scope.isFree()) 5544 && ($scope.BillingStatus.Editing || !$scope.isPayAtPickupEnabled() || !$scope.isPickup()) 5545 ); 5546 } 5547 5548 $scope.isTextMessagingSupported = function() { 5549 return $scope.InitInfo.storeTextMsgSendingEnabled; 5550 }; 5551 5552 /** @return {number} */ 5553 $scope.ItemsQuantity = function () { 5554 var totalQuantity = 0; 5555 if ($scope.CartData && $scope.CartData.Items && $scope.CartData.Items.length > 0) { 5556 for (var i = 0; i < $scope.CartData.Items.length; i++) { 5557 var item = $scope.CartData.Items[i]; 5558 totalQuantity += Number(item.qty); 5559 } 5560 } 5561 return totalQuantity; 5562 }; 5563 5564 $scope.CartVerify = function () { 5565 var totalQuantity = $scope.ItemsQuantity(); 5566 if (totalQuantity > 0) { 5567 $scope.CartStatus.Valid = true; 5568 $scope.CartStatus.NotValidMessages = null; 5569 } else { 5570 $scope.CartStatus.Valid = false; 5571 $scope.CartStatus.NotValidMessages = "No items are in your cart."; 5572 } 5573 }; 5574 5575 /** 5576 * NOTE: This method has become confusing, the cart can still be free and this will return false because shipping is required 5577 * We should refactor this functionality with the skipBilling method at some point. 5578 * @return {boolean} 5579 */ 5580 $scope.CartIsFree = function () { 5581 if ($scope.isShippingRequired() && !$scope.ShippingInfo.Option && !$scope.isPickup()) { 5582 return false; 5583 } 5584 var shipping = $scope.ShippingTotal + $scope.ShippingTaxes; 5585 5586 var total = 0; 5587 if ($scope.CartStatus.Valid && $scope.CartData && $scope.CartData.Items && $scope.CartData.Items.length > 0) { 5588 for (var i = 0; i < $scope.CartData.Items.length; i++) { 5589 var item = $scope.CartData.Items[i]; 5590 total += $scope.RoundTo2Dec(item.qty * $scope.getItemPrice(item)); 5591 } 5592 } else { 5593 return false; 5594 } 5595 5596 if ((total + shipping) === 0) { 5597 return true; 5598 } 5599 5600 if ($scope.knobby['module_better_coupons']) { 5601 if ($scope.CartData.Coupons && $scope.CartData.Coupons.length > 0 && !$scope.Due && !shipping) { //Discounted to 0 means free 5602 return true; 5603 } 5604 } else { 5605 if ($scope.CartData.Coupons && $scope.CartData.Coupons.length > 0 && !$scope.Due) { //Discounted to 0 means free 5606 return true; 5607 } 5608 } 5609 5610 return false;
5611 }; 5612 5613 $scope.CartEdit = function () { 5614 $scope.CartStatus.Entered = true; 5615 $scope.CartStatus.Editing = true; 5616 }; 5617 5618 $scope.trackEvent = function() { 5619 if ($scope.InitInfo.facebookPixelId || $scope.InitInfo.googleAnalyticsId) { 5620 var fbContentIds = []; 5621 var fbContents = []; 5622 var gaItems = []; 5623 var gaValue = 0; 5624 if ($scope.CartData.Items) { 5625 for (var i = 0; i < $scope.CartData.Items.length; i++) { 5626 var item = $scope.CartData.Items[i]; 5627 if (item.sku) { 5628 fbContentIds.push(item.sku); 5629 fbContents.push({ 5630 'id': item.sku, 5631 'quantity': item.qty 5632 }); 5633 } 5634 gaItems.push({ 5635 id: item.sku, 5636 name: item.title, 5637 quantity: item.qty, 5638 price: $scope.getItemPrice(item), 5639 }); 5640 5641 if(window.knobby && window.knobby['flag_google_analytics_v4']) { 5642 gaValue += $scope.getItemPrice(item); 5643 } 5644 } 5645 } 5646 if ($scope.InitInfo.facebookPixelId) { 5647 fbq('track', 'InitiateCheckout', { 5648 currency: $scope.InitInfo.Currency, 5649 value: $scope.SubTotal, 5650 num_items: $scope.ItemsQuantity(), 5651 content_type: 'product', 5652 content_ids: fbContentIds, 5653 contents: fbContents 5654 }); 5655 } 5656 if (window.knobby && window.knobby['flag_google_analytics_v4']) { 5657 if ($scope.InitInfo.googleAnalyticsId) { 5658 gtag('event', 'begin_checkout', { 5659 "currency": $scope.InitInfo.Currency, 5660 "value": gaValue, 5661 "items": gaItems 5662 }); 5663 } 5664 } else { 5665 if ($scope.InitInfo.googleAnalyticsId) { 5666 gtag('event', 'begin_checkout', { 5667 "currency": $scope.InitInfo.Currency, 5668 "items": gaItems 5669 }); 5670 } 5671 } 5672 } 5673 }; 5674 5675 $scope.CartDone = function (processor) { 5676 // set the "same as" checkbox to false, for wholesale it is hidden and we require the billing info only. 5677 if ($scope.isWholesaleEnabled()) { 5678 $scope.BillingInfo.AddressSameAsShipping = false; 5679 } 5680 5681 $scope.CartStatus.Editing = false; 5682 $scope.setProcessor(processor); 5683 5684 $timeout(function() { 5685 $scope.setShippingOption(); 5686 }, 100); 5687 5688 $scope.trackEvent(); 5689 }; 5690 5691 $scope.CartSync = function () { 5692 $http.get('/checkout/sync.php').then(function () { 5693 5694 }); 5695 }; 5696 5697 // Determines if we redirect to secure site for CC processing 5698 $scope.allowCreditCardCheckout = function () { 5699 if (($scope.getAmountDue() || $scope.requirePayment()) && $scope.hasCardProcessor()) { 5700 return !$scope.isWholesaleNoCCEnabled(); 5701 } 5702 return false; 5703 }; 5704 5705 $scope.allowPayPalCheckout = function () { 5706 if (($scope.Due || $scope.requirePayment()) && $scope.InitInfo && $scope.isPayPalEnabled()) { 5707 return !$scope.isWholesaleNoCCEnabled(); 5708 } 5709 return false; 5710 }; 5711 5712 $scope.isCardOnFileNeeded = function() { 5713 return ($scope.isRtoItemExist() || $scope.isSubscriptionExist() || $scope.isEnrollAutoPay() || $scope.isAddCardOnly()); 5714 }; 5715 5716 $scope.isRtoItemExist = function () { 5717 if ($scope.CartStatus.Valid && $scope.CartData && $scope.CartData.Items && $scope.CartData.Items.length > 0) { 5718 for (var i = 0; i < $scope.CartData.Items.length; i++) { 5719 var item = $scope.CartData.Items[i]; 5720 if (item.type === 'rto') { 5721 return true; 5722 } 5723 } 5724 } 5725 5726 return false;
5727 }; 5728 5729 $scope.isSubscriptionExist = function () { 5730 if ($scope.CartStatus.Valid && $scope.CartData && $scope.CartData.Items && $scope.CartData.Items.length > 0) { 5731 return $scope.CartData.Items.some(function (cartItem) { 5732 return cartItem.type === 'subscription' 5733 }); 5734 } 5735 return false; 5736 }; 5737 5738 $scope.isSubscriptionOnly = function () { 5739 if ($scope.CartStatus.Valid && $scope.CartData && $scope.CartData.Items && $scope.CartData.Items.length > 0) { 5740 return !$scope.CartData.Items.some(function (cartItem) { 5741 return cartItem.type !== 'subscription' 5742 }); 5743 } 5744 return true; 5745 }; 5746 5747 $scope.isGCClassSubscriptionCombinationOnly = function () { 5748 if ($scope.CartStatus.Valid && $scope.CartData && $scope.CartData.Items && $scope.CartData.Items.length > 0) { 5749 const allowedTypes = ['class', 'gc', 'subscription']; 5750 5751 var hasSubscriptionCatchUpShipping = $scope.CartData.Items.some(function (cartItem) { 5752 return cartItem.type === 'subscription' 5753 && cartItem.subscriptionData 5754 && cartItem.subscriptionData.previous_items_cnt > 0 5755 && cartItem.subscriptionData.delivery_type === 'ship'; 5756 }); 5757 5758 if (hasSubscriptionCatchUpShipping) { 5759 return false; 5760 } 5761 5762 return $scope.CartData.Items.every(function (cartItem) { 5763 if (!$scope.checkFlagBugDigitalDownloadWithClass()) { 5764 return allowedTypes.includes(cartItem.type); 5765 } else { 5766 return allowedTypes.includes(cartItem.type) || cartItem.downloadable; 5767 } 5768 }); 5769 } 5770 return true; 5771 }; 5772 5773 $scope.checkFlagForSubscriptionOnly = function () { 5774 if ($scope.knobby && $scope.knobby.module_paypal_subscription_only) { 5775 return $scope.isSubscriptionOnly(); 5776 } 5777 5778 return false; 5779 } 5780 5781 $scope.checkFlagStripeSendLevel3Data = function () { 5782 return $scope.knobby && $scope.knobby.module_stripe_send_level_3_data; 5783 } 5784 5785 $scope.checkFlagRestoreSchoolDeliveryShippingOption = function () { 5786 return $scope.knobby && $scope.knobby.module_restore_school_delivery_shipping_option; 5787 }; 5788 5789 $scope.checkFlagAdyenSendLevel3Data = function () { 5790 return $scope.knobby && $scope.knobby.module_adyen_send_level_3_data; 5791 } 5792 5793 $scope.checkFlagBugDigitalDownloadWithClass = function () { 5794 return $scope.knobby && $scope.knobby.module_bug_digital_download_with_class; 5795 } 5796 5797 $scope.checkFlagAdyenSendLevel3DataAuthCapture = function () { 5798 return $scope.knobby && $scope.knobby.module_adyen_send_level_3_data_auth_capture; 5799 } 5800 5801 $scope.checkFlagDisableExpressCheckout = function () { 5802 return $scope.knobby && $scope.knobby.module_disable_express_checkout; 5803 } 5804 5805 $scope.checkFlagAvsEcommerce = function () { 5806 return $scope.knobby && $scope.knobby.module_avs_ecommerce; 5807 }; 5808 5809 $scope.checkFlagFixAdyenBadShipping = function () { 5810 return $scope.knobby && $scope.knobby.module_fix_adyen_bad_shipping; 5811 }; 5812 5813 $scope.getEffectiveShippingInfo = function () { 5814 if ($scope.checkFlagFixAdyenBadShipping() && !$scope.isShippingRequired()) { 5815 return { 5816 FirstName: '', 5817 LastName: '', 5818 Address1: '', 5819 Address2: '', 5820 City: '', 5821 State: '', 5822 Zip: '', 5823 Country: '', 5824 CountryKey: null, 5825 company_name: '' 5826 }; 5827 } 5828 5829 return $scope.ShippingInfo; 5830 }; 5831 5832 $scope.checkFlagCarePlans = function () { 5833 return $scope.knobby && $scope.knobby.module_care_plans; 5834 } 5835 5836 $scope.checkFlagRetailDeliveryFee = function () { 5837 return $scope.knobby && $scope.knobby.module_colorado_retail_delivery_fee; 5838 } 5839 5840 $scope.checkFlagSendMixPanelEventZipCodeChange = function () { 5841 return $scope.knobby && $scope.knobby.module_send_mix_panel_event_on_zipcode_change_web_checkout; 5842 } 5843 5844 $scope.isDollarOffFiltersAllowed = function () { 5845 return $scope.knobby && $scope.knobby.module_item_filters_dollar_off_coupons; 5846 }; 5847 5848 $scope.checkFlagAvalaraTaxCodeIssue = function () { 5849 return $scope.knobby && $scope.knobby.module_avalara_tax_code_issue; 5850 } 5851
5852 $scope.checkFlagFixCheckoutJurisdictionTaxPrecision = function () { 5853 return $scope.knobby && $scope.knobby.module_fix_checkout_jurisdiction_tax_precision; 5854 } 5855 5856 $scope.checkFlagCannotViewTermsConditionsCheckoutScreenAddressInfo = function () { 5857 return $scope.knobby && $scope.knobby.module_cannot_view_terms_conditions_checkout_screen_address_info; 5858 } 5859 5860 $scope.canShowShipToSchool = function () { 5861 return $scope.knobby 5862 && $scope.knobby.module_ship_to_school_for_non_rto_or_osr 5863 && window.isMusicStore 5864 && !$scope.isRtoOrOsr() 5865 && $scope.districts 5866 && $scope.districts.length !== 0 5867 && $scope.InitInfo 5868 && $scope.InitInfo.ShipToSchoolsEnabled; 5869 }; 5870 5871 $scope.isWholesaleTaxExempt = function() { 5872 if ($scope.InitInfo.WholesaleSettings && $scope.InitInfo.WholesaleSettings.TaxWholesaleOrders) { 5873 return false; 5874 } 5875 5876 return ($scope.isWholesaleEnabled() && !$scope.isTaxInclusive()); 5877 }; 5878 5879 $scope.isWholesaleEnabled = function () { 5880 return ($scope.InitInfo.wholesale && $scope.InitInfo.wholesale.checkout_enabled); 5881 }; 5882 5883 $scope.isWholesaleNoCCEnabled = function () { 5884 return ($scope.InitInfo.wholesale && $scope.InitInfo.wholesale.checkout_enabled && $scope.InitInfo.wholesale.no_cc_enabled); 5885 }; 5886 5887 $scope.disableWholesaleShipping = function () { 5888 if ($scope.isWholesaleNoCCEnabled()) { 5889 return true; 5890 } 5891 5892 return ($scope.InitInfo.wholesale && $scope.InitInfo.wholesale.checkout_enabled && !$scope.InitInfo.wholesale.shipping_enabled); 5893 }; 5894 5895 $scope.setWholesaleUserInfo = function () { 5896 if ($scope.isWholesaleEnabled() && $scope.InitInfo.wholesale_user) { 5897 var u = $scope.InitInfo.wholesale_user; 5898 var b = $scope.BillingInfo; 5899 var s = $scope.ShippingInfo; 5900 if (u.email && !b.Email) { 5901 b.Email = u.email; 5902 } 5903 if (u.phone && !b.Phone) { 5904 b.Phone = u.phone; 5905 } 5906 if (u.first_name && !s.FirstName) { 5907 s.FirstName = u.first_name; 5908 } 5909 if (u.last_name && !s.LastName) { 5910 s.LastName = u.last_name; 5911 } 5912 } 5913 }; 5914 5915 $scope.setCustomerInfo = function () { 5916 if (!$scope.InitInfo.customer) { 5917 return; 5918 } 5919 5920 var customer = $scope.InitInfo.customer; 5921 5922 $scope.BillingInfo.Email = customer.email; 5923 $scope.BillingInfo.Phone = customer.phone_mobile || customer.phone; 5924 5925 $scope.ShippingInfo.FirstName = customer.name_first; 5926 $scope.ShippingInfo.LastName = customer.name_last; 5927 5928 $scope.BillingInfo.FirstName = customer.name_first; 5929 $scope.BillingInfo.LastName = customer.name_last; 5930 5931 5932 $scope.BillingInfo.Address1 = customer.address; 5933 $scope.BillingInfo.Address2 = customer.address2; 5934 $scope.BillingInfo.City = customer.city; 5935 $scope.BillingInfo.State = customer.state; 5936 $scope.BillingInfo.Zip = customer.zip; 5937 $scope.BillingInfo.Country = customer.country; 5938 5939 if (customer.shipping_address) { 5940 $scope.ShippingInfo.Address1 = customer.shipping_address.address; 5941 $scope.ShippingInfo.Address2 = customer.shipping_address.address2; 5942 $scope.ShippingInfo.City = customer.shipping_address.city; 5943 $scope.ShippingInfo.State = customer.shipping_address.state; 5944 $scope.ShippingInfo.Zip = customer.shipping_address.zip; 5945 $scope.ShippingInfo.Country = customer.shipping_address.country; 5946 $scope.BillingInfo.AddressSameAsShipping = customer.shipping_address.is_billing_default; 5947 } 5948 5949 }; 5950 5951 $scope.editRental = function (item) { 5952 $window.location.href = '/rentals.htm#/sync/' + item.id; 5953 }; 5954 5955 $scope.showContinueShopping = function() { 5956 return !$scope.isWebPayment(); 5957 }; 5958 5959 $scope.showItemSummary = function() { 5960 if ($scope.isWebPayment()) { 5961 return false; 5962 }
5963 return ($scope.CartStatus.Valid && !$scope.StatusInfo.Initializing); 5964 }; 5965 5966 $scope.showMainCheckout = function() { 5967 return ($scope.requireAddressInfo() || $scope.requireContactInfo() || $scope.RequireBillingInfo()) 5968 } 5969 5970 // Should this really be isNMI? Determines if we show NMI option 5971 $scope.showSecureOrPaypal = function () { 5972 return $scope.CartStatus.Valid 5973 && !$scope.isVantivHosted() 5974 && !$scope.isRainProcessor() 5975 && !$scope.isQuiltAdyen() 5976 && !$scope.isPayPal() 5977 && $scope.StatusInfo.Processor !== 'gc' 5978 && !$scope.isWholesaleNoCCEnabled() 5979 && !$scope.isCartTotalExceeded(); 5980 }; 5981 5982 $scope.getCartSettings = function () { 5983 if (!$scope.InitInfo || !$scope.InitInfo.CartSettings) { 5984 return null; 5985 } 5986 5987 return $scope.InitInfo.CartSettings; 5988 }; 5989 5990 $scope.getMaxCartTotal = function () { 5991 var cartSettings = $scope.getCartSettings(); 5992 5993 if (!cartSettings || !cartSettings.hasOwnProperty('MaxCartTotal')) { 5994 return null; 5995 } 5996 5997 return +cartSettings.MaxCartTotal || null; 5998 }; 5999 6000 $scope.isCartTotalExceeded = function () { 6001 var maxTotal = $scope.getMaxCartTotal(); 6002 6003 if (!maxTotal) { 6004 return false; 6005 } 6006 6007 return $scope.SubTotal > maxTotal; 6008 }; 6009 6010 // Determines if we show pre-checkout summary (Not NMI) 6011 $scope.showHostedCheckout = function () { 6012 if($scope.domainDisabled === true) { 6013 return false; 6014 } 6015 if ($scope.isWebPayment()) { 6016 return true; 6017 } 6018 6019 return $scope.isVantivHosted() 6020 || $scope.isRainProcessor() 6021 || $scope.isQuiltAdyen() 6022 || $scope.isPayPal() 6023 || $scope.StatusInfo.Processor === 'gc' 6024 || !$scope.requirePayment(); 6025 }; 6026 6027 $scope.showContactInfo = function() { 6028 return ($scope.CartStatus.Valid && !$scope.isWebPayment()); 6029 }; 6030 6031 $scope.showSummaries = function() { 6032 return ($scope.CartStatus.Valid && $scope.ShippingStatus.Entered && !$scope.ShippingStatus.Editing && !$scope.isWebPayment()); 6033 }; 6034 6035 $scope.showEnrollAutoPay = function() { 6036 return $scope.isWebPayment() 6037 && !$scope.isInvoicePayment() 6038 && !$scope.isInvoicesPayment() 6039 && !$scope.isAddCardOnly() 6040 && !$scope.isMmsPayment() 6041 && !$scope.isRtoPayment(); 6042 }; 6043 6044 // endregion 6045 6046 // ****************************************************************************************** 6047 // region Shipping 6048 // ****************************************************************************************** 6049 6050 $scope.shippingCountryHasRegions = function () { 6051 try { 6052 if ($scope.ShippingInfo.RegionsCount > 0) { 6053 return true; 6054 } 6055 } catch (e) { 6056 // Consume exception, return default of false 6057 } 6058 6059 return false; 6060 }; 6061 6062 $scope.getShippingCountryKey = function () { 6063 var countryKey = null; 6064 if ($scope.ShippingInfo && $scope.ShippingInfo.CountryKey) { 6065 countryKey = $scope.ShippingInfo.CountryKey; 6066 } 6067 if ($scope.isPickup()) { 6068 countryKey = $scope.getStoreCountryKey(); 6069 } 6070 return countryKey; 6071 }; 6072 6073 $scope.hasShippingState = function() { 6074 return $scope.ShippingInfo.State; 6075 }; 6076 6077 $scope.hasShippingZip = function() { 6078 return $scope.ShippingInfo.Zip; 6079 }; 6080 6081 $scope.getShippingState = function() { 6082 if ($scope.ShippingInfo && $scope.ShippingInfo.Option && $scope.ShippingInfo.Option.address && $scope.ShippingInfo.Option.address.state) { 6083 return $scope.InitInfo.Store.state; 6084 } 6085 if ($scope.ShippingInfo && $scope.ShippingInfo.State) { 6086 return $scope.ShippingInfo.State; 6087 } 6088 return null; 6089 } 6090 6091 $scope.ShippingCountrySet = function (country) { 6092 $scope.setCountry($scope.ShippingInfo, country); 6093 }; 6094 6095 $scope.getStoreCountryKey = function () { 6096 return $scope.CountryKeyGet($scope.InitInfo.DomesticCountry); 6097 }; 6098 6099 $scope.getStoreState = function() { 6100 if ($scope.InitInfo && $scope.InitInfo.Store && $scope.InitInfo.Store.state) { 6101 return $scope.InitInfo.Store.state; 6102 } 6103 return null; 6104 }; 6105 6106 $scope.setCountry = function (info, country) { 6107 // Set Country 6108 if (!country) { 6109 country = $scope.InitInfo.DomesticCountry; 6110 } 6111 6112 6113 6114 info.Country = country; 6115 6116 // Get Country Key 6117 info.CountryKey = $scope.CountryKeyGet(country); 6118 6119 if($scope.isExtendPayPalShippingInfo()) {
6120 info.CountryIso2Key = $scope.CountryIso2KeyGet(country); 6121 } 6122 6123 // Determine State or Province 6124 info.RegionsCount = 0; 6125 if (info.CountryKey) { 6126 switch (info.CountryKey) { 6127 case 'USA': 6128 case 'AUS': 6129 info.StateOrProvince = 'State'; 6130 break; 6131 default: 6132 info.StateOrProvince = 'Province'; 6133 break; 6134 } 6135 6136 info.ZipOrPostal = 'Postal Code'; 6137 if (info.CountryKey === 'USA') { 6138 info.ZipOrPostal = 'Zip Code'; 6139 } 6140 6141 // Regions Count 6142 info.RegionsCount = 0; 6143 if ($scope.InitInfo.Regions[info.CountryKey]) { 6144 info.RegionsCount = Object.keys($scope.InitInfo.Regions[info.CountryKey]).length; 6145 } 6146 6147 if (info.Country === $scope.InitInfo.DomesticCountry && info.RegionsCount == 0) { 6148 info.State = 'N/A'; 6149 } 6150 } else { 6151 info.StateOrProvince = ''; 6152 } 6153 }; 6154 6155 $scope.isStorePickupEnabled = function() { 6156 if (!$scope.InitInfo || !$scope.InitInfo.StorePickupEnabled) { 6157 return false; 6158 } 6159 6160 if ($scope.hasOsrItem() && !$scope.isOsrStorePickupEnabled()) { 6161 return false; 6162 } 6163 6164 return true; 6165 }; 6166 6167 $scope.isLocalDeliveryEnabled = function () { 6168 return $scope.knobby && $scope.knobby.module_add_local_delivery_tab; 6169 } 6170 6171 $scope.isStorePickup = function () { 6172 return ($scope.ShippingInfo && $scope.ShippingInfo.Option && $scope.ShippingInfo.Option.name === 'Store Pickup'); 6173 }; 6174 6175 $scope.isSchoolPickup = function () { 6176 return ($scope.ShippingInfo && $scope.ShippingInfo.Option && $scope.ShippingInfo.Option.name === 'School Delivery'); 6177 }; 6178 6179 $scope.isCurbsidePickupEnabled = function() { 6180 if (!$scope.InitInfo || !$scope.InitInfo.CurbsidePickupEnabled) { 6181 return false; 6182 } 6183 6184 if ($scope.hasOsrItem()) { 6185 return false; 6186 } 6187 6188 return true; 6189 }; 6190 6191 $scope.isLocalDelivery = function() { 6192 return $scope.ShippingInfo?.Option?.name === 'Local Delivery'; 6193 }; 6194 6195 $scope.isCurbsidePickup = function() { 6196 return ($scope.ShippingInfo && $scope.ShippingInfo.Option && $scope.ShippingInfo.Option.name === 'Curbside Pickup'); 6197 }; 6198 6199 $scope.isPickup = function() { 6200 return $scope.isStorePickup() || $scope.isCurbsidePickup() || $scope.isSchoolPickup(); 6201 }; 6202 6203 $scope.isDelivery = function() { 6204 return false; 6205 }; 6206 6207 $scope.isCartValid = function() { 6208 if ($scope.isWebPayment()) { 6209 return true; 6210 } 6211 return $scope.CartStatus.Valid; 6212 } 6213 6214 $scope.isContacStatusValid = function() { 6215 if ($scope.isWebPayment()) { 6216 return true; 6217 } 6218 return $scope.ContactStatus.Valid; 6219 } 6220 6221 $scope.getISO2digitCountryCode = function(country) { 6222 var iso3digit = $scope.CountryKeyGet(country); 6223 if (iso3digit === 'USA') { 6224 return 'US'; 6225 } 6226 } 6227 6228 $scope.getIso2CountryCodeFromIso3 = function (country) { 6229 let isoMap = { 6230 "AFG":"AF","ALA":"AX","ALB":"AL","DZA":"DZ","ASM":"AS","AND":"AD","AGO":"AO","AIA":"AI","ATG":"AG","ARG":"AR","ARM":"AM","ABW":"AW","AUS":"AU","AUT":"AT","AZE":"AZ","BHS":"BS","BHR":"BH","BGD":"BD","BRB":"BB","BLR":"BY","BEL":"BE","BLZ":"BZ","BEN":"BJ","BMU":"BM","BTN":"BT","BOL":"BO","BIH":"BA","BWA":"BW","BRA":"BR","VGB":"VG","IOT":"IO","BRN":"BN","BGR":"BG","BFA":"BF","BDI":"BI","KHM":"KH","CMR":"CM","CAN":"CA","CPV":"CV","CYM":"KY","CAF":"CF","TCD":"TD","CHL":"CL","CHN":"CN","HKG":"HK","MAC":"MO","CXR":"CX","CCK":"CC","COL":"CO","COM":"KM","COG":"CG","COD":"CD","COK":"CK","CRI":"CR","CIV":"CI","HRV":"HR","CUB":"CU","CYP":"CY","CZE":"CZ","DNK":"DK","DJI":"DJ","DMA":"DM","DOM":"DO","ECU":"EC","EGY":"EG","SLV":"SV","GNQ":"GQ","ERI":"ER","EST":"EE","ETH":"ET","FLK":"FK","FRO":"FO","FJI":"FJ","FIN":"FI","FRA":"FR","GUF":"GF","PYF":"PF","GAB":"GA","GMB":"GM","GEO":"GE","DEU":"DE","GHA":"GH","GIB":"GI","GRC":"GR","GRL":"GL","GRD":"GD","GLP":"GP","GUM":"GU","GTM":"GT","GGY":"GG","GIN":"GN","GNB":"GW","GUY":"GY","HTI":"HT","VAT":"VA","HND":"HN","HUN":"HU","ISL":"IS","IND":"IN","IDN":"ID","IRN":"IR","IRQ":"IQ","IRL":"IE","IMN":"IM","ISR":"IL","ITA":"IT","JAM":"JM","JPN":"JP","JEY":"JE","JOR":"JO","KAZ":"KZ","KEN":"KE","KIR":"KI","PRK":"KP","KOR":"KR","KWT":"KW","KGZ":"KG","LAO":"LA","LVA":"LV","LBN":"LB","LSO":"LS","LBR":"LR","LBY":"LY","LIE":"LI","LTU":"LT","LUX":"LU","MKD":"MK","MDG":"MG","MWI":"MW","MYS":"MY","MDV":"MV","MLI":"ML","MLT":"MT","MHL":"MH","MTQ":"MQ","MRT":"MR","MUS":"MU","MYT":"YT","MEX":"MX","FSM":"FM","MDA":"MD","MCO":"MC","MNG":"MN","MNE":"ME","MSR":"MS","MAR":"MA","MOZ":"MZ","MMR":"MM","NAM":"NA","NRU":"NR","NPL":"NP","NLD":"NL","NCL":"NC","NZL":"NZ","NIC":"NI","NER":"NE","NGA":"NG","NIU":"NU","NFK":"NF","MNP":"MP","NOR":"NO","OMN":"OM","PAK":"PK","PLW":"PW","PSE":"PS","PAN":"PA","PNG":"PG","PRY":"PY","PER":"PE","PHL":"PH","POL":"PL","PRT":"PT","PRI":"PR","QAT":"QA","REU":"RE","ROU":"RO","RUS":"RU","RWA":"RW","BLM":"BL","SHN":"SH","KNA":"KN","LCA":"LC","MAF":"MF","SPM":"PM","VCT":"VC","WSM":"WS","SMR":"SM","STP":"ST","SAU":"SA","SEN":"SN","SRB":"RS","SYC":"SC","SLE":"SL","SGP":"SG","SVK":"SK","SVN":"SI","SLB":"SB","SOM":"SO","ZAF":"ZA","SSD":"SS","ESP":"ES","
6230LKA":"LK","SDN":"SD","SUR":"SR","SJM":"SJ","SWZ":"SZ","SWE":"SE","CHE":"CH","SYR":"SY","TWN":"TW","TJK":"TJ","TZA":"TZ","THA":"TH","TLS":"TL","TGO":"TG","TKL":"TK","TON":"TO","TTO":"TT","TUN":"TN","TUR":"TR","TKM":"TM","TCA":"TC","TUV":"TV","UGA":"UG","UKR":"UA","ARE":"AE","GBR":"GB","USA":"US","URY":"UY","UZB":"UZ","VUT":"VU","VEN":"VE","VNM":"VN","VIR":"VI","WLF":"WF","ESH":"EH","YEM":"YE","ZMB":"ZM","ZWE":"ZW" 6231 }; 6232 6233 return isoMap[country] || null; 6234 }; 6235 6236 /** @return {string} */ 6237 $scope.CountryKeyGet = function (country) { 6238 if (!country || !$scope.InitInfo.Countries) { 6239 return null; 6240 } 6241 6242 var countries = $scope.InitInfo.Countries; 6243 for (var property in countries) { 6244 if (countries.hasOwnProperty(property)) { 6245 if (countries[property] === country) { 6246 return property; 6247 } 6248 } 6249 } 6250 6251 return null; 6252 }; 6253 6254 /** @return {string} */ 6255 $scope.CountryIso2KeyGet = function (country) { 6256 if (!country || !$scope.InitInfo.CountriesIso2) { 6257 return null; 6258 } 6259 6260 var countries = $scope.InitInfo.CountriesIso2; 6261 for (var property in countries) { 6262 if (countries.hasOwnProperty(property)) { 6263 if (countries[property] === country) { 6264 return property; 6265 } 6266 } 6267 } 6268 6269 return null; 6270 }; 6271 6272 $scope.changeShippingCountry = function () { 6273 $scope.ShippingInfo.State = null; 6274 $scope.ShippingInfo.Zip = null; 6275 $scope.ShippingStatus.DirtyZip = null; // Force options 6276 $scope.ShippingStatus.DirtyState = null; // Force options 6277 $scope.ShippingOptionsGet(); 6278 $scope.ProcessItems(); 6279 }; 6280 6281 $scope.shippingOptionsRequired = function () { 6282 return ($scope.isShippingRequired() && !$scope.InitInfo.PayPalShippingEnabled); 6283 }; 6284 6285 $scope.setShippingOption = function() { 6286 let element = null; 6287 if ($scope.isStorePickup()) { 6288 element = $('#inStorePickupTab'); 6289 } else if ($scope.isCurbsidePickup()) { 6290 element = $('#curbsidePickupTab'); 6291 } else if ($scope.isLocalDeliveryEnabled() && $scope.isLocalDelivery()) { 6292 element = $('#localDeliveryTab'); 6293 } else if ($scope.isSchoolPickup()) { 6294 element = $('#schoolPickupTab'); 6295 } else if (!$scope.isShippingEnabled()) { 6296 if ($scope.isStorePickupEnabled()) { 6297 element = $('#inStorePickupTab'); 6298 } else if ($scope.isCurbsidePickupEnabled()) { 6299 element = $('#curbsidePickupTab'); 6300 } else if ($scope.isOsrSchoolPickupEnabled()) { 6301 element = $('#schoolPickupTab'); 6302 } 6303 } 6304 6305 if (element && typeof element.tab !== "undefined") { 6306 element.tab('show'); 6307 } 6308 }; 6309 6310 $scope.otherPickupOptionsEnabled = function () { 6311 return $scope.InitInfo.StorePickupEnabled || $scope.InitInfo.CurbsidePickupEnabled; 6312 }; 6313 6314 $scope.ShippingOptionsGet = function (forceUpdate) { 6315 // Handle shipping calc data based on geo location data 6316 if (!$scope.ShippingInfo.State) { 6317 $scope.ShippingInfo.State = $scope.shippingCalcState; 6318 if ( 6319 $scope.InitInfo.DomesticCountry === $scope.ShippingInfo.Country && 6320 !$scope.ShippingInfo.State && 6321 !$scope.otherPickupOptionsEnabled() 6322 ) { 6323 $scope.ShippingStatus.OptionsValid = false; 6324 $scope.enhancedShippingInfo.shippingOptions = {}; 6325 $scope.enhancedShippingInfo.localDeliveryOptions = {}; 6326 return $scope.NoOpPromise(); 6327 } 6328 } 6329 if ($scope.ShippingInfo.Zip) { 6330 $scope.shippingCalcZipCode = $scope.ShippingInfo.Zip; 6331 $scope.geoLocationZipCode = ''; 6332 } 6333 6334 if ($scope.geoLocationZipCode !== '' && $scope.checkFlagSendMixPanelEventZipCodeChange()) { 6335 sendMixpanelEvent('Zipcode- changed', {'button-location': 'Website Checkout - Shipping Options'}); 6336 } 6337 6338 $scope.Validate(); 6339 if (!$scope.isShippingRequired()) { 6340 // Zero-out the shipping options 6341 $scope.ShippingInfo.Option = null; 6342 $scope.ShippingInfo.OptionId = null; 6343 $scope.ShippingSubtotal = null;
6344 return $scope.NoOpPromise(); 6345 } 6346 6347 if ($scope.InitInfo.ShipStationEnabled) { 6348 // When pickup-without-zip flag is ON and pickup options are enabled, we skip zip validation – 6349 // we still want to call the endpoint so that pickup rates are returned, even if ShipStation will error. 6350 var skipZipValidation = $scope.otherPickupOptionsEnabled() && !$scope.shippingCalcZipCode; 6351 6352 if (!skipZipValidation) { 6353 var postalInvalid; 6354 if ($scope.knobby && $scope.knobby['module_validate_shipping_postal_format']) { 6355 postalInvalid = !isValidPostalCode($scope.shippingCalcZipCode, $scope.ShippingInfo.CountryKey); 6356 } else { 6357 postalInvalid = !$scope.shippingCalcZipCode || ($scope.ShippingInfo.CountryKey === 'USA' && $scope.shippingCalcZipCode.length < 5) || ($scope.ShippingInfo.CountryKey && $scope.shippingCalcZipCode.length < 3) 6358 } 6359 6360 if (postalInvalid) { 6361 $scope.ShippingStatus.OptionsValid = false; 6362 $scope.ShippingStatus.Message = "Invalid zip/postal code"; 6363 if ($scope.isNewUIFlagSet()) { 6364 $scope.enhancedShippingInfo.shippingOptions = {}; 6365 $scope.enhancedShippingInfo.localDeliveryOptions = {}; 6366 } else { 6367 $scope.ShippingInfo.Options = null; 6368 return $scope.NoOpPromise(); 6369 } 6370 } else { 6371 $scope.ShippingStatus.OptionsValid = true; 6372 $scope.ShippingStatus.Message = ""; 6373 } 6374 } 6375 6376 if (!forceUpdate && $scope.ShippingStatus.DirtyZip === $scope.shippingCalcZipCode && $scope.ShippingStatus.DirtyState === $scope.ShippingInfo.State && $scope.ShippingInfo.Options) { 6377 // Don't waste our time, nothing has changed 6378 return $scope.NoOpPromise(); 6379 } 6380 $scope.ShippingStatus.DirtyZip = $scope.shippingCalcZipCode; 6381 $scope.ShippingStatus.DirtyState = $scope.ShippingInfo.State; 6382 } 6383 6384 var local = $scope.ShippingInfo.Country === $scope.InitInfo.DomesticCountry; 6385 6386 // Check if we should allow pickup options without zip code 6387 var allowPickupWithoutZip = $scope.otherPickupOptionsEnabled(); 6388 6389 // For regular shipping validation (when no pickup options available) 6390 var stateMissingForRegularShipping = $scope.ShippingInfo.RegionsCount > 0 && !$scope.ShippingInfo.State; 6391 6392 // Block API call only if: 6393 // 1. No country info, OR 6394 // 2. No pickup options available AND regular shipping requirements not met 6395 if ( 6396 (!$scope.ShippingInfo.Country && !local) || 6397 (!$scope.otherPickupOptionsEnabled() && stateMissingForRegularShipping && local && !$scope.InitInfo.ShipStationEnabled && !allowPickupWithoutZip) 6398 ) { 6399 $scope.ShippingStatus.OptionsValid = false; 6400 if ($scope.isNewUIFlagSet()) { 6401 $scope.enhancedShippingInfo.shippingOptions = {}; 6402 $scope.enhancedShippingInfo.localDeliveryOptions = {}; 6403 $scope.updateGstTotals(); 6404 } else { 6405 $scope.ShippingInfo.Options = null; 6406 $scope.updateGstTotals(); 6407 return $scope.NoOpPromise(); 6408 } 6409 } 6410 6411 // Update dynamic taxes based on new shipping info 6412 return $scope.updateDynamicTaxesOnce().then(function () { 6413 $scope.ShippingStatus.OptionsUpdating++; 6414 var params = { 6415 shipping_state: $scope.ShippingInfo.State, 6416 shipping_country: $scope.ShippingInfo.Country, 6417 shipping_postal: $scope.shippingCalcZipCode, 6418 qty: $scope.CartQty, 6419 subtotal: $scope.ShippingSubtotal, 6420 discount: $scope.Discounts, 6421 weight: $scope.Weight, 6422 for_web: 1, 6423 dimensions: $scope.getLargestDimensions() 6424 }; 6425 6426 var osrItem = $scope.getOsrItem(); 6427 if (osrItem) { 6428 params.osr_rto_contract_id = +osrItem.id; 6429 } 6430 return $http.get($scope.BuildUrl('/checkout/shippingoptions'), { 6431 params: params, 6432 withCredentials: true 6433 }).then(function ($result) { 6434 var options = $result.data.shipping; 6435 var isErrorResponse = options && ((!options.length && options.error_message) || options.primary_provider_error); 6436 6437 if (isErrorResponse) { 6438 // Preserve error information so the UI can alert the customer 6439 $scope.ShippingStatus.OptionsValid = false;
6440 $scope.ShippingInfo.Options = options; 6441 $scope.ShippingStatus.Message = options.error_message; 6442 6443 if (options.primary_provider_error && options.primary_provider_error.error) { 6444 $scope.ShippingStatus.Message = options.primary_provider_error.error; 6445 } 6446 6447 // Still build the enhanced option lists so that pickup choices render 6448 $scope.ShippingVerify(); 6449 $scope.Validate(); 6450 6451 // If pickup options are indeed present, mark options as valid so UI shows tabs 6452 if ($scope.hasStorePickupOptions() || $scope.hasCurbsidePickupOptions() || $scope.hasSchoolPickupOptions() || $scope.hasLocalDeliveryOptions()) { 6453 $scope.ShippingStatus.OptionsValid = true; 6454 } 6455 } else { 6456 // Normal success path (no carrier errors) 6457 if (!$scope.isNewUIFlagSet()) { 6458 $scope.ShippingStatus.OptionsValid = true; 6459 } 6460 $scope.ShippingInfo.Options = options; 6461 $scope.ShippingVerify(); 6462 $scope.Validate(); 6463 } 6464 $scope.ShippingStatus.OptionsUpdating--; 6465 console.log($scope.ShippingInfo); 6466 6467 if ($scope.knobby && $scope.knobby.module_hide_shipping_tab) { 6468 const allowedNames = ['Store Pickup', 'Curbside Pickup', 'School Delivery', 'Local Delivery']; 6469 const optionList = Object.values(options || {}); 6470 6471 $scope.isShippingOptionsProvided = optionList.some(o => !allowedNames.includes(o.name)); 6472 $scope.isLocalDeliveryOptionsProvided = optionList.some(o => o.name === 'Local Delivery'); 6473 } 6474 }); 6475 }); 6476 }; 6477 6478 $scope.updateDigitalOrder = function() { 6479 if ($scope.CartData && $scope.CartData.Items && $scope.CartData.Items.length > 0) { 6480 var downloadableItems = $scope.CartData.Items.filter(function (item) { return item.downloadable }); 6481 $scope.digitalOrder = downloadableItems.length === $scope.CartData.Items.length 6482 if ($scope.digitalOrder) { 6483 $scope.ShippingInfo.Address1 = null; 6484 $scope.ShippingInfo.Address2 = null; 6485 $scope.ShippingInfo.City = null; 6486 $scope.ShippingInfo.State = null; 6487 $scope.ShippingInfo.Zip = null; 6488 if (!$scope.digitalOrder) { 6489 $scope.dynamicRateCache = null; 6490 $scope.dynamicRateCacheKey = null; 6491 } 6492 $scope.SessionStorageSet(); 6493 } 6494 } else { 6495 $scope.digitalOrder = false; 6496 } 6497 } 6498 6499 $scope.BillingOptionsGet = function() { 6500 // Update dynamic taxes flag 6501 if ($scope.digitalOrder) { 6502 $scope.Validate(); 6503 } else { 6504 $scope.getTaxTable(); 6505 } 6506 }; 6507 6508 $scope.updateGstTotals = function () { 6509 6510 $scope.TotalsCalculate(); 6511 $scope.GST_Due -= ($scope.ShippingTotal + $scope.ShippingTaxes); 6512 $scope.ShippingTaxes = 0; 6513 }; 6514 6515 $scope.ShippingVerify = function () { 6516 // If feature flag is enabled and cart contains only classes/gift cards/subscriptions/digital downloads, skip all shipping validation 6517 if ($scope.checkFlagBugClassesCantCheckOutEcommerce() && $scope.isGCClassSubscriptionCombinationOnly()) { 6518 $scope.ShippingStatus.Valid = true; 6519 $scope.ShippingStatus.NotValidMessages = null; 6520 $scope.SessionStorageSet(); 6521 return Promise.resolve(); 6522 } 6523 6524 if ((!$scope.isShippingRequired() || $scope.InitInfo.PayPalShippingEnabled) && !$scope.InitInfo.StorePickupEnabled && !$scope.InitInfo.CurbsidePickupEnabled) { 6525 $scope.ShippingStatus.Valid = true; 6526 $scope.ShippingStatus.NotValidMessages = null; 6527 $scope.SessionStorageSet(); 6528 return Promise.resolve(); 6529 } 6530 6531 if ($scope.digitalOrder) {
6532 $scope.ShippingStatus.Valid = true; 6533 $scope.ShippingStatus.NotValidMessages = null; 6534 $scope.SessionStorageSet(); 6535 if ($scope.digitalOrder) { 6536 // Update taxes since things may have changed 6537 return $scope.updateDynamicTaxesOnce().then(function () { 6538 $scope.TotalsCalculate(); 6539 if ($scope.BillingStatus.Entered) { 6540 $scope.BillingVerify(); 6541 } 6542 $scope.safeApply(); 6543 }); 6544 } else { 6545 return Promise.resolve(); 6546 } 6547 } 6548 6549 // Default Country 6550 6551 $scope.ShippingInfo.Country = null; 6552 if ($scope.ShippingInfo.CountryKey && $scope.InitInfo.Countries) { 6553 if ($scope.InitInfo.InternationalEnabled === false) { 6554 $scope.ShippingCountrySet($scope.InitInfo.DomesticCountry); 6555 } else { 6556 $scope.ShippingCountrySet($scope.InitInfo.Countries[$scope.ShippingInfo.CountryKey]); 6557 } 6558 } 6559 6560 // Show currency 6561 $scope.InitInfo.CurrencyCode = ''; 6562 if ($scope.ShippingInfo.Country && $scope.InitInfo.DomesticCountry && $scope.ShippingInfo.Country !== $scope.InitInfo.DomesticCountry) { 6563 $scope.InitInfo.CurrencyCode = $scope.InitInfo.Currency; 6564 } 6565 6566 // Shipping Option 6567 if (!$scope.ShippingInfo.State && !$scope.ShippingInfo.Country) { 6568 $scope.ShippingInfo.OptionId = 0; 6569 } 6570 6571 if ($scope.ShippingInfo.OptionId && $scope.ShippingInfo.Options) { 6572 $scope.ShippingInfo.Option = $scope.ShippingInfo.Options[$scope.ShippingInfo.OptionId]; 6573 } else { 6574 $scope.ShippingInfo.Option = null; 6575 } 6576 6577 // Separate Options 6578 try { 6579 var allOptions = $scope.ShippingInfo.Options; 6580 $scope.enhancedShippingInfo.shippingOptions = {}; 6581 $scope.enhancedShippingInfo.storePickupOptions = {}; 6582 $scope.enhancedShippingInfo.curbsidePickupOptions = {}; 6583 $scope.enhancedShippingInfo.schoolPickupOptions = {}; 6584 $scope.enhancedShippingInfo.localDeliveryOptions = {}; 6585 6586 if (allOptions) { 6587 Object.keys(allOptions).forEach( 6588 function (key) { 6589 var option = this[key]; 6590 if (!option.hasOwnProperty('name') || ($scope.checkFlagRestoreSchoolDeliveryShippingOption() && option.name === '')) { 6591 return; 6592 } 6593 var optionName = option.name; 6594 6595 if (optionName === 'Store Pickup') { 6596 $scope.enhancedShippingInfo.storePickupOptions[key] = option; 6597 } else if (optionName === 'Curbside Pickup') { 6598 $scope.enhancedShippingInfo.curbsidePickupOptions[key] = option; 6599 } else if (optionName === 'School Delivery' && ($scope.canShowShipToSchool() && $scope.ShippingInfo.shipToSchool && $scope.ShippingInfo.selectedSchool)) { 6600 $scope.enhancedShippingInfo.shippingOptions[key] = option; 6601 } else if (optionName === 'School Delivery') { 6602 $scope.enhancedShippingInfo.schoolPickupOptions[key] = option; 6603 } else { 6604 if ($scope.isNewUIFlagSet()) { 6605 $scope.ShippingStatus.OptionsValid = true; 6606 } 6607 // Only include regular shipping options if address requirements are met and no validation errors 6608 var zipProvided = $scope.hasShippingZip(); 6609 var stateValid = $scope.ShippingInfo.RegionsCount > 0 ? $scope.hasShippingState() : true; 6610 //If zip is not provided, it will fail for other reasons, If zip is provided make sure there are no error messages 6611 var hasOtherValidationErrors = zipProvided && $scope.showShippingErrorMessage(); 6612
6613 if (zipProvided && stateValid && !hasOtherValidationErrors) { 6614 if ($scope.isLocalDeliveryEnabled() && optionName === 'Local Delivery') { 6615 $scope.enhancedShippingInfo.localDeliveryOptions[key] = option; 6616 } else { 6617 $scope.enhancedShippingInfo.shippingOptions[key] = option; 6618 } 6619 } 6620 } 6621 }, 6622 allOptions 6623 ); 6624 } 6625 6626 if ($scope.isPickup() && !$scope.isSchoolPickup()) { 6627 $scope.BillingInfo.AddressSameAsShipping = true; 6628 $scope.BillingAddressVerify(); 6629 } else if ($scope.StatusInfo.payAtPickup) { 6630 $scope.resetPayAtPickup(); 6631 } 6632 } catch (err) { 6633 // Consume and log, don't let errors affect existing behavior 6634 if (window.console) { 6635 console.error('Error Separating Shipping Options: ' + err); 6636 } 6637 } 6638 6639 // Update Local Storage 6640 $scope.ShippingStatus.Valid = ($scope.shippingForm && $scope.shippingForm.$valid); 6641 $scope.SessionStorageSet(); 6642 6643 if (!$scope.ShippingInfo.Option && $scope.areShippingOptionsRequired()) { 6644 $scope.ShippingStatus.Valid = false; 6645 } 6646 6647 // Update taxes since things may have changed 6648 return $scope.updateDynamicTaxesOnce().then(function () { 6649 $scope.TotalsCalculate(); 6650 if ($scope.BillingStatus.Entered) { 6651 $scope.BillingVerify(); 6652 } 6653 $scope.safeApply(); 6654 }); 6655 }; 6656 6657 $scope.checkShippingCoupon = function () { 6658 var coupon = $scope.getFreeShippingCoupon(); 6659 6660 $scope.couponStatusMessage = ''; 6661 6662 if (!coupon) { 6663 return; 6664 } 6665 6666 coupon.discount = 0; 6667 6668 if (!$scope.ShippingInfo.Option) { 6669 $scope.couponStatusMessage = 'Shipping Selection Required'; 6670 return; 6671 } 6672 6673 var shippingAmount = $scope.getShippingTotal(); 6674 6675 if (shippingAmount > coupon.maxDiscount && coupon.maxDiscount > 0) { 6676 $scope.couponStatusMessage ='Shipping max limit of '+$scope.InitInfo.CurrencySymbol2+parseFloat(coupon.maxDiscount).toFixed(2); 6677 } 6678 6679 coupon.discount = $scope.getShippingDiscount(); 6680 }; 6681 6682 $scope.ShippingEdit = function () { 6683 $scope.Validate(); 6684 $scope.ShippingStatus.Entered = true; 6685 $scope.ShippingStatus.Editing = true; 6686 $scope.BillingStatus.Editing = false; 6687 $scope.TotalsCalculate(); 6688 }; 6689 6690 $scope.showShippingForm = function() { 6691 if ($scope.isFixCheckoutForClassesFlagEnabled()) { 6692 return $scope.CartStatus.Valid && ($scope.ShippingStatus.Editing || !$scope.ShippingStatus.Entered); 6693 } 6694 6695 if (!$scope.checkFlagBugClassesCantCheckOutEcommerce()) { 6696 return ($scope.CartStatus.Valid && ($scope.ShippingStatus.Editing || !$scope.ShippingStatus.Entered)); 6697 } else { 6698 return ($scope.CartStatus.Valid && $scope.isShippingRequired() && ($scope.ShippingStatus.Editing || !$scope.ShippingStatus.Entered)); 6699 } 6700 }; 6701 6702 $scope.showShippingOptionsForm = function () { 6703 return ($scope.CartStatus.Valid && $scope.requirePayment() && $scope.isPayPal() && !$scope.disableWholesaleShipping()); 6704 }; 6705 6706 $scope.showShippingAddressForm = function() { 6707 return $scope.isShippingRequired(); 6708 }; 6709 6710 $scope.isShippingEnabled = function() { 6711 if (!$scope.InitInfo.ShippingEnabled && !$scope.InitInfo.ShipStationEnabled) { 6712 // Not sure this is correct 6713 return !$scope.isStorePickupEnabled() && !$scope.isCurbsidePickupEnabled(); 6714 } 6715 6716 if ($scope.hasOsrItem() && !$scope.isOsrShippingEnabled()) { 6717 return !$scope.isOsrStorePickupEnabled() && !$scope.isOsrSchoolPickupAvailable(); 6718 } 6719 6720 return true; 6721 }; 6722 6723 $scope.isShippingTabVisible = function () { 6724 if ($scope.knobby && $scope.knobby.module_hide_shipping_tab && !$scope.isShippingOptionsProvided) { 6725 return false;
6726 } 6727 6728 return $scope.isShippingEnabled(); 6729 }; 6730 6731 $scope.isShippingFormVisible = function () { 6732 return $scope.isShippingEnabled() || $scope.isLocalDeliveryEnabled(); 6733 }; 6734 6735 $scope.showNoShippingRequired = function() { 6736 return ($scope.CartStatus.Valid && !$scope.isShippingRequired() && !$scope.isPayPal()); 6737 }; 6738 6739 $scope.isShippingRequired = function() { 6740 if ($scope.isWebPayment()) { 6741 return false; 6742 } 6743 6744 return $scope.ShippingStatus.Required; 6745 }; 6746 6747 $scope.isShippingNeeded = function() { 6748 return (!$scope.ShippingStatus.Valid 6749 && $scope.isShippingRequired() 6750 && !$scope.isPickup() 6751 && !$scope.isPayAtPickup() 6752 ); 6753 }; 6754 6755 $scope.isShippingEntered = function() { 6756 if ($scope.isWebPayment()) { 6757 return true; 6758 } 6759 return $scope.ShippingStatus.Entered; 6760 } 6761 6762 $scope.areShippingOptionsRequired = function() { 6763 // If Wholesale is enabled, they also need to enable wholesale shipping in order to see shipping options 6764 if ($scope.disableWholesaleShipping()) { 6765 return false; 6766 } 6767 6768 // Always show shipping options if pickup is enabled 6769 if ($scope.isGCClassSubscriptionCombinationOnly()) { 6770 return false; 6771 } 6772 6773 if (($scope.hasOsrItem() && $scope.isOsrStorePickupEnabled()) || $scope.otherPickupOptionsEnabled()) { 6774 return true; 6775 } 6776 6777 // For regular shipping, require zip and state only if country has regions 6778 var stateMissingForRegularShipping = $scope.ShippingInfo.RegionsCount > 0 && !$scope.ShippingInfo.State; 6779 if (!$scope.ShippingInfo.Zip || stateMissingForRegularShipping) { 6780 return $scope.otherPickupOptionsEnabled(); 6781 } 6782 6783 // NOTE: Store can disable shipping and then collect at PayPal 6784 if ($scope.isWholesaleNoCCEnabled()) { 6785 return false; 6786 } 6787 6788 return $scope.isShippingRequired(); 6789 }; 6790 6791 6792 $scope.isShippingOptionsValid = function() { 6793 return ($scope.ShippingStatus && $scope.ShippingStatus.OptionsValid); 6794 }; 6795 6796 $scope.showShippingErrorMessage = function() { 6797 return ($scope.ShippingStatus && $scope.ShippingStatus.Message); 6798 }; 6799 6800 $scope.showShippingStateRequired = function() { 6801 return (!$scope.showShippingErrorMessage() && !$scope.isShippingOptionsValid() && !$scope.hasShippingState()); 6802 }; 6803 6804 6805 $scope.showNoShippingOptions = function() { 6806 if ($scope.showShippingErrorMessage()) { 6807 return false; 6808 } 6809 if ($scope.showShippingStateRequired()) { 6810 return false; 6811 } 6812 if (!$scope.isShippingOptionsValid() && $scope.hasShippingState()) { 6813 return true; 6814 } 6815 if ($scope.isShippingOptionsValid() && !$scope.hasShippingOptions()) { 6816 if (!$scope.hasShippingState() || !$scope.hasShippingZip()) { 6817 return false; 6818 } 6819 6820 return true; 6821 } 6822 return false; 6823 }; 6824 6825 $scope.hasShippingOptions = function () { 6826 try { 6827 var hasRegularShipping = false; 6828 var hasPickupOptions = false; 6829 6830 // Check for regular shipping options - always require proper validation 6831 // State is only required if country has regions 6832 var zipProvided = $scope.hasShippingZip(); 6833 var stateValid = $scope.ShippingInfo.RegionsCount > 0 ? $scope.hasShippingState() : true; 6834 6835 if (zipProvided && stateValid) { 6836 var options = $scope.enhancedShippingInfo.shippingOptions; 6837 var keys = Object.keys(options); 6838 hasRegularShipping = (keys.length > 0); 6839 } 6840 6841 hasPickupOptions = $scope.hasStorePickupOptions() || $scope.hasCurbsidePickupOptions() || $scope.hasSchoolPickupOptions() || $scope.hasLocalDeliveryOptions(); 6842 6843 return hasRegularShipping || hasPickupOptions; 6844 } catch (err) { 6845 return false; 6846 } 6847 }; 6848 6849 $scope.hasStorePickupOptions = function () { 6850 try { 6851 var options = $scope.enhancedShippingInfo.storePickupOptions; 6852 var keys = Object.keys(options); 6853 return (keys.length > 0); 6854 } catch (err) { 6855 return false;
6856 } 6857 }; 6858 6859 $scope.hasCurbsidePickupOptions = function () { 6860 try { 6861 var options = $scope.enhancedShippingInfo.curbsidePickupOptions; 6862 var keys = Object.keys(options); 6863 return (keys.length > 0); 6864 } catch (err) { 6865 return false; 6866 } 6867 }; 6868 6869 $scope.hasLocalDeliveryOptions = function () { 6870 var info = $scope.enhancedShippingInfo || {}; 6871 6872 function hasAny(opt) { 6873 if (!opt) { 6874 return false; 6875 } 6876 6877 return Array.isArray(opt) ? opt.length > 0 : Object.keys(opt).length > 0; 6878 } 6879 6880 var stateValid = $scope.ShippingInfo.RegionsCount > 0 ? !!$scope.hasShippingState() : true; 6881 var addressGate = $scope.hasShippingZip() && stateValid; 6882 var hasRegularShipping = hasAny(info.shippingOptions) && addressGate; 6883 var hasLocalDelivery = addressGate && hasAny(info.localDeliveryOptions); 6884 6885 var hasPickupOptions = ( 6886 $scope.hasStorePickupOptions() || 6887 $scope.hasCurbsidePickupOptions() || 6888 $scope.hasSchoolPickupOptions() || 6889 hasLocalDelivery 6890 ); 6891 6892 return !!(hasRegularShipping || hasPickupOptions); 6893 }; 6894 6895 $scope.hasSchoolPickupOptions = function () { 6896 try { 6897 return Object.keys($scope.enhancedShippingInfo.schoolPickupOptions).length > 0; 6898 } catch (err) { 6899 return false; 6900 } 6901 }; 6902
6903 $scope.focusShippingCalcZipCode = function () { 6904 $('#shipping_calc_zip').select(); 6905 }; 6906 6907 $scope.changeShippingCalcZipCode = function () { 6908 var zipCode = $('#shipping_calc_zip').val(); 6909 $scope.ShippingInfo.Zip = zipCode; 6910 $scope.ShippingOptionsGet(); 6911 }; 6912 6913 if ($scope.knobby && $scope.knobby['module_glitch_online_orders_canadian_tax_missing']) { 6914 $scope.$watch('ShippingInfo.State', function (newValue) { 6915 if (newValue && $scope.InitInfo?.Regions && $scope.InitInfo.Regions[$scope.ShippingInfo?.CountryKey]) { 6916 const validKeys = Object.keys($scope.InitInfo.Regions[$scope.ShippingInfo.CountryKey]); 6917 if (!validKeys.includes(newValue)) { 6918 $scope.ShippingInfo.State = ''; 6919 } 6920 } 6921 }); 6922 } 6923 6924 $scope.checkCaptcha = function (skipBilling) { 6925 if ($scope.knobby && $scope.knobby.module_update_to_captcha_v3) { 6926 if (!$scope.isMinimumAmountMet()) { 6927 return; 6928 } 6929 6930 if (!$scope.isCaptchaEnabled()) { 6931 if ($scope.skipBilling() || skipBilling) { 6932 $scope.ShippingDone(false); 6933 } else { 6934 $scope.ShippingDone(true, 'BillingEdit'); 6935 } 6936 return Promise.resolve(); 6937 } 6938 6939 var authToken = $scope.InitInfo.store_token; 6940 var url = $scope.RainApiUrl + 'store/captcha/validate'; 6941 var headers = {}; 6942 if (authToken) { 6943 headers.Authorization = 'Bearer ' + authToken; 6944 } 6945 6946 return new Promise(function(resolve, reject) { 6947 grecaptcha.ready(function() { 6948 grecaptcha.execute(window.recaptchaSiteKey, {action: 'checkout'}) 6949 .then(function(token) { 6950 $http.post(url, {token: token}, {headers: headers}) 6951 .then(function() { 6952 $scope.showCaptcha = false; 6953 6954 if ($scope.skipBilling() || skipBilling) { 6955 $scope.ShippingDone(false); 6956 } else { 6957 $scope.ShippingDone(true, 'BillingEdit'); 6958 } 6959 6960 resolve(token); 6961 }) 6962 .catch(function(response) { 6963 $scope.showCaptcha = true; 6964 6965 $.growlUI('Error <i class="fa fa-exclamation text-danger"></i>', response.data.error); 6966 6967 reject(response.data.error); 6968 }); 6969 }); 6970 }); 6971 }); 6972 } 6973 6974 if (!$scope.isMinimumAmountMet()) { 6975 return; 6976 } 6977 6978 if ($scope.showCaptcha !== true) { 6979 if ($scope.skipBilling() || skipBilling) { 6980 $scope.ShippingDone(false); 6981 } else { 6982 $scope.ShippingDone(true, 'BillingEdit'); 6983 } 6984 return; 6985 } 6986 6987 return $http.get($scope.BuildUrl('/captcha/check/' + window.captchaResponse), {withCredentials: true}).then(function (result) { 6988 $scope.StatusInfo.ErrorMessageBottom = null; 6989 if (result.data.success === true) { 6990 $scope.showCaptcha = false; 6991 6992 if ($scope.skipBilling() || skipBilling) { 6993 $scope.ShippingDone(false); 6994 } else { 6995 $scope.ShippingDone(true, 'BillingEdit'); 6996 } 6997 } else { 6998 $scope.StatusInfo.ErrorMessageBottom = "Please click the button that says, \"I'm not a robot\", to continue."; 6999 $scope.showCaptcha = true; 7000 recaptchaOnload(); 7001 return false; 7002 } 7003 }); 7004 }; 7005 7006 $scope.ShippingDone = function (checkContactInfo, next) { 7007 $scope.resetPayAtPickup(); 7008 7009 if ($scope.contactForm) { 7010 $scope.contactForm.$setSubmitted();
7011 if ($scope.contactForm.$invalid) { 7012 return; 7013 } 7014 } 7015 7016 $scope.ShippingStatus.Submitted = true; 7017 if ($scope.isShippingRequired() && $scope.shippingForm) { 7018 $scope.shippingForm.$setSubmitted(); 7019 7020 if ($scope.shippingForm.$invalid) { 7021 return; 7022 } 7023 } 7024 $scope.Validate(); 7025 7026 var contactValid = checkContactInfo ? $scope.ContactStatus.Valid : true; 7027 $scope.ShippingStatus.MessagesShow = false; 7028 7029 if ($scope.ShippingStatus.Valid && contactValid) { 7030 7031 $scope.SessionUpdate(); 7032 if ($scope.skipBilling()) { 7033 $scope.CheckoutProcess(); 7034 } else if (!$scope.isResponsive() && !$scope.isNewUIFlagSet() && ($scope.isVantivHosted() || $scope.isRainProcessor() || $scope.isQuiltAdyen())) { 7035 $scope.CheckoutProcess(); 7036 } else if (next) { 7037 $scope.ShippingStatus.Entered = true; 7038 if (next == 'BillingEdit') { 7039 $scope.BillingEdit(); 7040 } 7041 } 7042 } else { 7043 $scope.ShippingStatus.MessagesShow = !$scope.ShippingStatus.Valid; 7044 $scope.ContactStatus.MessagesShow = !contactValid; 7045 } 7046 }; 7047 7048 // endregion 7049 7050 // ****************************************************************************************** 7051 // region Billing 7052 // ****************************************************************************************** 7053 7054 $scope.isPayAtPickup = function() { 7055 return ($scope.StatusInfo && $scope.StatusInfo.payAtPickup); 7056 }; 7057 7058 $scope.payAtPickupSelected = function() { 7059 $scope.StatusInfo.payAtPickup = true; 7060 $scope.updateProcessor(); 7061 $scope.Validate(); 7062 }; 7063 7064 $scope.resetPayAtPickup = function() { 7065 $scope.StatusInfo.payAtPickup = false; 7066 $scope.StatusInfo.Processor = null; 7067 $scope.updateProcessor(); 7068 }; 7069 7070 $scope.payWithCardSelected = function() { 7071 $scope.resetPayAtPickup(); 7072 $scope.Validate(); 7073 }; 7074 7075 $scope.billingCountryHasRegions = function () { 7076 try { 7077 if ($scope.BillingInfo.RegionsCount > 0) { 7078 return true; 7079 } 7080 } catch (e) { 7081 // Consume exception, return default of false 7082 } 7083 7084 return false; 7085 }; 7086 7087 $scope.disableBillingFields = function() { 7088 return ($scope.ShippingStatus 7089 && $scope.isShippingRequired() 7090 && $scope.BillingInfo 7091 && $scope.BillingInfo.AddressSameAsShipping 7092 && !$scope.isPickup() 7093 && !$scope.digitalOrder 7094 ); 7095 }; 7096 7097 $scope.requirePayment = function () { 7098 $scope.updateProcessor(); 7099 if ($scope.isFree() && $scope.isShippingRequired()) { 7100 return !($scope.ShippingInfo.Option && ($scope.ShippingTotal + $scope.ShippingTaxes) === 0); 7101 } 7102 7103 return $scope.isCardOnFileNeeded() || !($scope.Due === 0 || $scope.StatusInfo.Processor === 'gc' || $scope.isFree() || $scope.StatusInfo.Processor === $scope.PROCESSOR_PAY_AT_PICKUP || $scope.isWholesaleNoCCEnabled()); 7104 }; 7105 7106 $scope.requireTermsWithShipping = function() { 7107 return ($scope.isTermsRequired() && ($scope.skipBilling() || $scope.isBillingDone()) && $scope.isNewUIFlagSet()); 7108 }; 7109 7110 $scope.isTermsRequired = function() { 7111 return ($scope.InitInfo && $scope.InitInfo.CheckoutSettings && $scope.InitInfo.CheckoutSettings.TermsEnabled && $scope.isNewUIFlagSet()); 7112 }; 7113 7114 $scope.TermsShow = function () { 7115 $scope.StatusInfo.TermsProcessing = true; 7116 $('#TermModal').modal('show'); 7117 if ($scope.InitInfo.terms) { 7118 $('#TermsAndC').html($scope.InitInfo.terms); 7119 $scope.StatusInfo.TermsProcessing = false; 7120 } else { 7121 $http.get($scope.BuildUrl('/checkout/terms'), {withCredentials: true}).then(function (result) { 7122 $scope.InitInfo.terms = result.data.terms; 7123 7124 $('#TermsAndC').html($scope.InitInfo.terms); 7125 $scope.StatusInfo.TermsProcessing = false;
7126 }); 7127 } 7128 }; 7129 7130 $scope.ExpirationOptionsGen = function () { 7131 // Years 7132 var startYear = new Date().getFullYear(); 7133 var years = []; 7134 for (var y = startYear; y < startYear + 11; y++) { 7135 years.push(y); 7136 } 7137 $scope.ExpYears = years; 7138 7139 // Months 7140 var months = []; 7141 for (var m = 1; m < 13; m++) { 7142 months.push(m); 7143 } 7144 $scope.ExpMonths = months; 7145 }; 7146 7147 $scope.TermsValidate = function () { 7148 if (!$scope.InitInfo.CheckoutSettings.TermsEnabled) { 7149 return true; 7150 } 7151 }; 7152 7153 /** 7154 * For NMI primarily 7155 * @return {string|null} 7156 * */ 7157 $scope.CCValidate = function () { 7158 if ($scope.isPayPal() || $scope.isVantivHosted() || $scope.isRainProcessor() || $scope.isQuiltAdyen() || $scope.isWholesaleNoCCEnabled() || $scope.StatusInfo.Processor === 'gc') { 7159 return null; 7160 } 7161 7162 // Check Card type accepted 7163 if (typeof CheckLuhn !== 'undefined') { 7164 var ccType = GetCardType($scope.CardInfo.CC); 7165 if (ccType != 'UNKNOWN') { 7166 $scope.CardInfo.CCType = GetCardType($scope.CardInfo.CC); 7167 7168 if ($scope.InitInfo.CheckoutSettings) { 7169 if ($scope.CardInfo.CCType == 'VISA' && !$scope.InitInfo.CheckoutSettings.VisaAccepted) { 7170 return "We're sorry, but we do not accept Visa credit cards"; 7171 } 7172 if ($scope.CardInfo.CCType == 'MC' && !$scope.InitInfo.CheckoutSettings.MCAccepted) { 7173 return "We're sorry, but we do not accept MasterCard credit cards"; 7174 } 7175 if ($scope.CardInfo.CCType == 'AMEX' && !$scope.InitInfo.CheckoutSettings.AmexAccepted) { 7176 return "We're sorry, but we do not accept American Express credit cards"; 7177 } 7178 if ($scope.CardInfo.CCType == 'DISC' && !$scope.InitInfo.CheckoutSettings.DiscAccepted) { 7179 return "We're sorry, but we do not accept Discover credit cards"; 7180 } 7181 } 7182 } 7183 } 7184 7185 // Check LUHN 7186 if (typeof CheckLuhn !== 'undefined' && CheckLuhn($scope.CardInfo.CC)) { 7187 $scope.CardInfo.CCValid = $scope.CardInfo.CC.replace(/[\s+|.|-]/g, ''); 7188 $scope.CardInfo.CCMasked = MaskCardNumber($scope.CardInfo.CC); 7189 7190 return null; 7191 } else { 7192 $scope.CardInfo.CCValid = null; 7193 $scope.CardInfo.CCMasked = null; 7194 $scope.CardInfo.CCType = null; 7195 } 7196 return "The credit card you entered does not appear to be a valid credit card number"; 7197 }; 7198 7199 /** @return {boolean} */ 7200 $scope.PhoneValid = function (phone) { 7201 if (!phone) { 7202 return false; 7203 } 7204 7205 // Simple for now, we may care to do better later 7206 var result = phone.match(/\d/g); 7207 if (!result) { 7208 return false; 7209 } 7210 result = result.length >= 10; 7211 if (result) { 7212 var reg = /([A-Za-z])/; 7213 result = reg.test(phone) != true; 7214 } 7215 return result; 7216 }; 7217 7218 $scope.requireContactInfo = function() { 7219 return ($scope.CartStatus.Valid && $scope.requirePayment() && $scope.isPayPal()); 7220 }; 7221 7222 $scope.isFixCheckoutForClassesFlagEnabled = function () { 7223 return $scope.knobby && $scope.knobby.module_fix_checkout_for_classes; 7224 }; 7225 7226 $scope.ContactVerify = function () { 7227 var hits = 0; 7228 var messages = []; 7229 7230 if (($scope.isPayPal() || $scope.isVantivHosted() || $scope.isRainProcessor() || $scope.isQuiltAdyen()) && $scope.Due > 0) { 7231 $scope.ContactStatus.Valid = true; 7232 return; 7233 } 7234 7235 if ($scope.isFixCheckoutForClassesFlagEnabled() && $scope.isGCClassSubscriptionCombinationOnly()) { 7236 $scope.ContactStatus.Valid = true; 7237 7238 return; 7239 } 7240 7241 var emailValid = $scope.isEmailAddressValid($scope.BillingInfo.Email); 7242 var phoneValid = $scope.PhoneValid($scope.BillingInfo.Phone); 7243 7244 if ($scope.isPickup() && emailValid && phoneValid) {
7245 $scope.ContactStatus.Valid = true; 7246 return; 7247 } 7248 7249 emailValid ? hits++ : messages.push("Please enter your email address"); 7250 hits = hits << 1; 7251 7252 var result = phoneValid; 7253 result ? hits++ : messages.push("Please enter a valid phone number"); 7254 7255 // Show message 7256 $scope.ContactStatus.NotValidMessages = messages; 7257 $scope.ContactStatus.Valid = hits == 3; 7258 }; 7259 7260 $scope.isEmailAddressValid = function (emailAddress) { 7261 return !!emailAddress; 7262 }; 7263 7264 $scope.changeBillingCountry = function () { 7265 $scope.BillingInfo.State = null; 7266 $scope.BillingInfo.Zip = null; 7267 $scope.BillingVerify(); 7268 }; 7269 7270 $scope.setBillingCountry = function (country) { 7271 $scope.setCountry($scope.BillingInfo, country); 7272 }; 7273 7274 $scope.setBillingOption = function() { 7275 let element = $('#payNowTab'); 7276 if ($scope.isPayAtPickup()) { 7277 element = $('#payAtPickupTab'); 7278 } 7279 if (element && typeof element.tab !== "undefined") { 7280 element.tab('show'); 7281 } 7282 }; 7283 7284 $scope.RequireBillingInfo = function() { 7285 return $scope.isWebPayment(); 7286 }; 7287 7288 $scope.BillingAddressVerify = function () { 7289 // Billing same as shipping 7290 if ($scope.BillingInfo.AddressSameAsShipping && $scope.isShippingRequired() && (!$scope.isPickup() || !$scope.enhancedShippingAndStorePickupOptions)) { 7291 $scope.BillingInfo.FirstName = $scope.ShippingInfo.FirstName; 7292 $scope.BillingInfo.LastName = $scope.ShippingInfo.LastName; 7293 $scope.BillingInfo.Address1 = $scope.ShippingInfo.Address1; 7294 $scope.BillingInfo.Address2 = $scope.ShippingInfo.Address2; 7295 $scope.BillingInfo.City = $scope.ShippingInfo.City; 7296 $scope.BillingInfo.State = $scope.ShippingInfo.State; 7297 $scope.BillingInfo.Zip = $scope.ShippingInfo.Zip; 7298 $scope.BillingInfo.Country = $scope.ShippingInfo.Country; 7299 $scope.BillingInfo.CountryKey = $scope.ShippingInfo.CountryKey; 7300 } 7301 }; 7302 7303 $scope.BillingVerify = function () { 7304 if ($scope.isWholesaleNoCCEnabled()) { 7305 if ($scope.isNewUIFlagSet()) { 7306 $scope.CardStatus.Valid = true; 7307 } else { 7308 $scope.BillingStatus.Required = false; 7309 } 7310 } 7311 7312 if (!$scope.BillingStatus.Required || ($scope.getAmountDue() == 0 && !$scope.isCardOnFileNeeded()) || $scope.isPayPal() || $scope.isPayAtPickup()) { 7313 $scope.BillingStatus.Valid = true; 7314 $scope.CardStatus.Valid = true; 7315 if (!$scope.digitalOrder) { 7316 return; 7317 } 7318 7319 } 7320 7321 // Default Country 7322 if (!$scope.BillingInfo.CountryKey) { 7323 $scope.BillingInfo.CountryKey = $scope.ShippingInfo.CountryKey; 7324 } 7325 if ($scope.BillingInfo.CountryKey && $scope.InitInfo.Countries) { 7326 $scope.setBillingCountry($scope.InitInfo.Countries[$scope.BillingInfo.CountryKey]); 7327 } 7328 $scope.BillingAddressVerify(); 7329 7330 if ($scope.billingForm) { 7331 $scope.BillingStatus.Valid = $scope.billingForm.$valid; 7332 $scope.SessionStorageSet(); 7333 } else { 7334 $scope.BillingStatus.Valid = true; 7335 } 7336 7337 // Vantiv 7338 if ($scope.isVantivHosted()) { 7339 $scope.CardStatus.Valid = true; // we don't collect it here. 7340 return; 7341 } 7342 7343 // Rain 7344 if ($scope.isRainProcessor() || $scope.isQuiltAdyen()) { 7345 $scope.CardStatus.Valid = true; // we will check the token 7346 return; 7347 } 7348 7349 // NMI 7350 // Format expiration date 7351 if ($scope.CardInfo.ExpMonth && $scope.CardInfo.ExpYear) { 7352 $scope.CardInfo.ExpFormated = ("0" + $scope.CardInfo.ExpMonth).slice(-2) + ("0" + $scope.CardInfo.ExpYear).slice(-2); 7353 } 7354 7355 // Card Validation 7356 $scope.CardStatus.Message = $scope.CCValidate(); 7357 $scope.CardStatus.Valid = ($scope.CardStatus.Message === null); 7358 7359 }; 7360 7361 $scope.BillingEdit = function () { 7362 $scope.BillingStatus.Editing = true; 7363 $scope.BillingStatus.Entered = true; 7364 $scope.ShippingStatus.Editing = false;
7365 $scope.Validate(); 7366 $scope.TotalsCalculate(); 7367 $scope.setBillingOption(); 7368 // IE does not autoscroll to the top 7369 window.scrollTo(0, 0); 7370 }; 7371 7372 $scope.BillingDone = function (next) { 7373 if ($scope.billingForm) { 7374 $scope.billingForm.$setSubmitted(); 7375 if ($scope.billingForm.$invalid) { 7376 return; 7377 } 7378 $scope.Validate(); 7379 } 7380 7381 if ($scope.BillingStatus.Valid && $scope.CardStatus.Valid) { 7382 $scope.BillingStatus.Editing = false; 7383 $scope.BillingStatus.Entered = true; 7384 $scope.TotalsCalculate(); 7385 $scope.SessionUpdate(); 7386 if (next == 'ShippingEdit') { 7387 $scope.ShippingEdit(); 7388 } 7389 } else { 7390 $scope.BillingStatus.MessagesShow = !$scope.BillingStatus.Valid; 7391 $scope.CardStatus.MessagesShow = !$scope.CardStatus.Valid; 7392 } 7393 }; 7394 7395 // endregion 7396 7397 // ****************************************************************************************** 7398 // region Processing 7399 // ****************************************************************************************** 7400 7401 // Sets the default processor 7402 $scope.updateProcessor = function () { 7403 var payWithGiftCard = !$scope.Due && $scope.GCTotal > 0; 7404 7405 if (payWithGiftCard && $scope.isRtoItemExist()) { 7406 payWithGiftCard = false; 7407 } 7408 7409 if ($scope.isWholesaleNoCCEnabled()) { 7410 $scope.StatusInfo.Processor = 'wholesaleNoCC'; 7411 } else if ($scope.isPayAtPickup()) { 7412 $scope.StatusInfo.Processor = $scope.PROCESSOR_PAY_AT_PICKUP; 7413 } else if (payWithGiftCard) { 7414 $scope.StatusInfo.Processor = 'gc'; 7415 } else if (!$scope.Due && $scope.CartIsFree() && !$scope.isCardOnFileNeeded()) { 7416 $scope.StatusInfo.Processor = $scope.PROCESSOR_FREE; 7417 } else if ($scope.StatusInfo.SelectedProcessor) { 7418 $scope.StatusInfo.Processor = $scope.StatusInfo.SelectedProcessor; 7419 } else if (!$scope.StatusInfo.Processor && $scope.InitInfo.Processors) { 7420 if ($scope.InitInfo.Processors.cc) { 7421 if (!$scope.isCardProcessorVantiv() && !$scope.isCardProcessorRain() && !$scope.isCardProcessorQuiltAdyen()) { 7422 // This will auto select the processor on the secure page to NMI 7423 // otherwise, we don't auto select if paypal is an option so that they can pick paypal 7424 $scope.StatusInfo.Processor = $scope.InitInfo.Processors.cc; 7425 } 7426 } else if ($scope.isPayPalEnabled()) { 7427 $scope.StatusInfo.Processor = $scope.PROCESSOR_PAYPAL; 7428 } 7429 } else if (!$scope.StatusInfo.SelectedProcessor && $scope.StatusInfo.Processor === $scope.PROCESSOR_FREE && $scope.InitInfo.Processors) { 7430 if ($scope.isCardProcessorVantiv()) { 7431 $scope.StatusInfo.Processor = $scope.InitInfo.Processors.cc; 7432 } else if ($scope.isPayPalEnabled()) { 7433 $scope.StatusInfo.Processor = $scope.PROCESSOR_PAYPAL; 7434 } 7435 } 7436 }; 7437 7438 $scope.setProcessor = function (processor) { 7439 if (processor) { 7440 $scope.StatusInfo.SelectedProcessor = processor; 7441 $scope.StatusInfo.Processor = processor; 7442 switch (processor) { 7443 case $scope.PROCESSOR_VANTIV: 7444 case $scope.PROCESSOR_PAYPAL: 7445 $scope.ShippingEdit(); 7446 // Disable the summary 7447 $scope.BillingStatus.Entered = false; 7448 $scope.ShippingStatus.Entered = false; 7449 break; 7450 default: 7451 $scope.ShippingVerify(); 7452 break; 7453 } 7454 } else { 7455 $scope.updateProcessor(); 7456 } 7457 }; 7458 7459 $scope.toggleSubscribeToMarketingEmails = function () { 7460 $scope.subscribeToMarketingEmails = !$scope.subscribeToMarketingEmails; 7461 }; 7462 7463 $scope.CheckoutProcess = function (checkoutOptions) { 7464 checkoutOptions = checkoutOptions || {}; 7465 console.log('checkout process'); 7466 7467 var bnplMethods = ['klarna', 'affirm', 'afterpay']; 7468 7469 var bnplMethodIndex = bnplMethods.findIndex(function (m) { 7470 return $scope.paymentMethod === m; 7471 }); 7472 7473 if (bnplMethodIndex !== -1) { 7474 return $scope.onBNPL($scope.paymentMethod); 7475 } 7476 7477 if ($scope.checkFlagBlockCheckoutOnDynamicTaxFailure() && checkoutOptions.taxesVerified) { 7478 if ($scope.HasDynamicTaxes && $scope.isDynamicTaxServiceEnabled()) { 7479 $scope.TotalsCalculate(); 7480 $scope.CartVerify(); 7481 $scope.BillingVerify(); 7482 } 7483 } 7484 7485 if (!($scope.checkFlagBlockCheckoutOnDynamicTaxFailure() && checkoutOptions.taxesVerified)) { 7486 if ($scope.forms.contact) { 7487 $scope.$broadcast('form-check-validity');
7488 if ($scope.forms.contact.$invalid) { 7489 return Promise.resolve(); 7490 } 7491 } 7492 7493 if ($scope.contactForm && $scope.showContactInfo()) { 7494 $scope.contactForm.$setSubmitted(); 7495 if ($scope.contactForm.$invalid) { 7496 return Promise.resolve(); 7497 } 7498 } 7499 7500 if ($scope.shippingForm && $scope.isShippingRequired() && !$scope.isPickup()) { 7501 $scope.ShippingStatus.Submitted = true; 7502 $scope.shippingForm.$setSubmitted(); 7503 if ($scope.shippingForm.$invalid) { 7504 return Promise.resolve(); 7505 } 7506 } 7507 7508 if ($scope.isVantivHosted() || $scope.isRainProcessor() || $scope.isQuiltAdyen()) { 7509 $scope.BillingAddressVerify(); 7510 } 7511 7512 if ($scope.billingForm && $scope.isBillingRequired() && !$scope.isPayAtPickup() && !$scope.isPayPal()) { 7513 $scope.billingForm.$setSubmitted(); 7514 if ($scope.billingForm.$invalid) { 7515 return Promise.resolve(); 7516 } 7517 } else if ($scope.billingForm && $scope.isTermsRequired()) { 7518 if (!$scope.BillingInfo.TermsAgree) { 7519 $scope.billingForm.$setSubmitted(); 7520 return Promise.resolve(); 7521 } 7522 } 7523 7524 if ($scope.billingForm && $scope.isBillingRequired() && $scope.digitalOrder) { 7525 $scope.billingForm.$setSubmitted(); 7526 if ($scope.billingForm.$invalid) { 7527 return Promise.resolve(); 7528 } 7529 } 7530 7531 // Check Captcha if needed and validate it first 7532 if ($scope.showCaptcha) { 7533 return $scope.checkCaptcha().then(function() { 7534 if (!$scope.showCaptcha) { 7535 return $scope.CheckoutProcess(checkoutOptions); 7536 } 7537 }); 7538 } 7539 7540 $scope.Validate(); 7541 } 7542 7543 var canPayViaCard = $scope.isCartValid() && $scope.ShippingStatus.Valid && $scope.BillingStatus.Valid && $scope.isContacStatusValid() && $scope.CardStatus.Valid; 7544 var isDigitalOrderAndWeCanPayForItViaPayPal = false; 7545 var isFreeDigitalOrder = false; 7546 var isDigitalOrderFullyPaidByGiftCard = false; 7547 if ($scope.digitalOrder && $scope.BillingStatus.Valid && $scope.isPayPal()) { 7548 isDigitalOrderAndWeCanPayForItViaPayPal = true; 7549 } 7550 if ($scope.digitalOrder && $scope.isFree()) { 7551 isFreeDigitalOrder = true; 7552 } 7553 7554 if ($scope.digitalOrder && $scope.isFullyPaidByGiftCard()) { 7555 isDigitalOrderFullyPaidByGiftCard = true; 7556 } 7557 7558 if (!canPayViaCard && !isDigitalOrderAndWeCanPayForItViaPayPal && !isFreeDigitalOrder && !isDigitalOrderFullyPaidByGiftCard) { 7559 $scope.ShippingStatus.MessagesShow = true; 7560 $scope.ContactStatus.MessagesShow = true; 7561 $scope.BillingStatus.MessagesShow = true; 7562 $scope.CardStatus.MessagesShow = true; 7563 7564 return Promise.resolve(); 7565 } 7566 7567 var processCheckoutPayment = function () { 7568 console.log('Processing'); 7569 $scope.makeTaxInclusive(); 7570 7571 if ($scope.paymentMethod === 'paypal') { 7572 $scope.setProcessor('paypal'); 7573 } 7574 7575 const isRainProcessing = $scope.isCardProcessorRain() && $scope.isRainProcessor() && $scope.Rain.card;
7576 const isQuiltAdyenProcessing = $scope.isCardProcessorQuiltAdyen() && $scope.isQuiltAdyen() && $scope.paymentMethod === 'cc'; 7577 7578 if (isRainProcessing) { 7579 // Rain Processing Validation 7580 $scope.Rain.processorToken = null; 7581 $scope.clearErrorMessage(); 7582 7583 if (!$scope.isMinimumAmountMet()) { 7584 $scope.setProcessingCheckout(false); 7585 return Promise.resolve(); 7586 } 7587 7588 if ($scope.Rain.error) { 7589 $scope.setProcessingCheckout(false); 7590 $scope.StatusInfo.ErrorMessageBottom = $scope.Rain.error.message; 7591 return Promise.resolve(); 7592 } 7593 7594 if (!$scope.isRainPaymentValid()) { 7595 $scope.setProcessingCheckout(false); 7596 $scope.StatusInfo.ErrorMessageBottom = "Please complete the credit information before placing your order."; 7597 return Promise.resolve(); 7598 } 7599 7600 if ($scope.paymentMethod !== 'cc') { 7601 return Promise.reject('Unknown Payment type'); 7602 } 7603 } 7604 7605 if (isQuiltAdyenProcessing) { 7606 // Quilt Processing Validation 7607 $scope.clearErrorMessage(); 7608 7609 if (!$scope.isMinimumAmountMet()) { 7610 $scope.setProcessingCheckout(false); 7611 return Promise.resolve(); 7612 } 7613 7614 if (!$scope.isAdyenPaymentValid()) { 7615 $scope.setProcessingCheckout(false); 7616 $scope.StatusInfo.ErrorMessageBottom = $scope.getAdyenError(); 7617 return Promise.resolve(); 7618 } 7619 } 7620 7621 const submitInfoGetData = $scope.getSubmitInfoGetData(); 7622 const authPaymentData = isQuiltAdyenProcessing ? $scope.getAuthPaymentData() : undefined; 7623 const processRainPaymentData = isRainProcessing ? $scope.getProcessRainPaymentData() : undefined; 7624 const postProcessData = $scope.getPostProcessData(); 7625 7626 if ($scope.checkFlagBlockCheckoutOnDynamicTaxFailure()) { 7627 if ((!$scope.isCardOnFileNeeded() && !$scope.getAmountDue()) || $scope.isWholesaleNoCCEnabled() || $scope.isPayAtPickup()) { 7628 return $scope.PreProcess($scope.buildCheckoutPreProcessData()).then(function () { 7629 if ($scope.TransactionId) { 7630 return $scope.PostProcess(postProcessData); 7631 } else if (!$scope.StatusInfo.ErrorMessage) { 7632 $scope.StatusInfo.ErrorMessageBottom = "An unknown error occurred, please try again"; 7633 $scope.setProcessingCheckout(false); 7634 7635 return Promise.resolve(); 7636 } 7637 }); 7638 } 7639 7640 if (isRainProcessing) { 7641 return $scope.PreProcess($scope.buildCheckoutPreProcessData()).then(function(id) { 7642 if (!id) { 7643 return Promise.resolve(); 7644 } 7645 7646 return $scope.SubmitInfoGet(submitInfoGetData).then(function() { 7647 if ($scope.Rain.paymentAlreadyPaid) { 7648 return $scope.PostProcess(postProcessData); 7649 } else if ($scope.Rain.processorToken && $scope.TransactionId) { 7650 return $scope.processRainPayment(processRainPaymentData).then(function(result) { 7651 $scope.setProcessingCheckout(false); 7652 7653 if (result !== 'success') { 7654 return $scope.NoOpPromise(); 7655 } 7656 7657 return $scope.PostProcess(postProcessData); 7658 }).catch(function (error) { 7659 console.log(error); 7660 $scope.setErrorMessageBottom('An unhandled error has occurred. Please try again. If the error continues, please contact the store'); 7661 $scope.setProcessingCheckout(false);
7662 return $scope.NoOpPromise(); 7663 }); 7664 } 7665 7666 $scope.setProcessingCheckout(false); 7667 7668 return Promise.resolve(); 7669 }); 7670 }); 7671 } 7672 7673 if (isQuiltAdyenProcessing) { 7674 return $scope.PreProcess($scope.buildCheckoutPreProcessData()).then(function(id) { 7675 if (!id) { 7676 return Promise.resolve(); 7677 } 7678 return $scope.SubmitInfoGet(submitInfoGetData).then(function() { 7679 return $scope.authPayment(authPaymentData).then(function() { 7680 if ($scope.Adyen.authData || $scope.Adyen.paymentAlreadyPaid) { 7681 return $scope.PostProcess(postProcessData); 7682 } 7683 $scope.setProcessingCheckout(false); 7684 }); 7685 }); 7686 }); 7687 } 7688 7689 if ($scope.StatusInfo.Processor === 'gc') { 7690 return $scope.PreProcess($scope.buildCheckoutPreProcessData()).then(function () { 7691 if ($scope.TransactionId) { 7692 return $scope.PostProcess(postProcessData); 7693 } else if (!$scope.StatusInfo.ErrorMessage) { 7694 $scope.StatusInfo.ErrorMessageBottom = "An unknown error occurred, please try again"; 7695 $scope.setProcessingCheckout(false); 7696 7697 return Promise.resolve(); 7698 } 7699 }); 7700 } 7701 7702 return $scope.SubmitInfoGet(submitInfoGetData).then(function () { 7703 if (!$scope.SubmitInfo) { 7704 return Promise.resolve(); 7705 } 7706 7707 return $scope.PreProcess($scope.buildCheckoutPreProcessData()).then(function () { 7708 if ($scope.TransactionId) { 7709 $scope.ProcessorSubmit(); 7710 } 7711 }); 7712 }); 7713 } 7714 7715 const preProcessData = $scope.getPreProcessData(); 7716 7717 if ((!$scope.isCardOnFileNeeded() && !$scope.getAmountDue()) || $scope.isWholesaleNoCCEnabled() || $scope.isPayAtPickup()) { 7718 return $scope.PreProcess(preProcessData).then(function () { 7719 if ($scope.TransactionId) { 7720 return $scope.PostProcess(postProcessData); 7721 } else if (!$scope.StatusInfo.ErrorMessage) { 7722 $scope.StatusInfo.ErrorMessageBottom = "An unknown error occurred, please try again"; 7723 $scope.setProcessingCheckout(false); 7724 7725 return Promise.resolve(); 7726 } 7727 }); 7728 } 7729 7730 // Rain Processing 7731 if (isRainProcessing) { 7732 return $scope.PreProcess(preProcessData).then(function(id) { 7733 if (!id) { 7734 return Promise.resolve(); 7735 } 7736 7737 return $scope.SubmitInfoGet(submitInfoGetData).then(function() { 7738 // NOTE: Payment Server will not return a new intent once we've requested one - an intent is associated with a cart ID 7739 if ($scope.Rain.paymentAlreadyPaid) { 7740 return $scope.PostProcess(postProcessData); 7741 } else if ($scope.Rain.processorToken && $scope.TransactionId) { 7742 return $scope.processRainPayment(processRainPaymentData).then(function(result) { 7743 $scope.setProcessingCheckout(false); 7744 7745 if (result !== 'success') { 7746 return $scope.NoOpPromise(); 7747 } 7748 7749 return $scope.PostProcess(postProcessData); 7750 }).catch(function (error) { 7751 console.log(error); 7752 $scope.setErrorMessageBottom('An unhandled error has occurred. Please try again. If the error continues, please contact the store'); 7753 $scope.setProcessingCheckout(false);
7754 return $scope.NoOpPromise(); 7755 }); 7756 } 7757 7758 $scope.setProcessingCheckout(false); 7759 7760 return Promise.resolve(); 7761 }); 7762 }); 7763 } 7764 7765 // Quilt Processing 7766 if (isQuiltAdyenProcessing) { 7767 return $scope.PreProcess(preProcessData).then(function(id) { 7768 if (!id) { 7769 return Promise.resolve(); 7770 } 7771 return $scope.SubmitInfoGet(submitInfoGetData).then(function() { 7772 return $scope.authPayment(authPaymentData).then(function() { 7773 if ($scope.Adyen.authData || $scope.Adyen.paymentAlreadyPaid) { 7774 return $scope.PostProcess(postProcessData); 7775 } 7776 $scope.setProcessingCheckout(false); 7777 }); 7778 }); 7779 }); 7780 } 7781 7782 if ($scope.StatusInfo.Processor === 'gc') { 7783 return $scope.PreProcess(preProcessData).then(function () { 7784 if ($scope.TransactionId) { 7785 return $scope.PostProcess(postProcessData); 7786 } else if (!$scope.StatusInfo.ErrorMessage) { 7787 $scope.StatusInfo.ErrorMessageBottom = "An unknown error occurred, please try again"; 7788 $scope.setProcessingCheckout(false); 7789 7790 return Promise.resolve(); 7791 } 7792 }); 7793 } 7794 7795 return $scope.SubmitInfoGet(submitInfoGetData).then(function () { 7796 if (!$scope.SubmitInfo) { 7797 return Promise.resolve(); 7798 } 7799 7800 return $scope.PreProcess(preProcessData).then(function () { 7801 if ($scope.TransactionId) { 7802 $scope.ProcessorSubmit(); 7803 } 7804 }); 7805 }); 7806 }; 7807 7808 if ($scope.checkFlagBlockCheckoutOnDynamicTaxFailure()) { 7809 if (!checkoutOptions.taxesVerified) { 7810 var freshTaxesPromise = $scope.ensureFreshDynamicTaxesForCheckout(); 7811 7812 if (freshTaxesPromise) { 7813 $scope.setProcessingCheckout(true); 7814 7815 return freshTaxesPromise.then(function () { 7816 if ($scope.hasActiveDynamicTaxFailure()) { 7817 $scope.setProcessingCheckout(false); 7818 $scope.safeApply(); 7819 7820 return Promise.resolve(); 7821 } 7822 7823 checkoutOptions.taxesVerified = true; 7824 7825 if ($scope.HasDynamicTaxes && $scope.isDynamicTaxServiceEnabled()) { 7826 $scope.TotalsCalculate(); 7827 $scope.CartVerify(); 7828 $scope.BillingVerify(); 7829 } 7830 7831 return processCheckoutPayment(); 7832 }); 7833 } 7834 } 7835 7836 if ($scope.hasActiveDynamicTaxFailure()) { 7837 return Promise.resolve(); 7838 } 7839 7840 $scope.setProcessingCheckout(true); 7841 } 7842 7843 if (!$scope.checkFlagBlockCheckoutOnDynamicTaxFailure()) { 7844 $scope.setProcessingCheckout(true); 7845 7846 if (!$scope.dynamicTaxesReady) { 7847 $scope.setErrorMessageBottom(null); 7848 7849 return $scope.updateDynamicTaxesOnce().then(function () { 7850 if (!$scope.dynamicTaxesReady) { 7851 $scope.setProcessingCheckout(false); 7852 $scope.safeApply(); 7853 7854 return Promise.reject('Tax system is not responding.'); 7855 } 7856 7857 return $scope.CheckoutProcess(); 7858 }).catch(function (error) { 7859 var phone = $scope.InitInfo.webInventoryLocationPhone || $scope.InitInfo.Store.phone; 7860 var suggestion = 'Please try your order again later or give us a call at ' + phone + ' to order by phone.'; 7861 7862 $scope.setProcessingCheckout(false); 7863 $scope.setErrorMessageBottom(error.message + ' ' + suggestion); 7864
7865 $scope.safeApply(); 7866 7867 return Promise.reject(error); 7868 }); 7869 } 7870 } 7871 7872 return processCheckoutPayment(); 7873 }; 7874 7875 $scope.makeTaxInclusive = function () { 7876 if ($scope.showAsGST()) { 7877 if ($scope.isShippingCountryGST()) { 7878 $scope.Taxes = $scope.GST_Taxes; 7879 $scope.Due = $scope.GST_Due; 7880 7881 if ($scope.CartData && $scope.CartData.Items && $scope.CartData.Items.length) { 7882 for (var i = 0; i < $scope.CartData.Items.length; i++) { 7883 var item = $scope.CartData.Items[i]; 7884 $scope.CartData.Items[i].tax = fixFloat(item.gst_tax) * fixFloat(item.qty); 7885 } 7886 } 7887 } else { 7888 $scope.Taxes = 0; 7889 $scope.ShippingTaxes = 0; 7890 $scope.taxable_sales = 0; 7891 if ($scope.CartData && $scope.CartData.Items && $scope.CartData.Items.length) { 7892 for (var i = 0; i < $scope.CartData.Items.length; i++) { 7893 var item = $scope.CartData.Items[i]; 7894 $scope.CartData.Items[i].tax = 0; 7895 $scope.CartData.Items[i].taxable = 0; 7896 $scope.CartData.Items[i].total = $scope.CartData.Items[i].discounted_total = (fixFloat(item.price) * fixFloat(item.qty)) - fixFloat(item.discount); 7897 } 7898 } 7899 } 7900 } 7901 }; 7902 7903 $scope.getPreProcessData = function () { 7904 var shippingDiscount = $scope.getShippingDiscount(); 7905 var shippingInfo = $scope.getEffectiveShippingInfo(); 7906 7907 // Adapter 7908 if ($scope.isScheduledPayment()) { 7909 var data = { 7910 type: 'scheduled_payment', 7911 spid: $scope.scheduledPaymentId, 7912 token: $scope.paymentToken, 7913 payment_info: $scope.InitInfo.payment_info, 7914 }; 7915 } else if ($scope.isSubscriptionPayment()) { 7916 var data = { 7917 type: 'subscription_payment', 7918 sid: $scope.subscriptionId, 7919 token: $scope.paymentToken, 7920 payment_info: $scope.InitInfo.payment_info, 7921 subscription_guid: $scope.paymentNonce, 7922 }; 7923 } else if ($scope.isInvoicePayment()) { 7924 var data = { 7925 type: 'invoice_payment', 7926 iid: $scope.invoiceId, 7927 token: $scope.paymentToken, 7928 payment_info: $scope.InitInfo.payment_info, 7929 }; 7930 } else if ($scope.isInvoicesPayment()) { 7931 var data = { 7932 type: 'invoices_payment', 7933 icid: $scope.invoicesCartId, 7934 token: $scope.paymentToken, 7935 payment_info: $scope.InitInfo.payment_info, 7936 }; 7937 } else if ($scope.isRtoPayment()) { 7938 var data = { 7939 type: 'rto_payment', 7940 rpid: $scope.rtoCartId, 7941 token: $scope.paymentToken, 7942 payment_info: $scope.InitInfo.payment_info, 7943 }; 7944 } else if ($scope.isAddCardOnly()) { 7945 var data = { 7946 type: 'add_card_only', 7947 cid: $scope.customerId, 7948 token: $scope.paymentToken, 7949 }; 7950 } else if ($scope.isMmsPayment()) { 7951 var data = { 7952 type: 'mms_payment', 7953 cid: $scope.customerId, 7954 token: $scope.paymentToken, 7955 payment_info: $scope.InitInfo.payment_info, 7956 }; 7957 } else { 7958 var data = { 7959 sub_total: $scope.SubTotal, 7960 discount_amount: $scope.Discounts, 7961 shipping_amount: ($scope.ShippingInfo.Option ? $scope.ShippingInfo.Option.price : 0), 7962 shipping_discount: shippingDiscount, 7963 shipping_tax_amount: $scope.ShippingTaxes, 7964 tax_amount: $scope.Taxes, 7965 tax_jurisdictions: $scope.JurisdictionalTax, 7966 shipping_tax_jurisdictions: JSON.stringify($scope.shipping_tax_jurisdictions), 7967 taxable_sales: $scope.taxable_sales, 7968 non_taxable_sales: $scope.non_taxable_sales, 7969 non_sales: $scope.non_sales, 7970 cart_total: $scope.CartTotal, 7971 gc_amount: $scope.GCTotal, 7972 cc_amount: $scope.getAmountDue(), 7973 billing_same_as_shipping: $scope.BillingInfo.AddressSameAsShipping, 7974 billing_first_name: $scope.BillingInfo.FirstName, 7975 billing_last_name: $scope.BillingInfo.LastName, 7976 billing_address1: $scope.BillingInfo.Address1, 7977 billing_address2: $scope.BillingInfo.Address2, 7978 billing_city: $scope.BillingInfo.City, 7979 billing_state: $scope.BillingInfo.State, 7980 billing_postal: $scope.BillingInfo.Zip, 7981 billing_country: $scope.BillingInfo.Country, 7982 billing_email: $scope.BillingInfo.Email, 7983 billing_phone: $scope.BillingInfo.Phone, 7984 shipping_first_name: shippingInfo.FirstName, 7985 shipping_last_name: shippingInfo.LastName, 7986 shipping_company: shippingInfo.company_name, 7987 shipping_address1: shippingInfo.Address1, 7988 shipping_address2: shippingInfo.Address2, 7989 shipping_city: shippingInfo.City, 7990 shipping_state: shippingInfo.State, 7991 shipping_postal: shippingInfo.Zip, 7992 shipping_country: shippingInfo.Country, 7993 shipping_id: $scope.ShippingInfo.Option ? $scope.ShippingInfo.Option.id : 0, 7994 shipping_service_id: $scope.ShippingInfo.Option ? $scope.ShippingInfo.Option.service_id : 0, 7995 shipping_package_id: $scope.ShippingInfo.Option ? $scope.ShippingInfo.Option.package_id : 0, 7996 shipping_option_name: $scope.ShippingInfo.Option ? $scope.ShippingInfo.Option.name : '', 7997 processor: ($scope.isCardOnFileNeeded() || $scope.Due) ? $scope.StatusInfo.Processor : 'nocc', 7998 gift_message: $scope.GiftMessage.Message, 7999 gift_enabled: $scope.GiftMessage.Checked, 8000 order_notes: $scope.orderNotes.note, 8001 join_email_list: ($scope.ContactInfo.JoinEmailList == true),
8002 terms_agree: $scope.BillingInfo.TermsAgree, 8003 items: $scope.CartData.Items, 8004 coupons: $scope.CartData.Coupons, 8005 gift_cards: $scope.isPayAtPickup() ? [] : $scope.CartData.GiftCards, 8006 send_text_notifications: $scope.CheckoutInfo.sendTextNotifications, 8007 subscribeToNewsletter: $scope.subscribeToNewsletter, 8008 subscribeToMarketingEmails: $scope.subscribeToMarketingEmails, 8009 }; 8010 8011 if ($scope.ShippingInfo?.Option?.name === 'School Delivery') { 8012 var osrItem = $scope.getOsrItem(); 8013 8014 if (osrItem && osrItem.osrData?.address) { 8015 data.shipping_address1 = osrItem.osrData.address.street1 || null; 8016 data.shipping_address2 = osrItem.osrData.address.street2 || null; 8017 data.shipping_city = osrItem.osrData.address.city || null; 8018 data.shipping_state = osrItem.osrData.address.state || null; 8019 data.shipping_postal = osrItem.osrData.address.postalCode || null; 8020 data.shipping_country = $scope.InitInfo.Countries?.[osrItem.osrData.address.country] 8021 || osrItem.osrData.address.country 8022 || null; 8023 } 8024 } 8025 } 8026 8027 if ($scope.isFlagInsuranceEnabled()) { 8028 data.isTransactionEligibleForJmInsurance = $scope.isProductInCart(); 8029 data.contactJMInsuranceInfo = $scope.contactJMInsuranceInfo; 8030 data.jMInsuranceChecked = $scope.isInsuranceSwitchedOn || false; 8031 } 8032 8033 if ($scope.isFlagLuxsuranceEnabled()) { 8034 data.contactJMInsuranceInfo = $scope.contactJMInsuranceInfo; 8035 data.isLuxsuranceSwitchedOn = $scope.isLuxsuranceSwitchedOn || false; 8036 } 8037 8038 if ($scope.isFree()) { 8039 data.processor = $scope.PROCESSOR_FREE; 8040 } 8041 8042 if ($scope.knobby && $scope.knobby.module_tax_profiles) { 8043 data.pos_tax_profile_id = $scope.getCurrentTaxProfileId(); 8044 } 8045 8046 if ($scope.checkFlagRetailDeliveryFee()) { 8047 data.retail_delivery_fee = $scope.retailDeliveryFee; 8048 data.retail_delivery_fee_avalara_tax_jurisdiction = $scope.retailDeliveryFeeTaxJurisdiction; 8049 } 8050 8051 return data; 8052 }; 8053 8054 $scope.PreProcess = function (data) { 8055 if (typeof data === 'undefined') { 8056 data = $scope.getPreProcessData(); 8057 } 8058 8059 var noCart = false; 8060 if ($scope.isInvoicePayment()) { 8061 noCart = true; 8062 } 8063 8064 return $http.post($scope.BuildUrl('/checkout/preprocess', noCart), data, {withCredentials: true}).then(function (result) { 8065 if (result.data.error) { 8066 $scope.CartDataGet().then(function () { 8067 $scope.setProcessingCheckout(false); 8068 $scope.ShippingOptionsGet(); 8069 }); 8070 if (result.data.error_type && result.data.error_type.indexOf('coupons_consumed') > -1) { 8071 if ($scope.isVantivHosted() || $scope.isRainProcessor() || $scope.isQuiltAdyen()) { 8072 $scope.giftCardCouponMsg = result.data.error; 8073 } else { 8074 alert(result.data.error); 8075 } 8076 angular.forEach(result.data.coupons_id_consumed, function (id){ 8077 $scope.removeCoupon(id); 8078 }); 8079 } else { 8080 $scope.StatusInfo.ErrorMessage = result.data.error; 8081 } 8082 8083 if (result.data.error_type && result.data.error_type.indexOf('coupons_expired') > -1) { 8084 $scope.giftCardCouponMsg = result.data.error; 8085 angular.forEach(result.data.coupons_id_expired, function(id) { 8086 $scope.removeCoupon(id); 8087 }); 8088 } 8089 8090 if (result.data.error_type && result.data.error_type.indexOf('coupons_deleted') > -1) { 8091 $scope.giftCardCouponMsg = result.data.error; 8092 angular.forEach(result.data.coupons_id_deleted, function(id) { 8093 $scope.removeCoupon(id); 8094 }); 8095 } 8096 8097 if (result.data.error_type && result.data.error_type == 'inventory') { 8098 $scope.InventoryMessages = result.data.message_array; 8099 $scope.StatusInfo.ErrorMessage = null; // This error is handled/displayed separately 8100 } 8101 8102 if (result?.data?.error_type === 'store_available') { 8103 $scope.AvailabilityMessages = result.data.message_array; 8104 $scope.StatusInfo.ErrorMessage = null; 8105 } 8106 8107 if ($scope.checkFlagBlockCheckoutOnDynamicTaxFailure() && result?.data?.error_type === 'dynamic_tax_error') { 8108 $scope.StatusInfo.ErrorMessage = null; 8109 $scope.StatusInfo.DynamicTaxError = result.data.error; 8110 $scope.dynamicTaxesReady = false;
8111 } 8112 8113 return 0; 8114 } else { 8115 $scope.TransactionId = result.data.transaction_id; 8116 return result.data.transaction_id; 8117 } 8118 }); 8119 }; 8120 8121 $scope.getPostProcessData = function () { 8122 var data = { 8123 processor: 'nocc' 8124 }; 8125 8126 if ($scope.isWholesaleNoCCEnabled()) { 8127 data.processor = 'wholesaleNoCC'; 8128 } else if ($scope.isFree()) { 8129 data.processor = $scope.PROCESSOR_FREE; 8130 } else if ($scope.isPayAtPickup()) { 8131 data.processor = $scope.PROCESSOR_PAY_AT_PICKUP; 8132 } else if ($scope.isRainProcessor()) { 8133 data.processor = $scope.PROCESSOR_RAIN; 8134 data.amount = $scope.getAmountDue(); 8135 } else if ($scope.isQuiltAdyen()) { 8136 data.processor = $scope.PROCESSOR_QUILT_ADYEN; 8137 data.amount = $scope.getAmountDue(); 8138 } 8139 8140 data.subscribeToMarketingEmails = $scope.subscribeToMarketingEmails; 8141 8142 if ($scope.checkFlagRetailDeliveryFee() && $scope.retailDeliveryFee > 0) { 8143 data.retail_delivery_fee = $scope.retailDeliveryFee; 8144 } 8145 8146 if ($scope.isScheduledPayment()) { 8147 data.type = 'scheduled_payment'; 8148 data.spid = $scope.scheduledPaymentId; 8149 data.token = $scope.paymentToken; 8150 data.enroll_autopay = $scope.isEnrollAutoPay() ? 1 : 0; 8151 data.payment_info = $scope.InitInfo.payment_info; 8152 } else if ($scope.isSubscriptionPayment()) { 8153 data.type = 'subscription_payment'; 8154 data.sid = $scope.subscriptionId; 8155 data.token = $scope.paymentToken; 8156 data.enroll_autopay = $scope.isEnrollAutoPay() ? 1 : 0; 8157 data.payment_info = $scope.InitInfo.payment_info; 8158 } else if ($scope.isInvoicePayment()) { 8159 data.type = 'invoice_payment'; 8160 data.iid = $scope.invoiceId; 8161 data.token = $scope.paymentToken; 8162 data.enroll_autopay = 0; 8163 data.add_card_on_file = $scope.BillingInfo.addCardOnFile ? 1 : 0; 8164 if (window.knobby && window.knobby['module_aim_add_card_on_file_for_invoice_payments']) { 8165 data.billing_email = $scope.BillingInfo.Email; 8166 data.billing_postal = $scope.BillingInfo.Zip; 8167 } 8168 data.payment_info = $scope.InitInfo.payment_info; 8169 } else if ($scope.isInvoicesPayment()) { 8170 data.type = 'invoices_payment'; 8171 data.icid = $scope.invoicesCartId; 8172 data.token = $scope.paymentToken; 8173 data.enroll_autopay = 0; 8174 data.add_card_on_file = $scope.BillingInfo.addCardOnFile ? 1 : 0; 8175 if (window.knobby && window.knobby['module_aim_add_card_on_file_for_invoice_payments']) { 8176 data.billing_email = $scope.BillingInfo.Email; 8177 data.billing_postal = $scope.BillingInfo.Zip; 8178 } 8179 data.payment_info = $scope.InitInfo.payment_info; 8180 } else if ($scope.isRtoPayment()) { 8181 data.type = 'rto_payment'; 8182 data.rpid = $scope.rtoCartId; 8183 data.token = $scope.paymentToken; 8184 data.payment_info = $scope.InitInfo.payment_info; 8185 } else if ($scope.isAddCardOnly()) { 8186 data.type = 'add_card_only'; 8187 data.cid = $scope.customerId; 8188 data.token = $scope.paymentToken; 8189 } else if ($scope.isMmsPayment()) { 8190 data.type = 'mms_payment'; 8191 data.cid = $scope.customerId; 8192 data.token = $scope.paymentToken; 8193 data.enroll_autopay = 0; 8194 data.payment_info = $scope.InitInfo.payment_info; 8195 } 8196 8197 if ($scope.checkFlagStripeSendLevel3Data() || $scope.checkFlagAdyenSendLevel3Data()) { 8198 data.level_3_transaction_id = $scope.TransactionId || null; 8199 data.level_3_cart_items = $scope.CartData.Items || []; 8200 } 8201 8202 return data; 8203 }; 8204 8205 $scope.PostProcess = function (data) { 8206 if (typeof data === 'undefined') { 8207 data = $scope.getPostProcessData(); 8208 } 8209 8210 if (typeof data === 'object') { 8211 const dynamicData = {}; 8212 8213 if (data.processor === $scope.PROCESSOR_RAIN) { 8214 dynamicData.gateway_transaction_id = $scope.Rain.intentId; 8215 dynamicData.payment_method_id = $scope.Rain.PaymentMethodId; 8216 } else if (data.processor === $scope.PROCESSOR_QUILT_ADYEN) { 8217 dynamicData.gateway_transaction_id = $scope.Adyen.authData.payment_id; 8218 // card on file 8219 if ($scope.Adyen.authData.payment_method_id) { 8220 dynamicData.payment_method_id = $scope.Adyen.authData.payment_method_id; 8221 } 8222 } else if (data.processor === $scope.PROCESSOR_FREE) { 8223 dynamicData.gateway_transaction_id = $scope.PROCESSOR_FREE; 8224 } 8225 8226 if (data.processor === $scope.PROCESSOR_QUILT_ADYEN && ['add_card_only', 'mms_payment'].includes(data.type)) { 8227 // Adyen can update customerId on SubmitInfo step 8228 dynamicData.cid = $scope.customerId; 8229 } 8230 8231 data = Object.assign({}, data, dynamicData); 8232 } 8233 8234 var noCart = false;
8235 if ($scope.isInvoicePayment()) { 8236 noCart = true; 8237 } 8238 8239 if ($scope.isSavedCardsFlagEnabled() && $scope.CardInfo.nickname) { 8240 data.nickname = $scope.CardInfo.nickname; 8241 } 8242 8243 $scope.setProcessing(true); 8244 8245 return $http.post($scope.BuildUrl('/checkout/postprocess', noCart), data, {withCredentials: true}).then(function (result) { 8246 if (result.data.error) { 8247 $scope.StatusInfo.ErrorMessage = result.data.error; 8248 $scope.setProcessing(false); 8249 } else if (result.data.redirect_url) { 8250 $window.location.href = result.data.redirect_url; 8251 } 8252 }); 8253 }; 8254 8255 $scope.GoToCheckoutUrl = function () { 8256 var wsurl = $scope.WsUrl.replace("https://", "").replace("http://", ""); 8257 var goto = $scope.InitInfo.CheckoutUrl + "?id=" + $scope.CartId + "&ws=" + wsurl; 8258 if ($scope.showAsGST()) { 8259 $scope.Taxes = 0; 8260 $scope.ShippingTaxes = 0; 8261 } 8262 8263 $scope.trackEvent(); 8264 $window.location.href = goto; 8265 }; 8266 8267 $scope.ProcessItems = function () { 8268 8269 if ($scope.showAsGST() && !$scope.isShippingCountryGST()) { 8270 8271 $scope.SubTotal += ($scope.Taxes + $scope.ShippingTaxes); 8272 $scope.Taxes = 0; 8273 $scope.ShippingTaxes = 0; 8274 8275 for (var i = 0; i < $scope.CartData.Items.length; i++) { 8276 8277 $scope.CartData.Items[i].price = $scope.getItemGstPrice($scope.CartData.Items[i]); 8278 } 8279 } 8280 }; 8281 8282 $scope.getSubmitInfoGetData = function (paymentMethod, paymentMethodTypes) { 8283 if (typeof paymentMethod === 'undefined') { 8284 paymentMethod = null; 8285 } 8286 if (typeof paymentMethodTypes === 'undefined') { 8287 paymentMethodTypes = null; 8288 } 8289 8290 var data = null; 8291 var shippingInfo = $scope.getEffectiveShippingInfo(); 8292 8293 if ($scope.StatusInfo.Processor == 'NMI3S') { 8294 data = { 8295 processor: $scope.StatusInfo.Processor, 8296 cart_amount: $scope.getAmountDue(), 8297 shipping_amount: $scope.getShippingWithDiscount(), 8298 tax_amount: $scope.Taxes, 8299 shipping_tax_amount: $scope.ShippingTaxes, 8300 'shipping-first-name': shippingInfo.FirstName, 8301 'shipping-last-name': shippingInfo.LastName, 8302 'shipping-address1': shippingInfo.Address1, 8303 'shipping-address2': shippingInfo.Address2, 8304 'shipping-city': shippingInfo.City, 8305 'shipping-state': shippingInfo.State, 8306 'shipping-postal': shippingInfo.Zip 8307 }; 8308 } else if ($scope.isPayPal()) { 8309 data = { 8310 processor: $scope.StatusInfo.Processor 8311 }; 8312 } else if ($scope.isVantivHosted()) { 8313 data = { 8314 processor: $scope.StatusInfo.Processor, 8315 cart_amount: $scope.Due, 8316 shipping_amount: $scope.getShippingWithDiscount(), 8317 tax_amount: $scope.Taxes, 8318 'billing-email': $scope.BillingInfo.Email, 8319 'billing-phone': $scope.BillingInfo.Phone, 8320 'shipping-first-name': shippingInfo.FirstName, 8321 'shipping-last-name': shippingInfo.LastName, 8322 'shipping-address1': shippingInfo.Address1, 8323 'shipping-address2': shippingInfo.Address2, 8324 'shipping-city': shippingInfo.City, 8325 'shipping-state': shippingInfo.State, 8326 'shipping-postal': shippingInfo.Zip, 8327 'billing-first-name': $scope.BillingInfo.FirstName, 8328 'billing-last-name': $scope.BillingInfo.LastName, 8329 'billing-address1': $scope.BillingInfo.Address1, 8330 'billing-address2': $scope.BillingInfo.Address2, 8331 'billing-city': $scope.BillingInfo.City, 8332 'billing-state': $scope.BillingInfo.State, 8333 'billing-postal': $scope.BillingInfo.Zip 8334 }; 8335 } else if ($scope.isRainProcessor()) { 8336 data = { 8337 processor: $scope.StatusInfo.Processor, 8338 cart_amount: $scope.getAmountDue(), 8339 save_card: $scope.isCardOnFileNeeded(), 8340 customer_email: $scope.BillingInfo.Email, 8341 subscription_only: $scope.isSubscriptionOnly(), 8342 spid: $scope.scheduledPaymentId, 8343 sid: $scope.subscriptionId, 8344 iid: $scope.invoiceId, 8345 icid: $scope.invoicesCartId, 8346 rpid: $scope.rtoCartId, 8347 token: $scope.paymentNonce, 8348 mms_payment: $scope.isMmsPayment(), 8349 }; 8350 8351 if (paymentMethod) { 8352 data.payment_method = paymentMethod; 8353 } 8354 if (paymentMethodTypes && paymentMethodTypes.length) { 8355 data.payment_method_types = paymentMethodTypes; 8356 } 8357 }
8357 else if ($scope.isQuiltAdyen()) { 8358 if (!$scope.checkFlagAvsEcommerce()) { 8359 data = { 8360 processor: $scope.StatusInfo.Processor, 8361 cart_amount: $scope.getAmountDue(), 8362 transaction_id: $scope.TransactionId, 8363 subscription_payment: $scope.isSubscriptionPayment(), 8364 scheduled_payment: $scope.isScheduledPayment(), 8365 invoice_payment: $scope.isInvoicePayment(), 8366 invoices_payment: $scope.isInvoicesPayment(), 8367 rto_payment: $scope.isRtoPayment(), 8368 mms_payment: $scope.isMmsPayment(), 8369 save_card: $scope.isCardOnFileNeeded(), 8370 spid: $scope.scheduledPaymentId, 8371 sid: $scope.subscriptionId, 8372 iid: $scope.invoiceId, 8373 icid: $scope.invoicesCartId, 8374 rpid: $scope.rtoCartId, 8375 token: $scope.paymentNonce, 8376 'billing-email': $scope.BillingInfo.Email, 8377 'billing-phone': $scope.BillingInfo.Phone, 8378 'shipping-first-name': shippingInfo.FirstName, 8379 'shipping-last-name': shippingInfo.LastName, 8380 'shipping-address1': shippingInfo.Address1, 8381 'shipping-address2': shippingInfo.Address2, 8382 'shipping-city': shippingInfo.City, 8383 'shipping-state': shippingInfo.State, 8384 'shipping-postal': shippingInfo.Zip, 8385 'billing-first-name': $scope.BillingInfo.FirstName, 8386 'billing-last-name': $scope.BillingInfo.LastName, 8387 'billing-address1': $scope.BillingInfo.Address1, 8388 'billing-address2': $scope.BillingInfo.Address2, 8389 'billing-city': $scope.BillingInfo.City, 8390 'billing-state': $scope.BillingInfo.State, 8391 'billing-postal': $scope.BillingInfo.Zip, 8392 }; 8393 } else { 8394 data = { 8395 processor: $scope.StatusInfo.Processor, 8396 cart_amount: $scope.getAmountDue(), 8397 transaction_id: $scope.TransactionId, 8398 subscription_payment: $scope.isSubscriptionPayment(), 8399 scheduled_payment: $scope.isScheduledPayment(), 8400 invoice_payment: $scope.isInvoicePayment(), 8401 invoices_payment: $scope.isInvoicesPayment(), 8402 rto_payment: $scope.isRtoPayment(), 8403 mms_payment: $scope.isMmsPayment(), 8404 save_card: $scope.isCardOnFileNeeded(), 8405 spid: $scope.scheduledPaymentId, 8406 sid: $scope.subscriptionId, 8407 iid: $scope.invoiceId, 8408 icid: $scope.invoicesCartId, 8409 rpid: $scope.rtoCartId, 8410 token: $scope.paymentNonce, 8411 }; 8412 8413 if ($scope.isSavedCardsFlagEnabled()) { 8414 const card = $scope.getCard(); 8415 8416 if (card?.gateway_token) { 8417 data.payment_method_id = card.gateway_token; 8418 } 8419 } 8420 8421 if ($scope.InitInfo.user_ip_address || null) { 8422 data.client_ip_address = $scope.InitInfo.user_ip_address; 8423 } 8424 8425 data.billing_address = $scope.quiltAdyenPrepareBillingAddress(); 8426 data.shipping_address = $scope.quiltAdyenPrepareShippingAddress(); 8427 data.shopper_datails = $scope.quiltAdyenPrepareShopperDetails(); 8428 } 8429 8430 if ($scope.customerId) { 8431 data.customer_id = $scope.customerId; 8432 } 8433 8434 if ($scope.checkFlagAdyenSendLevel3DataAuthCapture()) { 8435 data.level_3_transaction_id = $scope.TransactionId || null; 8436 data.level_3_cart_items = $scope.CartData.Items || []; //This data cannot be passed to adyen. Destroy on backend 8437 } 8438 8439 if ($scope.isScheduledPayment()) { 8440 data.type = 'scheduled_payment'; 8441 } else if ($scope.isSubscriptionPayment()) { 8442 data.type = 'subscription_payment'; 8443 } else if ($scope.isInvoicePayment()) { 8444 data.type = 'invoice_payment'; 8445 } else if ($scope.isInvoicesPayment()) { 8446 data.type = 'invoices_payment'; 8447 } else if ($scope.isRtoPayment()) { 8448 data.type = 'rto_payment'; 8449 } else if ($scope.isAddCardOnly()) { 8450 data.type = 'add_card_only'; 8451 } else if ($scope.isMmsPayment()) { 8452 data.type = 'mms_payment'; 8453 } 8454 } 8455 8456 return data; 8457 }; 8458 8459 $scope.SubmitInfoGet = function (data) { 8460 if (typeof data === 'undefined') { 8461 data = $scope.getSubmitInfoGetData(); 8462 } 8463 8464 $scope.SubmitInfo = null; 8465 8466 if (!data) { 8467 // Should not ever get here, but we did somehow 8468 $scope.StatusInfo.ErrorMessage = "(CH104) Unable to checkout, try refreshing your browser. If that does not help, contact customer service at " + $scope.getStoreCustomerServicePhone(); 8469
8470 return $scope.NoOpPromise(); 8471 } 8472 8473 if (typeof data === 'object') { 8474 const dynamicData = {}; 8475 8476 if (data.processor === $scope.PROCESSOR_QUILT_ADYEN) { 8477 data.transaction_id = $scope.TransactionId; 8478 8479 if ($scope.checkFlagAdyenSendLevel3DataAuthCapture()) { 8480 data.level_3_transaction_id = $scope.TransactionId || null; 8481 } 8482 } 8483 8484 data = Object.assign({}, data, dynamicData); 8485 } 8486 8487 var noCart = false; 8488 if ($scope.invoiceId) { 8489 noCart = true; 8490 } 8491 8492 var requestQuery = 'u=' + $scope.getStoreUuid(); 8493 requestQuery += '&isMobileBrowser=' + $scope.isMobileBrowser; 8494 8495 if ($scope.isSavedCardsFlagEnabled() && $scope.customerId && $scope.formData?.selectedCustomerCardId) { 8496 const card = $scope.getCard(); 8497 8498 data.customer_id = $scope.customerId; 8499 data.customer_vault_id = data.external_id = card.external_id; 8500 data.payment_method = card.gateway_token; 8501 } 8502 8503 return $http.post($scope.BuildUrl('/checkout/submitinfo', noCart, requestQuery), data, {withCredentials: true}).then(function ($result) { 8504 if ($result.data.error) { 8505 if ($result.data.error.error_type == 'captcha_expired') { 8506 $scope.showCaptcha = true; 8507 return $scope.checkCaptcha(); 8508 } else { 8509 var errorMessage = $result.data.error.error_message; 8510 var errorType = $result.data.error.error_type; 8511 if ($result.data.error.error_type) { 8512 8513 if (errorType === 'VH Timeout') { 8514 $scope.setErrorMessageBottom("(VH 1500) There was a temporary error while setting up your payment. Please try again."); 8515 } else if (!errorMessage) { 8516 $scope.setErrorMessageBottom("(VTV 400) There is a configuration error and we cannot continue processing this order, please contact the store to get this resolved" + $scope.getStoreCustomerServicePhone()); 8517 } else { 8518 $scope.setErrorMessageBottom('(CH' + errorType + ') ' + errorMessage); 8519 } 8520 } else { 8521 if (errorMessage === 'Invalid AccountToken') { 8522 $scope.setErrorMessageBottom("(VTV 401) There is a configuration error and we cannot continue processing this order, please contact customer service " + $scope.getStoreCustomerServicePhone()); 8523 } else { 8524 $scope.setErrorMessageBottom("(CH103) " + errorMessage); 8525 } 8526 } 8527 $scope.setProcessingCheckout(false); 8528 } 8529 } else { 8530 $scope.SubmitInfo = $result.data.info; 8531 8532 if ($scope.isRainProcessor()) { 8533 // Through the Quilt Stripe payfac an already-paid intent (page reload after auth) carries no 8534 // client secret; the already_paid path posts the order without confirming a payment in the browser. 8535 var quiltAlreadyPaid = window.knobby && window.knobby.module_stripe_to_quilt_payfac 8536 && $result.data && $result.data.info && $result.data.info.already_paid; 8537 if ($result.data && $result.data.info && ($result.data.info.token || quiltAlreadyPaid)) { 8538 $scope.Rain.processorToken = $result.data.info.token; 8539 $scope.Rain.intentId = $result.data.info.intent_id; 8540 $scope.Rain.paymentAlreadyPaid = $result.data.info.already_paid; 8541 } else { 8542 $scope.StatusInfo.ErrorMessage = "(CH100) An unexpected error occurred, please try again."; 8543 } 8544 } else if ($scope.isQuiltAdyen()) { 8545 if ($result.data?.info?.customer_id && !$scope.customerId) { 8546 $scope.customerId = $result.data.info.customer_id; 8547 } 8548 if (window.knobby && window.knobby['module_web_order_coming_through_as_abandoned_cart']) { 8549 $scope.Adyen.paymentAlreadyPaid = ['requires_capture', 'succeeded'].includes($result.data?.info?.status); 8550 } else { 8551 $scope.Adyen.paymentAlreadyPaid = $result.data?.info?.status === 'requires_capture'; 8552 } 8553 if ($scope.Adyen.paymentAlreadyPaid) { 8554 $scope.Adyen.authData = $result.data.info; 8555 } 8556 } 8557 } 8558 }, function(error) { 8559 $scope.StatusInfo.ErrorMessage = "(CH101) An unexpected error occurred, please try again."; 8560 $scope.setProcessingCheckout(false); 8561 }); 8562 }; 8563 8564 $scope.getAuthPaymentData = function () { 8565 const isSavedCard = 8566 $scope.isSavedCardsFlagEnabled() && 8567 $scope.paymentMode === 'saved' && 8568 $scope.formData?.selectedCustomerCardId; 8569 8570 let params = { 8571 'processor': $scope.PROCESSOR_QUILT_ADYEN, 8572 'amount': $scope.getAmountDue(), 8573 'billing_address1': $scope.BillingInfo.Address1, 8574 'billing_address2': $scope.BillingInfo.Address2, 8575 'billing_city': $scope.BillingInfo.City, 8576 'billing_state': $scope.BillingInfo.State, 8577 'billing_postal': $scope.BillingInfo.Zip,
8578 'billing_country': $scope.BillingInfo.CountryIso2Key, 8579 }; 8580 8581 if (isSavedCard) { 8582 const card = $scope.getCard(); 8583 8584 if (card) { 8585 params.pos_customer_card_id = card.pos_customer_card_id; 8586 params.gateway_token = card.gateway_token; 8587 params.pos_customer_id = $scope.customerId; 8588 if (window.knobby && window.knobby['module_fix_external_customer_id_for_migrated_adyen_cards']) { 8589 params.payfac_customer_id = card.external_id; 8590 } 8591 } 8592 } else { 8593 const method = $scope.Adyen.state.data.paymentMethod; 8594 8595 params.enc_number = method.encryptedCardNumber; 8596 params.enc_month = method.encryptedExpiryMonth; 8597 params.enc_year = method.encryptedExpiryYear; 8598 params.enc_cvv = method.encryptedSecurityCode; 8599 8600 if ($scope.isSavedCardsFlagEnabled()) { 8601 params.nickname = $scope.CardInfo.nickname; 8602 params.pos_customer_id = $scope.customerId; 8603 } 8604 } 8605 8606 return params; 8607 }; 8608 8609 $scope.authPayment = function(data) { 8610 if ($scope.Adyen.paymentAlreadyPaid) { 8611 $scope.setProcessingCheckout(false); 8612 return Promise.resolve(); 8613 } 8614 8615 const isSavedCard = ( 8616 $scope.isSavedCardsFlagEnabled() && 8617 $scope.paymentMode === 'saved' && 8618 $scope.formData?.selectedCustomerCardId 8619 ); 8620 8621 if (!isSavedCard && !$scope.Adyen.state.data.paymentMethod) { 8622 $scope.setErrorMessageBottom("Payment invalid"); 8623 $scope.setProcessingCheckout(false); 8624 return Promise.resolve(); 8625 } 8626 if (!$scope.SubmitInfo || !$scope.SubmitInfo.payment_id) { 8627 $scope.setErrorMessageBottom("Payment ID missing"); 8628 $scope.setProcessingCheckout(false); 8629 return Promise.resolve(); 8630 } 8631 8632 if (typeof data === 'undefined') { 8633 data = $scope.getAuthPaymentData(); 8634 } 8635 8636 if (typeof data === 'object') { 8637 data = Object.assign({}, data, { 8638 'transaction_id': $scope.SubmitInfo.transaction_id || $scope.TransactionId, 8639 'payment_id': $scope.SubmitInfo.payment_id, 8640 }); 8641 } 8642 8643 return $http.post($scope.BuildUrl('/checkout/auth'), data, {withCredentials: true}).then(function(result) { 8644 if (result.data.error) { 8645 $scope.setErrorMessageBottom(result.data.error.message); 8646 $scope.Adyen.authData = null; 8647 } else { 8648 $scope.Adyen.authData = result.data; 8649 } 8650 }, function(result) { 8651 if ($scope.knobby && $scope.knobby.module_show_card_on_file_decline_reason) { 8652 $scope.setErrorMessageBottom('We were unable to process this payment. Please try again.'); 8653 $scope.Adyen.authData = null; 8654 $scope.setProcessingCheckout(false); 8655 } 8656 8657 throw result; 8658 }); 8659 }; 8660 8661 $scope.requireAddressInfo = function () { 8662 if ($scope.CartStatus.Valid) { 8663 if ($scope.isVantivHosted() || $scope.isRainProcessor() || $scope.isQuiltAdyen()) { 8664 $scope.BillingAddress.Required = true; 8665 return true 8666 } else if (!$scope.requirePayment() || $scope.StatusInfo.Processor === 'gc') { 8667 return true; 8668 } 8669 } 8670 return false; 8671 }; 8672 8673 $scope.ProcessorSubmit = function () { 8674 $('#processorform').submit(); 8675 }; 8676 8677 /** @return {array|null} */ 8678 $scope.SubmitFields = function () { 8679 if ($scope.CartData) { 8680 if ($scope.StatusInfo.Processor == 'NMI3S') { 8681 return $scope.NMIFormFields(); 8682 } else if ($scope.isPayPal()) { 8683 return $scope.PayPalFields(); 8684 } 8685 } 8686 return null; 8687 }; 8688 8689 $scope.NMIFormFields = function () { 8690 var shippingInfo = $scope.getEffectiveShippingInfo(); 8691 8692 return { 8693 cart_amount: $scope.Due, 8694 shipping_amount: $scope.getShippingWithDiscount(), 8695 tax_amount: $scope.Taxes, 8696 'billing-cc-number': $scope.CardInfo.CCValid, 8697 'billing-cc-exp': $scope.CardInfo.ExpFormated, 8698 'billing-cvv': $scope.CardInfo.CVV, 8699 'billing-first-name': $scope.BillingInfo.FirstName, 8700 'billing-last-name': $scope.BillingInfo.LastName, 8701 'billing-address1': $scope.BillingInfo.Address1, 8702 'billing-address2': $scope.BillingInfo.Address2, 8703 'billing-city': $scope.BillingInfo.City, 8704 'billing-state': $scope.BillingInfo.State, 8705 'billing-postal': $scope.BillingInfo.Zip, 8706 'billing-country': $scope.BillingInfo.Country, 8707 'billing-email': $scope.BillingInfo.Email, 8708 'billing-phone': $scope.BillingInfo.Phone, 8709 'shipping-first-name': shippingInfo.FirstName, 8710 'shipping-last-name': shippingInfo.LastName, 8711 'shipping-address1': shippingInfo.Address1, 8712 'shipping-address2': shippingInfo.Address2, 8713 'shipping-city': shippingInfo.City, 8714 'shipping-state': shippingInfo.State, 8715 'shipping-postal': shippingInfo.Zip, 8716 'shipping-country': shippingInfo.Country, 8717 'shipping-email': $scope.ContactInfo.Email, 8718 'shipping-phone': $scope.ContactInfo.Phone 8719 }; 8720 }; 8721 8722 $scope.PayPalFields = function () { 8723 var gst_store_but_not_gst_buyer = ($scope.showAsGST() && !$scope.isShippingCountryGST()); 8724 8725 //TODO clean up bug##168771561 2019-10-01 8726 var totalDiscount = $scope.RoundTo2Dec($scope.GCTotal + $scope.Discounts); 8727 //We will change HTML form only in a case when discount amount >
8727 subtotal. 8728 //Because Paypal does not support this case 8729 if (totalDiscount >= $scope.SubTotal) { 8730 var fields = { 8731 cmd: '_cart', 8732 paymentaction: $scope.SubmitInfo ? $scope.SubmitInfo.payment_action : null, 8733 business: $scope.SubmitInfo ? $scope.SubmitInfo.business : null, 8734 notify_url: $scope.SubmitInfo ? $scope.SubmitInfo.notify_url : null, 8735 shopping_url: $scope.SubmitInfo ? $scope.SubmitInfo.shopping_url : null, 8736 invoice: $scope.SubmitInfo ? $scope.SubmitInfo.cart_id : null, 8737 weight_unit: $scope.InitInfo.WeightUnit ? $scope.InitInfo.WeightUnit : 'lbs', 8738 currency_code: $scope.InitInfo.Currency ? $scope.InitInfo.Currency : 'USD', 8739 upload: 1, 8740 bn: 'Rain_SP' 8741 }; 8742 } else { 8743 var fields = { 8744 cmd: '_cart', 8745 paymentaction: $scope.SubmitInfo ? $scope.SubmitInfo.payment_action : null, 8746 business: $scope.SubmitInfo ? $scope.SubmitInfo.business : null, 8747 notify_url: $scope.SubmitInfo ? $scope.SubmitInfo.notify_url : null, 8748 shopping_url: $scope.SubmitInfo ? $scope.SubmitInfo.shopping_url : null, 8749 invoice: $scope.SubmitInfo ? $scope.SubmitInfo.cart_id : null, 8750 weight_unit: $scope.InitInfo.WeightUnit ? $scope.InitInfo.WeightUnit : 'lbs', 8751 currency_code: $scope.InitInfo.Currency ? $scope.InitInfo.Currency : 'USD', 8752 upload: 1, 8753 discount_amount_cart: totalDiscount, 8754 bn: 'Rain_SP' 8755 }; 8756 8757 var tax = $scope.Taxes ? $scope.Taxes : 0; 8758 if ($scope.ShippingTaxes) { 8759 tax += $scope.ShippingTaxes; 8760 } 8761 8762 if (gst_store_but_not_gst_buyer) { 8763 8764 tax = 0; 8765 } else if ($scope.showAsGST()) { 8766 8767 tax = $scope.getGstTaxes(); 8768 } 8769 8770 fields['tax_cart'] = $scope.RoundTo2Dec(tax); 8771 } 8772 8773 fields['return'] = $scope.SubmitInfo ? $scope.SubmitInfo['return'] : null; 8774 8775 if ($scope.CartData.Items && $scope.CartData.Items.length) { 8776 //We will change HTML form only in a case when discount amount > subtotal. 8777 //Because Paypal does not support this case 8778 if (totalDiscount >= $scope.SubTotal) { 8779 var price = 0; 8780 var productsNames = []; 8781 $scope.CartData.Items.forEach(function(item) { 8782 productsNames.push(item.title); 8783 }); 8784 8785 fields['amount_1'] = $scope.CartTotal; 8786 fields['shipping_1'] = 0; //We included shipping amount in the $scope.CartTotal; 8787 fields['item_name_1'] = productsNames.join(';'); 8788 } else { 8789 var correctorThreeDecimalPrice = 0; 8790 8791 for (var i = 1; i <= $scope.CartData.Items.length; i++) { 8792 var item = $scope.CartData.Items[i - 1]; 8793 8794 // PayPal cannot handle fractional quantity, so on fractional quantity we always send them a quantity of 1 8795 var quantity = item.qty; 8796 var title = item.title; 8797 var price = $scope.getItemPriceForWeb(item); 8798 8799 if (gst_store_but_not_gst_buyer) { 8800 8801 price = $scope.getItemGstPrice(item); 8802 } 8803 8804 if (item.qty_type == 'fraction' && item.qty_remainder > 0) { 8805 if (gst_store_but_not_gst_buyer) { 8806 8807 price = item.qty * $scope.getItemGstPrice(item); 8808 } else { 8809 price = item.qty * $scope.getItemPriceForWeb(item); 8810 } 8811 quantity = 1; 8812 title += ' (' + item.qty + ' ' + item.measure_unit + 's)'; 8813 } 8814 8815 if (quantity > 1) { 8816 var differenceBetweenAmountAfterMultiply = $scope.RoundTo2Dec( 8817 fractionalMultiplication($scope.RoundTo2Dec(price), quantity) - $scope.RoundTo2Dec(fractionalMultiplication(price, quantity)) 8818 ); 8819 8820 if (differenceBetweenAmountAfterMultiply) { 8821 correctorThreeDecimalPrice += differenceBetweenAmountAfterMultiply; 8822 } 8823 } 8824 8825 fields['amount_' + i] = $scope.RoundTo2Dec(price); 8826 fields['item_name_' + i] = title; 8827 fields['quantity_' + i] = quantity; 8828 fields['item_number_' + i] = item.id; 8829 fields['weight_' + i] = item.weight; 8830 8831 if (!$scope.isShippingRequired()) { 8832 fields['shipping_' + i] = '0'; // If shipping is not required, we have to send an empty shipping field to paypal to prevent them from charging shipping <OldIPN> 8833 } else { 8834 // We add all shipping to the first item 8835 if (i == 1) { 8836 if ($scope.ShippingInfo.Option && (!$scope.InitInfo.PayPalShippingEnabled || $scope.ShippingInfo.Option.price > 0)) { 8837 fields['shipping_1'] = $scope.RoundTo2Dec($scope.ShippingInfo.Option.price - $scope.ShippingDiscount); 8838 } 8839 } else if (!$scope.InitInfo.PayPalShippingEnabled) { 8840 fields['shipping_' + i] = '0'; 8841 } 8842 } 8843 } 8844 if (correctorThreeDecimalPrice) { 8845 //since our system supports three decimal prices and PayPal does not, we do this calculation. In order to adjust the price which we send to PayPal 8846 fields.discount_amount_cart = fields.discount_amount_cart ? 8847 fields.discount_amount_cart + correctorThreeDecimalPrice : 8848 correctorThreeDecimalPrice; 8849 } 8850 } 8851 } 8852 8853 if ($scope.isExtendPayPalShippingInfo()) { 8854 fields["no_shipping"] = 0; 8855 fields["address_override"] = 1; 8856 8857 if ($scope.isPickup()) { 8858 if ($scope.ShippingInfo.Option.address) { 8859 var address = $scope.ShippingInfo.Option.address; 8860
8861 fields["country"] = $scope.CountryIso2KeyGet($scope.InitInfo.Countries[address.country]); 8862 fields["city"] = address.city; 8863 fields["state"] = address.state; 8864 fields["zip"] = address.postalCode; 8865 fields["address1"] = address.street1; 8866 if (address.street2) { 8867 fields["address2"] = address.street2 || ''; 8868 } 8869 } 8870 } else { 8871 fields["first_name"] = $scope.ShippingInfo.FirstName; 8872 fields["last_name"] = $scope.ShippingInfo.LastName; 8873 fields["country"] = $scope.ShippingInfo.CountryIso2Key; 8874 fields["city"] = $scope.ShippingInfo.City; 8875 fields["state"] = $scope.ShippingInfo.State; 8876 fields["zip"] = $scope.ShippingInfo.Zip; 8877 fields["address1"] = $scope.ShippingInfo.Address1; 8878 if ($scope.ShippingInfo.Address2) { 8879 fields["address2"] = $scope.ShippingInfo.Address2 || ''; 8880 } 8881 } 8882 } 8883 8884 return fields; 8885 }; 8886 8887 // endregion 8888 8889 // ****************************************************************************************** 8890 // region Taxes 8891 // ****************************************************************************************** 8892 8893 $scope.getTaxBitmaps = function () { 8894 if ($scope.InitInfo && $scope.InitInfo.tax_bitmaps) { 8895 return $scope.InitInfo.tax_bitmaps; 8896 } 8897 return {}; 8898 }; 8899 8900 $scope.getProductTaxBitmap = function () { 8901 var bitmaps = $scope.getTaxBitmaps(); 8902 if (bitmaps.product >= 0) { 8903 return +bitmaps.product; 8904 } 8905 return null; 8906 }; 8907 8908 $scope.getClassTaxBitmap = function () { 8909 var bitmaps = $scope.getTaxBitmaps(); 8910 if (bitmaps.class >= 0) { 8911 return +bitmaps.class; 8912 } 8913 return null; 8914 }; 8915 8916 $scope.getServiceTaxBitmap = function () { 8917 var bitmaps = $scope.getTaxBitmaps(); 8918 if (bitmaps.service >= 0) { 8919 return +bitmaps.service; 8920 } 8921 return null; 8922 }; 8923 8924 // endregion 8925 8926 // ****************************************************************************************** 8927 // region Items 8928 // ****************************************************************************************** 8929 8930 $scope.getItemsWithPackageItems = function () { 8931 var items = []; 8932 if ($scope.CartData && $scope.CartData.Items) { 8933 $scope.CartData.Items.forEach(function (i) { 8934 items.push(i); 8935 if (i.package_items && Array.isArray(i.package_items)) { 8936 i.package_items.forEach(function (pi) { 8937 pi.qty = i.qty; 8938 pi.total = pi.qty * pi.price; 8939 items.push(pi); 8940 }) 8941 } 8942 }); 8943 } 8944 return items; 8945 }; 8946 8947 $scope.getItemPrice = function (item) { 8948 var price = +item.price; 8949 8950 if (item.type === 'class') { 8951 return price; 8952 } 8953 8954 if (item.package_items && item.package_items.length) { 8955 item.package_items.forEach(function (i) { 8956 price += i.price; 8957 }) 8958 } 8959 8960 return price; 8961 }; 8962 8963 $scope.getItemPriceForWeb = function (item) { 8964 var price = +item.price; 8965 if (item.package_items && item.package_items.length) { 8966 item.package_items.forEach(function (i) { 8967 price += i.price; 8968 }) 8969 } 8970 8971 return price; 8972 }; 8973 8974 $scope.getItemGstPrice = function (item) { 8975 var gst_price = +item.gst_price; 8976 8977 if (item.package_items && item.package_items.length) { 8978 item.package_items.forEach(function (i) { 8979 gst_price += i.gst_price; 8980 }) 8981 } 8982 8983 return gst_price; 8984 }; 8985 8986 $scope.getItemTotal = function (item) { 8987 return fractionalMultiplication(item.qty, $scope.getItemPrice(item)); 8988 }; 8989 8990 $scope.getItemTotalForWeb = function (item) { 8991 return fractionalMultiplication(item.qty, $scope.getItemPriceForWeb(item)); 8992 }; 8993 8994 $scope.getItemGstTotal = function (item) { 8995 return fractionalMultiplication(item.qty, $scope.getItemGstPrice(item)); 8996 }; 8997 8998 $scope.getGstTaxes = function () { 8999 return (fixFloat($scope.GST_Taxes) + fixFloat($scope.ShippingTaxes)); 9000 }; 9001 9002 $scope.showAsGST = function () { 9003 return ($scope.isStoreGST() && !$scope.isWholesaleEnabled()); 9004 }; 9005 9006 $scope.breakDownTaxes = function () { 9007 return Object.keys($scope.JurisdictionalTaxes).length > 0 && 9008 ( 9009 /* Flag turned on OR it is Canadian store */ 9010 ($scope.knobby && ($scope.knobby['module_pos_show_tax_breakdown_on_receipt'] || $scope.knobby['pos_show_tax_breakdown_on_receipt'])) || $scope.useCanadaTax() 9011 ) 9012 ; 9013 }; 9014 9015 $scope.showTaxJurisdiction = function(jurisdiction, taxes, shipping) { 9016 if (!taxes.tax_paid) { 9017 return false;
9018 } 9019 var shippingTax = jurisdiction.indexOf('Shipping') >= 0; 9020 if (shippingTax != shipping) { 9021 return false; 9022 } 9023 return true; 9024 }; 9025 9026 $scope.showQuantityAsNumber = function (item) { 9027 return !item.quantity_updating && (item.class_attendees?.length > 0 || item.type === 'subscription'); 9028 }; 9029 9030 $scope.showQuantityAsSelector = function (item) { 9031 return !item.quantity_updating && item.type !== 'rental' && item.type !== 'subscription' && !(item.class_attendees?.length > 0); 9032 }; 9033 9034 $scope.showPaymentOptions = function() { 9035 return ($scope.isPayAtPickupEnabled() 9036 && ($scope.isPickup() || $scope.isDelivery()) 9037 && !$scope.isCardOnFileNeeded() 9038 ); 9039 }; 9040 9041 $scope.showPaymentSummary = function() { 9042 return (!$scope.isVantivHosted() 9043 && !$scope.isPayPal() 9044 && !$scope.isWholesaleNoCCEnabled() 9045 ); 9046 }; 9047 9048 $scope.showPaymentSection = function() { 9049 if ($scope.isWebPayment() || $scope.digitalOrder) { 9050 return true; 9051 } 9052 return $scope.BillingStatus.Editing; 9053 }; 9054 9055 $scope.showBillingSection = function() { 9056 if ($scope.isFullyPaidByGiftCard()) { 9057 return false; 9058 } 9059 9060 if ( 9061 $scope.isWebPayment() 9062 || ($scope.CartStatus.Valid && $scope.requireTermsWithShipping()) 9063 || $scope.digitalOrder 9064 ) { 9065 return true; 9066 } 9067 9068 return ($scope.CartStatus.Valid 9069 && $scope.ShippingStatus.Entered 9070 && !$scope.ShippingStatus.Editing 9071 ); 9072 }; 9073 9074 $scope.showBillingForm = function() { 9075 return ($scope.showBillingSection() && $scope.BillingStatus.Required ); 9076 } 9077 9078 // Determines if we show the billing address even if it's same as shipping 9079 $scope.showBillingAddress = function() { 9080 return ( 9081 ( 9082 !$scope.isShippingRequired() 9083 || !$scope.BillingInfo.AddressSameAsShipping 9084 || $scope.isStorePickup() 9085 || $scope.isCurbsidePickup() 9086 || $scope.isSchoolPickup() 9087 || $scope.isVantivHosted() 9088 || ($scope.isNewUIFlagSet() && $scope.isWholesaleNoCCEnabled()) // yes we still want to show it for now 9089 ) 9090 && !$scope.isPayAtPickup() 9091 ); 9092 }; 9093 9094 $scope.showBillingSummary = function() { 9095 return ( 9096 $scope.ShippingStatus.Editing 9097 && ($scope.BillingStatus.Entered || $scope.ShippingStatus.Entered) 9098 && $scope.BillingStatus.Required 9099 && !$scope.isWebPayment() 9100 ); 9101 }; 9102 9103 $scope.showCreditCardOption = function() { 9104 return ( 9105 ($scope.Due || $scope.requirePayment()) 9106 && $scope.isPayPal() 9107 && !$scope.isVantivHosted() 9108 && $scope.hasCardProcessor() 9109 && !$scope.isWholesaleNoCCEnabled() 9110 && !$scope.isProcessing() 9111 ); 9112 }; 9113 9114 $scope.areTermsEnabled = function() { 9115 return !!( 9116 $scope.InitInfo && 9117 $scope.InitInfo.CheckoutSettings && 9118 $scope.InitInfo.CheckoutSettings.TermsEnabled 9119 ); 9120 }; 9121 9122 $scope.showTerms = function() { 9123 if ($scope.checkFlagCannotViewTermsConditionsCheckoutScreenAddressInfo()) { 9124 return ( 9125 $scope.requireTermsWithShipping() || $scope.areTermsEnabled() 9126 ); 9127 } else { 9128 return ( 9129 $scope.requireTermsWithShipping() || 9130 ($scope.areTermsEnabled() && $scope.isShippingEntered()) 9131 ); 9132 } 9133 9134 }; 9135 9136 $scope.showPayPalOption = function() { 9137 return ( 9138 $scope.Due 9139 && !$scope.isPayPal() 9140 && $scope.isVantivHosted() 9141 && $scope.isPayPalEnabled() 9142 && !$scope.isCardOnFileNeeded() 9143 && !$scope.isProcessing() 9144 ); 9145 }; 9146 9147 // Primarily for NMI 9148 $scope.showCCPayment = function() { 9149 return (!$scope.isVantivHosted() 9150 && !$scope.isRainProcessor()
9151 && !$scope.isQuiltAdyen() 9152 && !$scope.isPayPal() 9153 && !$scope.isWholesaleNoCCEnabled() 9154 ); 9155 }; 9156 9157 // For Rain Payments 9158 $scope.showRainProcessing = function() { 9159 return ($scope.isRainProcessor() 9160 && !$scope.isWholesaleNoCCEnabled() 9161 ); 9162 } 9163 9164 $scope.showQuiltProcessing = function() { 9165 return ($scope.isQuiltAdyen() 9166 && !$scope.isWholesaleNoCCEnabled() 9167 ); 9168 } 9169 9170 $scope.showCheckoutButton = function() { 9171 return $scope.CartStatus.Valid 9172 && $scope.isBillingDone() 9173 && !$scope.isProcessing() 9174 && !$scope.isPayPal() 9175 && !$scope.isFree() 9176 }; 9177 9178 $scope.showBillingInitiallyNeededButton = function() { 9179 if (!$scope.checkFlagBlockCheckoutButtonWhileProcessing()) { 9180 return $scope.CartStatus.Valid && !$scope.ShippingStatus.Entered && !$scope.isPayPal() && !$scope.isFree(); 9181 } 9182 9183 return $scope.CartStatus.Valid 9184 && !$scope.ShippingStatus.Entered 9185 && !$scope.isPayPal() 9186 && !$scope.isFree() 9187 && !$scope.isProcessing() 9188 ; 9189 }; 9190 9191 $scope.showBillingStillRequiredButton = function() { 9192 return $scope.ShippingStatus.Editing && $scope.BillingStatus.Entered && (!$scope.BillingStatus.Valid || !$scope.CardStatus.Valid) && !$scope.isProcessing(); 9193 }; 9194 9195 $scope.isBillingDone = function() { 9196 return ( 9197 ($scope.BillingStatus.Editing && $scope.ShippingStatus.Valid && $scope.ContactStatus.Valid) 9198 || ($scope.ShippingStatus.Editing && $scope.BillingStatus.Entered && $scope.BillingStatus.Valid && $scope.CardStatus.Valid ) 9199 ); 9200 }; 9201 9202 $scope.isBillingRequired = function() { 9203 if ($scope.isWholesaleNoCCEnabled() && !$scope.isNewUIFlagSet()) { 9204 return false; 9205 } 9206 9207 if ($scope.Due === 0 && !$scope.isCardOnFileNeeded()) { 9208 return false; 9209 } 9210 9211 return $scope.BillingStatus.Required; 9212 }; 9213 9214 $scope.isStoreGST = function () { 9215 return ($scope.InitInfo.gstEnabled && $scope.getStoreCountryKey() == "AUS"); 9216 }; 9217 9218 $scope.isShippingCountryGST = function () { 9219 return ($scope.getShippingCountryKey() == 'AUS'); 9220 }; 9221 9222 $scope.isTaxInclusive = function () { 9223 return ($scope.isStoreGST() && $scope.isShippingCountryGST()); 9224 }; 9225 9226 $scope.isStoreHasLocationForRetailFee = function () { 9227 return $scope.InitInfo.store_locations.some(function (location) { 9228 return location.state === 'CO'; 9229 }) 9230 }; 9231 9232 $scope.gstToNon = function () { 9233 return ($scope.showAsGST() && !$scope.isShippingCountryGST()); 9234 }; 9235 9236 $scope.getShippingTotal = function () { 9237 9238 return $scope.ShippingTotal; 9239 }; 9240 9241 $scope.getGstShippingTotal = function () { 9242 9243 return $scope.ShippingTotal + $scope.ShippingTaxes; 9244 }; 9245 9246 $scope.addShippingTaxes = function (amt) { 9247 9248 return parseFloat(amt) + $scope.calculateShippingTaxAmount(amt); 9249 }; 9250 9251 $scope.QuantityChanged = function (item) { 9252 item.quantity_updating = true; 9253 9254 if (item.qty_type === 'fraction') { 9255 var remainderOptions = $scope.RemainderOptions(item); 9256 var availableRemainderValues = (remainderOptions || []).map(function (option) { 9257 return parseInt(option.id); 9258 }) 9259 9260 if (availableRemainderValues.length && !availableRemainderValues.includes(parseInt(item.qty_remainder))) { 9261 console.log('item.qty_remainder', item.qty_remainder, ' = ', availableRemainderValues[0]) 9262 item.qty_remainder = availableRemainderValues[0]; 9263 } 9264 } 9265 9266 item.qty = $scope.getItemQty(item); 9267 item.total = $scope.RoundTo2Dec(fractionalMultiplication(item.qty, $scope.getItemPrice(item))); 9268 9269 var requestQuantityChanged = () => { 9270 return $http.post($scope.BuildUrl('/checkout/itemupdate'), item, {withCredentials: true}); 9271 }; 9272 9273 if ($scope.isCarePlanEnable()) { 9274 requestQuantityChanged = () => { 9275 return $scope.blockUIWhileCallback(() => { 9276 return $http.post($scope.BuildUrl('/checkout/itemupdate'), item, {withCredentials: true}); 9277 }); 9278 }; 9279 } 9280 9281 requestQuantityChanged().then(function (result) { 9282 $scope.CartDataAdapt(result.data); 9283 $scope.ShippingStatus.DirtyZip = null; // Force options 9284 $scope.ShippingOptionsGet(); 9285 $scope.CartSync(); 9286 if ($scope.checkFlagCarePlans()) { 9287 $scope.updateCarePlanPrice(); 9288 } 9289 }); 9290 }; 9291 $scope.carePlanChanged = function (item) { 9292 item.quantity_updating = true; 9293 9294 var requestCarePlan = () => { 9295 return $http.post($scope.BuildUrl('/checkout/itemupdate'), item, {withCredentials: true}); 9296 }; 9297 9298 if ($scope.isCarePlanEnable()) { 9299 requestCarePlan = () => { 9300 return $scope.blockUIWhileCallback(() => { 9301 return $http.post($scope.BuildUrl('/checkout/itemupdate'), item, {withCredentials: true}); 9302 }); 9303 }; 9304 } 9305 9306 requestCarePlan().then(function (result) { 9307 $scope.CartDataAdapt(result.data); 9308 $scope.ShippingStatus.DirtyZip = null; // Force options 9309 $scope.ShippingOptionsGet(); 9310 $scope.CartSync(); 9311 if ($scope.checkFlagCarePlans()) { 9312 $scope.updateCarePlanPrice(); 9313 } 9314 $scope.Validate(); 9315 }); 9316 }; 9317 9318 $scope.updateCarePlanPrice = function () { 9319 var params = [];
9320 $scope.CartData.Items.forEach(function (cpItem) { 9321 if (cpItem.type === 'care_plan') { 9322 $scope.CartData.Items.forEach(function (item) { 9323 if (cpItem.parent_uuid === item.uid) { 9324 var data = { 9325 itemPrice: item.discounted_total / item.qty, 9326 pslrId: item.pslr_id, 9327 carePlanItem: cpItem, 9328 } 9329 params.push(data); 9330 } 9331 }); 9332 } 9333 }) 9334 if (params.length) { 9335 $.blockUI(); 9336 $http.post($scope.BuildUrl('/checkout/carePlanUpdate'), params, {withCredentials: false}).then(function (result) { 9337 $scope.CartDataAdapt(result.data); 9338 $scope.ShippingStatus.DirtyZip = null; // Force options 9339 $scope.ShippingOptionsGet(); 9340 $scope.CartSync(); 9341 }).finally(function () { 9342 $.unblockUI(); 9343 }); 9344 } 9345 }; 9346 9347 $scope.getItemQty = function(item) { 9348 var qty = null; 9349 9350 var quotient = Number(item.qty_integer); 9351 var remainder = Number(item.qty_remainder); 9352 var min = Number(item.qty_min); 9353 9354 if (quotient === 0 && remainder === 0) { 9355 qty = min; 9356 } else { 9357 qty = quotient + (remainder / 100); 9358 } 9359 9360 return qty; 9361 }; 9362 9363 $scope.IntegerOptions = function (item) { 9364 var min = Math.floor(Number(item.qty_min)); 9365 var max = Math.floor(Number(item.qty_max)); 9366 var options = []; 9367 for (var i = min; i <= max; i++) { 9368 if ((i % item.sold_in_increments) > 0) { 9369 continue; 9370 } 9371 options.push({id: i + '', name: i + ''}) 9372 } 9373 return options; 9374 }; 9375 9376 $scope.RemainderOptions = function (item) { 9377 var options = null; 9378 9379 options = $scope.getRemainderOptions(item); 9380 9381 return options; 9382 }; 9383 9384 $scope.getRemainderOptions = function (item) { 9385 var options = null; 9386 9387 var qtyType = item.qty_type; 9388 var measureUnit = item.measure_unit; 9389 9390 if ((qtyType === 'fraction') && (measureUnit === 'Meter')) { 9391 options = $scope.getMetricFractionalRemainderOptions(item); 9392 } else { 9393 options = $scope.getRemainderOptions1(item); 9394 } 9395 9396 return options; 9397 }; 9398 9399 $scope.getRemainderOptions1 = function (item) { 9400 var options = []; 9401 9402 var minint = Math.floor(item.qty_min); 9403 var maxint = Math.floor(item.qty_max); 9404 9405 if (item.qty_type == 'fraction') { 9406 if (item.measure_unit == 'Meter') { 9407 var minrem1 = Math.round(item.qty_min * 10) % 10; 9408 var maxrem1 = Math.round(item.qty_max * 10) % 10; 9409 var min1 = Number(item.qty_integer) == minint ? minrem1 : 0; 9410 var max1 = Number(item.qty_integer) == maxint ? maxrem1 : 9; 9411 for (var a = min1; a <= max1; a++) { 9412 var name1 = a == 0 ? '0' : '.' + a; 9413 options.push({id: a + '', name: name1}); 9414 } 9415 } else { 9416 var minrem2 = Math.round(item.qty_min * 100) % 100; 9417 var maxrem2 = Math.round(item.qty_max * 100) % 100; 9418 var min2 = Number(item.qty_integer) == minint ? minrem2 : 0; 9419 var max2 = Number(item.qty_integer) == maxint ? maxrem2 : 75; 9420 for (var b = min2; b <= max2; b += 25) { 9421 var name2 = '0'; 9422 if (b == 25) { 9423 name2 = '1/4'; 9424 if (item.FatQuarter && item.qty_integer == 0) { 9425 name2 = 'Fat Quarter'; 9426 } 9427 } 9428 if (b == 50) name2 = '1/2'; 9429 if (b == 75) name2 = '3/4'; 9430 options.push({id: b + '', name: name2}); 9431 } 9432 } 9433 } 9434 9435 return options; 9436 }; 9437 9438 9439 $scope.getMetricFractionalRemainderOptions = function (item) { 9440 var options = []; 9441 9442 var qtyMinStr = item.qty_min; 9443 var qtyMaxStr = item.qty_max; 9444 var qtyIntegerStr = item.qty_integer; 9445 9446 var qtyMin = parseFloat(qtyMinStr); 9447 var qtyMax = parseFloat(qtyMaxStr); 9448 var qtyInteger = parseFloat(qtyIntegerStr); 9449 9450 var includeFatQuarter = ((qtyInteger === 0) && (qtyMin <= 0.25)); 9451 9452 var minint = Math.floor(qtyMin); 9453 var maxint = Math.floor(qtyMax); 9454 9455 var minrem1 = Math.round(qtyMin * 100) % 100; 9456 var maxrem1 = Math.round(qtyMax * 100) % 100; 9457 9458 var min1 = (qtyInteger === minint) ? minrem1 : 0; 9459 var max1 = (qtyInteger === maxint) ? maxrem1 : 90; 9460 9461 if (min1 === 25) { 9462 min1 = 30; 9463 } 9464 for (var i = min1; i <= max1; i += 10) {
9465 if ((i === 30) && includeFatQuarter) { 9466 options.push( 9467 { 9468 id: "25", name: ".25" 9469 } 9470 ); 9471 } 9472 9473 var decimal = i / 10; 9474 9475 var label = (i === 0) ? '0' : ('.' + decimal); 9476 options.push( 9477 { 9478 id: (i + ''), name: label 9479 } 9480 ); 9481 } 9482 9483 return options; 9484 }; 9485 9486 $scope.showCarePlanInformation = function (item) { 9487 item.isShowCarePlanInformation = !item.isShowCarePlanInformation; 9488 } 9489 9490 $scope.viewAvailableCarePlans = function (item) { 9491 item.is_care_plan_selected = true; 9492 9493 var parentItem = $scope.getItemByParentUuid(item.parent_uuid); 9494 9495 var itemSalePrice = parentItem.discounted_total / parentItem.qty; 9496 window.vueEventHub.$eventHub.$emit('jm-care-plan-modal-open', { 9497 callFrom: 'Web', 9498 itemPrice: item.pslr_data.item_price, 9499 itemSalePrice: itemSalePrice || null, 9500 itemQty: item.qty, 9501 pslrId: item.pslr_data.parent_pslr_id, 9502 itemId: item.id, 9503 callback: function() { 9504 this.addToCart(); 9505 }.bind(this), 9506 }); 9507 }; 9508 9509 $scope.getItemByParentUuid = function (parentUuid) { 9510 return $scope.CartData.Items.find(function (item) { 9511 return item.uid === parentUuid; 9512 }); 9513 }; 9514 9515 $scope.registerCarePlanHandleEvent = function() { 9516 window.vueEventHub.$eventHub.$on('jm-care-plan-handle-line', function(data) { 9517 if (Object.prototype.toString.call(data) !== "[object Object]" 9518 || Object.prototype.toString.call(data.carePlanForItems) !== "[object Array]" 9519 || !data.carePlanForItems.length 9520 ) { 9521 return; 9522 } 9523 9524 var carePlanItem = data.carePlanForItems[0].length === 0 ? $scope.CartData.Items[0] : data.carePlanForItems[0]; 9525 var carePlanLine = {}; 9526 9527 carePlanLine.id = data.itemId; 9528 carePlanLine.type = 'care_plan'; 9529 carePlanLine.title = 'JM ® Care Plan - ' + data.pslrData.product_type_name + ' (' + carePlanItem.applicablePlanDuration + ')'; 9530 carePlanLine.planSku = carePlanItem.planSku; 9531 carePlanLine.pslr_data = data.pslrData; 9532 carePlanLine.is_care_plan_selected = true; 9533 carePlanLine.selected_care_plan = carePlanItem; 9534 carePlanLine.care_plans = data.carePlans; 9535 carePlanLine.price = carePlanItem.planRetailPrice; 9536 carePlanLine.qty = 1; 9537 carePlanLine.qty_type = 'integer'; 9538 carePlanLine.taxBitmap = data.taxBitmap; 9539 carePlanLine.parent_uuid = $scope.CartData.Items.find(item => item.pslr_id === data.pslrData.parent_pslr_id).uid; 9540 if (data.eligibleForDiscount) { 9541 carePlanLine.discount_eligible = true; 9542 carePlanLine.discount_applied = true; 9543 } 9544 9545 if (data.carePlanForItems[0].length === 0) { 9546 $scope.removeSelectedCarePlan(carePlanLine); 9547 } else { 9548 $scope.carePlanChanged(carePlanLine); 9549 } 9550 }); 9551 } 9552 9553 $scope.removeSelectedCarePlan = function (item) { 9554 item.selected_care_plan = []; 9555 item.is_care_plan_selected = false; 9556 9557 var requestRemoveSelectedCarePlan = () => { 9558 return $http.post($scope.BuildUrl('/checkout/itemupdate'), item, {withCredentials: true}); 9559 }; 9560 9561 if ($scope.isCarePlanEnable()) { 9562 requestRemoveSelectedCarePlan = () => { 9563 return $scope.blockUIWhileCallback(() => { 9564 return $http.post($scope.BuildUrl('/checkout/itemupdate'), item, {withCredentials: true}); 9565 }); 9566 }; 9567 } 9568 9569 requestRemoveSelectedCarePlan().then(function (result) { 9570 $scope.CartDataAdapt(result.data); 9571 $scope.ShippingStatus.DirtyZip = null; // Force options 9572 $scope.ShippingOptionsGet(); 9573 $scope.CartSync(); 9574 }); 9575 }; 9576 9577 $scope.ItemRemove = function (item) { 9578 item.removing = true; 9579 var id = item.id; 9580 9581 if (item.type === 'class') { 9582 id = item.uid; 9583 } 9584 9585 var params = { 9586 item_id: id 9587 }; 9588 9589 if (item.condition) { 9590 params['condition'] = item.condition.id; 9591 } 9592 9593 var requestItemRemove = () => { 9594 return $http.get($scope.BuildUrl('/checkout/itemremove'), { 9595 params: params, 9596 withCredentials: true 9597 }); 9598 }; 9599 9600 if ($scope.isCarePlanEnable()) { 9601 requestItemRemove = () => { 9602 return $scope.blockUIWhileCallback(() => { 9603 return $http.get($scope.BuildUrl('/checkout/itemremove'), { 9604 params: params, 9605 withCredentials: true 9606 }); 9607 }); 9608 }; 9609 } 9610 9611 requestItemRemove().then(function (result) { 9612 $scope.CartDataAdapt(result.data); 9613 $scope.ShippingStatus.DirtyZip = null; // Force options 9614 $scope.ShippingOptionsGet(); 9615 $scope.CartSync(); 9616 if ($scope.checkFlagCarePlans()) { 9617 $scope.updateCarePlanPrice(); 9618 } 9619 }); 9620 }; 9621 9622 // endregion 9623 9624 // ****************************************************************************************** 9625 // region Gift Cards and Coupons 9626 // ****************************************************************************************** 9627 9628 $scope.shouldShowGiftCardsCoupons = function () { 9629 var isWholesale = (typeof $scope.InitInfo.wholesale !== typeof undefined) && $scope.InitInfo.wholesale.checkout_enabled; 9630 9631 return !isWholesale && !$scope.isCartTotalExceeded(); 9632 }; 9633 9634 $scope.shouldShowGiftCardCouponErrorMsg = function () { 9635 return (($scope.giftCardCouponMsg !== '') && !$scope.shouldShowGiftCardAppliedMsg() && !$scope.shouldShowCouponAppliedMsg()); 9636 }; 9637 9638 $scope.shouldShowGiftCardAppliedMsg = function () { 9639 return ($scope.giftCardCouponMsg === 'Gift Card Applied'); 9640 }; 9641 9642 $scope.showGiftCardSummary = function() { 9643 return ($scope.CartData.GiftCards.length > 0 && !$scope.isPayAtPickup()); 9644 } 9645 9646 $scope.shouldShowCouponAppliedMsg = function () { 9647 return ($scope.giftCardCouponMsg === 'Coupon Added'); 9648 }; 9649 9650 $scope.shouldEnableGiftCardCouponApplyBtn = function () { 9651 var code = $scope.StatusInfo.giftCardOrCouponCode; 9652 return (code !== ''); 9653 }; 9654 9655 $scope.getGiftGardCouponCodeSelector = function () { 9656 return $('#gift-card-coupon-code'); 9657 }; 9658
9659 $scope.focusGCCouponCodeTextBox = function () { 9660 var textBox = $scope.getGiftGardCouponCodeSelector(); 9661 textBox.focus(); 9662 }; 9663 9664 $scope.clearGCCouponCodeTextBox = function () { 9665 $scope.StatusInfo.giftCardOrCouponCode = ''; 9666 }; 9667 9668 $scope.showGiftCardCouponMessage = function (msg) { 9669 $scope.clearGiftCardCouponMessage(); 9670 $scope.giftCardCouponMsg = msg; 9671 }; 9672 9673 $scope.clearGiftCardCouponMessage = function () { 9674 $scope.giftCardCouponMsg = ''; 9675 }; 9676 9677 $scope.giftCardOrCouponCodeKeyDown = function (event) { 9678 $scope.clearGiftCardCouponMessage(); 9679 9680 var code = $scope.StatusInfo.giftCardOrCouponCode; 9681 if (code === '') { 9682 return; 9683 } 9684 9685 var keyCode = event.keyCode; 9686 if (keyCode === 13 || keyCode === 10) { 9687 event.preventDefault(); 9688 event.stopPropagation(); 9689 9690 $scope.getGiftCardOrCoupon(); 9691 } 9692 }; 9693 9694 $scope.blockUIWhileCallback = function (promise) { 9695 var beforeunloadHandler = function (event) { 9696 event.returnValue = null; 9697 }; 9698 9699 $.blockUI(); 9700 window.addEventListener('beforeunload', beforeunloadHandler); 9701 window.localStorage.setItem('loading-interrupted', 'true'); 9702 9703 return promise().then((data) => { 9704 window.localStorage.removeItem('loading-interrupted'); 9705 9706 return data; 9707 }).finally(() => { 9708 window.removeEventListener('beforeunload', beforeunloadHandler); 9709 $.unblockUI(); 9710 }); 9711 }; 9712 9713 $scope.applyCouponFromInvitePage = function () { 9714 $scope.giftCardOrCouponDataState = $scope.DATA_STATE_LOADING; 9715 $scope.giftCardOrCoupon = []; 9716 9717 var code = $scope.StatusInfo.giftCardOrCouponCode.replace('CPN**', '').replace('cpn**', ''); 9718 var params = { 9719 gc_coupon_code: code 9720 }; 9721 9722 var url = $scope.getGiftCardOrCouponApiUrl(); 9723 9724 var requestGiftCard = () => { 9725 return $http.get(url, {params: params, withCredentials: true}); 9726 }; 9727 9728 if ($scope.isCarePlanEnable()) { 9729 requestGiftCard = () => { 9730 return $scope.blockUIWhileCallback(() => { 9731 return $http.get(url, {params: params, withCredentials: true}); 9732 }); 9733 }; 9734 } 9735 9736 requestGiftCard().then(function successCallback(response) { 9737 var data = null; 9738 9739 if (typeof response.data !== typeof undefined) { 9740 data = response.data; 9741 } 9742 9743 var couponFound = false; 9744 if (typeof data.coupon_code_found !== typeof undefined) { 9745 var couponCodeFound = data.coupon_code_found; 9746 if (couponCodeFound === code) { 9747 couponFound = true; 9748 } 9749 } 9750 9751 var gcFound = false; 9752 if (typeof data.gc_code_found !== typeof undefined) { 9753 var gcCodeFound = data.gc_code_found; 9754 if (gcCodeFound === code) { 9755 gcFound = true; 9756 } 9757 } 9758 9759 var responseError = null; 9760 if (typeof data.error !== typeof undefined) { 9761 responseError = data.error; 9762 if (responseError === 'already-added') { 9763 $scope.clearGCCouponCodeTextBox(); 9764 $scope.giftCardOrCouponDataState = $scope.DATA_STATE_LOADED; 9765 $scope.showGiftCardCouponMessage('Gift Card or Coupon ' + code + ' has already been applied'); 9766 return; 9767 } 9768 } 9769 9770 if (!couponFound) { 9771 return; 9772 } 9773 9774 var couponId = $scope.getResponseDataCouponId(data); 9775 if (!couponId) { 9776 if (window.console && window.console.info) { 9777 console.info("Coupon found but ID not returned - Code:" + code); 9778 } 9779 9780 return; 9781 } 9782 9783 $scope.StatusInfo.CouponError = responseError; 9784 $scope.CartDataAdapt(data); 9785 9786 if ($scope.giftCardOrCouponRemovalMap.hasOwnProperty(couponId)) { 9787 if (window.console && window.console.info) { 9788 console.info("Coupon found but can't be used - Coupon ID:" + couponId); 9789 } 9790 $scope.giftCardOrCouponDataState = $scope.DATA_STATE_LOADED; 9791 return; 9792 } 9793 9794 $scope.clearGCCouponCodeTextBox(); 9795 $scope.ShippingOptionsGet(true); 9796 9797 $scope.giftCardOrCouponDataState = $scope.DATA_STATE_LOADED; 9798 $scope.showGiftCardCouponMessage('Coupon Added'); 9799 }, function errorCallback(response) { 9800 alert('Gor some error'); 9801 }) 9802 }; 9803 9804 $scope.getGiftCardOrCoupon = function () { 9805 $scope.giftCardOrCouponDataState = $scope.DATA_STATE_LOADING; 9806 $scope.giftCardOrCoupon = []; 9807 9808 var code = $scope.StatusInfo.giftCardOrCouponCode.replace('CPN**', '').replace('cpn**', ''); 9809 var params = { 9810 gc_coupon_code: code 9811 }; 9812 9813 var couponIsInUse = $scope.CartData.Coupons.some((coupon) => { 9814 return coupon.couponCode === code; 9815 }); 9816 9817 if (couponIsInUse) { 9818 $scope.showGiftCardCouponMessage('Coupon ' + code + ' is already in use'); 9819 $scope.giftCardOrCouponDataState = $scope.DATA_STATE_LOADED; 9820 9821 return; 9822 } 9823 9824 var url = $scope.getGiftCardOrCouponApiUrl(); 9825 9826 var requestGiftCard = () => { 9827 return $http.get(url, {params: params, withCredentials: true}); 9828 }; 9829 9830 if ($scope.isCarePlanEnable()) { 9831 requestGiftCard = () => { 9832 return $scope.blockUIWhileCallback(() => { 9833 return $http.get(url, {params: params, withCredentials: true}); 9834 }); 9835 }; 9836 } 9837 9838 requestGiftCard().then(function successCallback(response) { 9839 var data = null; 9840 9841 if (typeof response.data !== typeof undefined) { 9842 data = response.data; 9843 } 9844 9845 var couponFound = false;
9846 if (typeof data.coupon_code_found !== typeof undefined) { 9847 var couponCodeFound = data.coupon_code_found; 9848 if (couponCodeFound === code) { 9849 couponFound = true; 9850 } 9851 } 9852 9853 var gcFound = false; 9854 if (typeof data.gc_code_found !== typeof undefined) { 9855 var gcCodeFound = data.gc_code_found; 9856 if (gcCodeFound === code) { 9857 gcFound = true; 9858 } 9859 } 9860 9861 var responseError = null; 9862 if (typeof data.error !== typeof undefined) { 9863 responseError = data.error; 9864 if (responseError === 'already-added') { 9865 $scope.clearGCCouponCodeTextBox(); 9866 $scope.giftCardOrCouponDataState = $scope.DATA_STATE_LOADED; 9867 $scope.showGiftCardCouponMessage('Gift Card or Coupon ' + code + ' has already been applied'); 9868 return; 9869 } 9870 } 9871 9872 if (couponFound) { 9873 var couponId = $scope.getResponseDataCouponId(data); 9874 if (!couponId) { 9875 if (window.console && window.console.info) { 9876 console.info("Coupon found but ID not returned - Code:" + code); 9877 } 9878 var errorMsg = 'Gift Card / Coupon Error'; 9879 if (data && data.error) { 9880 errorMsg = data.error; 9881 } 9882
9883 $scope.focusGCCouponCodeTextBox(); 9884 $scope.showGiftCardCouponMessage(errorMsg); 9885 $scope.giftCardOrCouponDataState = $scope.DATA_STATE_ERROR; 9886 return; 9887 } 9888 9889 $scope.StatusInfo.CouponError = responseError; 9890 if ((responseError === null) || (responseError === '')) { 9891 $scope.CartDataAdapt(data); 9892 //todo use it 9893 if ($scope.checkFlagCarePlans()) { 9894 $scope.updateCarePlanPrice(); 9895 } 9896 } 9897 if ($scope.giftCardOrCouponRemovalMap.hasOwnProperty(couponId)) { 9898 if (window.console && window.console.info) { 9899 console.info("Coupon found but can't be used - Coupon ID:" + couponId); 9900 } 9901 $scope.giftCardOrCouponDataState = $scope.DATA_STATE_LOADED; 9902 return; 9903 } 9904 9905 $scope.clearGCCouponCodeTextBox(); 9906 $scope.ShippingOptionsGet(true); 9907 9908 $scope.giftCardOrCouponDataState = $scope.DATA_STATE_LOADED; 9909 $scope.showGiftCardCouponMessage('Coupon Added'); 9910 } else if (gcFound) { 9911 $scope.clearGCCouponCodeTextBox(); 9912 9913 $scope.StatusInfo.GiftCardError = responseError; 9914 if ((responseError === null) || (responseError === '')) { 9915 $scope.CartDataAdapt(data); 9916 if ($scope.checkFlagCarePlans()) { 9917 $scope.updateCarePlanPrice(); 9918 } 9919 } 9920 9921 $scope.giftCardOrCouponDataState = $scope.DATA_STATE_LOADED; 9922 $scope.showGiftCardCouponMessage('Gift Card Applied'); 9923 } else { 9924 if (window.console && window.console.error) { 9925 console.error("Gift card / coupon error - Code: " + code, response); 9926 } 9927
9928 $scope.focusGCCouponCodeTextBox(); 9929 $scope.showGiftCardCouponMessage('Gift card / coupon error'); 9930 $scope.giftCardOrCouponDataState = $scope.DATA_STATE_ERROR; 9931 } 9932 }, function errorCallback(response) { 9933 var errorMsg = 'Unknown Error'; 9934 if ((typeof response.data !== typeof undefined) && (typeof response.data.error !== typeof undefined)) { 9935 errorMsg = response.data.error; 9936 } 9937 if (window.console && window.console.info) { 9938 console.info("Invalid Gift Card or Coupon Code:" + code, errorMsg); 9939 } 9940 9941 $scope.focusGCCouponCodeTextBox(); 9942 $scope.showGiftCardCouponMessage(response.data); 9943 $scope.giftCardOrCouponDataState = $scope.DATA_STATE_ERROR; 9944 }) 9945 }; 9946 9947 $scope.getResponseDataCouponId = function (data) { 9948 try { 9949 var couponId = data.cartdata.Coupons[0].couponId; 9950 return couponId; 9951 } catch (e) { 9952 return null; 9953 } 9954 }; 9955 9956 $scope.getGiftCardOrCouponApiUrl = function () { 9957 var url = $scope.BuildUrl('/checkout/get-gc-coupon'); 9958 return url; 9959 }; 9960 9961 $scope.removeCoupon = function (couponId, errorMsg) { 9962 if (!$scope.checkFlagShowCouponErrorMessageOnCheckoutPage()) { 9963 $scope.showGiftCardCouponMessage(''); 9964 } 9965 9966 if ($scope.giftCardOrCouponRemovalMap.hasOwnProperty(couponId)) { 9967 if (window.console && window.console.log) { 9968 console.info("Skipping Coupon Removal - Already In Progress - Coupon ID:" + couponId); 9969 } 9970 return; 9971 } 9972 9973 if ($scope.checkFlagShowCouponErrorMessageOnCheckoutPage()) { 9974 $scope.showGiftCardCouponMessage(''); 9975 } 9976 $scope.giftCardOrCouponRemovalMap[couponId] = true; 9977 9978 if (errorMsg) { 9979 if (window.console && window.console.log) { 9980 console.info("removeCoupon - Setting error msg: " + errorMsg); 9981 } 9982 9983 if ($scope.checkFlagShowCouponErrorMessageOnCheckoutPage()) { 9984 var message = errorMsg; 9985 9986 var removedCoupon = ($scope.CartData.Coupons || []).find(function (c) { 9987 return c.couponId == couponId; 9988 }); 9989 var couponCode = removedCoupon ? (removedCoupon.couponCode || removedCoupon.number) : ''; 9990 message = couponCode ? 'Coupon ' + couponCode + ' was removed: ' + errorMsg : errorMsg; 9991
9992 $scope.focusGCCouponCodeTextBox(); 9993 $scope.showGiftCardCouponMessage(message); 9994 } else { 9995 $scope.focusGCCouponCodeTextBox(); 9996 $scope.showGiftCardCouponMessage(errorMsg); 9997 } 9998 } 9999 10000 var params = { 10001 coupon_id: couponId 10002 }; 10003 10004 $scope.StatusInfo.CouponUpdating = true; 10005 10006 var requestRemoveCoupon = () => { 10007 return $http.get($scope.BuildUrl('/checkout/remove-coupon'), { 10008 params: params, 10009 withCredentials: true 10010 }); 10011 }; 10012 10013 if ($scope.isCarePlanEnable()) { 10014 requestRemoveCoupon = () => { 10015 return $scope.blockUIWhileCallback(() => { 10016 return $http.get($scope.BuildUrl('/checkout/remove-coupon'), { 10017 params: params, 10018 withCredentials: true 10019 }); 10020 }); 10021 }; 10022 } 10023 10024 requestRemoveCoupon().then(function (result) { 10025 delete $scope.giftCardOrCouponRemovalMap[couponId]; 10026 if (window.console && window.console.log) { 10027 console.info("removeCoupon - Removed coupon - Coupon ID:" + couponId); 10028 } 10029 10030 $scope.StatusInfo.CouponUpdating = false; 10031 10032 $scope.CartDataAdapt(result.data); 10033 if ($scope.checkFlagCarePlans()) { 10034 $scope.updateCarePlanPrice(); 10035 } 10036 $scope.ShippingOptionsGet(true); 10037 }); 10038 }; 10039 10040 $scope.removeGiftCard = function (gcId) { 10041 var params = 10042 { 10043 gc_id: gcId 10044 }; 10045 10046 $scope.StatusInfo.GiftCardUpdating = true; 10047 10048 var requestRemoveGiftCard = () => { 10049 return $http.get($scope.BuildUrl('/checkout/remove-gc'), { 10050 params: params, 10051 withCredentials: true 10052 }); 10053 }; 10054 10055 if ($scope.isCarePlanEnable()) { 10056 requestRemoveGiftCard = () => { 10057 return $scope.blockUIWhileCallback(() => { 10058 return $http.get($scope.BuildUrl('/checkout/remove-gc'), { 10059 params: params, 10060 withCredentials: true 10061 }); 10062 }); 10063 }; 10064 } 10065 10066 requestRemoveGiftCard().then(function (result) { 10067 $scope.StatusInfo.GiftCardUpdating = false; 10068 10069 $scope.CartDataAdapt(result.data); 10070 if ($scope.StatusInfo.Processor === 'gc') { 10071 $scope.StatusInfo.Processor = null; 10072 } 10073 }); 10074 }; 10075 10076 // endregion 10077 10078 // ****************************************************************************************** 10079 // region Gift Message 10080 // ****************************************************************************************** 10081 10082 $scope.GiftMessageEdit = function () { 10083 if ($scope.GiftMessage.Checked) { 10084 $scope.GiftMessage.Show = true; 10085 } else { 10086 $scope.GiftMessage.Show = !$scope.GiftMessage.Show; 10087 } 10088 }; 10089 10090 $scope.isProductInCart = function () { 10091 return $scope.CartData.Items && $scope.CartData.Items.length ? $scope.CartData.Items.some(function (item) { 10092 return item.type === 'pslr'; 10093 }) : false; 10094 10095 } 10096 10097 $scope.isFlagInsuranceEnabled = function () { 10098 return !!$scope.knobby && $scope.knobby.module_jm_insurance; 10099 } 10100 10101 $scope.isFlagLuxsuranceEnabled = function () { 10102 return !!$scope.knobby && $scope.knobby.module_luxsurance_functionality; 10103 } 10104 10105 $scope.isFlagSaveLuxsuranceInsuranceInfoAfterReloadingEnabled = function () { 10106 return !!$scope.knobby && $scope.knobby.module_save_luxsurance_insurance_info_after_reloading; 10107 } 10108 10109 $scope.handleInsuranceCheckbox = function () { 10110 document.getElementById('isInsuranceSwitchedOn').checked = false; 10111 if ($scope.isInsuranceSwitchedOn) { 10112 $scope.isInsuranceSwitchedOn = false; 10113 if ($scope.isFlagSaveLuxsuranceInsuranceInfoAfterReloadingEnabled()) { 10114 $scope.setInsuranceInfoToLocalStorage(); 10115 } 10116 } else { 10117 $scope.openJmQuoteContactModal({isInsuranceSwitchedOn: true}); 10118 } 10119
10120 $scope.safeApply(); 10121 } 10122 10123 $scope.handleLuxsuranceCheckbox = function () { 10124 document.getElementById('isLuxsuranceSwitchedOn').checked = false; 10125 if ($scope.isLuxsuranceSwitchedOn) { 10126 $scope.isLuxsuranceSwitchedOn = false; 10127 if ($scope.isFlagSaveLuxsuranceInsuranceInfoAfterReloadingEnabled()) { 10128 $scope.setInsuranceInfoToLocalStorage(); 10129 } 10130 } else { 10131 $scope.openJmQuoteContactModal({isLuxsuranceSwitchedOn: true}); 10132 } 10133 10134 $scope.safeApply(); 10135 } 10136 10137 $scope.hasContactInformationConfirmed = function () { 10138 return JSON.stringify($scope.contactJMInsuranceInfo) !== '{}'; 10139 } 10140 10141 $scope.showContactInformationConfirmModal = function () { 10142 var dataForContactInformationConfirmModal = {}; 10143 if (JSON.stringify($scope.contactJMInsuranceInfo) !== '{}') { 10144 dataForContactInformationConfirmModal = { 10145 contactInfo: $scope.contactJMInsuranceInfo, 10146 isInsuranceSwitchedOn: $scope.isInsuranceSwitchedOn, 10147 isLuxsuranceSwitchedOn: $scope.isLuxsuranceSwitchedOn, 10148 }; 10149 } 10150 10151 $scope.openJmQuoteContactModal({isViewMode: true, ...dataForContactInformationConfirmModal}); 10152 } 10153 10154 $scope.getDataForJmQuoteContactModal = function () { 10155 if (JSON.stringify($scope.contactJMInsuranceInfo) !== '{}') { 10156 return ({ 10157 contactInfo: $scope.contactJMInsuranceInfo, 10158 isInsuranceSwitchedOn: $scope.isInsuranceSwitchedOn, 10159 isLuxsuranceSwitchedOn: $scope.isLuxsuranceSwitchedOn, 10160 }); 10161 } else if ( 10162 $scope.ShippingInfo && 10163 ( 10164 $scope.ShippingInfo.CountryKey === 'USA' || 10165 $scope.ShippingInfo.CountryKey === 'CAN' 10166 ) 10167 ) { 10168 return ({ 10169 contactInfo: { 10170 name_first: $scope.ShippingInfo.FirstName || '', 10171 name_last: $scope.ShippingInfo.LastName || '', 10172 email: $scope.BillingInfo.Email || '', 10173 phone: $scope.BillingInfo.Phone || '', 10174 country: $scope.ShippingInfo.CountryKey || '', 10175 address: $scope.ShippingInfo.Address1 || '', 10176 address2: $scope.ShippingInfo.Address2 || '', 10177 city: $scope.ShippingInfo.City || '', 10178 state: $scope.ShippingInfo.State || '', 10179 zip: $scope.ShippingInfo.Zip || '', 10180 }, 10181 isInsuranceSwitchedOn: $scope.isInsuranceSwitchedOn, 10182 isLuxsuranceSwitchedOn: $scope.isLuxsuranceSwitchedOn, 10183 }); 10184 } 10185 10186 return {}; 10187 }; 10188 10189 $scope.openJmQuoteContactModal = function (dataForContactInformationConfirmModal = {}) { 10190 var isTransactionCompleted = false; 10191 var isConfirmed = $scope.confirmedJMInsurance; 10192 window.vueEventHub.$eventHub.$emit('jm-quote-contact-modal-open', { 10193 ...$scope.getDataForJmQuoteContactModal(), 10194 ...dataForContactInformationConfirmModal, 10195 isTransactionCompleted, 10196 isConfirmed, 10197 isFlagInsuranceEnabled: $scope.isFlagInsuranceEnabled(), 10198 isFlagLuxsuranceEnabled: $scope.isFlagLuxsuranceEnabled(), 10199 }); 10200 } 10201 10202 $scope.openCouponDetailsModal = function (coupon) { 10203 if ($scope.knobby['module_better_coupons']) { 10204 coupon.better_coupons_flag = true; 10205 } 10206 window.vueEventHub.$eventHub.$emit('coupon-details-modal-open', { coupon }); 10207 } 10208 10209 10210 $scope.GiftMessageAdd = function () { 10211 $scope.GiftMessage.Show = false; 10212 10213 }; 10214 10215 // endregion 10216 10217 // ****************************************************************************************** 10218 // region Session 10219 // ****************************************************************************************** 10220 10221 $scope.SessionUpdate = function () { 10222 var data = 10223 { 10224 ShippingInfo: $scope.ShippingInfo, 10225 BillingInfo: $scope.BillingInfo 10226 }; 10227 10228 $http.post($scope.BuildUrl('/checkout/session'), data); 10229 }; 10230 10231 // endregion 10232 10233 // ****************************************************************************************** 10234 // **** Errors 10235 // ****************************************************************************************** 10236 10237 $scope.ErrorMessageClose = function () { 10238 $scope.StatusInfo.ErrorMessage = null; 10239 }; 10240 10241 $scope.ErrorMessageBottomClose = function () { 10242 $scope.StatusInfo.ErrorMessageBottom = null; 10243 }; 10244 10245 $scope.InventoryMessagesClose = function () { 10246 $scope.InventoryMessages = {}; 10247 } 10248 10249 $scope.AvailabilityMessagesClose = function () { 10250 $scope.AvailabilityMessages = {}; 10251 }; 10252 10253 $scope.getItemUnitType = function (item) { 10254 if (item.unit_type == 'each') { 10255 return ''; 10256 } 10257 10258 if (item.unit_type == 'per yard') { 10259 return item.measure_unit == 'Meter' ? 'Metres' : (item.measure_unit + '(s)'); 10260 } 10261 10262 return item.unit_type + '(s)'; 10263 }; 10264 10265 $scope.isDynamicTaxServiceEnabled = function () { 10266 return $scope.knobby && $scope.tax_service; 10267 } 10268 10269 $scope.getShippingDiscount = function () { 10270 var coupon = $scope.getFreeShippingCoupon(); 10271 10272 if (!coupon || !$scope.ShippingTotal) { 10273 return 0; 10274 } 10275 10276 var shippingAmount = +$scope.ShippingTotal; 10277 var couponMaxDiscount = parseFloat(coupon.maxDiscount) ? parseFloat(coupon.maxDiscount) : shippingAmount; 10278 10279 if (shippingAmount <= couponMaxDiscount) { 10280 return shippingAmount; 10281 } else { 10282 return couponMaxDiscount; 10283 } 10284 } 10285 10286 $scope.getFreeShippingCoupon = function () { 10287 var coupons = $scope.CartData.Coupons || []; 10288 10289 for (var c = 0; c < coupons.length; c++) { 10290 var coupon = coupons[c]; 10291 10292 if (coupon.couponType === 'free_shipping_off') { 10293 return coupon 10294 } 10295 } 10296 10297 return null; 10298 } 10299 10300 $scope.getShippingWithDiscount = function() { 10301 var shipping = $scope.ShippingInfo.Option ? $scope.ShippingInfo.Option.price : 0.00; 10302 10303 if (shipping <= 0) { 10304 return shipping; 10305 } 10306 10307 return shipping - $scope.getShippingDiscount(); 10308 }; 10309 10310 $scope.getLargestDimensions = function() { 10311 var dimensions = {}; 10312 10313 if ($scope.CartData && $scope.CartData.Items && $scope.CartData.Items.length > 0) { 10314 var items = $scope.CartData.Items; 10315 items.sort(function (a, b) { 10316 if ((a.dimensions && Object.keys(a.dimensions).length > 0) && (b.dimensions && Object.keys(b.dimensions).length > 0)) { 10317 var aVolume = parseInt(a.dimensions.length) * parseInt(a.dimensions.height) * parseInt(a.dimensions.width); 10318 var bVolume = parseInt(b.dimensions.length) * parseInt(b.dimensions.height) * parseInt(b.dimensions.width); 10319 10320 return bVolume - aVolume; 10321 } 10322 10323 return 0; 10324 }); 10325 10326 if (items[0].dimensions && Object.keys(items[0].dimensions).length > 0) { 10327 dimensions = items[0].dimensions; 10328 } 10329 } 10330 return dimensions; 10331 }; 10332 10333 $scope.getCurrentTaxProfileId = function () { 10334 if ($scope.HasDynamicTaxes && $scope.isDynamicTaxServiceEnabled()) { 10335 return $scope.tax_service; 10336 } 10337 10338 var useStoreTaxTable = $scope.isPickup() ? true : $scope.taxAtCountryLevel(); 10339 10340 var state = $scope.ShippingInfo.State; 10341 if (useStoreTaxTable) { 10342 if ($scope.isPickup() && $scope.ShippingInfo && $scope.ShippingInfo.Option && $scope.ShippingInfo.Option.address && $scope.ShippingInfo.Option.address.state) { 10343 state = $scope.ShippingInfo.Option.address.state; 10344 } else if ($scope.InitInfo.Store) { 10345 state = $scope.InitInfo.Store.state; 10346 } 10347 } 10348 10349 var taxTable = $scope.InitInfo.TaxableRegions[state] || null; 10350 10351 if (!taxTable) { 10352 return null; 10353 } 10354 10355 return taxTable.tax_profile || null; 10356 }; 10357 10358 // OSR 10359 10360 $scope.getOsrItem = function () { 10361 if (!$scope.CartData || !$scope.CartData.Items) { 10362 return null; 10363 } 10364 10365 return $scope.CartData.Items.find(function (item) { 10366 return item.type === 'rto' && Object.hasOwn(item, 'osrData'); 10367 }) || null; 10368 }; 10369 10370 $scope.getOsrData = function () { 10371 var item = $scope.getOsrItem(); 10372 10373 return item ? item.osrData : null; 10374 }; 10375 10376 $scope.hasOsrItem = function () { 10377 return !!$scope.getOsrData(); 10378 }; 10379 10380 $scope.isOsrShippingEnabled = function () { 10381 var data = $scope.getOsrData(); 10382 10383 return data ? data.allow_shipping : false;
10384 }; 10385 10386 $scope.isOsrStorePickupEnabled = function () { 10387 var data = $scope.getOsrData(); 10388 10389 return data ? data.allow_store_pickup : false; 10390 }; 10391 10392 $scope.isOsrSchoolPickupEnabled = function () { 10393 var data = $scope.getOsrData(); 10394 10395 return data ? data.allow_school_pickup : false; 10396 }; 10397 10398 $scope.getOsrAvailablePslrIds = function () { 10399 var data = $scope.getOsrData(); 10400 10401 return data ? data.available_pslr_ids : []; 10402 }; 10403 10404 $scope.isOsrSchoolPickupAvailable = function () { 10405 if (!$scope.isOsrSchoolPickupEnabled()) { 10406 return false; 10407 } 10408 10409 var osrItem = $scope.getOsrItem(); 10410 var availablePslrIds = $scope.getOsrAvailablePslrIds(); 10411 10412 return $scope.CartData.Items.every(function (item) { 10413 if (item.id === osrItem.id && item.type === osrItem.type) { 10414 return true; 10415 } 10416 10417 return item.type === 'pslr' && availablePslrIds.includes(+item.pslr_id); 10418 }); 10419 }; 10420 10421 $scope.isRtoOrOsr = function () { 10422 return $scope.hasOsrItem() || $scope.isRtoItemExist(); 10423 }; 10424 10425 $scope.onDistrictChange = function () { 10426 if ($scope.ShippingInfo.selectedDistrict) { 10427 $scope.schools = $scope.allSchools.filter(school => school.district_id === $scope.ShippingInfo.selectedDistrict.id); 10428 $scope.ShippingInfo.selectedSchool = null; 10429 } else { 10430 $scope.schools = []; 10431 $scope.ShippingInfo.selectedSchool = null; 10432 $scope.makeShippingAddressFieldsEditable(); 10433 } 10434 }; 10435 10436 $scope.makeShippingAddressFieldsEditable = function () { 10437 $scope.ShippingInfoPrefilledFlags = { 10438 Address1: false, 10439 Address2: false, 10440 City: false, 10441 State: false, 10442 Zip: false, 10443 Country: false 10444 }; 10445 }; 10446 10447 $scope.fillShippingAddressFromSchool = function (school) { 10448 if (!school) { 10449 return; 10450 } 10451 10452 $scope.ShippingInfo.Address1 = school.address || ''; 10453 $scope.ShippingInfoPrefilledFlags.Address1 = !!school.address; 10454 10455 $scope.ShippingInfo.Address2 = school.address2 || ''; 10456 $scope.ShippingInfoPrefilledFlags.Address2 = !!school.address2; 10457 10458 $scope.ShippingInfo.City = school.city || ''; 10459 $scope.ShippingInfoPrefilledFlags.City = !!school.city; 10460 10461 $scope.ShippingInfo.State = school.state || ''; 10462 $scope.ShippingInfoPrefilledFlags.State = !!school.state; 10463 10464 $scope.ShippingInfo.Zip = school.zip || ''; 10465 $scope.ShippingInfoPrefilledFlags.Zip = !!school.zip; 10466 10467 $scope.ShippingInfoPrefilledFlags.Country = !!school.country; 10468 10469 $scope.ShippingOptionsGet(); 10470 }; 10471 10472 $scope.$watch('ShippingInfo.selectedSchool', function (newSchool) { 10473 $scope.makeShippingAddressFieldsEditable(); 10474 if (newSchool && $scope.ShippingInfo.shipToSchool) { 10475 $scope.fillShippingAddressFromSchool(newSchool); 10476 } 10477 }); 10478 10479 $scope.$watch('ShippingInfo.shipToSchool', function () { 10480 $scope.ShippingInfo.selectedSchool = null; 10481 $scope.ShippingInfo.selectedDistrict = null; 10482 if ($scope.ShippingInfo.shipToSchool) { 10483 $scope.HideBillingSameAsShipping = true; 10484 $scope.BillingInfo.AddressSameAsShipping = false; 10485 } else { 10486 $scope.HideBillingSameAsShipping = false; 10487 $scope.BillingInfo.AddressSameAsShipping = true; 10488 } 10489 $scope.makeShippingAddressFieldsEditable(); 10490 }); 10491 10492 $scope.intervalPromise = $interval(function() { 10493 if (!!$scope.knobby && Object.keys($scope.knobby).length > 0 && window.vueEventHub && window.vueEventHub.$eventHub) { 10494 $scope.registerEventHubEvents(); 10495 $interval.cancel($scope.intervalPromise); 10496 } 10497 }, 1000); 10498 10499 $scope.setInsuranceInfoToLocalStorage = function () { 10500 var insuranceInfo = JSON.stringify( 10501 { 10502 isInsuranceSwitchedOn: $scope.isInsuranceSwitchedOn, 10503 isLuxsuranceSwitchedOn: $scope.isLuxsuranceSwitchedOn, 10504 contactJMInsuranceInfo: $scope.contactJMInsuranceInfo, 10505 confirmedJMInsurance: $scope.confirmedJMInsurance, 10506 } 10507 ); 10508 10509 window.localStorage.setItem('insurance_info', insuranceInfo); 10510 }; 10511 10512 $scope.setInsuranceInfo = function () { 10513 var insuranceInfo = window.localStorage.getItem('insurance_info'); 10514 if (!insuranceInfo || typeof insuranceInfo !== 'string') { 10515 return; 10516 } 10517 10518 insuranceInfo = JSON.parse(insuranceInfo) 10519 10520 $scope.isInsuranceSwitchedOn = insuranceInfo.isInsuranceSwitchedOn; 10521 $scope.isLuxsuranceSwitchedOn = insuranceInfo.isLuxsuranceSwitchedOn; 10522 $scope.contactJMInsuranceInfo = insuranceInfo.contactJMInsuranceInfo; 10523 $scope.confirmedJMInsurance = insuranceInfo.customerIdForJMInsurance; 10524 if (document.getElementById('isInsuranceSwitchedOn')) { 10525 document.getElementById('isInsuranceSwitchedOn').checked = $scope.isInsuranceSwitchedOn; 10526 } 10527 10528 if (document.getElementById('isLuxsuranceSwitchedOn')) { 10529 document.getElementById('isLuxsuranceSwitchedOn').checked = $scope.isLuxsuranceSwitchedOn; 10530 } 10531
10532 $scope.safeApply(); 10533 }; 10534 10535 $scope.registerEventHubEvents = function () { 10536 if ($scope.isFlagInsuranceEnabled() || $scope.isFlagLuxsuranceEnabled()) { 10537 window.vueEventHub.$eventHub.$on('jm-quote-contact-modal-cancel', function () { 10538 $scope.confirmedJMInsurance = false; 10539 if (!$scope.isFlagLuxsuranceEnabled() && !$scope.isFlagInsuranceEnabled()) { 10540 $scope.contactJMInsuranceInfo = {}; 10541 } 10542 }); 10543 10544 window.vueEventHub.$eventHub.$on('jm-quote-contact-modal-confirm', function (data) { 10545 $scope.contactJMInsuranceInfo = data.contactJMInsuranceInfo; 10546 $scope.confirmedJMInsurance = true; 10547 $scope.isInsuranceSwitchedOn = data.isInsuranceSwitchedOn; 10548 $scope.isLuxsuranceSwitchedOn = data.isLuxsuranceSwitchedOn; 10549 10550 if ($scope.isFlagSaveLuxsuranceInsuranceInfoAfterReloadingEnabled()) { 10551 $scope.setInsuranceInfoToLocalStorage(); 10552 } 10553 10554 if (document.getElementById('isInsuranceSwitchedOn')) { 10555 document.getElementById('isInsuranceSwitchedOn').checked = data.isInsuranceSwitchedOn; 10556 } 10557 10558 if (document.getElementById('isLuxsuranceSwitchedOn')) { 10559 document.getElementById('isLuxsuranceSwitchedOn').checked = data.isLuxsuranceSwitchedOn; 10560 } 10561 10562 $scope.safeApply(); 10563 }); 10564 } 10565 }; 10566 10567 $scope.getFormattedExpirationDate = function (date) { 10568 if (!date) { 10569 return ''; 10570 } 10571 10572 var month = date.slice(0, 2); 10573 var year = 2000 + parseInt(date.slice(2, 4), 10); 10574 10575 var formatter = new Intl.DateTimeFormat('en-US', { 10576 year: 'numeric', 10577 month: '2-digit', 10578 day: '2-digit' 10579 }); 10580 10581 return formatter.format(new Date(year, month, 0)); 10582 }; 10583 10584 $scope.onSelectSavedCard = function (card) { 10585 $scope.paymentMode = 'saved'; 10586 $scope.formData.selectedCustomerCardId = card.pos_customer_card_id; 10587 }; 10588 10589 $scope.isSavedCardsFlagEnabled = function () { 10590 return $scope.knobby && $scope.knobby.module_saved_cards_at_checkout; 10591 }; 10592 10593 $scope.getFormattedExp = function(card) { 10594 if (!card?.exp_month || !card?.exp_year) { 10595 return ''; 10596 } 10597
10598 const month = String(card.exp_month).padStart(2, '0'); 10599 10600 return `(${month}/${card.exp_year})`; 10601 }; 10602 10603 $scope.isSavedCardsAllowed = function () { 10604 return $scope.isSavedCardsFlagEnabled() 10605 && $scope.customerCards?.length 10606 && !$scope.paymentType 10607 && ($scope.isCardProcessorRain() || $scope.isCardProcessorQuiltAdyen()); 10608 }; 10609 10610 $scope.getCard = function () { 10611 return $scope.customerCards?.find(c => c.pos_customer_card_id === $scope.formData.selectedCustomerCardId) || []; 10612 }; 10613 10614 $scope.$on('$destroy', function() { 10615 $interval.cancel($scope.intervalPromise); 10616 }); 10617}]); 10618 10619checkoutApp.directive('capitalizeFirst', function ($parse) { 10620 return { 10621 require: 'ngModel', 10622 link: function ($scope, $element, $attrs, $ctrl) { 10623 var capitalize = function (inputValue) { 10624 if (!inputValue) { 10625 inputValue = ''; 10626 } 10627 10628 return inputValue.replace(/\w\S*/g, function (txt) { 10629 return txt.charAt(0).toUpperCase() + txt.substring(1); 10630 }); 10631 }; 10632 10633 // Handle changes from the user 10634 var listener = function () { 10635 var value = $element.val(); 10636 $element.val(capitalize(value)); 10637 }; 10638 $element.bind('change', listener); 10639 10640 // Handles changes from the model 10641 $ctrl.$formatters.unshift(function () { 10642 return capitalize($ctrl.$modelValue); 10643 }); 10644 } 10645 }; 10646}); 10647 10648checkoutApp.directive('paymentMethods', function () { 10649 var controller = ['$scope', function ($scope) { 10650 $scope.paymentMethods = { 10651 creditCard: 'cc', 10652 affirm: 'affirm', 10653 klarna: 'klarna', 10654 paypal: 'paypal', 10655 afterpay: 'afterpay', 10656 pickup: 'pickup' 10657 }; 10658 10659 $scope.paymentMethod = $scope.paymentMethods.creditCard; 10660 10661 $scope.isBNPLPaymentMethodEnabled = function (type) { 10662 if (type === $scope.paymentMethods.affirm && !$scope.isAffirmEnabled) { 10663 return false; 10664 } 10665 10666 if (type === $scope.paymentMethods.klarna && !$scope.isKlarnaEnabled) { 10667 return false; 10668 } 10669 10670 if (type === $scope.paymentMethods.afterpay && !$scope.isAfterpayEnabled) { 10671 return false; 10672 } 10673 10674 return $scope.isBNPLPaymentMethodMerchantCountrySupported(type); 10675 }; 10676 10677 $scope.isBNPLPaymentMethodMerchantCountrySupported = function (type) { 10678 if (!$scope.model) { 10679 return false; 10680 } 10681 10682 return $scope.model.getModel(type).isSupportedMerchantCountry($scope.merchantCountry); 10683 }; 10684 10685 $scope.isBNPLPaymentMethodAmountSupported = function (type) { 10686 if (!$scope.model) { 10687 return false; 10688 } 10689 10690 return $scope.model.getModel(type).isSupportedAmount($scope.amount, $scope.currency, $scope.country); 10691 }; 10692 10693 $scope.isBNPLPaymentMethodCurrencySupported = function (type) { 10694 if (!$scope.model) { 10695 return false; 10696 } 10697 10698 return $scope.model.getModel(type).isSupportedCurrency($scope.currency, $scope.country); 10699 }; 10700 10701 $scope.isBNPLPaymentMethodSupported = function (type) { 10702 if (!$scope.model) { 10703 return false; 10704 } 10705 10706 return $scope.model.getModel(type).isSupported($scope.amount, $scope.currency, $scope.country, $scope.merchantCountry); 10707 }; 10708 10709 $scope.supportedAmountsByBNPLPaymentMethod = function (type) { 10710 if (!$scope.model) { 10711 return null; 10712 } 10713 10714 var formatter = new Intl.NumberFormat('en-US', { 10715 style: 'currency', 10716 currency: $scope.currency, 10717 }); 10718 10719 var amounts = $scope.model.getModel(type).getSupportedAmounts($scope.country, $scope.currency); 10720 10721 if (!amounts) { 10722 return null; 10723 } 10724 10725 return { 10726 min: formatter.format(amounts.minAmount ? amounts.minAmount / 100 : 0), 10727 max: formatter.format(amounts.maxAmount ? amounts.maxAmount / 100 : 0), 10728 }; 10729 }; 10730 10731 $scope.checkoutOldCart = function () { 10732 if (!window?.knobby.module_update_to_captcha_v3) { 10733 $scope.CheckoutProcess(); 10734 10735 return; 10736 } 10737 10738 return $scope.checkCaptcha().then(function() { 10739 if (!$scope.showCaptcha) { 10740 return $scope.CheckoutProcess(); 10741 } 10742 }); 10743 }; 10744 10745 $scope.init = function () { 10746 $scope.$watch('paymentMethod', function (paymentMethod) { 10747 $scope.onPaymentMethodChanged(paymentMethod); 10748 }); 10749 }; 10750 10751 $scope.init(); 10752 10753 if (typeof $scope.onReady === 'function') { 10754 $scope.onReady(); 10755 } 10756 }]; 10757 10758 return { 10759 restrict: 'E', 10760 controller: controller, 10761 scope: { 10762 isKlarnaEnabled: '=', 10763 isAffirmEnabled: '=', 10764 isAfterpayEnabled: '=', 10765 isPaypalEnabled: '=', 10766 isPayAtPickUpEnabled: '=', 10767 amount: '=', 10768 due: '=', 10769 currency: '=', 10770 currencySymbol:'=', 10771 country: '=', 10772 merchantCountry: '=', 10773 model: '=', 10774 onPaymentMethodChanged: '=', 10775 onReady: '=', 10776 }, 10777 templateUrl: '/checkout/partial/paymentMethods.html', 10778 }; 10779}); 10780 10781checkoutApp.directive('billingAddress', function () { 10782 var controller = ['$scope', function ($scope) { 10783 $scope.billingAddressOptions = { 10784 same: 'same', 10785 different: 'different', 10786 }; 10787 $scope.billingAddressOption = $scope.billingAddressOptions.same;
10788 $scope.checkoutCtrl = $scope.$parent.$parent; 10789 10790 $scope.init = function () { 10791 $scope.$watch('billingAddressOption', function (billingAddressOption) { 10792 $scope.checkoutCtrl.BillingInfo.AddressSameAsShipping = billingAddressOption === $scope.billingAddressOptions.same; 10793 10794 if (!$scope.checkoutCtrl.StatusInfo.Initializing) { 10795 $scope.checkoutCtrl.BillingVerify(); 10796 } 10797 }); 10798 10799 $scope.$watch('showBillingAddress', function (showBillingAddress) { 10800 if (showBillingAddress) { 10801 $scope.billingAddressOption = $scope.billingAddressOptions.different; 10802 } 10803 }); 10804 }; 10805 10806 $scope.init(); 10807 }]; 10808 10809 return { 10810 restrict: 'E', 10811 controller: controller, 10812 scope: { 10813 showBillingAddress: '=', 10814 isShippingRequired: '=', 10815 }, 10816 templateUrl: '/checkout/partial/billingAddress.html', 10817 }; 10818}); 10819 10820checkoutApp.directive('phone', function () { 10821 var validator = function (phone) { 10822 if (!phone) { 10823 return true; 10824 } 10825 10826 // Simple for now, we may care to do better later 10827 var result = phone.match(/\d/g); 10828 10829 if (!result) { 10830 return false; 10831 } 10832 10833 result = result.length >= 10; 10834 10835 if (result) { 10836 result = !/([A-Za-z])/.test(phone); 10837 } 10838 10839 return result; 10840 }; 10841 10842 return { 10843 restrict: 'A', 10844 require: 'ngModel', 10845 link: function(scope, elem, attr, ngModel) { 10846 //For DOM -> model validation 10847 ngModel.$parsers.unshift(function(value) { 10848 var valid = validator(value); 10849 10850 ngModel.$setValidity('phone', valid); 10851 10852 return valid ? value : undefined; 10853 }); 10854 10855 //For model -> DOM validation 10856 ngModel.$formatters.unshift(function(value) { 10857 var valid = validator(value); 10858 10859 ngModel.$setValidity('phone', valid); 10860 10861 return value; 10862 }); 10863 } 10864 }; 10865}); 10866 10867checkoutApp.directive('streetAddress', function () { 10868 function shouldValidate(scope) { 10869 try { 10870 return !!( 10871 ( 10872 (scope.isQuiltAdyen && scope.isQuiltAdyen()) 10873 || (scope.isCardProcessorQuiltAdyen && scope.isCardProcessorQuiltAdyen()) 10874 ) 10875 && !(scope.isPickup && scope.isPickup()) 10876 ); 10877 } catch (e) { 10878 return false; 10879 } 10880 } 10881 10882 function isValidAddress(value) { 10883 if (value === null || typeof value === 'undefined') { 10884 return true; 10885 } 10886 10887 var s = String(value).trim(); 10888 if (!s) { 10889 return true; 10890 } 10891 10892 var lower = s.toLowerCase(); 10893 10894 if (lower.indexOf(' ') === -1) { 10895 var generic = ['street','st','avenue','ave','road','rd','boulevard','blvd','lane','ln','drive','dr','highway','hwy','way']; 10896 if (generic.indexOf(lower) !== -1) { 10897 return false; 10898 } 10899 } 10900 10901 if (!/\d/.test(s)) { 10902 return false; 10903 } 10904 10905 // Require at least one letter and one number (house number pattern). 10906 if (!/[A-Za-z]/.test(s)) { 10907 return false; 10908 } 10909 10910 if (/^\d+$/.test(s)) { 10911 return false; 10912 } 10913 10914 if (/^street\s+\d+$/i.test(s) || /^address\s*\d*$/i.test(s) || /^test\s*\d*$/i.test(s)) { 10915 return false; 10916 } 10917 10918 return true; 10919 } 10920 10921 return { 10922 restrict: 'A', 10923 require: 'ngModel', 10924 link: function (scope, elem, attr, ngModel) { 10925 function validate(value) { 10926 if (!shouldValidate(scope)) { 10927 ngModel.$setValidity('streetAddress', true); 10928 return value; 10929 } 10930 10931 var valid = isValidAddress(value); 10932 ngModel.$setValidity('streetAddress', valid); 10933 return value; 10934 } 10935 10936 ngModel.$parsers.unshift(validate); 10937 ngModel.$formatters.unshift(function (value) { 10938 validate(value); 10939 return value; 10940 }); 10941 } 10942 }; 10943}); 10944 10945checkoutApp.directive('phoneNumber', phoneNumberDirective); 10946 10947function phoneNumberDirective() { 10948 function getDomesticRegion(countries, domesticCountry) { 10949 var alpha3ToAlpha2 = { 10950 USA: "US", 10951 CAN: "CA", 10952 AUS: "AU", 10953 }; 10954 10955 var defaultRegion = Object.keys(countries).find(function (key) { 10956 return countries[key] === domesticCountry; 10957 }) || 'USA'; 10958 10959 return alpha3ToAlpha2[defaultRegion] || 'US'; 10960 } 10961 10962 function formatNumber(value, region) { 10963 if (!value) { 10964 return ''; 10965 } 10966 10967 try { 10968 var formatter = new libphonenumber.asYouType(region); 10969 10970 return formatter.input(value); 10971 } catch (e) { 10972 console.warn('Phone formatting failed:', e); 10973 10974 return value; 10975 } 10976 } 10977 10978 function sanitize(value) { 10979 if (!value) { 10980 return ''; 10981 } 10982 10983 value = value.replace(/[^0-9+]/g, ''); 10984 10985 if (value.indexOf('+') > 0) { 10986 value = value.replace(/\+/g, ''); 10987 } 10988 10989 return value; 10990 } 10991 10992 function isValidNumber(value, region) { 10993 try { 10994 var number = libphonenumber.parse(value, region); 10995 10996 return libphonenumber.isValidNumber(number); 10997 } catch (e) { 10998 return false;
10999 } 11000 } 11001 11002 function detectRegion(value, fallbackRegion) { 11003 try { 11004 var parsed = libphonenumber.parse(value); 11005 11006 if (parsed && parsed.country) { 11007 return parsed.country; 11008 } 11009 } catch (e) { 11010 console.warn('Region detect is failed:', e); 11011 } 11012 11013 return fallbackRegion; 11014 } 11015 11016 function updatePlaceholder(elem, region) { 11017 var dummy = '9999999999'; 11018 var formatType = 'International'; 11019 var placeholder; 11020 11021 if (region === 'AU') { 11022 dummy = '0412345678'; 11023 } else if (region === 'US') { 11024 formatType = 'National'; 11025 } 11026 11027 try { 11028 var formatter = new libphonenumber.asYouType(region); 11029 var parsed = libphonenumber.parseCustom(dummy, region, formatter.metadata); 11030 var formatted = libphonenumber.formatCustom(parsed, formatType, formatter.metadata); 11031 11032 if (formatType === 'National') { 11033 placeholder = formatted.replace(/\d/g, '_'); 11034 } else { 11035 placeholder = formatted.replace(/^(\+\d{1,3}\s*)(.*)$/, function(_, prefix, rest) { 11036 return prefix + rest.replace(/\d/g, '_'); 11037 }); 11038 } 11039 11040 elem.attr('placeholder', placeholder); 11041 } catch (e) { 11042 elem.attr('placeholder', '(___) ___-____'); 11043 } 11044 } 11045 11046 var validator = function (phone) { 11047 if (!phone) { 11048 return true; 11049 } 11050 11051 var result = phone.match(/\d/g); 11052 11053 if (!result) { 11054 return false; 11055 } 11056 11057 result = result.length >= 10; 11058 11059 if (result) { 11060 result = !/([A-Za-z])/.test(phone); 11061 } 11062 11063 return result; 11064 }; 11065 11066 return { 11067 restrict: 'A', 11068 require: 'ngModel', 11069 link: function(scope, elem, attr, ngModel) { 11070 var defaultRegion = 'US'; 11071 var currentRegion = defaultRegion; 11072 11073 var unwatch = scope.$watch('InitInfo', function(initInfo) { 11074 if (initInfo && initInfo.DomesticCountry) { 11075 defaultRegion = getDomesticRegion(initInfo.Countries, initInfo.DomesticCountry); 11076 currentRegion = defaultRegion; 11077 11078 updatePlaceholder(elem, currentRegion); 11079 11080 unwatch(); 11081 } 11082 }); 11083 11084 updatePlaceholder(elem, currentRegion); 11085 11086 ngModel.$formatters.push(function(value) { 11087 if (!value) { 11088 return ''; 11089 } 11090 11091 return formatNumber(value, currentRegion); 11092 }); 11093 11094 ngModel.$parsers.push(function(value) { 11095 return sanitize(value); 11096 }); 11097 11098 ngModel.$validators.phone = function (modelValue) { 11099 return validator(modelValue); 11100 }; 11101 11102 elem.on('keypress', function (event) { 11103 var char = String.fromCharCode(event.which); 11104 11105 if (/[0-9]/.test(char)) { 11106 return; 11107 } 11108 11109 if (char === '+' && elem[0].selectionStart === 0) { 11110 return; 11111 } 11112 11113 event.preventDefault(); 11114 }); 11115 11116 elem.on('paste', function (event) { 11117 event.preventDefault(); 11118 11119 var e = event.originalEvent || event; 11120 var pasted = (e.clipboardData || window.clipboardData).getData('text') || ''; 11121 var cleanPasted = sanitize(pasted); 11122 var input = elem[0]; 11123 var selStart = input.selectionStart; 11124 var selEnd = input.selectionEnd; 11125 var currentValue = elem.val(); 11126 var before = currentValue.slice(0, selStart); 11127 var after = currentValue.slice(selEnd); 11128 var clean = sanitize(before + cleanPasted + after); 11129 11130 currentRegion = detectRegion(clean, defaultRegion); 11131 updatePlaceholder(elem, currentRegion); 11132 11133 if (!isValidNumber(clean, currentRegion) && !clean.startsWith('+')) { 11134 try { 11135 before = '+' + before; 11136 clean = sanitize(before + cleanPasted + after); 11137 11138 currentRegion = detectRegion(clean, defaultRegion); 11139 updatePlaceholder(elem, currentRegion); 11140 } catch (e) {
11141 console.warn('Failed to prepend code:', e); 11142 } 11143 } 11144 11145 var formatted = formatNumber(clean, currentRegion); 11146 var digitsBeforeCaret = sanitize(before + cleanPasted).length; 11147 11148 scope.$applyAsync(function () { 11149 ngModel.$setViewValue(formatted); 11150 ngModel.$render(); 11151 11152 elem.val(formatted); 11153 11154 var caret = 0; 11155 var seen = 0; 11156 11157 while (caret < formatted.length) { 11158 if (/\d/.test(formatted[caret])) { 11159 seen++; 11160 if (seen === digitsBeforeCaret) { 11161 caret--; 11162 break; 11163 } 11164 } 11165 caret++; 11166 } 11167 11168 input.setSelectionRange(caret, caret); 11169 }); 11170 }); 11171 11172 elem.on('input', function(event) { 11173 var e = event.originalEvent || event; 11174 var raw = elem.val(); 11175 var clean = sanitize(raw); 11176 11177 currentRegion = detectRegion(clean, defaultRegion); 11178 updatePlaceholder(elem, currentRegion); 11179 11180 scope.$applyAsync(function() { 11181 ngModel.$setViewValue(clean); 11182 }); 11183 11184 if (clean) { 11185 var formatted = formatNumber(clean, currentRegion); 11186 11187 if (formatted !== raw) { 11188 var input = e.target; 11189 var digitsBeforeCaret = sanitize(raw.slice(0, input.selectionStart)).length; 11190 11191 elem.val(formatted); 11192 11193 var caret = 0; 11194 var seen = 0; 11195 11196 while (caret < formatted.length && seen < digitsBeforeCaret) { 11197 if (/\d/.test(formatted[caret])) { 11198 seen++; 11199 } 11200 caret++; 11201 } 11202 11203 input.setSelectionRange(caret, caret); 11204 } 11205 } else { 11206 elem.val(''); 11207 } 11208 }); 11209 11210 elem.on('blur', function() { 11211 scope.$applyAsync(function() { 11212 var clean = sanitize(ngModel.$modelValue); 11213 11214 if (!clean) { 11215 return; 11216 } 11217 11218 var formatted = formatNumber(clean, currentRegion); 11219 11220 ngModel.$setViewValue(formatted); 11221 ngModel.$render(); 11222 }); 11223 }); 11224 } 11225 }; 11226} 11227 11228var getQueryString = function (field, url) { 11229 var href = url ? url : window.location.href; 11230 if (href) { 11231 href = href.replace(/\+/g, ' '); 11232 } 11233 var reg = new RegExp('[?&]' + field + '=([^&#]*)', 'i'); 11234 var string = reg.exec(href); 11235 return string ? decodeURIComponent(string[1]) : null; 11236}; 11237 11238var CouponConstants = { 11239 coupon_dollar_off: "dollar_off", 11240 coupon_percent: "percent_off", 11241 coupon_free_shipping: "free_shipping_off", 11242 coupon_regular: "regular", 11243 coupon_bogo: "bogo", 11244 better_coupon_type_free_shipping: "shipping", 11245 11246 use_single: 'single', 11247 use_multiple: 'multiple', 11248 use_per_customer: 'per_customer', 11249}; 11250 11251var BetterCouponFactory = function (dataForCoupon) { 11252 "use strict"; 11253 11254 this.dataForCoupon = dataForCoupon; 11255 11256 this.createCoupon = function () { 11257 var coupon = {}; 11258 11259 if (this.dataForCoupon.couponBaseType === CouponConstants.better_coupon_type_free_shipping) { 11260 coupon = new FreeShippingCoupon(this.dataForCoupon); 11261 } else if (this.dataForCoupon.couponBaseType === CouponConstants.coupon_regular) { 11262 coupon = new RegularCoupon(this.dataForCoupon); 11263 } else if (this.dataForCoupon.couponBaseType === CouponConstants.coupon_bogo) { 11264 coupon = new BOGOCoupon(this.dataForCoupon); 11265 } else { 11266 throw new Error("Cannot identify type of coupon"); 11267 } 11268 11269 return coupon; 11270 } 11271}; 11272 11273/**
11274 * Abstract class for coupons 11275 * @abstract 11276 */ 11277var BaseBetterCoupon = function (dataForCoupon) { 11278 "use strict"; 11279 11280 if (new.target === BaseBetterCoupon) { 11281 throw new Error("Cannot instantiate abstract class BaseBetterCoupon directly"); 11282 } 11283 11284 this.couponId = dataForCoupon.couponId || dataForCoupon.id || ''; 11285 this.couponCode = dataForCoupon.couponCode || ''; 11286 this.couponBaseType = dataForCoupon.couponBaseType || 'none'; 11287 this.couponType = dataForCoupon.couponType || 'none'; 11288 this.discount = dataForCoupon.discount || 0; 11289 this.dollarOffAmount = dataForCoupon.dollarOffAmount || 0; 11290 this.endDate = dataForCoupon.endDate || null; 11291 this.maxDiscount = dataForCoupon.maxDiscount || 0; 11292 this.minDollarAmount = dataForCoupon.minDollarAmount || 0; 11293 this.number = dataForCoupon.number || 0; 11294 this.percentOff = dataForCoupon.percentOff || null; 11295 this.title = dataForCoupon.title || 'Base Better Coupon'; 11296 this.use = dataForCoupon.use || null; 11297 11298 this.appliedLineUuid = []; 11299 this.totalDiscountableItemsValue = 0; 11300 11301 this.limitedCustomerGroupList = dataForCoupon.isLimitedToCustomerGroups ? dataForCoupon.customerGroupIdList : []; 11302 this.categoryIdArray = dataForCoupon.categoryIdArray || []; 11303 this.itemTypesArray = dataForCoupon.itemTypesArray || []; 11304 this.departmentIdArray = dataForCoupon.departmentIdArray || []; 11305 this.manufacturerNameArray = dataForCoupon.manufacturerNameArray || []; 11306 this.pslrIdArray = dataForCoupon.pslrIdArray || []; 11307 this.classIdArray = dataForCoupon.classIdArray || []; 11308 this.vendorIdArray = dataForCoupon.vendorIdArray || []; 11309 11310 this.selectedTiers = []; 11311 11312 this.setAmountsBasedOnTiers = function (spentMoneyValue) { 11313 } 11314 11315 this.setAmounts = function (amounts) { 11316 this.minDollarAmount = amounts.minDollarAmount || 0; 11317 this.percentOff = amounts.percentOff || 0; 11318 this.dollarOffAmount = amounts.dollarOffAmount || 0; 11319 } 11320 11321 this.getSortedSelectedTiers = function () { 11322 return this.selectedTiers.slice().sort((a, b) => a.title.localeCompare(b.title)); 11323 } 11324 11325 this.notMetMinimumAmount = function () { 11326 this.notEligible = true; 11327 this.discount = 0; 11328 } 11329 11330 this.resetNotEligible = function () { 11331 this.notEligible = false; 11332 } 11333 11334 this.allowedMaxDiscount = function () { 11335 return this.maxDiscount && this.maxDiscount > 0; 11336 } 11337 11338 if (!!window.knobby && window.knobby['module_better_coupons'] && (dataForCoupon.tiers || []).length > 0) { 11339 this.setAmounts({}); 11340 } 11341}; 11342 11343var RegularCoupon = function (dataForCoupon) { 11344 "use strict"; 11345 BaseBetterCoupon.call(this, dataForCoupon); 11346 11347 this.tiers = dataForCoupon.tiers || []; 11348 11349 this.setAmountsBasedOnTiers = function (spentMoneyValue) { 11350 var tier = this.tiers[0] || {}; 11351 11352 if (this.tiers.length > 1) { 11353 var that = this; 11354 var minDistance = Infinity; 11355 11356 //min tier in case we dont have suitable tier 11357 tier = this.tiers.reduce(function (minObj, currentObj) { 11358 if (that.couponType === CouponConstants.coupon_percent) { 11359 return (currentObj.min_purchase < minObj.min_purchase) ? currentObj : minObj; 11360 } else if (that.couponType === CouponConstants.coupon_dollar_off) { 11361 return (currentObj.spend_amount < minObj.spend_amount) ? currentObj : minObj; 11362 } 11363 }); 11364 11365 //select suitable tier 11366 this.tiers.forEach(function (closestTier) { 11367 var comparingValue; 11368 if (that.couponType === CouponConstants.coupon_percent) { 11369 comparingValue = closestTier.min_purchase; 11370 } else if (that.couponType === CouponConstants.coupon_dollar_off) { 11371 comparingValue = closestTier.spend_amount; 11372 } 11373 11374 if (spentMoneyValue >= comparingValue) { 11375 const distance = spentMoneyValue - comparingValue; 11376 if (distance < minDistance) { 11377 minDistance = distance; 11378 tier = closestTier; 11379 } 11380 } 11381 }); 11382 } 11383 11384 this.selectedTiers = [tier]; 11385 11386 if (this.couponType === CouponConstants.coupon_percent) { 11387 this.setAmounts({percentOff: tier.percent_off || 0, minDollarAmount: tier.min_purchase || 0}); 11388 } else if (this.couponType === CouponConstants.coupon_dollar_off) { 11389 this.setAmounts({dollarOffAmount: tier.amount_off || 0, minDollarAmount: tier.spend_amount || 0}); 11390 } 11391 } 11392 11393 this.allowedMaxDiscount = function () { 11394 return this.couponType === CouponConstants.coupon_percent && this.maxDiscount && this.maxDiscount > 0; 11395 } 11396}; 11397 11398var BOGOCoupon = function (dataForCoupon) { 11399 "use strict"; 11400 BaseBetterCoupon.call(this, dataForCoupon); 11401 11402 this.tiers = dataForCoupon.tiers || []; 11403 this.isBogoMultipleTimes = dataForCoupon.isBogoMultipleTimes || false;
11404 this.limitedBogoLines = {}; 11405 this.suitableTiers = {}; 11406 11407 this.setAmountsBasedOnTiers = function (objectLinesAndAmounts) { 11408 var that = this; 11409 var lines = objectLinesAndAmounts.lines; 11410 var disableCouponsForSalePriceItems = objectLinesAndAmounts.disableCouponsForSalePriceItems || false; 11411 this.resetNotEligible(); 11412 this.resetBogoLinesAndSuitableTiers(); 11413 11414 lines = lines.filter(function (line) { 11415 return line.type !== 'gc' && (line.discount_eligible === true || line.discount_eligible === 'true'); 11416 }); 11417 11418 if (disableCouponsForSalePriceItems) { 11419 lines = lines.filter(function (line) { 11420 return !line.on_sale; 11421 }); 11422 } 11423 11424 if (((this.pslrIdArray && this.pslrIdArray.length) || (this.classIdArray && this.classIdArray.length))) { 11425 var pslrLines = lines.filter(function (line) { 11426 return that.pslrIdArray.map(Number).includes(line.id); 11427 }); 11428 var classLines = lines.filter(function (line) { 11429 if (!!window.knobby && window.knobby['module_fix_web_class_coupon_id_matching']) { 11430 return that.classIdArray.map(Number).includes(Number(line.quiltClassId)); 11431 } 11432 return that.classIdArray.map(Number).includes(line.id); 11433 }); 11434 lines = pslrLines.concat(classLines); 11435 } else if (this.departmentIdArray && this.departmentIdArray.length) { 11436 lines = lines.filter(function (line) { 11437 return that.departmentIdArray.includes(line.departmentId); 11438 }); 11439 } else if (this.itemTypesArray && this.itemTypesArray.length) { 11440 lines = lines.filter(function (line) { 11441 var itemType; 11442 if (line.product_id) { 11443 itemType = 'products'; 11444 } else if (line.type === 'class') { 11445 itemType = 'classes'; 11446 } 11447 11448 return (that.itemTypesArray.includes(itemType)) && !line.package_id; 11449 }); 11450 } else if (this.categoryIdArray && this.categoryIdArray.length) { 11451 lines = lines.filter(function (line) { 11452 return line.categoryIdArray.some(function (categoryId) { 11453 return that.categoryIdArray.includes(categoryId); 11454 }); 11455 }); 11456 } else if (this.vendorIdArray && this.vendorIdArray.length) { 11457 lines = lines.filter(function (line) { 11458 return that.vendorIdArray.map(Number).includes(line.pos_vendor_id); 11459 }); 11460 } else if (this.manufacturerNameArray && this.manufacturerNameArray.length) { 11461 lines = lines.filter(function (line) { 11462 return that.manufacturerNameArray.includes(line.manufacturer_name); 11463 }); 11464 } 11465 11466 const productAmounts = lines.reduce(function (acc, line) { 11467 const { product_id, qty } = line; 11468 11469 if (!acc[product_id]) { 11470 acc[product_id] = { product_id, qty: 0 }; 11471 } 11472 11473 acc[product_id].qty += qty; 11474 11475 return acc; 11476 }, {}); 11477 11478 Object.values(productAmounts).forEach(function (product) { 11479 product.suitableBogoTiers = this.getSuitableBogoTiers(product, lines); 11480 }, this); 11481 11482 const allSuitableTiersAreEmpty = Object.values(productAmounts).every(function (product) { 11483 return product.suitableBogoTiers && !product.suitableBogoTiers.length 11484 }); 11485 11486 if (allSuitableTiersAreEmpty) { 11487 this.notMetMinimumAmount(); 11488 this.setAmounts({minDollarAmount: 0, percentOff: 0, dollarOffAmount: 0}); 11489 } 11490 11491 Object.values(productAmounts).forEach(function (product) { 11492 Object.values(product.suitableBogoTiers).forEach(function (suitableBogoTier) { 11493 const tierId = suitableBogoTier.tier.coupon_tiers_id; 11494 11495 this.suitableTiers[tierId] = this.suitableTiers[tierId] || { 11496 actualTotalDiscountCalculated: 0, 11497 totalDiscountToCompare: 0, 11498 tier: null, 11499 tierDiscounts: {} 11500 }; 11501 11502 this.suitableTiers[tierId].totalDiscountToCompare += suitableBogoTier.totalDiscountToCompare; 11503 this.suitableTiers[tierId].tier = suitableBogoTier.tier; 11504 this.suitableTiers[tierId].tierDiscounts = Object.assign(this.suitableTiers[tierId].tierDiscounts, suitableBogoTier.tierDiscounts); 11505
11506 this.limitedBogoLines = Object.assign(this.limitedBogoLines, suitableBogoTier.tierDiscounts); 11507 11508 this.selectedTiers.push(suitableBogoTier.tier); 11509 }, this); 11510 }, this); 11511 11512 const firstSuitableTier = Object.values(this.suitableTiers)[0] || {}; 11513 11514 var couponAmountOff = 0; 11515 11516 for (var uuid in this.limitedBogoLines) { 11517 if (this.limitedBogoLines.hasOwnProperty(uuid)) { 11518 var limitedBogoLine = this.limitedBogoLines[uuid]; 11519 11520 var lineByUUid = lines.find(function (line) { 11521 return line.uuid === uuid; 11522 }) || null; 11523 11524 if (lineByUUid) { 11525 if (this.couponType === CouponConstants.coupon_percent) { 11526 couponAmountOff += (lineByUUid.price * limitedBogoLine.discountableItemsAmount * (limitedBogoLine.bogoLinePercentOff/100)); 11527 } else if (this.couponType === CouponConstants.coupon_dollar_off) { 11528 if (lineByUUid.price > limitedBogoLine.bogoLineAmountOff) { 11529 couponAmountOff += limitedBogoLine.bogoLineAmountOff * limitedBogoLine.discountableItemsAmount; 11530 } else { 11531 couponAmountOff += lineByUUid.price * limitedBogoLine.discountableItemsAmount; 11532 } 11533 } 11534 } 11535 } 11536 } 11537 11538 if (this.couponType === CouponConstants.coupon_percent) { 11539 this.setAmounts({dollarOffAmount: couponAmountOff, percentOff: (firstSuitableTier.tier && firstSuitableTier.tier.percent_off) ? firstSuitableTier.tier.percent_off : 0, minDollarAmount: 0}); 11540 } else if (this.couponType === CouponConstants.coupon_dollar_off) { 11541 this.setAmounts({dollarOffAmount: couponAmountOff, minDollarAmount : 0}); 11542 } 11543 } 11544 11545 this.getSuitableBogoTiers = function ({product_id, qty}, lines) { 11546 const suitableTier = this.tiers.reduce(function (closestTier, tier) { 11547 var totalQty = parseFloat(tier.buy_qty) + parseFloat(tier.get_qty); 11548 var difference = qty - totalQty; 11549 11550 if (difference >= 0 && (closestTier === null || (difference >= 0 && difference < closestTier.difference))) { 11551 return { tier, difference }; 11552 } 11553 11554 return closestTier; 11555 }, null); 11556 11557 const suitableTiersForProductByQuantity = suitableTier ? [suitableTier.tier] : []; 11558 11559 suitableTiersForProductByQuantity.forEach(function (t) { 11560 var totalQty = parseFloat(t.buy_qty) + parseFloat(t.get_qty); 11561 var multiplier = Math.floor(qty / totalQty); 11562 if (this.isBogoMultipleTimes) { 11563 t.computed_get_qty = t.get_qty * multiplier; 11564 } else { 11565 t.computed_get_qty = t.get_qty; 11566 } 11567 }, this); 11568 11569 var tierDiscountsToComparing = []; 11570 Object.values(suitableTiersForProductByQuantity).forEach(function (suitableTier) { 11571 tierDiscountsToComparing.push(this.getBogoTierDiscountAmountsByTiers(lines, suitableTier, product_id)); 11572 }, this); 11573 11574 return tierDiscountsToComparing.filter(function (itemToCompare) { 11575 return itemToCompare.totalDiscountToCompare > 0; 11576 }); 11577 } 11578 11579 this.getBogoTierDiscountAmountsByTiers = function (lines, tier, product_id) { 11580 const { computed_get_qty, percent_off, amount_off, coupon_tiers_id} = tier; 11581 11582 var filteredLines = lines.filter(function (line) { 11583 return line.product_id === product_id; 11584 }); 11585 11586 filteredLines.sort(function (a, b) { 11587 return ( 11588 // a.qty - b.qty || 11589 a.price - b.price || 11590 a.product_store_location_rel_id - b.product_store_location_rel_id 11591 ); 11592 }); 11593 11594 var remainingQty = computed_get_qty; 11595 var totalDiscountToCompare = 0; 11596 var tierDiscounts = {}; 11597 11598 for (let line of filteredLines) { 11599 if (!tierDiscounts[line.uuid]) { 11600 tierDiscounts[line.uuid] = {bogoLinePercentOff: 0, bogoLineAmountOff: 0, discountableItemsAmount: 0, couponTiersId: coupon_tiers_id}; 11601 } 11602 11603 var qtyCounter = 0; 11604 11605 while (remainingQty > 0) { 11606 if (line.qty > 0 && qtyCounter !== line.qty && remainingQty > 0) { 11607 qtyCounter += 1; 11608 remainingQty -= 1; 11609 11610 var discountForUnit = 0; 11611 if (this.couponType === CouponConstants.coupon_percent) { 11612 discountForUnit = line.price * (percent_off / 100); 11613 tierDiscounts[line.uuid]['bogoLinePercentOff'] = percent_off; 11614 } else { 11615 tierDiscounts[line.uuid]['bogoLineAmountOff'] = discountForUnit = parseFloat(amount_off); 11616 } 11617 11618 tierDiscounts[line.uuid]['discountableItemsAmount']++; 11619 11620 totalDiscountToCompare += discountForUnit; 11621 } else { 11622 break; 11623 } 11624 } 11625 } 11626 11627 tierDiscounts = Object.fromEntries( 11628 Object.entries(tierDiscounts).filter(function(entry) { 11629 var value = entry[1]; 11630 return value.bogoLinePercentOff !== 0 || value.bogoLineAmountOff !== 0; 11631 }) 11632 ); 11633 11634 return { totalDiscountToCompare, tierDiscounts, tier}; 11635 } 11636 11637 this.resetBogoLinesAndSuitableTiers = function () {
11638 this.limitedBogoLines = {}; 11639 this.suitableTiers = {}; 11640 this.selectedTiers = []; 11641 } 11642 11643 this.getSortedSelectedTiers = function () { 11644 return Object.values(this.selectedTiers).slice().filter(function(item){ 11645 var suitable = this.suitableTiers[item.coupon_tiers_id]; 11646 if (suitable && suitable.actualTotalDiscountCalculated > 0) { 11647 return item; 11648 } 11649 11650 return false; 11651 }, this).sort(function (a, b) { return a.title.localeCompare(b.title); }); 11652 } 11653}; 11654 11655var FreeShippingCoupon = function (dataForCoupon) { 11656 "use strict"; 11657 BaseBetterCoupon.call(this, dataForCoupon); 11658 11659 this.setAmountsBasedOnTiers = function (spentMoneyValue) { 11660 this.setAmounts({percentOff: 100, minDollarAmount: this.minDollarAmount}); 11661 } 11662}; 11663 11664//extending coupons 11665FreeShippingCoupon.prototype = Object.create(BaseBetterCoupon.prototype); 11666FreeShippingCoupon.prototype.constructor = FreeShippingCoupon; 11667 11668RegularCoupon.prototype = Object.create(BaseBetterCoupon.prototype); 11669RegularCoupon.prototype.constructor = RegularCoupon; 11670 11671BOGOCoupon.prototype = Object.create(BaseBetterCoupon.prototype); 11672BOGOCoupon.prototype.constructor = BOGOCoupon;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.