Guide d’intégration
Ce guide détaille le flow complet d’intégration SahelPay pour une application marchande.
Principes fondamentaux
Règles non négociables :
- Votre app est un MERCHANT SahelPay, pas un PSP
- Votre app NE calcule AUCUN frais - SahelPay gère tout
- Le webhook est la SEULE source de vérité pour le statut paiement
Architecture
Flow détaillé
1. Créer le paiement (Backend)
// Votre API route: POST /api/payments/create
const response = await fetch('https://api.sahelpay.ml/v1/payments', {
method: 'POST',
headers: {
'Authorization': `Bearer ${SAHELPAY_SECRET_KEY}`,
'Content-Type': 'application/json',
'X-Idempotency-Key': `order-${orderId}`, // Important!
},
body: JSON.stringify({
amount: 5000,
currency: 'XOF',
payment_method: 'ORANGE_MONEY', // ou WAVE, MOOV, CARD, VISA, MASTERCARD, GIM_UEMOA
country: 'ML',
customer_phone: '+22370123456',
return_url: `${APP_URL}/checkout/return?order_id=${orderId}`,
client_reference: orderId,
metadata: { order_id: orderId }
})
});
const { data } = await response.json();
// Retourner data.redirect_url au frontend
Pour les paiements par carte (CARD, VISA, MASTERCARD, GIM_UEMOA), ajoutez customer_name et customer_email dans le body.
2. Rediriger le client
// Frontend
window.location.href = redirectUrl;
3. Recevoir le webhook
// POST /api/webhooks/sahelpay
export async function POST(request) {
const rawBody = await request.text();
const signature = request.headers.get('x-sahelpay-signature');
// TOUJOURS vérifier la signature
if (!verifySignature(rawBody, signature, WEBHOOK_SECRET)) {
return Response.json({ error: 'Invalid signature' }, { status: 401 });
}
const { event, data } = JSON.parse(rawBody);
// Idempotence: vérifier si déjà traité
const existing = await db.payments.findByTransactionId(data.id);
if (existing?.status === 'success') {
return Response.json({ received: true, already_processed: true });
}
switch (event) {
case 'payment.success':
await db.orders.update(data.metadata.order_id, { status: 'paid' });
break;
case 'payment.failed':
// Laisser la commande en pending pour retry
break;
}
return Response.json({ received: true });
}
Idempotence
Côté création
Utilisez X-Idempotency-Key basé sur l’order_id :
headers: {
'X-Idempotency-Key': `order-${orderId}`
}
Côté webhook
Vérifiez si le paiement est déjà traité avant de mettre à jour :
const existing = await db.payments.findByTransactionId(data.id);
if (existing?.status === 'success') {
return { already_processed: true };
}
Ce que votre app NE DOIT PAS faire
❌ Appeler les providers directement
Orange, Wave, Moov… Tout passe par SahelPay
❌ Calculer des frais
SahelPay gère les frais automatiquement
❌ Créer un wallet
Pas de stockage de solde dans votre app
❌ Marquer PAID sans webhook
Le return_url est pour l’UX uniquement