Skip to content

Finalize payments on your server ​

In a standard one-time payment flow, your backend creates a PaymentIntent upfront and the SDK confirms it directly. But sometimes you need to inspect the card before committing to a payment — for example, to apply an issuer-based discount, adjust the order total, or enforce business rules based on card type or funding source.

This guide covers a deferred confirmation flow. The SDK collects the card and creates a ConfirmationToken instead of immediately confirming a payment. Your backend retrieves the token to read card metadata, applies any custom logic, and only then creates and confirms the PaymentIntent — entirely server-side.

High-Level Flow ​

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

How It Works ​

Phase 1 — Create Order ​

  1. User clicks "Pay Now" — Merchant Web receives the action.
  2. Frontend creates an order — Merchant Web sends a request to your Merchant API to create an order.
  3. Backend returns the order ID — Merchant API responds with { order_id }. No PaymentIntent is created yet — the final payment amount may change once the card is known.

Phase 2 — Collect Card & Create ConfirmationToken ​

  1. Frontend initializes the SDK — Merchant Web initializes the embedded Bahn SDK. At this point the SDK is ready to collect card details.
  2. SDK renders the payment form — The SDK displays secure card input fields directly in the page. Your frontend never handles raw card data.
  3. User enters card details — The user fills in PAN, expiry, CVV, and billing details and submits.
  4. SDK creates a ConfirmationToken — The SDK calls CreateConfirmationToken with the card data. Bahn tokenizes the card server-side and returns a short-lived confirmation_token.
  5. SDK surfaces the token — The SDK passes the confirmation_token to your Merchant Web frontend. The raw card data never leaves the SDK.

Phase 3 — Perform Custom Logic ​

  1. Frontend sends the token to your backend — Merchant Web calls your Merchant API with FinalizeOrder(order_id, confirmation_token).
  2. Backend retrieves token details — Your Merchant API calls GetConfirmationToken to fetch the full token object, which includes card metadata such as issuer, BIN, and card type.
  3. Backend performs custom logic — Your Merchant API executes any business rules based on the card details — for example, applying an issuer-based discount or adjusting the order total.
  4. Backend returns the updated order — Merchant API responds with the finalized order details.
  5. Frontend displays the order summary — Merchant Web renders the order details (including any applied discounts or adjustments) to the user.

Phase 4 — Confirm Payment ​

  1. User confirms payment — The user reviews the order summary and confirms to proceed.
  2. Frontend requests payment confirmation — Merchant Web calls your Merchant API to confirm the order.
  3. Backend creates a PaymentIntent — Your Merchant API calls CreatePaymentIntent with the final amount and currency.
  4. Backend confirms the PaymentIntent — Your Merchant API calls ConfirmPaymentIntent with the confirmation_token. The entire payment confirmation happens server-side — the card data never re-enters your system.

If the card issuer requires additional authentication, the 3DS flow is triggered:

  1. Bahn signals action required — Bahn returns an action-required response to your Merchant API.
  2. Backend forwards the action — Merchant API passes the action-required signal to Merchant Web.
  3. Frontend delegates to the SDK — Merchant Web calls HandleNextAction on the SDK.
  4. SDK presents the 3DS challenge — The SDK renders the OTP or biometric challenge to the user.
  5. User completes the challenge — The user authenticates and the SDK proceeds.

If no 3DS is required, steps 18–22 are skipped.

Phase 5 — Payment Result ​

Once the payment is processed, Bahn notifies your backend:

  • Webhook notification — Bahn delivers charge.capture and payment_intent.status events to your registered webhook endpoint. Your Merchant API consumes these events and updates its order state. The webhook is the authoritative source of truth for the final payment outcome.

Phase 6 — Complete ​

  1. Bahn sends the payment result — The Bahn API sends the final payment result to your Merchant API.
  2. Backend returns the order status — Merchant API responds to Merchant Web with the updated order status.
  3. User sees the outcome — Merchant Web renders the success or failure screen to the user.

Merchant API Responsibilities ​

In this flow, your backend drives the entire payment lifecycle: it creates the order, inspects the card token to apply business logic, and both creates and confirms the PaymentIntent server-side.

1. Create an Order ​

When the user initiates checkout, create an order in your system and return the order_id to the frontend. The SDK is initialized without a PaymentIntent at this point — the final amount may depend on the card used.

2. Finalize the Order ​

After the SDK collects card details, it returns a confirmation_token to your frontend, which forwards it to your backend.

Call GetConfirmationToken to retrieve the full token object and inspect card metadata:

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

The response includes card details such as:

FieldDescription
card.brandCard network (e.g. visa, mastercard).
card.issuerName of the issuing bank.
card.binFirst 6–8 digits of the card number.
card.fundingFunding type: credit, debit, or prepaid.

Use this information to apply any business rules — for example, applying a discount for cards from a specific issuer — then return the updated order detail to the frontend for the user to review.

3. Confirm the Payment ​

Once the user confirms the order, create and confirm the PaymentIntent in sequence. Both calls happen entirely on your backend.

Step 1 — Create a PaymentIntent

Call CreatePaymentIntent with the final order amount:

http
POST /api/payment_intents
json
{
  "amount": "10000",
  "currency": "usd",
  "description": "Order #1234",
  "metadata": {
    "order_id": "1234"
  }
}
FieldRequiredDescription
amountYesAmount in the smallest currency unit (e.g. cents).
currencyYesThree-letter ISO 4217 currency code.
descriptionNoHuman-readable description shown on the customer's statement.
metadataNoArbitrary key-value pairs for storing additional information.

Step 2 — Confirm the PaymentIntent

Call ConfirmPaymentIntent with the confirmation_token from the SDK:

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

If the response indicates an action is required (3DS), forward the action payload to your frontend. Merchant Web will call HandleNextAction on the SDK, which will present the OTP or biometric challenge to the user. Once authentication completes, no further action is needed on your backend — Bahn will send the final result via webhook.

INFO

Your API key and payment_intent.id never leave your backend. The confirmation_token is a one-time-use token — it cannot be used to initiate a second payment.

4. Handle the Webhook ​

Once the payment reaches a terminal state, Bahn sends the result to your registered webhook endpoint. Your backend must treat the webhook as the authoritative source of truth when updating order state.

EventDescription
payment_intent.status.succeededPayment was captured successfully.
payment_intent.status.canceledPayment was canceled.
charge.capturedCharge was captured.

Developers