Merge branch 'develop' into feature/jzimmerman/INSR-8742
This commit is contained in:
commit
bc309884cd
15 changed files with 1099 additions and 5 deletions
41
package-lock.json
generated
41
package-lock.json
generated
|
|
@ -8,6 +8,7 @@
|
||||||
"name": "digitalconsumer.iss",
|
"name": "digitalconsumer.iss",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@adyen/adyen-web": "^6.31.1",
|
||||||
"axios": "^1.13.5",
|
"axios": "^1.13.5",
|
||||||
"axios-retry": "^3.5.0",
|
"axios-retry": "^3.5.0",
|
||||||
"bootstrap": "^5.3",
|
"bootstrap": "^5.3",
|
||||||
|
|
@ -93,6 +94,18 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@ampproject/remapping": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
|
@ -4125,6 +4138,12 @@
|
||||||
"node": ">= 10"
|
"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": {
|
"node_modules/@types/aria-query": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
|
@ -4283,6 +4302,12 @@
|
||||||
"@types/node": "*"
|
"@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": {
|
"node_modules/@types/graceful-fs": {
|
||||||
"version": "4.1.5",
|
"version": "4.1.5",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
|
@ -7371,6 +7396,12 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/clean-css": {
|
||||||
"version": "5.3.1",
|
"version": "5.3.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
|
@ -17407,6 +17438,16 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/prebuild-install": {
|
||||||
"version": "7.1.3",
|
"version": "7.1.3",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
"lint:inspect": "eslint --inspect-config"
|
"lint:inspect": "eslint --inspect-config"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@adyen/adyen-web": "^6.31.1",
|
||||||
"axios": "^1.13.5",
|
"axios": "^1.13.5",
|
||||||
"axios-retry": "^3.5.0",
|
"axios-retry": "^3.5.0",
|
||||||
"bootstrap": "^5.3",
|
"bootstrap": "^5.3",
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,9 @@ const applicationConfig = Object.freeze({
|
||||||
FRONTEND_LOGGER_PATH: "/analytics/api/v1/logging",
|
FRONTEND_LOGGER_PATH: "/analytics/api/v1/logging",
|
||||||
BAILOUT_ON_APPLICATION_ERROR: true,
|
BAILOUT_ON_APPLICATION_ERROR: true,
|
||||||
BAILOUT_ON_API_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;
|
export default applicationConfig;
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ const PARTS_BASE_URL = '/parts/api/v1/parts';
|
||||||
const PRICE_BASE_URL = '/price/api/v1/price';
|
const PRICE_BASE_URL = '/price/api/v1/price';
|
||||||
const SCHEDULE_BASE_URL = '/schedule/api/v1/schedule';
|
const SCHEDULE_BASE_URL = '/schedule/api/v1/schedule';
|
||||||
const VEHICLE_BASE_URL = '/vehicle/api/v1/vehicle';
|
const VEHICLE_BASE_URL = '/vehicle/api/v1/vehicle';
|
||||||
|
const PAYMENT_BASE_URL = '/payment/api/v1/payment';
|
||||||
|
|
||||||
const endpoints = Object.freeze({
|
const endpoints = Object.freeze({
|
||||||
GetRouteInfo: {
|
GetRouteInfo: {
|
||||||
|
|
@ -247,6 +248,14 @@ const endpoints = Object.freeze({
|
||||||
GetPaymentSignature: {
|
GetPaymentSignature: {
|
||||||
url: `${ORDER_BASE_URL}/sign`,
|
url: `${ORDER_BASE_URL}/sign`,
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
|
},
|
||||||
|
InitializeAdyenPayment: {
|
||||||
|
url: `${PAYMENT_BASE_URL}/payment/session/initialize-session`,
|
||||||
|
method: "POST",
|
||||||
|
},
|
||||||
|
GetAdyenSessionResult: {
|
||||||
|
url: `${PAYMENT_BASE_URL}/payment/session/session-result`,
|
||||||
|
method: "POST",
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
const experimentUniverses = Object.freeze({
|
const experimentUniverses = Object.freeze({
|
||||||
ISS_FUNNEL: 'ISSFunnel',
|
ISS_FUNNEL: 'ISSFunnel',
|
||||||
ISS_FEATURETOGGLE_AREFEES_HIDDEN: 'NextGenISS_FeatureToggle_AreFeesHidden',
|
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({
|
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_HIDDEN: 'HideMobileFee',
|
||||||
ISS_FEATURE_TOGGLE_IS_MOBILE_FEE_OVERRIDDEN: 'OverrideMobileFee',
|
ISS_FEATURE_TOGGLE_IS_MOBILE_FEE_OVERRIDDEN: 'OverrideMobileFee',
|
||||||
ISS_FEATURE_TOGGLE_IS_RECYCLE_FEE_HIDDEN: 'HideRecycleFee',
|
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({
|
const experimentTriggers = Object.freeze({
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,14 @@ export const pageProgressMapper = {
|
||||||
// No progress bar on this page
|
// No progress bar on this page
|
||||||
percent: 0
|
percent: 0
|
||||||
},
|
},
|
||||||
|
'payment-page-adyen': {
|
||||||
|
// No progress bar on this page
|
||||||
|
percent: 0
|
||||||
|
},
|
||||||
|
'payment-return-adyen': {
|
||||||
|
// No progress bar on this page
|
||||||
|
percent: 0
|
||||||
|
},
|
||||||
'tpa-search': {
|
'tpa-search': {
|
||||||
percent: 80
|
percent: 80
|
||||||
},
|
},
|
||||||
|
|
|
||||||
164
src/helpers/adyen-helper.js
Normal file
164
src/helpers/adyen-helper.js
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
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 getSessionInfo(sessionId, encryptedSessionResult) {
|
||||||
|
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
|
||||||
|
});
|
||||||
|
|
||||||
|
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",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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 zipCode = useMainStore().order.customer.address.zipCode
|
||||||
|
?? useMainStore().order.serviceLocation.zipCode
|
||||||
|
?? useMainStore().order.serviceLocation.provider.address.zipCode
|
||||||
|
|
||||||
|
const ccToken = {
|
||||||
|
subscriptionId: adyenSessionInfo?.storedToken,
|
||||||
|
expMonth: adyenSessionInfo?.cardExpiryMonth,
|
||||||
|
expYear: adyenSessionInfo?.cardExpiryYear,
|
||||||
|
cardType: adyenSessionInfo?.cardType,
|
||||||
|
billToPostalCode: zipCode,
|
||||||
|
billToFirstName: useMainStore().contactInfo.firstName,
|
||||||
|
billToLastName: useMainStore().contactInfo.lastName,
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
|
@ -239,8 +239,23 @@ export default {
|
||||||
|
|
||||||
return !isEnabled || useMainStore().isUnverified || (useMainStore().isDeductible && isDeductibleTotalEqualToZero);
|
return !isEnabled || useMainStore().isUnverified || (useMainStore().isDeductible && isDeductibleTotalEqualToZero);
|
||||||
},
|
},
|
||||||
|
isAdyenEnabled() {
|
||||||
|
const adyenEnabled = this.getSettingValue(experimentSettings.ISS_ENABLE_ADYEN_V1);
|
||||||
|
return adyenEnabled === 'true';
|
||||||
|
},
|
||||||
paymentMethodWidgetName() {
|
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() {
|
showCartTotal() {
|
||||||
return this.mainStore.isVerified && (this.mainStore.isITAC || this.mainStore.isNoComp
|
return this.mainStore.isVerified && (this.mainStore.isITAC || this.mainStore.isNoComp
|
||||||
|
|
@ -418,8 +433,12 @@ export default {
|
||||||
bailoutOnError: true
|
bailoutOnError: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const scenario = this.isAdyenEnabled
|
||||||
|
? this.navigationScenarios.CLICKED_PAY_NOW_ADYEN
|
||||||
|
: this.navigationScenarios.CLICKED_PAY_NOW;
|
||||||
|
|
||||||
this.$router.navigate(
|
this.$router.navigate(
|
||||||
this.navigationScenarios.CLICKED_PAY_NOW,
|
scenario,
|
||||||
this.$route,
|
this.$route,
|
||||||
{},
|
{},
|
||||||
{ [routerParams.SKIP_SAVE_SESSION]: true }
|
{ [routerParams.SKIP_SAVE_SESSION]: true }
|
||||||
|
|
|
||||||
623
src/layouts/payment-page-adyen/payment-page-adyen.vue
Normal file
623
src/layouts/payment-page-adyen/payment-page-adyen.vue
Normal file
|
|
@ -0,0 +1,623 @@
|
||||||
|
<template>
|
||||||
|
<Form>
|
||||||
|
<div class="fade-on-route-transition">
|
||||||
|
<div class="justify-content-center">
|
||||||
|
<siteHeader cmsWidgetName="SiteHeaderWidget" />
|
||||||
|
</div>
|
||||||
|
<div class="iss-heritage-container-width payment-page-container">
|
||||||
|
<div id="card-container" class="row">
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<alert
|
||||||
|
v-if="hasPaymentFailureError"
|
||||||
|
name="payInAdvanceErrorAlert"
|
||||||
|
class="pay-in-advance-alert"
|
||||||
|
cmsWidgetName="PayInAdvanceErrorAlertWidget"
|
||||||
|
alertClass="alert-danger"
|
||||||
|
:isDismissible="false"
|
||||||
|
:isCollapsible="true"
|
||||||
|
:preventDefaultOnClick="true"
|
||||||
|
@textLinkClicked="paymentFailedPayLater" />
|
||||||
|
<div id="adyen-container"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4 col-md-10 col-xs-12">
|
||||||
|
<div class="order-summary-panel">
|
||||||
|
<div class="cart-title">{{ cartTitle }}</div>
|
||||||
|
<cartDropdown
|
||||||
|
ref="cart"
|
||||||
|
class="cart-dropdown-component"
|
||||||
|
:readOnly="true"
|
||||||
|
:showAsPaid="false"
|
||||||
|
recyclingModalCmsWidgetName="RecycleModal"
|
||||||
|
servicePackageTitleWidgetName="ServicePackageTitle" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<siteFooter
|
||||||
|
class="footer-component"
|
||||||
|
ref="siteFooter"
|
||||||
|
cmsWidgetName="SiteFooterWidget"
|
||||||
|
:isForwardActionDisabled="true"
|
||||||
|
:isForwardButtonHidden="true"
|
||||||
|
@ForwardClicked="forwardButtonAction"
|
||||||
|
@backClicked="backButtonAction" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
// Components
|
||||||
|
import siteHeader from '@/iss-components/site-header/site-header.vue';
|
||||||
|
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
|
||||||
|
import alert from '@/ux-components/alert/alert.vue';
|
||||||
|
import cartDropdown from '@/iss-components/cart-dropdown/cart-dropdown.vue';
|
||||||
|
|
||||||
|
// Supporting files
|
||||||
|
import issPageValues from '@/router/router-constants/issPage-values.js';
|
||||||
|
import { fetchCmsContentForPage } from '@/helpers/cms-content-helper';
|
||||||
|
import settleAllPromises from '@/helpers/layout-helper';
|
||||||
|
import { Form } from 'vee-validate';
|
||||||
|
import BaseFormMixin from '@/mixins/base-form-mixin.js';
|
||||||
|
import { getCartTotal } from '@/helpers/cart-helper';
|
||||||
|
import { useMainStore } from '@/store';
|
||||||
|
import widgetFields from '@/constants/cms-widget-fields.js';
|
||||||
|
import { paymentMethods } from '@/constants/payment-method-constants.js';
|
||||||
|
import { AppointmentTypeStrings } from '@/constants/schedule-constants.js';
|
||||||
|
import queryStrings from '@/constants/query-strings';
|
||||||
|
import { formatAmountInDollars } from '@/helpers/text-helper';
|
||||||
|
import showIssLoadingModal from '@/helpers/loading-modal-helper';
|
||||||
|
import { submitWorkOrder } from '@/helpers/order-helper';
|
||||||
|
import submitType from '@/constants/submit-type';
|
||||||
|
|
||||||
|
import "@adyen/adyen-web/styles/adyen.css";
|
||||||
|
import { mapAdyenToIssPaymentMethod, mapIssToAdyenPaymentMethod } from '@/helpers/adyen-helper';
|
||||||
|
import { createAdyenCheckout } from "@/helpers/adyen-helper";
|
||||||
|
import { Dropin } from "@adyen/adyen-web/auto";
|
||||||
|
import applicationConfig from '@/constants/application-config';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'payment-page-adyen',
|
||||||
|
components: {
|
||||||
|
cartDropdown,
|
||||||
|
siteHeader,
|
||||||
|
siteFooter,
|
||||||
|
Form,
|
||||||
|
alert
|
||||||
|
},
|
||||||
|
mixins: [BaseFormMixin],
|
||||||
|
async beforeRouteEnter(to, from, next) {
|
||||||
|
const alertToDisplay = to.query[queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT];
|
||||||
|
|
||||||
|
// Call APIs
|
||||||
|
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
|
||||||
|
|
||||||
|
// Settle promises and get results
|
||||||
|
const promiseResultMap = [
|
||||||
|
{
|
||||||
|
resultKey: 'cmsContent',
|
||||||
|
promise: cmsContentPromise
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// use resultMap to populate layout content.
|
||||||
|
const resultMap = await settleAllPromises(promiseResultMap);
|
||||||
|
|
||||||
|
next((vm) => {
|
||||||
|
vm.setCmsContent(resultMap.cmsContent);
|
||||||
|
vm.$nextTick(() => {
|
||||||
|
if (vm.$refs.cart) {
|
||||||
|
const cartItems = vm.$refs.cart.cartItems;
|
||||||
|
vm.getPayInAdvanceLineItems(cartItems);
|
||||||
|
}
|
||||||
|
if (alertToDisplay) {
|
||||||
|
vm.hasPaymentFailureError = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
showIssLoadingModal(true);
|
||||||
|
this.initializeAdyen().finally(() => {
|
||||||
|
showIssLoadingModal(false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
referralSequenceNumber: useMainStore().order.referralSequenceNumber,
|
||||||
|
emailAddress: useMainStore().contactInfo.emailAddress,
|
||||||
|
address1: this.getAddress1(),
|
||||||
|
address2: this.getAddress2(),
|
||||||
|
city: this.getCity(),
|
||||||
|
state: this.getState(),
|
||||||
|
zipCode: this.getZipCode(),
|
||||||
|
firstName: useMainStore().contactInfo.firstName,
|
||||||
|
lastName: useMainStore().contactInfo.lastName,
|
||||||
|
phoneNumber: useMainStore().contactInfo.servicePhone,
|
||||||
|
ctu: useMainStore().order.serviceLocation.provider?.address?.zipCodeCtu ?? useMainStore().order.serviceLocation.zipCodeCtu,
|
||||||
|
referralCorrelationId: useMainStore().order.referralCorrelationId,
|
||||||
|
workOrderNumber: this.getWorkOrderNumber(),
|
||||||
|
invoiceNumber: this.getInvoiceNumber(),
|
||||||
|
totalAmount: this.getAmountDue(),
|
||||||
|
displayAmount: this.getDisplayAmountDue(),
|
||||||
|
payInAdvanceLineItems: '',
|
||||||
|
sessionId: '',
|
||||||
|
dropinComponent: null,
|
||||||
|
sourceSystem: 'ISS-NextGen',
|
||||||
|
hasPaymentFailureError: false,
|
||||||
|
adyenTimeout: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
unmounted() {
|
||||||
|
if (this.adyenTimeout) {
|
||||||
|
clearTimeout(this.adyenTimeout);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
piaType() {
|
||||||
|
return this.mainStore.payment.paymentMethod;
|
||||||
|
},
|
||||||
|
parentDomainName() {
|
||||||
|
return window.location.hostname;
|
||||||
|
},
|
||||||
|
payInAdvanceResponseUrl() {
|
||||||
|
const { protocol, host } = window.location;
|
||||||
|
return `${protocol}//${host}/?issPage=${issPageValues.PAYMENT_RETURN_ADYEN}&src=iss-nextgen`;
|
||||||
|
},
|
||||||
|
payInAdvanceCancelUrl() {
|
||||||
|
const { protocol, host } = window.location;
|
||||||
|
return `${protocol}//${host}/?issPage=${issPageValues.PAYMENT_METHOD}&src=iss-nextgen`;
|
||||||
|
},
|
||||||
|
cartTitle() {
|
||||||
|
return this.getCmsContent('OrderSummaryTextWidget', widgetFields.TEXT_BLOCK_WIDGET.TEXT);
|
||||||
|
},
|
||||||
|
switchPaymentText() {
|
||||||
|
return this.getCmsContent('SwitchPaymentTextWidget', widgetFields.TEXT_BLOCK_WIDGET.TEXT);
|
||||||
|
},
|
||||||
|
submitPaymentText() {
|
||||||
|
return this.getCmsContent('SiteFooterWidget', widgetFields.FOOTER_WIDGET.FORWARD_BUTTON_TEXT);
|
||||||
|
},
|
||||||
|
adyenPriceTotal() {
|
||||||
|
return Math.round(this.totalAmount * 100);
|
||||||
|
},
|
||||||
|
workOrderNumberLastSixDigits() {
|
||||||
|
const workOrderNumber = this.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;
|
||||||
|
},
|
||||||
|
splitStreetAddress() {
|
||||||
|
const address = this.address1;
|
||||||
|
const tokens = address.split(" ") ?? [""];
|
||||||
|
const number = tokens[0];
|
||||||
|
|
||||||
|
const street = tokens.slice(1).join(" ") ?? "";
|
||||||
|
|
||||||
|
return {
|
||||||
|
number: number ?? "",
|
||||||
|
street: street ?? "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
arePagePrerequisitesValid() {
|
||||||
|
// Line Items
|
||||||
|
const packageReqs =
|
||||||
|
!!useMainStore().order.lineItems.supportingItems;
|
||||||
|
|
||||||
|
// Service Location
|
||||||
|
const { serviceLocation } = useMainStore().order;
|
||||||
|
const mobileReqs = !!(
|
||||||
|
serviceLocation.address
|
||||||
|
&& serviceLocation.city
|
||||||
|
&& serviceLocation.state
|
||||||
|
&& serviceLocation.zipCode
|
||||||
|
);
|
||||||
|
|
||||||
|
const providerLocation = serviceLocation.provider.address;
|
||||||
|
const dropOffInshopReqs = !!(
|
||||||
|
providerLocation.streetAddress
|
||||||
|
&& providerLocation.city
|
||||||
|
&& providerLocation.state
|
||||||
|
&& providerLocation.zipCode
|
||||||
|
);
|
||||||
|
|
||||||
|
const isMobile = serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE
|
||||||
|
|| serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP;
|
||||||
|
|
||||||
|
const serviceLocationReqs =
|
||||||
|
(isMobile && mobileReqs) || (!isMobile && dropOffInshopReqs);
|
||||||
|
|
||||||
|
// Insurance
|
||||||
|
const isInsuranceSet =
|
||||||
|
useMainStore().order.payment.isInsurance !== null;
|
||||||
|
|
||||||
|
// Schedule
|
||||||
|
const { schedule } = useMainStore().order;
|
||||||
|
const scheduleReqs = !!(
|
||||||
|
schedule.date
|
||||||
|
&& schedule.startTime
|
||||||
|
&& schedule.endTime
|
||||||
|
&& schedule.jobMaxMinutes
|
||||||
|
&& schedule.jobMinMinutes
|
||||||
|
);
|
||||||
|
|
||||||
|
// Contact Info
|
||||||
|
const { contactInfo } = useMainStore();
|
||||||
|
const contactInfoReqs = !!(
|
||||||
|
contactInfo.firstName
|
||||||
|
&& contactInfo.lastName
|
||||||
|
&& contactInfo.servicePhone
|
||||||
|
&& contactInfo.emailAddress
|
||||||
|
);
|
||||||
|
|
||||||
|
const paymentMethodReqs = useMainStore().isPayInAdvance;
|
||||||
|
|
||||||
|
return (
|
||||||
|
packageReqs
|
||||||
|
&& serviceLocationReqs
|
||||||
|
&& isInsuranceSet
|
||||||
|
&& scheduleReqs
|
||||||
|
&& contactInfoReqs
|
||||||
|
&& paymentMethodReqs
|
||||||
|
);
|
||||||
|
},
|
||||||
|
getWorkOrderNumber() {
|
||||||
|
const { workOrderNumber } = useMainStore().order;
|
||||||
|
if (workOrderNumber) {
|
||||||
|
const items = workOrderNumber.split('-');
|
||||||
|
if (items.length > 1) {
|
||||||
|
return items[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
getInvoiceNumber() {
|
||||||
|
const { workOrderNumber } = useMainStore().order;
|
||||||
|
if (workOrderNumber) {
|
||||||
|
return workOrderNumber.replace('-', '');
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
getAddress1() {
|
||||||
|
return (
|
||||||
|
useMainStore().order.serviceLocation.address
|
||||||
|
?? useMainStore().order.serviceLocation.provider.address
|
||||||
|
.streetAddress
|
||||||
|
);
|
||||||
|
},
|
||||||
|
getAddress2() {
|
||||||
|
return useMainStore().order.serviceLocation.address2;
|
||||||
|
},
|
||||||
|
getCity() {
|
||||||
|
return (
|
||||||
|
useMainStore().order.serviceLocation.city
|
||||||
|
?? useMainStore().order.serviceLocation.provider.address.city
|
||||||
|
);
|
||||||
|
},
|
||||||
|
getState() {
|
||||||
|
return (
|
||||||
|
useMainStore().order.serviceLocation.state
|
||||||
|
?? useMainStore().order.serviceLocation.provider.address.state
|
||||||
|
);
|
||||||
|
},
|
||||||
|
getZipCode() {
|
||||||
|
return (
|
||||||
|
useMainStore().order.customer.address.zipCode
|
||||||
|
?? useMainStore().order.serviceLocation.zipCode
|
||||||
|
?? useMainStore().order.serviceLocation.provider.address.zipCode
|
||||||
|
);
|
||||||
|
},
|
||||||
|
getPayInAdvanceLineItems(cartItems) {
|
||||||
|
const { glassParts } = useMainStore().order.lineItems;
|
||||||
|
const lineItems =
|
||||||
|
glassParts === null
|
||||||
|
? [
|
||||||
|
this.getPayInAdvanceFormattedLineItem('Labor', 0, 1),
|
||||||
|
this.getPayInAdvanceFormattedLineItem('Repair supplies', 0, 1)
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
this.getPayInAdvanceFormattedLineItem('Parts and labor', 0, 1)
|
||||||
|
];
|
||||||
|
|
||||||
|
cartItems.forEach((item) => {
|
||||||
|
if (item.name !== null && item.category !== 'promos') {
|
||||||
|
lineItems.push(this.getPayInAdvanceFormattedLineItem(
|
||||||
|
item.name,
|
||||||
|
(item.salesTax + item.subTotal).toFixed(2),
|
||||||
|
1
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.payInAdvanceLineItems = lineItems.join('||');
|
||||||
|
},
|
||||||
|
getPayInAdvanceFormattedLineItem(itemName, price, quantity) {
|
||||||
|
return `${itemName}|${price}|${quantity}`;
|
||||||
|
},
|
||||||
|
getAmountDue() {
|
||||||
|
return getCartTotal(useMainStore().order);
|
||||||
|
},
|
||||||
|
getDisplayAmountDue() {
|
||||||
|
return formatAmountInDollars(this.getAmountDue());
|
||||||
|
},
|
||||||
|
async paymentFailedPayLater() {
|
||||||
|
this.showIssLoadingModal(true);
|
||||||
|
this.mainStore.savePaymentMethodChoice(paymentMethods.PAY_AT_TIME_OF_SERVICE);
|
||||||
|
await submitWorkOrder({ submitType: submitType.SAFELITE });
|
||||||
|
this.$router.navigate(
|
||||||
|
this.navigationScenarios.CLICKED_FORWARD,
|
||||||
|
this.$route
|
||||||
|
);
|
||||||
|
},
|
||||||
|
backButtonAction() {
|
||||||
|
// page will not render with normal navigation due to navigating from the iframe
|
||||||
|
window.location = this.payInAdvanceCancelUrl;
|
||||||
|
},
|
||||||
|
forwardButtonAction() {
|
||||||
|
this.$router.navigate(
|
||||||
|
this.navigationScenarios.CLICKED_FORWARD,
|
||||||
|
this.$route
|
||||||
|
);
|
||||||
|
},
|
||||||
|
async initializeAdyen() {
|
||||||
|
const requestBody = this.getAdyenInitRequestInfo();
|
||||||
|
|
||||||
|
const adyenResponse = await this.mainStore.initializeAdyenPayment(requestBody);
|
||||||
|
|
||||||
|
const session = {
|
||||||
|
id: adyenResponse.sessionId,
|
||||||
|
sessionData: adyenResponse.sessionData,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.sessionId = adyenResponse.sessionId;
|
||||||
|
|
||||||
|
const checkout = await createAdyenCheckout({
|
||||||
|
session: session,
|
||||||
|
handlers: {
|
||||||
|
onPaymentCompleted: (result, component) => {
|
||||||
|
this.handleCompletedPayment(result);
|
||||||
|
},
|
||||||
|
onPaymentFailed: (result, component) => {
|
||||||
|
this.handleFailedPayment(result);
|
||||||
|
},
|
||||||
|
onError: (error, component) => {
|
||||||
|
this.handleError(error);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
amount: this.adyenPriceTotal,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const expiryTime = new Date(checkout.options.expiresAt);
|
||||||
|
const expiryInterval = expiryTime.getTime() - new Date().getTime();
|
||||||
|
|
||||||
|
const handleTimeout = () => {
|
||||||
|
this.resetAdyenDropin();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.adyenTimeout) {
|
||||||
|
clearTimeout(this.adyenTimeout);
|
||||||
|
}
|
||||||
|
this.adyenTimeout = setTimeout(handleTimeout, expiryInterval);
|
||||||
|
|
||||||
|
const configuration = {
|
||||||
|
paymentMethodsConfiguration: {
|
||||||
|
ideal: {
|
||||||
|
showImage: true,
|
||||||
|
},
|
||||||
|
paypal: {
|
||||||
|
amount: {
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
hasHolderName: true,
|
||||||
|
holderNameRequired: true,
|
||||||
|
billingAddressRequired: true,
|
||||||
|
name: "Credit or debit card",
|
||||||
|
data: {
|
||||||
|
holderName: `${this.mainStore.contactInfo.firstName} ${this.mainStore.contactInfo.lastName}`,
|
||||||
|
billingAddress: {
|
||||||
|
postalCode: this.zipCode,
|
||||||
|
country: "US"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const adyenPaymentType = mapIssToAdyenPaymentMethod(this.piaType);
|
||||||
|
|
||||||
|
if (adyenPaymentType) {
|
||||||
|
configuration.openPaymentMethod = {
|
||||||
|
type: adyenPaymentType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const dropin = new Dropin(checkout, configuration);
|
||||||
|
|
||||||
|
this.dropinComponent = dropin;
|
||||||
|
|
||||||
|
dropin.mount("#adyen-container");
|
||||||
|
},
|
||||||
|
getAdyenInitRequestInfo() {
|
||||||
|
const request = {
|
||||||
|
sourceSystem: this.sourceSystem,
|
||||||
|
referralSequenceNumber: this.referralSequenceNumber,
|
||||||
|
workOrderNumber: this.workOrderNumberLastSixDigits,
|
||||||
|
zipCodeCtu: this.ctu,
|
||||||
|
amount: this.adyenPriceTotal,
|
||||||
|
city: this.city,
|
||||||
|
houseNumberOrName: this.splitStreetAddress.number,
|
||||||
|
street: this.splitStreetAddress.street,
|
||||||
|
zipCode: this.zipCode,
|
||||||
|
stateOrProvince: this.state,
|
||||||
|
returnUrl: this.payInAdvanceResponseUrl,
|
||||||
|
email: this.mainStore.contactInfo.emailAddress,
|
||||||
|
firstName: this.mainStore.contactInfo.firstName,
|
||||||
|
lastName: this.mainStore.contactInfo.lastName,
|
||||||
|
idempotencyKey: this.getIdempotencyKey(),
|
||||||
|
};
|
||||||
|
return request;
|
||||||
|
},
|
||||||
|
async resetAdyenDropin() {
|
||||||
|
this?.dropinComponent?.unmount();
|
||||||
|
await this.initializeAdyen();
|
||||||
|
},
|
||||||
|
getIdempotencyKey() {
|
||||||
|
const currentDateTime = new Date();
|
||||||
|
const currentHour = currentDateTime.getUTCHours();
|
||||||
|
const currentDate = currentDateTime.getUTCDate();
|
||||||
|
const id = this?.mainStore?.order?.referralCorrelationId;
|
||||||
|
const system = this.sourceSystem;
|
||||||
|
const total = this.adyenPriceTotal;
|
||||||
|
|
||||||
|
return `${id}-${system}-${currentDate}-${currentHour}-${total}`;
|
||||||
|
},
|
||||||
|
async handleCompletedPayment(result) {
|
||||||
|
// Fetch session info
|
||||||
|
const paymentSessionRequest = {
|
||||||
|
zipCodeCtu: this.ctu,
|
||||||
|
workOrderNumber: this.workOrderNumberLastSixDigits,
|
||||||
|
sourceSystem: this.sourceSystem,
|
||||||
|
sessionId: this.sessionId,
|
||||||
|
sessionResult: result?.sessionResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
showIssLoadingModal(true);
|
||||||
|
|
||||||
|
const adyenResponse = await this.mainStore.getAdyenSessionResult(paymentSessionRequest);
|
||||||
|
|
||||||
|
const ccToken = {
|
||||||
|
subscriptionId: adyenResponse?.storedToken,
|
||||||
|
expMonth: adyenResponse?.cardExpiryMonth,
|
||||||
|
expYear: adyenResponse?.cardExpiryYear,
|
||||||
|
cardType: adyenResponse?.cardType,
|
||||||
|
billToPostalCode: this.zipCode,
|
||||||
|
billToFirstName: this.mainStore.contactInfo.firstName,
|
||||||
|
billToLastName: this.mainStore.contactInfo.lastName,
|
||||||
|
referenceNumber: adyenResponse?.workOrderNumber,
|
||||||
|
authCode: adyenResponse?.authCode,
|
||||||
|
transactionId: adyenResponse?.transactionReference,
|
||||||
|
transReferenceNumber: adyenResponse?.transactionReference,
|
||||||
|
lastFour: adyenResponse?.last4DigitsOfCard,
|
||||||
|
};
|
||||||
|
|
||||||
|
const paymentMethodFromSession = adyenResponse?.paymentMethod;
|
||||||
|
|
||||||
|
const paymentMethod = mapAdyenToIssPaymentMethod(paymentMethodFromSession);
|
||||||
|
|
||||||
|
this.mainStore.savePaymentMethodChoice(paymentMethod);
|
||||||
|
|
||||||
|
if (paymentMethod === paymentMethods.PAYPAL) {
|
||||||
|
this.mainStore.updatePaypalToken(adyenResponse?.storedToken);
|
||||||
|
} else {
|
||||||
|
this.mainStore.updateCreditCardToken(ccToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.mainStore.updateNextGenSettledAmount(this.totalAmount);
|
||||||
|
|
||||||
|
await this.saveAndSubmitWorkOrder();
|
||||||
|
},
|
||||||
|
async handleFailedPayment(result) {
|
||||||
|
const wasPaymentCancelled = result?.resultCode === "Cancelled";
|
||||||
|
|
||||||
|
if (!wasPaymentCancelled) {
|
||||||
|
const stringToLog = `ADYEN PAYMENT NOT AUTHORIZED. Code = ${result?.resultCode}. Id = ${this.sessionId}`;
|
||||||
|
|
||||||
|
await global.$logger.logError(stringToLog);
|
||||||
|
|
||||||
|
this.hasPaymentFailureError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dropinComponent?.update();
|
||||||
|
},
|
||||||
|
async handleError(error) {
|
||||||
|
const wasPaymentCancelled = error?.name === "CANCEL";
|
||||||
|
|
||||||
|
if (!wasPaymentCancelled) {
|
||||||
|
const stringToLog = `ADYEN ERROR. Name = ${error?.name}. Details = ${error?.message}.`;
|
||||||
|
|
||||||
|
await global.$logger.logError(stringToLog);
|
||||||
|
|
||||||
|
this.hasPaymentFailureError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dropinComponent?.update();
|
||||||
|
},
|
||||||
|
async saveAndSubmitWorkOrder() {
|
||||||
|
// Final work order submit after returning from pay in advance.
|
||||||
|
await submitWorkOrder({ submitType: submitType.SAFELITE });
|
||||||
|
|
||||||
|
this.$router.navigate(
|
||||||
|
this.navigationScenarios.PAY_IN_ADVANCE_SUCCESS,
|
||||||
|
this.$route
|
||||||
|
);
|
||||||
|
},
|
||||||
|
showIssLoadingModal
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.iss-heritage-container-width.payment-page-container {
|
||||||
|
padding: 0 .9375rem;
|
||||||
|
|
||||||
|
#card-container {
|
||||||
|
padding-top: 1.375rem;
|
||||||
|
margin: 0;
|
||||||
|
> * {
|
||||||
|
padding: 0 .9375rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-summary-panel {
|
||||||
|
margin-bottom: 1.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-title {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-top: 1.25rem;
|
||||||
|
margin-bottom: .625rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cart-dropdown-component.cart-table {
|
||||||
|
font-size: .8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-component:deep(.footer) {
|
||||||
|
margin: 1.25rem 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pay-in-advance-alert {
|
||||||
|
:deep(a) {
|
||||||
|
font-weight: $font-weight-bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-on-route-transition {
|
||||||
|
height: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment p {
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
160
src/layouts/payment-return-adyen/payment-return-adyen.vue
Normal file
160
src/layouts/payment-return-adyen/payment-return-adyen.vue
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
<template>
|
||||||
|
<Form
|
||||||
|
ref="theForm"
|
||||||
|
@submit="onSubmit"
|
||||||
|
@invalidSubmit="onInvalidSubmit">
|
||||||
|
</Form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Form } from 'vee-validate';
|
||||||
|
import BaseFormMixin from '@/mixins/base-form-mixin.js';
|
||||||
|
import { useMainStore } from '@/store/index.js';
|
||||||
|
import queryStrings from '@/constants/query-strings';
|
||||||
|
import { getCartTotal } from '@/helpers/cart-helper';
|
||||||
|
import { submitWorkOrder } from '@/helpers/order-helper.js';
|
||||||
|
import { createAdyenCheckout, generateCcToken, getSessionInfo, mapAdyenToIssPaymentMethod } from '@/helpers/adyen-helper';
|
||||||
|
import navigationScenarios from '@/router/router-constants/navigation-scenarios';
|
||||||
|
import submitType from '@/constants/submit-type';
|
||||||
|
import showIssLoadingModal from '@/helpers/loading-modal-helper';
|
||||||
|
import { paymentMethods } from '@/constants/payment-method-constants';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'payment-return-adyen',
|
||||||
|
components: {
|
||||||
|
Form
|
||||||
|
},
|
||||||
|
mixins: [BaseFormMixin],
|
||||||
|
async mounted() {
|
||||||
|
showIssLoadingModal(true);
|
||||||
|
const store = useMainStore();
|
||||||
|
|
||||||
|
const sessionId = this.$route?.query?.sessionId;
|
||||||
|
const redirectResult = this.$route?.query?.redirectResult;
|
||||||
|
|
||||||
|
if (sessionId && redirectResult) {
|
||||||
|
let result = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
result = await this.finalizeAdyenPayment(sessionId, redirectResult);
|
||||||
|
} catch (unexpectedResult) {
|
||||||
|
// If user cancelled payment, handle without error message
|
||||||
|
if (unexpectedResult.code === "Cancelled" || unexpectedResult.code === "CANCEL") {
|
||||||
|
this.$router.navigate(navigationScenarios.PAY_IN_ADVANCE_CANCEL, this.$route);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, failure scenario.
|
||||||
|
// Payment fails, so return user to payment-page-adyen screen to try again or pay later.
|
||||||
|
this.$router.navigate(
|
||||||
|
navigationScenarios.PAY_IN_ADVANCE_ERROR,
|
||||||
|
this.$route,
|
||||||
|
{
|
||||||
|
[queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT]: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const sessionInfo = await getSessionInfo(sessionId, result.sessionResult);
|
||||||
|
|
||||||
|
const paymentMethod = mapAdyenToIssPaymentMethod(sessionInfo?.paymentMethod);
|
||||||
|
const amountDue = getCartTotal(store.order);
|
||||||
|
const ccToken = generateCcToken(sessionInfo);
|
||||||
|
if (paymentMethod === paymentMethods.AFTERPAY) {
|
||||||
|
ccToken.authCode = "831001";
|
||||||
|
ccToken.cardType = "VS";
|
||||||
|
ccToken.lastFour = "1111";
|
||||||
|
ccToken.expMonth = "03";
|
||||||
|
ccToken.expYear = "2030";
|
||||||
|
}
|
||||||
|
|
||||||
|
store.savePaymentMethodChoice(paymentMethod);
|
||||||
|
store.updateCreditCardToken(ccToken);
|
||||||
|
store.updateNextGenSettledAmount(amountDue);
|
||||||
|
store.clearSaveSessionPromise();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.saveAndSubmitWorkOrder();
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
this.$router.navigate(
|
||||||
|
navigationScenarios.PAY_IN_ADVANCE_ERROR,
|
||||||
|
this.$route,
|
||||||
|
{
|
||||||
|
[queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT]: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$router.navigate(
|
||||||
|
navigationScenarios.PAY_IN_ADVANCE_ERROR,
|
||||||
|
this.$route,
|
||||||
|
{
|
||||||
|
[queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT]: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
finalizeAdyenPayment(sessionId, redirectResult) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
createAdyenCheckout({
|
||||||
|
session: {
|
||||||
|
id: sessionId,
|
||||||
|
},
|
||||||
|
handlers: {
|
||||||
|
onPaymentCompleted: (result, component) => {
|
||||||
|
resolve(result);
|
||||||
|
},
|
||||||
|
onPaymentFailed: (result, component) => {
|
||||||
|
if (result?.resultCode !== "Cancelled") {
|
||||||
|
const stringToLog = `ADYEN PAYMENT NOT AUTHORIZED. Code = ${result?.resultCode}. Id = ${sessionId}`;
|
||||||
|
|
||||||
|
global.$logger.logError(stringToLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
reject({
|
||||||
|
code: result?.resultCode,
|
||||||
|
message: "Payment failed from Adyen",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error, component) => {
|
||||||
|
if (error?.name !== "CANCEL") {
|
||||||
|
const stringToLog = `ADYEN ERROR. Name = ${error?.name}. Details = ${error?.message}.`;
|
||||||
|
|
||||||
|
global.$logger.logError(stringToLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
reject({
|
||||||
|
code: error?.name,
|
||||||
|
message: "Error from Adyen",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: {},
|
||||||
|
}).then(
|
||||||
|
(checkout) => {
|
||||||
|
checkout.submitDetails({
|
||||||
|
details: {
|
||||||
|
redirectResult: redirectResult,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async saveAndSubmitWorkOrder() {
|
||||||
|
// Final work order submit after returning from pay in advance.
|
||||||
|
await submitWorkOrder({ submitType: submitType.SAFELITE });
|
||||||
|
|
||||||
|
this.$router.navigate(
|
||||||
|
this.navigationScenarios.PAY_IN_ADVANCE_SUCCESS,
|
||||||
|
this.$route
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
@ -19,6 +19,8 @@ const issPageValues = Object.freeze({
|
||||||
PAYMENT_METHOD: 'payment-method',
|
PAYMENT_METHOD: 'payment-method',
|
||||||
PAYMENT_PAGE: 'payment-page',
|
PAYMENT_PAGE: 'payment-page',
|
||||||
PAYMENT_RETURN: 'payment-return',
|
PAYMENT_RETURN: 'payment-return',
|
||||||
|
PAYMENT_PAGE_ADYEN: 'payment-page-adyen',
|
||||||
|
PAYMENT_RETURN_ADYEN: 'payment-return-adyen',
|
||||||
PART_QUESTIONS: 'part-questions',
|
PART_QUESTIONS: 'part-questions',
|
||||||
POLICY_HOLDER_DETAILS: 'policy-holder-details',
|
POLICY_HOLDER_DETAILS: 'policy-holder-details',
|
||||||
PROVIDER_PREFERENCE: 'provider-preference',
|
PROVIDER_PREFERENCE: 'provider-preference',
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,8 @@ const navigationScenarios = Object.freeze({
|
||||||
CLICKED_BACK_INSHOP: 'CLICKED_BACK_INSHOP',
|
CLICKED_BACK_INSHOP: 'CLICKED_BACK_INSHOP',
|
||||||
CLICKED_BACK_MOBILE: 'CLICKED_BACK_MOBILE',
|
CLICKED_BACK_MOBILE: 'CLICKED_BACK_MOBILE',
|
||||||
CLICKED_PAY_NOW: 'CLICKED_PAY_NOW',
|
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_ERROR: 'PAY_IN_ADVANCE_ERROR',
|
||||||
PAY_IN_ADVANCE_CREDIT_CARD_ERROR: 'PAY_IN_ADVANCE_CREDIT_CARD_ERROR',
|
PAY_IN_ADVANCE_CREDIT_CARD_ERROR: 'PAY_IN_ADVANCE_CREDIT_CARD_ERROR',
|
||||||
PAY_IN_ADVANCE_SUCCESS: 'PAY_IN_ADVANCE_SUCCESS',
|
PAY_IN_ADVANCE_SUCCESS: 'PAY_IN_ADVANCE_SUCCESS',
|
||||||
|
|
|
||||||
|
|
@ -641,6 +641,10 @@ const routingTable = () => [
|
||||||
scenario: navigationScenarios.CLICKED_PAY_NOW,
|
scenario: navigationScenarios.CLICKED_PAY_NOW,
|
||||||
destinationIssPageValue: issPageValues.PAYMENT_PAGE
|
destinationIssPageValue: issPageValues.PAYMENT_PAGE
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: navigationScenarios.CLICKED_PAY_NOW_ADYEN,
|
||||||
|
destinationIssPageValue: issPageValues.PAYMENT_PAGE_ADYEN
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: navigationScenarios.EDIT_SERVICE_LOCATION_INSHOP,
|
scenario: navigationScenarios.EDIT_SERVICE_LOCATION_INSHOP,
|
||||||
destinationIssPageValue: issPageValues.SCHEDULE_PAGE
|
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,
|
issPageValue: issPageValues.PAYMENT_RETURN,
|
||||||
maps: [
|
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,
|
issPageValue: issPageValues.TPA_CONFIRMATION,
|
||||||
maps: [
|
maps: [
|
||||||
|
|
|
||||||
|
|
@ -1780,6 +1780,9 @@ export const useMainStore = defineStore({
|
||||||
updatePaypalToken(token) {
|
updatePaypalToken(token) {
|
||||||
this.order.payment.paypalToken = token;
|
this.order.payment.paypalToken = token;
|
||||||
},
|
},
|
||||||
|
updateNextGenSettledAmount(amount) {
|
||||||
|
this.order.payment.nextGenSettledAmount = amount;
|
||||||
|
},
|
||||||
setSaveSessionPromise(promise) {
|
setSaveSessionPromise(promise) {
|
||||||
this.applicationUser.saveSessionPromise = promise;
|
this.applicationUser.saveSessionPromise = promise;
|
||||||
},
|
},
|
||||||
|
|
@ -3058,6 +3061,24 @@ export const useMainStore = defineStore({
|
||||||
endpoint: endpoints.GetPaymentSignature.url
|
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;
|
||||||
|
},
|
||||||
|
|
||||||
hasSubmittedOrder() {
|
hasSubmittedOrder() {
|
||||||
return window.sessionStorage.getItem(webStorageConstants.SUBMITTED_ORDER) !== null;
|
return window.sessionStorage.getItem(webStorageConstants.SUBMITTED_ORDER) !== null;
|
||||||
|
|
|
||||||
|
|
@ -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_CUSTOMER_PORTAL_URL = 'https://myaccountdev.safelite.com/';
|
||||||
process.env.VUE_APP_GOOGLE_PLACES_API_KEY = 'AIzaSyCuLhQcDdZTTb4JzpUFms1OCch2dk5lHF0';
|
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_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
|
// GA & GTM
|
||||||
// NOTE: Using the old ISS site GTM Cotnainer ID for now, will create a new one soon.
|
// NOTE: Using the old ISS site GTM Cotnainer ID for now, will create a new one soon.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue