's''
This commit is contained in:
@@ -2,6 +2,18 @@ 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;
|
||||
@@ -44,6 +56,121 @@ export function redirectToPay(params: {
|
||||
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";
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
export { redirectToPay, getDeviceFromEnv } from "./client";
|
||||
export {
|
||||
redirectToPay,
|
||||
redirectToPayH5,
|
||||
payWechatXorpaySync,
|
||||
getDeviceFromEnv,
|
||||
} from "./client";
|
||||
export { getPayEnv } from "../payEnv";
|
||||
export { usePayStatusPoll } from "./usePayStatusPoll";
|
||||
export type { PayChannel } from "./client";
|
||||
|
||||
Reference in New Issue
Block a user