Xử lý lỗi
Tất cả lỗi của SDK được throw dưới dạng instance APIError. Việc hiểu cấu trúc lỗi giúp bạn hiển thị thông báo phù hợp cho người dùng.
Để biết các mã ở tầng wire mà API trả về (code, type, decline_code), xem Mã lỗi.
APIError
class APIError extends Error {
name: "APIError";
status: number; // Mã trạng thái HTTP (0 cho lỗi mạng)
message: string; // Thông báo có thể đọc được bởi con người
isUserError: boolean; // true nếu lỗi có thể được người dùng tự sửa (4xx)
code?: string; // Mã lỗi có thể đọc bằng máy (tùy chọn)
}Script SDK chỉ expose Bahn dưới dạng biến toàn cục — bản thân APIError thì không, vì vậy hãy nhận diện lỗi SDK bằng err.name === "APIError" thay vì instanceof.
Phân loại lỗi
isUserError | HTTP Status | Ý nghĩa | Hành động |
|---|---|---|---|
true | 400, 401, 403, 404, 409, 422 | Request có vấn đề mà người dùng có thể tự sửa | Hiển thị message cho người dùng |
false | 429 | Giới hạn tốc độ (rate limit) | Yêu cầu người dùng đợi và thử lại |
false | 500, 502, 503, 504 | Sự cố phía server | Yêu cầu người dùng thử lại sau |
false | 0 | Lỗi mạng hoặc timeout | Yêu cầu người dùng kiểm tra kết nối |
Ánh xạ mã trạng thái → thông báo người dùng
SDK ánh xạ mã trạng thái HTTP thành các thông báo thân thiện với người dùng nội bộ và đặt chúng làm err.message trên APIError được throw:
400 → "Invalid request. Please check your information and try again."
401 → "Authentication failed. Please refresh the page and try again."
403 → "Access denied. Please contact support if this persists."
404 → "Resource not found. Please try again."
409 → "This request has already been processed. Please try again."
422 → "Invalid payment information. Please check your card details."
429 → "Too many requests. Please wait a moment and try again."
500 → "Server error. Please try again in a moment."
502/503/504 → "Service temporarily unavailable. Please try again later."Mọi trạng thái khác sẽ dùng thông báo mặc định "{context} failed. Please try again."
Mã lỗi kiểm tra hợp lệ của thẻ
Các iframe thẻ kiểm tra hợp lệ dữ liệu nhập cục bộ và trả về một mã lỗi cho từng field. Ghi đè thông báo mặc định thông qua tùy chọn errorMsg trên mountCard (xem Thanh toán thẻ).
| Field | Mã lỗi | Kích hoạt khi |
|---|---|---|
| Số thẻ | incomplete_number | Số chữ số ít hơn yêu cầu của scheme thẻ được phát hiện |
| Số thẻ | invalid_number | Không vượt qua checksum Luhn |
| Ngày hết hạn | incomplete_expiry | Nhập ít hơn 4 chữ số |
| Ngày hết hạn | invalid_expiry_month | Tháng không nằm trong khoảng 1–12 |
| Ngày hết hạn | invalid_expiry_year | Thẻ đã hết hạn |
| CVC | (một thông báo duy nhất) | Độ dài không khớp với scheme (3 chữ số, hoặc 4 với Amex) |
Đối với số thẻ và ngày hết hạn, errorMsg là một Record<string, string> dùng các mã ở trên làm khóa. Đối với CVC, errorMsg là một chuỗi thuần duy nhất (chỉ có một trạng thái lỗi CVC).
bahn.mountCard("#card-container", {
fields: {
number: {
errorMsg: {
incomplete_number: "Please enter your full card number",
invalid_number: "That card number isn't valid",
},
},
expiry: {
errorMsg: {
incomplete_expiry: "Enter the expiry date",
invalid_expiry_month: "Month must be 01–12",
invalid_expiry_year: "This card has expired",
},
},
cvc: { errorMsg: "Enter the security code on your card" },
},
});Mã lỗi 3DS
Khi thử thách 3DS thất bại, một chuỗi mã lỗi 3DS sẽ được trả về trong error.code. Xem Xác thực 3DS → Mã lỗi 3DS để biết danh sách đầy đủ và ví dụ dùng switch.
Các tình huống lỗi thường gặp
Component thẻ chưa được mount
try {
await bahn.confirmPayment({ client_secret });
} catch (err) {
if (err?.name === "APIError" && err.status === 0) {
// "Card component not mounted. Call mountCard() before confirmPayment()."
bahn.mountCard("#card-container");
}
}Thông tin thẻ không hợp lệ
try {
await bahn.confirmPayment({ client_secret });
} catch (err) {
if (err?.name === "APIError" && err.isUserError) {
// Hiển thị thông báo cho người dùng — đây là lỗi họ có thể tự sửa
showErrorToast(err.message);
} else if (err?.name === "APIError") {
// Lỗi server hoặc mạng — hiển thị thông báo chung
showErrorToast("Something went wrong. Please try again.");
}
}3DS thất bại
bahn.mount3DS(sessionId, {
onSuccess: () => {
// Tiếp tục luồng thanh toán
},
onFailure: (error) => {
// error.code — một trong các mã lỗi 3DS, ví dụ "challenge-failed"
// error.message — mô tả có thể đọc được bởi con người
console.error(`3DS failed: ${error?.code} — ${error?.message}`);
showBlockingError("3D Secure authentication failed. Please try a different card.");
},
});Timeout khi polling payment intent
bahn.pollPaymentIntentStatus(clientSecret, {
onSuccess: (pi) => redirectToSuccess(),
onFailure: (pi) => redirectToFailure(),
onTimeout: () => {
// Đã hết số lần thử polling — thanh toán có thể vẫn đang được xử lý phía server
// Cân nhắc hiển thị thông báo dạng "Chúng tôi sẽ thông báo cho bạn"
showDelayedResultMessage("Your payment is being processed. We'll notify you when it completes.");
},
onError: (err) => {
console.error("Poll error:", err);
},
});Thực hành tốt nhất
- Luôn bọc các lệnh gọi SDK trong try/catch. Mọi phương thức bất đồng bộ đều có thể throw
APIError. - Sử dụng
isUserErrorđể quyết định cách hiển thị thông báo.true→ hiển thị lỗi cụ thể cho người dùng.false→ hiển thị thông báo yêu cầu thử lại chung chung. - Xử lý
onTimeoutkhi polling. Giao dịch có thể vẫn đang được xử lý phía server sau khi timeout. - Dọn dẹp khi điều hướng trang. Gọi
bahn.cleanup()hoặc các phương thức unmount riêng lẻ khi người dùng rời khỏi trang.