This commit is contained in:
eric
2026-03-16 11:45:38 -05:00
parent 3e73917ad1
commit c5e057f0ed
9 changed files with 482 additions and 72 deletions

View File

@@ -7,6 +7,9 @@ import {
} from "@/config/services.config";
import { SITE_URL } from "@/app/lib/env";
/** ZPAY 创建订单较慢(约 610s需延长超时避免手机浏览器发起支付超时 */
export const maxDuration = 30;
const ORDER_COOKIE = "join_pay_order";
const SALON_PAY_PREFIX = "/salon";
@@ -165,23 +168,11 @@ h2{color:#1e293b;margin:0 0 8px;font-size:1.5rem;}
.then(function(r){return r.json();})
.then(function(d){
if(d&&d.paid){doConfirm(0);return;}
setTimeout(check,300);
setTimeout(check,800);
})
.catch(function(){setTimeout(check,1000);});
.catch(function(){setTimeout(check,1500);});
}
var manualBtn=document.createElement("button");
manualBtn.textContent="支付完成?点此手动检查";
manualBtn.style.cssText="margin-top:12px;padding:8px 16px;font-size:13px;color:#64748b;background:#f1f5f9;border:none;border-radius:8px;cursor:pointer";
manualBtn.onclick=function(){
manualBtn.disabled=true;
manualBtn.textContent="检查中…";
fetch("/api/pay/status?order_id="+encodeURIComponent(orderId),{cache:"no-store",credentials:"same-origin"})
.then(function(r){return r.json();})
.then(function(d){if(d&&d.paid){doConfirm(0);}else{manualBtn.disabled=false;manualBtn.textContent="支付完成?点此手动检查";}})
.catch(function(){manualBtn.disabled=false;manualBtn.textContent="检查失败,重试";});
};
if(tip&&tip.parentNode){tip.parentNode.appendChild(manualBtn);}
setTimeout(check,300);
setTimeout(check,500);
})();
</script>
</body></html>`;
@@ -243,7 +234,7 @@ async function requestRedirect(
payload: Record<string, unknown>
) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 12000);
const timeout = setTimeout(() => controller.abort(), 25000);
try {
return await fetch(`${apiUrl}${path}`, {
method: "POST",
@@ -309,30 +300,33 @@ export async function POST(request: NextRequest) {
device
);
const created = await createPayOrder(
apiUrl,
userId,
returnUrl,
totalFee,
type,
channel,
forcedProvider,
clientIp
);
// 微信内xorpay 收银台,需先 createPayOrder
// PC/手机浏览器zpay参考 nomadvip 直接走 redirect跳过 payh5 避免重复建单
const isWechat = device === "wechat";
let created: Awaited<ReturnType<typeof createPayOrder>> | null = null;
if (!wantHtml) {
const response = NextResponse.json({
ok: true,
params: created.params,
payUrl: created.payUrl,
provider: created.provider,
order_id: created.orderId,
});
if (created.orderId) setPayOrderCookie(response, created.orderId);
return response;
}
if (created.provider === "xorpay") {
if (isWechat) {
created = await createPayOrder(
apiUrl,
userId,
returnUrl,
totalFee,
type,
channel,
"xorpay",
clientIp
);
if (!wantHtml) {
const response = NextResponse.json({
ok: true,
params: created.params,
payUrl: created.payUrl,
provider: created.provider,
order_id: created.orderId,
});
if (created.orderId) setPayOrderCookie(response, created.orderId);
return response;
}
const html = buildPayHtml(created.payUrl, created.params, {
directToCashier: true,
orderId: created.orderId,
@@ -345,6 +339,7 @@ export async function POST(request: NextRequest) {
return response;
}
// PC/手机zpay 直接 redirect手机 h5 得 302 跳转支付页PC 得二维码页)
const redirectPayload: Record<string, unknown> = {
user_id: userId,
site_id: SITE_ID,
@@ -366,10 +361,18 @@ export async function POST(request: NextRequest) {
if (redirectRes.status >= 301 && redirectRes.status <= 308) {
const location = redirectRes.headers.get("location");
if (location) {
const orderId = redirectRes.headers.get("x-pay-order-id")?.trim();
if (!wantHtml) {
const res = NextResponse.json({
ok: true,
order_id: orderId,
redirect_url: location,
provider: "zpay",
});
if (orderId) setPayOrderCookie(res, orderId);
return res;
}
const response = NextResponse.redirect(location);
const orderId =
redirectRes.headers.get("x-pay-order-id")?.trim() ||
created.orderId;
if (orderId) setPayOrderCookie(response, orderId);
return response;
}
@@ -391,7 +394,7 @@ export async function POST(request: NextRequest) {
c
] || c)
);
const rawOrderId = String(resData.order_id || created.orderId).trim();
const rawOrderId = String(resData.order_id || "").trim();
const html = buildQrHtml(imgSrc, safeReturnUrl, rawOrderId, {
channel: resData.channel || "wxpay",
confirmUrl: "/api/pay/complete-order",
@@ -405,6 +408,16 @@ export async function POST(request: NextRequest) {
}
if (redirectRes.status === 200 && resData?.use_form) {
created = await createPayOrder(
apiUrl,
userId,
returnUrl,
totalFee,
type,
channel,
undefined,
clientIp
);
const html = buildPayHtml(payConfig.zpaySubmitUrl, created.params, {
directToCashier: true,
orderId: String(resData.order_id || created.orderId).trim(),
@@ -432,6 +445,17 @@ export async function POST(request: NextRequest) {
);
}
// 兜底redirect 失败时回退到 payh5
created = await createPayOrder(
apiUrl,
userId,
returnUrl,
totalFee,
type,
channel,
undefined,
clientIp
);
const html = buildPayHtml(created.payUrl, created.params, {
orderId: created.orderId,
});