forked from 77media/video-flow
238 lines
8.0 KiB
TypeScript
238 lines
8.0 KiB
TypeScript
'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>Processing...</CardTitle>
|
|
<CardDescription>
|
|
Confirming your payment, please wait
|
|
<br />
|
|
Attempts: {attempts}/30
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p className="text-sm text-gray-600 text-center">
|
|
Please do not close this page, we are processing your subscription
|
|
</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">Payment successful!</CardTitle>
|
|
<CardDescription>
|
|
Your subscription has been activated
|
|
</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} Plan
|
|
</h3>
|
|
<p className="text-sm text-green-600">
|
|
Status: {paymentData.subscription.status}
|
|
</p>
|
|
{paymentData.subscription.current_period_end && (
|
|
<p className="text-sm text-green-600">
|
|
Valid until: {new Date(paymentData.subscription.current_period_end).toLocaleDateString()}
|
|
</p>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
<div className="text-sm text-gray-600">
|
|
<p>Order number: {paymentData?.biz_order_no}</p>
|
|
{paymentData?.pay_time && (
|
|
<p>Payment time: {new Date(paymentData.pay_time).toLocaleString()}</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex gap-2">
|
|
<Button
|
|
onClick={() => window.location.href = '/dashboard'}
|
|
className="flex-1"
|
|
>
|
|
Go to dashboard
|
|
</Button>
|
|
<Button
|
|
onClick={() => window.location.href = '/'}
|
|
variant="outline"
|
|
className="flex-1"
|
|
>
|
|
Go to home
|
|
</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">Payment failed</CardTitle>
|
|
<CardDescription>
|
|
Sorry, your payment was not successful
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<p className="text-sm text-gray-600 text-center">
|
|
Please check your payment information or try again later
|
|
</p>
|
|
|
|
<div className="flex gap-2">
|
|
<Button
|
|
onClick={() => window.location.href = '/pricing'}
|
|
className="flex-1"
|
|
>
|
|
Re-select plan
|
|
</Button>
|
|
<Button
|
|
onClick={() => window.location.href = '/'}
|
|
variant="outline"
|
|
className="flex-1"
|
|
>
|
|
Go to home
|
|
</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">Processing</CardTitle>
|
|
<CardDescription>
|
|
Payment is being processed, please check later
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<p className="text-sm text-gray-600 text-center">
|
|
Your payment may still be processing, please check your subscription status later
|
|
</p>
|
|
|
|
<div className="flex gap-2">
|
|
<Button
|
|
onClick={() => window.location.reload()}
|
|
className="flex-1"
|
|
>
|
|
Refresh page
|
|
</Button>
|
|
<Button
|
|
onClick={() => window.location.href = '/dashboard'}
|
|
variant="outline"
|
|
className="flex-1"
|
|
>
|
|
Check subscription status
|
|
</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>
|
|
);
|
|
}
|