This commit is contained in:
eric
2026-03-10 07:46:21 -05:00
parent dbb421ed5a
commit 0c4066173f
3 changed files with 32 additions and 7 deletions

View File

@@ -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` |

View File

@@ -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<string, string>, payUrl: data.pay_url || "" };
return {
params: params as Record<string, string>,
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<string, unknown>, {
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<string, unknown>, {
const html = buildPayHtml(submitUrl, params as Record<string, unknown>, {
directToCashier: device === "wechat",
});
const res = new NextResponse(html, {

View File

@@ -11,8 +11,9 @@ export async function GET(request: NextRequest) {
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 25000);
// 使用 order_status 通用接口xorpay 查 PocketBasezpay 查 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(() => ({}));