Admin Finance & Trust Modules
Four ops panel modules cover the financial and fraud-prevention layers: Transaction Ledger, Financial Reports, Payout & Revenue, and Fraud Monitor. The first three live in the Commerce group; Fraud Monitor lives in the System group.
Transaction Ledger
Component: src/app/components/admin/LedgerView.tsx
Displays every ledger_entries row with type-keyed colour chips, direction badges, and funds-status labels. Paginated at 50 rows, filterable by entry type.
Ledger type config
| Type | Label | Colour | Direction | Funds status |
|---|---|---|---|---|
SALE | SALE | Teal (#006D77) | in | RELEASED |
REFUND | REFUND | Red (#EF4444) | out | RELEASED |
BPF | BPF | Amber (#F59E0B) | in | RELEASED |
PLATFORM_FEE | FEE | Blue (#2563EB) | in | RELEASED |
HOLD | HOLD | Orange (#F97316) | in | HELD |
RELEASE | RELEASE | Green (#059669) | out | RELEASED |
FREEZE | FREEZE | Blue (#2563EB) | in | HELD |
DISPUTE_HOLD | DISPUTE | Red (#EF4444) | in | HELD |
Endpoint
GET /admin/ledger, paginated; allowed roles: administrator, finance.
Implementation notes
- BRAND-01, FREEZE type colour: Purple (
#8B5CF6) is banned across the entire ops panel. The FREEZE chip was purple on initial build; corrected to#2563EB(blue). (Fixed PS-369, 2026-05-12.) - Never reintroduce any purple hex to
LedgerView.tsx.
Financial Reports
Component: src/app/components/admin/FinancialLedger.tsx
Summary dashboard with four KPI cards and an Escrow Balance Trend area chart.
KPI cards
| Card | Source table | Notes |
|---|---|---|
| Total Ledger Entries | ledger_entries | Count |
| Total Revenue | ledger_entries | Sum of amount |
| Avg Transaction | ledger_entries | Mean amount |
| Escrow Held | orders via escrow summary | Sum of held order value |
Escrow Balance Trend chart
7-day daily chart with held, released, and bpf series. Fetched from GET /admin/escrow-trend. Mapped in FinancialLedger.tsx as:
setEscrowTrend((data.trend ?? []).map(d => ({ ...d, bpf: 0 })));
bpf is set to 0 client-side because the endpoint does not yet return a BPF series.
Endpoints
| Endpoint | Purpose |
|---|---|
GET /admin/ledger | Paginated ledger entries for the table view |
GET /admin/escrow-summary-live | Live escrow KPI card values |
GET /admin/escrow-trend | 7-day escrow trend for the area chart |
All three accept roles administrator and finance.
Implementation notes
- Stale subtitle: The KPI section subtitle previously read "Live data from KV store", corrected to "Live data from ledger_entries" to reflect the live Postgres source. (Fixed PS-368, 2026-05-12.)
- Escrow trend was hardcoded empty: The initial build had
const _escrowTrend = []as a module-level constant (never fetched). The chart always showed a flat line. Replaced with state +useCallbackfetch wired intouseEffectand the manual refresh handler. (Fixed PS-370, 2026-05-12.)
Payout & Revenue
Component: src/app/components/admin/PayoutRevenue.tsx
Shows disbursement history and revenue metrics for the finance team.
Data source
Payout history reads from fin_disbursements via container.financeAdminRepo.getAllDisbursements(). Records are sorted descending by created_at after a null-safety filter.
Endpoints
| Endpoint | Purpose |
|---|---|
GET /admin/payouts | Payout (disbursement) history from fin_disbursements |
GET /admin/revenue | Revenue summary metrics |
Both endpoints require role administrator or finance.
Implementation notes
- Dead KV read, payout history: The initial
GET /admin/payoutshandler calledgetByPrefixAll("payout:")against the KV store, which was retired in Phase 14. Every call returned an empty array and the payout table was always blank. Replaced with a livefin_disbursementsread viafinanceAdminRepo. ThegetByPrefixAllimport was also removed from the route file. (Fixed PS-371, 2026-05-12.) - Role lockout,
administratormissing:verifyAdminRole(c, ["finance"])on bothGET /admin/payoutsandGET /admin/revenuelocked outadministrator-role users (same class of bug as PS-367). Fixed by adding"administrator"to both role arrays:verifyAdminRole(c, ["administrator", "finance"]). (Fixed PS-372, 2026-05-12.)
Fraud Monitor
Component: src/app/components/admin/FraudMonitor.tsx
Real-time fraud case viewer with inline state transitions.
Case shape, FraudCaseRow
Backend returns FraudCaseRow objects from the fraud_cases table. The frontend previously expected a FraudAlert shape with fields that do not exist on the backend type, every row rendered blank. The component now maps FraudCaseRow → FraudAlert display shape before rendering.
Backend field (FraudCaseRow) | Display field (FraudAlert) | Derived how |
|---|---|---|
case_type | type | direct cast |
severity (remaps "low" → "medium") | severity | low is treated as medium for display |
case_type | title | deriveCaseTitle() helper |
subject_type + subject_id + resolution_notes | description | template string |
opened_by | user | direct |
extra.listing_id / extra.order_id | listingId / orderId | optional chaining on extra |
created_at | time | toLocaleString("id-ID", ...) |
status | status | direct cast |
Case type → title mapping (deriveCaseTitle)
case_type | Displayed title |
|---|---|
fake_brand | Fake Brand Detected |
price_anomaly | Price Anomaly Detected |
account_suspicious | Suspicious Account Activity |
dispute | Dispute Escalated |
late_seller | Late Seller Pattern |
| anything else | Title-cased, underscores replaced with spaces |
5-state machine
Cases progress through five states. The state machine is enforced server-side at POST /admin/fraud-cases/:id/transition.
open → investigating → confirmed → resolved
→ rejected → resolved
| State | Colour | Who can transition to this |
|---|---|---|
open | Orange | Initial state |
investigating | Blue | From open |
confirmed | Red | From investigating |
rejected | Gray | From investigating |
resolved | Green | From confirmed or rejected |
Transition API
POST /admin/fraud-cases/:id/transition
{
"to": "investigating | confirmed | rejected | resolved",
"resolution_notes": "string (≥ 30 chars for confirmed / rejected / resolved)"
}
The body field is to (target status), not action. The resolution_notes field is required and must be at least 30 characters for any terminal transition (confirmed, rejected, resolved). The textarea in the panel enforces this client-side before enabling the submit button.
Frontend transition UI
The expanded case panel shows contextual action buttons based on current status:
| Current status | Available actions |
|---|---|
open | Investigate |
investigating | Confirm Fraud, Reject |
confirmed / rejected | Resolve |
resolved | (no actions) |
Filter tabs
The tab bar above the case list includes: All, Open, Investigating, Confirmed, Rejected, Resolved. Each tab filters the live FraudCaseRow array by the matching status value. Tab colours match the state colour table above.
Endpoints
| Endpoint | Purpose |
|---|---|
GET /admin/fraud-alerts | Fetch all fraud cases (FraudCaseRow[]) |
POST /admin/fraud-cases/:id/transition | Transition case state |
Both endpoints require role administrator or fraud_ops.
Implementation notes
- Field mismatch, all rows blank:
FraudMonitor.tsxread fields (title,description,user,time) that do not exist on the backendFraudCaseRowtype. All renders were undefined. Fixed by adding thederiveCaseTitle()helper and a fullFraudCaseRow → FraudAlertmapping in theallFraudAlertsderived array. Also updatedFraudCaseRowinterface intypes.ts,getFraudAlertsreturn type inapi-admin.ts, anduseFraudAlertsreturn type inuse-admin.ts. (Fixed PS-373, 2026-05-12.) - Transition UI added: Before PS-374, there was no way to change case status from the ops panel. Inline action buttons + resolution notes textarea added with full
transitionFraudCaseAPI method. (Fixed PS-374, 2026-05-12.) - Filter tabs extended: Original tabs only covered
open,investigating,resolved. Addedconfirmedandrejectedtabs to complete 5-state coverage. (Fixed PS-375, 2026-05-12.)
Prev: Admin Commerce Modules →