Каталоги per currency
Каталог per currency — это вариант каталога контента и прайсинга, где отображаемые цены, лимиты, бонусы, минимальные ставки, джекпоты и тексты промо адаптированы к валюте игрока/тенанта/региона. Цель — дать правильные прайс-поинты и правила без копипаста логики и без рисков из-за конвертаций «на лету».
Ключевые эффекты:- UX: естественные шаги ставок и «красивые» цены (₺9.99, R$5, €0.20).
- Доход: точные лимиты и бусты без «проедания» маржи из-за курсов.
- Комплаенс: соответствие локальным правилам (лицензии, налоги, age/geo).
1) Модель данных: разделяем «номинал» и «представление»
Base Price (номинал): единая внутренняя валюта `PLN`/`EUR`/`USD` для расчетов.
Display Price (представление): вычисляется из номинала + FX + округление + наценки/скидки (spread/fees).
Policy: правила округления, шаги ставок, мин/макс лимиты, джекпоты, бонусные суммы и wager — настраиваются per currency.
yaml price_model:
base_currency: "EUR"
items:
game_spin_min:
base: 0. 10 policy: "stake_min"
game_spin_step:
base: 0. 10 policy: "stake_step"
jackpot_seed:
base: 10000 policy: "jackpot_amount"
policies:
stake_min:
per_currency:
EUR: {round: "ceil_to_step", step: 0. 10}
TRY: {round: "ceil_to_step", step: 1. 00}
BRL: {round: "ceil_to_step", step: 0. 50}
stake_step:
per_currency:
EUR: {step: 0. 10}
USD: {step: 0. 10}
CLP: {step: 50}
jackpot_amount:
per_currency:
EUR: {round: "nearest_100"}
MXN: {round: "nearest_1000"}
2) Источник курсов (FX) и «свежесть»
FX-сервис — единая точка правды для конвертаций:- Поставщик курсов: основной и резервный; частота обновления (например, каждую минуту для волатильных, каждые 15 мин для стабильных).
- Bounded staleness: SLA «курсы не старше Δt» (например, p95 ≤ 5 мин).
- Спред и комиссии: конфигурируются per tenant/region/currency.
- Freeze windows: «заморозить» курсы на матч/турнир/промо-окна, чтобы прайс не «прыгал».
- Аудит: лог версий FX с `valid_from/valid_to`, чтобы воспроизводить чеки.
json
{
"as_of":"2025-10-31T12:00:00Z",
"base":"EUR",
"rates": { "TRY":34. 10, "BRL":5. 42, "MXN":19. 1, "UAH":43. 6, "USDT":1. 00 },
"spread_bps": { "TRY":120, "BRL":60 },
"fees_pct": { "default":0. 15 }
}
3) Округление и «красивые» прайс-поинты
Округление делайте после FX и спредов:- Цены/пакеты: `99`, `9.99`, `4.90` (психологические точки).
- Ставки и шаги: «ceil_to_step» к валютному шагу (₺1, CLP$50).
- Бонусы: округление вниз к шагу ваучера (R$1 / ₺5).
- Порядок операций: `raw = base fx (1+spread) (1+fee)` → `rounded = round_policy(raw)` → `min/max clamp`.
Анти-пример: «банковское округление» для ставок может дать «некрасивые» шаги — используйте явные политики.
4) Лимиты, мин/макс и джекпоты
Min/Max per currency: учитывают локальные законы и RGS-ограничения.
Джекпоты: если провайдер держит джекпот в своей валюте (например, EUR), показывайте либо локализованный эквивалент (информер), либо храните пер-валютные пулы.
Шаги валюты: CLP/JPY без копеек — все лимиты целочисленные.
sql
CREATE TABLE currency_limits (
tenant_id text,
currency text,
feature text, -- spin_min, spin_max, deposit_min, payout_max, jackpot_min value numeric,
step numeric,
PRIMARY KEY (tenant_id, currency, feature)
);
5) Бонусы и ваучеры per currency
Номинал бонуса: конфигурируется per currency (не «пересчет» в лоб).
Wager: храните как множитель (x30) или как сумму в валюте; избегайте смешения.
Кэп выигрыша/кэш-аут: тоже per currency.
Маркетинг-тексты: локализация чисел и валюта в шаблонах без хардкода.
yaml bonus:
welcome_pack:
EUR: {amount: 100, wager_x: 35, cap: 500}
BRL: {amount: 500, wager_x: 40, cap: 2500}
TRY: {amount: 2500, wager_x: 40, cap: 12500}
6) Ограничения провайдеров (RGS/PSP)
RGS: некоторые игры недоступны для валют `crypto`/локальных; часть провайдеров требует фиксированные минимумы (например, €0.20).
PSP: методы оплаты зависят от валюты (PIX ↔ BRL, PayID ↔ AUD, Papara ↔ TRY); лимиты депозита/вывода — тоже разные.
Правило: каталог/витрина фильтруют игры и способы оплаты по валюте и юрисдикции до показа.
7) Архитектурный контур
Currency Policy Store (CP): таблицы правил per currency (шаги, лимиты, прайс-поинты, округления).
FX-сервис: кэш курсов, версии и SLA свежести.
Каталог-билдер: производит Read Models per currency (проекции).
API слоя чтения: вытягивает готовые проекции; никаких on-the-fly конвертаций в горячем пути UI.
Outbox → Проекции: изменения FX/политик → события `CurrencyPolicyUpdated/FXUpdated` → инкрементальные апдейты витрин.
read_catalog_{tenant}_{region}_{currency}
Партиционирование по валюте ускоряет refresh и сбор метрик.
8) Проекции per currency (пример)
sql
CREATE TABLE read_catalog_currency (
tenant_id text,
region text,
currency text,
game_id text,
price_min numeric, -- displayed min-rate price_step numeric,
jackpot numeric,
bonus_badge text,
as_of timestamptz,
PRIMARY KEY (tenant_id, region, currency, game_id)
);
Обновления — идемпотентные `UPSERT`ы из событий каталога + событий FX/политик.
9) Форматирование и локали
Символ/код: `₺/TRY`, `R$/BRL`, `€`, `USDT` (для крипто — без копеек или с 2 знаками, согласно UX-политике).
Группировка и десятичный разделитель: зависят от `locale` (ru_RU, tr_TR, pt_BR).
RTL/арабские локали: отдельная проверка на корректность знака валюты.
10) Кэширование и производительность
Каталожные ответы per currency кэшируйте 30–120 с; FX-индикатор `as_of` отдавайте в ответе.
Инвалидация: события `FXUpdated`/`PolicyUpdated`/`GameUpserted` → целевая очистка ключей кэша.
Пагинация курсорами, чтобы порядок карточек не «прыгал» при мелких апдейтах прайса.
11) Наблюдаемость и SLO
Метрики:- `catalog_p95_ms` по валютам, `fx_freshness_ms` (p50/p95/p99), `policy_refresh_latency_ms`.
- Доля «некрасивых» цен (не лежат на шаге), доля отклоненных транзакций из-за лимитов.
- Расхождение «витрина vs расчет» на чек-ауте (где происходит реальный дебет).
- FX старше SLA, рост ошибок округления, всплеск отказов PSP по лимитам.
- Несоответствие RGS-минимума и витринного минимума.
12) Комплаенс, налоги и residency
Per currency ≠ per country: следите за комбинацией `currency + geo + license`.
Налоговые правила/fee — в политике валюты и в чеке.
Residency: данные и расчеты для локальных валют — в соответствующем регионе.
13) Тестирование
Property-based: инвариант «после конвертации и округления цена лежит на шаге»; «min ≤ value ≤ max».
Golden-cases: набор эталонных валют/прайсов для регрессии.
Chaos FX: «прыгающие» курсы, freeze windows, переключение провайдера FX.
E2E: матчимость суммы на витрине и итоговой списанной суммы; толеранс ≤ 0.01 единицы валюты (или 1 шаг).
14) Типичные ошибки
Пересчитывать на лету в API чтения → нестабильный UX и высокий p99.
Игнорировать шаги валют (CLP/JPY) → «полкопейки» и отказы RGS/PSP.
Округлять «по привычке» (bankers rounding) вместо четких правил per policy.
Не фиксировать FX-версию в чеке → невозможно разбирать споры.
Единые бонусные номиналы через FX → «странные» числа для локальных рынков.
Прятать комиссии в FX без прозрачности — риск претензий и штрафов.
15) Быстрые рецепты
Ставки в TRY/BRL: шаг ₺1 / R$0.50, мин-ставка округлять вверх к шагу, «красивые» прайс-поинты для пакетов.
Crypto (USDT/USDC): шаг $0.10, округление к ближайшему шагу, отсутствие комиссий в показе (но видимые в чеке).
High-volatility FX: freeze на матч/промо; алерты при отклонении > X% от базового прайса.
Мульти-тенант: разные спрэды/шаги у брендов; fairness в расчетах проекций per tenant.
16) Пример конфигурации (единый источник правды)
yaml catalog_currency:
base_currency: EUR fx_sla_ms: 300000 # 5 minutes rules:
- currency: "TRY"
stake_step: 1. 00 stake_min: 5. 00 display_round: "ceil_to_step"
psychological_points: [9, 19, 29, 49, 99]
psp_methods: ["Mefete","Papara","Crypto"]
- currency: "BRL"
stake_step: 0. 50 stake_min: 1. 00 display_round: "ceil_to_step"
psychological_points: [4. 90, 9. 90, 19. 90, 49. 90]
psp_methods: ["PIX","Boleto","Cards"]
- currency: "CLP"
stake_step: 50 stake_min: 200 display_round: "ceil_to_step"
psp_methods: ["WebPay","Cards"]
jackpot:
display_policy:
EUR: "nearest_100"
MXN: "nearest_1000"
bonuses:
welcome:
EUR: {amount: 100, wager_x: 35}
BRL: {amount: 500, wager_x: 40}
TRY: {amount: 2500, wager_x: 40}
17) Чек-лист перед продом
- Единая базовая валюта и версия FX в каждом чеке/событии.
- Политики округления/шагов/лимитов заданы per currency и покрыты тестами.
- Проекции каталога per currency готовы; горячий путь не делает конвертаций.
- Джекпоты и бонусы корректно отображаются/капятся per currency.
- PSP-методы фильтруются по валютам; лимиты совпадают с витриной.
- SLA свежести FX и алерты настроены; freeze windows для волатильных событий.
- Локализация чисел и символов валют; шаблоны промо без хардкода.
- Аудит изменений политик/FX; воспроизводимость чека.
- Мульти-тенант/регион: изоляция данных, различающиеся спрэды и лимиты.
- Плейбуки инцидентов: FX-скачок, несоответствие RGS-минимума, сбой PSP-лимитов.
Заключение
Каталоги per currency — это инженерная дисциплина, а не «умножить на курс». Разделите номинал и представление, централизуйте FX и политики округления, материализуйте проекции per currency и измеряйте свежесть. Тогда витрина будет быстрой, предсказуемой и честной, а бизнес — защищен от скрытых потерь маржи и регуляторных сюрпризов на локальных рынках.