Aller au contenu

Node.js

Le SDK serveur @wajub/node, ses ressources et les types TypeScript inclus.

@wajub/node est le client Wajub de votre serveur : avec votre clé secrète, il crée des paiements, lit leur statut et vérifie les signatures des webhooks. Il ne s'exécute jamais dans un navigateur.

Version
2.0.0
Runtime
Node.js 18+, Bun, Deno, any runtime with fetch

Couvre

  • Paiements
  • Facturation
  • Transferts
  • Sync
  • Shield
  • Taxes

Installer

npm install @wajub/node

Il s'utilise avec import comme avec require.

Créer le client une seule fois

Créez-le dans un module et importez ce module partout.

lib/wajub.ts
import { Wajub } from '@wajub/node';

if (!process.env.WAJUB_API_KEY) {
  throw new Error('WAJUB_API_KEY is missing');
}

export const wajub = new Wajub({
  secretKey: process.env.WAJUB_API_KEY,
  webhookSecret: process.env.WAJUB_WEBHOOK_SECRET,
});
OptionFonction
secretKeyVotre clé sk. ou sk_test.
webhookSecretSecret whsec_, uniquement nécessaire pour webhooks.constructEvent()
fetchOptionsRequestInit fusionné avec chaque appel, pour un proxy ou un certificat de développement
timeoutDélai d'expiration d'une requête en millisecondes, réglable aussi par appel

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',
  expires: { in: 60 }, // minutes before the payment expires
});

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

AccesseurMéthodes
wajub.globalping, channels, countries, currencies
wajub.paymentscreate, 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.webhooksconstructEvent
  • La vérification de signature de webhooks s'exécute en local, sans appel HTTP. Toutes les autres ressources appellent l'API REST.
  • links, invoices, tax et shield ne fonctionnent qu'en live. Une clé sandbox reçoit 403 This feature is only available in live mode. sur chacune de leurs méthodes.
  • refunds et transfers ne proposent que la création, la lecture et la liste.

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: 'succeeded', 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 les champs de pagination de la liste, décrits dans Pagination.

Idempotence et nouvelles tentatives

Les échecs temporaires sont réessayés pour vous. Pour qu'une création puisse aussi être répétée sans risque de votre côté, après un redémarrage ou dans une tâche réessayée, transmettez votre propre clé d'idempotence, le numéro de commande par exemple.

Votre propre clé d'idempotence
await wajub.payments.create(params, { idempotencyKey: `order-${orderId}` });

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 },
);

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);
    }
  },
);

Si vos horloges dérivent, constructEvent accepte la tolérance en secondes comme quatrième argument. 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.

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';

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 Mobile Money. 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 ?