Files
2026-03-10 01:35:37 -05:00

131 lines
3.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 };