Introduction

L'API MaliaPay vous permet d'accepter les paiements mobiles et bancaires en Côte d'Ivoire. Une seule intégration pour accéder à Wave, Orange Money, MTN MoMo, Moov, Djamo et les paiements par carte bancaire.

ℹ️ Base URL

https://business.malia.ci/api/v1

Format des requêtes

  • Toutes les requêtes sont en JSON
  • Header obligatoire : Content-Type: application/json
  • Authentification marchand : X-API-Key: VOTRE_CLE_API
  • Le champ montant accepte int ou string et est normalisé côté serveur
  • Le champ customer_email est facultatif

1. Authentification

L'API de paiement n'accepte qu'un seul mode d'authentification : la clé API (header X-API-Key).

Clé API — obligatoire

Envoyez votre clé API via le header X-API-Key. La clé est liée à un marchand actif et permet d'appeler les endpoints de paiement sans login préalable.

X-API-Key: mk_live_xxxxxxxxxxxxxxxx Content-Type: application/json
⚠️ Important

Le merchant_id présent dans le body est forcé côté serveur pour correspondre à celui de la clé API. Cela évite qu'un client paie au nom d'un autre marchand.

2. Clé API (X-API-Key)

Chaque marchand dispose d'une clé API unique, générée automatiquement à la création du compte. Elle est visible et régénérable depuis l'espace marchand.

Où trouver ma clé API ?

Connectez-vous à votre espace marchand → Paramètres → Clé API. Vous pouvez copier la clé ou en générer une nouvelle.

Utiliser la clé API

cURL
curl -X POST https://business.malia.ci/api/v1/payments \ -H "X-API-Key: mk_live_xxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{"channel":"WAVECI","montant":5000,...}'

Protection du merchant_id

Le middleware identifie le marchand grâce à la clé API et remplace le merchant_id du body par celui associé. Même si le client modifie merchant_id dans la requête, c'est la clé API qui fait autorité.

⚠️ Sécurité

Ne partagez jamais votre clé API dans du code côté client. Stockez-la dans vos variables d'environnement et appelez l'API depuis votre backend.

3. Canaux de paiement

Utilisez ces codes pour spécifier le canal de paiement lors de la création d'une transaction.

CanalCode APITypeDescription
Wave CIWAVECIMobile MoneyPaiement via l'application Wave
Orange MoneyOMCIMobile MoneyPaiement via Orange Money CI
MTN MoMoMTNCIIMobile MoneyPaiement via MTN Mobile Money
Moov CIMOOVCIMobile MoneyPaiement via Moov Money
DjamoDJAMOMobile MoneyPaiement via l'application Djamo
PeyaPayPEYA_PAYMobile MoneyPaiement via PeyaPay (avec OTP)
Carte BancaireCARDCartePaiement par carte Visa/Mastercard

4. Créer un paiement

Cet endpoint crée une transaction de paiement et retourne une URL de redirection pour le client final.

POST /api/v1/payments

Headers requis

X-API-Key: mk_live_xxxxxxxxxxxxxxxx Content-Type: application/json

Corps de la requête

JSON
{ "merchant_id": "MI_12345678", // Obligatoire - Votre ID Marchand "channel": "WAVECI", // Obligatoire - Code du canal "montant": 5000, // Obligatoire - Montant en FCFA (int ou string) "reference": "CMD-001", // Obligatoire - Votre référence commande "customer_name": "Jean", // Obligatoire - Nom du client "customer_surname": "Dupont", // Optionnel - Prénom du client "customer_phone_number": "0700000000", // Obligatoire - Téléphone client "customer_email": "client@email.com", // Optionnel "description": "Achat produit XYZ", // Optionnel "aggregated_merchant_id": "am-1erynpsng20s6", // Optionnel - ID agrégateur "return_url": "https://monsite.ci/merci", // Optionnel - URL après paiement "error_url": "https://monsite.ci/erreur", // Optionnel - URL paiement échoué "success_url": "https://monsite.ci/succes", // Optionnel - URL paiement validé "notification_url": "https://monsite.ci/webhook" // Optionnel - URL webhook }
ℹ️ Où trouver votre Merchant ID ?

Connectez-vous à votre espace marchand → Paramètres → Informations du compte. Votre ID Marchand commence par MI_.

Réponse succès (200/201)

JSON
{ "status": "pending", "message": "Transaction créée. Redirigez le client vers le lien.", "transaction_id": "Mxxxxxxxxxxxxxxxxxxxxxxx", "link": "https://business.malia.ci/checkout/Mxxxxxxxxxxxxxxxxxxxxxxx", "pay_token": "", "fees": { "montant_net": 4900, "frais_malia": 50, "frais_operateur": 50, "total_fees": 100 } }
✅ Prochaine étape

Redirigez votre client vers l'URL retournée dans le champ link. Le client complètera le paiement directement avec l'opérateur.

ℹ️ Numéro de téléphone

Formats acceptés : 0700000000, +2250700000000 ou 225700000000. Le serveur normalise automatiquement les numéros avant d'appeler les opérateurs.

⚠️ MTN MoMo

Avec MTNCII aucune redirection n'est retournée. Le statut est pending et le client reçoit une notification push/ USSD pour valider la demande. Surveillez le statut via GET /payments/{transaction_id} ou le webhook.

Flux de paiement par canal

1

Wave, Orange, Moov, Djamo

Redirection vers une page de confirmation. Le client valide sur son téléphone et retourne automatiquement.

2

PeyaPay

Nécessite une validation par OTP. Le client entre le code reçu par SMS sur la page de paiement.

3

Carte Bancaire

Redirection vers l'interface sécurisée de SycaPay pour saisie des informations de carte.

5. PeyaPay (OTP)

Le canal PEYA_PAY fonctionne en deux étapes : une requête d'initiation envoie un OTP au client, puis une requête de vérification finalise le paiement.

Étape 1 — Initier

POST /api/v1/peyapay/init

Le body est identique à la création de paiement standard, avec "channel": "PEYA_PAY" et un numéro de téléphone valide. La réponse contient peyapay_transaction_id à mémoriser.

Exemple de réponse (201)

JSON
{ "status": "otp_required", "requiresOtp": true, "transaction_id": "Mxxxxxxxxxxxxxxxxxxxxxxx", "peyapay_transaction_id": 12345, "fees": { "montant_net": 4900, "frais_malia": 50, "frais_operateur": 50, "total_fees": 100 } }

Étape 2 — Vérifier le OTP

POST /api/v1/peyapay/verify

Le client reçoit un code par SMS et le renvoie via votre backend.

JSON
{ "peyapay_transaction_id": 12345, "otp_code": "123456" }

Étape 3 — Consulter le statut (optionnel)

GET /api/v1/peyapay/status/{peyapay_transaction_id}
✅ Conseil

Le endpoint verify est synchrone. En cas de traitement asynchrone, utilisez le statut PeyaPay pour suivre l'opération.

6. Vérifier le statut d'un paiement

Consultez le statut d'une transaction pour savoir si elle a été complétée, est en attente, ou a échoué.

GET /api/v1/payments/{transaction_id}

Exemple de requête

cURL
curl -X GET https://business.malia.ci/api/v1/payments/Mxxxxxxxxxxxxxxxxxxxxxxx \
-H "X-API-Key: mk_live_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json"

Réponse

JSON
{ "transaction_id": "Mxxxxxxxxxxxxxxxxxxxxxxx", "reference": "CMD-001", "montant": 5000, "channel": "WAVECI", "status": "success", // pending, processing, success, failed, cancelled, pending_otp "customer_name": "Jean", "frais_malia": 50, "frais_operateur": 50, "created_at": "2024-01-15T10:30:00Z" }

Statuts possibles

StatutDescription
pendingPaiement initié, en attente de confirmation client
processingPaiement en cours de traitement chez l'opérateur
pending_otpEn attente de validation OTP (PeyaPay)
successPaiement confirmé et réussi
failedPaiement échoué (fonds insuffisants, erreur opérateur...)
cancelledPaiement annulé par le client ou expiré

7. Webhooks (Notifications)

Recevez des notifications en temps réel lorsqu'un paiement change de statut. Configurez votre URL webhook dans votre espace marchand.

ℹ️ Configuration

Dans votre espace marchand : Paramètres → Webhooks. Ajoutez l'URL de votre endpoint de réception.

Endpoints des webhooks (appelés par les opérateurs)

CanalURLVerbe
Wave/api/webhooks/wavePOST
Orange/api/webhooks/orangePOST
MTN/api/webhooks/mtnPOST
Moov/api/webhooks/moovPOST
Djamo/api/webhooks/djamoPOST
Carte/api/webhooks/cardPOST

Payload du webhook

JSON
{ "event": "payment.completed", "transaction_id": "TXN_abc123def456", "reference": "CMD-001", "montant": 5000, "channel": "WAVECI", "status": "completed", "customer_phone": "0700000000", "paid_at": "2024-01-15T10:32:15Z", "signature": "sha256=xxxxxxxxxxxxxxxx" }

Types d'événements

ÉvénementDescription
payment.pendingPaiement initié
payment.completedPaiement réussi
payment.failedPaiement échoué
payment.cancelledPaiement annulé

Vérification de la signature

Pour sécuriser vos webhooks, vérifiez la signature reçue dans l'en-tête ou le payload :

PHP
$signature = hash_hmac('sha256', json_encode($payload), $webhookSecret); $received = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE']; if (!hash_equals($signature, $received)) { http_response_code(401); exit; }
✅ Bonnes pratiques

Retournez toujours un HTTP 200 à la réception du webhook. Traitez les événements de manière idempotente (une transaction peut générer plusieurs webhooks).

8. Codes d'erreur

Code HTTPErreurDescription
400Bad RequestDonnées manquantes ou invalides
401UnauthorizedClé API ou token manquant/invalide
403ForbiddenAccès non autorisé à cette ressource
404Not FoundTransaction ou ressource introuvable
422Validation ErrorErreur de validation des champs
429Too Many RequestsRate limit dépassé
500Server ErrorErreur serveur, réessayez plus tard

Format d'erreur standard

JSON
{ "message": "Le champ montant est requis.", "errors": { "montant": ["Le champ montant est requis."], "channel": ["Le canal sélectionné n'est pas valide."] } }

9. Exemples complets

Exemple PHP complet

PHP
class MaliaPayClient { private $baseUrl = 'https://business.malia.ci/api/v1'; private $apiKey; public function __construct($apiKey) { $this->apiKey = $apiKey; } // 1. Créer un paiement public function createPayment($data) { return $this->request('POST', '/payments', $data); } // 2. Vérifier le statut public function getPayment($transactionId) { return $this->request('GET', "/payments/{$transactionId}"); } private function request($method, $path, $data = []) { $ch = curl_init($this->baseUrl . $path); $headers = [ 'Content-Type: application/json', "X-API-Key: {$this->apiKey}" ]; curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $headers, CURLOPT_POSTFIELDS => json_encode($data) ]); $result = json_decode(curl_exec($ch), true); curl_close($ch); return $result; } } // Utilisation $client = new MaliaPayClient('mk_live_xxxxxxxxxxxxxxxx'); $payment = $client->createPayment([ 'merchant_id' => 'MI_12345678', // Votre ID Marchand 'channel' => 'WAVECI', 'montant' => 5000, 'reference' => 'CMD-001', 'customer_name' => 'Jean', 'customer_surname' => 'Dupont', 'customer_phone_number' => '0700000000' ]); // Rediriger le client (hors MTN) if (!empty($payment['link'])) { header("Location: {$payment['link']}"); }

Exemple JavaScript (Node.js)

JavaScript
const API_KEY = 'mk_live_xxxxxxxxxxxxxxxx'; const createPayment = async () => { // 1. Créer le paiement const paymentRes = await fetch('https://business.malia.ci/api/v1/payments', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': API_KEY }, body: JSON.stringify({ merchant_id: 'MI_12345678', // Votre ID Marchand channel: 'WAVECI', montant: 5000, reference: 'CMD-001', customer_name: 'Jean', customer_surname: 'Dupont', customer_phone_number: '0700000000' }) }); const payment = await paymentRes.json(); // 2. Rediriger vers la page de paiement if (payment.link) { window.location.href = payment.link; } };

Exemple Python

Python
import requests BASE_URL = "https://business.malia.ci/api/v1" API_KEY = "mk_live_xxxxxxxxxxxxxxxx" headers = { "Content-Type": "application/json", "X-API-Key": API_KEY } # 1. Créer un paiement payment_res = requests.post( f"{BASE_URL}/payments", headers=headers, json={ "merchant_id": "MI_12345678", # Votre ID Marchand "channel": "WAVECI", "montant": 5000, "reference": "CMD-001", "customer_name": "Jean", "customer_surname": "Dupont", "customer_phone_number": "0700000000" } ) payment = payment_res.json() print(f"Statut: {payment['status']}") if payment.get("link"): print(f"Rediriger vers: {payment['link']}")

10. Bac à sable (sandbox)

Utilisez le bac à sable pour tester votre intégration sans effectuer de vrai paiement. L'endpoint accepte le même body que /payments mais ne déclenche aucun appel opérateur.

POST /api/v1/test

Comportement

  • Mêmes règles de validation que /payments
  • Mêmes frais calculés
  • Crée une transaction enregistrée avec le statut success et gateway_transaction_id = TEST
  • Aucun appel à Wave, Orange, MTN, Moov, Djamo ou SycaPay

Exemple de requête

cURL
curl -X POST https://business.malia.ci/api/v1/test \ -H "X-API-Key: mk_live_xxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{"merchant_id":"MI_12345678","channel":"WAVECI","montant":5000,"reference":"TEST-001","customer_name":"Jean","customer_phone_number":"0700000000"}'

Réponse (201)

JSON
{ "status": "success", "message": "[SANDBOX] Paiement simulé avec succès. Aucun appel opérateur effectué.", "transaction_id": "Mxxxxxxxxxxxxxxxxxxxxxxx", "link": "", "fees": { "montant_net": 4900, "frais_malia": 50, "frais_operateur": 50, "total_fees": 100 } }
✅ Conseil

Utilisez ce endpoint pour valider votre clé API, votre format JSON et la structure des réponses avant de passer en production.

Support & Contact

Besoin d'aide pour l'intégration ? Notre équipe technique est disponible pour vous accompagner.

Une solution proposée par Lkmdigital