Skip to main content

Code Review — Backend Cash Settlement V1

Reviewer perspective: Senior Engineer / Architect
Phạm vi: Backend kioskgaming_backend — 3 luồng cash settlement V1
Ngày review: 2026-06-20
Trạng thái: V1 đã triển khai, ship được staging/internal; cần hardening trước production scale


1. Tóm tắt điều hành

Ba luồng cash settlement V1 đã được triển khai theo cùng một mô hình:

  1. Validate buyer + cost rate snapshot
  2. Wallet transfer (credit CDN wallet của buyer)
  3. PaymentTransaction (audit legacy)
  4. cash_transactions + cash_transaction_events (ledger mới)
  5. API list/detail/export theo scope portal

Verdict tổng thể: Approve có điều kiện — nghiệp vụ V1 đúng, atomicity tốt, tái sử dụng cashTransactionBizService hợp lý. Tuy nhiên 3 luồng chưa đồng đều về chất lượng code (Admin→SA tốt nhất; Agent→Shop kế thừa nhiều technical debt cũ). Có 5 issue cross-cutting ảnh hưởng cả 3 luồng cần gom fix trong 1 PR refactor.

LuồngFlow enumMaturityShip V1?
Admin → Super Agentadmin_super_agent★★★★☆
Super Agent → Agentsuper_agent_agent★★★☆☆
Agent → Shopagent_shop★★★☆☆✅ (có caveats)

2. Kiến trúc tổng quan

sequenceDiagram
participant Portal as Portal_API
participant Biz as Flow_BizService
participant Wallet as walletTransferService
participant Pay as PaymentTransaction
participant Cash as cashTransactionBizService
participant DB as cash_transactions

Portal->>Biz: POST cash-credits / cash-funding
Biz->>Biz: validate buyer + cost rate
Biz->>Wallet: transfer credits
Biz->>Pay: create audit row
Biz->>Cash: createCashTransactionRecord
Cash->>DB: insert + event created
Biz-->>Portal: payment + wallet + cash_transaction

File map

LayerAdmin → SASA → AgentAgent → Shop
BizsuperAgentCreditPurchaseBizService.applyAdminCashFundingsuperAgentCreditDistributionBizService.distribute / distributeCashCreditscreditDistributionBizService.distribute / distributeCashCredits
Create APIadminSuperAgentManagementController.fundSuperAgentCashsuperAgentCashSettlementController.createAgentCashCreditsagentCashSettlementController.createShopCashCredits
Ledger APIadmin/cashTransactionControllersuperAgentCashSettlementController (scoped)agentCashSettlementController (scoped)
SharedcashTransactionBizService
Constantsconstants/cashTransaction.js

API endpoints

PortalCreateListDetailExport
AdminPOST /api/admin/super-agents/:id/cash-fundingGET /api/admin/cash-transactionsGET /api/admin/cash-transactions/:idGET /api/admin/cash-transactions/export
Super AgentPOST /api/super-agent/agents/:id/cash-creditsGET /api/super-agent/cash-transactionsGET /api/super-agent/cash-transactions/:idGET /api/super-agent/cash-transactions/export
AgentPOST /api/agent/shops/:id/cash-creditsGET /api/agent/cash-transactionsGET /api/agent/cash-transactions/:idGET /api/agent/cash-transactions/export

Legacy endpoints vẫn tồn tại:

  • POST /api/super-agent/agents/:id/credits/distribute → gọi distribute() (sau refactor cũng tạo cash_transaction)
  • POST /api/agent/credits/distribute → gọi distribute() (tương tự)

3. Review từng luồng

3.1 Admin → Super Agent (admin_super_agent)

File chính: superAgentCreditPurchaseBizService.jsapplyAdminCashFunding

Điểm mạnh

  • Validator tập trung: validateAdminSuperAgentPayload() — validate credits, rate, payment method, received_at một chỗ.
  • PaymentTransaction sync paymentMethod: dùng biến paymentMethod đã validate, không hardcode.
  • Dependency injection: paymentTransactionCore + superAgentCore qua setDependencies — dễ test.
  • Wallet flow đúng: credit từ platform (null → super_agent wallet), debitWalletTransactionId = null hợp lý (platform không có wallet debit).
  • Buyer balance after: refresh qua walletCoreService.findByOwner — chính xác hơn 2 luồng kia.
  • Super agent status check: status !== 'active' → reject.
  • Backward compat payload: hỗ trợ legacy (superAgentId, amount, context) và object payload mới.

Điểm yếu

#IssueMức
A1V1 UI khóa cash_received = expected, nhưng validator vẫn cho phép cash_received ≠ expected nếu client gửi khác — chưa enforce server-side lock V1P2
A2sellerType = platform, sellerId = null — list/filter theo seller khó; admin list OK vì không scope sellerP3
A3Withdraw (applyAdminCashWithdrawal) không tạo cash_transaction — asymmetry nếu sau này cần full audit trailP3

Tests

  • superAgentAdminCashFunding.test.js — happy path + integration mock
  • cashTransactionBizService.test.js — validator + formatRowForApi

Coverage: tốt cho happy path; thiếu test inactive SA, insufficient balance platform credit.


3.2 Super Agent → Agent (super_agent_agent)

File chính: superAgentCreditDistributionBizService.jsdistribute / distributeCashCredits

Điểm mạnh

  • Ownership check: assertManagedAgent(superAgentId, agentId) — agent phải thuộc SA.
  • Terminated agent blocked: status === 'terminated' → reject.
  • Cost rate snapshot: buyerCostRate = agent.costRate (buyer = agent).
  • V1 locked: cashReceived = expectedCash.
  • Links đầy đủ: payment + debit SA wallet + credit agent wallet.
  • Controller scope: list/detail ép seller_type=super_agent, seller_id=req.superAgent.id.

Điểm yếu

#IssueMức
S1PaymentTransaction.paymentMethod hardcode 'cash' trong khi cash_transactions.paymentMethod lấy từ payload — audit mismatchP1
S2Không check agent suspended — chỉ terminatedP2
S3Không check SA balance trước transfer — rely on walletTransferService.transfer throw (message có thể không user-friendly)P2
S4receivedAt: new Date(String(...)) không validate Invalid DateP2
S5Legacy POST /credits/distribute vẫn active, tạo cash tx thiếu metadata (payment_method, external_ref)P2
S6performedByType: 'super_agent' dùng string literal thay vì HIERARCHY_OWNER_TYPESP3
S7buyerBalanceAfter lấy từ wt.transaction?.balanceAfter — không refresh wallet như Admin flowP3

Tests

  • superAgentCreditDistributionBizService.test.js — happy path + terminated agent

Coverage: thiếu wrong agent, zero cost rate, insufficient balance, paymentMethod sync.


3.3 Agent → Shop (agent_shop)

File chính: creditDistributionBizService.jsdistribute / distributeCashCredits

Điểm mạnh

  • Shop ownership: shop.agentId !== agentId → reject.
  • Cost rate snapshot: buyerCostRate = shop.costRate.
  • V1 locked: cashReceived = expectedCash.
  • Pattern mirror SA→Agent: cùng structure create cash tx + event.
  • Controller scope: tương tự SA portal.

Điểm yếu

#IssueMức
G1PaymentTransaction.paymentMethod hardcode 'cash' — cùng bug S1P1
G2Không validate shop status (terminated / suspended) — API vẫn distribute đượcP1
G3global.db trực tiếp cho Agent + PaymentTransaction — lệch kiến trúc, khó test/migrateP2
G4PaymentTransaction.create trực tiếp thay vì paymentTransactionCoreServiceP2
G5Legacy POST /api/agent/credits/distribute + Dashboard/ShopsPage vẫn dùng → cash tx chất lượng thấpP2
G6Route param inconsistency: :id (cash-credits) vs :shop_id (withdraw, detail)P3
G7Không có shared validateAgentShopPayload — validation duplicate inlineP2

Tests

  • creditDistributionCashTx.test.js — 5 cases (happy, wrong agent, zero rate, wrapper, insufficient balance)

Coverage: tốt hơn SA flow về edge cases; thiếu shop status, legacy distribute path.


4. Hạ tầng dùng chung — cashTransactionBizService

File: cashTransactionBizService.js

Điểm mạnh

  • Single entry createCashTransactionRecord + auto event created
  • generateTransactionCode format CTX-YYYYMMDD-NNNN
  • buildListWhere hỗ trợ filter flow, seller, buyer, date range, search
  • formatRowForApi normalize snake_case/camelCase
  • Export CSV cap 10k rows

Issue cross-cutting (ảnh hưởng cả 3 luồng)

#IssueMứcChi tiết
X1getCashTransactionDetailperformedBy chỉ lookup AdminP1SA/Agent detail page không hiện tên người thực hiện
X2generateTransactionCode race conditionP2count + 1 không lock — concurrent request có thể trùng code
X3List summary tính trên page hiện tạiP2totalExpected/totalReceived trong summary chỉ aggregate items của page, không phải full filter
X4Partitioned table — created_at optional ở detailP2Không bắt buộc created_at → scan chậm khi data lớn
X5Chỉ có validateAdminSuperAgentPayload — thiếu validator cho SA→Agent và Agent→ShopP2Validation logic rải rác trong từng biz service
X6global.db trong getModels()P2Coupling, khó unit test integration thật

5. Ma trận so sánh 3 luồng

Tiêu chíAdmin → SASA → AgentAgent → Shop
Flow enumadmin_super_agentsuper_agent_agentagent_shop
Sellerplatform (null)super_agentagent
Buyersuper_agentagentshop
Cost rate snapshotSA.costRateAgent.costRateShop.costRate
V1 cash_received lockUI lock; server cho phép overrideServer lock (= expected)Server lock (= expected)
Wallet debit side— (platform mint)SA walletAgent wallet
Wallet credit sideSA walletAgent walletShop wallet
Payment provideradmin_cashsuper_agent_cashagent_cash
PaymentMethod sync❌ hardcode cash❌ hardcode cash
Shared validator❌ inline❌ inline
Buyer status checkSA activeAgent terminated only❌ none
Core service pattern✅ injected✅ partial❌ global.db
Legacy endpoint/credits/distribute/credits/distribute
Controller scope (list)Admin all flowsSA scopedAgent scoped
Controller scope (detail)Admin allSA 403 checkAgent 403 check
Unit tests★★★★★★★★★★★

6. Bảo mật & phân quyền

CheckAdminSuper AgentAgent
Auth middleware✅ admin✅ superAgent✅ agent
Create: ownershipAdmin can fund any SASA can only fund managed agentsAgent can only fund own shops
List: scope enforcedAll (permission-based frontend)✅ force seller_id✅ force seller_id
Detail: scope check❌ (admin sees all)✅ 403 if wrong seller✅ 403 if wrong seller
IDOR on cash tx UUIDAdmin OK by designMitigated by seller checkMitigated by seller check

Ghi chú: Admin detail không cần seller scope — đúng thiết kế. SA/Agent detail check seller là pattern tốt, nên giữ.


Mỗi cash transaction V1 nên có:

cash_transactions
├── payment_transaction_id → PaymentTransaction (audit)
├── credit_wallet_transaction_id → WalletTransaction (buyer credit)
├── debit_wallet_transaction_id → WalletTransaction (seller debit, null cho Admin→SA)
└── cash_transaction_events[] → event type 'created'
Flowdebit_wallet_txcredit_wallet_txpayment_tx
Admin → SAnullSA credit
SA → AgentSA debitAgent credit
Agent → ShopAgent debitShop credit

USD wallet: cả 3 luồng đều walletFlow: 'none' trên PaymentTransaction — shop/agent USD wallet không bị ảnh hưởng. ✅


8. Test coverage gap

ScenarioAdmin→SASA→AgentAgent→Shop
Happy path + cash tx links
Inactive/terminated buyerpartial✅ terminated
Zero cost rate✅ validator
Wrong ownershipN/Apartial
Insufficient seller balance
paymentMethod passthrough❌ (assert)
Legacy distribute creates cash txN/A
Controller scope 403❌ integration❌ integration❌ integration
Invalid received_at

9. Khuyến nghị theo priority

P1 — Fix trước khi production rộng (1 PR, ~1–2 ngày)

  1. Sync paymentMethod vào PaymentTransaction cho SA→Agent và Agent→Shop (copy pattern Admin→SA).
  2. Block distribute cho buyer/seller không active:
    • SA→Agent: thêm suspended
    • Agent→Shop: thêm terminated + suspended cho shop; optional check agent suspended
  3. getCashTransactionDetail — resolve performedBy theo type:
    // performedByType → lookup Admin | SuperAgent | Agent

P2 — Refactor cash settlement chung (1 PR, ~2–3 ngày)

  1. Extract validateCashSettlementPayload(payload, buyer, options) dùng chung 3 luồng (credits, payment_method, received_at, external_reference, V1 lock cash_received).
  2. Deprecate legacy distribute endpoints (410 hoặc redirect nội bộ sang cash-credits với defaults).
  3. Fix listCashTransactions summary — aggregate query riêng, không reduce trên page items.
  4. Migrate Agent→Shop sang paymentTransactionCoreService + inject dependencies (bỏ global.db).

P3 — Hardening sau V1

  1. Idempotency key (Idempotency-Key header hoặc client request_id) dedupe double-submit.
  2. generateTransactionCode — dùng DB sequence hoặc advisory lock thay vì count+1.
  3. Require created_at trên detail API cho partitioned table performance.
  4. Chuẩn hóa route params (:shop_id everywhere).
  5. Admin cash withdrawal → tạo cash_transaction nếu cần full ledger.

10. Kết luận

Ba luồng cash settlement V1 đạt mục tiêu nghiệp vụ:

  • Ghi nhận bán credit bằng cash với cost rate snapshot
  • Transfer CDN wallet đúng chiều hierarchy
  • Ledger cash_transactions + event audit
  • Portal list/detail/export theo scope

Điểm nổi bật: Admin→SA flow là reference implementation tốt nhất — nên dùng làm template refactor 2 luồng còn lại.

Rủi ro chính nếu không fix P1:

  • Payment audit (PaymentTransaction) và cash ledger (cash_transactions) lệch payment method
  • API distribute cho shop/agent inactive vẫn chạy được
  • Detail page không hiện performer cho SA/Agent flows

Recommended next step: Một PR "Cash Settlement Hardening P1" gom 3 fix cross-cutting, sau đó PR "Cash Settlement Refactor P2" unify validator + deprecate legacy.


Phụ lục — File reference

kioskgaming_backend/src/
├── constants/cashTransaction.js
├── services/biz/
│ ├── cashTransactionBizService.js # shared ledger
│ ├── superAgentCreditPurchaseBizService.js # Admin → SA
│ ├── superAgentCreditDistributionBizService.js # SA → Agent
│ └── creditDistributionBizService.js # Agent → Shop
├── controllers/
│ ├── admin/cashTransactionController.js
│ ├── superAgent/
│ │ ├── adminSuperAgentManagementController.js # fundSuperAgentCash
│ │ └── superAgentCashSettlementController.js
│ └── agent/agentCashSettlementController.js
├── routes/
│ ├── admin/cashTransactions.js
│ ├── admin/superAgents.js # POST :id/cash-funding
│ ├── superAgent.js
│ └── agent.js
└── tests/unit/
├── cashTransactionBizService.test.js
├── superAgentAdminCashFunding.test.js
├── superAgentCreditDistributionBizService.test.js
└── creditDistributionCashTx.test.js