UnitechPay API

Documentation technique

Introduction

Présentation de la plateforme et de ses capacités

L'API UnitechPay permet d'intégrer les paiements mobile money dans vos applications web, mobiles ou e-commerce. Au Sénégal, elle gère Wave et Orange Money ; elle couvre également d'autres pays d'Afrique de l'Ouest (Côte d'Ivoire, Togo, Burkina Faso, Bénin). Elle gère l'encaissement, les retraits automatiques, les commissions et les webhooks pour une expérience marchande complète.

URL de base

https://api.unitech.sn/api.php

Fonctionnalités

Paiements sécurisés Wave et Orange Money (Sénégal)
Paiements multi-pays : Côte d'Ivoire, Togo, Burkina Faso, Bénin
3 options Orange Money : QR Code, Max It, OM standard
Soldes séparés : Wave (sold_wave), Orange (sold_om), autres pays (sold_intl)
Retraits automatiques en temps réel (Sénégal et autres pays)
Commission personnalisable par marchand
Webhooks temps réel avec signature HMAC-SHA256
Plugin WordPress / WooCommerce officiel

Authentification

Toutes les requêtes nécessitent une clé API valide

Incluez votre clé API dans le header Authorization de chaque requête.

Authorization: Bearer VOTRE_CLE_API

Obtenir une clé API

Créez un compte marchand sur la plateforme, puis générez votre clé depuis le tableau de bord dans la section Paramètres → API.

Important : Ne partagez jamais votre clé API publiquement. Elle donne accès à vos fonds et données de transactions.

Endpoints

Liste complète des endpoints disponibles

Méthode Endpoint Description
POST/api.php?action=create_wave_paymentCréer un paiement Wave (Sénégal)
POST/api.php?action=create_orange_qrGénérer un QR Code Orange Money (Sénégal)
POST/api.php?action=create_orange_maxitPaiement Max It Orange (Sénégal)
POST/api.php?action=create_orange_omPaiement Orange Money standard (Sénégal)
POST/api.php?action=create_intl_paymentPaiement autres pays : CI, TG, BF, BJ NOUVEAU
POST/api.php?action=withdraw_fundsRetrait automatique (Sénégal + autres pays) MàJ
POST/api.php?action=configure_webhookConfigurer les webhooks
GET/api.php?action=balanceConsulter les soldes Wave / Orange / autres pays MàJ
GET/api.php?action=transactionsLister les transactions
GET/api.php?action=withdrawalsLister les retraits
GET/api.php?action=commissionsLister les commissions

Solde marchand MàJ v1.2

Les fonds sont répartis sur trois soldes distincts

Les fonds encaissés sont crédités sur un solde distinct selon le canal utilisé par le client : sold_wave pour Wave (Sénégal), sold_om pour Orange Money (Sénégal) et sold_intl pour les paiements dans les autres pays.

sold_wave

Fonds Wave (Sénégal). Retirable via method=wave.

sold_om

Fonds Orange Money (Sénégal). Retirable via method=orange.

sold_intl

Fonds des autres pays (CI, TG, BF, BJ). Retirable via method=intl.

GET /api.php?action=balance

Réponse

{
  "success": true,
  "data": {
    "sold_wave":  45000,
    "sold_om":    12500,
    "sold_intl":  30000,
    "total":      87500,
    "currency":   "XOF"
  }
}

Paiements Wave — Sénégal

Créer une session de paiement Wave

POST /api.php?action=create_wave_payment

Paramètres

ParamètreTypeObligatoireDescription
amountnumber✅ OuiMontant en XOF
customer_numberstring✅ OuiNuméro de téléphone du client
descriptionstringNonDescription du paiement
callback_successstringNonURL de redirection après succès
callback_cancelstringNonURL de redirection après annulation

Exemple de requête

{
  "amount": 5000,
  "customer_number": "771234567",
  "description": "Commande #123",
  "callback_success": "https://monsite.com/payment/success",
  "callback_cancel":  "https://monsite.com/payment/cancel"
}

Réponse

{
  "success": true,
  "data": {
    "transaction_id": 1,
    "reference":      "wave_66a1b2c3d4e5f_1716542100",
    "payment_url":    "https://pay.wave.com/c/cos-1xxxxx",
    "amount":         5000,
    "status":         "pending",
    "callback_urls": {
      "success": "https://monsite.com/payment/success",
      "error":   "https://monsite.com/payment/cancel"
    }
  }
}

Après paiement réussi, le montant net (après commission) est automatiquement crédité sur sold_wave du marchand. La confirmation arrive via webhook.

Paiements Orange Money — Sénégal

Trois modes de paiement disponibles

QR Code

Image base64 à afficher, liens deep MAXIT et OM inclus.

create_orange_qr

Max It

Redirection vers l'app Max It avec lien deep link.

create_orange_maxit

Orange Money

Paiement OM standard avec redirection.

create_orange_om

1. QR Code Orange Money

POST/api.php?action=create_orange_qr

Paramètres

ParamètreTypeObligatoireDescription
amountnumber✅ OuiMontant en XOF
referencestringNonRéférence personnalisée
descriptionstringNonDescription
callback_successstringNonURL succès
callback_cancelstringNonURL annulation

Réponse

{
  "success": true,
  "data": {
    "transaction_id": 1,
    "reference":  "commande_123",
    "qr_code":    "data:image/png;base64,iVBORw0KGgo...",
    "deep_links": {
      "MAXIT": "https://sugu.orange-sonatel.com/np/dgjuu_xxx",
      "OM":    "https://orangenonevsn.page.link/zzz"
    },
    "amount":   5000,
    "status":   "pending",
    "type":     "qr_code",
    "validity": 300,
    "callback_urls": {
      "success": "https://monsite.com/success",
      "cancel":  "https://monsite.com/cancel"
    }
  }
}

2. Paiement Max It

POST/api.php?action=create_orange_maxit

Paramètres

ParamètreTypeObligatoireDescription
amountnumber✅ OuiMontant en XOF
customer_numberstring✅ OuiNuméro du client
descriptionstringNonDescription
callback_successstringNonURL succès
callback_cancelstringNonURL annulation

Réponse

{
  "success": true,
  "data": {
    "transaction_id": 2,
    "reference":    "orange_maxit_123456",
    "payment_url":  "https://sugu.orange-sonatel.com/np/dgjuu_xxx",
    "deep_links": {
      "MAXIT": "https://sugu.orange-sonatel.com/np/dgjuu_xxx",
      "OM":    "https://orangenonevsn.page.link/zzz"
    },
    "amount": 5000,
    "status": "pending",
    "type":   "maxit"
  }
}

3. Orange Money standard

POST/api.php?action=create_orange_om

Mêmes paramètres que Max It. La payment_url retournée est le deep link OM.

{
  "success": true,
  "data": {
    "transaction_id": 3,
    "reference":    "orange_om_123456",
    "payment_url":  "https://orangenonevsn.page.link/zzz",
    "amount": 5000,
    "status": "pending",
    "type":   "orange_money"
  }
}

Après paiement Orange Money réussi, le montant net est crédité sur sold_om du marchand.

Paiements autres pays NOUVEAU

Encaisser en Côte d'Ivoire, Togo, Burkina Faso et Bénin

Un seul endpoint gère l'encaissement mobile money dans les autres pays. Précisez le pays (country) et l'opérateur (operator). Selon l'opérateur, la réponse contient un lien de paiement, un QR Code et/ou une instruction à afficher au client. Les fonds nets sont crédités sur sold_intl.

Côte d'Ivoire CI

wave_money, orange_money, mtn_money, moov

Togo TG

moov, togocell

Burkina Faso BF

orange_money, wave_money, moov

Bénin BJ

moov, mtn_money

POST /api.php?action=create_intl_payment

Paramètres

ParamètreTypeObligatoireDescription
countrystring✅ OuiCode pays : CI, TG, BF, BJ
operatorstring✅ OuiOpérateur : wave_money, orange_money, mtn_money, moov, togocell
amountnumber✅ OuiMontant en XOF (minimum 100)
customer_numberstringRecommandéNuméro du client (format local ou international)
customer_namestringNonNom du client
customer_emailstringNonEmail du client
otpstringConditionnelCode OTP client — Orange Money Côte d'Ivoire uniquement (le client compose #144*82#). Ne pas utiliser pour le Burkina Faso.
descriptionstringNonDescription du paiement
callback_successstringNonURL de redirection après succès
callback_cancelstringNonURL de redirection après annulation

Exemple de requête

{
  "country":  "CI",
  "operator": "orange_money",
  "amount":   5000,
  "customer_number": "0700000000",
  "customer_name":   "Amadou Fall",
  "description":     "Commande #123",
  "callback_success": "https://monsite.com/success",
  "callback_cancel":  "https://monsite.com/cancel"
}

Réponse

{
  "success": true,
  "data": {
    "transaction_id": 12,
    "reference":      "bic_7_9f8e7d6c5b4a3210",
    "country":        "CI",
    "operator":       "orange_money",
    "amount":         5000,
    "status":         "pending",
    "provider":       "intl",
    "payment_url":    "https://pay.example.com/c/xxxx",
    "link":           "https://pay.example.com/c/xxxx",
    "redirect_url":   "",
    "qr_code":        "data:image/png;base64,iVBORw0KGgo...",
    "message":        "",
    "callback_urls": {
      "success": "https://monsite.com/success",
      "cancel":  "https://monsite.com/cancel"
    }
  }
}

Selon la réponse, présentez au client : le link (bouton de paiement), le qr_code (à scanner) ou le message (instruction USSD).

Après paiement réussi, le montant net (après commission d'encaissement) est crédité sur sold_intl ; la confirmation arrive via webhook.

Validation selon l'opérateur

Orange Money Côte d'Ivoire : affichez un champ OTP — le client compose #144*82#, reçoit un code, le saisit, et vous l'envoyez dans otp.

Orange Money Burkina Faso, MTN, Moov… : pas d'OTP. Affichez simplement le message renvoyé par l'API et laissez le client valider sur son téléphone (le webhook confirmera).

Commission (autres pays)

Elle dépend du pays et de l'opérateur, et se répartit sur deux moments : la part encaissement ici (opérateur + 0,5 % agrégateur + 0,5 % marge), la part retrait au moment du payout (opérateur + 0,5 % agrégateur). Exemple Wave CI : 2,6 % à l'encaissement.

Retraits automatiques MàJ v1.2

Transfert des fonds vers mobile money ou compte bancaire

Les retraits mobile money sont traités automatiquement en temps réel. Chaque méthode débite le solde correspondant. En cas d'échec du payout, les fonds sont automatiquement restitués au marchand.

method=wave

Débite sold_wave. Payout automatique instantané (Sénégal).

method=orange

Débite sold_om. Cash-in automatique instantané (Sénégal).

method=intl NOUVEAU

Débite sold_intl. Payout automatique dans les autres pays (CI, TG, BF, BJ).

method=bank

Débite sold_wave + sold_om au prorata. Traitement manuel 3-5 jours.

POST /api.php?action=withdraw_funds

Paramètres

ParamètreTypeObligatoireDescription
amountnumber✅ Oui Montant à retirer en XOF. Doit être ≤ au solde disponible de la méthode choisie.
methodstring✅ Oui wave → débite sold_wave
orange → débite sold_om
intl → débite sold_intl (autres pays)
bank → débite sold_wave + sold_om au prorata
accountstring✅ Oui Numéro mobile money destinataire, ou numéro de compte bancaire.
countrystring✅ si intl Pays de réception : CI, TG, BF, BJ.
operatorstring✅ si intl Opérateur de réception : wave_money, orange_money, mtn_money, moov.

Exemple — retrait Wave (Sénégal)

{
  "amount":  10000,
  "method":  "wave",
  "account": "771234567"
}

Exemple — retrait autres pays

{
  "amount":   10000,
  "method":   "intl",
  "country":  "CI",
  "operator": "orange_money",
  "account":  "0700000000"
}

Réponse — succès (mobile money)

{
  "success": true,
  "data": {
    "withdrawal_id":   42,
    "reference":       "WDR_7_1716542100_3829",
    "amount":          10000,
    "commission":      150,
    "commission_rate": 0.015,
    "net_amount":      9850,
    "method":          "wave",
    "status":          "processed",
    "note":            "Payout automatique effectue"
  }
}

Réponse — retrait autres pays (succès)

{
  "success": true,
  "data": {
    "withdrawal_id":  43,
    "reference":      "WDB_7_1716542200_4912",
    "amount":         10000,
    "commission":     160,
    "commission_rate": 0.016,
    "net_amount":     9840,
    "method":         "intl",
    "country":        "CI",
    "operator":       "orange_money",
    "status":         "processed",
    "note":           "Payout autres pays effectue"
  }
}

Réponse — payout échoué (fonds restitués)

{
  "success": false,
  "message": "Payout echoue, fonds restitues. Ref : WDR_7_1716542100_3829. Detail : ..."
}

Délais de traitement

Mobile money (Sénégal & autres pays) : payout automatique instantané

Virement bancaire : 3 à 5 jours ouvrés (traitement manuel)

Autres pays : la commission est répartie. Une part est prélevée à l'encaissement (opérateur + 0,5 % + 0,5 %), l'autre au retrait (opérateur + 0,5 %). Exemple Wave CI : 2,6 % à l'encaissement puis 2,0 % au retrait. Le Sénégal reste à 1,5 % / 1,5 %.

Webhooks

Notifications en temps réel sur les événements de votre compte

Configurer un webhook

POST/api.php?action=configure_webhook

Paramètres

ParamètreTypeObligatoireDescription
webhook_urlstring✅ OuiURL HTTPS de votre endpoint webhook
eventsarrayNonÉvénements à écouter (tous par défaut)

Événements disponibles

payment_completed — Paiement réussi
payment_failed — Paiement échoué
payment_expired — Paiement expiré
withdrawal_processed — Retrait traité
withdrawal_failed — Retrait échoué

Exemple de payload webhook

{
  "event":          "payment_completed",
  "transaction_id": 18,
  "reference":      "wave_66a1b2_1716540000",
  "amount":         5000,
  "status":         "completed",
  "method":         "wave",
  "commission":     75,
  "net_amount":     4925,
  "timestamp":      1716540300,
  "signed_at":      1716540300,
  "signature":      "hmac_sha256..."
}

Méthode 1 — par l'en-tête (HMAC du body brut)

$payload   = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_UNITECHPAY_SIGNATURE'] ?? '';
$expected  = hash_hmac('sha256', $payload, VOTRE_CLE_API);

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit('Signature invalide');
}
$data = json_decode($payload, true);

Méthode 2 — par le body CDN-proof

Recommandée derrière un CDN (Hostinger, Cloudflare…). La signature du body couvre une chaîne canonique de champs stables : event|reference|amount|status|signed_at.

$data = json_decode(file_get_contents('php://input'), true);

$signed = ($data['event']     ?? '') . '|'
        . ($data['reference'] ?? '') . '|'
        . ($data['amount']    ?? '') . '|'
        . ($data['status']    ?? '') . '|'
        . ($data['signed_at'] ?? '');

$expected = hash_hmac('sha256', $signed, VOTRE_CLE_API);

if (!hash_equals($expected, $data['signature'] ?? '')) {
    http_response_code(401);
    exit('Signature invalide');
}
// Traiter l'événement...

Sécurité : Vérifiez toujours la signature (en-tête ou body) avant de traiter un webhook. Votre clé API est le secret HMAC. Les deux méthodes sont équivalentes — utilisez celle qui passe sur votre hébergement.

Plugins & Intégrations

Solutions prêtes à l'emploi pour vos projets

Plugin WordPress Officiel

Intégrez le mobile money dans votre site WordPress ou boutique WooCommerce en quelques minutes.

Compatible WooCommerce
Webhooks intégrés
Installation en 1 clic
Interface responsive

Guide d'installation WordPress

1

Installation

Depuis votre admin WordPress : Extensions → Ajouter → Rechercher "UnitechPay" → Installer & Activer.

2

Configuration

Allez dans UnitechPay Settings et renseignez vos pays activés et votre numéro de réception.

3

WooCommerce

Activez UnitechPay comme méthode de paiement dans WooCommerce → Réglages → Paiements.

Codes d'erreur

Réponses HTTP retournées par l'API

Code HTTPMessageDescription
200SuccessRequête traitée avec succès
400Bad RequestParamètres manquants, invalides ou solde insuffisant
401UnauthorizedClé API manquante ou invalide
404Not FoundAction inconnue ou ressource introuvable
500Internal Server ErrorErreur serveur ou échec payout (fonds restitués)

Format des erreurs

{
  "success": false,
  "message": "Description de l'erreur",
  "code":    400
}

Exemples d'intégration

Code prêt à l'emploi dans différents langages

PHP — Paiement Wave + Consultation des soldes

<?php
$api_url = 'https://api.unitech.sn/api.php';
$api_key = 'VOTRE_CLE_API';

function unitechRequest($action, $data = [], $method = 'POST') {
    global $api_url, $api_key;
    $url = $api_url . '?action=' . $action;
    $ch  = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 30,
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_HTTPHEADER     => [
            'Authorization: Bearer ' . $api_key,
            'Content-Type: application/json'
        ],
    ]);
    if ($method === 'POST') {
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    }
    $response = curl_exec($ch);
    curl_close($ch);
    return json_decode($response, true);
}

// Consulter les soldes (Wave + Orange + autres pays)
$balance = unitechRequest('balance', [], 'GET');
echo "Wave : "       . $balance['data']['sold_wave'] . " XOF\n";
echo "Orange : "     . $balance['data']['sold_om']   . " XOF\n";
echo "Autres pays : ". $balance['data']['sold_intl'] . " XOF\n";
echo "Total : "      . $balance['data']['total']     . " XOF\n";

// Créer un paiement Wave (Sénégal)
$result = unitechRequest('create_wave_payment', [
    'amount'           => 5000,
    'customer_number'  => '771234567',
    'description'      => 'Commande #123',
    'callback_success' => 'https://monsite.com/success',
    'callback_cancel'  => 'https://monsite.com/cancel',
]);

if ($result['success']) {
    header('Location: ' . $result['data']['payment_url']);
    exit;
}

// Retrait Wave automatique
$withdrawal = unitechRequest('withdraw_funds', [
    'amount'  => 10000,
    'method'  => 'wave',
    'account' => '771234567',
]);

if ($withdrawal['success']) {
    echo "Retrait traité : " . $withdrawal['data']['net_amount'] . " XOF net\n";
    echo "Statut : "         . $withdrawal['data']['status'] . "\n";
}
?>

PHP — Paiement autres pays (CI, TG, BF, BJ)

<?php
// Encaissement Orange Money en Côte d'Ivoire
$result = unitechRequest('create_intl_payment', [
    'country'          => 'CI',
    'operator'         => 'orange_money',
    'amount'           => 5000,
    'customer_number'  => '0700000000',
    'customer_name'    => 'Amadou Fall',
    'description'      => 'Commande CI #45',
    'callback_success' => 'https://monsite.com/success',
    'callback_cancel'  => 'https://monsite.com/cancel',
]);

if ($result['success']) {
    $d = $result['data'];
    if (!empty($d['link']))    echo '<a href="' . $d['link'] . '">Payer</a>';
    if (!empty($d['qr_code'])) echo '<img src="' . $d['qr_code'] . '" alt="QR" />';
    if (!empty($d['message'])) echo '<p>' . $d['message'] . '</p>';
}

// Retrait des fonds "autres pays"
$withdrawal = unitechRequest('withdraw_funds', [
    'amount'   => 10000,
    'method'   => 'intl',
    'country'  => 'CI',
    'operator' => 'orange_money',
    'account'  => '0700000000',
]);

if ($withdrawal['success']) {
    echo "Retrait autres pays : " . $withdrawal['data']['net_amount'] . " XOF\n";
}
?>

Node.js — Classe UnitechPay complète

const axios = require('axios');

class UnitechPay {
    constructor(apiKey) {
        this.client = axios.create({
            baseURL: 'https://api.unitech.sn/api.php',
            headers: {
                'Authorization': `Bearer ${apiKey}`,
                'Content-Type': 'application/json',
            },
            timeout: 30000,
        });
    }

    async _post(action, data) {
        const res = await this.client.post(`?action=${action}`, data);
        return res.data;
    }

    async _get(action, params = {}) {
        const res = await this.client.get(`?action=${action}`, { params });
        return res.data;
    }

    // Soldes Wave + Orange + autres pays
    async getBalance() {
        return this._get('balance');
    }

    // Paiement Wave (Sénégal)
    async wavePayment(amount, phone, description, callbacks = {}) {
        return this._post('create_wave_payment', {
            amount, customer_number: phone, description,
            callback_success: callbacks.success,
            callback_cancel:  callbacks.cancel,
        });
    }

    // Paiement autres pays (CI, TG, BF, BJ)
    async intlPayment(country, operator, amount, phone, opts = {}) {
        return this._post('create_intl_payment', {
            country, operator, amount, customer_number: phone,
            customer_name: opts.name, description: opts.description,
            callback_success: opts.success, callback_cancel: opts.cancel,
        });
    }

    // Retrait automatique (wave | orange | intl | bank)
    async withdraw(params) {
        return this._post('withdraw_funds', params);
    }
}

// Utilisation
(async () => {
    const pay = new UnitechPay('VOTRE_CLE_API');

    // Soldes
    const bal = await pay.getBalance();
    console.log(`Wave: ${bal.data.sold_wave} — Orange: ${bal.data.sold_om} — Autres pays: ${bal.data.sold_intl}`);

    // Paiement autres pays
    const intl = await pay.intlPayment('CI', 'orange_money', 5000, '0700000000', {
        name: 'Amadou Fall', success: 'https://monapp.com/success', cancel: 'https://monapp.com/cancel',
    });
    console.log('Lien de paiement:', intl.data.payment_url);

    // Retrait autres pays
    const wd = await pay.withdraw({ amount: 10000, method: 'intl', country: 'CI', operator: 'orange_money', account: '0700000000' });
    console.log(`Retrait: ${wd.data.net_amount} XOF — ${wd.data.status}`);
})();

Python — Classe UnitechPay

import requests

class UnitechPay:
    BASE_URL = "https://api.unitech.sn/api.php"

    def __init__(self, api_key: str):
        self.session = requests.Session()
        self.session.headers.update({
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
        })

    def _post(self, action: str, data: dict) -> dict:
        r = self.session.post(f"{self.BASE_URL}?action={action}", json=data, timeout=30)
        r.raise_for_status()
        return r.json()

    def _get(self, action: str, params: dict = {}) -> dict:
        r = self.session.get(f"{self.BASE_URL}?action={action}", params=params, timeout=30)
        r.raise_for_status()
        return r.json()

    def get_balance(self) -> dict:
        return self._get("balance")

    def wave_payment(self, amount, phone, description="", success_url="", cancel_url=""):
        return self._post("create_wave_payment", {
            "amount": amount, "customer_number": phone,
            "description": description,
            "callback_success": success_url, "callback_cancel": cancel_url,
        })

    def intl_payment(self, country, operator, amount, phone="", **opts):
        return self._post("create_intl_payment", {
            "country": country, "operator": operator, "amount": amount,
            "customer_number": phone,
            "customer_name": opts.get("name", ""),
            "description": opts.get("description", ""),
            "callback_success": opts.get("success_url", ""),
            "callback_cancel": opts.get("cancel_url", ""),
        })

    def withdraw(self, **params) -> dict:
        return self._post("withdraw_funds", params)


# Utilisation
if __name__ == "__main__":
    api = UnitechPay("VOTRE_CLE_API")

    # Soldes
    bal = api.get_balance()
    print(f"Wave: {bal['data']['sold_wave']} | Orange: {bal['data']['sold_om']} | Autres pays: {bal['data']['sold_intl']}")

    # Paiement autres pays
    p = api.intl_payment("CI", "orange_money", 5000, "0700000000", name="Amadou Fall")
    print("URL:", p["data"]["payment_url"])

    # Retrait autres pays
    r = api.withdraw(amount=10000, method="intl", country="CI", operator="orange_money", account="0700000000")
    print(f"Retrait: {r['data']['net_amount']} XOF — {r['data']['status']}")

Flutter/Dart — Intégration complète

import 'package:http/http.dart' as http;
import 'dart:convert';

class UnitechPay {
  static const String _base = 'https://api.unitech.sn/api.php';
  final String _apiKey;

  UnitechPay(this._apiKey);

  Future<Map<String, dynamic>> _post(String action, Map<String, dynamic> body) async {
    final res = await http.post(
      Uri.parse('$_base?action=$action'),
      headers: {
        'Authorization': 'Bearer $_apiKey',
        'Content-Type': 'application/json',
      },
      body: json.encode(body),
    ).timeout(const Duration(seconds: 30));
    return json.decode(res.body);
  }

  Future<Map<String, dynamic>> _get(String action) async {
    final res = await http.get(
      Uri.parse('$_base?action=$action'),
      headers: {'Authorization': 'Bearer $_apiKey'},
    ).timeout(const Duration(seconds: 30));
    return json.decode(res.body);
  }

  Future<Map<String, dynamic>> getBalance() => _get('balance');

  Future<Map<String, dynamic>> wavePayment({
    required double amount,
    required String phone,
    String description = '',
    String successUrl  = '',
    String cancelUrl   = '',
  }) => _post('create_wave_payment', {
    'amount':           amount,
    'customer_number':  phone,
    'description':      description,
    'callback_success': successUrl,
    'callback_cancel':  cancelUrl,
  });

  // Paiement autres pays (CI, TG, BF, BJ)
  Future<Map<String, dynamic>> intlPayment({
    required String country,
    required String operator,
    required double amount,
    String phone       = '',
    String name        = '',
    String successUrl  = '',
    String cancelUrl   = '',
  }) => _post('create_intl_payment', {
    'country':          country,
    'operator':         operator,
    'amount':           amount,
    'customer_number':  phone,
    'customer_name':    name,
    'callback_success': successUrl,
    'callback_cancel':  cancelUrl,
  });

  Future<Map<String, dynamic>> withdraw(Map<String, dynamic> params) =>
      _post('withdraw_funds', params);
}

// Utilisation
void _example() async {
  final api = UnitechPay('VOTRE_CLE_API');

  // Soldes
  final bal = await api.getBalance();
  print('Autres pays: ${bal['data']['sold_intl']} XOF');

  // Paiement autres pays
  final intl = await api.intlPayment(
    country: 'CI', operator: 'orange_money', amount: 5000,
    phone: '0700000000', name: 'Amadou Fall',
  );
  print('URL: ${intl['data']['payment_url']}');

  // Retrait autres pays
  final wd = await api.withdraw({
    'amount': 10000, 'method': 'intl',
    'country': 'CI', 'operator': 'orange_money', 'account': '0700000000',
  });
  print('Net: ${wd['data']['net_amount']} XOF — ${wd['data']['status']}');
}