SECTION 0 — PAYMENTS ARE A CORRECTNESS SYSTEM
Payments failures are rarely “bugs.”
They are usually violated invariants:
-
double charge
-
double fulfillment
-
money captured but order not created
-
order created but money not captured
Senior fullstack means you design the system so these classes of failure are prevented and recoverable.
SECTION 1 — REQUIREMENTS
-
Users can create an order and pay.
-
Users can retry if network fails.
-
Orders can be fulfilled asynchronously.
-
Support webhooks from a payment provider.
Non-functional:
-
Correctness: never double-capture; never double-fulfill.
-
Auditability: every money movement is traceable.
-
Recovery: reconcile mismatches automatically.
SECTION 2 — INVARIANTS (NON-NEGOTIABLE)
-
An
orderhas a single source of truth state machine. -
A
payment_intentis idempotent per cart/attempt. -
Fulfillment happens only after confirmed payment state.
-
Webhooks are treated as at-least-once (idempotent handling).
SECTION 3 — STATE MACHINES (ORDER + PAYMENT)
Order:
DRAFT -> PENDING_PAYMENT -> PAID -> FULFILLING -> FULFILLED
-> PAYMENT_FAILED
Payment intent:
CREATED -> AUTHORIZED -> CAPTURED
-> FAILED
-> CANCELED
Senior rule:
Put state machines in the database, not in scattered code paths.
SECTION 4 — API CONTRACT (IDEMPOTENCY FIRST)
-
POST /checkout/start-
header:
Idempotency-Key: <uuid> -
body:
{ cartId, paymentMethod, ... } -
returns:
{ orderId, paymentIntentId, clientSecret?, nextAction? }
-
-
POST /checkout/confirm- confirms client-side provider flow if needed
-
GET /orders/{id}- returns state + display-safe fields
Idempotency rules:
-
Same idempotency key + same user returns the same result.
-
If request payload differs, return a deterministic error.
SECTION 5 — PROVIDER WEBHOOKS (AT-LEAST-ONCE)
POST /webhooks/provider
Rules:
-
verify signature
-
store raw event for audit
-
dedupe by provider event ID
-
apply state transition only if valid (compare-and-swap)
SECTION 6 — SAGA (FULFILLMENT ASYNC)
Pattern:
-
Order created (PENDING_PAYMENT)
-
Payment captured (via webhook or polling)
-
Enqueue fulfillment job
-
Fulfillment updates order to FULFILLED
Compensations:
- payment captured but fulfillment fails → retry fulfillment; if irrecoverable, refund workflow
Senior rule:
“Happy path” is cheap. Recovery paths are where the design quality shows.
SECTION 7 — RECONCILIATION (THE SAFETY NET)
Nightly/continuous job:
-
fetch provider payment state for orders in ambiguous states
-
compare to internal state
-
auto-heal:
-
if provider says CAPTURED but order not PAID → mark PAID + trigger fulfillment
-
if internal says PAID but provider says FAILED → flag + support workflow
-
Track metrics:
-
mismatch count
-
auto-healed count
SECTION 8 — FRONTEND BEHAVIOR (USER-VISIBLE CORRECTNESS)
-
Disable “Pay” button on submit.
-
Persist checkout attempt ID.
-
On refresh, rehydrate from
GET /orders/{id}. -
If payment processing is async, show “processing” state (don’t claim success).
SECTION 9 — FAILURE MODES
-
User retries on timeout → idempotency key prevents double order/payment.
-
Webhook delivered twice → event dedupe prevents double transition.
-
Partial outage in fulfillment → saga retries + DLQ.
-
Provider returns success but UI crashed → order page shows truth.
SECTION 10 — OBSERVABILITY
-
conversion funnel: start -> payment initiated -> paid -> fulfilled
-
webhook latency + failures
-
idempotency collisions
-
reconciliation mismatch alerts
SECTION 11 — EXERCISES
-
Write your order and payment state machines.
-
Define idempotency behavior for
start checkout. -
Design webhook dedupe + state transition rules.
-
Write a reconciliation plan and what it auto-heals.