Docs API

Guides

Suivre un paiement

Créer un paiement ne fait que lancer la demande. Savoir s'il a abouti passe soit par un webhook, soit par une interrogation de l'API. Cette page explique quand utiliser l'un ou l'autre, et comment ne pas livrer une commande qui n'a pas été payée.

Les quatre statuts

L'API expose quatre statuts, et eux seuls. Ils suffisent à décider quoi faire :

StatutSignificationCe que vous faites
pending La demande est partie chez l'opérateur, l'issue est inconnue. Vous attendez. Rien n'est acquis.
succeeded Le client a été débité, l'argent est crédité sur votre compte MivaaPay. Vous livrez la commande.
failed L'opérateur a refusé : solde insuffisant, refus du client, numéro invalide, délai dépassé. Vous proposez un nouvel essai. Rien n'a été débité.
refunded Le paiement a été remboursé au client. Vous annulez la commande. Voir Remboursements.
Un état final ne change plus

succeeded et failed sont définitifs : un paiement validé ne redevient jamais pending, et un paiement échoué ne devient jamais succeeded. La seule transition possible après succeeded est refunded, à votre demande.

Votre back-office affiche davantage d'états (« À reverser », « Transférée »…) : ils décrivent où en est notre reversement vers vous, pas le paiement du client. Du point de vue de l'API, ils sont tous succeeded — vous avez été payé, le reste est notre affaire.

Webhooks — la méthode recommandée

Dès qu'un paiement bascule en succeeded ou failed, MivaaPay appelle votre endpoint. Vous n'attendez rien, ne consommez pas de quota, et êtes notifié en quelques secondes plutôt qu'au prochain tour de boucle.

Événements de paiement
payment.succeeded   # le client a été débité
payment.failed      # l'opérateur a refusé
payment.refunded    # le paiement a été remboursé

La configuration de l'endpoint, la vérification de signature et la gestion des rejeux sont détaillées dans Webhooks. C'est la page à lire avant la mise en production.

Interroger l'API

Si vous ne pouvez pas exposer d'URL publique — développement local, application interne — interrogez le paiement directement :

curl https://api.mivaapay.com/api/v1/payments/9f2c1a44-8b0e-4b2e-9b0a-2f1d6e5c7a31 \
  -H "Authorization: Bearer $MIVAAPAY_SECRET_KEY"
$payment = Http::withToken(env('MIVAAPAY_SECRET_KEY'))
    ->get("https://api.mivaapay.com/api/v1/payments/{$reference}")
    ->json('data');

if ($payment['status'] === 'succeeded') {
    // livrer la commande
}
const res = await fetch(`https://api.mivaapay.com/api/v1/payments/${reference}`, {
  headers: { Authorization: `Bearer ${process.env.MIVAAPAY_SECRET_KEY}` }
});

const { data: payment } = await res.json();
payment = requests.get(
    f'https://api.mivaapay.com/api/v1/payments/{reference}',
    headers={'Authorization': f"Bearer {SECRET_KEY}"},
    timeout=30,
).json()['data']
L'état est rafraîchi à la lecture

Tant qu'un paiement est pending, chaque appel à GET /payments/{reference} va redemander son état à l'opérateur avant de répondre. Vous obtenez donc l'information la plus fraîche possible, mais la requête est plus lente qu'une simple lecture en base : prévoyez un timeout d'au moins 30 secondes côté client.

Chercher par votre propre identifiant

Le même endpoint accepte votre external_id à la place de la référence MivaaPay. Pratique quand vous n'avez sous la main que votre numéro de commande :

Recherche par external_id
curl https://api.mivaapay.com/api/v1/payments/cmd_1042 \
  -H "Authorization: Bearer $MIVAAPAY_SECRET_KEY"

À quelle cadence interroger

L'API accepte 120 requêtes par minute et par adresse IP : une boucle serrée sur plusieurs paiements en parallèle vous fera atteindre la limite. Une cadence qui tient la route :

  • Attendez 5 secondes après la création avant la première vérification ;
  • puis interrogez toutes les 5 secondes pendant 2 minutes ;
  • puis espacez à 30 secondes jusqu'à 10 minutes ;
  • au-delà, arrêtez la boucle et traitez le paiement comme abandonné de votre côté.
$deadline = time() + 120;

do {
    sleep(5);

    $status = Http::withToken(env('MIVAAPAY_SECRET_KEY'))
        ->get("https://api.mivaapay.com/api/v1/payments/{$reference}")
        ->json('data.status');
} while ($status === 'pending' && time() < $deadline);

// $status vaut 'succeeded', 'failed', ou encore 'pending' si le client n'a rien fait
const sleep = ms => new Promise(r => setTimeout(r, ms));

async function waitForPayment(reference, timeoutMs = 120000) {
  const deadline = Date.now() + timeoutMs;

  while (Date.now() < deadline) {
    await sleep(5000);

    const res = await fetch(`https://api.mivaapay.com/api/v1/payments/${reference}`, {
      headers: { Authorization: `Bearer ${process.env.MIVAAPAY_SECRET_KEY}` }
    });
    const { data } = await res.json();

    if (data.status !== 'pending') return data;
  }

  return null; // toujours en attente : à traiter comme non payé
}
import time

def wait_for_payment(reference, timeout=120):
    deadline = time.time() + timeout

    while time.time() < deadline:
        time.sleep(5)

        payment = requests.get(
            f'https://api.mivaapay.com/api/v1/payments/{reference}',
            headers={'Authorization': f"Bearer {SECRET_KEY}"},
            timeout=30,
        ).json()['data']

        if payment['status'] != 'pending':
            return payment

    return None  # toujours en attente : à traiter comme non payé
Ne jamais livrer sur pending

Un paiement resté pending à la fin de votre boucle n'est pas un paiement échoué : le client peut très bien valider trois minutes plus tard. Ne libérez pas la commande, mais ne la marquez pas non plus définitivement perdue — laissez le webhook, ou une vérification différée, trancher.

Lister vos paiements

Pour un rapprochement comptable ou un écran d'historique, GET /payments renvoie la liste paginée avec des filtres :

Paiements validés du mois
curl -G https://api.mivaapay.com/api/v1/payments \
  -H "Authorization: Bearer $MIVAAPAY_SECRET_KEY" \
  --data-urlencode "status=succeeded" \
  --data-urlencode "from=2026-08-01" \
  --data-urlencode "to=2026-08-31" \
  --data-urlencode "per_page=100"

Filtres disponibles : status, external_id, from, to, per_page. Les détails sont dans la référence.

Rapprocher webhook et interrogation

Les deux mécanismes peuvent vous apprendre la même chose deux fois : le webhook arrive pendant que votre boucle tourne encore. C'est normal, et sans conséquence si votre traitement est idempotent.

La règle pratique : stockez la reference avec l'état que vous avez déjà traité, et ignorez toute notification qui ne change rien. Livrez la commande sur la première transition vers succeeded, jamais sur les suivantes.