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.
| What | Description |
|---|---|
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:
<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.
// 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
});
Quick Start: Card Payment
A minimal card payment flow has three steps:
1. Mount the card input
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:
// 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
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
- Card Payments — Full card input customization, styling, and validation
- QR Payments — Napas QR code integration
- Apple Pay — Express checkout with your own Apple Pay button
- Google Pay — Express checkout with your own Google Pay button
- Payment Flows — Confirm Payment, Token-Based Flow, Save a Card
- Type Definitions — Every SDK type, plus a downloadable
bahn.d.ts - 3DS Authentication — Self-hosted 3DS challenge flow
- SDK Reference — Complete API reference
- Environments — Sandbox vs production domain mapping, test cards, CSP and browser support
- Go-Live Checklist — What to check before switching to production