diff --git a/src/assets/img/icons/alert-circle-yellow.svg b/src/assets/img/icons/alert-circle-yellow.svg deleted file mode 100644 index 904aefb98..000000000 --- a/src/assets/img/icons/alert-circle-yellow.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js index 3afd4df24..26bcc25e1 100644 --- a/src/constants/store-actions.js +++ b/src/constants/store-actions.js @@ -123,6 +123,8 @@ const storeActions = { SAVE_LOGGING_OPTION: "saveLoggingOption", UPDATE_HAS_TRIGGERED_ERROR: "updateHasTriggeredError", + GET_VALID_IDEMPOTENCY_KEY: "getValidIdempotencyKey", + CORRECT_IDEMPOTENCY_KEY_EXPIRY: "correctIdempotencyKeyExpiry", }; export { storeActions }; diff --git a/src/constants/store-mutations.js b/src/constants/store-mutations.js index 9b4b3719c..5c6c2e22d 100644 --- a/src/constants/store-mutations.js +++ b/src/constants/store-mutations.js @@ -98,6 +98,7 @@ const storeMutations = { UPDATE_LAST_PAGE_VISITED: "updateLastPageVisited", UPDATE_LOGGING_OPTION: "updateLoggingOption", UPDATE_HAS_TRIGGERED_ERROR: "updateHasTriggeredError", + UPDATE_IDEMPOTENCY_KEY: "updateIdempotencyKey", // EXPERIMENT MUTATIONS UPDATE_EXPERIMENTS: "updateExperiments", diff --git a/src/helpers/cms-content-helper.js b/src/helpers/cms-content-helper.js index 450875d0f..727add296 100644 --- a/src/helpers/cms-content-helper.js +++ b/src/helpers/cms-content-helper.js @@ -343,14 +343,3 @@ export function splitCMSCopyOnParagraphTag(copy) { export function splitCMSCopyOnBR(copy) { return copy.split("
"); } - -// This function takes in a number and returns it formatted as USD currency, with or without cents depending on if the number is an integer or not. -export function formatToUSDollar(amount) { - const isInteger = amount % 1 === 0; - return new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD", - minimumFractionDigits: isInteger ? 0 : 2, - maximumFractionDigits: 2, - }).format(amount); -} diff --git a/src/layouts/payment-adyen/payment-adyen.vue b/src/layouts/payment-adyen/payment-adyen.vue index adb0a1210..4a4936a7f 100644 --- a/src/layouts/payment-adyen/payment-adyen.vue +++ b/src/layouts/payment-adyen/payment-adyen.vue @@ -163,7 +163,7 @@ export default { console.log(`Price = ${this.amountDue}`); console.log(`Adyen Price = ${this.adyenPriceTotal}`); - const requestBody = this.getAdyenInitRequestInfo(); + const requestBody = await this.getAdyenInitRequestInfo(); console.log(`Calling with:`); console.log(requestBody); @@ -257,6 +257,7 @@ export default { this.dropinComponent = dropin; dropin.mount("#adyen-container"); + await this.dispatchStoreAction(storeActions.CORRECT_IDEMPOTENCY_KEY_EXPIRY, expiryTime); }, async handleCompletedPayment(result) { @@ -438,19 +439,13 @@ export default { }, // Now a Method so it is always freshly called and not cached. - getIdempotencyKey() { - const currentDateTime = new Date(); - const currentHour = currentDateTime.getUTCHours(); - const currentDate = currentDateTime.getUTCDate(); - const id = this?.$store?.getters?.order?.referralCorrelationId; - const system = this.sourceSystem; - const total = this.adyenPriceTotal; - - return `${id}-${system}-${currentDate}-${currentHour}-${total}`; + async getIdempotencyKey() { + return await this.dispatchStoreAction(storeActions.GET_VALID_IDEMPOTENCY_KEY); }, // Now a Method so it is always freshly called and not cached. - getAdyenInitRequestInfo() { + async getAdyenInitRequestInfo() { + const key = await this.getIdempotencyKey(); return { sourceSystem: this.sourceSystem, referralSequenceNumber: this.$store.getters.order.referralSequenceNumber, @@ -464,10 +459,9 @@ export default { stateOrProvince: this.locationInfo.state, returnUrl: applicationConfig.PIA_ADYEN_RETURN_URL, email: this.$store.getters.order.customer.emailAddress, - IP: "127.0.0.1", // TODO firstName: this.$store.getters.order.customer.firstName, lastName: this.$store.getters.order.customer.lastName, - idempotencyKey: this.getIdempotencyKey(), + idempotencyKey: key, }; }, }, diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index c13026d36..d33695833 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -81,16 +81,6 @@ cmsWidgetName="AlertNoShopsWidget" v-if="displayNoShopsAlert" alertClass="alert-warning" /> -
{ @@ -207,6 +207,12 @@ const getDefaultState = () => { loggingOption: false, hasAlreadyTriggeredError: false, }, + idempotencyKeyFields: { + referralCorrelationId: null, + totalInCents: 0, + expiryTime: null, + idempotencyKey: null, + }, }; }; @@ -862,6 +868,15 @@ export const mutations = { updateHasTriggeredError(state, hasTriggeredError) { state.applicationUser.hasAlreadyTriggeredError = hasTriggeredError; }, + updateIdempotencyKey( + state, + { idempotencyKey, referralCorrelationId, totalInCents, expiryTime } + ) { + state.idempotencyKeyFields.idempotencyKey = idempotencyKey; + state.idempotencyKeyFields.referralCorrelationId = referralCorrelationId; + state.idempotencyKeyFields.totalInCents = totalInCents; + state.idempotencyKeyFields.expiryTime = expiryTime; + }, }; // Export Getters @@ -1105,6 +1120,35 @@ export const getters = { state.order.customer.emailAddress ); }, + + isIdempotencyKeyValid: (state) => { + if (!state.idempotencyKeyFields?.idempotencyKey) { + return false; + } + + const referralCorrelationId = state.order?.referralCorrelationId; + if ( + !referralCorrelationId || + referralCorrelationId !== state.idempotencyKeyFields?.referralCorrelationId + ) { + return false; + } + + const total = getAmountDue(state.order?.lineItems); + const totalInCents = Math.round(total * 100); + if (totalInCents !== state.idempotencyKeyFields?.totalInCents) { + return false; + } + + const currentTime = new Date(); + const expiryTime = new Date(state.idempotencyKeyFields.expiryTime); + if (!expiryTime || expiryTime.getTime() < currentTime.getTime()) { + return false; + } + + // If all checks pass, then the key is valid. + return true; + }, }; // Export Actions @@ -3665,6 +3709,63 @@ export const actions = { updateHasTriggeredError(context, hasAlreadyTriggeredError) { context.commit(storeMutations.UPDATE_HAS_TRIGGERED_ERROR, hasAlreadyTriggeredError); }, + + getValidIdempotencyKey(context) { + if (context.getters.isIdempotencyKeyValid) { + return context.state.idempotencyKeyFields.idempotencyKey; + } + + // If invalid, need to regenerate. + const referralCorrelationId = context.state.order?.referralCorrelationId; + if (!referralCorrelationId) { + // Can't generate! + return null; + } + + const total = getAmountDue(context.state.order?.lineItems); + const totalInCents = Math.round(total * 100); + if (Number.isNaN(totalInCents)) { + // Can't generate! + return null; + } + + const currentTime = new Date(); + const nextHour = currentTime.getUTCHours() + 1; + currentTime.setUTCHours(nextHour); + + const newId = crypto.randomUUID(); + + const newKeyInfo = { + idempotencyKey: newId, + referralCorrelationId: referralCorrelationId, + totalInCents: totalInCents, + expiryTime: currentTime, + }; + + context.commit(storeMutations.UPDATE_IDEMPOTENCY_KEY, newKeyInfo); + + return newId; + }, + + correctIdempotencyKeyExpiry(context, expiryTime) { + if (!context.getters.isIdempotencyKeyValid) { + return; + } + + const currentExpiryTime = new Date(context.state.idempotencyKeyFields.expiryTime); + const expiryTimeAsDate = new Date(expiryTime); + + if (expiryTimeAsDate.getTime() < currentExpiryTime.getTime()) { + const existingKey = context.state.idempotencyKeyFields; + const newKey = { + referralCorrelationId: existingKey.referralCorrelationId, + totalInCents: existingKey.totalInCents, + idempotencyKey: existingKey.idempotencyKey, + expiryTime: expiryTimeAsDate, + }; + context.commit(storeMutations.UPDATE_IDEMPOTENCY_KEY, newKey); + } + }, }; export default createStore({ diff --git a/src/ux-components/alert/alert.vue b/src/ux-components/alert/alert.vue index deb7bca8b..5c78853d4 100644 --- a/src/ux-components/alert/alert.vue +++ b/src/ux-components/alert/alert.vue @@ -7,15 +7,9 @@ isDismissible ? 'alert-dismissible' : '', this.alertClass, this.cssClassNameForCmsWidget, - this.hasBorder ? 'bordered' : '', ]">

Info Icon - Warning Icon {{ alertHeadline }}


@@ -69,9 +63,7 @@ export default { name: "alert", props: { showInfoIcon: Boolean, - showWarningIcon: Boolean, showHorizontalRow: Boolean, - hasBorder: Boolean, isDismissible: Boolean, alertClass: String, /* @@ -104,9 +96,6 @@ export default { infoIcon() { return require(`@/assets/img/icons/info-circle-blue.svg`); }, - warningIcon() { - return require(`@/assets/img/icons/alert-circle-yellow.svg`); - }, pageQueryString() { return applicationConfig.PAGE_QUERYSTRING; }, @@ -222,19 +211,12 @@ export default { background-color: $yellow-100; .alert-heading { color: $yellow-600; - - .warning-icon { - height: 1rem; - } } svg { fill: $yellow-600; width: 1rem; height: 1rem; } - hr { - border-color: $yellow-800; - } } &.alert-success { background-color: $green-100; @@ -247,9 +229,6 @@ export default { height: 1rem; } } - &.bordered { - border: 1px solid; - } & p { font-size: 0.875rem; }