Hosted checkout
Three ways to show the Wajub payment page, and the options behind each.
Hosted checkout is the whole payment page rendered by Wajub: method selection, order summary, OTP, 3DS, coupons, shipping. You choose where it appears. The configuration is the same in all three cases, so the only real decision is inline, overlay or redirect.
Looking for the product overview instead?
This page is the integration surface. For what the hosted page does for the payer, and when to prefer it over your own layout, see Payments, hosted checkout.
Which one
| Mode | Call | The payer | Pick it when |
|---|---|---|---|
| Inline | mount(el, config) | Stays on your page | The checkout is the page |
| Overlay | open(config) | Stays on your page, in a modal | The checkout interrupts something else |
| Redirect | checkout(config) | Leaves for pay.wajub.com | You want the least front end code |
All three need a sessionId, which is the authorization_token from
POST /payments. None of them needs a key.
Inline
mount takes a selector or an element, and returns the instance you drive afterwards.
import { mount } from '@wajub/js';
const checkout = await mount('#checkout', {
sessionId,
onSuccess: () => (window.location.href = '/order/complete'),
onError: (error) => showRetry(error),
});You do not need to reserve the height
The SDK gives both its loading placeholder and its iframe min-height: 480px, and it sets the
real height itself every time the checkout grows. An empty <div id="checkout"></div> is enough.
Two things throw immediately rather than failing later: a missing sessionId, and a selector that
matches nothing. Both raise a plain Error at call time, before any network request.
Configuration
Every option below is also a prop on CheckoutEmbed in React,
Vue and Svelte.
| Option | Type | Default | What it does |
|---|---|---|---|
sessionId | string | Required | The authorization_token from your server |
locale | string | Session default | fr, en, es, pt, ar |
layout | CheckoutLayout | Session default | classic, compact, tabs, accordion |
appearance | AppearanceConfig | Session branding | Appearance |
embedOrigin | string | Current page origin | Your canonical origin, for multi domain shops |
loadingText | string | Empty | Text inside the placeholder |
showLoading | boolean | true | Set false to render your own skeleton |
theme | EmbedTheme | Derived | Deprecated, the SDK fills it from appearance |
Callbacks
await mount('#checkout', {
sessionId,
onReady: (instance) => (checkoutRef = instance),
onSuccess: (transaction) => markPaidInTheInterface(transaction),
onError: (error) => console.error(error.code, error.message),
onLoadError: (error) => showSessionExpired(error),
onBreakdown: (b) => (total.textContent = `${b.total.toLocaleString()} ${b.currency}`),
});| Callback | Fires when | Receives |
|---|---|---|
onReady | The session loaded and the form is visible | CheckoutInstance |
onSuccess | The payment succeeded | The transaction object |
onError | The payment failed | WajubError |
onLoadError | The token is invalid, terminal, or the iframe refused to load | WajubError |
onCancel | The payer cancelled explicitly | Nothing |
onExpired | The session expired under the payer | Nothing |
onBreakdown | The totals changed, a coupon or a shipping choice | { subtotal, discount, tax, total, currency } |
onStateChange | The checkout moved to another state | { state, method } |
onMethodChange | The payer picked another method | { methodId, method_id } |
onResize | The iframe height changed, inline only | height in pixels |
onClose | The overlay was dismissed, overlay only | Nothing |
onResize is informational
The SDK already applies the new height to its own iframe. Use this callback to move something else on the page, a sticky summary or a footer, not to size the checkout.
onSuccess fires in a browser, so treat it as an interface signal. The order ships on the
webhook.
Driving the instance
checkout.getState(); // 'COLLECTING_DETAILS', 'PROCESSING', …
checkout.update({ locale: 'en' });
checkout.update({ appearance: { colorScheme: 'dark' } });
checkout.update({ currency: 'USD' }); // multi currency sessions only
checkout.selectMethod('pm_momo');
checkout.submit();
checkout.retry();
checkout.cancel();
checkout.destroy();update({ layout }) is the one that does nothing
locale, appearance and currency are sent to the checkout and applied live. layout is
only stored in the local config, so the payment methods keep the arrangement they were mounted
with. Change the layout by remounting, or set it on the session.
Overlay
Same configuration, same callbacks, same instance, plus the modal itself.
import { open } from '@wajub/js';
const popup = await open({
sessionId,
width: 960,
closeOnOverlay: true,
onSuccess: (transaction) => fulfillOrder(transaction),
onClose: () => console.log('dismissed'),
});
popup.isOpen();| Option | Default | What it does |
|---|---|---|
width | 920 | Maximum modal width in pixels |
height | 680 | Modal height in pixels |
closeOnOverlay | false | Close when the backdrop is clicked |
closeOnEscape | true | Close on the Escape key |
closeOnSuccess | true | Close shortly after onSuccess has run |
closeOnCancel | true | Close shortly after onCancel has run |
closeOnExpired | true | Close shortly after onExpired has run |
Closing the modal dismisses the interface only. The session stays open until it completes, expires, or is cancelled inside the checkout, so the same token still mounts.
onResize never fires in overlay mode, because the modal has a fixed height.
Redirect
The least code, and the only mode where callback matters.
// The URL your server already received from POST /payments
window.location.href = authorizationUrl;
// Or, when the browser only has the token
import { checkout } from '@wajub/js';
await checkout({ sessionId });The payer comes back to the callback URL you set when creating the payment, with
?status=complete|cancelled|failed|expired. That query string is a hint. The
webhook is the confirmation. See
Sessions and security for the full contract.
Styling
The checkout arrives already wearing your account branding, set once in the Dashboard. Pass
appearance only to change something for this one embed, a dark mode toggle or a campaign colour.
await mount('#checkout', {
sessionId,
layout: 'tabs',
appearance: { theme: 'night', primaryColor: '#6366f1', labels: 'floating' },
});The full key list is on Appearance, and the account wide defaults are on Branding and theming.