修复 使用新窗口重定向到支付页,防止拦截

This commit is contained in:
moux1024 2025-09-15 20:15:01 +08:00
parent d4bb1e7713
commit 7fefdc1b0d
4 changed files with 111 additions and 23 deletions

52
app/pay-redirect/page.tsx Normal file
View File

@ -0,0 +1,52 @@
"use client";
import React from "react";
export default function PayRedirectPage() {
const [status, setStatus] = React.useState<string>("等待安全收银台跳转...");
const [error, setError] = React.useState<string>("");
React.useEffect(() => {
const handleMessage = (event: MessageEvent) => {
try {
if (event.origin !== window.location.origin) return;
const data = event.data || {};
if (data?.type === "redirect-to-payment" && typeof data?.url === "string") {
setStatus("即将跳转到 Stripe 收银台...");
window.location.href = data.url as string;
} else if (data?.type === "redirect-error") {
setError(typeof data?.message === "string" ? data.message : "创建支付失败,请关闭此页重试");
}
} catch {
setError("处理跳转信息时发生错误,请关闭此页重试");
}
};
window.addEventListener("message", handleMessage);
// 超时兜底:若 15 秒内未收到跳转指令,提示用户
const timeoutId = window.setTimeout(() => {
setStatus("");
setError("未收到跳转指令,可能网络异常或页面被拦截。请返回重试。");
}, 15000);
return () => {
window.removeEventListener("message", handleMessage);
window.clearTimeout(timeoutId);
};
}, []);
return (
<div data-alt="pay-redirect-page" className="min-h-screen w-full flex items-center justify-center bg-black text-white">
<div data-alt="pay-redirect-card" className="max-w-md w-full p-6 rounded-xl border border-white/10 bg-white/5 backdrop-blur">
<h1 className="text-lg font-semibold mb-2"></h1>
{status && <p className="text-sm text-white/80 mb-2">{status}</p>}
{error && <p className="text-sm text-red-300">{error}</p>}
{!error && (
<p className="text-xs text-white/60 mt-2"></p>
)}
</div>
</div>
);
}

View File

@ -84,39 +84,53 @@ function HomeModule5() {
}, [plans, billingType]);
const handleSubscribe = async (planName: string) => {
setLoadingPlan(planName); // 设置加载状态
setLoadingPlan(planName);
// 先同步打开同域重定向页,避免拦截
const redirectWindow = window.open('/pay-redirect', '_blank');
if (!redirectWindow) {
setLoadingPlan(null);
throw new Error('Unable to open redirect window, please check popup settings');
}
try {
const { createCheckoutSession, redirectToCheckout } = await import(
"@/lib/stripe"
);
const { createCheckoutSession } = await import("@/lib/stripe");
// 从localStorage获取当前用户信息
const User = JSON.parse(localStorage.getItem("currentUser") || "{}");
if (!User.id) {
throw new Error("Unable to obtain user ID, please log in again");
}
// 1. 创建Checkout Session
const result = await createCheckoutSession({
user_id: String(User.id),
plan_name: planName,
billing_cycle: billingType,
});
if (!result.successful || !result.data) {
if (!result.successful || !result.data?.checkout_url) {
throw new Error("create checkout session failed");
}
// 通知当前窗口等待支付显示loading模态框
window.postMessage({
type: "waiting-payment",
paymentType: "subscription"
}, "*");
// 在新标签页中打开Stripe支付页面保持当前定价页面不变
window.open(result.data.checkout_url, '_blank');
window.postMessage({
type: 'waiting-payment',
paymentType: 'subscription',
}, '*');
// 通过 postMessage 通知新页面执行重定向
redirectWindow.postMessage({
type: 'redirect-to-payment',
url: result.data.checkout_url,
}, window.location.origin);
} catch (error) {
setLoadingPlan(null); // 出错时清除加载状态
// 通知新页错误信息
try {
redirectWindow.postMessage({
type: 'redirect-error',
message: 'Failed to create checkout session',
}, window.location.origin);
} catch {}
setLoadingPlan(null);
throw new Error("create checkout session failed, please try again later");
}
};

View File

@ -76,7 +76,7 @@ export default function CallbackModal({
})
} else {
// pending状态继续等待
setTimeout(fetchPaymentStatus, 2000)
setTimeout(fetchPaymentStatus, 5000)
}
} else {
throw new Error(response.message || 'Failed to get payment status')

View File

@ -81,7 +81,7 @@ export function TopBar({ collapsed, isDesktop=true }: { collapsed: boolean, isDe
}
};
// 处理Token购买
// 处理Token购买(同域新页 + postMessage 方式重定向)
const handleBuyTokens = async (tokenAmount: number) => {
if (!currentUser?.id) {
console.error("用户未登录");
@ -93,6 +93,13 @@ export function TopBar({ collapsed, isDesktop=true }: { collapsed: boolean, isDe
return;
}
// 先同步打开同域新页面,避免被拦截
const redirectWindow = window.open("/pay-redirect", "_blank");
if (!redirectWindow) {
console.error("无法打开支付重定向页面,可能被浏览器拦截");
return;
}
setIsBuyingTokens(true);
try {
const response = await buyTokens({
@ -102,17 +109,32 @@ export function TopBar({ collapsed, isDesktop=true }: { collapsed: boolean, isDe
if (response.successful && response.data?.checkout_url) {
// 通知当前窗口等待支付标识为Token购买
window.postMessage({
type: "waiting-payment",
paymentType: "token"
window.postMessage({
type: "waiting-payment",
paymentType: "token"
}, "*");
// 在新标签页中打开Stripe支付页面
window.open(response.data.checkout_url, '_blank');
// 通过 postMessage 通知新页面进行重定向
redirectWindow.postMessage({
type: "redirect-to-payment",
url: response.data.checkout_url
}, window.location.origin);
} else {
console.error("创建Token购买失败:", response.message);
// 通知新页显示错误
redirectWindow.postMessage({
type: "redirect-error",
message: response.message || "创建支付失败"
}, window.location.origin);
}
} catch (error) {
} catch (error: unknown) {
console.error("Token购买失败:", error);
try {
redirectWindow.postMessage({
type: "redirect-error",
message: "网络或服务异常,请关闭此页重试"
}, window.location.origin);
} catch {}
} finally {
setIsBuyingTokens(false);
}