Bonus engine
Pět kanonických typů bonusů s wagering pravidly + bidirectional adapter contract pro casino-platform integraci.
Typy
| Type | Účel | Povinný config |
|---|---|---|
free_spins | N free spins na slot listu | count, value_per_spin, currency, eligible_games[] |
deposit_match | Match X% next depositu hráče, capped | match_percent, cap_amount, currency |
no_deposit | Standalone bonus credit | amount, currency |
cashback | % ztrát vrácených v okně | percent, window_days, min_loss |
reload | Match deposit ale pro re-deposity | stejné jako deposit_match |
Všechny typy podporují:
- Wagering requirement —
wagering_requirement_multiplier× bonus částka musí být wagered před výběrem - Wagering basis —
bonus(jen bonus) nebobonus_plus_deposit(deposit_match typy) - Game contribution — slots 100% / table 10% / live 5% / atd. — váhy bet_amount na wagering progress
- Max bet — sázka přesahující toto během wageringu propadne bonus
- Time limit — auto-expiry po N hodinách
- Anti-stacking —
max_grants_per_playerbrání multi-claim - Max win cap — celkové výhry z bonusu capped
Lifecycle
active — vystaveno, wagering in progress
completed — wagering requirement splněn → bonus + winnings released
forfeited — max_bet violation NEBO fraud / admin override
expired — time_limit_hours překročen
cancelled — admin cancelled NEBO adapter cancellation webhook
Forward-only state machine. Jakmile terminální, řádek je immutable.
Issuance flow
1. operator/journey/campaign volá grantBonus(input)
2. engine resolvne adapter (per-tenant adapter_configs)
3. engine validuje anti-stacking + spočítá wagering_required
4. adapter.bonus.grantBonus() — synchronní HTTP na platformu
5. on success — INSERT bonus_grants s bonus_id platformy
6. (později, async) bonus.credited webhook dorazí → activated_at set
Pokud adapter call selže:
PLATFORM_REJECTED(4xx) → vrátíADAPTER_REJECTEDcalleroviPLATFORM_UNAVAILABLE(5xx / network) → vrátíADAPTER_UNAVAILABLE; retry zařazen
Multi-currency aware
Každý bonus řádek ukládá DUAL: bonus_amount + bonus_currency (wallet měna hráče v issue) + bonus_amount_base + base_currency_code + fx_rate_locked + fx_locked_at. Reporty query bonus_amount_base; player view zobrazí native amount.
Webhook lifecycle (inbound)
POST /v1/adapters/:slug/webhooks/bonus-events akceptuje:
| Event | Effect na bonus_grants |
|---|---|
bonus.credited | nastaví activated_at = ts (žádná status change — už 'active') |
bonus.wagered | bumpne wagering_progress + total_wagered o částky v data.wagered_amount_subunit + data.weighted_progress_subunit |
bonus.completed | status: completed, completed_at = ts |
bonus.expired | status: expired, expired_at = ts |
bonus.cancelled | status: cancelled, cancelled_at = ts, outcome_reason = data.reason |
bonus.forfeited | status: forfeited, forfeited_at = ts, outcome_reason = data.reason |
Idempotentní na (adapter_slug, event_id) přes webhook_events_processed.
Reconciliation
pnpm --filter @ucrm/api reconcile:bonuses běží denně — projde aktivní granty, ptá se každého adapteru na current platform view, replayne missing terminal transitions přes stejný state-sync pipeline. Chytá webhook drops + race conditions.
Bonus campaigns
Jako email campaigns ale pro bonusy: vyber (segment + bonus_template) → fan-out přes worker. Anti-stacking checked per-player at issue. Status: draft → issuing → issued. Stats: {pending, granted, skipped, failed}.