Bienvenue dans la documentation de l'API Nkap Pay. Cette API vous permet d'accepter des paiements Mobile Money (MTN, Orange Money) et par carte bancaire (Visa, Mastercard) en Afrique Centrale.
Base URL
text
https://pay.ltcgroup.site/api/v1
Devises supportées
FieldTypeDescription
XAFFranc CFA (CEMAC)Cameroun, Gabon, Congo
XOFFranc CFA (UEMOA)Côte d'Ivoire, Mali
CDFFranc congolaisRD Congo
GNFFranc guinéenGuinée
UGXShilling ougandaisOuganda
EUR / USDCarte via StripeUniquement quand Stripe traite le paiement carte. Le fournisseur carte par défaut au Cameroun est E-nkap, qui n'encaisse qu'en XAF : un paiement carte en EUR y est rejeté en 400.
Un paiement Mobile Money se fait TOUJOURS dans la devise du pays : il n'y a aucune conversion. Envoyer XAF pour la RD Congo est rejeté en 400. La devise de chaque pays est donnée par GET /payments/countries — fiez-vous à elle plutôt qu'à cette liste, qui évolue avec les pays ouverts.
Méthodes de paiement
FieldTypeDescription
MOBILE_MONEYDynamicMobile Money via TouchPay (SDK ou Direct API). Les operateurs et limites dependent du pays. Consultez GET /payments/countries.
BANK_CARDCarte + Mobile MoneyPage de paiement hébergée du fournisseur, ouverte via payment_url. Chez E-nkap (défaut au Cameroun) le client y choisit lui-même sa carte Visa/Mastercard OU son portefeuille Mobile Money, dans l'un des 10 pays couverts (Bénin, Burkina Faso, Cameroun, Centrafrique, Côte d'Ivoire, Gabon, Mali, Sénégal, Tchad, Togo). Le nom BANK_CARD est historique : il désigne le canal hébergé, pas seulement la carte. Chez Stripe, carte uniquement. Pas de limite de montant.
Flux de paiement
1. Créer un paiement via POST /api/v1/payments
2. Rediriger le client vers payment_url
3. Le client paie sur la page de checkout
4. Nkap Pay envoie un webhook à votre callback_url
5. Vérifier la signature et mettre à jour votre système
Getting started
Introduction
Bienvenue dans la documentation de l'API Nkap Pay. Cette API vous permet d'accepter des paiements Mobile Money (MTN, Orange Money) et par carte bancaire (Visa, Mastercard) en Afrique Centrale.
Base URL
text
https://pay.ltcgroup.site/api/v1
Devises supportées
FieldTypeDescription
XAFFranc CFA (CEMAC)Cameroun, Gabon, Congo
XOFFranc CFA (UEMOA)Côte d'Ivoire, Mali
CDFFranc congolaisRD Congo
GNFFranc guinéenGuinée
UGXShilling ougandaisOuganda
EUR / USDCarte via StripeUniquement quand Stripe traite le paiement carte. Le fournisseur carte par défaut au Cameroun est E-nkap, qui n'encaisse qu'en XAF : un paiement carte en EUR y est rejeté en 400.
Un paiement Mobile Money se fait TOUJOURS dans la devise du pays : il n'y a aucune conversion. Envoyer XAF pour la RD Congo est rejeté en 400. La devise de chaque pays est donnée par GET /payments/countries — fiez-vous à elle plutôt qu'à cette liste, qui évolue avec les pays ouverts.
Méthodes de paiement
FieldTypeDescription
MOBILE_MONEYDynamicMobile Money via TouchPay (SDK ou Direct API). Les operateurs et limites dependent du pays. Consultez GET /payments/countries.
BANK_CARDCarte + Mobile MoneyPage de paiement hébergée du fournisseur, ouverte via payment_url. Chez E-nkap (défaut au Cameroun) le client y choisit lui-même sa carte Visa/Mastercard OU son portefeuille Mobile Money, dans l'un des 10 pays couverts (Bénin, Burkina Faso, Cameroun, Centrafrique, Côte d'Ivoire, Gabon, Mali, Sénégal, Tchad, Togo). Le nom BANK_CARD est historique : il désigne le canal hébergé, pas seulement la carte. Chez Stripe, carte uniquement. Pas de limite de montant.
Flux de paiement
1. Créer un paiement via POST /api/v1/payments
2. Rediriger le client vers payment_url
3. Le client paie sur la page de checkout
4. Nkap Pay envoie un webhook à votre callback_url
5. Vérifier la signature et mettre à jour votre système
Getting started
What the API covers
Cette documentation couvre l'encaissement. Le reste de la plateforme existe, mais passe par le tableau de bord — mieux vaut le savoir avant de chercher un endpoint qui n'existe pas.
Disponible via l'API (clé + secret)
FieldTypeDescription
Créer un paiementPOST /paymentsMobile Money (SDK, Direct API) et canal hébergé carte + Mobile Money.
Suivre un paiementGET /payments/{reference}Statut, motif d'échec normalisé, référence opérateur. Re-vérifié en direct chez le fournisseur à chaque appel pour les paiements hébergés.
Lister les paiementsGET /paymentsFiltres par statut et par date, pagination.
Pays et opérateursGET /payments/countriesDevise, limites, opérateurs actifs, préfixes téléphoniques.
Votre configurationGET /payments/meTaux de frais par méthode, porteur des frais, mode par défaut.
Grille de fraisGET /payments/feesPourcentage exact facturé par pays et par opérateur — les frais Mobile Money ne sont pas uniformes.
Webhookspayment.status_changedSigné HMAC-SHA256, 5 tentatives avec backoff.
Tableau de bord uniquement (pas d'API)
FieldTypeDescription
RetraitsdashboardDemander le versement de votre solde vers un compte Mobile Money ou bancaire, et suivre l'état des demandes. Aucun endpoint par clé API : un retrait ne peut pas être déclenché par programme.
SoldedashboardSolde disponible, global et par pays.
RemboursementsmanuelUn remboursement se demande depuis le tableau de bord et se traite hors plateforme. Il ne change pas le statut du paiement et ne déclenche aucun webhook.
Liens de paiementdashboardLiens réutilisables à envoyer à un client, sans intégration.
Rapports et facturesdashboardExports de transactions et facturation de vos frais.
KYC et équipedashboardVérification d'identité de l'entreprise, membres et rôles.
Clés APIdashboardCréation et rotation de la clé et du secret. Une rotation invalide immédiatement l'ancien secret.
Conséquence pratique la plus fréquente : votre argent ne part pas tout seul. Un paiement COMPLETED crédite votre solde LtcPay ; le virement vers votre compte se demande depuis le tableau de bord, et il n'y a aucun moyen de l'automatiser aujourd'hui. Prévoyez-le dans votre exploitation.
Getting started
Authentication
Toutes les requêtes à l'API doivent inclure vos clés API dans les headers HTTP. Vous trouverez vos clés dans le tableau de bord marchand.
Headers
FieldTypeDescription
X-API-KeyREQstringVotre clé API (ltcpay_live_... en production, ltcpay_test_... en test).
Ne partagez jamais votre X-API-Secret. Si vous pensez que votre clé a été compromise, régénérez-la immédiatement depuis le tableau de bord.
Limite de requêtes
La création de paiement est limitée à 60 requêtes par minute par adresse IP. Au-delà, l'API répond 429 : espacez vos appels puis réessayez. Le polling de GET /payments/{reference} toutes les 3-5 secondes reste dans cette limite.
Account
Merchant info
Récupère la configuration de votre compte marchand : taux de frais, porteur des frais, mode de paiement par défaut.
fee_ratenumberTaux de frais de base en pourcentage (ex: 1.75).
fee_ratesobjectTaux par méthode : MOBILE_MONEY (votre taux de base) et BANK_CARD (max entre votre taux et le plancher carte). ATTENTION : MOBILE_MONEY est un plancher bas, pas le taux final — certains pays et opérateurs coûtent plus cher. Pour le taux exact par opérateur, utilisez GET /payments/fees.
mobile_rates_by_countryobjectPays et opérateurs facturés AU-DESSUS de votre taux de base, ex: { "CG": { "AIRTEL": 4.5, "MTN": 4.0 } }. Objet vide = votre taux de base s'applique partout.
card_min_fee_ratenumberPlancher de frais appliqué aux paiements par carte pour tous les marchands (actuellement 5).
fee_bearerstringQui supporte les frais : MERCHANT ou CLIENT.
default_payment_modestringMode de paiement par défaut : SDK ou DIRECT_API.
Cet endpoint vous permet de vérifier votre configuration de frais à tout moment, sans initier de paiement. Le taux Mobile Money n'est pas uniforme : voyez GET /payments/fees ci-dessous pour le pourcentage exact par opérateur.
Account
Grille de frais
Le pourcentage exact facturé sur chaque opérateur. Les frais Mobile Money ne sont pas uniformes : le fournisseur coûte plus cher dans certains pays, donc un seul taux ne suffit pas à décrire ce que coûtera un paiement.
fee_bearerstringQui supporte les frais. CLIENT : les frais sont AJOUTÉS au montant, votre client paie montant + frais. MERCHANT : les frais sont déduits, votre client paie exactement le montant demandé.
base_ratenumberVotre taux de base en pourcentage. C'est un plancher bas : il s'applique là où aucun taux supérieur n'est défini.
mobile_moneyobjectTaux facturé par pays puis par opérateur, ex: { "CG": { "AIRTEL": 4.5 } }. C'est le taux RÉELLEMENT appliqué : utilisez-le pour annoncer le total à votre client.
bank_cardnumberTaux carte effectif : max entre votre taux carte et le plancher plateforme.
card_min_fee_ratenumberPlancher carte appliqué à tous les marchands (actuellement 5).
// fee_bearer = CLIENT : les frais s'ajoutent au montant
const grille = await fetch(BASE + "/payments/fees", { headers }).then(r => r.json());
const taux = grille.mobile_money["CG"]["AIRTEL"]; // 4.5
const base = 5000;
const frais = Math.round(base * taux / 100); // 225 (XAF n'a pas de centimes)
const total = base + frais; // 5225 -> ce que paie le client
// fee_bearer = MERCHANT : le client paie 5000, vous recevez 5000 - 225 = 4775
Interrogez cet endpoint au moment d'afficher le prix, pas une fois pour toutes : les taux suivent les coûts des fournisseurs et peuvent changer. Un opérateur absent de mobile_money est facturé à base_rate.
Payments
Create a payment
Crée un nouveau paiement et retourne une URL de checkout vers laquelle rediriger votre client. La référence retournée est unique et stable.
POSThttps://pay.ltcgroup.site/api/v1/payments
Corps de la requête
FieldTypeDescription
amountREQdecimalMontant en unité entière. 5000 = 5 000 F CFA. Min: 100, max: 5 000 000 (toutes methodes). Sous ce plafond, les limites dependent du pays et de l'operateur ; la carte bancaire n'a pas de limite par operateur.
display_amountnumberOptionnel. Montant equivalent dans votre devise de facturation, affiche a titre indicatif sur le checkout sous le total reel. Jamais debite, jamais recalcule. A fournir avec display_currency.
display_currencystringOptionnel. Code ISO 4217 du montant indicatif (ex: EUR, USD). A fournir avec display_amount.
currencystringOptionnel. Auto-detecte depuis le pays si omis. Doit etre une devise que le fournisseur choisi sait encaisser : la devise du pays pour le Mobile Money, XAF pour la carte via E-nkap, XAF/XOF/EUR/USD pour Stripe. LtcPay ne convertit rien — convertissez avant l'envoi. Sinon : 400 CURRENCY_NOT_SUPPORTED.
merchant_referencestringVotre ID de commande interne. Retourné dans les webhooks. Max 255 car.
descriptionstringAffiché au client sur la page de checkout. Max 500 car.
payment_methodstringMOBILE_MONEY ou BANK_CARD. Omettez pour laisser le client choisir.
payment_modestringSDK (défaut), DIRECT_API ou STRIPE. Auto-détecté si operator + customer_phone fournis.
countrystringCode pays ISO 3166-1 alpha-2 (ex: CM, CI). Auto-detecte depuis customer_phone si omis.
operatorstringCode operateur (ex: MTN, ORANGE, WAVE). Requis si payment_mode = DIRECT_API. Consultez GET /payments/countries pour la liste.
customer_phonestringNuméro du client (max 20 car). Requis si payment_mode = DIRECT_API.
customer_info.namestringNom du client. Max 255 car.
customer_info.emailstringEmail du client. Max 255 car.
customer_info.phonestringTéléphone du client (format E.164). Max 20 car.
callback_urlstringURL webhook spécifique à ce paiement (remplace le défaut marchand). Max 500 car.
return_urlstringURL de redirection après paiement. Max 500 car.
metadataobjectDonnées personnalisées JSON (retournées dans les webhooks).
referencestringRéférence unique (PAY-XXXX). Utilisez-la pour les requêtes GET.
payment_tokenstringToken JWT pour la page de checkout.
amountdecimalMontant final (peut inclure les frais si fee_bearer = CLIENT).
feedecimalMontant des frais de transaction calculés.
fee_bearerstringQui supporte les frais : MERCHANT (défaut) ou CLIENT.
currencystringDevise du paiement.
display_amountnumber|nullMontant indicatif renvoye tel que fourni. Null si non fourni.
display_currencystring|nullDevise du montant indicatif. Null si non fournie.
statusstringPENDING (SDK, Stripe, REDIRECT) ou PROCESSING (Direct API).
payment_modestringSDK, DIRECT_API, STRIPE ou REDIRECT (page hebergee du fournisseur carte).
countrystring|nullCode pays ISO 3166-1 alpha-2 (ex: CM).
payment_urlstringURL vers laquelle envoyer le client : page de checkout LtcPay en mode SDK, page hébergée du fournisseur en mode REDIRECT. Non utilisée en DIRECT_API (le client répond sur son téléphone).
stripe_client_secretstring|nullClient secret Stripe, renseigné uniquement en payment_mode STRIPE. Null partout ailleurs — notamment en REDIRECT, où c'est le fournisseur qui héberge le formulaire.
Redirigez immédiatement le client vers payment_url. La session expire en 30 minutes par défaut.
Frais de transaction
Une commission est calculée sur chaque paiement selon votre taux configuré (défaut : 1.75%). Le porteur des frais dépend de votre configuration :
FieldTypeDescription
MERCHANTdéfautLes frais sont déduits du montant reversé au marchand. Le client paie le montant exact demandé.
CLIENToptionLes frais sont ajoutés au montant payé par le client. Le montant retourné dans la réponse inclut les frais.
Détection automatique du mode
Si vous envoyez operator et customer_phone sans spécifier payment_mode, le mode DIRECT_API est automatiquement sélectionné. Si vous envoyez payment_method: BANK_CARD, le fournisseur du pays décide du mode : REDIRECT avec une page hébergée (E-nkap, défaut au Cameroun) ou STRIPE avec un PaymentIntent. Lisez payment_mode dans la réponse plutôt que de le supposer.
Le paiement par carte est traité via Stripe. Le client saisit ses informations de carte sur la page de checkout sécurisée.
Payments
Get payment
Récupère les détails d'un paiement par sa référence. Utilisez cet endpoint pour vérifier le statut d'un paiement ou pour le polling en mode Direct API.
merchant_referencestring|nullVotre ID de commande interne.
provider_transaction_idstring|nullID de transaction côté fournisseur.
amountdecimalMontant du paiement.
feedecimalFrais de transaction calculés.
fee_bearerstringQui supporte les frais : MERCHANT ou CLIENT.
currencystringDevise reellement debitee.
display_amountnumber|nullMontant indicatif fourni a la creation. Null sinon.
display_currencystring|nullDevise du montant indicatif. Null sinon.
methodstring|nullMOBILE_MONEY ou BANK_CARD.
statusstringStatut actuel du paiement.
payment_modestringSDK, DIRECT_API, STRIPE ou REDIRECT (page de paiement hébergée, ex: carte via E-nkap).
providerstring|nullFournisseur ayant traité le paiement : TOUCHPAY ou ACCOUNTPE (Mobile Money), STRIPE ou ENKAP (carte). Le choix du fournisseur est automatique par pays, avec bascule sur un fournisseur secondaire en cas de panne — transparent pour votre intégration.
operatorstring|nullCode operateur Mobile Money (ex: MTN, ORANGE, WAVE).
operator_transaction_idstring|nullID de transaction côté opérateur.
failure_codestring|nullCode d'échec stable (ex: INSUFFICIENT_FUNDS, ACCOUNT_BLOCKED). Renseigné si status=FAILED, et aussi sur un paiement par carte encore payable dont la dernière tentative a échoué. Voir Error codes.
failure_reasonstring|nullMessage d'échec prêt à afficher au client, renseigné en même temps que failure_code.
operator_referencestring|nullReference de transaction de l'operateur (ex: Orange Money "MP2608..."). C'est l'identifiant a fournir au support de l'operateur pour faire tracer un refus conteste par le client. Null si l'operateur n'en renvoie pas (MTN).
completed_atdatetime|nullDate de complétion (null si non terminé).
include_unavailablebooleanPar defaut (false), seuls les operateurs disponibles sont retournes. Avec true, les operateurs temporairement desactives sont inclus avec available: false — utile pour les afficher grises (« momentanement indisponible ») au lieu de les masquer.
phone_digitsintegerNombre de chiffres apres l'indicatif.
phone_patternstringFormat d'affichage (ex: 6XX XX XX XX).
flag_emojistringEmoji drapeau du pays.
min_amountintegerMontant minimum par transaction.
max_amountintegerMontant maximum par transaction.
enforce_phone_prefix_checkbooleanFalse = les phone_prefixes des opérateurs sont indicatifs (portabilité des numéros) : l'API ne rejette pas un paiement sur un préfixe qui ne correspond pas. True = un numéro appartenant visiblement à un autre opérateur est refusé avant l'appel au fournisseur.
operatorsarrayListe des operateurs disponibles pour ce pays (tous les operateurs, y compris desactives, avec include_unavailable=true).
Champs operateur
FieldTypeDescription
codestringCode operateur (ex: MTN, ORANGE, WAVE).
namestringNom complet de l'operateur.
colorstringCouleur CSS hex pour l'affichage.
logo_urlstringURL du logo (peut etre vide).
min_amountintegerMontant minimum par transaction pour cet operateur. Toujours renseigne : cette limite prime sur celle du pays.
max_amountintegerMontant maximum par transaction pour cet operateur, frais compris lorsque le client les supporte. Un paiement hors limites est rejete en 400.
ussd_codestringCode USSD pour verifier le solde.
phone_prefixesstring[]Prefixes de numeros nationaux appartenant a cet operateur (ex: ["69", "655"]). Utilisez-les pour preselectionner l'operateur ou avertir le client d'une incoherence numero/operateur avant soumission. Si le numero appartient de facon averee a un autre operateur du meme pays, l'API rejette le paiement en 400 avant tout appel a l'operateur. Une liste vide = plages inconnues, aucun blocage.
fee_ratenumberPourcentage facturé sur cet opérateur pour le marchand authentifié — null sans authentification. Les frais Mobile Money varient par pays et par opérateur : fiez-vous à ce champ plutôt qu'à fee_rates.MOBILE_MONEY.
availablebooleanfalse si l'operateur est temporairement desactive par la plateforme (panne, maintenance). Les operateurs indisponibles n'apparaissent qu'avec include_unavailable=true. Un paiement soumis sur un operateur indisponible est rejete en 400.
Utilisez cet endpoint pour construire dynamiquement l'interface de selection d'operateur dans votre application. Les operateurs et limites peuvent changer sans modification de code. Rafraichissez la liste regulierement : un operateur en panne peut etre desactive par la plateforme, puis reactive une fois le service retabli. Avec include_unavailable=true, affichez les operateurs indisponibles grises plutot que de les masquer pour une meilleure experience client.
Payments
Payment modes
Nkap Pay supporte trois modes d'intégration pour s'adapter à tous les cas d'usage.
SDK (Intégration web)
Recommandé pour les sites web
Créez le paiement via l'API
Redirigez le client vers payment_url
Le client choisit MTN, Orange ou Carte sur la page de checkout
Recevez le résultat via webhook
Direct API (Intégration mobile)
Recommandé pour les apps mobiles
Aucune redirection — purement API
Envoyez country, operator et customer_phone
Le client reçoit une notification push sur son app MoMo
Pollez GET /payments/{'{reference}'} pour suivre le statut
REDIRECT (Carte bancaire et Mobile Money via API)
Page hébergée en intégration API pure — recommandé web et mobile
Envoyez payment_method: BANK_CARD et country dans la requête
Sur la page E-nkap, le client choisit lui-même carte OU Mobile Money, et son pays parmi les 10 couverts. Vous n'avez rien à envoyer pour ça : ni operator, ni customer_phone, ni la devise du pays du client — vous créez toujours la commande en XAF.
La réponse contient payment_mode: REDIRECT et payment_url = la page de paiement sécurisée du fournisseur carte (pas la page Nkap Pay)
Ouvrez payment_url dans une WebView (app mobile) ou une redirection (web)
3-D Secure géré automatiquement pour la carte — l'étape navigateur est imposée par la sécurité carte, mais votre intégration reste 100% API
Le suivi est identique quel que soit le moyen choisi par le client : même statut, même webhook, même failure_code. Vous n'avez pas à traiter la carte et le Mobile Money différemment.
Pollez GET /payments/{'{reference}'} comme en Direct API : le statut y est re-vérifié en direct chez le fournisseur à chaque appel — et recevez aussi le webhook
Session de paiement de 10 minutes ; en cas de carte refusée, le paiement reste ouvert et un nouvel appel du client à payment_url... recrée une session automatiquement
failure_code et failure_reason indiquent la cause exacte de l'échec (solde insuffisant, compte bloqué, refus du client...). Ils sont renseignés sur tout paiement FAILED, et aussi sur un paiement par carte encore payable dont la dernière tentative a échoué : le lien reste valable, le client peut réessayer. Affichez failure_reason à votre client pour qu'il sache quoi corriger. Voir la liste complète dans Error codes.
Les webhooks sont envoyés avec un mécanisme de retry (5 tentatives max) espacées par un backoff exponentiel : 2s, 4s, 8s puis 16s. Votre endpoint doit répondre avec un code HTTP 2xx.
Webhooks
Event types
Liste des événements envoyés à votre webhook endpoint.
FieldTypeDescription
payment.status_changedwebhookEnvoyé chaque fois qu'un paiement atteint COMPLETED, FAILED ou CANCELLED — y compris quand ce verdict arrive après l'expiration. Le passage à EXPIRED lui-même n'est PAS notifié par défaut : activez « Notifier les expirations » dans Réglages › Webhooks pour le recevoir, sinon détectez-le en pollant GET /payments/{reference}.
EXPIREDnon définitifSession de paiement expirée (30 minutes par défaut). Le client n'a pas payé dans les temps. Aucun webhook n'est envoyé pour ce passage, sauf si vous activez « Notifier les expirations » dans Réglages › Webhooks. Un verdict tardif de l'opérateur peut encore faire basculer le paiement en COMPLETED ou FAILED.
CANCELLEDterminalPaiement annulé par le client ou le marchand.
REFUNDEDréservéValeur réservée pour le remboursement. Aucun paiement ne prend ce statut aujourd'hui : les remboursements se traitent hors API, en nous contactant. N'attendez pas de webhook REFUNDED.
Les statuts COMPLETED, FAILED et CANCELLED sont définitifs. EXPIRED ne l'est pas : un opérateur peut rendre son verdict longtemps après l'expiration (jusqu'à 18 heures observées sur Orange), et le paiement bascule alors en COMPLETED ou en FAILED, avec le webhook payment.status_changed correspondant. Traitez EXPIRED comme un abandon probable, jamais comme une certitude — n'annulez pas la commande sur cette seule base.
Reference
Error codes
L'API utilise les codes de statut HTTP standards. Les erreurs incluent un message descriptif dans le corps de la réponse.
Format d'erreur
json
{
"detail": "Payment not found"
}
Codes HTTP
FieldTypeDescription
200OKRequête réussie.
201CreatedRessource créée (ex: nouveau paiement).
400Bad RequestParamètres invalides (montant < 100, devise non supportée, etc.).
401UnauthorizedClés API manquantes ou invalides.
403ForbiddenAccès refusé (ex: paiement d'un autre marchand).
404Not FoundRessource introuvable (référence de paiement invalide).
422Validation ErrorErreur de validation des données (détails dans le corps).
402Payment RequiredL'opérateur a refusé le paiement pour une raison qui tient au client : solde insuffisant, compte bloqué ou introuvable, numéro d'un autre opérateur. Le fournisseur a répondu normalement — NE RÉESSAYEZ PAS automatiquement, rien ne changera tant que le client n'a pas corrigé la cause. La réponse porte failure_code, un message à afficher dans detail, et operator_reference quand l'opérateur en fournit une.
429Rate LimitedRéessayez plus tard : quota d'API dépassé, ou paiement refusé par un garde-fou de fréquence (DUPLICATE_PAYMENT, TOO_MANY_ATTEMPTS). L'en-tête Retry-After donne le délai exact en secondes.
502Bad GatewayPanne réelle du fournisseur (TouchPay, E-nkap ou Stripe indisponible ou en erreur interne). C'est le seul cas où un nouvel essai a du sens. Un refus lié au client renvoie 402, jamais 502.
500Server ErrorErreur interne du serveur.
Paiement refuse par l'operateur (402)
Le fournisseur a fonctionné : c'est le client qui ne peut pas payer. Affichez detail tel quel, et branchez votre logique sur failure_code.
json
{
"detail": "Solde insuffisant sur le compte Mobile Money. Rechargez votre compte et reessayez.",
"failure_code": "INSUFFICIENT_FUNDS",
"operator_reference": "MP260921BCD8F33A6D5BB60CDE2F"
}
operator_reference est la référence de la transaction chez l'opérateur. C'est le seul identifiant que le support Orange ou MTN peut exploiter si votre client affirme avoir été débité. Conservez-la.
Devise non supportee (400)
LtcPay n'effectue aucune conversion de devise : le montant est encaisse tel quel. Chaque fournisseur n'accepte donc que les devises qu'il sait reellement traiter — la devise du pays pour le Mobile Money, XAF uniquement pour la carte via E-nkap. Si vous facturez dans une autre devise, convertissez le montant avant d'appeler l'API. La reponse liste les devises acceptees.
json
{
"detail": "Le fournisseur ENKAP n'accepte que XAF pour le pays 'CM'. Convertissez le montant en XAF avant l'envoi : LtcPay n'effectue aucune conversion de devise.",
"failure_code": "CURRENCY_NOT_SUPPORTED",
"supported_currencies": ["XAF"]
}
Afficher votre devise au client
Si vous facturez en euros ou en dollars, envoyez le montant converti en devise de reglement et joignez display_amount / display_currency : le checkout affiche votre prix d'origine sous le total, marque « indicatif ». Ce couple n'est jamais debite ni recalcule — si le client bascule sur la carte et que les frais changent, le total reel bouge mais pas le montant indicatif.
json
{
"amount": 32800, // XAF -- le seul montant debite
"currency": "XAF",
"display_amount": 50, // cosmetique
"display_currency": "EUR"
}
Erreur de validation (422)
json
{
"detail": [
{
"loc": ["body", "amount"],
"msg": "Le montant minimum est 100",
"type": "value_error"
}
]
}
Motifs d'échec de paiement
Quand un paiement passe au statut FAILED, l'API expose un code stable (failure_code) et un message prêt à afficher (failure_reason). Vous les trouvez dans le webhook payment.status_changed et dans GET /payments/{reference}. Affichez failure_reason à votre client : il saura exactement pourquoi le paiement n'est pas passé et quoi faire.
FieldTypeDescription
INSUFFICIENT_FUNDSclientSolde insuffisant sur le compte Mobile Money du client. Le client doit recharger son compte puis réessayer.
ACCOUNT_BLOCKEDclientLe compte Mobile Money du client est bloqué par l'opérateur. Le client doit contacter son opérateur (Orange/MTN).
ACCOUNT_NOT_FOUNDclientAucun compte Mobile Money n'existe pour ce numéro. Le client doit vérifier le numéro saisi.
NOT_AUTHORIZEDclientLe client n'a pas autorisé le paiement : demande de confirmation (push USSD) refusée ou non validée.
CONFIRMATION_TIMEOUTclientLe client n'a pas confirmé le paiement à temps sur son téléphone. À la différence de NOT_AUTHORIZED, il n'y a pas eu de refus : relancer immédiatement le paiement aboutit souvent.
REJECTED_BY_OPERATORclientPaiement rejeté par l'opérateur : demande non validée à temps, expirée ou refusée. Le client peut réessayer.
BALANCE_OR_LIMITclientMTN Congo renvoie trois causes possibles dans un seul message : solde insuffisant, limite de bénéficiaires atteinte, ou opération non autorisée sur le compte. Nous ne pouvons pas les distinguer, donc failure_reason les énonce toutes plutôt que d'en affirmer une. Le client vérifie son solde, puis contacte MTN si le solde est suffisant.
DUPLICATE_PAYMENTclientUne opération identique (même numéro, même opérateur, même montant) a été envoyée il y a moins de 5 minutes. L'opérateur refuse jusqu'à la fin de cette fenêtre, même si le paiement précédent a déjà échoué. Le refus arrive en HTTP 429 : failure_reason indique le temps restant exact et rappelle la raison de l'échec précédent.
WRONG_OPERATORclientLe numéro n'appartient pas à l'opérateur sélectionné (ex: numéro Orange avec MTN MoMo sélectionné).
INVALID_PHONEclientNuméro de téléphone invalide : le nombre de chiffres ne correspond pas au pays. La longueur n'est pas la même partout — 9 au Cameroun, au Gabon et au Congo, 10 en Côte d'Ivoire, 8 au Bénin, au Mali et au Togo. Lisez phone_digits dans GET /payments/countries plutôt que de coder une longueur en dur ; le numéro est rejeté en 400 avant tout appel à l'opérateur.
TOO_MANY_ATTEMPTSclientTrop de tentatives de paiement pour ce numéro (5 par 30 minutes). Le refus arrive en HTTP 429 avec le délai restant exact dans l'en-tête Retry-After. Les tentatives bloquées par DUPLICATE_PAYMENT ne sont pas comptées.
AMOUNT_NOT_ALLOWEDclientLe montant est hors du barème accepté par l'opérateur pour ce pays. Réessayer à l'identique ne passera jamais : proposez un montant dans la plage donnée par min_amount et max_amount dans GET /payments/countries. Vu au Togo le 28/09/2026 sur deux paiements de 103 XOF, refusés par les deux fournisseurs.
METHOD_NOT_SUPPORTEDplateformeLe fournisseur ne dessert pas cet opérateur dans ce pays. Contrairement à OPERATOR_UNAVAILABLE, réessayer ne changera rien tant que la configuration n'a pas évolué : proposez un autre opérateur à votre client, et signalez-nous le cas. Renvoyé en 502.
OPERATOR_UNAVAILABLEoperatorL'opérateur Mobile Money est momentanément indisponible (panne, maintenance). Réessayez dans quelques minutes.
PAYMENT_FAILEDgenericÉchec non catégorisé. Le client peut réessayer ou utiliser un autre moyen de paiement.
Exemple : webhook d'un paiement échoué
json
{
"event": "payment.status_changed",
"data": {
"reference": "PAY-A1B2C3D4E5F67890",
"status": "FAILED",
"failure_code": "INSUFFICIENT_FUNDS",
"failure_reason": "Solde insuffisant sur le compte Mobile Money du client.",
"operator_reference": "MP260824FD3C4BF9D397491AE59C",
...
},
"timestamp": "2026-08-14T20:13:50Z"
}
Les erreurs de type 'client' ne sont pas des pannes : c'est la situation du client (solde, compte, refus). Guidez-le avec le message plutôt que de lui proposer de réessayer en boucle.