Wajub.js — JavaScript SDK
Complete guide for @wajub/js — the official browser library for embedding Wajub Checkout. npm, CDN, hosted checkout, components, appearance, events and framework wrappers.
Wajub.js is the official JavaScript SDK for embedding Wajub Checkout on your website or web app. It supports three integration modes — redirect, inline embed and custom components — plus framework wrappers for React, Vue and Svelte.
Two package flavors
@wajub/js (npm) is the thin loader — it fetches the runtime from js.wajub.com.
<script src="https://js.wajub.com"> (CDN) loads the full runtime directly.
Both give you the same API — choose npm if you use TypeScript and a bundler.
Installation
npm (@wajub/js)
npm install @wajub/jsimport { loadWajub, mount } from "@wajub/js";
// Returns null on the server (SSR-safe) — mount in the browser only.
const rt = await loadWajub();
rt?.wajub.mount("#checkout", {
sessionId: "YOUR_SESSION_TOKEN",
onSuccess: (txn) => console.log("paid", txn),
});CDN script tag (HTML without a bundler)
<script src="https://js.wajub.com"></script>
<script>
wajub.mount("#checkout", {
sessionId: "YOUR_SESSION_TOKEN",
onSuccess: (txn) => console.log("paid", txn),
});
</script>ESM from CDN (no npm)
import { mount } from "https://js.wajub.com/wajub.mjs";
await mount("#checkout", { sessionId: "YOUR_SESSION_TOKEN" });Quickstart
1. Create a payment session (backend)
POST https://api.wajub.com/payments
Authorization: sk_test.xxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"amount": 35000,
"currency": "XOF",
"reference": "ORDER-123",
"description": "Commande #123",
"customer": {
"email": "buyer@example.com",
"name": "Amina N.",
"phone": "+2250700000000"
},
"callback": "https://shop.example.com/return"
}The response contains authorization_token — this is the sessionId for the frontend.
2. Mount the checkout (frontend)
<div id="checkout" style="min-height:480px"></div>
<script src="https://js.wajub.com"></script>
<script>
const checkout = wajub.mount("#checkout", {
sessionId: "YOUR_SESSION_TOKEN", // from your backend
locale: "fr",
layout: "tabs",
appearance: {
primaryColor: "#6366f1",
borderRadius: "12px",
},
onReady: () => console.log("Checkout ready"),
onSuccess: (transaction) => {
window.location.href = "/thank-you";
},
onError: (error) => console.error(error.code, error.message),
onCancel: () => console.log("Customer cancelled"),
onBreakdown: (breakdown) => {
// Sync your cart total
document.getElementById("cart-total").textContent =
breakdown.total.toLocaleString() + " " + breakdown.currency;
},
});
</script>Integration modes
Wajub.js supports four integration approaches, from simplest to most customizable:
| Mode | API | Who builds the UI | OTP / coupons / shipping |
|---|---|---|---|
| Redirect | wajub.checkout() | Wajub (full page) | All supported |
| Inline embed | wajub.mount() | Wajub (inside your page) | All supported |
| Overlay | wajub.open() | Wajub (modal) | All supported |
| Components | wajub.components() | You (custom layout) | Limited (see Components) |
Decision tree
Need full checkout (summary, coupons, OTP, 3DS)?
├─ Yes, fastest path ──────► Redirect (authorization_url)
├─ Yes, on my page ────────► wajub.mount() (inline embed)
├─ Yes, as a modal ────────► wajub.open() (overlay)
└─ No, I want my own UI ───► Wajub Components
card | mobileMoney | wallet | payment
+ your "Pay" button + confirmPayment()Redirect
The simplest path — redirect the browser to the hosted payment page:
wajub.checkout({ sessionId: "YOUR_SESSION_TOKEN" });After the payment attempt, the customer is sent back to your callback URL with
?status=complete|cancelled|failed|expired&reference=trx_….
Inline embed (mount)
Embed the full checkout inside a container on your page:
const checkout = wajub.mount("#checkout", {
sessionId: "YOUR_SESSION_TOKEN",
locale: "fr",
layout: "tabs",
appearance: { primaryColor: "#6366f1", borderRadius: "12px" },
onSuccess: (txn) => window.location.href = "/thanks",
onError: (err) => console.error(err),
});| Option | Type | Default | Description |
|---|---|---|---|
sessionId | string | — | Required. Session token from your backend |
locale | string | session default | fr, en, es, pt, ar |
layout | string | session default | classic, compact, tabs, accordion |
appearance | object | — | Theme customization (see below) |
embedOrigin | string | location.origin | Your canonical domain (multi-shop setups) |
showLoading | boolean | true | Show a loading placeholder |
Instance methods:
checkout.getState(); // Current checkout state
checkout.update({ locale: "en" }); // Change locale
checkout.update({ currency: "USD" }); // Multi-currency
checkout.update({ appearance: { ... } }); // Update theme
checkout.submit(); // Submit active form
checkout.selectMethod("pm_momo"); // Force payment method
checkout.cancel(); // Cancel payment
checkout.retry(); // Retry after failure
checkout.destroy(); // Remove embedOverlay (open)
Modal overlay on top of your page — same API as inline, plus modal options:
const popup = wajub.open({
sessionId: "YOUR_SESSION_TOKEN",
width: 920,
height: 680,
closeOnOverlay: false, // Clicking the backdrop does not close
closeOnEscape: true, // Escape dismisses the overlay
onClose: () => console.log("Modal dismissed — session is still active"),
onCancel: () => console.log("Customer cancelled payment"),
onSuccess: (txn) => popup.close(),
});Important: onClose means the overlay was dismissed (×, Escape, backdrop) — the
session remains open. onCancel means the customer actively clicked "Cancel".
The Wajub() client
Instead of the global wajub object, you can create a session-bound client:
// With a publishable key (for createPayment)
const client = Wajub("pk_test_...");
// With an existing session token
const client = Wajub.session("YOUR_SESSION_TOKEN");
// Object form
const client = Wajub({ publishableKey: "pk_test_...", sessionId: "..." });| Method | Description |
|---|---|
client.createPayment(params) | POST /payments — needs pk_... |
client.fetchSession() | Preview session (amount, methods, features) |
client.preload() | Preload checkout in background |
client.mount(el, config) | Embed inline checkout |
client.open(config) | Open overlay checkout |
client.checkout(config) | Redirect to checkout |
client.components(config?) | Create a Components factory |
client.confirmPayment(opts) | Submit a component, wait for success/error |
client.initCheckout(opts) | One-shot: create + mount in one call |
initCheckout — one-shot flow
await Wajub("pk_test_...").initCheckout({
mode: "inline",
container: "#checkout",
payment: {
amount: 35000,
currency: "XOF",
customer: { email: "buyer@example.com" },
},
appearance: { primaryColor: "#6366f1" },
onSuccess: (txn) => console.log("paid", txn),
});Appearance — visual customization
The same appearance object works for hosted checkout and Components:
const appearance = {
theme: "none", // Built-in preset: stripe | night | flat | none
primaryColor: "#6366f1",
borderRadius: "12px",
colorScheme: "auto", // light | dark | auto
labels: "floating", // above | floating
disableAnimations: false,
// Granular CSS variable overrides
variables: {
colorPrimary: "#6366f1",
colorText: "#f8fafc",
fontSizeBase: "15px",
spacingUnit: "4px",
},
// Selector-level CSS overrides
rules: {
".Input": { borderRadius: "10px", backgroundColor: "#1e293b" },
".Label": { fontSize: "13px" },
},
};
wajub.mount("#checkout", { sessionId, appearance });Presets: stripe (Stripe-like look), night (dark theme), flat (minimal), none (raw).
Update the appearance at any time:
checkout.update({ appearance: { primaryColor: "#111827" } });Events and lifecycle
Callbacks (hosted checkout)
| Callback | When |
|---|---|
onReady | Session loaded, form visible |
onSuccess | Payment succeeded — receives transaction |
onError | Payment failed — receives error |
onLoadError | Invalid token, terminal session, blocked embed |
onCancel | Customer explicitly cancelled |
onExpired | Session expired |
onStateChange | { state, method } on every transition |
onMethodChange | { methodId } when method changes |
onBreakdown | { subtotal, discount, tax, total, currency } |
onResize | { height } — inline embed only |
onClose | Overlay dismissed (not cancellation) |
Typical checkout flow
INITIATED → onReady
→ onStateChange PROCESSING
→ onStateChange 3DS_REQUIRED (when applicable)
→ onSuccess + transactionOr for an error path:
INITIATED → onReady
→ onError (WajubError with code, type, message)Checkout states
type CheckoutState =
| "INITIATED" | "COLLECTING_DETAILS"
| "PROCESSING" | "OTP_REQUIRED" | "3DS_REQUIRED"
| "USSD_REQUIRED" | "APPROVAL_REQUIRED" | "VERIFYING"
| "PENDING" | "SUCCESS" | "FAILED" | "EXPIRED" | "CANCELLED";| State | Meaning |
|---|---|
INITIATED | Initial form displayed |
PROCESSING | Charge in progress |
OTP_REQUIRED | Waiting for an OTP code |
3DS_REQUIRED | 3D Secure challenge |
PENDING | Awaiting provider confirmation (e.g., bank transfer) |
SUCCESS | Payment confirmed |
FAILED | Payment declined or errored |
Error handling (WajubError)
try {
await client.confirmPayment({ components: card });
} catch (err) {
// err.name === "WajubError"
console.log(err.code); // payment_declined, load_error, ...
console.log(err.type); // payment_error, invalid_request_error, ...
console.log(err.message); // Human-readable message
console.log(err.retryable); // Can this be retried?
console.log(err.decline_code); // Issuer decline code, if applicable
}Error types
type | Description |
|---|---|
api_error | Wajub API error |
authentication_error | Invalid token or key |
invalid_request_error | Incorrect SDK usage |
payment_error | Payment declined |
rate_limit_error | Too many attempts |
Common error codes
code | Context |
|---|---|
secret_key_in_browser | sk_... passed to the SDK |
missing_session | No sessionId provided |
load_error | Iframe/session issue |
payment_declined | Issuer declined |
session_terminal_expired | Session already completed |
fetchSession — preview before mounting
const preview = await wajub.fetchSession("YOUR_SESSION_TOKEN");
console.log(preview.amount, preview.currency); // 35000, "XOF"
console.log(preview.payment_methods); // Available methods
console.log(preview.environment); // "sandbox" | "live"
console.log(preview.features); // Feature flagsLocale, currency and layout
wajub.mount("#checkout", { sessionId, locale: "fr" });
checkout.update({ locale: "en" });
// Multi-currency display (when enabled on session)
checkout.update({ currency: "USD" });
// Layout options
checkout.update({ layout: "accordion" });Languages: fr, en, es, pt, ar
Layouts: classic, compact, tabs, accordion
Framework wrappers
React / Next.js — @wajub/react
npm install @wajub/react @wajub/jsimport { WajubProvider, CheckoutEmbed } from "@wajub/react";
function PayPage({ sessionId }: { sessionId: string }) {
return (
<WajubProvider>
<CheckoutEmbed
sessionId={sessionId}
onSuccess={() => (window.location.href = "/thanks")}
/>
</WajubProvider>
);
}Hooks: useWajub(), useConfirmPayment().
Vue 3 / Nuxt — @wajub/vue
npm install @wajub/vue @wajub/js<script setup lang="ts">
import { WajubProvider, CheckoutEmbed } from "@wajub/vue";
defineProps<{ sessionId: string }>();
</script>
<template>
<WajubProvider>
<CheckoutEmbed :session-id="sessionId" @success="() => (location.href = '/thanks')" />
</WajubProvider>
</template>Composables: useWajub(), useConfirmPayment().
Svelte / SvelteKit — @wajub/svelte
npm install @wajub/svelte @wajub/js<script lang="ts">
import { WajubProvider, CheckoutEmbed } from "@wajub/svelte";
export let sessionId: string;
</script>
<WajubProvider>
<CheckoutEmbed {sessionId} onSuccess={() => (location.href = "/thanks")} />
</WajubProvider>TypeScript
Types are included with @wajub/js — no extra setup:
import type {
CheckoutInstance, ComponentInstance, ComponentType,
AppearanceConfig, CheckoutState, WajubError,
EmbeddedConfig, PopupConfig, CreatePaymentParams,
ConfirmPaymentOptions,
} from "@wajub/js";With the CDN only (no npm), reference the types for your IDE:
/// <reference path="https://js.wajub.com/wajub-checkout.d.ts" />Production checklist
- Session created server-side with
sk_live_… - Only
authorization_tokenis exposed to the frontend - SDK loaded:
@wajub/js(npm) or<script src="https://js.wajub.com"> - Integration via
wajub.mount()/ framework wrapper — never a manual URL -
onSuccessredirects or updates the order -
onError/onLoadErrorhandled -
onBreakdownsyncs the cart total (inline mode) - Container has
min-heightduring loading (at least 480px) - HTTPS on the merchant site and callback URL
- Webhooks used for definitive server-side confirmation
- Live test with a small amount before going fully live