Aller au contenu
Développeurs

API Paiements
Encaisse depuis ton propre site

Paiements Mobile Money (Wave, Orange Money, MTN, Moov), carte et crypto créés par API depuis ton tunnel. Le net encaissé est crédité sur ton wallet Business. Le mode test n'engage aucun argent réel.

Démarrage rapide

La clé mnz_sk_test_… se génère dans Paramètres → Développeur. Le paiement se crée avec l'opérateur et le numéro collectés dans ton tunnel ; la demande de validation part sur le téléphone du client.

Requête minimale
curl -X POST https://api.monniz.shop/v1/payments \
  -H "Authorization: Bearer mnz_sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "15000",
    "payment_method": "moov-benin",
    "customer": { "phone": "90165245" }
  }'
Complet
curl -X POST https://api.monniz.shop/v1/payments \
  -H "Authorization: Bearer mnz_sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "15000",
    "currency": "XOF",
    "description": "Commande 42",
    "payment_method": "moov-benin",
    "customer": {
      "name": "Aminata D.",
      "phone": "90165245",
      "email": "[email protected]"
    },
    "success_url": "https://ta-boutique.com/merci",
    "cancel_url": "https://ta-boutique.com/panier"
  }'

Champs obligatoires et next_action

payment_method (tag opérateur, cf. GET /v1/config) est obligatoire ; customer.phone l'est dès qu'un numéro est à débiter. La demande arrive sur le téléphone du client et la réponse porte un next_action à afficher pendant la validation.

Carte bancaire : payment_method: "card" sans numéro — next_action renvoie l'URL du prestataire où ton client saisit sa carte (périmètre PCI SAQ A). Crypto : payment_method: "crypto" + crypto_asset next_action renvoie l'adresse de dépôt à afficher.
Réponse
{
  "success": true,
  "data": { "payment": {
    "id": "pay_01J8XKQ2M4N7P9R2S5T8V1W4X7",
    "payment_id": "mzp_01J8XKQ2M4N7P9R2S5T8V1W4X6",
    "status": "PENDING",
    "livemode": false,
    "amount": "15000",
    "currency": "XOF",
    "mode": "direct",
    "checkout_url": null,
    "expires_at": "2026-08-19T15:30:00+00:00",
    "next_action": {
      "type": "pin_prompt",
      "message": "Ton client doit valider sur son téléphone.",
      "instructions": "Composez *880# pour valider."
    }
  }},
  "meta": { "request_id": "req_01J8XKQ2M4N7P9R2S5T8V1W4X7" }
}

Endpoints

  • POST/v1/paymentsCréer un paiement → next_action
  • GET/v1/payments/{id}Statut (id pay_… ou référence mzp_…)
  • GET/v1/paymentsListe (curseur limit + starting_after)
  • GET/v1/configPays, opérateurs, bornes et consignes payeur
  • GET/v1/crypto/assetsActifs et réseaux crypto acceptés
  • POST/v1/payments/{id}/crypto/confirmTon client déclare avoir envoyé les fonds (throttle 20/min)
  • POST/v1/payment_linksCréer un lien de paiement
  • GET/v1/payment_linksListe de tes liens de paiement
  • GET/v1/payment_links/{id}Détail d'un lien
  • POST/v1/payment_links/{id}/deactivateDésactiver un lien

Les liens de paiement fournissent une page d'encaissement hébergée par Monniz. Le webhook payment_link.paid signale le règlement d'un lien.

Les remboursements sont exécutés par Monniz ; les événements refund.succeeded / refund.failed et les statuts REFUNDED / PARTIALLY_REFUNDED en notifient le marchand.

Idempotence sur POST /v1/payments

POST /v1/payments accepte un en-tête Idempotency-Key (un UUID par tentative métier). Sans Idempotency-Key ni payment_id fourni, deux appels identiques créent deux paiements. Les codes IDEMPOTENCY_KEY_REUSE et IDEMPOTENCY_IN_PROGRESS s'y rapportent.

Référence interactive complète (schémas, exemples, codes d'erreur) : api.monniz.shop/docs

Collection Postman

Deux dossiers, Paiements et Prépayés. La variable api_key de la collection porte ta clé.

Collection Postman

S'importe dans Postman via ImportLink avec cette URL — https://api.monniz.shop/docs.postman

Cycle de vie d'un paiement

Statuts
PENDING → PROCESSING → SUCCEEDED
                     → FAILED  (avec failure.code)
                     → IN_RECONCILIATION → SUCCEEDED | FAILED
        → CANCELLED | EXPIRED

Un paiement FAILED porte sa cause dans failure.code. La liste des codes peut s'allonger sans préavis.

failure.code
INSUFFICIENT_BALANCE   solde mobile money insuffisant
PAYMENT_NOT_APPROVED   le client n'a pas confirmé (PIN non saisi, abandon)
PAYMENT_DECLINED       refus opérateur, sans motif exploitable remonté
PAYER_NOT_FOUND        numéro inconnu chez l'opérateur
OPERATOR_TIMEOUT       l'opérateur n'a pas répondu à temps

Un paiement expire au bout de 30 minutes (expires_at) : resté PENDING, il passe EXPIRED. Cet état est final ; un encaissement ultérieur passe par un nouveau paiement, avec un nouveau payment_id le cas échéant.

Direct & hébergé

Direct — le mode par défaut, obtenu dès que payment_method est envoyé. L'opérateur et le numéro sont collectés dans ton tunnel ; l'encaissement part immédiatement. La carte et la crypto sont aussi du direct : sans numéro, leur next_action porte l'URL du prestataire ou l'adresse de dépôt.

Parcours crypto : GET /v1/crypto/assets liste les actifs et réseaux acceptés. Le paiement se crée avec payment_method: "crypto" et crypto_asset ; next_action porte l'adresse de dépôt à afficher. POST /v1/payments/{id}/crypto/confirm déclare l'envoi des fonds ; le règlement est signalé par le webhook payment.succeeded ou GET /v1/payments/{id}.

Page hébergée (mode: "hosted") — la page Monniz collecte le moyen de paiement et la réponse porte une checkout_url. Se demande explicitement ; destinée aux intégrations sans tunnel propre, comme le plugin WooCommerce.

Paiement direct
curl -X POST https://api.monniz.shop/v1/payments \
  -H "Authorization: Bearer mnz_sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "15000",
    "payment_method": "orange-money-ci",
    "customer": { "name": "Aminata D.", "phone": "0000000" },
    "otp_code": "123456"
  }'

En direct, pas de checkout_url : next_action indique la marche à suivre.

next_action
"next_action": {
  "type": "pin_prompt",
  "message": "Ton client doit valider sur son téléphone.",
  "instructions": {
    "ussd_code": "#144*82#",
    "timing": "before",
    "fr": "Composez #144*82# puis sélectionnez l'option 2…",
    "en": "Dial #144*82# on your phone, then follow the instructions."
  }
}

// ou, pour Wave et la carte :
"next_action": { "type": "redirect", "redirect_url": "https://pay.wave.com/c/cos-1p8xkq2m4n7p9r2s" }

OTP requis avant la création

Les opérateurs marqués otp_required dans /v1/config (Orange Money CI et Burkina) exigent un otp_code à la création — le timing before l'indique. L'OTP est obtenu par le client via le code USSD avant l'appel de création.

Configuration active

GET /v1/config décrit ce qui est encaissable maintenant. Chaque opérateur porte ses propres bornes et, le cas échéant, la consigne à afficher au payeur. Bornes et codes USSD peuvent changer sans préavis.

Un opérateur
{
  "tag": "orange-money-ci",
  "name": "Orange Money CI",
  "otp_required": true,
  "amount": { "min": "200", "max": "300000" },
  "instructions": {
    "ussd_code": "#144*82#",
    "timing": "before",
    "fr": "Composez #144*82# puis sélectionnez l'option 2 pour obtenir le code OTP.",
    "en": "Dial #144*82# on your phone, then follow the instructions."
  }
}

À l'encaissement, les bornes de l'opérateur choisi font foi, pas les bornes globales de la réponse.

Erreurs

Toute réponse en erreur porte un code machine dans errors.code. Le code est stable ; le message est en français et peut être reformulé sans préavis.

Réponse d'erreur
{
  "success": false,
  "message": "Paiement introuvable.",
  "errors": { "code": "PAYMENT_NOT_FOUND" }
}
errors.code
PAYMENT_NOT_FOUND              paiement inconnu de ton compte
PAYMENT_NOT_PAYABLE            déjà réglé, annulé ou expiré
PAYMENT_METHOD_UNAVAILABLE     opérateur indisponible ou inconnu
CUSTOMER_PHONE_REQUIRED        numéro manquant pour ce moyen de paiement
PAYMENT_LINK_NOT_FOUND         lien inconnu de ton compte
IDEMPOTENCY_KEY_REUSE          même clé, corps différent (409)
IDEMPOTENCY_IN_PROGRESS        requête identique encore en cours (409)
RATE_LIMITED                   trop de requêtes sur ta clé (429)
PROVIDER_UNAVAILABLE           l'opérateur ne répond pas — réessaie (400 ou 502)

PROVIDER_UNAVAILABLE peut porter un statut HTTP 400 ou 502 ; le code d'erreur est la valeur stable.

Chaque réponse publie ton quota :

En-têtes de quota
X-RateLimit-Limit       requêtes autorisées par minute sur ta clé
X-RateLimit-Remaining   ce qu'il te reste sur la minute en cours
X-RateLimit-Reset       secondes avant remise à zéro
Retry-After             (sur 429 uniquement) attends ce nombre de secondes

Mode test

Avec une clé mnz_sk_test_…, aucun argent réel ne circule : la page de paiement affiche un simulateur (payer, solde insuffisant, PIN non saisi, timeout, indéterminé). Certains montants forcent un scénario :

Montants magiques (test uniquement)
1001 FCFA  →  FAILED / INSUFFICIENT_BALANCE
1002 FCFA  →  reste PROCESSING (timeout opérateur)
1003 FCFA  →  FAILED / PAYMENT_NOT_APPROVED
1004 FCFA  →  IN_RECONCILIATION
autre      →  choix du scénario sur la page (simulateur)

IN_RECONCILIATION existe aussi en production

Le scénario 1004 (IN_RECONCILIATION) survient aussi en production.

Frais et crédit du wallet

Ton client paie exactement amount. Les frais opérateur sont à ta charge : ton wallet Business est crédité du net (montant − frais). Le détail des opérateurs et de leurs frais est dans GET /v1/config.

Le net passe d'abord en solde en attente

À la confirmation, le net est porté au solde en attente, puis libéré sur ton solde disponible au terme du délai de règlement — même règle qu'une vente en boutique.

Webhooks & support

Chaque réponse porte meta.request_id (req_…) : il identifie l'appel auprès du support.

Webhooks paiements

L'endpoint se configure dans l'onglet Développeur. Il reçoit payment.succeeded, payment.failed et payment_link.paid (règlement d'un lien créé par l'API). Chaque envoi est signé t=…,v1=… ; la fonction de vérification est fournie dans chaque SDK.

Source de vérité du statut

Le retour navigateur sur ta success_url ne confirme pas le paiement. Si ton endpoint est injoignable, l'événement est réessayé pendant ~2 jours puis abandonné. GET /v1/payments/{id} est la source de vérité du statut.

Accès live

Les clés live exigent un KYB approuvé et une demande d'accès validée par Monniz — depuis l'onglet Développeur. Le mode test est illimité et sans condition.

Aller plus loin

Prêt à encaisser depuis ton site ?

Crée ton compte Monniz Business, génère ta clé test et fais ton premier paiement simulé en cinq minutes.

API Paiements — Documentation développeurs | Monniz Business