76 lines
2.8 KiB
TypeScript
76 lines
2.8 KiB
TypeScript
import { jsonError, jsonOk, readJsonBody } from "@/lib/server/api";
|
|
import { requireSuperAdminUser } from "@/lib/server/auth/current-user";
|
|
import { getPlatformOrganization } from "@/lib/server/account-store";
|
|
import { postOrganizationTopUp } from "@/lib/server/billing-service";
|
|
import { postWalletEntry } from "@/lib/server/billing-store";
|
|
import { createId } from "@/lib/server/ids";
|
|
|
|
export const runtime = "nodejs";
|
|
|
|
export async function POST(request: Request) {
|
|
try {
|
|
const operator = await requireSuperAdminUser();
|
|
const body = await readJsonBody<Record<string, unknown>>(request);
|
|
const organizationId = requiredString(body.organizationId, "组织");
|
|
const organization = await getPlatformOrganization(organizationId);
|
|
if (!organization) throw badRequest("组织不存在。");
|
|
|
|
const amountFen = parseAmountFen(body.amountFen, body.amountYuan);
|
|
if (amountFen <= 0) throw badRequest("请输入大于 0 的金额。");
|
|
const direction = body.direction === "debit" ? "debit" : body.direction === "credit" ? "credit" : null;
|
|
if (!direction) throw badRequest("余额变动方向无效。");
|
|
|
|
const note = requiredString(body.note, "备注");
|
|
const amountLabel = (amountFen / 100).toFixed(2);
|
|
const idempotencyKey = `manual-adjustment:${createId("entry")}`;
|
|
const metadata = {
|
|
operation: direction === "credit" ? "admin_top_up" : "manual_adjustment",
|
|
direction,
|
|
note,
|
|
operatorId: operator.id,
|
|
amountYuan: amountLabel
|
|
};
|
|
const result = direction === "credit"
|
|
? await postOrganizationTopUp({
|
|
organizationId,
|
|
amountFen,
|
|
idempotencyKey,
|
|
description: `管理员上账 · ${note}`,
|
|
metadata
|
|
})
|
|
: await postWalletEntry({
|
|
organizationId,
|
|
kind: "adjustment",
|
|
deltaFen: -amountFen,
|
|
idempotencyKey,
|
|
description: `管理员扣减 · ${note}`,
|
|
metadata
|
|
});
|
|
return jsonOk({ wallet: result.wallet, entry: result.entry });
|
|
} catch (error) {
|
|
return jsonError(error, 500, { request, source: "api.admin.billing.adjustments" });
|
|
}
|
|
}
|
|
|
|
function parseAmountFen(amountFen: unknown, amountYuan: unknown): number {
|
|
const fen = Number(amountFen);
|
|
if (Number.isFinite(fen) && fen > 0) return Math.round(fen);
|
|
const yuan = Number(amountYuan);
|
|
if (!Number.isFinite(yuan) || yuan <= 0) return 0;
|
|
return Math.round(yuan * 100);
|
|
}
|
|
|
|
function requiredString(value: unknown, label: string): string {
|
|
const normalized = optionalString(value);
|
|
if (!normalized) throw badRequest(`${label}不能为空。`);
|
|
return normalized;
|
|
}
|
|
|
|
function optionalString(value: unknown): string | undefined {
|
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
}
|
|
|
|
function badRequest(message: string): Error & { status: number } {
|
|
return Object.assign(new Error(message), { status: 400 });
|
|
}
|