Techniquedetail
Bootstrap composition root, biz/core/infra layer, 4 payment gateways, 22 game adapters, RabbitMQ + masterWorker. Read later01-backend.html.
Directory treesrc/
Request lifecycle via bootstrap
buildAppWithRoutes() → startWorkers(db) → listen PORT (default 3001). Graceful shutdown: SIGTERM/SIGINT → stopWorkers().
Sequelize connect, load 78 models, associations. UTC timezone (process.env.TZ = 'UTC').
Wire 45 repositories into core/infra services; biz services receives core/infra dependencies via setter injection.
Express + helmet + cors (DB-driven origins + DEV_ORIGINS) + compression + morgan + rate limiters + language middleware.
mountRoutes: inject models/services into controllers, mount ~40 route groups,errorHandlerFinal.
Controller not calledglobal.dbarchitecture lint. Transaction boundaries are usually in biz or infra wallet services.
async function buildAppWithRoutes() { const db = await initDatabase(); const repos = createRepositories(db); const services = initServices(db, repos); const { app, authLimiter, portalAuthLimiter } = buildApp(); mountRoutes(app, authLimiter, db, repos, services, portalAuthLimiter); return { app, db, repos }; }
Service layer pattern (biz / core / infra)
Orchestration: validate business input, call multiple cores/infra, emit events. For example:agentCreditPurchaseBizService.js, paymentTransactionBizService.js.
Domain access via repository — CRUD + query for 1 aggregate. For example:paymentTransactionCoreService, userCoreService.
Side effects & integrations: email, SMS, telegram, wallet transfer, game sync, fraud. For example:walletService, gameWalletSyncService.
Sequelize queries only. Core services call the repository — biz does not import the repository directly (lint rule).
Controller → biz/core/infra OK. Biz → core/infra OK. Core → repository OK. Biz → repository = violation. Controller → repository = critical violation.
Deposit & withdrawal flows (4 providers)
Deposit
depositService.createDeposit()→ select the adapter viagetDepositAdapter(provider)- Adapters:
linkMePayDepositAdapter,btcPayDepositAdapter,zeroxProcessingDepositAdapter,meldDepositAdapter - Webhook routes:
/api/payment/linkmepay/*,/btcpay/webhook,/zeroxprocessing/webhook,/meld/webhook depositCallbackService+paymentWebhookShared: verify, idempotency, credit wallet, transaction state machine
Withdrawal
withdrawalApprovalService— admin/portal approve →getWithdrawalAdapter()- Adapters: LinkMePay, BTCPay, 0xProcessing (Meld does not support withdrawal)
- Payout status poll:
btcPayPayoutStatusWorker,zeroxProcessingPayoutStatusWorkerin masterWorker - Portal USD withdrawal:
portalUsdWithdrawalPayoutService+ OTP viawithdrawalOtpService
const ADAPTERS = {
btcpay: btcPayDepositAdapter,
linkmepay: linkMePayDepositAdapter,
zeroxprocessing: zeroxProcessingDepositAdapter,
meld: meldDepositAdapter
};Adapter patterns
Factory functions getDepositAdapter / getWithdrawalAdapter — validate contract (paymentUrl, externalTransactionId) fail-fast.
AdapterRegistry.getAdapter(code)— alias map (firekirin→fkterminal), load config from GameProvider model (strip sensitive fields), instantiate adapter class.
22 adapters + baseProvider.js fallback for vegas/apex/valor/mythic/arcadia.
Low-level HTTP clients (bluedragon, gamevault, jack2win, megaspin…). Adapter calls thirdpartyapi, logs throughgameProviderApiLogService.
Auth & JWT (multi-portal)
| Actor | Secret | Session table | Notes |
|---|---|---|---|
| Kiosk player | JWT_SECRET | UserSession | Refresh token rotation, phone/email OTP |
| Admin | ADMIN_JWT_SECRET (super: SUPER_ADMIN_JWT_SECRET) | AdminSession | Email OTP 2FA before issuing tokens |
| Agent / Shop / SuperAgent | JWT_SECRET | AgentSession / ShopSession / SuperAgentSession | Portal login 2FA, email verify gate writes |
| Cashier | Module-specific | CashierSession | modules/cashier-management/middleware/ |
| Kiosk device | Request signing HMAC | — | REQUEST_SIGNING_SECRET for /api/kiosk/* |
Shared flows: /api/portal-auth (forgot password), portalLogin2faService, portalCredentialNotifyServiceuseportalSiteUrl.jsto build links by role.
Database models (78)
Sequelize models in src/database/models/. Migrations: npm run migrate. Partitioned tables: wallet transactions — npm run partition:list.
User & Session
- User
- UserSession
- UserGameAccount
- UserGameWallet
- PasswordReset
- PasswordChangeHistory
- AccountOtp
- EmailCode
- PhoneCode
Wallet & Ledger
- Wallet
- WalletTransaction
- UsdWallet
- UsdWalletTransaction
- UsdWalletReservation
- GameWalletTransaction
- ZeroxStaticWallet
Payment
- PaymentTransaction
- PaymentApiLog
- PaymentProviderFeeConfig
- CallbackLog
- TransactionStep
- TransactionEvent
- TransactionFraudFlag
Hierarchy B2B
- Agent
- AgentSession
- AgentGameMapping
- SuperAgent
- SuperAgentSession
- SuperAgentGameMapping
- Shop
- ShopSession
- ShopGameMapping
- ShopActionLog
- CommissionLog
Cashier
- Cashier
- CashierSession
- CashierDevice
- CashierDeviceAccess
- CashierShift
- CashierShiftHandover
- CashierShiftAdjustment
- CashierTransaction
- CashierCashDrop
- CashierAuditLog
- CashierRedemptionApproval
- CashDenominationCount
Cash & CDN
- CashTransaction
- CashTransactionEvent
- OnlineDebtSettlement
- OnlineDebtSettlementLine
- PlayerTransferLog
Game & Domain
- GameProvider
- GameProviderApiLog
- GameAgentBalance
- Domain
- DomainGameMapping
- KioskHeartbeat
Admin & Support
- Admin
- AdminSession
- AdminOTP
- AdminPermission
- Permission
- AdminActionLog
- AdminLoginLog
- AdminBroadcast
- SupportTicket
- TicketReply
Ops & Config
- TransactionLimitsConfig
- VfxConfiguration
- VfxConfigSchedule
- InAppPopup
- ReconciliationReport
- GamifyRewardWebhookLog
Queue & background workers
RabbitMQ (khi RABBITMQ_ENABLED ≠ false)
emailQueueWorker,smsQueueWorker,telegramQueueWorker,lowBalanceCheckQueueWorker- Producer:
services/queues/rabbitmqProducer.js
masterWorker (60s tick, Redis lock)
retry—retryWorker.processRetryQueue+services/queues/retryQueue.jsdeposit_timeout/withdrawal_timeout— pending transaction cleanupbtcpay_payout/zerox_payout— poll external payout statusagent_balance_sync— sync game agent balancesexpired_data_cleanup— sessions, email/phone codes (daily)player_inactivity_spike— fraud detection
Standalone workers
cashierManagementWorker, lowBalanceDailyWorker, dailyStatsReportWorker, reconciliationWorker, agentBalanceSyncWorker
Game wallet async: gameWalletSyncQueue— decouple provider latency from HTTP response.
Architecture lint scripts
npm run lint # ESLint biz/core/infra + scripts/architecture npm run architecture:inventory # regenerate violation baseline npm run architecture:check # fail CI if NEW violations vs baseline npm run ci:backend # lint + architecture:check + jest
Scanner: scripts/architecture/scanArchitectureViolations.js — detect controller→repository, biz→repository, cross-layer imports. Baseline: architecture/architecture-violation-baseline.json.
Instructions for new developers
- Create biz service in
services/biz/ - Core service + repository if new DB is needed
- Controller calls biz — do not query DB directly
- Route in
routes/, mount inbootstrap/routes.js - Wire dependencies in
bootstrap/services.jsif new service
- Create deposit adapter (+ withdrawal if any) in
services/payment/ - Register in
depositAdapters/index.jsand/orwithdrawalAdapters/index.js - Webhook handler in
paymentUserController+ routepayment.js - Env vars + PaymentProviderFeeConfig seed
- HTTP client in
thirdpartyapi/myprovider/ - Adapter extend
baseProvider.jsingameProviderAdapters/ - Register in
gameProviderAdapters/index.js - Insert GameProvider record (code, config, gameUrl)
npm install && cp .env.example .env npm run migrate # DB migrations npm run dev # nodemon src/server.js npm run ci:backend # lint + architecture + tests curl http://localhost:3001/health
Common errors
| Symptom | Reason | Fix |
|---|---|---|
| Service undefined model | Not wired in bootstrap/services.js | Add setRepository/setModels in initServices() |
| architecture:check fail | Imported the wrong new floor | Move logic down to core/infra; Do not import repository from biz |
| Webhook double credit | Idempotency bypass | Check CallbackLog + syncMetadata.walletCreditedAt |
| Portal JWT invalid | Wrongly used ADMIN_JWT for portal | Agent/shop/super-agent uses JWT_SECRET + correct session table |
| RabbitMQ workers are not running | RABBITMQ_ENABLED=false or broker down | Check RABBITMQ_URL; fallback sync in some infra services |
| CORS blocked | Origin is not yet in the Domain table | Admin → Domains or add DEV_ORIGINS in bootstrap/app.js |
| 0x/BTCPay payout stuck | Poll worker disabled | ZEROX_PAYOUT_POLL_ENABLED / BTCPAY_PAYOUT_POLL_ENABLED + API keys |
| Game sync timeout | Provider is slow | Job into gameWalletSyncQueue; check GameProviderApiLog |