Stack guide
Django and Stripe Connect: views, signals, Celery tasks
Django’s signal machinery tempts developers toward implicit money movement — a post_save hook issuing refunds somewhere no reviewer thinks to look. Payment correctness demands the opposite: explicit views calling explicit services enqueueing explicit tasks. The stack-specific hazards here are signals-as-side-effects and Celery’s eager retry defaults meeting missing idempotency keys.
View/service split
Views validate and authorise; services execute Stripe calls with both flags and intent-scoped keys; nothing refund-related lives in save() paths. The grep audit applies: grep -rn "refunds.create" should surface services only.
Signals antipattern, named
post_save refund triggers couple money movement to unrelated state changes invisibly — status flips fire charges nobody associated. Every Django payments horror story involving surprise refunds traces here. Deprecate such signals ruthlessly.
Celery retries under keys
autoretry_for plus missing keys equals duplicate execution on broker redelivery. Derive keys from business intent inside task bodies, read live state before acting, persist responses onto the model for forensics.
DRF validation of invariants
Serializer-level assertions — amount_refunded ≤ amount, ratio ≤ 1 — catch upstream data anomalies before arithmetic amplifies them into impossible reversal requests.
Common questions
Async Python changes anything?
Same rules, different syntax — see the scripting guide for asyncio-flavoured reconciliation patterns.
Does this require a FeeGuard integration?
No — the page stands alone as stack guidance. FeeGuard observes your event stream externally rather than embedding in it.