This commit is contained in:
eric
2026-03-10 01:35:37 -05:00
parent ff51622bb6
commit be7a298604
69 changed files with 11016 additions and 57 deletions

130
lib/sdk/zpay/index.ts Normal file
View File

@@ -0,0 +1,130 @@
/**
* ZPay 支付网关 SDK - 创建订单、查询状态,随时可调用
* 复制自 digital 项目 pay API
*/
import { getZPayConfig } from "../config";
const ZPAY_REQUIRED = [
"pid",
"type",
"out_trade_no",
"notify_url",
"return_url",
"name",
"money",
"sign",
"sign_type",
];
function escapeAttr(s: string): string {
return String(s)
.replace(/&/g, "&")
.replace(/"/g, """)
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}
export interface CreateOrderOptions {
user_id: string;
return_url: string;
total_fee?: number;
type?: string;
channel?: string;
}
export interface CreateOrderResult {
params: Record<string, string>;
payUrl: string;
}
/** 创建支付订单 */
export async function createPayOrder(options: CreateOrderOptions): Promise<CreateOrderResult> {
const config = getZPayConfig();
if (!config.apiUrl) throw new Error("未配置 PAYMENT_API_URL");
const { user_id, return_url, total_fee, type, channel = "alipay" } = options;
const fee = total_fee ?? config.defaultAmount;
const orderType = type ?? config.defaultType;
const payload = {
user_id,
total_fee: Number(fee) || config.defaultAmount,
type: orderType,
channel,
return_url,
};
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
const res = await fetch(`${config.apiUrl}/payh5`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
signal: controller.signal,
}).finally(() => clearTimeout(timeout));
const data = await res.json().catch(() => ({}));
if (!res.ok || data?.status !== "ok") {
throw new Error(data?.detail || data?.msg || data?.message || "支付创建失败");
}
let params = data.params || data.xorpay_params || {};
const payUrl = data.pay_url || data.xorpay_url || config.zpaySubmitUrl;
if (!params || typeof params !== "object") {
throw new Error("payjsapi 未返回支付参数");
}
params = JSON.parse(JSON.stringify(params));
if (data.provider === "xorpay") {
throw new Error("当前 payjsapi 使用 xorpay,请设置 PAYMENT_PROVIDER=zpay");
}
return { params, payUrl };
}
/** 查询订单支付状态 */
export async function getOrderStatus(orderId: string): Promise<{ paid: boolean }> {
const config = getZPayConfig();
if (!config.apiUrl) throw new Error("未配置 PAYMENT_API_URL");
const res = await fetch(
`${config.apiUrl}/zpay_order_status?out_trade_no=${encodeURIComponent(orderId)}`,
{ cache: "no-store" }
);
const data = await res.json().catch(() => ({}));
return { paid: !!data?.paid };
}
/** 构建 ZPay 表单跳转 HTML */
export function buildPayHtml(payUrl: string, params: Record<string, unknown>): string {
const flat: Record<string, string> = {};
for (const [k, v] of Object.entries(params)) {
if (v == null) continue;
const s = String(v).trim();
if (s === "") continue;
flat[k] = s;
}
const inputs = Object.entries(flat)
.map(([k, v]) => `<input type="hidden" name="${escapeAttr(k)}" value="${escapeAttr(v)}" />`)
.join("\n");
return `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>跳转支付...</title>
<style>body{font-family:system-ui;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0;background:#f0f4f8;}p{color:#64748b;}</style>
</head>
<body>
<p>正在跳转到支付页面...</p>
<form id="zpayForm" action="${escapeAttr(payUrl)}" method="POST" enctype="application/x-www-form-urlencoded">
${inputs}
</form>
<script>document.getElementById("zpayForm").submit();</script>
</body>
</html>`;
}
/** 获取 ZPay 必填参数列表(用于校验) */
export { ZPAY_REQUIRED };