Skip to main content

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)

  1. An order has a single source of truth state machine.

  2. A payment_intent is idempotent per cart/attempt.

  3. Fulfillment happens only after confirmed payment state.

  4. 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:

  1. Order created (PENDING_PAYMENT)

  2. Payment captured (via webhook or polling)

  3. Enqueue fulfillment job

  4. 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

  1. Write your order and payment state machines.

  2. Define idempotency behavior for start checkout.

  3. Design webhook dedupe + state transition rules.

  4. Write a reconciliation plan and what it auto-heals.


🏁 END — PAYMENTS & CHECKOUT CASE STUDY