@paywallex/node
v1.3.0
Published
SDK serveur officiel de l'API Paywallex /v1 : sessions de paiement Mobile Money, remboursements, litiges, abonnements, webhooks signés.
Maintainers
Readme
@paywallex/node
SDK serveur officiel de l'API Paywallex /v1. Il sert à encaisser par Orange Money, MTN MoMo, Moov Money, Wave ou le solde Paywallex, à rembourser, à suivre les litiges et les abonnements, et à vérifier les webhooks signés.
Node 18 ou plus récent, Bun ou Deno. Aucune dépendance.
Côté serveur uniquement. Ne mettez jamais une clé secrète
pwx_sk_…dans un navigateur ou une application mobile.
Installation
Installez le paquet :
npm install @paywallex/nodeCréer une session de paiement
Le SDK ne lit pas PAYWALLEX_KEY tout seul. Passez votre clé explicitement à la création du client. Une clé pwx_sk_test_… vise le bac à sable, une clé pwx_sk_live_… vise la production.
Créez une session, puis redirigez le client vers session.url :
import { Paywallex } from "@paywallex/node";
const paywallex = new Paywallex({ apiKey: process.env.PAYWALLEX_KEY! });
const session = await paywallex.checkoutSessions.create({
currency: "XOF",
line_items: [
{ name: "Attiéké poisson", unit_amount: 2500, quantity: 2 },
{ name: "Jus de bissap", unit_amount: 500 },
],
success_url: "https://maboutique.ci/merci",
metadata: { order: "CMD-1042" },
});
// session.amount = 5500, session.url = page de paiement PaywallexLes montants sont en XOF entiers : 2500 veut dire 2 500 F.
Facturation à l'usage
const plan = await paywallex.plans.create({ name: "Formule SMS", amount: 2000, currency: "XOF", interval: "monthly",
usage: { mode: "metered", unit: "SMS", unit_amount: 5, cap: 10000 } });
await paywallex.subscriptions.reportUsage(subscriptionId, { quantity: 120 }, { idempotencyKey: "sms-2026-10-25" });
// 402 usage_cap_reached : l'abonné doit relever son plafond dans Paywallex.Paiements partagés (plateformes)
Indiquez la part de chaque vendeur par son @identifiant : elle part sur son solde dès que l'argent de la vente est disponible chez vous. Vous gardez le reste, frais Paywallex déduits.
const session = await paywallex.checkoutSessions.create({
currency: "XOF",
amount: 20000,
splits: [
{ destination: "@chezpapa", amount: 15000, description: "Commande 1042" },
{ destination: "@livraison-rapide", amount: 2000 },
],
});
// session.splits[0].status : "pending", puis "paid" (événement payment_split.paid)Paywallex Elements (payer sur votre site)
Créez la session sur votre serveur, puis passez seulement client_secret à elements.js dans la page. Le client saisit son @identifiant ou scanne le QR, confirme sur son téléphone avec Face ID, et l'encadré passe à « Payé ». Sans l'app, il paie dans une fenêtre Paywallex.
const session = await paywallex.elementSessions.create({
mode: "subscription",
plan: planId,
allowed_origins: ["https://boutique.ci"],
});
// Envoyez session.client_secret (pwx_es_…) à la page, jamais votre clé pwx_sk_…<div id="paywallex"></div>
<script src="https://app.paywallex.app/elements.js"></script>
<script>
Paywallex.elements(clientSecret).on("success", (e) => location.assign("/merci")).mount("#paywallex");
</script>Confirmez toujours le résultat côté serveur : elementSessions.retrieve(id) (statut succeeded) ou le webhook de la vente (payment.succeeded, subscription.created).
Vérifier un webhook
Vérifiez la signature avant de traiter un événement. Le SDK compare en temps constant et refuse un horodatage de plus de 5 minutes.
Passez-lui le corps brut de la requête, pas un objet déjà lu par votre framework :
import express from "express";
import { Paywallex } from "@paywallex/node";
const app = express();
app.post("/webhooks/paywallex", express.raw({ type: "application/json" }), (req, res) => {
try {
const event = Paywallex.constructEvent(req.body, req.get("Paywallex-Signature"), process.env.PAYWALLEX_WEBHOOK_SECRET!);
if (event.type === "checkout_session.completed") { /* livrer la commande */ }
res.sendStatus(200);
} catch {
res.sendStatus(400);
}
});Les webhooks partent des plages 74.220.51.0/24 et 74.220.59.0/24 (liste à jour : GET /api/developer/webhook-ips). Ces plages sont partagées : la signature reste la seule preuve.
Les erreurs
Chaque erreur est une PaywallexError avec un code stable. Testez le code, jamais le message :
import { PaywallexError } from "@paywallex/node";
try {
await paywallex.refunds.create({ payment: "…", amount: 1000 });
} catch (e) {
if (e instanceof PaywallexError && e.code === "refund_not_eligible") { /* … */ }
console.log(e.status, e.code, e.param, e.requestId);
}Passez language: "en" au client pour recevoir les messages en anglais.
Nouvelles tentatives et idempotence
Le SDK réessaie 2 fois en cas de coupure réseau, de 429 ou d'erreur 5xx, en respectant Retry-After. Chaque POST garde la même clé d'idempotence d'une tentative à l'autre : un paiement ou un remboursement n'est jamais doublé.
Pour rejouer vous-même une requête sans risque, donnez votre propre clé :
await paywallex.refunds.create({ payment: "…" }, { idempotencyKey: `remboursement-${order.id}` });Ce que couvre le SDK
| Objet | Méthodes |
| --- | --- |
| checkoutSessions | create, retrieve |
| payments | list, retrieve, all (toutes les pages) |
| refunds | create, list, retrieve, all |
| disputes | list, retrieve, all, addEvidence |
| files | upload (justificatifs PDF, PNG, JPEG de 5 Mo au plus) |
| payouts, events, statements | list, retrieve (statements.csv) |
| customers, paymentLinks, invoices, pots | list, create, retrieve, update, action |
| plans, coupons, subscriptions | création, lecture, cancel, pause, resume, changePlan, manageLink, reportUsage, usage |
| elementSessions | create, retrieve, cancel (Paywallex Elements) |
| webhookEndpoints | list, create, del, rotateSecret |
| client | account(), balance(), networkStatus() |
Accepter un litige (rembourser) ou le contester se fait dans le portail Paywallex, jamais par l'API. Un abonnement ou une hausse de prix est toujours accepté par le client dans Paywallex.
Licence
MIT.
