Appearance
Payments (admin)
Back-office payment management covers the unified transaction list and cashier configuration. Withdrawal operations remain on the dedicated withdrawals queue below.
Payments hub (/billing)
The Payments page has three everyday tabs:
| Tab | Purpose |
|---|---|
| Cashier methods | Primary configuration surface — filters, readiness, arrange order, and the method settings workspace |
| Integrations | Hosted provider credentials (Fynzah / Fintrix / CryptoCloud) — save, test, enable Accept new payments |
| Transactions | Unified deposit + withdrawal list with filters (date, type, status, method, email, user id, amount) |
?tab= syncs the active tab (default: Cashier methods). Staff without payments.manage can inspect configuration; add/delete/toggle/save/reorder controls stay hidden or disabled.
Configured deposit methods are consumed live by the player fiat deposit API (/payments/methods, /payments/create). Player catalog method id values are cashier-row UUIDs so multiple rows that share a provider slug stay unambiguous.
Everyday flow
- Open Integrations and select Fynzah (Paayzen) (or Fintrix / CryptoCloud)
- Fill merchant credentials → Save → Test connection
- Enable Accept new payments (activates the default cashier row and ensures its route)
- Optionally open Cashier methods → Configure for Player display name/icon/limits/segments, Amounts, or Routing failover
- Verify the method appears for eligible players (active + segment match). Incomplete credentials still allow activation with a warning, but deposits fail until the integration is ready
Credentials can also be edited from Cashier methods → Configure → Connection on any row for that provider (shared once across siblings).
Cashier method settings workspace
Open Configure on a Cashier methods row. Sections:
- Player — display name, uploaded/URL icon, limits, fees, segments, visibility
- Connection — shared credentials, enablement, readiness, connection test; labeled “Shared by every
<provider>method” with sibling rows listed - Amounts — deposit presets for the row currency (deposit methods only)
- Routing — ensure/default route; provider chain, traffic %, and limits (direction/currency-compatible methods, not P2P-only)
- Advanced (permission-gated mutations) — catalog enablement and technical metadata (name, description, icon, currencies, required fields, active)
Activation is allowed even when credentials are incomplete (with a confirmation warning). Active but incomplete methods show an amber Visible but unavailable badge; deposits fail safely until the integration is ready.
Use Arrange order on the Cashier methods tab to drag active methods within a direction + currency.
Readiness chips cover: visible to eligible players, integration enabled, credentials complete, connection configured, and route ready.
Hosted providers and CryptoCloud
Fynzah (Paayzen), Fintrix, and CryptoCloud are separate hosted integrations. Each stores merchant credentials in payment_provider_configs (AES-GCM envelope). Configure them from Payments → Integrations, or from Cashier methods → Configure → Connection on that provider’s row (for example Fynzah SBP). Tokens, signing keys, API keys, webhook secrets, and salts are encrypted at rest and are never returned to the browser or audit metadata. Saving from either surface updates the shared provider record once.
Fynzah SBP (fynzah:sbp:in / Paayzen method_code=sbp) is a first-class hosted UZS method, independent of Fintrix. On Integrations → Fynzah (Paayzen), fill Paayzen credentials, run Test connection, then enable Accept new payments. That save activates the cashier row and creates the default route so the method is available in the player cashier. MegaKassa has been removed from the product; see the MegaKassa purge runbook if a legacy database still contains MegaKassa rows.
Set the same PAYMENT_PROVIDER_CREDENTIAL_ENCRYPTION_KEY in the API and admin deployments; PAYMENT_PROVIDER_CREDENTIAL_ENCRYPTION_KEY_PREVIOUS supports key rotation. Setup scripts auto-provision the key; compose fails fast if it is missing so operators never hit a generic 500 on save.
The provider can be saved while disabled for connection testing. Enabling it requires a complete configuration. Disabling blocks new deposits/wallets but callbacks and reconciliation continue (allowDisabled) for in-flight payments.
Hosted-provider credentials are accepted only through the encrypted admin-managed integration record. Never put credentials in cashier rows, provider-method integrationConfig, or process environment variables for the hosted UZS/fiat providers.
CryptoCloud migration: existing CRYPTOCLOUD_* env values remain as a temporary fallback until imported with apps/api/src/scripts/migrate-cryptocloud-env-to-db.ts --apply. Prefer the DB row once imported; do not rotate the encryption key as part of that import.
Fintrix UZS H2H requires the merchant UUID and numeric site ID in addition to its access token and signing salt. Seeded cashier methods are Payzeen, Uzcard, and Humo. Deposits call POST /h2h/new-order with method=payzeen|uzcard|humo matching the provider-method slug and return payment requisites for in-app display (no Fintrix hosted redirect). The default amount contract uses minor units; select major units only when Fintrix confirms that contract for the merchant. Internal fiat order ids keep the PAY- prefix, but Fintrix coid/request_id must be the bare UUID — strip the prefix at the Fintrix boundary and restore PAY- when resolving callbacks/browser returns. Configure the Fintrix merchant-panel webhook as POST {WEBHOOK_BASE_URL}/webhook/fintrix. Fintrix confirms deposits only via the signed order_status_change webhook (status 1 paid / 2 cancelled).
Fintrix card withdrawals use the seeded fintrix:card:out UZS method (payoutMode: "provider", PAN in params). The player enters a card number; the request waits in the admin withdrawals queue; on approve the API calls POST /h2h/payout with the same H2H auth as deposits. Immediate status 1 confirms the withdrawal; status 0 stays processing until the signed payout_status_change webhook (1 paid / 2 cancelled + refund). Payout coid/request_id is the withdrawal UUID (not the WDR- public order id).
Fynzah callbacks are also restricted to the provider's supplied IPs configured on the integration (162.55.224.218, 157.90.134.135, and 213.111.139.171). Keep the stored allowlist synchronized with any future provider notice and with the network/firewall allowlist.
P2P and Mercado Pago
P2P and Mercado Pago appear as ordinary providers in the Cashier methods UI. Service-to-service secrets (P2P_PROVIDER_API_SECRET / P2P_WEBHOOK_SECRET and their P2P-side mirrors) and encryption master keys stay deployment-managed. Mercado Pago merchant accounts remain in the isolated P2P service/DB and are operated from P2P admin; the main Admin settings workspace shows connection readiness only (merchant secrets are never moved into the main DB).
Section save behaviour
- Player and Connection are draft sections. Footer Save persists only dirty drafts (Connection first when dirty, then Player). Blank secret fields mean “keep the current encrypted secret”.
- Amounts, Routing, and Advanced mutations save immediately and refresh only their returned workspace slice so unsaved Player/Connection drafts are preserved.
- Test connection is disabled while Connection has unsaved changes.
- Staff without
payments.managesee a read-only banner; mutation controls are hidden.
Advanced catalog rules
Disabling a provider method that still has an active cashier row returns 400 with guidance to deactivate the cashier row first. Relative bundled icon paths and uploaded public media URLs both use the shared payment-icon parser.
Payment logos
Operators crop logos to a 5:1 safe area before upload. The server normalizes to at most 1000×200 WebP and stores the asset in shared notification media (NOTIFICATION_MEDIA_DIR must be shared by Admin and API). Player cashier grids use object-contain with a method-name text fallback when the image is missing or fails to load.
Provider routing
When a player creates a deposit, the API walks the configured provider chain on decline:
- Skip providers over daily/monthly volume caps
- Apply traffic share per provider
- Prefer the player's last successful provider in the chain
- Fall back to sort order
On success, the last successful provider is remembered for the next attempt. The selected provider database id is stored on the payment record as selectedProviderMethodId.
High-load safeguards
- Public cashier method lists are cached briefly to reduce hot-path reads.
- Admin transaction pagination uses a database-level unified query instead of in-memory merging.
- Payment status transitions guard against overwriting terminal success states.
- Authed public method responses use
private, no-storewith viewer-scoped cache keys so segment targeting cannot leak across sessions. - Provider usage counters use atomic upserts;
provider_operation_idis unique when present. - Indexes on
payments.created_at,payments(status, created_at), andwithdrawals.created_atsupport large transaction lists.
Permissions
| Action | Permission |
|---|---|
| View transactions and cashier configuration | players.payments.view |
| Mutate methods, presets, routing, approve/cancel withdrawals from billing | payments.manage |
Cancel and reject both refund the player's balance when a withdrawal deduction exists (idempotent).
Withdrawals
When a player requests a withdrawal, it lands in a queue for the operations team to review and process. This is the back-office side of the wallet flow described in Wallet & payments.
The queue
The withdrawals screen opens on the work that's waiting: a pending count broken down by currency, how many were processed today, and the total number of requests. A collapsible analytics panel adds a 30-day view — approval rate, average processing time, volume over time, and a breakdown by currency — for anyone with payments.report.view.
From there you filter by status (processing, confirmed, rejected, cancelled), search by user, address, or currency, and sort by amount or date. Approving or rejecting can be done inline from the list.
Billing's transaction list links withdrawal rows to the detail page for chat and full context.
A single request
Opening a request shows the full picture: amount, currency, the destination address, the transaction hash once it exists, any admin comment, and who processed it. It also carries a message thread, so an operator can ask the player for clarification before approving a payout — the player sees the same thread from their side.
Status flow
A request starts as processing. An operator either approves it, moving it to confirmed, or rejects it. Cancelled requests are tracked as well. The deduction from the player's balance already happened at request time, so approval is about releasing the funds, not moving the balance again.
Fintrix card payouts stay in processing after approve until Fintrix reports paid (status 1), then move to confirmed. A Fintrix cancel (status 2) rejects the request and refunds the reserved balance.
Permissions
Viewing the queue needs players.payments.view, the analytics panel needs payments.report.view, and acting on a request — approving, rejecting, or posting in the thread — needs payments.manage.