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.