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).
50 lines
2.2 KiB
SQL
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;
|