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é.
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.
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/ |
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.
Configurer le client
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/")
const axios = require('axios'); const client = axios.create({ baseURL: 'https://integralpay.com/api', headers: { 'X-Api-Key': process.env.INTEGRALPAY_API_KEY }, });
$client = new GuzzleHttp\Client([ 'base_uri' => 'https://integralpay.com/api', 'headers' => ['X-Api-Key' => getenv('INTEGRALPAY_API_KEY')], ]);
curl https://integralpay.com/api/merchants/ \
-H "X-Api-Key: sk_live_..."
Générer une clé API
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"]
curl -X POST .../api/api-keys/generate/UUID/ \ -H "X-Api-Key: sk_live_..." \ -d '{"name":"Clé production"}'
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
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()
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; }
Démarrage rapide
Effectuez votre premier paiement de test en 5 étapes.
POST /api/merchants/ ou depuis le dashboard admin.POST /api/api-keys/generate/{merchant_id}/. Copiez
raw_key immédiatement.
INTEGRALPAY_API_KEY=sk_live_... — ne jamais commiter dans git./api/doc/ — cliquez Authorize, entrez votre clé, testez./api/doc/ (Swagger) et /api/redoc/ (Redoc).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.
Comment ça marche
POST /api/payment-links/ ou depuis le dashboard (sans code). Vous
obtenez une payment_url.Paramètres de création
| 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
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)
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);
$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'];
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
{
"id": "3f2d1a4b-...",
"token": "Yd8PxPQabcdef...",
"payment_url": "https://integralpay.com/pay/Yd8PxPQ.../",
"fixed_amount": "5000.00",
"currency": "XOF",
"is_active": true,
"expires_at": null
}
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.
Le parcours complet
redirect_url. Sauvegardez le token
retourné en session.payment_url. Le client remplit le
formulaire hébergé par Integral Pay.redirect_url?payment_token=xxx.
Implémentation complète
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")})
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 }); });
// 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 */ }
# 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." }
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.
Étape 1 — Créer l'endpoint webhook
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"]
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
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"})
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'}); });
$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']);
POST /api/webhook-endpoints/{id}/test/.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.
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.
<!-- 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 |
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.
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é.
| 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 |
// 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.'); } });
# 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
{
"status": "SUCCEEDED",
"already_claimed": false,
"amount": "5000.00",
"currency": "XOF",
"phone": "226065****",
"provider": "LIGDICASH_ORANGE",
"paid_at": "2026-06-20T14:30:00Z"
}
Signature HMAC-SHA256
Chaque webhook est signe avec votre secret. Payload JSON avec cles triees alphabetiquement.
Utilisez compare_digest pour eviter les timing attacks.
== — 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) |
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)
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)); }
function verify(string $s, array $p, string $r): bool { ksort($p); $exp = hash_hmac('sha256', json_encode($p), $s); return hash_equals($exp, $r); }
Idempotence
Les webhooks peuvent etre livres plusieurs fois. Utilisez X-Webhook-ID pour
dedupliquer.
@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"})
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'}); });
Endpoints principaux
Documentation complete sur /api/doc/.
Cree un lien avec redirect_url. Retourne
payment_url et token.
Retourne completed, failed ou
pending.
| Champ | Type | Description |
|---|---|---|
| url | string | URL HTTPS de votre endpoint |
| event_types | array | ["payment.updated"] |
raw_key retournee une seule fois. Sauvegardez-la
immediatement.
Rejoue manuellement une livraison echouee. Max 5 tentatives.
Payloads webhook
Structure des donnees envoyees lors d'un evenement payment.updated.
{
"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 |
/api/doc/ — bouton Authorize pour entrer votre cle API.