Files
Jyotisha/frontend/src/app/api/admin/orders/route.ts
T
Jesse_Chen c712787ec2
Independent Staging Quality Gate / validate (push) Has been cancelled
Independent Staging Quality Gate / publish (push) Has been cancelled
fix(admin): remove manual reasons and repair code creation
2026-08-16 16:49:11 +08:00

152 lines
4.6 KiB
TypeScript

import { NextResponse } from "next/server";
import { z } from "zod";
import { requirePermission } from "@/lib/admin/auth";
import { pageOffset, queryAdminRows } from "@/lib/admin/database";
import {
adminErrorResponse,
invalidQueryResponse,
parseListQuery,
requestId,
requireAdminMutation,
} from "@/lib/admin/http";
export const runtime = "nodejs";
const adjustmentSchema = z.object({
id: z.string().uuid(),
action: z.enum(["retry_grant", "compensate", "record_refund"]),
expectedVersion: z.number().int().min(0),
});
type Row = {
id: string;
order_no: string;
user_id: string;
email: string | null;
product_code: string | null;
product_version: number | null;
money_cents: number;
currency: string;
status: string;
grant_type: string | null;
grant_status: string;
grant_error: string | null;
adjustment_version: number;
refund_status: string;
refund_amount_cents: number | null;
refunded_at: Date | null;
paid_at: Date | null;
created_at: Date;
total_count: string;
};
type AdjustmentRow = {
id: string;
order_no: string;
status: string;
grant_status: string;
grant_error: string | null;
adjustment_version: number;
refund_status: string;
refund_amount_cents: number | null;
refunded_at: Date | null;
action_success: boolean;
};
export async function GET(request: Request) {
try {
await requirePermission("billing.orders.read");
const parsed = parseListQuery(request);
if (!parsed.success) return invalidQueryResponse(parsed.error.flatten());
const query = parsed.data.q ? `%${parsed.data.q}%` : null;
const rows = await queryAdminRows<Row>(
`select o.id,o.order_no,o.user_id,u.email,o.product_code,o.product_version,
o.money_cents,o.currency,o.status,o.grant_type,o.grant_status,o.grant_error,
o.adjustment_version,o.refund_status,o.refund_amount_cents,o.refunded_at,
o.paid_at,o.created_at,count(*) over()::text total_count
from public.payment_orders o
left join identity.users u on u.id=o.user_id
where ($1::text is null or o.order_no ilike $1 or u.email ilike $1 or o.user_id::text ilike $1)
and ($2::text is null or o.status=$2 or o.grant_status=$2 or o.refund_status=$2)
order by o.created_at desc limit $3 offset $4`,
[
query,
parsed.data.status ?? null,
parsed.data.pageSize,
pageOffset(parsed.data.page, parsed.data.pageSize),
],
);
return NextResponse.json({
data: rows.map((row) => ({
id: row.id,
orderNo: row.order_no,
userId: row.user_id,
email: row.email,
productCode: row.product_code,
productVersion: row.product_version,
moneyCents: row.money_cents,
currency: row.currency,
status: row.status,
grantType: row.grant_type,
grantStatus: row.grant_status,
grantError: row.grant_error,
adjustmentVersion: row.adjustment_version,
refundStatus: row.refund_status,
refundAmountCents: row.refund_amount_cents,
refundedAt: row.refunded_at?.toISOString() ?? null,
paidAt: row.paid_at?.toISOString() ?? null,
createdAt: row.created_at.toISOString(),
})),
total: Number(rows[0]?.total_count ?? 0),
});
} catch (error) {
return adminErrorResponse(error);
}
}
export async function POST(request: Request) {
try {
const session = await requireAdminMutation(
request,
"billing.adjustments.write",
);
const parsed = adjustmentSchema.safeParse(
await request.json().catch(() => null),
);
if (!parsed.success) return invalidQueryResponse(parsed.error.flatten());
const rows = await queryAdminRows<AdjustmentRow>(
`select * from public.admin_adjust_order(
$1::uuid,$2::uuid,$3::text,$4::integer,$5::text,$6::text
)`,
[
session.user.id,
parsed.data.id,
parsed.data.action,
parsed.data.expectedVersion,
"admin_console_adjust_order",
requestId(request),
],
);
const row = rows[0];
return NextResponse.json({
data: row
? {
id: row.id,
orderNo: row.order_no,
status: row.status,
grantStatus: row.grant_status,
grantError: row.grant_error,
adjustmentVersion: row.adjustment_version,
refundStatus: row.refund_status,
refundAmountCents: row.refund_amount_cents,
refundedAt: row.refunded_at?.toISOString() ?? null,
actionSuccess: row.action_success,
}
: null,
});
} catch (error) {
return adminErrorResponse(error);
}
}