Skip to content

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 ​

typescript
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 ​

isUserErrorHTTP StatusÝ nghĩaHành động
true400, 401, 403, 404, 409, 422Request có vấn đề mà người dùng có thể tự sửaHiển thị message cho người dùng
false429Giới hạn tốc độ (rate limit)Yêu cầu người dùng đợi và thử lại
false500, 502, 503, 504Sự cố phía serverYêu cầu người dùng thử lại sau
false0Lỗi mạng hoặc timeoutYê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ẻ).

FieldMã lỗiKích hoạt khi
Số thẻincomplete_numberSố chữ số ít hơn yêu cầu của scheme thẻ được phát hiện
Số thẻinvalid_numberKhông vượt qua checksum Luhn
Ngày hết hạnincomplete_expiryNhập ít hơn 4 chữ số
Ngày hết hạninvalid_expiry_monthTháng không nằm trong khoảng 1–12
Ngày hết hạninvalid_expiry_yearThẻ đã 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).

typescript
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 ​

typescript
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ệ ​

typescript
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 ​

typescript
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 ​

typescript
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 ​

  1. 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.
  2. 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.
  3. Xử lý onTimeout khi polling. Giao dịch có thể vẫn đang được xử lý phía server sau khi timeout.
  4. 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.

Developers