</> MAANG.io

Expert

Design landmark systems in depth, from Dynamo, GFS, Bigtable and Kafka to payments and ads pipelines.

0 of 232 solved 0%

Section 4: An Authorization Is a Promise, Not Money - Payment & Order Lifecycle Deep Dive

Executive Summary: Section 3 ended with Maya's checkout returning authorized, but no money has moved yet. This section follows ord_7Q2 from that authorization to two per-seller captures at shipment, each of which triggers a ledger split. We model the PaymentIntent as an explicit state machine, defend authorize-at-checkout and capture-at-shipment (D2) along with its fallback when multicapture isn't available, put the authorization's expiry on the calendar, and then spend most of our time on the moment that separates Staff answers from Senior ones: the lamp's $80 capture call times out, and nobody knows whether the money moved.

The interviewer points at the 200 OK on the whiteboard. "Maya got a success screen. Has the marketplace been paid?" No. An authorization is the issuer's promise to hold $120 against Maya's credit line. Money moves only on capture, and in a marketplace selling physical goods, capture waits for something the payment system doesn't control: a seller shipping a box.

The Lifecycle Is a State Machine

You draw the states before any arrows between services, because every bug in this area is an illegal transition: a capture on a voided intent, or a second capture after the final one. Here is the PaymentIntent lifecycle:

Payment intent state machine: requires_confirmation to processing, an optional 3-D Secure detour through requires_action, then authorized or failed, with partial and full capture, cancel by void, and reauthorization before the auth expires that ends authorized again or expired

Transitions are enforced in the database with a compare-and-set on (payment_id, status), never in application memory. Captures, though, aren't states of the intent. Each seller sub-order gets its own capture record, because one cart produces 1.5 captures on average, each with its own outcome:

CREATE TABLE capture (
  capture_id    text   PRIMARY KEY,
  payment_id    text   NOT NULL,          -- pi_7Q2
  sub_order_id  text   NOT NULL UNIQUE,   -- one capture per sub-order
  seller_id     text   NOT NULL,          -- routes the ledger post to a shard
  amount_minor  bigint NOT NULL,          -- 8000
  refunded_minor bigint NOT NULL DEFAULT 0, -- running refund total, capped at amount_minor
  psp_key       text   NOT NULL UNIQUE,   -- 'cap:pi_7Q2:1'
  psp_ref       text   UNIQUE,            -- PSP capture id, set on confirmation
  status        text   NOT NULL           -- pending | unknown | succeeded | failed
);

Two unique constraints carry the invariants: a sub-order can't be captured twice, and a PSP idempotency key can't be reused for different work.

3-D Secure in one paragraph. When the issuer wants strong authentication, the PSP answers the authorization with a challenge. We return requires_action with the PSP's next-action payload, the buyer completes the challenge inside the PSP's SDK, and confirmation resumes. These round trips are part of why the numbers ledger counts 1.25 authorization attempts per order, and why the 1.5 s p99 SLO is measured per API call, not across the minutes a buyer may spend in their banking app.

Sign in to continue reading

The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.

Sign in to read the full lesson

Sign in to MAANG.io

Use your Google or Microsoft account — no password to remember.

Continue with Google Continue with Microsoft

Please accept the terms above to continue.