feat: 创建订单必须通过 token 认证,移除 user_id 参数

- POST /api/orders 改为通过 token 解析用户身份,移除 user_id
- 前端不再从 URL 读取 user_id,完全依赖 token
- 前端提交前检查 pending 订单数量,超过 3 个禁止提交并提示
- 后端 createOrder 保留 MAX_PENDING_ORDERS=3 的服务端校验
- PaymentForm 增加 pendingBlocked 状态提示和按钮禁用

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
erio
2026-03-06 23:05:12 +08:00
parent e846cc1cce
commit 387bc96fc9
3 changed files with 96 additions and 65 deletions

View File

@@ -3,9 +3,10 @@ import { z } from 'zod';
import { createOrder, OrderError } from '@/lib/order/service';
import { getEnv } from '@/lib/config';
import { initPaymentProviders, paymentRegistry } from '@/lib/payment';
import { getCurrentUserByToken } from '@/lib/sub2api/client';
const createOrderSchema = z.object({
user_id: z.number().int().positive(),
token: z.string().min(1),
amount: z.number().positive(),
payment_type: z.string().min(1),
src_host: z.string().max(253).optional(),
@@ -24,7 +25,16 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: '参数错误', details: parsed.error.flatten().fieldErrors }, { status: 400 });
}
const { user_id, amount, payment_type, src_host, src_url, is_mobile } = parsed.data;
const { token, amount, payment_type, src_host, src_url, is_mobile } = parsed.data;
// 通过 token 解析用户身份
let userId: number;
try {
const user = await getCurrentUserByToken(token);
userId = user.id;
} catch {
return NextResponse.json({ error: '无效的 token请重新登录', code: 'INVALID_TOKEN' }, { status: 401 });
}
// Validate amount range
if (amount < env.MIN_RECHARGE_AMOUNT || amount > env.MAX_RECHARGE_AMOUNT) {
@@ -43,7 +53,7 @@ export async function POST(request: NextRequest) {
request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || request.headers.get('x-real-ip') || '127.0.0.1';
const result = await createOrder({
userId: user_id,
userId,
amount,
paymentType: payment_type,
clientIp,