Skip to content

Apple Pay ​

Chấp nhận thanh toán Apple Pay trên web. bahn.mountApplePay() hiển thị nút Apple Pay chính thức và xử lý việc xác thực merchant (merchant validation), ủy quyền thanh toán, và (nếu được kích hoạt) 3D Secure — bạn chỉ cần mount nó, lắng nghe một vài sự kiện, và xác nhận thanh toán.

Yêu cầu ​

Trước khi tích hợp, hãy hoàn tất các bước sau với Apple và với Cake:

Yêu cầuỞ đâuGhi chú
Xác minh domainApple Developer → Merchant IDs → Apple Pay on the WebDomain production của bạn phải phục vụ /.well-known/apple-developer-merchantid-domain-association tại root, và được đăng ký với Apple merchant identifier của bạn. Điều này bắt buộc ngay cả khi nút được hiển thị bên trong iframe do Cake host — Apple xác thực dựa trên domain của trang cấp cao nhất (top-level page), không phải domain của iframe.
Tài khoản merchant với CakeOnboardingDomain của bạn cũng phải được đăng ký với Cake gắn với merchant của bạn, khớp với những gì bạn đã đăng ký với Apple.
Safari 17 / iOS 17+—Bắt buộc để nút có thể hiển thị (xem Hỗ trợ trình duyệt).
Đăng ký cross-browser (tùy chọn)Apple DeveloperMột đăng ký riêng với Apple cho "Apple Pay on the Web for other browsers", nếu bạn cũng muốn hỗ trợ Chrome, Firefox, và Edge.

Bắt đầu nhanh ​

typescript
const bahn = new Bahn({ pk: "pk_live_...", environment: "sandbox" });

const ap = bahn.mountApplePay("#apple-pay-container", clientSecret, {
  amount: 100000, // đơn vị tiền tệ nhỏ nhất — VND không chia cho 100
  currency: "VND",
  country: "VN",
});

ap.on("ready", () => console.log("Apple Pay button rendered"));
ap.on("success", (paymentIntent) => (window.location.href = "/success"));
ap.on("error", (err) => console.error((err as { message: string }).message));
ap.on("cancel", () => console.log("Shopper dismissed the sheet"));

mountApplePay() gắn nút tại CSS selector bạn cung cấp, tự động điều chỉnh kích thước theo container, và tự quản lý mọi thứ khác — kiểm tra xem Apple Pay có thể sử dụng được không, hiển thị nút, và chạy toàn bộ payment sheet. Nếu Apple Pay không khả dụng trên trình duyệt của người mua, nút sẽ không được hiển thị và sự kiện "error" sẽ được kích hoạt thay vào đó — hãy dùng sự kiện này để chuyển sang phương thức thanh toán khác. merchantName, supportedNetworks, và merchantCapabilities đều mặc định lấy theo giá trị đã cấu hình trên tài khoản merchant của bạn, chỉ truyền các giá trị này khi bạn muốn ghi đè.

Tham số của mountApplePay(selector, clientSecret, options) ​

Tham sốKiểuBắt buộcMô tả
selectorstringCóCSS selector cho container để mount
clientSecretstring | undefinedCó (truyền undefined cho luồng dựa trên token)Client secret của payment intent cần xác nhận — xem Xác nhận thanh toán
options.amountnumberCóSố tiền giao dịch, theo đơn vị tiền tệ nhỏ nhất
options.currencystringCóMã tiền tệ ISO 4217 (ví dụ "VND", "USD")
options.countrystringCóMã quốc gia ISO 3166-1 alpha-2 (ví dụ "VN", "US")
options.priceLabelstringKhôngNhãn hiển thị cho tổng giao dịch trong sheet
options.lineItemsArray<{ label, amount }>KhôngBảng chi tiết từng khoản hiển thị trong sheet
options.merchantNamestringKhôngTên merchant hiển thị trong sheet — mặc định lấy theo tên đã cấu hình của merchant
options.typeApplePayButtonTypeKhôngBiến thể nhãn của nút (mặc định "plain")
options.styleApplePayButtonStyleKhông"black" | "white" | "white-outline" (mặc định "black")
options.localestringKhôngNgôn ngữ của nút (mặc định "en")
options.borderRadiusnumberKhôngBo góc nút, tính bằng pixel (mặc định 4)
options.size{ width?, height? }KhôngKích thước nút (mặc định 100% × 48px)
options.supportedNetworksApplePayCardNetwork[]KhôngCác mạng thẻ được hỗ trợ — mặc định theo cấu hình của merchant
options.merchantCapabilitiesApplePayMerchantCapability[]KhôngCác merchant capability được khai báo — mặc định theo cấu hình của merchant
options.requestBillingAddressbooleanKhôngYêu cầu địa chỉ thanh toán (billing address)
options.requestPayerDetails("name" | "email" | "phone")[]KhôngYêu cầu thông tin liên hệ của người mua
options.requestShippingbooleanKhôngThu thập địa chỉ giao hàng — xem Thu thập địa chỉ giao hàng
options.shippingMethodsArray<{ identifier, label, detail?, amount }>KhôngCác tùy chọn giao hàng ban đầu hiển thị trước khi địa chỉ được chọn

Giá trị trả về: IApplePayButtonComponent — .on(event, handler) (xem Sự kiện), .unmount(). bahn.cleanup()/bahn.unmountApplePay() cũng sẽ gỡ nút này.

Xác nhận thanh toán ​

mountApplePay() hỗ trợ hai cách xác nhận thanh toán, được chọn tùy theo việc bạn có truyền clientSecret hay không.

Xác nhận trực tiếp ​

Truyền client_secret của một payment intent bạn đã tạo phía server. Sheet sẽ tự xác nhận thanh toán và báo cáo kết quả:

typescript
const ap = bahn.mountApplePay("#apple-pay-container", clientSecret, {
  amount: 100000,
  currency: "VND",
  country: "VN",
});

ap.on("success", (paymentIntent) => (window.location.href = "/success"));
ap.on("error", (err) => console.error((err as { message: string }).message));

Nếu payment intent yêu cầu xác thực bổ sung, mountApplePay() sẽ tự động gọi bahn.mount3DS() để hiển thị thử thách, sau đó báo cáo "success"/"error" khi hoàn tất — trường hợp này hiếm khi xảy ra trong thực tế, vì việc ủy quyền bằng Face ID / Touch ID đã đáp ứng phần lớn yêu cầu của các ngân hàng phát hành thẻ.

Luồng dựa trên token ​

Bỏ qua clientSecret để xác nhận thanh toán thông qua backend của riêng bạn — gọi mountApplePay(selector, undefined, options):

typescript
const ap = bahn.mountApplePay("#apple-pay-container", undefined, {
  amount: 100000,
  currency: "VND",
  country: "VN",
  merchantName: "Your Merchant Name",
});

ap.on("token", async ({ confirmation_token }) => {
  // Gửi confirmation_token đến backend của riêng bạn, nơi xác nhận
  // PaymentIntent bằng confirmation_token và secret key của bạn, rồi
  // trả về client_secret.
  const { client_secret } = await fetch("/api/confirm-payment", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ confirmation_token, amount: 100000, currency: "vnd" }),
  }).then((r) => r.json());

  // Giữ nút Apple Pay ở trạng thái mount cho đến khi việc này hoàn tất — handleNextAction()
  // cần nó để kiểm tra trạng thái của payment intent.
  await bahn.handleNextAction(client_secret, {
    onSuccess: () => (window.location.href = "/success"),
    onFailure: () => (window.location.href = "/failed"),
    onTimeout: () => (window.location.href = "/processing"),
  });
});

ap.on("error", (err) => console.error((err as { message: string }).message));
ap.on("cancel", () => console.log("Shopper dismissed the sheet"));

Trong luồng này, Apple Pay sheet hoàn tất ngay khi confirmation_token được tạo — người mua sẽ thấy sheet đóng lại thành công ngay lập tức, mà không cần chờ backend của bạn. Hãy xác nhận thanh toán ở backend của bạn và hiển thị kết quả cuối cùng thông qua redirect hoặc giao diện riêng của bạn, không thông qua nút Apple Pay; sự kiện "success" sẽ không được phát ra cho luồng này.

Thu thập địa chỉ giao hàng ​

Đặt requestShipping: true và cung cấp các tùy chọn giao hàng khi người mua nhập địa chỉ:

typescript
const ap = bahn.mountApplePay("#apple-pay-container", clientSecret, {
  amount: 100000,
  currency: "VND",
  country: "VN",
  requestShipping: true,
  // Hiển thị trước khi người mua nhập địa chỉ. Bỏ qua nếu mức phí ship
  // luôn phụ thuộc vào địa chỉ — cung cấp chúng từ "shippingcontactselected" thay vào đó.
  shippingMethods: [{ identifier: "standard", label: "Standard", detail: "3-5 days", amount: 0 }],
});

ap.on("shippingcontactselected", (event) => {
  const { contact, resolve, reject } = event;

  if (contact.country !== "VN") {
    reject(); // hiển thị lỗi inline "không thể giao đến đây" trong sheet
    return;
  }

  resolve({
    shippingMethods: [
      { identifier: "standard", label: "Standard", detail: "3-5 days", amount: 0 },
      { identifier: "express", label: "Express", detail: "1-2 days", amount: 20000 },
    ],
  });
});

ap.on("shippingmethodselected", (event) => {
  const { shippingMethod, resolve } = event;
  // Tính lại tổng số tiền để bao gồm phí ship đã chọn.
  resolve({ amount: 100000 + shippingMethod.amount });
});

ap.on("token", async ({ confirmation_token }) => { /* same as the token-based flow above */ });

Sheet sẽ tạm dừng sau mỗi lựa chọn cho đến khi bạn gọi resolve() (kèm các tùy chọn giao hàng/tổng số tiền đã cập nhật) hoặc reject() (hiển thị lỗi inline). contact được truyền vào "shippingcontactselected" cố tình chỉ mang thông tin sơ bộ — chỉ gồm thành phố, tỉnh/bang, mã bưu điện, và quốc gia, không có địa chỉ đường phố cụ thể — đây là hành vi tiêu chuẩn cho loại thu thập địa chỉ này; địa chỉ đầy đủ sẽ có sẵn khi thanh toán được ủy quyền. Mọi trường amount ở đây đều tuân theo cùng quy ước đơn vị tiền tệ nhỏ nhất như options.amount.

Kiểm tra khả dụng trước khi mount ​

mountApplePay() đã tự kiểm tra khả dụng nội bộ và đơn giản là sẽ không hiển thị nút nếu Apple Pay không thể sử dụng. Chỉ gọi bahn.isApplePayAvailable() khi bạn muốn kiểm soát bố cục trang trước bước đó — ví dụ, ẩn toàn bộ phần "Hoặc thanh toán bằng Apple Pay" (nút và mọi giao diện xung quanh), thay vì phản ứng lại sự kiện "error" sau khi container đã hiển thị.

typescript
if (!(await bahn.isApplePayAvailable())) {
  // ẩn điểm truy cập — xem phần Hỗ trợ trình duyệt bên dưới để biết phạm vi áp dụng
}

Đây chỉ là một kiểm tra phía client — nó không xác nhận rằng Apple Pay đã được bật cho tài khoản merchant của bạn. Đây không phải là điều cần kiểm tra riêng trước: nếu merchant của bạn chưa được thiết lập đầy đủ, việc xác thực của sheet sẽ thất bại khi người mua nhấn nút, và được hiển thị qua cùng sự kiện "error" như mọi lỗi khác.

Sự kiện ​

.on(event, handler) trên đối tượng mà mountApplePay() trả về:

Sự kiệnKích hoạt khiHandler nhận được
"ready"Nút đã được hiển thị và có thể nhấn—
"success"Thanh toán đã được xác nhận (và mọi thử thách 3D Secure đã pass)payment_intent — chỉ áp dụng cho xác nhận trực tiếp
"token"Một confirmation token đã được tạo; sheet đã đóng thành công{ confirmation_token } — chỉ áp dụng cho luồng dựa trên token
"error"Xác thực merchant hoặc xác nhận thất bại{ message }
"cancel"Người mua đóng sheet mà không thanh toán—
"shippingcontactselected"Người mua nhập hoặc thay đổi địa chỉ giao hàng{ contact, resolve, reject } — yêu cầu requestShipping: true
"shippingmethodselected"Người mua chọn tùy chọn giao hàng khác{ shippingMethod, resolve, reject } — yêu cầu requestShipping: true

Hỗ trợ trình duyệt ​

Trình duyệtHỗ trợ
Safari (macOS / iOS)Safari 17 / iOS 17+
Chrome / Edge / FirefoxChỉ được hỗ trợ nếu merchant của bạn đã hoàn tất đăng ký riêng với Apple cho "Apple Pay on the Web for other browsers"

bahn.isApplePayAvailable() phản ánh việc trình duyệt của người mua có khả năng hiển thị sheet hay không. Trên Chrome, Firefox, và Edge, việc kiểm tra khả năng này chỉ xác nhận phiên bản trình duyệt — nó không thể xác nhận trạng thái đăng ký của bạn với Apple, vì vậy kết quả true ở đây chỉ có nghĩa là trình duyệt có thể chạy luồng thanh toán, không đảm bảo nó sẽ thành công cho merchant của bạn nếu chưa hoàn tất đăng ký đó. Hãy xác nhận trạng thái đăng ký của bạn với Apple trực tiếp trước khi phụ thuộc vào việc hỗ trợ ngoài Safari trong môi trường production.

Kiểm thử ​

Sử dụng trình duyệt Safari 17+ (macOS) hoặc thiết bị iOS 17+ đã đăng nhập vào một Apple ID sandbox, với một thẻ test Apple Pay Sandbox đã được thêm vào Wallet, trỏ đến môi trường sandbox của bạn.

Developers