Skip to content

Svelte

Provider, stores, embed props, and the SvelteKit wiring that goes with them.

@wajub/svelte wraps @wajub/js in Svelte syntax: a provider that loads the runtime once, five field components, and four helpers built on stores. The payment API itself does not change, so the behaviour of every option is documented on Hosted checkout and Payment fields. This page covers what is specific to Svelte.

@wajub/svelte

Stable · GA

npm

Version
1.5.1
Runtime
Svelte 4 and 5 · SvelteKit

Covers

  • Payments
  • Payment Links

Not in this package: Billing, Transfers, Sync, Shield, Tax.

Install

@wajub/js is a peer dependency, not a bundled one, so it goes on the same line. The package is ESM only and ships its components as source, under a svelte export condition: your project's own Svelte compiles them, so the same package serves Svelte 4 and Svelte 5.

npm install @wajub/svelte @wajub/js

The components are written without runes, which both versions compile: Svelte 5 compiles them in legacy mode, file by file. A Svelte 5 project that forces runes on every file stops its build at Cannot use export let in runes mode, so force them on your own files only, as npx sv create does.

Runes for your files, not for node_modules
compilerOptions: {
  runes: ({ filename }) =>
    filename.split(/[/\\]/).includes('node_modules') ? undefined : true,
},

In a component written with runes, listen to the field components with on:ready and on:instance as below: they dispatch Svelte events, not callback props.

The provider

WajubProvider calls loadWajub() from @wajub/js/pure and puts the runtime in Svelte context. One provider covers a whole subtree, so it belongs at the layout level rather than around each component.

src/routes/checkout/+layout.svelte
<script lang="ts">
  import { WajubProvider } from '@wajub/svelte';
</script>

<WajubProvider>
  <slot />
</WajubProvider>
PropTypeDefaultWhat it does
deferbooleanfalseSkips the automatic load. Leave it off: in 1.5.1 nothing loads the runtime into the provider afterwards
loadOptions{ jsOrigin?, jsUrl? }CDN defaultsOverrides where the runtime is fetched from

Stores and helpers

Everything reactive here is a Svelte store, so read it with $ in the template.

The safe form
<script lang="ts">
  import { useWajubOptional } from '@wajub/svelte';

  export let sessionId: string;
  const runtime = useWajubOptional();
</script>

<button
  disabled={!$runtime}
  on:click={() => $runtime?.wajub.open({ sessionId, onSuccess: onUpgraded })}
>
  Upgrade plan
</button>

The store is what makes this button work: it holds null when the component is created, because the provider only starts loading once it is mounted, and it fills when the runtime arrives. Read it with $, like every other store on this page.

HelperReturnsNotes
useWajub(){ wajub, Wajub, WajubError }Throws when the runtime is unavailable
useWajubOptional()A read-only store of the same, null until the runtime has loadedSafe during SSR and before the script lands
useLoadWajub(){ runtime, loading, error, loadWajub }The first three are stores. Its loadWajub() does not update the provider
useConfirmPayment(sessionId){ confirm, isConfirming }isConfirming is a writable store. Submits a mounted field component

useWajubContext() returns the same { runtime, loading, error } stores if you would rather build your own helper on top. It is an alias of getWajubContext(), and setWajubContext() is what the provider itself calls.

CheckoutEmbed

The hosted checkout inside your own layout. Every EmbeddedConfig option is a prop, and so is every callback.

src/routes/checkout/+page.svelte
<script lang="ts">
  import { CheckoutEmbed } from '@wajub/svelte';

  export let sessionId: string;
</script>

{#key sessionId}
  <CheckoutEmbed
    {sessionId}
    layout="tabs"
    minHeight={480}
    onSuccess={() => (window.location.href = '/order/complete')}
    onError={(error) => console.error(error.code, error.message)}
  />
{/key}

Wrapping in {#key sessionId} is what rebuilds the embed for a second session, and keeping the instance from onReady is what lets you restyle it without rebuilding.

Switching the theme after mount
<script lang="ts">
  import type { CheckoutInstance } from '@wajub/js';
  import { CheckoutEmbed } from '@wajub/svelte';

  export let sessionId: string;
  export let dark = false;

  let checkout: CheckoutInstance | null = null;

  $: checkout?.update({ appearance: { colorScheme: dark ? 'dark' : 'light' } });
</script>

{#key sessionId}
  <CheckoutEmbed {sessionId} onReady={(instance) => (checkout = instance)} />
{/key}

Payment fields

Five components render one field group each: CardComponent, MobileMoneyComponent, WalletComponent, PaymentComponent and AddressComponent. They share the props of ComponentConfig, and each one is ComponentEmbed with its type already set. Use ComponentEmbed directly when the choice is made at runtime.

Your own layout, your own button
<script lang="ts">
  import type { ComponentInstance } from '@wajub/js';
  import { AddressComponent, PaymentComponent, useConfirmPayment } from '@wajub/svelte';

  export let sessionId: string;

  const { confirm, isConfirming } = useConfirmPayment(sessionId);
  let field: ComponentInstance | null = null;
</script>

<AddressComponent {sessionId} addressMode="shipping" />
<PaymentComponent
  {sessionId}
  collectAddress="shipping"
  collectName
  on:instance={(event) => (field = event.detail)}
/>
<button disabled={$isConfirming || !field} on:click={() => field && confirm(field)}>
  {$isConfirming ? 'Processing…' : 'Pay now'}
</button>
EventDetailWhen
on:readyComponentInstanceThe field is mounted and interactive
on:instanceComponentInstance or nullOn mount, and again with null on unmount

useConfirmPayment guards against a double submission, so a second click while $isConfirming is true is ignored. It resolves with { status } and rejects with a WajubError, including confirmation_timeout after 60 seconds.

Types

Every helper is fully typed, and so is everything @wajub/js exports. From 1.6.0 the components are too: each one ships a declaration generated from its props, read by your editor and by svelte-check in Svelte 4 and Svelte 5 alike.

ComponentWhat its types check
CheckoutEmbed, ComponentEmbedEvery prop: a misspelled name, a missing sessionId or a wrong value is an error
CardComponent, MobileMoneyComponent, WalletComponent, PaymentComponent, AddressComponentsessionId, minHeight and the two events. Every other prop passes through to ComponentEmbed untyped
Typing the config yourself
import type { AppearanceConfig, CheckoutLayout } from '@wajub/js';

const appearance: AppearanceConfig = { primaryColor: '#0f172a', labels: 'floating' };
const layout: CheckoutLayout = 'tabs';

SvelteKit

The session is created on your server. Nothing in this package needs a secret key, and nothing should ever receive one.

src/routes/api/checkout/session/+server.ts
import { json } from '@sveltejs/kit';
import { WAJUB_API_KEY } from '$env/static/private';
import { Wajub } from '@wajub/node';
import type { RequestHandler } from './$types';

const wajub = new Wajub({ secretKey: WAJUB_API_KEY });

export const POST: RequestHandler = async ({ request }) => {
  const { cartId } = await request.json();

  const payment = await wajub.payments.create(
    {
      amount: 25000,
      currency: 'XAF',
      description: `Cart ${cartId}`,
      customer: { email: '[email protected]' },
      metadata: { cart_id: cartId },
    },
    { idempotencyKey: `cart-${cartId}` },
  );

  return json({ sessionId: payment.authorization_token });
};

Two more things SvelteKit asks for:

  • Turn off server rendering on checkout routes with export const ssr = false in +page.ts, or mount the embed inside an {#if browser} block. The runtime needs a window.
  • Put WajubProvider in +layout.svelte so a navigation inside the checkout flow does not reload the runtime.

What the package exports

ExportWhat it is
WajubProviderLoads the runtime once for the subtree
CheckoutEmbedThe hosted checkout, inline
CardComponent, MobileMoneyComponent, WalletComponent, PaymentComponent, AddressComponentOne field group each
ComponentEmbedThe same, with the type given as a prop
useWajub, useWajubOptional, useLoadWajubReach the runtime
useConfirmPaymentSubmit a field component
getWajubContext, setWajubContext, useWajubContextThe raw context, for your own helper

The package declares two types: WajubContextValue and, from 1.6.0, WajubRuntimeStore, which is what useWajubOptional() returns.

What did you think of this content?