Billing HTTP API (Flask, token-auth, fail-closed) #2

Closed
opened 2026-08-25 11:03:45 +02:00 by pbwraith · 0 comments
Owner

Goal

Flask HTTP API over the billing core from issue #1. Token-auth'd, fail-closed, testable without real Stripe.

Depends on: issue #1 (billing core). Files allowed: gitea_saas/server.py, gitea_saas/config.py, tests/test_server.py, requirements.txt. Do NOT modify anything else.

Spec

  • Flask app create_app(core, api_key) factory.
  • Auth: X-API-Key header must match SAAS_API_KEY (env or constructor arg). Missing/wrong key -> 401. No auth on /health.
  • Endpoints:
    • GET /health -> {"ok": true}
    • POST /api/clients {name, tier?, meta?} -> client with balance
    • GET /api/clients/{id} -> client + balance (404 if missing)
    • POST /api/clients/{id}/credits {amount_minor, idempotency_key, ref_type?, ref_id?} -> new balance
    • POST /api/clients/{id}/debits {amount_minor, idempotency_key, ref_type?, ref_id?} -> new balance; 409 if would go negative
    • POST /api/clients/{id}/holds {amount_minor, ref_type?, ref_id?, ttl_s?} -> hold id; 409 if insufficient
    • POST /api/holds/{id}/release -> released
    • GET /api/clients/{id}/ledger?from=&to= -> rows
    • GET /api/clients (list, admin) -> all clients + balances
  • JSON in/out, errors as {"error": "...", "code": "..."}.
  • SAAS_PORT env for app.run (only when python -m gitea_saas.server); tests use Flask test client.
  • Config module reads env with sane defaults; never reads real Stripe keys.

Acceptance

  • unauthenticated request -> 401; bad key -> 401; correct key -> 200
  • full client lifecycle via API: create -> credit -> debit -> hold -> release -> ledger shows all
  • debit below zero -> 409, no balance change, idempotency preserved
  • duplicate idempotency_key -> same result, no double-apply
  • /health public
  • tests use tmp DB + Flask test client, no network

Verification: python -m pytest tests/test_server.py -q passes.

## Goal Flask HTTP API over the billing core from issue #1. Token-auth'd, fail-closed, testable without real Stripe. **Depends on:** issue #1 (billing core). **Files allowed:** `gitea_saas/server.py`, `gitea_saas/config.py`, `tests/test_server.py`, `requirements.txt`. Do NOT modify anything else. ## Spec - Flask app `create_app(core, api_key)` factory. - Auth: `X-API-Key` header must match `SAAS_API_KEY` (env or constructor arg). Missing/wrong key -> 401. No auth on `/health`. - Endpoints: - `GET /health` -> `{"ok": true}` - `POST /api/clients` `{name, tier?, meta?}` -> client with balance - `GET /api/clients/{id}` -> client + balance (404 if missing) - `POST /api/clients/{id}/credits` `{amount_minor, idempotency_key, ref_type?, ref_id?}` -> new balance - `POST /api/clients/{id}/debits` `{amount_minor, idempotency_key, ref_type?, ref_id?}` -> new balance; 409 if would go negative - `POST /api/clients/{id}/holds` `{amount_minor, ref_type?, ref_id?, ttl_s?}` -> hold id; 409 if insufficient - `POST /api/holds/{id}/release` -> released - `GET /api/clients/{id}/ledger?from=&to=` -> rows - `GET /api/clients` (list, admin) -> all clients + balances - JSON in/out, `errors` as `{"error": "...", "code": "..."}`. - `SAAS_PORT` env for `app.run` (only when `python -m gitea_saas.server`); tests use Flask test client. - Config module reads env with sane defaults; never reads real Stripe keys. ## Acceptance - [x] unauthenticated request -> 401; bad key -> 401; correct key -> 200 - [x] full client lifecycle via API: create -> credit -> debit -> hold -> release -> ledger shows all - [x] debit below zero -> 409, no balance change, idempotency preserved - [x] duplicate idempotency_key -> same result, no double-apply - [x] `/health` public - [x] tests use tmp DB + Flask test client, no network **Verification:** `python -m pytest tests/test_server.py -q` passes.
pbwraith added the
p1
label 2026-08-25 11:03:45 +02:00
pbwraith added the
in-progress
label 2026-08-25 11:15:08 +02:00
pbwraith added
in-review
and removed
in-progress
labels 2026-08-25 13:37:13 +02:00
Sign in to join this conversation.
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: pbwraith/gitea-saas-core#2
No description provided.