fix(payment): route Philippine GCash separately from QRIS
This commit is contained in:
@@ -13,15 +13,15 @@ type PaymentLaunchDialogFlow = Pick<
|
||||
PaymentLaunchFlow,
|
||||
| "handleEzpayCancel"
|
||||
| "handleEzpayConfirm"
|
||||
| "handleQrisClose"
|
||||
| "handleRegionalQrClose"
|
||||
| "handleStripeClose"
|
||||
| "handleStripeConfirmed"
|
||||
| "isConfirmingEzpay"
|
||||
| "qrisErrorMessage"
|
||||
| "qrisPayment"
|
||||
| "qrisStatus"
|
||||
| "regionalQrErrorMessage"
|
||||
| "regionalQrPayment"
|
||||
| "regionalQrStatus"
|
||||
| "shouldShowEzpayConfirmDialog"
|
||||
| "shouldShowQrisDialog"
|
||||
| "shouldShowRegionalQrDialog"
|
||||
| "shouldShowStripeDialog"
|
||||
| "stripeClientSecret"
|
||||
>;
|
||||
@@ -53,12 +53,12 @@ export function PaymentLaunchDialogs({
|
||||
onConfirm={launch.handleEzpayConfirm}
|
||||
/>
|
||||
) : null}
|
||||
{launch.shouldShowQrisDialog && launch.qrisPayment ? (
|
||||
<QrisPaymentDialog
|
||||
payment={launch.qrisPayment}
|
||||
status={launch.qrisStatus}
|
||||
errorMessage={launch.qrisErrorMessage}
|
||||
onClose={launch.handleQrisClose}
|
||||
{launch.shouldShowRegionalQrDialog && launch.regionalQrPayment ? (
|
||||
<RegionalQrPaymentDialog
|
||||
payment={launch.regionalQrPayment}
|
||||
status={launch.regionalQrStatus}
|
||||
errorMessage={launch.regionalQrErrorMessage}
|
||||
onClose={launch.handleRegionalQrClose}
|
||||
/>
|
||||
) : null}
|
||||
{launch.shouldShowStripeDialog && launch.stripeClientSecret ? (
|
||||
@@ -73,43 +73,56 @@ export function PaymentLaunchDialogs({
|
||||
);
|
||||
}
|
||||
|
||||
interface QrisPaymentDialogProps {
|
||||
interface RegionalQrPaymentDialogProps {
|
||||
errorMessage: string | null;
|
||||
onClose: () => void;
|
||||
payment: NonNullable<PaymentLaunchFlow["qrisPayment"]>;
|
||||
status: PaymentLaunchFlow["qrisStatus"];
|
||||
payment: NonNullable<PaymentLaunchFlow["regionalQrPayment"]>;
|
||||
status: PaymentLaunchFlow["regionalQrStatus"];
|
||||
}
|
||||
|
||||
function formatQrisAmount(amountCents: number, currency: string): string {
|
||||
function formatRegionalQrAmount(amountCents: number, currency: string): string {
|
||||
const normalizedCurrency = currency.trim().toUpperCase() || "IDR";
|
||||
try {
|
||||
return new Intl.NumberFormat("id-ID", {
|
||||
style: "currency",
|
||||
currency: normalizedCurrency,
|
||||
maximumFractionDigits: normalizedCurrency === "IDR" ? 0 : 2,
|
||||
}).format(amountCents / 100);
|
||||
return new Intl.NumberFormat(
|
||||
normalizedCurrency === "PHP" ? "en-PH" : "id-ID",
|
||||
{
|
||||
style: "currency",
|
||||
currency: normalizedCurrency,
|
||||
maximumFractionDigits: normalizedCurrency === "IDR" ? 0 : 2,
|
||||
},
|
||||
).format(amountCents / 100);
|
||||
} catch {
|
||||
return `${normalizedCurrency} ${(amountCents / 100).toFixed(2)}`;
|
||||
}
|
||||
}
|
||||
|
||||
function qrisStatusMessage(
|
||||
status: PaymentLaunchFlow["qrisStatus"],
|
||||
function regionalQrStatusMessage(
|
||||
status: PaymentLaunchFlow["regionalQrStatus"],
|
||||
errorMessage: string | null,
|
||||
paymentName: string,
|
||||
): string {
|
||||
if (status === "failed") return errorMessage || "Payment failed. Please try again.";
|
||||
if (status === "expired") return errorMessage || "This QRIS order has expired.";
|
||||
if (status === "failed") {
|
||||
return errorMessage || "Payment failed. Please try again.";
|
||||
}
|
||||
if (status === "expired") {
|
||||
return errorMessage || `This ${paymentName} order has expired.`;
|
||||
}
|
||||
return "Waiting for payment";
|
||||
}
|
||||
|
||||
function QrisPaymentDialog({
|
||||
function RegionalQrPaymentDialog({
|
||||
errorMessage,
|
||||
onClose,
|
||||
payment,
|
||||
status,
|
||||
}: QrisPaymentDialogProps) {
|
||||
}: RegionalQrPaymentDialogProps) {
|
||||
const titleId = useId();
|
||||
const statusMessage = qrisStatusMessage(status, errorMessage);
|
||||
const copy = regionalQrCopy(payment.experience);
|
||||
const statusMessage = regionalQrStatusMessage(
|
||||
status,
|
||||
errorMessage,
|
||||
copy.paymentName,
|
||||
);
|
||||
|
||||
return (
|
||||
<ModalPortal
|
||||
@@ -123,17 +136,17 @@ function QrisPaymentDialog({
|
||||
>
|
||||
<div className={`${styles.header} text-center`}>
|
||||
<h2 id={titleId} className={styles.title}>
|
||||
Scan to pay with QRIS
|
||||
{copy.title}
|
||||
</h2>
|
||||
<p className={styles.content}>
|
||||
Open a QRIS-compatible banking or wallet app and scan this code.
|
||||
{copy.description}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mb-4 flex min-h-64 items-center justify-center rounded-2xl bg-white p-4">
|
||||
{payment.qrData ? (
|
||||
<QRCodeSVG
|
||||
value={payment.qrData}
|
||||
title="QRIS payment QR code"
|
||||
title={copy.qrTitle}
|
||||
size={256}
|
||||
level="M"
|
||||
marginSize={2}
|
||||
@@ -141,14 +154,14 @@ function QrisPaymentDialog({
|
||||
/>
|
||||
) : (
|
||||
<p className={styles.error} role="alert">
|
||||
QRIS data is unavailable. Please close this dialog and try again.
|
||||
{copy.unavailableMessage}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="mb-4 flex flex-col gap-2 text-center">
|
||||
<p className={styles.content}>Order No. {payment.orderId}</p>
|
||||
<p className="m-0 text-xl font-bold text-text-foreground">
|
||||
{formatQrisAmount(payment.amountCents, payment.currency)}
|
||||
{formatRegionalQrAmount(payment.amountCents, payment.currency)}
|
||||
</p>
|
||||
<p
|
||||
className={status === "failed" || status === "expired" ? styles.error : styles.content}
|
||||
@@ -170,6 +183,41 @@ function QrisPaymentDialog({
|
||||
);
|
||||
}
|
||||
|
||||
function regionalQrCopy(
|
||||
experience: NonNullable<PaymentLaunchFlow["regionalQrPayment"]>["experience"],
|
||||
) {
|
||||
if (experience === "gcashQrPh") {
|
||||
return {
|
||||
paymentName: "GCash / QR Ph",
|
||||
title: "Pay with GCash / QR Ph",
|
||||
description:
|
||||
"Open GCash or another QR Ph-compatible app and scan this code.",
|
||||
qrTitle: "GCash / QR Ph payment QR code",
|
||||
unavailableMessage:
|
||||
"GCash / QR Ph data is unavailable. Please close this dialog and try again.",
|
||||
};
|
||||
}
|
||||
if (experience === "qris") {
|
||||
return {
|
||||
paymentName: "QRIS",
|
||||
title: "Scan to pay with QRIS",
|
||||
description:
|
||||
"Open a QRIS-compatible banking or wallet app and scan this code.",
|
||||
qrTitle: "QRIS payment QR code",
|
||||
unavailableMessage:
|
||||
"QRIS data is unavailable. Please close this dialog and try again.",
|
||||
};
|
||||
}
|
||||
return {
|
||||
paymentName: "payment QR",
|
||||
title: "Scan to pay",
|
||||
description: "Open a compatible banking or wallet app and scan this code.",
|
||||
qrTitle: "Payment QR code",
|
||||
unavailableMessage:
|
||||
"Payment QR data is unavailable. Please close this dialog and try again.",
|
||||
};
|
||||
}
|
||||
|
||||
interface EzpayRedirectConfirmDialogProps {
|
||||
description: ReactNode;
|
||||
externalCheckoutAnalyticsKey: string;
|
||||
|
||||
Reference in New Issue
Block a user