Technique

MTN MoMo API : Guide d'intégration pour développeurs

J

Jean-Pierre Mbarga

Lead Developer

12 décembre 202612 min de lecture
M

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 :

PaysDevisePréfixes téléphone
CamerounXAF67x, 68x, 650, 651
Côte d'IvoireXOF05x
GhanaGHS024, 054, 055
BéninXOF96, 97
CongoXAF06x

Limites de transaction

TypeLimite journalièreLimite mensuelle
Utilisateur standard500 000 XAF2 000 000 XAF
Utilisateur vérifié2 000 000 XAF10 000 000 XAF
Marchand5 000 000 XAF50 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.js
npm 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

CodeMessageCauseSolution
insufficient_fundsSolde insuffisantLe client n'a pas assez d'argentInformer de recharger
invalid_phoneNuméro invalideNuméro non MTN ou mal formatéValider le préfixe
transaction_limitLimite atteintePlafond journalier dépasséReporter au lendemain
user_not_foundCompte inexistantNuméro pas enregistré MoMoVérifier le numéro
user_blockedCompte bloquéCompte suspendu par MTNContacter MTN
timeoutDélai dépasséClient n'a pas confirméProposer de réessayer
cancelledAnnulé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éroComportement
+237670000001Paiement réussi
+237670000002Solde insuffisant
+237670000003Timeout (pas de confirmation)
+237670000004Refusé par l'utilisateur
+237670000005Compte 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
Partager cet article
J

Jean-Pierre Mbarga

Lead Developer

Passionné par les technologies financières et l'inclusion financière en Afrique. Contribue régulièrement au blog Simiz sur les sujets liés aux paiements Mobile Money.

Articles similaires