diff --git a/package-lock.json b/package-lock.json
index c2c89abc..c8ac4360 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,7 @@
"name": "digitalconsumer.iss",
"version": "0.1.0",
"dependencies": {
+ "@adyen/adyen-web": "^6.31.1",
"axios": "^1.13.5",
"axios-retry": "^3.5.0",
"bootstrap": "^5.3",
@@ -93,6 +94,18 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@adyen/adyen-web": {
+ "version": "6.31.1",
+ "resolved": "https://registry.npmjs.org/@adyen/adyen-web/-/adyen-web-6.31.1.tgz",
+ "integrity": "sha512-vxeJW1bTvjXxYf6CkPQV8gbYIhm/hupcjjeoKkkBdxk7geW3qy18Jlco4/MuSeb88ctTRM3gebwFXpqF2NjBLQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/applepayjs": "14.0.9",
+ "@types/googlepay": "0.7.10",
+ "classnames": "2.5.1",
+ "preact": "10.28.2"
+ }
+ },
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"dev": true,
@@ -4125,6 +4138,12 @@
"node": ">= 10"
}
},
+ "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/aria-query": {
"version": "5.0.1",
"dev": true,
@@ -4283,6 +4302,12 @@
"@types/node": "*"
}
},
+ "node_modules/@types/googlepay": {
+ "version": "0.7.10",
+ "resolved": "https://registry.npmjs.org/@types/googlepay/-/googlepay-0.7.10.tgz",
+ "integrity": "sha512-ByXjDfxEp87zsjw5cVsNKEeA+AIJYcWwJvRqPu85jO0Lp/FeG67csnf4mqbPaIxmdI6+Khp3v5I0yyjq9MrGhw==",
+ "license": "MIT"
+ },
"node_modules/@types/graceful-fs": {
"version": "4.1.5",
"dev": true,
@@ -7371,6 +7396,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.1",
"dev": true,
@@ -17407,6 +17438,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/preact": {
+ "version": "10.28.2",
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.2.tgz",
+ "integrity": "sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/preact"
+ }
+ },
"node_modules/prebuild-install": {
"version": "7.1.3",
"dev": true,
diff --git a/package.json b/package.json
index 32e0fad3..c60c9e10 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"lint:inspect": "eslint --inspect-config"
},
"dependencies": {
+ "@adyen/adyen-web": "^6.31.1",
"axios": "^1.13.5",
"axios-retry": "^3.5.0",
"bootstrap": "^5.3",
diff --git a/src/constants/application-config.js b/src/constants/application-config.js
index 1e5ee6d8..c8231ecb 100644
--- a/src/constants/application-config.js
+++ b/src/constants/application-config.js
@@ -26,7 +26,9 @@ const applicationConfig = Object.freeze({
FRONTEND_LOGGER_PATH: "/analytics/api/v1/logging",
BAILOUT_ON_APPLICATION_ERROR: true,
BAILOUT_ON_API_ERROR: true,
- BAILOUT_ON_ROUTER_ERROR: true
+ BAILOUT_ON_ROUTER_ERROR: true,
+ ADYEN_CLIENT_KEY: process.env.VUE_APP_ADYEN_CLIENT_KEY,
+ ADYEN_ENVIRONMENT: process.env.VUE_APP_ADYEN_ENVIRONMENT
});
export default applicationConfig;
diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js
index 057ef9e4..e322bb48 100644
--- a/src/constants/endpoints.js
+++ b/src/constants/endpoints.js
@@ -10,6 +10,7 @@ const PARTS_BASE_URL = '/parts/api/v1/parts';
const PRICE_BASE_URL = '/price/api/v1/price';
const SCHEDULE_BASE_URL = '/schedule/api/v1/schedule';
const VEHICLE_BASE_URL = '/vehicle/api/v1/vehicle';
+const PAYMENT_BASE_URL = '/payment/api/v1/payment';
const endpoints = Object.freeze({
GetRouteInfo: {
@@ -239,6 +240,14 @@ const endpoints = Object.freeze({
GetPaymentSignature: {
url: `${ORDER_BASE_URL}/sign`,
method: 'POST'
+ },
+ InitializeAdyenPayment: {
+ url: `${PAYMENT_BASE_URL}/payment/session/initialize-session`,
+ method: "POST",
+ },
+ GetAdyenSessionResult: {
+ url: `${PAYMENT_BASE_URL}/payment/session/session-result`,
+ method: "POST",
}
});
diff --git a/src/constants/experiments.js b/src/constants/experiments.js
index f1d52ede..9e959331 100644
--- a/src/constants/experiments.js
+++ b/src/constants/experiments.js
@@ -1,7 +1,8 @@
const experimentUniverses = Object.freeze({
ISS_FUNNEL: 'ISSFunnel',
ISS_FEATURETOGGLE_AREFEES_HIDDEN: 'NextGenISS_FeatureToggle_AreFeesHidden',
- ISS_FEATURETOGGLE_AREFEES_OVERRIDDEN: 'NextGenISS_FeatureToggle_AreFeesOverridden'
+ ISS_FEATURETOGGLE_AREFEES_OVERRIDDEN: 'NextGenISS_FeatureToggle_AreFeesOverridden',
+ ADYEN_PAYMENT_TEST: 'NextGenAdyenPaymentTest'
});
const experimentSettings = Object.freeze({
@@ -10,7 +11,8 @@ const experimentSettings = Object.freeze({
ISS_FEATURE_TOGGLE_IS_MOBILE_FEE_HIDDEN: 'HideMobileFee',
ISS_FEATURE_TOGGLE_IS_MOBILE_FEE_OVERRIDDEN: 'OverrideMobileFee',
ISS_FEATURE_TOGGLE_IS_RECYCLE_FEE_HIDDEN: 'HideRecycleFee',
- ISS_FEATURE_TOGGLE_IS_RECYCLE_FEE_OVERRIDDEN: 'OverrideRecycleFee'
+ ISS_FEATURE_TOGGLE_IS_RECYCLE_FEE_OVERRIDDEN: 'OverrideRecycleFee',
+ ISS_ENABLE_ADYEN_V1: 'ISS_Enable_Adyen_V1'
});
const experimentTriggers = Object.freeze({
diff --git a/src/constants/progress-bar-mapper.js b/src/constants/progress-bar-mapper.js
index c47c738a..935c3b38 100644
--- a/src/constants/progress-bar-mapper.js
+++ b/src/constants/progress-bar-mapper.js
@@ -66,6 +66,10 @@ export const pageProgressMapper = {
// No progress bar on this page
percent: 0
},
+ 'payment-adyen': {
+ // No progress bar on this page
+ percent: 0
+ },
'tpa-search': {
percent: 80
},
diff --git a/src/helpers/adyen-helper.js b/src/helpers/adyen-helper.js
new file mode 100644
index 00000000..1cc719d2
--- /dev/null
+++ b/src/helpers/adyen-helper.js
@@ -0,0 +1,171 @@
+import { AdyenCheckout } from "@adyen/adyen-web/auto";
+import { useMainStore } 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";
+import applicationConfig from "@/constants/application-config";
+
+export async function getNewSession(sessionConfig) {}
+
+export async function getSessionInfo(sessionId, encryptedSessionResult, pageNameToLog = "") {
+ const order = useMainStore().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: applicationConfig.ADYEN_CLIENT_KEY,
+ environment: applicationConfig.ADYEN_ENVIRONMENT,
+ 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);
+}
+
+export function mapAdyenToIssPaymentMethod(adyenMethod) {
+ const paymentMethodMap = {
+ ["scheme"]: paymentMethods.CREDIT_CARD,
+ ["afterpaytouch_US"]: paymentMethods.AFTERPAY,
+ ["paypal"]: paymentMethods.PAYPAL,
+ ["applepay"]: paymentMethods.APPLEPAY,
+ };
+
+ const match = paymentMethodMap[adyenMethod];
+
+ return match ?? paymentMethods.CREDIT_CARD;
+}
+
+export function mapIssToAdyenPaymentMethod(issMethod) {
+ const paymentMethodMap = {
+ [paymentMethods.CREDIT_CARD]: "scheme",
+ [paymentMethods.AFTERPAY]: "afterpaytouch_US",
+ [paymentMethods.PAYPAL]: "paypal",
+ [paymentMethods.APPLEPAY]: "applepay",
+ [paymentMethods.PayNow]: null,
+ [paymentMethods.PAY_AT_TIME_OF_SERVICE]: null,
+ };
+
+ const match = paymentMethodMap[issMethod];
+
+ return match;
+}
+
+export function generateCcToken(adyenSessionInfo) {
+ const order = useMainStore().order;
+ const locationInfo = getLocationInfo(order);
+ console.log("Generating CC Token with the following Adyen Session Info:");
+ console.log(JSON.parse(JSON.stringify(adyenSessionInfo)));
+
+ 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 ||
+ serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP;
+ 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 "ISS-NextGen";
+}
diff --git a/src/layouts/payment-method/payment-method.vue b/src/layouts/payment-method/payment-method.vue
index 41eee5ee..0c3d0c7d 100644
--- a/src/layouts/payment-method/payment-method.vue
+++ b/src/layouts/payment-method/payment-method.vue
@@ -239,8 +239,23 @@ export default {
return !isEnabled || useMainStore().isUnverified || (useMainStore().isDeductible && isDeductibleTotalEqualToZero);
},
+ isAdyenEnabled() {
+ const adyenEnabled = this.getSettingValue(experimentSettings.ISS_ENABLE_ADYEN_V1);
+ return adyenEnabled === 'true';
+ },
paymentMethodWidgetName() {
- return supportsApplePay() ? this.widget.paymentMethodApplePay : this.widget.paymentMethod;
+ let showApplePay = false;
+ if (this.isAdyenEnabled) {
+ // Apple Pay is Available in Adyen if the protocol is HTTPS
+ if (window.location.protocol === 'https:') {
+ showApplePay = true;
+ }
+ }
+ else {
+ showApplePay = supportsApplePay();
+ }
+
+ return showApplePay ? this.widget.paymentMethodApplePay : this.widget.paymentMethod;
},
showCartTotal() {
return this.mainStore.isVerified && (this.mainStore.isITAC || this.mainStore.isNoComp
@@ -418,8 +433,12 @@ export default {
bailoutOnError: true
});
+ const scenario = this.isAdyenEnabled
+ ? this.navigationScenarios.CLICKED_PAY_NOW_ADYEN
+ : this.navigationScenarios.CLICKED_PAY_NOW;
+
this.$router.navigate(
- this.navigationScenarios.CLICKED_PAY_NOW,
+ scenario,
this.$route,
{},
{ [routerParams.SKIP_SAVE_SESSION]: true }
diff --git a/src/layouts/payment-page-adyen/payment-page-adyen.vue b/src/layouts/payment-page-adyen/payment-page-adyen.vue
new file mode 100644
index 00000000..afc42249
--- /dev/null
+++ b/src/layouts/payment-page-adyen/payment-page-adyen.vue
@@ -0,0 +1,653 @@
+
+
+
+
+
diff --git a/src/layouts/payment-return-adyen/payment-return-adyen.vue b/src/layouts/payment-return-adyen/payment-return-adyen.vue
new file mode 100644
index 00000000..b84bcb5c
--- /dev/null
+++ b/src/layouts/payment-return-adyen/payment-return-adyen.vue
@@ -0,0 +1,182 @@
+
+
+
+
+
diff --git a/src/router/router-constants/issPage-values.js b/src/router/router-constants/issPage-values.js
index c683e971..4d36c097 100644
--- a/src/router/router-constants/issPage-values.js
+++ b/src/router/router-constants/issPage-values.js
@@ -19,6 +19,8 @@ const issPageValues = Object.freeze({
PAYMENT_METHOD: 'payment-method',
PAYMENT_PAGE: 'payment-page',
PAYMENT_RETURN: 'payment-return',
+ PAYMENT_PAGE_ADYEN: 'payment-page-adyen',
+ PAYMENT_RETURN_ADYEN: 'payment-return-adyen',
PART_QUESTIONS: 'part-questions',
POLICY_HOLDER_DETAILS: 'policy-holder-details',
PROVIDER_PREFERENCE: 'provider-preference',
diff --git a/src/router/router-constants/navigation-scenarios.js b/src/router/router-constants/navigation-scenarios.js
index 2c9768bf..b0b5bea9 100644
--- a/src/router/router-constants/navigation-scenarios.js
+++ b/src/router/router-constants/navigation-scenarios.js
@@ -102,6 +102,8 @@ const navigationScenarios = Object.freeze({
CLICKED_BACK_INSHOP: 'CLICKED_BACK_INSHOP',
CLICKED_BACK_MOBILE: 'CLICKED_BACK_MOBILE',
CLICKED_PAY_NOW: 'CLICKED_PAY_NOW',
+ CLICKED_PAY_NOW_ADYEN: 'CLICKED_PAY_NOW_ADYEN',
+ PAY_IN_ADVANCE_CANCEL: 'PAY_IN_ADVANCE_CANCEL',
PAY_IN_ADVANCE_ERROR: 'PAY_IN_ADVANCE_ERROR',
PAY_IN_ADVANCE_CREDIT_CARD_ERROR: 'PAY_IN_ADVANCE_CREDIT_CARD_ERROR',
PAY_IN_ADVANCE_SUCCESS: 'PAY_IN_ADVANCE_SUCCESS',
diff --git a/src/router/router-constants/routing-table.js b/src/router/router-constants/routing-table.js
index aa686acd..f461bf59 100644
--- a/src/router/router-constants/routing-table.js
+++ b/src/router/router-constants/routing-table.js
@@ -641,6 +641,10 @@ const routingTable = () => [
scenario: navigationScenarios.CLICKED_PAY_NOW,
destinationIssPageValue: issPageValues.PAYMENT_PAGE
},
+ {
+ scenario: navigationScenarios.CLICKED_PAY_NOW_ADYEN,
+ destinationIssPageValue: issPageValues.PAYMENT_PAGE_ADYEN
+ },
{
scenario: navigationScenarios.EDIT_SERVICE_LOCATION_INSHOP,
destinationIssPageValue: issPageValues.SCHEDULE_PAGE
@@ -676,6 +680,23 @@ const routingTable = () => [
}
]
},
+ {
+ issPageValue: issPageValues.PAYMENT_PAGE_ADYEN,
+ maps: [
+ {
+ scenario: navigationScenarios.CLICKED_BACK,
+ destinationIssPageValue: issPageValues.PAYMENT_METHOD
+ },
+ {
+ scenario: navigationScenarios.CLICKED_FORWARD,
+ destinationIssPageValue: issPageValues.ORDER_CONFIRMATION
+ },
+ {
+ scenario: navigationScenarios.PAY_IN_ADVANCE_SUCCESS,
+ destinationIssPageValue: issPageValues.ORDER_CONFIRMATION
+ }
+ ]
+ },
{
issPageValue: issPageValues.PAYMENT_RETURN,
maps: [
@@ -693,6 +714,23 @@ const routingTable = () => [
}
]
},
+ {
+ issPageValue: issPageValues.PAYMENT_RETURN_ADYEN,
+ maps: [
+ {
+ scenario: navigationScenarios.PAY_IN_ADVANCE_CANCEL,
+ destinationIssPageValue: issPageValues.PAYMENT_PAGE_ADYEN
+ },
+ {
+ scenario: navigationScenarios.PAY_IN_ADVANCE_ERROR,
+ destinationIssPageValue: issPageValues.PAYMENT_PAGE_ADYEN
+ },
+ {
+ scenario: navigationScenarios.PAY_IN_ADVANCE_SUCCESS,
+ destinationIssPageValue: issPageValues.ORDER_CONFIRMATION
+ }
+ ]
+ },
{
issPageValue: issPageValues.TPA_CONFIRMATION,
maps: [
diff --git a/src/store/index.js b/src/store/index.js
index 59df6e99..b628cd16 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -1778,6 +1778,9 @@ export const useMainStore = defineStore({
updatePaypalToken(token) {
this.order.payment.paypalToken = token;
},
+ updateNextGenSettledAmount(amount) {
+ this.order.payment.nextGenSettledAmount = amount;
+ },
setSaveSessionPromise(promise) {
this.applicationUser.saveSessionPromise = promise;
},
@@ -2896,6 +2899,33 @@ export const useMainStore = defineStore({
endpoint: endpoints.GetPaymentSignature.url
});
},
+ async initializeAdyenPayment(requestBody) {
+ const response = await globalMethods.callHttpClient({
+ method: endpoints.InitializeAdyenPayment.method,
+ endpoint: endpoints.InitializeAdyenPayment.url,
+ payload: requestBody,
+ logApiCall: true
+ });
+ return response.data;
+ },
+ async getAdyenSessionResult(paymentSessionRequest) {
+ const response = await globalMethods.callHttpClient({
+ method: endpoints.GetAdyenSessionResult.method,
+ endpoint: endpoints.GetAdyenSessionResult.url,
+ payload: paymentSessionRequest,
+ logApiCall: true
+ });
+ return response.data;
+ },
+ async getAdyenSessionResult(paymentSessionRequest) {
+ const response = await globalMethods.callHttpClient({
+ method: endpoints.GetAdyenSessionResult.method,
+ endpoint: endpoints.GetAdyenSessionResult.url,
+ payload: paymentSessionRequest,
+ logApiCall: true
+ });
+ return response.data;
+ },
hasSubmittedOrder() {
return window.sessionStorage.getItem(webStorageConstants.SUBMITTED_ORDER) !== null;
diff --git a/vue.config.js b/vue.config.js
index 3e5f6ffa..d9d97c60 100644
--- a/vue.config.js
+++ b/vue.config.js
@@ -3,6 +3,8 @@ process.env.VUE_APP_CURRENT_ENVIRONMENT = 'Localhost';
process.env.VUE_APP_CUSTOMER_PORTAL_URL = 'https://myaccountdev.safelite.com/';
process.env.VUE_APP_GOOGLE_PLACES_API_KEY = 'AIzaSyCuLhQcDdZTTb4JzpUFms1OCch2dk5lHF0';
process.env.VUE_APP_SAFELITE_HOP = 'https://iss-hop-dev.glassclaim.com/fmgCheckoutShared.aspx';
+process.env.VUE_APP_ADYEN_ENVIRONMENT = 'test';
+process.env.VUE_APP_ADYEN_CLIENT_KEY = 'test_WYGT4F5ZT5BRRL5MPWHK6QLYOIXZXROD';
// GA & GTM
// NOTE: Using the old ISS site GTM Cotnainer ID for now, will create a new one soon.