Skip to content

Commit

Permalink
Feat: ORV2-1933 Update PayBC Redirection Flow (#1189)
Browse files Browse the repository at this point in the history
Signed-off-by: erikataot <[email protected]>
Co-authored-by: Krishnan Subramanian <[email protected]>
  • Loading branch information
erikataot and krishnan-aot authored Feb 13, 2024
1 parent e7dc65c commit 9d13da8
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ const PAYMENT_API_BASE = `${VEHICLES_URL}/payment`;
export const PAYMENT_API_ROUTES = {
START: PAYMENT_API_BASE,
COMPLETE: PAYMENT_API_BASE,
GET: PAYMENT_API_BASE,
PAYMENT_GATEWAY: `payment-gateway`,
};
40 changes: 34 additions & 6 deletions frontend/src/features/permits/helpers/payment.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { PayBCPaymentDetails } from "../types/payment";
import { parseRedirectUriPath } from "../pages/Payment/PaymentRedirect";
import { PayBCPaymentDetails, StartTransactionResponseData } from "../types/payment";
import { Nullable } from "../../../common/types/common";
import {
PAYMENT_GATEWAY_METHODS,
Expand All @@ -12,6 +11,9 @@ import {
applyWhenNotNullable,
getDefaultRequiredVal,
} from "../../../common/helpers/util";
import { httpGETRequest } from "../../../common/apiManager/httpRequestHandler";
import { PAYMENT_API_ROUTES } from "../apiManager/endpoints/endpoints";
import { keepPreviousData, useQuery } from "@tanstack/react-query";

/**
* Extracts PayBCPaymentDetails from the query parameters of a URL.
Expand All @@ -23,9 +25,6 @@ export const getPayBCPaymentDetails = (
params: URLSearchParams,
): PayBCPaymentDetails => {
// Extract the query parameters and assign them to the corresponding properties of PayBCPaymentDetails
const path = getDefaultRequiredVal("", params.get("path"));
const { trnApproved } = parseRedirectUriPath(path);

const payBCPaymentDetails: PayBCPaymentDetails = {
authCode: params.get("authCode"),
avsAddrMatch: getDefaultRequiredVal("", params.get("avsAddrMatch")),
Expand All @@ -36,7 +35,10 @@ export const getPayBCPaymentDetails = (
avsResult: getDefaultRequiredVal("", params.get("avsResult")),
cardType: getDefaultRequiredVal("", params.get("cardType")),
cvdId: applyWhenNotNullable((cvdId) => Number(cvdId), params.get("cvdId")),
trnApproved: trnApproved,
trnApproved: applyWhenNotNullable(
(approved) => Number(approved),
params.get("trnApproved"),
0),
messageId: applyWhenNotNullable(
(messageId) => Number(messageId),
params.get("messageId"),
Expand Down Expand Up @@ -85,3 +87,29 @@ export const isValidTransaction = (
(!!transactionApproved && transactionApproved > 0)
);
};

/**
* Fetch payment information by transaction id.
* @param transactionId transaction id of the payment details to fetch
* @returns PaymentTransaction data as response, or null if fetch failed
*/
export const getPaymentByTransactionId = async (
transactionId?: string,
): Promise<Required<StartTransactionResponseData>> => {
const url = `${PAYMENT_API_ROUTES.GET}/${transactionId}`;
return httpGETRequest(url).then((response) => response.data);
};

/**
* Hook to fetch the payment information by the given transactionId
* @returns UseQueryResult containing the query results.
*/
export const usePaymentByTransactionIdQuery = (transactionId: string) => {
return useQuery({
queryKey: ["paymentByTransactionId"],
queryFn: () => getPaymentByTransactionId(transactionId),
placeholderData: keepPreviousData,
refetchOnWindowFocus: false,
enabled: true,
});
};
73 changes: 20 additions & 53 deletions frontend/src/features/permits/pages/Payment/PaymentRedirect.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { useEffect, useRef } from "react";
import { Navigate, useNavigate, useSearchParams } from "react-router-dom";

import { getPayBCPaymentDetails } from "../../helpers/payment";
import { getPayBCPaymentDetails, usePaymentByTransactionIdQuery } from "../../helpers/payment";
import { Loading } from "../../../../common/pages/Loading";
import { useCompleteTransaction, useIssuePermits } from "../../hooks/hooks";
import { getDefaultRequiredVal } from "../../../../common/helpers/util";
import { DATE_FORMATS, toUtc } from "../../../../common/helpers/formatDate";
import { hasPermitsActionFailed } from "../../helpers/permitState";
import { PaymentCardTypeCode } from "../../../../common/types/paymentMethods";
import { Nullable } from "../../../../common/types/common";
import {
APPLICATIONS_ROUTES,
ERROR_ROUTES,
Expand All @@ -20,40 +19,6 @@ import {
PayBCPaymentDetails,
} from "../../types/payment";

const PERMIT_ID_DELIM = ",";
const PATH_DELIM = "?";

const getPermitIdsArray = (permitIds?: Nullable<string>) => {
return getDefaultRequiredVal("", permitIds)
.split(PERMIT_ID_DELIM)
.filter((id) => id !== "");
};

export const parseRedirectUriPath = (path?: Nullable<string>) => {
const splitPath = path?.split(PATH_DELIM);
let permitIds = "";
let trnApproved = 0;
if (splitPath?.[0]) {
permitIds = splitPath[0];
}

if (splitPath?.[1]) {
trnApproved = parseInt(splitPath[1]?.split("=")?.[1]);
}

return { permitIds, trnApproved };
};

const exportPathFromSearchParams = (
params: URLSearchParams,
trnApproved: number,
) => {
const localParams = new URLSearchParams(params);
localParams.delete("path");
const updatedPath = localParams.toString();
return encodeURIComponent(`trnApproved=${trnApproved}&` + updatedPath);
};

/**
* React component that handles the payment redirect and displays the payment status.
* If the payment status is "Approved", it renders the SuccessPage component.
Expand All @@ -66,14 +31,9 @@ export const PaymentRedirect = () => {
const [searchParams] = useSearchParams();
const paymentDetails = getPayBCPaymentDetails(searchParams);
const transaction = mapTransactionDetails(paymentDetails);

const path = getDefaultRequiredVal("", searchParams.get("path"));
const { permitIds, trnApproved } = parseRedirectUriPath(path);
const transactionQueryString = exportPathFromSearchParams(
searchParams,
trnApproved,
);
const transactionId = getDefaultRequiredVal("", searchParams.get("ref2"));
const transactionQueryString = encodeURIComponent(searchParams.toString());
const transactionIdQuery = usePaymentByTransactionIdQuery(transactionId);

const { mutation: completeTransactionMutation, paymentApproved } =
useCompleteTransaction(
Expand All @@ -82,7 +42,6 @@ export const PaymentRedirect = () => {
);

const { mutation: issuePermitsMutation, issueResults } = useIssuePermits();

const issueFailed = hasPermitsActionFailed(issueResults);

useEffect(() => {
Expand All @@ -97,24 +56,32 @@ export const PaymentRedirect = () => {
}, [paymentDetails.trnApproved]);

useEffect(() => {
if (issuedPermit.current === false) {
const permitIdsArray = getPermitIdsArray(permitIds);

if (permitIdsArray.length === 0) {
// permit ids should not be empty, if so then something went wrong
navigate(ERROR_ROUTES.UNEXPECTED, { replace: true });
} else if (paymentApproved === true) {
const applicationIds:string[] = [];
if (transactionIdQuery?.isSuccess && issuedPermit?.current === false) {

transactionIdQuery?.data?.applicationDetails?.forEach((application) => {
if (application?.applicationId) {
applicationIds.push(application.applicationId);
}
})

if (paymentApproved === true) {
// Payment successful, proceed to issue permit
issuePermitsMutation.mutate(permitIdsArray);
issuePermitsMutation.mutate(applicationIds);
issuedPermit.current = true;
} else if (paymentApproved === false) {
// Payment failed, redirect back to pay now page
navigate(APPLICATIONS_ROUTES.PAY(permitIdsArray[0], true), {
navigate(APPLICATIONS_ROUTES.PAY(applicationIds[0], true), {
replace: true,
});
}
}
}, [paymentApproved, permitIds]);

if (transactionIdQuery?.isError)
navigate(ERROR_ROUTES.UNEXPECTED, { replace: true });

}, [paymentApproved, transactionIdQuery]);

if (issueFailed) {
return <Navigate to={`${ERROR_ROUTES.UNEXPECTED}`} replace={true} />;
Expand Down
11 changes: 1 addition & 10 deletions vehicles/src/modules/payment/payment.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,7 @@ export class PaymentService {
};

private queryHash = (transaction: Transaction) => {
const permitIds = transaction.permitTransactions.map(
(permitTransaction) => {
return permitTransaction.permit.permitId;
},
);
// Construct the URL with the transaction details for the payment gateway
const redirectUrl = permitIds
? `${process.env.PAYBC_REDIRECT}` + `?path=${permitIds.join(',')}`
: `${process.env.PAYBC_REDIRECT}`;

const redirectUrl = process.env.PAYBC_REDIRECT;
const date = new Date().toISOString().split('T')[0];

// There should be a better way of doing this which is not as rigid - something like
Expand Down

0 comments on commit 9d13da8

Please sign in to comment.