System Design Interview: Design a Payment Service
A payment service design: 1) Checkout Service (initializes with gateway, returns auth URL), 2) Webhook Processor (validates HMAC, deduplicates by reference, dispatches to Order Service), 3) Order Service (updates order status, triggers fulfillment), 4) Idempotency Store (Redis or DB unique constraint on reference), 5) Settlement Reconciler (daily job reconciling DB with gateway statements). Key decisions: how do you handle duplicate webhooks? What happens on DB failure during fulfillment? How do you verify a payment if the webhook never arrives?
Step 1: Clarifying Questions
Before drawing anything, ask these:
- "What countries and payment methods are we supporting?"
- "What is the expected transaction volume — transactions per second?"
- "Are we building for a marketplace (split payments) or a direct merchant?"
- "Do we need recurring/subscription payments or one-time only?"
- "What are the consistency requirements — can we tolerate eventual consistency or do we need strong consistency for order status?"
Taking 2-3 minutes to ask these before touching the whiteboard signals seniority. Junior candidates jump to drawing. Senior candidates clarify.
Step 2: Core Components
Client Browser
|
| POST /checkout/start
v
Checkout Service ──── POST /transaction/initialize ──→ Paystack API
| |
| returns authorization_url | webhook
| v
Customer Browser ←── redirect to Paystack checkout Webhook Receiver
| |
| (after payment) redirect to callback URL | validate HMAC
| | deduplicate on reference
v v
Callback Handler Order Service
| |
| GET /transaction/verify/:ref | update orders SET status='paid'
v | trigger fulfillment
Paystack API v
Fulfillment Service
(email, access grant, etc.)
Key database tables: orders (reference, amount, status, customer_id, created_at, paid_at), webhook_events (event_id, reference, event_type, processed_at) — the second table is your idempotency store.
Step 3: Failure Modes
Your interviewer wants to hear you think about failure:
- Webhook never arrives — the callback redirect triggers verification. Both paths call the same fulfillment logic through the same idempotency check. Neither path double-fulfills.
- DB write fails after Paystack confirms — use a database transaction: update order status and insert webhook event in one atomic operation. If the transaction fails, the webhook will be retried by Paystack and succeed on the next attempt (idempotency store is checked first).
- Duplicate webhooks — insert into webhook_events with a UNIQUE constraint on (reference, event_type). Duplicate insert → constraint violation → return 200 → skip fulfillment.
- Paystack API is down — checkout initialization fails gracefully with a user-friendly error. No partial orders. Retry from client.
Learn More
See system design: payout system for the payout-specific version.
Key Takeaways
- ✓Start by clarifying requirements: countries, payment methods, volume, consistency requirements.
- ✓Separate concerns: checkout initialization, webhook processing, order fulfillment are three separate components.
- ✓Idempotency is the most important concept — explain it clearly and name the implementation.
- ✓Design for the failure case: webhook never arrives, DB write fails mid-transaction, gateway returns 5xx.
- ✓Mention settlement reconciliation — this shows you understand the full payment lifecycle.
Frequently Asked Questions
- Should I mention specific Paystack API endpoints in the interview?
- Yes, if the company uses Paystack (most Nigerian/East African fintechs do). Naming POST /transaction/initialize, GET /transaction/verify, and HMAC-SHA512 webhook validation shows you know the domain, not just abstract architecture. Generic architecture answers are fine for generic companies; specific answers are better for fintech companies.
- How do I handle the question "how does it scale to 1,000 TPS?"
- Webhook processor scales horizontally — multiple instances reading from a message queue (SQS, RabbitMQ, or Kafka). Checkout service scales horizontally (stateless). Order service needs careful DB connection pooling. Idempotency store moves from DB UNIQUE constraint to Redis SET NX for higher throughput. Settlement reconciler is a background job — no scaling concern for the real-time path.
- How long should this system design answer take?
- In a 45-60 minute interview, allocate: 5 minutes for clarifying questions, 10 minutes for component overview, 15 minutes for deep-dive on one or two components (webhook processing and idempotency are good choices), 10 minutes for failure handling, 5 minutes for scale considerations, 5 minutes for questions. Do not rush — interviewers prefer depth over breadth.
Ready to build real-world apps?
Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.
Apply to the McTaba Marathon