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_spins | N free spins на slot list | count, value_per_spin, currency, eligible_games[] |
deposit_match | Match X% наступного депозиту гравця, capped | match_percent, cap_amount, currency |
no_deposit | Standalone bonus credit | amount, currency |
cashback | % втрат повернутих у window | percent, window_days, min_loss |
reload | Match deposit але для re-deposits | те саме, що deposit_match |
Усі типи підтримують:
- Wagering requirement —
wagering_requirement_multiplier× bonus сума має бути wagered перед withdrawal - Wagering basis —
bonus(ті льки 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-stacking —
max_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_REJECTEDcaller-у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.credited | sets activated_at = ts (no status change — вже 'active') |
bonus.wagered | bumps wagering_progress + total_wagered на amounts в 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 |
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}.