Escrow as an Internal Ledger, Not Held Cash
Because Balinyaar cannot custody buyer funds (§2.2), "escrow" must be a software construct: a double-entry ledger STATE over money that legally sits at a licensed provider/bank. The original ~45-table model had no ledger — escrow was only inferable by joining bookings.status, bookings.payout_released, payment_transactions.status, and refunds, with no single answer to "how much do we owe nurses right now?" Three independent critiques rated this a critical gap. The fix is one append-only table.
The ledger entity is detailed in the data model: see payments, ledger & refunds.
3.1 ledger_entries — the financial source of truth
Append-only, never updated or deleted. Every money event posts balanced rows sharing a transaction_group_id (Σ debit = Σ credit). Per-nurse balances are derived by filter, never cached in a drifting wallet-balance column.
Account types:
| account_type | Meaning |
|---|---|
escrow_held | Funds received and held (over provider custody) not yet released or refunded |
platform_revenue | Balinyaar's own commission income |
nurse_payable | What the platform owes the nurse (accrued, awaiting weekly payout) |
refund_payable | Amount owed back to the customer / in-flight reversal |
bnpl_fee_expense | The BNPL provider's merchant commission — a platform expense |
nurse_clawback_receivable | Money a nurse owes back after a refund-after-payout |
3.2 The postings
Amounts are positive; direction carries the sign. The three-amount split (gross_price_irr, balinyaar_commission_irr, bnpl_commission_irr) is defined in §7.
(a) Card payment capture (inbound):
DEBIT escrow_held gross_price_irr
CREDIT platform_revenue balinyaar_commission_irr
CREDIT nurse_payable nurse_payout_amount (= gross − balinyaar_commission)
(b) BNPL settle (inbound) — identical to a card capture, plus the provider-fee leg:
DEBIT escrow_held gross_price_irr
CREDIT platform_revenue balinyaar_commission_irr
CREDIT nurse_payable nurse_payout_amount
DEBIT bnpl_fee_expense bnpl_commission_irr
CREDIT escrow_held bnpl_commission_irr (escrow reflects NET cash actually received)
Posted once, idempotently, keyed on the settling transaction. No installment-level postings — the customer's repayment schedule is SnappPay's ledger, not ours.
(c) Refund — BEFORE the nurse is paid out (clean reversal):
DEBIT platform_revenue platform_fee_refunded_irr
DEBIT nurse_payable nurse_payout_refunded_irr
CREDIT refund_payable (sum)
Clear refund_payable when the PSP / SnappPay confirms the customer cash-back. Nothing leaves Balinyaar toward the nurse — the nurse_payable accrual is simply reversed.
(d) Clawback — refund AFTER the nurse was already paid: The nurse's nurse_payable was already drained by a processed payout batch, so there is nothing left to reverse. Instead the platform books a receivable:
DEBIT nurse_clawback_receivable amount_irr (nurse_id set; nurse now owes the platform)
CREDIT refund_payable amount_irr
Recovered by netting against the nurse's next nurse_payable at batch time, or marked written_off if uncollectable. A nurse_clawbacks row carries the lifecycle (pending / recovered / written_off). This is unavoidable because Iranian payouts are real bank transfers — hard/impossible to reverse — so the right defense is gating payout on the dispute window, with clawback as the fallback.
3.3 Why the ledger, not more columns
A marketplace that holds escrow, pays out weekly minus commission, and handles refunds + clawbacks has exactly the shape double-entry was invented for. The MVP cost is one table + posting discipline. The alternative (more money columns on bookings/payouts) cannot answer "how much is held but unreleased" without fragile joins and makes bank/Shaparak reconciliation nearly impossible. Keep the per-booking fee snapshot as the pricing record; the ledger is the financial-truth / reconciliation layer posted alongside.
↑ Back to top