Créer un paiement

L'appel serveur-à-serveur qui ouvre une transaction, et tout ce qui peut lui arriver ensuite.

Créer un paiement

POST/v1/payments

Authentification par clé secrète. Le provider est obligatoire : c'est votre serveur qui décide de l'opérateur. Pour laisser le choix au client, utilisez la session de checkout.

ChampTypeRequisDescription
amountentier✅Montant en XOF entiers. Minimum 100, maximum 5 000 000.
providerchaîne✅orange (Orange Money CI) ou stripe (carte). wave, mtn et pawapay sont temporairement indisponibles : refusés avec NGP003 (422).
referencechaîne (≤ 150)✅Votre identifiant de commande. C'est la clé d'idempotence.
phonechaîne (≤ 20)❌Numéro du payeur, +225 + 10 chiffres.
emaile-mail❌Adresse du client, pour le reçu.
descriptionchaîne (≤ 255)❌Libellé affiché au client.
webhook_urlURL (≤ 255)❌Destination unique pour cette transaction. Elle remplace vos destinations déclarées. Mêmes règles qu'une destination déclarée : voir URL acceptées.
return_urlURL (≤ 500)❌Où ramener le client après paiement. Sans rapport avec webhook_url.
metadataobjet❌Vos données libres, rendues telles quelles. 20 clés au plus, 4 Ko au plus une fois encodé en JSON.
curl -X POST https://api.live.nguepay.com/api/v1/payments \
  -H "Authorization: Bearer sk_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 15000,
    "provider": "orange",
    "reference": "cmd_10452",
    "phone": "+2250700000000",
    "description": "Commande #10452"
  }'

La réponse

{
  "success": true,
  "data": {
    "transaction_id": "9f1c0c2e-2b4a-4f0d-9a1b-6d2f7c3e4a55",
    "uuid": "9f1c0c2e-2b4a-4f0d-9a1b-6d2f7c3e4a55",
    "status": "pending",
    "provider": "orange",
    "provider_code": "orange",
    "amount": 1500000,
    "amount_xof": 15000,
    "refunded_amount_xof": 0,
    "currency": "XOF",
    "reference": "cmd_10452",
    "phone": "+225 07 *** ** 00",
    "checkout_url": "URL de la page de paiement Orange Money",
    "attempts": 0,
    "metadata": [],
    "failure_reason": null,
    "paid_at": null,
    "expires_at": "2026-09-17T22:30:00.000000Z",
    "created_at": "2026-09-17T22:00:00.000000Z",
    "updated_at": "2026-09-17T22:00:01.000000Z",
    "links": {
      "self": "/api/v1/transactions/9f1c0c2e-2b4a-4f0d-9a1b-6d2f7c3e4a55",
      "refund": "/api/v1/transactions/9f1c0c2e-2b4a-4f0d-9a1b-6d2f7c3e4a55/refund"
    }
  },
  "message": "Paiement initié."
}

Chaque champ est décrit dans l'objet Transaction. À retenir : transaction_id et provider sont des alias historiques de uuid et provider_code ; phone revient masqué ; metadata vaut [] (tableau vide) quand vous n'en envoyez pas ; links contient des chemins relatifs. Pour Stripe, la réponse peut porter en plus un objet merchant (uuid, merchant_code, name de votre compte) : ne vous y fiez pas, il n'est pas renvoyé de façon constante.

checkout_url est la page de l'opérateur (ici la page de paiement Orange Money) : ouvrez-la sur le téléphone du client, qui y valide le paiement. Pour la page de paiement hébergée NguePay, créez plutôt une session (POST /checkout/sessions).
amount_xof est le montant que vous avez demandé ; amount est le même montant dans l'unité de stockage (centimes). Affichez amount_xof à vos utilisateurs.

Qui choisit le moyen de paiement

Les moyens proposés à votre client viennent de votre compte NguePay, pas de votre code : activez-les dans l'écran Moyens de paiement de votre dashboard. Rien à configurer côté intégration.

AppelproviderQui choisit
POST /v1/paymentsobligatoireVous — le client arrive directement chez l'opérateur.
POST /v1/checkout/sessionsfacultatifLe client, sur la page NguePay, parmi vos moyens activés.

Omettez provider sur une session de checkout et la première page affiche vos moyens activés, au choix du client. Un moyen que vous n'avez pas activé n'y figure pas, et ne peut pas être atteint en devinant l'URL.

Si vous imposez un provider non activé sur votre compte, l'appel est refusé avec NGP010. Pour l'instant, seuls orange et stripe sont ouverts : wave, mtn et pawapay sont temporairement indisponibles et refusés avec NGP003 (422).

Ramener le client sur votre site

return_url et webhook_url répondent à deux besoins qui ne se recouvrent pas, et il faut les deux.

return_urlwebhook_url
DestinataireLe navigateur du clientVotre serveur
RôleConfort : ramener le client chez vousFaire foi : confirmer le paiement
Si absentLe client reste sur la page NguePayVous n'apprenez jamais le paiement
Si le client ferme l'ongletRien ne se passeLa notification part quand même
N'attendez jamais le retour du navigateur pour valider une commande. Un client qui ferme son onglet, perd le réseau, ou paie depuis l'application de son opérateur ne reviendra pas — et son paiement est pourtant valide. C'est le webhook qui tranche.
curl -X POST https://api.live.nguepay.com/api/v1/payments \
  -H "Authorization: Bearer sk_live_VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 15000,
    "provider": "orange",
    "reference": "cmd_10452",
    "return_url": "https://votre-boutique.ci/commande/10452",
    "webhook_url": "https://votre-boutique.ci/webhooks/nguepay"
  }'

Ce que contient l'URL de retour

NguePay renvoie le client sur votre return_url exactement telle que vous l'avez envoyée : aucun paramètre n'y est ajouté — ni uuid, ni statut, ni référence, ni signature. Il n'existe pas non plus de success_url ou de cancel_url côté marchand : les opérateurs (Orange Money, Stripe) ramènent toujours le client sur les pages NguePay (/pay/{uuid}/success, /failed, /cancelled, /waiting), et ce sont elles qui proposent votre return_url.

Page NguePayCe qui mène à return_url
Paiement réussiRedirection automatique après 5 secondes, et bouton de retour. Dans l'iframe du SDK, pas de redirection automatique : c'est votre page qui reprend la main (onSuccess).
Paiement échoué ou expiréBouton de retour.
Paiement annuléBouton de retour.
Choix du moyen de paiementBouton « Retour au site marchand ».

Le retour arrive donc aussi après un échec ou un abandon. Sans return_url, NguePay utilise metadata.return_url s'il existe ; une valeur qui ne commence pas par http est ignorée. Quand checkout_url vaut null (validation sur le téléphone), aucune page NguePay n'est affichée et return_url ne sert pas.

Pour retrouver la transaction au retour, placez vous-même votre identifiant dans l'URL (https://votre-boutique.ci/commande/10452), puis interrogez l'API depuis votre serveur : GET /transactions?reference=cmd_10452, ou GET /payments/{uuid} avec l'uuid conservé à la création. N'accordez aucune valeur à un paramètre de statut que vous auriez ajouté vous-même : le client peut le modifier. Le webhook reste la confirmation qui fait foi.

Le montant est toujours en XOF entiers

Quel que soit l'opérateur, vous envoyez des XOF entiers : 1000 pour 1 000 F. N'appliquez aucune conversion avant l'appel — ni multiplication par cent, ni division.

NguePay convertit ensuite vers l'unité attendue par chaque opérateur, et elle n'est pas la même partout : Orange Money raisonne en francs, Stripe dans la plus petite unité de la devise — qui, pour le XOF, est également le franc, parce que le XOF est une devise zero-decimal. Ces différences sont notre affaire, pas la vôtre.

Si un montant vous paraît anormal sur une page de paiement, ne le compensez pas dans votre appel. Vous corrigeriez un opérateur en cassant les autres : le mobile money encaisserait alors un centième du dû, et toute garde comparant le montant payé à l'attendu rejetterait vos webhooks. Signalez-le — c'est chez nous que la conversion doit être juste.

Dans les réponses, deux champs coexistent : amount_xof est ce que vous avez demandé, amount le même montant dans notre unité de stockage (centimes). Affichez amount_xof.

Idempotence : la référence fait foi

Une même reference ne peut être payée qu'une fois. Si vous rejouez l'appel — double clic, retry réseau, worker relancé — NguePay renvoie la transaction existante avec un 200 au lieu d'un 201, et n'encaisse pas deux fois.

Conséquence pratique : dérivez la référence de votre commande (cmd_10452), jamais d'un horodatage ou d'un aléatoire. Un identifiant aléatoire vous ferait perdre exactement la protection que vous cherchez.

Cycle de vie

StatutSignificationTerminal
pendingCréée, en attente d'action du client.non
processingL'opérateur traite l'encaissement.non
succeededEncaissé. Le seul statut qui autorise la livraison.oui
failedRefusé (solde, code erroné, refus opérateur).oui
canceledAnnulé par vous (POST /transactions/{uuid}/cancel, sans webhook) ou signalé annulé par l'opérateur (webhook payment.canceled).oui
expiredDélai de paiement écoulé.oui

Une transaction remboursée reste en succeeded : le remboursement est un objet distinct, et refunded_amount_xof suit le cumul.

Une confirmation d'opérateur dont le montant diffère de celui de la transaction n'est jamais créditée : la transaction reste dans son état, et l'écart est examiné par NguePay. Vous ne recevez pas de payment.succeeded pour un montant que vous n'avez pas demandé.

Lire, lister, annuler

# Lire une transaction
curl https://api.live.nguepay.com/api/v1/payments/{uuid} \
  -H "Authorization: Bearer sk_live_VOTRE_CLE"

# Lister avec filtres
curl "https://api.live.nguepay.com/api/v1/transactions?status=succeeded&per_page=50" \
  -H "Authorization: Bearer sk_live_VOTRE_CLE"

# Annuler une transaction non terminale
curl -X POST https://api.live.nguepay.com/api/v1/transactions/{uuid}/cancel \
  -H "Authorization: Bearer sk_live_VOTRE_CLE"

Quand l'annulation est refusée

Seule une transaction en pending s'annule. Avant de la clore, NguePay interroge l'opérateur — canceled est définitif, et un paiement validé juste après serait encaissé sans jamais vous être crédité. L'annulation est donc refusée en 422, avec un message explicite, lorsque :

Dans les deux derniers cas, réessayez plus tard ou laissez le paiement expirer. Une annulation acceptée renvoie la transaction en canceled.

Ne faites pas de la relecture une boucle d'attente : les webhooks existent pour ça. Réservez-la aux vérifications avant livraison et à la réconciliation.

Erreurs à prévoir

CodeHTTPQuand
NGP002422Montant hors bornes ou non entier.
NGP003422 / 502Opérateur indisponible — réessayer ou proposer un autre moyen.
NGP004422Numéro de téléphone invalide.
NGP005422Référence déjà utilisée pour un autre montant.
NGP009422Refus anti-fraude. Le motif n'est jamais détaillé.
NGP010422Opérateur non activé sur votre compte.
NGP011422Plafond mensuel d'un compte particulier atteint.

Plafond des comptes particuliers

Un compte ouvert en tant que particulier peut encaisser jusqu'à 3 000 000 XOF par mois calendaire. Le cumul compte les paiements réussis et ceux encore en cours (pending, processing) : un paiement parti chez l'opérateur engage le plafond même s'il n'est pas encore confirmé.

Au-delà, la création est refusée en 422 avec NGP011 et le message « Plafond mensuel atteint pour un compte particulier (3 000 000 XOF/mois). ». Les comptes entreprise n'ont pas de plafond mensuel.

Besoin d'aide ?