Documentation API ElyonPay
Bienvenue dans la documentation technique de l'API ElyonPay. Cette référence vous permet d'intégrer les paiements Mobile Money, Visa, Mastercard et virements dans votre application en quelques lignes de code.
https://api.elyonpay.net/api (Sandbox) / https://api.elyonpay.org/api (Production)Toutes les requêtes doivent être envoyées en HTTPS. Les requêtes HTTP non sécurisées sont refusées. Voir la section Environnements pour plus de détails.
Fonctionnalités clés
- Mobile Money unifié — MTN MoMo, Orange Money, Wave, Moov, Airtel Money
- Cartes bancaires — Visa, Mastercard avec 3D Secure, conformité PCI DSS niveau 1
- Intégration simple — Lien de paiement + iframe, vérification active du statut
- Sandbox complet — Testez sans frais avec des données simulées
- Multi-devises — XAF, XOF, EUR, USD, GBP, NGN, CDF, KES
- SDKs officiels — JavaScript, PHP, Python, Java, React Native, Flutter
Démarrage rapide
Suivez ces 3 étapes pour effectuer votre premier paiement :
- Contactez-nous pour obtenir votre clé API
- Installez le SDK de votre langage ou utilisez cURL directement
- Appelez
POST /api/request-to-pay/payment/linkavec le montant et les URLs de redirection
curl -X POST https://api.elyonpay.org/api/request-to-pay/payment/link \
-H "Authorization: Bearer <your_jwt_token>" \
-H "Content-Type: application/json" \
-H "language: fr" \
-d '{
"amount": 15000,
"user_lang": "fr",
"msisdn": "+237600000000",
"success": "https://yoursite.com/success",
"error": "https://yoursite.com/error"
}'Authentification
L'API ElyonPay utilise l'authentification JWT (JSON Web Token). Obtenez votre token via l'endpoint de login, puis incluez-le dans chaque requête.
| Paramètre | Type | Requis | Description |
|---|---|---|---|
| username | string | Requis | Email du compte marchand |
| password | string | Requis | Mot de passe du compte |
| role | string | Requis | ROLE_MERCHANT_ADMIN |
curl -X POST https://api.elyonpay.org/api/login \
-H "Content-Type: application/json" \
-d '{
"username": "you@example.com",
"password": "***",
"role": "ROLE_MERCHANT_ADMIN"
}'Réponse
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9...."
}Utilisation dans l'en-tête HTTP
Authorization: Bearer <your_jwt_token>Clé API (accès scopé)
En alternative au JWT, une clé API serveur-à-serveur authentifie vos appels de paiement. Elle est disponible pour tous les comptes marchands et se récupère — ou se régénère — depuis votre tableau de bord.
Authorization: Bearer pk_live_...Environnements
ElyonPay propose deux environnements distincts pour vous permettre de développer et tester en toute sécurité avant la mise en production.
URLs de base
| Environnement | URL de base | Description |
|---|---|---|
Sandbox | https://api.elyonpay.net/api | Aucun débit réel — données simulées |
Production | https://api.elyonpay.org/api | Transactions réelles |
Numéros de test Mobile Money
| Numéro | Réseau | Comportement simulé |
|---|---|---|
+237600000001 | MTN MoMo | Paiement réussi |
+237600000002 | MTN MoMo | Solde insuffisant |
+237690000001 | Orange Money | Paiement réussi |
+237690000002 | Orange Money | Timeout simulé |
+221700000001 | Wave | Paiement réussi |
Sandbox & Tests
L'environnement sandbox vous permet de tester l'intégration complète sans effectuer de vrais paiements. Connectez-vous sur api.elyonpay.net — aucun débit réel ne sera effectué.
Comptes de test par pays
Utilisez ces identifiants pour tester l'intégration sur l'environnement sandbox. Chaque compte est associé à un pays et une devise spécifiques.
| Téléphone | Mot de passe | |
|---|---|---|
+237683191284 | demo+23733@elyonpay.com | CompteDemo1 |
| Téléphone | Mot de passe | |
|---|---|---|
+2250758789285 | demo+225@elyonpay.com | CompteDemo1 |
| Téléphone | Mot de passe | |
|---|---|---|
+33895555555 | demo+33@elyonpay.com | CompteDemo1 |
Limites du sandbox
- Les transactions sandbox n'engendrent aucuns frais
- Les données sont réinitialisées tous les 30 jours
- Limite de 1 000 requêtes/heure par clé test
Passer en production
- Complétez la vérification KYB (Know Your Business) dans votre tableau de bord
- Changez l'URL de base de
api.elyonpay.netversapi.elyonpay.org - Vérifiez que vos URLs de redirection (success / error) sont en HTTPS
- Configurez vos alertes de solde bas
Format des requêtes
Toutes les requêtes et réponses utilisent le format JSON. L'en-tête Content-Type: application/json est obligatoire pour les requêtes avec un body.
En-têtes requis
| En-tête | Valeur | Obligatoire |
|---|---|---|
Authorization | Bearer {votre_token_jwt} | Requis |
Content-Type | application/json | Requis (POST) |
Accept | application/json | Optionnel |
Idempotency-Key | UUID unique | Recommandé |
Format de réponse standard
{
"success": true,
"data": {
"id": "pay_1A2B3C4D5E6F",
"status": "pending",
"amount": 15000,
"currency": "XAF"
},
"meta": {
"request_id": "req_xxxxxxx",
"timestamp": "2024-03-24T10:15:00Z"
}
}Créer un paiement
Génère un lien de paiement sécurisé. Le client est redirigé vers ce lien pour choisir sa méthode de paiement (Mobile Money, carte bancaire) et finaliser la transaction.
Paramètres du body
| Paramètre | Type | Requis | Description |
|---|---|---|---|
| amount | number | Requis | Montant du paiement |
| user_lang | string | Requis | Langue de l'interface de paiement (fr, en) |
| msisdn | string | Requis | Numéro de téléphone au format international : +237600000000 |
| success | string | Requis | URL de redirection en cas de succès |
| error | string | Requis | URL de redirection en cas d'erreur |
| webhook | string | Optionnel | URL HTTPS notifiée à chaque changement d'état de la transaction (voir la section Webhooks) |
| external_id | string | Optionnel | Votre référence de commande, 1 à 64 caractères, unique par marchand. Renvoyée dans GET /api/transactions/{uuid} (externalId) et dans les webhooks (data.external_id) |
| customer | object | Optionnel | Votre client final, en un bloc : { account_ref, name, email, phone, locale }. Le même bloc est accepté sur tous les endpoints d'encaissement ; renvoyé dans GET /api/transactions/{uuid} (playerRef, playerName, customerEmail) et dans les webhooks (data.customer) |
| customer.account_ref | string | Optionnel | Identifiant du client chez vous (joueur, abonné, compte…), 1 à 64 caractères. Obligatoire pour un marchand en mode wallet (422 PLAYER_REF_REQUIRED) : il lie le dépôt au client, puis le décaissement au numéro qui a déposé. Alias accepté : player_ref |
| customer.name | string | Optionnel | Nom déclaré du client, 128 caractères au plus. Affiché dans votre espace marchand ; jamais utilisé comme clé. Alias accepté : player_name |
| customer.email | string | Optionnel | E-mail du client, 190 caractères au plus, validé (422 sinon). Conservé sur la transaction pour les messages qui lui sont adressés |
| customer.phone | string | Optionnel | Numéro déclaré du client, au format international. Équivalent de msisdn : l'un des deux suffit. Au paiement, le numéro réellement débité le remplace |
| customer.locale | string | Optionnel | Langue des messages adressés au client (fr, en, en-GB…) |
| onbehalfof | string | number | Optionnel | Encaisser pour le compte d'un sous-marchand : son id ou son code partner. Réservé aux marchands plateforme (voir la section Paiements plateforme) |
| fees | number | Optionnel | Commission plateforme à prélever (même unité que amount). Si absent, les frais par défaut du sous-marchand puis de la plateforme s'appliquent |
Réponse
{
"url": "https://app.elyonpay.org/t/CI032667e43abf11?success=https://www.success.com&error=https://www.error.com"
}Exemples de code
cURL
curl -X POST https://api.elyonpay.org/api/request-to-pay/payment/link \
-H "Authorization: Bearer <your_jwt_token>" \
-H "Content-Type: application/json" \
-H "language: fr" \
-d '{
"amount": 15000,
"user_lang": "fr",
"msisdn": "+237600000001",
"success": "https://yoursite.com/success",
"error": "https://yoursite.com/error",
"webhook": "https://yoursite.com/webhooks/elyonpay",
"external_id": "CMD-2026-0042",
"customer": {
"account_ref": "PLR-1001",
"name": "Jean Mballa",
"email": "jean@example.com"
}
}'Node.js / JavaScript
const response = await fetch('https://api.elyonpay.org/api/request-to-pay/payment/link', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json',
'language': 'fr'
},
body: JSON.stringify({
amount: 15000,
user_lang: 'fr',
msisdn: '+237600000001',
success: 'https://yoursite.com/success',
error: 'https://yoursite.com/error',
webhook: 'https://yoursite.com/webhooks/elyonpay',
external_id: 'CMD-2026-0042',
customer: {
account_ref: 'PLR-1001',
name: 'Jean Mballa',
email: 'jean@example.com'
}
})
});
const data = await response.json();
console.log(data.url); // Payment link to redirect the customerPHP
$ch = curl_init('https://api.elyonpay.org/api/request-to-pay/payment/link');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
'language: fr'
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => 15000,
'user_lang' => 'fr',
'msisdn' => '+237600000001',
'success' => 'https://yoursite.com/success',
'error' => 'https://yoursite.com/error',
'webhook' => 'https://yoursite.com/webhooks/elyonpay',
'external_id' => 'CMD-2026-0042',
'customer' => [
'account_ref' => 'PLR-1001',
'name' => 'Jean Mballa',
'email' => 'jean@example.com'
]
])
]);
$response = json_decode(curl_exec($ch), true);
echo $response['url']; // Payment linkTransactions
Récupérez l'historique complet de vos transactions : paiements reçus, remboursements, frais.
Retourne la liste paginée de toutes vos transactions. Vous pouvez également récupérer le détail d'une transaction spécifique via son identifiant.
Détail d'une transaction
curl -X GET https://api.elyonpay.org/api/transactions/1553 \
-H "Authorization: Bearer <your_jwt_token>" \
-H "Accept: application/json"Exemple de réponse
{
"id": 1553,
"PSPid": "FR033169cbd9c1f1",
"uuid": "FR033169cbd9c1f1",
"creation_date": "2026-03-31T14:27:13+00:00",
"validation_date": null,
"delivery_date": null,
"amount": 100,
"totalCustomer": 100,
"state": "CREATED",
"ticket_type": "SMS_EMAIL",
"operation_code": "03",
"payer_phone_number": "+33782112127",
"beneficiary_phone_number": "+33782112127",
"payer_currency": "EUR",
"beneficiary_currency": "EUR",
"payer_country": "FR",
"beneficiary_country": "FR",
"payment": {
"name": "MOLLIE"
},
"customer": {
"phoneNumber": "+33782112127",
"email": "client@example.com"
},
"merchant": {
"id": 258,
"name": "Ma Boutique",
"city": "Paris",
"address": "10 Rue de la Paix",
"zipCode": "75002",
"type": "individual",
"country_code": "FR",
"country": "France"
},
"transactionStates": [
{
"state": "CREATED",
"date": "2026-03-31T14:27:14+00:00"
}
],
"commission_operateur": 0,
"commission_marchand": 0,
"commission_pays": 0,
"commission_customer": 0,
"deliveryPrice": 0
}Champs de la réponse
| Champ | Type | Description |
|---|---|---|
| id | integer | Identifiant interne de la transaction |
| PSPid | string | Identifiant unique ElyonPay (ex : FR033169cbd9c1f1) |
| uuid | string | Identifiant unique — même valeur que PSPid |
| creation_date | datetime | Date de création (ISO 8601 avec timezone) |
| validation_date | datetime|null | Date de validation du paiement — null si non encore validé |
| delivery_date | datetime|null | Date de livraison — null si non applicable |
| amount | number | Montant de la transaction dans la devise du bénéficiaire |
| totalCustomer | number | Montant total facturé au client (commissions incluses) |
| state | string | Statut actuel : CREATED, PENDING, WAITING_FOR_PAYMENT, ACCEPTED, DELIVERED, REJECTED, DECLINED, CANCELLED |
| ticket_type | string | Type de notification : SMS_EMAIL, SMS, EMAIL |
| operation_code | string | Code d'opération interne |
| payer_phone_number | string | Numéro de téléphone du payeur (format international) |
| externalId | string|null | Votre référence de commande (external_id), telle que fournie à la création |
| playerRef | string|null | Identifiant de votre client final (customer.account_ref), tel que fourni à la création |
| playerName | string|null | Nom déclaré du client final (customer.name) |
| customerEmail | string|null | E-mail déclaré du client final (customer.email) |
| beneficiary_phone_number | string | Numéro de téléphone du bénéficiaire |
| payer_currency | string | Devise du payeur (code ISO 4217 : EUR, XAF, XOF...) |
| beneficiary_currency | string | Devise du bénéficiaire |
| payer_country | string | Code pays du payeur (ISO 3166 : FR, CM, CI...) |
| beneficiary_country | string | Code pays du bénéficiaire |
| payment.name | string | Fournisseur de paiement utilisé (MOLLIE, MTN, ORANGE...) |
Objet customer
| Champ | Type | Description |
|---|---|---|
| phoneNumber | string | Numéro de téléphone du client |
| string | Adresse email du client |
Objet merchant
| Champ | Type | Description |
|---|---|---|
| id | integer | Identifiant du marchand |
| name | string | Nom du marchand ou de l'enseigne |
| city | string | Ville du marchand |
| address | string | Adresse postale |
| zipCode | string | Code postal |
| type | string | Type de compte : individual, company |
| country_code | string | Code pays ISO 3166 (FR, CM, CI...) |
| country | string | Nom du pays |
Tableau transactionStates
Historique des changements de statut de la transaction, du plus ancien au plus récent.
| Champ | Type | Description |
|---|---|---|
| state | string | Statut à cet instant (CREATED, PENDING, WAITING_FOR_PAYMENT, ACCEPTED, DELIVERED...) |
| date | datetime | Date du changement de statut |
Commissions
| Champ | Type | Description |
|---|---|---|
| commission_operateur | number | Commission opérateur télécom |
| commission_marchand | number | Commission marchand |
| commission_pays | number | Commission pays |
| commission_customer | number | Commission appliquée au client |
| deliveryPrice | number | Frais de livraison |
Custom Checkout
Le Custom Checkout vous permet de construire votre propre interface de paiement tout en utilisant les APIs ElyonPay. Contrairement au lien de paiement simple (redirect), vous gardez le contrôle complet de l'expérience utilisateur.
Flux en 4 étapes
Récupérer les méthodes de paiement
Appelez GET /api/public/configuration/payments/{countryCode} pour obtenir la liste des méthodes disponibles dans le pays du client.
Afficher votre checkout
Présentez le récapitulatif et les méthodes de paiement dans votre propre interface. Le client choisit comment payer.
Initier le paiement
Envoyez la requête de paiement direct (Mobile Money ou carte bancaire) avec la méthode choisie.
Suivre le statut
Interrogez GET /api/transactions/{id} toutes les 3 à 5 secondes jusqu'à obtenir un statut terminal (DELIVERED, FAILED, CANCELLED).
Diagramme de séquence
sequenceDiagram
participant Customer
participant Merchant
participant ElyonPay
Customer->>Merchant: Browses products
Merchant->>ElyonPay: GET /api/public/configuration/payments/{country}
ElyonPay-->>Merchant: Available payment methods
Customer->>Merchant: Selects payment method
Merchant->>ElyonPay: POST /api/mobile-money/payment/request
ElyonPay-->>Merchant: PaymentResult (transactionId, state)
loop Poll every 3-5s (max 2 min)
Merchant->>ElyonPay: GET /api/transactions/{id}
ElyonPay-->>Merchant: Transaction state
end
Merchant->>Customer: Displays confirmation or error
Méthodes de paiement
Récupérez dynamiquement la liste des méthodes de paiement disponibles dans un pays donné. Cet endpoint est public — aucune authentification n'est requise.
Paramètre de chemin
| Paramètre | Type | Requis | Description |
|---|---|---|---|
| countryCode | string | Requis | Code pays ISO 3166 — voir le tableau ci-dessous |
Codes pays supportés
| Code | Pays |
|---|---|
CM | Cameroun |
CI | Côte d'Ivoire |
GA | Gabon |
FR | France |
TG | Togo |
BJ | Bénin |
Exemple de réponse
{
"payments": [
{
"name": "ORANGE_MONEY",
"label": "Orange Money",
"type": "MOBILE_MONEY",
"available": true
},
{
"name": "MTN_MONEY",
"label": "MTN Mobile Money",
"type": "MOBILE_MONEY",
"available": true
},
{
"name": "STRIPE",
"label": "Carte bancaire (Visa / Mastercard)",
"type": "CARD",
"available": true
}
]
}curl https://api.elyonpay.org/api/public/configuration/payments/CMPaiement direct
Initiez un paiement directement depuis votre serveur sans redirection vers une page ElyonPay. Le client reste sur votre site pendant toute la transaction.
Mobile Money
Paramètres du body
| Paramètre | Type | Requis | Description |
|---|---|---|---|
| amount | number | Requis | Montant du paiement dans la devise locale |
| user_lang | string | Requis | Langue de l'utilisateur (fr, en) |
| merchant_name | string | Requis | Nom du marchand affiché au client |
| merchant_id | integer | Requis | Identifiant du marchand |
| currency | string | Requis | Code devise ISO 4217 (XAF, XOF, EUR...) |
| country_name | string | Requis | Nom du pays du client |
| payment_method | string | Requis | Doit correspondre au champ name retourné par GET /api/public/configuration/payments/{countryCode} |
| msisdn | string | Requis | Numéro de téléphone du client (format international) |
| transaction | string | Optionnel | Référence de transaction externe (optionnel) |
cURL
curl -X POST https://api.elyonpay.org/api/mobile-money/payment/request \
-H "Authorization: Bearer <your_jwt_token>" \
-H "Content-Type: application/json" \
-d '{
"amount": 15000,
"user_lang": "fr",
"merchant_name": "Ma Boutique",
"merchant_id": 207,
"currency": "XAF",
"country_name": "Cameroon",
"payment_method": "ORANGE_MONEY",
"msisdn": "+237690000001"
}'Node.js / JavaScript
const response = await fetch('https://api.elyonpay.org/api/mobile-money/payment/request', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: 15000,
user_lang: 'fr',
merchant_name: 'Ma Boutique',
merchant_id: 207,
currency: 'XAF',
country_name: 'Cameroon',
payment_method: 'ORANGE_MONEY',
customer_msisdn: '+237690000001'
})
});
const result = await response.json();
// Start polling result.data.uuid for statusPHP
$ch = curl_init('https://api.elyonpay.org/api/mobile-money/payment/request');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $token,
'Content-Type: application/json'
],
CURLOPT_POSTFIELDS => json_encode([
'amount' => 15000,
'user_lang' => 'fr',
'merchant_name' => 'Ma Boutique',
'merchant_id' => 207,
'currency' => 'XAF',
'country_name' => 'Cameroon',
'payment_method' => 'ORANGE_MONEY',
'customer_msisdn' => '+237690000001'
])
]);
$response = json_decode(curl_exec($ch), true);
// Start polling $response['data']['uuid'] for statusCarte bancaire
Même payload que le paiement Mobile Money, à une exception près : payment_method ne désigne pas un opérateur mais le prestataire qui traite la carte. S'y ajoutent deux paramètres pour contrôler la redirection après paiement.
Paramètres supplémentaires
| Paramètre | Type | Requis | Description |
|---|---|---|---|
| payment_method | string | Requis | MOLLIE ou STRIPE — le prestataire qui traite la carte. Ce sont les deux seules valeurs acceptées : toute autre valeur est refusée en 422. |
| success | string | Optionnel | URL de redirection après un paiement réussi. Le client sera redirigé vers {success}?id={uuid}&status=pending |
| error | string | Optionnel | URL de redirection après un paiement échoué ou annulé. Le client sera redirigé vers {error}?id={uuid}&status=cancelled |
Si ces paramètres ne sont pas fournis, le client sera redirigé vers la page de transaction ElyonPay par défaut.
Important : ne vous fiez jamais uniquement à l'URL de redirection (success/error) pour confirmer un paiement. Un utilisateur peut modifier l'URL manuellement. Vérifiez toujours le statut côté serveur via GET /api/transactions/{uuid}.
Réponse — PaymentResult
{
"success": true,
"data": {
"PSPid": "tr_aHYQAw4rudrttLDwtdKTJ",
"PSPnotifyId": "tr_aHYQAw4rudrttLDwtdKTJ",
"PSPtxnId": "tr_aHYQAw4rudrttLDwtdKTJ",
"url": "https://www.mollie.com/checkout/credit-card/session/aHYQAw4rudrttLDwtdKTJ",
"uuid": "CM06306a438d0958",
"PSPMessage": null,
"PSPstatus": ""
}
}Champs de la réponse
| Champ | Type | Description |
|---|---|---|
| success | boolean | Indique si la requête a réussi |
| data.PSPid | string | Identifiant du prestataire de paiement (PSP) |
| data.PSPnotifyId | string | Identifiant de notification PSP |
| data.PSPtxnId | string | Identifiant de transaction PSP |
| data.url | string | URL de paiement (redirection carte bancaire si applicable) |
| data.uuid | string | Identifiant unique ElyonPay — utilisez-le pour le polling |
| data.PSPMessage | string|null | Message retourné par le PSP (null si aucun) |
| data.PSPstatus | string | Statut retourné par le PSP |
Suivi du statut
Après avoir initié un paiement direct, interrogez périodiquement l'API pour connaître le résultat de la transaction. Le polling est le mécanisme de référence ; des webhooks optionnels sont également disponibles pour être notifié en temps réel.
États terminaux
| État | Résultat | Description |
|---|---|---|
| DELIVERED | Succès | Paiement confirmé, fonds transférés |
| FAILED | Échec | Paiement échoué (solde insuffisant, erreur réseau...) |
| DECLINED | Échec | Paiement refusé par l'opérateur |
| CANCELLED | Échec | Annulé par le client ou le système |
| PENDING | Payé | Paiement confirmé. Malgré son nom, cet état signifie que l'argent est encaissé : vous pouvez honorer la commande. Il n'attend plus que la livraison, pas le paiement. |
Le mot « pending » prête à confusion : chez la plupart des prestataires de paiement il signifie « en cours, rien n'est décidé », alors qu'ici il signifie l'inverse — le paiement est encaissé. Nous le renommons donc en WAITING_FOR_DELIVERY, qui dit ce qu'il attend réellement.
Aujourd'hui : rien ne change. L'API et les webhooks continuent d'envoyer state = "PENDING" et l'événement transaction.pending. Vos intégrations existantes fonctionnent sans modification.
Pour vous préparer : acceptez dès maintenant les deux valeurs, PENDING et WAITING_FOR_DELIVERY, ainsi que les deux noms d'événement. Traitez-les comme un paiement confirmé et non comme une transaction en cours. La bascule sera annoncée à l'avance et ne se fera pas avant que vous ayez eu le temps de migrer.
Exemple complet — Node.js / Express
Cet exemple couvre les 4 étapes du Custom Checkout : méthodes de paiement, affichage, paiement direct et polling du statut.
const express = require('express');
const app = express();
app.use(express.json());
const API = 'https://api.elyonpay.org/api';
let token = ''; // Obtained via POST /api/login
// Helper: poll transaction status
async function pollStatus(txId, maxMs = 120000, intervalMs = 4000) {
const start = Date.now();
while (Date.now() - start < maxMs) {
const res = await fetch(`${API}/transactions/${txId}`, {
headers: { 'Authorization': 'Bearer ' + token }
});
const tx = await res.json();
const terminal = ['DELIVERED', 'FAILED', 'DECLINED', 'CANCELLED'];
if (terminal.includes(tx.state)) return tx;
await new Promise(r => setTimeout(r, intervalMs));
}
throw new Error('Polling timeout');
}
// Step 1 — Get payment methods for customer's country
app.get('/payment-methods/:country', async (req, res) => {
const methods = await fetch(
`${API}/public/configuration/payments/${req.params.country}`
).then(r => r.json());
res.json(methods);
});
// Steps 2-3-4 — Customer picks method, initiate payment, poll result
app.post('/pay', async (req, res) => {
const { payment_method, phone, merchant_id, amount } = req.body;
// Step 3 — Initiate direct payment
const payment = await fetch(`${API}/mobile-money/payment/request`, {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount,
user_lang: 'fr',
merchant_name: 'Ma Boutique',
merchant_id,
currency: 'XAF',
country_name: 'Cameroon',
payment_method,
customer_msisdn: phone
})
}).then(r => r.json());
// Step 4 — Poll for terminal state
try {
const tx = await pollStatus(payment.data.uuid);
if (tx.state === 'DELIVERED') {
res.json({ success: true, transaction: tx });
} else {
res.json({ success: false, state: tx.state, transaction: tx });
}
} catch (err) {
res.status(408).json({ success: false, error: 'Payment timeout' });
}
});
app.listen(3000);Preuve de livraison
Des champs supplémentaires, ajoutés à vos appels de paiement existants, pour constituer automatiquement un dossier lorsqu'un porteur de carte conteste avoir reçu un bien ou service numérique. Aucun nouvel endpoint à intégrer : les champs viennent s'ajouter au paiement que vous envoyez déjà.
Où ces champs s'appliqueront
Sur les paiements par carte, où une contestation est possible. Les paiements Mobile Money et Cash restent inchangés : irrévocables, ils ne peuvent pas faire l'objet d'un litige.
| Endpoint | Rail | Statut prévu |
|---|---|---|
| /stripe/payment/request | Carte | Requis |
| /bankcard/payment/request | Carte | Requis |
| /request-to-pay/payment/request | Carte possible | Requis |
| /request-to-pay/payment/initialize | Carte possible | Requis |
| /request-to-pay/payment/link | Carte, payée plus tard | Requis |
| /mobile-money/payment/request, /cash/payment/request | Irrévocable | Accepté, jamais requis |
Champs
Tous s'ajoutent au premier niveau du corps de la requête, à côté de amount, merchant_id, etc. — pas de bloc imbriqué.
| Paramètre | Type | Requis | Description |
|---|---|---|---|
| external_id | string | Requis | Votre référence de commande. C'est ce qui vous permet de rapprocher nos transactions des vôtres — nous ne pouvons pas l'inventer à votre place. |
| product_type | string (enum) | Requis | Ce que vous livrez : DIGITAL_DOWNLOAD, DIGITAL_ACCESS, SERVICE ou PHYSICAL. Détermine automatiquement le mode de confirmation (voir ci-dessous) — vous ne le choisissez pas séparément. |
| service_description | string | Requis | Une phrase décrivant ce qui est vendu. C'est ce que lira l'analyste du réseau de carte en cas de litige. |
| terms.accepted_at | string (ISO 8601) | Requis | Horodatage d'acceptation de vos CGV par le client. |
| terms.version | string | Requis | La version des CGV acceptée. |
| customer.email | string | Conditionnel | E-mail du payeur. Au moins l'un des deux — email ou phone — sera exigé ; les deux peuvent être fournis. |
| customer.phone | string | Conditionnel | Téléphone du payeur. Sur /stripe/payment/request et /bankcard/payment/request, peut aussi être fourni via le champ existant customer_msisdn, qui restera pris en charge — vous n'êtes pas obligé de migrer avant la date d'activation. |
| customer.account_ref | string | Optionnel | Identifiant du client chez vous, 1 à 64 caractères. C'est le même bloc customer que sur le lien de paiement : il est déjà accepté aujourd'hui, avec customer.name, customer.email et customer.locale. |
| customer.name | string | Optionnel | Nom déclaré du client, 128 caractères au plus. |
Sur /request-to-pay/payment/request, /initialize et /link, le téléphone du payeur est déjà connu (le msisdn transmis) : rien à ajouter pour satisfaire la condition email/phone, sauf si vous voulez aussi fournir l'e-mail.
Valeurs de product_type
| Valeur | Exemple | Mode de confirmation |
|---|---|---|
| DIGITAL_DOWNLOAD | Film, PDF, formation à télécharger | Lien signé — le clic vaut confirmation |
| DIGITAL_ACCESS | Abonnement, accès ouvert dans un système tiers | Validation par votre système |
| SERVICE | Prestation de service | QR code présenté à la remise |
| PHYSICAL | Bien physique remis en main propre | QR code présenté à la remise |
Aperçu du payload
Sur /bankcard/payment/request, à titre d'illustration — les cinq derniers champs n'ont aujourd'hui aucun effet (voir l'encart ci-dessus).
{
"amount": 15000,
"merchant_id": 4821,
"operation_code": "33",
"currency": "XAF",
"payment_method": "MOLLIE",
"user_lang": "fr",
"customer_msisdn": "+24106000000",
"external_id": "CMD-88213",
"product_type": "DIGITAL_DOWNLOAD",
"service_description": "Formation Trésorerie PME, 4 modules vidéo",
"customer": { "email": "jean@example.com", "phone": "+24106000000" },
"terms": { "accepted_at": "2026-09-06T10:00:00Z", "version": "cgv-2026-03" }
}SDKs & plugins officiels
Des SDKs et plugins officiels sont disponibles pour intégrer ElyonPay dans vos projets. Ils gèrent automatiquement l'authentification, les appels API et la vérification du statut des paiements.
SDK PHP officiel pour intégrer l'API ElyonPay. Zéro dépendance externe — utilise uniquement cURL natif.
Plugin WooCommerce officiel. Acceptez Mobile Money et cartes bancaires directement depuis votre boutique WordPress.
SDK officiel pour intégrer le Custom Checkout dans vos applications React et Vue.js. Hooks, composables et polling automatique inclus.
Flux d'intégration
ElyonPay utilise un modèle en 6 étapes basé sur un lien de paiement et une vérification active du statut (modèle pull). Aucune infrastructure webhook n'est requise — des webhooks optionnels sont toutefois disponibles en complément.
Obtenir un lien de paiement
Votre backend appelle POST /api/request-to-pay/payment/link avec le montant et les URLs de redirection (success / error). L'API retourne une URL de paiement unique.
Afficher l'iframe de paiement
Redirigez le client vers l'URL retournée, ou intégrez-la dans une iframe. Le client choisit sa méthode de paiement (Mobile Money, carte bancaire) et saisit ses informations.
Validation du paiement
Le client confirme le paiement côté opérateur (code USSD Mobile Money, 3D Secure carte). ElyonPay traite la transaction.
Redirection vers votre backend
Une fois le paiement traité, le client est redirigé vers votre URL success ou error selon le résultat. L'UUID de la transaction est transmis dans l'URL de redirection.
Vérifier le statut de la transaction
Votre backend appelle GET /api/transactions/{uuid} pour récupérer le statut définitif de la transaction. Cette vérification côté serveur est indispensable — ne vous fiez jamais uniquement à l'URL de redirection.
Afficher la confirmation au client
Selon le statut retourné (DELIVERED, REJECTED, CANCELLED...), affichez la page de confirmation ou d'erreur appropriée à votre client.
Diagramme — Initiation du paiement
sequenceDiagram
participant Customer
participant Merchant
participant ElyonPay
Customer->>Merchant: Finalizes cart
Customer->>Merchant: Selects "Pay"
Merchant->>ElyonPay: POST /api/login
ElyonPay-->>Merchant: Returns JWT token
Merchant->>ElyonPay: POST /api/request-to-pay/payment/link
ElyonPay-->>Merchant: Returns payment URL
Merchant->>Customer: Displays payment page (iframe or redirect)
Customer->>ElyonPay: Selects payment method
Customer->>ElyonPay: Completes transaction
ElyonPay-->>Customer: Shows confirmation popup
Diagramme — Résultat du paiement
sequenceDiagram
participant Customer
participant Merchant
participant ElyonPay
Customer->>ElyonPay: Transaction completed
alt Successful payment
ElyonPay->>Merchant: Redirect to success URL
else Failed payment
ElyonPay->>Merchant: Redirect to error URL
end
Merchant->>ElyonPay: GET /api/transactions/[ID_TRANSACTION]
ElyonPay-->>Merchant: Returns transaction status
Merchant->>Merchant: Updates order status
Merchant->>Customer: Shows order confirmation
Exemple d'implémentation (étapes 1 → 5)
Node.js / Express
// 1. Create payment link
app.post('/checkout', async (req, res) => {
const response = await fetch('https://api.elyonpay.org/api/request-to-pay/payment/link', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json',
'language': 'fr'
},
body: JSON.stringify({
amount: req.body.amount,
user_lang: 'fr',
msisdn: req.body.phone,
success: 'https://yoursite.com/payment/done',
error: 'https://yoursite.com/payment/error'
})
});
const { url } = await response.json();
res.redirect(url); // 2. Redirect customer to payment page
});
// 4 → 5. Handle redirect & verify status
app.get('/payment/done', async (req, res) => {
// The transaction uuid is available from your order context
const tx = await fetch(`https://api.elyonpay.org/api/transactions/${uuid}`, {
headers: { 'Authorization': 'Bearer ' + token }
}).then(r => r.json());
if (tx.state === 'DELIVERED') {
// 6. Show confirmation
res.render('success', { transaction: tx });
} else {
res.render('pending', { transaction: tx });
}
});Mobile Money
ElyonPay unifie 5 réseaux Mobile Money africains sous une seule API. Chaque réseau possède ses propres codes opérateurs et pays de couverture.
Réseaux supportés
| Code méthode | Réseau | Pays | Devises |
|---|---|---|---|
mtn_mobile_money | MTN MoMo | CM, NG, CI, GH, UG | XAF, NGN, XOF |
orange_money | Orange Money | CM, CI, SN, ML | XAF, XOF |
wave | Wave | SN, CI, BF, ML | XOF |
moov_money | Moov Africa | BJ, TG, BF, NE | XOF |
airtel_money | Airtel Money | KE, TZ, UG, ZM, CD | KES, TZS, UGX, CDF |
Couverture : Cameroun, Nigeria, Côte d'Ivoire, Ghana, Ouganda, Sénégal, Mali, Burkina Faso, Bénin, Togo, Niger, Kenya, Tanzanie, Zambie, République Démocratique du Congo.
Cartes bancaires
Acceptez les paiements Visa et Mastercard avec 3D Secure, tokenisation et conformité PCI DSS niveau 1.
Cartes de test (Sandbox)
| Numéro de carte | Expiration | CVV | Comportement |
|---|---|---|---|
4242 4242 4242 4242 | 12/28 | 123 | Paiement réussi |
4000 0000 0000 0002 | 12/28 | 123 | Carte refusée |
4000 0000 0000 3220 | 12/28 | 123 | 3D Secure requis |
5555 5555 5555 4444 | 12/28 | 123 | Mastercard réussi |
Paiements plateforme (Split)
Une plateforme (marketplace) peut encaisser un paiement pour le compte de ses marchands. ElyonPay répartit automatiquement les fonds entre le sous-marchand et la plateforme : le sous-marchand reçoit le montant net et la plateforme prélève sa commission, en une seule transaction côté client.
Étape 1 — Créer vos sous-marchands
Avant d'encaisser pour le compte d'un marchand, vous devez le déclarer comme sous-marchand de votre plateforme. Deux méthodes sont disponibles :
- Depuis le dashboard — dans la section Sous-marchands de votre tableau de bord plateforme, ajoutez un marchand via le formulaire (identité, pays, frais par défaut). Idéal pour une gestion manuelle.
- Via l'API — créez vos sous-marchands par programmation avec l'endpoint dédié ci-dessous. Idéal pour un onboarding automatisé.
Paramètres du body
| Paramètre | Type | Requis | Description |
|---|---|---|---|
| name | string | Requis | Nom commercial du sous-marchand |
| country_code | string | Requis | Code pays ISO 3166 (ex. CM) |
| city | string | Requis | Ville |
| address | string | Requis | Adresse |
| type | string | Requis | Type d'activité (ex. RETAIL) |
| owner_email | string | Requis | E-mail du propriétaire du compte |
| owner_phone_number | string | Requis | Téléphone du propriétaire |
| owner_first_name | string | Requis | Prénom du propriétaire |
| owner_last_name | string | Requis | Nom du propriétaire |
| owner_password | string | Requis | Mot de passe initial du compte propriétaire |
| default_marketplace_fee_percent | number | Optionnel | Commission par défaut (%) prélevée sur les paiements de ce sous-marchand. Sinon, les frais par défaut de la plateforme s'appliquent |
curl -X POST https://api.elyonpay.org/api/merchant/sub-merchants \
-H "Authorization: Bearer <your_jwt_token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Boutique ABC",
"country_code": "CM",
"city": "Douala",
"address": "123 Rue Commerce",
"type": "RETAIL",
"owner_email": "owner@boutique-abc.com",
"owner_phone_number": "+237600000000",
"owner_first_name": "Jean",
"owner_last_name": "Dupont",
"owner_password": "s3cur3-p4ssword",
"default_marketplace_fee_percent": 5.00
}'{
"merchant_id": 725,
"uuid": "a1b2c3d4-...",
"name": "Boutique ABC",
"owner_email": "owner@boutique-abc.com",
"owner_phone": "+237600000000",
"kyb_required": true,
"default_fee_percent": "5.00"
}Notez le merchant_id retourné : c'est l'identifiant que vous passerez dans onbehalfof pour encaisser pour ce marchand. Le sous-marchand doit avoir un compte de payout configuré (pour recevoir les fonds) ; sa vérification KYB peut par ailleurs être requise avant la mise en production.
Gérer vos sous-marchands
| Méthode | Endpoint | Description |
|---|---|---|
| GET | /api/merchant/sub-merchants | Lister vos sous-marchands |
| GET | /api/merchant/sub-merchants/{id} | Détail d'un sous-marchand |
| PATCH | /api/merchant/sub-merchants/{id}/fees | Modifier sa commission par défaut |
Étape 2 — Encaisser pour un sous-marchand
Créez le paiement normalement (voir Créer un paiement), en ajoutant le paramètre onbehalfof contenant l'id ou le code partner du sous-marchand. ElyonPay crée alors un paiement réparti.
curl -X POST https://api.elyonpay.org/api/request-to-pay/payment/link \
-H "Authorization: Bearer <your_jwt_token>" \
-H "Content-Type: application/json" \
-H "language: fr" \
-d '{
"amount": 15000,
"user_lang": "fr",
"msisdn": "+237600000001",
"success": "https://yoursite.com/success",
"error": "https://yoursite.com/error",
"webhook": "https://yoursite.com/webhooks/elyonpay",
"onbehalfof": 725
}'Comment la commission est calculée
La commission prélevée par la plateforme est résolue dans cet ordre de priorité :
- Le paramètre
feesfourni dans l'appel (montant exact, prioritaire s'il est présent). - Sinon, la commission par défaut du sous-marchand (default_marketplace_fee_percent).
- Sinon, la commission par défaut configurée sur la plateforme.
- Si aucune source n'est disponible, la requête est rejetée (erreur 400).
La commission doit être positive et strictement inférieure au montant, et respecter les plafonds définis sur la plateforme. Le sous-marchand reçoit amount − commission, la plateforme reçoit la commission.
Sous-marchand encaissant directement
Un sous-marchand peut aussi initier lui-même un paiement, avec sa propre clé API, sans passer par la plateforme. Dans ce cas, n'ajoutez pas onbehalfof : le split vers la plateforme parente s'applique automatiquement, avec les mêmes règles de commission.
Codes d'erreur
| Situation | Code |
|---|---|
| Le compte appelant n'est pas une plateforme, ou le sous-marchand appartient à une autre plateforme | 403 |
| Sous-marchand introuvable (id ou code partner) | 404 |
| Commission invalide (négative, ≥ montant, plafond dépassé) ou aucun compte de payout configuré | 400 |
Webhooks
ElyonPay peut notifier votre serveur en temps réel à chaque changement d'état d'une transaction, via une requête HTTP POST (serveur à serveur). Les webhooks sont optionnels : ils complètent le modèle pull (polling) mais ne le remplacent pas.
Activer les webhooks
Aucune configuration globale n'est nécessaire : l'URL de webhook se fournit transaction par transaction, via le paramètre optionnel webhook lors de la création du paiement. L'URL doit être une URL HTTPS valide et accessible publiquement.
curl -X POST https://api.elyonpay.org/api/request-to-pay/payment/link \
-H "Authorization: Bearer <your_jwt_token>" \
-H "Content-Type: application/json" \
-H "language: fr" \
-d '{
"amount": 15000,
"user_lang": "fr",
"msisdn": "+237600000000",
"success": "https://yoursite.com/success",
"error": "https://yoursite.com/error",
"webhook": "https://yoursite.com/webhooks/elyonpay"
}'Événements notifiés
Une notification est envoyée à chaque fois que la transaction atteint l'un des états suivants :
| Événement | État | Description |
|---|---|---|
| transaction.pending | PENDING | Paiement initié, en attente de confirmation |
| transaction.delivered | DELIVERED | Paiement confirmé, fonds transférés au marchand |
| transaction.declined | DECLINED | Paiement refusé par l'opérateur |
| transaction.cancelled | CANCELLED | Paiement annulé par le client ou le système |
Format de la notification
Chaque notification est une requête POST en JSON, accompagnée des en-têtes suivants :
| En-tête | Description |
|---|---|
| Content-Type | Toujours application/json |
| X-Mip-Event | Nom de l'événement (ex. transaction.delivered) |
| X-Mip-Delivery | Identifiant unique de la notification — utilisez-le pour dédupliquer |
| X-Mip-Attempt | Numéro de la tentative (1 à 5) |
Exemple de payload
{
"event": "transaction.delivered",
"created_at": "2026-07-08T18:45:12+02:00",
"data": {
"transaction": "CI032667e43abf11",
"state": "DELIVERED",
"amount": 15000,
"currency": "XAF",
"operation_code": "OP-2026-000123",
"merchant_id": 42,
"external_id": "CMD-2026-0042",
"player_ref": "PLR-1001",
"customer": {
"account_ref": "PLR-1001",
"name": "Jean Mballa",
"email": "jean@example.com",
"phone": "+237670000001"
},
"creation_date": "2026-07-08T18:40:03+02:00",
"validation_date": "2026-07-08T18:44:57+02:00",
"delivery_date": "2026-07-08T18:45:10+02:00"
}
}Le payload reprend vos propres références quand vous les avez fournies à la création : external_id (votre commande) et le bloc customer (votre client final : account_ref, name, email ; phone est le numéro réellement débité). Les clés sont toujours présentes, nulles sinon. player_ref reste l'alias de customer.account_ref.
Pour les transactions marketplace (paiement réparti entre plusieurs marchands), le payload contient en plus un tableau data.split listant la part de chaque marchand (merchant_id, amount) — voir Paiements plateforme.
Accusé de réception et retries
Votre serveur doit répondre avec un code HTTP 2xx en moins de 10 secondes. Toute autre réponse (4xx, 5xx, timeout, erreur réseau) est considérée comme un échec et déclenche une nouvelle tentative selon le calendrier suivant :
| Tentative | Délai après l'échec précédent |
|---|---|
| 1 | Immédiate (au changement d'état) |
| 2 | 3 secondes |
| 3 | 30 secondes |
| 4 | 60 secondes |
| 5 | 90 secondes |
Après 5 tentatives infructueuses, la notification est définitivement abandonnée. Le statut de la transaction reste toujours consultable via GET /api/transactions/{uuid}.
Exemple de réception (Node.js / Express)
app.post('/webhooks/elyonpay', express.json(), async (req, res) => {
// 1. Acknowledge immediately — heavy processing must happen async
res.sendStatus(200);
const event = req.headers['x-mip-event']; // e.g. "transaction.delivered"
const deliveryId = req.headers['x-mip-delivery']; // unique per notification
// 2. Deduplicate: the same delivery may be received more than once
if (await alreadyProcessed(deliveryId)) return;
// 3. Verify the state server-side before acting on it
const { transaction } = req.body.data;
const status = await fetch(
'https://api.elyonpay.org/api/transactions/' + transaction,
{ headers: { Authorization: 'Bearer ' + token } }
).then((r) => r.json());
if (status.state === 'DELIVERED') {
await fulfillOrder(transaction);
}
});Bonnes pratiques
- Répondez vite — Renvoyez le code 2xx avant tout traitement long : au-delà de 10 secondes, la notification est considérée comme échouée et sera rejouée.
- Dédupliquez — Stockez l'en-tête X-Mip-Delivery et ignorez les notifications déjà traitées — les retries peuvent provoquer des doublons.
- Vérifiez côté serveur — Utilisez le webhook comme un signal, puis confirmez l'état via GET /api/transactions/{uuid} avant toute action métier.
- Gardez le polling en filet de sécurité — Si votre serveur est indisponible plus de ~3 minutes, la notification est abandonnée : le polling reste la source de vérité.
Statuts des paiements
Chaque paiement passe par plusieurs statuts au cours de son cycle de vie.
Diagramme des transitions d'état
stateDiagram-v2
[*] --> CREATED
CREATED --> PENDING: Payment initiated
PENDING --> WAITING_FOR_PAYMENT: Awaiting customer
WAITING_FOR_PAYMENT --> ACCEPTED: Payment confirmed
PENDING --> ACCEPTED: Direct approval
PENDING --> REJECTED: Validation failed
PENDING --> DECLINED: Payment issue
WAITING_FOR_PAYMENT --> DECLINED: Payment issue
WAITING_FOR_PAYMENT --> CANCELLED: User cancellation
ACCEPTED --> DELIVERED: Funds transferred
ACCEPTED --> CANCELLED: Administrative action
REJECTED --> [*]
DECLINED --> [*]
CANCELLED --> [*]
DELIVERED --> [*]
Codes d'erreur
En cas d'erreur, l'API retourne un objet JSON avec un code d'erreur machine et un message lisible.
{
"success": false,
"error": {
"code": "INSUFFICIENT_FUNDS",
"message": "Le solde Mobile Money du client est insuffisant.",
"http_status": 402
}
}Table des erreurs
| Code erreur | HTTP | Description |
|---|---|---|
| UNAUTHORIZED | 401 | Clé API manquante ou invalide |
| INVALID_AMOUNT | 400 | Montant invalide ou inférieur au minimum autorisé |
| CURRENCY_NOT_SUPPORTED | 400 | Devise non supportée pour ce réseau |
| PHONE_INVALID | 400 | Numéro de téléphone invalide ou non enregistré |
| INSUFFICIENT_FUNDS | 402 | Solde insuffisant côté client |
| OPERATOR_TIMEOUT | 408 | L'opérateur Mobile Money n'a pas répondu dans les délais |
| DUPLICATE_REQUEST | 409 | Requête dupliquée — utiliser Idempotency-Key |
| PAYMENT_NOT_FOUND | 404 | Paiement introuvable |
| RATE_LIMIT_EXCEEDED | 429 | Trop de requêtes — réessayez dans quelques secondes |
| INTERNAL_ERROR | 500 | Erreur interne ElyonPay — contactez le support |
Devises supportées
| Code | Devise | Montant minimum | Méthodes |
|---|---|---|---|
XAF | Franc CFA BEAC | 100 XAF | MTN MoMo, Orange Money, Carte |
XOF | Franc CFA BCEAO | 100 XOF | Wave, Moov, Orange Money, MTN, Carte |
EUR | Euro | 0,50 EUR | Carte (Visa, Mastercard), PayPal |
USD | Dollar américain | 0,50 USD | Carte, PayPal |
GBP | Livre sterling | 0,30 GBP | Carte |
NGN | Naira nigérian | 100 NGN | MTN MoMo NG |
KES | Shilling kenyan | 10 KES | Airtel Money |
CDF | Franc congolais | 500 CDF | Airtel Money |
