# Intégrer moneriz dans votre SaaS

Documentation dʼintégration · mise à jour le 27 septembre 2026.
Origine de lʼAPI : https://api.moneriz.com
Les chemins ci-dessous incluent /v1 : ne pas ajouter ce préfixe deux fois.

## 1. Clés et environnements

Dans le dashboard, ouvrir « Clés & webhook ». Créer une paire dans le mode voulu.
- Test : izp_test_pk_… et izp_test_sk_… ; aucun encaissement de production.
- Production : izp_live_pk_… et izp_live_sk_… ; paiements réels.
- La clé publique peut lire GET /v1/payment-methods, et seulement ce catalogue.
- La clé secrète authentifie les opérations serveur : Authorization: Bearer $MONERIZ_SECRET_KEY.
- Plusieurs paires sont possibles. La clé publique reste visible ; le secret est affiché une seule fois.
- Conserver les secrets dans les variables dʼenvironnement du serveur. Ne jamais les mettre dans le frontend, le dépôt ou un assistant IA.
- Le mode dépend de la clé utilisée. Séparer également les endpoints et secrets de webhook test/live.
- MODE_NOT_CONFIGURED signifie que le prestataire du mode concerné nʼest pas configuré sur la plateforme.

## 2. Choisir le parcours

### Session SaaS : parcours recommandé

Dans « Intégration » (/integration), choisir redirect ou iframe, enregistrer les domaines autorisés et les URL de retour. Le serveur du SaaS crée une session par commande avec POST /v1/checkout-sessions, la clé secrète et Idempotency-Key (16 à 128 caractères, conservée sur la commande).

Corps JSON : { "amount": 5000, "currency": "XOF", "title": "Accès Pro", "country": "SN", "reference": "commande-2026-001", "metadata": { "orderId": "commande-2026-001" }, "successUrl": "https://exemple.sn/merci", "cancelUrl": "https://exemple.sn/panier" }.

amount, title et reference sont obligatoires. Montant entier, minimum 100 XOF, maximum configuré (5 000 000 par défaut), déterminé par le serveur marchand. country vaut SN par défaut (catalogue SN, CI, BF, ML, TG, BJ). expiresInMinutes est facultatif, de 5 à 1440, 30 par défaut. metadata est un objet de 16 000 caractères JSON maximum ; checkoutSessionId et paymentLink* sont réservés. Les coordonnées, OTP et moyen de paiement sont collectés au checkout, pas dans cette requête.

Réponse 201 : id (cs_…), object=checkout_session, mode, status, checkoutUrl, embedUrl, url, integrationMode, paymentId, paymentStatus, amount, currency, reference, metadata, successUrl, cancelUrl, expiresAt et createdAt. Aucune charge nʼest lancée lors de la création de session.

Le frontend choisit selon session.integrationMode : redirect => window.location.assign(session.checkoutUrl) ; iframe => monter une iframe avec src=session.embedUrl. Cette dernière URL vaut null sans domaine configuré. url reprend celle du mode choisi et ne remplace pas cette distinction. Fournir integrationMode et embedOrigin dans le POST pour déroger à la préférence ; le domaine doit toujours être autorisé. En production, iframe et URL de retour exigent HTTPS.

Les valeurs du dashboard sont utilisées si le POST omet integrationMode, embedOrigin, successUrl ou cancelUrl. null supprime explicitement une URL de retour. Une préférence modifiée ne change pas une session existante. Le retrait dʼun domaine du dashboard retire son droit dʼafficher les iframes.

Une référence est unique par organisation et mode : réutiliser la même session et la même clé pour cette commande. Une nouvelle clé sur une référence déjà utilisée renvoie 409 CHECKOUT_REFERENCE_USED. GET /v1/checkout-sessions/{id} consulte la session avec la clé secrète, sans appel prestataire. Statuts : open, processing, complete, expired, disabled, reversed.

Le webhook payment.succeeded reprend votre reference et vos metadata, avec metadata.checkoutSessionId. Après vérification, votre serveur active la commande. Le retour vers successUrl ajoute aussi checkout_session_id, payment_id, payment_link_id, status et reference, sans constituer une preuve de paiement. Un échec revient au checkout pour réessayer. cancelUrl est un retour volontaire et ne change pas le statut financier.

### Lien de paiement et checkout moneriz

Depuis « Liens de paiement », créer un lien à montant fixe ou libre, unique ou réutilisable. Les montants sont des entiers XOF (FCFA sans décimales), de 100 à 5 000 000 par défaut. Le serveur revalide le montant. Le client choisit son pays puis son moyen de paiement ; nom et téléphone sont obligatoires, email facultatif.

Configurer une URL de retour après paiement pour renvoyer le client vers votre SaaS après confirmation. Le retour ajoute payment_id, payment_link_id, status et reference. Ces paramètres ne prouvent pas un paiement : vérifier le webhook signé côté serveur.

Les liens et leurs réglages se gèrent depuis le dashboard. Ne pas inventer une route publique /v1/payment-links : elle nʼexiste pas. Les routes /api/organizations/... sont réservées à la session du dashboard et au contrôle CSRF, pas aux clés API.

### API de paiement direct : parcours avancé

Créer un paiement par commande, conserver son identifiant et associer reference ou metadata à votre utilisateur côté serveur. Le montant doit venir de votre catalogue serveur, jamais être accepté tel quel depuis le navigateur.

```sh
curl 'https://api.moneriz.com/v1/payments' \
  -H "Authorization: Bearer $MONERIZ_SECRET_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: commande-2026-001' \
  -d '{
    "amount": 5000,
    "currency": "XOF",
    "country": "SN",
    "reference": "commande-2026-001",
    "customer": { "name": "Client Exemple", "phone": "+221771234567" },
    "metadata": { "orderId": "commande-2026-001" },
    "successUrl": "https://exemple.sn/merci",
    "cancelUrl": "https://exemple.sn/panier"
  }'
```

Le corps est strict : amount, currency, country, paymentType, otp, customer, reference, description, metadata, successUrl et cancelUrl sont les champs acceptés. Les coordonnées sont imbriquées dans customer ; utiliser successUrl et checkoutUrl en camelCase.

Réponse 201 : id, status, checkoutUrl, nextAction, confirmation et autres informations du paiement. Une création ne prouve pas lʼencaissement. Sans paymentType, le parcours hébergé permet le choix chez le prestataire uniquement si aucun pay-in nʼest suspendu sur moneriz ; sinon PAYMENT_TYPE_NOT_AVAILABLE demande un choix explicite ou une session checkout moneriz. POST /v1/payments retourne une URL prestataire. Utiliser POST /v1/checkout-sessions pour le checkout moneriz hébergé ou iframe.

Chaque POST /v1 exige Idempotency-Key. Conserver la même clé et le même corps lors des reprises dʼune même intention ; une nouvelle clé représente un nouveau paiement. Un corps différent avec la même clé produit IDEMPOTENCY_KEY_REUSED. Après une réponse ambiguë, un timeout ou une erreur réseau, ne pas créer une deuxième charge avec une autre clé.

## 3. Pays, moyens et actions du client

Lire GET /v1/payment-methods avec une clé du bon mode. Les champs du catalogue incluent country, paymentType, available, availability, phoneRequired, otpRequired, otpInstructions, channel, qrCode, platformEnabled et unavailableReason. Seuls les moyens avec available=true sont sélectionnables ; les autres peuvent rester visibles avec « Indisponible ». unknown nʼest pas une disponibilité. Une suspension moneriz retourne platformEnabled=false et unavailableReason=platform_disabled.

moneriz synchronise le catalogue marchand Bictorys toutes les 72 heures et le rafraîchit aussi à la demande avec un cache de 10 minutes. Les couples implémentés suivent les activations et retraits du compte ; un nouvel opérateur inconnu attend une intégration de son parcours avant dʼêtre proposé. Une disponibilité indique une configuration active, pas la réussite garantie dʼun paiement. Les suspensions du propriétaire moneriz sont relues sans cache et restent indépendantes de la synchronisation. Les nouvelles demandes indisponibles sont refusées avec PAYMENT_TYPE_NOT_AVAILABLE, sans interrompre le suivi des opérations déjà engagées.

Le country racine est un pays du catalogue (SN, CI, BF, ML, TG, BJ). customer.country porte le pays réel du client, au format ISO alpha-2. Pour Mobile Money, les pays doivent correspondre. Pour la carte, utiliser une entrée carte active du catalogue et transmettre le pays réel dans customer.country. Ne pas déduire une disponibilité mondiale garantie.

Pour un paiement direct, passer le paymentType exact du catalogue et les coordonnées requises. Respecter phoneRequired et otpRequired ; ne pas conserver un OTP. Le checkout moneriz impose un téléphone pour tous les moyens, même si certains parcours de lʼAPI prestataire le rendent facultatif.

Traiter nextAction sans supposer une redirection pour chaque moyen :
- redirect : ouvrir url (ou checkoutUrl), par exemple la page carte Bictorys ou Wave.
- ussd : afficher message et attendre la validation sur le téléphone, puis la confirmation serveur.
- qr_code : afficher qrCode et attendre la confirmation.
- null : aucune action disponible ; ne pas fabriquer de lien ou annoncer un paiement réussi.

La carte se saisit chez Bictorys. moneriz filtre le checkout carte avec payment_category=card ; ne jamais recueillir le numéro de carte ou le CVV sur votre serveur. Une autorisation carte (confirmation=authorized) nʼest pas encore un règlement confirmé.

## 4. Confirmer par webhook signé

Déclarer votre URL HTTPS publique dans « Clés & webhook », dans le bon mode, et conserver son secret affiché une seule fois dans MONERIZ_WEBHOOK_SECRET. Les requêtes du webhook arrivent sur votre backend. Le navigateur, une redirection et un postMessage ne sont jamais une preuve de paiement.

Enveloppe JSON : { id, type, createdAt, data: { payment } } pour les paiements, ou data.withdrawal pour les retraits.
Événements : payment.succeeded, payment.failed, payment.expired, payment.reversed, withdrawal.completed, withdrawal.failed.
Statuts du paiement : pending, paid, failed, expired, reversed. payment.succeeded correspond à status=paid.

Vérification obligatoire avant tout traitement :
1. Lire les octets bruts du corps, avant le parseur JSON.
2. Extraire X-Moneriz-Signature : t=<horodatage Unix en millisecondes>,v1=<hex>.
3. Refuser les signatures mal formées et les horodatages éloignés de plus de cinq minutes.
4. Calculer HMAC-SHA256 avec le secret de votre endpoint sur la concaténation t + "." + corpsBrut, puis comparer le résultat hexadécimal en temps constant.
5. Vérifier le mode, lʼidentifiant du paiement, la commande associée, le montant et la devise attendus. Dédoublonner avec event.id et rendre lʼactivation du service idempotente.
6. Enregistrer durablement lʼévénement ou le traitement avant de répondre 2xx, en moins de dix secondes. Ne pas acquitter un événement encore uniquement en mémoire.

Les échecs de livraison sont retentés après 30 s, 2 min, 10 min, 1 h, 6 h, 24 h ; un rejeu manuel est disponible dans le dashboard. Un échec synchrone du POST /v1/payments est communiqué dans sa réponse, sans forcément émettre payment.failed.

Accepter une confirmation tardive après payment.expired (data.late=true). Gérer payment.reversed selon votre politique dʼaccès. Ne pas activer un abonnement sur un simple retour successUrl.

GET /v1/payments/{id} et GET /v1/payments existent côté serveur avec la clé secrète : ils lisent lʼétat enregistré par moneriz. Ils ne vérifient pas le prestataire. Utiliser les webhooks pour les notifications, sans interroger lʼAPI en boucle.

## 5. Logo, couleurs et iframe

Dans « Paramètres », personnaliser le checkout avec les couleurs et un logo PNG, JPEG ou WebP de 2 Mo maximum, stocké dans Cloudflare R2. Enregistrer les origines exactes autorisées à intégrer le checkout.

Pour une session SaaS, utiliser embedUrl et le code de la page « Intégration ». Pour un lien manuel, utiliser « Intégrer sur mon site » dans « Liens de paiement ». Ne pas mettre la page prestataire dans une iframe à la place du checkout moneriz. La carte et les validations externes peuvent ouvrir un nouvel onglet ; conserver le suivi du paiement dans le module.

Lʼiframe peut envoyer moneriz.ready, moneriz.resize et moneriz.payment. Vérifier event.origin et event.source avant de modifier lʼinterface. Ces événements ne remplacent jamais le webhook signé pour activer un accès. Le retour SaaS sʼeffectue par bouton depuis le checkout intégré, pour éviter de charger le SaaS dans sa propre iframe.

## 6. Wallet et limites du produit

- Commission moneriz : 5 % sur les nouveaux paiements confirmés, arrondie à lʼentier inférieur en FCFA.
- Le net devient disponible 72 heures après la confirmation serveur de chaque vente. Un retrait se demande ensuite ; il nʼest pas automatique.
- Les soldes et opérations test/live restent séparés.
- Un lien réutilisable nʼest pas un abonnement automatique. Les prélèvements récurrents et le remboursement marchand automatique ne sont pas des fonctionnalités disponibles.
- Les erreurs /v1 ont la forme { error: { code, message, param? } }. Traiter error.code, respecter Retry-After lorsquʼil est présent, et conserver lʼidempotence pendant les reprises.
- Ne placer ni secret, ni document dʼidentité, ni donnée de carte dans reference, description ou metadata.
