Přeskočit na hlavní obsah

Bonus engine

Pět kanonických typů bonusů s wagering pravidly + bidirectional adapter contract pro casino-platform integraci.

Typy

TypeÚčelPovinný config
free_spinsN free spins na slot listucount, value_per_spin, currency, eligible_games[]
deposit_matchMatch X% next depositu hráče, cappedmatch_percent, cap_amount, currency
no_depositStandalone bonus creditamount, currency
cashback% ztrát vrácených v okněpercent, window_days, min_loss
reloadMatch deposit ale pro re-depositystejné jako deposit_match

Všechny typy podporují:

  • Wagering requirementwagering_requirement_multiplier × bonus částka musí být wagered před výběrem
  • Wagering basisbonus (jen bonus) nebo bonus_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-stackingmax_grants_per_player brá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_REJECTED callerovi
  • PLATFORM_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:

EventEffect na bonus_grants
bonus.creditednastaví activated_at = ts (žádná status change — už 'active')
bonus.wageredbumpne wagering_progress + total_wagered o částky v data.wagered_amount_subunit + data.weighted_progress_subunit
bonus.completedstatus: completed, completed_at = ts
bonus.expiredstatus: expired, expired_at = ts
bonus.cancelledstatus: cancelled, cancelled_at = ts, outcome_reason = data.reason
bonus.forfeitedstatus: 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}.