Encaisser un paiement

L'encaissement (checkout hébergé) est la façon la plus simple de faire payer vos clients par Mobile Money. Vous n'avez ni page de paiement à construire, ni opérateur à intégrer un par un : Tchin héberge une page de paiement sécurisée sur laquelle le client choisit son pays, son opérateur (Orange Money, MTN, Moov, Wave, Yas…), saisit son numéro et valide. Vous recevez ensuite la confirmation par webhook.

Le parcours en 4 temps

  1. Créer le paiement — votre serveur appelle POST /payments et reçoit une payment_url ainsi qu'un token.
  2. Rediriger le navigateur du client vers cette payment_url.
  3. Le client paie sur la page hébergée Tchin (choix pays + opérateur, saisie du numéro, OTP).
  4. Confirmation — le client est renvoyé sur votre return_url, et Tchin notifie votre callback_url (le webhook est la source de vérité pour valider la commande).

💡 Toute la création de paiement se fait côté serveur. Ne placez jamais votre clé secrète (tchin_sk_…) dans une page web ou une application mobile.

Créer un paiement

POST /api/v1/payments

Cette requête crée un paiement en attente et renvoie l'URL de la page de paiement. Elle ne déplace aucun argent : le débit du client survient seulement lorsqu'il paie sur la page hébergée.

Paramètres du corps (JSON)

ChampTypeRequisDescription
amountentierOuiMontant en FCFA (XOF ; XAF au Cameroun), sans décimales. Entre 100 et 100 000 000.
descriptiontexteNonLibellé affiché au client sur la page de paiement (max 255 caractères). Ex. « Commande #1024 ».
envtexteNontest (défaut) ou live. En test, aucun argent réel n'est déplacé.
return_urlURLNonPage de votre site où renvoyer le client après un paiement réussi (max 500 caractères).
cancel_urlURLNonPage de retour en cas d'annulation. Par défaut : la return_url.
callback_urlURLNonURL de votre webhook pour CE paiement. Remplace le webhook par défaut de l'application (max 500 caractères).
fees_on_customerbooléenNonSi true, la commission Tchin est ajoutée au montant payé par le client. Par défaut : le réglage de votre application.

Exemple de requête

curl -X POST https://tchin.tech/api/v1/payments \
  -H "TCHIN-PUBLIC-KEY: tchin_pk_xxxxxxxx" \
  -H "TCHIN-PRIVATE-KEY: tchin_sk_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
        "amount": 5000,
        "description": "Commande #1024",
        "env": "live",
        "return_url": "https://monsite.com/merci",
        "cancel_url": "https://monsite.com/panier",
        "callback_url": "https://monsite.com/tchin/webhook",
        "fees_on_customer": false
      }'
<?php
// Création d'un paiement — À EXÉCUTER CÔTÉ SERVEUR uniquement.
// Ne placez JAMAIS la clé secrète dans une page web ou une app mobile.

$body = [
    'amount'           => 5000,                              // FCFA (XOF), entier, min 100
    'description'      => 'Commande #1024',
    'env'             => 'live',                             // "test" ou "live"
    'return_url'      => 'https://monsite.com/merci',
    'cancel_url'      => 'https://monsite.com/panier',
    'callback_url'    => 'https://monsite.com/tchin/webhook',
    'fees_on_customer'=> false,
];

$ch = curl_init('https://tchin.tech/api/v1/payments');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'TCHIN-PUBLIC-KEY: '  . getenv('TCHIN_PUBLIC_KEY'),
        'TCHIN-PRIVATE-KEY: ' . getenv('TCHIN_SECRET_KEY'),
        'Content-Type: application/json',
        'Accept: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode($body),
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$data = json_decode($response, true);

if ($httpCode === 200 && ($data['success'] ?? false)) {
    // 1) Mémorisez $data['token'] avec votre commande (statut « en attente »).
    // 2) Redirigez le client vers la page de paiement hébergée.
    header('Location: ' . $data['payment_url']);
    exit;
}

// Sinon : afficher/journaliser $data['message'].
http_response_code(400);
echo $data['message'] ?? 'Erreur lors de la création du paiement.';
// Node.js (18+) / Express — création d'un paiement côté serveur.
import express from 'express';
const app = express();

app.post('/payer', async (req, res) => {
  try {
    const r = await fetch('https://tchin.tech/api/v1/payments', {
      method: 'POST',
      headers: {
        'TCHIN-PUBLIC-KEY':  process.env.TCHIN_PUBLIC_KEY,
        'TCHIN-PRIVATE-KEY': process.env.TCHIN_SECRET_KEY,
        'Content-Type': 'application/json',
        'Accept': 'application/json',
      },
      body: JSON.stringify({
        amount: 5000,                                  // FCFA (XOF), entier, min 100
        description: 'Commande #1024',
        env: 'live',                                   // "test" ou "live"
        return_url: 'https://monsite.com/merci',
        cancel_url: 'https://monsite.com/panier',
        callback_url: 'https://monsite.com/tchin/webhook',
        fees_on_customer: false,
      }),
    });

    const data = await r.json();

    if (!r.ok || !data.success) {
      return res.status(400).send(data.message || 'Paiement impossible.');
    }

    // Mémorisez data.token avec la commande, puis redirigez le client.
    res.redirect(data.payment_url);
  } catch (e) {
    res.status(500).send('Erreur serveur.');
  }
});

app.listen(3000);
# Python (requests) — création d'un paiement côté serveur.
import os
import requests

resp = requests.post(
    'https://tchin.tech/api/v1/payments',
    headers={
        'TCHIN-PUBLIC-KEY':  os.environ['TCHIN_PUBLIC_KEY'],
        'TCHIN-PRIVATE-KEY': os.environ['TCHIN_SECRET_KEY'],
        'Accept': 'application/json',
    },
    json={
        'amount': 5000,                                  # FCFA (XOF), entier, min 100
        'description': 'Commande #1024',
        'env': 'live',                                   # "test" ou "live"
        'return_url': 'https://monsite.com/merci',
        'cancel_url': 'https://monsite.com/panier',
        'callback_url': 'https://monsite.com/tchin/webhook',
        'fees_on_customer': False,
    },
    timeout=30,
)

data = resp.json()

if resp.status_code == 200 and data.get('success'):
    token = data['token']            # à mémoriser avec la commande
    payment_url = data['payment_url']  # rediriger le client vers cette URL
    # return redirect(payment_url)
else:
    raise Exception(data.get('message', 'Paiement impossible.'))

Exemple de réponse

Réponse
{
  "success": true,
  "token": "abc123def456ghi789",
  "payment_url": "https://tchin.tech/pay/abc123def456ghi789",
  "env": "live"
}
ChampDescription
successBooléen. Vérifiez-le toujours en plus du code HTTP 200.
tokenRéférence unique du paiement. Mémorisez-la avec votre commande : elle vous servira à suivre le statut et à recouper les webhooks.
payment_urlURL de la page de paiement hébergée vers laquelle rediriger le client.
envEnvironnement effectif du paiement (test ou live).

Rediriger le client vers payment_url

Une fois la payment_url reçue, il ne vous reste qu'à y envoyer le client. Deux options :

  • Redirection HTTP 302 depuis votre serveur (le plus courant) — voir header('Location: …') et res.redirect(…) dans les exemples ci-dessus.
  • Lien ou bouton : affichez simplement un bouton « Payer » pointant sur la payment_url.

Sur cette page, le client réalise l'intégralité du paiement (choix du pays, de l'opérateur, saisie du numéro, validation OTP). Vous n'avez rien d'autre à coder côté paiement. Pour connaître les opérateurs disponibles par pays, consultez Moyens de paiement.

⚠️ Une payment_url correspond à un seul paiement, à usage unique. Pour chaque commande, créez un nouveau paiement.

Frais : absorbés ou répercutés (fees_on_customer)

Tchin prélève une commission à l'encaissement (dès 3,5 %, variable selon le pays et votre volume). Le paramètre fees_on_customer détermine qui la supporte :

  • false (frais absorbés) — le client paie exactement le amount demandé ; la commission est déduite de votre part. Vous êtes crédité du net.
  • true (frais répercutés) — le montant est majoré de la commission sur la page de paiement ; le client paie plus, et vous recevez l'intégralité du amount.

Si vous ne fournissez pas ce champ, c'est le réglage par défaut de votre application (tableau de bord) qui s'applique.

Comparaison (amount = 5000)
// fees_on_customer = false  (frais absorbés par le marchand)
//   Le client paie exactement 5 000 FCFA.
//   Commission Tchin (ex. 3,5 % = 175) déduite de votre part.
//   Vous êtes crédité de : net = 5000 - 175 = 4825 FCFA.

// fees_on_customer = true   (frais répercutés au client)
//   Le montant est majoré de la commission sur la page de paiement.
//   Le client paie 5 175 FCFA.
//   Vous êtes crédité de la totalité : 5000 FCFA.

Retour du client : return_url et cancel_url

Ces deux URL contrôlent où le navigateur du client atterrit à la fin du parcours :

  • return_url — page affichée après un paiement réussi (ou, à défaut de cancel_url, après une annulation).
  • cancel_url — page affichée si le client annule. Par défaut, elle vaut la return_url.

Toutes deux sont optionnelles : sans elles, Tchin renvoie le client sur la page d'origine. Tchin ajoute deux paramètres à l'URL de retour :

ParamètreValeur
statussuccess (payé) ou cancel (annulé).
tokenLa référence du paiement (identique à celle reçue à la création).

Exemple d'URL de retour : https://monsite.com/merci?status=success&token=abc123def456ghi789

PHP — votre page de retour
<?php
// Votre return_url — page où le client atterrit après le paiement.
// Tchin y ajoute deux paramètres : status et token.
$status = $_GET['status'] ?? null;   // "success" ou "cancel"
$token  = $_GET['token']  ?? null;

if ($status === 'success') {
    // Afficher un écran « Merci ». NE livrez PAS encore : attendez le webhook.
    echo 'Merci ! Votre paiement est en cours de confirmation.';
} elseif ($status === 'cancel') {
    echo 'Paiement annulé. Vous pouvez réessayer.';
}
// La commande n'est validée/livrée QUE lorsque le webhook confirme "completed".

⚠️ Le retour navigateur sert uniquement à l'affichage. Le client peut fermer son onglet avant de revenir : ne validez et ne livrez jamais une commande sur ce seul retour. La source de vérité est le webhook.

Suivre l'état d'un paiement

GET /api/v1/payments/{token}/status

À tout moment, interrogez l'état d'un paiement grâce à son token. Utile en complément du webhook — par exemple depuis votre page de retour, ou pour rattraper un webhook manqué.

curl https://tchin.tech/api/v1/payments/abc123def456ghi789/status \
  -H "TCHIN-PUBLIC-KEY: tchin_pk_xxxxxxxx" \
  -H "TCHIN-PRIVATE-KEY: tchin_sk_xxxxxxxx" \
  -H "Accept: application/json"
<?php
// Vérifier l'état d'un paiement à partir de son token.
$token = 'abc123def456ghi789';

$ch = curl_init('https://tchin.tech/api/v1/payments/' . $token . '/status');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'TCHIN-PUBLIC-KEY: '  . getenv('TCHIN_PUBLIC_KEY'),
        'TCHIN-PRIVATE-KEY: ' . getenv('TCHIN_SECRET_KEY'),
        'Accept: application/json',
    ],
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);

// $data['status'] : pending | completed | failed | cancelled
if (($data['status'] ?? null) === 'completed') {
    // Paiement confirmé.
}
// Node.js (18+) — état d'un paiement.
const token = 'abc123def456ghi789';

const r = await fetch(`https://tchin.tech/api/v1/payments/${token}/status`, {
  headers: {
    'TCHIN-PUBLIC-KEY':  process.env.TCHIN_PUBLIC_KEY,
    'TCHIN-PRIVATE-KEY': process.env.TCHIN_SECRET_KEY,
    'Accept': 'application/json',
  },
});

const data = await r.json();
// data.status : pending | completed | failed | cancelled
console.log(data.status, data.amount, data.currency);
# Python (requests) — état d'un paiement.
import os
import requests

token = 'abc123def456ghi789'

r = requests.get(
    f'https://tchin.tech/api/v1/payments/{token}/status',
    headers={
        'TCHIN-PUBLIC-KEY':  os.environ['TCHIN_PUBLIC_KEY'],
        'TCHIN-PRIVATE-KEY': os.environ['TCHIN_SECRET_KEY'],
        'Accept': 'application/json',
    },
    timeout=30,
)

data = r.json()
# data['status'] : pending | completed | failed | cancelled
print(data['status'], data['amount'], data['currency'])

Exemple de réponse

Réponse
{
  "success": true,
  "token": "abc123def456ghi789",
  "status": "completed",
  "amount": 5000,
  "currency": "XOF",
  "env": "live",
  "customer": {
    "name": "Awa Diop",
    "email": "awa@example.com",
    "phone": "+221771234567"
  }
}
ChampDescription
statuspending (en attente), completed (payé), failed (échoué) ou cancelled (annulé).
amountMontant du paiement, en entier FCFA.
currencyDevise, généralement XOF.
envEnvironnement du paiement (test ou live).
customerCoordonnées du payeur (nom, email, téléphone), lorsqu'elles sont connues.

Un token inconnu renvoie une erreur 404.

💡 Ne « bouclez » pas sur cet endpoint en attendant qu'un paiement passe à completed. Laissez le webhook vous prévenir, et servez-vous du statut comme d'un simple contrôle ponctuel.

Tester votre intégration (mode test)

Avant la production, validez tout le parcours en passant "env": "test" à la création du paiement. Le paiement est alors entièrement simulé : aucun argent réel n'est déplacé et votre solde n'est pas crédité.

  • Utilisez le compte de test fictif de votre tableau de bord (menu API → « Compte test ») : email, numéro et mot de passe factices à saisir sur la page de paiement.
  • Tchin envoie tout de même un webhook de test (champ mode: test) : vous validez ainsi la réception et la vérification du hash sans risque.
  • Le retour navigateur (return_url / cancel_url) fonctionne exactement comme en production.

Quand tout est vert en test, passez simplement à "env": "live" pour encaisser réellement.

Exemple complet, de bout en bout

Voici l'enchaînement typique pour une commande de 5 000 FCFA, de la création du paiement jusqu'au point où le webhook prend le relais :

PHP — parcours complet
<?php
// ---------------------------------------------------------------
// EXEMPLE DE BOUT EN BOUT (PHP) : payer une commande de 5 000 FCFA.
// ---------------------------------------------------------------

// 1) L'ACHETEUR clique « Payer » -> votre serveur crée le paiement.
$ch = curl_init('https://tchin.tech/api/v1/payments');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'TCHIN-PUBLIC-KEY: '  . getenv('TCHIN_PUBLIC_KEY'),
        'TCHIN-PRIVATE-KEY: ' . getenv('TCHIN_SECRET_KEY'),
        'Content-Type: application/json',
        'Accept: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'amount'       => 5000,
        'description'  => 'Commande #1024',
        'env'          => 'live',
        'return_url'   => 'https://monsite.com/merci',
        'callback_url' => 'https://monsite.com/tchin/webhook',
    ]),
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);

// 2) On enregistre le token AVEC la commande (statut = "en attente").
$orders[$data['token']] = ['ref' => 'CMD-1024', 'status' => 'pending'];

// 3) On redirige le client vers la page de paiement hébergée Tchin.
header('Location: ' . $data['payment_url']);   // https://tchin.tech/pay/...
exit;

// 4) Le client choisit SN + Wave, saisit 771234567, valide l'OTP -> il paie.

// 5) Tchin appelle votre callback_url (webhook) : c'est LÀ qu'on livre.
//    (voir la page Webhooks pour vérifier data[hash] puis marquer "payé".)

Pour aller plus loin

  • Webhooks — recevoir la confirmation, vérifier le hash et livrer la commande (source de vérité).
  • Moyens de paiement — la liste des pays et opérateurs, avec leur disponibilité du moment.
  • Erreurs & statuts — comprendre les codes 400/401/403/404/422 et les messages métier.
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é à .