SDK TypeScript
Le SDK TypeScript de Papi (@ibonia/papi-api-client) fournit un client typé pour l'API de paiement Papi. Il gère la communication HTTP, la sérialisation et les types de modèles afin que vous puissiez vous concentrer sur votre logique métier. Il fonctionne sur Node.js 18 ou une version ultérieure.
Installation
Le SDK est hébergé sur le dépôt de paquets Ibonia. Ajoutez le registre à votre .npmrc, puis installez le paquet.
Registre
@ibonia:registry=https://package-repository.ibonia.com/repository/ibonia-npm/
Yarn
yarn add @ibonia/papi-api-client
npm
npm install @ibonia/papi-api-client
Le SDK dépend d'axios (~1.7.9), qui est installé automatiquement.
Classes principales
| Export | Nature | Description |
|---|---|---|
Configuration | classe | Configuration du client. Contient le chemin de base ; créez-la une seule fois et partagez-la. |
PaymentLinksApi | classe | Méthodes pour créer des liens de paiement et les relire. |
PaymentsApi | classe | Méthode pour vérifier le statut d'un paiement à partir de la référence Papi. |
PaymentLinkRequest | type | Corps de la requête pour créer un lien de paiement. |
PaymentLinkResponse | type | Réponse retournée après la création d'un lien de paiement. |
PaymentLinkStatusResponse | type | Un lien de paiement relu par référence, avec son état et l'issue du paiement. Mêmes noms de champs que PaymentResponse, sauf la référence Papi, nommée papiPaymentReference. |
PaymentResponse | type | Charge envoyée par Papi à votre endpoint de notification. |
ErrorResponse | type | Corps d'erreur retourné par l'API. |
Chaque méthode retourne une AxiosResponse, et chaque corps Papi est une enveloppe. La charge utile est donc toujours response.data.data.
Utilisation
Créez une seule Configuration et passez-la aux classes d'API dont vous avez besoin.
import { Configuration, PaymentLinksApi, PaymentsApi } from '@ibonia/papi-api-client';
const config = new Configuration({ basePath: 'https://app.papi.mg' });
const links = new PaymentLinksApi(config);
const payments = new PaymentsApi(config);
Les champs amount, clientName, reference et description sont obligatoires. notificationUrl est facultatif mais fortement recommandé.
import { PaymentLinkRequest } from '@ibonia/papi-api-client';
const request: PaymentLinkRequest = {
amount: 15000,
clientName: 'Nom du client',
reference: 'ORDER-123',
description: 'Paiement pour la commande #123',
notificationUrl: 'https://votreapp.com/payment-notify',
validDuration: 2, // le lien expire après 2 heures
};
Pour restreindre le lien à un seul prestataire, utilisez l'énumération de prestataires :
import { PaymentLinkRequestProviderEnum } from '@ibonia/papi-api-client';
request.provider = PaymentLinkRequestProviderEnum.Mvola;
Appelez createPaymentLink avec votre clé API (disponible dans le tableau de bord) et la requête.
const res = await links.createPaymentLink(apiKey, request);
const link = res.data.data; // PaymentLinkResponse
const paymentUrl = link.paymentLink; // redirigez l'utilisateur ici
const notifToken = link.notificationToken; // stockez ceci pour la vérification
const myReference = link.paymentReference; // votre propre référence, renvoyée telle quelle
Redirigez le client vers paymentUrl. Après le paiement, Papi appellera votre notificationUrl avec le résultat.
Rappeler createPaymentLink avec la même reference tant que le lien est actif retourne ce même lien (même URL, même jeton) : un réessai est sans risque. La même reference avec un montant ou une devise différents répond 409.
try {
const res = await links.createPaymentLink(apiKey, request);
} catch (err: any) {
const status = err.response?.status; // 409, 401, 400…
const message = err.response?.data?.error?.message;
console.error(status, message);
}
Exposez un endpoint POST qui lit un corps PaymentResponse. Papi appelle cet endpoint après chaque tentative de paiement. Vérifiez la notification avant toute autre action.
import express from 'express';
import { PaymentResponse, PaymentResponsePaymentStatusEnum } from '@ibonia/papi-api-client';
const app = express();
app.use(express.json());
app.post('/payment-notify', (req, res) => {
const notification = req.body as PaymentResponse;
// Valeurs stockées lors de la création du lien
const expectedToken = 'xyz789';
const expectedReference = 'ORDER-123';
const tokenMatches = notification.notificationToken === expectedToken;
const refMatches = notification.merchantPaymentReference === expectedReference;
if (!tokenMatches || !refMatches) {
return res.status(403).end();
}
switch (notification.paymentStatus) {
case PaymentResponsePaymentStatusEnum.Success:
// Marquer la commande comme payée
break;
case PaymentResponsePaymentStatusEnum.Failed:
// Gérer l'échec
break;
case PaymentResponsePaymentStatusEnum.Pending:
// Attendre la notification finale
break;
}
res.status(200).end();
});
Une notification n'est envoyée qu'une fois. Si votre endpoint était indisponible ou si l'appel s'est perdu, interrogez Papi directement : getPaymentLink relit le lien par votre référence marchande (la reference envoyée à la création, merchantPaymentReference dans les notifications) et retourne la même issue que celle qu'aurait portée la notification.
import { PaymentLinkStatusResponseLinkStatusEnum } from '@ibonia/papi-api-client';
const res = await links.getPaymentLink(apiKey, 'ORDER-123');
const link = res.data.data; // PaymentLinkStatusResponse
switch (link.linkStatus) {
case PaymentLinkStatusResponseLinkStatusEnum.Paid:
confirmOrder(link.papiPaymentReference); // paymentStatus vaut SUCCESS
break;
case PaymentLinkStatusResponseLinkStatusEnum.Active:
// encore payable ; paymentStatus décrit la dernière tentative
break;
case PaymentLinkStatusResponseLinkStatusEnum.Expired:
case PaymentLinkStatusResponseLinkStatusEnum.Disabled:
issueNewLinkOrCancel();
break;
}
getPaymentLink répond 404 quand aucun lien de votre boutique ne porte cette référence, et 401 quand la clé API est incorrecte.
Lorsque vous disposez déjà de la référence de Papi pour le paiement — paymentReference sur une notification, ou papiPaymentReference sur un lien relu —, vous pouvez interroger le paiement lui-même. Cet appel ne prend aucune clé API.
import { PaymentResponsePaymentStatusEnum } from '@ibonia/papi-api-client';
const res = await payments.getPaymentStatus('mvola', papiPaymentReference);
const payment = res.data.data; // PaymentResponse
if (payment.paymentStatus === PaymentResponsePaymentStatusEnum.Success) {
confirmOrder(payment.merchantPaymentReference);
}
Le premier argument est le prestataire par lequel le paiement est passé, sous forme de chaîne : 'mvola', 'airtel-money', 'orange-money' ou 'bred-card'. Une référence de paiement inconnue répond 400.
Champs de PaymentLinkRequest
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
amount | number | ✓ | Montant du paiement (minimum 300 MGA). |
clientName | string | ✓ | Nom complet du client. |
reference | string | ✓ | Votre identifiant unique pour ce paiement. |
description | string | ✓ | Courte description du paiement (max 255 caractères). |
notificationUrl | string | ✗ | Endpoint qui reçoit les notifications de statut de paiement. Fortement recommandé ; sans lui, utilisez getPaymentLink pour relire l'issue. |
payerEmail | string | ✗ | Adresse e-mail du client. |
payerPhone | string | ✗ | Numéro de téléphone du client. |
successUrl | string | ✗ | URL de redirection après un paiement réussi. |
failureUrl | string | ✗ | URL de redirection après un paiement échoué. |
validDuration | number | ✗ | Durée de validité du lien en heures (défaut : 1). |
provider | PaymentLinkRequestProviderEnum | ✗ | Restreindre à un seul prestataire : Mvola, AirtelMoney, OrangeMoney ou Bred. |
displayCurrency | string | ✗ | Devise affichée au payeur (MGA uniquement, défaut MGA). |
isTestMode | boolean | ✗ | true pour marquer la transaction comme test dans le tableau de bord. |
testReason | string | ✗ | Raison affichée dans le tableau de bord lorsque le mode test est activé. |
successUrl et failureUrl vont de pair : envoyez les deux ou aucun.
Champs de PaymentResponse
| Champ | Type | Description |
|---|---|---|
paymentStatus | PaymentResponsePaymentStatusEnum | Success, Pending ou Failed. |
paymentMethod | string | Fournisseur utilisé : MVOLA, AIRTEL_MONEY, ORANGE_MONEY, BRED. |
currency | string | Toujours MGA. |
displayCurrency | string | La devise que le payeur a vue sur le formulaire (toujours MGA aujourd'hui). |
amount | number | Montant payé. |
estimatedAmount | number | Le montant exprimé en displayCurrency (égal à amount tant que seul MGA est pris en charge). |
fee | number | Frais de transaction déduits. |
clientName | string | Nom du client. |
description | string | Description du paiement. |
merchantPaymentReference | string | Votre reference de la requête initiale. |
paymentReference | string | La référence de Papi pour ce paiement (un UUID). Passez-la à getPaymentStatus. |
notificationToken | string | Token de la réponse initiale du lien — utilisez-le pour vérifier l'authenticité. |
message | string | Motif d'échec, s'il y en a un. |
payerEmail | string | E-mail du client (si fourni). |
payerPhone | string | Téléphone du client (si fourni). |
Champs de PaymentLinkStatusResponse
| Champ | Type | Description |
|---|---|---|
linkStatus | PaymentLinkStatusResponseLinkStatusEnum | Active, Expired, Paid ou Disabled. Paid l'emporte sur Disabled et Expired. |
paymentStatus | PaymentResponsePaymentStatusEnum | Success, Pending ou Failed — les mêmes valeurs que la notification. null tant que personne n'a tenté de payer. |
paymentMethod | string | Prestataire utilisé par le payeur. null tant qu'aucune tentative de paiement n'existe. |
currency | string | Toujours MGA. |
displayCurrency | string | Devise affichée au payeur. |
amount | number | Montant du lien. |
clientName | string | Nom du client. |
description | string | Description du paiement. |
merchantPaymentReference | string | Votre reference de la requête initiale. |
papiPaymentReference | string | Référence Papi de la tentative de paiement (le paymentReference de la notification). null tant qu'aucune tentative n'existe. |
notificationToken | string | Jeton retourné à la création du lien. |
message | string | Motif d'échec de la tentative de paiement, s'il y en a un. |
payerEmail | string | E-mail du client (si fourni). |
payerPhone | string | Téléphone du client (si fourni). |
paymentLink | string | L'URL de paiement. |
shortLink | string | Forme courte de l'URL de paiement, si elle a été générée. |
linkCreationDateTime | number | Date de création, en millisecondes epoch. |
linkExpirationDateTime | number | Date d'expiration, en millisecondes epoch. |
isTestMode | boolean | Indique si le lien a été marqué comme test. |
Exemple complet (Express)
L'exemple ci-dessous illustre une application Express complète qui crée un lien de paiement et gère la notification de callback.
import express from 'express';
import {
Configuration,
PaymentLinksApi,
PaymentLinkRequest,
PaymentResponse,
PaymentResponsePaymentStatusEnum,
} from '@ibonia/papi-api-client';
const API_KEY = process.env.PAPI_API_KEY!;
const APP_DOMAIN = process.env.APP_DOMAIN!;
const config = new Configuration({ basePath: 'https://app.papi.mg' });
const links = new PaymentLinksApi(config);
const app = express();
app.use(express.json());
app.post('/checkout', async (req, res) => {
const { amount, reference, clientName, email, phone } = req.body;
const request: PaymentLinkRequest = {
amount,
clientName,
reference,
description: `Paiement ${reference}`,
payerEmail: email,
payerPhone: phone,
notificationUrl: `${APP_DOMAIN}/payment-notify`,
validDuration: 2,
};
try {
const created = await links.createPaymentLink(API_KEY, request);
const link = created.data.data;
// Persistez link.notificationToken avec la référence de commande
// pour pouvoir le vérifier lors de la réception de la notification.
await storeNotificationToken(reference, link.notificationToken);
res.json({ paymentLink: link.paymentLink });
} catch (err: any) {
res.status(err.response?.status ?? 500).json({
message: err.response?.data?.error?.message ?? 'Erreur inattendue',
});
}
});
app.post('/payment-notify', async (req, res) => {
const notification = req.body as PaymentResponse;
const storedToken = await lookupNotificationToken(
notification.merchantPaymentReference,
);
if (storedToken !== notification.notificationToken) {
return res.status(403).end();
}
if (notification.paymentStatus === PaymentResponsePaymentStatusEnum.Success) {
await confirmOrder(notification.merchantPaymentReference);
}
res.status(200).end();
});
app.listen(3000);
// ... implémentations de storeNotificationToken, lookupNotificationToken et confirmOrder