From 7fefdc1b0d6dd4603e55ce9bc5efc54779f250eb Mon Sep 17 00:00:00 2001 From: moux1024 <403053463@qq.com> Date: Mon, 15 Sep 2025 20:15:01 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E6=96=B0=E7=AA=97=E5=8F=A3=E9=87=8D=E5=AE=9A=E5=90=91=E5=88=B0?= =?UTF-8?q?=E6=94=AF=E4=BB=98=E9=A1=B5,=E9=98=B2=E6=AD=A2=E6=8B=A6?= =?UTF-8?q?=E6=88=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/pay-redirect/page.tsx | 52 +++++++++++++++++++++++++++++ app/pricing/page.tsx | 44 +++++++++++++++--------- components/common/CallbackModal.tsx | 2 +- components/layout/top-bar.tsx | 36 ++++++++++++++++---- 4 files changed, 111 insertions(+), 23 deletions(-) create mode 100644 app/pay-redirect/page.tsx diff --git a/app/pay-redirect/page.tsx b/app/pay-redirect/page.tsx new file mode 100644 index 0000000..7ec78c2 --- /dev/null +++ b/app/pay-redirect/page.tsx @@ -0,0 +1,52 @@ +"use client"; + +import React from "react"; + +export default function PayRedirectPage() { + const [status, setStatus] = React.useState("等待安全收银台跳转..."); + const [error, setError] = React.useState(""); + + 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 ( +
+
+

正在准备跳转

+ {status &&

{status}

} + {error &&

{error}

} + {!error && ( +

如长时间未跳转,请返回原页重试或检查网络。

+ )} +
+
+ ); +} + + diff --git a/app/pricing/page.tsx b/app/pricing/page.tsx index f4ff642..9ea40bd 100644 --- a/app/pricing/page.tsx +++ b/app/pricing/page.tsx @@ -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"); } }; diff --git a/components/common/CallbackModal.tsx b/components/common/CallbackModal.tsx index e4deb49..206b8b8 100644 --- a/components/common/CallbackModal.tsx +++ b/components/common/CallbackModal.tsx @@ -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') diff --git a/components/layout/top-bar.tsx b/components/layout/top-bar.tsx index 7978b1c..3a9c3f3 100644 --- a/components/layout/top-bar.tsx +++ b/components/layout/top-bar.tsx @@ -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); }