Skip to content

Getting Started ​

Integrate card payments and 3DS authentication into your application in minutes. New to Bahn? Read the Web SDK Overview first for how the pieces fit together and which flow to pick.

Prerequisites ​

You need a publishable key (pk) from Cake Digital Bank. Contact your account manager to obtain one. See Onboarding for what Bahn issues to you.

WhatDescription
Publishable Key (pk)Sent as X-Bn-Publishable-Key header on all API calls. Identifies your merchant account.
Environment"sandbox" for testing, "production" for live payments.

Installation ​

Load the SDK with a script tag from Cake's CDN. No bundler, no import — Bahn is available as a global as soon as the script loads.

Every release is published to its own version-pinned path (/bn-sdk/v<version>/index.js), so the file at a given URL never changes — pin a version and add the integrity attribute so the browser verifies the bundle hasn't been tampered with. There's a single script URL for both sandbox and production — the environment option you pass to new Bahn() is what controls which API/iframe domains it talks to, not which script you load:

html
<script
  src="https://bn.cake.vn/bn-sdk/v2.4.0/index.js"
  integrity="sha384-jBbonUZLgKU82F+X/ITqcmeInXYYwZYdmCDoPFX8wkWL9dCkeUHyi0Fs1EUnkeDS"
  crossorigin="anonymous"
></script>

<script>
  const bahn = new Bahn({ pk: "pk_test_...", environment: "sandbox" });
</script>

The matching hash for each release is published alongside the bundle at .../v<version>/index.js.sha384 — e.g. https://bn.cake.vn/bn-sdk/v2.4.0/index.js.sha384.

See Environments for the full domain mapping and version details.

Initialize the SDK ​

The Bahn class is a singleton. Call new Bahn() once — subsequent calls return the same instance.

typescript
// Bahn is a global once the SDK script has loaded — no import needed
const bahn = new Bahn({
  pk: "pk_live_...",                 // Your publishable key
  environment: "sandbox",             // or "production"
  appId: "your-app-id",               // optional — for 3DS tenant routing
  teamId: "your-team-id",             // optional — for 3DS tenant routing
});

Complete checkout form with card input and pay button

Quick Start: Card Payment ​

A minimal card payment flow has three steps:

1. Mount the card input ​

typescript
const card = bahn.mountCard("#card-container", {
  fields: {
    number: { label: "Card Number" },
    expiry: { label: "Expiry" },
    cvc: { label: "CVC" },
  },
});

card.on("ready", () => console.log("Card input ready"));
card.on("complete", () => console.log("All fields valid"));

2. Create a payment intent on your backend ​

confirmPayment confirms an existing payment intent. Your backend must create one first and return the client_secret:

typescript
// Your backend: POST /api/create-payment-intent
// → returns { client_secret: "pi_xxx_secret_yyy" }
const { client_secret } = await fetch("/api/create-payment-intent", {
  method: "POST",
  body: JSON.stringify({ amount: 100000, currency: "vnd" }),
}).then(r => r.json());

3. Confirm the payment ​

typescript
try {
  const { payment_intent, three_ds_payload } = await bahn.confirmPayment({
    client_secret,
  });

  // If 3DS is required, three_ds_payload will be present
  if (three_ds_payload) {
    bahn.mount3DS(three_ds_payload.id, {
      onSuccess: () => console.log("3DS passed"),
      onFailure: (err) => console.error("3DS failed", err),
    });
  }
} catch (err) {
  console.error("Payment failed:", err);
}

4. Handle 3DS (if triggered) ​

After a successful 3DS challenge, poll for the final payment status using pollPaymentIntentStatus(client_secret, callbacks). See Confirm Payment for details.

How It Works ​

sequenceDiagram
    participant App as Your App
    participant SDK as Bahn SDK
    participant Iframe as Card Iframe
    participant API as Cake API

    App->>SDK: new Bahn({ pk, environment })
    SDK-->>App: Singleton instance

    App->>SDK: mountCard
    SDK->>Iframe: Load 3 iframes (number, expiry, CVC)
    Iframe-->>SDK: Validity state (boolean only)
    SDK-->>App: CardComponent

    App->>SDK: confirmPayment(payload)
    SDK->>Iframe: Delegate API call (card data merged inside)
    Iframe->>API: POST /bn/sdk/payment_intents/confirm
    API-->>Iframe: PaymentIntent + optional 3DS payload
    Iframe-->>SDK: Result (card values never exposed)
    SDK-->>App: { payment_intent, three_ds_payload }

    App->>SDK: mount3DS(sessionId, callbacks)
    SDK->>SDK: Render 3DS challenge in iframe
    SDK-->>App: callbacks.onSuccess() / onFailure()

Next Steps ​

Developers