From b2934987e9f61da18ea2f0f0db9216b97e22e0d8 Mon Sep 17 00:00:00 2001 From: binduchalla <35506898+binduchalla@users.noreply.github.com> Date: Mon, 1 Dec 2025 12:30:40 -0500 Subject: [PATCH 01/35] Cash-1877 Apply bundle promo code on quote page Applying bundle promo code on quote page for existing cash replace script part of E2E tests --- playwright-tests/tests/CashReplaceDynamicRecalMobile.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/playwright-tests/tests/CashReplaceDynamicRecalMobile.ts b/playwright-tests/tests/CashReplaceDynamicRecalMobile.ts index 7b16f582b..5705a95a3 100644 --- a/playwright-tests/tests/CashReplaceDynamicRecalMobile.ts +++ b/playwright-tests/tests/CashReplaceDynamicRecalMobile.ts @@ -1,6 +1,6 @@ //Imports here import { ITestData } from 'framework/TestData' -import { ServiceLocation, PaymentType } from 'safelite-playwright-core'; +import { ServiceLocation, ServicePackage, PaymentType } from 'safelite-playwright-core'; import { ITestCase } from '../framework/Typedefs' import { VehicleLookupType } from 'safelite-playwright-core'; import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core'; @@ -29,7 +29,8 @@ const cashReplaceDynamicRecalMobileData: Partial = { postalCode: '21237' } }, - + + servicePackage: ServicePackage.Premium, // Override vehicle details vehicleDetails: { ...getDefaultTestData().vehicleDetails!, @@ -59,7 +60,8 @@ const cashReplaceDynamicRecalMobileData: Partial = { // Override payment details paymentDetails: { - paymentType: PaymentType.PayAtService + paymentType: PaymentType.PayAtService, + promoCode: 'digitalrain' } } From a212894a26d48d9fcfba15bf819363894e8851b0 Mon Sep 17 00:00:00 2001 From: Chloe Herd Date: Wed, 10 Dec 2025 16:12:58 -0500 Subject: [PATCH 02/35] Adyen POC 0.0 --- src/constants/endpoints.js | 8 + src/layouts/payment-adyen/payment-adyen.vue | 319 ++++++++++++++++++ .../payment-pia-return/payment-pia-return.vue | 3 + src/router/constants/routes.js | 4 + src/router/constants/routing-table.js | 15 +- src/router/methods/routes.js | 1 + 6 files changed, 349 insertions(+), 1 deletion(-) create mode 100644 src/layouts/payment-adyen/payment-adyen.vue diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index c1771b3f5..214431e5c 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -216,6 +216,14 @@ const endpoints = { url: "/order/api/v1/order/add-donation-part", method: "POST", }, + InitializeAdyenPayment: { + url: "/order/api/v1/order/payment/session/initialize-session", + method: "POST", + }, + GetAdyenSessionResult: { + url: "/order/api/v1/order/payment/session/session-result", + method: "POST", + }, }; export { endpoints }; diff --git a/src/layouts/payment-adyen/payment-adyen.vue b/src/layouts/payment-adyen/payment-adyen.vue new file mode 100644 index 000000000..d105382d6 --- /dev/null +++ b/src/layouts/payment-adyen/payment-adyen.vue @@ -0,0 +1,319 @@ + + diff --git a/src/layouts/payment-pia-return/payment-pia-return.vue b/src/layouts/payment-pia-return/payment-pia-return.vue index b1ec2b120..c905f3d78 100644 --- a/src/layouts/payment-pia-return/payment-pia-return.vue +++ b/src/layouts/payment-pia-return/payment-pia-return.vue @@ -156,6 +156,9 @@ export default { lastFour: lastFour, }; + console.log(`CCToken =`); + console.log(ccToken); + baseMixin.methods.dispatchStoreAction(storeActions.SAVE_CCTOKEN, ccToken, false); baseMixin.methods.dispatchStoreAction( storeActions.SAVE_NEXTGEN_SETTLED_AMOUNT, diff --git a/src/router/constants/routes.js b/src/router/constants/routes.js index 98d6c503a..4b51ff857 100644 --- a/src/router/constants/routes.js +++ b/src/router/constants/routes.js @@ -89,6 +89,10 @@ export const routeData = { name: "payment-pia-return", path: "/payment-pia-return", }, + PAYMENT_ADYEN: { + name: "payment-adyen", + path: "/payment-adyen", + }, CONFIRMATION: { name: "confirmation", path: "/confirmation", diff --git a/src/router/constants/routing-table.js b/src/router/constants/routing-table.js index 02a0d9384..cd41e8fcf 100644 --- a/src/router/constants/routing-table.js +++ b/src/router/constants/routing-table.js @@ -535,7 +535,7 @@ const routingTable = function () { }, { scenario: navigationScenarios.CLICKED_PAY_NOW, - destinationPageData: routeData.PAYMENT, + destinationPageData: routeData.PAYMENT_ADYEN, }, { scenario: navigationScenarios.CLICKED_INSURANCE, @@ -563,6 +563,19 @@ const routingTable = function () { }, ], }, + { + pageName: routeData.PAYMENT_ADYEN.name, + maps: [ + { + scenario: navigationScenarios.CLICKED_BACK, + destinationPageData: routeData.PAYMENT_METHOD, + }, + { + scenario: navigationScenarios.CLICKED_FORWARD, + destinationPageData: routeData.CONFIRMATION, + }, + ], + }, { pageName: routeData.PAYMENT_PIA_RETURN.name, maps: [ diff --git a/src/router/methods/routes.js b/src/router/methods/routes.js index 97af04068..cf0392ccf 100644 --- a/src/router/methods/routes.js +++ b/src/router/methods/routes.js @@ -36,6 +36,7 @@ export const routes = [ createRoute(routeData.PAYMENT_METHOD, paymentMethodBeforeEnter), createRoute(routeData.PAYMENT, paymentBeforeEnter), createRoute(routeData.PAYMENT_PIA_RETURN), + createRoute(routeData.PAYMENT_ADYEN), createRoute(routeData.CONFIRMATION), createRoute(routeData.RETURN_USER), createRoute(routeData.MOBILE_DETAILS), From 3b1b000a1387df7bf8f64a17f2a73edc1d624ce2 Mon Sep 17 00:00:00 2001 From: Chloe Herd Date: Thu, 11 Dec 2025 11:58:11 -0500 Subject: [PATCH 03/35] In progress --- src/constants/endpoints.js | 4 +- src/layouts/payment-adyen/payment-adyen.vue | 77 ++++++++++++++++----- 2 files changed, 61 insertions(+), 20 deletions(-) diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js index 214431e5c..e40fce79a 100644 --- a/src/constants/endpoints.js +++ b/src/constants/endpoints.js @@ -217,11 +217,11 @@ const endpoints = { method: "POST", }, InitializeAdyenPayment: { - url: "/order/api/v1/order/payment/session/initialize-session", + url: "/payment/api/v1/payment/payment/session/initialize-session", method: "POST", }, GetAdyenSessionResult: { - url: "/order/api/v1/order/payment/session/session-result", + url: "/payment/api/v1/payment/payment/session/session-result", method: "POST", }, }; diff --git a/src/layouts/payment-adyen/payment-adyen.vue b/src/layouts/payment-adyen/payment-adyen.vue index d105382d6..86c8a67d2 100644 --- a/src/layouts/payment-adyen/payment-adyen.vue +++ b/src/layouts/payment-adyen/payment-adyen.vue @@ -42,6 +42,10 @@ import { settleAllPromises } from "@/helpers/layout-helper"; import globalMethods from "@/global-methods"; import { endpoints } from "../../constants/endpoints"; import { AppointmentTypeStrings } from "../../constants/schedule-constants"; +import baseMixin from "@/mixins/base-mixin.js"; +import { storeActions } from "@/constants/store-actions"; +import { getAmountDue } from "@/helpers/pricing-helper.js"; +import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js"; export default { name: "payment-adyen", @@ -90,6 +94,9 @@ export default { }, async initializeAdyen() { + console.log(`Price = ${this.amountDue}`); + console.log(`Adyen Price = ${this.adyenPriceTotal}`); + const requestBody = this.adyenInitRequestInfo; console.log(`Calling with:`); @@ -153,23 +160,23 @@ export default { window.checkout = checkOut; // eslint-disable-next-line const dropin = new AdyenWeb.Dropin(checkOut, { - paymentMethodsConfiguration: { - card: { - showBrandIcon: true, // when false not showing the brand logo - hasHolderName: true, // show holder name - holderNameRequired: true, // make holder name mandatory - billingAddressRequired: true, - billingAddressAllowedCountries: ["US"], - // configure placeholders - placeholders: { - cardNumber: "1234 5678 9012 3456", - expiryDate: "MM/YY", - securityCodeThreeDigits: "123", - securityCodeFourDigits: "1234", - holderName: "J. Smith", - }, - }, - }, + // paymentMethodsConfiguration: { + // card: { + // showBrandIcon: true, // when false not showing the brand logo + // hasHolderName: true, // show holder name + // holderNameRequired: true, // make holder name mandatory + // billingAddressRequired: true, + // billingAddressAllowedCountries: ["US"], + // // configure placeholders + // placeholders: { + // cardNumber: "1234 5678 9012 3456", + // expiryDate: "MM/YY", + // securityCodeThreeDigits: "123", + // securityCodeFourDigits: "1234", + // holderName: "J. Smith", + // }, + // }, + // }, }).mount("#adyen-container"); }, @@ -218,9 +225,39 @@ export default { console.log(`CC Token generated =`); console.log(ccToken); + + await baseMixin.methods.dispatchStoreAction(storeActions.SAVE_CCTOKEN, ccToken, false); + await baseMixin.methods.dispatchStoreAction( + storeActions.SAVE_NEXTGEN_SETTLED_AMOUNT, + this.amountDue, + false + ); + + await this.saveAndSubmitWorkOrder(); }, handleFailedPayment(result) {}, handleError(error) {}, + + async saveAndSubmitWorkOrder() { + // Work order submission after successful payment. + await this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE); + try { + await submitWorkOrder({ + pageNameToLog: "payment-adyen", + submitAfterSave: true, + }); + } catch (error) { + console.log(`error: response from submit work order: ${error.message}`); + + // navigate to error page + + //this.$refs.loadingModal.isModalVisible = false; + } + + //this.$refs.loadingModal.isModalVisible = false; + // clear order + // navigate forward + }, }, computed: { @@ -304,8 +341,12 @@ export default { return "FMG-2.0"; }, + amountDue() { + return getAmountDue(this.$store.getters.order.lineItems); + }, + adyenPriceTotal() { - return 10000; + return this.amountDue * 100; }, }, From 75417c4a34efc311020622ca98db225b25829294 Mon Sep 17 00:00:00 2001 From: Chloe Herd Date: Fri, 12 Dec 2025 14:12:20 -0500 Subject: [PATCH 04/35] Attempts to resolve PayPal --- src/layouts/payment-adyen/payment-adyen.vue | 56 +++++++++++++-------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/src/layouts/payment-adyen/payment-adyen.vue b/src/layouts/payment-adyen/payment-adyen.vue index 86c8a67d2..f7e85b019 100644 --- a/src/layouts/payment-adyen/payment-adyen.vue +++ b/src/layouts/payment-adyen/payment-adyen.vue @@ -79,6 +79,10 @@ export default { methods: { async backButtonAction() { // Stub, for navigating back via nav-bar. + this.$router.navigateWithoutSaving( + this.navigationScenarios.CLICKED_BACK, + this.pageName + ); }, async forwardButtonAction() { @@ -130,14 +134,15 @@ export default { value: this.adyenPriceTotal, currency: "USD", }, - locale: "en-US", + locale: "en_US", countryCode: "US", showPayButton: true, translations: { - "en-US": { + "en_US": { "creditCard.securityCode.label": "CVV/CVC", }, }, + onPaymentCompleted: (result, component) => { console.log(`Payment completed from Adyen.`); this.handleCompletedPayment(result); @@ -160,23 +165,27 @@ export default { window.checkout = checkOut; // eslint-disable-next-line const dropin = new AdyenWeb.Dropin(checkOut, { - // paymentMethodsConfiguration: { - // card: { - // showBrandIcon: true, // when false not showing the brand logo - // hasHolderName: true, // show holder name - // holderNameRequired: true, // make holder name mandatory - // billingAddressRequired: true, - // billingAddressAllowedCountries: ["US"], - // // configure placeholders - // placeholders: { - // cardNumber: "1234 5678 9012 3456", - // expiryDate: "MM/YY", - // securityCodeThreeDigits: "123", - // securityCodeFourDigits: "1234", - // holderName: "J. Smith", - // }, - // }, - // }, + paymentMethodsConfiguration: { + ideal: { + showImage: true, + }, + paypal: { + amount: { + value: this.adyenPriceTotal, + currency: "USD", + }, + environment: "test", // Change this to "live" when you're ready to accept live PayPal payments + countryCode: "US", // Only needed for test. This will be automatically retrieved when you are in production. + blockPayPalVenmoButton: false, + //blockPayPalPayLaterButton: true + }, + card: { + hasHolderName: true, + holderNameRequired: true, + billingAddressRequired: true, + name: "Credit or debit card", + }, + }, }).mount("#adyen-container"); }, @@ -235,7 +244,10 @@ export default { await this.saveAndSubmitWorkOrder(); }, - handleFailedPayment(result) {}, + async handleFailedPayment(result) { + console.log(`Result =`); + console.log(result); + }, handleError(error) {}, async saveAndSubmitWorkOrder() { @@ -273,12 +285,12 @@ export default { street: this.splitStreetAddress.street, zipCode: this.locationInfo.zipCode, stateOrProvince: this.locationInfo.state, - returnUrl: "localhost:8080/fmg/confirmation", + returnUrl: "http://localhost:8080/fmg/confirmation", email: this.$store.getters.order.customer.emailAddress, IP: "127.0.0.1", // TODO firstName: this.$store.getters.order.customer.firstName, lastName: this.$store.getters.order.customer.lastName, - idempotencyKey: `${this.$store.getters.order.referralCorrelationId}-${this.sourceSystem}`, + idempotencyKey: crypto.randomUUID(), // `${this.$store.getters.order.referralCorrelationId}-${this.sourceSystem}`, }; }, From a8453c76ae50c27edd04effb3da57a26a02d98b8 Mon Sep 17 00:00:00 2001 From: Chloe Herd Date: Mon, 15 Dec 2025 10:17:57 -0500 Subject: [PATCH 05/35] Misc --- src/layouts/payment-adyen/payment-adyen.vue | 35 ++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/layouts/payment-adyen/payment-adyen.vue b/src/layouts/payment-adyen/payment-adyen.vue index f7e85b019..e38578e54 100644 --- a/src/layouts/payment-adyen/payment-adyen.vue +++ b/src/layouts/payment-adyen/payment-adyen.vue @@ -46,6 +46,7 @@ import baseMixin from "@/mixins/base-mixin.js"; import { storeActions } from "@/constants/store-actions"; import { getAmountDue } from "@/helpers/pricing-helper.js"; import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js"; +import { paymentMethods } from "@/constants/payment-method-constants"; export default { name: "payment-adyen", @@ -138,7 +139,7 @@ export default { countryCode: "US", showPayButton: true, translations: { - "en_US": { + en_US: { "creditCard.securityCode.label": "CVV/CVC", }, }, @@ -235,6 +236,11 @@ export default { console.log(`CC Token generated =`); console.log(ccToken); + // Save payment type. + // For now, CC. + // Need to determine payment type from Adyen response + await this.dispatchStoreAction(storeActions.SAVE_PAYMENT_METHOD_CHOICE, paymentMethods.CREDIT_CARD, false); + await baseMixin.methods.dispatchStoreAction(storeActions.SAVE_CCTOKEN, ccToken, false); await baseMixin.methods.dispatchStoreAction( storeActions.SAVE_NEXTGEN_SETTLED_AMOUNT, @@ -247,6 +253,33 @@ export default { async handleFailedPayment(result) { console.log(`Result =`); console.log(result); + + // Fetch session info + const paymentSessionRequest = { + zipCodeCtu: this.locationInfo.zipCodeCtu, + workOrderNumber: this.workOrderNumberLastSixDigits, + sourceSystem: this.sourceSystem, + sessionId: this.sessionId, + sessionResult: result?.sessionResult, + }; + + console.log(`Get session payload =`); + console.log(paymentSessionRequest); + + try { + const response = await globalMethods.callHttpClient({ + method: endpoints.GetAdyenSessionResult.method, + endpoint: endpoints.GetAdyenSessionResult.url, + payload: paymentSessionRequest, + logApiCall: true, + pageNameToLog: "payment-adyen", + }); + + console.log(response); + } catch (error) { + console.log(`Error getting session info after failure.`); + } + }, handleError(error) {}, From 72a3d8588eb64e8136937b1c74d0b38e8fef19e0 Mon Sep 17 00:00:00 2001 From: Chloe Herd Date: Tue, 16 Dec 2025 09:45:44 -0500 Subject: [PATCH 06/35] Afterpay return page --- package-lock.json | 41 +++++++ package.json | 1 + src/helpers/adyen-helper.js | 116 ++++++++++++++++++ src/layouts/payment-adyen/payment-adyen.vue | 67 +++++----- src/router/constants/routes.js | 5 + .../methods/route-logic/adyen-return.js | 73 +++++++++++ src/router/methods/routes.js | 2 + 7 files changed, 267 insertions(+), 38 deletions(-) create mode 100644 src/helpers/adyen-helper.js create mode 100644 src/router/methods/route-logic/adyen-return.js diff --git a/package-lock.json b/package-lock.json index 7d44b181a..2e45411d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "Safelite", "version": "0.1.0", "dependencies": { + "@adyen/adyen-web": "^6.27.0", "@iframe-resizer/child": "^5.3.3", "axios": "^0.30.2", "bootstrap": "^5.3.3", @@ -59,6 +60,18 @@ "node": "8 || 9 || 10 || 11 || 12 || 13 || 14 || 15 || 16 || 17 || 18 || 19 || 20 || 21 || 22" } }, + "node_modules/@adyen/adyen-web": { + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/@adyen/adyen-web/-/adyen-web-6.27.0.tgz", + "integrity": "sha512-6E6hqEkMkoY/FevjO53zhgeCWMt+i/PDc11MX6eVM7Fvd+Sdgc9cIwTBz+NKIXs4ZerG6n2gd+ZIZEywqF/aBQ==", + "license": "MIT", + "dependencies": { + "@types/applepayjs": "14.0.9", + "@types/googlepay": "0.7.8", + "classnames": "2.5.1", + "preact": "10.22.1" + } + }, "node_modules/@ampproject/remapping": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -3173,6 +3186,12 @@ "node": ">=10.13.0" } }, + "node_modules/@types/applepayjs": { + "version": "14.0.9", + "resolved": "https://registry.npmjs.org/@types/applepayjs/-/applepayjs-14.0.9.tgz", + "integrity": "sha512-xEprYbb0TEP/XIiDPbVnTYpDai8fTFpsQfVSfTd81Is2GOMUy7ie019eyX6Mz2ECxfjoUVKaiGSL577roIeHCg==", + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -3328,6 +3347,12 @@ "@types/send": "*" } }, + "node_modules/@types/googlepay": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/@types/googlepay/-/googlepay-0.7.8.tgz", + "integrity": "sha512-EHl7G7jIeit/Px/fi/Du/19SZDvqrRhy0DjJX40Vzs/97m3sro9mzVyAYNC0pfzpWGDY+zPjtTLEfhV+OYWUcQ==", + "license": "MIT" + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -5789,6 +5814,12 @@ "dev": true, "license": "MIT" }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/clean-css": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", @@ -13681,6 +13712,16 @@ "dev": true, "license": "MIT" }, + "node_modules/preact": { + "version": "10.22.1", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.22.1.tgz", + "integrity": "sha512-jRYbDDgMpIb5LHq3hkI0bbl+l/TQ9UnkdQ0ww+lp+4MMOdqaUYdFc5qeyP+IV8FAd/2Em7drVPeKdQxsiWCf/A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", diff --git a/package.json b/package.json index 7d06ad112..c55259920 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "lint": "vue-cli-service lint" }, "dependencies": { + "@adyen/adyen-web": "^6.27.0", "@iframe-resizer/child": "^5.3.3", "axios": "^0.30.2", "bootstrap": "^5.3.3", diff --git a/src/helpers/adyen-helper.js b/src/helpers/adyen-helper.js new file mode 100644 index 000000000..93bd53bc8 --- /dev/null +++ b/src/helpers/adyen-helper.js @@ -0,0 +1,116 @@ +import { AdyenCheckout } from "@adyen/adyen-web/auto"; +import store from "@/store"; +import globalMethods from "@/global-methods"; +import { endpoints } from "@/constants/endpoints"; +import { AppointmentTypeStrings } from "@/constants/schedule-constants"; + +export async function getNewSession(sessionConfig) {} + +export async function getSessionInfo(sessionId, encryptedSessionResult, pageNameToLog = "") { + const order = store.getters.order; + const location = getLocationInfo(order); + const workOrder = getWorkOrderLastSixDigits(order); + const system = getSourceSystem(); + + const request = { + sessionId: sessionId, + sessionResult: encryptedSessionResult, + zipCodeCtu: location.zipCodeCtu, + workOrderNumber: workOrder, + sourceSystem: system, + }; + + const response = await globalMethods.callHttpClient({ + method: endpoints.GetAdyenSessionResult.method, + endpoint: endpoints.GetAdyenSessionResult.url, + payload: request, + logApiCall: true, + pageNameToLog: pageNameToLog, + }); + + return response?.data; +} + +export async function createAdyenCheckout({ + session: { id, sessionData }, + handlers: { onPaymentCompleted, onPaymentFailed, onError }, + options: { showPayButton = true, amount }, +}) { + const config = { + session: { + id: id, + sessionData: sessionData, + }, + clientKey: "test_WYGT4F5ZT5BRRL5MPWHK6QLYOIXZXROD", + environment: "test", + locale: "en_US", + countryCode: "US", + showPayButton: showPayButton, + translations: { + en_US: { + "creditCard.securityCode.label": "CVV/CVC", + }, + }, + onPaymentCompleted: onPaymentCompleted, + onPaymentFailed: onPaymentFailed, + onError: onError, + }; + + if (amount !== undefined && amount !== null) { + config.amount = { + value: amount, + currency: "USD", + }; + } + + console.log(`Creating checkout with configuration:`); + console.log(config); + + return await AdyenCheckout(config); +} + +function getLocationInfo(order) { + const serviceLocation = order.serviceLocation; + const isMobile = serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE; + if (isMobile) { + return { + address: serviceLocation.address, + address2: serviceLocation.address2, + city: serviceLocation.city, + state: serviceLocation.state, + zipCode: serviceLocation.zipCode, + zipCodeCtu: serviceLocation.zipCodeCtu, + }; + } else { + const providerInfo = serviceLocation.provider.address; + return { + address: providerInfo.streetAddress, + address2: "", + city: providerInfo.city, + state: providerInfo.state, + zipCode: providerInfo.zipCode, + zipCodeCtu: providerInfo.zipCodeCtu, + }; + } +} + +function getWorkOrderLastSixDigits(order) { + const workOrderNumber = order.workOrderNumber ?? ""; + const tokens = workOrderNumber.split("-"); + const lastSegment = tokens[tokens.length - 1] ?? ""; + const numDigits = lastSegment.length; + const numZeroes = 6 - numDigits; + + let result = ""; + for (let i = 0; i < numZeroes; i++) { + result += "0"; + } + + result += lastSegment; + + return result; +} + +function getSourceSystem() { + return "FMG-2.0"; +} diff --git a/src/layouts/payment-adyen/payment-adyen.vue b/src/layouts/payment-adyen/payment-adyen.vue index e38578e54..a2d7651a2 100644 --- a/src/layouts/payment-adyen/payment-adyen.vue +++ b/src/layouts/payment-adyen/payment-adyen.vue @@ -47,6 +47,8 @@ import { storeActions } from "@/constants/store-actions"; import { getAmountDue } from "@/helpers/pricing-helper.js"; import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js"; import { paymentMethods } from "@/constants/payment-method-constants"; +import { createAdyenCheckout } from "@/helpers/adyen-helper"; +import { Dropin } from "@adyen/adyen-web/auto"; export default { name: "payment-adyen", @@ -127,45 +129,31 @@ export default { this.sessionId = responseJson.sessionId; - const configuration = { + const checkout = await createAdyenCheckout({ session: session, - clientKey: "test_WYGT4F5ZT5BRRL5MPWHK6QLYOIXZXROD", - environment: "test", - amount: { - value: this.adyenPriceTotal, - currency: "USD", - }, - locale: "en_US", - countryCode: "US", - showPayButton: true, - translations: { - en_US: { - "creditCard.securityCode.label": "CVV/CVC", + handlers: { + onPaymentCompleted: (result, component) => { + console.log(`Payment completed from Adyen.`); + this.handleCompletedPayment(result); + }, + onPaymentFailed: (result, component) => { + console.log(`Payment failed from Adyen`); + this.handleFailedPayment(result); + }, + onError: (error, component) => { + console.log(`Error from Adyen`); + this.handleError(error); }, }, - - onPaymentCompleted: (result, component) => { - console.log(`Payment completed from Adyen.`); - this.handleCompletedPayment(result); + options: { + amount: this.adyenPriceTotal, }, - onPaymentFailed: (result, component) => { - console.log(`Payment failed from Adyen`); - this.handleFailedPayment(result); - }, - onError: (error, component) => { - console.log(`Error from Adyen`); - this.handleError(error); - }, - }; + }); - console.log(`Configuration =`); - console.log(configuration); + console.log(checkout); - // eslint-disable-next-line - const checkOut = await AdyenWeb.AdyenCheckout(configuration); - window.checkout = checkOut; - // eslint-disable-next-line - const dropin = new AdyenWeb.Dropin(checkOut, { + window.checkout = checkout; + const dropin = new Dropin(checkout, { paymentMethodsConfiguration: { ideal: { showImage: true, @@ -239,7 +227,11 @@ export default { // Save payment type. // For now, CC. // Need to determine payment type from Adyen response - await this.dispatchStoreAction(storeActions.SAVE_PAYMENT_METHOD_CHOICE, paymentMethods.CREDIT_CARD, false); + await this.dispatchStoreAction( + storeActions.SAVE_PAYMENT_METHOD_CHOICE, + paymentMethods.CREDIT_CARD, + false + ); await baseMixin.methods.dispatchStoreAction(storeActions.SAVE_CCTOKEN, ccToken, false); await baseMixin.methods.dispatchStoreAction( @@ -279,7 +271,6 @@ export default { } catch (error) { console.log(`Error getting session info after failure.`); } - }, handleError(error) {}, @@ -312,18 +303,18 @@ export default { referralSequenceNumber: this.$store.getters.order.referralSequenceNumber, workOrderNumber: this.workOrderNumberLastSixDigits, zipCodeCtu: this.locationInfo.zipCodeCtu, - amount: this.adyenPriceTotal, // TODO + amount: this.adyenPriceTotal, city: this.locationInfo.city, houseNumberOrName: this.splitStreetAddress.number, street: this.splitStreetAddress.street, zipCode: this.locationInfo.zipCode, stateOrProvince: this.locationInfo.state, - returnUrl: "http://localhost:8080/fmg/confirmation", + returnUrl: "http://localhost:8080/fmg/virtual/payment/return", email: this.$store.getters.order.customer.emailAddress, IP: "127.0.0.1", // TODO firstName: this.$store.getters.order.customer.firstName, lastName: this.$store.getters.order.customer.lastName, - idempotencyKey: crypto.randomUUID(), // `${this.$store.getters.order.referralCorrelationId}-${this.sourceSystem}`, + idempotencyKey: `${this.$store.getters.order.referralCorrelationId}-${this.sourceSystem}`, }; }, diff --git a/src/router/constants/routes.js b/src/router/constants/routes.js index 4b51ff857..5090df2b3 100644 --- a/src/router/constants/routes.js +++ b/src/router/constants/routes.js @@ -93,6 +93,11 @@ export const routeData = { name: "payment-adyen", path: "/payment-adyen", }, + PAYMENT_ADYEN_RETURN: { + name: "adyen-return", + path: "/virtual/payment/return", + virtual: true, + }, CONFIRMATION: { name: "confirmation", path: "/confirmation", diff --git a/src/router/methods/route-logic/adyen-return.js b/src/router/methods/route-logic/adyen-return.js new file mode 100644 index 000000000..9bf1940ba --- /dev/null +++ b/src/router/methods/route-logic/adyen-return.js @@ -0,0 +1,73 @@ +import { AdyenCheckout } from "@adyen/adyen-web"; +import { routeData } from "@router/constants/routes"; +import { createAdyenCheckout, getSessionInfo } from "@/helpers/adyen-helper"; + +export async function adyenReturnBeforeEnter(to, from) { + console.log(`In adyen return.`); + console.log(JSON.parse(JSON.stringify(to))); + + const sessionId = to?.query?.sessionId; + const redirectResult = to?.query?.redirectResult; + + if (sessionId && redirectResult) { + const result = await finalizeAdyenPayment(sessionId, redirectResult); + console.log(`Out of promise`); + console.log(result); + + const sessionInfo = await getSessionInfo(sessionId, result.sessionResult); + + console.log(`Got session info:`); + console.log(sessionInfo); + + // TODO once afterpay info is returned, create cc token and submit order. + + return { + name: routeData.CONFIRMATION.name, + replace: true, + }; + } + + return { + name: routeData.ERROR.name, + replace: true, + }; +} + +function finalizeAdyenPayment(sessionId, redirectResult) { + return new Promise((resolve, reject) => { + createAdyenCheckout({ + session: { + id: sessionId, + }, + handlers: { + onPaymentCompleted: (result, component) => { + console.log(`Payment successful`); + console.log(result); + resolve(result); + }, + onPaymentFailed: (result, component) => { + console.log(`Payment failed`); + console.log(result); + reject("Payment failed from Adyen"); + }, + onError: (error, component) => { + console.log(`Error occured`); + console.log(error); + reject("Error from Adyen"); + }, + }, + options: {}, + }).then( + (checkout) => { + checkout.submitDetails({ + details: { + redirectResult: redirectResult, + }, + }); + }, + (error) => { + reject(error); + } + ); + }); +} diff --git a/src/router/methods/routes.js b/src/router/methods/routes.js index cf0392ccf..ec6077270 100644 --- a/src/router/methods/routes.js +++ b/src/router/methods/routes.js @@ -13,6 +13,7 @@ import { restartBeforeEnter } from "@/router/methods/route-logic/restart"; import { paymentMethodBeforeEnter } from "@/router/methods/route-logic/payment-method"; import { paymentBeforeEnter } from "@/router/methods/route-logic/payment"; import { serviceLocationBeforeEnter } from "@/router/methods/route-logic/service-location"; +import { adyenReturnBeforeEnter } from "@/router/methods/route-logic/adyen-return"; export const routes = [ // Non-virtual pages. @@ -48,4 +49,5 @@ export const routes = [ createVirtualRoute(routeData.AUTO_ROUTE, autoRouteBeforeEnter), createVirtualRoute(routeData.ERROR, errorBeforeEnter), createVirtualRoute(routeData.RESTART, restartBeforeEnter), + createVirtualRoute(routeData.PAYMENT_ADYEN_RETURN, adyenReturnBeforeEnter), ]; From 8385928d8c5c14061f0acf9b5b076888b4c3c94d Mon Sep 17 00:00:00 2001 From: Chloe Herd Date: Tue, 16 Dec 2025 11:10:56 -0500 Subject: [PATCH 07/35] Changes for Dev --- src/constants/application-config.js | 1 + src/constants/experiments.js | 1 + src/layouts/payment-adyen/payment-adyen.vue | 26 ++++++++++++++----- src/layouts/payment-method/payment-method.vue | 14 +++++++--- src/router/constants/navigation-scenarios.js | 1 + src/router/constants/routing-table.js | 4 +++ .../methods/route-logic/adyen-return.js | 2 +- 7 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/constants/application-config.js b/src/constants/application-config.js index 93a6f70d8..b739d9ff9 100644 --- a/src/constants/application-config.js +++ b/src/constants/application-config.js @@ -22,6 +22,7 @@ const applicationConfig = { PIA_ERROR_URL: location.protocol + "//" + location.host + "/fmg/payment-pia-return?src=concept-funnel", CONFIRMATION_URL: location.protocol + "//" + location.host + "/fmg/confirmation", + PIA_ADYEN_RETURN_URL: location.protocol + "//" + location.host + "/fmg/virtual/payment/return", GOOGLE_CALENDAR: "https://www.google.com/calendar/render?action=TEMPLATE", YAHOO_CALENDAR: "https://calendar.yahoo.com/?v=60", OUTLOOK_CALENDAR: diff --git a/src/constants/experiments.js b/src/constants/experiments.js index f358fc89f..0ca3987f0 100644 --- a/src/constants/experiments.js +++ b/src/constants/experiments.js @@ -33,6 +33,7 @@ const experimentSettings = { SHOW_PM_MOBILE_DAYS: "Show_PMmobileDays", SHOW_NO_PM_MOBILE_DAYS: "Show_NoPMmobileDays", SHOW_NO_MOBILE_AVAILABLE_DAYS: "Show_NoMobileAvailableDays", + USE_ADYEN_PAYMENT: "UseAdyenPayment", }; const experimentTriggers = { diff --git a/src/layouts/payment-adyen/payment-adyen.vue b/src/layouts/payment-adyen/payment-adyen.vue index a2d7651a2..5e74692d5 100644 --- a/src/layouts/payment-adyen/payment-adyen.vue +++ b/src/layouts/payment-adyen/payment-adyen.vue @@ -49,6 +49,7 @@ import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js" import { paymentMethods } from "@/constants/payment-method-constants"; import { createAdyenCheckout } from "@/helpers/adyen-helper"; import { Dropin } from "@adyen/adyen-web/auto"; +import { applicationConfig } from "@/constants/application-config"; export default { name: "payment-adyen", @@ -279,20 +280,29 @@ export default { await this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE); try { await submitWorkOrder({ - pageNameToLog: "payment-adyen", + pageNameToLog: this.pageName, submitAfterSave: true, }); } catch (error) { console.log(`error: response from submit work order: ${error.message}`); // navigate to error page + const errorPayload = { + cause: `Error while submitting work order after Adyen payment.`, + currentPage: this.pageName, + sessionId: this.sessionId, + idempotencyKey: this.idempotencyKey, + }; - //this.$refs.loadingModal.isModalVisible = false; + this.$router.handleSoftError(errorPayload); } - //this.$refs.loadingModal.isModalVisible = false; // clear order - // navigate forward + await baseMixin.methods.dispatchStoreAction(storeActions.CREATE_SUBMITTED_STATE); + this.$router.navigateWithoutSaving( + this.navigationScenarios.CLICKED_FORWARD, + this.pageName + ); }, }, @@ -309,12 +319,12 @@ export default { street: this.splitStreetAddress.street, zipCode: this.locationInfo.zipCode, stateOrProvince: this.locationInfo.state, - returnUrl: "http://localhost:8080/fmg/virtual/payment/return", + returnUrl: applicationConfig.PIA_ADYEN_RETURN_URL, email: this.$store.getters.order.customer.emailAddress, IP: "127.0.0.1", // TODO firstName: this.$store.getters.order.customer.firstName, lastName: this.$store.getters.order.customer.lastName, - idempotencyKey: `${this.$store.getters.order.referralCorrelationId}-${this.sourceSystem}`, + idempotencyKey: this.idempotencyKey, }; }, @@ -373,6 +383,10 @@ export default { }; }, + idempotencyKey() { + return `${this.$store.getters.order.referralCorrelationId}-${this.sourceSystem}`; + }, + sourceSystem() { return "FMG-2.0"; }, diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue index 955d106b5..0fa02e80a 100644 --- a/src/layouts/payment-method/payment-method.vue +++ b/src/layouts/payment-method/payment-method.vue @@ -170,6 +170,7 @@ import { consumeQueryFromStash } from "@/router/methods/helpers/querystring-stas import { debugLog } from "@/helpers/debug-log-helper"; import { ErrorMessage } from "vee-validate"; import { Field } from "vee-validate"; +import experimentMixin from "../../mixins/experiment-mixin"; defineRule("payment-method-required", required(errorMessages.OPTION_REQUIRED)); defineRule("recal-ack-required", required(errorMessages.RECAL_ACK_REQUIRED)); @@ -609,10 +610,17 @@ export default { } this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE); - this.$router.navigateWithoutSaving( - this.navigationScenarios.CLICKED_PAY_NOW, - this.pageName + + const shouldUseAdyen = experimentMixin.methods.hasSettingEqualTo( + experimentSettings.USE_ADYEN_PAYMENT, + "true" ); + + const scenarioName = shouldUseAdyen + ? this.navigationScenarios.CLICKED_PAY_NOW_ADYEN + : this.navigationScenarios.CLICKED_PAY_NOW; + + this.$router.navigateWithoutSaving(scenarioName, this.pageName); }, async evaluatePromosAndTaxItemsOnOrder() { //Revalidate promos if there are any inactive, or active promos diff --git a/src/router/constants/navigation-scenarios.js b/src/router/constants/navigation-scenarios.js index b660afca3..c2247d59b 100644 --- a/src/router/constants/navigation-scenarios.js +++ b/src/router/constants/navigation-scenarios.js @@ -61,6 +61,7 @@ const navigationScenarios = { // Payment CLICKED_INSURANCE: "CLICKED_INSURANCE", CLICKED_PAY_NOW: "CLICKED_PAY_NOW", + CLICKED_PAY_NOW_ADYEN: "CLICKED_PAY_NOW_ADYEN", CLICKED_PAY_LATER: "CLICKED_PAY_LATER", PIA_ERROR: "PIA_ERROR", PIA_CC_ERROR: "PIA_CC_ERROR", diff --git a/src/router/constants/routing-table.js b/src/router/constants/routing-table.js index cd41e8fcf..192e1572e 100644 --- a/src/router/constants/routing-table.js +++ b/src/router/constants/routing-table.js @@ -535,6 +535,10 @@ const routingTable = function () { }, { scenario: navigationScenarios.CLICKED_PAY_NOW, + destinationPageData: routeData.PAYMENT, + }, + { + scenario: navigationScenarios.CLICKED_PAY_NOW_ADYEN, destinationPageData: routeData.PAYMENT_ADYEN, }, { diff --git a/src/router/methods/route-logic/adyen-return.js b/src/router/methods/route-logic/adyen-return.js index 9bf1940ba..a516518ac 100644 --- a/src/router/methods/route-logic/adyen-return.js +++ b/src/router/methods/route-logic/adyen-return.js @@ -1,5 +1,5 @@ import { AdyenCheckout } from "@adyen/adyen-web"; -import { routeData } from "@router/constants/routes"; +import { routeData } from "@/router/constants/routes"; import { createAdyenCheckout, getSessionInfo } from "@/helpers/adyen-helper"; export async function adyenReturnBeforeEnter(to, from) { From 652684782150b8e286421ec3468e4018459d52ef Mon Sep 17 00:00:00 2001 From: kpatel8hs4io <31411746+kpatel8hs4io@users.noreply.github.com> Date: Tue, 16 Dec 2025 15:13:20 -0500 Subject: [PATCH 08/35] Updated few playwright scripts --- playwright-tests/pages/HomePage.ts | 9 +++++---- playwright-tests/pages/PaypalPage.ts | 2 ++ playwright-tests/pages/SchedulePage.ts | 5 +++++ .../tests/CashReplaceMultiSlidingGlassDropoff.ts | 2 +- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/playwright-tests/pages/HomePage.ts b/playwright-tests/pages/HomePage.ts index 39e349790..3d44b023a 100644 --- a/playwright-tests/pages/HomePage.ts +++ b/playwright-tests/pages/HomePage.ts @@ -47,7 +47,7 @@ export class HomePage extends BasePage { //Zip Entry this.enterServiceZipTextBox = this.page.locator('#zipCodeTextbox'); - this.zipEntryLetsGetStartedButton = this.page.locator('#zipCodeTextboxButton'); + this.zipEntryLetsGetStartedButton = this.page.locator('#zipCodeTextbox'); this.getQuoteAndScheduleButton = this.page.getByLabel('main').getByRole('link', { name: 'Get quote + schedule' }); } @@ -61,10 +61,11 @@ export class HomePage extends BasePage { async letsGetStarted(zip: string, enterFunnelWithZip: boolean) { if (enterFunnelWithZip) { - await this.letsGetStartedButton.evaluate((element, zip) => { + await this.zipEntryLetsGetStartedButton.fill(zip); + /* await this.letsGetStartedButton.evaluate((element, zip) => { const currentHref = element.getAttribute('href') || ''; - element.setAttribute('href', `${currentHref}&zipCode=${zip}`); - }, zip); + element.setAttribute('href', `${currentHref}?zipCode=${zip}`); + }, zip);*/ } // Wait until modal pop up opens and close pop up diff --git a/playwright-tests/pages/PaypalPage.ts b/playwright-tests/pages/PaypalPage.ts index ce5547f3c..e67e0a57b 100644 --- a/playwright-tests/pages/PaypalPage.ts +++ b/playwright-tests/pages/PaypalPage.ts @@ -41,6 +41,8 @@ export class PaypalPage extends BasePage { await this.paypalLoginButton.click(); await this.completePurchaseButton.click(); } else { + await this.usernameTextBox.waitFor({ state: 'visible' }); + await this.page.screenshot({ path: `test-results\\ortoni-data\\paypal-username-${Date.now()}.png`, fullPage: true }); await this.usernameTextBox.fill(paymentDetails.username!); await this.nextButton.click(); await this.tryAnotherWayButton.click(); diff --git a/playwright-tests/pages/SchedulePage.ts b/playwright-tests/pages/SchedulePage.ts index 77b330aff..944b84136 100644 --- a/playwright-tests/pages/SchedulePage.ts +++ b/playwright-tests/pages/SchedulePage.ts @@ -35,6 +35,7 @@ export class SchedulePage extends BasePage { readonly timeSlots: Locator; readonly allDayDropOffButton: Locator; readonly pickATimeButton: Locator; + readonly mobileFirstModalCloseButton: Locator; constructor(page: Page) { super(page); @@ -65,6 +66,7 @@ export class SchedulePage extends BasePage { this.viewMoreDatesLink = this.page.getByText(/View more dates/).first(); this.appointmentDuration = this.page.locator('.duration-text-block'); this.timeSlots = this.page.locator('fieldset:has(>legend#chooseTimeSlot) label').filter({ visible: true}); + this.mobileFirstModalCloseButton = this.page.getByRole('dialog').getByRole('button', { name: 'Close' }); } async selectLocation(testData: Partial) { @@ -229,6 +231,9 @@ export class SchedulePage extends BasePage { async handleSchedulePage(testData: Partial) { const { appointmentDetails } = testData; await this.validateProgressBar(ProgressBarPercentages.SchedulePage); + if (await this.mobileFirstModalCloseButton.isVisible()) { + await this.mobileFirstModalCloseButton.click(); + } await this.selectLocation(testData); await this.scheduleFirstAppointment(testData); await this.nextPage(); diff --git a/playwright-tests/tests/CashReplaceMultiSlidingGlassDropoff.ts b/playwright-tests/tests/CashReplaceMultiSlidingGlassDropoff.ts index f488a3555..4042c67de 100644 --- a/playwright-tests/tests/CashReplaceMultiSlidingGlassDropoff.ts +++ b/playwright-tests/tests/CashReplaceMultiSlidingGlassDropoff.ts @@ -35,7 +35,7 @@ const cashReplaceMultiSlidingGlassDropoffData: Partial = { appointmentDetails: { serviceLocation: ServiceLocation.DropOff, appointmentDate: getDefaultTestData().appointmentDetails?.appointmentDate, - shopAddress: "6826 sawmill rd, Columbus, OH 43235" + shopAddress: "1343 cameron ave, lewis center, oh 43035" }, // Override vehicle details From 7c2fb6eee025937948f43d8218e1689779081314 Mon Sep 17 00:00:00 2001 From: kpatel8hs4io <31411746+kpatel8hs4io@users.noreply.github.com> Date: Wed, 17 Dec 2025 14:53:32 -0500 Subject: [PATCH 09/35] fixed alert validation playwright tests --- playwright-tests/pages/LookupPage.ts | 14 +++++++------- playwright-tests/pages/ServiceZipPage.ts | 4 ++-- playwright-tests/pages/VehicleLookupAddressPage.ts | 4 ++-- playwright-tests/pages/VehicleLookupLicensePage.ts | 4 ++-- playwright-tests/pages/VinLookupPage.ts | 6 +++--- playwright-tests/pages/forms/AddressForm.ts | 2 +- .../alert-validation/alert0007_VinNotFound.ts | 4 ++-- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/playwright-tests/pages/LookupPage.ts b/playwright-tests/pages/LookupPage.ts index e5f2fca04..7d6c1df03 100644 --- a/playwright-tests/pages/LookupPage.ts +++ b/playwright-tests/pages/LookupPage.ts @@ -81,9 +81,9 @@ export class LookupPage extends BasePage { // Map of expected messages by lookup type const expectedMessages = { - 'address': "Your address didn't return a VIN match.Please re-enter the information below or provide your VIN in a different way.", - 'license plate': "Your license plate didn’t return a VIN match.Please re-enter the information above or provide your VIN in a different way.", - 'VIN': "Your VIN didn’t return a vehicle matchPlease re-enter your VIN or we can look up your VIN for you.", + 'address': "Your address didn't return a VIN match.Please re-enter the information below or provide your VIN in a different way.", + 'license plate': "Your license plate didn’t return a VIN match.Please re-enter the information above or provide your VIN in a different way.", + 'VIN': "Your VIN didn’t return a vehicle matchPlease re-enter your VIN or we can look up your VIN for you.", 'ZIP code': "Your ZIP code didn't return a match.Please re-enter the information above or provide your VIN in a different way." }; @@ -106,19 +106,19 @@ export class LookupPage extends BasePage { } } - async handleZipValidation(testData: Partial, zip: string, lookupType: VehicleLookupType, alertFlags?: IAlertFlags): Promise { + async handleZipValidation(testData: Partial): Promise { - let { vehicleDamage } = testData; + let { customerDetails,vehicleDamage, vehicleDetails, alertFlags } = testData; // Only proceed with validation if alertFlags is provided if (alertFlags) { if (alertFlags.isUnserviceableZip) { - await this.checkForUnserviceableZipAlertMessage(zip); + await this.checkForUnserviceableZipAlertMessage(customerDetails!.address.postalCode!); throw new TestSuccessAlert('Unserviceable ZIP validation successful.'); } else if (alertFlags.isInvalidZip) { await this.checkForInvalidZipAlertMessage(); throw new TestSuccessAlert('Invalid ZIP validation successful.'); } else if (alertFlags.isVinNotFound) { - await this.checkForVinNotFoundAlertMessage(lookupType); + await this.checkForVinNotFoundAlertMessage(vehicleDetails!.vehicleLookupType!); throw new TestSuccessAlert('Vin not found validation successful.'); } else { diff --git a/playwright-tests/pages/ServiceZipPage.ts b/playwright-tests/pages/ServiceZipPage.ts index 91393f892..6c1a178f5 100644 --- a/playwright-tests/pages/ServiceZipPage.ts +++ b/playwright-tests/pages/ServiceZipPage.ts @@ -12,9 +12,9 @@ export class ServiceZipPage extends LookupPage { @step("ZipLookupPage >> Lookup by service ZIP: ") async handleServiceZipPage(testData: Partial) { - const { customerDetails, vehicleDetails, alertFlags } = testData; + const { customerDetails } = testData; await this.validateProgressBar(ProgressBarPercentages.ServiceZipPage); await this.enterZip(customerDetails!.address.postalCode!); - await this.handleZipValidation(testData, customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); + await this.handleZipValidation(testData); } } \ No newline at end of file diff --git a/playwright-tests/pages/VehicleLookupAddressPage.ts b/playwright-tests/pages/VehicleLookupAddressPage.ts index c5f37ee3d..50a7f1b24 100644 --- a/playwright-tests/pages/VehicleLookupAddressPage.ts +++ b/playwright-tests/pages/VehicleLookupAddressPage.ts @@ -29,10 +29,10 @@ export class VehicleLookupAddressPage extends LookupPage { @step("VehicleLookupAddressPage >> Lookup by address: ") async handleVehicleLookupAddressPage(testData: Partial) { - const { customerDetails, vehicleDetails, alertFlags } = testData; + const { customerDetails, vehicleDetails } = testData; await this.validateProgressBar(ProgressBarPercentages.VehicleLookupAddressPage); await this.lookupVehicleByAddress(customerDetails!, vehicleDetails!); - await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); + await this.handleZipValidation(testData); } } \ No newline at end of file diff --git a/playwright-tests/pages/VehicleLookupLicensePage.ts b/playwright-tests/pages/VehicleLookupLicensePage.ts index e20828b6e..a19d2156b 100644 --- a/playwright-tests/pages/VehicleLookupLicensePage.ts +++ b/playwright-tests/pages/VehicleLookupLicensePage.ts @@ -34,11 +34,11 @@ export class VehicleLookupLicensePage extends LookupPage { @step("VehicleLookupLicensePage >> Lookup by license plate: ") async handleVehicleLookupLicensePage(testData: Partial) { - const { customerDetails, vehicleDetails, alertFlags } = testData; + const { customerDetails, vehicleDetails } = testData; await this.validateProgressBar(ProgressBarPercentages.VehicleLookupLicensePage); await this.enterPlateDetails(vehicleDetails!); await this.enterZip(customerDetails!.address.postalCode!); - await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); + await this.handleZipValidation(testData); } } \ No newline at end of file diff --git a/playwright-tests/pages/VinLookupPage.ts b/playwright-tests/pages/VinLookupPage.ts index 3b69fc2d6..2f508acda 100644 --- a/playwright-tests/pages/VinLookupPage.ts +++ b/playwright-tests/pages/VinLookupPage.ts @@ -24,11 +24,11 @@ export class VinLookupPage extends LookupPage { @step("VinLookupPage >> Lookup by VIN: ") async handleVehicleLookupVinPage(testData: Partial) { - const { customerDetails, vehicleDetails, alertFlags } = testData; + const { customerDetails, vehicleDetails } = testData; let vin: string; if (Array.isArray(vehicleDetails?.vin)) { - vin = vehicleDetails.vin[0]; + vin = vehicleDetails!.vin[0]; } else { vin = vehicleDetails?.vin || ''; } @@ -36,6 +36,6 @@ export class VinLookupPage extends LookupPage { await this.validateProgressBar(ProgressBarPercentages.VinLookupPage); await this.enterVin(vin); await this.enterZip(customerDetails!.address.postalCode!); - await this.handleZipValidation(customerDetails!.address.postalCode!, vehicleDetails!.vehicleLookupType!, alertFlags!); + await this.handleZipValidation(testData); } } \ No newline at end of file diff --git a/playwright-tests/pages/forms/AddressForm.ts b/playwright-tests/pages/forms/AddressForm.ts index 4ddb6a512..92f270320 100644 --- a/playwright-tests/pages/forms/AddressForm.ts +++ b/playwright-tests/pages/forms/AddressForm.ts @@ -57,7 +57,7 @@ export class AddressForm extends BasePage { ); }); - await this.page.hover(".pac-container .pac-item"); + await this.addressSuggestionList.hover(); await new Promise(resolve => setTimeout(resolve, 1000)); await this.addressSuggestionList.click(); // Select the first suggestion //await this.streetAddressTextBox.press('Tab'); // Move focus to ZIP code field diff --git a/playwright-tests/tests/alert-validation/alert0007_VinNotFound.ts b/playwright-tests/tests/alert-validation/alert0007_VinNotFound.ts index d88ceeab6..7c01188cb 100644 --- a/playwright-tests/tests/alert-validation/alert0007_VinNotFound.ts +++ b/playwright-tests/tests/alert-validation/alert0007_VinNotFound.ts @@ -75,10 +75,10 @@ const lookupTypesToTest: LookupTestCase[] = [ }, customerDetails: { address: { - street: '4076 Spectacle Drive', + street: '4219 Turpin Ln', city: 'Columbus', state: 'OH', - postalCode: '59261', + postalCode: '43230', country: 'United States' } }, From 5802ccead3094091634516915e7edcf0a86e90cc Mon Sep 17 00:00:00 2001 From: Chloe Herd Date: Thu, 18 Dec 2025 11:15:36 -0500 Subject: [PATCH 10/35] Paypal + Afterpay + Apple Pay Submission --- src/helpers/adyen-helper.js | 36 ++++++++++++++++ src/layouts/payment-adyen/payment-adyen.vue | 21 ++++++---- .../methods/route-logic/adyen-return.js | 41 ++++++++++++++++++- 3 files changed, 88 insertions(+), 10 deletions(-) diff --git a/src/helpers/adyen-helper.js b/src/helpers/adyen-helper.js index 93bd53bc8..96d04f767 100644 --- a/src/helpers/adyen-helper.js +++ b/src/helpers/adyen-helper.js @@ -3,6 +3,7 @@ import store from "@/store"; import globalMethods from "@/global-methods"; import { endpoints } from "@/constants/endpoints"; import { AppointmentTypeStrings } from "@/constants/schedule-constants"; +import { paymentMethods } from "@/constants/payment-method-constants"; export async function getNewSession(sessionConfig) {} @@ -69,6 +70,41 @@ export async function createAdyenCheckout({ return await AdyenCheckout(config); } +export function mapAdyenToFmgPaymentMethod(adyenMethod) { + const paymentMethodMap = { + ["scheme"]: paymentMethods.CREDIT_CARD, + ["afterpaytouch"]: paymentMethods.AFTERPAY, + ["paypal"]: paymentMethods.PAYPAL, + ["applepay"]: paymentMethods.APPLE_PAY, + }; + + const match = paymentMethodMap[adyenMethod]; + + return match ?? paymentMethods.CREDIT_CARD; +} + +export function generateCcToken(adyenSessionInfo) { + const order = store.getters.order; + const locationInfo = getLocationInfo(order); + + const ccToken = { + subscriptionId: adyenSessionInfo?.storedToken, + expMonth: adyenSessionInfo?.cardExpiryMonth, + expYear: adyenSessionInfo?.cardExpiryYear, + cardType: adyenSessionInfo?.cardType, + billToPostalCode: locationInfo.zipCode, // TODO - real data? + billToFirstName: order.customer.firstName, // TODO - real data? + billToLastName: order.customer.lastName, // TODO - real data? + referenceNumber: adyenSessionInfo?.workOrderNumber, + authCode: adyenSessionInfo?.authCode, + transactionId: adyenSessionInfo?.transactionReference, + transReferenceNumber: adyenSessionInfo?.transactionReference, + lastFour: adyenSessionInfo?.last4DigitsOfCard, + }; + + return ccToken; +} + function getLocationInfo(order) { const serviceLocation = order.serviceLocation; const isMobile = serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE; diff --git a/src/layouts/payment-adyen/payment-adyen.vue b/src/layouts/payment-adyen/payment-adyen.vue index 5e74692d5..2d351472c 100644 --- a/src/layouts/payment-adyen/payment-adyen.vue +++ b/src/layouts/payment-adyen/payment-adyen.vue @@ -22,13 +22,6 @@ - - + From 3f9df4c331e7a5f4172bf8d9d220680ea549456f Mon Sep 17 00:00:00 2001 From: Chloe Herd Date: Wed, 7 Jan 2026 10:27:20 -0500 Subject: [PATCH 30/35] Page prereqs --- src/layouts/payment-adyen/payment-adyen.vue | 109 +++++++++++++++++++- 1 file changed, 105 insertions(+), 4 deletions(-) diff --git a/src/layouts/payment-adyen/payment-adyen.vue b/src/layouts/payment-adyen/payment-adyen.vue index 3e6853aaf..a8b1704a1 100644 --- a/src/layouts/payment-adyen/payment-adyen.vue +++ b/src/layouts/payment-adyen/payment-adyen.vue @@ -66,6 +66,8 @@ import { showFmgLoadingModal } from "../../helpers/loading-modal-helper"; import cart from "@/fmg-components/cart/cart"; import { coverageStatus } from "@/constants/insurance"; import { deepClone } from "@/helpers/object-helper"; +import store from "@/store"; +import { debugLog } from "@/helpers/debug-log-helper.js"; export default { name: "payment-adyen", @@ -110,11 +112,110 @@ export default { }, arePagePrerequisitesValid() { - // Stub, for checking if application is capable of entering this page - // And/or, is all information required for this page present? - // To prevent sequence-breaking from the end-user. + // Service Location + const serviceLocation = store.getters.order.serviceLocation; + const mobileReqs = !!( + serviceLocation.address && + serviceLocation.city && + serviceLocation.state && + serviceLocation.zipCode + ); - return true; + 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 isInsuranceSet = store.getters.order.payment.isInsurance !== null; + + // 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 paymentMethodReqs = + store.getters.order.payment.isPia !== null && + (store.getters.order.payment.isPia || !!store.getters.order.payment.piaType); + + const preReqResult = + serviceLocationReqs && + isInsuranceSet && + scheduleReqs && + customerReqs && + paymentMethodReqs; + + // prettier-ignore + { + debugLog("--- payment-adyen.vue pagePrereqs start ---", null, !preReqResult); + debugLog("mobileReqs:", mobileReqs, !preReqResult); + debugLog("serviceLocation.address:", serviceLocation.address, !preReqResult); + debugLog("serviceLocation.city:", serviceLocation.city, !preReqResult); + debugLog("serviceLocation.state:", serviceLocation.state, !preReqResult); + debugLog("serviceLocation.zipCode:", serviceLocation.zipCode, !preReqResult); + + debugLog("", null, !preReqResult); + + debugLog("dropOffInshopReqs:", dropOffInshopReqs, !preReqResult); + debugLog("serviceLocationReqs:", serviceLocationReqs, !preReqResult); + debugLog("providerLocation.streetAddress:", providerLocation.streetAddress, !preReqResult); + debugLog("providerLocation.city:", providerLocation.city, !preReqResult); + debugLog("providerLocation.state:", providerLocation.state, !preReqResult); + debugLog("providerLocation.zipCode:", providerLocation.zipCode, !preReqResult); + + debugLog("", null, !preReqResult); + + debugLog("isInsuranceSet:", isInsuranceSet, !preReqResult); + + debugLog("", null, !preReqResult); + + debugLog("scheduleReqs:", scheduleReqs, !preReqResult); + debugLog("schedule.date:", schedule.date, !preReqResult); + debugLog("schedule.startTime:", schedule.startTime, !preReqResult); + debugLog("schedule.endTime:", schedule.endTime, !preReqResult); + debugLog("schedule.jobMaxMinutes:", schedule.jobMaxMinutes, !preReqResult); + debugLog("schedule.jobMinMinutes:", schedule.jobMinMinutes, !preReqResult); + + debugLog("", null, !preReqResult); + + debugLog("customerReqs:", customerReqs, !preReqResult); + debugLog("customer.firstName:", customer.firstName, !preReqResult); + debugLog("customer.lastName:", customer.lastName, !preReqResult); + debugLog("customer.phoneNumber:", customer.phoneNumber, !preReqResult); + debugLog("customer.emailAddress:", customer.emailAddress, !preReqResult); + + debugLog("", null, !preReqResult); + + debugLog("paymentMethodReqs:", paymentMethodReqs, !preReqResult); + debugLog("store.getters.order.payment.isPia:", store.getters.order?.payment?.isPia, !preReqResult); + debugLog("store.getters.order.payment.piaType:", store.getters.order?.payment?.piaType, !preReqResult); + + debugLog("--- payment-adyen.vue pagePrereqs end ---", null, !preReqResult); + } + + return preReqResult; }, async initializeAdyen() { From 3d3d48a1ec2722102c252a1db6f85463d58ece40 Mon Sep 17 00:00:00 2001 From: Chloe Herd Date: Wed, 7 Jan 2026 11:17:14 -0500 Subject: [PATCH 31/35] Move hardcoded test values to env variables --- environment-variables.js | 2 ++ src/constants/application-config.js | 2 ++ src/helpers/adyen-helper.js | 5 +++-- src/layouts/payment-adyen/payment-adyen.vue | 1 - 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/environment-variables.js b/environment-variables.js index 4d894b006..17c797cfd 100644 --- a/environment-variables.js +++ b/environment-variables.js @@ -13,6 +13,8 @@ const environmentVariables = { ['__VUE_APP_SAFELITE_HOP__']: "https://sv2-safelitehop-dev.safelite.com/fmgCheckoutShared.aspx", ['__VUE_APP_SESSION_TIMEOUT_MINUTES__']: 30, ['__VUE_APP_SOLARWINDS_MONITORING_SCRIPT__']: "", + ['__VUE_APP_ADYEN_ENVIRONMENT__']: "test", + ['__VUE_APP_ADYEN_CLIENT_KEY__']: "test_WYGT4F5ZT5BRRL5MPWHK6QLYOIXZXROD", }; const keysOnly = Object.keys(environmentVariables); diff --git a/src/constants/application-config.js b/src/constants/application-config.js index b739d9ff9..91873d455 100644 --- a/src/constants/application-config.js +++ b/src/constants/application-config.js @@ -32,6 +32,8 @@ const applicationConfig = { COOKIE_NAME_LENGTH_MAX_LIMIT: 44, SESSION_TIMEOUT_MINUTES: process.env.__VUE_APP_SESSION_TIMEOUT_MINUTES__, RETURN_USER_PAGE: "/fmg/return-user", + ADYEN_CLIENT_KEY: process.env.__VUE_APP_ADYEN_CLIENT_KEY__, + ADYEN_ENVIRONMENT: process.env.__VUE_APP_ADYEN_ENVIRONMENT__, }; export { applicationConfig }; diff --git a/src/helpers/adyen-helper.js b/src/helpers/adyen-helper.js index 96d04f767..8de92bf89 100644 --- a/src/helpers/adyen-helper.js +++ b/src/helpers/adyen-helper.js @@ -4,6 +4,7 @@ import globalMethods from "@/global-methods"; import { endpoints } from "@/constants/endpoints"; import { AppointmentTypeStrings } from "@/constants/schedule-constants"; import { paymentMethods } from "@/constants/payment-method-constants"; +import { applicationConfig } from "../constants/application-config"; export async function getNewSession(sessionConfig) {} @@ -42,8 +43,8 @@ export async function createAdyenCheckout({ id: id, sessionData: sessionData, }, - clientKey: "test_WYGT4F5ZT5BRRL5MPWHK6QLYOIXZXROD", - environment: "test", + clientKey: applicationConfig.ADYEN_CLIENT_KEY, + environment: applicationConfig.ADYEN_ENVIRONMENT, locale: "en_US", countryCode: "US", showPayButton: showPayButton, diff --git a/src/layouts/payment-adyen/payment-adyen.vue b/src/layouts/payment-adyen/payment-adyen.vue index a8b1704a1..d09ffd14e 100644 --- a/src/layouts/payment-adyen/payment-adyen.vue +++ b/src/layouts/payment-adyen/payment-adyen.vue @@ -281,7 +281,6 @@ export default { value: this.adyenPriceTotal, currency: "USD", }, - environment: "test", // Change this to "live" when you're ready to accept live PayPal payments countryCode: "US", // Only needed for test. This will be automatically retrieved when you are in production. blockPayPalVenmoButton: true, blockPayPalPayLaterButton: true, From 5488b3d6a341aabdf79d0067eb64d23a1a5d4cc1 Mon Sep 17 00:00:00 2001 From: Chloe Herd Date: Wed, 7 Jan 2026 11:55:26 -0500 Subject: [PATCH 32/35] Prevent unwanted execution in error scenario --- src/layouts/payment-adyen/payment-adyen.vue | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/layouts/payment-adyen/payment-adyen.vue b/src/layouts/payment-adyen/payment-adyen.vue index d09ffd14e..d4420f437 100644 --- a/src/layouts/payment-adyen/payment-adyen.vue +++ b/src/layouts/payment-adyen/payment-adyen.vue @@ -430,6 +430,8 @@ export default { }; this.$router.handleSoftError(errorPayload); + + return; } // clear order From f02751ad6fd57f959606d41ab1ec3329eccd4640 Mon Sep 17 00:00:00 2001 From: Chloe Herd Date: Wed, 7 Jan 2026 12:14:17 -0500 Subject: [PATCH 33/35] Remove window.checkout Assignment as it is Unneeded --- src/layouts/payment-adyen/payment-adyen.vue | 1 - 1 file changed, 1 deletion(-) diff --git a/src/layouts/payment-adyen/payment-adyen.vue b/src/layouts/payment-adyen/payment-adyen.vue index d4420f437..fffd56b21 100644 --- a/src/layouts/payment-adyen/payment-adyen.vue +++ b/src/layouts/payment-adyen/payment-adyen.vue @@ -270,7 +270,6 @@ export default { console.log(checkout); - window.checkout = checkout; const dropin = new Dropin(checkout, { paymentMethodsConfiguration: { ideal: { From e3d705702cffbe337fb0ba4035025c7f00855b18 Mon Sep 17 00:00:00 2001 From: Chloe Herd Date: Wed, 7 Jan 2026 12:46:47 -0500 Subject: [PATCH 34/35] Add back environment setting per Adyen documentation. --- src/layouts/payment-adyen/payment-adyen.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/layouts/payment-adyen/payment-adyen.vue b/src/layouts/payment-adyen/payment-adyen.vue index fffd56b21..b407f9d55 100644 --- a/src/layouts/payment-adyen/payment-adyen.vue +++ b/src/layouts/payment-adyen/payment-adyen.vue @@ -280,6 +280,7 @@ export default { value: this.adyenPriceTotal, currency: "USD", }, + environment: applicationConfig.ADYEN_ENVIRONMENT, countryCode: "US", // Only needed for test. This will be automatically retrieved when you are in production. blockPayPalVenmoButton: true, blockPayPalPayLaterButton: true, From 9d8e8c851bb931b819628ecccbd887f2805fb0ad Mon Sep 17 00:00:00 2001 From: Minojhini Valaiyapathi Date: Wed, 7 Jan 2026 13:32:22 -0500 Subject: [PATCH 35/35] CASH-1936 - Added parentAccountNumber property to partsorquestion endpoint --- src/store/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/store/index.js b/src/store/index.js index ca1d76f6f..14ecceece 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1765,7 +1765,8 @@ export const actions = { const glassArray = damage.glassToReplace; const zipCode = order.serviceLocation.zipCode; const vin = vehicle.vin; - const serviceType = order.serviceLocation?.appointmentType; + const serviceType = damage.isRepair ? "Repair" : "Install"; + const parentAccountNumber = context.getters.payment.parentAccountNumber; const referralSeqNumber = order.referralSequenceNumber; // create a new array to avoid mutating state @@ -1781,6 +1782,7 @@ export const actions = { vin: vin, serviceType: serviceType, referralSeqNumber: referralSeqNumber, + parentAccountNumber: parentAccountNumber, }, logApiCall: true, pageNameToLog: pageNameToLog,