Documentation Index
Fetch the complete documentation index at: https://docs.sahelpay.ml/llms.txt
Use this file to discover all available pages before exploring further.
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