diff --git a/environment-variables.js b/environment-variables.js index 17c797cfd..55ffdf54e 100644 --- a/environment-variables.js +++ b/environment-variables.js @@ -15,6 +15,7 @@ const environmentVariables = { ['__VUE_APP_SOLARWINDS_MONITORING_SCRIPT__']: "", ['__VUE_APP_ADYEN_ENVIRONMENT__']: "test", ['__VUE_APP_ADYEN_CLIENT_KEY__']: "test_WYGT4F5ZT5BRRL5MPWHK6QLYOIXZXROD", + ['__VUE_APP_SESSION_EXPIRATION_INTERVAL_CHECK_MILLISECONDS__']: 60000, }; const keysOnly = Object.keys(environmentVariables); diff --git a/src/App.vue b/src/App.vue index 2b9bc5a33..36c4b58a3 100644 --- a/src/App.vue +++ b/src/App.vue @@ -18,6 +18,8 @@ import { showFmgLoadingModal } from "@/helpers/loading-modal-helper"; import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer.vue"; import salesforceWebchat from "./digital-components/salesforce-webchat/salesforce-webchat.vue"; import sierraWebchat from "./digital-components/sierra-webchat/sierra-webchat.vue"; +import analyticsMixin from "@/mixins/analytics-mixin"; +import { applicationConfig } from "@/constants/application-config"; export default { name: "app", @@ -25,8 +27,18 @@ export default { const { globalNonpersistedState } = showFmgLoadingModal(); return { globalNonpersistedState }; }, + data() { + return { + sessionCheckInterval: null, + }; + }, methods: { handleAnyComponentFocus: handleAnyComponentFocus, + checkSessionAndRedirect() { + if (analyticsMixin.methods.sessionExpired()) { + this.$router.sendToReturnUser(); + } + }, }, computed: { shouldShowLoader() { @@ -44,6 +56,16 @@ export default { }, mounted() { showFmgLoadingModal(true); + // Check session expiration and redirect if expired + this.sessionCheckInterval = setInterval( + this.checkSessionAndRedirect, + applicationConfig.SESSION_EXPIRATION_INTERVAL_CHECK_MILLISECONDS + ); + }, + beforeUnmount() { + if (this.sessionCheckInterval) { + clearInterval(this.sessionCheckInterval); + } }, }; diff --git a/src/constants/application-config.js b/src/constants/application-config.js index 91873d455..c64ae0db6 100644 --- a/src/constants/application-config.js +++ b/src/constants/application-config.js @@ -31,6 +31,8 @@ const applicationConfig = { AFFILIATE_COOKIE_CONTAINING_NAME: "_Track", COOKIE_NAME_LENGTH_MAX_LIMIT: 44, SESSION_TIMEOUT_MINUTES: process.env.__VUE_APP_SESSION_TIMEOUT_MINUTES__, + SESSION_EXPIRATION_INTERVAL_CHECK_MILLISECONDS: + process.env.__VUE_APP_SESSION_EXPIRATION_INTERVAL_CHECK_MILLISECONDS__, RETURN_USER_PAGE: "/fmg/return-user", ADYEN_CLIENT_KEY: process.env.__VUE_APP_ADYEN_CLIENT_KEY__, ADYEN_ENVIRONMENT: process.env.__VUE_APP_ADYEN_ENVIRONMENT__, diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index 9e884df91..abcf06502 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -69,7 +69,7 @@ const endpoints = { method: "GET", }, GetMobileFeePart: { - url: "/parts/api/v1/parts/mobile-fee", + url: "/parts/api/v2/parts/mobile-fee", method: "GET", }, GetPricingByDayPart: { diff --git a/src/fmg-components/cart/cart.vue b/src/fmg-components/cart/cart.vue index 9a6f55576..faa28376d 100644 --- a/src/fmg-components/cart/cart.vue +++ b/src/fmg-components/cart/cart.vue @@ -962,7 +962,8 @@ export default { return this.supportingItems.find( (lineItem) => lineItem.partType == partTypeStrings.MOBILE_FEE && - lineItem.partNumber == partNumberStrings.MOBILE_STATIC_RECAL_FEE + (lineItem.partNumber == partNumberStrings.MOBILE_STATIC_RECAL_FEE || + lineItem.partNumber == partNumberStrings.MOBILE_DUAL_RECAL_FEE) ); }, msrFeeCartItem() { diff --git a/src/helpers/damage-helper.js b/src/helpers/damage-helper.js index 1763ffbea..5c7a2de7c 100644 --- a/src/helpers/damage-helper.js +++ b/src/helpers/damage-helper.js @@ -103,7 +103,7 @@ export function getSideDoorGlassString(glassLocation, glassName) { export function getDamageInfoWordingText(damageInfo) { const glassTextArray = []; - damageInfo.glassToReplace.forEach((item) => { + damageInfo.glassToReplace?.forEach((item) => { let string = ""; if (item.glassLocation === glassLocations.WINDSHIELD) { string = item.glassLocation; diff --git a/src/helpers/pricing-helper.js b/src/helpers/pricing-helper.js index 9564b5c1f..fff87d82f 100644 --- a/src/helpers/pricing-helper.js +++ b/src/helpers/pricing-helper.js @@ -108,7 +108,9 @@ export async function getPricingByDayPartWithPrice(pageNameToLog) { export function getMSRFeePartPrice(supportingItems, includeTax) { let msrFeePrice = 0; const msrFeeLineItem = supportingItems?.find( - (lineItem) => lineItem.partNumber === partNumberStrings.MOBILE_STATIC_RECAL_FEE + (lineItem) => + lineItem.partNumber === partNumberStrings.MOBILE_STATIC_RECAL_FEE || + lineItem.partNumber === partNumberStrings.MOBILE_DUAL_RECAL_FEE ); if (msrFeeLineItem) { msrFeePrice = baseMixin?.methods?.getTotalPriceOfAllLineItemsAndChildParts( diff --git a/src/layouts/payment-adyen/payment-adyen.vue b/src/layouts/payment-adyen/payment-adyen.vue index 5b3a663ea..4297eb317 100644 --- a/src/layouts/payment-adyen/payment-adyen.vue +++ b/src/layouts/payment-adyen/payment-adyen.vue @@ -77,6 +77,7 @@ import { createAdyenCheckout } from "@/helpers/adyen-helper"; import { Dropin } from "@adyen/adyen-web/auto"; import { applicationConfig } from "@/constants/application-config"; import { paymentMethods } from "@/constants/payment-method-constants"; +import analyticsMixin from "@/mixins/analytics-mixin"; import "@adyen/adyen-web/styles/adyen.css"; import { mapAdyenToFmgPaymentMethod, mapFmgToAdyenPaymentMethod } from "../../helpers/adyen-helper"; @@ -133,6 +134,25 @@ export default { }, methods: { + async initializeAdyenWithErrorHandling() { + try { + await this.initializeAdyen(); + } catch (error) { + console.error("Failed to initialize Adyen payment:", error); + + const errorJson = JSON.stringify(error, Object.getOwnPropertyNames(Object(error))); + analyticsMixin.methods.pushFmgSessionData(errorJson); + await global.$logger.logError(errorJson); + + this.$router.navigateWithoutSaving( + this.navigationScenarios.PIA_ERROR, + this.pageName + ); + + return; + } + }, + async backButtonAction() { // Stub, for navigating back via nav-bar. this.$router.navigateWithoutSaving( @@ -203,6 +223,12 @@ export default { }, onError: (error, component) => { console.log(`Error from Adyen`); + const errorJson = JSON.stringify( + error, + Object.getOwnPropertyNames(Object(error)) + ); + analyticsMixin.methods.pushFmgSessionData(errorJson); + this.handleError(error); }, }, @@ -371,7 +397,12 @@ export default { await global.$logger.logError(stringToLog); - this.hasPaymentFailureError = true; + this.$router.navigateWithoutSaving( + this.navigationScenarios.PIA_ERROR, + this.pageName + ); + + return; } this.dropinComponent?.update(); @@ -566,7 +597,7 @@ export default { }, mounted() { - this.initializeAdyen(); + this.initializeAdyenWithErrorHandling(); }, components: { diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index c483c90df..6f2db110b 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -706,10 +706,16 @@ export default { isMobileStaticRecalibrationApplicable() { return ( this.displayMSR && - this.mobileFeePart?.partNumber == partNumberStrings.MOBILE_STATIC_RECAL_FEE && + this.isMSRFeePartNumber && (this.enableMSRSplitPay || this.isCashItacNoComp || this.mobileFeePart?.isInsurable) ); }, + isMSRFeePartNumber() { + return ( + this.mobileFeePart?.partNumber === partNumberStrings.MOBILE_STATIC_RECAL_FEE || + this.mobileFeePart?.partNumber === partNumberStrings.MOBILE_DUAL_RECAL_FEE + ); + }, displayMSR() { return ( experimentMixin.methods diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js index 3ab575f58..1ea812c49 100644 --- a/src/mixins/analytics-mixin.js +++ b/src/mixins/analytics-mixin.js @@ -160,12 +160,16 @@ export default { // and aws FireHose stream, DigitalConsumer-Session-Firehose, to push data to an // S3 bucket, safelite-dev-digitalconsumer-session-data-us-east-2/1. // This bucket data is then picked up by snowflake for analytics use. - async pushFmgSessionData() { + async pushFmgSessionData(errorJson = null) { var currentPageName = getPageNameFromRouter(true); if (!currentPageName) { return; } + if (errorJson) { + currentPageName += ` ERROR: ${errorJson}`; + } + const hasSubmittedOrder = baseMixin.methods.hasSubmittedOrder(); const submittedOrder = baseMixin.methods.getSubmittedOrder(); const order = hasSubmittedOrder ? submittedOrder : store.getters.order; @@ -209,6 +213,7 @@ export default { var appointment = `${order?.schedule?.date ?? ""} ${order?.schedule?.startTime ?? ""}`; var sessionData = {}; + sessionData.currentPage = currentPageName; sessionData.sid = getSessionIdValue(); sessionData.deviceId = getDeviceIdValue(); @@ -931,6 +936,11 @@ function getPageNameFromRouter(useDefaultUrl = false) { router.currentRoute.value && router.currentRoute.value.name ) { + const query = router.currentRoute.value.query; + if (query && Object.keys(query).length > 0 && useDefaultUrl === true) { + const queryString = new URLSearchParams(query).toString(); + return `${router.currentRoute.value.name}?${queryString}`; + } return router.currentRoute.value.name; } diff --git a/src/router/constants/auto-route-table.js b/src/router/constants/auto-route-table.js index 0bf3503b9..ea1dd69e0 100644 --- a/src/router/constants/auto-route-table.js +++ b/src/router/constants/auto-route-table.js @@ -6,7 +6,6 @@ export const autoRouteTable = [ routeData.MOLDING_QUESTIONS, routeData.VEHICLE_PARTS, routeData.PART_QUESTIONS, - routeData.VIN_LOOKUP, routeData.ESTIMATE, routeData.VEHICLE_DAMAGE, routeData.VEHICLE,