fix: 修复微信支付 Native/H5 判断逻辑,改为前端设备检测驱动

- 修复 clientIp 始终存在导致永远走 H5 的 bug,改用 isMobile 判断
- 前端通过 detectDeviceIsMobile() 传 is_mobile 给后端
- ENABLED_PAYMENT_TYPES 默认改为空,必须显式配置才启用
- 补充 .env.example 配置说明
This commit is contained in:
erio
2026-03-06 14:04:51 +08:00
parent 937f54dec2
commit b0f1daf469
7 changed files with 24 additions and 7 deletions

View File

@@ -7,6 +7,7 @@ const createOrderSchema = z.object({
user_id: z.number().int().positive(),
amount: z.number().positive(),
payment_type: z.enum(['alipay', 'wxpay', 'stripe']),
is_mobile: z.boolean().optional(),
src_host: z.string().max(253).optional(),
src_url: z.string().max(2048).optional(),
});
@@ -21,7 +22,7 @@ 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 } = parsed.data;
const { user_id, amount, payment_type, is_mobile, src_host, src_url } = parsed.data;
// Validate amount range
if (amount < env.MIN_RECHARGE_AMOUNT || amount > env.MAX_RECHARGE_AMOUNT) {
@@ -44,6 +45,7 @@ export async function POST(request: NextRequest) {
amount,
paymentType: payment_type,
clientIp,
isMobile: is_mobile,
srcHost: src_host,
srcUrl: src_url,
});

View File

@@ -249,6 +249,7 @@ function PayContent() {
user_id: effectiveUserId,
amount,
payment_type: paymentType,
is_mobile: isMobile,
src_host: srcHost,
src_url: srcUrl,
}),

View File

@@ -55,12 +55,16 @@ const envSchema = z.object({
STRIPE_PUBLISHABLE_KEY: optionalTrimmedString,
STRIPE_WEBHOOK_SECRET: optionalTrimmedString,
// ── 启用的支付渠道(在已配置服务商支持的渠道中选择 ──
// 易支付支持: alipay, wxpayStripe 支持: stripe
// ── 启用的支付渠道(必须显式配置,未列出的渠道不会展示给用户 ──
ENABLED_PAYMENT_TYPES: z
.string()
.default('alipay,wxpay')
.transform((v) => v.split(',').map((s) => s.trim())),
.default('')
.transform((v) =>
v
.split(',')
.map((s) => s.trim())
.filter(Boolean),
),
ORDER_TIMEOUT_MINUTES: z.string().default('5').transform(Number).pipe(z.number().int().positive()),
MIN_RECHARGE_AMOUNT: z.string().default('1').transform(Number).pipe(z.number().positive()),

View File

@@ -16,6 +16,7 @@ export interface CreateOrderInput {
amount: number;
paymentType: PaymentType;
clientIp: string;
isMobile?: boolean;
srcHost?: string;
srcUrl?: string;
}
@@ -144,6 +145,7 @@ export async function createOrder(input: CreateOrderInput): Promise<CreateOrderR
notifyUrl,
returnUrl,
clientIp: input.clientIp,
isMobile: input.isMobile,
});
await prisma.order.update({

View File

@@ -10,6 +10,7 @@ export interface CreatePaymentRequest {
notifyUrl?: string;
returnUrl?: string;
clientIp?: string;
isMobile?: boolean;
}
/** Response from creating a payment */

View File

@@ -35,7 +35,10 @@ export class WxpayProvider implements PaymentProvider {
throw new Error('WXPAY_NOTIFY_URL is required');
}
if (request.clientIp) {
if (request.isMobile) {
if (!request.clientIp) {
throw new Error('clientIp is required for H5 payment');
}
const h5Url = await createH5Order({
out_trade_no: request.orderId,
description: request.subject,