adds alipay

This commit is contained in:
Zixin Zhou 2025-09-09 22:24:39 +08:00
parent 3022a497d5
commit f8b2ec2d4b
5 changed files with 95 additions and 12 deletions

View File

@ -25,8 +25,10 @@ export default function RootLayout({
children: React.ReactNode
}) {
const [showCallbackModal, setShowCallbackModal] = useState(false)
const [paymentType, setPaymentType] = useState<'subscription' | 'token'>('subscription')
const openCallback = async function (ev: MessageEvent<any>) {
if (ev.data.type === 'waiting-payment') {
setPaymentType(ev.data.paymentType || 'subscription')
setShowCallbackModal(true)
}
}
@ -84,7 +86,7 @@ export default function RootLayout({
{/* <ScreenAdapter /> */}
<div id="app" className='h-full w-full'>
{children}
{showCallbackModal && <CallbackModal onClose={() => setShowCallbackModal(false)} />}
{showCallbackModal && <CallbackModal paymentType={paymentType} onClose={() => setShowCallbackModal(false)} />}
</div>
</Providers>
</CallbackModalContext.Provider>

View File

@ -28,8 +28,8 @@ export default function PricingPage() {
/**价格方案 */
function HomeModule5() {
const [billingType, setBillingType] = useState<"month" | "year">("month");
const [plans, setPlans] = useState<SubscriptionPlan[]>([]);
const [loadingPlan, setLoadingPlan] = useState<string | null>(null); // 跟踪哪个计划正在加载
// 从后端获取订阅计划数据
useEffect(() => {
@ -71,6 +71,7 @@ function HomeModule5() {
}, [plans, billingType]);
const handleSubscribe = async (planName: string) => {
setLoadingPlan(planName); // 设置加载状态
try {
const { createCheckoutSession, redirectToCheckout } = await import(
@ -94,10 +95,14 @@ 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",
paymentType: "subscription"
}, "*");
// 2. 直接跳转到Stripe托管页面就这么简单
window.location.href = result.data.checkout_url;
} catch (error) {
setLoadingPlan(null); // 出错时清除加载状态
throw new Error("create checkout session failed, please try again later");
}
};

View File

@ -21,7 +21,13 @@ enum PaymentStatus {
*
* Stripe Checkout支付完成后的状态展示和用户操作
*/
export default function CallbackModal({ onClose }: { onClose: () => void }) {
export default function CallbackModal({
paymentType = 'subscription',
onClose
}: {
paymentType?: 'subscription' | 'token';
onClose: () => void;
}) {
const router = useRouter()
const searchParams = useSearchParams()
const [paymentStatus, setPaymentStatus] = useState<PaymentStatus>(PaymentStatus.LOADING)
@ -119,6 +125,12 @@ export default function CallbackModal({ onClose }: { onClose: () => void }) {
)}
</>
)}
{paymentInfo?.tokenPurchase && (
<>
<p>Token Purchase Successful!</p>
<p>Your credits will be updated shortly.</p>
</>
)}
{paymentInfo?.paymentTime && (
<p>Payment Time: {new Date(paymentInfo.paymentTime).toLocaleString()}</p>
)}
@ -154,14 +166,52 @@ export default function CallbackModal({ onClose }: { onClose: () => void }) {
setPaymentStatus(PaymentStatus.FAILED)
return
}
const userId = JSON.parse(localStorage.getItem('currentUser') || '{}').id
try {
if (paymentType === 'subscription') {
// 订阅支付状态查询
const res = await getCheckoutSessionStatus(ev.data.sessionId, userId)
if (res.successful && res.data) {
if (res.data.payment_status === 'success') {
setPaymentStatus(PaymentStatus.SUCCESS)
setPaymentInfo({
orderId: res.data.biz_order_no,
sessionId: ev.data.sessionId,
paymentTime: res.data.pay_time,
subscription: res.data.subscription
})
} else if (res.data.payment_status === 'fail') {
setPaymentStatus(PaymentStatus.FAILED)
}
} else {
setPaymentStatus(PaymentStatus.FAILED)
}
} else if (paymentType === 'token') {
// Token购买状态查询
const { getTokenPurchaseStatus } = await import('@/lib/stripe')
const res = await getTokenPurchaseStatus(ev.data.sessionId)
if (res.successful && res.data) {
if (res.data.payment_status === 'success') {
setPaymentStatus(PaymentStatus.SUCCESS)
setPaymentInfo({
orderId: ev.data.sessionId,
sessionId: ev.data.sessionId,
tokenPurchase: { success: true }
})
// Token购买成功后延迟刷新页面以更新积分显示
setTimeout(() => window.location.reload(), 2000)
} else if (res.data.payment_status === 'fail') {
setPaymentStatus(PaymentStatus.FAILED)
}
} else {
setPaymentStatus(PaymentStatus.FAILED)
}
}
} catch (error) {
console.error('支付状态查询失败:', error)
setPaymentStatus(PaymentStatus.FAILED)
}
}
}

View File

@ -100,8 +100,13 @@ export function TopBar({ collapsed, isDesktop=true }: { collapsed: boolean, isDe
});
if (response.successful && response.data?.checkout_url) {
// 跳转到Stripe支付页面
window.location.href = response.data.checkout_url;
// 通知当前窗口等待支付标识为Token购买
window.postMessage({
type: "waiting-payment",
paymentType: "token"
}, "*");
// 在新标签页中打开Stripe支付页面
window.open(response.data.checkout_url, '_blank');
} else {
console.error("创建Token购买失败:", response.message);
}

View File

@ -77,6 +77,12 @@ export interface BuyTokensData {
export type BuyTokensResponse = ApiResponse<BuyTokensData>;
export interface TokenPurchaseStatusData {
payment_status: "success" | "fail" | "pending";
}
export type TokenPurchaseStatusResponse = ApiResponse<TokenPurchaseStatusData>;
/**
*
* API获取所有活跃的订阅计划
@ -219,6 +225,21 @@ export async function buyTokens(
}
}
/**
* Token购买状态
*
* Token购买的支付状态
*/
export async function getTokenPurchaseStatus(
sessionId: string
): Promise<TokenPurchaseStatusResponse> {
try {
return await get<TokenPurchaseStatusResponse>(`/api/payment/token-purchase-status/${sessionId}`);
} catch (error) {
throw error;
}
}
/**
* Customer Portal页面的工具函数
*/