API paiement pour logiciels de caisse
Pour un restaurant, une boutique ou tout logiciel qui encaisse des commandes de montants différents. Chaque commerce a sa propre clé et reçoit l'argent sur son propre compte ZingoPay.
Démonstration : argent fictif, aucun fonds réel. Les clés commencent par zp_test_.
À lire avant de commencer
- Démonstration. Tout l'argent est fictif. Aucun vrai franc n'est encaissé ni versé. Ne promettez pas de vrai paiement à vos clients.
- Il n'y a pas de dépôt par l'API. L'API sert seulement à encaisser : créer un paiement et savoir s'il est payé. On ne peut pas déposer ni envoyer de l'argent par l'API. Le client dépose chez un agent ZingoPay ; le commerçant ne dépose jamais.
- Où va l'argent encaissé. Sur le compte ZingoPay Business du commerçant (un compte par restaurant ou boutique). Une commission de 0,8 % est retenue au commerçant ; le client ne paie aucun frais.
- Comment retirer. Depuis l'espace Business, bouton « Retirer mon argent » : soit on vire l'argent vers un compte client (le vôtre, créé avec une autre adresse e-mail), soit on retire chez un agent. Un retrait chez un agent coûte 1 % (payé en plus du montant, sans frais à partir du 11e retrait du jour).
- Le statut fait foi. Une commande est payée uniquement quand
statusvautpaidsur ZingoPay. Un client qui dit « j'ai payé » ne prouve rien. - Mot de passe et code. À la première connexion, le commerçant remplace le mot de passe reçu par le sien et crée un code à 4 chiffres. ZingoPay ne connaît jamais le nouveau mot de passe. Ne partagez jamais votre clé API.
- Avant de nous écrire. Vérifiez dans l'espace Business que la clé existe, que le montant est entre 100 et 1 000 000 FCFA, que la référence est nouvelle et que l'adresse de notification répond 200 (bouton « Envoyer un test »).
Comment ça marche
- Le commerçant crée sa clé secrète dans son espace (Liens de paiement, puis « Connecter mon logiciel de caisse »).
- À chaque commande, votre serveur crée un paiement avec le montant exact et le numéro de commande.
- Le client ouvre
pay_url(ou scanne le QR) et paie avec son compte ZingoPay. Un paiement ne peut être payé qu'une seule fois. - Votre serveur lit le statut sur ZingoPay (ou reçoit la notification) : seul
paidprouve que l'argent est arrivé.
Authentification
Toutes les requêtes portent l'en-tête Authorization: Bearer <clé secrète>. La clé reste sur votre serveur : jamais dans une application mobile ni une page web. Un compte commerçant = une clé = un compte qui reçoit l'argent : pour plusieurs restaurants, créez une clé par restaurant.
1. Créer un paiement au montant exact
POST /api/v1/payments avec amount (entier, FCFA, de 100 à 1 000 000), reference (le numéro de commande, 64 caractères au plus, unique par commerçant), description (facultatif) et expires_in_minutes (facultatif, 60 par défaut, 10 080 au plus).
curl -X POST https://VOTRE-DOMAINE/api/v1/payments \
-H "Authorization: Bearer zp_test_..." \
-H "Content-Type: application/json" \
-d '{ "amount": 12500, "reference": "CMD-2041", "description": "Table 7", "expires_in_minutes": 30 }'{
"id": "0b9c1c0e-5d5e-4a39-9a7c-3f1c2b7d9e11",
"reference": "CMD-2041",
"amount_xaf": 12500,
"description": "Table 7",
"status": "pending",
"expires_at": "2026-11-06T15:30:00Z",
"paid_at": null,
"payment_reference": null,
"pay_url": "https://VOTRE-DOMAINE/payer/3BBD3A015E"
}Rejouer la même requête (même reference, même montant) renvoie le même paiement, sans en créer un second. Même référence avec un autre montant : erreur reference_conflict.
2. Vérifier côté serveur que c'est payé
GET /api/v1/payments/{id} ou GET /api/v1/payments?reference=CMD-2041. Le champ status vaut pending, paid, expired ou cancelled. Quand il vaut paid, paid_at et payment_reference (le numéro de reçu ZingoPay) sont remplis. N'affichez jamais « payé » sur la seule parole du téléphone du client.
Pour rapprocher la caisse : GET /api/v1/payments (sans paramètre) renvoie les 50 derniers paiements encaissés dans data, liens fixes compris (reference vaut null pour un lien fixe).
Annuler une commande non payée : POST /api/v1/payments/{id}/cancel.
3. Notification (webhook, facultatif)
Si le commerçant enregistre une adresse https, ZingoPay y envoie {"type":"payment.paid","data":{...}} dès le paiement (3 essais). Elle part aussi pour un lien fixe (reference vaut alors null). Le bouton « Envoyer un test » de l'espace commerçant envoie un événement payment.test pour vérifier votre adresse. Répondez avec un code 2xx. Vérifiez la signature ZingoPay-Signature avec le secret de signature, puis relisez le statut avec GET avant de valider la commande.
import { createHmac, timingSafeEqual } from "node:crypto";
// Corps brut de la requête (avant tout JSON.parse) et en-tête ZingoPay-Signature: t=1730900000,v1=<hex>
export function isValid(rawBody: string, header: string, secret: string) {
const [t, v1] = header.split(",").map((p) => p.split("=")[1]);
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = createHmac("sha256", secret).update(t + "." + rawBody).digest("hex");
return v1?.length === expected.length && timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}Erreurs
Format : {"error":{"code":"invalid_amount","message":"..."}}. Codes : invalid_api_key (401), merchant_not_ready (403), invalid_amount, invalid_reference, invalid_expiry (400), not_found (404), reference_conflict (409), rate_limited (429).
Cette API fonctionne en démonstration, avec de l'argent fictif. Nous contacter