CASH-2961: Pass PIA authorization amount

This commit is contained in:
credelinghuys 2026-07-21 15:04:24 -04:00
parent 4e3a6d3067
commit a6e7019861
10 changed files with 228 additions and 28 deletions

View file

@ -48,6 +48,7 @@ const queryStrings = {
FROM_HERITAGE: "fromheritage",
PHONE_NUMBER: "phonenumber",
OFFER_QUOTE: "offerquote",
PIA_AUTH_AMOUNT_ADJUST: "piaauthamountadjust",
};
export { queryStrings };

View file

@ -118,6 +118,7 @@ const storeActions = {
SAVE_CCTOKEN: "saveCCToken",
SAVE_PAYPAL_TOKEN: "savePaypalToken",
SAVE_NEXTGEN_SETTLED_AMOUNT: "saveNextGenSettledAmount",
SAVE_AUTHORIZATION_AMOUNT: "saveAuthorizationAmount",
SAVE_IS_RECAL_ACK_OPT_IN: "saveIsRecalAckOptIn",
SAVE_IS_RECAL_ACKNOWLEDGED_FOR_SCHEDULING: "saveIsRecalAcknowledgedForScheduling",
SAVE_IS_OEM_GLASS_SELECTED: "saveIsOemGlassSelected",

View file

@ -3,6 +3,7 @@ const storeMutations = {
UPDATE_CCTOKEN: "updateCCToken",
UPDATE_PAYPAL_TOKEN: "updatePaypalToken",
UPDATE_NEXTGEN_SETTLED_AMOUNT: "updateNextGenSettledAmount",
UPDATE_AUTHORIZATION_AMOUNT: "updateAuthorizationAmount",
// VEHICLE MUTATIONS
UPDATE_YEAR: "updateYear",

View file

@ -3,6 +3,10 @@ import { storeActions } from "@/constants/store-actions";
import baseMixin from "@/mixins/base-mixin.js";
import { deepClone } from "@/helpers/object-helper";
import { partNumberStrings } from "@/constants/part-number-strings";
import { applicationConfig } from "@/constants/application-config";
import { queryStrings } from "@/constants/query-strings";
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { peekQueryFromStash } from "@/router/methods/helpers/querystring-stash";
export function getDisplayAmountDue(lineItemsObject, includeTax = true) {
return getAmountDue(lineItemsObject, includeTax).toLocaleString("en-US", {
@ -56,6 +60,35 @@ export function getAmountDue(lineItemsObject, includeTax = true) {
return ((amountDue * 100) / 100).toFixed(2);
}
function getPiaAuthAmountAdjustment() {
if (applicationConfig.CURRENT_ENVIRONMENT === "Prod") {
return 0;
}
const adjustParam =
peekQueryFromStash(queryStrings.PIA_AUTH_AMOUNT_ADJUST) ??
getQuerystringParameter(queryStrings.PIA_AUTH_AMOUNT_ADJUST);
if (adjustParam == null || adjustParam === "") {
return 0;
}
const adjustment = parseFloat(adjustParam);
return Number.isFinite(adjustment) ? adjustment : 0;
}
export function getPiaAuthorizationAmount(lineItemsObject) {
const amountDue = parseFloat(getAmountDue(lineItemsObject));
const adjustment = getPiaAuthAmountAdjustment();
if (adjustment === 0) {
return amountDue.toFixed(2);
}
const adjustedAmount = Math.max(0.01, amountDue + adjustment);
return adjustedAmount.toFixed(2);
}
export function getSubTotal(lineItemsObject) {
// this is amountDue without sales tax
return getAmountDue(lineItemsObject, false);

View file

@ -1,9 +1,22 @@
import {
getDisplayAmountDue,
getAmountDue,
getPiaAuthorizationAmount,
getSubTotal,
getSalesTax,
} from "@/helpers/pricing-helper.js";
import { applicationConfig } from "@/constants/application-config";
jest.mock("@/helpers/querystring-helper", () => ({
getQuerystringParameter: jest.fn(),
}));
jest.mock("@/router/methods/helpers/querystring-stash", () => ({
peekQueryFromStash: jest.fn(),
}));
import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { peekQueryFromStash } from "@/router/methods/helpers/querystring-stash";
const lineItems = {
glassParts: [],
@ -61,4 +74,30 @@ describe("pricing-helper", () => {
expect(result).toBe("3.00");
});
});
describe("getPiaAuthorizationAmount", () => {
beforeEach(() => {
jest.clearAllMocks();
peekQueryFromStash.mockReturnValue(null);
getQuerystringParameter.mockReturnValue(null);
applicationConfig.CURRENT_ENVIRONMENT = "QA";
});
it("returns amount due when no adjustment is configured", () => {
expect(getPiaAuthorizationAmount(lineItems)).toBe("53.00");
});
it("ignores adjustment in production", () => {
applicationConfig.CURRENT_ENVIRONMENT = "Prod";
peekQueryFromStash.mockReturnValue("-5");
expect(getPiaAuthorizationAmount(lineItems)).toBe("53.00");
});
it("applies non-production query string adjustment", () => {
peekQueryFromStash.mockReturnValue("-5");
expect(getPiaAuthorizationAmount(lineItems)).toBe("48.00");
});
});
});

View file

@ -72,7 +72,7 @@ import { endpoints } from "../../constants/endpoints";
import { AppointmentTypeStrings } from "../../constants/schedule-constants";
import baseMixin from "@/mixins/base-mixin.js";
import { storeActions } from "@/constants/store-actions";
import { getAmountDue } from "@/helpers/pricing-helper.js";
import { getAmountDue, getPiaAuthorizationAmount } from "@/helpers/pricing-helper.js";
import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js";
import { createAdyenCheckout } from "@/helpers/adyen-helper";
import { Dropin } from "@adyen/adyen-web/auto";
@ -364,14 +364,22 @@ export default {
);
}
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_NEXTGEN_SETTLED_AMOUNT,
this.amountDue,
false
);
await this.savePiaPaymentAmounts();
await this.saveAndSubmitWorkOrder();
},
async savePiaPaymentAmounts() {
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_NEXTGEN_SETTLED_AMOUNT,
this.piaAuthorizationAmount,
false
);
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_AUTHORIZATION_AMOUNT,
this.piaAuthorizationAmount,
false
);
},
async handleFailedPayment(result) {
console.log(`Result =`);
console.log(result);
@ -555,8 +563,12 @@ export default {
return getAmountDue(this.$store.getters.order.lineItems);
},
piaAuthorizationAmount() {
return getPiaAuthorizationAmount(this.$store.getters.order.lineItems);
},
adyenPriceTotal() {
return Math.round(this.amountDue * 100);
return Math.round(parseFloat(this.piaAuthorizationAmount) * 100);
},
// Cart info

View file

@ -14,12 +14,7 @@ import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
import { Form } from "vee-validate";
import { paymentMethods } from "@/constants/payment-method-constants";
import baseMixin from "@/mixins/base-mixin.js";
import {
getDisplayAmountDue,
getAmountDue,
getSubTotal,
getSalesTax,
} from "@/helpers/pricing-helper.js";
import { getPiaAuthorizationAmount } from "@/helpers/pricing-helper.js";
// iframeResizer IS loaded into the page and necessary for the package to
// to auto scale the iFrame this page is loaded in
// Do not remove despite showing as "unused" CASH-309
@ -103,11 +98,7 @@ export default {
const payerId = getQuerystringParameter(queryStrings.PAYERID);
baseMixin.methods.dispatchStoreAction(storeActions.SAVE_PAYPAL_TOKEN, token, false);
baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_NEXTGEN_SETTLED_AMOUNT,
this.getAmountDue(),
false
);
this.savePiaPaymentAmounts();
await this.saveAndSubmitWorkOrder();
},
@ -160,16 +151,22 @@ export default {
console.log(ccToken);
baseMixin.methods.dispatchStoreAction(storeActions.SAVE_CCTOKEN, ccToken, false);
baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_NEXTGEN_SETTLED_AMOUNT,
this.getAmountDue(),
false
);
this.savePiaPaymentAmounts();
await this.saveAndSubmitWorkOrder();
}
},
getAmountDue() {
return getAmountDue(store.getters.order.lineItems);
savePiaPaymentAmounts() {
const authorizationAmount = getPiaAuthorizationAmount(store.getters.order.lineItems);
baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_NEXTGEN_SETTLED_AMOUNT,
authorizationAmount,
false
);
baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_AUTHORIZATION_AMOUNT,
authorizationAmount,
false
);
},
async saveAndSubmitWorkOrder() {
// Final work order submit after returning from PIA.

View file

@ -2,7 +2,7 @@ import { routeData } from "@/router/constants/routes";
import { createAdyenCheckout, getSessionInfo } from "@/helpers/adyen-helper";
import { mapAdyenToFmgPaymentMethod, generateCcToken } from "@/helpers/adyen-helper";
import baseMixin from "@/mixins/base-mixin.js";
import { getAmountDue } from "@/helpers/pricing-helper";
import { getPiaAuthorizationAmount } from "@/helpers/pricing-helper";
import store from "@/store";
import { storeActions } from "@/constants/store-actions";
import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js";
@ -49,7 +49,7 @@ export async function adyenReturnBeforeEnter(to, from) {
// TODO once afterpay info is returned, create cc token and submit order.
const paymentMethod = mapAdyenToFmgPaymentMethod(sessionInfo?.paymentMethod);
const amountDue = getAmountDue(store.getters.order.lineItems);
const authorizationAmount = getPiaAuthorizationAmount(store.getters.order.lineItems);
const ccToken = generateCcToken(sessionInfo);
ccToken.authCode = "831001";
@ -68,7 +68,13 @@ export async function adyenReturnBeforeEnter(to, from) {
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_NEXTGEN_SETTLED_AMOUNT,
amountDue,
authorizationAmount,
false
);
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_AUTHORIZATION_AMOUNT,
authorizationAmount,
false
);

View file

@ -162,6 +162,7 @@ const getDefaultState = () => {
inactivePromos: null,
paypalToken: null,
nextGenSettledAmount: 0,
authorizationAmount: null,
ccToken: {
subscriptionId: null,
expMonth: null,
@ -488,6 +489,9 @@ export const mutations = {
updateNextGenSettledAmount(state, nextGenSettledAmount) {
state.order.payment.nextGenSettledAmount = nextGenSettledAmount;
},
updateAuthorizationAmount(state, authorizationAmount) {
state.order.payment.authorizationAmount = authorizationAmount;
},
updateInsuranceVerifiedStatus(state, isVerified) {
state.order.payment.insuranceCoverage.isVerified = isVerified;
},
@ -2893,6 +2897,9 @@ export const actions = {
order.payment.piaType == paymentMethods.APPLE_PAY ? true : false,
paypalToken: order.payment.paypalToken,
nextGenSettledAmount: order.payment.nextGenSettledAmount,
...(order.payment.authorizationAmount > 0
? { authorizationAmount: order.payment.authorizationAmount }
: {}),
ccToken: {
subscriptionId: order.payment.ccToken.subscriptionId,
expMonth: order.payment.ccToken.expMonth,
@ -3047,6 +3054,10 @@ export const actions = {
context.commit(storeMutations.UPDATE_NEXTGEN_SETTLED_AMOUNT, nextGenSettledAmount);
},
saveAuthorizationAmount(context, authorizationAmount) {
context.commit(storeMutations.UPDATE_AUTHORIZATION_AMOUNT, authorizationAmount);
},
savePaypalToken(context, ppToken) {
context.commit(storeMutations.UPDATE_PAYPAL_TOKEN, ppToken);
},

View file

@ -920,6 +920,105 @@ describe("Actions", () => {
);
});
it("saveSession action, includes authorizationAmount when PIA was authorized", async () => {
const context = createAuthorizationAmountSaveSessionContext(312.45);
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: { referralNumber: 123 } });
});
await actions.saveSession(context, { pageNameToLog: "test" });
expect(globalMethods.callHttpClient).toHaveBeenCalledWith(
expect.objectContaining({
payload: expect.objectContaining({
order: expect.objectContaining({
payment: expect.objectContaining({
authorizationAmount: 312.45,
}),
}),
}),
})
);
});
it("saveSession action, omits authorizationAmount when PIA was not authorized", async () => {
const context = createAuthorizationAmountSaveSessionContext(null);
globalMethods.callHttpClient.mockImplementation(() => {
return Promise.resolve({ data: { referralNumber: 123 } });
});
globalMethods.callHttpClient.mockClear();
await actions.saveSession(context, { pageNameToLog: "test" });
const callPayload = globalMethods.callHttpClient.mock.calls[0][0].payload;
expect(callPayload.order.payment.authorizationAmount).toBeUndefined();
});
function createAuthorizationAmountSaveSessionContext(authorizationAmount) {
const context = state;
const damage = {
numberOfChips: "2",
partQuestionAnswers: {},
moldingQuestionAnswers: {},
capabilityQuestionAnswers: {},
};
context.getters = {
vehicle: { registration: {} },
order: { damage },
damage: {},
applicationUser: {
lastPageVisited: "test-page",
crmCustomerId: "xxx-xxx-xxx",
savedSessionId: "xxx-xxx-xxx",
},
};
context.state = {
order: {
damage,
payment: {
insuranceCoverage: { isVerified: false },
isInsurance: false,
authorizationAmount,
ccToken: {
subscriptionId: authorizationAmount ? "sub-123" : null,
expMonth: null,
expYear: null,
cardType: null,
billToPostalCode: null,
billToFirstName: null,
billToLastName: null,
referenceNumber: null,
authCode: authorizationAmount ? "831001" : null,
transactionId: null,
transReferenceNumber: null,
lastFour: null,
},
},
serviceLocation: {},
customer: {
emailAddress: "test@safelite.com",
firstName: "John",
lastName: "Doe",
isSmsOptIn: true,
phoneNumber: "1234567890",
address: {
streetAddress: "123 Main St",
streetAddress2: "Apt 1",
city: "Anytown",
state: "OH",
zipCode: "12345",
},
},
lineItems: {},
},
};
return context;
}
it("loadSession action, returns order information, calls mutation", async () => {
// Arrange
const context = state;