feat: add card rotation, customer lookup, and concurrent eligibility checks

Add encrypted card pool management, persisted card and outbound rotation, and per-card decline cooldowns. Improve customer and redemption-code lookup, recovery controls, and filtering; rebuild frontend assets and refresh mock screenshots.

Give read-only eligibility checks four independent slots so payment processing no longer blocks them. Cover concurrency, slot cleanup, card rotation, and lookup behavior with tests.
This commit is contained in:
mizorewww committed 2026-10-04 11:44:38 +08:00
1 parent 92f09423c3
commit 83ac3f00ae
47 files changed
+3033 -501

No files matched your search

+72 -2
View File
@@ -121,6 +121,49 @@ createServer(async (req, res) => {
return;
}
if (req.method !== "POST") {
if (req.method === "GET" && url.pathname === "/api/admin/recovery") {
json(200, {
batch: null,
network: { mode: "direct", nodes: 1 },
summary: {
review: codes.filter((code) => code.status === "review").length,
processing: codes.filter((code) => code.status === "processing")
.length,
},
});
return;
}
if (req.method === "GET" && url.pathname === "/api/admin/customer") {
const key = url.searchParams.get("id") || "";
const user = (url.searchParams.get("username") || "")
.replace(/^@/, "")
.toLowerCase();
const found = codes.find(
(code) =>
(key && code.id === key) || (user && code.username === user),
);
if (!found) {
json(404, { message: "没有找到对应的订单,请核对后重试。" });
return;
}
json(200, {
order: {
id: found.id,
username: found.username,
hint: found.hint,
months: found.months,
status: found.status,
message: found.message,
batch: found.batch,
},
code: plaintext.get(found.id) || "",
checkout_url: "",
previous_checkout_url: "",
can_recover: false,
replacement_count: 0,
});
return;
}
if (req.method === "GET" && url.pathname === "/api/admin/stats") {
const daily = [];
for (let offset = 29; offset >= 0; offset--) {
@@ -174,8 +217,11 @@ createServer(async (req, res) => {
}
}
const body = JSON.parse(raw);
if (
url.pathname === "/api/admin/folders" ||
if (url.pathname.startsWith("/api/admin/recovery/")) {
json(503, { message: "本地模拟预览不提供补单操作。" });
return;
}
if (url.pathname === "/api/admin/folders" ||
url.pathname === "/api/admin/folders/rename"
) {
const name = typeof body.name === "string" ? body.name.trim() : "";
@@ -304,6 +350,30 @@ createServer(async (req, res) => {
});
return;
}
if (url.pathname === "/api/admin/lookup") {
const full =
typeof body.code === "string" ? body.code.trim().toUpperCase() : "";
if (!/^XG-[A-F0-9]{48}$/.test(full)) {
json(400, { message: "请填写 XG- 开头、后接 48 位字符的完整兑换码。" });
return;
}
// Fixed demo hit so a found result can be previewed without generating.
if (full === "XG-" + "5".repeat(48)) {
json(200, { ...codes[2], progress: 100, updated: now });
return;
}
const entry = [...plaintext.entries()].find(
([, value]) => value === full,
);
const found = entry && codes.find((code) => code.id === entry[0]);
json(
found ? 200 : 404,
found
? { ...found, updated: found.created, progress: 0 }
: { message: "没有找到这个兑换码,请核对后重试。" },
);
return;
}
if (url.pathname === "/api/admin/codes/copy") {
const code = plaintext.get(body.id);
json(