99 lines
4.0 KiB
TypeScript
99 lines
4.0 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { getPaymentConfig } from "@/config/services.config";
|
|
import { SITE_URL } from "@/app/lib/env";
|
|
|
|
async function createPayOrder(
|
|
user_id: string,
|
|
return_url: string,
|
|
total_fee?: number,
|
|
type?: string,
|
|
channel = "alipay"
|
|
) {
|
|
const config = getPaymentConfig();
|
|
if (!config.apiUrl) throw new Error("未配置 PAYMENT_API_URL");
|
|
|
|
const payload = {
|
|
user_id,
|
|
site_id: "meetup",
|
|
total_fee: Number(total_fee) || config.defaultAmount,
|
|
type: type || config.defaultType,
|
|
channel,
|
|
return_url,
|
|
};
|
|
|
|
const controller = new AbortController();
|
|
const timeout = setTimeout(() => controller.abort(), 10000);
|
|
|
|
const res = await fetch(`${config.apiUrl}/cnomadcna/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 || "支付创建失败");
|
|
}
|
|
|
|
const 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 未返回支付参数");
|
|
}
|
|
return { params: JSON.parse(JSON.stringify(params)), payUrl };
|
|
}
|
|
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
let body: Record<string, string | number>;
|
|
const contentType = request.headers.get("content-type") || "";
|
|
if (contentType.includes("application/json")) {
|
|
body = await request.json();
|
|
} else {
|
|
const form = await request.formData();
|
|
body = Object.fromEntries(form.entries()) as Record<string, string>;
|
|
}
|
|
|
|
const user_id = String(body?.user_id || "").trim();
|
|
const return_url = String(body?.return_url || "").trim() || `${SITE_URL}/zh/join/paid`;
|
|
const total_fee = Number(body?.total_fee) || 100;
|
|
const type = String(body?.type || "meetup");
|
|
const channel = String(body?.channel || "alipay");
|
|
const device = String(body?.device || "pc");
|
|
|
|
if (!user_id) {
|
|
return NextResponse.json({ ok: false, error: "缺少 user_id" }, { status: 400 });
|
|
}
|
|
|
|
const { params, payUrl } = await createPayOrder(user_id, return_url, total_fee, type, channel);
|
|
|
|
const wantHtml = String(body?.html) === "1" || (request.headers.get("accept") || "").includes("text/html");
|
|
if (wantHtml) {
|
|
const escapeAttr = (s: string) =>
|
|
String(s)
|
|
.replace(/&/g, "&")
|
|
.replace(/"/g, """)
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">");
|
|
const inputs = Object.entries(params as Record<string, unknown>)
|
|
.filter(([, v]) => v != null && String(v).trim() !== "")
|
|
.map(([k, v]) => `<input type="hidden" name="${escapeAttr(k)}" value="${escapeAttr(String(v))}" />`)
|
|
.join("\n");
|
|
const orderId = String((params as Record<string, unknown>)?.out_trade_no || "").trim();
|
|
const html = `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>跳转支付</title><style>body{margin:0;background:#f0f4f8;opacity:0;}</style></head><body><form id="f" action="${escapeAttr(payUrl)}" method="POST">${inputs}</form><script>document.getElementById("f").submit();</script></body></html>`;
|
|
const res = new NextResponse(html, { status: 200, headers: { "Content-Type": "text/html; charset=utf-8" } });
|
|
if (orderId) {
|
|
res.cookies.set("join_pay_order", `${orderId}|${Date.now()}`, { path: "/", maxAge: 900 });
|
|
}
|
|
return res;
|
|
}
|
|
|
|
return NextResponse.json({ ok: true, pay_url: payUrl, params, provider: "zpay" });
|
|
} catch (e) {
|
|
const err = e instanceof Error ? e : new Error(String(e));
|
|
console.error("Pay API error:", err);
|
|
return NextResponse.json({ ok: false, error: err.message }, { status: 500 });
|
|
}
|
|
}
|