Paiement direct — sans redirection

Par défaut, l'encaissement Tchin fonctionne par redirection : vous créez un paiement, puis vous envoyez le client sur notre page de paiement hébergée. Simple, rien à gérer côté design ni sécurité.

Le paiement direct vous donne une seconde option : vous construisez votre propre page de paiement (votre design, votre tunnel) et vous déclenchez la charge depuis votre serveur, sans jamais rediriger le client vers Tchin. En contrepartie, c'est vous qui gérez le choix de l'opérateur et le déroulé propre à chaque moyen (certains valident par notification sur le téléphone, d'autres par code OTP, d'autres par redirection vers l'opérateur).

Quel mode choisir ?

CritèreRedirection (hébergé)Direct (cette page)
Effort d'intégrationMinimalPlus élevé (vous gérez l'UI et les flux)
Design de la page de paiementFourni par Tchin100 % le vôtre
Choix de l'opérateurFait par le client sur notre pageFait chez vous (via /methods)
Gestion OTP / redirection opérateurGérée par TchinÀ votre charge
Sécurité (PCI, secrets)Aucune expositionAppels serveur uniquement

Bonne nouvelle : c'est le même paiement. Vous créez toujours le paiement avec POST /payments. Ensuite, soit vous redirigez vers payment_url (mode hébergé), soit vous appelez /charge (mode direct). Vous pouvez donc proposer les deux dans la même appli.

Le principe en 4 étapes

  1. Lister les opérateurs disponibles par pays — GET /api/v1/methods.
  2. Créer le paiementPOST /api/v1/payments → vous récupérez un token.
  3. Charger directementPOST /api/v1/payments/{token}/charge avec l'opérateur + le numéro.
  4. Suivre le résultatGET /api/v1/payments/{token}/status et/ou votre webhook.

Étape 1 — Lister les moyens de paiement

GET /api/v1/methods renvoie, pour chaque pays, les opérateurs et surtout leur type de flux (flow). C'est ce champ qui vous dit quoi faire sur votre page.

JSON
{
  "success": true,
  "countries": [
    {
      "country": "CI",
      "country_name": "Côte d’Ivoire",
      "dial": "+225",
      "methods": [
        { "code": "orange-money-ci", "name": "Orange Money", "flow": "otp",      "needs_otp": true,  "available": true },
        { "code": "wave-ci",         "name": "Wave",         "flow": "redirect", "needs_otp": false, "available": true },
        { "code": "mtn-ci",          "name": "MTN",          "flow": "push",     "needs_otp": false, "available": true }
      ]
    }
  ]
}
ChampDescription
codeIdentifiant de l'opérateur à renvoyer dans /charge (ex. orange-money-ci).
flowpush, otp ou redirect — voir le tableau plus bas.
needs_otptrue si le client doit fournir un code OTP (équivaut à flow = "otp").
availablefalse si l'opérateur est momentanément hors service — désactivez-le dans votre UI.

Étape 2 — Créer le paiement

Identique au mode hébergé : POST /api/v1/payments avec amount (et éventuellement return_url, callback_url…). Voir la page Encaissement. Vous récupérez un token — gardez-le.

Astuce : pour les opérateurs à flow = "redirect" (Wave, Orange Money QR…), définissez un return_url à la création : le client y sera renvoyé sur votre site après avoir payé chez l'opérateur.

Étape 3 — Charger directement

POST /api/v1/payments/{token}/charge

ParamètreRequisDescription
countryouiPays du client (code ISO 2 lettres, ex. CI).
operatorouiLe code d'un moyen renvoyé par /methods.
phoneouiNuméro mobile money du client.
otpsi flow = otpCode de paiement fourni par le client (voir le flux OTP).
passworden testEn mode test uniquement : mot de passe de votre compte de test (voir plus bas).
customer_name / customer_emailnonOptionnels ; générés automatiquement s'ils sont absents.
# 1) Créez d'abord le paiement (voir « Encaissement ») → vous obtenez un "token".
# 2) Puis chargez-le DIRECTEMENT, sans rediriger le client :
curl -X POST https://tchin.tech/api/v1/payments/TOKEN/charge \
  -H "TCHIN-PUBLIC-KEY: tchin_pk_xxxxxxxx" \
  -H "TCHIN-PRIVATE-KEY: tchin_sk_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
        "country": "CI",
        "operator": "orange-money-ci",
        "phone": "0700000000",
        "otp": "123456"
      }'
<?php
// Paiement DIRECT — CÔTÉ SERVEUR uniquement (ne jamais exposer la clé secrète).
// $token provient de POST /api/v1/payments.
$token = 'e56praamokkkk5';

$body = [
    'country'  => 'CI',                 // pays du client (code ISO 2 lettres)
    'operator' => 'orange-money-ci',    // code d'un moyen renvoyé par GET /methods
    'phone'    => '0700000000',         // numéro mobile money du client
    'otp'      => '123456',             // REQUIS seulement si le moyen a flow = "otp"
    // 'customer_name'  => 'Awa Diop',  // optionnels
    // 'customer_email' => 'awa@mail.com',
];

$ch = curl_init("https://tchin.tech/api/v1/payments/{$token}/charge");
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),
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);

// On agit selon "action" renvoyé :
switch ($data['action'] ?? null) {
    case 'confirm_on_phone':
        // Affichez « Validez le paiement sur votre téléphone », puis interrogez le statut.
        break;
    case 'redirect':
        // Redirigez le client vers l'opérateur (Wave, Orange Money QR…).
        header('Location: ' . $data['redirect_url']);
        exit;
    case 'otp_required':
        // Faites composer le code USSD au client, récupérez l'OTP, puis renvoyez CETTE requête avec "otp".
        break;
    default:
        if (! ($data['success'] ?? false)) {
            echo $data['message'] ?? 'Paiement refusé.';
        }
}
// Paiement DIRECT (Node 18+) — CÔTÉ SERVEUR. token vient de POST /api/v1/payments.
const token = 'e56praamokkkk5';

const r = await fetch(`https://tchin.tech/api/v1/payments/${token}/charge`, {
  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({
    country:  'CI',
    operator: 'orange-money-ci',   // code renvoyé par GET /methods
    phone:    '0700000000',
    otp:      '123456',            // seulement si flow === "otp"
  }),
});
const data = await r.json();

switch (data.action) {
  case 'redirect':         return res.redirect(data.redirect_url);   // Wave, OM…
  case 'confirm_on_phone': /* afficher "validez sur le téléphone" + poller le statut */ break;
  case 'otp_required':     /* demander l'OTP au client, renvoyer avec otp */ break;
  default: if (!data.success) console.error(data.message);
}
# Paiement DIRECT (requests) — CÔTÉ SERVEUR. token vient de POST /api/v1/payments.
import os, requests

token = 'e56praamokkkk5'
resp = requests.post(
    f'https://tchin.tech/api/v1/payments/{token}/charge',
    headers={
        'TCHIN-PUBLIC-KEY':  os.environ['TCHIN_PUBLIC_KEY'],
        'TCHIN-PRIVATE-KEY': os.environ['TCHIN_SECRET_KEY'],
        'Accept': 'application/json',
    },
    json={
        'country':  'CI',
        'operator': 'orange-money-ci',   # code renvoyé par GET /methods
        'phone':    '0700000000',
        'otp':      '123456',            # seulement si flow == "otp"
    },
    timeout=45,
)
data = resp.json()

action = data.get('action')
if action == 'redirect':
    redirect(data['redirect_url'])            # Wave, Orange Money…
elif action == 'confirm_on_phone':
    pass                                      # afficher "validez sur le téléphone" + poller le statut
elif action == 'otp_required':
    pass                                      # demander l'OTP, renvoyer avec "otp"
elif not data.get('success'):
    print(data.get('message'))                # échec

Gérer la réponse selon le flow

La réponse contient un champ action qui vous dit exactement quoi faire ensuite :

flowaction renvoyéCe que VOUS faites sur votre page
pushconfirm_on_phoneAffichez « Validez le paiement sur votre téléphone », puis interrogez le statut jusqu'à completed.
otpotp_required (si pas d'OTP)Demandez au client de composer le code USSD de son opérateur, faites-lui saisir l'OTP reçu, puis rappelez /charge avec otp.
redirectredirectRedirigez le client vers redirect_url (Wave, Orange Money…). Il revient ensuite sur votre return_url.

Exemples de réponses

Validation sur téléphone (push) :

JSON
{
  "success": true,
  "token": "e56praamokkkk5",
  "status": "pending",
  "action": "confirm_on_phone",
  "message": "Paiement en cours, le client doit valider sur son téléphone."
}

Redirection opérateur (Wave, OM QR…) :

JSON
{
  "success": true,
  "token": "e56praamokkkk5",
  "status": "pending",
  "action": "redirect",
  "redirect_url": "https://pay.wave.com/c/cos-XXXXXXXX"
}

OTP requis — l'opérateur exige un code ; recommencez avec otp :

JSON
{
  "success": false,
  "token": "e56praamokkkk5",
  "status": "failed",
  "action": "otp_required",
  "message": "Cet opérateur exige un code de paiement (OTP). Faites composer le code USSD au client, puis renvoyez la requête avec « otp »."
}

Échec :

JSON
{
  "success": false,
  "token": "e56praamokkkk5",
  "status": "failed",
  "message": "Solde insuffisant / opérateur momentanément indisponible."
}

Étape 4 — Confirmer le paiement

Un status: "pending" n'est pas un paiement confirmé. La confirmation finale arrive de deux façons (utilisez au moins l'une) :

  • Webhook (recommandé) : Tchin appelle votre callback_url dès que le paiement est completed ou failed. Voir Webhooks.
  • Polling : interrogez GET /api/v1/payments/{token}/status toutes les quelques secondes jusqu'à obtenir completed ou failed.

Ne livrez jamais la commande sur un simple pending : attendez completed.

Mode test (sandbox)

En environnement de test (env: "test" à la création), l'appel /charge exige un champ password : le mot de passe de votre compte de test. Cela reproduit fidèlement un vrai paiement sans mouvement d'argent, et déclenche un webhook de test pour valider votre intégration de bout en bout.

Sécurité

  • Tous ces appels contiennent votre clé secrète : effectuez-les uniquement côté serveur. Jamais dans une page web, une app mobile ou du JavaScript navigateur.
  • Ne montrez jamais le numéro/OTP d'un client à un tiers ; ne journalisez pas les OTP.
  • Vérifiez la signature de vos webhooks avant de créditer une commande.
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é à .