2025-08-28 17:04:00 +08:00

238 lines
7.9 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 { useEffect, useState } from 'react';
import { useSearchParams } from 'next/navigation';
import { CheckCircle, Loader2, XCircle } from 'lucide-react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
interface PaymentStatus {
payment_status: 'pending' | 'success' | 'fail';
biz_order_no: string;
pay_time?: string;
subscription?: {
plan_name: string;
plan_display_name: string;
status: string;
current_period_end?: string;
};
}
export default function PaymentSuccessPage() {
const searchParams = useSearchParams();
const sessionId = searchParams.get('session_id');
const [status, setStatus] = useState<'loading' | 'success' | 'failed' | 'timeout'>('loading');
const [paymentData, setPaymentData] = useState<PaymentStatus | null>(null);
const [attempts, setAttempts] = useState(0);
useEffect(() => {
if (!sessionId) {
setStatus('failed');
return;
}
const pollPaymentStatus = async () => {
const maxAttempts = 30; // 最多轮询30次
const interval = 2000; // 每2秒轮询一次
for (let i = 0; i < maxAttempts; i++) {
setAttempts(i + 1);
try {
// 使用新的Checkout Session状态查询
const { getCheckoutSessionStatus } = await import('@/lib/stripe');
const User = JSON.parse(localStorage.getItem("currentUser") || "{}");
const result = await getCheckoutSessionStatus(sessionId, String(User.id));
if (result.successful && result.data) {
setPaymentData(result.data);
if (result.data.payment_status === 'success') {
setStatus('success');
return;
} else if (result.data.payment_status === 'fail') {
setStatus('failed');
return;
}
}
// 等待下次轮询
await new Promise(resolve => setTimeout(resolve, interval));
} catch (error) {
console.error('轮询Checkout Session状态失败:', error);
}
}
// 轮询超时
setStatus('timeout');
};
pollPaymentStatus();
}, [sessionId]);
const renderContent = () => {
switch (status) {
case 'loading':
return (
<Card className="max-w-md mx-auto">
<CardHeader className="text-center">
<div className="flex justify-center mb-4">
<Loader2 className="w-16 h-16 text-blue-500 animate-spin" />
</div>
<CardTitle>...</CardTitle>
<CardDescription>
<br />
: {attempts}/30
</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-gray-600 text-center">
</p>
</CardContent>
</Card>
);
case 'success':
return (
<Card className="max-w-md mx-auto">
<CardHeader className="text-center">
<div className="flex justify-center mb-4">
<CheckCircle className="w-16 h-16 text-green-500" />
</div>
<CardTitle className="text-green-600"></CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{paymentData?.subscription && (
<div className="bg-green-50 p-4 rounded-lg">
<h3 className="font-semibold text-green-800">
{paymentData.subscription.plan_display_name}
</h3>
<p className="text-sm text-green-600">
: {paymentData.subscription.status}
</p>
{paymentData.subscription.current_period_end && (
<p className="text-sm text-green-600">
: {new Date(paymentData.subscription.current_period_end).toLocaleDateString()}
</p>
)}
</div>
)}
<div className="text-sm text-gray-600">
<p>: {paymentData?.biz_order_no}</p>
{paymentData?.pay_time && (
<p>: {new Date(paymentData.pay_time).toLocaleString()}</p>
)}
</div>
<div className="flex gap-2">
<Button
onClick={() => window.location.href = '/dashboard'}
className="flex-1"
>
</Button>
<Button
onClick={() => window.location.href = '/'}
variant="outline"
className="flex-1"
>
</Button>
</div>
</CardContent>
</Card>
);
case 'failed':
return (
<Card className="max-w-md mx-auto">
<CardHeader className="text-center">
<div className="flex justify-center mb-4">
<XCircle className="w-16 h-16 text-red-500" />
</div>
<CardTitle className="text-red-600"></CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-sm text-gray-600 text-center">
</p>
<div className="flex gap-2">
<Button
onClick={() => window.location.href = '/pricing'}
className="flex-1"
>
</Button>
<Button
onClick={() => window.location.href = '/'}
variant="outline"
className="flex-1"
>
</Button>
</div>
</CardContent>
</Card>
);
case 'timeout':
return (
<Card className="max-w-md mx-auto">
<CardHeader className="text-center">
<div className="flex justify-center mb-4">
<Loader2 className="w-16 h-16 text-yellow-500" />
</div>
<CardTitle className="text-yellow-600"></CardTitle>
<CardDescription>
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-sm text-gray-600 text-center">
</p>
<div className="flex gap-2">
<Button
onClick={() => window.location.reload()}
className="flex-1"
>
</Button>
<Button
onClick={() => window.location.href = '/dashboard'}
variant="outline"
className="flex-1"
>
</Button>
</div>
</CardContent>
</Card>
);
default:
return null;
}
};
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center p-4">
<div className="w-full max-w-lg">
{renderContent()}
</div>
</div>
);
}