API tạo mã QR ngân hàng ACB (VietQR động) theo đơn hàng

Cập nhật 28/08/2026Bản Markdownllms.txtopenapi.json

Video hướng dẫn

Tạo mã QR ngân hàng ACB (VietQR động) theo đơn hàng bằng API · Xem trên YouTube
Xem transcript video (9 phần)

QR điền sẵn đúng đơn

Khách gõ sai số tiền hoặc nội dung chuyển khoản thì hệ thống rất khó khớp đơn. Trong video này, mình tạo VietQR động từ API ACB, điền sẵn tài khoản, số tiền và mã đơn, hiển thị QR rồi nhận webhook sau khi khách quét. Bạn cũng sẽ biết cách hủy mã tạo nhầm và xử lý các lỗi trường bắt buộc.

Điều kiện tạo QR

Endpoint tạo là POST /api/v1/acb/qr-payment/generate, cần Bearer và X-Client-Secret. Điều kiện là tài khoản ACB đã nối và đã có tài khoản ảo. Nếu chưa muốn gọi API, dashboard có mục Tạo QR để chọn VA, nhập số tiền rồi tải ảnh. Qua API, response quan trọng nhất là qr_data_url, chuỗi dữ liệu dùng để thư viện của bạn vẽ thành ảnh QR.

Thông tin bên nhận

Nhóm nhận tiền gồm ownerNumber là số tài khoản ACB, ownerType nhận PER cho cá nhân hoặc ORG cho doanh nghiệp. merchantId là mã đơn vị chấp nhận thanh toán và terminalId là mã điểm bán, cả hai do ACB cấp và hiển thị trong mục Tạo QR. beneficiaryName là tên người hoặc đơn vị hưởng hiện trên ứng dụng ngân hàng của khách.

Thông tin đơn hàng

Nhóm đơn hàng gồm orderId, mã duy nhất cho mỗi lần thanh toán; virtualAccountPrefix, đầu số VA dài từ một đến mười ký tự; và amount, số nguyên VND từ không đến một tỷ. Số không cho khách tự nhập tiền. description là nội dung chuyển khoản, còn traceNumber là mã theo dõi riêng để tra soát. Bạn có thể thêm userId, mã ưu đãi và additionalInfo khi cần.

Vẽ QR từ response

Response trả bản ghi QR với id, virtual_account_number, thông tin đơn và qr_data_url. Đây là dữ liệu QR theo chuẩn VietQR EMVCo. Dùng chuỗi qr_data_url với thư viện vẽ QR, chẳng hạn qrcode trong Node, rồi gắn ảnh vào trang thanh toán. Lưu id để hủy khi đơn bị hủy hoặc đổi số tiền. Đừng dùng virtualAccountPrefix thay cho virtual_account_number, vì số đầy đủ do ACB trả về.

Hủy QR tạo nhầm

Nếu QR tạo nhầm và khách chưa trả, gọi DELETE /api/v1/acb/qr-payment/{qr_code_id}/cancellation. Request cần Bearer, X-Client-Secret và body khớp lúc tạo gồm ownerNumber, ownerType, orderId, amount; traceNumber là tùy chọn. Chỉ hủy mã cũ trước khi tạo mã thay thế với cùng đơn. Khi ứng dụng ngân hàng báo QR hết hạn, cũng hủy mã cũ rồi tạo lại.

Khớp đơn sau khi quét

Khi khách quét và chuyển tiền, ACB báo cho MONA Pay rồi webhook mang amount, description, transfer_date, transaction_code, account_number, bank_name và type. Khớp đơn bằng số VA trong account_number hoặc mã đơn nằm trong description. Trước khi đổi trạng thái đã thanh toán, so amount với tổng đơn và chống trùng bằng transaction_code. Giao dịch QR đi qua đúng luồng webhook như tiền vào VA.

Sửa lỗi tạo QR

Nếu tạo QR trả bốn trăm, kiểm tra tài khoản đã đăng ký đúng đầu số VA và hai mã merchantId, terminalId. Bốn trăm hai mươi hai nghĩa là thiếu trường, amount vượt một tỷ hoặc virtualAccountPrefix dài hơn mười ký tự; đọc detail[]. Khách quét được nhưng không có webhook thì VA có thể chưa hoàn tất bước đăng ký thông báo và OTP lần hai. QR hết hạn trên ứng dụng ngân hàng cần được hủy và tạo lại.

Mở docs và tạo thử

Body mẫu, response và code cURL, PHP, Node nằm tại monapay.vn/docs/api/qr-thanh-toan. Bạn đăng ký tại my.monapay.vn, thử tạo một mã QR trong dashboard để kiểm tra đầy đủ thông tin ACB trước khi gọi API thật. Nếu còn lỗi mã đơn, đầu số hoặc dữ liệu QR, gọi tổng đài một chín không không, sáu ba sáu, sáu bốn tám để đội MONA hỗ trợ.

QR thanh toán của MONA Pay là mã VietQR động do ACB tạo: đã điền sẵn số tài khoản (hoặc VA), số tiền và mã đơn, khách mở app ngân hàng bất kỳ quét là chuyển đúng số tiền, đúng nội dung, không gõ tay. Gọi POST /api/v1/acb/qr-payment/generate với orderId, amount (VND, số nguyên, tối đa 1.000.000.000) và đầu số VA, MONA Pay trả về qr_data_url để bạn hiển thị ngay trên trang thanh toán. Khi khách trả xong, ACB báo về và MONA Pay bắn webhook y như tiền vào VA. QR tạo nhầm thì hủy bằng endpoint cancellation trước khi khách quét.

Không muốn gọi API thì dashboard có mục Tạo QR: chọn VA, nhập số tiền, tải ảnh QR về in hoặc gửi khách.

POST /api/v1/acb/qr-payment/generate

Cần Bearer + X-Client-Secret. Điều kiện: tài khoản ACB đã nối và có VA (xem Tài khoản ảo).

Trường Kiểu Bắt buộc Ghi chú
ownerNumber string có Số tài khoản ACB nhận tiền
ownerType PER / ORG có PER cá nhân, ORG doanh nghiệp
merchantId string có Mã merchant do ACB cấp khi mở dịch vụ QR. Giá trị đúng hiển thị ở dashboard mục Tạo QR
terminalId string có Mã điểm bán do ACB cấp, cùng chỗ với merchantId
orderId string có Mã đơn của bạn, duy nhất cho mỗi lần thanh toán
virtualAccountPrefix string (1-10) có Đầu số VA
beneficiaryName string (1-100) có Tên người/đơn vị hưởng hiện trên app ngân hàng của khách
amount integer có Số tiền VND, 0 đến 1.000.000.000. 0 = khách tự nhập số tiền
description string (≤255) không Nội dung chuyển khoản hiện sẵn cho khách
traceNumber string không Mã theo dõi riêng của bạn, nên đặt để tra soát
userId string không Mã khách trong hệ thống của bạn
voucherCode, loyaltyCode string không Mã giảm giá / tích điểm nếu bạn cần lưu kèm
additionalInfo array [{key, value}] không Thông tin phụ dạng khoá-giá trị

Body mẫu:

JSON
{
  "ownerNumber": "123456789",
  "ownerType": "ORG",
  "merchantId": "MC00012345",
  "terminalId": "TM0001",
  "orderId": "DH10234",
  "virtualAccountPrefix": "MONA",
  "beneficiaryName": "CONG TY ABC",
  "amount": 2500000,
  "description": "Thanh toan DH10234",
  "traceNumber": "DH10234-20260828"
}

Response 200: data là bản ghi QR, quan trọng nhất là qr_data_url và virtual_account_number.

JSON
{
  "success": true,
  "message": "Success",
  "data": {
    "id": "0190c0d1-...",
    "virtual_account_id": "0190b0c3-...",
    "owner_number": "123456789",
    "owner_type": "ORG",
    "merchant_id": "MC00012345",
    "terminal_id": "TM0001",
    "user_id": null,
    "order_id": "DH10234",
    "virtual_account_prefix": "MONA",
    "beneficiary_name": "CONG TY ABC",
    "amount": 2500000,
    "voucher_code": null,
    "loyalty_code": null,
    "description": "Thanh toan DH10234",
    "additional_info": null,
    "virtual_account_number": "MONA0000010234",
    "trace_number": "DH10234-20260828",
    "qr_data_url": "00020101021238...6304ABCD",
    "created_at": "2026-08-28T10:40:00",
    "updated_at": null
  }
}

qr_data_url là chuỗi dữ liệu QR chuẩn VietQR (EMVCo). Bạn đưa chuỗi này vào thư viện vẽ QR bất kỳ (ví dụ qrcode của Node, endroid/qr-code của PHP) để ra ảnh. Lưu id để hủy khi cần.

cURL

Terminal
curl -X POST https://api.monapay.vn/api/v1/acb/qr-payment/generate \
  -H "Authorization: Bearer $MONA_TOKEN" -H "X-Client-Secret: $MONA_SECRET" \
  -H 'Content-Type: application/json' \
  -d '{"ownerNumber":"123456789","ownerType":"ORG","merchantId":"MC00012345","terminalId":"TM0001","orderId":"DH10234","virtualAccountPrefix":"MONA","beneficiaryName":"CONG TY ABC","amount":2500000,"description":"Thanh toan DH10234"}'

PHP

PHP
<?php
$ch = curl_init('https://api.monapay.vn/api/v1/acb/qr-payment/generate');
curl_setopt_array($ch, [
    CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Authorization: Bearer ' . getenv('MONA_TOKEN'), 'X-Client-Secret: ' . getenv('MONA_SECRET')],
    CURLOPT_POSTFIELDS => json_encode([
        'ownerNumber' => '123456789', 'ownerType' => 'ORG', 'merchantId' => getenv('ACB_MERCHANT_ID'), 'terminalId' => getenv('ACB_TERMINAL_ID'),
        'orderId' => $order->code, 'virtualAccountPrefix' => 'MONA', 'beneficiaryName' => 'CONG TY ABC',
        'amount' => (int) $order->total, 'description' => 'Thanh toan ' . $order->code,
    ]),
]);
$res = json_decode(curl_exec($ch), true);
curl_close($ch);
if (empty($res['success'])) throw new RuntimeException($res['message'] ?? 'Tạo QR thất bại');

$qrString = $res['data']['qr_data_url']; // đưa vào thư viện vẽ QR
$qrId     = $res['data']['id'];          // lưu để hủy nếu đơn bị huỷ

Node

JavaScript
import QRCode from 'qrcode'; // npm i qrcode

const r = await fetch('https://api.monapay.vn/api/v1/acb/qr-payment/generate', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.MONA_TOKEN}`, 'X-Client-Secret': process.env.MONA_SECRET },
  body: JSON.stringify({
    ownerNumber: '123456789', ownerType: 'ORG', merchantId: process.env.ACB_MERCHANT_ID, terminalId: process.env.ACB_TERMINAL_ID,
    orderId: order.code, virtualAccountPrefix: 'MONA', beneficiaryName: 'CONG TY ABC', amount: order.total, description: `Thanh toan ${order.code}`,
  }),
});
const { success, data, message } = await r.json();
if (!success) throw new Error(message);
const pngDataUrl = await QRCode.toDataURL(data.qr_data_url); // <img src=...>

DELETE /api/v1/acb/qr-payment/{qr_code_id}/cancellation

Hủy QR đã tạo nhưng khách chưa thanh toán (đơn bị hủy, đổi số tiền). Cần Bearer + X-Client-Secret. Body bắt buộc gửi lại thông tin khớp với lúc tạo:

Trường Kiểu Bắt buộc
ownerNumber string có
ownerType PER / ORG có
orderId string có
amount integer có
traceNumber string không
Terminal
curl -X DELETE https://api.monapay.vn/api/v1/acb/qr-payment/0190c0d1-.../cancellation \
  -H "Authorization: Bearer $MONA_TOKEN" -H "X-Client-Secret: $MONA_SECRET" \
  -H 'Content-Type: application/json' \
  -d '{"ownerNumber":"123456789","ownerType":"ORG","orderId":"DH10234","amount":2500000}'

Response: {"success": true, "message": "Success", "data": null}.

Khách quét xong thì sao

ACB ghi nhận tiền vào, báo cho MONA Pay, MONA Pay bắn webhook tới URL bạn cấu hình với payload y như giao dịch VA:

JSON
{"amount":2500000,"description":"Thanh toan DH10234","transfer_date":"10:45:12 28/08/2026","transaction_code":"FT26240001234","account_number":"MONA0000010234","bank_name":"ACB","type":"income"}

Khớp đơn bằng account_number (số VA gắn với QR) hoặc description chứa orderId. Nhớ so amount với số tiền đơn trước khi đổi trạng thái đã thanh toán.

Lỗi thường gặp

Tình huống Nguyên nhân Cách xử lý
400 khi tạo Tài khoản chưa đăng ký VA với đầu số này, hoặc merchantId/terminalId sai Kiểm tra ở dashboard mục Tạo QR, tạo thử 1 mã trên web trước
422 Thiếu trường bắt buộc, amount vượt 1.000.000.000, virtualAccountPrefix dài quá 10 ký tự Sửa theo detail[]
Khách quét được nhưng không thấy webhook VA chưa đăng ký nhận thông báo (bước 3, 4 khi tạo VA) Xem Tài khoản ảo
QR hết hạn trên app ngân hàng Mỗi QR động có thời hạn theo quy định ACB Tạo QR mới với cùng orderId sau khi hủy mã cũ