Passerelle de paiement Mobile Money (MTN, Orange, Wave… via Tara Money) utilisable par n'importe quelle app, SaaS ou site. URL de base : https://pay-api.doc4geeks.com. Montants en FCFA (XAF), entiers.
tpk_…) et le secret de callback (whsec_…) s'affichent une seule fois.curl -X POST https://pay-api.doc4geeks.com/v1/checkout \
-H "Authorization: Bearer $TARAPAY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: cmd_123" \
-d '{"amount":5000,"name":"Commande #123","reference":"cmd_123",
"customer":{"email":"client@exemple.com","name":"Awa"},
"successUrl":"https://monsite.com/merci","cancelUrl":"https://monsite.com/panier"}'
# → 201 {"order":{…,"status":"PENDING"},"checkoutUrl":"https://pay-api.doc4geeks.com/pay/tp_…"}
order.paid sur votre URL de callback (signature à vérifier), ou interrogez GET /v1/orders/:id.En-tête Authorization: Bearer tpk_… sur tous les endpoints /v1/*. Les clés sont stockées hachées (SHA-256) : une clé perdue doit être régénérée (Apps → Nouvelle clé ; l'ancienne est révoquée immédiatement).
| Méthode | Chemin | Description |
|---|---|---|
POST | /v1/checkout | Crée une commande + page de paiement hébergée (le client choisit MTN/Orange/Wave ou carte). Champs : amount ou productId, name, description?, reference?, customer{email,name,phone,externalId}?, metadata?, tags?, successUrl?, cancelUrl?, callbackUrl?, expiresInMinutes? (défaut 7 j). |
POST | /v1/payment-links | Lien de paiement Tara direct (WhatsApp, Telegram, carte, SMS…). Champs : amount, name, description (requis), pictureUrl?, returnUrl? + champs communs. |
POST | /v1/mobile-money/collect | Collecte directe : phoneNumber (avec indicatif, ex. 2376…), network?=wave. Le client confirme sur son téléphone (ou redirection authUrl pour Wave). |
GET | /v1/orders | Liste paginée (limit, status, before). |
GET | /v1/orders/:id | Détail (vérification live si PENDING ; ?by=reference pour chercher par référence ; ?live=0 pour désactiver). |
POST | /v1/orders/:id/refresh | Force la re-vérification chez Tara. |
GET/POST | /v1/products | Catalogue produits/prix (name, amount, interval? week|month|quarter|year). Chaque produit a un lien public réutilisable /p/:id. |
POST | /v1/invoices | Facture numérotée (items[{label,quantity,unitPrice}], customer, dueInDays?, notes?) → url (HTML imprimable/PDF) + checkoutUrl. |
GET | /v1/invoices/:id | Détail d'une facture. |
POST | /v1/subscriptions | Abonnement (amount+name ou productId, interval, customer, startAt?). À chaque échéance un lien est généré et order.created est envoyé. |
GET | /v1/subscriptions/:id | Détail. |
POST | /v1/subscriptions/:id/cancel | Annule. |
GET | /v1/customers | Clients de l'app (dédupliqués par téléphone/email). |
POST | /v1/webhooks/resend | Demande à Tara de renvoyer le webhook d'une commande (orderId ou reference). |
GET | /health | État du service. |
Objet commande (extrait) :
{"id":"tp_…","reference":"cmd_123","kind":"checkout","amount":5000,"currency":"XAF","status":"PAID",
"payerPhone":"2376XXXXXXXX","operator":"MTN_CAMEROON","netAmount":4850,"feeAmount":150,
"invoiceUrl":"https://www.dklo.co/…","transactionId":"…","paymentId":"…",
"customer":{"id":"cus_…","email":"…","name":"…","externalId":"…"},"metadata":{…},"tags":["promo"],
"links":{"checkoutUrl":"https://pay-api.doc4geeks.com/pay/tp_…"},"createdAt":"…","paidAt":"…","expiresAt":"…"}
https://pay-api.doc4geeks.com/pay/:id : page à vos couleurs, choix de l'opérateur, saisie du numéro, statut en temps réel, puis redirection vers successUrl (payé) ou cancelUrl (échec/annulation). Liens réutilisables : https://pay-api.doc4geeks.com/p/:productId crée une nouvelle commande à chaque visite.
POST JSON vers callbackUrl de la commande ou l'URL par défaut de l'app. Événements : order.created, order.paid, order.failed, order.expired (+ ping pour les tests). Relances automatiques avec backoff exponentiel (8 essais max) ; rejouables depuis le back-office. Répondez 2xx rapidement ; traitez de façon idempotente via X-TaraPay-Event-Id.
POST /votre/webhook
X-TaraPay-Signature: sha256=<hex HMAC-SHA256(corps brut, secret)>
X-TaraPay-Event-Id: order.paid:tp_…
{"id":"order.paid:tp_…","type":"order.paid","createdAt":"…","data":{ …objet commande… }}
Node / Workers :
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyTaraPay(rawBody: string, header: string | null, secret: string) {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(expected), b = Buffer.from(header ?? "");
return a.length === b.length && timingSafeEqual(a, b);
}
// Express : app.post("/webhooks/tarapay", express.raw({ type: "application/json" }), (req, res) => {
// if (!verifyTaraPay(req.body.toString(), req.get("X-TaraPay-Signature"), process.env.TARAPAY_CALLBACK_SECRET)) return res.sendStatus(401);
// const evt = JSON.parse(req.body); if (evt.type === "order.paid") { /* livrer */ } res.sendStatus(200); });
PHP :
$raw = file_get_contents("php://input");
$ok = hash_equals("sha256=" . hash_hmac("sha256", $raw, getenv("TARAPAY_CALLBACK_SECRET")), $_SERVER["HTTP_X_TARAPAY_SIGNATURE"] ?? "");
if (!$ok) { http_response_code(401); exit; }
Python :
import hmac, hashlib
ok = hmac.compare_digest("sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest(), signature_header)
| Statut | Signification |
|---|---|
PENDING | En attente (le client n'a pas encore confirmé). Re-vérifié toutes les 5 min pendant 7 jours. |
PAID | Payé — toujours confirmé par transactions/status chez Tara (un webhook seul ne suffit pas). |
FAILED | Refusé / annulé / échec d'initiation. |
EXPIRED | Non payé avant expiresAt. |
Les statuts finaux ne changent plus.
Deux mécanismes, sur tous les POST de création :
reference : unique par app ; une 2ᵉ création avec la même référence renvoie la commande existante ("idempotent": true).Idempotency-Key : la réponse est mémorisée ; un rejeu renvoie la même réponse (en-tête Idempotent-Replayed: true). Même clé + corps différent → 422 IDEMPOTENCY_KEY_REUSED.Format : {"error":"CODE","detail":…}.
| HTTP | Codes |
|---|---|
| 400 | INVALID_AMOUNT, INVALID_NAME, INVALID_PHONE, INVALID_URL, INVALID_CALLBACK_URL, INVALID_EMAIL, INVALID_ITEMS, INVALID_INTERVAL, INVALID_JSON, INVALID_PRODUCT… |
| 401 / 403 | UNAUTHORIZED (clé absente/invalide/révoquée), ORIGIN_NOT_ALLOWED |
| 404 | NOT_FOUND, ORDER_NOT_FOUND… |
| 409 / 422 | CONFLICT, IDEMPOTENCY_IN_PROGRESS / IDEMPOTENCY_KEY_REUSED |
| 502 | TARA_INIT_FAILED (détail Tara inclus ; la commande passe FAILED) |
| 503 | TARA_NOT_CONFIGURED |
Fichier unique sdk/tarapay.ts (Node 18+, Workers, Deno, backends Expo) :
import { TaraPay, verifySignature } from "./tarapay";
const tp = new TaraPay({ apiKey: process.env.TARAPAY_API_KEY! });
const { checkoutUrl } = await tp.createCheckout({ amount: 5000, name: "Commande #123", reference: "cmd_123" });
const order = await tp.getOrder("tp_…");
const ok = await verifySignature(rawBody, req.headers.get("X-TaraPay-Signature"), process.env.TARAPAY_CALLBACK_SECRET!);
productId : rattachement par numéro, sinon re-vérification par le cron.