Docs API

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

  1. Vous demandez le remboursement

    POST /payments/{reference}/refund crée un remboursement au statut pending.

  2. MivaaPay exécute le décaissement

    La demande passe par un contrôle back-office, puis l'argent repart vers le compte du client.

  3. Vous êtes notifié

    Un webhook refund.succeeded ou refund.failed clôt l'opération.

Ce n'est pas instantané

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ègleDé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']
201 Created
{
  "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

StatutSignification
pendingLa demande est enregistrée, le décaissement n'a pas encore abouti.
succeededLe client a été recrédité. Le paiement associé passe à refunded.
failedLe 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é :

Consulter un remboursement
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é :

Lister les remboursements
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énementCe qu'il signifie
refund.succeededLe décaissement a abouti, le client est recrédité.
refund.failedLe décaissement a échoué. Le paiement reste succeeded.
payment.refundedÉmis sur le paiement associé quand celui-ci bascule à refunded.
Webhook — refund.succeeded
{
  "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

HTTPCodeCause
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.
409 — paiement non validé
{
  "success": false,
  "error": {
    "code": "conflict",
    "message": "Seul un paiement au statut « succeeded » peut être remboursé (statut actuel : pending)."
  }
}
Effet sur votre solde

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.