Files
sub2apipay/src/app/api/orders/route.ts
erio ac0772b0f4 fix: API 路由安全加固与架构优化 — 认证、错误处理、Registry 统一
- /api/user 添加 token 认证,防止用户枚举
- Admin token 支持 Authorization header
- /api/orders/my 区分认证失败和服务端错误
- Admin orders userId/date 参数校验
- Decimal 字段统一 Number() 转换
- 抽取 handleApiError/extractHeaders 工具函数
- Webhook 路由改用 Registry 获取 Provider
- PaymentRegistry lazy init 自动初始化

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 04:15:54 +08:00

72 lines
2.4 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.
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { createOrder } from '@/lib/order/service';
import { getEnv } from '@/lib/config';
import { paymentRegistry } from '@/lib/payment';
import { getCurrentUserByToken } from '@/lib/sub2api/client';
import { handleApiError } from '@/lib/utils/api';
const createOrderSchema = z.object({
token: z.string().min(1),
amount: z.number().positive(),
payment_type: z.string().min(1),
src_host: z.string().max(253).optional(),
src_url: z.string().max(2048).optional(),
is_mobile: z.boolean().optional(),
});
export async function POST(request: NextRequest) {
try {
const env = getEnv();
const body = await request.json();
const parsed = createOrderSchema.safeParse(body);
if (!parsed.success) {
return NextResponse.json({ error: '参数错误', details: parsed.error.flatten().fieldErrors }, { status: 400 });
}
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) {
return NextResponse.json(
{ error: `充值金额需在 ${env.MIN_RECHARGE_AMOUNT} - ${env.MAX_RECHARGE_AMOUNT} 之间` },
{ status: 400 },
);
}
// Validate payment type is enabled
if (!paymentRegistry.getSupportedTypes().includes(payment_type)) {
return NextResponse.json({ error: `不支持的支付方式: ${payment_type}` }, { status: 400 });
}
const clientIp =
request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() || request.headers.get('x-real-ip') || '127.0.0.1';
const result = await createOrder({
userId,
amount,
paymentType: payment_type,
clientIp,
isMobile: is_mobile,
srcHost: src_host,
srcUrl: src_url,
});
// 不向客户端暴露 userName / userBalance 等隐私字段
const { userName: _u, userBalance: _b, ...safeResult } = result;
return NextResponse.json(safeResult);
} catch (error) {
return handleApiError(error, '创建订单失败,请稍后重试');
}
}