KioskGaming — Test Cases cho QC
Phiên bản: 2026-05-10
Nguồn tài liệu:site-documents/docs/
Dành cho: QC Tester
Ký hiệu kết quả: ✅ Pass | ❌ Fail | ⏭ Skip
Mục lục
- Kiến trúc & Phân cấp vai trò
- Đăng ký Player
- Hệ thống Ví (Wallet)
- Hệ thống Nạp tiền (Deposit)
- Tính phí (Fee Calculation)
- Hệ thống Rút tiền (Withdrawal)
- Payment Gateway Callbacks
- Game Wallet & Sync
- Transaction State Machine
- Tính năng Xác nhận Crypto khi Rút tiền
- Giới hạn Giao dịch & Fraud Flag
- Shop USD Wallet — Mua Credit & Rút tiền
- API Endpoints & Error Handling
1. Kiến trúc & Phân cấp vai trò
TC-ARCH-001 — Phân cấp tạo tài khoản
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Admin tạo Super Agent | Đăng nhập Admin panel → Tạo tài khoản Super Agent | Tài khoản Super Agent được tạo thành công | |
| 2 | Super Agent tạo Agent | Đăng nhập Super Agent portal → Tạo tài khoản Agent | Tài khoản Agent được tạo thành công | |
| 3 | Agent tạo Shop | Đăng nhập Agent portal → Tạo tài khoản Shop | Tài khoản Shop được tạo thành công | |
| 4 | Shop tạo Player | Đăng nhập Shop portal → Tạo tài khoản Player | Tài khoản Player được tạo thành công | |
| 5 | Shop không thể tạo Agent | Đăng nhập Shop portal → Thử tạo Agent | Hệ thống từ chối, trả về lỗi phân quyền | |
| 6 | Agent không thể tạo Super Agent | Đăng nhập Agent portal → Thử tạo Super Agent | Hệ thống từ chối, trả về lỗi phân quyền |
TC-ARCH-002 — Cô lập phiên đăng nhập theo vai trò
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Shop session không truy cập được API của Agent | Dùng token của Shop gọi API endpoint dành cho Agent | HTTP 403 hoặc 401 | |
| 2 | Player session không truy cập được Admin API | Dùng token của Player gọi /api/admin/* | HTTP 403 hoặc 401 |
2. Đăng ký Player
TC-REG-001 — Shop tạo Player (Managed Player)
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Shop tạo player thành công | Đăng nhập Shop portal → Tạo player với tên + thông tin đăng nhập | Player được tạo, liên kết với Shop | |
| 2 | Player đăng nhập sau khi Shop tạo | Dùng thông tin Shop cấp để đăng nhập Player Web / kiosk | Đăng nhập thành công | |
| 3 | Tên player không được để trống | Tạo player với tên rỗng | Validation lỗi, không tạo được |
TC-REG-002 — Player tự đăng ký (Free Player)
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Đăng ký bằng email | Truy cập Player Web → Điền email + thông tin → Submit | Tài khoản được tạo, không liên kết Shop | |
| 2 | Đăng ký bằng số điện thoại | Truy cập Player Web → Điền số điện thoại + thông tin → Submit | Tài khoản được tạo, không liên kết Shop | |
| 3 | Email đã tồn tại | Thử đăng ký với email đã có trong hệ thống | Thông báo lỗi "email đã được sử dụng" | |
| 4 | Free player không liên kết Shop | Kiểm tra DB sau khi self-register | shop_id = NULL trên tài khoản vừa tạo |
3. Hệ thống Ví (Wallet)
TC-WALLET-001 — Master Wallet
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Mỗi tài khoản có đúng 1 master wallet | Tạo player mới → Kiểm tra bảng wallets | Có đúng 1 bản ghi với (owner_type='player', owner_id=<id>) | |
| 2 | Balance ban đầu = 0 | Tạo tài khoản mới → Kiểm tra balance | balance = 0, reservedBalance = 0 | |
| 3 | Wallet status mặc định là active | Tạo tài khoản mới | status = 'active' | |
| 4 | Không thể tạo 2 master wallet cho cùng 1 owner | Insert bản ghi trùng (owner_type, owner_id) vào DB | Lỗi unique constraint |
TC-WALLET-002 — USD Wallet (Shop/Agent)
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Shop có USD wallet | Tạo shop mới → Kiểm tra bảng usd_wallets | Có bản ghi USD wallet cho shop | |
| 2 | Player không có USD wallet | Kiểm tra bảng usd_wallets cho player | Không có bản ghi | |
| 3 | balanceGross và balanceNet khi shop nhận deposit từ player | Thực hiện nạp tiền player → Kiểm tra shop USD wallet | balanceGross tăng theo tổng USD, balanceNet = gross − fee |
TC-WALLET-003 — Game Wallet (Player)
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Mỗi player có 1 game wallet per game provider | Player nạp tiền vào game lần đầu → Kiểm tra user_game_wallets | Có bản ghi với đúng userId + gameProviderId | |
| 2 | syncStatus ban đầu là never_synced hoặc pending | Kiểm tra sau khi tạo game wallet lần đầu | syncStatus = 'never_synced' hoặc 'pending' | |
| 3 | walletBalance và inGameBalance khớp sau sync thành công | Nạp tiền vào game → Đợi sync hoàn tất | walletBalance == inGameBalance, syncStatus = 'in_sync' |
4. Hệ thống Nạp tiền (Deposit)
TC-DEP-001 — Flow 1: Player nạp online (Wallet-only)
Tiên quyết: Player là free player, không chọn game cụ thể.
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Tạo giao dịch thành công | Player chọn số tiền + phương thức → POST /api/payment/deposit | Trả về { transactionId, paymentUrl, status: 'pending', expiresAt } | |
| 2 | transactionId có format đúng khi tạo | Kiểm tra transactionId trả về | Có format TEMP_{timestamp}_{userId} | |
| 3 | Trạng thái ban đầu là pending | Kiểm tra DB sau khi tạo | status = 'pending' | |
| 4 | Sau khi IPN/callback nhận được | Giả lập IPN thành công từ gateway | Status chuyển pending → processing → completed | |
| 5 | Credit vào master wallet sau khi hoàn thành | Hoàn tất nạp $10 → Kiểm tra wallet | balance += 10 credits | |
| 6 | Không nạp được với player inactive | Đặt status player = inactive → Thử nạp tiền | HTTP 400 "User inactive" | |
| 7 | Không nạp được với số tiền âm | POST deposit với amount = -5 | HTTP 400 "Invalid amount" | |
| 8 | Không nạp được với số tiền = 0 | POST deposit với amount = 0 | HTTP 400 "Invalid amount" |
TC-DEP-002 — Flow 1 (phụ): Nạp thẳng vào game (Direct-to-game)
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Chọn game provider khi nạp tiền | POST deposit với gameProviderId được điền | walletFlow = 'master_to_game' trên transaction | |
| 2 | Credits đi vào inGameBalance thay vì chỉ master wallet | Hoàn tất nạp $10 trực tiếp vào game → Kiểm tra user_game_wallets | inGameBalance += 10 | |
| 3 | syncStatus = 'pending' sau khi master wallet được credit | Kiểm tra sau khi payment completed | syncStatus = 'pending' | |
| 4 | Sau khi game sync thành công | Đợi async sync hoàn tất | syncStatus = 'completed', walletBalance và inGameBalance khớp | |
| 5 | Game provider không tồn tại | POST deposit với gameProviderId không có trong DB | HTTP 400 "Provider not found" |
TC-DEP-003 — Flow 2: Shop Player Deposit (CDN Flow)
Tiên quyết: Player liên kết với Shop.
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Shop đủ credit — Shop wallet bị trừ | Shop có 50 credits, player nạp $10 (10 credits) → Hoàn tất | Shop master wallet: −10 credits; Player master wallet: +10 credits | |
| 2 | Shop đủ credit — Shop nhận USD | Kiểm tra Shop USD wallet sau khi player nạp $10, fee 0% | Shop USD wallet: +$10 | |
| 3 | Shop đủ credit — Shop nhận USD trừ fee | Shop nhận deposit với fee 10%, player nạp $10 | Shop USD wallet: +$9 (trừ $1 fee) | |
| 4 | Player nhận đủ credit dù shop có hay không có credit | Shop không có credit, player nạp $10 | Player master wallet: +10 credits | |
| 5 | Shop không nhận USD khi hệ thống cover | Shop = 0 credits, player nạp $10 → Hoàn tất | Shop USD wallet không tăng; hệ thống giữ USD | |
| 6 | Payer cascade khi Shop hết credit | Shop = 0 credit, Agent có credit → Player nạp | Agent wallet bị trừ, Player nhận đủ |
TC-DEP-004 — Flow 3: Shop Counter Deposit (Cashier)
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Staff chuyển credit thành công | Đăng nhập Shop portal → "Transfer to Player" → Nhập player + amount → Confirm | Shop master wallet: −amount, Player master wallet: +amount | |
| 2 | CashierTransaction được tạo | Kiểm tra DB sau khi transfer | Có bản ghi type = 'deposit_credits' trong cashier_transactions | |
| 3 | paymentMethod được lưu đúng | Thực hiện transfer với paymentMethod = 'cash' | cashier_transactions.paymentMethod = 'cash' | |
| 4 | Player không thuộc shop bị từ chối | Thử transfer cho player thuộc shop khác | Lỗi "Player không thuộc shop này" | |
| 5 | Shop inactive không thể thực hiện | Đặt shop status = 'inactive' → Thử cashier deposit | HTTP 4xx / thông báo lỗi shop không hoạt động |
TC-DEP-005 — Flow 4: Multi-Game Deposit
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Nạp $50 phân bổ cho 3 game | Tạo deposit với syncMetadata.allocations = [{game1: $20}, {game2: $20}, {game3: $10}] → Hoàn tất | Mỗi game wallet được credit đúng số lượng | |
| 2 | multiGameProcessedAt được set sau khi xử lý | Kiểm tra syncMetadata sau khi hoàn tất | syncMetadata.multiGameProcessedAt có giá trị ISO timestamp | |
| 3 | Game provider có depositType = 'manual' → vào manual_pending | Có 1 game trong danh sách có depositType = 'manual' | allocationResults[i].status = 'manual_pending', tạo manual deposit request | |
| 4 | Idempotency: gọi lại xử lý lần 2 | Trigger processMultiGameDeposit() lần 2 cho cùng transaction | Không bị credit 2 lần (bị skip vì multiGameProcessedAt đã tồn tại) |
TC-DEP-006 — Flow 5: CDN Credit Purchase (Agent/Shop mua credit qua gateway)
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Shop mua credit thành công | Shop dùng payment gateway thanh toán $80, costRate=0.80 | Shop credit wallet: +100 credits | |
| 2 | Tính đúng số credit theo costRate | costRate = 0.80, grossUsd = $80 → creditComputed = 80 / 0.80 | creditComputed = 100 | |
| 3 | USD wallet shop được cộng sau khi mua | Kiểm tra shop USD wallet sau khi CDN purchase hoàn tất | balanceGross += 80, balanceNet += 80 − fee | |
| 4 | Xử lý 0x insufficient (partial payment) | 0x webhook trả về status='insufficient' nhưng receivedUsd ≥ expected × (1 − feeRate) | Transaction vẫn được mark completed, xử lý với receivedNetUsd |
5. Tính phí (Fee Calculation)
TC-FEE-001 — Deposit Fee Logic
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Player nhận đủ credit không bị trừ fee | Nạp $10 với feeRate = 10% | Player nhận 10 credits (không phải 9) | |
| 2 | Fee trừ vào phần Shop nhận | Shop nhận USD sau khi player nạp $10, feeRate=10% | Shop nhận $10 − $1 = $9 (USD) | |
| 3 | Tính đúng lợi nhuận shop | shopRate=0.80, player nạp $10, fee=10% | Shop profit = $9 − ($10 × $0.80) = $9 − $8 = $1 | |
| 4 | Fee lookup — Exact match | Cấu hình fee cho (provider, 'deposit', methodId) | Sử dụng fee rate của exact match | |
| 5 | Fee lookup — Fallback khi không có exact match | Không có fee cho methodId cụ thể, có fee cho (provider, 'deposit', 'all') | Sử dụng fee rate của 'all' fallback | |
| 6 | Fee lookup — Default = 0 | Không có cấu hình fee nào khớp | platformFeeRate = 0, platformFeeAmount = 0 | |
| 7 | Fee rate là snapshot bất biến | Tạo transaction, sau đó thay đổi fee config | Fee trên transaction không thay đổi theo config mới | |
| 8 | Lỗi resolve fee không block giao dịch | Simulate lỗi khi resolve fee rate | Giao dịch vẫn tiếp tục với feeRate = 0 (non-blocking) |
6. Hệ thống Rút tiền (Withdrawal)
TC-WD-001 — Luồng rút tiền cơ bản
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Tạo yêu cầu rút tiền thành công | Player submit withdrawal với amount, withdrawalType | PaymentTransaction tạo với status = 'pending', reservedBalance += amount | |
| 2 | Credit bị lock khi pending | Kiểm tra master wallet sau khi submit | reservedBalance += amount, balance không thay đổi | |
| 3 | Admin approve → chuyển sang processing | Admin duyệt withdrawal | Status: pending → approved → processing | |
| 4 | Admin approve → Payout adapter được gọi | Sau khi admin approve | Payout adapter (LinkMePay/BTCPay/0x) nhận lệnh chuyển tiền | |
| 5 | Hoàn tất — wallet bị trừ, status completed | Payout thành công | Status → completed, balance −= amount, reservedBalance −= amount | |
| 6 | Admin reject — reserved balance được giải phóng | Admin từ chối withdrawal | Status → rejected, reservedBalance −= amount (credits trả lại) | |
| 7 | Tính phí rút tiền đúng | Rút $100 với platformFeeRate = 2% | grossUsd = 100, platformFeeAmount = $2, netPayoutUsd = $98 | |
| 8 | Pending timeout → auto-reject | Để giao dịch pending quá timeout | Hệ thống tự chuyển sang rejected, credits được giải phóng |
TC-WD-002 — Phương thức rút tiền
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Rút qua bank transfer (LinkMePay) | withdrawalType = 'bank_transfer' | Gọi LinkMePay payout adapter | |
| 2 | Rút qua Bitcoin (BTCPay) | withdrawalType = 'bitcoin_transfer' | Gọi BTCPay withdrawal adapter | |
| 3 | Rút qua USDT (0x Processing) | withdrawalType = 'zerox_usdt' | Gọi 0xProcessing withdrawal adapter |
7. Payment Gateway Callbacks
TC-CALLBACK-001 — LinkMePay IPN
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | IPN với HMAC hợp lệ được xử lý | Gửi POST tới /api/payment/linkmepay/ipn/:id với signature đúng | Raw payload lưu vào CallbackLog, transaction được cập nhật | |
| 2 | IPN với HMAC sai bị từ chối | Gửi IPN với signature không đúng | HTTP 403 | |
| 3 | ALLOW_UNVERIFIED_LINKMEPAY_CALLBACKS=true bypass signature | Bật env var, gửi IPN không có signature | IPN được xử lý (không bị từ chối) | |
| 4 | IPN thành công → processCompletedDeposit | Gửi IPN với status=success hoặc state=2 | Gọi processCompletedDeposit(), credit wallet | |
| 5 | IPN thất bại (state=3) | Gửi IPN với state=3 | Transaction chuyển sang failed |
TC-CALLBACK-002 — 0x Processing Webhook
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Webhook deposit (status='success') | Gửi webhook 0x với status='success' | Transaction → completed, credit wallet | |
| 2 | Webhook status='insufficient' đủ threshold | Gửi webhook với status='insufficient', receivedUsd ≥ expected × (1 − feeRate) | Transaction được mark completed, syncMetadata.receivedNetUsd lưu giá trị nhận được | |
| 3 | Webhook status='insufficient' không đủ threshold | receivedUsd < expected × (1 − feeRate) | Transaction không được settle, bị skip | |
| 4 | Webhook withdrawal (has ID + Address) | Gửi webhook với ID + Address | Xử lý như withdrawal callback | |
| 5 | Webhook CDN static wallet (ClientId starts with 'shop:') | Gửi webhook với ClientId = 'shop:xxx' | Xử lý như CDN static wallet |
TC-CALLBACK-003 — BTCPay Webhook
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Webhook với signature hợp lệ | Gửi webhook với HMAC đúng trong header btcpay-sig | Được xử lý | |
| 2 | Webhook không có signature (khi secret đã cấu hình) | Gửi webhook không có header btcpay-sig | HTTP 401 | |
| 3 | BTCPay invoice settled → processCompletedDeposit | Gửi webhook event InvoiceSettled | Gọi processCompletedDeposit(), credit wallet |
8. Game Wallet & Sync
TC-GAMEWALLET-001 — Sync quy trình 2 transaction
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Txn 1: lock + set status processing | Theo dõi DB khi sync bắt đầu | GameWalletTransaction.status = 'processing' | |
| 2 | External API call thành công → Txn 2 hoàn tất | Game API trả về thành công | status = 'completed', walletBalance được trừ đúng | |
| 3 | External API call thất bại | Simulate game API lỗi | GameWalletTransaction → 'failed' trong transaction riêng | |
| 4 | Balance thay đổi giữa Txn 1 và Txn 2 → reject | Thay đổi balance giữa 2 transaction DB | Txn 2 phát hiện bất nhất → không commit | |
| 5 | Idempotency khi gọi lại sync | Trigger sync 2 lần cho cùng transaction | Chỉ credit 1 lần (lần 2 bị bỏ qua) |
TC-GAMEWALLET-002 — syncStatus flow
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Wallet-only deposit có syncStatus = 'none' | Thực hiện deposit không chọn game | syncStatus = 'none' | |
| 2 | Direct-to-game: pending → in_progress → completed | Thực hiện direct-to-game deposit và sync thành công | syncStatus lần lượt = pending → in_progress → completed | |
| 3 | Retry khi syncStatus = 'failed' | Sync thất bại → Worker retry | retryCount tăng lên, nextRetryAt được set |
9. Transaction State Machine
TC-SM-001 — Deposit state transitions
| # | Chuyển trạng thái | Thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | pending → processing | Gateway trả về đang xử lý | Cho phép | |
| 2 | processing → completed | Gateway xác nhận thành công | Cho phép | |
| 3 | processing → failed | Gateway báo lỗi | Cho phép | |
| 4 | processing → expired | Quá DEPOSIT_TIMEOUT_MS | Cho phép | |
| 5 | processing → cancelled | User/admin cancel | Cho phép | |
| 6 | awaiting_admin → completed | Admin duyệt manual game deposit | Cho phép | |
| 7 | awaiting_admin → rejected | Admin từ chối | Cho phép | |
| 8 | completed → pending (không hợp lệ) | Thử đảo ngược trạng thái | HTTP 409 — Transition not allowed, wallet không bị thay đổi | |
| 9 | failed → completed (không hợp lệ) | Thử chuyển từ failed sang completed trực tiếp | HTTP 409 — Transition not allowed |
TC-SM-002 — Withdrawal state transitions
| # | Chuyển trạng thái | Thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | pending → approved | Admin approve | Cho phép | |
| 2 | approved → processing | Gọi payout adapter | Cho phép | |
| 3 | processing → completed | Payout thành công | Cho phép | |
| 4 | pending → rejected | Admin reject | Cho phép | |
| 5 | completed → pending (không hợp lệ) | Thử đảo ngược | HTTP 409 — không cho phép | |
| 6 | approved → rejected (không hợp lệ) | Thử skip step | HTTP 409 — không cho phép (phải qua processing) |
10. Tính năng Xác nhận Crypto khi Rút tiền
Tính năng mới: Hiển thị số crypto ước tính trước khi player xác nhận rút tiền.
TC-CRYPTO-001 — API Endpoint /api/payment/crypto-estimate
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Estimate cho BTC (BTCPay) | GET /api/payment/crypto-estimate?withdrawalType=bitcoin_transfer&netUsd=98 | Trả về { coin: 'BTC', provider: 'btcpay', estimatedCryptoAmount, rateUsd, source: 'btcpay_rates_api' } | |
| 2 | Estimate cho LTC (BTCPay) | withdrawalType=litecoin_transfer&netUsd=98 | Trả về coin: 'LTC', estimate tương ứng | |
| 3 | Estimate cho DOGE (BTCPay) | withdrawalType=dogecoin_transfer&netUsd=98 | Trả về coin: 'DOGE', estimate tương ứng | |
| 4 | Estimate cho USDT (0xProcessing) | withdrawalType=zerox_usdt&netUsd=98 | Trả về { coin: 'USDT', provider: 'zeroxprocessing', estimatedCryptoAmount ≈ 98, rateUsd ≈ 1 } | |
| 5 | Estimate cho ETH (0xProcessing) | withdrawalType=zerox_eth&netUsd=98 | Trả về coin: 'ETH', estimate theo tỉ giá hiện tại | |
| 6 | Thiếu tham số withdrawalType | Gọi API không có withdrawalType | HTTP 400 / validation error | |
| 7 | Thiếu tham số netUsd | Gọi API không có netUsd | HTTP 400 / validation error | |
| 8 | netUsd = 0 | netUsd=0 | Skip fetch, không tính estimate (hoặc trả về 0) | |
| 9 | Coin không tồn tại trong 0x | withdrawalType=zerox_fakecoin | HTTP 422 với reason: 'coin_not_supported' | |
| 10 | Không có player auth | Gọi không có token | HTTP 401 | |
| 11 | fetchedAt là ISO-8601 | Kiểm tra response | fetchedAt có format YYYY-MM-DDTHH:mm:ss.sssZ |
TC-CRYPTO-002 — Hiển thị modal (Frontend)
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Modal BTC hiển thị ước tính với prefix ≈ | Rút tiền BTC → Mở confirm modal | Hiển thị ≈ 0.00101234 BTC | |
| 2 | Modal BTC hiển thị rate | Xem modal BTC | Hiển thị Rate: 1 BTC = $XX,XXX.XX (live estimate) | |
| 3 | USDT hiển thị note stablecoin | Rút tiền USDT → Mở modal | Hiển thị 1 USDT ≈ $1.00 — stablecoin | |
| 4 | Địa chỉ ví được mask | Địa chỉ dài → Hiển thị trong modal | Hiển thị dạng bc1q...a7fg (6 ký tự đầu + ... + 4 ký tự cuối) | |
| 5 | Disclaimer của BTCPay đúng nội dung | Kiểm tra modal BTC | Có text "Final amount determined by BTCPay at payout time" | |
| 6 | Disclaimer của 0xProcessing đúng nội dung | Kiểm tra modal USDT | Có text "Final amount confirmed by 0xProcessing at payout time" | |
| 7 | Bank transfer không hiển thị crypto section | Chọn bank_transfer → Mở modal | Không có section "Estimated crypto payout" | |
| 8 | BTC decimal precision: 8 chữ số | Kiểm tra hiển thị BTC | 0.00101234 (8 chữ số thập phân) | |
| 9 | DOGE decimal precision: 2 chữ số | Kiểm tra hiển thị DOGE | XX.XX DOGE (2 chữ số thập phân) | |
| 10 | USDT decimal precision: 2 chữ số | Kiểm tra hiển thị USDT | 98.00 USDT (2 chữ số thập phân) |
TC-CRYPTO-003 — Edge cases
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Estimate API timeout → modal vẫn mở được | Simulate API timeout | Modal mở không có crypto section; player vẫn submit được | |
| 2 | Estimate API lỗi → modal vẫn mở được | Simulate API 500 | Modal mở không có crypto section; không block submit | |
| 3 | Modal mở > 2 phút — badge "Rate may be stale" | Để modal mở hơn 2 phút | Xuất hiện badge cảnh báo "Rate may be stale — close and reopen to refresh" | |
| 4 | Rate không tự động refresh | Đợi > 2 phút trong modal | Rate không tự động cập nhật (tránh race condition) | |
| 5 | BTCPay testnet — label (testnet) | Dùng môi trường testnet | Hiển thị thêm (testnet) bên cạnh coin symbol |
TC-CRYPTO-004 — Snapshot lưu trong transaction
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Snapshot được lưu vào metadata | Submit withdrawal với cryptoEstimateSnapshot | transaction.metadata.cryptoEstimateSnapshot có đủ các field | |
| 2 | Snapshot không ảnh hưởng tính toán backend | Gửi snapshot sai tỉ giá | Payout amount vẫn được tính từ netPayoutUsd theo thời gian thực | |
| 3 | Bank transfer — không có snapshot | Submit bank_transfer withdrawal | metadata.cryptoEstimateSnapshot = null | |
| 4 | Snapshot chứa đúng field | Kiểm tra DB | Có { coin, rateUsd, estimatedCryptoAmount, source, fetchedAt } |
11. Giới hạn Giao dịch & Fraud Flag
TC-LIMIT-001 — Transaction Limits
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Vượt giới hạn deposit tối đa per transaction | Cấu hình max $500/tx → Thử nạp $600 | Bị từ chối, thông báo vượt giới hạn | |
| 2 | Vượt giới hạn deposit hàng ngày | Cấu hình max $1000/ngày → Nạp tổng >$1000 trong ngày | Giao dịch bị từ chối | |
| 3 | Vượt giới hạn số lượt deposit per giờ | Cấu hình max 5 deposits/giờ → Thực hiện lượt 6 trong cùng giờ | Bị từ chối, rate limit |
TC-LIMIT-002 — Fraud Flags
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Giao dịch đáng ngờ bị flag | Thực hiện giao dịch vượt ngưỡng fraudFlags | Giao dịch được tạo bản ghi trong transaction_fraud_flags | |
| 2 | Admin có thể xem fraud flags | Đăng nhập Admin panel → Kiểm tra fraud flags | Hiển thị danh sách giao dịch bị flag |
12. Shop USD Wallet — Mua Credit & Rút tiền
TC-SHOP-001 — Mua credit từ USD wallet
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Shop mua 100 credits với costRate=0.80 | Shop USD wallet = $100, mua 100 credits | USD wallet: −$80, Credit wallet: +100 | |
| 2 | Shop không đủ USD → bị từ chối | USD wallet = $50, thử mua 100 credits ($80) | Lỗi không đủ số dư | |
| 3 | Số dư USD còn lại đúng sau mua | Sau bước 1 | USD wallet balance = $20 | |
| 4 | costRate được áp dụng đúng | Mua với costRate khác nhau | Credits = USD / costRate |
TC-SHOP-002 — Shop rút tiền USD
| # | Mô tả | Bước thực hiện | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | Shop tạo yêu cầu rút USD | Shop submit withdrawal từ USD wallet | Tạo PaymentTransaction { type: 'withdrawal' } | |
| 2 | Quy trình duyệt giống player withdrawal | Admin approve, payout adapter được gọi | Cùng flow: pending → approved → processing → completed |
13. API Endpoints & Error Handling
TC-API-001 — Player-facing Endpoints
| # | Endpoint | Test | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | POST /api/payment/deposit | Không có auth | HTTP 401 | |
| 2 | GET /api/payment/deposit-history | Player có auth | Trả về danh sách transactions của player đó | |
| 3 | GET /api/payment/balance | Player có auth | Trả về { balance, reservedBalance } | |
| 4 | GET /api/payment/supported-methods | Player có auth | Trả về danh sách phương thức thanh toán đang hoạt động | |
| 5 | GET /api/payment/provider-fee-rate | Player có auth, provider hợp lệ | Trả về fee rate | |
| 6 | GET /api/payment/zeroxprocessing/coins | Player có auth | Trả về danh sách coin 0x hỗ trợ | |
| 7 | GET /api/payment/reserved-balance-detail | Player có auth | Trả về chi tiết reserved balance |
TC-API-002 — Shop-facing Endpoints
| # | Endpoint | Test | Kết quả mong đợi | Kết quả thực tế |
|---|---|---|---|---|
| 1 | POST /api/shop/deposits | Shop auth, player thuộc shop | Credits được chuyển thành công | |
| 2 | POST /api/shop/deposits | Shop auth, player không thuộc shop | HTTP 400/403 | |
| 3 | GET /api/shop/deposits | Shop auth | Trả về lịch sử deposits của shop |
TC-API-003 — Error Responses
| # | Mô tả | Kích hoạt | HTTP Status | Message mong đợi |
|---|---|---|---|---|
| 1 | User inactive | Deposit với user status inactive | 400 | "User inactive" |
| 2 | Invalid amount | Amount = 0 hoặc âm | 400 | "Invalid amount" |
| 3 | Unknown game provider | gameProviderId không tồn tại | 400 | "Provider not found" |
| 4 | Payment adapter failure | Gateway down | 500 | "Gateway error" |
| 5 | Webhook signature invalid | HMAC sai | 403 | "Signature mismatch" |
| 6 | Invalid state transition | Chuyển trạng thái không hợp lệ | 409 | "Transition not allowed" |
Tổng hợp Test Cases
| Module | Số TC | Ghi chú |
|---|---|---|
| Kiến trúc & Vai trò | 8 | |
| Đăng ký Player | 7 | |
| Hệ thống Ví | 9 | |
| Nạp tiền (5 flows) | 27 | Flow CDN, cashier, multi-game, direct-to-game |
| Tính phí | 8 | |
| Rút tiền | 10 | |
| Callbacks (3 gateways) | 13 | LinkMePay, 0x, BTCPay |
| Game Wallet & Sync | 8 | |
| State Machine | 11 | |
| Xác nhận Crypto (tính năng mới) | 26 | API + UI + edge cases + snapshot |
| Giới hạn & Fraud | 5 | |
| Shop USD Wallet | 6 | |
| API Endpoints | 16 | |
| Tổng | 154 |
Tài liệu được tạo từ site-documents/docs/ — phiên bản 2026-05-10