Staff can grant or correct a firm’s AI credits from the admin billing page
This story adds a staff-only /billing page that grants or
claws back a firm’s credits (RFD-BILL-001 §5.8, goal G7).
The money math, cap enforcement, append-only ledger write, and
exactly-once idempotency all live behind the merged S3 route in
pramaan-functions (pramaan-docs ADR-158). The admin only
proxies it through src/lib/gateway.ts.
Flow
The operator picks a firm, enters a signed credit amount (positive
grants, negative claws back), chooses the bucket (top-up/bonus or
allowance), and writes a mandatory audit reason. Submitting calls the
recordCreditAdjustment server action, which posts to
POST /admin/firms/{firmId}/billing/credits/adjustment via
the gateway and parses the confirmed response with the generated
contract in src/gen/.
Amounts are INR (1 credit = ₹1). On success the resulting balance card shows the firm’s server-confirmed total, allowance, and top-up buckets, and the adjustment is added to this session’s list. The write is server-confirmed only — no optimistic cache flip.
Idempotent submit
Each pending grant carries one caller-supplied adjustmentId
(a UUID held in component state). A double-click reuses the same key,
and the S3 writer’s partial unique index makes the second write a
no-op (applied: false), so credits are never doubled. A
fresh key is minted only after a write lands.
Acceptance evidence
| Criterion | Evidence | State |
|---|---|---|
| Staff grant/correct (firm, delta, bucket, reason) via S3 |
src/lib/actions/credit-adjustment.ts +
src/lib/billing/gateway.ts proxy the merged
credits/adjustment route; contract parsed from
src/gen/.
|
done |
| Balance shown |
Resulting-balance card renders the server-confirmed
totalCredits / allowanceCredits /
topupCredits from the write response, in INR.
|
done |
| Recent adjustments shown | This session’s applied adjustments are listed. Full ledger history has no admin read route yet (see scope note). | done |
| Idempotent submit |
Stable per-grant adjustmentId; backend enforces
exactly-once. Submit disabled while in flight.
|
done |
| Staff-only |
Page sits under the (admin) group gated by
checkAdminMe; the S3 route is StaffDbDep.
|
done |
| Local verify gate |
pnpm exec tsc --noEmit, pnpm run lint,
and pnpm run test:unit pass locally.
|
done |
Scope note: pramaan-functions serves no admin read route for a firm’s credit balance or ledger history, so pre-submit balance and full history are not shown — the page shows the server-confirmed balance and adjustments from this session. Wiring a live balance/history read needs a functions read endpoint (follow-up), per ADR-031 single-repo executable scope.