feat: 更新支付modal和订阅对话框的逻辑和接口

This commit is contained in:
zyh 2024-10-22 10:30:17 +00:00
parent 68cccbb822
commit cd3a79e954
5 changed files with 61 additions and 20 deletions

View File

@ -24,28 +24,42 @@ interface PaymentResponse {
return_url: string;
}
interface PaymentStatusResponse {
status: string;
error?: string;
}
export function PaymentModal({ isOpen, onClose, paymentData, onPaymentSuccess }: PaymentModalProps) {
const [timeLeft, setTimeLeft] = useState(parseInt(paymentData.expires_in));
const checkPaymentStatus = useCallback(async () => {
try {
const response = await fetch(`/api/check-payment-status?orderNo=${paymentData.no}`);
const data = await response.json();
if (!response.ok) {
if (response.status === 404) {
console.error('Order not found');
return;
}
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json() as PaymentStatusResponse;
if (data.status === 'completed') {
clearInterval(timer);
onPaymentSuccess();
onClose();
toast.success('支付成功!');
}
} catch (error) {
console.error('Error checking payment status:', error);
toast.error('检查支付状态时出错,请稍后再试');
}
}, [paymentData.no, onPaymentSuccess, onClose]);
useEffect(() => {
if (!isOpen) return;
const timer = setInterval(() => {
let timer: NodeJS.Timeout;
const checkAndUpdateStatus = () => {
setTimeLeft((prevTime) => {
if (prevTime <= 1) {
clearInterval(timer);
@ -57,7 +71,9 @@ export function PaymentModal({ isOpen, onClose, paymentData, onPaymentSuccess }:
});
checkPaymentStatus();
}, 3000); // 每3秒检查一次支付状态
};
timer = setInterval(checkAndUpdateStatus, 3000); // 每3秒检查一次支付状态
return () => clearInterval(timer);
}, [isOpen, onClose, checkPaymentStatus]);

View File

@ -15,10 +15,19 @@ interface SubscriptionDialogProps {
}
interface UserSubscription {
plan: SubscriptionPlan;
tokensLeft: number;
nextReloadDate: string;
}
tokenBalance: number;
subscription: {
plan: {
_id: string;
name: string;
tokens: number;
price: number;
description: string;
save_percentage?: number;
};
expirationDate: string;
} | null;
}
interface PaymentResponse {
status: string;
@ -164,14 +173,14 @@ export function SubscriptionDialog({ isOpen, onClose }: SubscriptionDialogProps)
</p>
</div>
{isAuthenticated && userSubscription && (
{isAuthenticated && userSubscription && userSubscription.subscription && (
<div className="bg-bolt-elements-background-depth-2 p-4 rounded-lg">
<div className="flex justify-between items-center">
<div>
<span className="text-bolt-elements-textPrimary font-bold">{toString(userSubscription.tokensLeft)}</span>
<span className="text-bolt-elements-textPrimary font-bold">{toString(userSubscription.tokenBalance)}</span>
<span className="text-bolt-elements-textSecondary"> </span>
<span className="text-bolt-elements-textSecondary">
{userSubscription.plan.tokens.toLocaleString()}{new Date(userSubscription.nextReloadDate).toLocaleDateString()}
{userSubscription.subscription.plan.tokens.toLocaleString()}{new Date(userSubscription.subscription.expirationDate).toLocaleDateString()}
</span>
</div>
<div className="text-right">
@ -220,7 +229,7 @@ export function SubscriptionDialog({ isOpen, onClose }: SubscriptionDialogProps)
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{subscriptionPlans.map((plan) => (
<div key={plan._id} className={`bg-bolt-elements-background-depth-2 p-4 rounded-lg ${isAuthenticated && plan._id === userSubscription?.plan._id ? 'border-2 border-bolt-elements-item-contentAccent' : ''}`}>
<div key={plan._id} className={`bg-bolt-elements-background-depth-2 p-4 rounded-lg ${isAuthenticated && userSubscription && userSubscription.subscription && plan._id === userSubscription.subscription.plan._id ? 'border-2 border-bolt-elements-item-contentAccent' : ''}`}>
<h3 className="text-bolt-elements-textPrimary font-bold text-lg">{plan.name}</h3>
<div className="text-bolt-elements-textSecondary mb-2">
{(plan.tokens / 1000000).toFixed(0)}M
@ -235,12 +244,12 @@ export function SubscriptionDialog({ isOpen, onClose }: SubscriptionDialogProps)
<button
onClick={() => handlePurchase(plan._id)}
className={`w-full py-2 rounded-md ${
isAuthenticated && plan._id === userSubscription?.plan._id
isAuthenticated && userSubscription && userSubscription.subscription && plan._id === userSubscription.subscription.plan._id
? 'bg-bolt-elements-button-secondary-background text-bolt-elements-button-secondary-text'
: 'bg-bolt-elements-button-primary-background text-bolt-elements-button-primary-text'
}`}
>
{isAuthenticated && plan._id === userSubscription?.plan._id ? '管理当前计划' : `升级到${plan.name}`}
{isAuthenticated && userSubscription && userSubscription.subscription && plan._id === userSubscription.subscription.plan._id ? '管理当前计划' : `升级到${plan.name}`}
</button>
</div>
))}

View File

@ -1,26 +1,35 @@
import { json } from '@remix-run/cloudflare';
import { db } from '~/utils/db.server';
import { requireAuth } from '~/middleware/auth.server';
export async function loader({ request }: { request: Request }) {
let userId;
try {
userId = await requireAuth(request);
} catch (error) {
return error as Response;
}
const url = new URL(request.url);
const orderNo = url.searchParams.get('orderNo');
if (!orderNo) {
return json({ error: 'Order number is required' }, { status: 400 });
return json({ error: '订单号不能为空' }, { status: 400 });
}
try {
const transaction = await db('user_transactions')
.where('transaction_id', orderNo)
.where('user_id', userId)
.first();
if (!transaction) {
return json({ error: 'Transaction not found' }, { status: 404 });
return json({ error: '订单不存在' }, { status: 404 });
}
return json({ status: transaction.status });
} catch (error) {
console.error('Error checking payment status:', error);
return json({ error: 'Failed to check payment status' }, { status: 500 });
return json({ error: '检查支付状态失败' }, { status: 500 });
}
}

View File

@ -35,7 +35,7 @@ export async function action({ request }: { request: Request }) {
orderNo,
`${plan.name} 订阅 (${billingCycle === 'yearly' ? '年付' : '月付'})`,
'alipay', // 或其他支付方式
price * 100, // 转换为分
price, // 不用转换为分
userId.toString()
);

View File

@ -30,9 +30,16 @@ export async function loader({ request }: { request: Request }) {
return json({
tokenBalance: user.token_balance,
subscription: subscription
subscription: subscription && subscriptionPlan
? {
planName: subscriptionPlan.name,
plan: {
_id: subscriptionPlan._id,
name: subscriptionPlan.name,
tokens: subscriptionPlan.tokens,
price: subscriptionPlan.price,
description: subscriptionPlan.description,
save_percentage: subscriptionPlan.save_percentage,
},
expirationDate: subscription.expiration_date,
}
: null,