Баланс и кошельки: архитектура multi-wallet
1) Зачем multi-wallet и какие цели
Одна запись «баланс = число» не покрывает реальность iGaming. Нужны отдельные кошельки/субсчета: реальные деньги (cash), бонусные средства, вэйджер-пул, фриспины, комп-поинты, иногда — валютные кошельки (EUR/USD/BRL).
Цели архитектуры:- Точность денег (double-entry, аудируемость).
- Политики списания (например, сначала бонус/вэйджер, потом cash).
- Скорость (p95 API ≤ 250–400 мс, ставка/сеттлмент в реальном времени).
- Безопасность и комплаенс (KYC/AML, лимиты ответственной игры, регуляторы).
- Масштаб: пики → десятки тысяч операций/сек, миллиарды постингов/месяц.
2) Модель данных: «Ledger + Subwallets»
Минимальные сущности
Account: игрок/бренд/маркет.
Пример таблиц (упрощённо)
sql
-- Балансовые счета для double-entry (включая служебные)
accounts(id, owner_user_id, type, currency, status,...)
-- Проводка (двойная запись, ссылка на бизнес-операцию)
ledger_entries(id, posting_id, debit_account_id, credit_account_id,        amount_minor, currency, category, operation_id, created_at)
-- Холды (резервы)
holds(id, account_id, amount_minor, currency, reason, expires_at, state,    operation_id, created_at)
-- Политики списания (приоритеты)
spend_policies(id, market, wallet_priority jsonb, updated_at)
-- Кросс-валютные курсы fx_rates(ccy_from, ccy_to, rate, precision, valid_from)Правило: истина живет в журнале проводок (`ledger_entries`). Текущий баланс — либо агрегат (материализованный снэпшот), либо вычисляется из журнала (дорого, но единственно верно).
3) Типы кошельков и их поведение
4) Политики списания и порядок приоритета
Чётко формализуйте алгоритм источника средств: Пример (слоты/казино):1. Сначала списывать из WAGER (если активен вэйджер).
2. Затем из BONUS, пока не исчерпан.
3. Остаток — из CASH.
Пример (спорт):1. Сначала CASH (регулятор/налог).
2. Затем BONUS (freebet), переводя в WAGER.
Сохраняйте в Postings «решение политики» как атрибут, чтобы саппорт и аудит видели «почему так списали».
5) Жизненный цикл денег и операций
Депозит
1. `POST /wallet/deposit` → создаём pending запись (inbox колбэка PSP).
2. Вебхук PSP (подпись HMAC, идемпотентность по `operation_id`) → credit CASH, category=`DEPOSIT`.
3. Публикуем событие `wallet_updated`.
Ставка
1. `POST /bet/place` → создаём hold (резерв) на счёте источника (CASH/BONUS/WAGER).
2. При подтверждении ставки → перевод hold → debit источника, credit служебного «расчетного» счёта провайдера.
3. При отмене — release hold.
Сеттлмент (результат)
Выигрыш: debit «расчетного» счёта провайдера → credit CASH или WAGER→BONUS→CASH по политике.
Проигрыш: закрываем проводкой «расход» провайдера → без кредитов игроку.
1. Проверка KYC/AML, лимиты ответственной игры.
2. Hold на сумму вывода.
3. Успех PSP → финальная debit CASH → credit счёт «выплаты».
4. Отказ PSP → release hold.
6) Идемпотентность и exactly-once «по смыслу»
Везде `operation_id` (UUID/улучшенный ULID) с уникальным индексом. Повторный запрос → статус прошлой операции.
Вебхуки PSP/провайдера игр: Inbox-таблица с dedupe по `event_id+signature`. Обработка — идемпотентный воркер (Outbox-паттерн).
Idempotency-Key на HTTP для клиента; TTL хранить ≥ 24–72 ч.
7) Резервы и холды (holds)
Холд — не списание, а «заморозка» доступного остатка.
Правила:- Срок жизни холда: seconds→minutes (ставка) или часы (вывод).
- Холд может быть частично или полностью погашен (partial settle).
- При expire — автоматический release и событие.
- Храните связь `hold_id` ↔ `bet_id/withdraw_id`.
8) Валюты, FX и округления
Денежные суммы — в минорных единицах (cents), тип — целое.
Округления банковские (round half to even) либо по T&C.
FX: `CASH(EUR)` ↔ `CASH(USD)` лучше разделять кошельки. Конверсию делать как отдельную операцию:- `debit EUR, credit FX_EURUSD` и `debit FX_EURUSD, credit USD` — прозрачно для аудита.
- Запрещено автоматом «дотягивать» курс при споре; все правила — в политике FX.
9) Ответственная игра и лимиты
Deposit/Bet/Loss/Session лимиты (день/неделя/месяц), «cooling-off», self-exclusion.
Реализуются как pre-check перед hold/debit.
Логи отказов — в отдельный аудит-журнал, доступны саппорту и регулятору.
10) Антифрод-сигналы вокруг кошелька
Кластеры устройств/ASN, частые депозиты малой суммы → крупные выводы, отмывочные паттерны.
Velocity-лимиты на `deposit/withdraw` по BIN/стране/устройству.
Блок-листы для получателей (кошельки/ IBAN), список «мулов».
События кошелька → в feature store скоринга (логин/депозит/ставка).
11) Консистентность и производительность
Истина vs кэш
Истина — в ledger. Для API «получить баланс» — держите материализованный снэпшот (`user_id+wallet_type → balance_minor, version`).
Писать: транзакция в БД → инвалидировать кэш.
В «тяжёлых» флоу (live) уместно short-TTL 1–5 с + обязательная проверка истины перед выводом/крупной ставкой.
Скалирование
Шардирование по `user_id` (модуль/ранжирование), отдельные шард-пулы под CASH vs BONUS.
Горячие ключи (VIP/боты) — request coalescing по `user_id`.
Асинхронные агрегации (скомпонуйте `posting` → «снапшот-апдейтер» в фоне).
12) API-контракты (псевдо)
Баланс
http
GET /v1/wallets?types=CASH,BONUS
→ 200 {"wallets":[
{"type":"CASH","currency":"EUR","available":12050,"hold":500,"version":1942},  {"type":"BONUS","currency":"EUR","available":3000,"wager_req":15000}
]}Ставка (с холдом)
http
POST /v1/bets/place
{"bet_id":"b_123","amount":500,"currency":"EUR","source_policy":"casino_default", "idempotency_key":"ik_abc"}
→ 201 {"status":"HELD","hold_id":"h_789","expires_in":30}Сеттлмент
http
POST /v1/bets/settle
{"bet_id":"b_123","result":"WIN","payout":1250}
→ 200 {"status":"SETTLED","cash_delta":+1250}http
POST /v1/withdrawals
{"withdraw_id":"w_456","amount":10000,"currency":"EUR","method":"sepa", "idempotency_key":"ik_def"}
→ 202 {"state":"PENDING","next_check_sec":2,"status_url":"/v1/withdrawals/w_456"}13) Примеры проводок (double-entry)
Депозит €100 (PSP fee €1, коммис. счёт — отдельный)
Debit: PSP_Settlements(EUR)   10000
Credit: User.CASH(EUR)         10000
Debit: User.CASH(EUR)       100 (fee перекладываем)
Credit: PSP_Fees(EUR)          100Ставка €5 из BONUS (перевод в WAGER)
Debit: User.BONUS(EUR)       500
Credit: User.WAGER(EUR)         500  (перемещение в «вэйджер»)
Debit: User.WAGER(EUR)       500
Credit: Provider.Settlement(EUR)    500  (ставка списана)Выигрыш €12.5 → в CASH
Debit: Provider.Settlement(EUR)  1250
Credit: User.CASH(EUR)         1250Холдовое списание (реализация через служебный счёт HOLD)
Debit: User.CASH(EUR)       500
Credit: User.HOLD(EUR)         500  (создан hold)
-- при settle
Debit: User.HOLD(EUR)       500
Credit: Provider.Settlement(EUR)   500
-- при отмене
Debit: User.HOLD(EUR)       500
Credit: User.CASH(EUR)         50014) Аудит, неизменяемость и соответствие
WORM/immutability для журнала (объектное хранилище/архив WAL).
Метажурналы доступа: кто читал/менял лимиты, кто делал ручные корректировки (только через «adjustment-posting» с обоснованием).
GDPR/регуляторы: хранение транзакций 5–10 лет (по юрисдикции), прозрачность расчётов для игрока (история списаний/вэйджера).
15) Отказоустойчивость и DR
Multi-AZ обязательно; DR-регион для кошелька: sync-репликация в зоне, async — в регион; PITR включён.
Promote standby — только вручную по чек-листу (исключить split-brain).
Восстановление проверять еженедельно (test-restore), сверка суммы по контрольным отчётам.
16) Наблюдаемость кошелька
SLI: `deposit_success_ratio`, `withdraw_success_ratio`, `bet_hold_latency_p95`, `settlement_latency_p95`.
Тех: `ledger_postings_rate`, `db_connections_saturation`, `queue_lag_seconds`, `hold_expired_rate`.
Алерты: падение success PSP по рынку, рост `hold_expired_rate`, рассинхрон провайдера игр (нет подтверждений > N мин).
17) Тестирование и контроль качества
Контрактные тесты с PSP/игровыми провайдерами (вебхуки/подписи).
Property-based тесты денег: сумма дебетов == сумма кредитов в каждой Posting.
Fuzz/chaos: задержки PSP/провайдера, повторы вебхуков, сетевые флаппи.
Нагрузочные: burst ставок (60–120 с), soaks (4–8 ч), контроль `queue_lag` и p99.
18) Чек-лист продакшен-готовности
- Двойная запись ledger, все операции через Posting с `operation_id`.
- Чёткие spend-policies и порядок приоритета (персистится вместе с постингом).
- Холды с TTL/partial settle/expiry, связь с bet/withdraw.
- Inbox/Outbox, HMAC-вебхуки, идемпотентность на всех границах.
- Отдельные кошельки CASH/BONUS/WAGER/FS/POINTS; разделение по валютам.
- FX и округления в минорных единицах; конверсия — отдельной операцией.
- Лимиты ответственной игры до hold/debit; аудит отказов.
- Кэш для чтений (короткий TTL) + обязательная проверка истины перед критичными действиями.
- PITR/бэкапы/DR-скрипты; ручной promote, регулярные DR-учения.
- Дашборды/алерты SLI + технические; логи WORM и метажурналы доступа.
- Нагрузочные/хаос-тесты; отчёты reconciliation с PSP/провайдерами.
Резюме
Архитектура multi-wallet — это не «много чисел баланса», а финансовая система с двойной записью, политиками расходования, резервацией и прозрачным следом для аудита и игроков. Держите истину в журнале, используйте холды и идемпотентность, разделяйте кошельки и валюты, автоматизируйте reconciliation и DR. Так кошелёк будет быстрым для UX, точным для денег и устойчивым под пиковые нагрузки и регуляторные проверки.
