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( `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( `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); } }