API BluePay
Encaissez par MTN Mobile Money et Orange Money, reversez instantanément, répartissez entre bénéficiaires. Une API REST, des montants en FCFA entiers, des réponses JSON.
https://<votre-domaine-bluepay>. Toutes les requêtes passent en HTTPS. Créez votre compte dans l'espace marchand : une clé de test est fournie immédiatement.Authentification
Chaque requête porte votre clé secrète dans l'en-tête Authorization. Les clés sk_test_… agissent en mode test, les clés sk_live_… en production (disponibles après vérification de votre compte). Une clé peut être restreinte à des adresses IP.
curl https://VOTRE-DOMAINE-BLUEPAY/v1/balance \ -H "Authorization: Bearer sk_test_VotreCleSecrete"
Ne placez jamais une clé secrète dans une application mobile ou du code exécuté dans le navigateur. Créez les paiements depuis votre serveur.
Mode test
Le mode test simule les opérateurs : aucun argent réel ne circule et les soldes de test sont séparés de la production.
- Numéro finissant par
99: paiement refusé par le client. - Numéro finissant par
98: le client ne répond jamais (le paiement expire). - Numéro finissant par
97: reversement refusé (compte du bénéficiaire introuvable). - Tout autre numéro : succès en quelques secondes.
Conventions et erreurs
- Montants en FCFA entiers (
amount: 5000). Devise du compte :XAFau Cameroun. - Idempotence : envoyez un en-tête
Idempotency-Keyunique sur chaque création. Une même clé renvoie le même objet, sans double débit. - Identifiants préfixés :
pay_paiement,po_reversement,lnk_lien,evt_événement. - Erreurs : statut HTTP et corps
{"error": "CODE", "message": "…"}. 400 requête invalide, 401 clé invalide, 402 solde insuffisant, 403 action non autorisée, 404 introuvable, 409 conflit d'état, 429 limite atteinte, 503 opérateur indisponible.
Paiements
Avec channel et phone, BluePay envoie directement la demande au téléphone du client, qui valide avec son code Mobile Money. Sans eux, vous obtenez une page de paiement hébergée (voir plus bas).
curl https://VOTRE-DOMAINE-BLUEPAY/v1/payments \
-H "Authorization: Bearer sk_test_…" -H "Idempotency-Key: commande-1042" \
-H "Content-Type: application/json" \
-d '{ "amount": 15000, "channel": "MTN", "phone": "677123456",
"reference": "CMD-1042", "description": "Commande 1042",
"metadata": { "client": "8812" } }'
{ "id": "pay_7hQ2…", "object": "payment", "livemode": false, "status": "pending",
"amount": 15000, "currency": "XAF", "fee": 375, "fee_payer": "merchant",
"amount_charged": 15000, "net": 14625, "channel": "MTN", "phone": "+237677123456",
"reference": "CMD-1042", "checkout_url": null, "failure": null, … }
Statuts : open (page de paiement en attente du client), pending (demande envoyée), succeeded, failed, expired. Le solde est crédité dès succeeded. Option fee_payer: "customer" : les frais s'ajoutent au montant payé par le client.
Fiez-vous aux webhooks ou à cette lecture pour connaître l'état final, jamais au seul retour du client sur votre site.
Page de paiement hébergée
{ "amount": 20000, "description": "Abonnement annuel", "fee_payer": "customer",
"return_url": "https://votre-site.cm/merci" }
→ { "id": "pay_…", "status": "open", "checkout_url": "https://…/pay/pay_…?k=…" }
Redirigez le client vers checkout_url. Il choisit MTN ou Orange, saisit son numéro et valide. À la fin, il revient sur return_url?payment=pay_…&status=succeeded. La page expire après 30 minutes.
Liens de paiement
{ "title": "Cotisation 2027", "amount": 25000 } // montant fixe
{ "title": "Don", "amount": null, "min_amount": 500 } // montant libre
→ { "id": "lnk_…", "url": "https://…/l/k3m9x2p7qa", "uses": 0, … }
À partager par WhatsApp, SMS ou QR code. Aussi disponible sans code depuis l'espace marchand.
Remboursements
{ "amount": 2000, "reason": "Article manquant" } // sans montant : remboursement total
Le client est remboursé sur son numéro Mobile Money ; le montant est débité de votre solde. Événement payment.refunded à la réception.
Répartition entre bénéficiaires
Les plateformes créent des sous-comptes (vendeurs, chauffeurs, agents) puis répartissent chaque paiement automatiquement.
{ "name": "Boutique Kamga", "phone": "677555444" } → { "id": "4b1f…", … }
POST /v1/payments
{ "amount": 10000, "channel": "ORANGE", "phone": "699111222",
"split": [ { "merchant": "4b1f…", "amount": 8000 } ] }
Le sous-compte reçoit 8 000 FCFA ; vous recevez le reste après frais.
Solde
{ "object": "balance", "livemode": true, "currency": "XAF", "available": 1254300, "payouts_in_progress": 0 }
Reversements
{ "amount": 50000, "channel": "MTN", "phone": "677333222",
"beneficiary_name": "Fournisseur Ngo", "reference": "FAC-889" }
→ { "id": "po_…", "status": "processing", "fee": 500, … }
Les fonds sont réservés immédiatement ; si l'opérateur refuse, le montant et les frais sont recrédités automatiquement (payout.failed). Un plafond quotidien s'applique selon votre profil.
Paiements de masse
{ "label": "Salaires novembre", "approve": true,
"items": [ { "amount": 85000, "channel": "MTN", "phone": "677100001", "name": "Awa" },
{ "amount": 92000, "channel": "ORANGE", "phone": "699100002", "name": "Paul" } ] }
Si la double validation est activée sur votre compte, le lot reste en brouillon et doit être approuvé dans l'espace marchand par un autre membre.
Webhooks
{ "url": "https://votre-site.cm/bluepay/webhook", "events": ["payment.succeeded", "payout.failed"] }
→ { "id": "we_…", "secret": "whsec_…" } // secret affiché une seule fois
Événements : payment.succeeded, payment.failed, payment.expired, payment.refunded, payout.succeeded, payout.failed, payout_batch.approved. Répondez 2xx en moins de 10 secondes ; sinon BluePay réessaie pendant 72 heures. Chaque événement peut être renvoyé depuis l'espace marchand.
{ "id": "evt_…", "type": "payment.succeeded", "livemode": true, "created": "…", "data": { "id": "pay_…", "status": "succeeded", … } }
Vérifier la signature
L'en-tête BluePay-Signature vaut t=<horodatage>,v1=<signature>, où la signature est le HMAC-SHA256, avec votre secret, de t + "." + corps brut. Refusez les messages de plus de 5 minutes.
Node.js
import crypto from 'node:crypto';
function verify(secret, rawBody, header) {
const p = Object.fromEntries(header.split(',').map((x) => x.split('=')));
if (Math.abs(Date.now() / 1000 - Number(p.t)) > 300) return false;
const expected = crypto.createHmac('sha256', secret).update(`${p.t}.${rawBody}`).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(p.v1));
}
PHP
function bluepay_verify(string $secret, string $raw, string $header): bool {
parse_str(str_replace(',', '&', $header), $p);
if (abs(time() - (int)$p['t']) > 300) return false;
$expected = hash_hmac('sha256', $p['t'] . '.' . $raw, $secret);
return hash_equals($expected, $p['v1']);
}
BluePay · Blue Energy Holding · Douala