Requirements checklist
□ Gateway timeout semantics — ask early: outcome is UNKNOWN, the charge may or may not have happened (the round's pivot) □ Interface: createIntent / confirm / status / refund (full + partial); several currencies, handled fine in v1 □ Duplicates: double-clicks AND client retries must be safe; gateway accepts an idempotency key on charges □ Records: money history never edited — corrections are new entries, never changed ones □ Retries of unknowns: allowed only when provably safe □ Volume: thousands/day — correctness dominates throughput
The core model
PaymentState CREATED -> CONFIRMING -> SUCCEEDED | FAILED
-> UNKNOWN (timeout/crash)
UNKNOWN exits ONLY via reconciliation — no guessing
Idempotency key client-supplied, fixed on the INTENT (not the
request); create replays return the existing
intent; every charge attempt carries the key
Ledger append-only, separate from mutable state;
appended exactly at settling moments;
corrections = compensating entries
Gateway port charge(key, amount, timeout) + lookup(key) —
lookup is the reconciler's whole toolkit
Reconciler sweeps UNKNOWNs, lookup, settle via the SAME
settle() as the happy path; no answer -> stays
UNKNOWN
Refund own machine, references original, validates
against refundable remainder
Money integer minor units, never floats
Principles demonstrated (name them at the decision)
- Single responsibility — the state machine is the sole authority on legal transitions; UNKNOWN's only exit is reconciliation.
- Separation of concerns — mutable state answers "where is it now"; the append-only ledger answers "what has ever happened"; different masters, different change rules.
- Dependency inversion — the gateway behind a port with a timeout-capable stub; the reconciler testable without a network.
- Open/closed with judgment — a fraud-check socket between confirm and the charge call; policy grows without touching callers.
Complexity facts
all operations O(1) local hash-map work + one gateway call;
the gateway's 30+ s worst case dominates — never
loop on it synchronously
reconciler O(u) in unknown payments per sweep; cost bounded
by gateway calls, so pace it
the argument correctness dominates throughput at
thousands/day — no caching or sharding talk
What earns points, per report dimension
- Requirements & interface — asked what a timeout means unprompted, and let the answer visibly reshape the design; pinned duplicate semantics and the never-edit rule.
- Core design & invariants — UNKNOWN named as a state, not an error; keys anchored to the intent with the why; ledger appends exactly at settling moments; refunds validated against the remainder.
- Extension probe — place the new requirement on an existing discipline (a state, an entry type, a port) and say which invariants are untouched.
- Complexity honesty — the gateway call as the dominating cost; the reconciler's pacing argument said aloud.
- Communication — the timeout catch block narrated ("this is the whole problem"); rejected alternatives (timeout-as-failure, sync retry loops, one record type) named with reasons.
Ready? Sit the live mock → — the interviewer will run a twist this chapter deliberately hasn't shown you.