Design a payment flow

System design Senior Python 3.13+ PostgreSQL 17+ Reviewed By Best Latest Apps

Predict the output

A 10.00 EUR refund is split across 3 sellers in integer cents. What prints?

Python 3.13
total = 1000  # cents
shares = [total // 3] * 3
shares[0] += total - sum(shares)
print(shares, sum(shares))

Options
Hint 1 of 1

What is 1000 // 3, and what is left over?

Choose an answer, then open Show answer below to check it.

Boxes for client, payments API, Postgres with outbox, worker, payment provider and webhook handler; retry a timed-out charge safely.
Text version

Boxes on the board, in order:

  1. Client
  2. Payments API
  3. Postgres + outbox
  4. Worker
  5. Provider
  6. Webhook handler
Show answer

Answer: A [334, 333, 333] 1000

Correct. Floor division gives 333 each, and the leftover cent goes to the first share, so nothing is lost.

Integer cents can't be split three ways evenly, so someone must get the extra cent. Floor division gives 333 each, the 1 cent left over goes to the first share, and the total stays 1,000.

Why not the others?
  • B [333, 333, 333] 999. The third line adds the missing cent back before printing.
  • C [333.33, 333.33, 333.33] 999.99. // on integers gives integers; there are no fractions of a cent here.
  • D [334, 334, 334] 1002. Only the first share gets the remainder, and the remainder is 1.

30-second answer

Never touch raw card data: the client tokenises the card with the provider, and your backend creates a payment with that token. Every create call carries an idempotency key, so a timeout and retry can't charge twice. Store a payment record with a state machine (created, authorised, captured, failed, refunded), update it from the provider's signed webhooks, and reconcile daily against the provider's reports. Money is integer minor units with a currency code, never floats.

Practise saying it · 40 s. No recording, just a timer

Design payments.

Say your answer out loud, then check which of these points you covered:

  • Tokenise; no raw cards
  • Idempotency keys
  • State machine + webhooks
  • Reconcile
  • Integer minor units

Deep dive

Write the order and an outbox event in one transaction; a worker calls the provider.

Webhooks can arrive late, twice or out of order; make transitions idempotent and monotonic.

A double-entry ledger makes balances auditable.

Keep PCI scope small by letting the provider host card fields.

Common mistakes

  • Floats for money.
  • Retrying a charge without an idempotency key.
  • Trusting the client's 'payment succeeded' message.

Senior take & follow-ups

Tokenisation, idempotency keys, state machine fed by webhooks, reconciliation, integer money.

Follow-up questions

Think of your answer first, then open the hint.

  1. Why integers for money?

    Show hint

    Floats can't represent 0.1 exactly.

  2. What if the webhook never arrives?

    Show hint

    Poll the provider and reconcile.

Similar on iOS: A secure payment flow The iOS page builds the checkout on the device; here the server makes charges safe to retry.

Suggest a fix if something here is wrong or out of date.