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.