</> 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 7: When Money Flows Backwards - Refunds, Disputes & Risk Controls

Executive Summary: Sections 4 through 6 moved money forward. This section runs it in reverse. A refund posts different entries depending on how far the money has travelled, and Maya's $40 tea-set refund leaves the platform $1.26 down because the PSP keeps its fee. A chargeback on the $80 lamp lands after Lantern & Loom has been paid, so the seller's balance goes negative and we recover it through a fixed waterfall. Risk controls bound this exposure without slowing checkout: a 50 ms score, velocity limits, a review before a seller's first payouts, and rolling reserves.

The interviewer crosses out the arrow from the platform to the seller's bank and draws it pointing the other way. "Maya returns the tea set. A month later their bank disputes the lamp. Who pays, and how do your books show it?" There are only 450k refunds and 10k disputes a day against 10M orders, but this is where the money is lost.

Refunds: The Entry Depends on Where the Money Is

A refund first checks its cap: refunds so far plus this one must not exceed what was captured for that seller sub-order. Those totals live on the capture record in the Payments DB, sharded by payment_id, not in the ledger. So the cap check runs there, under a row lock on the capture record (refunded_minor + amount ≤ amount_minor, with refunded_minor a running total kept on that row), and inserts the refund as pending. The ledger posting then commits separately on the seller's shard (D5), keyed by ref:rf_T1, before the PSP call. No transaction spans both databases; a retry after a crash between them re-posts under the same external_ref and is a no-op. Then timing picks the entry, as a walk down the money's path:

Refund decision tree: reject if the amount exceeds captured minus refunded; if not yet captured, release that part of the auth with no postings; if captured but funds not released, debit seller_pending; if released, debit seller_available, and send a balance that drops below zero to negative-balance recovery.

  • Before capture. Nothing has moved, so nothing posts. That portion is never captured, and we release it on the authorization where the PSP supports it.
  • After capture, before release. Debit seller_pending. This is Maya's tea set: it shipped on day 2, came back before release, and Copperleaf's $35.20 is still pending.
  • After release or after payout. Debit seller_available. If the money already left in a payout, the balance goes negative, and that is allowed on purpose.

Here is the tea-set refund on Copperleaf's shard k':

txn refund rf_T1 (shard k', kind=refund, external_ref=ref:rf_T1)
  Dr seller_pending:copperleaf      35.20
  Dr commission_revenue@k'           4.80
  Cr psp_receivable@k'              40.00
  debits 40.00 = credits 40.00

Copperleaf is back to zero on this sale, and Maya gets $40. The capture had already booked the $1.26 processing fee to processing_fee_expense@k', and the PSP keeps that fee on a refund. So with the commission given up and the fee gone, the platform's net on the tea set is −$1.26.

Interviewer: "Why not just post the reversal of the original capture transaction? Cleaner."

You: "A refund isn't the capture being undone. It's a new event with its own date, often a partial amount, and its own policy. A mirror image of the capture would also reverse the $1.26 fee, so our books would say the fee is gone while the PSP's settlement report still shows it, and reconciliation would break on every refund. Reversing entries are for our mistakes (D3)."

The refund follows the payout's discipline (D11): its transaction commits before the PSP call, so tonight's payout batch can't also send that $35.20 to the seller. The PSP key derives from refund_id, a definite rejection posts a compensating entry that returns the $35.20 to seller_pending and restores the commission, and an unknown outcome waits for a status lookup before any retry.

The Pushback: "If the seller's balance can't cover it, block the refund." No. The buyer is owed it either way, and a chargeback will collect it if we refuse. The balance goes negative, the platform carries the receivable, and recovery is our problem, not Maya's.

Quick check before moving on:


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.