Skip to main content

Finance System

This page explains the complete financial model of KioskGaming — wallet architecture, currency units, how deposits and withdrawals flow through the role hierarchy, fee structures, and payment gateway integrations.


Currency Units

KioskGaming uses two currency units in parallel:

UnitUsed byMeaning
CreditPlayers, shops, agents, super agentsInternal platform currency. All in-game balances are in credits.
USDShops, agents, super agentsReal money. Used to buy credits from the system or withdraw.

From the player's perspective, the exchange rate is always:

1 USD deposited = 1 credit received

The margin between what a shop pays per credit and what a player pays per credit is the shop's profit. See Cost Rate below.


Wallet Architecture

Every account in the system may hold one or more wallet types depending on their role.

Master Wallet (wallets table)

The primary credit balance for every account type.

FieldTypeDescription
ownerTypeplayer | agent | shop | super_agentWho owns this wallet
balanceDECIMAL 18,8Available credit balance
reservedBalanceDECIMAL 18,8Locked credits (pending transactions)
currencySTRINGAlways USD (maps to credits internally)
statusactive | frozen | closedWallet operational state
versionINTEGEROptimistic lock — prevents double-spend

Each owner has exactly one master wallet (owner_type, owner_id) is unique.


USD Wallet (usd_wallets table)

Tracks real-money (online) balances, separate from game credits. Used primarily by shops and agents.

FieldTypeDescription
balanceGrossDECIMAL 18,8Total USD before fees
balanceNetDECIMAL 18,8USD after fees are deducted
reservedGrossDECIMAL 18,8Gross amount locked in pending payouts
reservedNetDECIMAL 18,8Net amount locked in pending payouts
statusactive | frozen | closedWallet state

The USD wallet is how shop income from player deposits accumulates. Shops use this balance to buy more credits or withdraw.


Game Wallet (user_game_wallets table)

Each player has one game wallet per game provider they've ever interacted with.

FieldTypeDescription
userIdUUIDPlayer
gameProviderIdUUIDGame platform (e.g., Fire Kirin, Game Room)
walletBalanceDECIMAL 15,2Credits allocated from master wallet to this provider
inGameBalanceDECIMAL 15,2Balance inside the game's own environment
winningBalanceDECIMAL 15,2Withdrawable winnings (River Pay only)
syncStatusENUMnever_synced | in_sync | pending | failed
lastSyncedAtDATELast successful sync with game provider API

walletBalance and inGameBalance can diverge when a sync is pending or failed. The system reconciles them via gameWalletSyncService.


Wallet Summary by Role

RoleMaster WalletUSD WalletGame Wallet
Player✅ (credits)✅ (per game)
Shop✅ (credits)✅ (USD)
Agent✅ (credits)✅ (USD)
Super Agent✅ (credits)✅ (USD)

Cost Rate (costRate)

costRate is a decimal multiplier (0–1) that defines the effective cost per credit for each level of the hierarchy.

costRate = 0.80 → this entity pays $0.80 to acquire 1 credit

Cost rate is inherited top-down: super agent → agent → shop. A child's rate must always be ≥ their parent's rate (no inverted margins allowed).

Profit example

shopRate = 0.80 (shop pays $0.80 per credit)

Player deposits $10 via payment gateway
Player receives: 10 credits (1 USD = 1 credit, always)

Shop's credit cost: 10 credits × $0.80 = $8.00
Shop collects: $10.00 (player's deposit)
Shop gross profit: $10.00 − $8.00 = $2.00

The margin between a parent's rate and a child's rate determines how profit is shared up the chain.


Deposit Flows

Flow 1 — Free Player Online Deposit (via Payment Gateway)

Used when a player not linked to a shop deposits through the Player Web.

Player selects amount and payment method on Player Web


Backend creates PaymentTransaction { type: deposit, status: pending }


Player redirected to payment gateway (LinkMePay / BTCPay / 0x)


Payment gateway processes payment


IPN / callback received by backend


Status: pending → processing → completed


Platform fee deducted (platformFeeRate × amount = platformFeeAmount)


Net amount credited to player's master wallet

├── No game selected → credits stay in master wallet

└── Game selected → walletFlow: master_to_game
GameWalletTransaction created
credits forwarded to inGameBalance

Flow 2 — Shop Player Deposit (CDN Flow)

Used when a player linked to a shop deposits. The shop's credit wallet backs the player's balance.

Payment confirmed (same gateway flow as Flow 1)


Backend identifies player as shop-linked


Check shop's master wallet (credit balance)

├── Shop HAS sufficient credit ──────────────────────────┐
│ ▼
│ Shop master wallet −10 credits
│ Player master wallet +10 credits
│ Shop USD wallet +$X (player's USD, after fee)

└── Shop LACKS sufficient credit ───────────────────────┐

System covers 10 credits (from system reserve)
Player master wallet +10 credits
Shop receives nothing
warning

When the system covers a deposit because the shop lacks credit, the shop does not receive the player's USD payment. The USD is absorbed by the system.


Flow 3 — Shop Counter Deposit (Cashier)

A physical deposit made by shop staff on behalf of a player at the kiosk counter. No payment gateway is involved.

Shop staff opens Shop Portal → "Transfer to Player"


Staff enters player + amount


Shop master wallet debited
Player master wallet credited
CashierTransaction recorded { type: deposit_credits, paymentMethod: cash | card | bank_transfer }

Flow 4 — Direct-to-Game Deposit

The player selects a specific game platform on the deposit screen. Credits go directly into the game instead of stopping in the master wallet.

Flows 1 or 2 run first (payment confirmed, master wallet credited)


GameWalletTransaction created { direction: master_to_game }


master wallet balance −N credits
inGameBalance +N credits

The game provider's API is called to sync the balance. syncStatus tracks the result.


Deposit Fee

Fees are configured per payment provider and method in the payment_provider_fee_configs table.

feeRate = paymentGateFeeRate + systemFeeRate

Fee is deducted from the USD amount the shop receives, not from the credits given to the player.

Example — shopRate = 0.80, feeRate = 10%

Player deposits: $10.00
Platform fee (10%): $1.00
Shop receives (USD): $9.00 ← deposited amount − fee
Player receives: 10 credits ← always full face value

Shop credit cost: 10 × $0.80 = $8.00
Shop net profit: $9.00 − $8.00 = $1.00
tip

The fee is borne by the shop, not the player. From the player's perspective, depositing $10 always yields 10 credits regardless of the configured fee rate.

Fee lookup resolution

For every transaction the system resolves the applicable fee rate in this order:

1. Exact match: (provider, operation, methodId)

└── Not found →
2. Fallback: (provider, operation, 'all')

└── Not found →
3. Default: feeRate = 0

Withdrawal Flow

Players can withdraw credits back to USD via bank transfer or cryptocurrency.

Player submits withdrawal request
{ amount, withdrawalType: bank_transfer | bitcoin_transfer }


Withdrawal fee calculated:
grossUsd = amount
platformFeeAmount = grossUsd × platformFeeRate
netPayoutUsd = grossUsd − platformFeeAmount


PaymentTransaction created { type: withdrawal, status: pending }
Player's master wallet: reservedBalance += amount (credits locked)


Admin reviews in Admin Panel

├── Approved ────────────────────────────────────────────┐
│ ▼
│ status → approved → processing
│ Payout adapter called (LinkMePay / BTCPay / 0x)
│ status → completed
│ Player wallet debited

└── Rejected ───────────────────────────────────────────┐

status → rejected
Reserved balance released back to available
note

Pending withdrawals that exceed the configured timeout are auto-rejected by the system.


Payment Gateways

Deposit adapters

ProviderCurrencyMethod
LinkMePayVNDBank transfer, QR code
BTCPayBTCOn-chain Bitcoin
0x ProcessingUSDC, WBTCCrypto (Arbitrum, Base)

Withdrawal adapters

ProviderPayout method
LinkMePayBank transfer (VND)
BTCPayOn-chain Bitcoin payout
0x ProcessingUSDC (Base), WBTC (Arb1)

Transaction State Machine

All PaymentTransaction records follow strict state transitions:

Deposit:
pending → processing → completed
└──► failed
└──► expired
└──► cancelled

Withdrawal:
pending → approved → processing → completed
└──► rejected

Invalid state transitions are rejected by transactionStateMachine.js before any wallet mutation occurs.


Transaction Ledger

Every wallet balance change is recorded as a double-entry ledger in wallet_transactions:

FieldDescription
fromWalletIdSource wallet (debited)
toWalletIdDestination wallet (credited)
amountCredit amount transferred
costRateRate applied at time of transfer
platformFeeRateFee rate snapshot
platformFeeAmountFee amount snapshot
balanceBefore / balanceAfterAudit trail of wallet state
referenceType / referenceIdLinks back to the originating transaction

The transactionId field uses semantic prefixes for traceability:

player_deposit:{UUID} ← CDN deposit from shop to player

Transaction Limits & Fraud Flags

Configurable limits are stored in transaction_limits_configs as JSONB:

Config groupExamples
transactionLimitsMax deposit per transaction, max daily deposit
rateLimitsMax deposits per hour, max withdrawals per hour
fraudFlagsSuspicious amount thresholds, velocity windows

Suspicious transactions are flagged in transaction_fraud_flags for admin review.


USD Wallet Operations (Shop)

The shop's USD wallet supports two operations:

OperationEffect
Buy creditsShop converts USD → credits at their costRate. USD wallet debited, master credit wallet credited.
WithdrawShop requests payout of USD to an external bank or crypto address. Routed through the same withdrawal adapter flow as player withdrawals.
Example — shop costRate = 0.80, USD wallet balance = $100

Buy 100 credits:
USD wallet: − $80.00
Credit wallet: +100 credits
Remaining USD: $20.00

Summary — Who Gets What on a Player Deposit

ScenarioPlayer receivesShop receivesSystem receives
Free player, no feeFull creditsFull USD
Shop player, shop has credit, no feeFull creditsPlayer's USD
Shop player, shop has credit, fee 10%Full creditsPlayer's USD − 10%Fee amount
Shop player, shop has NO creditFull creditsNothingPlayer's USD
Cashier (counter) depositFull credits— (internal transfer)