Guides
Remboursements
Un remboursement rend au client la totalité de ce qu'il a payé. L'appel API enregistre une demande : le décaissement est ensuite exécuté par MivaaPay, et vous êtes notifié de son issue par webhook.
Comment ça marche
-
Vous demandez le remboursement
POST /payments/{reference}/refundcrée un remboursement au statutpending. -
MivaaPay exécute le décaissement
La demande passe par un contrôle back-office, puis l'argent repart vers le compte du client.
-
Vous êtes notifié
Un webhook
refund.succeededourefund.failedclôt l'opération.
Contrairement à un encaissement, un remboursement n'est pas exécuté dans la seconde : il y a une validation humaine côté MivaaPay. Ne promettez pas au client un retour immédiat de l'argent, et ne bloquez pas votre interface en attendant.
Conditions
| Règle | Détail |
|---|---|
| Paiement validé | Seul un paiement au statut succeeded peut être remboursé. Un pending ou un failed est refusé. |
| Un seul par paiement | Une deuxième demande sur le même paiement renvoie un conflict. |
| Montant intégral |
Le remboursement porte sur total_amount, c'est-à-dire ce que le client a
réellement été débité — votre montant et la commission MivaaPay.
|
| Pas de remboursement partiel | L'API v1 ne permet pas de rembourser une fraction du paiement. |
Demander un remboursement
curl -X POST https://api.mivaapay.com/api/v1/payments/9f2c1a44-8b0e-4b2e-9b0a-2f1d6e5c7a31/refund \
-H "Authorization: Bearer $MIVAAPAY_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "reason": "Article en rupture de stock" }'
$refund = Http::withToken(env('MIVAAPAY_SECRET_KEY'))
->post("https://api.mivaapay.com/api/v1/payments/{$reference}/refund", [
'reason' => 'Article en rupture de stock',
])
->json('data');
const res = await fetch(
`https://api.mivaapay.com/api/v1/payments/${reference}/refund`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.MIVAAPAY_SECRET_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ reason: 'Article en rupture de stock' })
}
);
const { data: refund } = await res.json();
refund = requests.post(
f'https://api.mivaapay.com/api/v1/payments/{reference}/refund',
json={'reason': 'Article en rupture de stock'},
headers={'Authorization': f"Bearer {SECRET_KEY}"},
timeout=30,
).json()['data']
{
"success": true,
"data": {
"reference": "3b1f7c02-9d5e-4a6b-8f21-0c4e9a7b2d13",
"payment_reference": "9f2c1a44-8b0e-4b2e-9b0a-2f1d6e5c7a31",
"amount": 5090,
"currency": "XOF",
"status": "pending",
"reason": "Article en rupture de stock",
"processed_at": null,
"created_at": "2026-08-06T09:12:44+00:00"
}
}
Le champ reason est optionnel mais recommandé : il apparaît dans le back-office
et accélère le traitement de votre demande. Sans lui, la mention
« Remboursement demandé par le marchand » est enregistrée.
La reference renvoyée est celle du remboursement, distincte de celle
du paiement. C'est elle qu'attend GET /refunds/{reference}.
Statuts d'un remboursement
| Statut | Signification |
|---|---|
pending | La demande est enregistrée, le décaissement n'a pas encore abouti. |
succeeded | Le client a été recrédité. Le paiement associé passe à refunded. |
failed | Le décaissement n'a pas pu être exécuté. Contactez le support. |
Suivre un remboursement
Comme pour les paiements, le webhook est la voie recommandée ; l'interrogation directe reste possible et rafraîchit l'état auprès de l'opérateur quand le décaissement est déjà lancé :
curl https://api.mivaapay.com/api/v1/refunds/3b1f7c02-9d5e-4a6b-8f21-0c4e9a7b2d13 \
-H "Authorization: Bearer $MIVAAPAY_SECRET_KEY"
Et pour l'historique complet, paginé :
curl "https://api.mivaapay.com/api/v1/refunds?per_page=50" \
-H "Authorization: Bearer $MIVAAPAY_SECRET_KEY"
Notifications
Un remboursement déclenche jusqu'à deux notifications :
| Événement | Ce qu'il signifie |
|---|---|
refund.succeeded | Le décaissement a abouti, le client est recrédité. |
refund.failed | Le décaissement a échoué. Le paiement reste succeeded. |
payment.refunded | Émis sur le paiement associé quand celui-ci bascule à refunded. |
{
"id": "8a2d5e10-6b7c-4f3a-b0e9-1d2c3f4a5b6c",
"event": "refund.succeeded",
"created_at": "2026-08-06T14:03:27+00:00",
"data": {
"reference": "3b1f7c02-9d5e-4a6b-8f21-0c4e9a7b2d13",
"payment_reference": "9f2c1a44-8b0e-4b2e-9b0a-2f1d6e5c7a31",
"amount": 5090,
"currency": "XOF",
"status": "succeeded",
"reason": "Article en rupture de stock",
"processed_at": "2026-08-06T14:03:20+00:00",
"created_at": "2026-08-06T09:12:44+00:00"
}
}
Erreurs possibles
| HTTP | Code | Cause |
|---|---|---|
| 404 | not_found |
Aucun paiement de votre compte ne porte cette référence. |
| 409 | conflict |
Le paiement n'est pas succeeded — le message indique son statut réel. |
| 409 | conflict |
Un remboursement existe déjà pour ce paiement. |
{
"success": false,
"error": {
"code": "conflict",
"message": "Seul un paiement au statut « succeeded » peut être remboursé (statut actuel : pending)."
}
}
Un remboursement rend au client le total qu'il avait payé, commission comprise. Assurez-vous
que votre solde MivaaPay le couvre — GET /balance — avant d'enchaîner plusieurs
demandes.