MTN MoMo API : Guide d'intégration pour développeurs
MTN Mobile Money (MoMo) est l'un des services de paiement mobile les plus populaires en Afrique. Ce guide technique vous accompagne dans l'intégration de MTN MoMo via l'API Simiz.
Présentation de MTN MoMo
Couverture géographique
MTN MoMo est disponible dans plusieurs pays africains :
| Pays | Devise | Préfixes téléphone |
|---|
| Cameroun | XAF | 67x, 68x, 650, 651 |
|---|---|---|
| Côte d'Ivoire | XOF | 05x |
| Ghana | GHS | 024, 054, 055 |
| Bénin | XOF | 96, 97 |
| Congo | XAF | 06x |
Limites de transaction
| Type | Limite journalière | Limite mensuelle |
|---|
| Utilisateur standard | 500 000 XAF | 2 000 000 XAF |
|---|---|---|
| Utilisateur vérifié | 2 000 000 XAF | 10 000 000 XAF |
| Marchand | 5 000 000 XAF | 50 000 000 XAF |
Architecture de l'intégration
Flux de paiement MTN MoMo
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Votre │ │ Simiz │ │ MTN │ │ Client │
│ App │ │ API │ │ MoMo │ │ │
└────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘
│ │ │ │
│ 1. POST /payments │ │
│──────────────>│ │ │
│ │ │ │
│ │ 2. Request │ │
│ │──────────────>│ │
│ │ │ │
│ │ │ 3. USSD/Push │
│ │ │──────────────>│
│ │ │ │
│ 4. Response (pending) │ │
│<──────────────│ │ │
│ │ │ │
│ │ │ 5. PIN │
│ │ │<──────────────│
│ │ │ │
│ │ 6. Callback │ │
│ │<──────────────│ │
│ │ │ │
│ 7. Webhook │ │ │
│<──────────────│ │ │
│ │ │ │
Configuration initiale
1. Installation
Node.jsnpm install @simiz/sdk
PHP
composer require simiz/simiz-php
Python
pip install simiz
2. Configuration des clés
// config/simiz.js
import { Simiz } from '@simiz/sdk';
export const simiz = new Simiz({
apiKey: process.env.SIMIZ_API_KEY,
secretKey: process.env.SIMIZ_SECRET_KEY,
sandbox: process.env.NODE_ENV !== 'production',
timeout: 30000, // 30 secondes
retries: 3
});
Implémentation du paiement
Créer un paiement MTN MoMo
// services/payment.service.js
async function createMTNMoMoPayment(orderData) {
try {
const payment = await simiz.payments.create({
// Montant et devise
amount: orderData.amount,
currency: 'XAF',
// Informations client
phone: orderData.customerPhone,
provider: 'mtn_momo',
// Références
reference: ORD-${Date.now()},
description: orderData.description,
// URLs de callback
callbackUrl: ${process.env.API_URL}/webhooks/simiz,
returnUrl: ${process.env.FRONTEND_URL}/order/success,
cancelUrl: ${process.env.FRONTEND_URL}/order/cancel,
// Métadonnées personnalisées
metadata: {
orderId: orderData.orderId,
customerId: orderData.customerId,
items: orderData.items
}
});
return {
success: true,
paymentId: payment.id,
status: payment.status,
expiresAt: payment.expiresAt
};
} catch (error) {
console.error('Payment creation failed:', error);
return {
success: false,
error: error.code,
message: error.message
};
}
}
Vérifier le statut
async function checkPaymentStatus(paymentId) {
const payment = await simiz.payments.retrieve(paymentId);
return {
id: payment.id,
status: payment.status,
amount: payment.amount,
paidAt: payment.paidAt,
payerPhone: payment.payerPhone,
payerName: payment.payerName,
transactionId: payment.providerTransactionId
};
}
Gestion des webhooks
Configuration du endpoint
// routes/webhooks.js
import express from 'express';
import { simiz } from '../config/simiz.js';
import { orderService } from '../services/order.service.js';
const router = express.Router();
router.post('/simiz', express.raw({ type: 'application/json' }), async (req, res) => {
// 1. Vérifier la signature
const signature = req.headers['x-simiz-signature'];
try {
const isValid = simiz.webhooks.verify(
req.body.toString(),
signature,
process.env.SIMIZ_WEBHOOK_SECRET
);
if (!isValid) {
console.error('Invalid webhook signature');
return res.status(401).json({ error: 'Invalid signature' });
}
// 2. Parser l'événement
const event = JSON.parse(req.body.toString());
// 3. Traiter selon le type
await handleWebhookEvent(event);
// 4. Répondre rapidement (< 5 secondes)
res.json({ received: true });
} catch (error) {
console.error('Webhook processing error:', error);
res.status(500).json({ error: 'Processing failed' });
}
});
async function handleWebhookEvent(event) {
const { type, data } = event;
switch (type) {
case 'payment.success':
await handlePaymentSuccess(data);
break;
case 'payment.failed':
await handlePaymentFailed(data);
break;
case 'payment.expired':
await handlePaymentExpired(data);
break;
default:
console.log('Unhandled event type:', type);
}
}
async function handlePaymentSuccess(data) {
const { reference, amount, payerPhone, providerTransactionId } = data;
// Mettre à jour la commande
await orderService.markAsPaid(reference, {
amount,
payerPhone,
transactionId: providerTransactionId,
paidAt: new Date()
});
// Envoyer confirmation au client
await notificationService.sendPaymentConfirmation(reference);
// Logger pour audit
console.log(Payment success: ${reference} - ${amount} XAF);
}
async function handlePaymentFailed(data) {
const { reference, failureReason } = data;
await orderService.markAsFailed(reference, failureReason);
await notificationService.sendPaymentFailed(reference, failureReason);
console.log(Payment failed: ${reference} - ${failureReason});
}
async function handlePaymentExpired(data) {
const { reference } = data;
await orderService.markAsExpired(reference);
console.log(Payment expired: ${reference});
}
export default router;
Gestion des erreurs
Codes d'erreur MTN MoMo
| Code | Message | Cause | Solution |
|---|
insufficient_funds | Solde insuffisant | Le client n'a pas assez d'argent | Informer de recharger |
|---|---|---|---|
invalid_phone | Numéro invalide | Numéro non MTN ou mal formaté | Valider le préfixe |
transaction_limit | Limite atteinte | Plafond journalier dépassé | Reporter au lendemain |
user_not_found | Compte inexistant | Numéro pas enregistré MoMo | Vérifier le numéro |
user_blocked | Compte bloqué | Compte suspendu par MTN | Contacter MTN |
timeout | Délai dépassé | Client n'a pas confirmé | Proposer de réessayer |
cancelled | Annulé | Client a refusé | Proposer alternative |
Implémentation de la gestion d'erreurs
async function createPaymentWithErrorHandling(orderData) {
try {
const payment = await simiz.payments.create({
// ... paramètres
});
return { success: true, payment };
} catch (error) {
// Erreurs de validation
if (error.code === 'validation_error') {
return {
success: false,
type: 'validation',
errors: error.details
};
}
// Erreurs MTN spécifiques
const mtnErrors = {
'invalid_phone': {
message: 'Ce numéro n\'est pas un numéro MTN MoMo valide',
action: 'verify_phone'
},
'insufficient_funds': {
message: 'Solde MTN MoMo insuffisant',
action: 'retry_later'
},
'transaction_limit': {
message: 'Limite journalière atteinte',
action: 'retry_tomorrow'
},
'user_not_found': {
message: 'Ce numéro n\'est pas enregistré sur MTN MoMo',
action: 'check_number'
},
'service_unavailable': {
message: 'MTN MoMo temporairement indisponible',
action: 'retry_later'
}
};
const errorInfo = mtnErrors[error.code] || {
message: 'Une erreur est survenue',
action: 'contact_support'
};
return {
success: false,
type: 'payment_error',
code: error.code,
...errorInfo
};
}
}
Validation des numéros MTN
// utils/phone-validation.js
const MTN_PREFIXES = {
cameroun: ['23767', '23768', '237650', '237651'],
'cote-ivoire': ['22505'],
ghana: ['233024', '233054', '233055'],
benin: ['22996', '22997']
};
export function isMTNNumber(phone, country = 'cameroun') {
// Nettoyer le numéro
const cleanPhone = phone.replace(/[^0-9]/g, '');
// Ajouter le préfixe pays si absent
let fullNumber = cleanPhone;
if (!cleanPhone.startsWith('237') && country === 'cameroun') {
fullNumber = '237' + cleanPhone;
}
// Vérifier les préfixes MTN
const prefixes = MTN_PREFIXES[country] || MTN_PREFIXES['cameroun'];
return prefixes.some(prefix => fullNumber.startsWith(prefix));
}
export function formatMTNNumber(phone, country = 'cameroun') {
const cleanPhone = phone.replace(/[^0-9]/g, '');
// Format international
if (country === 'cameroun') {
if (cleanPhone.startsWith('237')) {
return '+' + cleanPhone;
}
if (cleanPhone.startsWith('6')) {
return '+237' + cleanPhone;
}
}
return '+' + cleanPhone;
}
Tests en Sandbox
Numéros de test MTN
| Numéro | Comportement |
|---|
| +237670000001 | Paiement réussi |
|---|---|
| +237670000002 | Solde insuffisant |
| +237670000003 | Timeout (pas de confirmation) |
| +237670000004 | Refusé par l'utilisateur |
| +237670000005 | Compte bloqué |
Script de test automatisé
// tests/mtn-momo.test.js
describe('MTN MoMo Payments', () => {
it('should create a successful payment', async () => {
const result = await createMTNMoMoPayment({
amount: 1000,
customerPhone: '+237670000001',
description: 'Test payment',
orderId: 'TEST-001'
});
expect(result.success).toBe(true);
expect(result.paymentId).toBeDefined();
expect(result.status).toBe('pending');
});
it('should handle insufficient funds', async () => {
const result = await createPaymentWithErrorHandling({
amount: 1000,
customerPhone: '+237670000002',
description: 'Test payment',
orderId: 'TEST-002'
});
expect(result.success).toBe(false);
expect(result.code).toBe('insufficient_funds');
});
it('should validate MTN numbers', () => {
expect(isMTNNumber('+237670123456')).toBe(true);
expect(isMTNNumber('+237680123456')).toBe(true);
expect(isMTNNumber('+237691234567')).toBe(false); // Orange
});
});
Bonnes pratiques
1. Timeout et retry
const payment = await simiz.payments.create({
// ...
}, {
timeout: 30000, // 30 secondes max
retries: 2, // 2 tentatives en cas d'erreur réseau
idempotencyKey: order-${orderId}-v1 // Éviter les doublons
});
2. Logging structuré
import winston from 'winston';
const logger = winston.createLogger({
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'payments.log' })
]
});
// À chaque paiement
logger.info('Payment initiated', {
orderId,
amount,
provider: 'mtn_momo',
phone: maskPhone(customerPhone),
timestamp: new Date().toISOString()
});
3. Monitoring
// Métriques à suivre
const metrics = {
payments_initiated: 0,
payments_success: 0,
payments_failed: 0,
payments_expired: 0,
average_confirmation_time: 0
};
// Exposer via endpoint /metrics pour Prometheus/Grafana
Conclusion
L'intégration de MTN MoMo avec Simiz simplifie considérablement le processus. Notre API gère la complexité technique et vous permet de vous concentrer sur votre produit.
Ressources supplémentaires :Questions techniques ? developer@simiz.io