From 0c4066173f45147b33b1d2081d4d07b081d7cb18 Mon Sep 17 00:00:00 2001 From: eric Date: Tue, 10 Mar 2026 07:46:21 -0500 Subject: [PATCH] 's' --- README.md | 6 ++++-- app/api/pay/route.ts | 30 ++++++++++++++++++++++++++---- app/api/pay/status/route.ts | 3 ++- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f97490c..35539c7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # NomadVIP | 异度星球 -VIP 会员解锁页,集成 PocketBase 支付校验、ZPAY 支付。 +VIP 会员解锁页,集成 PocketBase 支付校验、XorPay/ZPAY 支付。 ## 服务配置(参考 desktop digital) @@ -10,7 +10,9 @@ ZPAY、PocketBase、MinIO 配置与 `C:\project\digital` 保持一致,复制 ` |------|------|--------| | `NEXT_PUBLIC_POCKETBASE_URL` | PocketBase 地址 | `https://pocketbase.hackrobot.cn` | | `NEXT_PUBLIC_API_URL` / `PAYMENT_API_URL` | payjsapi 地址 | `https://api.hackrobot.cn` | -| `PAYMENT_PROVIDER` | 支付提供商 | `zpay` | +| `PAYMENT_PROVIDER` | 支付提供商(payjsapi 环境变量) | `xorpay` / `zpay` | + +**XorPay**:直接调用 payh5,无 redirect 中转,表单提交至 xorpay 收银台。payjsapi 需设置 `PAYMENT_PROVIDER=xorpay`。 | `MINIO_ENDPOINT` | MinIO 端点 | `minioweb.hackrobot.cn` | | `MINIO_BUCKET` | 存储桶 | `hackrobot` | diff --git a/app/api/pay/route.ts b/app/api/pay/route.ts index 6935950..730850c 100644 --- a/app/api/pay/route.ts +++ b/app/api/pay/route.ts @@ -91,11 +91,17 @@ async function createPayOrder( if (!res.ok || data?.status !== "ok") { throw new Error(data?.detail || data?.msg || "支付创建失败"); } - const params = data.params || {}; + // xorpay: xorpay_params + xorpay_url | zpay: params + pay_url + const params = (data.xorpay_params ?? data.params) || {}; + const payUrl = data.xorpay_url || data.pay_url || ""; if (!params || typeof params !== "object") { throw new Error("payjsapi 未返回支付参数"); } - return { params: params as Record, payUrl: data.pay_url || "" }; + return { + params: params as Record, + payUrl, + provider: data.provider || "zpay", + }; } export async function POST(request: NextRequest) { @@ -153,7 +159,7 @@ export async function POST(request: NextRequest) { request.headers.get("x-real-ip") || ""; - const { params } = await createPayOrder( + const { params, payUrl, provider } = await createPayOrder( apiUrl, user_id, finalReturnUrl, @@ -173,6 +179,22 @@ export async function POST(request: NextRequest) { return NextResponse.json({ ok: false, error: "支付参数为空" }, { status: 500 }); } + // xorpay:直接 payh5 返回收银台参数,无需 redirect 中转 + const submitUrl = provider === "xorpay" ? payUrl : payConfig.zpaySubmitUrl; + if (provider === "xorpay") { + const orderId = String(params?.order_id || params?.out_trade_no || "").trim(); + const html = buildPayHtml(submitUrl, params as Record, { + directToCashier: device === "wechat" || device === "h5", + }); + const res = new NextResponse(html, { + status: 200, + headers: { "Content-Type": "text/html; charset=utf-8" }, + }); + if (orderId) setPayOrderCookie(res, orderId); + return res; + } + + // zpay:需要 redirect 获取 device 相关跳转 const redirectPayload = { user_id, total_fee, @@ -209,7 +231,7 @@ export async function POST(request: NextRequest) { if (redirectRes.status === 200 && resData?.use_form) { const orderId = String(params?.out_trade_no || "").trim(); - const html = buildPayHtml(payConfig.zpaySubmitUrl, params as Record, { + const html = buildPayHtml(submitUrl, params as Record, { directToCashier: device === "wechat", }); const res = new NextResponse(html, { diff --git a/app/api/pay/status/route.ts b/app/api/pay/status/route.ts index 3161fa1..78fd67c 100644 --- a/app/api/pay/status/route.ts +++ b/app/api/pay/status/route.ts @@ -11,8 +11,9 @@ export async function GET(request: NextRequest) { try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 25000); + // 使用 order_status 通用接口(xorpay 查 PocketBase,zpay 查 ZPAY) const res = await fetch( - `${apiUrl}/nomadvip/zpay_order_status?out_trade_no=${encodeURIComponent(orderId)}`, + `${apiUrl}/nomadvip/order_status?order_id=${encodeURIComponent(orderId)}`, { cache: "no-store", signal: controller.signal } ).finally(() => clearTimeout(timeout)); const data = await res.json().catch(() => ({}));