Version 1 · serveur à serveur · JSON · HTTPS. Environnement pilote : clés ewk_test_…, aucune valeur légale.
checkout_url.payment.succeeded à votre serveur, puis redirige le client vers votre return_url.GET qui confirme status = succeeded) : le retour du navigateur seul ne prouve rien.Base : https://marchands-pilote.up.railway.app/v1 · Authentification : Authorization: Bearer ewk_test_… (clé créée dans l'espace marchand). Montants en F CFA entiers (XOF), de 100 à 2 000 000. 120 requêtes par minute au plus.
| Appel | Rôle |
|---|---|
POST /v1/payments | Créer un paiement. Champs : amount (obligatoire), reference (n° de commande, obligatoire), currency (XOF), description, customer_phone, return_url, cancel_url, metadata (objet), expires_in (300 à 86 400 s, 1 800 par défaut). En-tête conseillé : Idempotency-Key. Réponse 201. |
GET /v1/payments/{id} | Consulter un paiement et son statut à jour. |
POST /v1/payments/{id}/cancel | Annuler un paiement encore en attente. |
POST /v1/payments/{id}/refund | Demander le remboursement total d'un paiement réussi (reason facultatif). Réponse 202 : le service client e-Wari valide, puis le webhook payment.refunded part. |
Bac à sable : POST /v1/payments/{id}/test_complete simule le paiement du client (clés test uniquement).
curl https://marchands-pilote.up.railway.app/v1/payments \
-H "Authorization: Bearer ewk_test_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
-H "Content-Type: application/json" -H "Idempotency-Key: commande-1042" \
-d '{"amount":25000,"reference":"CMD-1042","description":"Frais de dossier","return_url":"https://votre-site.com/paiement/retour"}'
{ "id": "pay_7KQ2M9XW4D1P8R3T", "object": "payment", "status": "pending", "amount": 25000, "currency": "XOF",
"reference": "CMD-1042", "checkout_url": "https://marchands-pilote.up.railway.app/c/pay_7KQ2M9XW4D1P8R3T", "expires_at": "…", "livemode": false, … }
Statuts : pending, succeeded, failed, cancelled, expired, refunded. Un remboursement en cours apparaît dans refund.status (pending, succeeded, rejected). Erreurs : {"error":{"code":"…","message":"…","param":"…"}} avec les codes HTTP 400, 401, 404, 409, 429.
Événements : payment.succeeded, payment.failed, payment.cancelled, payment.expired, payment.refund_requested, payment.refunded, payment.refund_rejected (et ping pour les tests). Corps : {"id":"evt_…","type":"…","created_at":"…","data":{"payment":{…}}}.
En-tête EWari-Signature: t=<horodatage>,v1=<signature> où signature = HMAC-SHA256 en hexadécimal, avec votre secret whsec_…, de la chaîne t + "." + corps brut. Vérifiez la signature, refusez un horodatage de plus de 5 minutes, et traitez chaque id d'événement une seule fois. Répondez 2xx en moins de 10 s ; sinon e-Wari réessaie jusqu'à 10 fois sur 24 h.
// Node.js (Express : express.raw({ type: "application/json" }))
const crypto = require("crypto");
function verifier(corpsBrut, entete, secret) {
const p = Object.fromEntries(entete.split(",").map(x => x.split("=")));
if (Math.abs(Date.now() / 1000 - Number(p.t)) > 300) return false;
const attendu = crypto.createHmac("sha256", secret).update(p.t + "." + corpsBrut).digest("hex");
return crypto.timingSafeEqual(Buffer.from(attendu), Buffer.from(p.v1 || ""));
}
// PHP
function verifier($corpsBrut, $entete, $secret) {
parse_str(str_replace(",", "&", $entete), $p);
if (abs(time() - (int)$p["t"]) > 300) return false;
return hash_equals(hash_hmac("sha256", $p["t"] . "." . $corpsBrut, $secret), $p["v1"] ?? "");
}