Display the 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 and 3DS, shown inline, in an overlay or after a redirect with the same configuration. Coupons and the shipping step are not available on it, whatever the session carries.
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
Inline with mount(el, config), in a modal with open(config), or on the hosted payment page with
checkout(config). The four modes, payment fields included, are compared side by side on the
overview.
All three need a sessionId, which is the authorization_token from
POST /payments. None of them needs a key.
Inline and overlay frame the checkout in your page. Declare the origins that embed it in Settings → Checkout embedding before you take live payments: once the list is filled, only those origins can show it. The page is on every plan. See where the checkout may be embedded.
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 checkout sizes itself. An empty <div id="checkout"></div> is enough.
A missing sessionId or a selector that matches nothing fails at once with a plain Error:
window.wajub throws it, and the @wajub/js helpers reject with it.
Configuration
| Option | Type | Default | What it does |
|---|---|---|---|
sessionId | string | Required | The authorization_token from your server |
locale | string | Session default | The language the checkout opens in: fr, en, es, pt, ar |
layout | CheckoutLayout | Session default | classic, compact, tabs, accordion |
appearance | AppearanceConfig | Your Dashboard branding | Appearance |
loadingText | string | Empty | Text inside the placeholder |
showLoading | boolean | true | Set false to render your own skeleton |
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 | PaymentSuccess: id, reference, amount in major units… |
onError | The payment failed, or submit() had no button to press (nothing_to_submit) | WajubError |
onLoadError | The checkout could not start: an unknown token, a token that has stopped, or an iframe that refused to load | WajubError |
onCancel | The payer cancelled explicitly | Nothing |
onExpired | The session expired under the payer | Nothing |
onBreakdown | The totals changed | { subtotal, discount, tax, total, currency }, in major units |
onStateChange | The checkout moved to another state | { state, method } |
onMethodChange | The payer picked another method | { methodId } |
onResize | The iframe height changed, inline only | height in pixels |
onClose | The overlay was dismissed, or the payer pressed Done on an inline confirmation, or you called close() | Nothing |
onLoadError carries session_not_found for a token Wajub does not know,
session_terminal_expired for one whose payment ended and whose token has stopped, and
load_error when the iframe itself failed. onError receives none of them: it is for a payment
that failed. Right after the callback, the overlay closes or the inline checkout leaves the page. onClose is not called: the checkout never started, so nothing was
closed. Show your own message from the callback.
onResize is informational
Use this callback to move something else on the page, a sticky summary or a footer.
onSuccess fires in a browser, so treat it as an interface signal. The order ships on the
webhook. It receives the checkout's summary of the payment:
id is Wajub's id of the payment, the one GET /payments/{id} takes, reference is yours, and
amount is in major units like the API.
Inline, the confirmation keeps a Done button. When the payer presses it, onClose fires and
the checkout leaves the page, so put your own next step there.
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(); // presses the checkout's own pay button
checkout.retry();
checkout.cancel();
checkout.unmount(); // off the page, the instance is kept
checkout.mount(); // back on the page
checkout.destroy();locale, appearance, currency and layout apply live, inline and in the overlay.
appearance.inherit gives the checkout your page's own look: mount() reads it around its
container, open() over the whole page, under the keys you set. See
Taking your page's look.
update({ appearance }) then changes the keys you give and keeps the others: a key given as
undefined or null is cleared, and appearance: undefined clears it all, back to your Dashboard
branding. With inherit, each call reads the page again. A new theme preset replaces the previous
one. See Changing it after mount.
submit() presses the pay button of the method the payer is on. The checkout validates the form
as it does for the payer and shows its own messages, so follow the result with onStateChange.
When there is no button to press (before a method is chosen, on an OTP or confirmation step, on
Apple Pay and Google Pay, which only answer the payer's own tap, and while a payment is already on
its way), nothing is pressed and onError receives a WajubError with the code
nothing_to_submit.
unmount() takes the checkout off the page and keeps the instance, and mount() puts it back,
inline and in the overlay alike. When it returns, onReady fires once more, with the locale,
appearance and layout last given to update(). On an
overlay, unmount() does not call onClose. After destroy(), or close() on an overlay,
mount() does nothing.
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=succeeded|cancelled|failed|expired, reference, Wajub's id of the payment, and
trxref, your own reference, whatever the outcome. Read the payment with that reference on your
server: the query string is a hint. The webhook is the
confirmation. See Sessions and security for the
details.
Through a framework
In @wajub/react, @wajub/vue and @wajub/svelte, the inline checkout is the CheckoutEmbed
component, and in @wajub/angular it is <wajub-checkout-embed>. Its options are props, inputs in
Angular, beside the component's own: the session, a class, a style in React and Vue, on the host
element in Angular, and the height kept while the checkout loads, 480 pixels by default. React and
Svelte take every option of EmbeddedConfig;
Vue and Angular take a closed list of them, given on Vue
and Angular. In Angular, the callbacks are outputs.
The embed follows its props. A change to appearance, appearance.inherit included, locale or
layout applies to the mounted checkout without a reload, so an appearance
object written inline is fine. A top-level appearance key removed since the last render is
cleared, and an appearance that becomes undefined or {} goes back to your branding. A new
session loads a new checkout in the same container. loadingText and showLoading are read when
the checkout loads, and a new handler takes over without a reload.
// A new value applies without a reload.
<CheckoutEmbed
sessionId={sessionId}
appearance={{ colorScheme: dark ? 'dark' : 'light' }}
/>;The provider, the exact props and the server side wiring are on React, Vue, Svelte and Angular.
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.