From 506438dc7f7a2b9ec8f6e1a217b84cddd0a5f32d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=B7=E9=BE=99?= Date: Thu, 28 Aug 2025 18:40:17 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=B9=E6=8E=A5=E8=8E=B7=E5=8F=96=E7=A7=AF?= =?UTF-8?q?=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/payCallback/page.tsx | 2 +- app/pricing/page.tsx | 2 +- components/layout/top-bar.tsx | 50 +++++++++++++++++++++++++++-------- lib/stripe.ts | 38 ++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 13 deletions(-) diff --git a/app/payCallback/page.tsx b/app/payCallback/page.tsx index 2581e94..4a80837 100644 --- a/app/payCallback/page.tsx +++ b/app/payCallback/page.tsx @@ -10,7 +10,7 @@ export default function payCallback() { const canceled = searchParams.get("canceled")||false; useEffect(() => { - window.opener.postMessage( + window.opener?.postMessage( { type: "payment-callback", canceled, sessionId, userId }, "*" ); diff --git a/app/pricing/page.tsx b/app/pricing/page.tsx index e600f29..660dd98 100644 --- a/app/pricing/page.tsx +++ b/app/pricing/page.tsx @@ -93,7 +93,7 @@ function HomeModule5() { if (!result.successful || !result.data) { throw new Error("create checkout session failed"); } - window.opener.postMessage({ type: "waiting-payment" }, "*"); + window.opener?.postMessage({ type: "waiting-payment" }, "*"); // 2. 直接跳转到Stripe托管页面(就这么简单!) window.location.href = result.data.checkout_url; } catch (error) { diff --git a/components/layout/top-bar.tsx b/components/layout/top-bar.tsx index 54e6d57..f72539d 100644 --- a/components/layout/top-bar.tsx +++ b/components/layout/top-bar.tsx @@ -18,7 +18,7 @@ import ReactDOM from 'react-dom'; import { useRouter, usePathname } from 'next/navigation'; import React, { useRef, useEffect, useLayoutEffect, useState } from 'react'; import { logoutUser } from '@/lib/auth'; -import { createPortalSession, redirectToPortal } from '@/lib/stripe'; +import { createPortalSession, redirectToPortal, getUserSubscriptionInfo } from '@/lib/stripe'; interface User { @@ -46,6 +46,28 @@ export function TopBar({ const [isLogin, setIsLogin] = useState(false); const pathname = usePathname(); const [isManagingSubscription, setIsManagingSubscription] = useState(false); + const [subscriptionStatus, setSubscriptionStatus] = useState(''); + const [credits, setCredits] = useState(100); + const [isLoadingSubscription, setIsLoadingSubscription] = useState(false); + + // 获取用户订阅信息 + const fetchSubscriptionInfo = async () => { + if (!currentUser?.id) return; + + setIsLoadingSubscription(true); + try { + const response = await getUserSubscriptionInfo(String(currentUser.id)); + if (response.successful && response.data) { + setSubscriptionStatus(response.data.subscription_status); + setCredits(response.data.credits); + } + } catch (error) { + console.error('获取订阅信息失败:', error); + } finally { + setIsLoadingSubscription(false); + } + }; + useEffect(() => { const currentUser = localStorage.getItem("currentUser"); if (JSON.parse(currentUser || "{}")?.token) { @@ -213,6 +235,10 @@ export function TopBar({ size="sm" onClick={() => { console.log("Button clicked, current isOpen:", isOpen); + if (!isOpen) { + // 每次打开菜单时重新获取订阅信息 + fetchSubscriptionInfo(); + } setIsOpen(!isOpen); }} data-alt="user-menu-trigger" @@ -270,7 +296,7 @@ export function TopBar({
- 100 credits + {isLoadingSubscription ? '...' : `${credits} credits`}
- + {subscriptionStatus === 'ACTIVE' && ( + + )} {/* Menu Items */} diff --git a/lib/stripe.ts b/lib/stripe.ts index 4d49097..9a62380 100644 --- a/lib/stripe.ts +++ b/lib/stripe.ts @@ -138,6 +138,44 @@ export async function createPortalSession( throw error; } } +/** + * 获取用户订阅信息 + * @param {string} userId - 用户ID + * @returns {Promise} - 用户订阅信息 + * @throws {Error} - 请求失败时抛出异常 + */ +export async function getUserSubscriptionInfo( + userId: string +): Promise { + if (!userId) { + throw new Error('userId不能为空'); + } + try { + return await get(`/api/subscription/user/info?user_id=${userId}`); + } catch (error) { + console.error('获取用户订阅信息失败:', error); + throw error; + } +} + +/** 用户订阅信息响应体 */ +export interface UserSubscriptionInfoResponse { + /** 状态码 */ + code: number; + /** 消息 */ + message: string; + /** 数据 */ + data: { + /** 剩余点数 */ + credits: number; + /** 订阅状态 */ + subscription_status: string; + /** 订阅计划名 */ + plan_name: string; + }; + /** 是否成功 */ + successful: boolean; +} /** * 简单的跳转到Checkout页面的工具函数