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 · GAnpm
- 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/jsThe 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.
<script lang="ts">
import { WajubProvider } from '@wajub/svelte';
</script>
<WajubProvider>
<slot />
</WajubProvider>| Prop | Type | Default | What it does |
|---|---|---|---|
defer | boolean | false | Skips the automatic load, leaving it to useLoadWajub() |
loadOptions | { jsOrigin?, jsUrl? } | CDN defaults | Overrides where the runtime is fetched from |
Stores and helpers
Everything reactive here is a Svelte store, so read it with $ in the template.
useWajub throws when the runtime is not there yet
It throws outside a provider and while the script is still on its way, which is every render
before the load resolves. Use useWajubOptional(), which returns null instead, unless you
already know the runtime has landed.
<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>| Helper | Returns | Notes |
|---|---|---|
useWajub() | { wajub, Wajub, WajubError } | Throws when the runtime is unavailable |
useWajubOptional() | The same, or null | Safe 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.
<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}The embed and the fields do not use the same convention
CheckoutEmbed takes its callbacks as props: onSuccess, onError, onReady and the rest.
Field components dispatch Svelte events instead: on:ready and on:instance. Writing
on:success on the embed, or onInstance on a field, silently does nothing.
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.
<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.
<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>| Event | Detail | When |
|---|---|---|
on:ready | ComponentInstance | The field is mounted and interactive |
on:instance | ComponentInstance or null | On 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.
The components ship without prop types
All eight .d.ts files are a bare export { SvelteComponent as default } from 'svelte', and the
package exports no CheckoutEmbedProps or ComponentEmbedProps. Your editor will not complete a
prop, and svelte-check will not catch a misspelled one. Read the prop names off
Hosted checkout and
Payment fields, or import the config types from
@wajub/js and annotate your own variables.
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.
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 });
};$env/static/public is for the publishable key only
Anything imported from $env/static/public is inlined into the client bundle. pk. belongs
there, sk. never does, and $env/static/private refuses to be imported from client code for
exactly that reason. A secret key sent from a browser is answered 403 and its owner is emailed
about it.
Two more things SvelteKit asks for:
- Turn off server rendering on checkout routes with
export const ssr = falsein+page.ts, or mount the embed inside an{#if browser}block. The runtime needs awindow. - Put
WajubProviderin+layout.svelteso a navigation inside the checkout flow does not reload the runtime.
What the package exports
| Export | What it is |
|---|---|
WajubProvider | Loads the runtime once for the subtree |
CheckoutEmbed | The hosted checkout, inline |
CardComponent, MobileMoneyComponent, WalletComponent, PaymentComponent, AddressComponent | One field group each |
ComponentEmbed | The same, with the type given as a prop |
useWajub, useWajubOptional, useLoadWajub | Reach the runtime |
useConfirmPayment | Submit a field component |
getWajubContext, setWajubContext, useWajubContext | The raw context, for your own helper |
WajubContextValue is the only type the package declares.