Skip to content

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

ModeCallThe payerPick it when
Inlinemount(el, config)Stays on your pageThe checkout is the page
Overlayopen(config)Stays on your page, in a modalThe checkout interrupts something else
Redirectcheckout(config)Leaves for pay.wajub.comYou 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.

The smallest version that works
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.

OptionTypeDefaultWhat it does
sessionIdstringRequiredThe authorization_token from your server
localestringSession defaultfr, en, es, pt, ar
layoutCheckoutLayoutSession defaultclassic, compact, tabs, accordion
appearanceAppearanceConfigSession brandingAppearance
embedOriginstringCurrent page originYour canonical origin, for multi domain shops
loadingTextstringEmptyText inside the placeholder
showLoadingbooleantrueSet false to render your own skeleton
themeEmbedThemeDerivedDeprecated, the SDK fills it from appearance

Callbacks

The ones worth wiring on day one
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}`),
});
CallbackFires whenReceives
onReadyThe session loaded and the form is visibleCheckoutInstance
onSuccessThe payment succeededThe transaction object
onErrorThe payment failedWajubError
onLoadErrorThe token is invalid, terminal, or the iframe refused to loadWajubError
onCancelThe payer cancelled explicitlyNothing
onExpiredThe session expired under the payerNothing
onBreakdownThe totals changed, a coupon or a shipping choice{ subtotal, discount, tax, total, currency }
onStateChangeThe checkout moved to another state{ state, method }
onMethodChangeThe payer picked another method{ methodId, method_id }
onResizeThe iframe height changed, inline onlyheight in pixels
onCloseThe overlay was dismissed, overlay onlyNothing

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

CheckoutInstance
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();

Overlay

Same configuration, same callbacks, same instance, plus the modal itself.

Pay without leaving the page
import { open } from '@wajub/js';

const popup = await open({
  sessionId,
  width: 960,
  closeOnOverlay: true,
  onSuccess: (transaction) => fulfillOrder(transaction),
  onClose: () => console.log('dismissed'),
});

popup.isOpen();
OptionDefaultWhat it does
width920Maximum modal width in pixels
height680Modal height in pixels
closeOnOverlayfalseClose when the backdrop is clicked
closeOnEscapetrueClose on the Escape key
closeOnSuccesstrueClose shortly after onSuccess has run
closeOnCanceltrueClose shortly after onCancel has run
closeOnExpiredtrueClose 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.

Two ways to leave
// 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.

Per embed override
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.

What did you think of this content?