> ## Documentation Index
> Fetch the complete documentation index at: https://docs.alphapay.me/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK Node.js

> Intégrez les paiements AlphaPay côté serveur dans une application Node.js/TypeScript via le SDK officiel.

Le **SDK Node.js `alphapay-node`** enveloppe l'API REST AlphaPay (checkout, softpay, liens de paiement, clients, webhooks...) avec retry automatique (backoff exponentiel + gigue sur 429/5xx/erreur réseau), gestion d'idempotence et exceptions typées par cas d'erreur. Écrit en TypeScript, types fournis nativement.

```bash theme={null}
npm install alphapay-node
# ou : yarn add alphapay-node / pnpm add alphapay-node
```

<Note>
  Chaque exemple ci-dessous est autonome : copiez-le, remplacez `process.env.ALPHAPAY_SECRET_KEY` par votre clé (`sk_test_...` en sandbox, `sk_live_...` en production) et lancez-le avec `tsx`/`ts-node` ou après compilation.
</Note>

## Sommaire

1. [Checkout (session hébergée)](#1-checkout-session-hébergée)
2. [Softpay (encaissement direct)](#2-softpay-encaissement-direct)
3. [Lien de paiement](#3-lien-de-paiement)
4. [Solde et grand livre](#4-solde-et-grand-livre)
5. [Clients (CRM)](#5-clients-crm)
6. [Webhooks](#6-webhooks)
7. [Reversements et transferts entre wallets](#7-reversements-et-transferts-entre-wallets)
8. [Clés API et whitelist IP](#8-clés-api-et-whitelist-ip)
9. [Gestion des erreurs](#9-gestion-des-erreurs)

***

## 1. Checkout (session hébergée)

Crée une page de paiement hébergée par AlphaPay et pré-remplie pour un
client précis — l'usage le plus simple pour un e-commerce classique
(redirigez le client vers `checkout_url`).

```ts theme={null}
import { AlphaPayClient } from "alphapay-node";

const alphapay = new AlphaPayClient({ apiKey: process.env.ALPHAPAY_SECRET_KEY! });

async function creerCheckout() {
  const session = await alphapay.checkoutSessions.create(
    {
      amount: 5000,
      currency: "XOF",
      description: "Commande #1042",
      customer_email: "client@exemple.com",
      customer_name: "Ayaba Client",
      customer_phone: "+22900000000",
      return_url: "https://boutique.exemple.com/merci",
      metadata: { order_id: "1042" },
    },
    { idempotencyKey: true }, // génère une clé unique — un retry réseau ne recrée pas de doublon
  );

  console.log("Redirigez le client vers :", session.checkout_url);
  console.log("Slug (à stocker pour retrouver la session) :", session.slug);
  return session;
}

async function suivreCheckout(id: string) {
  const session = await alphapay.checkoutSessions.get(id);
  console.log("Statut :", session.status); // PENDING, PAID, EXPIRED, CANCELLED
}

async function annulerCheckout(id: string) {
  const session = await alphapay.checkoutSessions.cancel(id);
  console.log("Annulée :", session.status === "CANCELLED");
}
```

***

## 2. Softpay (encaissement direct)

Pousse directement une demande de paiement (USSD mobile money) sans page
de checkout à afficher — utile pour une app où vous collectez déjà le
numéro du client.

```ts theme={null}
import { AlphaPayClient, AlphaPayValidationError } from "alphapay-node";

const alphapay = new AlphaPayClient({ apiKey: process.env.ALPHAPAY_SECRET_KEY! });

async function encaisser() {
  try {
    const payment = await alphapay.transactions.payin.initialize(
      {
        amount: 2500,
        currency: "XOF",
        country: "BJ",
        network: "mtn_bj", // cf. GET /networks/ pour la liste à jour
        description: "Abonnement mensuel",
        customer: {
          email: "client@exemple.com",
          first_name: "Ayaba",
          last_name: "Client",
          phone: "+22900000000",
        },
      },
      { idempotencyKey: true },
    );

    console.log("Paiement initié :", payment.id, payment.status);

    // Sondez jusqu'à confirmation (push USSD confirmé/refusé côté client).
    const result = await alphapay.transactions.payin.verify(payment.id);
    console.log("Statut final :", result.status);
  } catch (err) {
    if (err instanceof AlphaPayValidationError) {
      console.error("Champs invalides :", err.fieldErrors);
    } else {
      throw err;
    }
  }
}

// Réseaux à confirmation en 2 temps (ex. Wizall Sénégal, Coris Bénin).
async function confirmerOtp(paymentId: string, otp: string) {
  const result = await alphapay.transactions.payin.confirmOtp(paymentId, otp);
  console.log("Confirmé :", result.status);
}
```

***

## 3. Lien de paiement

Un lien réutilisable (partageable sur WhatsApp, réseaux sociaux, etc.),
avec ses propres champs personnalisés et suivi publicitaire.

```ts theme={null}
import { AlphaPayClient } from "alphapay-node";

const alphapay = new AlphaPayClient({ apiKey: process.env.ALPHAPAY_SECRET_KEY! });

async function creerLien() {
  const link = await alphapay.paymentLinks.create({
    name: "Formation en ligne",
    description: "Accès à vie à la formation",
    amount_type: "FIXED",
    amount: 15000,
    currency: "XOF",
    facebook_pixel_id: process.env.FACEBOOK_PIXEL_ID,
    google_ads_id: process.env.GOOGLE_ADS_ID,
    custom_fields: [
      { key: "email_formation", label: "E-mail pour l'accès", required: true },
    ],
  });

  console.log("Lien partageable :", link.url);
  return link;
}

// Ce que voit la page publique du lien — pas d'auth marchand nécessaire.
async function consulterLienPublic(slug: string) {
  const publicLink = await alphapay.paymentLinks.getPublic(slug);
  console.log(`${publicLink.name} — ${publicLink.amount ?? "montant libre"} ${publicLink.currency}`);
  console.log("Utilisable :", publicLink.is_usable, publicLink.unusable_reason ?? "");
}

// Crée une CheckoutSession one-shot à partir du lien (ex. depuis votre propre
// front public, sans jamais exposer la clé secrète côté client).
async function payerDepuisLien(slug: string) {
  const result = await alphapay.paymentLinks.createPublicCheckout(slug, {
    customer: { email: "client@exemple.com", first_name: "Ayaba", last_name: "Client" },
    custom_field_values: { email_formation: "ayaba@exemple.com" },
  });
  console.log("Session créée :", result.checkout_url);
}

async function listerLiens() {
  const { results } = await alphapay.paymentLinks.list({ is_active: true });
  for (const link of results) console.log(link.name, link.usage_count, "utilisations");
}
```

***

## 4. Solde et grand livre

```ts theme={null}
import { AlphaPayClient, AlphaPayPermissionError } from "alphapay-node";

const alphapay = new AlphaPayClient({ apiKey: process.env.ALPHAPAY_SECRET_KEY! });

async function verifierSoldes() {
  const { results } = await alphapay.balances.list();
  for (const balance of results) {
    console.log(`${balance.country} (${balance.currency}) : ${balance.available_amount} disponible`);
  }
}

async function grandLivre() {
  try {
    // ⚠️ Dashboard-only — lève AlphaPayPermissionError (403) via une clé API,
    // conservé ici pour documenter la forme réelle de l'endpoint.
    await alphapay.balances.ledgerEntries({ country: "BJ" });
  } catch (err) {
    if (err instanceof AlphaPayPermissionError) {
      console.log("Grand livre détaillé : consultable uniquement depuis le dashboard.");
    } else {
      throw err;
    }
  }
}
```

***

## 5. Clients (CRM)

```ts theme={null}
import { AlphaPayClient } from "alphapay-node";

const alphapay = new AlphaPayClient({ apiKey: process.env.ALPHAPAY_SECRET_KEY! });

async function gererClient() {
  // `country` est l'UUID d'un `geo.Country` côté API — PAS un code ISO2 ("BJ").
  const countryId = "00000000-0000-0000-0000-000000000000";

  const customer = await alphapay.customers.create({
    email: "client@exemple.com",
    full_name: "Ayaba Client",
    phone: "+22900000000",
    country: countryId,
  });

  await alphapay.customers.update(customer.id, { phone: "+22900000001" });

  const { results: transactions } = await alphapay.customers.transactions(customer.id, { page_size: 20 });
  console.log(`${transactions.length} transaction(s) pour ce client.`);

  return customer;
}

async function listerClients() {
  const { results } = await alphapay.customers.list({ search: "ayaba" });
  console.log(results.map((c) => c.email));
}
```

***

## 6. Webhooks

Réception et vérification d'un webhook entrant (exemple avec un serveur
HTTP natif Node ; adaptez `req`/`res` à Express/Fastify au besoin — le
principe ne change pas : toujours vérifier la signature sur le **corps
brut**, avant tout `JSON.parse`).

```ts theme={null}
import { createServer } from "node:http";
import {
  AlphaPayWebhookSignatureError,
  verifyWebhookSignature,
  type WebhookEvent,
} from "alphapay-node";

const WEBHOOK_SECRET = process.env.ALPHAPAY_WEBHOOK_SECRET!;

const server = createServer((req, res) => {
  const chunks: Buffer[] = [];
  req.on("data", (chunk) => chunks.push(chunk));
  req.on("end", () => {
    const rawBody = Buffer.concat(chunks).toString("utf8");

    try {
      const event: WebhookEvent = verifyWebhookSignature({
        payload: rawBody,
        signature: req.headers["x-webhook-signature"] as string,
        timestamp: req.headers["x-webhook-timestamp"] as string,
        secret: WEBHOOK_SECRET,
      });

      switch (event.event) {
        case "payment.succeeded":
          console.log("Paiement réussi :", event.data);
          break;
        case "payment.failed":
          console.log("Paiement échoué :", event.data);
          break;
        default:
          console.log("Événement reçu :", event.event);
      }

      res.writeHead(200).end("ok");
    } catch (err) {
      if (err instanceof AlphaPayWebhookSignatureError) {
        console.error("Webhook rejeté :", err.message);
        res.writeHead(400).end("signature invalide");
      } else {
        throw err;
      }
    }
  });
});

server.listen(3000);
```

Consultation en lecture (CRUD d'écriture réservé au dashboard) :

```ts theme={null}
import { AlphaPayClient } from "alphapay-node";

const alphapay = new AlphaPayClient({ apiKey: process.env.ALPHAPAY_SECRET_KEY! });

async function inspecterWebhooks() {
  const { results: webhooks } = await alphapay.webhookEndpoints.list();
  for (const webhook of webhooks) console.log(webhook.url, webhook.is_active);

  const { results: logs } = await alphapay.webhookEndpoints.logs.list({ status: "FAILED" });
  console.log(`${logs.length} livraison(s) échouée(s).`);
}
```

***

## 7. Reversements et transferts entre wallets

<Warning>
  Ces deux ressources sont entièrement inaccessibles via clé API (403 `dashboard_only` sur toutes leurs méthodes, y compris en lecture) — elles ne peuvent être pilotées que depuis le dashboard AlphaPay par un compte utilisateur connecté. Elles restent dans le SDK pour documenter la forme réelle des endpoints, pas pour un usage serveur automatisé.
</Warning>

```ts theme={null}
import { AlphaPayClient, AlphaPayPermissionError } from "alphapay-node";

const alphapay = new AlphaPayClient({ apiKey: process.env.ALPHAPAY_SECRET_KEY! });

async function tenterReversement() {
  try {
    await alphapay.settlements.create(
      { country: "BJ", requested_amount: 10000, payout_method: "mtn_bj" },
      { idempotencyKey: true },
    );
  } catch (err) {
    if (err instanceof AlphaPayPermissionError) {
      console.log("Reversement : dashboard uniquement, pas via clé API.");
    } else {
      throw err;
    }
  }
}

async function tenterTransfertWallet() {
  try {
    await alphapay.walletTransfers.create({
      from_country: "BJ",
      to_country: "CI",
      from_amount: 5000,
    });
  } catch (err) {
    if (err instanceof AlphaPayPermissionError) {
      console.log("Transfert entre wallets : dashboard uniquement, pas via clé API.");
    } else {
      throw err;
    }
  }
}
```

***

## 8. Clés API et whitelist IP

`apiKeys.list/create/get/revoke/delete` sont dashboard-only (403 via clé
API — une clé compromise ne doit pas pouvoir en créer d'autres). Seule
`ipWhitelist` fonctionne via clé API, et elle est requise pour les
payouts.

```ts theme={null}
import { AlphaPayClient } from "alphapay-node";

const alphapay = new AlphaPayClient({ apiKey: process.env.ALPHAPAY_SECRET_KEY! });

async function gererWhitelist() {
  const entry = await alphapay.apiKeys.ipWhitelist.create({
    ip_address: "203.0.113.42",
    label: "Serveur de production",
  });

  await alphapay.apiKeys.ipWhitelist.update(entry.id, { status: "ACTIVE" });

  const { results } = await alphapay.apiKeys.ipWhitelist.list();
  console.log(results.map((e) => `${e.ip_address} (${e.status})`));
}
```

***

## 9. Gestion des erreurs

Toutes les erreurs API héritent de `AlphaPayError` — vérifiez le type le
plus spécifique d'abord.

```ts theme={null}
import {
  AlphaPayClient,
  AlphaPayAuthenticationError,
  AlphaPayConnectionError,
  AlphaPayError,
  AlphaPayIdempotencyError,
  AlphaPayNotFoundError,
  AlphaPayPermissionError,
  AlphaPayRateLimitError,
  AlphaPayServerError,
  AlphaPayValidationError,
} from "alphapay-node";

const alphapay = new AlphaPayClient({ apiKey: process.env.ALPHAPAY_SECRET_KEY! });

async function appelSecurise() {
  try {
    return await alphapay.checkoutSessions.get("id-inexistant");
  } catch (err) {
    if (err instanceof AlphaPayNotFoundError) {
      console.log("Session introuvable.");
    } else if (err instanceof AlphaPayValidationError) {
      console.log("Erreurs par champ :", err.fieldErrors);
    } else if (err instanceof AlphaPayAuthenticationError) {
      console.log("Clé API invalide ou manquante.");
    } else if (err instanceof AlphaPayPermissionError) {
      console.log("Action réservée au dashboard :", err.code); // ex. "dashboard_only"
    } else if (err instanceof AlphaPayIdempotencyError) {
      console.log("Idempotency-Key déjà utilisée avec un payload différent.");
    } else if (err instanceof AlphaPayRateLimitError) {
      console.log(`Trop de requêtes, réessayez dans ${err.retryAfter ?? "quelques"}s.`);
    } else if (err instanceof AlphaPayServerError) {
      console.log("Erreur côté AlphaPay — déjà retentée automatiquement.");
    } else if (err instanceof AlphaPayConnectionError) {
      console.log("Impossible de joindre l'API (réseau/DNS/timeout).");
    } else if (err instanceof AlphaPayError) {
      console.log(`${err.status} ${err.code ?? ""}: ${err.message}`);
    } else {
      throw err; // erreur non-AlphaPay — ne jamais l'avaler silencieusement
    }
    return null;
  }
}
```

Le client retente déjà automatiquement (backoff exponentiel + gigue) sur
429/5xx/erreur réseau — configurable via `maxRetries` dans les options du
client. Les erreurs ci-dessus ne surviennent donc qu'après épuisement de
ces tentatives automatiques.
