Merge pull request #3276 from Safelite/feature/CASH-2961.1

CASH-2961: Pass PIA authorization amount
This commit is contained in:
Chris 2026-07-22 09:32:21 -04:00 committed by GitHub
commit 19e92dc8f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 228 additions and 28 deletions

View file

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

View file

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

View file

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

View file

@ -3,6 +3,10 @@ import { storeActions } from "@/constants/store-actions";
import baseMixin from "@/mixins/base-mixin.js"; import baseMixin from "@/mixins/base-mixin.js";
import { deepClone } from "@/helpers/object-helper"; import { deepClone } from "@/helpers/object-helper";
import { partNumberStrings } from "@/constants/part-number-strings"; 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) { export function getDisplayAmountDue(lineItemsObject, includeTax = true) {
return getAmountDue(lineItemsObject, includeTax).toLocaleString("en-US", { return getAmountDue(lineItemsObject, includeTax).toLocaleString("en-US", {
@ -56,6 +60,35 @@ export function getAmountDue(lineItemsObject, includeTax = true) {
return ((amountDue * 100) / 100).toFixed(2); 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) { export function getSubTotal(lineItemsObject) {
// this is amountDue without sales tax // this is amountDue without sales tax
return getAmountDue(lineItemsObject, false); return getAmountDue(lineItemsObject, false);

View file

@ -1,9 +1,22 @@
import { import {
getDisplayAmountDue, getDisplayAmountDue,
getAmountDue, getAmountDue,
getPiaAuthorizationAmount,
getSubTotal, getSubTotal,
getSalesTax, getSalesTax,
} from "@/helpers/pricing-helper.js"; } 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 = { const lineItems = {
glassParts: [], glassParts: [],
@ -61,4 +74,30 @@ describe("pricing-helper", () => {
expect(result).toBe("3.00"); 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 { AppointmentTypeStrings } from "../../constants/schedule-constants";
import baseMixin from "@/mixins/base-mixin.js"; import baseMixin from "@/mixins/base-mixin.js";
import { storeActions } from "@/constants/store-actions"; 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 { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js";
import { createAdyenCheckout } from "@/helpers/adyen-helper"; import { createAdyenCheckout } from "@/helpers/adyen-helper";
import { Dropin } from "@adyen/adyen-web/auto"; import { Dropin } from "@adyen/adyen-web/auto";
@ -364,14 +364,22 @@ export default {
); );
} }
await baseMixin.methods.dispatchStoreAction( await this.savePiaPaymentAmounts();
storeActions.SAVE_NEXTGEN_SETTLED_AMOUNT,
this.amountDue,
false
);
await this.saveAndSubmitWorkOrder(); 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) { async handleFailedPayment(result) {
console.log(`Result =`); console.log(`Result =`);
console.log(result); console.log(result);
@ -555,8 +563,12 @@ export default {
return getAmountDue(this.$store.getters.order.lineItems); return getAmountDue(this.$store.getters.order.lineItems);
}, },
piaAuthorizationAmount() {
return getPiaAuthorizationAmount(this.$store.getters.order.lineItems);
},
adyenPriceTotal() { adyenPriceTotal() {
return Math.round(this.amountDue * 100); return Math.round(parseFloat(this.piaAuthorizationAmount) * 100);
}, },
// Cart info // Cart info

View file

@ -14,12 +14,7 @@ import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
import { Form } from "vee-validate"; import { Form } from "vee-validate";
import { paymentMethods } from "@/constants/payment-method-constants"; import { paymentMethods } from "@/constants/payment-method-constants";
import baseMixin from "@/mixins/base-mixin.js"; import baseMixin from "@/mixins/base-mixin.js";
import { import { getPiaAuthorizationAmount } from "@/helpers/pricing-helper.js";
getDisplayAmountDue,
getAmountDue,
getSubTotal,
getSalesTax,
} from "@/helpers/pricing-helper.js";
// iframeResizer IS loaded into the page and necessary for the package to // iframeResizer IS loaded into the page and necessary for the package to
// to auto scale the iFrame this page is loaded in // to auto scale the iFrame this page is loaded in
// Do not remove despite showing as "unused" CASH-309 // Do not remove despite showing as "unused" CASH-309
@ -103,11 +98,7 @@ export default {
const payerId = getQuerystringParameter(queryStrings.PAYERID); const payerId = getQuerystringParameter(queryStrings.PAYERID);
baseMixin.methods.dispatchStoreAction(storeActions.SAVE_PAYPAL_TOKEN, token, false); baseMixin.methods.dispatchStoreAction(storeActions.SAVE_PAYPAL_TOKEN, token, false);
baseMixin.methods.dispatchStoreAction( this.savePiaPaymentAmounts();
storeActions.SAVE_NEXTGEN_SETTLED_AMOUNT,
this.getAmountDue(),
false
);
await this.saveAndSubmitWorkOrder(); await this.saveAndSubmitWorkOrder();
}, },
@ -160,16 +151,22 @@ export default {
console.log(ccToken); console.log(ccToken);
baseMixin.methods.dispatchStoreAction(storeActions.SAVE_CCTOKEN, ccToken, false); baseMixin.methods.dispatchStoreAction(storeActions.SAVE_CCTOKEN, ccToken, false);
baseMixin.methods.dispatchStoreAction( this.savePiaPaymentAmounts();
storeActions.SAVE_NEXTGEN_SETTLED_AMOUNT,
this.getAmountDue(),
false
);
await this.saveAndSubmitWorkOrder(); await this.saveAndSubmitWorkOrder();
} }
}, },
getAmountDue() { savePiaPaymentAmounts() {
return getAmountDue(store.getters.order.lineItems); 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() { async saveAndSubmitWorkOrder() {
// Final work order submit after returning from PIA. // 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 { createAdyenCheckout, getSessionInfo } from "@/helpers/adyen-helper";
import { mapAdyenToFmgPaymentMethod, generateCcToken } from "@/helpers/adyen-helper"; import { mapAdyenToFmgPaymentMethod, generateCcToken } from "@/helpers/adyen-helper";
import baseMixin from "@/mixins/base-mixin.js"; import baseMixin from "@/mixins/base-mixin.js";
import { getAmountDue } from "@/helpers/pricing-helper"; import { getPiaAuthorizationAmount } from "@/helpers/pricing-helper";
import store from "@/store"; import store from "@/store";
import { storeActions } from "@/constants/store-actions"; import { storeActions } from "@/constants/store-actions";
import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js"; 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. // TODO once afterpay info is returned, create cc token and submit order.
const paymentMethod = mapAdyenToFmgPaymentMethod(sessionInfo?.paymentMethod); const paymentMethod = mapAdyenToFmgPaymentMethod(sessionInfo?.paymentMethod);
const amountDue = getAmountDue(store.getters.order.lineItems); const authorizationAmount = getPiaAuthorizationAmount(store.getters.order.lineItems);
const ccToken = generateCcToken(sessionInfo); const ccToken = generateCcToken(sessionInfo);
ccToken.authCode = "831001"; ccToken.authCode = "831001";
@ -68,7 +68,13 @@ export async function adyenReturnBeforeEnter(to, from) {
await baseMixin.methods.dispatchStoreAction( await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_NEXTGEN_SETTLED_AMOUNT, storeActions.SAVE_NEXTGEN_SETTLED_AMOUNT,
amountDue, authorizationAmount,
false
);
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_AUTHORIZATION_AMOUNT,
authorizationAmount,
false false
); );

View file

@ -162,6 +162,7 @@ const getDefaultState = () => {
inactivePromos: null, inactivePromos: null,
paypalToken: null, paypalToken: null,
nextGenSettledAmount: 0, nextGenSettledAmount: 0,
authorizationAmount: null,
ccToken: { ccToken: {
subscriptionId: null, subscriptionId: null,
expMonth: null, expMonth: null,
@ -488,6 +489,9 @@ export const mutations = {
updateNextGenSettledAmount(state, nextGenSettledAmount) { updateNextGenSettledAmount(state, nextGenSettledAmount) {
state.order.payment.nextGenSettledAmount = nextGenSettledAmount; state.order.payment.nextGenSettledAmount = nextGenSettledAmount;
}, },
updateAuthorizationAmount(state, authorizationAmount) {
state.order.payment.authorizationAmount = authorizationAmount;
},
updateInsuranceVerifiedStatus(state, isVerified) { updateInsuranceVerifiedStatus(state, isVerified) {
state.order.payment.insuranceCoverage.isVerified = isVerified; state.order.payment.insuranceCoverage.isVerified = isVerified;
}, },
@ -2893,6 +2897,9 @@ export const actions = {
order.payment.piaType == paymentMethods.APPLE_PAY ? true : false, order.payment.piaType == paymentMethods.APPLE_PAY ? true : false,
paypalToken: order.payment.paypalToken, paypalToken: order.payment.paypalToken,
nextGenSettledAmount: order.payment.nextGenSettledAmount, nextGenSettledAmount: order.payment.nextGenSettledAmount,
...(order.payment.authorizationAmount > 0
? { authorizationAmount: order.payment.authorizationAmount }
: {}),
ccToken: { ccToken: {
subscriptionId: order.payment.ccToken.subscriptionId, subscriptionId: order.payment.ccToken.subscriptionId,
expMonth: order.payment.ccToken.expMonth, expMonth: order.payment.ccToken.expMonth,
@ -3047,6 +3054,10 @@ export const actions = {
context.commit(storeMutations.UPDATE_NEXTGEN_SETTLED_AMOUNT, nextGenSettledAmount); context.commit(storeMutations.UPDATE_NEXTGEN_SETTLED_AMOUNT, nextGenSettledAmount);
}, },
saveAuthorizationAmount(context, authorizationAmount) {
context.commit(storeMutations.UPDATE_AUTHORIZATION_AMOUNT, authorizationAmount);
},
savePaypalToken(context, ppToken) { savePaypalToken(context, ppToken) {
context.commit(storeMutations.UPDATE_PAYPAL_TOKEN, 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 () => { it("loadSession action, returns order information, calls mutation", async () => {
// Arrange // Arrange
const context = state; const context = state;