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

Merged
pbwraith merged 2 commits from issue-2 into main 2026-08-26 09:08:40 +02:00

2 Commits

Author SHA1 Message Date
Claude
4d3123af9e Merge remote-tracking branch 'origin/main' into issue-2
# Conflicts:
#	requirements.txt
2026-08-25 06:29:19 -05:00
Claude
d7d462de14 Add billing HTTP API (Flask, token-auth, fail-closed)
Implements gitea-saas-core#2: Flask app over the issue #1 billing core.

- create_app(core, api_key) factory; X-API-Key auth on every route
  except /health (missing/wrong key -> 401), hmac.compare_digest,
  no key configured -> deny all (fail-closed).
- Endpoints: POST /api/clients, GET /api/clients (admin list),
  GET /api/clients/{id}, POST .../credits, POST .../debits (409 on
  negative balance), POST .../holds (409 on insufficient funds),
  POST /api/holds/{id}/release, GET .../ledger?from=&to=.
- JSON in/out, errors always {"error", "code"}; core (ok, details)
  contract enforced; missing core op -> 501, unexpected core failure
  -> 500 JSON (never HTML, never leaked internals).
- config.py reads SAAS_API_KEY/SAAS_PORT/SAAS_DB_PATH with sane
  defaults; never reads Stripe keys. CLI (python -m gitea_saas.server)
  refuses to start without SAAS_API_KEY.
- tests use temp SQLite + Flask test client, no network. The billing
  core (#1) is not merged onto main yet, so tests carry a reference
  implementation of the documented core contract; a skipped-until-then
  integration test runs against the real core once it lands.

Verification: python -m pytest tests/test_server.py -q -> 17 passed, 1 skipped.
2026-08-25 04:30:07 -05:00