VietQR là gì?
VietQR là tiêu chuẩn mã QR thanh toán ngân hàng tại Việt Nam, được Napas và Ngân hàng Nhà nước Việt Nam ban hành. Một mã QR VietQR chứa đầy đủ thông tin để thực hiện chuyển khoản ngân hàng: số tài khoản, tên ngân hàng, số tiền (tuỳ chọn), nội dung chuyển khoản.
Điểm đặc biệt: VietQR hoạt động cross-bank. Khách hàng dùng app ngân hàng bất kỳ đều quét được — không cần cùng ngân hàng với merchant.
QR tĩnh vs QR động
| QR tĩnh | QR động | |
|---|---|---|
| Số tiền | Không cố định — khách tự nhập | Cố định trong QR |
| Nội dung | Cố định | Có thể kèm mã đơn hàng |
| Dùng khi | Quầy thanh toán đơn giản, donate | Checkout e-commerce, kiosk |
| Đối soát | Khó tự động | Dễ tự động qua order_id |
Với hầu hết hệ thống B2B và e-commerce, QR động là lựa chọn đúng — bạn gắn order_id vào QR và nhận IPN callback khi thanh toán thành công.
Kiến trúc tích hợp
[Ứng dụng của bạn]
│
▼
POST /openapi/v1/qr ──► [TConnect API] ──► [Ngân hàng / Napas]
│ │
│◄─── qr_content / image_png_base64 ────────────┘
│
▼
[Render QR cho khách hàng]
│
│ Khách quét & thanh toán
▼
[TConnect IPN Callback] ──► [Backend của bạn] ──► Cập nhật đơn hàngBước 1: Lấy service_code
Trước khi tạo QR, gọi API Get Services để lấy service_code phù hợp với ngân hàng bạn dùng.
curl -X GET "https://sme-open-api-sandbox.tconnect.vn/openapi/v1/services?payment_method=qr&limit=10&page=1" \
-H "Partner-Code: YOUR_PARTNER_CODE" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"Response trả về danh sách service, ví dụ "code": "bidv-qr". Đây là giá trị bạn sẽ truyền vào header x-service-code.
Bước 2: Tạo Virtual Account
VA (Virtual Account / Tài khoản ảo) là số tài khoản định danh cho merchant. Tạo một lần, dùng mãi.
// Tạo Virtual Account một lần, dùng mãi
payload = {
bank_code: "970454", // BIDV
account_name: "CONG TY THUONG MAI ABC"
}
encrypt payload then POST to /openapi/v1/va/va-account/create
// Response: { "va_number": "VA100023312", "status": "active" }Bước 3: Tạo QR cho đơn hàng
// Khởi tạo client và đăng nhập
client = new TConnectClient(base_url, partner_code, aes_key)
client.login(username, password, client_id, client_secret)
// Tạo QR cho đơn hàng
qr_response = client.create_qr(
order_id: "ORDER_20260510_001",
va: "VA100023312",
bincode: "970454",
service_code: "bidv-qr",
amount: 250000 // 250,000 VND — truyền 0 cho QR tĩnh
)
// qr_response.image_png_base64 → dùng trong <img src="data:image/png;base64,...">
// qr_response.qr_content → dùng render bằng thư viện QR codeHiển thị trong React
// Component hiển thị ảnh QR từ base64 data
function PaymentQR(qrData):
render image:
src = "data:image/png;base64," + qrData.image_png_base64
alt = "QR thanh toán"
size = 192 x 192 pxBước 4: Nhận IPN khi thanh toán thành công
// IPN endpoint: POST /webhook/tconnect/ipn
function ipn_handler(request):
body = parse_json(request)
txn = decrypt_aes(body.data, AES_KEY)
// Idempotency check
if is_processed(txn.request_id):
return { status: "ok" }
mark_order_paid(txn.order_id, txn.amount)
return { status: "ok" } // Bắt buộc trả 200Quan trọng: TConnect retry IPN nếu không nhận được HTTP 200. Luôn implement idempotency bằng
request_id.
Xử lý edge cases
QR hết hạn: QR VietQR không có TTL cứng, nhưng bạn nên tự set timeout phía backend (ví dụ: 15 phút) và huỷ đơn nếu quá hạn.
Duplicate IPN: Xảy ra khi network retry. Dùng request_id làm unique key để tránh charge 2 lần.
Amount mismatch: Kiểm tra txn["amount"] trong IPN so với expected_amount của đơn hàng trước khi mark paid.
Kết luận
VietQR đơn giản hơn card payment và phổ biến hơn nhiều tại Việt Nam (đặc biệt với SME). Luồng tích hợp:
- Login → lấy token
- Get Services → lấy
service_code - Create VA → lấy
va_number - Create QR → hiển thị cho khách
- IPN Callback → cập nhật đơn hàng
Toàn bộ luồng có thể implement trong dưới 4 giờ với Sandbox của TConnect.