Writing · 2026
Getting Money Right
How Parkzy moves real money without losing a cent: an IOU ledger, a two-phase commit against the double-pay race, refund clawbacks that survive webhook replays — and code that screams instead of lying.
The problem
A marketplace takes a driver's money and later pays the host — with cancellations, partial refunds, disputed charges, and Stripe webhooks that can arrive late, twice, or out of order in between. Money bugs are the worst kind: silent, compounding, and trust-destroying. Parkzy defers host payouts (hosts shouldn't have to clear full KYC before their first booking), which means the platform holds debts — and debts need a ledger, not a boolean.
The machinery
An IOU ledger, not a flag
Every deferred payout is a row: amount (checked positive), the payment intent it came from, the transfer that settles it, a settled-at stamp. A unique index per booking makes backfills idempotent, and row-level security allows no user writes at all — money rows are written only by service-role code.
Two-phase commit vs. the double-pay race
Phase 1 reserves the row by writing a placeholder transfer id, guarded on “transfer id is null, not settled, amount positive” — so two concurrent workers can't both claim it and a clawed-back row can't transfer. Phase 2 creates the Stripe transfer with a deterministic idempotency key. Phase 3 swaps in the real transfer id. Crash at any point and the retry reuses the same key — Stripe returns the original transfer instead of a second one.
Refund clawbacks that survive replays
When a refund lands out-of-band, the webhook atomically claims the clawback (a null-guarded stamp, so the webhook and the cancellation path can race and exactly one wins) and clamps the IOU to an absolute target — computed from what was refunded, not multiplied by a ratio — so Stripe redelivering the same webhook is a no-op instead of a double-clamp.
Scream, don't fabricate
If a transfer succeeds but the ledger update reports zero rows affected, the code refuses to count it as a settle: it logs “money moved with no ledger record” and demands manual reconciliation. The old code counted a fabricated success. The new code's comment says it plainly: if it ever happens, scream instead of lying.
Did it work?
An August 2026 audit reconciled every paid booking and cancellation directly against Stripe. After the fixes and backfills, a clean re-run reported zero dashboard-vs-Stripe mismatches on the pending set; 15 payouts totalling $803 settled within 48 hours of maturity; and of the IOUs still unsettled, every matured one was correctly unpayable (canceled booking, demo host, Connect not yet enabled). Honest caveat: that was a one-time audit, not yet a standing monitor — three reconciliation tools (charges, refunds, customers) run on demand, and a repeatable transfers-vs-dashboard report is on the backlog.
What I keep
Nothing here is exotic — it's the boring discipline applied everywhere at once: make illegal states unrepresentable, make every retry idempotent, make race winners explicit, and when reality and the ledger disagree, prefer the loud failure. Correctness with money isn't a feature; it's a posture.