@lino_sap/campay-plugin
v1.0.0
Published
Plugin d'intégration CamPay pour paiements Mobile Money au Cameroun (MTN et Orange)
Downloads
124
Maintainers
Readme
@linoSAP/campay-plugin
Plugin d'intégration pour l'API de paiement CamPay (Mobile Money Cameroun : MTN et Orange).
Table des matières
- Installation
- Prérequis
- Configuration
- Utilisation
- Récupération du statut après paiement
- Webhook
- Polling
- Redirection après lien de paiement
- Gestion des erreurs
- Environnements
- Numéros de test
- API Reference
- Tests
- Liens utiles
- Licence
Installation
npm install @linoSAP/campay-pluginPrérequis
- Node.js >= 18.0.0
- Un compte CamPay avec des clés API (sandbox ou production)
- Pour le mode production : compte validé par CamPay (demande "Go Live")
Configuration
Via constructeur (recommandé)
import { CamPayClient } from '@linoSAP/campay-plugin';
const campay = new CamPayClient({
username: 'votre_app_username', // Nom d'utilisateur de l'application
password: 'votre_app_password', // Mot de passe de l'application
environment: 'DEV' // 'DEV' ou 'PROD'
});Via variables d'environnement
CAMPAY_USERNAME=votre_app_username
CAMPAY_PASSWORD=votre_app_password
CAMPAY_ENVIRONMENT=DEVconst campay = new CamPayClient(); // Lit automatiquement les variablesNote : Les paramètres explicites priment sur les variables d'environnement.
Utilisation
Initier un paiement (collect)
Déclenche une notification sur le téléphone du client.
const result = await campay.initCollect({
amount: '1500', // Montant en XAF (string)
currency: 'XAF', // Uniquement XAF
from: '2376XXXXXXXX', // Numéro client (format 2376XXXXXXXX)
description: 'Achat sur ma boutique',
external_reference: 'CMD-001' // Optionnel : votre référence interne
});
console.log('Référence CamPay:', result.reference);
console.log('Code USSD:', result.ussd_code);
console.log('Opérateur:', result.operator); // 'mtn' ou 'orange'Vérifier le statut d'une transaction (polling)
const status = await campay.getStatus('la_reference_campay');
if (status.status === 'SUCCESSFUL') {
console.log('✅ Paiement confirmé');
} else if (status.status === 'FAILED') {
console.log('❌ Paiement échoué');
} else {
console.log('⏳ En attente de confirmation');
}Créer un lien de paiement
Génère une URL à envoyer par SMS, email ou à afficher sur votre site.
const link = await campay.createPaymentLink({
amount: '5000',
currency: 'XAF',
description: 'Facture Décembre 2025',
external_reference: 'FACT-001',
redirect_url: 'https://monsite.com/merci', // Où rediriger après succès
failure_redirect_url: 'https://monsite.com/echec' // Optionnel : après échec
});
console.log('Lien de paiement:', link.link);
console.log('Référence CamPay:', link.reference);Récupération du statut après paiement
Votre application a besoin de savoir quand un paiement est confirmé. Voici les trois méthodes possibles.
Méthode 1 : Webhook (RECOMMANDÉ pour la production)
CamPay appelle automatiquement votre serveur dès qu'un paiement change de statut.
Configuration dans votre dashboard CamPay :
- Renseignez l'URL de votre webhook (ex:
https://votre-api.com/campay/webhook) - Choisissez les événements à écouter (
collect,disburse, etc.)
Dans votre application (Express.js exemple) :
import express from 'express';
const app = express();
app.post('/campay/webhook', express.json(), async (req, res) => {
const { reference, status, external_reference, amount, operator, signature } = req.body;
// Mettre à jour votre base de données
await db.orders.update({
where: { reference: external_reference },
data: {
paymentStatus: status,
campayRef: reference,
amount: amount,
operator: operator
}
});
// Répondre rapidement à CamPay (200 OK)
res.json({ received: true });
});Vérification de la signature JWT (sécurité) :
import jwt from 'jsonwebtoken';
function verifyWebhookSignature(signature: string, webhookSecret: string): boolean {
try {
const decoded = jwt.verify(signature, webhookSecret);
return true;
} catch {
return false;
}
}La webhookSecret est votre "Clé webhook de l'application" disponible sur le dashboard.
Méthode 2 : Polling (simple pour le développement)
Votre application interroge périodiquement l'API.
// Après avoir initié un paiement
const collect = await campay.initCollect({ ... });
let status = 'PENDING';
let attempts = 0;
const maxAttempts = 10;
while (status === 'PENDING' && attempts < maxAttempts) {
await new Promise(r => setTimeout(r, 3000)); // Attendre 3 secondes
const result = await campay.getStatus(collect.reference);
status = result.status;
attempts++;
}
if (status === 'SUCCESSFUL') {
// Valider la commande
} else if (status === 'FAILED') {
// Annuler la commande
}Méthode 3 : Redirection après lien de paiement
Quand vous utilisez createPaymentLink, CamPay redirige l'utilisateur vers votre redirect_url avec tous les paramètres de la transaction.
Exemple d'URL après redirection :
https://monsite.com/merci?reference=2f69b2e4-e6ad-421c-a5fc-a455de9dbd5b&status=SUCCESSFUL&amount=25.00¤cy=XAF&operator=MTN&phone_number=237673082287&external_reference=LINK-1776439725074&signature=eyJhbGciOiJIUzI1NiIs...Dans votre page de confirmation (Next.js / React / Express) :
// Avec Express
app.get('/merci', (req, res) => {
const { reference, status, amount, external_reference, signature } = req.query;
if (status === 'SUCCESSFUL') {
// Mettre à jour votre commande
db.orders.update({
where: { reference: external_reference },
data: { paymentStatus: 'SUCCESSFUL', campayRef: reference }
});
}
res.render('confirmation', { status, amount });
});Gestion des erreurs
Le plugin exporte plusieurs classes d'erreur :
import {
CamPayClient,
ValidationError,
AuthError,
ApiError,
RateLimitError,
NetworkError,
ConfigError
} from '@linoSAP/campay-plugin';
try {
await campay.initCollect(params);
} catch (error) {
if (error instanceof ValidationError) {
console.error('Donnée invalide:', error.message, 'Champ:', error.field);
} else if (error instanceof AuthError) {
console.error('Échec authentification:', error.message);
} else if (error instanceof RateLimitError) {
console.error('Trop de requêtes, réessayer dans:', error.retryAfterSeconds, 'secondes');
} else if (error instanceof ApiError) {
console.error('Erreur CamPay:', error.message, 'Code:', error.statusCode);
} else if (error instanceof NetworkError) {
console.error('Erreur réseau:', error.message);
} else if (error instanceof ConfigError) {
console.error('Erreur configuration:', error.message);
}
}Types d'erreurs disponibles
| Classe | Description | Quand ça arrive |
|--------|-------------|-----------------|
| ValidationError | Paramètre invalide | Numéro mal formaté, montant nul |
| AuthError | Échec d'authentification | Clés API erronées, token expiré |
| ApiError | Erreur métier CamPay | Solde insuffisant, opérateur indisponible |
| NetworkError | Problème réseau | Timeout, connexion impossible |
| ConfigError | Configuration invalide | Clés manquantes, environnement inconnu |
| RateLimitError | Trop de requêtes | Limite de débit dépassée |
Environnements
| Environnement | URL API | Montant max | Utilisation |
|---------------|---------|-------------|-------------|
| DEV | https://demo.campay.net | 25 XAF | Tests et développement |
| PROD | https://app.campay.net | Illimité | Production réelle |
Pour passer en production :
- Testez votre intégration en environnement
DEV - Cliquez sur "Go Live" dans votre dashboard CamPay
- Remplacez
environment: 'DEV'parenvironment: 'PROD' - Utilisez vos clés API de production
Numéros de test (Sandbox)
En environnement DEV, utilisez ces numéros pour simuler des comportements :
| Numéro | Opérateur | Comportement simulé |
|--------|-----------|---------------------|
| 237677777777 | MTN | Paiement réussi |
| 237677777770 | MTN | Paiement échoué |
| 237699999999 | Orange | Paiement réussi |
| 237699999990 | Orange | Paiement échoué |
⚠️ Ces numéros ne fonctionnent qu'en sandbox. En production, utilisez de vrais numéros clients.
API Reference
CamPayClient
new CamPayClient(config?: Partial<CamPayConfig>)CamPayConfig :
| Champ | Type | Requis | Défaut |
|-------|------|--------|--------|
| username | string | Oui | - |
| password | string | Oui | - |
| environment | 'DEV' \| 'PROD' | Non | 'DEV' |
initCollect(params)
initCollect(params: CollectParams): Promise<InitCollectResponse>CollectParams :
| Champ | Type | Requis | Description |
|-------|------|--------|-------------|
| amount | string \| number | Oui | Montant en XAF (ex: "1500" ou 1500) |
| currency | 'XAF' | Oui | Devise (uniquement XAF) |
| from | string | Oui | Numéro client (2376XXXXXXXX) |
| description | string | Oui | Description de la transaction |
| external_reference | string | Non | Votre référence interne |
InitCollectResponse :
| Champ | Type | Description |
|-------|------|-------------|
| reference | string | Référence unique CamPay |
| ussd_code | string | Code USSD pour le client |
| operator | string | 'mtn' ou 'orange' |
getStatus(reference)
getStatus(reference: string): Promise<TransactionStatusResponse>TransactionStatusResponse :
| Champ | Type | Description |
|-------|------|-------------|
| reference | string | Référence CamPay |
| external_reference | string? | Votre référence interne |
| status | 'PENDING' \| 'SUCCESSFUL' \| 'FAILED' | Statut |
| amount | number | Montant en XAF |
| currency | 'XAF' | Devise |
| operator | 'MTN' \| 'ORANGE' | Opérateur |
| operator_reference | string? | Référence chez l'opérateur |
createPaymentLink(params)
createPaymentLink(params: PaymentLinkParams): Promise<PaymentLinkResponse>PaymentLinkParams :
| Champ | Type | Requis | Description |
|-------|------|--------|-------------|
| amount | string \| number | Oui | Montant en XAF |
| currency | 'XAF' | Oui | Devise |
| description | string | Non | Description |
| external_reference | string | Non | Votre référence |
| redirect_url | string | Non | URL après paiement réussi |
| failure_redirect_url | string | Non | URL après échec |
PaymentLinkResponse :
| Champ | Type | Description |
|-------|------|-------------|
| link | string | URL du lien de paiement |
| reference | string | Référence CamPay |
Tests
Lancer les tests unitaires :
npm run testLancer les tests en mode watch (développement) :
npm run test:watchTester l'intégration réelle avec la sandbox :
npx tsx test-integration.tsLancer la page de demo de test graphique :
npm run demo : ("démarre le serveur local et ouvre directement la page dans le navigateur")
npm run demo:serve : ("démarre seulement le serveur")
npm run demo:open : ("ouvre la page si le serveur tourne déjà")Liens utiles
Licence
MIT
Auteur
Développé par linokouete — Yaoundé, Cameroun 🇨🇲 Contactez-moi : https://www.linkedin.com/in/lino-kouete-6538502b3/ ou [email protected]
Résumé des méthodes pour récupérer le statut
| Méthode | Recommandation | Quand l'utiliser | |---------|----------------|------------------| | Webhook | ✅ Production | Applications critiques, fort trafic | | Polling | ⚠️ Développement | Petits projets, tests | | Redirect URL | ✅ Complément | Paiements par lien, expérience utilisateur |
Meilleure pratique : Webhook + polling comme fallback. Le webhook notifie votre serveur en temps réel, le polling permet de récupérer le statut si le webhook échoue.
