Platform Billing Dashboard (Super-Admin)
The Platform Billing Dashboard is Michael's bird's-eye view of Ledgerline as a business. It spans every tenant, surfaces the headline SaaS metrics (MRR, ARR, ARPU, churn), and exposes the levers for managing tenant plans, sending manual invoices, and cancelling failing accounts.
This dashboard is restricted to platform owners — Ledgerline staff (currently Michael). Tenant admins do not see it. The auth gate is enforced server-side; tenant admins who guess the URL get a 403 banner with no data leakage.
Access
The dashboard sits under Platform → Platform Billing in the left sidebar of the Admin panel. The drill-down per-tenant view is reachable from any "Drill in →" link in the tables, or directly via:
/admin/pages/platform-billing/tenant/<platformAccountId>
Who counts as a platform owner?
The PLATFORM_OWNER_DISCORD_IDS environment variable holds a comma-separated list of Discord user ids. Any session whose Discord id appears in that list passes the gate; everyone else gets 403. The list is read fail-closed — an unset or blank env var means nobody is a platform owner. To add a co-owner, append their Discord id and redeploy.
PLATFORM_OWNER_DISCORD_IDS=111111111111111111,222222222222222222
Top KPI strip
Five cards across the top, refreshed on every page load (and cached in Valkey for 60 seconds):
| KPI | Definition |
|---|---|
| Total Revenue | Sum of all paid PlatformInvoice amounts within the selected window. |
| MRR | sum(currently-active monthly_flat plans' monthlyFeeCents) + (3-month trailing average of revenue_share invoices) / 3. |
| ARR | MRR × 12 + (annual_prepay invoices issued in last 12 months). |
| ARPU | totalRevenue / activeTenantCount. |
| Active Tenants | Tenants currently in active status (excludes trialing, past_due, cancelled). |
A second sub-row shows three triage indicators:
- Past-Due Tenants — count of tenants currently in
past_duestatus. Card turns red when above zero. - Churn (last 90d) —
cancellations in trailing 90d / (active + past_due + cancelled-in-window)× 100. Card turns red above 5%. - Failing Invoices (30d) — count of
failedPlatformInvoices in the last 30 days. Card turns yellow when above zero.
Date range
The picker at the top right toggles between 1m / 3m / 6m / 12m / 24m windows. Default is 12 months. The window controls Total Revenue and the Revenue-by-Month chart; MRR/ARR are point-in-time and don't change with the picker. Churn always uses a fixed 90-day trailing window.
Charts
- Revenue by Month — line chart of paid revenue per UTC calendar month, full window.
- Plan Distribution — pie chart of current plan types across all tenants, with a
trialingslice for tenants whose status is stilltrialing.
Tables
Top 20 Tenants by Revenue
Sorted by lifetime paid revenue within the selected window. Columns:
- Tenant — tenant id (slug).
- Plan — current plan type (
revenue_share/monthly_flat/annual_prepay) or—if no current plan. - Status —
active/trialing/past_due/cancelled. - Lifetime Revenue — sum of paid invoices in window.
- MTD GMV — month-to-date GMV from PlatformUsage (the revenue_share signal).
- Last Invoice — status of the most-recent invoice for the tenant.
- Drill in → — link to the per-tenant detail page.
Past-Due Tenants
Every tenant currently in past_due status, with day count and amount owed.
- Days Past Due — calendar days since the oldest unpaid invoice was issued. Turns red after 14 days.
- Amount Owed — sum of
issuedandfailedPlatformInvoice amounts for the account. - Manage → — opens the tenant drill-down page.
Failing Invoices (last 30 days)
Recent failed invoices (status = failed). Useful for triage when a card decline cascade hits.
Tenant drill-down
Clicking Drill in → or Manage → opens /admin/pages/platform-billing/tenant/:id, which shows:
- KPI cards — lifetime revenue, Stripe customer id, trial end date.
- Plan history — every PlatformPlan record (immutable; new plan = new record + supersede).
- Invoice history — every PlatformInvoice with hosted-invoice / PDF / crypto-tx links where available.
- Usage history — GMV + order count per period from PlatformUsage snapshots.
- Payment methods — Stripe-saved cards/bank accounts (when the Stripe adapter is wired). Never includes raw card numbers — only the sanitized
last4/brand. - Cancel Tenant button — see "Mutating actions" below.
Mutating actions
The dashboard exposes three mutating actions, all restricted to platform owners and audit-logged on every call.
Change plan
POST /api/admin/platform-billing/tenants/:id/plan
Body:
{
"type": "revenue_share | monthly_flat | annual_prepay",
"revenueSharePct": 500,
"monthlyFeeCents": 9900,
"annualPrepayDiscountBps": 1500,
"billingPeriod": "monthly | annual"
}
Calls platformAccountService.setPlan — supersedes the current plan record (sets expiresAt), inserts a new record, and points account.currentPlanId at the new plan.
Manual invoice
POST /api/admin/platform-billing/tenants/:id/invoice
Body:
{
"amountCents": 19900,
"description": "March 2026 — pro-rated catch-up",
"dueDate": "2026-04-30T00:00:00.000Z"
}
Calls platformInvoiceService.createDraft — creates a draft PlatformInvoice covering the current calendar month. Once drafted, the existing event subscriber (Stripe or NOWPayments adapter, when wired) picks up platform.invoice.draft_created and dispatches the actual external invoice.
Cancel tenant
POST /api/admin/platform-billing/tenants/:id/cancel
Sets account.status = 'cancelled'. If a Stripe subscription canceller is wired and the account has a stripeCustomerId, also cancels the Stripe-side subscription (best-effort; never blocks the local cancellation).
Audit log
Every mutating call writes one AuditLog row with:
tenantId— the affected tenant.actorId—discord:<platform-owner-discord-id>.action—platform-billing.plan.change/platform-billing.invoice.create/platform-billing.account.cancel.entityType—PlatformAccountorPlatformInvoice.entityId— the affected document id.meta— request-specific details (new plan id, amounts, previous status, etc.).
Audit log entries respect the existing TTL (AUDIT_LOG_RETENTION_DAYS, default 365 days).
Caching
KPIs and revenue-by-month are cached in Valkey for 60 seconds per (rangeMonths) tuple. Cache keys:
platform-billing:kpis:<N>m
platform-billing:revenue-by-month:<N>m
If you need to force-refresh, hit the Refresh button in the dashboard header — it bypasses the React state cache. The Valkey TTL is short enough that you'll never wait long.
API endpoints (for scripted access)
All require an authenticated session whose user passes isPlatformOwner. Non-owners get 403; unauthenticated requests get 401.
| Method | Path | Purpose |
|---|---|---|
GET | /api/admin/platform-billing/kpis?range=12m | Top KPIs envelope. |
GET | /api/admin/platform-billing/revenue-by-month?range=12m | Monthly paid revenue series. |
GET | /api/admin/platform-billing/tenants?sort=revenue&limit=20&range=12m | Top tenants by lifetime revenue. |
GET | /api/admin/platform-billing/past-due | Past-due tenants + day count + amount owed. |
GET | /api/admin/platform-billing/failing-invoices?days=30 | Recent failed invoices. |
GET | /api/admin/platform-billing/tenants/:id | Per-tenant detail (drill-down payload). |
POST | /api/admin/platform-billing/tenants/:id/plan | Change plan. |
POST | /api/admin/platform-billing/tenants/:id/invoice | Manual invoice draft. |
POST | /api/admin/platform-billing/tenants/:id/cancel | Cancel tenant. |