Stripe adapter (mockable seam: checkout + webhook -> idempotent credit) #3

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

Goal

Stripe adapter as a mockable seam: checkout session creation + payment webhook -> idempotent credits. Real impl behind env keys; mock impl for deterministic tests. No live Stripe key needed in CI.

Depends on: issue #1 (billing core). Files allowed: gitea_saas/stripe_client.py, tests/test_stripe_client.py. Do NOT modify anything else.

Spec

  • StripeClient interface (abstract or duck):
    • create_checkout_session(client_id, amount_minor, currency='usd', success_url, cancel_url) -> dict {checkout_url, session_id}
    • process_webhook(payload: bytes, signature: str) -> dict {event_id, type, client_id?, amount_minor?} — raises StripeSignatureError on bad signature.
  • Two impls:
    • MockStripeClient — deterministic, records events, no network. Default when STRIPE_SECRET_KEY absent.
    • StripeClient (real) — uses stripe python lib only if STRIPE_SECRET_KEY + STRIPE_WEBHOOK_SECRET set; CI runs mock-only.
  • In the server, payment.succeeded events call core credit(client_id, amount_minor, idempotency_key=event_id). Duplicate webhook delivery (same event_id) must not double-credit.
  • Webhook endpoint in server.py is issue #4's job — here only the client + credit tie-in helper.

Acceptance

  • mock client creates checkout session with correct fields
  • payment.succeeded -> credit applied once; re-delivered same event_id -> no double credit
  • bad signature raises signature error (mock raises on wrong sig too)
  • handler maps payment.succeeded to (client_id, amount_minor) correctly from event payload
  • mock mode enabled when env absent

Verification: python -m pytest tests/test_stripe_client.py -q passes, no network.

## Goal Stripe adapter as a mockable seam: checkout session creation + payment webhook -> idempotent credits. Real impl behind env keys; mock impl for deterministic tests. No live Stripe key needed in CI. **Depends on:** issue #1 (billing core). **Files allowed:** `gitea_saas/stripe_client.py`, `tests/test_stripe_client.py`. Do NOT modify anything else. ## Spec - `StripeClient` interface (abstract or duck): - `create_checkout_session(client_id, amount_minor, currency='usd', success_url, cancel_url) -> dict {checkout_url, session_id}` - `process_webhook(payload: bytes, signature: str) -> dict {event_id, type, client_id?, amount_minor?}` — raises `StripeSignatureError` on bad signature. - Two impls: - `MockStripeClient` — deterministic, records events, no network. Default when `STRIPE_SECRET_KEY` absent. - `StripeClient` (real) — uses `stripe` python lib only if `STRIPE_SECRET_KEY` + `STRIPE_WEBHOOK_SECRET` set; CI runs mock-only. - In the server, `payment.succeeded` events call core `credit(client_id, amount_minor, idempotency_key=event_id)`. Duplicate webhook delivery (same event_id) must not double-credit. - Webhook endpoint in server.py is issue #4's job — here only the client + credit tie-in helper. ## Acceptance - [x] mock client creates checkout session with correct fields - [x] `payment.succeeded` -> credit applied once; re-delivered same event_id -> no double credit - [x] bad signature raises signature error (mock raises on wrong sig too) - [x] handler maps `payment.succeeded` to `(client_id, amount_minor)` correctly from event payload - [x] mock mode enabled when env absent **Verification:** `python -m pytest tests/test_stripe_client.py -q` passes, no network.
pbwraith added the
p1
label 2026-08-25 11:04:03 +02:00
pbwraith added the
in-progress
label 2026-08-25 11:18:45 +02:00
pbwraith added
in-review
and removed
in-progress
labels 2026-08-25 11:22:31 +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#3
No description provided.