Svelte
Provider, stores, props d'intégration et configuration SvelteKit associée.
@wajub/svelte vous donne un provider, un CheckoutEmbed, sept composants de champs et quatre
helpers basés sur des stores. Le rôle de chaque option est présenté dans
Afficher le checkout et
Champs de paiement. Cette page traite des éléments propres à Svelte.
@wajub/svelte
Stable · GAnpm
- Version
- 1.8.1
- Runtime
- Svelte 4 et 5 · SvelteKit
Couvre
- Paiements
- Liens de paiement
Absent de ce paquet : Facturation, Transferts, Sync, Shield, Taxes.
Installer
@wajub/js est une dépendance homologue. Installez-la donc sur la même ligne. Le package est
uniquement ESM et fonctionne avec Svelte 4 et Svelte 5.
npm install @wajub/svelte @wajub/jsUn projet Svelte 5 qui impose les runes à tous les fichiers voit sa compilation s'arrêter sur
Cannot use export let in runes mode. Imposez-les donc à vos seuls fichiers, comme le fait
npx sv create.
compilerOptions: {
runes: ({ filename }) =>
filename.split(/[/\\]/).includes('node_modules') ? undefined : true,
},Dans un composant écrit avec les runes, passez les handlers comme props : les composants de champs
reçoivent onReady, onInstance, onStateChange et chaque autre handler comme prop. Leurs
événements Svelte, on:ready, on:instance et les autres, restent là pour le balisage écrit sans
runes.
Provider
Un seul WajubProvider suffit autour du checkout : il couvre toute une sous-arborescence. Placez-le
donc au niveau de la mise en page, pas autour de chaque composant.
<script lang="ts">
import { WajubProvider } from '@wajub/svelte';
</script>
<WajubProvider>
<slot />
</WajubProvider>Stores et helpers
Tous les éléments réactifs sont des stores Svelte. Lisez-les donc avec $ dans le template.
useWajub génère une erreur pendant le chargement de Wajub.js
Il génère une erreur hors d'un provider et tant que Wajub.js n'est pas chargé. Utilisez
useWajubOptional(), qui renvoie un store valant null tant que l'environnement n'est pas
chargé, sauf si vous savez qu'il l'est déjà.
<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>Le store vaut null à la création du composant et se remplit une fois Wajub.js chargé. Lisez-le
avec $, comme les autres stores de cette page.
| Helper | Valeur renvoyée | Remarques |
|---|---|---|
useWajub() | { wajub, Wajub, WajubError } | Génère une erreur si l'environnement est indisponible |
useWajubOptional() | Un store en lecture seule de la même valeur, null tant que l'environnement n'est pas chargé | Sûr pendant le SSR et pendant le chargement |
useLoadWajub() | { runtime, loading, error, loadWajub } | Les trois premiers sont des stores. loading et error permettent d'afficher un indicateur de chargement ou une nouvelle tentative |
useConfirmPayment(sessionId) | { confirm, isConfirming } | isConfirming est un store. Envoie un composant de champ monté. sessionId est une chaîne, un store ou un getter |
CheckoutEmbed
Le checkout hébergé dans votre propre mise en page. Chaque option de EmbeddedConfig est une prop, comme chaque callback.
<script lang="ts">
import { CheckoutEmbed } from '@wajub/svelte';
export let sessionId: string;
</script>
<CheckoutEmbed
{sessionId}
layout="tabs"
minHeight={480}
onSuccess={() => (window.location.href = '/order/complete')}
onError={(error) => console.error(error.code, error.message)}
/>Les handlers sont des props, sur l'intégration comme sur les champs
CheckoutEmbed reçoit ses callbacks comme props : onSuccess, onError, onReady et les
autres, et n'émet aucun événement Svelte, donc on:success n'y a aucun effet. Les composants de
champs reçoivent eux aussi chaque handler comme prop, onInstance et onReady compris, et émettent
aussi on:ready, on:instance, on:statechange, on:resize et on:redirect.
Les props peuvent changer après le montage, et un nouveau sessionId charge un nouveau checkout,
sans {#key sessionId}.
Champs de paiement
Les sept composants affichent chacun un groupe de champs : CardComponent, MobileMoneyComponent,
WalletComponent, PaymentComponent, AddressComponent, BankTransferComponent et
CryptoComponent. Chaque option et chaque callback de
ComponentConfig est une prop, plus les props ci-dessous, et
chacun est un ComponentEmbed avec son type déjà défini. Utilisez directement ComponentEmbed
lorsque le choix intervient pendant l'exécution.
<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>| Prop | Événement Svelte | Valeur par défaut | Fonction |
|---|---|---|---|
type | Définie par l'alias | Uniquement sur ComponentEmbed | |
loader | 'skeleton' | 'skeleton' : le conteneur garde la hauteur du formulaire dès le premier rendu, avec un squelette dans le style de vos champs. 'none' : aucun squelette | |
minHeight | Non définie | Hauteur minimale du conteneur en pixels | |
readyTimeout | Non définie | Millisecondes d'attente du formulaire. Au-delà, onLoadError reçoit une WajubError de code ready_timeout. Non définie, aucune limite | |
onInstance | on:instance | aucune | L'instance une fois montée, null une fois détruite |
onReady | on:ready | aucune | Le champ est monté et interactif |
onStateChange | on:statechange | aucune | { state, method } : le paiement passe à une autre étape, par exemple APPROVAL_REQUIRED pendant que le payeur valide sur son téléphone |
onResize | on:resize | aucune | { height } : le champ prend une nouvelle hauteur, en pixels |
onRedirect | on:redirect | aucune | { url, method } : le paiement continue sur la page du prestataire, et votre page va s'y rendre |
onBeforeSubmit | aucune | S'exécute avant le débit, pour passer votre commande : false, ou une erreur, ne débite rien. Définissez-le dès le premier rendu du champ |
Un événement Svelte porte sa donnée dans event.detail. Les autres handlers, onChange,
onSuccess, onError, onLoadError et les autres, sont seulement des props.
isConfirming est un store : lisez-le sous la forme $isConfirming. useConfirmPayment prend la
session comme une chaîne, un store ou un getter tel que () => sessionId (le type
SessionIdSource).
Types
Chaque helper et chaque composant est typé, comme tous les exports de @wajub/js, pour votre
éditeur et svelte-check en Svelte 4 comme en Svelte 5 : un nom de prop mal orthographié, un
sessionId manquant ou une valeur erronée est une erreur. Les composants de champs acceptent chaque
prop de ComponentEmbed sauf type, le type TypedComponentProps.
Le package exporte ComponentEmbedProps, TypedComponentProps et SessionIdSource, la session que
prend useConfirmPayment(). Il n'exporte aucun CheckoutEmbedProps.
Mobile Money et carte dans votre propre checkout
Votre checkout garde son formulaire, son choix de moyen de paiement et son bouton Payer, et chaque
moyen n'affiche que ses champs, dans le style de votre page. Les étapes, les appels serveur et la
raison de chaque option sont dans
Champs de paiement.
Ici, votre serveur a préparé le paiement avec deferred: true à l'ouverture du checkout, et a
transmis à la page son authorization_token et son authorization_url.
<script lang="ts">
import {
CardComponent,
MobileMoneyComponent,
useConfirmPayment,
type ComponentInstance,
type ComponentStateChangeEvent,
} from '@wajub/svelte';
export let sessionId: string; // l'authorization_token du paiement préparé, transmis par votre serveur
export let hostedUrl: string; // son authorization_url, pour la solution de repli
// Le paiement est en cours, ou attend le payeur : rien ne doit changer sur la page.
const RUNNING = [
'PROCESSING', 'VERIFYING', 'PENDING', 'OTP_REQUIRED', 'PIN_REQUIRED', 'PHONE_REQUIRED',
'BIRTHDAY_REQUIRED', 'ADDRESS_REQUIRED', '3DS_REQUIRED', 'USSD_REQUIRED', 'APPROVAL_REQUIRED',
];
let method: 'mobileMoney' | 'card' = 'mobileMoney';
let component: ComponentInstance | null = null;
let complete = false;
let step = 'INITIATED';
let fallback = false;
const { confirm, isConfirming } = useConfirmPayment(() => sessionId);
$: locked = $isConfirming || RUNNING.includes(step);
// Votre serveur vérifie le panier, passe la commande et active le paiement.
async function placeOrder() {
const response = await fetch('/checkout/place-order', { method: 'POST' });
return response.ok; // false : rien n'est débité
}
function onInstance(instance: ComponentInstance | null) {
component = instance;
complete = false;
}
function onStateChange(event: ComponentStateChangeEvent) {
step = event.state;
}
function onLoadError(error: { code?: unknown }) {
if (error.code === 'ready_timeout') fallback = true;
}
async function pay() {
if (fallback) {
// Un paiement préparé n'est débité qu'une fois activé : passez d'abord la commande.
if (await placeOrder()) window.location.assign(hostedUrl);
return;
}
if (!component) return;
try {
await confirm(component);
window.location.assign('/thanks');
} catch {
// Le formulaire affiche la raison, et le payeur peut y réessayer.
}
}
</script>
<fieldset disabled={locked}>
<label><input type="radio" bind:group={method} value="mobileMoney" /> Mobile Money</label>
<label><input type="radio" bind:group={method} value="card" /> Carte</label>
</fieldset>
{#if fallback}
<p>Vous finirez sur la page de paiement sécurisée de Wajub.</p>
{:else}
<!-- loader="skeleton" par défaut : le bloc garde sa hauteur pendant le chargement. -->
<svelte:component
this={method === 'card' ? CardComponent : MobileMoneyComponent}
{sessionId}
variant="fields"
appearance={{ inherit: true }}
readyTimeout={10000}
fields={method === 'card'
? { customer: 'external' }
: { customer: 'external', country: 'CM', operator: 'detect' }}
onBeforeSubmit={placeOrder}
onChange={(event) => (complete = Boolean(event.complete))}
{onInstance}
{onStateChange}
{onLoadError}
/>
{/if}
{#if step === 'APPROVAL_REQUIRED'}<p role="status">Validez le paiement sur votre téléphone.</p>{/if}
<button type="button" disabled={locked || (!fallback && !complete)} on:click={pay}>Payer</button>Les champs affichent eux-mêmes un paiement refusé, et le payeur y réessaie. S'ils ne s'affichent
pas dans le délai readyTimeout, le bouton Payer passe la commande puis envoie le payeur sur la
page de Wajub : un paiement préparé n'est débité qu'une fois activé. appearance.inherit lit un
champ texte de votre page, son libellé, votre bouton principal et le fond derrière les champs.
Affichez donc les champs de votre propre formulaire avant cette étape, ou nommez-les avec
appearance={{ inherit: { input: '#email', button: '#pay' } }}. Marquez la commande payée sur
votre serveur, à partir du webhook payment.succeeded, jamais depuis le navigateur.
SvelteKit
La session est créée sur votre serveur. Aucun élément de ce package ne nécessite ni ne doit recevoir de clé secrète.
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: 'buyer@example.com' },
metadata: { cart_id: cartId },
},
{ idempotencyKey: `cart-${cartId}` },
);
return json({ sessionId: payment.authorization_token });
};$env/static/public est réservé à la clé publique
Tout élément importé depuis $env/static/public est intégré au bundle client. pk. peut y figurer,
jamais sk.. Pour cette raison, $env/static/private refuse toute importation depuis le code client.
Une clé secrète envoyée depuis un navigateur reçoit une réponse 403 et son propriétaire est alerté par e-mail.
SvelteKit impose deux autres règles :
export const ssr = falsen'est pas nécessaire : les composants s'affichent sur le serveur, et les champs se montent dans le navigateur.- Placez
WajubProviderdans+layout.svelte, pour qu'il reste monté pendant que le payeur avance dans le flux de checkout.
Éléments exportés par le package
Les exports sont listés dans la Référence du SDK, les types que le package ajoute dans TypeScript.
Pages associées