Integral Pay API - Documentation d'intégration avec exemples Python, JavaScript, PHP et cURL

Integral Pay API
Documentation d'intégration

API Integral Pay — Paiement Mobile

Intégrez Orange Money et Moov Money dans votre application. Trois flux adaptés à tous les cas d'usage, de la boutique no-code au système entièrement automatisé.

REST JSON Orange Money Moov Money Webhooks HMAC OAS 3.0
01 — Informations générales

Présentation de l'API

L'API Integral Pay permet d'accepter des paiements mobiles via Ligdicash. Toutes les requêtes retournent du JSON. Authentification via le header X-Api-Key.

i
URL de base : https://integralpay.com/api/
Développement : http://127.0.0.1:8000/api/
Caractéristique Valeur
Format JSON · Content-Type: application/json
Authentification Header X-Api-Key
Encodage UTF-8
HTTPS Obligatoire en production
Timezone UTC · format ISO 8601
Spec OpenAPI /api/schema/ · Swagger sur /api/doc/
02 — Authentification

Clés API

Chaque requête doit inclure votre clé API dans le header X-Api-Key. La clé en clair n'est affichée qu'une seule fois à la création.

!
Ne jamais exposer votre clé côté client. Utilisez toujours un backend serveur.

Configurer le client

client.py
import requests, os

API_BASE = "https://integralpay.com/api"
session  = requests.Session()
session.headers.update({
    "X-Api-Key":    os.environ["INTEGRALPAY_API_KEY"],
    "Content-Type": "application/json",
})

# Test de connexion
resp = session.get(f"{API_BASE}/merchants/")
client.js
const axios = require('axios');
const client = axios.create({
  baseURL: 'https://integralpay.com/api',
  headers: { 'X-Api-Key': process.env.INTEGRALPAY_API_KEY },
});
client.php
$client = new GuzzleHttp\Client([
    'base_uri' => 'https://integralpay.com/api',
    'headers'  => ['X-Api-Key' => getenv('INTEGRALPAY_API_KEY')],
]);
terminal
curl https://integralpay.com/api/merchants/ \
  -H "X-Api-Key: sk_live_..."

Générer une clé API

generate_key.py
resp = session.post(
    f"{API_BASE}/api-keys/generate/{MERCHANT_ID}/",
    json={"name": "Clé production"}
)
# Sauvegardez raw_key — affichée une seule fois
RAW_KEY = resp.json()["raw_key"]
terminal
curl -X POST .../api/api-keys/generate/UUID/ \
  -H "X-Api-Key: sk_live_..." \
  -d '{"name":"Clé production"}'
03 — Erreurs

Gestion des erreurs

Codes HTTP standard. En cas d'erreur, le corps contient un objet JSON avec le détail.

Code Signification Action
200 Succès Traiter la réponse
201 Ressource créée Utiliser l'ID retourné
400 Données invalides Lire error
401 Non authentifié Vérifier la clé API
402 Paiement échoué Afficher message au client
403 Accès refusé Vérifier les permissions
404 Introuvable Vérifier l'ID / token
503 Erreur temporaire Réessayer

Wrapper d'erreurs

errors.py
class Integral PayError(Exception):
    def __init__(self, status, message):
        self.status  = status
        self.message = message

class PaymentFailed(Integral PayError):
    def __init__(self, msg):
        super().__init__(402, msg)

def api(method, endpoint, **kw):
    resp = session.request(method, f"{API_BASE}{endpoint}", **kw)
    if resp.status_code == 402:
        raise PaymentFailed(resp.json().get("message"))
    if resp.status_code >= 400:
        raise Integral PayError(resp.status_code, resp.json().get("error"))
    return resp.json()
errors.js
async function api(method, endpoint, body) {
  const r = await fetch(`${API_BASE}${endpoint}`, {
    method, body: body ? JSON.stringify(body) : undefined,
    headers: { 'X-Api-Key': API_KEY },
  });
  const d = await r.json();
  if (!r.ok) throw Object.assign(new Error(d.message || d.error), { status: r.status });
  return d;
}
04 — Démarrage

Démarrage rapide

Effectuez votre premier paiement de test en 5 étapes.

1
Créer un compte merchant
Via POST /api/merchants/ ou depuis le dashboard admin.
2
Générer une clé API
Via POST /api/api-keys/generate/{merchant_id}/. Copiez raw_key immédiatement.
3
Définir les variables d'environnement
INTEGRALPAY_API_KEY=sk_live_... — ne jamais commiter dans git.
4
Choisir le flux d'intégration
Flux 1 (no-code), Flux 2 (redirection), ou Flux 3 (webhooks push).
5
Tester via Swagger UI
Explorez /api/doc/ — cliquez Authorize, entrez votre clé, testez.
ok
Documentation complète sur /api/doc/ (Swagger) et /api/redoc/ (Redoc).
05 — Flux 1

Lien de paiement no-code

Créez un lien de paiement, partagez-le par SMS, WhatsApp ou email. Le client paie via le formulaire hébergé par Integral Pay — aucune intégration technique côté client. C'est le flux le plus simple : il ne nécessite aucun serveur.

+
Cas d'usage : collectes de fonds, factures ponctuelles, vente sur réseaux sociaux, dons, cotisations associatives.

Comment ça marche

1
Créer le lien
Via POST /api/payment-links/ ou depuis le dashboard (sans code). Vous obtenez une payment_url.
2
Partager l'URL
Envoyez le lien à votre client par le canal de votre choix. Le lien reste valide jusqu'à expiration.
3
Le client paie
Il choisit Orange ou Moov Money, saisit son numéro et valide. Integral Pay gère tout le reste.

Paramètres de création

POST/api/payment-links/Crée un lien▶
Paramètre Type Requis Description
title string optionnel Titre affiché sur la page de paiement
description string optionnel Description sous le titre
is_amount_fixed boolean requis true = montant fixe, false = le client saisit le montant
fixed_amount decimal si fixe Montant en XOF (minimum 100)
currency string optionnel Défaut "XOF"
provider uuid requis UUID du provider (Orange ou Moov)
redirect_url string optionnel URL de retour après paiement
expires_at datetime optionnel ISO 8601 · null = permanent
metadata object optionnel Données custom. Ex: {"order_id":"1234"}

Créer un lien

flux1.py
resp = session.post(f"{API_BASE}/payment-links/", json={
    "title":          "Abonnement mensuel",
    "is_amount_fixed": True,
    "fixed_amount":   "5000",
    "currency":       "XOF",
    "provider":       "uuid-provider-orange",
    "metadata":       {"order_id": "1234"},
})
resp.raise_for_status()
data = resp.json()
payment_url = data["payment_url"]
print("Partagez ce lien :", payment_url)
flux1.js
const { data } = await client.post('/payment-links/', {
  title:          'Abonnement mensuel',
  is_amount_fixed: true,
  fixed_amount:   '5000',
  currency:       'XOF',
  provider:       'uuid-provider-orange',
  metadata:       { order_id: '1234' },
});
console.log('Partagez ce lien :', data.payment_url);
flux1.php
$resp = $client->post('/payment-links/', ['json' => [
    'title'           => 'Abonnement mensuel',
    'is_amount_fixed' => true,
    'fixed_amount'    => '5000',
    'currency'        => 'XOF',
    'provider'        => 'uuid-provider-orange',
]]);
$data = json_decode($resp->getBody(), true);
echo 'Lien : ' . $data['payment_url'];
terminal
curl -X POST .../api/payment-links/ \
  -H "X-Api-Key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"title":"Abonnement","is_amount_fixed":true,
     "fixed_amount":"5000","currency":"XOF","provider":"uuid"}'

Réponse · 201 Created

response.json
{
  "id":           "3f2d1a4b-...",
  "token":        "Yd8PxPQabcdef...",
  "payment_url":  "https://integralpay.com/pay/Yd8PxPQ.../",
  "fixed_amount": "5000.00",
  "currency":     "XOF",
  "is_active":    true,
  "expires_at":   null
}
i
Pour vérifier le paiement ensuite, consultez le Flux 2 (vérification de statut) ou le Flux 3 (webhooks). Le lien no-code seul ne notifie pas automatiquement votre système.
06 — Flux 2

Redirection + vérification statut

Votre serveur crée un lien, redirige le client vers Integral Pay, puis vérifie le résultat à son retour. C'est le flux recommandé pour l'e-commerce et les applications avec backend.

i
Cas d'usage : boutiques en ligne, SaaS, applications web où vous contrôlez le serveur et devez valider une commande.

Le parcours complet

1
POST /api/payment-links/init/
Créez le lien avec une redirect_url. Sauvegardez le token retourné en session.
2
Rediriger le client
Redirigez le navigateur vers payment_url. Le client remplit le formulaire hébergé par Integral Pay.
3
Retour sur votre site
Après paiement, Integral Pay redirige vers redirect_url?payment_token=xxx.
4
GET /api/pay/{token}/result/
Vérifiez le statut côté serveur et validez la commande. Ne validez jamais sur la seule redirection.
!
La redirection seule ne prouve pas le paiement — un utilisateur peut forger l'URL de retour. Vérifiez toujours le statut via l'API avant de valider une commande.

Implémentation complète

flux2.py · Django
from django.shortcuts import redirect, render

def checkout(request):
    resp = session.post(f"{API_BASE}/payment-links/init/", json={
        "title":           "Commande #1234",
        "is_amount_fixed": True,
        "fixed_amount":    str(request.session['montant']),
        "currency":        "XOF",
        "provider":        "uuid-provider",
        "redirect_url":    "https://ma-boutique.com/retour/",
        "metadata":        {"order_id": "1234"},
    })
    data = resp.json()
    request.session["payment_token"] = data["token"]
    return redirect(data["payment_url"])

def retour(request):
    token = request.GET.get("payment_token")
    data  = session.get(f"{API_BASE}/pay/{token}/result/").json()

    if data["status"] == "completed":
        # Vérifiez le montant pour éviter la fraude
        if data["amount"] == str(request.session['montant']):
            valider_commande(data["reference"])
            return render(request, "success.html", data)
    return render(request, "echec.html", {"msg": data.get("message")})
flux2.js · Express
app.post('/checkout', async (req, res) => {
  const { data } = await client.post('/payment-links/init/', {
    title: 'Commande #1234', is_amount_fixed: true,
    fixed_amount: String(req.session.montant),
    currency: 'XOF', provider: 'uuid',
    redirect_url: 'https://ma-boutique.com/retour/',
  });
  req.session.payment_token = data.token;
  res.redirect(data.payment_url);
});

app.get('/retour', async (req, res) => {
  const { data } = await client.get(`/pay/${req.query.payment_token}/result/`);
  if (data.status === 'completed') {
    validerCommande(data.reference);
    return res.render('success', data);
  }
  res.render('echec', { msg: data.message });
});
flux2.php
// 1. Création + redirection
$r = $client->post('/payment-links/init/', ['json' => [
    'title'           => 'Commande #1234',
    'is_amount_fixed' => true,
    'fixed_amount'    => '5000',
    'currency'        => 'XOF',
    'provider'        => 'uuid',
    'redirect_url'    => 'https://ma-boutique.com/retour/',
]]);
$d = json_decode($r->getBody(), true);
$_SESSION['payment_token'] = $d['token'];
header('Location: ' . $d['payment_url']); exit();

// 2. Au retour (retour.php)
$token = $_GET['payment_token'];
$res = json_decode($client->get("/pay/$token/result/")->getBody(), true);
if ($res['status'] === 'completed') { /* valider commande */ }
terminal
# 1. Créer le lien avec redirect_url
curl -X POST .../api/payment-links/init/ \
  -H "X-Api-Key: sk_live_..." \
  -d '{"title":"Commande","is_amount_fixed":true,
     "fixed_amount":"5000","currency":"XOF","provider":"uuid",
     "redirect_url":"https://ma-boutique.com/retour/"}'

# 2. Vérifier le statut au retour
curl .../api/pay/TOKEN/result/ -H "X-Api-Key: sk_live_..."

Réponse GET /pay/{token}/result/

{
  "status":     "completed",
  "payment_id": "a3f7...",
  "amount":     "5000.00",
  "phone":      "07XXXXXXXX",
  "reference":  "bd7b...",
  "paid_at":    "2026-05-27T14:30:00Z"
}
{ "status": "failed", "message": "Solde insuffisant." }
{ "status": "pending", "message": "En attente de l'operateur." }
07 — Flux 3

Webhooks — Notifications push

Integral Pay appelle automatiquement votre URL dès qu'un paiement est finalisé. C'est la méthode la plus robuste : pas de polling, notification en temps réel, payload signé cryptographiquement.

!
Vérifiez toujours la signature HMAC. Retournez toujours HTTP 200 si reçu — sinon retry automatique avec backoff : 5 min, 10 min, 20 min, 40 min, 80 min (5 tentatives max).

Étape 1 — Créer l'endpoint webhook

create_webhook.py
resp = session.post(f"{API_BASE}/webhook-endpoints/", json={
    "url":         "https://ma-boutique.com/webhooks/",
    "event_types": ["payment.updated"],
})
# Le secret n'est affiché qu'une seule fois — sauvegardez-le
WEBHOOK_SECRET = resp.json()["secret"]
terminal
curl -X POST .../api/webhook-endpoints/ \
  -H "X-Api-Key: sk_live_..." \
  -d '{"url":"https://...","event_types":["payment.updated"]}'

Étape 2 — Recevoir et vérifier

webhook.py · Django
import hmac, hashlib, json
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from django.http import JsonResponse

def _verify(request, payload):
    sig  = request.headers.get("X-Signature-SHA256", "")
    body = json.dumps(payload, separators=(",",":"), sort_keys=True)
    exp  = hmac.new(WEBHOOK_SECRET.encode(), body.encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(sig, exp)

@csrf_exempt
@require_POST
def webhook(request):
    data = json.loads(request.body)
    if not _verify(request, data):
        return JsonResponse({"error": "Signature invalide"}, status=401)
    if data["status"] == "succeeded":
        valider_commande(data["reference"])
    return JsonResponse({"message": "OK"})
webhook.js · Express
const crypto = require('crypto');
app.post('/webhooks', express.json(), (req, res) => {
  const sig  = req.headers['x-signature-sha256'];
  const body = JSON.stringify(Object.fromEntries(Object.entries(req.body).sort()));
  const exp  = crypto.createHmac('sha256', process.env.WEBHOOK_SECRET).update(body).digest('hex');
  if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(exp)))
    return res.status(401).json({error: 'Signature invalide'});
  if (req.body.status === 'succeeded') validerCommande(req.body.reference);
  res.json({message: 'OK'});
});
webhook.php
$p    = json_decode(file_get_contents('php://input'), true);
$recv = $_SERVER['HTTP_X_SIGNATURE_SHA256'] ?? '';
ksort($p);
$exp = hash_hmac('sha256', json_encode($p), getenv('WEBHOOK_SECRET'));
if (!hash_equals($exp, $recv)) { http_response_code(401); die(); }
if ($p['status'] === 'succeeded') { /* valider commande */ }
echo json_encode(['message' => 'OK']);
i
Voir les sections Signature HMAC et Idempotence pour sécuriser complètement votre endpoint. Testez via POST /api/webhook-endpoints/{id}/test/.
08 — Low-code

Bouton de paiement embarquable

Ajoutez un bouton de paiement sur n'importe quel site avec deux lignes de HTML. Au clic, une fenêtre de paiement sécurisée s'ouvre sans quitter votre page. Idéal pour les sites vitrines, WordPress, ou toute page web — sans backend obligatoire.

+
Cas d'usage : dons, produits à prix unique, pages de vente, sites statiques, intégration WordPress/Wix sans plugin.

Intégration — 2 lignes de HTML

Collez le SDK puis ajoutez un bouton avec vos attributs. Le SDK détecte automatiquement votre domaine Integral Pay.

index.html
<!-- 1. Charger le SDK Integral Pay -->
<script src="https://api.integralpay.com/static/js/integralpay-button.js"></script>

<!-- 2. Ajouter le bouton -->
<button class="integralpay-btn"
        data-token="VOTRE_TOKEN"
        data-amount="5000"
        data-currency="XOF"
        data-label="Payer 5000 XOF"
        data-success-url="https://mon-site.com/merci"></button>

Attributs disponibles

Attribut Requis Description
data-token requis Token du lien de paiement créé dans votre dashboard
data-amount optionnel Montant en XOF. Requis si le lien a un montant variable
data-currency optionnel Devise. Défaut "XOF"
data-label optionnel Texte affiché sur le bouton
data-success-url optionnel URL de redirection après paiement réussi
data-cancel-url optionnel URL de redirection si le client annule
!
Le bouton doit être écrit sur une seule ligne dans le HTML si vous utilisez data-label — sinon le texte n'est pas appliqué.

Écouter la confirmation côté client

Le SDK déclenche un événement integralpay:success sur le bouton après paiement. Utile pour un feedback visuel — mais ne validez jamais une commande sur cet événement seul.

listener.js
document.querySelector('.integralpay-btn').addEventListener(
  'integralpay:success',
  function (e) {
    console.log('Paiement reçu :', e.detail.amount);
    // Feedback visuel uniquement — la vraie validation
    // passe par le webhook ou /pay/verify/ côté serveur
  }
);

Vérification sans backend

Pour les vendeurs sans serveur, confirmez un paiement depuis une page statique via l'endpoint public /pay/verify/. Données non sensibles uniquement, rate-limité.

GET/pay/verify/Vérifier un paiement▶
Paramètre Description
token Le payment_token reçu dans l'URL de redirection
claim 1 = consomme le token (anti-double-validation). Absent = simple consultation
verify.js
// Sur la page de remerciement (page statique du vendeur)
const token = new URLSearchParams(location.search).get('payment_token');

// claim=1 pour valider une seule fois (anti-rejeu)
fetch(`https://api.integralpay.com/pay/verify/?token=${token}&claim=1`)
  .then(r => r.json())
  .then(data => {
    if (data.status !== 'SUCCEEDED') return;
    if (data.already_claimed) {
      afficher('Commande déjà confirmée.');
    } else {
      afficher('Merci ! Paiement de ' + data.amount + ' XOF.');
    }
  });
terminal
# Consultation (lecture seule)
curl "https://api.integralpay.com/pay/verify/?token=TOKEN"

# Consommation (anti-double-validation)
curl "https://api.integralpay.com/pay/verify/?token=TOKEN&claim=1"

Réponse · 200 OK

response.json
{
  "status":          "SUCCEEDED",
  "already_claimed": false,
  "amount":          "5000.00",
  "currency":        "XOF",
  "phone":           "226065****",
  "provider":        "LIGDICASH_ORANGE",
  "paid_at":         "2026-06-20T14:30:00Z"
}
!
Limite de sécurité : la vérification confirme qu'un paiement existe, mais ne le lie pas à une commande précise. Pour les paniers variables (e-commerce), utilisez le webhook (Flux 3) qui transmet montant, référence et métadonnées.
09 — Sécurité

Signature HMAC-SHA256

Chaque webhook est signe avec votre secret. Payload JSON avec cles triees alphabetiquement. Utilisez compare_digest pour eviter les timing attacks.

!
Ne jamais comparer les signatures avec == — utilisez hmac.compare_digest().
Header Description
X-Webhook-ID UUID unique — utilisez pour la deduplication
X-Webhook-Event Type d'evenement ex: payment.updated
X-Signature-SHA256 HMAC-SHA256 du body (cles triees, pas d'espaces)
verify.py
import hmac, hashlib, json

def verify(secret: str, payload: dict, received: str) -> bool:
    body = json.dumps(payload, separators=(",",":"), sort_keys=True)
    exp  = hmac.new(
        key=secret.encode("utf-8"),
        msg=body.encode("utf-8"),
        digestmod=hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(received, exp)
verify.js
function verify(secret, payload, received) {
  const body = JSON.stringify(Object.fromEntries(Object.entries(payload).sort()));
  const exp  = crypto.createHmac('sha256', secret).update(body).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(received), Buffer.from(exp));
}
verify.php
function verify(string $s, array $p, string $r): bool {
    ksort($p);
    $exp = hash_hmac('sha256', json_encode($p), $s);
    return hash_equals($exp, $r);
}
10 — Sécurité

Idempotence

Les webhooks peuvent etre livres plusieurs fois. Utilisez X-Webhook-ID pour dedupliquer.

i
Retry automatique avec backoff : 5 min - 10 min - 20 min - 40 min - 80 min (5 tentatives max).
idempotence.py
@csrf_exempt
@require_POST
def webhook(request):
    data       = json.loads(request.body)
    webhook_id = request.headers.get("X-Webhook-ID")

    if WebhookReceived.objects.filter(webhook_id=webhook_id).exists():
        return JsonResponse({"message": "Deja traite"})

    WebhookReceived.objects.create(webhook_id=webhook_id)
    if data["status"] == "succeeded":
        valider_commande(data["reference"])
    return JsonResponse({"message": "OK"})
idempotence.js
const processed = new Set(); // En production : Redis
app.post('/webhooks', (req, res) => {
  const id = req.headers['x-webhook-id'];
  if (processed.has(id)) return res.json({message: 'Deja traite'});
  processed.add(id);
  res.json({message: 'OK'});
});
11 — Référence

Endpoints principaux

Documentation complete sur /api/doc/.

POST/api/payment-links/init/Flux 2 — creer un lien▶

Cree un lien avec redirect_url. Retourne payment_url et token.

GET/api/pay/{token}/result/Verifier le statut▶

Retourne completed, failed ou pending.

POST/api/webhook-endpoints/Creer un endpoint webhook▶
Champ Type Description
url string URL HTTPS de votre endpoint
event_types array ["payment.updated"]
POST/api/api-keys/generate/{merchant_id}/Generer une cle API▶

raw_key retournee une seule fois. Sauvegardez-la immediatement.

POST/api/webhook-deliveries/{id}/retry/Rejouer une livraison▶

Rejoue manuellement une livraison echouee. Max 5 tentatives.

12 — Payloads

Payloads webhook

Structure des donnees envoyees lors d'un evenement payment.updated.

payload.json
{
  "event":      "payment.updated",
  "payment_id": "a3f7c2d1-...",
  "amount":     "5000",
  "currency":   "XOF",
  "status":     "succeeded",
  "phone":      "07XXXXXXXX",
  "provider":   "LIGDICASH_ORANGE",
  "reference":  "bd7bd362-...",
  "created_at": "2026-05-27T14:30:00Z"
}
Champ Type Description
status string succeeded ou canceled
reference string ID CheckoutSession — votre reference commande
payment_id uuid ID unique du paiement Integral Pay
phone string Numero ayant effectue le paiement
provider string LIGDICASH_ORANGE ou LIGDICASH_MOOV
+
Testez sur /api/doc/ — bouton Authorize pour entrer votre cle API.