Files
mercadodevida/work/artifacts/F-023/architect.md
2026-08-17 22:23:10 +02:00

1.9 KiB

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.