Aller au contenu

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.

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/node

Cré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.

lib/wajub.ts
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,
});
OptionFonction
secretKeyVotre clé sk. ou sk_test.. apiKey est un alias accepté
webhookSecretSecret whsec_, uniquement nécessaire pour webhooks.constructEvent()
fetchOptionsRequestInit fusionné avec chaque appel, pour un proxy ou un certificat de développement
idempotencyKeyPrefixPréfixe de la valeur Idempotency-Key générée. wajub par défaut
timeoutMillisecondes 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

Créer un paiement
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

GetterMéthodes
wajub.globalping, channels, countries, currencies
wajub.paymentscreate, initialize, retrieve, list, cancel, process, processSplit, listRefunds
wajub.customerscreate, retrieve, update, delete, list, block, unblock, activate, deactivate, listTaxIds, createTaxId, deleteTaxId
wajub.refundscreate, retrieve, list
wajub.transferscreate, retrieve, list
wajub.beneficiariescreate, retrieve, update, delete, list
wajub.linkscreate, retrieve, update, delete, list
wajub.invoicescreate, retrieve, update, delete, list, send, markPaid, cancel
wajub.accountscreate, retrieve, update, delete, list, regenerateToken
wajub.webhookEndpointscreate, retrieve, update, delete, list, rotateSecret
wajub.balanceretrieve
wajub.eventslist, retrieve, resend
wajub.disputeslist, retrieve, submitEvidence, accept, close, sendMessage
wajub.identityresolve, validate
wajub.taxgetSettings, updateSettings, rates, calculate, reports, listCodes, retrieveCode, listRegistrations, createRegistration, retrieveRegistration, updateRegistration, deleteRegistration, jurisdictions, thresholds, thresholdAlerts
wajub.shieldgetSettings, updateSettings, stats, listBlocklist, addToBlocklist, removeFromBlocklist
wajub.listenconfig, auth
wajub.webhooksconstructEvent
  • webhooks signature verification runs locally, no HTTP call. All other resources call the merchant REST API.
  • links, invoices, tax and shield are live only. A sandbox key gets 403 This feature is only available in live mode. on every one of their methods.
  • refunds and transfers are create, retrieve and list only. The shared CRUD base also exposes update and delete on 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.

Deux façons de parcourir une liste
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.

Votre clé est prioritaire sur la clé générée
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émentValeur
Statuts réessayés429, 500, 502, 503, 504 et tout échec réseau
Tentatives4 au total, un appel initial et trois nouvelles tentatives
Attente progressive500 ms, doublée à chaque fois, avec jusqu'à 30 % de part d'aléa
Retry-AfterRespecté lorsque l'API l'envoie avec une réponse 429
GET et DELETEToujours réessayés, car idempotents par définition
POST et PUTRéessayés uniquement grâce à leur clé d'idempotence

Agir pour un compte connecté

Les marketplaces transmettent un compte connecté par appel au lieu de conserver un second client.

Un appel pour un vendeur
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);
    }
  },
);

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

Affiner le type avant de lire
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;
}
ClasseCause
WajubAuthenticationError401 et 403
WajubInvalidRequestError400, 404 et 422
WajubPaymentError402
WajubRateLimitError429, avec retryAfter en secondes
WajubApiErrorTout autre statut
WajubConnectionErrorAucune 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é.

Les éléments que vous importerez réellement
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.

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.

Requête push Mobile Money
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

Un transfert vers un numéro de téléphone
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.

Deux terminaux
wajub listen --forward-to localhost:3000/webhooks/wajub
wajub trigger payment.succeeded

Consultez la CLI pour plus de détails.

Que pensez-vous de ce contenu ?