fix(payment): route Philippine GCash separately from QRIS
Docker Image / Build and Push Docker Image (push) Successful in 2m12s
Docker Image / Build and Push Docker Image (push) Successful in 2m12s
This commit is contained in:
@@ -13,9 +13,20 @@ const log = new Logger("LibPaymentPaymentLaunch");
|
||||
|
||||
export type EzpayLaunchTarget =
|
||||
| { kind: "url"; paymentUrl: string }
|
||||
| { kind: "qr"; qrData: string }
|
||||
| {
|
||||
kind: "qr";
|
||||
experience: EzpayQrExperience;
|
||||
qrData: string;
|
||||
}
|
||||
| { kind: "error"; errorMessage: string };
|
||||
|
||||
export type EzpayQrExperience = "gcashQrPh" | "qris" | "paymentQr";
|
||||
|
||||
export interface ResolveEzpayLaunchContext {
|
||||
countryCode?: string | null;
|
||||
currency?: string | null;
|
||||
}
|
||||
|
||||
function getNonEmptyString(
|
||||
payParams: Record<string, unknown>,
|
||||
...keys: string[]
|
||||
@@ -41,6 +52,12 @@ function getHttpPaymentUrl(value: string | null): string | null {
|
||||
}
|
||||
}
|
||||
|
||||
export function getPaymentUrlHostname(value: string | null): string | null {
|
||||
const paymentUrl = getHttpPaymentUrl(value);
|
||||
if (!paymentUrl) return null;
|
||||
return new URL(paymentUrl).hostname;
|
||||
}
|
||||
|
||||
export function getPaymentUrl(payParams: Record<string, unknown>): string | null {
|
||||
const keys = [
|
||||
"cashierUrl",
|
||||
@@ -71,6 +88,7 @@ export function isEzpayPayment(payParams: Record<string, unknown>): boolean {
|
||||
|
||||
export function resolveEzpayLaunchTarget(
|
||||
payParams: Record<string, unknown>,
|
||||
context: ResolveEzpayLaunchContext = {},
|
||||
): EzpayLaunchTarget {
|
||||
const rawChannelType = getNonEmptyString(
|
||||
payParams,
|
||||
@@ -79,13 +97,33 @@ export function resolveEzpayLaunchTarget(
|
||||
);
|
||||
const channelType = rawChannelType?.toUpperCase() ?? null;
|
||||
const payData = getNonEmptyString(payParams, "payData", "pay_data");
|
||||
const payDataUrl = getHttpPaymentUrl(payData);
|
||||
const namedPaymentUrl = getHttpPaymentUrl(getPaymentUrl(payParams));
|
||||
const paymentUrl = payDataUrl ?? namedPaymentUrl;
|
||||
const qrData = payData && !payDataUrl ? payData : null;
|
||||
const region = resolveEzpayRegion(payParams, context);
|
||||
|
||||
if (
|
||||
region === "ph" &&
|
||||
paymentUrl &&
|
||||
(channelType === "QR" || channelType === "URL" || channelType === null)
|
||||
) {
|
||||
return { kind: "url", paymentUrl };
|
||||
}
|
||||
|
||||
if (channelType === "QR") {
|
||||
return payData
|
||||
? { kind: "qr", qrData: payData }
|
||||
if (qrData) {
|
||||
return {
|
||||
kind: "qr",
|
||||
experience: resolveEzpayQrExperience(region),
|
||||
qrData,
|
||||
};
|
||||
}
|
||||
return paymentUrl
|
||||
? { kind: "url", paymentUrl }
|
||||
: {
|
||||
kind: "error",
|
||||
errorMessage: "QRIS payment data is missing. Please try again.",
|
||||
errorMessage: regionalQrMissingMessage(region),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -97,8 +135,6 @@ export function resolveEzpayLaunchTarget(
|
||||
}
|
||||
|
||||
if (channelType === "URL") {
|
||||
const paymentUrl =
|
||||
getHttpPaymentUrl(payData) ?? getHttpPaymentUrl(getPaymentUrl(payParams));
|
||||
return paymentUrl
|
||||
? { kind: "url", paymentUrl }
|
||||
: {
|
||||
@@ -114,14 +150,66 @@ export function resolveEzpayLaunchTarget(
|
||||
};
|
||||
}
|
||||
|
||||
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.",
|
||||
};
|
||||
if (paymentUrl) return { kind: "url", paymentUrl };
|
||||
if (qrData && region !== null) {
|
||||
return {
|
||||
kind: "qr",
|
||||
experience: resolveEzpayQrExperience(region),
|
||||
qrData,
|
||||
};
|
||||
}
|
||||
return {
|
||||
kind: "error",
|
||||
errorMessage:
|
||||
"Ezpay payment parameters did not include a supported URL or payment QR code.",
|
||||
};
|
||||
}
|
||||
|
||||
function resolveEzpayRegion(
|
||||
payParams: Record<string, unknown>,
|
||||
context: ResolveEzpayLaunchContext,
|
||||
): "ph" | "id" | null {
|
||||
const currency = (
|
||||
context.currency ?? getNonEmptyString(payParams, "currency")
|
||||
)
|
||||
?.trim()
|
||||
.toUpperCase();
|
||||
if (currency === "PHP") return "ph";
|
||||
if (currency === "IDR") return "id";
|
||||
|
||||
const countryCode = (
|
||||
context.countryCode ??
|
||||
getNonEmptyString(
|
||||
payParams,
|
||||
"purchaseCountryCode",
|
||||
"purchase_country_code",
|
||||
"countryCode",
|
||||
"country_code",
|
||||
)
|
||||
)
|
||||
?.trim()
|
||||
.toUpperCase();
|
||||
if (countryCode === "PH") return "ph";
|
||||
if (countryCode === "ID") return "id";
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveEzpayQrExperience(
|
||||
region: "ph" | "id" | null,
|
||||
): EzpayQrExperience {
|
||||
if (region === "ph") return "gcashQrPh";
|
||||
if (region === "id") return "qris";
|
||||
return "paymentQr";
|
||||
}
|
||||
|
||||
function regionalQrMissingMessage(region: "ph" | "id" | null): string {
|
||||
if (region === "ph") {
|
||||
return "GCash / QR Ph payment data is missing. Please try again.";
|
||||
}
|
||||
if (region === "id") {
|
||||
return "QRIS payment data is missing. Please try again.";
|
||||
}
|
||||
return "Payment QR data is missing. Please try again.";
|
||||
}
|
||||
|
||||
export function getStripeClientSecret(
|
||||
@@ -166,18 +254,19 @@ export async function launchEzpayRedirect({
|
||||
onOpened,
|
||||
onFailed,
|
||||
}: LaunchEzpayRedirectInput): Promise<void> {
|
||||
const paymentUrlHost = getPaymentUrlHostname(paymentUrl);
|
||||
log.debug("[payment-launch] launchEzpayRedirect START", {
|
||||
hasOrderId: Boolean(orderId),
|
||||
orderId,
|
||||
subscriptionType,
|
||||
paymentUrl,
|
||||
paymentUrlHost,
|
||||
});
|
||||
|
||||
if (!orderId) {
|
||||
const errorMessage = "Missing order id before opening Ezpay.";
|
||||
log.error("[payment-launch] pending ezpay order save skipped", {
|
||||
subscriptionType,
|
||||
paymentUrl,
|
||||
paymentUrlHost,
|
||||
errorMessage,
|
||||
});
|
||||
onFailed(errorMessage);
|
||||
|
||||
Reference in New Issue
Block a user