Files
2026-03-16 11:45:38 -05:00

178 lines
5.3 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.
import { getPaymentConfig, getJoinPaymentConfig } from "@/config/services.config";
export type PayChannel = "wxpay" | "alipay";
const ORDER_COOKIE = "join_pay_order";
function savePendingOrder(orderId: string): void {
if (typeof document === "undefined") return;
try {
const v = `${orderId}|${Date.now()}`;
document.cookie = `${ORDER_COOKIE}=${encodeURIComponent(v)}; path=/; max-age=900`;
localStorage.setItem(ORDER_COOKIE, v);
} catch {}
}
/** 同步表单 POST用于 PC 和微信内(微信内避免异步 fetch 导致手势失效) */
export function redirectToPay(params: {
user_id: string;
return_url: string;
total_fee?: number;
type?: string;
channel: PayChannel;
device: "pc" | "h5" | "wechat";
useJoinConfig?: boolean;
}): void {
const baseConfig = getPaymentConfig();
const joinConfig = params.useJoinConfig ? getJoinPaymentConfig() : null;
const total_fee = params.total_fee ?? joinConfig?.amount ?? baseConfig.defaultAmount;
const type = params.type ?? joinConfig?.type ?? baseConfig.defaultType;
const payForm = document.createElement("form");
payForm.method = "POST";
payForm.action = "/api/pay";
payForm.target = "_self";
payForm.style.display = "none";
const fields: [string, string][] = [
["user_id", params.user_id],
["return_url", params.return_url],
["total_fee", String(total_fee)],
["type", type],
["channel", params.channel],
["device", params.device],
["html", "1"],
];
fields.forEach(([k, v]) => {
const input = document.createElement("input");
input.type = "hidden";
input.name = k;
input.value = v;
payForm.appendChild(input);
});
document.body.appendChild(payForm);
payForm.submit();
}
/** 微信内:同步表单 POST 到 /api/pay避免异步 fetch 后 form.submit 被微信拦截(参考 nomadvip */
export function payWechatXorpaySync(params: {
user_id: string;
return_url: string;
total_fee?: number;
type?: string;
channel: PayChannel;
useJoinConfig?: boolean;
}): void {
const baseConfig = getPaymentConfig();
const joinConfig = params.useJoinConfig ? getJoinPaymentConfig() : null;
const total_fee = params.total_fee ?? joinConfig?.amount ?? baseConfig.defaultAmount;
const type = params.type ?? joinConfig?.type ?? baseConfig.defaultType;
const payForm = document.createElement("form");
payForm.method = "POST";
payForm.action = "/api/pay";
payForm.target = "_self";
payForm.style.display = "none";
const fields: [string, string][] = [
["user_id", params.user_id],
["return_url", params.return_url],
["total_fee", String(total_fee)],
["type", type],
["channel", params.channel],
["device", "wechat"],
["html", "1"],
];
fields.forEach(([k, v]) => {
const input = document.createElement("input");
input.type = "hidden";
input.name = k;
input.value = v;
payForm.appendChild(input);
});
document.body.appendChild(payForm);
payForm.submit();
}
/** 手机浏览器 H5异步 fetch 获取 redirect_url 后跳转,支持 ZPAY 微信 H5 唤醒(参考 nomadvip + payjsapi */
export async function redirectToPayH5(params: {
user_id: string;
return_url: string;
total_fee?: number;
type?: string;
channel: PayChannel;
useJoinConfig?: boolean;
}): Promise<void> {
const baseConfig = getPaymentConfig();
const joinConfig = params.useJoinConfig ? getJoinPaymentConfig() : null;
const total_fee = params.total_fee ?? joinConfig?.amount ?? baseConfig.defaultAmount;
const type = params.type ?? joinConfig?.type ?? baseConfig.defaultType;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 28000);
const res = await fetch("/api/pay", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
user_id: params.user_id,
return_url: params.return_url,
total_fee,
type,
channel: params.channel,
device: "h5",
html: "0",
}),
redirect: "manual",
signal: controller.signal,
}).finally(() => clearTimeout(timeoutId));
if (res.status >= 301 && res.status <= 308) {
const location = res.headers.get("location");
if (location) {
const orderId = res.headers.get("x-pay-order-id")?.trim();
if (orderId) savePendingOrder(orderId);
window.location.href = location;
return;
}
}
const data = await res.json().catch(() => ({}));
if (!data?.ok) {
alert(data?.error || "支付发起失败");
return;
}
if (data?.order_id) savePendingOrder(data.order_id);
if (data?.redirect_url) {
window.location.href = data.redirect_url;
return;
}
if (data?.params && data?.payUrl) {
const form = document.createElement("form");
form.method = "POST";
form.action = data.payUrl;
form.target = "_self";
form.style.display = "none";
for (const [k, v] of Object.entries(data.params as Record<string, string>)) {
if (v == null) continue;
const input = document.createElement("input");
input.type = "hidden";
input.name = k;
input.value = String(v);
form.appendChild(input);
}
document.body.appendChild(form);
form.submit();
return;
}
alert("支付发起失败");
}
export function getDeviceFromEnv(env: "pc" | "h5" | "wechat"): "pc" | "h5" | "wechat" {
if (env === "wechat") return "wechat";
return env === "pc" ? "pc" : "h5";
}