Přeskočit na hlavní obsah

Multi-currency model

casinocrm.io ukládá každou peněžní částku DUAL: originál (měna peněženky hráče) + base-converted (reporting měna tenanta), s FX kurzem zafixovaným v čase události. Reporty se vždy renderují v base tenanta; profil hráče zobrazí native peněženku.

Proč DUAL ukládání

Naivní single-currency CRMs konvertují at query time pomocí current FX. To způsobí drift lifetime metrik — "lifetime deposit" hráče skáče, jak se mění FX. DUAL pattern v casinocrm.io ukládá converted částku v čase události, takže historické metriky zůstávají stabilní.

Konvence ukládání

  • Subunit BIGINT — každá částka uložená jako integer v nejmenší jednotce. EUR €123.45 = 12345; BTC 0.00012345 = 12345.
  • Currency code — spárováno s každou částkou. "EUR", "USD", "BTC", "USDT", "ETH".
  • Scale — desetinná místa pro měnu. EUR/USD = 2, BTC = 8, USDT = 6, JPY = 0.
  • Crypto cap — max 8 desetinných míst (industry standard). ETH wei (18 desetinných) přetéká BIGINT a je pro CRM display nepraktický.

Currency catalogue

Každý tenant má currencies katalog. Migrace seedne EUR + USD pro každého tenanta; operátoři přidávají krypto podle potřeby.

SELECT code, kind, scale, symbol, display_name, is_base
FROM currencies WHERE tenant_id = $1;

code | kind | scale | symbol | display_name | is_base
------+--------+-------+--------+--------------+---------
EUR | fiat | 2 || Euro | t
USD | fiat | 2 | $ | US Dollar | f
BTC | crypto | 8 || Bitcoin | f
USDT | crypto | 6 | USDT | Tether | f
ETH | crypto | 8 | Ξ | Ethereum | f

Přesně jedna měna per tenant má is_base = true; to je reporting měna.

FX kurzy

fx_rates je per-tenant timeline. FX fetcher (pnpm --filter @ucrm/api fx:fetch) běží hodinově a:

  • Stahuje FIAT páry z Frankfurter (ECB-backed, bez auth)
  • Stahuje krypto páry z CoinGecko (no-auth tier)
  • Upsertuje řádky s (tenant_id, base, quote, ts) PK + source field

Lookup je WHERE tenant_id = ? AND base = ? AND quote = ? AND ts <= ? ORDER BY ts DESC LIMIT 1 — nejnovější kurz at-or-před event time.

DUAL sloupce na transakčních tabulkách

bonus_grants (vystaveno v event time):

bonus_amount BIGINT NOT NULL, -- originál (např. 1000 USD cents)
bonus_currency TEXT NOT NULL, -- "USD"
bonus_amount_base BIGINT NOT NULL, -- např. 910 EUR cents (zafixováno)
base_currency_code TEXT NOT NULL, -- "EUR"
fx_rate_locked NUMERIC(28, 12) NOT NULL,
fx_locked_at TIMESTAMPTZ NOT NULL

Stejný DUAL pattern na wagering_events. ClickHouse raw_events properties se obohatí o amount_base + base_currency + fx_rate_locked v insert time, takže dashboard queries SUM(amount_base) fungují koherentně napříč heterogenními wallet měnami.

Per-player wallet měna

players.default_currency_code je primární wallet měna hráče, captured na prvním deposit_confirmed. Do té doby NULL → admin UI zobrazuje tenant base. Po nastavení profil hráče renderuje částky nativně (₿0.0023 BTC) plus base ekvivalent v tooltipu.

Querying

  • Reporty + segmenty — query přes amount_base_subunit sloupce nebo JSONExtractInt(properties, 'amount_base') v CH. Vždy koherentní.
  • Player profile — zobrazte native + base (už poskytuje schéma).
  • Bonus issuance — předejte currency_override, pokud má být bonus denominován v něčem jiném než wallet měna hráče; engine zafixuje FX v issue time.