Skip to content

Hoàn tất thanh toán trên server của bạn ​

Trong luồng thanh toán một lần tiêu chuẩn, backend của bạn tạo trước một PaymentIntent và SDK xác nhận nó trực tiếp. Nhưng đôi khi bạn cần kiểm tra thông tin thẻ trước khi thực hiện thanh toán — ví dụ, để áp dụng ưu đãi theo ngân hàng phát hành, điều chỉnh tổng đơn hàng, hoặc áp dụng quy tắc nghiệp vụ dựa trên loại thẻ hoặc nguồn tiền.

Hướng dẫn này mô tả một luồng xác nhận trì hoãn (deferred confirmation). SDK sẽ thu thập thông tin thẻ và tạo một ConfirmationToken thay vì xác nhận thanh toán ngay lập tức. Backend của bạn lấy token đó để đọc metadata của thẻ, áp dụng logic tùy chỉnh, và chỉ sau đó mới tạo và xác nhận PaymentIntent — hoàn toàn ở phía server.

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 Order
    User ->> MerchantWeb: Click "Pay Now" button
    MerchantWeb ->> MerchantAPI: Create order
    MerchantAPI -->> MerchantWeb: {order_id}

    Note over User, API: Phase 2: Collect Card & Create ConfirmationToken
    MerchantWeb ->> SDK: Initialize SDK
    SDK -->> User: Display payment form (card input fields)
    User ->> SDK: Enter card details (PAN, exp, CVV, billing)
    SDK ->> API: CreateConfirmationToken
    API -->> SDK: confirmation_token
    SDK -->> MerchantWeb: confirmation_token

    Note over User, API: Phase 3: Perform Custom Logic
    MerchantWeb ->> MerchantAPI: FinalizeOrder(order_id, confirmation_token)
    MerchantAPI ->> API: GetConfirmationToken
    API -->> MerchantAPI: ConfirmationToken Object
    MerchantAPI ->> MerchantAPI: Perform custom logic
    MerchantAPI -->> MerchantWeb: Order detail
    MerchantWeb -->> User: display order detail

    Note over User, API: Phase 4: Confirm Payment
    User ->> MerchantWeb: Confirm to pay
    MerchantWeb ->> MerchantAPI: Confirm Order
    MerchantAPI ->> API: CreatePaymentIntent(amount, currency)
    API -->> MerchantAPI: PaymentIntent
    MerchantAPI ->> API: ConfirmPaymentIntent(id, confirmation_token)

    alt 3DS flow
        API -->> MerchantAPI: Action required
        MerchantAPI -->> MerchantWeb: Action required
        MerchantWeb ->> SDK: HandleNextAction
        SDK -->> User: Display 3DS challenge (OTP / biometric)
        User ->> SDK: Complete authentication challenge
    end

    Note over MerchantAPI, API: Phase 5: Payment Result
    par Notify
        API ->> MerchantAPI: Webhook: charge.capture, payment_intent.status
        MerchantAPI ->> MerchantAPI: Consumer webhook event
    end

    Note over User, API: Phase 6: Complete
    API -->> MerchantAPI: Payment result
    MerchantAPI -->> MerchantWeb: Order Status
    MerchantWeb -->> User: Show order status

Cách hoạt động ​

Giai đoạn 1 — Tạo đơn hàng ​

  1. Người dùng nhấn "Pay Now" — Merchant Web nhận thao tác này.
  2. Frontend tạo đơn hàng — Merchant Web gửi request đến Merchant API của bạn để tạo đơn hàng.
  3. Backend trả về order ID — Merchant API phản hồi với { order_id }. Chưa có PaymentIntent nào được tạo — số tiền thanh toán cuối cùng có thể thay đổi khi thẻ được xác định.

Giai đoạn 2 — Thu thập thẻ & tạo ConfirmationToken ​

  1. Frontend khởi tạo SDK — Merchant Web khởi tạo Bahn SDK được nhúng. Lúc này SDK đã sẵn sàng để thu thập thông tin thẻ.
  2. SDK hiển thị form thanh toán — SDK hiển thị các ô nhập thẻ an toàn trực tiếp trên trang. Frontend của bạn không bao giờ xử lý dữ liệu thẻ thô.
  3. 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.
  4. SDK tạo một ConfirmationToken — SDK gọi CreateConfirmationToken với dữ liệu thẻ. Bahn tokenize thẻ phía server và trả về một confirmation_token có thời hạn ngắn.
  5. SDK trả token về — SDK truyền confirmation_token về cho frontend Merchant Web của bạn. Dữ liệu thẻ thô không bao giờ rời khỏi SDK.

Giai đoạn 3 — Thực hiện logic tùy chỉnh ​

  1. Frontend gửi token đến backend của bạn — Merchant Web gọi Merchant API của bạn với FinalizeOrder(order_id, confirmation_token).
  2. Backend lấy chi tiết token — Merchant API của bạn gọi GetConfirmationToken để lấy đầy đủ đối tượng token, bao gồm metadata của thẻ như ngân hàng phát hành, BIN, và loại thẻ.
  3. Backend thực hiện logic tùy chỉnh — Merchant API của bạn thực thi các quy tắc nghiệp vụ dựa trên thông tin thẻ — ví dụ, áp dụng ưu đãi theo ngân hàng phát hành hoặc điều chỉnh tổng đơn hàng.
  4. Backend trả về đơn hàng đã cập nhật — Merchant API phản hồi với chi tiết đơn hàng đã hoàn tất.
  5. Frontend hiển thị tóm tắt đơn hàng — Merchant Web hiển thị chi tiết đơn hàng (bao gồm mọi ưu đãi hoặc điều chỉnh đã áp dụng) cho người dùng.

Giai đoạn 4 — Xác nhận thanh toán ​

  1. Người dùng xác nhận thanh toán — Người dùng xem lại tóm tắt đơn hàng và xác nhận để tiếp tục.
  2. Frontend yêu cầu xác nhận thanh toán — Merchant Web gọi Merchant API của bạn để xác nhận đơn hàng.
  3. Backend tạo một PaymentIntent — Merchant API của bạn gọi CreatePaymentIntent với amount và currency cuối cùng.
  4. Backend xác nhận PaymentIntent — Merchant API của bạn gọi ConfirmPaymentIntent với confirmation_token. Toàn bộ việc xác nhận thanh toán diễn ra ở phía server — dữ liệu thẻ không bao giờ quay trở lại hệ thống của bạn.

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:

  1. 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 (action-required) cho Merchant API của bạn.
  2. Backend chuyển tiếp hành động — Merchant API chuyển tín hiệu action-required đến Merchant Web.
  3. Frontend ủy quyền cho SDK — Merchant Web gọi HandleNextAction trên SDK.
  4. 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.
  5. 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 18–22 sẽ được bỏ qua.

Giai đoạn 5 — 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.capture và 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 6 — Hoàn tất ​

  1. Bahn gửi kết quả thanh toán — Bahn API gửi kết quả thanh toán cuối cùng đến Merchant API của bạn.
  2. Backend trả về trạng thái đơn hàng — Merchant API phản hồi cho Merchant Web với trạng thái đơn hàng đã cập nhật.
  3. 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 ​

Trong luồng này, backend của bạn điều khiển toàn bộ vòng đời thanh toán: tạo đơn hàng, kiểm tra card token để áp dụng logic nghiệp vụ, và vừa tạo vừa xác nhận PaymentIntent ở phía server.

1. Tạo một đơn hàng ​

Khi người dùng bắt đầu checkout, tạo một đơn hàng trong hệ thống của bạn và trả về order_id cho frontend. Lúc này SDK được khởi tạo mà chưa có PaymentIntent — số tiền cuối cùng có thể phụ thuộc vào thẻ được sử dụng.

2. Hoàn tất đơn hàng ​

Sau khi SDK thu thập thông tin thẻ, nó trả về một confirmation_token cho frontend của bạn, và frontend chuyển tiếp nó đến backend của bạn.

Gọi GetConfirmationToken để lấy đầy đủ đối tượng token và kiểm tra metadata của thẻ:

http
POST /api/confirmation_token/get
json
{
  "confirmation_token": "ct_xxxxxxxx"
}

Response bao gồm các thông tin thẻ như:

TrườngMô tả
card.brandMạng thẻ (ví dụ visa, mastercard).
card.issuerTên ngân hàng phát hành.
card.bin6–8 chữ số đầu của số thẻ.
card.fundingLoại nguồn tiền: credit, debit, hoặc prepaid.

Sử dụng thông tin này để áp dụng các quy tắc nghiệp vụ — ví dụ, áp dụng ưu đãi cho thẻ của một ngân hàng phát hành cụ thể — sau đó trả về chi tiết đơn hàng đã cập nhật cho frontend để người dùng xem lại.

3. Xác nhận thanh toán ​

Khi người dùng xác nhận đơn hàng, hãy tạo và xác nhận PaymentIntent theo trình tự. Cả hai lệnh gọi đều diễn ra hoàn toàn trên backend của bạn.

Bước 1 — Tạo một PaymentIntent

Gọi CreatePaymentIntent với số tiền cuối cùng của đơn hàng:

http
POST /api/payment_intents
json
{
  "amount": "10000",
  "currency": "usd",
  "description": "Order #1234",
  "metadata": {
    "order_id": "1234"
  }
}
TrườngBắt buộcMô tả
amountCóSố tiền theo đơn vị tiền tệ nhỏ nhất (ví dụ cent).
currencyCóMã tiền tệ ISO 4217 ba ký tự.
descriptionKhôngMô tả dễ đọc hiển thị trên sao kê của khách hàng.
metadataKhôngCác cặp key-value tùy ý để lưu thêm thông tin.

Bước 2 — Xác nhận PaymentIntent

Gọi ConfirmPaymentIntent với confirmation_token từ SDK:

http
POST /api/payment_intents/{id}/confirm
json
{
  "confirmation_token": "ct_xxxxxxxx"
}

Nếu response chỉ ra rằng cần thêm hành động (3DS), hãy chuyển tiếp payload hành động đó đến frontend của bạn. Merchant Web sẽ gọi HandleNextAction trên SDK, SDK sẽ hiển thị thử thách OTP hoặc sinh trắc học cho người dùng. Sau khi xác thực hoàn tất, không cần thêm hành động nào ở backend của bạn — Bahn sẽ gửi kết quả cuối cùng qua webhook.

INFO

API key và payment_intent.id của bạn không bao giờ rời khỏi backend của bạn. confirmation_token là một token chỉ dùng được một lần — nó không thể được dùng để khởi tạo một thanh toán thứ hai.

4. 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.

EventMô tả
payment_intent.status.succeededThanh toán đã được capture thành công.
payment_intent.status.canceledThanh toán đã bị hủy.
charge.capturedCharge đã được capture.

Developers