mirror of
https://github.com/stackblitz/bolt.new
synced 2025-06-26 18:17:50 +00:00
feat: 更新支付modal和订阅对话框的逻辑和接口
This commit is contained in:
parent
68cccbb822
commit
cd3a79e954
@ -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]);
|
||||
|
||||
@ -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>
|
||||
))}
|
||||
|
||||
@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ export async function action({ request }: { request: Request }) {
|
||||
orderNo,
|
||||
`${plan.name} 订阅 (${billingCycle === 'yearly' ? '年付' : '月付'})`,
|
||||
'alipay', // 或其他支付方式
|
||||
price * 100, // 转换为分
|
||||
price, // 不用转换为分
|
||||
userId.toString()
|
||||
);
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user