SDK PHPConsole

Documentation de l'API SysPay

SysPay expose une API REST JSON unique pour encaisser (mobile money, cartes), verser, rembourser et gérer une trésorerie multi-pays. Toutes les URL sont relatives à https://votre-domaine/api/v1.

Le plus rapide : créez une session de checkout côté serveur, redirigez le client vers checkout_url, puis livrez la commande à réception du webhook payment.succeeded.

Authentification & modes

Chaque requête porte une clé secrète dans l'en-tête Authorization. Le préfixe de la clé détermine le mode :

sk_test_…Sandbox : routée exclusivement vers le simulateur. Il est structurellement impossible d'atteindre un réseau réel.
sk_live_…Production : disponible après validation KYC du compte.
curl https://votre-domaine/api/v1/balances \ -H "Authorization: Bearer sk_test_4f1c…"

Les clés sont hachées côté SysPay : elles ne sont affichées qu'une fois à la création. Ne les placez jamais dans une application mobile ou un navigateur.

Conventions

  • Montants : toujours des entiers en unités mineures (amount_minor). XOF, XAF, GNF : 0 décimale (5000 = 5 000 FCFA). USD, EUR : centimes.
  • Téléphones : format E.164 (+221771234567). Un numéro local est complété par l'indicatif du pays.
  • Dates : ISO 8601 UTC (2026-11-07T16:27:12Z).
  • Enveloppe : {"success": true, "data": …} ou {"success": false, "error": {code, message, fields}}.
  • Listes : ?page=1&limit=20 (max 100) → {object:"list", data:[…], total, has_more}.

Erreurs

{ "success": false, "error": { "code": "invalid_phone", "message": "Numéro invalide pour Sénégal. Exemple : +221771234567", "fields": { "phone": ["Format attendu : +221771234567"] } } }
CodeHTTPSignification
unauthorized401Clé absente, invalide ou révoquée
validation_error422Paramètre manquant ou invalide (voir fields)
otp_required422Le réseau exige un code OTP pré-paiement
idempotency_conflict409Même Idempotency-Key, corps différent
insufficient_balance402Solde disponible insuffisant pour le versement
live_not_enabled / payout_not_enabled403Compte non activé pour le live ou les versements
limit_exceeded422Plafond KYC par transaction ou mensuel dépassé
no_route503Aucun fournisseur actif pour ce réseau et ce pays

Idempotence

Ajoutez Idempotency-Key (≤ 120 caractères, par exemple votre numéro de commande) sur toute création. Rejouer la même clé avec le même corps renvoie l'objet existant (idempotent_replay: true) ; avec un corps différent, SysPay répond 409 idempotency_conflict.

En cas de coupure réseau pendant une création, rejouez avec la même clé : c'est la garantie de ne jamais débiter deux fois.

Sessions de checkout

POST/checkout/sessions
country *Code pays ISO (SN, CI, CM…)
amount_minor *Montant en unités mineures
currencyFacultatif, doit correspondre au pays
networkImposer un réseau (sinon le client choisit)
description, merchant_referenceAffichés au client / renvoyés dans les webhooks
customer {name, email, phone}Préremplissage de la page de paiement
return_url, cancel_urlRetour après paiement (succès et échec)
expires_in_minutesDurée de validité (5 à 10080, défaut 60)
metadataObjet libre renvoyé tel quel
curl -X POST https://votre-domaine/api/v1/checkout/sessions \ -H "Authorization: Bearer sk_test_…" -H "Idempotency-Key: CMD-1042" \ -H "Content-Type: application/json" \ -d '{"country":"SN","amount_minor":25000,"merchant_reference":"CMD-1042","return_url":"https://boutique.sn/retour"}' → { "success": true, "data": { "id": "cs_…", "status": "OPEN", "checkout_url": "https://votre-domaine/pay.html?s=cs_…", … } }

Après paiement, SysPay revérifie le statut auprès du fournisseur, puis redirige vers return_url?syspay_status=success|failed&syspay_transaction_id=…&syspay_reference=…&merchant_reference=…. Ces paramètres sont indicatifs : la source de vérité reste le webhook ou GET /checkout/sessions/{id}.

GET/checkout/sessions/{id}
POST/checkout/sessions/{id}/cancel

États : OPEN → COMPLETED | EXPIRED | CANCELLED. Un client peut retenter sur une session ouverte après un échec.

Paiements directs (push)

Pour débiter le client depuis votre propre interface : SysPay envoie la demande à l'opérateur, le client valide sur son téléphone.

POST/payments
{ "country": "SN", "network": "wave", "amount_minor": 5000, "phone": "+221771234567", "description": "Commande 42", "merchant_reference": "CMD-42", "customer": {"name": "Awa Diop"}, "return_url": "https://boutique.sn/retour" }

Certains fournisseurs renvoient un checkout_url (page opérateur) : redirigez le client si présent.

EndpointRôle
GET /payments/{id}Statut (revérifie si en attente)
POST /payments/{id}/verifyForce une vérification auprès du fournisseur
POST /payments/{id}/retryRejoue un échec : même id, nouvelle référence fournisseur, jamais de doublon
POST /payments/{id}/cancelAnnule un paiement PENDING (jamais UNKNOWN)
POST /payments/{id}/confirm-otpCode OTP post-push
GET /payments/{id}/attemptsHistorique détaillé des appels
GET /paymentsListe (filtres status, merchant_reference)

OTP

Les règles sont déclarées par réseau × pays et exposées par GET /reference/networks (otp_mode, otp_instructions) :

  • prepayment (ex. Orange Money SN, CI, BF) : le client génère un code via USSD ; envoyez-le dans otp à la création, sinon 422 otp_required.
  • postpush (ex. Wizall SN) : un SMS est envoyé après la demande ; transmettez le code avec POST /payments/{id}/confirm-otp.

États et cycle de vie

PENDINGDemande envoyée, en attente de validation client
UNKNOWNLe fournisseur n'a pas répondu à temps. SysPay vérifie automatiquement chaque minute : ne rejouez pas.
SUCCESSConfirmé ; fonds crédités au portefeuille (net de frais)
FAILED / CANCELLED / EXPIREDTerminal sans mouvement de fonds (EXPIRED après 24 h sans réponse)
PENDING_APPROVALVersement en attente de validation SysPay (fonds gelés)
REJECTEDVersement refusé, fonds restitués

Versements

POST/payouts
{ "country": "CI", "network": "mtn_momo", "amount_minor": 15000, "phone": "+2250712345678", "beneficiary_name": "Koffi K.", "description": "Paie livreur" }

Le montant et les frais sont gelés avant l'appel : en cas de solde insuffisant, rien ne part (402). En cas d'échec, les fonds sont restitués par écriture inverse. GET /payouts/{id}, GET /payouts.

Remboursements

POST/payments/{id}/refund

{"amount_minor": 2000, "reason": "Article indisponible"} — total si amount_minor est omis. Versé sur le numéro d'origine, sans frais, débité du solde disponible. GET /refunds.

Soldes & transferts

GET/balances
[ { "country": "SN", "currency": "XOF", "available_minor": 33810, "frozen_minor": 0, "incoming_minor": 7000 } ]

Un portefeuille par pays. Conversion entre portefeuilles : GET /wallet-transfers/quote?from_country=SN&to_country=CM&amount_minor=100000 puis POST /wallet-transfers (validation SysPay en live, un seul transfert en attente à la fois).

Webhooks

Configurez vos endpoints dans la console. Événements : payment.created, payment.succeeded, payment.failed, payment.unknown, payment.cancelled, payment.expired, checkout.completed, payout.created, payout.succeeded, payout.failed, payout.rejected, refund.succeeded, refund.failed, transfer.approved, transfer.rejected.

POST https://monsite.com/webhooks/syspay X-SysPay-Event: payment.succeeded X-SysPay-Signature: t=1762532832,v1=5f0c… { "id": "evt_…", "type": "payment.succeeded", "livemode": false, "created": 1762532832, "data": { "id": "pay_…", "status": "SUCCESS", "amount_minor": 25000, "currency": "XOF", … } }

Vérifier la signature

v1 = HMAC_SHA256(secret, t + "." + corps_brut). Refusez les messages de plus de 5 minutes et répondez 2xx rapidement. Les échecs sont rejoués : 10 s, 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h.

// PHP, avec le SDK $event = SysPay::verifyWebhook(file_get_contents('php://input'), $_SERVER['HTTP_X_SYSPAY_SIGNATURE'], 'whsec_…'); // Node.js const [t, v1] = header.split(',').map(p => p.split('=')[1]); const ok = crypto.timingSafeEqual(Buffer.from(v1), Buffer.from( crypto.createHmac('sha256', secret).update(t + '.' + rawBody).digest('hex')));

Traitez les webhooks de manière idempotente (même data.id = même commande) : un événement peut être reçu plus d'une fois.

Référentiel

GET/reference/networks?country=SN

Réseaux réellement disponibles pour votre mode (encaissement et versement), limites, format de numéro et règles OTP.

GET/pricing/my-rates
GET/pricing/quote?amount_minor=10000&kind=payin
GET/events
GET/health

Sandbox

Numéro se terminant parComportement
1111Refus opérateur (insufficient_funds)
0000Délai dépassé → UNKNOWN, puis SUCCESS à la vérification
2222Fournisseur injoignable (teste la bascule de route)
autrePENDING ; boutons Valider / Refuser sur la page de paiement ; SUCCESS automatique après 8 s à la vérification
OTP 000000Code post-push refusé ; tout autre code de 4 à 6 chiffres est accepté

Les versements sandbox réussissent immédiatement (sauf 1111, 0000, 2222) ; les transferts entre portefeuilles sont approuvés automatiquement.

Mise en production

  1. Soumettez vos pièces KYC depuis Console → Activation.
  2. Après validation, générez une clé sk_live_.
  3. Configurez un webhook HTTPS en mode live.
  4. Remplacez la clé test par la clé live côté serveur. Aucune autre modification n'est nécessaire.