跳到主要内容

ADR-008: Coupon Saga Rolling Contract

Status

Accepted

Date

2026-04-27

Owners

  • Platform Backend
  • Promotion Domain
  • Wallet Domain
  • Rolling Domain

Affected Services

  • promotion_service
  • wallet_service
  • rolling_service
  • docs/services/promotion-service.md
  • docs/services/wallet-service.md
  • docs/services/rolling-service.md
  • docs/specs/wallet/2026-04-23-ruby-wallet-split-structure.md

Context

Coupon activation is a cross-service saga:

  • promotion_service creates or activates a coupon log.
  • wallet_service creates a topology coupon grant.
  • rolling_service creates the rolling requirement.
  • wallet_service later 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.remarks and rolling creation reference_id include coupon_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_id as a first-class nullable column on player_rolling in a future migration.
  • Backfill active coupon rollings before switching wallet completion to the new column.