981 lines
39 KiB
Vue
981 lines
39 KiB
Vue
<template>
|
|
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
|
<loadingModal notFullScreen ref="loadingModal" />
|
|
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
|
<div class="container payment-method">
|
|
<div class="row">
|
|
<div class="col-12 col-md-10 col-lg-8 col-xl-7">
|
|
<funnelSubHeader class="mb-5" cmsWidgetName="FunnelSubHeaderWidget" alignLeft />
|
|
|
|
<hr class="my-0" />
|
|
|
|
<reviewDropdown
|
|
class="appt-details-snapshot"
|
|
ref="reviewDropdown"
|
|
:apptWordingText="AppointmentWordingText" />
|
|
|
|
<hr class="my-0" />
|
|
|
|
<cart
|
|
ref="cart"
|
|
:damage="damageInfo"
|
|
:availableVaps="availableVaps"
|
|
:allowItemRemoval="true"
|
|
v-model="lineItems"
|
|
pageName="payment-method"
|
|
servicePackageOptionsCmsName="ServicePackageTitle"
|
|
recyclingModalCmsWidgetName="RecycleModal"
|
|
:isInsurance="isInsurance"
|
|
:insuranceDeductible="currentDeductible"
|
|
:insuranceCompanyName="insuranceCompanyName"
|
|
:showInsuranceCoverageAs="showInsuranceCoverageAs"
|
|
:shouldHideRecalibration="shouldHideRecalibration"
|
|
:isItac="isItac"
|
|
:isNoComp="isNoComp"
|
|
:isExpandedOnLoad="false"
|
|
:isMSRFeeApplicable="isMSRFeeApplicable" />
|
|
|
|
<afterpayBreakout
|
|
v-if="isAfterpayBreakoutDisplay"
|
|
cmsWidgetName="AfterpayBreakoutAlertWidget"
|
|
:totalAmount="totalAmountDue" />
|
|
<hr />
|
|
|
|
<div>
|
|
<alert
|
|
ref="piaErrorAlert"
|
|
class="my-4"
|
|
v-if="shouldDisplayPiaAlert"
|
|
cmsWidgetName="PIAErrorAlertWidget"
|
|
alertClass="alert-danger"
|
|
v-bind:isDismissible="false" />
|
|
</div>
|
|
|
|
<div
|
|
v-if="isRecalibrationOnOrder && shouldHideRecalibration"
|
|
class="questions-about-service mt-5">
|
|
<textBlock
|
|
cmsWidgetName="QuestionsAboutYourServiceWidget"
|
|
justifyText="left"
|
|
class="service-questions" />
|
|
<textBlock cmsWidgetName="CallOrTextWidget" justifyText="left" />
|
|
<hr class="my-5" />
|
|
<div class="d-flex flex-column checkbox-group">
|
|
<Field
|
|
name="recalAckOptIn"
|
|
:rules="'recal-ack-required'"
|
|
v-model="isRecalAckOptIn"
|
|
v-slot="{ meta, errors }">
|
|
<checkboxQuestion
|
|
cmsWidgetName="RecalConfirmWidget"
|
|
class="mb-3"
|
|
isRequired="true"
|
|
v-model="isRecalAckOptIn"
|
|
validationRules="recal-ack-required"
|
|
checkboxName="recalAckOptIn"
|
|
:class="{ 'is-invalid': meta.touched && errors.length > 0 }"
|
|
@text-link-clicked="openModal('RecalModal')" />
|
|
</Field>
|
|
<div id="form-test-error" class="row form-test-error">
|
|
<error-message
|
|
role="comment"
|
|
aria-atomic="true"
|
|
aria-live="polite"
|
|
class="small mt-1 text-danger d-block"
|
|
:name="'recalAckOptIn'"></error-message>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<paymentMethodQuestion
|
|
v-if="showPaymentMethodQuestions"
|
|
v-model="paymentMethodInternalModel"
|
|
:showApplePay="showApplePay"
|
|
:isInsurance="isInsurance"
|
|
validationRules="payment-method-required" />
|
|
|
|
<navbar
|
|
cmsWidgetName="FunnelFooterWidget"
|
|
ref="navbar"
|
|
:isForwardActionDisabled="!meta.valid"
|
|
:isBackButtonHidden="shouldHideBackButton"
|
|
buttonSize
|
|
@back-clicked="backButtonAction"
|
|
@ForwardClicked="forwardButtonAction" />
|
|
|
|
<contentGroupModal
|
|
ref="RecalModal"
|
|
cmsWidgetName="RecalModal"
|
|
class="recal-modal"
|
|
isRecal />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Form>
|
|
</template>
|
|
|
|
<script>
|
|
// Components
|
|
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
|
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
|
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
|
import paymentMethodQuestion from "@/layouts/payment-method/payment-method-question/payment-method-question";
|
|
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
|
|
import cart from "@/fmg-components/cart/cart";
|
|
import reviewDropdown from "@/layouts/payment-method/review-dropdown/review-dropdown";
|
|
import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js";
|
|
import alert from "@/ux-components/alert/alert";
|
|
import textBlock from "@/digital-components/text-block/text-block";
|
|
import checkboxQuestion from "@/digital-components/checkbox-question/checkbox-question";
|
|
import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal";
|
|
import afterpayBreakout from "@/layouts/payment-method/afterpay-breakout/afterpay-breakout";
|
|
|
|
// Supporting Items
|
|
import baseMixin from "@/mixins/base-mixin.js";
|
|
import { settleAllPromises } from "@/helpers/layout-helper";
|
|
import { storeActions } from "@/constants/store-actions";
|
|
import store from "@/store";
|
|
import {
|
|
fetchCmsContentForPage,
|
|
doesCopyContainRouterLink,
|
|
getRouterLinkRouteFromCopy,
|
|
} from "@/helpers/cms-content-helper";
|
|
import { paymentMethods } from "@/constants/payment-method-constants";
|
|
import { experimentSettings } from "@/constants/experiments";
|
|
import {
|
|
revalidatePromosAndValidateQueryStringPromo,
|
|
buildToastMessagesFromRevalidateOrValidatePromoResponse,
|
|
getVapsThatNeedToBeAddedToSatisfyPromos,
|
|
getPromoCodeWithoutBundleIdentifier,
|
|
removeCurrentlyActivePromoCodesFromInactivePromos,
|
|
createPromoSuccessAlert,
|
|
createPromoErrorAlert,
|
|
getNewlyInactivatedPromos,
|
|
} from "@/helpers/promotions-helper";
|
|
import { queryStrings } from "@/constants/query-strings";
|
|
import { getQuerystringParameter } from "@/helpers/querystring-helper";
|
|
import { deepClone } from "@/helpers/object-helper";
|
|
|
|
import { Form } from "vee-validate";
|
|
import { defineRule } from "vee-validate";
|
|
import { required } from "@/helpers/validation-rules";
|
|
import { errorMessages } from "@/constants/error-messages";
|
|
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
|
|
import { partTypeStrings } from "@/constants/part-type-strings";
|
|
import { mapTaxedLineItemsToStoreFormat } from "../../store";
|
|
import { coverageStatus } from "@/constants/insurance";
|
|
import { containsLineItemWithPartType } from "@/helpers/service-package-helper";
|
|
import { containsRecalParts } from "@/helpers/recal-helper";
|
|
import { getBoolFromString } from "@/helpers/boolean-helper";
|
|
import {
|
|
getDisplayAmountDue,
|
|
getAmountDue,
|
|
getSubTotal,
|
|
getSalesTax,
|
|
} from "@/helpers/pricing-helper.js";
|
|
import { consumeQueryFromStash } from "@/router/methods/helpers/querystring-stash";
|
|
import { debugLog } from "@/helpers/debug-log-helper";
|
|
import { ErrorMessage } from "vee-validate";
|
|
import { Field } from "vee-validate";
|
|
|
|
defineRule("payment-method-required", required(errorMessages.OPTION_REQUIRED));
|
|
defineRule("recal-ack-required", required(errorMessages.RECAL_ACK_REQUIRED));
|
|
|
|
export default {
|
|
name: "paymentMethod",
|
|
props: {
|
|
recyclingModalCmsWidgetName: String,
|
|
},
|
|
async beforeRouteEnter(to, from, next) {
|
|
// Call APIs
|
|
const cmsContentPromise = fetchCmsContentForPage(to.name);
|
|
|
|
const lineItemsFromStore = deepClone(store.getters.order.lineItems);
|
|
|
|
const frontWipersOnOrder =
|
|
lineItemsFromStore.vaps.filter(
|
|
(wiper) => wiper.partType == partTypeStrings.FRONT_WIPER
|
|
) ?? [];
|
|
|
|
const rearWipersOnOrder =
|
|
lineItemsFromStore.vaps.filter(
|
|
(wiper) => wiper.partType == partTypeStrings.REAR_WIPER
|
|
) ?? [];
|
|
|
|
const orderHasFrontWipers = frontWipersOnOrder.length > 0;
|
|
const orderHasRearWipers = rearWipersOnOrder.length > 0;
|
|
|
|
const wipersPromise =
|
|
!orderHasFrontWipers || !orderHasRearWipers
|
|
? baseMixin.methods.dispatchStoreActionWithLogging(
|
|
storeActions.GET_WIPERS,
|
|
{
|
|
serviceZipCode: store.getters.order.serviceLocation.zipCode,
|
|
carId: store.getters.vehicle.carId,
|
|
},
|
|
"payment-method"
|
|
)
|
|
: Promise.resolve([]);
|
|
|
|
const rainDefensePromise = baseMixin.methods.dispatchStoreActionWithLogging(
|
|
storeActions.GET_RAIN_DEFENSE,
|
|
null,
|
|
"payment-method"
|
|
);
|
|
|
|
// const reviewDropdownPromise = reviewDropdown.methods.loadInitialData();
|
|
// (removed temporarily for Heritage parity effort)
|
|
|
|
const promiseResultMap = [
|
|
{
|
|
resultKey: "cmsContent",
|
|
promise: cmsContentPromise,
|
|
},
|
|
{
|
|
resultKey: "wipers",
|
|
promise: wipersPromise,
|
|
},
|
|
{
|
|
resultKey: "rainDefense",
|
|
promise: rainDefensePromise,
|
|
},
|
|
// {
|
|
// resultKey: "reviewDropdownData",
|
|
// promise: reviewDropdownPromise,
|
|
// },
|
|
// (removed temporarily for Heritage parity effort)
|
|
];
|
|
|
|
const resultMap = await settleAllPromises(promiseResultMap);
|
|
|
|
const glassParts = lineItemsFromStore.glassParts ?? [];
|
|
const supportingItems = lineItemsFromStore.supportingItems ?? [];
|
|
const vaps = lineItemsFromStore.vaps ?? [];
|
|
|
|
// if the order already has wipers on it from the quote page, use those as the available wipers
|
|
// instead of what comes from the backend. This is to prevent issues with part interchange.
|
|
const availableFrontWipers = orderHasFrontWipers
|
|
? frontWipersOnOrder
|
|
: (resultMap.wipers.filter((wiper) => wiper.partType == partTypeStrings.FRONT_WIPER) ??
|
|
[]);
|
|
const availableRearWipers = orderHasRearWipers
|
|
? rearWipersOnOrder
|
|
: (resultMap.wipers.filter((wiper) => wiper.partType == partTypeStrings.REAR_WIPER) ??
|
|
[]);
|
|
|
|
const lineItemsToTax = [
|
|
resultMap.rainDefense,
|
|
...supportingItems,
|
|
...availableFrontWipers,
|
|
...availableRearWipers,
|
|
...glassParts,
|
|
...vaps,
|
|
];
|
|
|
|
const availableVaps = [
|
|
resultMap.rainDefense,
|
|
...availableFrontWipers,
|
|
...availableRearWipers,
|
|
];
|
|
|
|
const pricedLineItemsToTax = await baseMixin.methods.dispatchStoreActionWithLogging(
|
|
storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
|
{
|
|
availableLineItems: lineItemsToTax,
|
|
},
|
|
"payment-method",
|
|
false
|
|
);
|
|
|
|
// Promo logic
|
|
// Populate the previous state of promos for toast message usage in "next()"
|
|
const oldActivePromos = store.getters.lineItems.promos?.slice(0);
|
|
const oldInactivePromos = store.getters.order.payment.inactivePromos?.slice(0);
|
|
|
|
const promoCodeFromQueryString = consumeQueryFromStash(queryStrings.PROMO);
|
|
const { validatePromoResponse, revalidatePromoResponse } =
|
|
await revalidatePromosAndValidateQueryStringPromo(
|
|
promoCodeFromQueryString,
|
|
pricedLineItemsToTax,
|
|
"payment-method"
|
|
);
|
|
|
|
// Add newly validated promos to the array to get taxed
|
|
const newValidatedPromos = validatePromoResponse?.orderPromos ?? [];
|
|
newValidatedPromos.push(
|
|
...(revalidatePromoResponse ? revalidatePromoResponse.promoLineItems : [])
|
|
);
|
|
pricedLineItemsToTax.push(...newValidatedPromos);
|
|
// End of promo logic
|
|
|
|
const taxedLineItems = await baseMixin.methods.dispatchStoreActionWithLogging(
|
|
storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
|
{
|
|
billToAccountNumber: store.getters.payment.billToAccountNumber,
|
|
providerNumber: store.getters.order.serviceLocation.provider.providerNumber,
|
|
appointmentType: store.getters.order.serviceLocation.appointmentType,
|
|
serviceLocationCity: store.getters.order.serviceLocation.city,
|
|
serviceLocationState: store.getters.order.serviceLocation.state,
|
|
serviceLocationZipCode: store.getters.order.serviceLocation.zipCode,
|
|
pricedLineItems: pricedLineItemsToTax,
|
|
},
|
|
"payment-method",
|
|
false
|
|
);
|
|
|
|
// Match all line items to the line items as they are in the store
|
|
// and rebuild the original structure.
|
|
const lineItems = mapTaxedLineItemsToStoreFormat(taxedLineItems, lineItemsFromStore);
|
|
const taxedVaps = mapTaxedLineItemsToStoreFormat(taxedLineItems, availableVaps);
|
|
|
|
lineItems.promos = newValidatedPromos ?? [];
|
|
const vapsToAddToCart = getVapsThatNeedToBeAddedToSatisfyPromos(
|
|
newValidatedPromos,
|
|
taxedVaps,
|
|
lineItems
|
|
);
|
|
lineItems.vaps = lineItems.vaps ?? [];
|
|
lineItems.vaps.push(...vapsToAddToCart);
|
|
|
|
// Call the "next" function to complete the transition to this page.
|
|
next((vm) => {
|
|
vm.setCmsContent(resultMap.cmsContent);
|
|
vm.availableVaps = taxedVaps;
|
|
vm.lineItems = lineItems;
|
|
vm.inactivePromos = removeCurrentlyActivePromoCodesFromInactivePromos(
|
|
vm.lineItems.promos,
|
|
vm.inactivePromos
|
|
);
|
|
vm.updateFooterButtonText(vm.customCtaCopy);
|
|
|
|
// vm.$refs.reviewDropdown.initializeComponent(resultMap.reviewDropdownData);
|
|
// (removed temporarily for Heritage parity effort)
|
|
|
|
if (revalidatePromoResponse) {
|
|
const revalidateAlerts = buildToastMessagesFromRevalidateOrValidatePromoResponse(
|
|
revalidatePromoResponse,
|
|
oldActivePromos,
|
|
oldInactivePromos
|
|
);
|
|
|
|
revalidateAlerts.forEach((alert) => {
|
|
vm.$refs.funnelHeader.pushGlobalAlert(alert, alert.shouldAutoFade);
|
|
});
|
|
}
|
|
if (validatePromoResponse) {
|
|
const validateAlerts =
|
|
buildToastMessagesFromRevalidateOrValidatePromoResponse(validatePromoResponse);
|
|
vm.$refs.funnelHeader.pushGlobalAlert(
|
|
validateAlerts[0],
|
|
validateAlerts[0].shouldAutoFade
|
|
);
|
|
}
|
|
});
|
|
},
|
|
data() {
|
|
return {
|
|
lineItems: [],
|
|
availableVaps: [],
|
|
paymentMethodInternalModel: this.getPaymentMethodFromStore(),
|
|
inactivePromos: this.getInactivePromosFromStore(),
|
|
isRecalAckOptIn: this.getIsRecalAckOptInFromStore(),
|
|
forwardClicked: false, // this prevents the pia disclaimer from showing when switching to insurance and nav forward
|
|
};
|
|
},
|
|
methods: {
|
|
doesCopyContainRouterLink,
|
|
getRouterLinkRouteFromCopy,
|
|
navigateWithScenario(scenarioName) {
|
|
this.$router.navigateWithoutSaving(scenarioName, this.pageName);
|
|
},
|
|
arePagePrerequisitesValid() {
|
|
// Service Location
|
|
const serviceLocation = store.getters.order.serviceLocation;
|
|
const mobileReqs = !!(
|
|
serviceLocation.address &&
|
|
serviceLocation.city &&
|
|
serviceLocation.state &&
|
|
serviceLocation.zipCode
|
|
);
|
|
|
|
const providerLocation = serviceLocation.provider.address;
|
|
const dropOffInshopReqs = !!(
|
|
providerLocation.streetAddress &&
|
|
providerLocation.city &&
|
|
providerLocation.state &&
|
|
providerLocation.zipCode
|
|
);
|
|
|
|
const isMobile = serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE;
|
|
|
|
const serviceLocationReqs =
|
|
(isMobile && mobileReqs) || (!isMobile && dropOffInshopReqs);
|
|
|
|
// Insurance
|
|
const isInsurance = store.getters.order.payment.isInsurance;
|
|
const insuranceCoverageStatus = store.getters.payment.insuranceCoverage.coverageStatus;
|
|
const currentDeductible = store.getters.policy.currentDeductible;
|
|
const isInsuranceSet = () => {
|
|
if (isInsurance !== null) {
|
|
if (
|
|
insuranceCoverageStatus === coverageStatus.VERIFIED &&
|
|
!(currentDeductible === 0 || currentDeductible > 0)
|
|
) {
|
|
return false; // if coverageStatus is verified there must be a valid deductible also
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
// Schedule
|
|
const schedule = store.getters.order.schedule;
|
|
const scheduleReqs = !!(
|
|
schedule.date &&
|
|
schedule.startTime &&
|
|
schedule.endTime &&
|
|
schedule.jobMaxMinutes &&
|
|
schedule.jobMinMinutes
|
|
);
|
|
|
|
// Customer
|
|
const customer = store.getters.order.customer;
|
|
const customerReqs = !!(
|
|
customer.firstName &&
|
|
customer.lastName &&
|
|
customer.phoneNumber &&
|
|
customer.emailAddress
|
|
);
|
|
|
|
const preReqResult =
|
|
serviceLocationReqs && isInsuranceSet() && scheduleReqs && customerReqs;
|
|
|
|
// prettier-ignore
|
|
{
|
|
debugLog("--- payment-method.vue pagePrereqs start ---", null, !preReqResult);
|
|
debugLog("serviceLocationRequs::isMobile:", isMobile, !preReqResult);
|
|
debugLog("serviceLocationReqs::mobileReqs:", mobileReqs, !preReqResult);
|
|
debugLog("serviceLocationReqs::dropOffInshopReqs:", dropOffInshopReqs, !preReqResult);
|
|
debugLog("serviceLocationReqs:: result", serviceLocationReqs, !preReqResult);
|
|
|
|
debugLog("isInsuranceSet::", isInsuranceSet(), !preReqResult);
|
|
|
|
debugLog("scheduleReqs::schedule.date:", schedule.date, !preReqResult);
|
|
debugLog("scheduleReqs::schedule.startTime:", schedule.startTime, !preReqResult);
|
|
debugLog("scheduleReqs::schedule.endTime:", schedule.endTime, !preReqResult);
|
|
debugLog("scheduleReqs::schedule.jobMaxMinutes:", schedule.jobMaxMinutes, !preReqResult);
|
|
debugLog("scheduleReqs::schedule.jobMinMinutes:", schedule.jobMinMinutes, !preReqResult);
|
|
|
|
debugLog("customerReqs::customer.firstName:", customer.firstName, !preReqResult);
|
|
debugLog("customerReqs::customer.lastName:", customer.lastName, !preReqResult);
|
|
debugLog("customerReqs::customer.phoneNumber:", customer.phoneNumber, !preReqResult);
|
|
debugLog("customerReqs::customer.emailAddress:", customer.emailAddress, !preReqResult);
|
|
debugLog("--- payment-method.vue pagePrereqs end ---", null, !preReqResult);
|
|
}
|
|
|
|
return preReqResult;
|
|
},
|
|
getPaymentMethodFromStore() {
|
|
const piaType = store.getters.order.payment.piaType;
|
|
const isPia = store.getters.order.payment.isPia && !!piaType;
|
|
|
|
return isPia ? piaType : paymentMethods.LATER;
|
|
},
|
|
updateFooterButtonText(newValue) {
|
|
this.$refs.navbar.updateButtonText(newValue);
|
|
},
|
|
getInactivePromosFromStore() {
|
|
return this.$store.getters.order.payment.inactivePromos;
|
|
},
|
|
async revalidatePromos() {
|
|
this.$refs.loadingModal.showModal();
|
|
const revalidatePromoResponse = await baseMixin.methods.dispatchStoreActionWithLogging(
|
|
storeActions.REVALIDATE_ORDER_PROMOS_AND_SAVE_SERVER_DATA,
|
|
{
|
|
activePromosToUse: this.lineItems.promos,
|
|
inactivePromosToUse: this.inactivePromos,
|
|
lineItemsToUse: this.lineItems,
|
|
},
|
|
"payment-method",
|
|
false
|
|
);
|
|
|
|
// Handle error alerts here, success alerts are handled in the watcher
|
|
if (revalidatePromoResponse.errors.length) {
|
|
getNewlyInactivatedPromos(
|
|
this.inactivePromos,
|
|
revalidatePromoResponse.errors
|
|
).forEach((promoCode) => {
|
|
const errorAlert = createPromoErrorAlert(promoCode);
|
|
this.$refs.funnelHeader.pushGlobalAlert(errorAlert, errorAlert.shouldAutoFade);
|
|
});
|
|
}
|
|
|
|
if (revalidatePromoResponse.promoLineItems.length > 0) {
|
|
await baseMixin.methods.dispatchStoreActionWithLogging(
|
|
storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA,
|
|
{
|
|
billToAccountNumber: store.getters.payment.billToAccountNumber,
|
|
providerNumber:
|
|
this.$store.getters.order.serviceLocation.provider.providerNumber,
|
|
appointmentType: this.$store.getters.order.serviceLocation.appointmentType,
|
|
serviceLocationCity: this.$store.getters.order.serviceLocation.city,
|
|
serviceLocationState: this.$store.getters.order.serviceLocation.state,
|
|
serviceLocationZipCode: this.$store.getters.order.serviceLocation.zipCode,
|
|
pricedLineItems: revalidatePromoResponse.promoLineItems,
|
|
},
|
|
"payment-method",
|
|
false
|
|
);
|
|
}
|
|
|
|
const vapsToAddToCart = getVapsThatNeedToBeAddedToSatisfyPromos(
|
|
revalidatePromoResponse.promoLineItems,
|
|
this.availableVaps,
|
|
this.lineItems
|
|
);
|
|
|
|
this.lineItems.vaps.push(...vapsToAddToCart);
|
|
|
|
this.lineItems.promos = revalidatePromoResponse.promoLineItems;
|
|
this.inactivePromos = revalidatePromoResponse.errors.map((x) =>
|
|
getPromoCodeWithoutBundleIdentifier(x.promoCode)
|
|
);
|
|
|
|
this.$refs.loadingModal.hideModal();
|
|
},
|
|
getIsRecalAckOptInFromStore() {
|
|
return this.$store.getters.order.isRecalAckOptIn;
|
|
},
|
|
backButtonAction() {
|
|
this.$router.navigateWithoutSaving(
|
|
this.navigationScenarios.CLICKED_BACK,
|
|
this.pageName
|
|
);
|
|
},
|
|
async forwardButtonAction() {
|
|
this.forwardClicked = true;
|
|
|
|
this.$refs.loadingModal.showModal();
|
|
|
|
await this.dispatchStoreAction(
|
|
storeActions.SAVE_PAYMENT_METHOD_CHOICE,
|
|
this.paymentMethod,
|
|
false
|
|
);
|
|
// save lineitems as they now have salestax added
|
|
await this.dispatchStoreAction(
|
|
storeActions.SAVE_GLASS_PARTS_SUPPRESSING_STATE_RESETTING,
|
|
this.lineItems.glassParts,
|
|
false
|
|
);
|
|
await this.dispatchStoreAction(
|
|
storeActions.SAVE_SUPPORTING_ITEMS_SUPPRESSING_STATE_RESETTING,
|
|
this.lineItems.supportingItems,
|
|
false
|
|
);
|
|
await this.dispatchStoreAction(storeActions.SAVE_VAPS, this.lineItems.vaps, false);
|
|
this.dispatchStoreAction(
|
|
storeActions.SAVE_ACTIVE_AND_OR_INACTIVE_PROMOS,
|
|
{
|
|
activePromos: this.lineItems.promos,
|
|
inactivePromos: this.inactivePromos,
|
|
},
|
|
false
|
|
);
|
|
this.dispatchStoreAction(storeActions.SAVE_IS_RECAL_ACK_OPT_IN, this.isRecalAckOptIn);
|
|
if (this.paymentMethod == paymentMethods.LATER) {
|
|
// this creates the final work order
|
|
await submitWorkOrder({ pageNameToLog: "payment-method", submitAfterSave: true });
|
|
this.$router.navigateWithoutSaving(
|
|
this.navigationScenarios.CLICKED_FORWARD,
|
|
this.pageName
|
|
);
|
|
} else {
|
|
if (this.paymentMethod == paymentMethods.INSURANCE) {
|
|
this.dispatchStoreAction(this.storeActions.SAVE_PAYMENT_TYPE, true, false);
|
|
return this.$router.navigateWithSaving(
|
|
this.navigationScenarios.CLICKED_INSURANCE,
|
|
this.pageName
|
|
);
|
|
} else {
|
|
this.setupPia();
|
|
}
|
|
}
|
|
},
|
|
async setupPia() {
|
|
// we need a work order number for pia so we can pass it to safeliteHop.
|
|
// this will create one in delete substatus.
|
|
try {
|
|
await submitWorkOrder({
|
|
pageNameToLog: "payment-method",
|
|
submitAfterSave: false,
|
|
createUnscheduledStatusWorkOrderForPIA: true,
|
|
});
|
|
} catch (error) {
|
|
console.log("error: response from pia submit work order:" + error.message);
|
|
this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
|
|
this.$route.query.piaError = true;
|
|
return;
|
|
}
|
|
|
|
this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
|
|
this.$router.navigateWithoutSaving(
|
|
this.navigationScenarios.CLICKED_PAY_NOW,
|
|
this.pageName
|
|
);
|
|
},
|
|
hasSubmittedOrder() {
|
|
return baseMixin.methods.hasSubmittedOrder();
|
|
},
|
|
getSubmittedOrder() {
|
|
return baseMixin.methods.getSubmittedOrder();
|
|
},
|
|
openModal(modalName) {
|
|
this.$refs[modalName].openModal();
|
|
},
|
|
},
|
|
computed: {
|
|
AppointmentType() {
|
|
return this.hasSubmittedOrder()
|
|
? this.getSubmittedOrder()?.serviceLocation?.appointmentType
|
|
: this.$store.getters.order.serviceLocation?.appointmentType;
|
|
},
|
|
ServiceLocationFullAddressText() {
|
|
return `${this.ServiceLocationAddress?.toLowerCase()}${this.ServiceLocationAddress2 ? ", " + this.ServiceLocationAddress2 : ""}, ${this.ServiceLocationCity?.toLowerCase()}, ${this.ServiceLocationState} ${this.ServiceLocationZipCode}`;
|
|
},
|
|
ServiceLocationFullAddress() {
|
|
if (this.AppointmentType === AppointmentTypeStrings.MOBILE) {
|
|
return {
|
|
streetAddress: this.$store.getters.order.serviceLocation?.address,
|
|
address2: this.$store.getters.order.serviceLocation?.address2,
|
|
city: this.$store.getters.order.serviceLocation?.city,
|
|
state: this.$store.getters.order.serviceLocation?.state,
|
|
zipCode: this.$store.getters.order.serviceLocation?.zipCode,
|
|
};
|
|
} else {
|
|
return this.$store.getters.order.serviceLocation?.provider?.address;
|
|
}
|
|
},
|
|
ServiceLocationAddress() {
|
|
return this.hasSubmittedOrder()
|
|
? this.getSubmittedOrder()?.serviceLocation?.address
|
|
: this.ServiceLocationFullAddress.streetAddress;
|
|
},
|
|
ServiceLocationAddress2() {
|
|
return this.hasSubmittedOrder()
|
|
? this.getSubmittedOrder()?.serviceLocation?.address
|
|
: this.ServiceLocationFullAddress.address2;
|
|
},
|
|
ServiceLocationCity() {
|
|
return this.hasSubmittedOrder()
|
|
? this.getSubmittedOrder()?.serviceLocation?.city
|
|
: this.ServiceLocationFullAddress.city;
|
|
},
|
|
ServiceLocationState() {
|
|
return this.hasSubmittedOrder()
|
|
? this.getSubmittedOrder()?.serviceLocation?.state
|
|
: this.ServiceLocationFullAddress.state;
|
|
},
|
|
ServiceLocationZipCode() {
|
|
return this.hasSubmittedOrder()
|
|
? this.getSubmittedOrder()?.serviceLocation?.zipCode
|
|
: this.ServiceLocationFullAddress.zipCode;
|
|
},
|
|
AppointmentWordingText() {
|
|
return this.getCmsContent("ApptDetailsSnapshotWidget", "BodyText")?.replaceAll(
|
|
"{custom:ADDRESS}",
|
|
this.ServiceLocationFullAddressText
|
|
);
|
|
},
|
|
isRecalibrationOnOrder() {
|
|
const order = baseMixin.methods.hasSubmittedOrder()
|
|
? baseMixin.methods.getSubmittedOrder()
|
|
: this.$store.getters.order;
|
|
|
|
return containsRecalParts(order.lineItems);
|
|
},
|
|
shouldHideRecalibration() {
|
|
return this.$store.getters.shouldHideRecalibration;
|
|
},
|
|
showApplePay() {
|
|
return baseMixin.methods.showApplePay();
|
|
},
|
|
damageInfo() {
|
|
return this.$store.getters.damage;
|
|
},
|
|
isMSRFeeApplicable() {
|
|
return this.$store.getters.order.isMSRFeeApplicable;
|
|
},
|
|
isInsurance() {
|
|
return this.hasSubmittedOrder()
|
|
? this.getSubmittedOrder()?.payment.isInsurance
|
|
: this.$store.getters.payment.isInsurance;
|
|
},
|
|
isNoComp() {
|
|
return this.hasSubmittedOrder()
|
|
? this.getSubmittedOrder()?.policy.isNoComp
|
|
: this.$store.getters.policy.isNoComp;
|
|
},
|
|
isItac() {
|
|
return this.hasSubmittedOrder()
|
|
? this.getSubmittedOrder()?.policy.isItac
|
|
: this.$store.getters.policy.isItac;
|
|
},
|
|
hasVapsInCart() {
|
|
if (this.lineItems?.vaps?.length > 0) {
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
showInsuranceCoverageAs() {
|
|
if (!this.isInsurance) return null;
|
|
|
|
if (this.isNoComp || this.isItac) {
|
|
return coverageStatus.NOCOMP;
|
|
}
|
|
|
|
if (this.$store.getters.coverageIsVerified) {
|
|
return coverageStatus.VERIFIED;
|
|
}
|
|
|
|
return coverageStatus.PENDING;
|
|
},
|
|
currentDeductible() {
|
|
return this.hasSubmittedOrder()
|
|
? this.getSubmittedOrder()?.policy.currentDeductible
|
|
: this.$store.getters.policy.currentDeductible;
|
|
},
|
|
insuranceCompanyName() {
|
|
return this.$store.getters.policy.insuranceCompanyName;
|
|
},
|
|
noPiaDisclaimer() {
|
|
return this.getCmsContent("NoPiaDisclaimerWidget", "Text");
|
|
},
|
|
customCtaCopy() {
|
|
switch (this.paymentMethod) {
|
|
case paymentMethods.PAY_NOW:
|
|
return "Continue"; // Before Heritage Parity it was "Continue to checkout"
|
|
case paymentMethods.INSURANCE:
|
|
return "Continue"; // Before Heritage Parity it was "Continue to insurance"
|
|
case paymentMethods.CREDIT_CARD:
|
|
return "Continue"; // Before Heritage Parity it was "Continue to checkout"
|
|
case paymentMethods.PAYPAL:
|
|
return "Continue"; // Before Heritage Parity it was "Continue to Paypal"
|
|
case paymentMethods.AFTERPAY:
|
|
return "Continue"; // Before Heritage Parity it was "Continue to Afterpay"
|
|
default:
|
|
return null;
|
|
}
|
|
},
|
|
showPaymentMethodQuestions() {
|
|
if (this.isInsurance) {
|
|
if (this.isPiaEnabled && this.totalAmountDue > 0) {
|
|
return true;
|
|
}
|
|
} else {
|
|
if (this.isPiaEnabled && this.totalAmountDue > 0 && !this.shouldHideRecalibration) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
},
|
|
totalAmountDue() {
|
|
return getAmountDue(this.lineItems);
|
|
},
|
|
isPiaEnabled() {
|
|
const piaExperience = this.getSettingValue(experimentSettings.PIA_EXPERIENCE);
|
|
const piaInsurance = this.getSettingValue(experimentSettings.PIA_INSURANCE);
|
|
|
|
if (this.isInsurance) {
|
|
return piaInsurance === "true" && this.currentDeductible > 0;
|
|
} else {
|
|
return piaExperience === "PIA Optional" || piaExperience === "PIA Required";
|
|
}
|
|
},
|
|
paymentMethod() {
|
|
if (!this.isPiaEnabled) {
|
|
return paymentMethods.LATER;
|
|
}
|
|
|
|
return this.paymentMethodInternalModel;
|
|
},
|
|
shouldDisplayPiaAlert() {
|
|
return getBoolFromString(this.$route?.query?.piaError);
|
|
},
|
|
// Necessary to make the watcher of lineItems work
|
|
// JavaScript does not keep a record of the old value, only a reference to it's location in the memory.
|
|
// This creates a separate location for oldValue/newValue so they can be compared
|
|
// See Vue github issue #2164
|
|
lineItemsCloneForWatcher() {
|
|
return Object.assign({}, this.lineItems);
|
|
},
|
|
isRecalPriceRemove() {
|
|
return (
|
|
this.getSettingValue(experimentSettings.RECAL_PRICE_REMOVE)?.toLowerCase() ===
|
|
"true"
|
|
);
|
|
},
|
|
isAfterpayBreakoutDisplay() {
|
|
return (
|
|
this.showInsuranceCoverageAs !== coverageStatus.PENDING &&
|
|
(!this.isRecalPriceRemove || !this.isRecalibrationOnOrder) &&
|
|
this.hasSettingEqualTo(experimentSettings.DISPLAY_AFTERPAY_BREAKOUT_DISPLAY, "true")
|
|
);
|
|
},
|
|
},
|
|
watch: {
|
|
customCtaCopy(newValue) {
|
|
this.updateFooterButtonText(newValue);
|
|
},
|
|
lineItemsCloneForWatcher: {
|
|
handler(newValue, oldValue) {
|
|
if (
|
|
!oldValue ||
|
|
oldValue.length == 0 ||
|
|
!oldValue.vaps ||
|
|
!newValue ||
|
|
newValue.length == 0
|
|
) {
|
|
return;
|
|
}
|
|
|
|
if (oldValue.promos.length < newValue.promos.length) {
|
|
const oldPromoCodes = oldValue.promos.map(
|
|
(promoObject) => promoObject.promoCode
|
|
);
|
|
|
|
const newlyActivatedPromoCodes = newValue.promos.filter(
|
|
(newPromo) => !oldPromoCodes.includes(newPromo.promoCode)
|
|
);
|
|
|
|
// Remove any existing promo error alerts for this promo code
|
|
const existingAlerts = this.$refs.funnelHeader.getGlobalAlerts();
|
|
if (existingAlerts.length > 0) {
|
|
const errorPromoAlertToRemove = existingAlerts.find(
|
|
(alert) =>
|
|
alert.messageHeadline === "Promo code error" &&
|
|
alert.messageCopy.includes(newlyActivatedPromoCodes[0].promoCode)
|
|
);
|
|
errorPromoAlertToRemove &&
|
|
this.$refs.funnelHeader.removeGlobalAlert(errorPromoAlertToRemove.id);
|
|
}
|
|
|
|
const alert = createPromoSuccessAlert(newlyActivatedPromoCodes[0].promoCode);
|
|
this.$refs.funnelHeader.pushGlobalAlert(alert, alert.shouldAutoFade);
|
|
} else if (
|
|
oldValue.promos.length == newValue.promos.length &&
|
|
oldValue.vaps.length != newValue.vaps.length
|
|
) {
|
|
this.revalidatePromos();
|
|
}
|
|
},
|
|
deep: true,
|
|
},
|
|
},
|
|
components: {
|
|
funnelHeader,
|
|
navbar,
|
|
funnelSubHeader,
|
|
Form,
|
|
Field,
|
|
loadingModal,
|
|
cart,
|
|
alert,
|
|
paymentMethodQuestion,
|
|
reviewDropdown,
|
|
textBlock,
|
|
checkboxQuestion,
|
|
contentGroupModal,
|
|
afterpayBreakout,
|
|
ErrorMessage,
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style></style>
|
|
|
|
<style lang="scss" scoped>
|
|
.appt-details-snapshot {
|
|
:deep(p) {
|
|
margin: 0.75rem 0 1rem 0;
|
|
font-size: 0.875rem;
|
|
padding: 0;
|
|
text-transform: capitalize;
|
|
span {
|
|
text-transform: lowercase;
|
|
}
|
|
}
|
|
:deep(.review-table p) {
|
|
margin: 0.25rem 0 0 0;
|
|
}
|
|
}
|
|
.checkbox-group {
|
|
align-items: start;
|
|
> * {
|
|
margin-top: 0;
|
|
}
|
|
|
|
:deep(.form-check-input:invalid),
|
|
:deep(.form-check-input[aria-invalid="true"]),
|
|
:deep(.is-invalid .form-check-input) {
|
|
border-color: $danger;
|
|
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
|
|
}
|
|
|
|
:deep(.is-invalid .form-check-label) {
|
|
color: $danger;
|
|
}
|
|
}
|
|
.questions-about-service {
|
|
.service-questions {
|
|
font-weight: 600;
|
|
}
|
|
}
|
|
.cart {
|
|
margin-bottom: 0;
|
|
}
|
|
.no-pia-disclaimer {
|
|
color: $gray-550;
|
|
margin: 0;
|
|
}
|
|
.payment-method {
|
|
:deep(.almost-done) {
|
|
font-family: UrbanistSemibold;
|
|
}
|
|
:deep(h5) {
|
|
margin-bottom: 1rem;
|
|
}
|
|
.recal-modal {
|
|
:deep(.modal-body) {
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: visible !important;
|
|
|
|
p.subheader-text {
|
|
color: $red;
|
|
order: 1 !important;
|
|
margin-top: -2.25rem;
|
|
}
|
|
h5 {
|
|
text-align: left !important;
|
|
order: 2 !important;
|
|
}
|
|
p {
|
|
order: 4;
|
|
}
|
|
img {
|
|
order: 3 !important;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
:deep(span.label) {
|
|
font-weight: 600;
|
|
}
|
|
:deep(div.service-questions span) {
|
|
color: $black;
|
|
font-family: UrbanistSemibold;
|
|
}
|
|
</style>
|