Управління промо і бонусами на рівні backend
Повний текст статті
1) Навіщо виносити промо в окремий backend
Грошові інваріанти. Бонус ≠ «приплюсували баланс»: це контракт з умовами (вейджер, внесок по іграх, максимум ставки/виграшу).
Швидкість змін. Команди маркетингу випускають кампанії щодня - потрібен декларативний рушій правил і відкат.
Анти-аб'юз/комплаєнс. KYC/RG/AML, velocity, сегментація, таски «чотирьох очей» на дорогі оффери.
Спостережуваність і звітність. SLO, вартість промо, вплив на GGR/NGR/LTV.
Принцип: промо-ядро - окремий сервіс з власними статусними машинами, а гроші рухаються тільки через гаманець, ідемпотентно.
2) Типологія бонусів та інваріанти
Deposit match (100% до X): нараховується після capture депозиту, вейджер X ×.
Cashback (програш-бек): розраховується за вікном часу/ігор, може бути sticky/non-sticky.
Free Spins / Free Bets: купони/токени з прайсом за спін/ставку, фіксований RTP-пул.
Квести/місії: завдання → прогрес → нагорода.
Турніри/рейс-івенти: внесок подій, рейтинг, призові.
Інваріанти:- Sticky: не можна вивести до виконання умов.
- Max bet / Max win: ліміти на ставку/виплату з бонусних коштів.
- Contribution: внесок по іграх (наприклад, slots = 100%, live = 10%).
- Expiry: термін дії бонусу і вікна вейджера.
3) Архітектура сервісу бонусів
Admin (кампанії/правила) ─Promo API ─Rules Engine/Eligibility
│
├─Bonus Ledger (стан офферів)
├─Wagering Engine (прогрес)
├─Anti -Abuse (ліміти/фрод/velocity)
└─Outbox (events) ─Kafka/Pulsar ─BI/DWH/CRM
Wallet/Ledger── Idempotent Commands ───┘Rules Engine - декларативні умови (сегменти, гео/ліцензія, канали, KYC/RG).
4) Модель даних (спрощено)
`bonus_grant`
`wager_progress`- `grant_id, required_minor, contributed_minor, remaining_minor, last_update_at`
- `schema_id, rules: [{game_type:"slot", pct:100},{game_type:"live", pct:10}]`
'bonus _ ledger _ entry'( аудит)
5) Статусні машини і саги
5. 1 Видача (issue) - сага
1. eligibility. check (сегмент, RG/KYC, velocity)
2. grant. create (status=`issued`)
3. wallet. credit [bonus] (ідемпотентно; при sticky - в бонусний суб-баланс)
4. activate (status=`active`)
5. emit `bonus. issued`
Rollback: при падінні на 3 кроці →'grant. cancel'+ подія'bonus. revoked`.
5. 2 Прогрес вейджера
На'bet. settled'рахувати внесок ='stake _ minor contribution_pct' (або за win/loss правилами).
Оновлювати'wager _ progress'атомарно; при досягненні 100% -'complete'.
5. 3 Завершення (consume)
complete → `wallet. convert_bonus_to_cash' (якщо non-sticky) або зняття обмежень на вивід.
emit `bonus. consumed`.
5. 4 Закінчення/відгук
За'expires _ at'або правилом фроду →'revoke'( ідемпотентно), можлива компенсація згідно з політикою.
6) Контракти з гаманцем (тільки через API, завжди ідемпотентно)
Нарахувати бонус
POST /v1/wallet/credit
Headers: X-Idempotency-Key: bonus_grant_123
{
"player_id":"p_001",  "amount":{"minor_units":100000,"currency":"EUR"},  "balance_type":"bonus",  "reference":{"grant_id":"gr_123","offer_id":"of_777"}
}
→ 200 {"status":"credited","entry_id":"e_9001"}Конвертація в кеш після виконання умов
POST /v1/wallet/convert
Headers: X-Idempotency-Key: bonus_convert_gr_123
{
"player_id":"p_001",  "from_balance":"bonus",  "to_balance":"cash",  "amount_minor":100000,  "reference":{"grant_id":"gr_123"}
}
→ 200 {"status":"converted","entry_id":"e_9010"}- запит'bets. authorize'відхиляється кодом'BONUS _ MAX _ BET _ EXCEEDED'.
7) API сервісу промо (еталони)
Створити оффер (адмін)
POST /v1/offers
{
"name":"Welcome 100% up to 100€",  "type":"deposit_match",  "params":{"match_pct":100,"cap_minor":10000,"wager_x":20,"sticky":true,       "max_bet_minor":200,"max_win_minor":50000,"contribution_schema_id":"c_slot100_live10"},  "eligibility":{"brands":["A"],"regions":["EU"],"segment":"new_depositors"},  "schedule":{"start":"2025-10-20T00:00:00Z","end":"2025-11-30T23:59:59Z"}
}
→ 201 {"offer_id":"of_777"}Видати бонус (runtime)
POST /v1/bonus/grants
Headers: X-Idempotency-Key: grant_p001_of777
{
"player_id":"p_001","offer_id":"of_777","trigger":"deposit_captured","amount_minor":10000
}
→ 200 {"grant_id":"gr_123","status":"active"}Прогрес вейджера (read)
GET /v1/bonus/grants/gr_123/progress
→ 200 {"required_minor":200000,"contributed_minor":45000,"remaining_minor":155000,"pct":0. 225}Анулювати/відкликати
POST /v1/bonus/grants/gr_123/revoke
Headers: X-Idempotency-Key: revoke_gr_123
{ "reason":"fraud_velocity" }
→ 200 {"status":"revoked"}Всі write-виклики - з'X-Idempotency-Key'і'X-Trace-Id'.
8) Анти-аб'юз і комплаєнс
Velocity ліміти: видач/конверсій/спроб депозиту (Redis counters + TTL + Lua).
Дедуп тригерів: один депозит → один grant за правилом.
Сегментація та RG: виключити self-excluded/лімітних; per brand/region ліцензії.
Блок конфлікту офферів: одночасно активний тільки один welcome-бонус; Пріоритети.
Детектор аномалій: множинні акаунти/пристрої/ASN, швидкі «обнулення» вейджера.
«Чотири ока» на великі гранти і ручні коригування.
WORM-аудит всіх змін правил/грантів/конверсій.
9) Спостережуваність, метрики і SLO
SLO (орієнтири):- `grant. issue p95` (issue→credited) ≤ 300–500 мс.
- Оновлення'wager _ progress p95'≤ 200 мс з моменту'bet. settled`.
- Події «bonus». в шині p95 ≤ 2 хв від події.
- «Втрачених/дубльованих грантів/конверсій» = 0.
- Rate/latency по `issue/convert/revoke`, error-rate (business/4xx/5xx), `IDEMPOTENCY_MISMATCH`.
- Конверсія вейджера, середній'time-to-complete', частка прострочених.
- Вартість промо: 'promo _ cost'( minor) і'promo _ roi'на когортах.
- Анти-аб'юз: спрацьовування velocity, відхилені max bet/win.
Трейсинг: OpenTelemetry по ланцюжку'trigger → grant → wallet. credit → progress. update → convert`.
10) Інтеграція з RGS/іграми
Купони Free Spins/Free Bets - через'entitlements'API: видача токенів, списання в рантаймі, телеметрія по використанню.
Max bet/win - правила в'bets. authorize` и `bets. settle`; повертати коди'BONUS _ RULE _ VIOLATION'.
Contribution - схема на рівні'bet. settled'( по'game _ type/provider _ id'), версія схем.
11) DWH/BI і звіти
Outbox події → Lake (bronze) → Silver (дедуп, SCD2) → Gold вітрини:- `fact_bonus_grants`, `fact_wager_progress`, `fact_bonus_cost`, `fact_promo_roi`.
- SLA свіжості: Silver ≤ 15 хв, Gold ≤ 30-60 хв.
- Панелі: конверсія по оферах/сегментах, time-to-complete, внесок по іграх, аб'юз-інциденти.
12) Безпека і резидентність
mTLS + OAuth2 CC; scope’ы `promo:issue`, `promo:convert`, `promo:revoke`.
Ключі/токени - per brand/region, короткоживучі; секрети в Vault/HSM.
PII-ізоляція: 'player _ id'- псевдонім; RLS по `brand/region`.
Rate limits і квоти на видачі; захист від штормів ретраїв.
13) Чек-листи
Платформа/оператор
- Всі грошові операції йдуть через Wallet з'Idempotency-Key'.
- Rules/Eligibility версіонуються; «подвійний лист» подій на міграціях.
- Contribution-схеми централізовані, покриті тестами.
- Velocity і анти-фрод включені; «чотири ока» на великі суми.
- Outbox/CDC, DLQ і керований replay для'bonus.'.
- SLO-дашборди, OpenTelemetry, WORM-аудит.
- DWH-вітрини для ROI і комплаєнсу (RG/AML).
Інтеграції (RGS/гаманець/CRM)
- Перевіряю max bet/win; повертаю код бізнес-помилки.
- Прокидаю'trace _ id'і'idempotency _ key'.
- Дедуп тригерів і гарантії доставок (webhooks підписані).
14) Червоні прапори (анти-патерни)
Нарахування бонусу «вручну» прямо в баланс, минаючи Wallet.
Відсутність ідемпотентності → подвійні гранти/конверсії.
Вейджер вважається по'bet. placed', а не за підсумками'bet. settled`.
Немає contribution-схем або вони «зашиті» в коді провайдерів.
Конфліктуючі оффери активуються одночасно.
Немає velocity/анти-фрода і WORM-аудиту.
Події'bonus.'публікуються в обхід outbox/CDC.
Показники промо не сходяться з Ledger/BI (немає вітрин ROI).
15) Підсумок
Надійний backend промо - це контракти та інваріанти, а не «додати баланс». Він відокремлює правила від грошей, вважає прогрес за фактичними наслідками, гарантує ідемпотентність і спостережуваність, захищає від абьюза і забезпечує комплаенс. З таким ядром маркетинг рухається швидко, гравець бачить чесні умови, а фінанси і регулятори отримують точну картину вартості і ефекту кожного оффера.
