Version de travail · contenus et exemples à affiner Voir les 95 pages ↗
Parlons de votre projet ↗
Encaissements / P017

Page de paiement hébergée

Adaptation éditoriale
Référence de travail. Les routes, schémas, limites et procédures hérités du référentiel sont à aligner avec l’API d’Éric. Ils ne prouvent pas une fonctionnalité livrée. Les hôtes .invalid sont des exemples inertes.

Les paiements en application utilisent le type redirect qui permet de contrôler l'expérience utilisateur dans votre application tout en redirigeant le client vers son application de paiement mobile pour finaliser le paiement.

Pour plus de détails : Consulter la fiche de référence : Créer une demande de paiement

Point d’attention.

Pour retenir l'encaissement au lieu de créditer le solde tout de suite, ne passez pas par cet endpoint : créez un pot, puis appelez POST /partner_api/escrows/{id}/collect (clé plateforme uniquement). escrowId n'est plus accepté ici (400 escrow_id_not_supported). Voir Escrow.

Comment ça marche

Votre serveur crée la demande de paiement et reçoit une redirectUrl. Vous y envoyez le client : il paie dans son application Mobile Money, puis revient sur l'URL de succès ou d'erreur que vous avez fournie. Le résultat définitif vous parvient par webhook.

Vous décidez donc du montant, de la référence, du moyen de paiement et des pages de retour.

Quand choisir ce mode

  • Applications mobiles natives
  • Applications web avec intégration personnalisée
  • Expériences de paiement contrôlées par le développeur
  • Intégrations nécessitant un contrôle total du flux

Créer une demande de paiement redirect

Pour créer une demande de paiement redirect, utilisez l'endpoint /partner_api/payment_requests avec le type redirect.

Paramètres requis

  • storeId : Identifiant du magasin
  • amountCents : Montant en centimes, multiple de 100 (minimum 100)
  • currency : Code devise (ISO 4217), généralement "XOF"
  • reference : Référence unique du paiement (5 à 100 caractères)
  • paymentDetails.type : "redirect"
  • paymentDetails.data.paymentMethod : Méthode de paiement (wave, orange, mtn, moov, djamo, Kelyko ; pi bientôt disponible)
  • paymentDetails.data.successUrl : URL de redirection en cas de succès (requis)
  • paymentDetails.data.errorUrl : URL de redirection en cas d'erreur (requis)

Contourner le checkout Kelyko (API direct)

Par défaut, une demande redirect renvoie une URL de paiement hébergée qui guide le payeur jusqu'à son opérateur, et c'est le flux recommandé : la page Kelyko lui demande elle-même son moyen de paiement et son numéro.

Si vous connaissez déjà la méthode (paymentMethod), l'API direct renvoie à la place l'URL de l'opérateur, et vos pages de retour deviennent son point d'arrivée. Elle s'active avec les champs forceProviderDirect et payerPhone. Attention, redirectUrl ne mène plus chez l'opérateur sur MTN et Moov, qui confirment par USSD.

Tout est détaillé dans API direct.

Exemple de requête

Exemple · cURL

curl -X POST "https://api.kelyko.invalid/partner_api/payment_requests" \
  -H "X-API-KEY: your_api_key_here" \
  -H "X-API-KEY-ID: your_api_key_id_here" \
  -H "Content-Type: application/json" \
  -d '{
    "storeId": "59ae202a-f583-4a15-970f-9e99bd1e0baa",
    "amountCents": 10000,
    "currency": "XOF",
    "reference": "PAY-APP-2024-001",
    "paymentDetails": {
      "type": "redirect",
      "data": {
        "paymentMethod": "wave",
        "successUrl": "https://myapp.com/payment/success?reference=PAY-APP-2024-001",
        "errorUrl": "https://myapp.com/payment/error?reference=PAY-APP-2024-001"
      }
    }
  }'

Exemple · PHP

<?php
$ch = curl_init('https://api.kelyko.invalid/partner_api/payment_requests');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'X-API-KEY: your_api_key_here',
    'X-API-KEY-ID: your_api_key_id_here',
    'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
    'storeId' => '59ae202a-f583-4a15-970f-9e99bd1e0baa',
    'amountCents' => 10000,
    'currency' => 'XOF',
    'reference' => 'PAY-APP-2024-001',
    'paymentDetails' => [
        'type' => 'redirect',
        'data' => [
            'paymentMethod' => 'wave',
            'successUrl' => 'https://myapp.com/payment/success?reference=PAY-APP-2024-001',
            'errorUrl' => 'https://myapp.com/payment/error?reference=PAY-APP-2024-001'
        ]
    ]
]));

$response = curl_exec($ch);
curl_close($ch);
$paymentRequest = json_decode($response, true);
?>

Exemple · Node.js

const response = await fetch('https://api.kelyko.invalid/partner_api/payment_requests', {
  method: 'POST',
  headers: {
    'X-API-KEY': 'your_api_key_here',
    'X-API-KEY-ID': 'your_api_key_id_here',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    storeId: '59ae202a-f583-4a15-970f-9e99bd1e0baa',
    amountCents: 10000,
    currency: 'XOF',
    reference: 'PAY-APP-2024-001',
    paymentDetails: {
      type: 'redirect',
      data: {
        paymentMethod: 'wave',
        successUrl: 'https://myapp.com/payment/success?reference=PAY-APP-2024-001',
        errorUrl: 'https://myapp.com/payment/error?reference=PAY-APP-2024-001'
      }
    }
  })
});

const paymentRequest = await response.json();

Exemple · Ruby

require 'net/http'
require 'json'
require 'uri'

uri = URI('https://api.kelyko.invalid/partner_api/payment_requests')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Post.new(uri.path)
request['X-API-KEY'] = 'your_api_key_here'
request['X-API-KEY-ID'] = 'your_api_key_id_here'
request['Content-Type'] = 'application/json'
request.body = {
  storeId: '59ae202a-f583-4a15-970f-9e99bd1e0baa',
  amountCents: 10000,
  currency: 'XOF',
  reference: 'PAY-APP-2024-001',
  paymentDetails: {
    type: 'redirect',
    data: {
      paymentMethod: 'wave',
      successUrl: 'https://myapp.com/payment/success?reference=PAY-APP-2024-001',
      errorUrl: 'https://myapp.com/payment/error?reference=PAY-APP-2024-001'
    }
  }
}.to_json

response = http.request(request)
payment_request = JSON.parse(response.body)

Exemple · Python

import requests

url = 'https://api.kelyko.invalid/partner_api/payment_requests'
headers = {
    'X-API-KEY': 'your_api_key_here',
    'X-API-KEY-ID': 'your_api_key_id_here',
    'Content-Type': 'application/json'
}
data = {
    'storeId': '59ae202a-f583-4a15-970f-9e99bd1e0baa',
    'amountCents': 10000,
    'currency': 'XOF',
    'reference': 'PAY-APP-2024-001',
    'paymentDetails': {
        'type': 'redirect',
        'data': {
            'paymentMethod': 'wave',
            'successUrl': 'https://myapp.com/payment/success?reference=PAY-APP-2024-001',
            'errorUrl': 'https://myapp.com/payment/error?reference=PAY-APP-2024-001'
        }
    }
}

response = requests.post(url, headers=headers, json=data)
payment_request = response.json()

Réponse réussie

{
  "id": "d22c81f3-ee04-4ec5-8bd2-cd8af5dabcfc",
  "storeId": "59ae202a-f583-4a15-970f-9e99bd1e0baa",
  "reference": "PAY-APP-2024-001",
  "type": "redirect",
  "paymentMethod": "wave",
  "status": "pending",
  "redirectUrl": "https://pay.kelyko.invalid/pay_request/pr/d22c81f3-ee04-4ec5-8bd2-cd8af5dabcfc",
  "errorReason": null
}

Redirigez le client vers l'URL du champ redirectUrl. Elle le mène ensuite à son application de paiement.

Du clic au webhook

  1. Créer la demande : Créez une demande de paiement redirect avec vos URLs de callback
  2. Rediriger le client : Redirigez le client vers redirectUrl
  3. Redirection automatique : Kelyko redirige le client vers son application de paiement mobile
  4. Paiement client : Le client complète le paiement dans son application
  5. Retour automatique : Le client est redirigé vers votre successUrl ou errorUrl
  6. Notification : Vous recevez également une notification via webhook

Intégration dans votre application

Exemple : Application web

async function initiatePayment(orderId, amount) {
  // Créer la demande de paiement
  const response = await fetch('https://api.kelyko.invalid/partner_api/payment_requests', {
    method: 'POST',
    headers: {
      'X-API-KEY': 'your_api_key_here',
      'X-API-KEY-ID': 'your_api_key_id_here',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      storeId: '59ae202a-f583-4a15-970f-9e99bd1e0baa',
      amountCents: amount,
      currency: 'XOF',
      reference: `ORDER-${orderId}`,
      paymentDetails: {
        type: 'redirect',
        data: {
          paymentMethod: 'wave', // ou la méthode sélectionnée par l'utilisateur
          successUrl: `https://myapp.com/payment/success?order=${orderId}`,
          errorUrl: `https://myapp.com/payment/error?order=${orderId}`
        }
      }
    })
  });

  const paymentRequest = await response.json();

  // Rediriger le client vers l'URL de redirection
  window.location.href = paymentRequest.redirectUrl;
}

Exemple : Application mobile (React Native)

import { Linking } from 'react-native';

async function initiatePayment(orderId, amount) {
  // Créer la demande de paiement
  const response = await fetch('https://api.kelyko.invalid/partner_api/payment_requests', {
    method: 'POST',
    headers: {
      'X-API-KEY': 'your_api_key_here',
      'X-API-KEY-ID': 'your_api_key_id_here',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      storeId: '59ae202a-f583-4a15-970f-9e99bd1e0baa',
      amountCents: amount,
      currency: 'XOF',
      reference: `ORDER-${orderId}`,
      paymentDetails: {
        type: 'redirect',
        data: {
          paymentMethod: 'wave',
          successUrl: `myapp://payment/success?order=${orderId}`,
          errorUrl: `myapp://payment/error?order=${orderId}`
        }
      }
    })
  });

  const paymentRequest = await response.json();

  // Ouvrir l'URL de redirection
  Linking.openURL(paymentRequest.redirectUrl);
}

Gérer les callbacks

Page de succès

// Dans votre page /payment/success
function PaymentSuccessPage() {
  const params = new URLSearchParams(window.location.search);
  const reference = params.get('reference');
  const orderId = params.get('order');

  // Vérifier le statut du paiement via l'API
  useEffect(() => {
    checkPaymentStatus(reference).then(status => {
      if (status === 'success') {
        // Mettre à jour la commande comme payée
        updateOrderStatus(orderId, 'paid');
        // Afficher un message de succès
        showSuccessMessage('Paiement effectué avec succès !');
      }
    });
  }, [reference, orderId]);

  return <div>Paiement réussi !</div>;
}

Page d'erreur

// Dans votre page /payment/error
function PaymentErrorPage() {
  const params = new URLSearchParams(window.location.search);
  const reference = params.get('reference');
  const orderId = params.get('order');

  // Vérifier le statut du paiement via l'API
  useEffect(() => {
    checkPaymentStatus(reference).then(status => {
      if (status === 'error') {
        // Afficher un message d'erreur
        showErrorMessage('Le paiement a échoué. Veuillez réessayer.');
      }
    });
  }, [reference, orderId]);

  return <div>Le paiement a échoué. Veuillez réessayer.</div>;
}

Vérifier le statut

Pour vérifier le statut d'une demande de paiement :

Exemple · cURL

curl -X GET "https://api.kelyko.invalid/partner_api/payment_requests/{paymentRequestId}" \
  -H "X-API-KEY: your_api_key_here" \
  -H "X-API-KEY-ID: your_api_key_id_here"

Exemple · PHP

<?php
$paymentRequestId = 'd22c81f3-ee04-4ec5-8bd2-cd8af5dabcfc';
$ch = curl_init("https://api.kelyko.invalid/partner_api/payment_requests/{$paymentRequestId}");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'X-API-KEY: your_api_key_here',
    'X-API-KEY-ID: your_api_key_id_here'
]);

$response = curl_exec($ch);
curl_close($ch);
$paymentRequest = json_decode($response, true);
?>

Exemple · Node.js

const paymentRequestId = 'd22c81f3-ee04-4ec5-8bd2-cd8af5dabcfc';
const response = await fetch(
  `https://api.kelyko.invalid/partner_api/payment_requests/${paymentRequestId}`,
  {
    headers: {
      'X-API-KEY': 'your_api_key_here',
      'X-API-KEY-ID': 'your_api_key_id_here'
    }
  }
);

const paymentRequest = await response.json();

Exemple · Ruby

require 'net/http'
require 'json'
require 'uri'

payment_request_id = 'd22c81f3-ee04-4ec5-8bd2-cd8af5dabcfc'
uri = URI("https://api.kelyko.invalid/partner_api/payment_requests/#{payment_request_id}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Get.new(uri.path)
request['X-API-KEY'] = 'your_api_key_here'
request['X-API-KEY-ID'] = 'your_api_key_id_here'

response = http.request(request)
payment_request = JSON.parse(response.body)

Exemple · Python

import requests

payment_request_id = 'd22c81f3-ee04-4ec5-8bd2-cd8af5dabcfc'
url = f'https://api.kelyko.invalid/partner_api/payment_requests/{payment_request_id}'
headers = {
    'X-API-KEY': 'your_api_key_here',
    'X-API-KEY-ID': 'your_api_key_id_here'
}

response = requests.get(url, headers=headers)
payment_request = response.json()

Réponse avec transaction réussie :

{
  "id": "d22c81f3-ee04-4ec5-8bd2-cd8af5dabcfc",
  "storeId": "59ae202a-f583-4a15-970f-9e99bd1e0baa",
  "reference": "PAY-APP-2024-001",
  "type": "redirect",
  "paymentMethod": "wave",
  "status": "success",
  "transaction": {
    "id": "txn_1234567890",
    "amount": {
      "amount": 10000,
      "currency": "XOF"
    },
    "fees": {
      "amount": 100,
      "currency": "XOF"
    },
    "status": "success",
    "counterpartLabel": "Customer Name",
    "counterpartIdentifier": "+2250701234567",
    "description": "Payment for order #12345",
    "executedAt": "2024-01-15 14:30:25"
  }
}

Point d’attention.

Un paiement créé avec une clé plateforme facture le barème du SP (champ fees) et n'est pas remboursable. Détail dans Tarifs et catalogues.

Les moyens de paiement

  • "wave" : Wave Mobile Money
  • "orange" : Orange Money
  • "mtn" : MTN Mobile Money
  • "moov" : Moov Money
  • "djamo" : DJAMO
  • "Kelyko" : compte Kelyko. redirectUrl est le lien de paiement Kelyko (https://destination.kelyko.invalid/a-configurer), qu'un autre compte Kelyko règle. Le commerce doit avoir pay-with-Kelyko activé (activé par défaut pour un service provider et les commerces liés). Sinon la création répond 403pay_with_Kelyko_not_enabled. Une méthode inconnue reste 404unsupported_payment_method`. Ce n'est pas un rail d'escrow.
  • "pi" : PI-SPI (bientôt disponible). En redirect, redirectUrl est le lien de paiement Kelyko : le payeur y choisit le QR dynamique ou une demande de paiement dans son application PI (il saisit alors son alias PI). Le mode physique soundbox de la référence est exclu du périmètre Kelyko. Un magasin sans nom répond 400 empty_store_name_error. L’activation effective de pi doit être confirmée pour le dispositif Kelyko : une demande pi est refusée tant que le moyen de paiement n'est pas activé sur votre compte.

URLs de callback

Format des URLs

Les URLs de succès et d'erreur doivent être des URLs HTTPS valides. Vous pouvez inclure des paramètres de requête pour identifier la transaction :

https://myapp.com/payment/success?reference=PAY-APP-2024-001&order=12345
https://myapp.com/payment/error?reference=PAY-APP-2024-001&order=12345

Deep links (applications mobiles)

Pour les applications mobiles, vous pouvez utiliser des deep links :

myapp://payment/success?reference=PAY-APP-2024-001
myapp://payment/error?reference=PAY-APP-2024-001

Important : Assurez-vous que votre application est configurée pour gérer ces deep links.

Ne vous fiez pas au retour du client

  1. URLs sécurisées : Utilisez toujours HTTPS pour les URLs de callback
  2. Paramètres de requête : Incluez des paramètres pour identifier la transaction (référence, orderId, etc.)
  3. Vérification du statut : Toujours vérifier le statut du paiement via l'API après le callback
  4. Gestion des erreurs : Prévoyez un traitement pour chaque cas d'échec documenté
  5. Webhooks : Utilisez les webhooks comme source de vérité principale, les callbacks comme fallback
  6. Expérience utilisateur : Affichez des messages clairs sur les pages de succès et d'erreur
  7. Timeouts : Gérez les cas où le client ne revient pas de l'application de paiement

Laisser le client choisir son moyen de paiement

Permettez à l'utilisateur de choisir sa méthode de paiement avant de créer la demande :

function PaymentMethodSelector({ orderId, amount }) {
  const [selectedMethod, setSelectedMethod] = useState('wave');

  const methods = ['wave', 'orange', 'mtn', 'moov', 'djamo', 'Kelyko'];

  const handlePayment = async () => {
    await initiatePayment(orderId, amount, selectedMethod);
  };

  return (
    <div>
      <h3>Sélectionnez votre méthode de paiement</h3>
      {methods.map(method => (
        <button
          key={method}
          onClick={() => setSelectedMethod(method)}
          className={selectedMethod === method ? 'selected' : ''}
        >
          {method.toUpperCase()}
        </button>
      ))}
      <button onClick={handlePayment}>Payer</button>
    </div>
  );
}

Si le client ne revient pas

Vérifiez le statut vous-même plutôt que d'attendre indéfiniment :

function PaymentPage({ orderId, amount }) {
  const [paymentStatus, setPaymentStatus] = useState('pending');

  useEffect(() => {
    // Créer la demande de paiement
    initiatePayment(orderId, amount).then(paymentRequest => {
      // Rediriger le client
      window.location.href = paymentRequest.redirectUrl;

      // Vérifier le statut toutes les 5 secondes (timeout après 5 minutes)
      const interval = setInterval(async () => {
        const status = await checkPaymentStatus(paymentRequest.id);
        setPaymentStatus(status);

        if (status !== 'pending') {
          clearInterval(interval);
        }
      }, 5000);

      // Timeout après 5 minutes
      setTimeout(() => {
        clearInterval(interval);
        if (paymentStatus === 'pending') {
          // Gérer le timeout
          handlePaymentTimeout(orderId);
        }
      }, 300000);
    });
  }, [orderId, amount]);

  return <div>Redirection en cours...</div>;
}

Et ensuite