fix(payment): guard ezpay redirect persistence
This commit is contained in:
@@ -26,26 +26,13 @@ export function PwaInstallOverlay() {
|
|||||||
let mounted = true;
|
let mounted = true;
|
||||||
|
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
// 0) PWA 支持 / 安装 双重门禁(两种环境都检查):
|
const isDev = AppEnvUtil.isDevelopment();
|
||||||
// isSupported() 检查 serviceWorker + window(SSR 安全)
|
const shouldShowDialog = await shouldShowPwaInstallDialog(isDev);
|
||||||
// isInstalled() 检查 standalone display-mode(避免给已安装用户再弹)
|
if (!shouldShowDialog) return;
|
||||||
if (!pwaUtil.isSupported()) return;
|
|
||||||
if (pwaUtil.isInstalled()) return;
|
|
||||||
if (BrowserDetector.isInAppBrowser()) return;
|
|
||||||
// 提前挂 beforeinstallprompt 监听,避免用户点 OK 时事件已在更早时机丢失。
|
// 提前挂 beforeinstallprompt 监听,避免用户点 OK 时事件已在更早时机丢失。
|
||||||
pwaUtil.prepareInstallPrompt();
|
pwaUtil.prepareInstallPrompt();
|
||||||
|
|
||||||
const isDev = AppEnvUtil.isDevelopment();
|
|
||||||
|
|
||||||
if (!isDev) {
|
|
||||||
// 生产环境:每日只弹一次(防骚扰)
|
|
||||||
const lastResult = await SpAsyncUtil.getString(PWA_DIALOG_KEY);
|
|
||||||
if (!lastResult.success) return;
|
|
||||||
const today = new Date().toISOString().slice(0, 10);
|
|
||||||
if (lastResult.data === today) return; // 今天已显示过
|
|
||||||
}
|
|
||||||
// 开发环境:跳过"每日一次"门禁,每次进入都弹,方便 UI 测试
|
|
||||||
|
|
||||||
// 开发环境 1s 后弹(快);生产环境 3.5s 后弹(让用户先看到聊天内容)
|
// 开发环境 1s 后弹(快);生产环境 3.5s 后弹(让用户先看到聊天内容)
|
||||||
const delay = isDev ? 1000 : 3500;
|
const delay = isDev ? 1000 : 3500;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -63,6 +50,23 @@ export function PwaInstallOverlay() {
|
|||||||
return <div className={styles.hidden} aria-hidden="true" />;
|
return <div className={styles.hidden} aria-hidden="true" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function shouldShowPwaInstallDialog(isDevelopment: boolean) {
|
||||||
|
// PWA 支持 / 安装 双重门禁(两种环境都检查)。
|
||||||
|
if (!pwaUtil.isSupported()) return false;
|
||||||
|
if (pwaUtil.isInstalled()) return false;
|
||||||
|
if (BrowserDetector.isInAppBrowser()) return false;
|
||||||
|
|
||||||
|
// 开发环境:跳过"每日一次"门禁,每次进入都弹,方便 UI 测试。
|
||||||
|
if (isDevelopment) return true;
|
||||||
|
|
||||||
|
// 生产环境:每日只弹一次(防骚扰)。
|
||||||
|
const lastResult = await SpAsyncUtil.getString(PWA_DIALOG_KEY);
|
||||||
|
if (!lastResult.success) return false;
|
||||||
|
|
||||||
|
const today = new Date().toISOString().slice(0, 10);
|
||||||
|
return lastResult.data !== today;
|
||||||
|
}
|
||||||
|
|
||||||
function showPwaDialog() {
|
function showPwaDialog() {
|
||||||
if (typeof document === "undefined") return;
|
if (typeof document === "undefined") return;
|
||||||
// 记录显示时间
|
// 记录显示时间
|
||||||
|
|||||||
@@ -63,10 +63,12 @@ export function SubscriptionCheckoutButton({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isEzpay) {
|
if (isEzpay) {
|
||||||
void redirectToEzpay({
|
void launchEzpayRedirect({
|
||||||
orderId: payment.currentOrderId,
|
orderId: payment.currentOrderId,
|
||||||
paymentUrl,
|
paymentUrl,
|
||||||
subscriptionType,
|
subscriptionType,
|
||||||
|
onFailed: (errorMessage) =>
|
||||||
|
paymentDispatch({ type: "PaymentLaunchFailed", errorMessage }),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -144,10 +146,14 @@ export function SubscriptionCheckoutButton({
|
|||||||
const handleEzpayConfirm = () => {
|
const handleEzpayConfirm = () => {
|
||||||
if (!payment.currentOrderId || !ezpayPaymentUrl) return;
|
if (!payment.currentOrderId || !ezpayPaymentUrl) return;
|
||||||
setIsConfirmingEzpay(true);
|
setIsConfirmingEzpay(true);
|
||||||
void redirectToEzpay({
|
void launchEzpayRedirect({
|
||||||
orderId: payment.currentOrderId,
|
orderId: payment.currentOrderId,
|
||||||
paymentUrl: ezpayPaymentUrl,
|
paymentUrl: ezpayPaymentUrl,
|
||||||
subscriptionType,
|
subscriptionType,
|
||||||
|
onFailed: (errorMessage) => {
|
||||||
|
setIsConfirmingEzpay(false);
|
||||||
|
paymentDispatch({ type: "PaymentLaunchFailed", errorMessage });
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -235,47 +241,57 @@ interface RedirectToEzpayInput {
|
|||||||
orderId: string | null;
|
orderId: string | null;
|
||||||
paymentUrl: string;
|
paymentUrl: string;
|
||||||
subscriptionType: "vip" | "voice";
|
subscriptionType: "vip" | "voice";
|
||||||
|
onFailed: (errorMessage: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function redirectToEzpay({
|
async function launchEzpayRedirect({
|
||||||
orderId,
|
orderId,
|
||||||
paymentUrl,
|
paymentUrl,
|
||||||
subscriptionType,
|
subscriptionType,
|
||||||
|
onFailed,
|
||||||
}: RedirectToEzpayInput): Promise<void> {
|
}: RedirectToEzpayInput): Promise<void> {
|
||||||
log.debug("[subscription-checkout] redirectToEzpay START", {
|
log.debug("[subscription-checkout] launchEzpayRedirect START", {
|
||||||
hasOrderId: Boolean(orderId),
|
hasOrderId: Boolean(orderId),
|
||||||
orderId,
|
orderId,
|
||||||
subscriptionType,
|
subscriptionType,
|
||||||
paymentUrl,
|
paymentUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (orderId) {
|
if (!orderId) {
|
||||||
|
const errorMessage = "Missing order id before opening Ezpay.";
|
||||||
|
log.error("[subscription-checkout] pending ezpay order save skipped", {
|
||||||
|
subscriptionType,
|
||||||
|
paymentUrl,
|
||||||
|
errorMessage,
|
||||||
|
});
|
||||||
|
onFailed(errorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const saveResult = await PendingPaymentOrderStorage.setPendingOrder({
|
const saveResult = await PendingPaymentOrderStorage.setPendingOrder({
|
||||||
orderId,
|
orderId,
|
||||||
payChannel: "ezpay",
|
payChannel: "ezpay",
|
||||||
subscriptionType,
|
subscriptionType,
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
});
|
});
|
||||||
if (Result.isOk(saveResult)) {
|
if (Result.isErr(saveResult)) {
|
||||||
log.debug("[subscription-checkout] pending ezpay order saved", {
|
const errorMessage =
|
||||||
orderId,
|
"Could not save payment order before opening Ezpay. Please try again.";
|
||||||
subscriptionType,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
log.error("[subscription-checkout] pending ezpay order save failed", {
|
log.error("[subscription-checkout] pending ezpay order save failed", {
|
||||||
orderId,
|
orderId,
|
||||||
subscriptionType,
|
subscriptionType,
|
||||||
error: saveResult.error,
|
error: saveResult.error,
|
||||||
});
|
});
|
||||||
}
|
onFailed(errorMessage);
|
||||||
} else {
|
return;
|
||||||
log.warn("[subscription-checkout] skip pending ezpay order save: missing orderId", {
|
|
||||||
subscriptionType,
|
|
||||||
paymentUrl,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("[subscription-checkout] redirectToEzpay NOW", {
|
log.debug("[subscription-checkout] pending ezpay order saved", {
|
||||||
|
orderId,
|
||||||
|
subscriptionType,
|
||||||
|
});
|
||||||
|
|
||||||
|
log.debug("[subscription-checkout] launchEzpayRedirect NOW", {
|
||||||
orderId,
|
orderId,
|
||||||
subscriptionType,
|
subscriptionType,
|
||||||
paymentUrl,
|
paymentUrl,
|
||||||
|
|||||||
Reference in New Issue
Block a user