fix: harden alipay direct pay flow
This commit is contained in:
@@ -3,23 +3,7 @@ import { Prisma } from '@prisma/client';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { verifyAdminToken, unauthorizedResponse } from '@/lib/admin-auth';
|
||||
import { OrderStatus } from '@prisma/client';
|
||||
|
||||
/** 业务时区偏移(东八区,+8 小时) */
|
||||
const BIZ_TZ_OFFSET_MS = 8 * 60 * 60 * 1000;
|
||||
const BIZ_TZ_NAME = 'Asia/Shanghai';
|
||||
|
||||
/** 获取业务时区下的 YYYY-MM-DD */
|
||||
function toBizDateStr(d: Date): string {
|
||||
const local = new Date(d.getTime() + BIZ_TZ_OFFSET_MS);
|
||||
return local.toISOString().split('T')[0];
|
||||
}
|
||||
|
||||
/** 获取业务时区下"今天 00:00"对应的 UTC 时间 */
|
||||
function getBizDayStartUTC(d: Date): Date {
|
||||
const bizDateStr = toBizDateStr(d);
|
||||
// bizDateStr 00:00 在业务时区 = bizDateStr 00:00 - offset 在 UTC
|
||||
return new Date(`${bizDateStr}T00:00:00+08:00`);
|
||||
}
|
||||
import { BIZ_TZ_NAME, getBizDayStartUTC, toBizDateStr } from '@/lib/time/biz-day';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
if (!(await verifyAdminToken(request))) return unauthorizedResponse();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { queryMethodLimits } from '@/lib/order/limits';
|
||||
import { initPaymentProviders, paymentRegistry } from '@/lib/payment';
|
||||
import { getNextBizDayStartUTC } from '@/lib/time/biz-day';
|
||||
|
||||
/**
|
||||
* GET /api/limits
|
||||
@@ -13,19 +14,14 @@ import { initPaymentProviders, paymentRegistry } from '@/lib/payment';
|
||||
* wxpay: { dailyLimit: 10000, used: 10000, remaining: 0, available: false },
|
||||
* stripe: { dailyLimit: 0, used: 500, remaining: null, available: true }
|
||||
* },
|
||||
* resetAt: "2026-03-02T00:00:00.000Z" // UTC 次日零点(限额重置时间)
|
||||
* resetAt: "2026-03-02T16:00:00.000Z" // 业务时区(Asia/Shanghai)次日零点对应的 UTC 时间
|
||||
* }
|
||||
*/
|
||||
export async function GET() {
|
||||
initPaymentProviders();
|
||||
const types = paymentRegistry.getSupportedTypes();
|
||||
|
||||
const todayStart = new Date();
|
||||
todayStart.setUTCHours(0, 0, 0, 0);
|
||||
const resetAt = new Date(todayStart);
|
||||
resetAt.setUTCDate(resetAt.getUTCDate() + 1);
|
||||
|
||||
const methods = await queryMethodLimits(types);
|
||||
const resetAt = getNextBizDayStartUTC();
|
||||
|
||||
return NextResponse.json({ methods, resetAt });
|
||||
}
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { verifyAdminToken } from '@/lib/admin-auth';
|
||||
import { deriveOrderState } from '@/lib/order/status';
|
||||
import { ORDER_STATUS_ACCESS_QUERY_KEY, verifyOrderStatusAccessToken } from '@/lib/order/status-access';
|
||||
|
||||
/**
|
||||
* 订单状态轮询接口 — 仅返回 status / expiresAt 两个字段。
|
||||
* 订单状态轮询接口。
|
||||
*
|
||||
* 安全考虑:
|
||||
* - 订单 ID 使用 CUID(25 位随机字符),具有足够的不可预测性,
|
||||
* 暴力猜测的成本远高于信息价值。
|
||||
* - 仅暴露 status 和 expiresAt,不涉及用户隐私或金额信息。
|
||||
* - 前端 PaymentQRCode 组件每 2 秒轮询此接口以更新支付状态,
|
||||
* 添加认证会增加不必要的复杂度且影响轮询性能。
|
||||
* 返回最小必要信息供前端判断:
|
||||
* - 原始订单状态(status / expiresAt)
|
||||
* - 支付是否成功(paymentSuccess)
|
||||
* - 充值是否成功 / 当前充值阶段(rechargeSuccess / rechargeStatus)
|
||||
*/
|
||||
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
const accessToken = request.nextUrl.searchParams.get(ORDER_STATUS_ACCESS_QUERY_KEY);
|
||||
const isAuthorized = verifyOrderStatusAccessToken(id, accessToken) || (await verifyAdminToken(request));
|
||||
|
||||
if (!isAuthorized) {
|
||||
return NextResponse.json({ error: '未授权访问该订单状态' }, { status: 401 });
|
||||
}
|
||||
|
||||
const order = await prisma.order.findUnique({
|
||||
where: { id },
|
||||
@@ -20,6 +27,8 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
||||
id: true,
|
||||
status: true,
|
||||
expiresAt: true,
|
||||
paidAt: true,
|
||||
completedAt: true,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -27,9 +36,14 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
||||
return NextResponse.json({ error: '订单不存在' }, { status: 404 });
|
||||
}
|
||||
|
||||
const derived = deriveOrderState(order);
|
||||
|
||||
return NextResponse.json({
|
||||
id: order.id,
|
||||
status: order.status,
|
||||
expiresAt: order.expiresAt,
|
||||
paymentSuccess: derived.paymentSuccess,
|
||||
rechargeSuccess: derived.rechargeSuccess,
|
||||
rechargeStatus: derived.rechargeStatus,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user