Merge pull request #575 from Safelite/feature/digital/SSR-1081

Payment-return port
This commit is contained in:
brich1212safe 2024-03-05 13:44:35 -05:00 committed by GitHub
commit 970df61c05
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 297 additions and 33 deletions

View file

@ -1,5 +1,21 @@
const queryStrings = Object.freeze({ const queryStrings = Object.freeze({
ISS_PAGE: 'issPage' ISS_PAGE: 'issPage',
AUTH_CODE: 'auth_code',
BILL_TO_FIRST_NAME: 'billto_firstname',
BILL_TO_LAST_NAME: 'billto_lastname',
BILL_TO_POSTAL_CODE: 'billto_postalcode',
CARD_EXPIRATION_MONTH: 'card_expirationmonth',
CARD_EXPIRATION_YEAR: 'card_expirationyear',
CARD_TYPE: 'sgcardtype',
DISPLAY_PAY_IN_ADVANCE_ALERT: 'displayPayInAdvanceAlert',
ERROR: 'error',
LAST_FOUR: 'last_four',
REFERENCE_NUMBER: 'req_reference_number',
REFERRAL_SEQ_NUM: 'referralseqnum',
SUBSCRIPTIONID: 'subscriptionid',
TRANS_REFERENCE_NUMBER: 'auth_trans_ref_no',
TRANSACTION_ID: 'transaction_id'
}); });
export default queryStrings; export default queryStrings;

View file

@ -0,0 +1,5 @@
const webStorageConstants = Object.freeze({
SUBMITTED_ORDER: 'submittedOrder'
});
export default webStorageConstants;

View file

@ -1,6 +1,17 @@
import { useMainStore } from '@/store'; import { useMainStore } from '@/store';
import { updateOrCreateISSCookie } from '@/helpers/cookie-helper'; import { updateOrCreateISSCookie } from '@/helpers/cookie-helper';
/*
Encapsulates asynchronous Save Session logic inside a promise to allow for Save Session queuing
*/
async function saveSessionHelper(store) {
const savedSessionInfo = await store.saveSession();
if (savedSessionInfo) {
store.setSaveSessionInfo(savedSessionInfo.data);
}
updateOrCreateISSCookie();
}
/* /*
Will call API to save existing order, or create new one depending where it's called from. Will call API to save existing order, or create new one depending where it's called from.
This will also set Referral information in the store after saving, and then This will also set Referral information in the store after saving, and then
@ -8,8 +19,8 @@ import { updateOrCreateISSCookie } from '@/helpers/cookie-helper';
*/ */
export async function saveSession({ shouldAwaitSaveSessionQueue = false }) { export async function saveSession({ shouldAwaitSaveSessionQueue = false }) {
const store = useMainStore(); const store = useMainStore();
var saveSessionPromise = store.applicationUser.saveSessionPromise const saveSessionPromise = store.applicationUser.saveSessionPromise
? store.applicationUser.saveSessionPromise.then(() => { return saveSessionHelper(store); }) ? store.applicationUser.saveSessionPromise.then(() => saveSessionHelper(store))
: saveSessionHelper(store); : saveSessionHelper(store);
store.setSaveSessionPromise(saveSessionPromise); store.setSaveSessionPromise(saveSessionPromise);
@ -20,12 +31,18 @@ export async function saveSession({ shouldAwaitSaveSessionQueue = false }) {
} }
/* /*
Encapsulates asynchronous Save Session logic inside a promise to allow for Save Session queuing Will determine if to submitWorkOrder.
TODO: Add more description to this
*/ */
async function saveSessionHelper(store) { export async function submitWorkOrder({
const savedSessionInfo = await store.saveSession(); pageNameToLog,
if (savedSessionInfo) { submitAfterSave = false,
store.setSaveSessionInfo(savedSessionInfo.data); createDeleteStatusWorkOrderForPia = false
} }) {
updateOrCreateISSCookie(); await saveSession({
} pageNameToLog,
shouldAwaitSaveSessionQueue: true,
submitAfterSave,
createDeleteStatusWorkOrderForPia
});
}

View file

@ -0,0 +1,11 @@
export default function getQueryStringParameter(key) {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const lowerCaseParams = new URLSearchParams();
urlParams.forEach((value, name) => {
lowerCaseParams.append(name.toLowerCase(), value);
});
return lowerCaseParams.get(key.toLowerCase());
}

View file

@ -460,15 +460,11 @@ export default {
computed: { computed: {
payInAdvanceResponseUrl() { payInAdvanceResponseUrl() {
const { protocol, host } = window.location; const { protocol, host } = window.location;
return `${protocol}//${host}/?issPage=${ return `${protocol}//${host}/?issPage=${issPageValues.PAYMENT_RETURN}&src=iss-nextgen`;
issPageValues.PAYMENT_RETURN
}&src=iss-nextgen`;
}, },
payInAdvanceCancelUrl() { payInAdvanceCancelUrl() {
const { protocol, host } = window.location; const { protocol, host } = window.location;
return `${protocol}//${host}/?issPage=${ return `${protocol}//${host}/?issPage=${issPageValues.PAYMENT_METHOD}&src=iss-nextgen`;
issPageValues.PAYMENT_METHOD
}&src=iss-nextgen`;
}, },
dynamicCSSUrl() { dynamicCSSUrl() {
const { protocol, hostname, port } = window.location; const { protocol, hostname, port } = window.location;

View file

@ -0,0 +1,143 @@
<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 getQueryStringParameter from '@/helpers/querystring-helper.js';
import { paymentMethods } from '@/constants/payment-method-constants.js';
import { submitWorkOrder } from '@/helpers/order-helper.js';
import showIssLoadingModal from '@/helpers/loading-modal-helper';
export default {
name: 'payment-return',
components: {
// eslint-disable-next-line vue/no-reserved-component-names
Form
},
mixins: [BaseFormMixin],
computed: {
creditCardToken() {
return {
subscriptionId: getQueryStringParameter(queryStrings.SUBSCRIPTIONID),
expMonth: getQueryStringParameter(queryStrings.CARD_EXPIRATION_MONTH),
expYear: getQueryStringParameter(queryStrings.CARD_EXPIRATION_YEAR),
cardType: getQueryStringParameter(queryStrings.CARD_TYPE),
billToPostalCode: getQueryStringParameter(queryStrings.BILL_TO_POSTAL_CODE),
billToFirstName: getQueryStringParameter(queryStrings.BILL_TO_FIRST_NAME),
billToLastName: getQueryStringParameter(queryStrings.BILL_TO_LAST_NAME),
referenceNumber: getQueryStringParameter(queryStrings.REFERENCE_NUMBER),
authCode: getQueryStringParameter(queryStrings.AUTH_CODE),
transactionId: getQueryStringParameter(queryStrings.TRANSACTION_ID),
transReferenceNumber: getQueryStringParameter(queryStrings.TRANS_REFERENCE_NUMBER),
lastFour: getQueryStringParameter(queryStrings.LAST_FOUR)
};
}
},
async mounted() {
showIssLoadingModal(true);
const payInAdvanceError = getQueryStringParameter(queryStrings.ERROR);
const { payInAdvanceType } = useMainStore().order.payment;
if (payInAdvanceError) {
console.error(`Error during payment: ${payInAdvanceError}`);
const paymentPageNavScenario =
payInAdvanceType === paymentMethods.CREDIT_CARD || payInAdvanceType === paymentMethods.AFTERPAY;
if (paymentPageNavScenario) {
this.$router.navigate(
this.navigationScenarios.PAY_IN_ADVANCE_CREDIT_CARD_ERROR,
this.$route,
{
[queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT]: payInAdvanceType
}
);
} else {
this.$router.navigate(
this.navigationScenarios.PAY_IN_ADVANCE_ERROR,
this.$route,
{
[queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT]: true
}
);
}
} else {
switch (payInAdvanceType) {
case paymentMethods.CREDIT_CARD:
case paymentMethods.AFTERPAY:
await this.processCreditCardResponse();
break;
case paymentMethods.PAYPAL:
break;
default:
console.log(`Unknown pay in advance type: ${payInAdvanceType}`);
this.$router.navigate(
this.navigationScenarios.PAY_IN_ADVANCE_ERROR,
this.$route,
{
[queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT]: true
}
);
}
}
},
methods: {
arePagePrerequisitesValid() {
return true;
},
navigateOnPayInAdvanceError() {
this.$router.navigate(
this.navigationScenarios.PAY_IN_ADVANCE_ERROR,
this.$route,
{
[queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT]: true
}
);
},
async processPaypalResponse() {
const token = getQueryStringParameter(queryStrings.TOKEN);
this.mainStore.updatePaypalToken(token);
await this.saveAndSubmitWorkOrder();
},
async processCreditCardResponse() {
const referralSeqNum = getQueryStringParameter(queryStrings.REFERRAL_SEQ_NUM);
if (referralSeqNum !== useMainStore().order.referralSequenceNumber) {
console.error(`error: unknown ref:${referralSeqNum} ${useMainStore().order.referralSequenceNumber}`);
this.navigateOnPayInAdvanceError();
} else {
useMainStore().updateCreditCardToken(this.creditCardToken);
await this.saveAndSubmitWorkOrder();
}
},
async saveAndSubmitWorkOrder() {
// Final work order submit after returning from pay in advance.
useMainStore().resetSubmittedOrder();
try {
await submitWorkOrder({
pageNameToLog: 'payment-return',
submitAfterSave: true
});
} catch (error) {
console.error(`error: response from submit work order:${error.message}`);
this.navigateOnPayInAdvanceError();
showIssLoadingModal(false);
return;
}
showIssLoadingModal(false);
useMainStore().createSubmittedOrder();
this.$router.navigate(
this.navigationScenarios.PAY_IN_ADVANCE_SUCCESS,
this.$route
);
}
}
};
</script>

View file

@ -2,7 +2,7 @@
import { createWebHistory, createRouter } from 'vue-router'; import { createWebHistory, createRouter } from 'vue-router';
import lazyLoadComponent from '@/router/dynamic-routing/component-loader'; import lazyLoadComponent from '@/router/dynamic-routing/component-loader';
import issPageValues from '@/router/router-constants/issPage-values'; import issPageValues from '@/router/router-constants/issPage-values';
import { routingTable } from '@/router/router-constants/routing-table'; import routingTable from '@/router/router-constants/routing-table';
import { useMainStore } from '@/store'; import { useMainStore } from '@/store';
import eventBus from '@/helpers/event-bus/event-bus'; import eventBus from '@/helpers/event-bus/event-bus';
import { globalEvents, globalEventTypes } from '@/constants/events'; import { globalEvents, globalEventTypes } from '@/constants/events';
@ -16,11 +16,9 @@ import showIssLoadingModal from '@/helpers/loading-modal-helper';
import analyticsMixin from '@/mixins/analytics-mixin'; import analyticsMixin from '@/mixins/analytics-mixin';
import { saveSession } from '@/helpers/order-helper.js'; import { saveSession } from '@/helpers/order-helper.js';
import routerParams from '@/router/router-constants/router-params'; import routerParams from '@/router/router-constants/router-params';
import bailoutCode from '@/constants/bailoutCode'; import canBailoutNavigateBack from '@/helpers/bailout-helper';
import IssPageValues from '@/router/router-constants/issPage-values'; import bailoutMessage from '@/constants/bailoutMessage';
import navigationScenarios from './router-constants/navigation-scenarios'; import navigationScenarios from './router-constants/navigation-scenarios';
import canBailoutNavigateBack from "@/helpers/bailout-helper";
import bailoutMessage from "@/constants/bailoutMessage";
const routes = [ const routes = [
{ {
@ -129,7 +127,7 @@ router.beforeEach(async (to, from) => {
showIssLoadingModal(true); showIssLoadingModal(true);
} }
const isInIframe = fromQueryPage === IssPageValues.PAYMENT_PAGE; const isInIframe = fromQueryPage === issPageValues.PAYMENT_PAGE;
if (isInIframe) { if (isInIframe) {
// need to set window.top.location.href directly when navigating out of an iframe // need to set window.top.location.href directly when navigating out of an iframe
// especially when navigating with browser buttons // especially when navigating with browser buttons
@ -139,7 +137,7 @@ router.beforeEach(async (to, from) => {
const store = useMainStore(); const store = useMainStore();
// Prevent navigating backwards if we enter a bailout that we are not allowed to go back on // Prevent navigating backwards if we enter a bailout that we are not allowed to go back on
if (store.isBailout && from.name === IssPageValues.BAILOUT_PAGE && to.name !== 'root' && to.name !== IssPageValues.CONTACT_CONFIRMATION if (store.isBailout && from.name === issPageValues.BAILOUT_PAGE && to.name !== 'root' && to.name !== issPageValues.CONTACT_CONFIRMATION
&& !canBailoutNavigateBack()) { && !canBailoutNavigateBack()) {
return false; return false;
} }

View file

@ -96,6 +96,9 @@ const navigationScenarios = Object.freeze({
// Payment // Payment
CLICKED_PAY_NOW: 'CLICKED_PAY_NOW', CLICKED_PAY_NOW: 'CLICKED_PAY_NOW',
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',
// Bailout // Bailout
CLICKED_FORWARD_WITH_BAILOUT: 'CLICKED_FORWARD_WITH_BAILOUT' CLICKED_FORWARD_WITH_BAILOUT: 'CLICKED_FORWARD_WITH_BAILOUT'

View file

@ -641,6 +641,23 @@ const routingTable = () => [
} }
] ]
}, },
{
issPageValue: issPageValues.PAYMENT_RETURN,
maps: [
{
scenario: navigationScenarios.PAY_IN_ADVANCE_ERROR,
destinationIssPageValue: issPageValues.PAYMENT_METHOD
},
{
scenario: navigationScenarios.PAY_IN_ADVANCE_CREDIT_CARD_ERROR,
destinationIssPageValue: issPageValues.PAYMENT_PAGE
},
{
scenario: navigationScenarios.PAY_IN_ADVANCE_SUCCESS,
destinationIssPageValue: issPageValues.CONFIRMATION
}
]
},
{ {
issPageValue: issPageValues.TPA_CONFIRMATION, issPageValue: issPageValues.TPA_CONFIRMATION,
maps: [ maps: [
@ -746,4 +763,4 @@ const routingTable = () => [
]; ];
export { routingTable }; export default routingTable;

View file

@ -14,6 +14,7 @@ import coverageStatuses from '@/constants/coverage-statuses';
import { AppointmentTypeStrings, PREMIUM_FEE_PART_TYPE } from '@/constants/schedule-constants'; import { AppointmentTypeStrings, PREMIUM_FEE_PART_TYPE } from '@/constants/schedule-constants';
import { convertDateStringToDate, getDateDifferenceInDays, militaryToTwelveHourTime } from '@/helpers/date-helper'; import { convertDateStringToDate, getDateDifferenceInDays, militaryToTwelveHourTime } from '@/helpers/date-helper';
import { paymentMethods } from '@/constants/payment-method-constants'; import { paymentMethods } from '@/constants/payment-method-constants';
import webStorageConstants from '@/constants/web-storage-constants';
import { import {
deductibleForSelectedVehicle, endorsementsForSelectedVehicle, deductibleForSelectedVehicle, endorsementsForSelectedVehicle,
noCoverageForSelectedVehicle, noCoverageForSelectedVehicle,
@ -155,7 +156,22 @@ const getDefaultState = () => ({
}, },
parentAccountNumber: 0, parentAccountNumber: 0,
isPayInAdvance: null, isPayInAdvance: null,
payInAdvanceType: null payInAdvanceType: null,
paypalToken: null,
creditCardToken: {
subscriptionId: null,
expMonth: null,
expYear: null,
cardType: null,
billToPostalCode: null,
billToFirstName: null,
billToLastName: null,
referenceNumber: null,
authCode: null,
transactionId: null,
transReferenceNumber: null,
lastFour: null
}
}, },
contactInfo: { contactInfo: {
firstName: null, firstName: null,
@ -933,7 +949,6 @@ export const useMainStore = defineStore({
}).then((response) => resolve(response), (error) => reject(error)); }).then((response) => resolve(response), (error) => reject(error));
}); });
}, },
getCarrierAccountInfo() { getCarrierAccountInfo() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
globalMethods.callHttpClient({ globalMethods.callHttpClient({
@ -945,7 +960,6 @@ export const useMainStore = defineStore({
}).catch((error) => reject(error)); }).catch((error) => reject(error));
}); });
}, },
async getSupportingItems() { async getSupportingItems() {
const { glassParts } = this.lineItems; const { glassParts } = this.lineItems;
const { carId } = this.vehicle; const { carId } = this.vehicle;
@ -1198,7 +1212,7 @@ export const useMainStore = defineStore({
submitToMainframe: !!this.order.referralNumber, submitToMainframe: !!this.order.referralNumber,
loadedFromDupeCheck loadedFromDupeCheck
}, },
additionalSuccessEventDataHandler: (response) => additionalSuccessEventDataHandler: () =>
`Email provided: ${customer.emailAddress ? 'true' : 'false'}` `Email provided: ${customer.emailAddress ? 'true' : 'false'}`
}).then((response) => { }).then((response) => {
if (loadedFromDupeCheck) { if (loadedFromDupeCheck) {
@ -1300,7 +1314,23 @@ export const useMainStore = defineStore({
throw ex; throw ex;
} }
}, },
updateCreditCardToken(token) {
this.order.payment.creditCardToken.subscriptionId = token.subscriptionId;
this.order.payment.creditCardToken.expMonth = token.expMonth;
this.order.payment.creditCardToken.expYear = token.expYear;
this.order.payment.creditCardToken.cardType = token.cardType;
this.order.payment.creditCardToken.billToPostalCode = token.billToPostalCode;
this.order.payment.creditCardToken.billToFirstName = token.billToFirstName;
this.order.payment.creditCardToken.billToLastName = token.billToLastName;
this.order.payment.creditCardToken.referenceNumber = token.referenceNumber;
this.order.payment.creditCardToken.authCode = token.authCode;
this.order.payment.creditCardToken.transactionId = token.transactionId;
this.order.payment.creditCardToken.transReferenceNumber = token.transReferenceNumber;
this.order.payment.creditCardToken.lastFour = token.lastFour;
},
updatePaypalToken(token) {
this.order.payment.paypalToken = token;
},
setSaveSessionPromise(promise) { setSaveSessionPromise(promise) {
this.applicationUser.saveSessionPromise = promise; this.applicationUser.saveSessionPromise = promise;
}, },
@ -1382,6 +1412,10 @@ export const useMainStore = defineStore({
this.order.serviceLocation.searchFilter = serviceLocationInfo.searchFilter; this.order.serviceLocation.searchFilter = serviceLocationInfo.searchFilter;
}, },
resetState() {
Object.assign(this, getDefaultState());
},
resetRegistrationState() { resetRegistrationState() {
this.order.vehicle.registration.licensePlate = null; this.order.vehicle.registration.licensePlate = null;
this.order.vehicle.registration.address = null; this.order.vehicle.registration.address = null;
@ -1391,7 +1425,7 @@ export const useMainStore = defineStore({
this.order.vehicle.registration.firstName = null; this.order.vehicle.registration.firstName = null;
this.order.vehicle.registration.lastName = null; this.order.vehicle.registration.lastName = null;
}, },
resetServiceLocationAndDependencies(context) { resetServiceLocationAndDependencies() {
this.resetServiceLocationAppointmentType(); this.resetServiceLocationAppointmentType();
this.resetServiceLocationProvider(); this.resetServiceLocationProvider();
this.resetSchedule(); this.resetSchedule();
@ -2122,7 +2156,6 @@ export const useMainStore = defineStore({
this.resetInsurance(); this.resetInsurance();
this.resetBailout(); this.resetBailout();
}, },
savePaymentMethodChoice(paymentMethod) { savePaymentMethodChoice(paymentMethod) {
const isPayInAdvance = paymentMethod !== paymentMethods.PAY_AT_TIME_OF_SERVICE; const isPayInAdvance = paymentMethod !== paymentMethods.PAY_AT_TIME_OF_SERVICE;
this.order.payment.isPayInAdvance = isPayInAdvance; this.order.payment.isPayInAdvance = isPayInAdvance;
@ -2134,8 +2167,33 @@ export const useMainStore = defineStore({
method: endpoints.GetPaymentSignature.method, method: endpoints.GetPaymentSignature.method,
endpoint: endpoints.GetPaymentSignature.url endpoint: endpoints.GetPaymentSignature.url
}); });
} },
hasSubmittedOrder() {
return window.sessionStorage.getItem(webStorageConstants.SUBMITTED_ORDER) !== null;
},
createSubmittedOrder() {
if (this.hasSubmittedOrder()) {
return;
}
const submittedOrder = this.order;
const { experiments } = this.applicationUser;
// set to local storage
window.sessionStorage.setItem(webStorageConstants.SUBMITTED_ORDER, JSON.stringify(submittedOrder));
// clear vuex
this.resetState();
// restore user's experiments
this.applicationUser.experiments = experiments;
},
resetSubmittedOrder() {
// clear from local storage
window.sessionStorage.removeItem(webStorageConstants.SUBMITTED_ORDER);
}
}, },
persist: true persist: true
}); });