Chuyển tới nội dung chính

Nhận giao dịch không cần tạo đơn

Dùng luồng này khi hệ thống của bạn đã có mã đơn và chỉ cần biết khi tiền cho đơn đó về, ví dụ mã QR tĩnh ở quầy thu ngân. Bạn không tạo payment cho từng đơn. Hạ Tầng báo mọi giao dịch vào tài khoản dưới dạng transaction.received, kèm mã đơn của bạn nếu tìm thấy trong nội dung.

Trước khi bắt đầu​

  • Một kết nối ngân hàng đang hoạt động.
  • Một webhook endpoint đăng ký sự kiện transaction.received. Luồng này không có IPN, vì không có payment nào để gắn ipn_url.

1. Khai báo định dạng mã đơn​

Đặt payment_code_pattern trên tài khoản ngân hàng: tiền tố và khoảng độ dài. Hạ Tầng sẽ tìm mã này trong nội dung mọi giao dịch.

curl -X PATCH https://api.hatang.dev/payments/bank-connections/bcn_123 \
-H "Authorization: Bearer $HATANG_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "config": { "payment_code_pattern": { "prefix": "DH", "min_length": 4, "max_length": 8, "charset": "digits" } } }'

Cách tìm mã​

  1. Nội dung được chuẩn hóa: bỏ dấu, đ thành D, viết hoa toàn bộ, và bỏ mọi ký tự không phải chữ hoặc số. ck dh 2025-0919 cam on thành CKDH20250919CAMON.
  2. Hạ Tầng tìm tiền tố theo sau là min_length đến max_length ký tự thuộc charset, và lấy kết quả đầu tiên, dài nhất có thể.

Với cấu hình trên, DH20250919 được tìm thấy trong ck dh 2025-0919 cam on.

cẩn thận

Khoảng trắng bị bỏ trước khi tìm, nên với charset: "alnum" các chữ phía sau sẽ dính vào mã: DH1001 cam on thành DH1001CAMON và kết quả là DH1001CAMO. Nên dùng digits, hoặc cho mọi mã dài đúng max_length.

2. Hiển thị VietQR chứa mã​

GET /payments/qr tạo VietQR cho số tiền và nội dung bất kỳ. Không lưu gì.

# JSON kèm chuỗi EMVCo
curl "https://api.hatang.dev/payments/qr?amount=99000&content=DH20250919" \
-H "Authorization: Bearer $HATANG_API_KEY"

# Ảnh SVG
curl "https://api.hatang.dev/payments/qr?amount=99000&content=DH20250919&format=svg" \
-H "Authorization: Bearer $HATANG_API_KEY" -o qr.svg
{
"object": "vietqr",
"payload": "00020101021238530010A0000007270123000697042201099104526580208QRIBFTTA53037045405990005802VN62140810DH202509196304FCC9",
"connection_id": "bcn_01a0b90428fd7c499145a12c133abe41",
"bank": { "bin": "970422", "account_number": "910452658", "account_name": "NGUYEN VAN A" },
"amount": 99000,
"content": "DH20250919"
}
Tham sốÝ nghĩa
amountVND. Bỏ trống để người mua tự nhập số tiền (QR dán ở quầy)
contentNội dung điền sẵn, tối đa 50 ký tự
connection_idTài khoản nào; chỉ cần khi có nhiều tài khoản
formatjson (mặc định) hoặc svg
logoVới format=svg: 0 để bỏ logo Hạ Tầng khỏi ảnh

QR chỉ điền sẵn nội dung. Người chuyển khoản thủ công phải tự gõ mã, vì vậy hãy in mã cạnh QR.

3. Nhận transaction.received​

{
"type": "transaction.received",
"created_at": "2026-09-19T09:34:12.946Z",
"data": {
"object": "transaction",
"id": "txn_01a0b90429dc79c7a5689c7a0c485a01",
"environment": "production",
"connection_id": "bcn_01a0b90428fd7c499145a12c133abe41",
"direction": "in",
"amount": 99000,
"currency": "VND",
"description": "DH20250919 chuyen tien",
"code": "DH20250919",
"merchant_code": "DH20250919",
"reference": "FT1789810452930810",
"counterparty": { "account": "0987654321", "name": "NGUYEN VAN KHACH", "bank": "970436" },
"booked_at": "2026-09-19T09:34:12.930Z",
"source": "push",
"payment_id": null,
"match_status": "unmatched",
"invoice_exempt": null,
"created_at": "2026-09-19T09:34:12.946Z"
}
}
TrườngÝ nghĩa
merchant_codeMã đơn của bạn tìm được trong nội dung; null nếu không tìm thấy
codeMã lưu trên giao dịch: mã thanh toán Hạ Tầng khi giao dịch trả cho một payment, ngược lại là mã đơn của bạn
referenceMã tham chiếu của ngân hàng
sourceHạ Tầng biết giao dịch bằng cách nào: push (ngân hàng báo), reconcile (đọc từ lịch sử tài khoản), import (sao kê) hoặc simulator (development)
payment_id, match_statusCó giá trị khi giao dịch trả cho một payment của bạn (matched); ngược lại là unmatched
counterparty.bankMã BIN ngân hàng người chuyển, nếu ngân hàng cung cấp

Handler của bạn nên:

  1. Xác minh chữ ký webhook (xem Webhook).
  2. Tìm đơn theo merchant_code, và so amount với số tiền cần thu.
  3. Lưu data.id. Cùng một giao dịch không bao giờ được báo hai lần, nhưng endpoint có thể nhận lại cùng một lần gửi sau khi timeout.
  4. Kiểm tra environment và direction.

Tình huống đặc biệt​

Tình huốngBạn nhận được
Người mua quên ghi mãmerchant_code: null. Đối chiếu theo số tiền và counterparty.name, hoặc hỏi người mua
Người mua chuyển sai số tiềnSố tiền đúng như đã chuyển; bạn tự quyết định
Người mua chuyển hai lầnHai giao dịch với id và reference khác nhau
Ngân hàng đẩy cùng giao dịch hai lầnMột giao dịch (cùng mã tham chiếu và số tiền trên tài khoản)
Giao dịch chứa cả mã thanh toánGiao dịch cập nhật payment đó (payment.succeeded…) và transaction.received có payment_id. Một giao dịch không bao giờ bị tính hai lần

Lọc giao dịch được báo​

Theo từng tài khoản ngân hàng, trong transaction_events của config hoặc trên dashboard:

Cấu hìnhMặc địnhTác dụng
directioninall báo cả tiền ra
require_codefalsetrue chỉ báo giao dịch tìm thấy mã hoặc khớp với payment
min_amount0Giao dịch dưới số tiền này không được báo

Danh sách và tra cứu giao dịch​

Mọi giao dịch đều được lưu, dù có được báo hay không.

curl "https://api.hatang.dev/payments/transactions?direction=in&match_status=unmatched&limit=50" \
-H "Authorization: Bearer $HATANG_API_KEY"
Tham sốÝ nghĩa
limit1 đến 100, mặc định 20
afternext_cursor của trang trước
connection_idMột tài khoản ngân hàng
directionin hoặc out
match_statusmatched, unmatched hoặc ignored
payment_idCác giao dịch đã trả cho payment này

Phản hồi có dạng { "object": "list", "data": [...], "has_more": true, "next_cursor": "txn_..." }, mới nhất trước. GET /payments/transactions/{id} trả về một giao dịch.

Bước tiếp theo​

Xem trang này dạng Markdown