Google Pay™
Chấp nhận thanh toán Google Pay™ trên web. bahn.mountGooglePay() hiển thị nút Google Pay chính thức và xử lý việc ủy quyền thanh toán — 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
Google Pay không có bước đăng ký domain. Cake cấu hình Google Pay cho merchant của bạn; nếu chưa được cấu hình, nút sẽ phát sự kiện error với thông báo "Google Pay is not configured for this merchant". Các yêu cầu còn lại xoay quanh trình duyệt của người mua:
| Yêu cầu | Ghi chú |
|---|---|
| Trình duyệt được Google Pay hỗ trợ | Chrome, Safari, Firefox, Edge, Opera hoặc UC Browser, trên desktop và di động — xem Hỗ trợ trình duyệt. |
| Một thẻ đã được lưu vào Google Pay | Người mua cần có ít nhất một thẻ đã lưu vào tài khoản Google của họ để hoàn tất thanh toán. |
Chính sách của Google Pay
Merchant phải tuân thủ Chính sách sử dụng được chấp nhận (Acceptable Use Policy) của Google Pay and Wallet API và chấp nhận các điều khoản được quy định trong Điều khoản dịch vụ của Google Pay API (Google Pay API Terms of Service).
Bắt đầu nhanh
const bahn = new Bahn({ pk: "pk_live_...", environment: "sandbox" });
const gp = bahn.mountGooglePay("#google-pay-container", clientSecret, {
amount: 100000, // đơn vị tiền tệ nhỏ nhất — VND không chia cho 100
currency: "VND",
country: "VN",
});
gp.on("ready", () => console.log("Google Pay button rendered"));
gp.on("success", (paymentIntent) => (window.location.href = "/success"));
gp.on("error", (err) => console.error((err as { message: string }).message));
gp.on("cancel", () => console.log("Shopper dismissed the sheet"));mountGooglePay() 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 Google Pay có thể sử dụng được không, hiển thị nút, và chạy toàn bộ payment sheet. Nếu Google 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.
Tham số của mountGooglePay(selector, clientSecret, options)
| Tham số | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
selector | string | Có | CSS selector cho container để mount |
clientSecret | string | undefined | Có (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.amount | number | Có | Số tiền giao dịch, theo đơn vị tiền tệ nhỏ nhất |
options.currency | string | Có | Mã tiền tệ ISO 4217 (ví dụ "VND", "USD") |
options.country | string | Có | Mã quốc gia ISO 3166-1 alpha-2 (ví dụ "VN", "US") |
options.priceLabel | string | Không | Nhãn hiển thị cho tổng giao dịch trong sheet |
options.lineItems | Array<{ label, amount }> | Không | Bảng chi tiết từng khoản hiển thị trong sheet |
options.merchantName | string | Không | Tên merchant hiển thị trong sheet |
options.type | GooglePayButtonType | Không | Biến thể nhãn của nút (mặc định "pay") |
options.color | GooglePayButtonColor | Không | "black" | "white" (mặc định "black") |
options.locale | string | Không | Ngôn ngữ của nút (mặc định "en") |
options.size | { width?, height? } | Không | Kích thước nút (mặc định 100% × 48px) |
options.allowedCardNetworks | GooglePayCardNetwork[] | Không | Các mạng thẻ được phép (mặc định: amex, discover, interac, jcb, mastercard, visa) |
options.allowedAuthMethods | GooglePayAuthMethod[] | Không | Các phương thức xác thực thẻ được phép (mặc định: PAN_ONLY, CRYPTOGRAM_3DS) |
options.requestBillingAddress | boolean | Không | Yêu cầu địa chỉ thanh toán (billing address) |
options.requestPayerDetails | ("email" | "phone")[] | Không | Yêu cầu thông tin liên hệ của người mua |
options.requestShipping | boolean | Không | Thu thập địa chỉ giao hàng — xem Thu thập địa chỉ giao hàng |
options.shippingOptions | GooglePayShippingOption[] | Không | Các tùy chọn giao hàng ban đầu hiển thị trước khi địa chỉ được nhập |
Giá trị trả về: IGooglePayButtonComponent — .on(event, handler) (xem Sự kiện), .unmount(). bahn.cleanup()/bahn.unmountGooglePay() cũng sẽ gỡ nút này.
Xác nhận thanh toán
mountGooglePay() 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 — tương tự cách chia của Apple Pay và thẻ.
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. SDK sẽ tự xác nhận thanh toán và báo cáo kết quả:
const gp = bahn.mountGooglePay("#google-pay-container", clientSecret, {
amount: 100000,
currency: "VND",
country: "VN",
});
gp.on("success", (paymentIntent) => (window.location.href = "/success"));
gp.on("error", (err) => console.error((err as { message: string }).message));Nếu payment intent yêu cầu xác thực bổ sung, mountGooglePay() 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.
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 mountGooglePay(selector, undefined, options):
const gp = bahn.mountGooglePay("#google-pay-container", undefined, {
amount: 100000,
currency: "VND",
country: "VN",
merchantName: "Your Merchant Name",
});
gp.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 Google 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"),
});
});
gp.on("error", (err) => console.error((err as { message: string }).message));
gp.on("cancel", () => console.log("Shopper dismissed the sheet"));Trong luồng này, 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 Google 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ỉ:
const gp = bahn.mountGooglePay("#google-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ừ "shippingaddresschanged" thay vào đó.
shippingOptions: [{ id: "standard", label: "Standard", description: "3-5 days", amount: 0 }],
});
gp.on("shippingaddresschanged", (event) => {
const { address, resolve, reject } = event;
if (address.countryCode !== "VN") {
reject(); // hiển thị lỗi inline "không thể giao đến đây" trong sheet
return;
}
resolve({
shippingOptions: [
{ id: "standard", label: "Standard", description: "3-5 days", amount: 0 },
{ id: "express", label: "Express", description: "1-2 days", amount: 20000 },
],
});
});
gp.on("shippingoptionchanged", (event) => {
const { shippingOption, resolve } = event;
// Tính lại tổng số tiền để bao gồm phí ship đã chọn.
resolve({ amount: 100000 + shippingOption.amount });
});
gp.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). address được truyền vào "shippingaddresschanged" cố tình chỉ mang thông tin sơ bộ — chỉ gồm khu vực, tỉnh/thành, 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
mountGooglePay() đã 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 Google Pay không thể sử dụng. Chỉ gọi bahn.isGooglePayAvailable() 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 Google 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ị.
if (!(await bahn.isGooglePayAvailable())) {
// ẩ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
}Sự kiện
.on(event, handler) trên đối tượng mà mountGooglePay() trả về:
| Sự kiện | Kích hoạt khi | Handler 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 | { confirmation_token } — chỉ áp dụng cho luồng dựa trên token |
"error" | Ủy quyền hoặc xác nhận thất bại | { message } |
"cancel" | Người mua đóng sheet mà không thanh toán | — |
"shippingaddresschanged" | Người mua nhập hoặc thay đổi địa chỉ giao hàng | { address, resolve, reject } — yêu cầu requestShipping: true |
"shippingoptionchanged" | Người mua chọn tùy chọn giao hàng khác | { shippingOption, resolve, reject } — yêu cầu requestShipping: true |
Hỗ trợ trình duyệt
Việc trình duyệt nào dùng được Google Pay do Google quyết định. SDK không tự kiểm tra trình duyệt. bahn.isGooglePayAvailable() tải pay.js của Google vào trang của bạn, còn mountGooglePay() tải nó bên trong iframe của Bahn. Cả hai đều hỏi isReadyToPay() của Google.
Google liệt kê các trình duyệt được hỗ trợ, trên cả desktop và di động:
| Trình duyệt | Hỗ trợ |
|---|---|
| Chrome, Edge, Opera | Được hỗ trợ |
| Safari (macOS, iOS, iPadOS) | Được hỗ trợ |
| Firefox | Được hỗ trợ |
| UC Browser | Được hỗ trợ |
Trên các trình duyệt không phải Chrome, Google có thể mở payment sheet trong một cửa sổ popup để người mua đăng nhập tài khoản Google. Trình chặn popup, hoặc trang đặt nút bên trong một popup khác, có thể làm luồng này không hoạt động.
bahn.isGooglePayAvailable() trả về false khi pay.js không tải được trong 10 giây, khi isReadyToPay() trả về false, hoặc khi có bất kỳ lỗi nào.
Kiểm thử
Sử dụng một tài khoản Google có thẻ đã thêm vào Google Pay, trỏ đến môi trường sandbox của bạn. Hãy test trên Chrome và cả Safari hoặc Firefox, vì các trình duyệt này hiển thị payment sheet khác nhau. Môi trường test của Google Pay chấp nhận cùng các thẻ test như tài khoản Bahn sandbox của bạn.