Erreurs & statuts

Cette page décrit la façon dont l'API Tchin signale les erreurs, comment les interpréter et comment écrire un code robuste face à chaque situation. La règle d'or est simple : ne vous fiez jamais au seul code HTTP. Une réponse 200 peut contenir success: false, et une confirmation de paiement définitive ne vous parvient que par webhook. Traitez systématiquement les deux niveaux : le statut HTTP et le champ success du corps JSON.

Format d'une erreur métier

Lorsqu'une requête est bien formée mais ne peut aboutir (solde insuffisant, moyen de paiement non pris en charge, ressource introuvable…), l'API renvoie un JSON contenant success: false et un message lisible. Ce format est stable et identique sur tous les endpoints.

Format
{
  "success": false,
  "message": "Description lisible de l'erreur"
}

Certaines erreurs enrichissent la réponse de champs supplémentaires. Par exemple, un solde insuffisant lors d'un déboursement ajoute un champ balance indiquant le solde disponible sur le pays concerné, ce qui vous permet d'informer précisément l'utilisateur ou de déclencher un réapprovisionnement.

Format d'une erreur de validation (422)

Quand un champ obligatoire est absent, hors bornes ou mal typé, la validation de Laravel intervient avant tout traitement métier et renvoie un code 422 Unprocessable Entity. Le corps contient un message global et un objet errors qui liste, champ par champ, tous les problèmes détectés (chaque champ pointe vers un tableau de messages). C'est le format idéal pour afficher les erreurs à côté des champs d'un formulaire.

Validation 422
{
  "message": "The amount field is required. (and 1 more error)",
  "errors": {
    "amount": [
      "The amount field is required."
    ],
    "withdraw_mode": [
      "The withdraw mode field is required."
    ]
  }
}

⚠️ Ne confondez pas 422 (validation d'entrée : le format de votre requête est en cause) et 400 (erreur métier : la requête est correcte mais l'opération est refusée, par exemple un solde insuffisant). Les deux se gèrent différemment : le 422 se corrige côté client, le 400 dépend de l'état de votre compte.

Codes HTTP

Voici l'ensemble des codes que l'API peut retourner et la manière de les traiter.

CodeSignificationQue faire
200Requête traitée.Vérifiez toujours le champ success du corps avant de considérer l'opération réussie.
400Requête invalide au sens métier (champ manquant, solde insuffisant, moyen non pris en charge…).Lisez message et corrigez la cause. Ne réessayez pas à l'identique.
401Clés API manquantes ou invalides.Ajoutez / corrigez les en-têtes TCHIN-PUBLIC-KEY et TCHIN-PRIVATE-KEY.
403Compte marchand suspendu.Contactez le support Tchin : aucune opération n'aboutira tant que le compte est suspendu.
404Ressource introuvable (token de paiement ou disburse_token inexistant).Vérifiez l'identifiant transmis ; il n'existe pas ou n'appartient pas à votre compte.
422Validation des données échouée.Parcourez errors et corrigez chaque champ signalé, puis renvoyez la requête.
500Erreur interne côté serveur.Réessayez plus tard, idéalement avec un délai croissant (backoff). Ne considérez rien comme effectué.

Erreurs fréquentes — Encaissement

Ces messages concernent la création et le suivi des paiements.

Message / situationCodeCause et solution
Clés API manquantes.401Aucun en-tête d'authentification transmis. Ajoutez TCHIN-PUBLIC-KEY et TCHIN-PRIVATE-KEY sur chaque requête.
Clés API invalides.401Les clés ne correspondent à aucun compte. Récupérez-les dans votre tableau de bord (menu API → Voir).
Compte suspendu.403Votre compte marchand est bloqué. Contactez le support ; aucune requête n'aboutira entre-temps.
Paiement momentanément indisponible.400Configuration de paiement incomplète côté Tchin (opérateur non provisionné). Contactez le support.
Paiement refusé.400En test : vérifiez les identifiants du compte de test. En live : le client a annulé ou l'opérateur a rejeté la transaction — le statut définitif arrivera par webhook.
Paiement introuvable.404Le token passé à GET /payments/{token}/status n'existe pas. Vérifiez qu'il provient bien de la réponse de création.
Champ amount requis / hors bornes.422Le montant est un entier en FCFA, sans décimale, entre 100 et 100 000 000. Corrigez et renvoyez.

Erreurs fréquentes — Déboursement

Ces messages concernent l'initiation et l'exécution des retraits. Le déboursement est disponible en mode live uniquement.

Message / situationCodeCause et solution
withdraw_mode non pris en charge.400Le code opérateur transmis n'est pas reconnu. Utilisez une valeur exacte de la liste des withdraw_mode (ex. wave-senegal, mtn-ci).
Solde insuffisant sur le pays SN.400Le débit vise le solde du pays associé au withdraw_mode. La réponse inclut un champ balance. Réapprovisionnez le pays concerné, puis réessayez.
Déboursement introuvable.404Le disburse_token n'existe pas ou n'appartient pas à votre compte. Vérifiez la valeur retournée par POST /disburse/initiate.
Échec du déboursement.400L'exécution a échoué côté opérateur. Interrogez le statut ; s'il est failed, relancez une nouvelle transaction (l'ancienne n'a rien débité).
Champ account_alias requis.422Renseignez le numéro du bénéficiaire, sans indicatif pays (ex. 771234567), 30 caractères maximum.

Statuts d'un déboursement

Un déboursement suit un cycle de vie précis. À chaque étape correspond une action recommandée. Rien n'est débité tant que la transaction n'a pas atteint l'état success, et le débit est idempotent (il ne s'applique qu'une fois).

StatutSignificationQue faire
createdLa transaction est initiée mais pas encore exécutée. Aucun débit.Appelez POST /disburse/submit avec le disburse_token pour l'exécuter.
pendingExécution en cours côté opérateur (traitement asynchrone).Patientez, puis interrogez POST /disburse/status. Ne resoumettez pas et ne re-débitez pas.
successFonds transférés au bénéficiaire. Le solde du pays est débité.Terminé : vous pouvez clore la transaction dans votre système.
failedLe transfert a échoué. Aucun débit n'a été appliqué.Informez le bénéficiaire, vérifiez le numéro et l'opérateur, puis relancez une nouvelle transaction si besoin.

ℹ️ Un statut pending n'est pas un échec. Il signifie « en cours ». Rejouer l'opération à cet instant risquerait de créer un doublon : interrogez plutôt le statut jusqu'à obtenir success ou failed.

Gérer les erreurs en pratique

Le patron suivant centralise le traitement : il distingue les incidents serveur (à réessayer), la validation 422 (à corriger côté client) et les erreurs métier (success: false). Adaptez-le à chaque endpoint.

<?php
// Appel générique à l'API Tchin puis gestion homogène des erreurs.
$ch = curl_init('https://tchin.tech/api/v1/disburse/initiate');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'TCHIN-PUBLIC-KEY: tchin_pk_xxxxxxxx',
        'TCHIN-PRIVATE-KEY: tchin_sk_xxxxxxxx',
        'Content-Type: application/json',
        'Accept: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'account_alias' => '771234567',
        'amount'        => 5000,
        'withdraw_mode' => 'wave-senegal',
    ]),
]);

$body   = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($body, true);

// 1) Erreur réseau / serveur
if ($status >= 500) {
    // Réessayez plus tard (avec backoff). Ne considérez rien comme fait.
    throw new RuntimeException('Tchin indisponible, réessayez.');
}

// 2) Validation Laravel (422) : champ manquant ou mal formé
if ($status === 422) {
    foreach ($data['errors'] ?? [] as $champ => $messages) {
        // Affichez $messages[0] à côté du champ $champ
    }
    return;
}

// 3) Erreur métier : toujours vérifier "success"
if (empty($data['success'])) {
    // $data['message'] contient la cause (solde insuffisant, mode non pris en charge…)
    // $data['balance'] est présent en cas de solde insuffisant.
    error_log('Tchin: ' . ($data['message'] ?? 'erreur inconnue'));
    return;
}

// 4) Succès
$disburseToken = $data['disburse_token'];
// Node.js — gestion homogène des réponses Tchin
async function callTchin(path, payload) {
  const res = await fetch(`https://tchin.tech/api/v1${path}`, {
    method: 'POST',
    headers: {
      'TCHIN-PUBLIC-KEY': process.env.TCHIN_PUBLIC_KEY,
      'TCHIN-PRIVATE-KEY': process.env.TCHIN_PRIVATE_KEY,
      'Content-Type': 'application/json',
      'Accept': 'application/json',
    },
    body: JSON.stringify(payload),
  });

  // 500+ : incident serveur, à réessayer avec backoff
  if (res.status >= 500) {
    throw new Error('Tchin indisponible, réessayez plus tard.');
  }

  const data = await res.json();

  // 422 : erreurs de validation Laravel, champ par champ
  if (res.status === 422) {
    const details = Object.entries(data.errors || {})
      .map(([champ, msgs]) => `${champ}: ${msgs[0]}`)
      .join(' | ');
    throw new Error(`Validation: ${details}`);
  }

  // Erreur métier : toujours tester success
  if (!data.success) {
    const err = new Error(data.message || 'Erreur Tchin');
    err.balance = data.balance; // présent si solde insuffisant
    throw err;
  }

  return data;
}
# Python (requests) — gestion homogène des erreurs Tchin
import os
import requests

def call_tchin(path, payload):
    res = requests.post(
        f"https://tchin.tech/api/v1{path}",
        json=payload,
        headers={
            "TCHIN-PUBLIC-KEY": os.environ["TCHIN_PUBLIC_KEY"],
            "TCHIN-PRIVATE-KEY": os.environ["TCHIN_PRIVATE_KEY"],
            "Content-Type": "application/json",
            "Accept": "application/json",
        },
        timeout=30,
    )

    # 500+ : incident serveur — réessayez avec backoff
    if res.status_code >= 500:
        raise RuntimeError("Tchin indisponible, réessayez plus tard.")

    data = res.json()

    # 422 : validation Laravel, détaillée champ par champ
    if res.status_code == 422:
        details = {champ: msgs[0] for champ, msgs in data.get("errors", {}).items()}
        raise ValueError(f"Validation: {details}")

    # Erreur métier : toujours vérifier "success"
    if not data.get("success"):
        raise RuntimeError(data.get("message", "Erreur Tchin"))

    return data

Conseils de robustesse

  • Vérifiez toujours le champ success, même sur un 200 : un code HTTP favorable ne garantit pas que l'opération a réussi.
  • Pour les paiements, la source de vérité est le webhook, pas le retour navigateur ni la seule réponse de création — le client peut fermer l'onglet avant la redirection.
  • Pour un déboursement pending, ne re-soumettez jamais : interrogez le statut. Rejouer une opération en cours peut provoquer un double transfert.
  • Sur un 500, réessayez avec un délai croissant (backoff) et ne considérez pas l'opération comme effectuée tant que vous n'avez pas confirmé son statut.
  • Rendez vos traitements idempotents : identifiez chaque paiement par son reference (token) et n'appliquez l'effet (marquer payé, livrer) qu'une seule fois.
  • Stockez systématiquement le token ou le disburse_token : c'est votre seul moyen de retrouver et de vérifier une transaction ultérieurement.
  • Journalisez le message renvoyé lors d'un success: false : il contient la cause exacte et facilite grandement le support.

Pour aller plus loin sur la validation asynchrone des paiements, consultez la page Webhooks.

Base API : https://tchin.tech/api/v1 · Tchin Documentation

Connexion / Inscription

En vous inscrivant, vous acceptez notre politique de confidentialité.

Entrez le code

Code envoyé à .