doc4geeks Pay — Documentation

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.

1. Démarrage rapide

  1. Dans le back-office (/admin) → Apps → « Créer l'app » avec son nom et l'URL HTTPS de callback. La clé API (tpk_…) et le secret de callback (whsec_…) s'affichent une seule fois.
  2. Stockez-les côté serveur uniquement (variables d'environnement). Ne jamais embarquer la clé dans une app mobile ou du JavaScript navigateur.
  3. Créez un checkout et redirigez votre client :
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_…"}
  1. Recevez l'événement order.paid sur votre URL de callback (signature à vérifier), ou interrogez GET /v1/orders/:id.

2. Authentification

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).

3. Endpoints

MéthodeCheminDescription
POST/v1/checkoutCré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-linksLien de paiement Tara direct (WhatsApp, Telegram, carte, SMS…). Champs : amount, name, description (requis), pictureUrl?, returnUrl? + champs communs.
POST/v1/mobile-money/collectCollecte directe : phoneNumber (avec indicatif, ex. 2376…), network?=wave. Le client confirme sur son téléphone (ou redirection authUrl pour Wave).
GET/v1/ordersListe paginée (limit, status, before).
GET/v1/orders/:idDétail (vérification live si PENDING ; ?by=reference pour chercher par référence ; ?live=0 pour désactiver).
POST/v1/orders/:id/refreshForce la re-vérification chez Tara.
GET/POST/v1/productsCatalogue produits/prix (name, amount, interval? week|month|quarter|year). Chaque produit a un lien public réutilisable /p/:id.
POST/v1/invoicesFacture numérotée (items[{label,quantity,unitPrice}], customer, dueInDays?, notes?) → url (HTML imprimable/PDF) + checkoutUrl.
GET/v1/invoices/:idDétail d'une facture.
POST/v1/subscriptionsAbonnement (amount+name ou productId, interval, customer, startAt?). À chaque échéance un lien est généré et order.created est envoyé.
GET/v1/subscriptions/:idDétail.
POST/v1/subscriptions/:id/cancelAnnule.
GET/v1/customersClients de l'app (dédupliqués par téléphone/email).
POST/v1/webhooks/resendDemande à 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":"…"}

4. Checkout hébergé

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.

5. Callbacks (événements sortants)

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)

6. Statuts

StatutSignification
PENDINGEn attente (le client n'a pas encore confirmé). Re-vérifié toutes les 5 min pendant 7 jours.
PAIDPayé — toujours confirmé par transactions/status chez Tara (un webhook seul ne suffit pas).
FAILEDRefusé / annulé / échec d'initiation.
EXPIREDNon payé avant expiresAt.

Les statuts finaux ne changent plus.

7. Idempotence

Deux mécanismes, sur tous les POST de création :

8. Erreurs

Format : {"error":"CODE","detail":…}.

HTTPCodes
400INVALID_AMOUNT, INVALID_NAME, INVALID_PHONE, INVALID_URL, INVALID_CALLBACK_URL, INVALID_EMAIL, INVALID_ITEMS, INVALID_INTERVAL, INVALID_JSON, INVALID_PRODUCT…
401 / 403UNAUTHORIZED (clé absente/invalide/révoquée), ORIGIN_NOT_ALLOWED
404NOT_FOUND, ORDER_NOT_FOUND…
409 / 422CONFLICT, IDEMPOTENCY_IN_PROGRESS / IDEMPOTENCY_KEY_REUSED
502TARA_INIT_FAILED (détail Tara inclus ; la commande passe FAILED)
503TARA_NOT_CONFIGURED

9. SDK TypeScript

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!);

10. Limites connues (Tara)