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.4.0
Runtime
Svelte 4+ · 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 declares a svelte export condition, so the bundler resolves the components directly.

npm install @wajub/svelte @wajub/js

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, leaving it to useLoadWajub()
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>
HelperReturnsNotes
useWajub(){ wajub, Wajub, WajubError }Throws when the runtime is unavailable
useWajubOptional()The same, or nullSafe during SSR and before the script lands
useLoadWajub(){ runtime, loading, error, loadWajub }The first three are stores. Drives the load yourself, with defer
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. The components are not.

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_SECRET_KEY } from '$env/static/private';
import { Wajub } from '@wajub/node';
import type { RequestHandler } from './$types';

const wajub = new Wajub({ secretKey: WAJUB_SECRET_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}`,
      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

WajubContextValue is the only type the package declares.

What did you think of this content?