dexpay-sdk
v1.3.0
Published
SDK TypeScript moderne pour l'API DexPay Africa — Mobile Money & Card payments
Maintainers
Readme
dexpay-sdk
SDK TypeScript moderne pour l'API DexPay Africa — paiements Mobile Money & carte bancaire.
Table des matières
- Fonctionnalités
- Installation
- Démarrage rapide
- Sessions de paiement
- Payouts
- Balances
- Providers de paiement
- Payout Providers
- Webhooks
- Utilitaires
- Gestion des erreurs
- Configuration avancée
- Types exportés
- Architecture interne
- Environnements supportés
- Contributing & Release
- License
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-sdkDé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
DexPaysansapiKeyouapiSecretlève immédiatement uneDexPayConfigError.
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érateurLister 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`);⚠️
listAlleffectue 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. LanceDexPayWebhookSignatureErrorsi invalide. - Signature absente (ex: sandbox) : parsing sans vérification.
- JSON invalide : lance une
Errorstandard.
É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"); // falseUtilitaires
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 / falseGestion 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); // falseEndpoints 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/bunTypes 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 BiomeSetup 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 :
- Typecheck TypeScript
- Lint Biome
- Tests + couverture
- Build
- Release semantic-release (uniquement sur
main,beta,alpha)
License
MIT — voir LICENSE
