Kỹ thuật chi tiết
Bootstrap composition root, phân lớp biz/core/infra, 4 payment gateway, 22 game adapters, RabbitMQ + masterWorker. Đọc sau 01-backend.html.
Cây thư mục src/
Request lifecycle qua 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 vào core/infra services; biz services nhận core/infra dependencies qua setter injection.
Express + helmet + cors (DB-driven origins + DEV_ORIGINS) + compression + morgan + rate limiters + language middleware.
mountRoutes: inject models/services vào controllers, mount ~40 route groups, errorHandler cuối cùng.
Controller không gọi global.db trực tiếp (architecture lint). Transaction boundaries thường ở biz hoặc 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 input nghiệp vụ, gọi nhiều core/infra, emit events. Ví dụ: agentCreditPurchaseBizService.js, paymentTransactionBizService.js.
Domain access qua repository — CRUD + query cho 1 aggregate. Ví dụ: paymentTransactionCoreService, userCoreService.
Side effects & integrations: email, SMS, telegram, wallet transfer, game sync, fraud. Ví dụ: walletService, gameWalletSyncService.
Sequelize queries only. Core services gọi repository — biz không import repository trực tiếp (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()→ chọn adapter quagetDepositAdapter(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 không hỗ trợ withdrawal)
- Payout status poll:
btcPayPayoutStatusWorker,zeroxProcessingPayoutStatusWorkertrong masterWorker - Portal USD withdrawal:
portalUsdWithdrawalPayoutService+ OTP quawithdrawalOtpService
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 từ GameProvider model (strip sensitive fields), instantiate adapter class.
22 adapters + baseProvider.js fallback cho vegas/apex/valor/mythic/arcadia.
Low-level HTTP clients (bluedragon, gamevault, jack2win, megaspin…). Adapter gọi thirdpartyapi, log qua gameProviderApiLogService.
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 trước khi cấp token |
| 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 cho /api/kiosk/* |
Shared flows: /api/portal-auth (forgot password), portalLogin2faService, portalCredentialNotifyService dùng portalSiteUrl.js để build link theo role.
Database models (78)
Sequelize models trong 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 khỏi 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.
Hướng dẫn cho dev mới
- Tạo biz service trong
services/biz/ - Core service + repository nếu cần DB mới
- Controller gọi biz — không query DB trực tiếp
- Route trong
routes/, mount trongbootstrap/routes.js - Wire dependencies trong
bootstrap/services.jsnếu service mới
- Tạo deposit adapter (+ withdrawal nếu có) trong
services/payment/ - Đăng ký trong
depositAdapters/index.jsvà/hoặcwithdrawalAdapters/index.js - Webhook handler trong
paymentUserController+ routepayment.js - Env vars + PaymentProviderFeeConfig seed
- HTTP client trong
thirdpartyapi/myprovider/ - Adapter extend
baseProvider.jstronggameProviderAdapters/ - Register trong
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
Lỗi thường gặp
| Triệu chứng | Nguyên nhân | Fix |
|---|---|---|
| Service undefined model | Chưa wire trong bootstrap/services.js | Thêm setRepository/setModels trong initServices() |
| architecture:check fail | Import sai tầng mới | Di chuyển logic xuống core/infra; không import repository từ biz |
| Webhook double credit | Idempotency bypass | Kiểm tra CallbackLog + syncMetadata.walletCreditedAt |
| Portal JWT invalid | Dùng nhầm ADMIN_JWT cho portal | Agent/shop/super-agent dùng JWT_SECRET + đúng session table |
| RabbitMQ workers không chạy | RABBITMQ_ENABLED=false hoặc broker down | Check RABBITMQ_URL; fallback sync trong một số infra services |
| CORS blocked | Origin chưa có trong Domain table | Admin → Domains hoặc thêm DEV_ORIGINS trong bootstrap/app.js |
| 0x/BTCPay payout stuck | Poll worker disabled | ZEROX_PAYOUT_POLL_ENABLED / BTCPAY_PAYOUT_POLL_ENABLED + API keys |
| Game sync timeout | Provider chậm | Job vào gameWalletSyncQueue; check GameProviderApiLog |