Apply payout-status webhooks to the available balance
Python · Python · intermediate · modification
Migrates payout-status handling from the old paid/failed if-chain to a `STATUS_DELTAS` dispatch table covering the provider's full documented status set, with a safe no-op default so an unexpected webhook can't crash the worker. Verified against the provider's webhook fixtures.
balance_delta runs on every payout webhook the billing worker consumes; its return value is applied directly to the user's spendable balance, so a wrong delta is a real money discrepancy on the account.
Requirements
- The payments provider sends a payout status webhook whose `status` is one of its five documented values: `pending`, `paid`, `failed`, `returned`, `canceled`. Produce the correct balance delta for every one of them.
- A payout debits the user's available balance the moment it is initiated. For a status that means the payout did NOT complete — `failed`, `returned`, and `canceled` — credit the full amount back (delta = amount_cents). For `pending` and `paid` the balance already reflects reality, so the delta is 0.
- A status outside the documented set means our integration is out of date: raise on it (fail loudly) rather than applying a silent zero delta.
Files touched
- app/billing/payout_status.py
--- app/billing/payout_status.py
-def balance_delta(payout: Payout) -> int:
- # A payout debits the available balance the moment it is initiated; if it
- # fails, we credit the amount back so the user isn't left short.
- if payout.status == "failed":
- return payout.amount_cents
+def _credit_full(payout: Payout) -> int:
+ # Payout never reached the bank: return the debited amount to the balance.
+ return payout.amount_cents
+
+
+def _no_movement(payout: Payout) -> int:
+ # Balance already reflects this state; nothing to apply.
return 0
+
+# Provider payout statuses -> the delta to apply to the user's available balance.
+STATUS_DELTAS = {
+ "pending": _no_movement,
+ "paid": _no_movement,
+ "failed": _credit_full,
+ "canceled": _credit_full,
+}
+
+
+def balance_delta(payout: Payout) -> int:
+ handler = STATUS_DELTAS.get(payout.status, _no_movement)
+ return handler(payout)