ADR-008: Coupon Saga Rolling Contract
Status
Accepted
Date
2026-04-27
Owners
- Platform Backend
- Promotion Domain
- Wallet Domain
- Rolling Domain
Affected Services
promotion_servicewallet_servicerolling_service
Related Docs
docs/services/promotion-service.mddocs/services/wallet-service.mddocs/services/rolling-service.mddocs/specs/wallet/2026-04-23-ruby-wallet-split-structure.md
Context
Coupon activation is a cross-service saga:
promotion_servicecreates or activates a coupon log.wallet_servicecreates a topology coupon grant.rolling_servicecreates the rolling requirement.wallet_servicelater releases completed coupon money to withdrawable funds.
The current schema does not have a first-class coupon_log_id column on
player_rolling, so rolling completion must recover the originating coupon log
through stable references.
Decision
Until a first-class coupon_log_id column is introduced, the following string
contract is canonical:
- coupon credit request IDs are
coupon-credit-<coupon_log_id> - coupon rolling request IDs are
coupon-rolling-<coupon_log_id> - coupon reverse request IDs are
coupon-reverse-<coupon_log_id> player_rolling.remarksand rolling creationreference_idincludecoupon_log:<coupon_log_id>
promotion_service.app.services.coupon_saga.build_saga_request_ids() is the
source of truth for request ID formatting. Wallet coupon rolling completion must
fail loudly when the matching coupon grant cannot be found.
Consequences
Positive:
- retries are deterministic and idempotent across promotion, wallet, and rolling
- wallet completion can identify the original coupon grant without trusting a caller-supplied amount source
- missing or drifted references surface as errors instead of silently releasing no funds
Negative:
- the system relies on stable string formats until the schema is upgraded
- future refactors must update promotion, wallet, rolling, and tests together
Constraints
- Do not change coupon saga request ID formats without updating this ADR, tests, wallet completion lookup, and recovery logic.
- Do not parse arbitrary free-form remarks except for the exact
coupon_log:<id>token. - Coupon rolling completion must not fall back to unrelated active rollings or generic coupon balances.
Follow-Up
- Add
coupon_log_idas a first-class nullable column onplayer_rollingin a future migration. - Backfill active coupon rollings before switching wallet completion to the new column.