From b0f1daf469c4677960ba7d0c9474b7ebd66d97d1 Mon Sep 17 00:00:00 2001 From: erio Date: Fri, 6 Mar 2026 14:04:51 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E6=94=AF=E4=BB=98=20Native/H5=20=E5=88=A4=E6=96=AD=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E6=94=B9=E4=B8=BA=E5=89=8D=E7=AB=AF=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E6=A3=80=E6=B5=8B=E9=A9=B1=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 clientIp 始终存在导致永远走 H5 的 bug,改用 isMobile 判断 - 前端通过 detectDeviceIsMobile() 传 is_mobile 给后端 - ENABLED_PAYMENT_TYPES 默认改为空,必须显式配置才启用 - 补充 .env.example 配置说明 --- .env.example | 6 +++++- src/app/api/orders/route.ts | 4 +++- src/app/pay/page.tsx | 1 + src/lib/config.ts | 12 ++++++++---- src/lib/order/service.ts | 2 ++ src/lib/payment/types.ts | 1 + src/lib/wxpay/provider.ts | 5 ++++- 7 files changed, 24 insertions(+), 7 deletions(-) diff --git a/.env.example b/.env.example index df6aa18..cb19ec7 100644 --- a/.env.example +++ b/.env.example @@ -29,6 +29,7 @@ EASY_PAY_RETURN_URL="https://pay.example.com/pay/result" #STRIPE_WEBHOOK_SECRET="whsec_..." # ── 支付宝直连(PAYMENT_PROVIDERS 含 alipay 时必填) ──────────────────── +# 不在 PAYMENT_PROVIDERS 中配置 alipay 则不启用支付宝直连 # ALIPAY_APP_ID= # ALIPAY_PRIVATE_KEY= # PKCS8 格式私钥(不含 -----BEGIN/END----- 头尾) # ALIPAY_PUBLIC_KEY= # 支付宝公钥(非应用公钥,从开放平台获取) @@ -36,6 +37,8 @@ EASY_PAY_RETURN_URL="https://pay.example.com/pay/result" # ALIPAY_RETURN_URL=https://pay.example.com/pay/result # ── 微信支付直连(PAYMENT_PROVIDERS 含 wxpay 时必填) ──────────────────── +# 前端自动检测设备类型:PC 端使用 Native 扫码支付,移动端使用 H5 跳转微信 APP 支付 +# 不在 PAYMENT_PROVIDERS 中配置 wxpay 则不启用微信支付 # WXPAY_APP_ID= # 公众号或移动应用 AppID # WXPAY_MCH_ID= # 商户号(10位数字) # WXPAY_PRIVATE_KEY= # 商户 API 私钥 PEM(含 -----BEGIN/END----- 头尾) @@ -45,8 +48,9 @@ EASY_PAY_RETURN_URL="https://pay.example.com/pay/result" # WXPAY_PUBLIC_KEY_ID= # 微信支付公钥 ID # WXPAY_NOTIFY_URL=https://pay.example.com/api/wxpay/notify -# ── 启用的支付渠道(在已配置服务商支持的渠道中选择) ───────────────────────── +# ── 启用的支付渠道(必须显式配置,未列出的渠道不会展示给用户) ───────────── # 可选值: alipay, wxpay, stripe +# 默认值为空 = 不启用任何渠道,必须手动配置 ENABLED_PAYMENT_TYPES="alipay,wxpay" # ── 订单配置 ────────────────────────────────────────────────────────────────── diff --git a/src/app/api/orders/route.ts b/src/app/api/orders/route.ts index b751cec..c98acf9 100644 --- a/src/app/api/orders/route.ts +++ b/src/app/api/orders/route.ts @@ -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, }); diff --git a/src/app/pay/page.tsx b/src/app/pay/page.tsx index 1c8f73b..1b6738a 100644 --- a/src/app/pay/page.tsx +++ b/src/app/pay/page.tsx @@ -249,6 +249,7 @@ function PayContent() { user_id: effectiveUserId, amount, payment_type: paymentType, + is_mobile: isMobile, src_host: srcHost, src_url: srcUrl, }), diff --git a/src/lib/config.ts b/src/lib/config.ts index 8731240..9f6c4e2 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -55,12 +55,16 @@ const envSchema = z.object({ STRIPE_PUBLISHABLE_KEY: optionalTrimmedString, STRIPE_WEBHOOK_SECRET: optionalTrimmedString, - // ── 启用的支付渠道(在已配置服务商支持的渠道中选择) ── - // 易支付支持: alipay, wxpay;Stripe 支持: 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()), diff --git a/src/lib/order/service.ts b/src/lib/order/service.ts index 2c8fd8c..7957ba4 100644 --- a/src/lib/order/service.ts +++ b/src/lib/order/service.ts @@ -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