Tài khoản ảo (Virtual Account) là gì?
Virtual Account (VA) — hay còn gọi là tài khoản ảo, tài khoản định danh — là một số tài khoản ngân hàng được tạo ra và gắn với một merchant cụ thể. Về mặt kỹ thuật, VA không phải tài khoản thật theo nghĩa truyền thống: không có số dư riêng, không mở được bằng CMND. Thay vào đó, VA là một alias (bí danh) được ngân hàng nhận diện và định tuyến vào tài khoản master của merchant.
Khi khách hàng chuyển khoản vào số VA, ngân hàng nhận diện giao dịch, gắn metadata (số VA, số tiền, nội dung) và thông báo về hệ thống của merchant qua IPN callback.
VA khác tài khoản thông thường như thế nào?
| Tài khoản ngân hàng thường | Virtual Account | |
|---|---|---|
| Cách tạo | Mở tại chi nhánh, cần giấy tờ | Tạo qua API trong vài giây |
| Số lượng | Giới hạn | Tạo được hàng nghìn VA/ngày |
| Số dư | Có số dư riêng | Không có số dư — tiền vào master account |
| Nhận diện giao dịch | Dựa vào nội dung chuyển khoản | Dựa vào chính số VA → chính xác 100% |
| Automation | Khó đối soát tự động | Đối soát tự động qua va_number |
Hai mô hình VA Billing chính
Mô hình 1: VA 1 lần (One-time VA)
Tạo một VA mới cho mỗi giao dịch / đơn hàng. Sau khi thanh toán xong, VA hết hiệu lực.
[Đơn hàng #1001] → [VA: VA100001] → [Khách chuyển 500,000đ] → [IPN: PAID ✓]
[Đơn hàng #1002] → [VA: VA100002] → [Khách chuyển 320,000đ] → [IPN: PAID ✓]Ưu điểm: Mỗi giao dịch được định danh tuyệt đối, không nhầm lẫn.
Phù hợp với: E-commerce checkout, đặt cọc dịch vụ, thanh toán hóa đơn một lần.
Mô hình 2: VA nhiều lần (Persistent VA)
Một VA được gán cố định cho một khách hàng / hợp đồng. Khách hàng chuyển khoản vào VA đó mỗi kỳ.
Học sinh Nguyễn Văn A
└─ VA: VA200050 (cố định cả năm học)
├─ Tháng 9: chuyển 2,500,000đ → IPN: Sep fee PAID ✓
└─ Tháng 10: chuyển 2,500,000đ → IPN: Oct fee PAID ✓Ưu điểm: Khách hàng nhớ một số tài khoản duy nhất, không cần thao tác thêm mỗi kỳ.
Phù hợp với: Học phí định kỳ, viện phí theo đợt điều trị, thuê mặt bằng, subscription.
Ứng dụng thực tế
1. Thanh toán học phí (Giáo dục)
Đây là use case phù hợp nhất với mô hình Persistent VA.
Luồng hiện tại (thủ công):
- Phụ huynh chuyển khoản vào tài khoản chung của trường
- Kế toán đối chiếu nội dung chuyển khoản thủ công để xác định học sinh
- Sai nội dung → nhầm học sinh → mất nhiều ngày đối soát
Luồng với VA:
1. Nhập học
└─ POST /openapi/v1/va/va-account/create
{ bank_code: "970403", account_name: "NGUYEN VAN A - LOP 10A1" }
→ VA mới: VA300125
2. Gửi VA cho phụ huynh
└─ In trên phiếu thu, gửi qua app/email
3. Phụ huynh chuyển khoản vào VA300125 (hàng tháng)
└─ Hệ thống tự nhận diện: học sinh A, 2,500,000đ
4. IPN Callback tự động
└─ { order_id: "SCHOOL-A-SEP2026", amount: 2500000 }
→ Cập nhật trạng thái học phí trong phần mềm quản lýKết quả: Xoá bỏ hoàn toàn bước đối soát thủ công. Kế toán chỉ cần xem báo cáo.
2. Thanh toán viện phí (Y tế)
Y tế có thêm yêu cầu phức tạp hơn giáo dục: cùng một bệnh nhân có thể có nhiều đợt điều trị, mỗi đợt nhiều lần thanh toán (thuốc, thủ thuật, giường bệnh...).
Mô hình đề xuất:
Bệnh nhân vào viện → Tạo VA cho đợt điều trị
└─ VA: VA400200 "NGUYEN THI B - DOT DIEU TRI 15052026"
│
├─ Lần 1: Viện phí khám ban đầu 500,000đ
├─ Lần 2: Tiền thuốc ngày 1 1,200,000đ
├─ Lần 3: Phí phẫu thuật 15,000,000đ
└─ Lần 4: Tiền phòng 3 ngày 1,500,000đ
│
└─ Mỗi IPN: loại viện phí + mã đợt điều trị
→ HIS tự cập nhật từng khoản phíTích hợp với hệ thống HIS (Hospital Information System):
# Tiếp nhận bệnh nhân
POST /openapi/v1/va/va-account/create
bank_code: "970415" (Vietinbank)
account_name: "{patient.name} - {admission.code}"
→ Lưu va_number vào bản ghi đợt điều trị
# IPN Handler
function handleIPN(encryptedData):
txn = AES_decrypt(encryptedData)
admission = db.find(txn.order_id)
admission.addPayment(amount=txn.amount, ref=txn.retrieval_ref_no)
return 200 OK3. Bán lẻ (Retail)
Retail có đặc thù: hàng nghìn giao dịch/ngày với nhiều quầy, nhiều nhân viên thu ngân.
Mô hình QR + VA:
Quầy thu ngân #1 → VA cố định #1 → QR động theo order_id
Quầy thu ngân #2 → VA cố định #2 → QR động theo order_idMỗi QR được tạo với order_id chứa mã quầy + mã đơn, giúp đối soát chi tiết đến từng giao dịch.
# POS tạo QR khi khách checkout
POST /openapi/v1/qr/create
order_id: "STORE01-CASH01-{invoice.id}"
va: store.va_number
bincode: "970454"
service_code: "bidv-qr"
amount: invoice.total
→ Hiển thị QR trên màn hình khách
# Sau khi nhận IPN → POS tự đóng đơnĐối soát cuối ca: Báo cáo tổng hợp theo order_id prefix, tự động khớp với POS system.
4. Subscription và dịch vụ định kỳ (SaaS)
Với SaaS B2B, mỗi khách hàng doanh nghiệp có VA riêng. Hệ thống gửi thông báo trước kỳ gia hạn, khách chuyển khoản vào VA → hệ thống tự kích hoạt tiếp.
Doanh nghiệp ABC
└─ VA: VA500010 (hợp đồng năm 2026)
├─ Tháng 5/2026: 1,200,000đ → Gia hạn tháng 6 ✓
├─ Tháng 6/2026: 1,200,000đ → Gia hạn tháng 7 ✓
└─ ...Mô hình này đặc biệt phù hợp với doanh nghiệp Việt Nam vì phần lớn kế toán doanh nghiệp ưa chuyển khoản ngân hàng hơn thẻ khi thanh toán B2B.
Tạo VA qua TConnect API
# 1. Login
POST /openapi/v1/auth/login
{ partner_code: "YOUR_PARTNER_CODE", ... }
→ access_token
# 2. Tạo VA
POST /openapi/v1/va/va-account/create
{ bank_code: "970454", account_name: "NGUYEN VAN A LOP 10A1" }// Response
{ "va_number": "VA100023312", "bank_code": "970454",
"account_name": "NGUYEN VAN A LOP 10A1", "status": "active" }Sau đó dùng va_number này khi tạo QR:
POST /openapi/v1/qr/create
{ order_id: "SCHOOL-NGUYENVANA-SEP2026", va: "VA100023312",
bincode: "970454", service_code: "bidv-qr", amount: 2500000 }Lưu ý kỹ thuật quan trọng
Đặt tên account_name chuẩn
account_name hiển thị trên màn hình ứng dụng ngân hàng của người chuyển tiền. Đặt rõ ràng giúp khách hàng xác nhận đúng trước khi chuyển. Ví dụ:
- ✅
"TRUONG THPT XYZ - NGUYEN VAN A 10A1" - ❌
"student_12345"(khách không nhận ra)
Idempotency trong IPN
function handleIPN(encryptedData):
txn = AES_decrypt(encryptedData)
# Kiểm tra đã xử lý chưa (idempotency)
if cache.exists("ipn:" + txn.request_id):
return 200 OK # Trả 200 để TConnect không retry
cache.set("ipn:" + txn.request_id, ttl=24h)
processPayment(txn)
return 200 OKXử lý chuyển thiếu / thừa tiền
Với học phí / viện phí, khách đôi khi chuyển sai số tiền:
if txn.amount < expected_amount:
admission.addPartialPayment(txn.amount)
# Gửi thông báo yêu cầu nộp bổ sung
elif txn.amount > expected_amount:
admission.addOverpayment(txn.amount - expected_amount)
# Cờ để xử lý hoàn tiền thừa
else:
admission.markFullyPaid()Kết luận
VA không chỉ là "một số tài khoản ngân hàng khác." Đây là công cụ automation mạnh nhất trong hệ sinh thái thanh toán Việt Nam cho các tổ chức xử lý nhiều giao dịch có tính chất định kỳ hoặc cần đối soát tự động.
| Use case | Mô hình VA | Lợi ích chính |
|---|---|---|
| E-commerce | One-time VA mỗi đơn | Đối soát tự động 100% |
| Học phí | Persistent VA mỗi học sinh | Xoá đối soát thủ công |
| Viện phí | Persistent VA mỗi đợt điều trị | Tích hợp trực tiếp HIS |
| Retail POS | Persistent VA mỗi quầy + QR động | Tốc độ + đối soát ca |
| SaaS B2B | Persistent VA mỗi khách hàng | Phù hợp hành vi kế toán VN |
Bắt đầu với Sandbox TConnect để test toàn bộ luồng VA + QR + IPN trong môi trường an toàn, không cần KYB.