npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dexpay-sdk

v1.3.0

Published

SDK TypeScript moderne pour l'API DexPay Africa — Mobile Money & Card payments

Readme

dexpay-sdk

SDK TypeScript moderne pour l'API DexPay Africa — paiements Mobile Money & carte bancaire.

npm version CI/CD License: MIT


Table des matières


Fonctionnalités

  • Typage strict — types exhaustifs pour toute l'API DexPay
  • 100% Bun — zéro dépendance runtime, fetch natif, crypto natif
  • Erreurs typées — hiérarchie d'erreurs claire (DexPayAuthError, DexPayValidationError, etc.)
  • Webhooks — vérification HMAC-SHA256 + parsing typé
  • Providers — filtrage côté SDK
  • Payouts — retraits vers Mobile Money avec pagination et rapport complet
  • Payout Providers — liste des opérateurs de retrait disponibles avec montants min/max
  • Balances — consultation des soldes marchands par pays et devise
  • Semantic Release — versioning automatique via Conventional Commits
  • Compatible — Node.js 18+, Bun 1.3.11+, Edge Runtime (Vercel, Cloudflare Workers)

Installation

bun add dexpay-sdk
# ou
npm install dexpay-sdk

Démarrage rapide

import { DexPay } from "dexpay-sdk";

const dexpay = new DexPay({
  apiKey: process.env.DEXPAY_API_KEY!,
  apiSecret: process.env.DEXPAY_API_SECRET!,
  environment: "sandbox", // "production" en production
});

Le client instancie automatiquement toutes les ressources :

| Propriété | Description | | ------------------------- | ------------------------------------ | | dexpay.sessions | Sessions de paiement hébergées | | dexpay.paymentProviders | Opérateurs de paiement disponibles | | dexpay.payouts | Retraits vers Mobile Money | | dexpay.payoutProviders | Opérateurs de retrait disponibles | | dexpay.balances | Soldes marchands | | dexpay.webhooks | Vérification et parsing des webhooks |

Note : La construction d'un DexPay sans apiKey ou apiSecret lève immédiatement une DexPayConfigError.


Sessions de paiement

Créer une session hébergée

import { DexPay, generateReference } from "dexpay-sdk";

const reference = generateReference(); // "ORD-M0JKZ8C4-X3KP"

const session = await dexpay.sessions.create({
  reference,
  item_name: "Commande #42",
  amount: 5000, // en XOF (entier)
  currency: "XOF",
  countryISO: "SN",
  webhook_url: "https://monapp.com/api/webhooks/dexpay",
  success_url: `https://monapp.com/success?ref=${reference}`,
  failure_url: `https://monapp.com/failure?ref=${reference}`,
  customer: {
    name: "Alpha Diop",
    email: "[email protected]",
    phone: "+221771234567",
  },
});

// Redirigez l'utilisateur vers la page de paiement hébergée
redirect(session.payment_url);

Réponse DexPaySession :

| Champ | Type | Description | | --------------------- | --------------------- | ----------------------------------- | | reference | string | Référence unique de la commande | | amount | number | Montant en unités entières | | currency | Currency | Devise (XOF, XAF, GNF) | | payment_url | string | URL de la page de paiement hébergée | | success_url | string | URL de redirection si succès | | failure_url | string | URL de redirection si échec | | webhook_url | string | URL appelée après paiement | | expires_at | string | Date d'expiration ISO 8601 | | status | string | Statut de la session | | isSandbox | boolean | true si mode test | | payment_attempt | object \| undefined | Tentative de paiement en cours | | sandbox_payment_url | string \| undefined | URL de test sandbox uniquement |


Tentative directe (sans redirection)

Pour une expérience utilisateur personnalisée (votre propre UI, QR code, etc.) :

const attempt = await dexpay.sessions.attempt(reference, {
  payment_method: "mobile_money",
  operator: "WAVE_SN",
  customer: {
    name: "Alpha Diop",
    email: "[email protected]",
    phone: "+221771234567",
  },
  countryISO: "SN",
});

if (attempt.cashout_url) {
  if (isMobileDevice()) {
    // Rediriger vers l'app Mobile Money
    window.location.href = attempt.cashout_url;
  } else {
    // Afficher un QR code (ex: avec qrcode package)
    showQRCode(attempt.cashout_url);
  }
} else {
  // Paiement USSD — poller le statut
  pollStatus(reference);
}

Réponse DexPayAttemptResponse :

| Champ | Type | Description | | ---------------- | ---------------- | ------------------------------------------------------------------------ | | id | string | Identifiant de la tentative | | transaction_id | string | Identifiant de transaction | | status | AttemptStatus | initiated, pending, processing, completed, failed, cancelled | | operator | string | Opérateur de paiement | | is_active | boolean | Tentative toujours active | | cashout_url | string \| null | URL QR/redirection mobile, null si USSD | | expires_at | string | Date d'expiration ISO 8601 |


Récupérer une session

const session = await dexpay.sessions.retrieve("ORD-M0JKZ8C4-X3KP");
console.log(session.status);

Payouts

Les payouts permettent d'envoyer des fonds vers un compte Mobile Money. Le montant spécifié dans amount est exactement ce que le destinataire recevra — votre solde est débité de amount + fees.

Montants minimum :

| Devise | Minimum | | ------ | ---------- | | XOF | 250 FCFA | | XAF | 250 FCFA | | GNF | 10 000 GNF |


Créer un payout

const payout = await dexpay.payouts.create({
  amount: 10000,
  currency: "XOF",
  destination_phone: "+221771234567",
  destination_details: {
    operator: "wave_sn_payout", // provider_short_name via dexpay.payoutProviders
    countryISO: "SN",
    recipient_name: "Jean Dupont", // optionnel
  },
  metadata: {
    invoice_id: "INV_123", // optionnel
    reason: "Remboursement client",
  },
});

console.log(payout.reference); // "PO_20251227_A1B2C3"
console.log(payout.status); // "PENDING"
console.log(payout.fee_amount); // 150
console.log(payout.merchant_net); // 10150 (amount + fees débités)

Paramètres CreatePayoutParams :

| Paramètre | Type | Requis | Description | | ------------------------------------ | ------------------------- | ------ | ---------------------------------------------------- | | amount | number | ✅ | Montant exact reçu par le destinataire | | currency | Currency | ✅ | XOF, XAF, GNF | | destination_phone | string | ✅ | Numéro au format international (ex: +221771234567) | | destination_details.operator | string | ✅ | provider_short_name du payout provider | | destination_details.countryISO | CountryISO | ✅ | Code ISO du pays | | destination_details.recipient_name | string | ❌ | Nom du destinataire | | metadata | Record<string, unknown> | ❌ | Données personnalisées |

Réponse DexPayPayout :

| Champ | Type | Description | | --------------------- | -------------------------- | ----------------------------------------------------------- | | id | string | Identifiant unique MongoDB | | reference | string | Référence lisible (ex: PO_20251227_A1B2C3) | | amount | number | Montant envoyé | | currency | Currency | Devise | | fee_amount | number | Frais prélevés | | merchant_net | number | Total débité (amount + fee_amount) | | status | PayoutStatus | PENDING, PROCESSING, COMPLETED, FAILED, CANCELLED | | destination_phone | string | Numéro destinataire | | destination_details | PayoutDestinationDetails | Détails de la destination | | provider_name | string | Nom de l'opérateur | | provider_tx_id | string \| undefined | ID de transaction opérateur | | failure_reason | string \| null | Raison d'échec si applicable | | completed_at | string \| null | Date de completion ISO 8601 | | createdAt | string | Date de création | | updatedAt | string | Date de mise à jour |


Récupérer un payout

const payout = await dexpay.payouts.retrieve("6901aaf7ecb319a1016c06a1");

console.log(payout.status); // "COMPLETED"
console.log(payout.completed_at); // "2025-12-27T10:30:00.000Z"
console.log(payout.provider_tx_id); // ID de transaction chez l'opérateur

Lister les payouts

// Page 1 — 20 derniers payouts (défaut)
const { data, hasNextPage, totalCount } = await dexpay.payouts.list();

// Filtrer par statut
const completed = await dexpay.payouts.list({
  status: "COMPLETED",
  limit: 50,
});

// Pagination manuelle
let page = 1;
let hasNext = true;

while (hasNext) {
  const result = await dexpay.payouts.list({ page, limit: 100 });
  process(result.data);
  hasNext = result.hasNextPage;
  page++;
}

Options ListPayoutsOptions :

| Paramètre | Type | Défaut | Description | | --------- | -------------- | ------ | --------------------------- | | page | number | 1 | Numéro de page | | limit | number | 20 | Éléments par page (max 100) | | status | PayoutStatus | — | Filtre par statut |

Statuts possibles : PENDING · PROCESSING · COMPLETED · FAILED · CANCELLED


Rapport complet (toutes pages)

// Récupère automatiquement toutes les pages (100 par batch)
const allPayouts = await dexpay.payouts.listAll({ status: "COMPLETED" });

console.log(`${allPayouts.length} payouts complétés au total`);

⚠️ listAll effectue plusieurs requêtes en séquence. À utiliser pour les exports/rapports, pas pour afficher une liste paginée.


Suivi après webhook

// Dans votre handler webhook
const payoutId = webhookPayload.payout_id;
const payout = await dexpay.payouts.retrieve(payoutId);

if (payout.status === "FAILED") {
  console.error("Échec :", payout.failure_reason);
}

Balances

dexpay.balances expose la consultation de vos soldes marchands via GET /balances. Les données retournées dépendent de vos clés API :

  • Clés production → soldes réels
  • Clés sandbox → soldes de test

Lister tous les soldes

const { data } = await dexpay.balances.list();

for (const balance of data) {
  console.log(`${balance.countryISO}: ${balance.balance} ${balance.currency}`);
  console.log(`Disponible: ${balance.balance}`);
  console.log(`Réservé: ${balance.reserved_balance}`);
  console.log(`En attente: ${balance.pending_balance}`);
}

Solde par pays

const snBalance = await dexpay.balances.findByCountry("SN");
if (snBalance) {
  console.log(`Solde Sénégal: ${snBalance.balance} ${snBalance.currency}`);
}

Solde par devise

const xofBalance = await dexpay.balances.findByCurrency("XOF");
if (xofBalance) {
  console.log(`Solde XOF: ${xofBalance.balance}`);
}

Total disponible

const total = await dexpay.balances.getTotalAvailable();
console.log(`Total disponible: ${total}`);

Structure DexPayBalance :

| Propriété | Type | Description | | ------------------ | -------- | ------------------------------------- | | id | string | Identifiant unique de la balance | | countryISO | string | Code ISO du pays (ex: SN, CI) | | currency | string | Devise (ex: XOF, XAF) | | balance | number | Solde disponible pour les paiements | | reserved_balance | number | Fonds réservés/bloqués temporairement | | pending_balance | number | Fonds en attente de compensation |

💡 Utilité : Vérifiez vos fonds disponibles avant d'initier des payouts pour éviter les erreurs de solde insuffisant.


Providers de paiement

// Tous les providers actifs au Sénégal
const providers = await dexpay.paymentProviders.list({ country: "SN" });

// Filtrer par méthode de paiement
const mobileMoneyProviders = await dexpay.paymentProviders.list({
  country: "SN",
  paymentMethod: "mobile_money",
});

// Inclure les providers inactifs
const allProviders = await dexpay.paymentProviders.list({
  country: "SN",
  includeInactive: true,
});

// Tous les providers sans filtre (brut)
const raw = await dexpay.paymentProviders.all();

// Trouver un provider spécifique
const wave = await dexpay.paymentProviders.findByShortName("WAVE_SN");

// Noms uniques pour le badge "Sécurisé par..."
const names = await dexpay.paymentProviders.uniqueNames({ country: "SN" });
// → ["Wave", "Orange Money", "Free Money"]

Options ProviderFilterOptions :

| Paramètre | Type | Description | | ----------------- | --------------- | ------------------------------------------------ | | country | CountryISO | Code ISO du pays (ex: SN, CI) | | paymentMethod | PaymentMethod | mobile_money ou card | | includeInactive | boolean | Inclure les providers inactifs (défaut: false) |

Note : Le filtre par pays de l'API DexPay ne fonctionne pas toujours en sandbox. Ce SDK filtre manuellement côté client. Les providers avec provider_country === "GLOBAL" sont inclus pour tous les pays.

Structure DexPayProvider :

| Propriété | Type | Description | | --------------------- | ------------------------ | ---------------------------- | | provider_name | string | Nom d'affichage (ex: Wave) | | provider_short_name | string | Code court (ex: WAVE_SN) | | provider_logo | string | URL du logo | | provider_country | CountryISO \| "GLOBAL" | Pays ou GLOBAL | | provider_currency | Currency | Devise supportée | | provider_type | PaymentMethod | mobile_money ou card | | provider_status | ProviderStatus | active ou inactive | | provider_fee_type | FeeType | percentage ou fixed | | provider_fee | number | Montant des frais | | isSandbox | boolean | Provider sandbox |


Payout Providers

dexpay.payoutProviders expose les opérateurs disponibles pour les retraits via GET /payouts-providers. Le provider_short_name retourné est la valeur à utiliser dans destination_details.operator lors de la création d'un payout.

// Lister les providers payout actifs au Sénégal
const { data } = await dexpay.payoutProviders.list({ country: "SN" });

for (const p of data) {
  console.log(p.provider_short_name); // "wave_sn_payout"
  console.log(p.min_amount); // 500
  console.log(p.max_amount); // 1_000_000
  console.log(p.provider_fee); // 1 (%)
}

// Catalogue complet (toutes pages)
const all = await dexpay.payoutProviders.all({ country: "CI" });

// Trouver un provider par son short name (insensible à la casse)
const wave = await dexpay.payoutProviders.findByShortName("wave_sn_payout");
if (wave) {
  console.log(
    `${wave.provider_name} — min: ${wave.min_amount} ${wave.provider_currency}`,
  );
}

Filtres disponibles ListPayoutProvidersOptions :

| Paramètre | Type | Description | | --------- | ------------------------ | --------------------------------------- | | country | CountryISO | Code ISO du pays (ex: SN, CI) | | status | "active" \| "inactive" | Statut du provider (défaut: "active") | | page | number | Numéro de page (défaut: 1) | | limit | number | Éléments par page (défaut: 50) |

Structure DexPayPayoutProvider :

| Propriété | Type | Description | | --------------------- | -------------------- | ------------------------------------- | | provider_name | string | Nom d'affichage (ex: Wave SN) | | provider_short_name | string | Code opérateur (ex: wave_sn_payout) | | provider_logo | string | URL du logo | | provider_country | CountryISO | Code pays | | provider_currency | Currency | Devise supportée | | provider_type | PayoutProviderType | mobile_money ou bank | | provider_status | ProviderStatus | active ou inactive | | provider_fee_type | FeeType | percentage ou fixed | | provider_fee | number | Taux ou montant des frais | | min_amount | number | Montant minimum accepté | | max_amount | number | Montant maximum accepté |


Lier providers et création de payout

// 1. Récupérer les providers disponibles
const { data: providers } = await dexpay.payoutProviders.list({
  country: "SN",
});

// 2. Vérifier que le montant est dans les limites
const provider = providers.find(
  (p) => p.provider_short_name === "wave_sn_payout",
);
const amount = 10_000;

if (
  provider &&
  amount >= provider.min_amount &&
  amount <= provider.max_amount
) {
  // 3. Créer le payout
  const payout = await dexpay.payouts.create({
    amount,
    currency: provider.provider_currency,
    destination_phone: "+221771234567",
    destination_details: {
      operator: provider.provider_short_name,
      countryISO: provider.provider_country,
    },
  });
}

Webhooks

Dans une route Next.js / Hono / Fastify

// app/api/webhooks/dexpay/route.ts (Next.js App Router)
import { DexPay, DexPayWebhookSignatureError } from "dexpay-sdk";

const dexpay = new DexPay({
  apiKey: process.env.DEXPAY_API_KEY!,
  apiSecret: process.env.DEXPAY_API_SECRET!,
  environment: "production",
});

export async function POST(request: Request) {
  const rawBody = await request.text();
  const signature = request.headers.get("x-webhook-signature") ?? "";

  let payload;
  try {
    payload = await dexpay.webhooks.constructEvent(rawBody, signature);
  } catch (err) {
    if (err instanceof DexPayWebhookSignatureError) {
      return Response.json({ error: "Invalid signature" }, { status: 401 });
    }
    return Response.json({ error: "Bad request" }, { status: 400 });
  }

  switch (payload.event) {
    case "checkout.completed":
      await db.orders.update({
        where: { reference: payload.reference },
        data: { status: "paid", transactionId: payload.transaction_id },
      });
      break;

    case "checkout.failed":
      await db.orders.update({
        where: { reference: payload.reference },
        data: { status: "failed" },
      });
      break;

    case "checkout.cancelled":
      await db.orders.update({
        where: { reference: payload.reference },
        data: { status: "cancelled" },
      });
      break;
  }

  return Response.json({ received: true }); // Toujours 200 pour stopper les retries
}

Comportement de vérification :

  • Signature présente : vérification HMAC-SHA256 avec votre apiSecret. Lance DexPayWebhookSignatureError si invalide.
  • Signature absente (ex: sandbox) : parsing sans vérification.
  • JSON invalide : lance une Error standard.

Événements WebhookEvent supportés :

| Événement | Description | | -------------------------------- | ---------------------------- | | checkout.initiated | Paiement initié | | checkout.completed | Paiement réussi | | checkout.failed | Paiement échoué | | checkout.cancelled | Paiement annulé | | checkout.refunded | Paiement remboursé | | subscription.created | Abonnement créé | | subscription.activated | Abonnement activé | | subscription.cancelled | Abonnement annulé | | subscription.payment.succeeded | Paiement d'abonnement réussi | | subscription.payment.failed | Paiement d'abonnement échoué |

Utilitaires webhooks supplémentaires :

// Vérifier uniquement la signature (sans parser)
const isValid = await dexpay.webhooks.verifySignature(rawBody, signature);

// Type guard pour valider un événement
const isKnown = dexpay.webhooks.isValidEvent("checkout.completed"); // true
const isUnknown = dexpay.webhooks.isValidEvent("unknown.event"); // false

Utilitaires

import {
  generateReference,
  formatAmount,
  calculateFee,
  calculateNetAmount,
  isMobileDevice,
} from "dexpay-sdk";

// ── Référence unique ──────────────────────────────────
generateReference(); // "ORD-M0JKZ8C4-X3KP"
generateReference("INV"); // "INV-M0JKZ8C4-X3KP"
generateReference("PO"); // "PO-M0JKZ8C4-X3KP"

// ── Formatage montant ─────────────────────────────────
formatAmount(5000, "XOF"); // "5 000 FCFA"
formatAmount(5000, "XOF", "en-US"); // "XOF 5,000"
formatAmount(0, "XOF"); // "0 FCFA"

// ── Calcul des frais ──────────────────────────────────
const provider = { provider_fee: 1.5, provider_fee_type: "percentage" };
calculateFee(10000, provider); // 150
calculateNetAmount(10000, provider); // 9850

const fixedProvider = { provider_fee: 100, provider_fee_type: "fixed" };
calculateFee(10000, fixedProvider); // 100
calculateNetAmount(10000, fixedProvider); // 9900

// ── Détection mobile (navigateur uniquement) ──────────
isMobileDevice(); // true / false

Gestion des erreurs

import {
  DexPay,
  DexPayApiError,
  DexPayAuthError,
  DexPayValidationError,
  DexPayRateLimitError,
  DexPayTimeoutError,
  DexPayWebhookSignatureError,
  DexPayNotFoundError,
  DexPayConfigError,
} from "dexpay-sdk";

try {
  const payout = await dexpay.payouts.create({ ... });
} catch (err) {
  if (err instanceof DexPayAuthError) {
    // 401 — clé API invalide ou secret incorrect
  } else if (err instanceof DexPayValidationError) {
    // 422 — paramètres invalides (montant trop bas, opérateur inconnu...)
    console.log(err.body?.details);
    console.log(err.body?.message);
  } else if (err instanceof DexPayApiError && err.statusCode === 402) {
    // 402 — solde insuffisant
  } else if (err instanceof DexPayNotFoundError) {
    // 404 — ressource introuvable
  } else if (err instanceof DexPayRateLimitError) {
    // 429 — trop de requêtes
  } else if (err instanceof DexPayTimeoutError) {
    // Timeout réseau (configurable, défaut 30s)
  } else if (err instanceof DexPayConfigError) {
    // Configuration SDK manquante (apiKey / apiSecret)
  } else if (err instanceof DexPayWebhookSignatureError) {
    // Signature webhook invalide (payload potentiellement falsifié)
  } else {
    throw err;
  }
}

Hiérarchie des erreurs :

| Classe | Code HTTP | Description | | ----------------------------- | --------- | ------------------------------------------------------------------------------------- | | DexPayError | — | Classe de base (toutes les erreurs héritent de celle-ci) | | DexPayConfigError | — | Configuration SDK manquante (apiKey / apiSecret vide) | | DexPayApiError | 4xx/5xx | Erreur HTTP générique — expose statusCode, body, isClientError, isServerError | | DexPayAuthError | 401 | Clé API ou secret invalide | | DexPayNotFoundError | 404 | Ressource introuvable | | DexPayValidationError | 422 | Paramètres invalides — body.details contient le détail | | DexPayApiError (402) | 402 | Solde insuffisant pour un payout | | DexPayRateLimitError | 429 | Rate limit atteint | | DexPayTimeoutError | — | Timeout réseau | | DexPayWebhookSignatureError | — | Signature HMAC-SHA256 invalide |

Propriétés DexPayApiError :

err.statusCode; // number
err.body; // DexPayErrorBody | undefined
err.body?.message; // string | undefined
err.body?.error; // string | undefined
err.body?.details; // unknown
err.isClientError; // true si 400–499
err.isServerError; // true si 500+

Configuration avancée

const dexpay = new DexPay({
  apiKey: "pk_...",
  apiSecret: "sk_...",
  environment: "production", // "sandbox" (défaut) | "production"
  timeout: 15_000, // Timeout HTTP en ms (défaut: 30 000)
});

// Inspecter l'environnement
console.log(dexpay.environment); // "production"
console.log(dexpay.isSandbox); // false

Endpoints API utilisés en interne :

| Environnement | Base URL | | ------------- | ------------------------------------------ | | sandbox | https://api-sandbox.dexpay.africa/api/v1 | | production | https://api.dexpay.africa/api/v1 |

Headers envoyés automatiquement :

Content-Type: application/json
x-api-key: <apiKey>
x-api-secret: <apiSecret>
User-Agent: dexpay-sdk/bun

Types exportés

Tous les types sont exportés depuis le point d'entrée principal :

import type {
  // Shared
  Currency, // "XOF" | "XAF" | "GNF"
  CountryISO, // "SN" | "CI" | "CM" | "GN" | string
  PaymentMethod, // "mobile_money" | "card"
  ProviderStatus, // "active" | "inactive"
  FeeType, // "percentage" | "fixed"
  OrderStatus,
  AttemptStatus,
  PayoutStatus,
  WebhookEvent,
  PayoutProviderType,

  // Entities
  Customer,
  DexPayProvider,
  DexPayPayoutProvider,
  DexPayBalance,
  DexPaySession,
  DexPayAttemptResponse,
  DexPayPayout,
  DexPayWebhookPayload,

  // Request params
  CreateSessionParams,
  CreateAttemptParams,
  CreatePayoutParams,
  PayoutDestinationDetails,
  ListPayoutsOptions,
  ListPayoutProvidersOptions,
  ProviderFilterOptions,

  // Responses
  ListPayoutsResponse,
  ListPayoutProvidersResponse,
  ListBalancesResponse,

  // Config & errors
  DexPayConfig,
  DexPayErrorBody,
} from "dexpay-sdk";

Architecture interne

src/
├── client.ts                      # Classe principale DexPay
├── errors.ts                      # Hiérarchie d'erreurs typées
├── http.ts                        # Client HTTP (fetch natif, gestion erreurs)
├── index.ts                       # Exports publics
├── types.ts                       # Types TypeScript exhaustifs
├── resources/
│   ├── sessions.ts                # Sessions de paiement (create, retrieve, attempt)
│   ├── payment-providers.ts       # Providers paiement (list, findByShortName, uniqueNames)
│   ├── payouts.ts                 # Payouts (create, retrieve, list, listAll)
│   ├── payout-providers.ts        # Providers retrait (list, all, findByShortName)
│   ├── balances.ts                # Balances (list, findByCountry, findByCurrency, getTotalAvailable)
│   └── webhooks.ts                # Webhooks (constructEvent, verifySignature, isValidEvent)
└── utils/
    └── index.ts                   # Utilitaires (generateReference, formatAmount, calculateFee, ...)

Le HttpClient interne gère :

  • Timeout configurable via AbortController
  • Unwrapping automatique des réponses { data: ... }
  • Mapping d'erreurs HTTP vers les classes d'erreurs typées
  • Cache hints Next.js (next: { revalidate }) sur les requêtes GET

Environnements supportés

| Runtime | Support | | ------------------- | --------------------------------------- | | Bun 1.3.11+ | ✅ Recommandé (natif) | | Node.js 18+ | ✅ (fetch & crypto.subtle natifs) | | Vercel Edge Runtime | ✅ | | Cloudflare Workers | ✅ | | Navigateur | ✅ (sans isMobileDevice côté serveur) |


Contributing & Release

Ce projet utilise Conventional Commits et semantic-release pour le versioning automatique.

# Features → minor release (1.0.0 → 1.1.0)
git commit -m "feat: add payout support"

# Bug fixes → patch release (1.0.0 → 1.0.1)
git commit -m "fix: handle empty cashout_url correctly"

# Breaking changes → major release (1.0.0 → 2.0.0)
git commit -m "feat!: rename createSession to create"

# Pas de release
git commit -m "docs: update README"
git commit -m "test: add balance tests"
git commit -m "chore: update deps"

Règles de release (.releaserc.json) :

| Type | Section changelog | Release | | ---------- | ----------------- | ------- | | feat | 🚀 Features | minor | | fix | 🐛 Bug Fixes | patch | | perf | ⚡ Performance | patch | | refactor | ♻️ Refactoring | patch | | docs | 📚 Documentation | aucune | | test | 🧪 Tests | aucune | | chore | — | aucune | | breaking | — | major |

Setup du projet

bun install        # Installer les dépendances
bun run build      # Build TypeScript → dist/
bun test           # Lancer les tests
bun run typecheck  # Vérification TypeScript
bun run lint       # Lint avec Biome

Setup NPM Token (CI/CD)

# Dans les secrets GitHub du repo :
NPM_TOKEN=<votre_token_npm>

Le pipeline CI/CD (.github/workflows/ci.yml) exécute sur main, beta, alpha :

  1. Typecheck TypeScript
  2. Lint Biome
  3. Tests + couverture
  4. Build
  5. Release semantic-release (uniquement sur main, beta, alpha)

License

MIT — voir LICENSE