Files
gitlab-instance-0a899031_salon/app/api/pay/complete-order/route.ts
2026-03-16 09:32:35 -05:00

152 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 本地直接确认支付:检测到 paid 后立即写入 PocketBase不依赖 salonapi
* 用于扫码页 doConfirm避免「检测到支付正在确认…」等待过久
*/
import { NextRequest, NextResponse } from "next/server";
import { getAdminToken } from "@/app/lib/pocketbase/admin";
import { getPocketBaseConfig, SITE_ID } from "@/config/services.config";
import { queryZPayOrderStatus } from "@/app/lib/zpayStatus";
import { queryXorPayOrderStatus } from "@/app/lib/xorpayStatus";
function parseOrderId(orderId: string): { userId: string; payType: string } {
const id = (orderId || "").trim();
if (!id.includes("_order_")) return { userId: "", payType: "unknown" };
const prefix = id.split("_order_")[0];
const lastUnderscore = prefix.lastIndexOf("_");
if (lastUnderscore > 0) {
return {
userId: prefix.substring(0, lastUnderscore),
payType: prefix.substring(lastUnderscore + 1).toLowerCase(),
};
}
return { userId: prefix, payType: "unknown" };
}
export async function POST(request: NextRequest) {
try {
const body = await request.json().catch(() => ({}));
const orderId = String(body?.order_id || "").trim();
if (!orderId) {
return NextResponse.json({ ok: false, error: "缺少 order_id" }, { status: 400 });
}
const { userId, payType } = parseOrderId(orderId);
if (!userId || payType !== "salon") {
return NextResponse.json({ ok: false, error: "订单格式无效" }, { status: 400 });
}
const [zpay, xorpay] = await Promise.all([
queryZPayOrderStatus(orderId, 5000),
queryXorPayOrderStatus(orderId, 5000),
]);
const paid = zpay?.paid || xorpay?.paid;
if (!paid) {
return NextResponse.json({ ok: false, error: "订单未支付" }, { status: 400 });
}
const moneyYuan = parseFloat(zpay?.money || xorpay?.payPrice || "0") || 0;
const amountCents = moneyYuan > 0 ? Math.round(moneyYuan * 100) : 100;
const token = await getAdminToken();
if (!token) {
return NextResponse.json({ ok: false, error: "PocketBase 未配置" }, { status: 503 });
}
const pb = getPocketBaseConfig();
const base = pb.url.replace(/\/$/, "");
const escapedUserId = userId.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
const filter = encodeURIComponent(`user_id = "${escapedUserId}"`);
const listRes = await fetch(
`${base}/api/collections/${pb.joinCollection}/records?filter=${filter}&sort=-created&perPage=1`,
{
headers: { Authorization: `Bearer ${token}` },
cache: "no-store",
}
);
if (!listRes.ok) {
return NextResponse.json({ ok: false, error: "查询记录失败" }, { status: 500 });
}
const listData = await listRes.json().catch(() => ({}));
const items = Array.isArray(listData?.items) ? listData.items : [];
const rec = items[0] ?? null;
if (rec?.id) {
const patchRes = await fetch(
`${base}/api/collections/${pb.joinCollection}/records/${rec.id}`,
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
order_id: orderId,
amount: amountCents,
vip: true,
}),
}
);
if (!patchRes.ok) {
const err = await patchRes.json().catch(() => ({}));
console.warn("[pay/complete-order] solanRed patch failed:", patchRes.status, err);
}
}
const siteVipFilter = encodeURIComponent(
`user_id = "${escapedUserId}" && site_id = "${SITE_ID}"`
);
const siteVipList = await fetch(
`${base}/api/collections/site_vip/records?filter=${siteVipFilter}&perPage=1`,
{
headers: { Authorization: `Bearer ${token}` },
cache: "no-store",
}
);
if (siteVipList.ok) {
const svData = await siteVipList.json().catch(() => ({}));
const svItems = svData?.items ?? [];
if (svItems.length > 0) {
await fetch(
`${base}/api/collections/site_vip/records/${svItems[0].id}`,
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ order_id: orderId }),
}
);
} else {
await fetch(`${base}/api/collections/site_vip/records`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
user_id: userId,
site_id: SITE_ID,
order_id: orderId,
expires_at: "",
}),
});
}
}
return NextResponse.json({ ok: true });
} catch (error) {
console.error("[pay/complete-order] error:", error);
return NextResponse.json(
{ ok: false, error: error instanceof Error ? error.message : "操作失败" },
{ status: 500 }
);
}
}