Lỗi và mã lỗi
Request lỗi trả mã HTTP 4xx hoặc 5xx kèm một object error. Rẽ nhánh theo code; hiện message cho người đọc.
Định dạng lỗi
{
"error": {
"code": "INVALID_AMOUNT",
"message": "Số tiền phải là số nguyên từ 1.000 đến 500.000.000 VND."
}
}code: mã lỗi viết hoa, cố định. Hệ thống của bạn so khớp theo chuỗi này.message: câu tiếng Việt giải thích lỗi và việc cần làm. Câu chữ có thể được sửa cho rõ hơn, nên đừng so khớp theomessage.
Mã HTTP
| Mã | Ý nghĩa |
|---|---|
200 | Thành công. |
201 | Tạo mới thành công (tạo yêu cầu thanh toán). |
400 | Request sai: body không phải JSON, tham số thiếu hoặc sai giá trị. |
401 | Thiếu API key, khóa sai hoặc đã thu hồi. |
404 | Không tìm thấy đối tượng (hoặc đối tượng thuộc tài khoản khác), hoặc sai đường dẫn. |
409 | Thao tác không hợp với trạng thái hiện tại, ví dụ hủy yêu cầu đã nhận tiền. |
422 | Request đúng nhưng chưa làm được, ví dụ chưa liên kết tài khoản ngân hàng nào. |
429 | Gửi quá nhiều request. Đợi theo header Retry-After rồi gửi lại. |
500 | Lỗi phía BankOpenAPI. Gửi lại sau ít phút. |
Mã lỗi
| code | HTTP | Khi nào | Cách xử lý |
|---|---|---|---|
INVALID_JSON | 400 | Body không phải JSON object. | Gửi body dạng JSON object kèm Content-Type: application/json. |
INVALID_AMOUNT | 400 | amount không phải số nguyên từ 1.000 đến 500.000.000. | Sửa số tiền (đơn vị VND, không có phần thập phân). |
INVALID_ORDER_ID | 400 | orderId dài quá 64 ký tự hoặc có ký tự không cho phép. | Chỉ dùng chữ, số và _ . : / # - |
INVALID_EXPIRY | 400 | expiresInMinutes ngoài khoảng 5 đến 10.080 phút. | Chọn thời hạn từ 5 phút tới 7 ngày. |
INVALID_BANK_ACCOUNT | 400 | bankAccountId không thuộc tài khoản của bạn hoặc chưa liên kết xong. | Bỏ trường này để dùng tài khoản liên kết gần nhất, hoặc kiểm tra lại ID trong app. |
INVALID_CURSOR | 400 | starting_after không phải ID yêu cầu thanh toán của bạn. | Dùng id của phần tử cuối ở trang trước. |
INVALID_API_KEY | 401 | Thiếu, sai, đã thu hồi hoặc đã hết hạn API key. | Đọc message để biết lý do; roll hoặc tạo khóa mới nếu cần. |
INSUFFICIENT_PERMISSION | 403 | API key chỉ có quyền đọc mà gọi lệnh tạo, sửa hoặc hủy. | Dùng khóa toàn quyền cho lệnh ghi dữ liệu. |
IP_NOT_ALLOWED | 403 | API key có giới hạn IP mà request đến từ IP ngoài danh sách. Message ghi IP mà BankOpenAPI nhìn thấy. | Không retry. Gọi từ hệ thống có IP đã khai báo, hoặc thêm IP đó ở mục "Giới hạn IP" của khóa. |
NOT_FOUND | 404 | Không có đối tượng với ID này trong tài khoản của bạn, hoặc sai đường dẫn. | Kiểm tra ID và đường dẫn (có /v1). |
CANNOT_CANCEL | 409 | Yêu cầu thanh toán đã nhận tiền hoặc đã hủy. | Không retry. Xử lý theo trạng thái hiện tại. |
NO_BANK_ACCOUNT | 422 | Chưa liên kết tài khoản ngân hàng nào. | Liên kết tài khoản trong app rồi gửi lại. |
IDEMPOTENCY_KEY_REUSED | 422 | Idempotency-Key đã dùng cho một lệnh khác (khác đường dẫn hoặc body). | Mỗi lệnh mới dùng một khóa mới; chỉ dùng lại khóa khi gửi lại đúng lệnh cũ. |
IDEMPOTENCY_IN_PROGRESS | 409 | Lệnh với Idempotency-Key này đang được xử lý. | Đợi vài giây rồi gửi lại cùng khóa. |
RATE_LIMITED | 429 | Quá giới hạn request của API key, hoặc IP xác thực sai quá nhiều lần. | Đợi số giây ghi ở Retry-After rồi gửi lại. |
LIVE_MODE | 400 | Gọi /v1/test/* bằng API key thật, hoặc tạo tài khoản thử ở dữ liệu thật. | Dùng API key sandbox (boa_test_…). |
TEST_MODE | 400 | Liên kết ngân hàng thật khi đang ở sandbox. | Tắt sandbox để liên kết tài khoản thật. |
INTERNAL | 500 | Lỗi không lường trước phía BankOpenAPI. | Gửi lại sau ít phút, có backoff. |
Danh sách này có thể có thêm mã mới khi API có thêm endpoint. Gặp mã chưa biết thì xử lý theo mã HTTP: 4xx là lỗi của request, 5xx là lỗi tạm thời.
Khi nào nên retry
- Nên retry:
429,500và lỗi mạng (timeout, mất kết nối). Đợi lâu dần giữa các lần gửi (backoff), có chút ngẫu nhiên. - Không retry: các lỗi 4xx còn lại. Gửi lại y nguyên vẫn nhận đúng lỗi đó.
const res = await fetch(url, options);
const data = await res.json();
if (!res.ok) {
const { code, message } = data.error;
if (res.status === 429 || res.status >= 500) {
// Lỗi tạm thời: đợi rồi gửi lại (xem Giới hạn tần suất)
} else if (code === "NO_BANK_ACCOUNT") {
// Báo người quản trị liên kết tài khoản ngân hàng
} else {
// Lỗi do request: sửa request, không retry
throw new Error(`${code}: ${message}`);
}
}