Aller au contenu principal

API de création de lien de paiement

Crée un lien de paiement pour votre boutique. Vous redirigez le client vers le paymentLink retourné, et Papi notifie votre notificationUrl lorsque le statut du paiement change.

Pour un parcours pas à pas de l'ensemble du flux de paiement, consultez le guide d'intégration.


Endpoint

POST https://app.papi.mg/engine/api/payment-links

ÉlémentValeur
URL de basehttps://app.papi.mg/engine/api
Chemin/payment-links
MéthodePOST
Corps de la requêteJSON
Corps de la réponseJSON
URL historique

POST https://app.papi.mg/dashboard/api/payment-links est toujours accepté pour les intégrations existantes. Il transmet la requête au moteur sans la modifier et renvoie la réponse du moteur sans la modifier : le comportement décrit sur cette page est donc le même sur les deux URLs. Seule différence : si le moteur est injoignable, l'URL historique répond 503 avec le code ENGINE_UNAVAILABLE. Utilisez l'URL du moteur pour les nouvelles intégrations.


Authentification

Chaque requête doit porter la clé API de votre boutique dans l'en-tête Token. Vous trouvez la clé API dans le tableau de bord : Icône avatar → Boutiques → sélectionnez la boutique → onglet Développeur.

Le lien est créé dans la boutique à laquelle appartient la clé API. L'idempotence (voir Idempotence) est également évaluée dans cette boutique.

attention

Sur cet endpoint, une clé API absente ou inconnue reçoit une réponse 400 (et non 401). Voir Erreurs.


En-têtes de la requête

En-têteRequisValeurDescription
Token<YOUR_API_KEY>Clé API de votre boutique.
Content-Typeapplication/jsonLe corps est en JSON. L'URL historique du tableau de bord refuse tout autre type de contenu.

Aucun autre en-tête n'est lu par cet endpoint. En particulier, l'en-tête LinkToken utilisé par la page de paiement n'est pas accepté ici comme méthode d'authentification. Le mode test se définit dans le corps avec isTestMode, pas avec un en-tête.


Paramètres du corps

ChampTypeRequisContraintes / validationDescription
amountnumberNon nul. Minimum 300.Montant à payer, en currency.
referencestringNon vide.Votre identifiant pour ce paiement (par exemple, votre ID de commande). C'est la clé d'idempotence : voir Idempotence. Elle est retournée comme paymentReference dans la réponse de création et comme merchantPaymentReference dans les notifications et dans la relecture.
clientNamestringNon vide.Nom du client.
descriptionstringNon vide. 255 caractères maximum.Description courte du paiement.
successUrlstringDoit commencer par http:// ou https://. Doit être envoyé avec failureUrl.URL vers laquelle le client est redirigé après un paiement réussi.
failureUrlstringDoit commencer par http:// ou https://. Doit être envoyé avec successUrl.URL vers laquelle le client est redirigé après un paiement échoué.
notificationUrlstringDoit commencer par http:// ou https://.Votre endpoint qui reçoit les notifications de paiement. Fortement recommandé. Sans lui, lisez l'issue avec l'API de statut de lien de paiement.
validDurationintegerEntier supérieur à 0, au plus 596. Défaut : 1.Validité du lien, en heures, à compter de sa création (596 heures représentent environ 24 jours).
providerstringL'une des valeurs MVOLA, AIRTEL_MONEY, ORANGE_MONEY, BRED.Restreint le lien à un seul prestataire. S'il est omis, le client choisit sur la page de paiement.
currencystringUn code de devise connu de Papi (de type ISO 4217, en majuscules). Défaut : MGA.Devise de amount. Seul MGA est réglé aujourd'hui : envoyez MGA ou omettez le champ. Un autre code connu est accepté par cet endpoint, mais Papi ne règle pas de paiements dans cette devise.
displayCurrencystringSeul MGA est accepté. Défaut : MGA.Devise affichée au client sur la page de paiement.
payerEmailstringAdresse email valide. Une chaîne vide est traitée comme une absence.Adresse email du client.
payerPhonestringNuméro de téléphone valide. Caractères autorisés : chiffres, +, -, espace, (, ), .. Les numéros sans indicatif pays sont lus comme des numéros malgaches. Une chaîne vide est traitée comme une absence.Numéro de téléphone du client. Il est retourné au format international (par exemple +261 34 00 000 00).
isTestModebooleanDéfaut : false.Définir à true pour marquer le lien comme test. Voir Mode test.
testReasonstringAucune.Raison du test. Affichée dans le tableau de bord.
paymentTesterstringL'une des valeurs PAPI_DEV, PAPI_TEST, MERCHANT_DEV, MERCHANT, EXTERNAL_DEV (insensible à la casse).Qui effectue le test. Enregistré avec le lien.
linkStatebooleanAucune.Accepté pour compatibilité et ignoré : un lien créé via l'API est toujours activé.

Tout autre champ du corps est ignoré.


Exemple de requête

curl -X POST "https://app.papi.mg/engine/api/payment-links" \
-H "Content-Type: application/json" \
-H "Token: <YOUR_API_KEY>" \
-d '{
"amount": 15000,
"reference": "ORDER-123",
"clientName": "Client Name",
"description": "Payment for Order #123",
"successUrl": "https://yourapp.com/payment-success",
"failureUrl": "https://yourapp.com/payment-failure",
"notificationUrl": "https://yourapp.com/payment-notify",
"validDuration": 60,
"provider": "MVOLA",
"payerEmail": "customer@example.com",
"payerPhone": "+261340000000",
"isTestMode": false
}'

Corps de la requête seul :

{
"amount": 15000,
"reference": "ORDER-123",
"clientName": "Client Name",
"description": "Payment for Order #123",
"successUrl": "https://yourapp.com/payment-success",
"failureUrl": "https://yourapp.com/payment-failure",
"notificationUrl": "https://yourapp.com/payment-notify",
"validDuration": 60,
"provider": "MVOLA",
"payerEmail": "customer@example.com",
"payerPhone": "+261340000000",
"isTestMode": false
}

Réponse en cas de succès

Statut : 200 OK. Le même statut est retourné lorsqu'un lien en vigueur existant est renvoyé (voir Idempotence).

{
"data": {
"amount": 15000.0,
"currency": "MGA",
"displayCurrency": "MGA",
"linkCreationDateTime": 1788065989011,
"linkExpirationDateTime": 1788281989011,
"paymentLink": "https://payment-form.papi.mg/yourshop/payments/eyJhbGciOiJIUzI1NiJ9...",
"clientName": "Client Name",
"paymentReference": "ORDER-123",
"description": "Payment for Order #123",
"successUrl": "https://yourapp.com/payment-success",
"failureUrl": "https://yourapp.com/payment-failure",
"notificationUrl": "https://yourapp.com/payment-notify",
"payerEmail": "customer@example.com",
"payerPhone": "+261 34 00 000 00",
"notificationToken": "5b8f0c3e-2a7d-4f61-9e0b-7c4d1a2e9f38",
"testReason": null,
"isTestMode": false,
"shortLink": "https://link.papi.mg/8NW7R"
}
}

Champs de la réponse

Tous les champs sont présents dans la réponse. Un champ sans valeur vaut null.

ChampTypeDescription
amountnumberMontant du lien.
currencystringDevise de amount (MGA).
displayCurrencystringDevise affichée au client (MGA).
linkCreationDateTimeintegerDate de création, en millisecondes epoch.
linkExpirationDateTimeintegerDate d'expiration, en millisecondes epoch (linkCreationDateTime + validDuration heures).
paymentLinkstringL'URL vers laquelle le client doit être redirigé pour payer. Sa forme est https://payment-form.papi.mg/<code-de-votre-application>/payments/<jwt>. Le JWT est émis par Papi et expire avec le lien. Lorsque provider est défini, l'URL se termine par /mobile/<PROVIDER> (par exemple /mobile/MVOLA) ou /card/BRED.
clientNamestringNom du client.
paymentReferencestringDans cette réponse, c'est votre reference. Ce n'est pas la référence de paiement de Papi.
descriptionstringDescription du paiement.
successUrlstringURL de redirection après succès, ou null.
failureUrlstringURL de redirection après échec, ou null.
notificationUrlstringURL de notification, ou null.
payerEmailstringEmail du client, ou null.
payerPhonestringNuméro de téléphone du client au format international, ou null.
notificationTokenstringJeton généré par Papi pour ce lien (un UUID). Conservez-le : chaque notification pour ce lien porte la même valeur, comme contrôle supplémentaire après la signature.
testReasonstringRaison du test, ou null.
isTestModebooleanIndique si le lien est marqué comme test.
shortLinkstringForme courte de paymentLink. null lorsque le lien court n'a pas pu être généré ; le lien lui-même reste valide.
Quelle référence est laquelle
  • reference (requête) = paymentReference (cette réponse) = merchantPaymentReference (notifications et relecture) : votre référence.
  • paymentReference (notifications) = papiPaymentReference (relecture) : la référence de Papi pour une tentative de paiement. Elle n'existe pas encore lorsque le lien est créé.

Idempotence

La création d'un lien de paiement est idempotente sur la reference, dans votre boutique, tant que le lien est en vigueur.

Un lien est en vigueur lorsque toutes les conditions suivantes sont réunies :

  • il a été créé via cette API (et non depuis le tableau de bord) ;
  • il est activé ;
  • il n'est pas payé ;
  • sa date d'expiration est dans le futur.

Comportement

Vous envoyez un POST avec une reference qui…Réponse de Papi
…n'a aucun lien en vigueur dans votre boutique200 avec un nouveau lien.
…a un lien en vigueur avec les mêmes amount, currency et displayCurrency200 avec le lien existant : même paymentLink, même notificationToken, même linkExpirationDateTime. Aucun nouveau lien n'est créé.
…a un lien en vigueur avec un amount, une currency ou une displayCurrency différents409 avec le code PAYMENT_LINK_CONFLICT. Aucun lien n'est créé.
…a servi pour un lien désormais payé, expiré ou désactivé200 avec un nouveau lien. L'ancien lien n'est pas modifié.

Détails

  • Seul l'argent est comparé. amount, currency et displayCurrency doivent être identiques. amount est comparé comme une valeur exacte : 15000 et 15000.0 sont identiques, 15000 et 15000.5 sont différents. Des currency et displayCurrency omis valent MGA.
  • Les autres champs du réessai sont ignorés. Lorsque le lien existant est retourné, la réponse décrit le lien tel qu'il a été créé la première fois. Les champs clientName, description, successUrl, failureUrl, notificationUrl, validDuration, provider, payerEmail, payerPhone, isTestMode et testReason du réessai ne sont pas appliqués. En particulier, un réessai ne prolonge pas la validité du lien.
  • La référence est comparée exactement. ORDER-123 et order-123 sont deux références différentes.
  • La portée est votre boutique. La même reference utilisée par une autre boutique n'a aucun effet sur vos requêtes.
  • Les requêtes simultanées sont sérialisées. Si plusieurs requêtes avec la même reference arrivent en même temps, la première crée le lien et les autres reçoivent ce même lien (ou un 409 si leur montant ou leur devise est différent). Un double envoi du checkout ne peut pas créer deux liens payables pour une même commande.
  • Les liens du tableau de bord sont hors de la règle. Un lien créé à la main depuis le tableau de bord avec la même référence n'est jamais retourné par cet endpoint et n'est jamais une raison de 409.
  • La validation passe en premier. Une requête qui échoue à la validation (400) est refusée avant le contrôle d'idempotence. Elle ne retourne jamais un lien existant.

Réponse de conflit

Statut : 409 Conflict

{
"error": {
"code": "PAYMENT_LINK_CONFLICT",
"message": "Un lien de paiement actif existe déjà pour la référence ORDER-123 avec un montant ou une devise différents. Attendez son expiration ou utilisez une autre référence."
}
}

Pour résoudre un conflit, choisissez l'une de ces options :

  • attendez l'expiration du lien en vigueur, puis renvoyez la requête ;
  • envoyez la requête avec une autre reference (par exemple ORDER-123-2) ;
  • si vous ne connaissez pas l'état du lien existant, lisez-le avec l'API de statut de lien de paiement.

Erreurs

Toutes les erreurs du tableau ci-dessous utilisent ce format de corps :

{
"error": {
"code": "<ERROR_CODE>",
"message": "<Human-readable message, in French>"
}
}
Statut HTTPCode d'erreurSignificationExemple de message
400CORE_INPUT_400Un champ du corps a échoué à la validation. Lorsque plusieurs champs échouent, leurs messages sont joints par , sans ordre fixe.Le montant doit être supérieur ou égal à 300.
400CORE_INPUT_400L'en-tête Token est absent, ou la clé API est inconnue.API key invalide
400CORE_INPUT_400displayCurrency n'est pas MGA.EUR is not supported yet
400CORE_INPUT_400currency n'est pas un code de devise connu de Papi (le contrôle est sensible à la casse : mga est refusé).Unité monétaire invalide
400CORE_INPUT_400paymentTester ne fait pas partie des valeurs acceptées.Invalid value 'SOMEONE
400CORE_INPUT_400payerEmail ou payerPhone n'est pas valide.Invalid email address: not-an-email / Numéro de téléphone invalide
400CORE_INPUT_400Le corps n'est pas un JSON valide, ou un champ a un mauvais type (par exemple "amount": "abc").Corps de la requête invalide : JSON attendu / Valeur invalide pour le champ 'amount'
409PAYMENT_LINK_CONFLICTUn lien en vigueur existe déjà pour cette reference avec un montant ou une devise différents. Voir Idempotence.Un lien de paiement actif existe déjà pour la référence ORDER-123 avec un montant ou une devise différents. Attendez son expiration ou utilisez une autre référence.
503ENGINE_UNAVAILABLEURL historique du tableau de bord uniquement : le moteur n'a pas pu être joint.Le service de paiement est momentanément indisponible. Veuillez réessayer.

Messages de validation

ChampRèglemessage
amountAbsentLe montant est requis
amountInférieur à 300Le montant doit être supérieur ou égal à 300.
clientNameAbsent ou videLe nom du client est réquis
referenceAbsent ou videLa référence est réquise
descriptionAbsent ou videLa description est réquise
descriptionPlus de 255 caractèresLa description ne doit pas dépasser 255 caractères
successUrlNe commence pas par http:// ou https://L'URL de succès doit être valide et commencer par http ou https
failureUrlNe commence pas par http:// ou https://L'URL d'échec doit être valide et commencer par http ou https
successUrl / failureUrlUn seul des deux est envoyéLes URLs de succès et d'échec doivent être toutes les deux définies ou toutes les deux absentes
notificationUrlNe commence pas par http:// ou https://L'URL de notification doit être valide et commencer par http ou https
validDuration0 ou négatifLa durée de validité doit être supérieure à 0
providerNe fait pas partie des valeurs acceptéesValeur invalide pour provider
payerEmailAdresse email invalideInvalid email address: <valeur>
payerPhoneNuméro de téléphone invalideNuméro de téléphone invalide

Exemple d'erreur de validation :

{
"error": {
"code": "CORE_INPUT_400",
"message": "La description est réquise, Le montant doit être supérieur ou égal à 300."
}
}
astuce

Pour les champs URL, une chaîne vide n'équivaut pas à un champ absent. "successUrl": "" échoue à la validation. Si vous n'utilisez pas une URL, omettez le champ.


Mode test

  • Si votre boutique est une application sandbox, chaque lien qu'elle crée est un lien de test : isTestMode vaut toujours true dans la réponse, quoi que vous envoyiez.
  • Si votre boutique est en production, le lien est un lien de test uniquement lorsque vous envoyez "isTestMode": true. Le lien est marqué comme test dans votre tableau de bord, mais de l'argent réel est quand même déplacé.

Pour des paiements de test par carte sans argent réel, consultez Mode Test dans le guide d'intégration.


Notes pratiques

Réessayer une requête

  • Si la requête expire ou si la connexion est coupée, envoyez à nouveau la même requête avec la même reference. Vous recevez le lien créé par la première requête, ou un nouveau lien si la première requête n'a jamais atteint Papi. Dans les deux cas, un seul lien en vigueur existe.
  • Réessayez en cas d'erreur réseau et de réponse 5xx. Ne réessayez pas un 400 sans modifier la requête : le même corps est refusé à nouveau.
  • Ne réessayez pas un 409 avec le même corps. Suivez les options de Réponse de conflit.
  • Utilisez le notificationToken de la dernière réponse réussie. Pour un réessai qui a retourné le lien existant, c'est la même valeur que dans la première réponse.
  • Le lien est toujours en vigueur et le montant est inchangé : renvoyez la requête. Vous recevez le même lien. Vous pouvez l'envoyer à nouveau au client.
  • Le montant de la commande a changé pendant que le lien est en vigueur : vous recevez un 409. Utilisez une nouvelle reference, ou attendez l'expiration du lien.
  • Le lien a expiré ou a été désactivé : renvoyez la requête avec la même reference. Vous recevez un nouveau lien avec un nouveau notificationToken.
  • Le lien a été payé : n'émettez pas de nouveau lien. Avant de réémettre un lien, vérifiez son état avec l'API de statut de lien de paiement. Si vous créez malgré tout un nouveau lien, la relecture retourne toujours le lien payé pour cette référence.

Récupérer une notification manquée

Une notification n'est envoyée qu'une fois. Si votre endpoint ne l'a pas reçue, lisez l'issue avec l'API de statut de lien de paiement, en utilisant votre reference. Vous pouvez aussi renvoyer la notification depuis le tableau de bord : voir Notifications en échec.