Evaluation
In one line: the non-functional table lists redundancy, load balancing, caching, and autoscaling — an accurate description of a web service and a poor description of what makes a payment system correct.
Functional requirements
| Requirement | Stated mechanism | Assessment |
|---|---|---|
| Registration and auth | Handled by the merchant's online store; authenticated requests forwarded to the payment service | ✅ Correct scoping — and worth saying out loud |
| Payment processing | Payment service with PSP, fraud detection, reconciliation | ⚠️ Authorization only — no capture, no settlement (Lesson 1) |
| Transaction history | The ledger stores immutable logs — ID, amount, currency, parties, status, timestamp | 🔴 The schema's ledger has none of those fields except ID and date (Lesson 3) |
| Balance management | The wallet maintains balances; ledger provides auditable history | 🔴 Mutable balance, no version, no derivation (Lessons 3, 5) |
| Mobile accessibility | Responsive merchant UI | ✅ |
Pushing authentication to the merchant is the right boundary, and stating it is worth credit
"Handled by the merchant's online store. It provides secure login, session management, and user identity validation. Authenticated requests forwarded to the payment service."
This looks like the design dodging a requirement. It is actually the correct architecture, and drawing the boundary explicitly is a good habit.
The customer has a relationship with the merchant, not with the payment processor. They log into the store; the store proves who they are; the payment service trusts that assertion for the request. A payment service that maintained its own customer accounts would be duplicating identity that already exists somewhere better.
The system boundary is a design decision, and naming what is outside it is as valuable as naming what is inside. The chapter's conclusion does this again — "entities such as banks and merchants are external systems outside the system boundary" — which is honest and unusual.
The unstated half is what the payment service must verify about that forwarded assertion. A signed token with an audience, an expiry, and the merchant's identity — not a bare user ID it trusts because the request arrived. Trusting an upstream identity assertion requires verifying it, and the design does not say how.
The transaction-history row describes a ledger the schema does not contain
"The ledger stores immutable transaction logs, including details such as ID, amount, currency, involved parties, status, and timestamp."
That is an accurate description of a proper ledger entry, and it is not what the schema defines:
| Compliance table claims | Schema actually has |
|---|---|
| ID | ✅ transaction_id |
| amount | 🔴 absent |
| currency | 🔴 absent |
| involved parties | 🔴 absent — only token_id |
| status | 🔴 absent |
| timestamp | ⚠️ transaction_date (a DATE, not a timestamp) |
Four of six claimed fields do not exist. The compliance table describes the ledger from Lesson 5 — the one the chapter should have designed — while the schema describes an audit log.
This is the recurring pattern in the module's compliance tables and its sharpest instance: the table records what the design should contain rather than what it does. The previous chapter claimed disaster recovery its prose disclaimed; here the claim is contradicted by a diagram in the same lesson.
Check a compliance table against the schema, not against the prose.
Non-functional requirements
| Requirement | Stated mechanism | Assessment |
|---|---|---|
| Data integrity and security | PCI DSS, encryption, secure storage | 🔴 Named, not designed — the schema stores the PAN in plaintext (Lesson 3) |
| Reliability and availability | Redundant services, cross-DC replication, local and global load balancers | ⚠️ Generic — and it never states the CAP choice payments must make |
| Scalability | Scale databases, cache frequently accessed data, load balancing, autoscaling | 🔴 Caching balances is wrong; and scale is not this system's problem |
| Performance | Async processing, sufficient bandwidth, retry/timeout/fallback | ⚠️ Async is right; fallback fails open (Lesson 8) |
The design never states the consistency choice, and payments is the one domain that chooses differently
The availability row lists redundancy, replication across data centres, and global load balancers redirecting traffic during regional failures. All standard, all correct for a web service, and none of it addresses the question a payment system must answer.
What happens during a network partition?
A region is cut off from the primary datastore.
ACCEPT payments locally -> available, but you cannot verify balances
or check for duplicates. Double-spend risk.
REFUSE payments -> unavailable, and correct.
Almost every system in this module chose availability. Payments chooses consistency, and that is the notable, defensible, interview-worthy decision the design never makes explicitly:
Declining a payment is recoverable. The customer retries in a minute. It costs a sale and some goodwill.
Double-charging or double-spending is not. It requires refunds, disputes, possibly regulatory reporting, and it damages trust permanently.
When the cost of being wrong exceeds the cost of being unavailable, choose consistency — and payments is one of the few domains where that inequality genuinely holds.
There is a nuance worth adding, because "always choose C" is too blunt: the two phases have different tolerances. Authorization must be strongly consistent — it decides whether funds exist. Settlement and reconciliation are already batch processes and can tolerate partition and catch up. So the correct answer is per-phase, and it is another dividend of the two-phase model this design collapsed.
'Cache frequently accessed data' is the wrong instinct in this domain
The scalability row is a generic web-service checklist, and one entry is actively wrong.
You do not cache balances. A stale balance is a payment authorized against money that is no longer there. The whole point of the balance check is that it reflects reality at the instant of the decision:
Balance cached at t=0: $100
Payment at t=1 for $80: approved against the cache
Payment at t=2 for $80: approved against the SAME cache
-> $160 authorized against $100
That is the read-modify-write hazard from Lesson 3 with a cache layer making it worse and harder to see.
What is safe to cache here is narrow and worth knowing: merchant configuration, currency and fee tables, risk scores for known customers within a staleness bound, and completed transaction history — which is immutable, so it can be cached indefinitely.
Cache what is immutable or advisory; never cache what a correctness decision is made against.
And the broader point: scalability is not this system's hard problem. Per Lesson 2, peak load is 1,400 TPS, which is one machine. Listing autoscaling and load balancing as the scalability answer describes a system whose constraint is throughput — and this system's constraints are correctness, external dependency latency, and durable ordered writes.
What the evaluation should have said
Five rows that would actually characterize this design:
| Requirement | The mechanism that delivers it |
|---|---|
| No duplicate charges | Idempotency keys with a UNIQUE constraint at every mutating interface — client, gateway, and consumer |
| Books always balance | Double-entry ledger; SUM(entries) == 0 asserted continuously |
| No lost payments | At-least-once delivery with idempotent consumers; dead-letter queue for the unresolvable |
| Detect what all of the above miss | Daily settlement reconciliation against an externally produced record |
| Survive opaque dependencies | Backoff with jitter, circuit breakers, fail-closed security controls, and multi-PSP failover |
Every one of those is specific, testable, and could not be copied onto a different system — which is the test a compliance table should pass and the module's tables routinely fail.
What the evaluation omits
Five things a strong answer adds, each covered earlier:
Settlement. Authorization is implemented; capture, batching, and settlement-file processing are not — and capturePayment is never called (Lesson 1).
Duplicate prevention at the consumer. Idempotency is applied to the client and not to at-least-once redelivery, where duplicates are far more common (Lesson 7).
The dual write. Wallet and ledger are separate databases with nothing making them atomic — generating exactly the corruption reconciliation is meant to catch (Lesson 7).
Card data handling. PCI DSS is named while the schema stores the PAN in plaintext and the API accepts CVV (Lesson 3).
Authorization expiry. Holds lapse, and nothing sweeps them — which is the answer to the chapter's own unanswered question (Lessons 1, 3).
The ledger is the component doing the most work here, because it is the only one that lets you answer a question about the past rather than just report the present. Balances derived from an immutable log can be recomputed and audited; a mutable balance column can only be trusted.
Key takeaway
The compliance tables describe a generic distributed system on the one design in the module where that is least appropriate. The transaction-history row claims six ledger fields of which four do not exist in the schema — check a compliance table against the schema, not the prose. The availability row never states the choice payments must make: when the cost of being wrong exceeds the cost of being unavailable, choose consistency, and here that is per-phase, since authorization needs strong consistency while settlement is already batch. Caching balances is actively wrong — cache what is immutable or advisory, never what a correctness decision is made against — and scalability is not this system's hard problem at 1,400 TPS. What the evaluation should list instead is specific and testable: idempotency at every mutating interface, a balancing ledger invariant, idempotent consumers with a dead-letter queue, external reconciliation, and fail-closed controls.
Next: the interview walkthrough.