Aller au contenu principal

3. Intégrez Papi à votre application web

Intégrez Papi dans votre application, notamment la gestion des flux de paiement, des notifications et des paramètres spécifiques à l'environnement.


Environnements

Papi fournit un seul endpoint de production. Pour les tests, utilisez les indicateurs mode test décrits ci-dessous.

  • Créer un lien de paiement (transactions en direct) : https://app.papi.mg/engine/api/payment-links
  • Relire un lien de paiement : https://app.papi.mg/engine/api/payment-links/{merchantPaymentReference} (voir Relire un lien de paiement)

https://app.papi.mg/dashboard/api/payment-links est l'URL historique, toujours acceptée pour les intégrations existantes ; utilisez l'URL engine pour les nouvelles.


Vue d'ensemble du flux de paiement

Le processus de paiement se compose des étapes suivantes :

  1. Obtenez votre clé API depuis le tableau de bord.
  2. Créez un lien de paiement : Envoyez une requête API pour générer un lien de paiement unique pour le client.
  3. Redirigez le client : Utilisez le paymentLink retourné pour envoyer le client vers la page de paiement sécurisée.
  4. Traitement du paiement : Le client finalise le paiement sur la page sécurisée.
  5. Recevez une notification : Après le paiement, Papi appelle votre notificationUrl avec le statut final.
  6. Vérifiez et gérez le résultat : Vérifiez d'abord l'en-tête X-Papi-Signature, puis contrôlez le notificationToken et le merchantPaymentReference, et mettez à jour votre application. Voir Sécuriser les notifications (callbacks).

Guide pas à pas pour implémenter l'intégration des paiements

Ce guide vous accompagne pas à pas pour intégrer l'API de paiement, depuis la création des pages de redirection jusqu'à la gestion des notifications.

1
Créez des pages de redirection

Vous devez configurer deux pages sur votre site web vers lesquelles les clients seront redirigés après le paiement :

  • URL de succès – La page affichée après un paiement réussi (ex. : confirmation de commande).
  • URL d'échec – La page affichée après un paiement échoué (ex. : message d'erreur avec option de réessai).

Notez ces deux URLs – vous en aurez besoin lors de la création du lien de paiement.

2
Créez l'endpoint de notification (URL de callback)

L'endpoint de notification est une URL de callback que vous implémentez pour recevoir les mises à jour de statut automatiques de Papi. Il doit accepter les requêtes POST.

Si vous développez sur votre poste local

Pourquoi http://localhost:… ne fonctionne pas. Papi appelle votre notificationUrl depuis ses propres serveurs, sur l'Internet public. localhost (comme 127.0.0.1 ou une IP privée type 192.168.x.x) désigne la machine qui fait l'appel : depuis les serveurs de Papi, cette adresse ne pointe pas vers votre poste. La notification n'arrivera donc jamais.

Exposez temporairement votre port local. Utilisez un tunnel qui publie une URL HTTPS publique redirigée vers votre serveur local, par exemple :

# ngrok — expose le port local 3000
ngrok http 3000

# Cloudflare Tunnel — équivalent
cloudflared tunnel --url http://localhost:3000

L'outil affiche une URL publique, par exemple https://abcd-1234.ngrok-free.app.

Construisez la notificationUrl publique. Concaténez l'URL du tunnel et le chemin de votre endpoint :

https://abcd-1234.ngrok-free.app/api/papi/notification

C'est cette valeur (et non http://localhost:3000/api/papi/notification) que vous envoyez dans le champ notificationUrl lors de la création du lien de paiement.

L'URL du tunnel est temporaire. Avec la plupart des offres gratuites, elle change à chaque redémarrage du tunnel (et expire après un délai d'inactivité). Après un redémarrage, récupérez la nouvelle URL et créez un nouveau lien de paiement avec la notificationUrl mise à jour — les liens déjà créés continuent de pointer vers l'ancienne adresse, devenue morte.

Observez la requête reçue et testez les deux cas. Journalisez le corps POST reçu dès l'entrée de votre handler, avant tout traitement. Les tunnels fournissent aussi une interface d'inspection des requêtes (ngrok : http://127.0.0.1:4040) qui permet de voir en-têtes, corps et code de réponse, et de rejouer une requête sans refaire un paiement. Testez ensuite :

  • Succès : effectuez un paiement complet et vérifiez la réception d'un paymentStatus à SUCCESS.
  • Échec : annulez le paiement ou laissez le lien expirer (validDuration court) pour recevoir FAILED, et vérifiez que votre code ne valide pas la commande.

Précautions minimales

  • Utilisez l'URL HTTPS du tunnel, jamais l'URL http://.
  • Vérifiez systématiquement l'en-tête X-Papi-Signature, puis le notificationToken et le merchantPaymentReference, avant de mettre à jour vos données — l'URL du tunnel est publique et n'importe qui peut y envoyer une requête. Voir Sécuriser les notifications (callbacks).
  • Ne mettez jamais votre clé API dans l'URL de notification (ni dans une query string) : elle apparaîtrait dans les journaux du tunnel et des serveurs intermédiaires.
  • Arrêtez le tunnel dès la fin du test : tant qu'il tourne, votre serveur local est accessible depuis Internet.

Exemple de corps de notification envoyé par Papi

{
"paymentStatus": "SUCCESS",
"paymentMethod": "MVOLA",
"currency": "MGA",
"amount": 15000,
"fee": 500,
"clientName": "Nom du client",
"description": "Paiement pour la commande #123",
"merchantPaymentReference": "ORDER-123",
"paymentReference": "c1f4a5b0-6f5e-4e1b-9f0e-2b7d8a9c3d21",
"notificationToken": "xyz789",
"message": "Paiement effectué avec succès.",
"payerEmail": "client@example.com",
"payerPhone": "+261340000000"
}

Explication des champs de notification

ChampTypeDescription
paymentStatusstringSUCCESS, PENDING ou FAILED.
paymentMethodstringLa méthode utilisée (MVOLA, AIRTEL_MONEY, ORANGE_MONEY, BRED).
currencystringCode de la devise (toujours MGA).
displayCurrencystringLa devise que le payeur a vue sur le formulaire (toujours MGA aujourd'hui).
amountintegerMontant payé.
estimatedAmountintegerLe montant exprimé en displayCurrency (égal à amount tant que seul MGA est pris en charge).
feeintegerFrais de transaction déduits.
clientNamestringNom du client tel que fourni.
descriptionstringLa description du paiement que vous avez envoyée.
merchantPaymentReferencestringVotre référence pour ce paiement — la reference que vous avez envoyée à la création du lien.
paymentReferencestringLa référence de Papi pour ce paiement (un UUID). Elle est retournée comme papiPaymentReference lorsque vous relisez le lien.
notificationTokenstringJeton retourné lors de la création du lien de paiement – utilisez-le comme contrôle d'authenticité supplémentaire, après la signature.
messagestringInformations supplémentaires lisibles par l'humain.
payerEmailstringAdresse email du client (si fournie).
payerPhonestringNuméro de téléphone du client (si fourni).

Vérification de la notification

Papi signe chaque notification qu'il envoie à votre notificationUrl. Avant de mettre à jour vos données :

  1. Vérifiez la signature contenue dans l'en-tête X-Papi-Signature, avec le secret de signature de votre application.
  2. Vérifiez que merchantPaymentReference et notificationToken correspondent aux valeurs de votre lien de paiement.
Procédure complète

Consultez Sécuriser les notifications (callbacks) pour les en-têtes, le secret de signature, la construction de la signature, des exemples de code (Node.js, PHP, Python, Java), un vecteur de test et les bonnes pratiques pour sécuriser votre endpoint.

Si une notification n'arrive jamais

Une notification n'est envoyée qu'une fois. Si votre endpoint était indisponible ou si l'appel s'est perdu, ne laissez pas la commande en suspens : relisez le lien de paiement avec GET /engine/api/payment-links/{merchantPaymentReference} (voir Relire un lien de paiement) et utilisez le paymentStatus retourné. Vous pouvez aussi renvoyer la notification depuis le tableau de bord : voir Notifications en échec.

3
Préparez le corps et les en-têtes pour la création d'un lien de paiement

Vous enverrez une requête POST pour générer un lien de paiement.
Le corps doit inclure les URLs créées aux étapes 1 et 2, ainsi que les détails du paiement.

Endpoint

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

En-têtes

{
"Content-Type": "application/json",
"Token": "<VOTRE_CLÉ_API>"
}

Exemple de corps de requête

{
"amount": 15000.0,
"clientName": "Nom du client",
"reference": "ORDER-123",
"description": "Paiement pour la commande #123",
"successUrl": "https://votreapp.com/paiement-succes",
"failureUrl": "https://votreapp.com/paiement-echec",
"notificationUrl": "https://votreapp.com/paiement-notif",
"validDuration": 60,
"provider": "MVOLA",
"payerEmail": "client@example.com",
"payerPhone": "+261340000000",
"testReason": "QA interne",
"isTestMode": false
}

Explication des champs de la requête

ChampTypeRequisDescription
clientNamestringNom du client.
amountnumberMontant du paiement (minimum 300).
referencestringVotre identifiant unique pour ce paiement (ex. : ID de commande).
descriptionstringDescription courte (max 255 caractères).
successUrlstringURL de redirection après le succès (doit commencer par http(s)://).
failureUrlstringURL de redirection après l'échec (doit commencer par http(s)://).
notificationUrlstringVotre endpoint qui reçoit les notifications de paiement (http(s)://). Fortement recommandé ; sans elle, relisez l'issue avec le GET ci-dessous.
validDurationintegerDurée de validité du lien en heures (défaut : 1, doit être > 0).
providerstringRestreindre à un seul prestataire : MVOLA, AIRTEL_MONEY, ORANGE_MONEY, BRED.
payerEmailstringAdresse email du client.
payerPhonestringNuméro de téléphone du client.
testReasonstringRaison de l'utilisation du mode test (apparaît dans le tableau de bord).
isTestModebooleanDéfinir à true pour activer le mode test (voir la section « Mode Test » ci-dessous).
Référence complète

Tous les champs, règles de validation, codes d'erreur et exemples : API de création de lien de paiement.

4
Envoyez la requête POST pour récupérer le lien de paiement

Effectuez la requête avec le corps et les en-têtes de l'étape 3.
En cas de succès, vous recevez une réponse comme celle-ci :

{
"data": {
"amount": 15000.0,
"currency": "MGA",
"linkCreationDateTime": 1788065989011,
"linkExpirationDateTime": 1788281989011,
"paymentLink": "https://payment-form.papi.mg/yourshop/payments/eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJDbGllbnQgTmFtZSIsImV4cCI6MTc4ODI4MTk4OX0.Yx8kQ0rN2mVvL5tJ7pF3cHqA9sWbZ1dE4gT6uK0nX2Y",
"clientName": "Nom du client",
"paymentReference": "ORDER-123",
"description": "Paiement pour la commande #123",
"successUrl": "https://votreapp.com/paiement-succes",
"failureUrl": "https://votreapp.com/paiement-echec",
"notificationUrl": "https://votreapp.com/paiement-notif",
"payerEmail": "client@example.com",
"payerPhone": "+261340000000",
"notificationToken": "xyz789",
"testReason": "QA interne",
"isTestMode": false
}
}

Champs importants de la réponse

ChampDescription
paymentLinkL'URL vers laquelle le client doit être redirigé pour payer. Sa forme est https://payment-form.papi.mg/<code-de-votre-application>/payments/<jwt>, où le JWT est émis par Papi et expire avec le lien.
notificationTokenConservez-le – vous pourrez le comparer au notificationToken des futures notifications, comme contrôle supplémentaire après la signature.
paymentReferenceDans cette réponse, il reprend votre reference — la même valeur que celle retournée comme merchantPaymentReference dans les notifications et dans la relecture. Ce n'est pas la référence de paiement de Papi, qui s'appelle paymentReference dans la notification et papiPaymentReference dans la relecture.

Envoyer deux fois la même requête est sans risque : voir Réessais et idempotence.

5
Redirigez l'utilisateur vers l'URL de paiement

Choisissez comment envoyer le client vers la page de paiement. Deux flux sont pris en charge – choisissez celui qui correspond à votre UX.

Option A — Redirection standard

Extrayez le paymentLink de la réponse et redirigez le client :

  • Applications web : Ouvrez le lien dans un nouvel onglet de navigateur ou effectuez une redirection HTTP.
  • Applications mobiles : Utilisez une WebView ou le navigateur par défaut de l'appareil.
    Conseil : Certaines plateformes réinitialisent les WebViews lorsque l'application passe en arrière-plan – gérez cela avec soin pour éviter la perte d'état.

Une fois redirigé, le client finalise le paiement sur la page sécurisée de Papi. Après la transaction, il est renvoyé vers votre successUrl ou failureUrl, et votre notificationUrl reçoit le statut final.

Écran de choix du paiement


Option B — Fenêtre pop-up intégrée à l'application

Gardez le client sur votre site en ouvrant le paymentLink dans une fenêtre pop-up et en écoutant un événement postMessage envoyé par Papi lorsque le paiement est confirmé.

1. Ouvrir la pop-up de paiement

Ouvrez l'URL retournée par le serveur dans une nouvelle fenêtre pop-up. Cela doit se produire à l'intérieur d'un gestionnaire d'événement utilisateur (ex. un événement submit de formulaire) — sinon le navigateur bloquera la pop-up. Nous utilisons JavaScript pour la démonstration, mais la même logique s'applique dans n'importe quel framework frontend.

var paymentWindow = window.open(
paymentLink, // URL de l'étape précédente
'payment-form-window', // nom de fenêtre réutilisable
'width=500,height=700' // dimensions de la pop-up
);

Conservez une référence à paymentWindow — vous en aurez besoin pour vérifier que le message provient bien de cette pop-up.

2. Écouter le succès du paiement

Une fois la pop-up ouverte, enregistrez un écouteur message sur la fenêtre parente. Papi publiera { type: 'PAYMENT_STATUS' } lorsque le paiement est confirmé.

let PAPI_ORIGIN  = 'https://payment-form.papi.mg';

let paymentMessageHandler = null;
let paymentSuccess = false;

function teardownPaymentMessageListener() {
if (paymentMessageHandler) {
window.removeEventListener('message', paymentMessageHandler);
paymentMessageHandler = null;
}
}

function onPaymentSuccessMessage () {
// Votre logique de gestion du succès ici
}

function setupPaymentMessageListener() {
teardownPaymentMessageListener();

paymentMessageHandler = function (event) {
// 1. Rejeter tout ce qui ne vient pas de l'origine Papi
if (event.origin !== PAPI_ORIGIN) return;
// 2. Rejeter tout ce qui ne vient pas de la pop-up ouverte
if (event.source !== paymentWindow) return;
// 3. Ne réagir qu'aux charges utiles PAYMENT_STATUS
if (!event.data || event.data.type !== 'PAYMENT_STATUS') return;
// 4. Éviter le double traitement
if (paymentSuccess) return;

paymentSuccess = true;
teardownPaymentMessageListener();
onPaymentSuccessMessage();
};

window.addEventListener('message', paymentMessageHandler);
}

Appelez setupPaymentMessageListener() immédiatement après window.open() afin de ne manquer aucun message entre les deux appels :

paymentWindow = window.open(paymentLink, 'payment-form-window', 'width=500,height=700');
setupPaymentMessageListener();

3. Garde-fous de sécurité

Trois vérifications doivent toutes passer avant d'agir sur un message :

VérificationPourquoi c'est important
event.origin === PAPI_ORIGINRejette les messages provenant de tout autre domaine. Doit correspondre exactement — schéma + hôte, sans barre oblique finale.
event.source === paymentWindowRejette les messages des iframes ou onglets non liés qui partagent la même origine.
event.data.type === 'PAYMENT_STATUS'Ignore les autres trafics postMessage sur la même page (analytics, widgets, etc.).

4. Nettoyage

Retirez toujours l'écouteur une fois terminé — en cas de succès, d'annulation ou lorsque l'utilisateur ferme la pop-up :

// En cas de succès → géré automatiquement dans l'écouteur ci-dessus

// En cas d'annulation (l'utilisateur clique sur « Annuler »)
teardownPaymentMessageListener();
if (paymentWindow && !paymentWindow.closed) { paymentWindow.close(); }
paymentWindow = null;

Pièges courants

SymptômeCause
L'écouteur ne se déclenche jamaisMauvaise correspondance de PAPI_ORIGIN (http vs https, barre oblique finale, port).
L'écouteur se déclenche pour des messages non liésGarde-fou event.source === paymentWindow manquant.
La redirection se produit deux foisIndicateur paymentSuccess manquant ou écouteur non retiré après le premier déclenchement.
event.source est nullPop-up fermée avant l'envoi du message — assurez-vous que le message est envoyé avant window.close().

Réessais et idempotence

La création d'un lien de paiement est idempotente sur la reference, dans votre boutique, tant que le lien est actif (ni expiré, ni payé, ni désactivé) :

Vous refaites un POST avec la même referenceRéponse de Papi
…même amount et même devise, pendant que le premier lien est actif200 avec le lien existant : même paymentLink, même notificationToken. La description, les URLs et les coordonnées du payeur du réessai sont ignorées.
…un amount ou une devise différents, pendant que le premier lien est actif409 avec le code PAYMENT_LINK_CONFLICT. Attendez l'expiration du lien ou utilisez une autre référence.
…après que le premier lien a été payé, a expiré ou a été désactivé200 avec un nouveau lien.

Un réessai réseau ou un double envoi du checkout ne peut donc jamais laisser deux liens payables pour une même commande. Les requêtes simultanées sont sérialisées : elles reçoivent toutes le même lien.

{
"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."
}
}

Les liens créés à la main depuis le tableau de bord sont hors de cette règle : ils ne sont ni retournés ni une raison de refuser.

Référence complète

Règles d'idempotence détaillées, conseils de réessai et codes d'erreur : API de création de lien de paiement.


Tout lien créé avec POST /payment-links peut être relu par votre référence marchande. C'est ainsi que vous récupérez l'issue d'un paiement dont la notification ne vous est jamais parvenue, et que vous vérifiez où en est un lien avant d'en réémettre un.

Référence complète

Tous les champs, règles de statut, codes d'erreur et exemples : API de statut de lien de paiement.

Endpoint

GET https://app.papi.mg/engine/api/payment-links/{merchantPaymentReference}

{merchantPaymentReference} est votre référence marchande : le champ reference envoyé à la création du lien, la même valeur que le merchantPaymentReference des réponses et des notifications. Ce n'est pas la référence de paiement Papi. Elle est résolue dans la boutique à laquelle appartient votre Token. Si plusieurs liens ont été créés sous la même référence, un lien payé est retourné s'il en existe un, quel que soit son âge ; sinon le plus récent.

En-têtes

{
"Token": "<VOTRE_CLE_API>"
}

Exemple de réponse

{
"data": {
"linkStatus": "PAID",
"paymentStatus": "SUCCESS",
"paymentMethod": "MVOLA",
"currency": "MGA",
"displayCurrency": "MGA",
"amount": 15000.0,
"clientName": "Nom du client",
"description": "Paiement pour la commande #123",
"merchantPaymentReference": "ORDER-123",
"papiPaymentReference": "c1f4a5b0-6f5e-4e1b-9f0e-2b7d8a9c3d21",
"notificationToken": "xyz789",
"message": null,
"payerEmail": "client@example.com",
"payerPhone": "+261340000000",
"paymentLink": "https://payment-form.papi.mg/votreboutique/payments/eyJhbGciOiJIUzI1NiJ9...",
"shortLink": "https://link.papi.mg/8NW7R",
"linkCreationDateTime": 1788065989011,
"linkExpirationDateTime": 1788281989011,
"isTestMode": false
}
}

Champs de la réponse

Les champs portent les mêmes noms et la même signification que le corps de la notification, à une exception près : la référence Papi s'appelle ici papiPaymentReference (paymentReference dans la notification) pour ne jamais être confondue avec votre merchantPaymentReference.

ChampTypeDescription
linkStatusstringÉtat du lien : ACTIVE (encore payable), EXPIRED, PAID ou DISABLED. PAID l'emporte sur DISABLED et EXPIRED.
paymentStatusstringIssue du paiement effectué via le lien : SUCCESS, PENDING ou FAILED — les mêmes valeurs que la notification. null tant que personne n'a tenté de payer.
paymentMethodstringPrestataire utilisé par le payeur (MVOLA, AIRTEL_MONEY, ORANGE_MONEY, BRED). null tant qu'aucune tentative de paiement n'existe.
currencystringCode de la devise (toujours MGA).
displayCurrencystringDevise affichée au payeur.
amountnumberMontant du lien.
clientNamestringNom du client tel que fourni.
descriptionstringLa description que vous avez envoyée.
merchantPaymentReferencestringVotre reference de la requête de création.
papiPaymentReferencestringRéférence Papi de la tentative de paiement — le paymentReference de la notification. null tant qu'aucune tentative n'existe.
notificationTokenstringLe jeton retourné à la création du lien.
messagestringMotif d'échec de la tentative de paiement, s'il y en a un.
payerEmailstringEmail du client (si fourni).
payerPhonestringNuméro de téléphone du client (si fourni).
paymentLinkstringL'URL de paiement, telle que retournée à la création.
shortLinkstringForme courte de l'URL de paiement, si elle a été générée.
linkCreationDateTimeintegerDate de création, en millisecondes epoch.
linkExpirationDateTimeintegerDate d'expiration, en millisecondes epoch.
isTestModebooleanIndique si le lien a été marqué comme test.

Lire les deux statuts ensemble

linkStatuspaymentStatusSignification
ACTIVEnullPersonne n'a encore tenté de payer.
ACTIVEFAILEDLa dernière tentative a été refusée ou abandonnée ; le payeur peut réessayer sur le même lien.
ACTIVEPENDINGUne tentative est en cours chez le prestataire. Revérifiez sous peu.
PAIDSUCCESSLe paiement est passé. Confirmez la commande.
EXPIREDnull / FAILEDLe lien a expiré avant un paiement réussi. Créez-en un nouveau.
DISABLEDnull / FAILEDLe lien a été désactivé depuis le tableau de bord.

Erreurs

StatutSignification
401En-tête Token absent ou inconnu.
404Aucun lien avec cette reference dans votre boutique. Une référence utilisée par une autre boutique renvoie aussi 404.

Mode Test

Papi propose deux façons de tester votre intégration :

1. Indicateur isTestMode dans la requête

  • Définissez "isTestMode": true dans le corps de la requête.
  • La transaction est marquée comme test dans votre tableau de bord, mais de l'argent réel est quand même déplacé.
  • Utile pour les tests de bout en bout avec de vrais prestataires (sauf pour le mobile money, qui ne prend pas en charge les transactions de test non réelles).

2. Mode Test de l'application (cartes uniquement)

  • Dans les paramètres de votre boutique dans le tableau de bord, activez le Mode Test.
  • Utilisez les informations de la carte de test suivante :
    • Numéro de carte : 4000 0000 0000 5126
    • Date d'expiration : 01/2028
    • CVV : 123
  • Cela simule un paiement par carte sans mouvement de fonds réels.

Récapitulatif du flux de travail

  1. Obtenez votre clé API depuis le tableau de bord (Icône avatar → Boutiques → sélectionnez la boutique → onglet Développeur).
  2. Créez un lien de paiement en envoyant une requête POST avec les champs requis. Les réessais sont sans risque : la même reference avec le même montant retourne le même lien.
  3. Redirigez le client vers le paymentLink retourné.
  4. Recevez une notification sur votre notificationUrl lorsque le statut du paiement change.
  5. Vérifiez la notification : contrôlez d'abord l'en-tête X-Papi-Signature, puis merchantPaymentReference et notificationToken. Voir Sécuriser les notifications (callbacks).
  6. Mettez à jour vos enregistrements et informez le client.
  7. Relisez le lien avec GET /engine/api/payment-links/{merchantPaymentReference} dès qu'une notification manque ou qu'une issue doit être revérifiée.

En suivant ces étapes, vous pouvez accepter des paiements en ligne de manière sécurisée avec Papi. Testez toujours minutieusement en mode test avant de passer en production.