GMI · TECHNOLOGY OBSERVATORY // ALL SYSTEMS NOMINAL
ENGINEERED BY LEOPARD DATA

Billing & Subscription Architecture

A Stripe-backed, storage-usage billing engine built around a durable job queue, a safe two-pass preview-then-charge workflow, and idempotency guards that make double-billing structurally impossible.

TWO-PASS QUEUE FLOW
rendering diagram…
flowchart TB
    A([Admin enqueues run]):::app
    Q[(BillingRun queue<br/>Status Queued)]:::data
    P[Console poll-queue<br/>ClaimNextQueuedRunAsync<br/>atomic claim - Running]:::svc
    PV[Preview pass<br/>read-only - no charges]:::svc
    CSV[(CSV export)]:::data
    CH[Charge pass<br/>idempotency guard<br/>one charge per account]:::svc
    ST[Stripe<br/>tokenized cards<br/>last4 plus brand]:::ext
    INV[(Invoices plus DB commit)]:::data
    BRA[(BillingRunAccount<br/>Success - Failed<br/>Skipped - NoCharges)]:::data
    FPR[(FailedPaymentReconciliation<br/>Stripe ok - DB write failed<br/>manual resolution)]:::sec
    DONE([Status Complete]):::app
    A --> Q
    Q -->|poll| P
    P --> PV
    PV --> CSV
    PV --> CH
    CH --> ST
    ST -->|charge ok| INV
    INV --> BRA
    ST -->|charged but DB write fails| FPR
    BRA --> DONE
classDef app fill:#1f2630,stroke:#C0894E,color:#F3EFE8;
classDef svc fill:#241c14,stroke:#E2B583,color:#F3EFE8;
classDef data fill:#22201a,stroke:#C0894E,color:#EBD3AE;
classDef ext fill:#1b1f27,stroke:#5A616B,color:#C4C0B8;
classDef sec fill:#1d2733,stroke:#5cc8e0,color:#eafaff;
An admin enqueues a billing run; the console claims it atomically, runs a read-only preview pass, then a charge pass that commits to the database and Stripe, recording per-account outcomes and reconciling any partial failures.

Stripe & Storage-Based Pricing

The billing engine charges customers through Stripe (Stripe.net v46.2.0) based on how much storage each file consumes over time.

charge = days_stored × file_size_GB × daily_rate  (per file)
  • Per-file storage usage pricing
  • Honors coupon-free storage
  • Accounts for soft-deleted files
Card Data Never Stored

Cards are tokenized by Stripe. Grade My Investments persists only the last4 digits and card brand for display, never raw card numbers.

Two-Pass Billing

Every billing run is split into two distinct passes so totals can be inspected before a single dollar moves.

Pass 1 — Preview
  • Read-only, no charges
  • CSV export of computed charges
  • Auditable before committing
Pass 2 — Charge
  • Commits charges to the database
  • Calls Stripe to bill customers
  • Records per-account outcomes

Durable Queue

The BillingRun table doubles as a durable job queue, decoupling the admin who triggers a run from the console worker that executes it.

  1. Admin enqueues a billing run
  2. Console polls the queue for work
  3. ClaimNextQueuedRunAsync atomically claims the next run
  4. Status advances Queued Running Complete

Idempotency & Safety

  • No double-billing a period. HasSuccessfulBillingRunForPeriodAsync(year, month) blocks repeat runs for the same period.
  • One charge per job per account. Each account is charged at most once within a run.
  • One-time coupon use. Coupons are consumed a single time.

Failed Payment Reconciliation

The hardest case is a partial success: Stripe charges the customer but the database write that records it fails. The FailedPaymentReconciliation table captures exactly this scenario.

  • Stripe success + DB write failure is captured, not lost
  • Preserved for manual resolution
  • Prevents silent revenue or audit gaps

Per-Account Auditing

BillingRunAccount rows record the outcome of each account within a run, with an invoice id or error message for drill-down.

Success Failed Skipped NoCharges

CLI Commands

Command Pass Purpose
billing-report Preview Read-only preview with CSV export, no charges
charge Charge Run the monthly charge pass
charge-account Charge Retry a single account
poll-queue Primary Poll the BillingRun queue and process claimed runs

Implemented across Gmi.BillingEngine.Console and the BillingService in Gmi.Services.