feat(payment): support Indonesia QRIS checkout

This commit is contained in:
Codex
2026-07-22 18:57:22 +08:00
parent a0b2d44ec6
commit 1e25279e8f
19 changed files with 806 additions and 42 deletions
+85
View File
@@ -11,6 +11,36 @@ import {
const log = new Logger("LibPaymentPaymentLaunch");
export type EzpayLaunchTarget =
| { kind: "url"; paymentUrl: string }
| { kind: "qr"; qrData: string }
| { kind: "error"; errorMessage: string };
function getNonEmptyString(
payParams: Record<string, unknown>,
...keys: string[]
): string | null {
for (const key of keys) {
const value = payParams[key];
if (typeof value === "string" && value.trim().length > 0) {
return value.trim();
}
}
return null;
}
function getHttpPaymentUrl(value: string | null): string | null {
if (!value) return null;
try {
const parsed = new URL(value);
return parsed.protocol === "https:" || parsed.protocol === "http:"
? value
: null;
} catch {
return null;
}
}
export function getPaymentUrl(payParams: Record<string, unknown>): string | null {
const keys = [
"cashierUrl",
@@ -39,6 +69,61 @@ export function isEzpayPayment(payParams: Record<string, unknown>): boolean {
return typeof provider === "string" && provider.toLowerCase() === "ezpay";
}
export function resolveEzpayLaunchTarget(
payParams: Record<string, unknown>,
): EzpayLaunchTarget {
const rawChannelType = getNonEmptyString(
payParams,
"channelType",
"channel_type",
);
const channelType = rawChannelType?.toUpperCase() ?? null;
const payData = getNonEmptyString(payParams, "payData", "pay_data");
if (channelType === "QR") {
return payData
? { kind: "qr", qrData: payData }
: {
kind: "error",
errorMessage: "QRIS payment data is missing. Please try again.",
};
}
if (channelType === "VA") {
return {
kind: "error",
errorMessage: "Virtual account payments are not supported in this app.",
};
}
if (channelType === "URL") {
const paymentUrl =
getHttpPaymentUrl(payData) ?? getHttpPaymentUrl(getPaymentUrl(payParams));
return paymentUrl
? { kind: "url", paymentUrl }
: {
kind: "error",
errorMessage: "Ezpay payment URL is missing or invalid. Please try again.",
};
}
if (channelType) {
return {
kind: "error",
errorMessage: `Unsupported Ezpay payment channel: ${channelType}.`,
};
}
const legacyPaymentUrl = getHttpPaymentUrl(getPaymentUrl(payParams));
return legacyPaymentUrl
? { kind: "url", paymentUrl: legacyPaymentUrl }
: {
kind: "error",
errorMessage:
"Ezpay payment parameters did not include a supported URL or QRIS code.",
};
}
export function getStripeClientSecret(
payParams: Record<string, unknown>,
): string | null {