Tài liệu nhà phát triển
Chào mừng bạn đến với cổng thông tin dành cho lập trình viên tích hợp vào Aureus Payment Gateway. Tài liệu này cung cấp các thông số kỹ thuật, hướng dẫn cấu hình và mẫu mã nguồn để bạn có thể tích hợp tính năng thanh toán QR tự động vào hệ thống ERP, Web hoặc App của mình.
Luồng hoạt động hệ thống
Sơ đồ: Quy trình xử lý thông báo gạch nợ tự động qua Aureus Gateway
Dựa trên luồng hoạt động trên, quy trình tích hợp thực tế đã được Aureus Gateway tinh gọn chỉ còn 2 bước kỹ thuật cốt lõi, giúp rút ngắn tối đa thời gian triển khai cho hệ thống của bạn.
Ứng dụng của bạn gọi API này để yêu cầu Gateway sinh mã QR động. Khách hàng quét mã QR và chuyển khoản trực tiếp vào tài khoản ngân hàng của Merchant.
API Tạo lệnh thanh toán mới
X-Api-Key + RSA SignatureHeaders bắt buộc
| Header | Yêu cầu | Mô tả |
|---|---|---|
Content-Type | Bắt buộc | application/json (Gateway chỉ nhận JSON thô). |
X-Api-Key | Bắt buộc | API Key được cấp trên Portal. |
X-Timestamp | Bắt buộc | Unix Timestamp (giây). Sai lệch tối đa 5 phút. |
X-Signature | Bắt buộc | Chữ ký RSA-SHA256 (Base64). |
1. Request Body (JSON Interface)
| Trường | Kiểu | Yêu cầu | Mô tả |
|---|---|---|---|
order_code | string | Bắt buộc | Mã đơn hàng duy nhất trong hệ thống của bạn. |
amount | decimal | Bắt buộc | Số tiền (VNĐ). Tối thiểu 1.000đ. |
content | string | Bắt buộc | Nội dung hiển thị khi khách quét mã QR. |
Response (200 OK - Standard Snake Case):
{
"payment_code": "PMT1715077421234",
"order_code": "APP-2024-ABCD",
"amount": 2500000.0,
"qr_link": "https://img.vietqr.io/image/MB-123...png",
"qr_content": "00020101021238580010A00000072701240006970422011004123456785204701...",
"deep_link": "https://bankhub.aureus.vn/pay/PMT1715077421234"
}
Giải thích tham số Response:
| Trường | Kiểu | Mô tả |
|---|---|---|
payment_code | string | Mã giao dịch chuyên biệt của hệ thống Aureus Gateway (dùng để đối soát hoặc tra cứu sau). |
order_code | string | Mã đơn hàng gốc do hệ thống của Merchant/Partner đã truyền lên. |
amount | decimal | Số tiền thanh toán thực tế của giao dịch (VNĐ). |
qr_link | string | Đường link trả về ảnh chứa mã QR động. Có thể gắn trực tiếp vào src của thẻ <img>. |
qr_content | string | Nội dung text thuần chuẩn VietQR (phục vụ cho việc hệ thống hoặc thiết bị POS tự sinh ảnh QR). |
deep_link | string | Link chuyển hướng tự động (Intent). Dùng riêng cho nền tảng Mobile web/app nhằm gọi trực tiếp tới App ngân hàng và điền sẵn hóa đơn. |
deep_link thay vì hiển thị ảnh QR. Link này tự động mở App ngân hàng và điền sẵn 100% thông tin thanh toán cho người dùng.🔐 Hướng dẫn sinh chữ ký X-Signature
Để đảm bảo tính toàn vẹn và xác thực, mỗi yêu cầu POST gửi đến Gateway cần đi kèm một chữ ký RSA-SHA256 trong Header X-Signature theo các bước sau:
| Bước | Mô tả chi tiết |
|---|---|
| Bước 1 | Lấy toàn bộ nội dung Request Body (JSON thô) và giá trị X-Timestamp. |
| Bước 2 | Tạo chuỗi dữ liệu cần ký bằng cách nối chúng qua ký tự gạch đứng: {JSON_Body}|{Timestamp}. |
| Bước 3 | Sử dụng thuật toán RSA-SHA256 và Private Key để thực hiện ký chuỗi dữ liệu trên. |
| Bước 4 | Chuyển đổi dữ liệu sau khi ký sang định dạng Base64 String để gán vào header X-Signature. |
Để tạo chữ ký X-Signature, đối tác cần dùng Khóa bí mật (Private Key) dạng XML được hệ thống cấp.
Mẫu mã nguồn thực hiện:
using System;
using System.Security.Cryptography;
using System.Text;
public class RsaSigner
{
///
/// Ký số RSA cho yêu cầu tạo thanh toán
///
/// Nội dung JSON của Request Body
/// Giá trị X-Timestamp (số giây Unix)
/// Khóa Private Key (dạng <RSAKeyValue>...</RSAKeyValue>)
public static string SignRequest(string bodyJson, string timestamp, string privateKeyXml)
{
// 1. Tạo chuỗi dữ liệu để ký: {bodyJson}|{timestamp}
string dataToSign = $"{bodyJson}|{timestamp}";
byte[] dataBytes = Encoding.UTF8.GetBytes(dataToSign);
using (var rsa = new RSACryptoServiceProvider())
{
// 2. Import Private Key
rsa.FromXmlString(privateKeyXml);
// 3. Ký dữ liệu bằng thuật toán SHA256
byte[] signatureBytes = rsa.SignData(dataBytes, CryptoConfig.MapNameToOID("SHA256"));
// 4. Trả về mã Base64 để đưa vào Header X-Signature
return Convert.ToBase64String(signatureBytes);
}
}
}
import java.security.*;
import java.security.spec.*;
import java.util.Base64;
public class RsaSigner {
// Ký số RSA SHA256 cho yêu cầu
// - bodyJson: Nội dung Request Body
// - timestamp: Unix Timestamp
// - privateKey: RSA Private Key
public static String signRequest(String bodyJson, String timestamp, String privateKey) throws Exception {
String dataToSign = bodyJson + "|" + timestamp;
Signature rsa = Signature.getInstance("SHA256withRSA");
// Khởi tạo và ký dữ liệu
rsa.initSign(getPrivateKey(privateKey));
rsa.update(dataToSign.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(rsa.sign());
}
}
class RsaSigner {
// Ký số RSA cho yêu cầu thanh toán
public static function signRequest($bodyJson, $timestamp, $privateKey) {
$dataToSign = $bodyJson . "|" . $timestamp;
openssl_sign($dataToSign, $signature, $privateKey, OPENSSL_ALGO_SHA256);
return base64_encode($signature);
}
}
const crypto = require('crypto');
class RsaSigner {
// Ký số RSA-SHA256
static signRequest(bodyJson, timestamp, privateKey) {
const dataToSign = `${bodyJson}|${timestamp}`;
const signer = crypto.createSign('SHA256');
signer.update(dataToSign);
return signer.sign(privateKey, 'base64');
}
}
Sau khi khách hàng chuyển khoản thành công, Gateway lập tức gửi một HTTP POST về NotifyUrl.
Thông tin Webhook (Callback)
POST từ Gateway → NotifyUrlHeaders gửi kèm Webhook
| Header | Mô tả |
|---|---|
Content-Type | application/json |
X-Signature | Mã băm HMAC-SHA256 để xác thực nguồn gốc gói tin. |
Cấu trúc dữ liệu Webhook (Payload)
| Trường | Kiểu | Mô tả |
|---|---|---|
merchant_id | int | ID duy nhất của Merchant trên hệ thống. |
order_code | string | Mã đơn hàng phía Merchant. |
payment_code | string | Mã thanh toán duy nhất phía Gateway. |
payment_id | long | ID nội bộ của giao dịch (Internal ID). |
transaction_id | string | Mã tham chiếu ngân hàng (Provider ID). |
amount | decimal | Số tiền thực nhận (VNĐ). |
status | string | PAID (Thành công) hoặc MISMATCH (Lệch tiền). |
{
"merchant_id": 105,
"order_code": "APP-2024-ABCD",
"payment_code": "PMT1715077421234",
"payment_id": 12345,
"transaction_id": "FT2413800881234",
"amount": 2500000.0,
"status": "PAID"
}
Công cụ tạo chuỗi rawBody thực tế (7 trường)
Cấu trúc Webhook thực tế gồm 7 trường. Hãy dùng công cụ này để tạo chuỗi test chữ ký.
{"merchant_id":105,"order_code":"DH12345","payment_code":"PMT123456","payment_id":999,"transaction_id":"FT88888","amount":50000,"status":"PAID"}
string rawBody = "{\"merchant_id\":105,\"order_code\":\"DH12345\",\"payment_code\":\"PMT123456\",\"payment_id\":999,\"transaction_id\":\"FT88888\",\"amount\":50000,\"status\":\"PAID\"}";
Mã nguồn tạo Raw Body lập trình
Dùng các hàm này để đảm bảo chuỗi JSON tạo ra luôn là chuỗi Minified (không khoảng trắng thừa), khớp với cơ chế ký của Gateway.
// Dùng Newtonsoft.Json với Formatting.None
var rawBody = JsonConvert.SerializeObject(yourModel, Formatting.None);
// Dùng json_encode (mặc định là minified)
$rawBody = json_encode($yourArray, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
// Dùng Jackson ObjectMapper
ObjectMapper mapper = new ObjectMapper();
String rawBody = mapper.writeValueAsString(yourModel); // Mặc định là minified
// Dùng JSON.stringify không kèm tham số indent
const rawBody = JSON.stringify(yourModel);
🔐 Phần giải mã & Xác thực Webhook (HMAC-SHA256)
Để đảm bảo gói tin Webhook thực sự đến từ Aureus Gateway, đối tác cần dùng Secret Key để xác thực chữ ký HMAC-SHA256 gửi trong Header X-Signature.
| Bước | Mô tả chi tiết |
|---|---|
| Bước 1 |
Lấy nội dung Raw Request Body.
C#: await Request.Content.ReadAsStringAsync();
PHP: file_get_contents('php://input');
NodeJS: req.body (với raw-body parser)
|
| Bước 2 | Sử dụng Secret Key để tính toán mã băm HMAC-SHA256. |
| Bước 3 | So sánh mã băm vừa tính được với header X-Signature. |
Mẫu mã nguồn xác thực:
using System.Security.Cryptography;
using System.Text;
public class WebhookVerifier {
public static bool Verify(string rawBody, string secretKey, string headerSignature) {
byte[] keyBytes = Encoding.UTF8.GetBytes(secretKey);
using (var hmac = new HMACSHA256(keyBytes)) {
byte[] hashBytes = hmac.ComputeHash(Encoding.UTF8.GetBytes(rawBody));
string result = BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
return result == headerSignature;
}
}
}
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class WebhookVerifier {
public static boolean verify(String rawBody, String secretKey, String headerSig) throws Exception {
Mac hmac = Mac.getInstance("HmacSHA256");
hmac.init(new SecretKeySpec(secretKey.getBytes(), "HmacSHA256"));
byte[] rawHmac = hmac.doFinal(rawBody.getBytes());
// Chuyển sang Hex và so sánh
return bytesToHex(rawHmac).equals(headerSig);
}
}
class WebhookVerifier {
public static function verify($rawBody, $secretKey, $headerSignature) {
$signature = hash_hmac('sha256', $rawBody, $secretKey);
return hash_equals($signature, $headerSignature);
}
}
const crypto = require('crypto');
class WebhookVerifier {
static verify(rawBody, secretKey, headerSignature) {
const signature = crypto.createHmac('sha256', secretKey)
.update(rawBody)
.digest('hex');
return signature === headerSignature;
}
}
payment_code trong database xem đã xử lý thành công chưa trước khi thực hiện logic gạch nợ.
Tại sao Webhook báo lỗi hoặc sai Chữ ký?
Nếu bạn nhận được Webhook nhưng không thể gạch nợ thành công, hãy kiểm tra 2 vấn đề sau:
Nhiều framework (như Java, Go) sẽ báo lỗi nếu nhận được trường dữ liệu lạ (ví dụ payment_id).
Giải pháp: Cấu hình cho phép bỏ qua các trường không khai báo (Ignore Unknown Properties) trong Model của bạn.
Tuyệt đối không tự dùng Model để build lại JSON nhằm Verify chữ ký. Chỉ cần sai một dấu cách hoặc thứ tự trường là HMAC sẽ sai.
Giải pháp: Đọc chuỗi thô trực tiếp từ stream của Request.
merchant_id, order_code, payment_code, payment_id, transaction_id, amount, status
Best Practice: Bền vững hóa API Webhook
Để tránh việc hệ thống bị lỗi (HTTP 400/500) mỗi khi Gateway cập nhật hoặc bổ sung trường dữ liệu mới, đối tác cần tuân thủ nguyên tắc thiết kế sau:
- KHÔNG dùng Class/Model cố định để nhận tham số trực tiếp tại hàm Webhook.
- KHÔNG chặn các trường dữ liệu lạ (Unknown properties).
- CHỈ nên quan tâm đến các trường cốt lõi bạn cần dùng cho logic gạch nợ.
Cấu trúc API Webhook khuyên dùng (C#):
// Thiết kế API "Mở" - Không sợ thay đổi tham số
[HttpPost]
public async Task<IHttpActionResult> ReceiveWebhook()
{
// 1. Nhận chuỗi thô (Luôn luôn thành công)
string rawBody = await Request.Content.ReadAsStringAsync();
// 2. Lấy chữ ký X-Signature từ Header để xác thực (Tùy framework)
// - Với ASP.NET Web API:
Request.Headers.TryGetValues("X-Signature", out IEnumerable<string> values);
string signature = values?.FirstOrDefault() ?? "";
// - Với ASP.NET Core:
// string signature = Request.Headers["X-Signature"].FirstOrDefault();
// 3. Parse linh hoạt vào Dictionary
var data = JsonConvert.DeserializeObject<Dictionary<string, object>>(rawBody);
// 4. Lấy dữ liệu cần thiết
string orderCode = data.ContainsKey("order_code") ? data["order_code"].ToString() : "";
string status = data.ContainsKey("status") ? data["status"].ToString() : "";
// ... Kiểm tra chữ ký và gạch nợ ...
return Ok();
}
🔧 Quy trình mở rộng
A. Lấy Token xác thực (Auth Login)
Dùng để lấy thẻ truy cập (JWT Token) phục vụ cho việc gọi các API quản trị cấp cao (Management API) không yêu cầu chữ ký RSA.
Cấu trúc Request Body (JSON):
| Trường | Kiểu | Yêu cầu | Mô tả |
|---|---|---|---|
email | string | Bắt buộc | Tên tài khoản quản trị hệ thống. |
password | string | Bắt buộc | Mật khẩu truy cập. |
Ví dụ Request:
{
"email": "admin@company.com",
"password": "my-secure-password"
}
Ví dụ Response (200 OK):
{
"success": true,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6Ik...",
"refresh_token": "fb8b7c3d...",
"role": "OWNER",
"route": "/portal",
"tenant_type": "EXTERNAL"
}
Authorization: Bearer {token}.B. Đăng ký Merchant tự động
API cho phép các nền tảng ERP/App đối tác tự động khởi tạo điểm bán hoặc tòa nhà (Merchant) mà không cần thao tác tay thủ công trên Portal.
Headers bắt buộc:
| Header | Mô tả |
|---|---|
Authorization | Bearer Token nhận được từ API Xác thực hệ thống (Login). |
Content-Type | application/json |
Cấu trúc Request Body:
| Trường | Kiểu | Yêu cầu | Mô tả |
|---|---|---|---|
name | string | Bắt buộc | Tên tòa nhà, điểm thu phí hoặc công ty (Vd: Tòa nhà The Landmark). |
tenant_key | string | Bắt buộc | Mã định danh nội bộ (ID đối chiếu hệ thống của đối tác). |
notify_url | string | Bắt buộc | URL Webhook để hệ thống gọi Push sang khi có tiền vào. |
Ví dụ Response (200 OK):
{
"success": true,
"data": {
"api_key": "GA_1A2B3C...",
"secret_key": "7f8b9c...",
"hosted_link_url": "https://bankhub.aureus.vn/link/...",
"private_key": "<RSAKeyValue>...</RSAKeyValue>"
},
"message": "Merchant registered. Please open HostedLinkUrl to link bank account."
}
hosted_link_url mở Popup để khách hàng liên kết ngân hàng thực tế. Bạn phải lưu lại private_key an toàn ở phía Server vì hệ thống chỉ cung cấp duy nhất 1 lần!C. Kiểm tra trạng thái giao dịch (Polling)
Trong trường hợp tín hiệu mạng chập chờn khiến Webhook không truyền tới được server của bạn, hoặc bạn cần đối soát độc lập, API này cho phép chủ động hỏi Gateway xem đơn hàng đã được thanh toán xong chưa.
| Tham số (Query) | Yêu cầu | Mô tả |
|---|---|---|
payment_code | Bắt buộc | Mã giao dịch (payment_code) sinh ra khi chạy API Tạo lệnh thanh toán. |
Ví dụ Response (200 OK):
{
"status": "COMPLETED"
}
Danh sách trạng thái (Status):
| Trạng thái | Ý nghĩa |
|---|---|
| COMPLETED | Giao dịch thành công trọn vẹn, tiền đã khớp vào tải khoản ngân hàng. |
| PENDING | Đang trong trạng thái treo, chờ người dùng mở App quét mã QR. |
| FAILED | Giao dịch thất bại hoặc đã bay QR quá hạn mà chưa có tiền vào. |
D. Bảo mật nâng cao
Aureus sử dụng định dạng RSA Key dạng XML (.NET). Vui lòng chuyển đổi từ PEM sang XML khi cập nhật khóa lên hệ thống.
E. Ký quỹ SaaS
Phí giao dịch: 1.000 VNĐ cho mỗi giao dịch thành công. Thuế VAT 8% áp dụng một lần khi nạp tiền.
F. Bảng mã lỗi
| Mã lỗi | Mô tả |
|---|---|
AUTH_FAILED | Sai API Key hoặc Token xác thực. |
INVALID_SIGNATURE | Chữ ký RSA/HMAC không khớp hoặc sai định dạng. |
INSUFFICIENT_BALANCE | Số dư quỹ SaaS không đủ. |