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/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/endpoints.js b/src/constants/endpoints.js
index c1771b3f5..e40fce79a 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: "/payment/api/v1/payment/payment/session/initialize-session",
+ method: "POST",
+ },
+ GetAdyenSessionResult: {
+ url: "/payment/api/v1/payment/payment/session/session-result",
+ method: "POST",
+ },
};
export { endpoints };
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/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
new file mode 100644
index 000000000..5e74692d5
--- /dev/null
+++ b/src/layouts/payment-adyen/payment-adyen.vue
@@ -0,0 +1,410 @@
+
+
+
+
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/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/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/routes.js b/src/router/constants/routes.js
index 98d6c503a..5090df2b3 100644
--- a/src/router/constants/routes.js
+++ b/src/router/constants/routes.js
@@ -89,6 +89,15 @@ export const routeData = {
name: "payment-pia-return",
path: "/payment-pia-return",
},
+ PAYMENT_ADYEN: {
+ 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/constants/routing-table.js b/src/router/constants/routing-table.js
index 02a0d9384..192e1572e 100644
--- a/src/router/constants/routing-table.js
+++ b/src/router/constants/routing-table.js
@@ -537,6 +537,10 @@ const routingTable = function () {
scenario: navigationScenarios.CLICKED_PAY_NOW,
destinationPageData: routeData.PAYMENT,
},
+ {
+ scenario: navigationScenarios.CLICKED_PAY_NOW_ADYEN,
+ destinationPageData: routeData.PAYMENT_ADYEN,
+ },
{
scenario: navigationScenarios.CLICKED_INSURANCE,
destinationPageData: routeData.INSURANCE_COMPANY,
@@ -563,6 +567,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/route-logic/adyen-return.js b/src/router/methods/route-logic/adyen-return.js
new file mode 100644
index 000000000..a516518ac
--- /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 97af04068..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.
@@ -36,6 +37,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),
@@ -47,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),
];