39 lines
1.9 KiB
Markdown
39 lines
1.9 KiB
Markdown
# Architect — F-023 Payments: provider interface + Stripe + webhooks
|
|
|
|
## Feature
|
|
F-023 introduces real payment processing without coupling domain to Stripe.
|
|
|
|
## Design
|
|
|
|
### Module boundaries
|
|
Create `project/src/modules/payments/` with domain/application/infrastructure/api/tests. Payments owns `payments_transactions`; domain code never imports the Stripe SDK.
|
|
|
|
### Data model
|
|
Add migration `017_payments.js`:
|
|
- `payments_transactions`: id, provider text, provider_event_id text unique per provider, provider_payment_id text, order_id uuid, amount_cents integer, currency text, status text, raw jsonb, created_at timestamptz.
|
|
- CHECK `status IN ('requires_payment','succeeded','failed','refunded','chargeback')`.
|
|
- UNIQUE `(provider, provider_event_id)` for idempotency.
|
|
|
|
### Payment provider interface
|
|
- `PaymentProvider.createIntent(input)` and `PaymentProvider.verifyWebhook(rawBody, signature)` returning a typed `PaymentEvent` or throwing.
|
|
- Domain never touches Stripe SDK; only the Stripe adapter does.
|
|
|
|
### Webhook flow
|
|
- `POST /payments/webhook` reads raw body + signature header.
|
|
- Verifies signature with `STRIPE_WEBHOOK_SECRET` (or env-derived) — invalid -> 400.
|
|
- Looks up `(provider, provider_event_id)` in transactions; if seen, return 200 without re-processing (idempotent).
|
|
- Maps Stripe events to `PaymentEvent` (`PaymentSucceeded`, `PaymentFailed`, `PaymentRefunded`, `ChargebackCreated`) and processes them: persist transaction, call `OrderService.transition` and emit event through `OrderEventPublisher`.
|
|
|
|
### API
|
|
- `POST /payments/webhook` public (signature-protected).
|
|
- Admin/dev `GET /payments/transactions/:orderId` for inspection.
|
|
|
|
### Domain events
|
|
- Emit `OrderPaid`/`OrderCancelled`/`OrderRefunded` via `OrderEventPublisher` already exposed by orders.
|
|
|
|
## Acceptance trace
|
|
- Domain code contains zero Stripe SDK imports.
|
|
- Bad signature -> 400, no DB row.
|
|
- Replayed event -> single processed transaction.
|
|
- PaymentSucceeded -> order state PAID, event emitted.
|