Aller au contenu principal

Démarrage rapide

Ce guide est conçu pour les développeurs afin d'intégrer Papi à votre site ou application. Suivez ces étapes pour mettre en place un système de paiement qui fonctionne de manière fluide pour vos clients.

Prérequis

  1. Votre application est en ligne et prête à accepter les paiements.
  2. Vous disposez de votre clé API depuis le tableau de bord Papi (voir Où trouver votre clé API ci-dessous).
  3. Choisissez une URL de redirection après un paiement réussi (successUrl).
  4. Choisissez une URL de redirection après un paiement échoué (failureUrl).
  5. Choisissez une URL sur votre serveur qui recevra les notifications de paiement de Papi (notificationUrl).

Où trouver votre clé API

  1. Connectez-vous à votre tableau de bord : https://dashboard.papi.mg.
  2. En haut à droite, cliquez sur l'icône de votre avatar (photo ou initiales).
  3. Cliquez sur Boutiques dans le menu déroulant.
  4. Cliquez sur l'application que vous souhaitez utiliser.
  5. Dans le tableau de bord de l'application, ouvrez l'onglet Développeur.
  6. Sous Clé API, vous verrez votre clé (une longue chaîne de caractères).

Important : Gardez votre clé API secrète. Toute personne qui la possède peut créer des paiements en votre nom.

Vue d'ensemble du flux

Avant d'entrer dans le détail des étapes, voici comment le flux s'articule :

  1. Un client passe commande sur votre site.
  2. Vous créez un lien de paiement sécurisé via Papi.
  3. Le client atteint la page de paiement pour finaliser son paiement, de l'une des deux manières suivantes :
    • 3-a. Il est redirigé vers la page de paiement sécurisée de Papi (redirection standard).
    • 3-b. La page de paiement est ouverte dans une fenêtre pop-up intégrée à votre site.
  4. Papi envoie le résultat du paiement à votre système via votre URL de notification (requête POST).
  5. Votre système vérifie la notification et met à jour le statut de la commande (succès ou échec).
  6. Le client voit le résultat sur votre site (succès ou échec).

Étape 1 : Générer un lien de paiement

Pour permettre aux clients de payer, vous devez créer un lien sécurisé qui les envoie vers une page de paiement. Ce lien contient le montant, les informations client et l'URL à laquelle Papi notifiera votre système du résultat du paiement.

Ce que vous devez faire

Envoyez une requête POST à l'endpoint suivant :

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

Authentification

Chaque requête doit inclure votre clé API dans les en-têtes :

Content-Type: application/json
Token: <VOTRE_CLÉ_API>

Corps de la requête

Envoyez un corps JSON avec les champs requis et optionnels. Exemple :

{
"amount": 15000.0,
"clientName": "Nom du client",
"reference": "ORDER-123",
"description": "Paiement pour la commande #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",
"testReason": "Tests internes",
"isTestMode": false
}
Nom du champTypeRequisDescription
clientNamestringNom du client.
amountnumberMontant du paiement (>= 300).
referencestringVotre référence unique pour ce paiement.
descriptionstringDescription du paiement (max 255 caractères).
successUrlstring×URL de redirection après succès (http(s)://).
failureUrlstring×URL de redirection après échec (http(s)://).
notificationUrlstring×URL qui recevra les notifications de paiement (http(s)://). Facultative mais fortement recommandée ; sans elle, relisez le lien de paiement ou vérifiez le statut du paiement pour connaître l'issue.
validDurationinteger×Durée de validité en heures (>0). Par défaut : 1.
providerstring×Un parmi : MVOLA, AIRTEL_MONEY, ORANGE_MONEY, BRED.
payerEmailstring×Adresse e-mail du client.
payerPhonestring×Numéro de téléphone du client (ex. +261340000000).
testReasonstring×Motif du mode test (si isTestMode=true).
isTestModeboolean×Mettre à true pour activer le mode test.

Réponse en cas de succès

Si la requête est valide, vous recevez :

{
"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://yourapp.com/payment-success",
"failureUrl": "https://yourapp.com/payment-failure",
"notificationUrl": "https://yourapp.com/payment-notify",
"payerEmail": "customer@example.com",
"payerPhone": "+261340000000",
"notificationToken": "xyz789",
"testReason": "Tests internes",
"isTestMode": false
}
}
  • paymentLink — Redirigez le client vers cette URL pour qu'il effectue le paiement. 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.
  • notificationToken — Conservez-le pour vérifier ensuite que les notifications sont authentiques.
  • paymentReference — Dans cette réponse, il reprend votre reference, la même valeur que celle retournée comme merchantPaymentReference dans les notifications ; ce n'est pas la référence de paiement de Papi.

Réponse en cas d'erreur

En cas de problème, vous pouvez recevoir :

{
"error": {
"code": "VALIDATION_ERROR",
"message": "Le montant est requis"
}
}

Réessayer en toute sécurité

La création d'un lien de paiement est idempotente sur votre reference, dans votre boutique, tant que le lien est actif — ni expiré, ni payé, ni désactivé. Un réessai après un timeout réseau ou un double envoi du checkout peut donc se faire avec la même reference : vous ne vous retrouverez jamais avec deux liens payables pour une même commande.

Vous refaites un POST avec la même referenceMême lien ?Réponse
…avec le même amount et la même devise, pendant que le lien est actif200 avec le paymentLink et le notificationToken existants. Les autres champs envoyés lors du réessai sont ignorés.
…avec un amount ou une devise différents, pendant que le lien est actif×409, code d'erreur PAYMENT_LINK_CONFLICT.
…après que le lien a été payé, a expiré ou a été désactivé×200 avec un nouveau lien.

Les requêtes simultanées sont sérialisées : elles reçoivent toutes le même lien. Pour les règles complètes, voir Réessais et idempotence.

Exemples

import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.*;

@Service
public class PaymentService {

private static final String API_URL = "https://app.papi.mg/engine/api/payment-links";
private static final String API_KEY = "<VOTRE_CLÉ_API>";

public Map<String, Object> createPaymentLink() {
RestTemplate restTemplate = new RestTemplate();

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Token", API_KEY);

Map<String, Object> body = new LinkedHashMap<>();
body.put("amount", 15000.0);
body.put("clientName", "Nom du client");
body.put("reference", "ORDER-123");
body.put("description", "Paiement pour la commande #123");
body.put("successUrl", "https://yourapp.com/payment-success");
body.put("failureUrl", "https://yourapp.com/payment-failure");
body.put("notificationUrl", "https://yourapp.com/payment-notify");
body.put("validDuration", 60);
body.put("provider", "MVOLA");
body.put("payerEmail", "customer@example.com");
body.put("payerPhone", "+261340000000");

HttpEntity<Map<String, Object>> request = new HttpEntity<>(body, headers);
ResponseEntity<Map> response = restTemplate.postForEntity(API_URL, request, Map.class);

Map<String, Object> data = (Map<String, Object>) response.getBody().get("data");
String paymentLink = (String) data.get("paymentLink");
String notificationToken = (String) data.get("notificationToken");
// Stocker notificationToken pour vérifier les notifications plus tard
System.out.println("Lien de paiement : " + paymentLink);
return data;
}
}

Étape 2 : Rediriger le client vers la page de paiement

Une fois le lien de paiement généré, le client doit finaliser le paiement via ce lien. Deux flux sont pris en charge — choisissez celui qui correspond à votre UX.

Option A — Redirection standard

  1. Extraire paymentLink de la réponse de l'étape 1.
  2. Rediriger le client vers cette URL (même onglet, nouvel onglet ou WebView dans une app mobile).

Applications mobiles : Utilisez une WebView ou le navigateur par défaut. Certaines plateformes réinitialisent la WebView lorsque l'app passe en arrière-plan ; gérez ce cas pour ne pas perdre l'état.

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. Extraire paymentLink de la réponse de l'étape 1.
  2. Ouvrez-le dans une pop-up avec window.open() à l'intérieur d'un gestionnaire d'événement utilisateur (ex. un submit de formulaire), sinon le navigateur bloquera la pop-up.
  3. Enregistrez un écouteur message sur la fenêtre parente pour réagir lorsque Papi publie { type: 'PAYMENT_STATUS' }.

Consultez le guide d'intégration pour le code complet de la pop-up et de l'écouteur, les garde-fous de sécurité et le nettoyage.

Exemples

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

@Controller
public class CheckoutController {

private final PaymentService paymentService;

public CheckoutController(PaymentService paymentService) {
this.paymentService = paymentService;
}

@PostMapping("/checkout")
public void checkout(HttpServletResponse response) throws IOException {
Map<String, Object> data = paymentService.createPaymentLink();
String paymentLink = (String) data.get("paymentLink");
response.sendRedirect(paymentLink);
}
}

Étape 3 : Mettre en place l'endpoint de notification

Après que le client a terminé le paiement sur la page Papi, Papi envoie une requête POST à votre notificationUrl pour informer votre système du résultat.

Ce que vous devez faire

  1. Créer un endpoint qui accepte les requêtes POST et lit le corps JSON (l'URL que vous avez fournie comme notificationUrl à l'étape 1).
  2. Dans ce script, vérifier la notification puis mettre à jour votre système (ex. marquer la commande comme payée ou échouée).

Exemple de payload de notification

Papi envoie un corps JSON du type :

{
"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": "customer@example.com",
"payerPhone": "+261340000000"
}
Nom du champTypeDescription
paymentStatusstringSUCCESS, PENDING ou FAILED.
paymentMethodstringMéthode utilisée (ex. MVOLA).
currencystringCode devise.
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.
clientNamestringNom du client.
descriptionstringVotre description.
merchantPaymentReferencestringVotre référence (la reference envoyée à l'étape 1).
paymentReferencestringLa référence de Papi pour ce paiement (un UUID).
notificationTokenstringPermet de vérifier l'authenticité.
messagestringDétails supplémentaires.
payerEmailstringE-mail du client.
payerPhonestringTéléphone du client.

Comment vérifier la notification

Papi signe chaque notification avec l'en-tête X-Papi-Signature. Vérifiez d'abord cette signature, avec le secret de signature de votre application : voir Sécuriser les notifications (callbacks) pour la procédure complète et des exemples de code. Effectuez ensuite ces contrôles supplémentaires :

  1. Vérifier que merchantPaymentReference correspond à la référence envoyée lors de la création du lien de paiement.
  2. Vérifier que notificationToken correspond au token reçu dans la réponse de l'étape 1.

Si les deux correspondent, considérez la notification comme authentique et mettez à jour vos enregistrements.

Exemples

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;

@RestController
public class NotificationController {

@PostMapping("/payment-notify")
public ResponseEntity<Void> handleNotification(@RequestBody Map<String, Object> payload) {
String merchantReference = (String) payload.getOrDefault("merchantPaymentReference", "");
String notificationToken = (String) payload.getOrDefault("notificationToken", "");
String paymentStatus = (String) payload.getOrDefault("paymentStatus", "");
Number amount = (Number) payload.getOrDefault("amount", 0);

// Vérifier par rapport aux valeurs stockées lors de la création du lien de paiement
String expectedToken = "xyz789"; // Récupérer depuis votre base de données
String expectedReference = "ORDER-123"; // Récupérer depuis votre base de données

if (!merchantReference.equals(expectedReference) || !notificationToken.equals(expectedToken)) {
return ResponseEntity.status(403).build();
}

if ("SUCCESS".equals(paymentStatus)) {
// Marquer la commande comme payée dans votre base de données
System.out.println("Paiement " + merchantReference + " : SUCCÈS (montant : " + amount + ")");
} else if ("FAILED".equals(paymentStatus)) {
// Gérer l'échec
System.out.println("Paiement " + merchantReference + " : ÉCHEC");
} else {
System.out.println("Paiement " + merchantReference + " : " + paymentStatus);
}

return ResponseEntity.ok().build();
}
}

Étape 4 : Créer les pages de résultat du paiement

À la fin du processus de paiement, Papi affiche un message de succès ou d'échec à l'utilisateur, puis le redirige vers les URLs fournies à l'étape 1 (successUrl et failureUrl).

Ce que vous devez faire

Option 1 : Utiliser une seule URL pour le succès et l'échec (ex. retour à l'accueil ou à la page de détail de commande).

Option 2 : Utiliser deux pages distinctes :

  • Une page pour les paiements réussis, à l'URL définie comme successUrl (ex. prochaines étapes, livraison).
  • Une page pour les paiements échoués, à l'URL définie comme failureUrl (ex. « Paiement échoué », « Réessayer » ou contacter le support).

Mode test

Pour tester votre intégration :

  1. isTestMode=true — Marque la transaction comme test (attention : selon la configuration, des mouvements réels peuvent tout de même avoir lieu).
  2. Mode test de l'application (cartes uniquement) — Dans le tableau de bord, activez le mode test dans les paramètres de l'application. Vous pouvez utiliser cette carte de test :
    • Numéro : 4000 0000 0000 5126
    • Date d'expiration : 01/2028
    • CVV : 123

Remarque : Le mobile money ne permet pas de transactions de test non réelles.