Node.js
Le SDK serveur @wajub/node, ses ressources et les types TypeScript inclus.
@wajub/node constitue la partie serveur d'une intégration Wajub. Il conserve votre clé secrète,
crée des paiements, lit leur statut réel et vérifie les signatures des webhooks. Il ne s'exécute jamais dans un navigateur.
@wajub/node
Stable · GAnpm
- Version
- 1.1.1
- Runtime
- Node.js 18+, Bun, Deno, any runtime with fetch
Couvre
- Paiements
- Facturation
- Transferts
- Sync
- Shield
- Taxes
Le package prend en charge ESM et CommonJS, fournit ses propres types et ne possède aucune dépendance
d'exécution. Il appelle fetch, ce qui explique le minimum Node 18 et permet au même build de
fonctionner sur Bun, Deno et un environnement edge.
Installer
npm install @wajub/nodeCréer le client une seule fois
Créez-le dans un module et importez ce module partout. Une seconde instance n'est pas dangereuse, mais crée inutilement un autre ensemble de connexions.
import { Wajub } from '@wajub/node';
if (!process.env.WAJUB_SECRET_KEY) {
throw new Error('WAJUB_SECRET_KEY is missing');
}
export const wajub = new Wajub({
secretKey: process.env.WAJUB_SECRET_KEY,
webhookSecret: process.env.WAJUB_WEBHOOK_SECRET,
});Ce SDK est le seul sans valeur de repli issue de l'environnement
Python, PHP, Go, Ruby, Java et C# lisent tous WAJUB_API_KEY dans l'environnement si vous ne
transmettez rien. @wajub/node ne le fait pas. Il génère Wajub: secretKey (or apiKey) is required.
Lisez donc vous-même la variable comme ci-dessus.
| Option | Fonction |
|---|---|
secretKey | Votre clé sk. ou sk_test.. apiKey est un alias accepté |
webhookSecret | Secret whsec_, uniquement nécessaire pour webhooks.constructEvent() |
fetchOptions | RequestInit fusionné avec chaque appel, pour un proxy ou un certificat de développement |
idempotencyKeyPrefix | Préfixe de la valeur Idempotency-Key générée. wajub par défaut |
timeout | Millisecondes par requête. 30000 par défaut, modifiable pour chaque appel |
Il n'existe aucune option baseUrl. Le SDK cible https://api.wajub.com et lit WAJUB_API_URL
pour permettre à une stack locale d'utiliser une autre adresse. C'est le seul SDK Wajub qui respecte
cette variable. Les six autres conservent l'URL comme constante.
Premier appel
import { wajub } from '@/lib/wajub';
const payment = await wajub.payments.create({
amount: 25000,
currency: 'XAF',
email: 'amina@example.com',
description: 'Order 4172',
reference: 'order-4172',
callback: 'https://shop.example.com/complete',
});
// payment.authorization_url is where the customer pays.
// payment.authorization_token is what the browser SDK mounts.Les champs de réponse conservent le snake_case de l'API. authorization_url et created_at
s'écrivent donc comme dans la référence API. Seuls les noms de méthodes utilisent le camelCase.
Les montants utilisent l'unité principale
25000 en XAF représente vingt-cinq mille francs. Une devise décimale utilise une valeur
décimale : amount: 12.5 en GHS.
Toutes les ressources du client
| Getter | Méthodes |
|---|---|
| wajub.global | ping, channels, countries, currencies |
| wajub.payments | create, initialize, retrieve, list, cancel, process, processSplit, listRefunds |
| wajub.customers | create, retrieve, update, delete, list, block, unblock, activate, deactivate, listTaxIds, createTaxId, deleteTaxId |
| wajub.refunds | create, retrieve, list |
| wajub.transfers | create, retrieve, list |
| wajub.beneficiaries | create, retrieve, update, delete, list |
| wajub.links | create, retrieve, update, delete, list |
| wajub.invoices | create, retrieve, update, delete, list, send, markPaid, cancel |
| wajub.accounts | create, retrieve, update, delete, list, regenerateToken |
| wajub.webhookEndpoints | create, retrieve, update, delete, list, rotateSecret |
| wajub.balance | retrieve |
| wajub.events | list, retrieve, resend |
| wajub.disputes | list, retrieve, submitEvidence, accept, close, sendMessage |
| wajub.identity | resolve, validate |
| wajub.tax | getSettings, updateSettings, rates, calculate, reports, listCodes, retrieveCode, listRegistrations, createRegistration, retrieveRegistration, updateRegistration, deleteRegistration, jurisdictions, thresholds, thresholdAlerts |
| wajub.shield | getSettings, updateSettings, stats, listBlocklist, addToBlocklist, removeFromBlocklist |
| wajub.listen | config, auth |
| wajub.webhooks | constructEvent |
webhookssignature verification runs locally, no HTTP call. All other resources call the merchant REST API.links,invoices,taxandshieldare live only. A sandbox key gets403 This feature is only available in live mode.on every one of their methods.refundsandtransfersare create, retrieve and list only. The shared CRUD base also exposesupdateanddeleteon them, but the API serves no such route.
Parcourir une liste paginée
list() renvoie une page, pas un tableau. Elle contient les lignes, les métadonnées et un moyen de récupérer la suivante.
const page = await wajub.payments.list({ status: 'success', per_page: 50 });
// One page at a time, when you control the loop.
for (const payment of page.data) {
console.log(payment.id, payment.amount);
}
if (page.has_more) {
const next = await page.getNextPage();
}
// Or let it fetch the following pages for you.
for await (const payment of page) {
await reconcile(payment);
}page.meta contient total, per_page, current_page et last_page lorsque l'endpoint les renvoie.
Idempotence et nouvelles tentatives
Chaque POST et PUT contient un en-tête Idempotency-Key, généré si vous n'en fournissez pas.
Il sécurise les nouvelles tentatives automatiques.
await wajub.payments.create(params, { idempotencyKey: `order-${orderId}` });Une clé générée protège une nouvelle tentative dans un même appel. Votre propre clé protège aussi après le redémarrage du processus. Utilisez donc le numéro de commande lorsqu'il existe.
| Élément | Valeur |
|---|---|
| Statuts réessayés | 429, 500, 502, 503, 504 et tout échec réseau |
| Tentatives | 4 au total, un appel initial et trois nouvelles tentatives |
| Attente progressive | 500 ms, doublée à chaque fois, avec jusqu'à 30 % de part d'aléa |
Retry-After | Respecté lorsque l'API l'envoie avec une réponse 429 |
GET et DELETE | Toujours réessayés, car idempotents par définition |
POST et PUT | Réessayés uniquement grâce à leur clé d'idempotence |
Node.js effectue plus de nouvelles tentatives
Les six autres SDKs s'arrêtent après deux nouvelles tentatives et permettent de modifier cette
valeur avec maxNetworkRetries. @wajub/node en effectue trois sans option de configuration.
Une requête qui échoue continuellement dure donc plus longtemps ici qu'en PHP ou en Go.
Agir pour un compte connecté
Les marketplaces transmettent un compte connecté par appel au lieu de conserver un second client.
await wajub.payments.create(
{ amount: 25000, currency: 'XAF', email: buyer.email },
{ sync: seller.wajubAccountId },
);L'option devient l'en-tête X-Sync. La configuration et les possibilités figurent dans Sync.
Webhooks
constructEvent vérifie la signature et renvoie l'événement analysé. Il nécessite le corps exact
reçu, octet par octet.
import express from 'express';
import { wajub } from './lib/wajub';
const app = express();
app.post(
'/webhooks/wajub',
express.raw({ type: 'application/json' }),
(req, res) => {
try {
const event = wajub.webhooks.constructEvent(
req.body,
req.header('x-wajub-signature')!,
req.header('x-wajub-timestamp')!,
);
if (event.event === 'payment.succeeded') {
void fulfil(event.data);
}
res.sendStatus(200);
} catch {
res.sendStatus(400);
}
},
);express.json() détruit la signature
La signature couvre {timestamp}.{raw body}. La resérialisation d'un objet analysé modifie l'ordre
des clés et les espaces. Le hash ne correspond alors plus. Montez express.raw() uniquement sur
la route du webhook avant tout analyseur JSON global, ou lisez req.text() comme dans l'onglet Next.js.
Le nom de l'événement est dans event, mais le type indique type
Le corps livré contient le nom de l'événement dans un champ event, lu par le code ci-dessus.
Le type exporté WebhookEvent déclare plutôt { type, data }. TypeScript rejette donc event.event
et accepte event.type, qui vaut undefined pendant l'exécution. Déclarez votre propre type en attendant la correction :
type WajubEvent = { id: string; event: string; data: Record<string, unknown> };
const event = wajub.webhooks.constructEvent(body, sig, ts) as unknown as WajubEvent;La tolérance est de 300 secondes par défaut. constructEvent accepte un quatrième argument si le
décalage de vos horloges est supérieur. Consultez
Vérification de signature.
Erreurs
import {
WajubError,
WajubInvalidRequestError,
WajubRateLimitError,
} from '@wajub/node';
try {
await wajub.payments.create(params);
} catch (error) {
if (error instanceof WajubInvalidRequestError) {
return res.status(422).json({ fields: error.errors });
}
if (error instanceof WajubRateLimitError) {
return res.status(503).set('Retry-After', String(error.retryAfter ?? 5)).end();
}
if (error instanceof WajubError) {
logger.error({ code: error.code, status: error.httpStatus, raw: error.raw });
}
throw error;
}| Classe | Cause |
|---|---|
WajubAuthenticationError | 401 et 403 |
WajubInvalidRequestError | 400, 404 et 422 |
WajubPaymentError | 402 |
WajubRateLimitError | 429, avec retryAfter en secondes |
WajubApiError | Tout autre statut |
WajubConnectionError | Aucune réponse, problème réseau ou délai d'expiration |
WebhookSignatureVerificationError | Échec de vérification d'un webhook |
Toutes étendent WajubError et contiennent message, code, httpStatus, errors et raw.
La propriété s'appelle httpStatus, pas status.
Ces noms sont propres à Node.js
Les six autres SDKs retirent le préfixe Wajub et séparent plutôt 403 et 404 en
PermissionError et NotFoundError. L'interception de la classe de base fonctionne partout.
TypeScript
Les types sont inclus dans le package. Aucun @types/wajub à installer ni aucune configuration à effectuer. Tout est exporté.
import type {
CreatePaymentParams,
PaymentObject,
PaymentListParams,
CustomerObject,
RefundObject,
TransferObject,
InvoiceObject,
DisputeObject,
EventObject,
WebhookEvent,
PagedResult,
RequestOptions,
WajubConfig,
} from '@wajub/node';Les objets contiennent une signature d'index. Un champ ajouté ultérieurement par l'API est donc lisible sans erreur de type, mais sans autocomplétion.
Deux champs de CreatePaymentParams n'atteignent pas l'API
expires_in est typé, mais l'endpoint lit expires.in, un objet imbriqué. Le champ plat est donc
ignoré et votre session conserve la durée par défaut de 24 heures. items[].unit_price est typé,
mais le schéma ne contient pas cette colonne. Transmettez expires: { in: 60 } comme champ
supplémentaire non typé jusqu'à la correction des types.
Débiter sans la page hébergée
Lorsque vous collectez vous-même les informations du payeur, créez d'abord le paiement, puis traitez-le sur un canal.
const payment = await wajub.payments.create({
amount: 25000,
currency: 'XAF',
phone: '+237670000000',
});
const result = await wajub.payments.process(payment.id, {
channel: 'cm.mtn',
phone: '+237670000000',
});Un canal suit le format country.operator. cm.mtn désigne donc MTN Mobile Money au Cameroun. La
liste complète figure dans Moyens de paiement et canaux. Le client doit
encore approuver sur son téléphone. Le résultat arrive donc dans le webhook, pas dans result.
Effectuer un payout
const transfer = await wajub.transfers.create(
{
amount: 100000,
currency: 'XAF',
beneficiary: {
name: 'Amina Diallo',
channel: 'cm.mtn',
phone: '+237670000000',
},
reference: 'payout-892',
},
{ idempotencyKey: 'payout-892' },
);beneficiary accepte aussi l'identifiant ben_… d'un bénéficiaire enregistré. Préférez cette
forme dès que vous payez deux fois la même personne.
Développement local
Le SDK appelle toujours la véritable API. Une clé de sandbox rend donc l'appel sans conséquence. Pour recevoir les webhooks sur votre machine, transférez-les avec la CLI au lieu d'exposer un tunnel.
wajub listen --forward-to localhost:3000/webhooks/wajub
wajub trigger payment.succeededConsultez la CLI pour plus de détails.
Pages associées
- Wajub.jsLa partie navigateur, montée avec le jeton renvoyé par ce SDK.
- ReactProvider et composants pour un front-end Next.js.
- WebhooksTous les événements et leurs garanties de livraison.
- IdempotenceCe que protège une clé et pendant combien de temps.
- Gestion des erreursLes échecs à réessayer et ceux à afficher.
- Vue d'ensemble de la référence APILes endpoints utilisés par toutes les méthodes ci-dessus.