gitea-saas-core/gitea_saas/schema.sql
pbwraith-worker 6d764d46f6 gitea-saas-core#1: billing ledger core (clients/ledger/holds, idempotent, fail-closed)
SQLite schema + ledger operations in gitea_saas/billing_core.py (+ db.py,
schema.sql). balance = credits + refunds - debits - active holds, never
negative; every mutation idempotent via UNIQUE idempotency_key; holds
expire (default 30 min) and auto-release on the next read/query. All ops
return (ok, details) with structured errors; never raise. Thread-safe via
single lock + WAL. 26 hermetic pytest tests (no network/Stripe/Gitea).
2026-08-25 04:16:35 -05:00

50 lines
2.2 KiB
SQL

-- gitea-saas-core billing schema (SQLite).
--
-- Idempotent by construction: every object is created with IF NOT EXISTS,
-- so executing this file any number of times is safe.
--
-- Conventions:
-- * All amounts are INTEGER minor units (cents). No floats, ever.
-- * Currency defaults to USD.
-- * Timestamps are ISO-8601 UTC text written by the application layer;
-- the DEFAULT expressions are fallbacks for manual/one-off inserts only.
-- * ledger.kind is constrained to {credit, debit, hold, release, refund}.
-- * ledger.idempotency_key is globally UNIQUE: every mutation is replay-safe.
CREATE TABLE IF NOT EXISTS clients (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
tier TEXT NOT NULL DEFAULT 'free',
status TEXT NOT NULL DEFAULT 'active',
meta_json TEXT NOT NULL DEFAULT '{}',
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);
CREATE TABLE IF NOT EXISTS ledger (
id INTEGER PRIMARY KEY AUTOINCREMENT,
client_id INTEGER NOT NULL REFERENCES clients(id),
kind TEXT NOT NULL CHECK (kind IN ('credit', 'debit', 'hold', 'release', 'refund')),
amount_minor INTEGER NOT NULL CHECK (amount_minor >= 0),
currency TEXT NOT NULL DEFAULT 'USD',
idempotency_key TEXT NOT NULL UNIQUE,
ref_type TEXT,
ref_id TEXT,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
);
CREATE TABLE IF NOT EXISTS holds (
id INTEGER PRIMARY KEY AUTOINCREMENT,
client_id INTEGER NOT NULL REFERENCES clients(id),
amount_minor INTEGER NOT NULL CHECK (amount_minor >= 0),
ref_type TEXT,
ref_id TEXT,
expires_at TEXT NOT NULL,
released_at TEXT
);
-- Balance / listing lookups.
CREATE INDEX IF NOT EXISTS idx_ledger_client_created ON ledger (client_id, created_at, id);
CREATE INDEX IF NOT EXISTS idx_ledger_client_kind ON ledger (client_id, kind);
CREATE INDEX IF NOT EXISTS idx_holds_client ON holds (client_id);
CREATE INDEX IF NOT EXISTS idx_holds_stale ON holds (client_id) WHERE released_at IS NULL;