Svelte
Provider, stores, props d'intégration et configuration SvelteKit associée.
@wajub/svelte enveloppe @wajub/js avec la syntaxe Svelte : un provider qui charge une fois
l'environnement d'exécution, sept composants de champs et quatre helpers basés sur des stores.
L'API de paiement ne change pas. Le comportement 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, non intégrée. Installez-la donc sur la même ligne. Le package
est uniquement ESM et distribue ses composants en source, sous une condition d'export svelte : le Svelte
de votre projet les compile. Le même package sert donc Svelte 4 et Svelte 5.
npm install @wajub/svelte @wajub/jsInstallez la version 1.6.0 ou une version ultérieure
La version 1.5.1 embarque sa propre copie de l'environnement d'exécution de Svelte à côté de celle de votre projet, et génère une erreur au premier rendu. La version 1.6.0 laisse Svelte à votre projet, en version 4 ou 5.
Les composants sont écrits sans runes, ce que les deux versions compilent : Svelte 5 les compile en
mode legacy, fichier par fichier. Un 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 : depuis la 1.8.0, 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. Jusqu'à la 1.7.2, on:ready et on:instance n'étaient que des
événements.
Provider
WajubProvider appelle loadWajub() depuis @wajub/js/pure et place l'environnement d'exécution
dans le contexte Svelte. Un provider 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>| Prop | Type | Valeur par défaut | Fonction |
|---|---|---|---|
defer | boolean | false | Ignore le chargement automatique. Laissez-le désactivé : en 1.8.1, rien ne charge ensuite l'environnement d'exécution dans le provider |
loadOptions | { jsOrigin?, jsUrl? } | Valeurs du CDN | Remplace l'origine de chargement de l'environnement d'exécution |
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 tant que l'environnement n'est pas chargé
Il génère une erreur hors d'un provider et pendant le chargement du script, donc à chaque rendu
avant la fin du chargement. 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>C'est le store qui fait fonctionner ce bouton : il vaut null à la création du composant, car le
provider ne commence le chargement qu'une fois monté, puis il se remplit à l'arrivée de
l'environnement d'exécution. Lisez-le avec $, comme les autres stores de cette page.
Un store à partir de la version 1.6.0, une simple valeur avant
Jusqu'à la version 1.5.1, useWajubOptional() renvoyait la valeur lue une seule fois à la
création du composant, soit null avant le chargement. La version 1.6.0 renvoie le store. Le code
écrit pour l'ancienne forme qui appelle runtime.wajub fonctionne encore une fois l'environnement
chargé, mais !runtime n'est plus un test : un bouton écrit disabled={!runtime} n'est jamais
désactivé. Remplacez les deux par $runtime.
| 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 avant l'arrivée du script |
useLoadWajub() | { runtime, loading, error, loadWajub } | Les trois premiers sont des stores. Son loadWajub() ne met pas le provider à jour |
useConfirmPayment(sessionId) | { confirm, isConfirming } | isConfirming est un store modifiable. Envoie un composant de champ monté. Depuis la 1.8.0, sessionId est une chaîne, un store ou un getter, lu à chaque confirm() |
useWajubContext() renvoie les mêmes stores { runtime, loading, error } si vous préférez créer
votre propre helper. Il s'agit d'un alias de getWajubContext(). Le provider appelle lui-même setWajubContext().
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 : on:success y reste sans effet. Depuis la 1.8.0, 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. Jusqu'à la 1.7.2, un champ n'avait que les deux événements on:ready et
on:instance, et une prop onInstance restait sans effet.
Depuis la 1.8.0, un nouveau sessionId charge un nouveau checkout de lui-même : {#key sessionId}
n'est nécessaire que jusqu'à la 1.7.2.
Ce que les quatre wrappers ont en commun, depuis la 1.8.0 chaque prop qui atteint le checkout par
update() et une nouvelle session qui charge un nouveau checkout, est décrit dans
Afficher le checkout, avec cet
exemple dans chaque framework.
Champs de paiement
Les sept composants affichent chacun un groupe de champs : CardComponent, MobileMoneyComponent,
WalletComponent, PaymentComponent, AddressComponent et, depuis la 1.8.0,
BankTransferComponent et CryptoComponent. Ils partagent les props de
ComponentConfig, loader, readyTimeout et les callbacks
compris, 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, et le formulaire apparaît en fondu une fois prêt. 'none' : l'iframe telle qu'elle arrive, la valeur par défaut de @wajub/js lui-même. Depuis la 1.8.0 | |
minHeight | Non définie | Hauteur minimale du conteneur en pixels, gardée pendant toute la vie du composant. Non définie, voir ci-dessous | |
readyTimeout | Non définie | Millisecondes d'attente du formulaire. Au-delà, onLoadError reçoit une WajubError de code ready_timeout. Non définie, jamais. Depuis la 1.8.0 | |
onInstance | on:instance | aucune | L'instance une fois montée, null une fois détruite. La prop depuis la 1.8.0 |
onReady | on:ready | aucune | Le champ est monté et interactif. La prop depuis la 1.8.0 |
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. Depuis la 1.8.0 |
onResize | on:resize | aucune | { height } : l'iframe prend une nouvelle hauteur, en pixels. Depuis la 1.8.0 |
onRedirect | on:redirect | aucune | { url, method } : le paiement continue sur la page du prestataire, où Wajub.js emmène votre page juste après. Depuis la 1.8.0 |
onBeforeSubmit | aucune | S'exécute avant le débit, pour passer votre commande. Il compte s'il est défini au montage 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.
Sans minHeight, la place suit loader, de la même façon dans les quatre wrappers : ce que tient le
conteneur au rendu serveur, à l'hydratation et aux rendus suivants est décrit dans
Champs de paiement. Il finit à la
hauteur du formulaire : un bloc variant="fields" épouse ses champs sans minHeight={0}.
isConfirming est un store : lisez-le sous la forme $isConfirming. Depuis la 1.8.0,
useConfirmPayment prend la session comme une chaîne, un store ou un getter tel que
() => sessionId (le type SessionIdSource), lu à chaque confirm(). Une prop atteint le champ
quand son propre contenu change, cette option seule, et un objet modifié sur place puis réassigné
compte comme modifié. L'événement redirect a une prop et un événement Svelte depuis la 1.8.0,
onRedirect et on:redirect.
Ce que les composants de champs ont en commun, depuis la 1.8.0 chaque prop qui atteint le
formulaire par update() ou par un nouveau montage, l'événement redirect et ce dont
useConfirmPayment vous protège, est décrit dans
Champs de paiement.
Types
Chaque helper est entièrement typé, comme tous les exports de @wajub/js. Depuis la version 1.6.0,
les composants le sont aussi : chacun est livré avec une déclaration générée à partir de ses props, que
votre éditeur et svelte-check lisent en Svelte 4 comme en Svelte 5.
| Composant | Ce que ses types vérifient |
|---|---|
CheckoutEmbed, ComponentEmbed | Chaque prop : un nom mal orthographié, un sessionId manquant ou une valeur erronée est une erreur |
CardComponent, MobileMoneyComponent, WalletComponent, PaymentComponent, AddressComponent, BankTransferComponent, CryptoComponent | Depuis la 1.8.0, chaque prop de ComponentEmbed sauf type, le type TypedComponentProps. De la 1.6.0 à la 1.7.2, seulement sessionId, minHeight et les deux événements, les autres props passant sans type |
Depuis la 1.8.0, le package exporte ComponentEmbedProps, TypedComponentProps et
SessionIdSource, la session que prend useConfirmPayment(). Il n'exporte aucun
CheckoutEmbedProps.
La version 1.5.1 n'a pas de types de props
Ses huit fichiers .d.ts contiennent uniquement export { SvelteComponent as default } from 'svelte'.
Votre éditeur ne complète donc aucune prop et svelte-check ne détecte aucune faute de frappe. Dans
ce cas, consultez les noms dans Afficher le checkout et
Champs de paiement, ou importez les types de configuration
depuis @wajub/js pour annoter vos variables.
import type { AppearanceConfig, CheckoutLayout } from '@wajub/js';
const appearance: AppearanceConfig = { primaryColor: '#0f172a', labels: 'floating' };
const layout: CheckoutLayout = 'tabs';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 en 1.8.1. Les composants s'affichent sur le serveur, où rien ne litwindownidocument: un composant de champ affiche son conteneur, qui tient déjà la hauteur estimée du formulaire, la même à l'hydratation, et l'environnement d'exécution se charge et les champs se montent dans le navigateur.- Placez
WajubProviderdans+layout.sveltepour éviter qu'une navigation dans le flux de checkout recharge l'environnement d'exécution.
Éléments exportés par le package
Les exports sont les mêmes dans les packages React, Vue et Svelte et sont listés dans la Référence du SDK, les types qu'ils ajoutent dans TypeScript.
Pages associées