GVI Phase 3 — Activation Playbook

Admin Login · Bank Account · VietQR/VNPAY · eKYC C06 VNPT  |  DB: v5 · goldvninvest.online

📊 Trạng Thái Hiện Tại (2026-07-20)

Thành phầnTrạng tháiGiá trị
Admin account✅ Hoạt độngadmin@goldvninvest.online · roleId=52
superadmin@example.com✅ Hoạt độngroleId=53 · emailVerified=1
role_permission✅ 1413 permsSuper Admin + Admin + User đầy đủ
binary_market✅ 7 cặpBTC/ETH/BNB/SOL/XRP/DOGE/GIV
binary_duration✅ 6 durations1,3,5,15,30,60 phút
investment_plan✅ 11 plansstarter_vnd, growth_vnd, vip...
staking_pools❌ 0 recordsBảng có — dữ liệu trống
kyc_level❌ 0 recordskyc_verification_service: 3 (SumSub/DeepSeek/Gemini)
internal_wallet❌ Bảng chưa tạoMigration script chưa chạy --apply
payment_provider_config❌ Bảng chưa tạoMigration script chưa chạy --apply
BANK_ACCOUNT_NUMBER (.env)❌ TrốngCần điền số TK thật
VIETQR_WEBHOOK_SECRET (.env)❌ TrốngCần điền secret thật
eKYC VNPT C06⚠️ Chưa cấu hìnhkyc_verification_service chỉ có SumSub/AI — chưa có VNPT

🔐 Bước 3.1 — Test Admin Login

⚠️ Mật khẩu mặc định: Tài khoản superadmin@example.com đang dùng mật khẩu mặc định 12345678. Đổi ngay sau khi đăng nhập thành công.

Thông tin đăng nhập

AccountEmailPasswordRole
Super Admin (chính)admin@goldvninvest.onlinexem DB hoặc .envroleId=52
Super Admin (mặc định)superadmin@example.com12345678roleId=53

Checklist kiểm tra sau login

Fix nhanh nếu Admin Panel vẫn lỗi

pm2 logs backend --lines 50   # Xem lỗi mới nhất
pm2 restart backend           # Restart backend, reload cache
node scripts/redis-seed-cache.js --force  # Warm Redis cache

🗄️ Bước 3.2 — Tạo Bảng internal_wallet & payment_provider_config

❌ Bắt buộc chạy trước: Hai bảng này chưa tồn tại trong DB v5. Phải tạo trước khi điền bank account hoặc cấu hình VietQR.
1
Chạy migration tạo bảng
# Từ thư mục gốc (/var/app/public_html)
cd /var/app/public_html

# DRY-RUN trước — xem thay đổi mà không ghi DB
node backend/scripts/create-internal-wallet-table.mjs

# Kết quả mong đợi DRY-RUN:
# [CREATE] Bảng 'internal_wallet'.
# [ADD]    INDEX 'internalWalletProviderIdx' ...
# DRY-RUN — 5 thay đổi cần áp dụng. Chạy lại với --apply.

# APPLY thực tế
node backend/scripts/create-internal-wallet-table.mjs --apply
2
Tạo bảng payment_provider_config thủ công nếu chưa có
mysql -u goldvninvest v5 -e "
CREATE TABLE IF NOT EXISTS \`payment_provider_config\` (
  \`id\`                  CHAR(36)      NOT NULL,
  \`name\`                VARCHAR(100)  NOT NULL UNIQUE,
  \`provider\`            ENUM('STRIPE','PAYPAL','COINBASE','NOWPAYMENTS',
                              'MANUAL','VIETQR','VNPAY','MOMO','ZALOPAY')
                            NOT NULL,
  \`environment\`         ENUM('SANDBOX','PRODUCTION') NOT NULL DEFAULT 'SANDBOX',
  \`credentials\`         JSON NULL,
  \`supportedCurrencies\` JSON NULL,
  \`webhookSecret\`       VARCHAR(500) NULL,
  \`callbackUrl\`         VARCHAR(1000) NULL,
  \`status\`              ENUM('ACTIVE','INACTIVE','ERROR') NOT NULL DEFAULT 'INACTIVE',
  \`priority\`            INT NOT NULL DEFAULT 10,
  \`createdAt\`           DATETIME NOT NULL,
  \`updatedAt\`           DATETIME NOT NULL,
  PRIMARY KEY (\`id\`),
  INDEX \`paymentProviderConfigStatusIdx\` (\`status\`),
  INDEX \`paymentProviderConfigProviderIdx\` (\`provider\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
"
3
Xác nhận tạo thành công
mysql -u goldvninvest v5 -e "SHOW TABLES LIKE 'internal_wallet'; SHOW TABLES LIKE 'payment_provider_config';"
# Expected: 2 tables listed

pm2 restart backend  # Backend cần restart để nhận model mới

🏦 Bước 3.3 — Điền Bank Account (internal_wallet)

Thay STK_THUC_TE, TEN_CHU_TK, MA_NGAN_HANG bằng thông tin thật của công ty trước khi chạy SQL. Tên chủ tài khoản phải đúng như in trên tài khoản ngân hàng để VietQR xác minh auto-credit.

Mã ngân hàng Napas (bankCode) — tham khảo nhanh

Ngân hàngbankCode (BIN Napas)Số TK bắt đầu bằng
Vietcombank (VCB)9704360071…
Techcombank (TCB)9704071903…
MB Bank9704220909… / 0777…
BIDV97041821…
Vietinbank (CTG)9704151001… / 1002…
ACB97041605… / 06…
Sacombank97040303…

SQL — Insert tài khoản ngân hàng nhận tiền (thay STK thật)

-- Ưu tiên 1: Tài khoản nhận chính (VD: VCB)
INSERT INTO internal_wallet
  (id, name, provider, currency, balance,
   bankAccountNumber, bankName, bankCode, accountHolderName,
   isDepositWallet, isWithdrawWallet,
   minDeposit, maxDeposit, status,
   accountDetails, createdAt, updatedAt)
VALUES (
  UUID(),
  'VCB Nhận Nạp Chính',
  'VIETQR',
  'VND',
  0,
  'STK_THUC_TE_VCB',         -- ← thay bằng số TK thật (VD: 0071000123456)
  'Vietcombank',
  '970436',                   -- BIN Napas của VCB
  'CONG TY TNHH GVI',        -- ← tên chủ TK đúng như ngân hàng
  1,  -- isDepositWallet
  0,  -- isWithdrawWallet
  50000,     -- minDeposit: 50,000 VND
  500000000, -- maxDeposit: 500 triệu VND
  1,
  JSON_OBJECT(
    'qrTemplate',  'https://img.vietqr.io/image/970436-STK_THUC_TE_VCB-compact2.png',
    'napasCode',   '970436',
    'transferNote', 'GVN{userId}'
  ),
  NOW(), NOW()
);

-- Ưu tiên 2: Tài khoản dự phòng (VD: Techcombank)
INSERT INTO internal_wallet
  (id, name, provider, currency, balance,
   bankAccountNumber, bankName, bankCode, accountHolderName,
   isDepositWallet, isWithdrawWallet,
   minDeposit, maxDeposit, status,
   accountDetails, createdAt, updatedAt)
VALUES (
  UUID(),
  'TCB Dự Phòng',
  'VIETQR',
  'VND',
  0,
  'STK_THUC_TE_TCB',         -- ← thay bằng số TK thật
  'Techcombank',
  '970407',
  'CONG TY TNHH GVI',
  1, 0,
  50000, 500000000,
  1,
  JSON_OBJECT('napasCode', '970407'),
  NOW(), NOW()
);

-- Xác nhận
SELECT name, bankName, bankAccountNumber, bankCode,
       accountHolderName, isDepositWallet, status
FROM internal_wallet;

Cập nhật .env sau khi điền TK

# --------------- Bank Account (VietQR IPN auto-credit) --------------- BANK_ACCOUNT_NUMBER=0071000123456 # Số TK ngân hàng nhận tiền BANK_ACCOUNT_NAME=CONG TY TNHH GVI # Tên chủ TK (không dấu, in hoa) BANK_CODE=VCB # Alias ngân hàng (VCB/MB/TCB/ACB...) VNL_MEMO_PREFIX=GVN # Prefix memo — đã có, không đổi

💳 Bước 3.4 — Cấu Hình VietQR & VNPAY

A. VietQR (Casso / MB Open Banking)

Cách lấy webhook secret: Đăng nhập casso.vn → Tích hợp → Webhook → Tạo mới → copy secretKey. Hoặc dùng api.vietqr.io.

Insert VietQR vào payment_provider_config

INSERT INTO payment_provider_config
  (id, name, provider, environment, credentials,
   supportedCurrencies, webhookSecret, callbackUrl,
   status, priority, createdAt, updatedAt)
VALUES (
  UUID(),
  'VietQR Production',
  'VIETQR',
  'PRODUCTION',
  JSON_OBJECT(
    'bankCode',     '970436',         -- BIN Napas ngân hàng nhận
    'accountNo',    'STK_THUC_TE',    -- ← thay bằng số TK thật
    'accountName',  'CONG TY TNHH GVI',
    'memoPrefix',   'GVN',
    'cassoToken',   'CASSO_API_TOKEN_DAY',  -- ← token từ casso.vn
    'ipnSecretKey', 'WEBHOOK_SECRET_DAY'    -- ← secret từ casso.vn
  ),
  JSON_ARRAY('VND', 'VNL'),
  'WEBHOOK_SECRET_DAY',   -- phải giống ipnSecretKey
  'https://goldvninvest.online/api/webhook/vietqr',
  'ACTIVE',
  1,
  NOW(), NOW()
);

-- Thêm webhook secret vào .env
-- VIETQR_WEBHOOK_SECRET=WEBHOOK_SECRET_DAY

Update .env — VietQR secrets

VIETQR_WEBHOOK_SECRET=your_casso_webhook_secret # từ casso.vn → Webhook → secretKey

Luồng hoạt động VietQR tự động

BướcHành độngEndpoint
1User gọi tạo memoPOST /api/finance/deposit/fiat/create-memo
2User chuyển khoản với memo GVN{id}— (ngân hàng) —
3Casso/Bank gửi IPNPOST /api/webhook/vietqr
4vnfAutoDepositService xử lýCộng VNL vào ví FIAT
5WebSocket thông báoUser nhận thông báo < 30s

B. VNPAY (Sandbox → Production)

Cách lấy TMN Code & Hash Secret: Đăng ký tại sandbox.vnpayment.vn/devreg → Nhận tmnCode + hashSecret qua email.
INSERT INTO payment_provider_config
  (id, name, provider, environment, credentials,
   supportedCurrencies, webhookSecret, callbackUrl,
   status, priority, createdAt, updatedAt)
VALUES (
  UUID(),
  'VNPay Sandbox',
  'VNPAY',
  'SANDBOX',
  JSON_OBJECT(
    'tmnCode',    'YOUR_TMN_CODE',     -- ← từ VNPay developer portal
    'hashSecret', 'YOUR_HASH_SECRET',  -- ← từ VNPay developer portal
    'payUrl',     'https://sandbox.vnpayment.vn/paymentv2/vpcpay.html',
    'returnUrl',  'https://goldvninvest.online/finance/deposit/vnpay/return',
    'ipnUrl',     'https://goldvninvest.online/api/webhook/vnpay'
  ),
  JSON_ARRAY('VND'),
  NULL,
  'https://goldvninvest.online/api/webhook/vnpay',
  'INACTIVE',   -- Để INACTIVE cho đến khi test sandbox xong
  2,
  NOW(), NOW()
);

-- Khi test sandbox thành công → đổi sang PRODUCTION + ACTIVE:
UPDATE payment_provider_config
SET environment = 'PRODUCTION', status = 'ACTIVE',
    credentials = JSON_SET(credentials,
      '$.payUrl', 'https://pay.vnpay.vn/vpcpay.html',
      '$.tmnCode', 'PROD_TMN_CODE',
      '$.hashSecret', 'PROD_HASH_SECRET'
    )
WHERE provider = 'VNPAY';
VNPAY_HASH_SECRET=your_vnpay_hash_secret

C. Xác nhận cấu hình thanh toán

-- Kiểm tra payment providers
SELECT name, provider, environment, status, priority
FROM payment_provider_config
ORDER BY priority;

-- Kiểm tra internal wallet
SELECT name, bankName, bankAccountNumber, bankCode,
       isDepositWallet, status
FROM internal_wallet
WHERE isDepositWallet = 1;

-- Smoke test IPN endpoint (thay memo và amount thật)
curl -X POST https://goldvninvest.online/api/webhook/vietqr \
  -H "Content-Type: application/json" \
  -d '{"id":"TEST-001","amount":100000,"description":"GVN","bankCode":"VCB"}'
# Expected: {"success":true,"processed":{...}}

🪪 Bước 3.5 — eKYC C06 qua VNPT (ekyc.vnpt.vn)

C06 là gì? Cơ sở dữ liệu căn cước công dân Bộ Công An (CCCD). VNPT eKYC tích hợp tra cứu C06 giúp xác minh danh tính người dùng theo đúng quy định pháp lý Việt Nam. Endpoint: https://api.idg.vnpt.vn/

Bước 3.5.1 — Đăng ký & lấy token VNPT eKYC

1
Truy cập https://ekyc.vnpt.vn → Đăng nhập tài khoản doanh nghiệp (hoặc đăng ký mới)
2
Vào menu Token Management → Tạo token mới → Copy 3 giá trị:
Tham sốMô tảDùng ở đâu
TOKEN_KEYAPI KeyHeader token-key
TOKEN_IDToken IDHeader token-id
AUTHORIZATIONBearer token (bỏ chữ "Bearer")Header Authorization: Bearer ...
3
Gửi email yêu cầu whitelist domain production: goldvninvest.online
Liên hệ: https://ekyc.vnpt.vn/vi/contact
(Môi trường dev: chỉ cần localhost:4200 — không cần whitelist)

Bước 3.5.2 — Seed kyc_verification_service VNPT

Bảng kyc_verification_service hiện có 3 records (SumSub, DeepSeek, Gemini). Cần thêm VNPT-C06.
-- Thêm VNPT eKYC service (VNPT C06 — Căn cước công dân)
INSERT INTO kyc_verification_service
  (id, name, description, type, integrationDetails, createdAt, updatedAt)
VALUES (
  'vnpt-ekyc-1',
  'VNPT eKYC — C06 Bộ Công An',
  'Xác minh danh tính công dân Việt Nam qua cơ sở dữ liệu C06 Bộ Công An. Hỗ trợ CCCD gắn chip, ePassport, nhận diện khuôn mặt liveness.',
  'VNPT_EKYC',
  JSON_OBJECT(
    'features', JSON_ARRAY(
      'CCCD_CHIP',
      'C06_LOOKUP',
      'FACE_MATCHING',
      'LIVENESS_CHECK',
      'EPASSPORT'
    ),
    'apiBase',      'https://api.idg.vnpt.vn',
    'cdnBase',      'https://ekyc-web.vnpt.vn',
    'sdkGuide',     'https://ekyc.vnpt.vn/vi/docs/web-sdk',
    'tokenKey',     'THAY_BANG_TOKEN_KEY_THUC_TE',
    'tokenId',      'THAY_BANG_TOKEN_ID_THUC_TE',
    'authorization', 'THAY_BANG_ACCESS_TOKEN_THUC_TE'
  ),
  NOW(), NOW()
);

-- Xác nhận
SELECT id, name, type FROM kyc_verification_service;

Bước 3.5.3 — Seed kyc_level cho VNPT

-- Level 1: Xác minh CCCD cơ bản (Rút dưới 50 triệu/ngày)
INSERT INTO kyc_level
  (id, name, description, level, fields, features, status, serviceId, createdAt, updatedAt)
VALUES (
  UUID(),
  'KYC Level 1 — Xác minh CCCD',
  'Xác minh Căn cước công dân qua VNPT eKYC. Cho phép nạp/rút tối đa 50 triệu VND/ngày.',
  1,
  JSON_ARRAY(
    JSON_OBJECT('name', 'frontImage',   'title', 'Ảnh mặt trước CCCD',  'type', 'image', 'required', JSON_VALUE('true','$')),
    JSON_OBJECT('name', 'backImage',    'title', 'Ảnh mặt sau CCCD',   'type', 'image', 'required', JSON_VALUE('true','$')),
    JSON_OBJECT('name', 'selfieImage',  'title', 'Ảnh selfie nhận diện', 'type', 'image', 'required', JSON_VALUE('true','$'))
  ),
  JSON_ARRAY('DEPOSIT', 'WITHDRAW', 'TRADE'),
  'ACTIVE',
  'vnpt-ekyc-1',
  NOW(), NOW()
);

-- Level 2: Xác minh nâng cao (tra cứu C06 + liveness — Rút trên 50 triệu)
INSERT INTO kyc_level
  (id, name, description, level, fields, features, status, serviceId, createdAt, updatedAt)
VALUES (
  UUID(),
  'KYC Level 2 — C06 + Liveness',
  'Xác minh nâng cao: tra cứu C06 Bộ Công An + nhận diện khuôn mặt liveness. Không giới hạn giao dịch.',
  2,
  JSON_ARRAY(
    JSON_OBJECT('name', 'frontImage',   'title', 'Ảnh mặt trước CCCD gắn chip', 'type', 'image'),
    JSON_OBJECT('name', 'backImage',    'title', 'Ảnh mặt sau CCCD gắn chip',  'type', 'image'),
    JSON_OBJECT('name', 'livenessVideo', 'title', 'Video liveness check',          'type', 'video'),
    JSON_OBJECT('name', 'cccdNumber',   'title', 'Số CCCD/CMND',               'type', 'text')
  ),
  JSON_ARRAY('DEPOSIT', 'WITHDRAW_UNLIMITED', 'TRADE', 'P2P', 'FUTURES'),
  'ACTIVE',
  'vnpt-ekyc-1',
  NOW(), NOW()
);

-- Xác nhận
SELECT id, name, level, status, serviceId FROM kyc_level;

Bước 3.5.4 — Cập nhật .env với VNPT credentials

# --------------- VNPT eKYC (C06 Bộ Công An) --------------- VNPT_EKYC_TOKEN_KEY=your_token_key_from_ekyc.vnpt.vn VNPT_EKYC_TOKEN_ID=your_token_id_from_ekyc.vnpt.vn VNPT_EKYC_AUTHORIZATION=your_access_token_no_bearer_prefix VNPT_EKYC_API_BASE=https://api.idg.vnpt.vn VNPT_EKYC_CDN=https://ekyc-web.vnpt.vn

Bước 3.5.5 — Thêm handler VNPT vào check-connection.get.js

File backend/dist/src/api/admin/crm/kyc/service/[id]/check-connection.get.js hiện chỉ handle sumsub, gemini, deepseek. Cần thêm branch cho vnpt-ekyc.
// Thêm vào hàm default export, sau block `else if (id.startsWith("deepseek"))`:

else if (id.startsWith("vnpt")) {
  result = await checkVnptEkycConnection();
}

// Thêm hàm checkVnptEkycConnection() vào cuối file:
async function checkVnptEkycConnection() {
  const tokenKey = process.env.VNPT_EKYC_TOKEN_KEY;
  const tokenId  = process.env.VNPT_EKYC_TOKEN_ID;
  const auth     = process.env.VNPT_EKYC_AUTHORIZATION;
  if (!tokenKey || !tokenId || !auth) {
    return { connected: false,
      message: "Missing VNPT_EKYC_TOKEN_KEY / TOKEN_ID / AUTHORIZATION in .env" };
  }
  try {
    const resp = await fetch(
      `https://api.idg.vnpt.vn/ai/v1/server-info`,
      { method: "GET", headers: {
          "token-key":    tokenKey,
          "token-id":     tokenId,
          "Authorization": `Bearer ${auth}`,
          "Content-Type": "application/json"
        }
      }
    );
    if (resp.ok) return { connected: true, message: "VNPT eKYC API reachable" };
    return { connected: false, message: `VNPT eKYC: HTTP ${resp.status}` };
  } catch(e) {
    return { connected: false, message: `VNPT eKYC error: ${e.message}` };
  }
}

Bước 3.5.6 — Seed staking_pools (còn thiếu)

INSERT INTO staking_pools
  (id, name, token, symbol, description, walletType,
   apr, lockPeriod, minStake, maxStake, availableToStake,
   earlyWithdrawalFee, adminFeePercentage, status, isPromoted,
   \`order\`, earningFrequency, autoCompound, externalPoolUrl,
   profitSource, fundAllocation, risks, rewards, createdAt, updatedAt)
VALUES
(UUID(), 'USDT Flexible Savings', 'Tether USD', 'USDT',
 'Gửi USDT linh hoạt, nhận lãi hàng ngày, rút bất kỳ lúc nào.', 'SPOT',
 0.08, 0, 10, NULL, 1000000, 0, 2,
 'ACTIVE', 1, 1, 'DAILY', 0, NULL,
 'Market Making nội bộ', '70% thanh khoản, 30% dự trữ',
 'Rủi ro thị trường thấp', 'APR 8%, rút bất kỳ lúc nào',
 NOW(), NOW()),

(UUID(), 'USDT 30-Day Lock', 'Tether USD', 'USDT',
 'Khoá USDT 30 ngày, nhận APR 12%.', 'SPOT',
 0.12, 30, 50, 100000, 500000, 5, 2,
 'ACTIVE', 1, 2, 'MONTHLY', 1, NULL,
 'Lending + arbitrage', '60% lending, 40% hedge',
 'Không rút trước 30 ngày (phạt 5%)', 'APR 12%, tự động tái tục',
 NOW(), NOW()),

(UUID(), 'GIV Loyalty Pool', 'GIV Token', 'GIV',
 'Pool đặc biệt cho GIV holder. APR 20%, nhận lãi hàng tuần.', 'ECO',
 0.20, 30, 1000, NULL, 0, 5, 1,
 'COMING_SOON', 1, 3, 'WEEKLY', 1, NULL,
 'Phí platform chia sẻ cho GIV holder', '100% phí giao dịch',
 'GIV là token nội bộ', 'APR 20%, nhận GIV hàng tuần',
 NOW(), NOW());

-- Xác nhận
SELECT name, symbol, apr, lockPeriod, status FROM staking_pools;

✅ Checklist Cuối — Phase 3 Complete

#Hạng mụcLệnh xác nhậnExpected
1internal_wallet bảng tồn tạiSHOW TABLES LIKE 'internal_wallet'1 row
2payment_provider_config tồn tạiSHOW TABLES LIKE 'payment_provider_config'1 row
3Bank account đã điềnSELECT COUNT(*) FROM internal_wallet WHERE isDepositWallet=1≥ 1
4VietQR provider ACTIVESELECT status FROM payment_provider_config WHERE provider='VIETQR'ACTIVE
5kyc_level seededSELECT COUNT(*) FROM kyc_level≥ 2
6VNPT eKYC service thêmSELECT id FROM kyc_verification_service WHERE id='vnpt-ekyc-1'1 row
7staking_pools có dataSELECT COUNT(*) FROM staking_pools WHERE status='ACTIVE'≥ 2
8Admin login OKcurl https://goldvninvest.online/api/settings | jq '.total'149
9VietQR IPN testcurl POST /api/webhook/vietqr{"success":true}
10VNPT eKYC test connectionAdmin → KYC Services → Test ConnectionConnected ✅
11BANK_ACCOUNT_NUMBER .envgrep BANK_ACCOUNT_NUMBER .envKhông còn trống
12VIETQR_WEBHOOK_SECRET .envgrep VIETQR_WEBHOOK_SECRET .envKhông còn trống

Lệnh reload sau tất cả thay đổi

# 1. Restart backend để model nhận bảng mới + .env mới
pm2 restart backend --update-env

# 2. Warm Redis cache với data mới
node scripts/redis-seed-cache.js --force

# 3. Kiểm tra logs không có lỗi mới
pm2 logs backend --lines 100 | grep -E "ERROR|WARN|CRIT"

# 4. Smoke test
curl https://goldvninvest.online/api/health
curl https://goldvninvest.online/api/settings | jq '.total'  # Expected: 149
curl https://goldvninvest.online/api/binary/market | jq '.total'  # Expected: 7
✅ Sau Phase 3: Hệ thống sẵn sàng nhận nạp VNL qua VietQR tự động, xác minh KYC qua VNPT C06, Admin panel đầy đủ quyền. Bước tiếp theo: smoke test end-to-end (nạp tiền thật → nhận VNL → staking → rút).