The promotions service had no record of the wallet transfers it makes -- the Promotion row's state was the only trace. Add PromotionTransaction, a ledger row per transfer (mirrors advertising's AdPayment / escrow's EscrowWalletPayment): PAYOUT (promotions credit -> recipient wallet) and ROLLBACK (advertising transit -> promotions credit). - Promotion.promote() now drives its payout through a PromotionTransaction PAYOUT row (.execute() does the submit/verify dance) instead of an inline, unrecorded wallet call. - rollback_promotion_payout(user, event_label): reverses a payout that landed in the advertising transit wallet, back to the promotions credit wallet, when the advertising side discards what it paid for (e.g. a captured billboard deleted while pending approval). The Promotion stays consumed -- only the money moves; payouts straight to the user's wallet are not reversible. Idempotent; returns reversed | deferred | nothing. - The ROLLBACK row doubles as the async-race marker: when the request arrives before the Celery payout task has run, a ROLLBACK row is recorded and Recipient.promote() suppresses (or, if it raced, reverses) the payout. Replaces the separate PromotionRollback table from the first cut of this change. - POST .../application/<user>/event/<event_label>/rollback/ - migration 0011 (hand-written; verified via makemigrations --dry-run + check) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| 0001_initial.py | ||
| 0002_promotion_event_alter_plan_processor_and_more.py | ||
| 0003_alter_recipient_promotion_type.py | ||
| 0004_alter_promotion_state.py | ||
| 0005_alter_event_user.py | ||
| 0006_event_promotions_event_data_bc656abc_and_more.py | ||
| 0007_alter_event_options_plan_banner_and_more.py | ||
| 0008_plan_balance_holder_alter_plan_description_details.py | ||
| 0009_recipient_access_type_alloweduser.py | ||
| 0010_recipient_wallet_destination.py | ||
| 0011_promotiontransaction.py | ||
| __init__.py | ||