Thanh toán một lần
Luồng thanh toán của Bahn được chia thành hai phạm vi trách nhiệm. Merchant API (backend) của bạn điều phối phiên thanh toán và xử lý kết quả của nó, trong khi Bahn SDK (frontend) được nhúng thu thập thông tin thẻ và điều khiển trải nghiệm thanh toán ngay trong trang.
Điều kiện tiên quyết
Luồng tổng quan
sequenceDiagram
autonumber
participant User as User
participant MerchantWeb as Merchant Web<br/>(Frontend)
participant SDK as SDK<br/>(Embedded)
participant MerchantAPI as Merchant API<br/>(Backend)
participant API as Bahn API
Note over User, API: Phase 1: Create PaymentIntent
User ->> MerchantWeb: Click "Pay Now" button
MerchantWeb ->> MerchantAPI: Request checkout
MerchantAPI ->> API: CreatePaymentIntent(amount, currency)
API -->> MerchantAPI: {payment_intent, client_secret}
MerchantAPI -->> MerchantWeb: {client_secret}
MerchantWeb ->> SDK: Initialize SDK
Note over User, API: Phase 2: Collect Card & Confirm
SDK -->> User: Display payment form (card input fields)
User ->> SDK: Enter card details (PAN, exp, CVV, billing)
SDK ->> API: ConfirmPaymentIntent
alt 3DS flow
API -->> SDK: Action required
SDK -->> User: Display 3DS challenge (OTP / biometric)
User ->> SDK: Complete authentication challenge
end
Note over MerchantAPI, API: Phase 4: Payment Result
par Notify
API ->> MerchantAPI: Webhook: charge.capture, payment_intent.status
MerchantAPI ->> MerchantAPI: Consumer webhook event
end
Note over User, API: Phase 5: Complete
API -->> SDK: Payment result
SDK -->> MerchantWeb: Payment result
MerchantWeb ->> MerchantAPI: GET Order status
MerchantAPI -->> MerchantWeb: Order
MerchantWeb -->> User: Show order status
Cách hoạt động
Giai đoạn 1 — Tạo PaymentIntent
- Người dùng nhấn "Pay Now" — Merchant Web nhận thao tác này và yêu cầu checkout từ backend của bạn.
- Frontend yêu cầu checkout — Merchant Web gửi request checkout đến Merchant API của bạn.
- Backend tạo một PaymentIntent — Merchant API của bạn gọi
CreatePaymentIntentvớiamountvàcurrency. Bahn trả về đối tượngpayment_intentvàclient_secrettương ứng. - Backend chỉ chuyển tiếp
client_secret— Merchant API của bạn trả về{ client_secret }cho frontend.payment_intent.idvà API key vẫn ở phía backend. - Frontend khởi tạo SDK — Merchant Web truyền
client_secretcho Bahn SDK được nhúng.
INFO
client_secret chỉ có hiệu lực cho một phiên thanh toán duy nhất và an toàn khi expose cho client. payment_intent.id và API key của bạn không bao giờ được rời khỏi backend.
Giai đoạn 2 — Thu thập thẻ & xác nhận
- SDK hiển thị form thanh toán — SDK hiển thị các ô nhập thẻ an toàn trên trang. Frontend của bạn không bao giờ xử lý dữ liệu thẻ thô.
- Người dùng nhập thông tin thẻ — Người dùng điền PAN, ngày hết hạn, CVV, và thông tin thanh toán rồi submit.
- SDK xác nhận thanh toán — SDK gọi
ConfirmPaymentIntentvớiclient_secretvà dữ liệu thẻ.
Nếu ngân hàng phát hành thẻ yêu cầu xác thực bổ sung, luồng 3DS sẽ được kích hoạt:
- Bahn báo hiệu cần thêm hành động — Bahn trả về một response yêu cầu hành động cho SDK.
- SDK hiển thị thử thách 3DS — SDK hiển thị thử thách OTP hoặc sinh trắc học cho người dùng.
- Người dùng hoàn tất thử thách — Người dùng xác thực, và SDK tiếp tục.
Nếu không cần 3DS, các bước 9–11 sẽ được bỏ qua.
Giai đoạn 4 — Kết quả thanh toán
Khi thanh toán được xử lý xong, Bahn sẽ thông báo cho backend của bạn:
- Thông báo qua webhook — Bahn gửi các event
charge.capturevàpayment_intent.statusđến webhook endpoint đã đăng ký của bạn. Merchant API của bạn xử lý các event này và cập nhật trạng thái đơn hàng. Webhook là nguồn sự thật chính thức cho kết quả thanh toán cuối cùng.
Giai đoạn 5 — Hoàn tất
- Bahn đẩy kết quả về SDK — Bahn API gửi kết quả thanh toán cuối cùng trực tiếp đến SDK.
- SDK trả kết quả về — SDK phát ra kết quả thanh toán cho frontend Merchant Web của bạn.
- Frontend lấy thông tin đơn hàng — Merchant Web gọi Merchant API của bạn để lấy trạng thái đơn hàng mới nhất.
- Backend trả về đơn hàng — Merchant API của bạn phản hồi với đơn hàng, đã được cập nhật từ webhook event.
- Người dùng thấy kết quả — Merchant Web hiển thị màn hình thành công hoặc thất bại cho người dùng.
Trách nhiệm của merchant API
Backend của bạn chịu trách nhiệm tạo phiên thanh toán và xử lý kết quả cuối cùng của nó.
1. Tạo một PaymentIntent
Khi frontend của bạn yêu cầu checkout, gọi CreatePaymentIntent. Bahn trả về một đối tượng payment_intent với client_secret được SDK dùng để xác nhận thanh toán thay mặt khách hàng.
POST /api/payment_intentsRequest
{
"amount": "10000",
"currency": "usd",
"description": "Order #1234",
"metadata": {
"order_id": "1234"
}
}| Trường | Bắt buộc | Mô tả |
|---|---|---|
amount | Có | Số tiền theo đơn vị tiền tệ nhỏ nhất (ví dụ cent). |
currency | Có | Mã tiền tệ ISO 4217 ba ký tự. |
description | Không | Mô tả dễ đọc hiển thị trên sao kê của khách hàng. |
metadata | Không | Các cặp key-value tùy ý để lưu thêm thông tin. |
Response
{
"payment_intent": {
"id": "pi_xxxxxxxx",
"client_secret": "cs_xxxxxxxx",
"amount": "10000",
"currency": "usd",
"status": "requires_payment_method",
"description": "Order #1234",
"metadata": {
"order_id": "1234"
}
}
}INFO
Chỉ trả về client_secret cho frontend của bạn — SDK sử dụng nó để khởi tạo và xác nhận thanh toán. Giữ payment_intent.id ở backend để đối chiếu webhook. Không bao giờ để lộ API key của bạn cho client.
2. Xử lý webhook
Khi thanh toán đạt trạng thái cuối, Bahn sẽ gửi kết quả đến webhook endpoint đã đăng ký của bạn. Backend của bạn phải coi webhook là nguồn sự thật chính thức khi cập nhật trạng thái đơn hàng.
| Event | Mô tả |
|---|---|
payment_intent.status.succeeded | Thanh toán đã được capture thành công. |
payment_intent.status.canceled | Thanh toán đã bị hủy. |
charge.captured | Charge đã được capture. |