When the advertising service discards what a promotion paid for (e.g. a
captured billboard deleted while still pending approval), the promotion
money sitting in the advertising transit wallet needs to go back to the
promotions credit wallet. The promotion itself stays consumed -- only the
money is returned -- and only payouts that landed in the transit wallet
are reversible (a payout straight to the user's wallet is the user's).
- Promotion.rolled_back_at + rollback_to_credit(): row-locked, CAS-stamped,
idempotent transfer transit -> credit for SUCCESS/transit-destined payouts.
- PromotionRollback model: keyed (user, event_label) -- all the advertising
side knows. Handles the async race (payout runs in a Celery task, so the
Promotion may not exist yet): Recipient.promote() checks for an unsettled
request before paying (suppresses the payout) and after (reverses a
payout that landed mid-request).
- POST .../event/<event_label>/rollback/ -> {status: reversed|deferred|nothing, amount}.
- migration 0011 (hand-written; verified via makemigrations --dry-run + check).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| gooyal_oauth2 | ||
| promotions | ||
| users | ||
| __init__.py | ||