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 { 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)) { 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"; }