Backend · Deep dive · dev branch

Techniquedetail

Bootstrap composition root, biz/core/infra layer, 4 payment gateways, 22 game adapters, RabbitMQ + masterWorker. Read later01-backend.html.

bootstrap/ 58 biz · 48 core · 58 infra 45 repositories 16 workers

Directory treesrc/

kioskgaming_backend/ ├── src/server.js ← entry: buildAppWithRoutes + startWorkers ├── src/bootstrap/ ← composition root │ ├── index.js, database.js, repositories.js │ ├── services.js, app.js, routes.js, workers.js ├── controllers/ admin/ agent/ shop/ user/ … ├── routes/ 59 route files │ ├── users/ admin/ payment/ kiosk/ │ ├── agent.js shop.js superAgent.js cashier.js │ └── *Auth.js portalAuth.js ├── middleware/ JWT, RBAC, request signing, domain ├── repositories/ 45 repository classes ├── services/ │ ├── biz/ 58 orchestration services │ ├── core/ 48 domain + repository access │ ├── infra/ 58 integrations (email, wallet, fraud…) │ ├── payment/ deposit/withdrawal adapters, webhooks │ ├── gameProviderAdapters/ 22 game adapters + baseProvider │ ├── queues/ retryQueue, rabbitmqProducer, gameWalletSyncQueue │ └── outbound/ outbound queue payloads ├── database/models/ 78 Sequelize models ├── workers/ 16 background workers ├── thirdpartyapi/ low-level HTTP clients per provider ├── modules/cashier-management/ cashier domain module └── utils/ redis, rabbitmq, logger, caches ├── scripts/architecture/ checkLayering.js, violation inventory ├── tests/unit/ (148) · tests/integration/ └── stress-test/ load test v1/v2 + scenarios

Request lifecycle via bootstrap

1
server.js

buildAppWithRoutes()startWorkers(db) → listen PORT (default 3001). Graceful shutdown: SIGTERM/SIGINT → stopWorkers().

2
bootstrap/database.js

Sequelize connect, load 78 models, associations. UTC timezone (process.env.TZ = 'UTC').

3
bootstrap/repositories.js + services.js

Wire 45 repositories into core/infra services; biz services receives core/infra dependencies via setter injection.

4
bootstrap/app.js

Express + helmet + cors (DB-driven origins + DEV_ORIGINS) + compression + morgan + rate limiters + language middleware.

5
bootstrap/routes.js

mountRoutes: inject models/services into controllers, mount ~40 route groups,errorHandlerFinal.

6
Controller → Biz → Core → Infra

Controller not calledglobal.dbarchitecture lint. Transaction boundaries are usually in biz or infra wallet services.

src/bootstrap/index.jsjs
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)

biz/

Orchestration: validate business input, call multiple cores/infra, emit events. For example:agentCreditPurchaseBizService.js, paymentTransactionBizService.js.

core/

Domain access via repository — CRUD + query for 1 aggregate. For example:paymentTransactionCoreService, userCoreService.

infra/

Side effects & integrations: email, SMS, telegram, wallet transfer, game sync, fraud. For example:walletService, gameWalletSyncService.

repositories/

Sequelize queries only. Core services call the repository — biz does not import the repository directly (lint rule).

💡
Import rules

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, zeroxProcessingPayoutStatusWorker in masterWorker
  • Portal USD withdrawal: portalUsdWithdrawalPayoutService + OTP via withdrawalOtpService
src/services/payment/depositAdapters/index.js
const ADAPTERS = {
  btcpay: btcPayDepositAdapter,
  linkmepay: linkMePayDepositAdapter,
  zeroxprocessing: zeroxProcessingDepositAdapter,
  meld: meldDepositAdapter
};

Adapter patterns

PaymentDeposit / Withdrawal registry

Factory functions getDepositAdapter / getWithdrawalAdapter — validate contract (paymentUrl, externalTransactionId) fail-fast.

GamegameProviderAdapters registry

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.

Third-partythirdpartyapi/

Low-level HTTP clients (bluedragon, gamevault, jack2win, megaspin…). Adapter calls thirdpartyapi, logs throughgameProviderApiLogService.

Auth & JWT (multi-portal)

ActorSecretSession tableNotes
Kiosk playerJWT_SECRETUserSessionRefresh token rotation, phone/email OTP
AdminADMIN_JWT_SECRET (super: SUPER_ADMIN_JWT_SECRET)AdminSessionEmail OTP 2FA before issuing tokens
Agent / Shop / SuperAgentJWT_SECRETAgentSession / ShopSession / SuperAgentSessionPortal login 2FA, email verify gate writes
CashierModule-specificCashierSessionmodules/cashier-management/middleware/
Kiosk deviceRequest signing HMACREQUEST_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)

  • retryretryWorker.processRetryQueue + services/queues/retryQueue.js
  • deposit_timeout / withdrawal_timeout — pending transaction cleanup
  • btcpay_payout / zerox_payout — poll external payout status
  • agent_balance_sync — sync game agent balances
  • expired_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

AAdd API endpoint
  1. Create biz service inservices/biz/
  2. Core service + repository if new DB is needed
  3. Controller calls biz — do not query DB directly
  4. Route in routes/, mount in bootstrap/routes.js
  5. Wire dependencies in bootstrap/services.jsif new service
BAdd payment provider
  1. Create deposit adapter (+ withdrawal if any) inservices/payment/
  2. Register indepositAdapters/index.jsand/orwithdrawalAdapters/index.js
  3. Webhook handler in paymentUserController + route payment.js
  4. Env vars + PaymentProviderFeeConfig seed
CAdd game provider
  1. HTTP client in thirdpartyapi/myprovider/
  2. Adapter extend baseProvider.js in gameProviderAdapters/
  3. Register in gameProviderAdapters/index.js
  4. Insert GameProvider record (code, config, gameUrl)
DRun locally
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

SymptomReasonFix
Service undefined modelNot wired in bootstrap/services.jsAdd setRepository/setModels in initServices()
architecture:check failImported the wrong new floorMove logic down to core/infra; Do not import repository from biz
Webhook double creditIdempotency bypassCheck CallbackLog + syncMetadata.walletCreditedAt
Portal JWT invalidWrongly used ADMIN_JWT for portalAgent/shop/super-agent uses JWT_SECRET + correct session table
RabbitMQ workers are not runningRABBITMQ_ENABLED=false or broker downCheck RABBITMQ_URL; fallback sync in some infra services
CORS blockedOrigin is not yet in the Domain tableAdmin → Domains or add DEV_ORIGINS in bootstrap/app.js
0x/BTCPay payout stuckPoll worker disabledZEROX_PAYOUT_POLL_ENABLED / BTCPAY_PAYOUT_POLL_ENABLED + API keys
Game sync timeoutProvider is slowJob into gameWalletSyncQueue; check GameProviderApiLog