2025-08-28 16:03:14 +08:00

198 lines
6.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import React, { useEffect, useState } from 'react'
import { Result, Button, Spin, Card } from 'antd'
import { CheckCircleOutlined, CloseCircleOutlined, LoadingOutlined } from '@ant-design/icons'
import { useRouter, useSearchParams } from 'next/navigation'
import { getCheckoutSessionStatus, PaymentStatusResponse } from '@/lib/stripe'
/**
* 支付状态枚举
*/
enum PaymentStatus {
LOADING = 'loading',
SUCCESS = 'success',
FAILED = 'failed'
}
/**
* 支付回调页面组件
* 处理Stripe Checkout支付完成后的状态展示和用户操作
*/
export default function PayCallbackPage() {
const router = useRouter()
const searchParams = useSearchParams()
const [paymentStatus, setPaymentStatus] = useState<PaymentStatus>(PaymentStatus.LOADING)
const [paymentInfo, setPaymentInfo] = useState<any>(null)
const callBackUrl = localStorage.getItem('callBackUrl') || '/'
/**
* 获取Stripe Checkout Session支付状态
*/
const fetchPaymentStatus = async () => {
try {
// 从URL参数获取session_id和user_id
const sessionId = searchParams.get('session_id')
const userId = searchParams.get('user_id')
if (!sessionId || !userId) {
throw new Error('缺少必要的参数: session_id 或 user_id')
}
// 调用真实的Stripe API获取支付状态
const response = await getCheckoutSessionStatus(sessionId, userId)
if (response.successful && response.data) {
const { payment_status, biz_order_no, pay_time, subscription } = response.data
if (payment_status === 'success') {
setPaymentStatus(PaymentStatus.SUCCESS)
setPaymentInfo({
orderId: biz_order_no,
sessionId,
paymentTime: pay_time,
subscription: subscription ? {
planName: subscription.plan_name,
planDisplayName: subscription.plan_display_name,
status: subscription.status,
currentPeriodEnd: subscription.current_period_end
} : null
})
} else if (payment_status === 'fail') {
setPaymentStatus(PaymentStatus.FAILED)
setPaymentInfo({
orderId: biz_order_no,
sessionId,
errorMessage: '支付处理失败,请重试'
})
} else {
// pending状态继续等待
setTimeout(fetchPaymentStatus, 2000)
}
} else {
throw new Error(response.message || '获取支付状态失败')
}
} catch (error) {
console.error('获取支付状态失败:', error)
setPaymentStatus(PaymentStatus.FAILED)
setPaymentInfo({
errorMessage: error instanceof Error ? error.message : '网络错误,无法获取支付状态'
})
}
}
/**
* 返回上一页的函数
*/
const handleGoBack = () => {
router.push(callBackUrl)
}
/**
* 重新支付的函数
*/
const handleRetryPayment = () => {
router.push('/pricing')
}
useEffect(() => {
fetchPaymentStatus()
}, [])
// 加载状态
if (paymentStatus === PaymentStatus.LOADING) {
return (
<div className="min-h-screen flex items-center justify-center bg-black">
<div className="w-96 text-center bg-black border border-white/20 rounded-lg p-8">
<Spin
indicator={<LoadingOutlined style={{ fontSize: 48, color: 'white' }} spin />}
size="large"
/>
<div className="mt-6 text-lg text-white">...</div>
<div className="mt-2 text-sm text-white/70"></div>
</div>
</div>
)
}
// 支付成功状态
if (paymentStatus === PaymentStatus.SUCCESS) {
return (
<div className="min-h-screen flex items-center justify-center bg-black">
<div className="w-[32rem] text-center bg-black border border-white/20 rounded-lg p-8">
<div className="mb-6">
<CheckCircleOutlined className="text-white text-6xl mb-4" />
<h1 className="text-white text-2xl font-bold mb-2"></h1>
<p className="text-white/70 text-base">{`订单号: ${paymentInfo?.orderId || 'N/A'}`}</p>
</div>
<div className="text-center text-white/70 mb-8 space-y-2">
{paymentInfo?.subscription && (
<>
<p>: {paymentInfo.subscription.planDisplayName}</p>
<p>: {paymentInfo.subscription.status}</p>
{paymentInfo.subscription.currentPeriodEnd && (
<p>: {new Date(paymentInfo.subscription.currentPeriodEnd).toLocaleString()}</p>
)}
</>
)}
{paymentInfo?.paymentTime && (
<p>: {new Date(paymentInfo.paymentTime).toLocaleString()}</p>
)}
<p></p>
</div>
<Button
type="primary"
size="large"
onClick={handleGoBack}
data-alt="back-to-previous-page-button"
className="w-full h-12 bg-white text-black hover:bg-white/90 border border-white/20"
>
</Button>
</div>
</div>
)
}
// 支付失败状态
return (
<div className="min-h-screen flex items-center justify-center bg-black">
<div className="w-[32rem] text-center bg-black border border-white/20 rounded-lg p-8">
<div className="mb-6">
<CloseCircleOutlined className="text-white text-6xl mb-4" />
<h1 className="text-white text-2xl font-bold mb-2"></h1>
<p className="text-white/70 text-base">{paymentInfo?.errorMessage || '支付处理过程中发生错误'}</p>
</div>
<div className="text-center text-white/70 mb-8 space-y-2">
<p>: {paymentInfo?.orderId || 'N/A'}</p>
<p></p>
</div>
<div className="space-y-3">
<Button
type="primary"
size="large"
onClick={handleRetryPayment}
data-alt="retry-payment-button"
className="w-full h-12 bg-white text-black hover:bg-white/90 border border-white/20"
>
</Button>
<Button
size="large"
onClick={handleGoBack}
data-alt="back-to-previous-page-button"
className="w-full h-12 bg-black text-white hover:bg-white hover:text-black border border-white/20"
>
</Button>
</div>
</div>
</div>
)
}