This commit is contained in:
eric
2026-03-10 07:35:28 -05:00
parent a46a31afb5
commit ed0a94c467

View File

@@ -15,6 +15,12 @@ function escapeAttr(s: string): string {
.replace(/>/g, ">"); .replace(/>/g, ">");
} }
function buildErrorHtml(errorMsg: string): string {
const esc = (s: string) =>
s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c] || c);
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;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;margin:0;background:#f0f4f8;}h2{color:#dc2626;margin-bottom:12px;}p{color:#64748b;margin-bottom:20px;text-align:center;padding:0 20px;}a{color:#2563eb;text-decoration:none;font-weight:500;}a:hover{text-decoration:underline;}</style></head><body><h2>支付发起失败</h2><p>${esc(errorMsg)}</p><p><a href="/">返回首页重试</a></p></body></html>`;
}
function buildPayHtml( function buildPayHtml(
payUrl: string, payUrl: string,
params: Record<string, unknown>, params: Record<string, unknown>,
@@ -31,8 +37,9 @@ function buildPayHtml(
.map(([k, v]) => `<input type="hidden" name="${escapeAttr(k)}" value="${escapeAttr(v)}" />`) .map(([k, v]) => `<input type="hidden" name="${escapeAttr(k)}" value="${escapeAttr(v)}" />`)
.join("\n"); .join("\n");
const direct = options?.directToCashier; const direct = options?.directToCashier;
// 微信内:显示加载提示,避免 visibility:hidden 导致部分 WebView 表单提交异常
return direct return direct
? `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>支付</title><style>body{margin:0;overflow:hidden;visibility:hidden}</style></head><body><form id="f" action="${escapeAttr(payUrl)}" method="POST" enctype="application/x-www-form-urlencoded">${inputs}</form><script>document.getElementById("f").submit()</script></body></html>` ? `<!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="f" action="${escapeAttr(payUrl)}" method="POST" enctype="application/x-www-form-urlencoded">${inputs}</form><script>document.getElementById("f").submit()</script></body></html>`
: `<!DOCTYPE html> : `<!DOCTYPE html>
<html> <html>
<head> <head>
@@ -120,6 +127,12 @@ export async function POST(request: NextRequest) {
const device = String(body?.device || "pc"); const device = String(body?.device || "pc");
if (!user_id) { if (!user_id) {
if (wantHtml) {
return new NextResponse(buildErrorHtml("缺少 user_id"), {
status: 400,
headers: { "Content-Type": "text/html; charset=utf-8" },
});
}
return NextResponse.json({ ok: false, error: "缺少 user_id" }, { status: 400 }); return NextResponse.json({ ok: false, error: "缺少 user_id" }, { status: 400 });
} }
@@ -152,6 +165,12 @@ export async function POST(request: NextRequest) {
); );
if (!params || Object.keys(params).length === 0) { if (!params || Object.keys(params).length === 0) {
if (wantHtml) {
return new NextResponse(buildErrorHtml("支付参数为空"), {
status: 500,
headers: { "Content-Type": "text/html; charset=utf-8" },
});
}
return NextResponse.json({ ok: false, error: "支付参数为空" }, { status: 500 }); return NextResponse.json({ ok: false, error: "支付参数为空" }, { status: 500 });
} }
@@ -262,13 +281,24 @@ export async function POST(request: NextRequest) {
} }
if (redirectRes.status === 400 || redirectRes.status === 500) { if (redirectRes.status === 400 || redirectRes.status === 500) {
return NextResponse.json( const errMsg = resData?.detail || resData?.msg || "支付跳转失败";
{ ok: false, error: resData?.detail || resData?.msg || "支付跳转失败" }, if (wantHtml) {
{ status: redirectRes.status } return new NextResponse(buildErrorHtml(errMsg), {
); status: redirectRes.status,
headers: { "Content-Type": "text/html; charset=utf-8" },
});
}
return NextResponse.json({ ok: false, error: errMsg }, { status: redirectRes.status });
} }
return NextResponse.json({ ok: false, error: "支付创建失败" }, { status: 500 }); const createFailMsg = "支付创建失败";
if (wantHtml) {
return new NextResponse(buildErrorHtml(createFailMsg), {
status: 500,
headers: { "Content-Type": "text/html; charset=utf-8" },
});
}
return NextResponse.json({ ok: false, error: createFailMsg }, { status: 500 });
} catch (e) { } catch (e) {
const err = e instanceof Error ? e : new Error(String(e)); const err = e instanceof Error ? e : new Error(String(e));
const msg = err.message || ""; const msg = err.message || "";
@@ -279,6 +309,15 @@ export async function POST(request: NextRequest) {
? "请求超时" ? "请求超时"
: msg; : msg;
console.error("Pay API error:", err); console.error("Pay API error:", err);
return NextResponse.json({ ok: false, error: `支付请求失败: ${hint}` }, { status: 500 }); const errorMsg = `支付请求失败: ${hint}`;
const ct = request.headers.get("content-type") || "";
const isForm = ct.includes("form") || ct.includes("x-www-form-urlencoded");
if (isForm) {
return new NextResponse(buildErrorHtml(errorMsg), {
status: 500,
headers: { "Content-Type": "text/html; charset=utf-8" },
});
}
return NextResponse.json({ ok: false, error: errorMsg }, { status: 500 });
} }
} }