forked from 77media/video-flow
198 lines
6.6 KiB
TypeScript
198 lines
6.6 KiB
TypeScript
'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>
|
||
)
|
||
}
|