Documentation développeur
Intègre les paiements Mobile Money et carte de 15+ pays africains avec une seule API. Guides, référence complète et SDKs.
Introduction à l'API Cartflox
Cartflox est une API de paiement panafricaine qui vous permet d'accepter des paiements Mobile Money (Orange Money, Wave, MTN MoMo, Moov) et carte bancaire, ainsi que d'effectuer des transferts d'argent dans plus de 15 pays africains — avec une seule intégration.
Fonctionnalités principales
Paiements Mobile Money
Orange Money, Wave, MTN MoMo, Moov, Airtel, Free Money…
Paiements par carte
Visa, Mastercard via notre passerelle sécurisée PCI DSS
Smart Routing
5 algorithmes (Priority, Volume Split, DSL, Dynamic) + fallback automatique
Transferts d'argent
Envoi de fonds vers des comptes Mobile Money et bancaires
Webhooks temps réel
Notifications instantanées sur l'état de vos transactions
Personnalisation
Page de paiement entièrement brandée à votre identité
SDKs multi-langages
Node.js, Python, PHP, Flutter, React Native
Audit log complet
Traçabilité de chaque décision de routage pour debug en prod
Base URL
Toutes les requêtes API doivent être effectuées en HTTPS :
# Sandbox (tests) https://sandbox.api.cartflox.com/v2 # Production https://api.cartflox.com/v2
Format des réponses
Toutes les réponses sont au format JSON avec la structure suivante :
{
"success": true,
"data": { ... },
"meta": {
"requestId": "req_01HXYZ...",
"timestamp": "2026-03-01T12:00:00Z"
}
}
// En cas d'erreur :
{
"success": false,
"error": {
"code": "INSUFFICIENT_FUNDS",
"message": "Le solde du compte est insuffisant.",
"details": {}
}
}Environnements
Sandbox
Pour les tests. Aucun vrai argent n'est débité. Utilisez les numéros de test fournis.
Production
Pour les vrais paiements. Nécessite la validation KYB de votre compte.
Démarrage rapide
Intégrez Cartflox et effectuez votre premier paiement en moins de 5 minutes.
1. Créer votre compte
Rendez-vous sur dashboard.cartflox.com pour créer un compte. Après inscription, vous avez immédiatement accès à l'environnement sandbox.
2. Obtenir vos clés API
Dans votre dashboard → Paramètres → API, copiez votre clé secrète.
CARTFLOX_SECRET_KEY=cf_live_sec_xxxxxxxxxxxxxxxxxxxxxxxx CARTFLOX_PUBLIC_KEY=cf_live_pub_xxxxxxxxxxxxxxxxxxxxxxxx CARTFLOX_BASE_URL=https://cartflox.com
3. Créer votre premier paiement
Appelez directement l'API REST avec votre clé secrète :
const response = await fetch(
`${process.env.CARTFLOX_BASE_URL}/api/v1/checkout/sessions`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.CARTFLOX_SECRET_KEY}`,
},
body: JSON.stringify({
amount: 5000,
currency: "XOF",
customer_name: "Kouassi Jean",
customer_email: "client@example.com",
customer_phone: "+22507070707",
description: "Commande #order_123",
success_url: "https://monsite.com/paiement/succes",
cancel_url: "https://monsite.com/paiement/annule",
metadata: { orderId: "order_123" },
}),
}
);
const session = await response.json();
// Rediriger le client vers la page de paiement
console.log(session.url);
// → https://cartflox.com/checkout/cmmtlfj27...+22500000001, ou refusé avec +22500000002.4. Configurer le webhook
Enregistrez votre URL de réception pour être notifié des paiements :
curl -X PATCH https://cartflox.com/api/v1/config/webhook \
-H "Authorization: Bearer cf_live_sec_xxxx" \
-H "Content-Type: application/json" \
-d '{ "webhookUrl": "https://monsite.com/api/webhooks/cartflox" }'5. Écouter les webhooks
Cartflox vous notifie en temps réel du statut de chaque paiement.
import { NextRequest, NextResponse } from "next/server";
export async function POST(req: NextRequest) {
const { event, data } = await req.json();
if (event === "payment.completed") {
// ✅ Mettre à jour votre base de données
await db.orders.update({
where: { cartfloxId: data.id },
data: { status: "paid" }
});
}
return NextResponse.json({ received: true });
}6. Vérifier le paiement (optionnel)
const res = await fetch(
`${process.env.CARTFLOX_BASE_URL}/api/v1/checkout/sessions/${sessionId}`,
{ headers: { "Authorization": `Bearer ${process.env.CARTFLOX_SECRET_KEY}` } }
);
const session = await res.json();
if (session.status === "SUCCESS") {
console.log("Paiement reçu :", session.amount, session.currency);
}Numéros de test (Sandbox)
| Numéro | Résultat | Description |
|---|---|---|
| +22500000001 | ✅ Succès | Paiement approuvé immédiatement |
| +22500000002 | ❌ Échec | Fonds insuffisants |
| +22500000003 | ⏳ En attente | Paiement en attente de confirmation (timeout après 30s) |
| +22500000004 | 🚫 Refus | Numéro non enregistré auprès de l'opérateur |
Authentification & Clés API
Cartflox utilise des clés API pour authentifier les requêtes. Vous pouvez gérer vos clés API depuis votre dashboard → Paramètres → API.
Types de clés
Clé secrète
cf_live_sec_... / cf_live_pub_...Utilisée côté serveur uniquement. Ne jamais exposer dans le frontend. Donne accès à toutes les opérations.
Clé publique
cf_live_sec_... / cf_live_pub_...Peut être utilisée dans le frontend (initialiser le SDK JS, identifier votre compte). Ne peut pas créer de paiements directement.
Utilisation
Incluez votre clé secrète dans le header Authorization de chaque requête :
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx
Avec curl
curl https://cartflox.com/api/v1/checkout/sessions \ -H "Authorization: Bearer cf_live_sec_xxxxxxxxxxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json"
Avec le SDK Node.js
import Cartflox from "@cartflox/node"; const cartflox = new Cartflox(process.env.CARTFLOX_SECRET_KEY!); // Le SDK gère automatiquement l'authentification
Environnements
Chaque clé est associée à un environnement :
| Préfixe | Environnement | Base URL |
|---|---|---|
| af_sandbox_sec_ / af_sandbox_pub_ | Sandbox | https://cartflox.com |
| cf_live_sec_ / cf_live_pub_ | Production | https://cartflox.com |
Rotation des clés
Il est recommandé de faire tourner vos clés API tous les 90 jours. Pour ce faire :
- Créez une nouvelle clé dans le dashboard
- Mettez à jour vos variables d'environnement en production
- Attendez que votre déploiement soit actif
- Révoquez l'ancienne clé dans le dashboard
IP Allowlist (optionnel)
Pour une sécurité renforcée, vous pouvez restreindre les requêtes API à une liste d'adresses IP dans Paramètres → Sécurité → IP Allowlist.
Créer un paiement
Initialisez une session de paiement et redirigez votre client vers la page de paiement Cartflox.
/api/v1/checkout/sessionsCréer une nouvelle session de paiement
Paramètres
| Paramètre | Type | Requis | Description |
|---|---|---|---|
amount | integer | Oui | Montant entier (ex: 5000 XOF) |
currency | string | Non | Code ISO 4217 : XOF, XAF, GHS, KES… (défaut : XOF) |
customer_name | string | Non | Nom complet du client |
customer_email | string | Non | Email du client |
customer_phone | string | Non | Téléphone du client au format international |
description | string | Non | Description affichée sur la page de paiement |
success_url | string | Non | URL de redirection après paiement réussi |
cancel_url | string | Non | URL de redirection si le client annule |
metadata | object | Non | Données personnalisées retournées dans les webhooks |
Objet customer
| Paramètre | Type | Requis | Description |
|---|---|---|---|
phone | string | Oui | Numéro de téléphone au format international (+22507…) |
email | string | Non | Adresse email du client |
name | string | Non | Nom complet du client |
country | string | Non | Code pays ISO 3166-1 alpha-2 (ex: CI, SN, BJ…) |
Exemples
Node.js
const payment = await cartflox.payments.create({
amount: 25000,
currency: "XOF",
method: "wave",
customer: {
phone: "+22507070707",
email: "client@example.com",
name: "Kouassi Jean"
},
description: "Abonnement Premium - Janvier 2026",
redirectUrl: "https://monsite.com/success",
cancelUrl: "https://monsite.com/cancel",
metadata: {
orderId: "ORD-2026-001",
userId: "usr_123"
}
});
// Rediriger le client
res.redirect(payment.checkoutUrl);cURL
curl -X POST https://cartflox.com/api/v1/checkout/sessions \
-H "Authorization: Bearer cf_live_sec_xxxx" \
-H "Content-Type: application/json" \
-d '{
"amount": 25000,
"currency": "XOF",
"customer_name": "Kouassi Jean",
"customer_email": "client@example.com",
"customer_phone": "+22507070707",
"description": "Abonnement Premium",
"success_url": "https://monsite.com/success",
"cancel_url": "https://monsite.com/cancel"
}'Réponse
{
"id": "cmmtlfj27000004l4nuzy1zz5",
"object": "checkout.session",
"url": "https://cartflox.com/checkout/cmmtlfj27000004l4nuzy1zz5",
"order_id": "CS-MMTLFJ1Z-B4HEE",
"amount": 25000,
"currency": "XOF",
"status": "pending",
"created": "2026-03-01T12:00:00Z"
}Méthode "auto"
En utilisant method: "auto", Cartflox affiche automatiquement toutes les méthodes disponibles dans le pays du client et laisse ce dernier choisir.
Vérifier un paiement
Récupérez les détails et le statut actuel d'un paiement existant.
/api/v1/checkout/sessions/:idRécupérer une session de paiement par son identifiant
Statuts possibles
pendingEn attente de paiement
processingTraitement en cours
completedPaiement réussi
failedPaiement échoué
cancelledAnnulé par le client
refundedRemboursé
Exemple
Node.js
const payment = await cartflox.payments.retrieve("pi_01HXYZ789ABC");
switch (payment.status) {
case "completed":
await fulfillOrder(payment.metadata.orderId);
break;
case "failed":
await notifyCustomerOfFailure(payment.customer.email);
break;
default:
console.log("Statut:", payment.status);
}cURL
curl https://cartflox.com/api/v1/checkout/sessions/cmmtlfj27000004l4nuzy1zz5 \ -H "Authorization: Bearer cf_live_sec_xxxx"
Réponse
{
"id": "cmmtlfj27000004l4nuzy1zz5",
"object": "checkout.session",
"url": "https://cartflox.com/checkout/cmmtlfj27000004l4nuzy1zz5",
"order_id": "CS-MMTLFJ1Z-B4HEE",
"amount": 25000,
"currency": "XOF",
"status": "SUCCESS",
"created": "2026-03-01T12:00:00Z"
}Lister les paiements
Récupérez la liste paginée de tous vos paiements avec des filtres avancés.
/v2/paymentsLister les paiements avec filtres et pagination
Paramètres de requête
| Paramètre | Type | Requis | Description |
|---|---|---|---|
limit | integer | Non | Nombre de résultats par page (défaut: 20, max: 100) |
cursor | string | Non | Curseur de pagination (retourné dans la réponse précédente) |
status | string | Non | Filtrer par statut : pending | completed | failed | cancelled | refunded |
method | string | Non | Filtrer par méthode de paiement |
currency | string | Non | Filtrer par devise (ex: XOF, XAF) |
from | string | Non | Date de début ISO 8601 (ex: 2026-01-01T00:00:00Z) |
to | string | Non | Date de fin ISO 8601 |
customerId | string | Non | Filtrer par identifiant client |
Exemple
Node.js
const payments = await cartflox.payments.list({
limit: 20,
status: "completed",
currency: "XOF",
from: "2026-01-01T00:00:00Z",
to: "2026-03-01T23:59:59Z"
});
for (const payment of payments.data) {
console.log(payment.id, payment.amount, payment.status);
}
// Pagination
if (payments.hasMore) {
const nextPage = await cartflox.payments.list({
cursor: payments.nextCursor
});
}cURL
curl "https://api.cartflox.com/v2/payments?limit=20&status=completed¤cy=XOF" \ -H "Authorization: Bearer sk_live_xxxx"
Réponse
{
"success": true,
"data": [
{
"id": "pi_01HXYZ789ABC",
"status": "completed",
"amount": 25000,
"currency": "XOF",
"method": "wave",
"customer": { "phone": "+22507070707" },
"createdAt": "2026-03-01T12:00:00Z"
},
{ ... }
],
"meta": {
"total": 1543,
"count": 20,
"hasMore": true,
"nextCursor": "cursor_eyJpZCI6InBpXzAxSFhZ..."
}
}Rembourser un paiement
Remboursez un paiement complété, entièrement ou partiellement.
/v2/payments/:id/refundCréer un remboursement pour un paiement
Paramètres
| Paramètre | Type | Requis | Description |
|---|---|---|---|
amount | integer | Non | Montant à rembourser (en centimes). Si omis, remboursement total. |
reason | string | Non | Raison du remboursement : duplicate | fraudulent | requested_by_customer | other |
note | string | Non | Note interne (non visible par le client) |
metadata | object | Non | Données personnalisées |
Exemples
Remboursement total
const refund = await cartflox.payments.refund("pi_01HXYZ789ABC");
console.log(refund.status); // "processing"Remboursement partiel
const refund = await cartflox.payments.refund("pi_01HXYZ789ABC", {
amount: 10000, // Rembourser 100 XOF sur 250 XOF
reason: "requested_by_customer",
note: "Remboursement partiel suite à réclamation client #TKT-456"
});cURL
curl -X POST https://api.cartflox.com/v2/payments/pi_01HXYZ789ABC/refund \
-H "Authorization: Bearer sk_live_xxxx" \
-H "Content-Type: application/json" \
-d '{ "amount": 10000, "reason": "requested_by_customer" }'Réponse
{
"success": true,
"data": {
"id": "ref_01HABC123",
"paymentId": "pi_01HXYZ789ABC",
"status": "processing",
"amount": 10000,
"currency": "XOF",
"reason": "requested_by_customer",
"createdAt": "2026-03-01T15:30:00Z"
}
}Conditions de remboursement
Paiements complétés (status: completed)
Remboursements partiels multiples jusqu'au montant total
Paiements en attente ou échoués
Paiements de plus de 90 jours (contactez le support)
Transfert d'argent
Envoyez des fonds directement vers des comptes Mobile Money ou bancaires dans toute l'Afrique.
/v2/transfersInitier un transfert d'argent
Paramètres
| Paramètre | Type | Requis | Description |
|---|---|---|---|
amount | integer | Oui | Montant en centimes à transférer |
currency | string | Oui | Devise du transfert (XOF, XAF, GHS…) |
destination | object | Oui | Informations du destinataire (voir ci-dessous) |
description | string | Non | Description du transfert |
reference | string | Non | Référence unique pour éviter les doublons (idempotency key) |
metadata | object | Non | Données personnalisées |
Objet destination
| Paramètre | Type | Requis | Description |
|---|---|---|---|
type | string | Oui | mobile_money | bank_account |
phone | string | Non | Numéro Mobile Money (requis si type=mobile_money) |
network | string | Non | Opérateur : wave | orange-money | mtn-momo | moov | airtel |
iban | string | Non | IBAN du compte bancaire (si type=bank_account) |
name | string | Non | Nom du destinataire |
country | string | Non | Code pays ISO (ex: CI, SN) |
Exemples
Transfert Mobile Money
const transfer = await cartflox.transfers.create({
amount: 50000,
currency: "XOF",
destination: {
type: "mobile_money",
phone: "+22507070707",
network: "orange-money",
name: "Kouassi Jean",
country: "CI"
},
description: "Paiement fournisseur Mars 2026",
reference: "PAY-2026-03-001" // Unique - évite les doublons
});
console.log(transfer.status); // "processing"Transfert bancaire
const transfer = await cartflox.transfers.create({
amount: 500000,
currency: "XOF",
destination: {
type: "bank_account",
iban: "CI00XY1234567890123456789",
name: "Société XYZ SARL",
country: "CI"
},
description: "Virement salaire Mars 2026"
});Réponse
{
"success": true,
"data": {
"id": "tr_01HABC456DEF",
"status": "processing",
"amount": 50000,
"currency": "XOF",
"fee": 500,
"netAmount": 49500,
"destination": {
"type": "mobile_money",
"phone": "+22507070707",
"network": "orange-money",
"name": "Kouassi Jean"
},
"estimatedArrival": "2026-03-01T13:00:00Z",
"reference": "PAY-2026-03-001",
"createdAt": "2026-03-01T12:00:00Z"
}
}Récupérer un transfert
/v2/transfers/:idVérifier le statut d'un transfert
const transfer = await cartflox.transfers.retrieve("tr_01HABC456DEF");
console.log(transfer.status); // processing | completed | failedFrais de transfert
| Type | Frais | Min |
|---|---|---|
| Mobile Money (même pays) | 0.8% du montant | 50 XOF |
| Mobile Money (international) | 1.5% du montant | 100 XOF |
| Virement bancaire (UEMOA) | 0.5% du montant | 200 XOF |
Smart Routing — orchestration multi-passerelles
Cartflox ne se limite pas à transmettre vos paiements à une seule passerelle. Le moteur de routage choisit dynamiquement la meilleure passerelle pour chaque paiement, tente automatiquement un fallback en cas d'échec, et expose une trace complète de chaque décision.
/methods?tab=routing — aucun appel API supplémentaire n'est nécessaire. Vos clés marchand restent inchangées.Les 5 algorithmes
Choisissez la stratégie adaptée à votre usage. Vous pouvez basculer à tout moment depuis le dashboard.
SINGLE
Une seule passerelle, toujours. Aucun fallback. Le plus simple — utile si vous avez une seule intégration.
PRIORITY
Liste ordonnée. Essaie la passerelle 1, si échec → passe à la 2, etc. Recommandé pour la plupart des cas — c'est l'algo par défaut.
VOLUME_SPLIT
Répartition probabiliste pondérée (ex: 70% PayDunya, 30% PawaPay). Idéal pour A/B tester deux passerelles ou répartir la charge.
ADVANCED
Règles métier en DSL : « SI montant > 50000 ET pays = CI ALORS PayDunya ». Première règle correspondante gagne.
DYNAMIC
Auto-pilote. Le moteur classe les passerelles par taux de succès récent (EMA) et favorise la plus performante. Mise à jour automatique après chaque paiement.
Override par méthode
Indépendamment de l'algorithme choisi, vous pouvez forcer une passerelle pour une combinaisonméthode + pays spécifique. Exemple : « pour Orange Money CI, utiliser toujours PayDunya même si l'algo dit autre chose ». L'override gagne sur l'algorithme.
Configuration via API (à venir)
Pour l'instant le routage se configure exclusivement via le dashboard. Une API publique/v2/routing/config sera exposée prochainement. En attendant, voici le modèle de données qu'Cartflox stocke par application :
{
"algorithmKind": "PRIORITY",
"fallbackOrder": ["gw_paydunya_xxx", "gw_pawapay_yyy"],
"volumeSplits": [
{ "gatewayId": "gw_paydunya_xxx", "split": 70 },
{ "gatewayId": "gw_pawapay_yyy", "split": 30 }
],
"routingRules": [
{
"name": "Gros montants → PayDunya",
"conditions": [
{ "field": "amount", "operator": "gt", "value": 50000 },
{ "field": "country", "operator": "eq", "value": "CI" }
],
"gatewayIds": ["gw_paydunya_xxx"]
}
],
"methodAssignments": {
"orange_money_ci||CI": "gw_paydunya_xxx"
},
"allowedProviders": ["gw_paydunya_xxx", "gw_pawapay_yyy"],
"maxRetries": 3
}Champs des règles ADVANCED
Le DSL accepte les champs suivants :
amount — montant numérique de la transaction currency — code ISO de la devise (XOF, XAF, USD, …) country — code ISO alpha-2 (CI, SN, BJ, …) methodCode — code de la méthode (orange_money_ci, mtn_momo_ben, …) methodType — MOBILE_MONEY | CARD | BANK_TRANSFER
Opérateurs supportés :
eq, neq — égalité / inégalité gt, gte, lt, lte — comparaisons numériques (ou lexicographiques pour les strings) in — appartenance à une liste (value: ["CI","SN","ML"])
Simulateur de routage
Avant de déployer un changement de config, testez son comportement sans paiement réel. Le simulateur (/methods?tab=routing) prend en entrée un paiement type (montant, pays, méthode) et retourne :
- la passerelle qui serait choisie ;
- l'ordre complet des tentatives en cas d'échec ;
- la trace de décision (algo appliqué, override hit, filtres autorisés) ;
- le nombre maximum de tentatives.
Audit log des décisions
Chaque décision de routage est persistée pour debugging et analyse. Visible dans/methods?tab=decisions — les 50 dernières par défaut, avec :
- date · méthode · pays · montant ;
- algorithme appliqué (badge) + indicateur d'override ;
- chaîne complète des passerelles tentées ;
- passerelle finalement choisie ;
- issue :
PENDING/SUCCESS/FAILED/CANCELLED.
Bonnes pratiques
Démarrez en PRIORITY
C'est l'algo le plus prévisible. Configurez votre passerelle préférée en 1ère position et au moins une de fallback.
Validez avec le simulateur
Avant chaque modification de config en prod, simulez 3-4 paiements types pour valider l'ordre attendu.
Activez DYNAMIC après stabilisation
Une fois que vous avez du volume (>100 paiements/jour), DYNAMIC optimise automatiquement les coûts/succès.
Monitorez les décisions
Consultez l'audit log régulièrement pour repérer les patterns d'échec — ex: une passerelle qui plante toujours sur Orange Money RDC.
Réservez ADVANCED aux cas spécifiques
Les règles DSL sont puissantes mais cassent vite si la config change. Documentez chaque règle.
Configuration des Webhooks
Les webhooks permettent à Cartflox de notifier votre serveur en temps réel lorsqu'un événement se produit (paiement reçu, transfert effectué, remboursement traité…).
Configurer votre URL webhook
Enregistrez l'URL de votre endpoint via l'API ou depuis le dashboard Cartflox.
Via l'API (recommandé)
curl -X PATCH https://cartflox.com/api/v1/config/webhook \
-H "Authorization: Bearer cf_live_sec_xxxx" \
-H "Content-Type: application/json" \
-d '{ "webhookUrl": "https://monsite.com/api/webhooks/cartflox" }'Vérifier la configuration
curl https://cartflox.com/api/v1/config/webhook \ -H "Authorization: Bearer cf_live_sec_xxxx"
POST à votre URL configurée à chaque événement de paiement (succès, échec, mise à jour).Implémenter l'endpoint
Next.js (App Router)
import { NextRequest, NextResponse } from "next/server";
export async function POST(req: NextRequest) {
const body = await req.json();
// Structure du payload Cartflox
const { event, data, timestamp } = body;
switch (event) {
case "payment.completed":
// ✅ Mettre à jour votre base de données
await db.orders.update({
where: { cartfloxId: data.id },
data: { status: "paid" }
});
break;
case "payment.failed":
await db.orders.update({
where: { cartfloxId: data.id },
data: { status: "failed" }
});
break;
case "payment.updated":
console.log("Statut mis à jour:", data.status);
break;
}
return NextResponse.json({ received: true });
}Express.js
import express from "express";
import Cartflox from "@cartflox/node";
const app = express();
const cartflox = new Cartflox(process.env.CARTFLOX_SECRET_KEY!);
// Important: utiliser raw body parser pour les webhooks
app.post(
"/webhook",
express.raw({ type: "application/json" }),
(req, res) => {
const sig = req.headers["cartflox-signature"] as string;
let event;
try {
event = cartflox.webhooks.constructEvent(
req.body,
sig,
process.env.CARTFLOX_WEBHOOK_SECRET!
);
} catch (err) {
return res.status(400).send("Signature invalide");
}
// Traitement
if (event.type === "payment.completed") {
console.log("Paiement reçu:", event.data.id);
}
res.json({ received: true });
}
);Logique de retry
En cas d'échec, Cartflox retente la livraison selon le calendrier suivant :
1re retry
après 5 min
2e retry
après 30 min
3e retry
après 2 h
4e retry
après 8 h
Événements Webhook
Liste de tous les événements que vous pouvez écouter via vos endpoints webhook.
Paiements
payment.createdUn nouveau paiement a été créé
payment.processingLe paiement est en cours de traitement
payment.completedLe paiement a été complété avec succès
payment.failedLe paiement a échoué
payment.cancelledLe paiement a été annulé par le client
payment.expiredLa session de paiement a expiré
Remboursements
refund.createdUn remboursement a été initié
refund.completedLe remboursement a été traité avec succès
refund.failedLe remboursement a échoué
Transferts
transfer.createdUn transfert a été initié
transfer.completedLe transfert a été effectué avec succès
transfer.failedLe transfert a échoué
Structure d'un événement
Chaque webhook envoyé à votre endpoint a la structure suivante :
{
"event": "payment.completed",
"data": {
"id": "cmmtlfj27000004l4nuzy1zz5",
"order_id": "CS-MMTLFJ1Z-B4HEE",
"status": "SUCCESS",
"amount": 25000,
"currency": "XOF",
"provider": "paydunya",
"provider_reference": "WAVE-TXN-98765",
"customer_name": "Kouassi Jean",
"customer_email": "client@example.com",
"customer_phone": "+22507070707",
"metadata": {
"orderId": "ORD-2026-001"
},
"completed_at": "2026-03-01T12:05:33Z"
},
"timestamp": "2026-03-01T12:05:33Z"
}Gestion des doublons
Cartflox peut parfois envoyer le même événement plusieurs fois (en cas de retry). Utilisez le champ id de l'événement pour dédupliquer :
// Stocker les IDs d'événements déjà traités
const processedEvents = new Set<string>();
function handleWebhook(event: CartfloxEvent) {
if (processedEvents.has(event.id)) {
console.log("Événement déjà traité:", event.id);
return;
}
processedEvents.add(event.id);
// Traiter l'événement...
}Tester vos webhooks en local
Utilisez ngrok pour exposer votre serveur local :
# Installer ngrok et exposer votre port local
ngrok http 3000
# Puis configurer l'URL ngrok comme webhookUrl
curl -X PATCH https://cartflox.com/api/v1/config/webhook \
-H "Authorization: Bearer cf_live_sec_xxxx" \
-H "Content-Type: application/json" \
-d '{ "webhookUrl": "https://xxxx.ngrok.io/api/webhooks/cartflox" }'Sécurité des Webhooks
Cartflox signe chaque webhook avec une clé HMAC-SHA256 pour garantir que la requête provient bien d'Cartflox et n'a pas été altérée en transit.
Vérification de la signature
Chaque requête webhook contient le header cartflox-signature qui contient le timestamp et la signature HMAC.
Format du header
cartflox-signature: t=1740823233,v1=5257a869e7ecebeda32affa62cdca3fa...
Algorithme de vérification
import crypto from "crypto";
function verifyWebhookSignature(
rawBody: string,
signature: string,
secret: string
): boolean {
const parts = signature.split(",");
const timestamp = parts.find(p => p.startsWith("t="))?.split("=")[1];
const hash = parts.find(p => p.startsWith("v1="))?.split("=")[1];
if (!timestamp || !hash) return false;
// Rejeter les événements de plus de 5 minutes (replay attack)
const age = Math.abs(Date.now() / 1000 - parseInt(timestamp));
if (age > 300) return false;
// Calculer la signature attendue
const payload = `${timestamp}.${rawBody}`;
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
// Comparaison sécurisée (timing-safe)
return crypto.timingSafeEqual(
Buffer.from(hash),
Buffer.from(expected)
);
}Avec le SDK (recommandé)
const event = cartflox.webhooks.constructEvent( rawBody, // string — corps brut de la requête signature, // string — header cartflox-signature webhookSecret // string — votre secret de signature ); // Lance une erreur si la signature est invalide
req.body parsé par JSON. Vous devez utiliser le corps brut (raw body)pour la vérification de signature, sinon elle échouera toujours.Protection contre les replay attacks
Le header contient un timestamp que vous devez valider. Si l'horodatage est trop ancien (plus de 5 minutes), rejetez la requête pour prévenir les attaques par rejeu.
const MAX_AGE = 5 * 60; // 5 minutes en secondes
const timestamp = parseInt(headerParts.find(p => p.startsWith("t="))!.split("=")[1]);
if (Date.now() / 1000 - timestamp > MAX_AGE) {
throw new Error("Webhook trop ancien — possible replay attack");
}Bonnes pratiques
SDK Node.js
Le SDK officiel Cartflox pour Node.js et TypeScript. Supporte CommonJS et ESM.
Installation
npm install @cartflox/node # ou yarn add @cartflox/node # ou pnpm add @cartflox/node
Configuration
import Cartflox from "@cartflox/node";
export const cartflox = new Cartflox(process.env.CARTFLOX_SECRET_KEY!, {
// Options avancées
timeout: 30000, // Timeout en ms (défaut: 30s)
maxRetries: 3, // Nombre de retries automatiques
idempotencyKey: true, // Générer des clés d'idempotence automatiquement
});Paiements
// Créer
const payment = await cartflox.payments.create({ ... });
// Récupérer
const payment = await cartflox.payments.retrieve("pi_01HXYZ...");
// Lister
const list = await cartflox.payments.list({ status: "completed", limit: 50 });
// Rembourser
const refund = await cartflox.payments.refund("pi_01HXYZ...", { amount: 5000 });
// Annuler (si en attente)
await cartflox.payments.cancel("pi_01HXYZ...");Transferts
// Créer un transfert
const transfer = await cartflox.transfers.create({
amount: 50000,
currency: "XOF",
destination: { type: "mobile_money", phone: "+22507070707", network: "wave" }
});
// Récupérer
const transfer = await cartflox.transfers.retrieve("tr_01HABC...");
// Lister
const list = await cartflox.transfers.list({ status: "completed" });Webhooks
// Vérifier et parser l'événement
const event = cartflox.webhooks.constructEvent(rawBody, signature, secret);
// Accéder aux données
console.log(event.type); // "payment.completed"
console.log(event.data.id); // "pi_01HXYZ..."
// Types TypeScript disponibles
import type { CartfloxEvent, Payment, Transfer } from "@cartflox/node";TypeScript
Le SDK est entièrement typé. Vous bénéficiez de l'autocomplétion et de la validation des types.
import Cartflox, { Payment, CreatePaymentParams } from "@cartflox/node";
const params: CreatePaymentParams = {
amount: 5000,
currency: "XOF",
method: "wave",
customer: { phone: "+22507070707" }
};
const payment: Payment = await cartflox.payments.create(params);Gestion des erreurs
import { CartfloxError } from "@cartflox/node";
try {
const payment = await cartflox.payments.create({ ... });
} catch (err) {
if (err instanceof CartfloxError) {
console.error("Code:", err.code); // INSUFFICIENT_FUNDS
console.error("Message:", err.message); // "Solde insuffisant"
console.error("Status:", err.status); // 400
console.error("RequestId:", err.requestId);
}
}Compatibilité
SDK Python
Le SDK officiel Cartflox pour Python 3.8+. Compatible avec Django, FastAPI, Flask.
Installation
pip install cartflox-python
Configuration
import os
from cartflox import Cartflox
cartflox = Cartflox(
api_key=os.environ["CARTFLOX_SECRET_KEY"],
timeout=30,
max_retries=3
)Paiements
# Créer un paiement
payment = cartflox.payments.create(
amount=25000,
currency="XOF",
method="wave",
customer={
"phone": "+22507070707",
"email": "client@example.com",
"name": "Kouassi Jean"
},
redirect_url="https://monsite.com/success",
metadata={"order_id": "ORD-2026-001"}
)
print(payment.checkout_url) # Rediriger le client
# Récupérer
payment = cartflox.payments.retrieve("pi_01HXYZ...")
# Lister avec filtres
payments = cartflox.payments.list(
status="completed",
currency="XOF",
limit=50
)
for p in payments.data:
print(p.id, p.amount, p.status)
# Rembourser
refund = cartflox.payments.refund(
payment_id="pi_01HXYZ...",
amount=5000,
reason="requested_by_customer"
)Webhooks avec FastAPI
from fastapi import FastAPI, Request, HTTPException
from cartflox import Cartflox, WebhookVerificationError
app = FastAPI()
cartflox = Cartflox(api_key=os.environ["CARTFLOX_SECRET_KEY"])
@app.post("/webhook/cartflox")
async def handle_webhook(request: Request):
body = await request.body()
signature = request.headers.get("cartflox-signature", "")
try:
event = cartflox.webhooks.construct_event(
payload=body.decode("utf-8"),
sig_header=signature,
secret=os.environ["CARTFLOX_WEBHOOK_SECRET"]
)
except WebhookVerificationError:
raise HTTPException(status_code=400, detail="Invalid signature")
if event.type == "payment.completed":
payment = event.data
await fulfill_order(payment.metadata["order_id"])
return {"received": True}Webhooks avec Django
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from cartflox import Cartflox
cartflox = Cartflox(api_key=settings.CARTFLOX_SECRET_KEY)
@csrf_exempt
@require_POST
def cartflox_webhook(request):
try:
event = cartflox.webhooks.construct_event(
payload=request.body.decode("utf-8"),
sig_header=request.META.get("HTTP_CARTFLOX_SIGNATURE", ""),
secret=settings.CARTFLOX_WEBHOOK_SECRET
)
except Exception:
return JsonResponse({"error": "Invalid signature"}, status=400)
if event.type == "payment.completed":
Order.objects.filter(
id=event.data.metadata["order_id"]
).update(status="paid")
return JsonResponse({"received": True})Gestion des erreurs
from cartflox import Cartflox, CartfloxError
try:
payment = cartflox.payments.create(...)
except CartfloxError as e:
print(f"Code: {e.code}") # INSUFFICIENT_FUNDS
print(f"Message: {e.message}") # "Solde insuffisant"
print(f"Status: {e.http_status}") # 400
except Exception as e:
print(f"Erreur réseau: {e}")SDK PHP
Le SDK officiel Cartflox pour PHP 8.0+. Compatible avec Laravel, Symfony, WordPress.
Installation
composer require cartflox/cartflox-php
Configuration
<?php
use Cartflox\Cartflox;
$cartflox = new Cartflox(getenv('CARTFLOX_SECRET_KEY'), [
'timeout' => 30,
'max_retries' => 3,
]);Paiements
<?php
// Créer un paiement
$payment = $cartflox->payments->create([
'amount' => 25000,
'currency' => 'XOF',
'method' => 'wave',
'customer' => [
'phone' => '+22507070707',
'email' => 'client@example.com',
'name' => 'Kouassi Jean',
],
'redirect_url' => 'https://monsite.com/success',
'metadata' => ['order_id' => 'ORD-2026-001'],
]);
// Rediriger le client
header('Location: ' . $payment->checkout_url);
exit;
// Récupérer un paiement
$payment = $cartflox->payments->retrieve('pi_01HXYZ...');
// Lister
$list = $cartflox->payments->list(['status' => 'completed', 'limit' => 20]);
foreach ($list->data as $payment) {
echo $payment->id . ' - ' . $payment->amount . ' ' . $payment->currency . PHP_EOL;
}
// Rembourser
$refund = $cartflox->payments->refund('pi_01HXYZ...', [
'amount' => 5000,
'reason' => 'requested_by_customer',
]);Webhooks
<?php
use Cartflox\Cartflox;
use Cartflox\Exception\WebhookSignatureException;
$cartflox = new Cartflox(getenv('CARTFLOX_SECRET_KEY'));
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_CARTFLOX_SIGNATURE'] ?? '';
try {
$event = $cartflox->webhooks->constructEvent(
$payload,
$signature,
getenv('CARTFLOX_WEBHOOK_SECRET')
);
} catch (WebhookSignatureException $e) {
http_response_code(400);
echo json_encode(['error' => 'Signature invalide']);
exit;
}
switch ($event->type) {
case 'payment.completed':
$payment = $event->data;
// Mettre à jour la commande en base de données
updateOrderStatus($payment->metadata['order_id'], 'paid');
break;
case 'payment.failed':
notifyCustomer($event->data->customer->email);
break;
}
http_response_code(200);
echo json_encode(['received' => true]);Laravel — Intégration rapide
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Cartflox\Cartflox;
class PaymentController extends Controller
{
private Cartflox $cartflox;
public function __construct()
{
$this->cartflox = new Cartflox(config('services.cartflox.secret'));
}
public function create(Request $request)
{
$payment = $this->cartflox->payments->create([
'amount' => $request->amount,
'currency' => 'XOF',
'method' => $request->method ?? 'auto',
'customer' => ['phone' => $request->phone],
'redirect_url' => route('payment.success'),
'metadata' => ['order_id' => $request->order_id],
]);
return redirect($payment->checkout_url);
}
public function webhook(Request $request)
{
$event = $this->cartflox->webhooks->constructEvent(
$request->getContent(),
$request->header('cartflox-signature'),
config('services.cartflox.webhook_secret')
);
if ($event->type === 'payment.completed') {
// Traiter le paiement
}
return response()->json(['received' => true]);
}
}SDK Flutter
Le SDK officiel Cartflox pour Flutter (iOS & Android). Intègre la page de paiement native ou via WebView.
Installation
dependencies: cartflox_flutter: ^2.0.0
flutter pub get
Configuration Android
<activity android:name=".MainActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="cartflox" android:host="callback"/>
</intent-filter>
</activity>Initialisation
import 'package:cartflox_flutter/cartflox_flutter.dart';
void main() {
Cartflox.initialize(
publicKey: const String.fromEnvironment('CARTFLOX_PUBLIC_KEY'),
environment: CartfloxEnvironment.production, // ou .sandbox
);
runApp(const MyApp());
}Lancer un paiement
import 'package:cartflox_flutter/cartflox_flutter.dart';
class PaymentScreen extends StatelessWidget {
Future<void> _startPayment(BuildContext context) async {
final result = await CartfloxCheckout.present(
context: context,
params: PaymentParams(
amount: 25000,
currency: 'XOF',
method: PaymentMethod.auto, // Affiche toutes les méthodes
customer: Customer(
phone: '+22507070707',
email: 'client@example.com',
name: 'Kouassi Jean',
),
metadata: {'orderId': 'ORD-2026-001'},
),
);
switch (result.status) {
case PaymentStatus.completed:
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('✅ Paiement réussi !')),
);
// Valider la commande côté serveur via webhook
break;
case PaymentStatus.failed:
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('❌ Échec : ${result.errorMessage}')),
);
break;
case PaymentStatus.cancelled:
// Le client a annulé
break;
}
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () => _startPayment(context),
child: const Text('Payer maintenant'),
);
}
}Personnalisation du thème
CartfloxCheckout.present(
context: context,
params: PaymentParams( ... ),
theme: CartfloxTheme(
primaryColor: const Color(0xFF10B981), // Votre couleur de marque
backgroundColor: const Color(0xFF1A1A1A),
textColor: Colors.white,
borderRadius: 16.0,
logoUrl: 'https://monsite.com/logo.png',
businessName: 'Ma Boutique',
),
);Architecture recommandée
// 1. Votre backend crée le paiement
// POST https://votre-api.com/create-payment
// → Retourne { checkoutToken: "pi_01HXYZ...", checkoutUrl: "..." }
// 2. Le SDK Flutter utilise le token
final paymentIntent = await api.createPayment(amount: 25000);
final result = await CartfloxCheckout.presentWithToken(
context: context,
checkoutToken: paymentIntent.checkoutToken,
);Plugin WooCommerce
Acceptez les paiements Mobile Money et carte directement dans votre boutique WooCommerce, sans écrire une ligne de code.
Installation
Option 1 — Via le dashboard WordPress
- Allez dans Extensions → Ajouter
- Recherchez
Cartflox Payment - Cliquez sur Installer maintenant puis Activer
Option 2 — Installation manuelle
# Télécharger le plugin curl -L https://downloads.cartflox.com/woocommerce/cartflox-payment-latest.zip -o cartflox-payment.zip # Dézipper dans le dossier plugins unzip cartflox-payment.zip -d /var/www/html/wp-content/plugins/
Configuration
- Allez dans WooCommerce → Réglages → Paiements
- Activez Cartflox Payment
- Cliquez sur Gérer
- Entrez vos clés API (sandbox pour les tests, production pour les vrais paiements)
- Configurez l'URL webhook : copiez l'URL affichée dans WordPress et ajoutez-la dans votre dashboard Cartflox
- Sauvegardez
Configuration du webhook
L'URL webhook WordPress générée par le plugin :
https://votre-boutique.com/wc-api/cartflox_webhook
Ajoutez cette URL dans votre dashboard Cartflox → Paramètres → Webhooks → Ajouter un endpoint et sélectionnez tous les événements payment.*.
Méthodes de paiement disponibles
Options avancées
| Option | Description |
|---|---|
| Titre affiché | Texte affiché à la caisse (défaut: "Cartflox Payment") |
| Description | Description affichée sous le titre |
| Méthodes actives | Choisir quelles méthodes de paiement afficher |
| Mode sandbox | Activer/désactiver le mode test |
| Logo personnalisé | Afficher votre logo sur la page de paiement |
| Couleur de marque | Couleur principale de la page de paiement |
• payment.completed → Statut commande : Traitement
• payment.failed → Statut commande : Échoué
• refund.completed → Statut commande : Remboursé
Compatibilité
Codes d'erreur
Lorsqu'une requête échoue, l'API retourne un objet d'erreur structuré avec un code machine et un message lisible.
Format d'erreur
{
"success": false,
"error": {
"code": "INSUFFICIENT_FUNDS",
"message": "Le solde du compte est insuffisant pour effectuer cette transaction.",
"details": {
"field": "customer.phone",
"value": "+22507070707"
},
"requestId": "req_01HXYZ123"
}
}Codes HTTP
| Code | Signification |
|---|---|
| 200 OK | Requête réussie |
| 201 Created | Ressource créée avec succès |
| 400 Bad Request | Requête malformée |
| 401 Unauthorized | Authentification invalide ou manquante |
| 403 Forbidden | Accès refusé |
| 404 Not Found | Ressource introuvable |
| 422 Unprocessable Entity | Erreur de validation métier |
| 429 Too Many Requests | Limite de débit dépassée |
| 500 Internal Server Error | Erreur côté Cartflox |
| 503 Service Unavailable | Service ou opérateur temporairement indisponible |
Codes d'erreur métier
| Code | HTTP | Description |
|---|---|---|
| INVALID_API_KEY | 401 | La clé API est invalide ou a été révoquée. |
| UNAUTHORIZED | 401 | Authentification requise. |
| FORBIDDEN | 403 | Vous n'avez pas les droits pour cette opération. |
| NOT_FOUND | 404 | La ressource demandée est introuvable. |
| VALIDATION_ERROR | 422 | Les données envoyées sont invalides. Vérifiez les champs requis. |
| INVALID_AMOUNT | 422 | Le montant est invalide (trop petit, trop grand ou non entier). |
| INVALID_CURRENCY | 422 | La devise n'est pas supportée. |
| INVALID_PHONE | 422 | Le numéro de téléphone est invalide ou non supporté. |
| METHOD_NOT_AVAILABLE | 422 | La méthode de paiement n'est pas disponible dans ce pays. |
| INSUFFICIENT_FUNDS | 422 | Le solde du client est insuffisant. |
| ACCOUNT_NOT_REGISTERED | 422 | Le numéro n'est pas enregistré auprès de l'opérateur. |
| TRANSACTION_LIMIT | 422 | La limite de transaction journalière ou mensuelle est atteinte. |
| PAYMENT_EXPIRED | 422 | La session de paiement a expiré. |
| ALREADY_REFUNDED | 422 | Ce paiement a déjà été intégralement remboursé. |
| REFUND_AMOUNT_EXCEEDED | 422 | Le montant de remboursement dépasse le montant payé. |
| RATE_LIMIT_EXCEEDED | 429 | Trop de requêtes. Attendez avant de réessayer. |
| PROVIDER_UNAVAILABLE | 503 | L'opérateur de paiement est temporairement indisponible. |
| INTERNAL_ERROR | 500 | Erreur interne Cartflox. Réessayez ou contactez le support. |
Gestion des erreurs côté client
import Cartflox, { CartfloxError } from "@cartflox/node";
try {
const payment = await cartflox.payments.create({ ... });
} catch (err) {
if (err instanceof CartfloxError) {
switch (err.code) {
case "INSUFFICIENT_FUNDS":
return res.status(400).json({ message: "Solde insuffisant, veuillez recharger votre compte." });
case "ACCOUNT_NOT_REGISTERED":
return res.status(400).json({ message: "Ce numéro n'est pas enregistré sur ce réseau." });
case "METHOD_NOT_AVAILABLE":
return res.status(400).json({ message: "Cette méthode n'est pas disponible dans votre pays." });
case "RATE_LIMIT_EXCEEDED":
// Retry après un délai
await sleep(1000);
return retryPayment();
default:
// Erreur inattendue — logguer et afficher message générique
logger.error("Cartflox error", { code: err.code, requestId: err.requestId });
return res.status(500).json({ message: "Une erreur est survenue. Veuillez réessayer." });
}
}
throw err; // Re-throw si ce n'est pas une erreur Cartflox
}Limites de taux (Rate Limiting)
Cartflox applique des limites de débit pour garantir la stabilité de la plateforme. Les limites sont appliquées par clé API.
Limites par plan
| Plan | Requêtes / minute | Requêtes / jour | Webhooks / sec |
|---|---|---|---|
| Sandbox | 60 | 10 000 | 10 |
| Starter | 120 | 50 000 | 30 |
| Business | 600 | 500 000 | 100 |
| Enterprise | 3 000 | Illimité | 500 |
Headers de réponse
Chaque réponse API inclut des headers indiquant votre utilisation :
X-RateLimit-Limit: 120 # Limite totale par minute X-RateLimit-Remaining: 87 # Requêtes restantes dans la fenêtre X-RateLimit-Reset: 1740823293 # Timestamp UNIX de réinitialisation X-RateLimit-Window: 60 # Durée de la fenêtre en secondes
Gestion du dépassement
Quand la limite est atteinte, l'API retourne une erreur 429 Too Many Requests avec un header Retry-After :
HTTP/1.1 429 Too Many Requests
Retry-After: 23
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1740823293
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Limite de débit atteinte. Veuillez attendre 23 secondes.",
"retryAfter": 23
}
}Retry avec backoff exponentiel
async function apiCallWithRetry<T>(
fn: () => Promise<T>,
maxRetries = 3
): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (err: any) {
if (err.code === "RATE_LIMIT_EXCEEDED" && attempt < maxRetries) {
const delay = (err.retryAfter ?? Math.pow(2, attempt)) * 1000;
console.log(`Rate limit. Retry dans ${delay}ms...`);
await new Promise(r => setTimeout(r, delay));
continue;
}
throw err;
}
}
throw new Error("Max retries atteint");
}
// Utilisation
const payment = await apiCallWithRetry(() =>
cartflox.payments.create({ ... })
);Bonnes pratiques
Idempotence
Pour les requêtes POST, vous pouvez envoyer un header Idempotency-Key pour éviter les doublons en cas de retry :
curl -X POST https://api.cartflox.com/v2/payments \
-H "Authorization: Bearer sk_live_xxxx" \
-H "Idempotency-Key: order_ORD-2026-001_attempt_1" \
-H "Content-Type: application/json" \
-d '{ ... }'Si vous renvoyez la même clé d'idempotence dans les 24h, Cartflox retourne le résultat de la première requête sans re-créer de paiement.
Changelog
Historique des mises à jour de l'API Cartflox et des SDKs officiels.
Restez informé des mises à jour
Abonnez-vous à notre newsletter technique ou suivez notre GitHub pour être notifié des nouvelles versions.