Перейти до основного вмісту

Bonus engine

Translator note: machine-translated baseline. Needs human review by a native UK speaker. EN source is canonical.

П'ять канонічних типів бонусів з wagering правилами + bidirectional adapter contract для casino-platform інтеграції.

Типи

TypeПризначенняОбов'язковий config
free_spinsN free spins на slot listcount, value_per_spin, currency, eligible_games[]
deposit_matchMatch X% наступного депозиту гравця, cappedmatch_percent, cap_amount, currency
no_depositStandalone bonus creditamount, currency
cashback% втрат повернутих у windowpercent, window_days, min_loss
reloadMatch deposit але для re-depositsте саме, що deposit_match

Усі типи підтримують:

  • Wagering requirementwagering_requirement_multiplier × bonus сума має бути wagered перед withdrawal
  • Wagering basisbonus (тільки bonus) або bonus_plus_deposit (deposit_match типи)
  • Game contribution — slots 100% / table 10% / live 5% / тощо — ваги bet_amount до wagering progress
  • Max bet — ставка, що перевищує це під час wagering, forfeits бонус
  • Time limit — auto-expiry після N годин
  • Anti-stackingmax_grants_per_player запобігає multi-claim
  • Max win cap — total winnings з бонусу capped

Lifecycle

active — issued, wagering in progress
completed — wagering requirement met → bonus + winnings released
forfeited — max_bet violation АБО fraud / admin override
expired — time_limit_hours exceeded
cancelled — admin cancelled АБО adapter cancellation webhook

Forward-only state machine. Раз terminal, row immutable.

Issuance flow

1. operator/journey/campaign викликає grantBonus(input)
2. engine resolve-ить adapter (per-tenant adapter_configs)
3. engine validate-ить anti-stacking + обчислює wagering_required
4. adapter.bonus.grantBonus() — синхронне HTTP до платформи
5. on success — INSERT bonus_grants з bonus_id платформи
6. (пізніше, async) bonus.credited webhook надходить → activated_at set

Якщо adapter call fails:

  • PLATFORM_REJECTED (4xx) → повертає ADAPTER_REJECTED caller-у
  • PLATFORM_UNAVAILABLE (5xx / network) → повертає ADAPTER_UNAVAILABLE; retry queued

Multi-currency aware

Кожен bonus row зберігає DUAL: bonus_amount + bonus_currency (валюта гаманця гравця at issue) + bonus_amount_base + base_currency_code + fx_rate_locked + fx_locked_at. Reports query bonus_amount_base; player view показує native amount.

Webhook lifecycle (inbound)

POST /v1/adapters/:slug/webhooks/bonus-events приймає:

EventЕфект на bonus_grants
bonus.creditedsets activated_at = ts (no status change — вже 'active')
bonus.wageredbumps wagering_progress + total_wagered на amounts в 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

Idempotent on (adapter_slug, event_id) через webhook_events_processed.

Reconciliation

pnpm --filter @ucrm/api reconcile:bonuses запускається щодня — проходить активні grants, питає кожен adapter про current platform view, replay-ить пропущені terminal transitions через той самий state-sync pipeline. Catches webhook drops + race conditions.

Bonus campaigns

Як email campaigns але для bonus-ів: pick (segment + bonus_template) → fan-out через worker. Anti-stacking checked per-player at issue. Status: draft → issuing → issued. Stats: {pending, granted, skipped, failed}.