SENPROG PayHub

Panneau d'administration

Entrez le code de votre application d'authentification

Code TOTP ou code de recuperation

Notifications

Aucune notification

Transactions

Reussies

Taux de succes

Erreurs

Emails envoyes

Emails reussis

SMS envoyes

SMS reussis

Transactions journalieres (30j)

Par provider

Aucune donnee

Revenus par devise

Aucune donnee

Top tenants

Aucune donnee

tenant(s)

Nom Slug Statut Cree le Actions
Aucun tenant
ID Tenant Montant Statut Provider Client Date
Aucune transaction
Chargement...
|
Date Action Methode Chemin Code Provider Duree Erreur
Aucun log
Page

Taux de conversion par provider

Revenus par provider / devise

ProviderDeviseTxReussisVolume

Tendance mensuelle (12 mois)

Distribution horaire

Top tenants

TenantTotalReussisTauxVolume
Aucune donnee

Gerez les comptes administrateurs et leurs permissions.

Nom Email Role Statut Derniere connexion Actions
Aucun utilisateur. Creez le premier compte admin.

Rapports automatiques envoyes par email.

Commande cron : php bin/console app:reports:send

Nom Frequence Type Destinataire Tenant Dernier envoi Actions
Aucun rapport planifie

Authentification a deux facteurs (2FA)

Protegez votre panneau d'administration avec une verification en deux etapes via une application d'authentification (Google Authenticator, Authy, etc.).

Etape 1 : Scannez le QR code

Ouvrez votre application d'authentification et scannez le code ci-dessous.

Cle manuelle :

Etape 2 : Confirmez avec un code

Entrez le code a 6 chiffres affiche dans votre application.

Codes de recuperation

Sauvegardez ces codes dans un endroit sur. Chaque code ne peut etre utilise qu'une seule fois.

L'authentification a deux facteurs est activee. Pour la desactiver, entrez un code TOTP valide.

Modifier la cle API admin

Changez la cle utilisee pour vous connecter au dashboard

Minimum 16 caracteres

/ 160 caracteres

Services

Base de donnees

Redis

Memoire
Clients connectes
Version
Uptime

PHP

Version
Memoire max
OPcache

Documentation interactive OpenAPI/Swagger disponible

Explorez tous les endpoints avec les schemas de requete/reponse

Ouvrir Swagger UI

Authentification HMAC

Toutes les requetes API doivent etre signees avec HMAC-SHA256. Chaque tenant possede une paire API_KEY / API_SECRET.

Headers requis

X-API-KEYCle API du tenant
X-SIGNATUREHMAC-SHA256 de timestamp.method.path.body
X-TIMESTAMPTimestamp Unix (tolerance : 5 min)
X-NONCEValeur unique anti-rejeu (UUID recommande)
Calcul de la signature
// Signature HMAC-SHA256
$timestamp = time();
$nonce = bin2hex(random_bytes(16));
$method = 'POST';
$path = '/api/v1/payment-intents';
$body = json_encode(['amount' => 5000, 'currency' => 'XAF']);

$message = $timestamp . '.' . $method . '.' . $path . '.' . $body;
$signature = hash_hmac('sha256', $message, $apiSecret);

// Headers
$headers = [
    'X-API-KEY'    => $apiKey,
    'X-SIGNATURE'  => $signature,
    'X-TIMESTAMP'  => $timestamp,
    'X-NONCE'      => $nonce,
    'Content-Type' => 'application/json',
];

Header optionnel

Idempotency-KeyCle unique pour les requetes POST (evite les doublons). Le systeme retourne la reponse cachee si la meme cle est reutilisee.

Flux de paiement

Le flux suit le modele Stripe : PaymentIntent → Charge → Refund/Payout.

PENDING PROCESSING SUCCEEDED REFUNDED

1. Creer un PaymentIntent

POST /api/v1/payment-intents
// Requete
{
    "amount": 5000,           // En plus petite unite (5000 = 5000 XAF)
    "currency": "XAF",
    "provider": "mtn_momo",
    "customer_phone": "+242064000000",
    "description": "Commande #1234",
    "metadata": { "order_id": "1234" }
}

// Reponse 201
{
    "id": "01912345-6789-7abc-def0-123456789abc",
    "status": "pending",
    "amount": 5000,
    "currency": "XAF",
    "provider": "mtn_momo",
    "reference": "PAY-XXXXXX",
    "created_at": "2026-03-09T10:00:00+00:00"
}

2. Confirmer le paiement

POST /api/v1/payment-intents/{id}/confirm
// Reponse 200
{
    "id": "01912345-6789-7abc-def0-123456789abc",
    "status": "processing",
    "provider_transaction_id": "ext_ref_123",
    "message": "Payment is being processed"
}

3. Consulter le statut

GET /api/v1/payment-intents/{id}

4. Rembourser

POST /api/v1/payment-intents/{id}/refund
// Requete (optionnel: remboursement partiel)
{
    "amount": 2000,
    "reason": "Client insatisfait"
}

// Reponse 200
{
    "id": "01912345-...",
    "status": "refunded",
    "refund_amount": 2000
}

Providers supportes

MTN

MTN Mobile Money

Congo-Brazzaville (XAF)

provider: "mtn_momo"
Airtel

Airtel Money

Congo-Brazzaville (XAF)

provider: "airtel_money"
PayPal

PayPal

International (USD, EUR...)

provider: "paypal"
2Checkout

2Checkout

Paiements en ligne

provider: "two_checkout"

Webhooks

PayHub envoie des notifications webhook a l'URL configuree pour chaque tenant. Les webhooks sont signes avec HMAC-SHA256.

Verification de la signature

Le header X-Webhook-Signature contient : t=timestamp,v1=signature

Verification (PHP)
// Verifier la signature d'un webhook
$header = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'];
preg_match('/t=(\d+),v1=(.+)/', $header, $m);
$timestamp = $m[1];
$receivedSig = $m[2];

$payload = file_get_contents('php://input');
$expected = hash_hmac('sha256', $timestamp . '.' . $payload, $endpointSecret);

if (!hash_equals($expected, $receivedSig)) {
    http_response_code(401);
    exit('Signature invalide');
}

// Verifier la fraicheur (< 5 min)
if (abs(time() - (int)$timestamp) > 300) {
    http_response_code(401);
    exit('Timestamp expire');
}

$event = json_decode($payload, true);

Evenements

payment.succeeded Le paiement a ete confirme et le montant credite
payment.failed Le paiement a echoue (fonds insuffisants, timeout, etc.)
payment.refunded Un remboursement a ete effectue
payment.processing Le paiement est en cours de traitement par le provider
Exemple de payload webhook
{
    "event": "payment.succeeded",
    "timestamp": "2026-03-09T10:05:00+00:00",
    "data": {
        "id": "01912345-6789-7abc-def0-123456789abc",
        "reference": "PAY-XXXXXX",
        "status": "succeeded",
        "amount": 5000,
        "currency": "XAF",
        "provider": "mtn_momo",
        "provider_transaction_id": "ext_ref_123",
        "customer_phone": "+242064000000",
        "metadata": { "order_id": "1234" }
    }
}

Retentatives : En cas d'echec (non-2xx), PayHub retente jusqu'a 5 fois avec un backoff exponentiel (1min, 5min, 30min, 2h, 24h).

Format des erreurs

Toutes les erreurs suivent un format JSON uniforme :

{
    "error": "error_type",
    "message": "Description lisible de l'erreur"
}

Codes HTTP

200Succes
201Ressource creee
400Requete invalide (champs manquants, validation)
401Authentification echouee (cle/signature invalide)
404Ressource non trouvee
409Conflit d'idempotence (meme cle, payload different)
422Transition d'etat invalide
429Rate limit depasse (60 req/min par defaut)
500Erreur interne

Types d'erreurs courants

errorDescription
auth_failedCle API ou signature HMAC invalide
invalid_nonceNonce deja utilise (rejeu detecte)
validation_errorChamps requis manquants ou invalides
invalid_transitionChangement d'etat non autorise
provider_errorErreur retournee par le provider de paiement
idempotency_conflictCle d'idempotence reutilisee avec un payload different
rate_limitedTrop de requetes

Exemples d'integration

Exemples de code pour integrer PayHub dans vos applications.

PHP (cURL)

<?php
$apiKey = 'YOUR_API_KEY';
$apiSecret = 'YOUR_API_SECRET';
$baseUrl = 'https://pay.example.com';

function payhubRequest(string $method, string $path, ?array $body = null): array {
    global $apiKey, $apiSecret, $baseUrl;
    $timestamp = time();
    $nonce = bin2hex(random_bytes(16));
    $bodyJson = $body ? json_encode($body) : '';

    $message = "$timestamp.$method.$path.$bodyJson";
    $signature = hash_hmac('sha256', $message, $apiSecret);

    $ch = curl_init($baseUrl . $path);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CUSTOMREQUEST => $method,
        CURLOPT_HTTPHEADER => [
            "X-API-KEY: $apiKey",
            "X-SIGNATURE: $signature",
            "X-TIMESTAMP: $timestamp",
            "X-NONCE: $nonce",
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => $bodyJson ?: null,
    ]);
    $response = curl_exec($ch);
    curl_close($ch);
    return json_decode($response, true);
}

// Creer un paiement MTN MoMo
$payment = payhubRequest('POST', '/api/v1/payment-intents', [
    'amount' => 5000,
    'currency' => 'XAF',
    'provider' => 'mtn_momo',
    'customer_phone' => '+242064000000',
    'description' => 'Commande #1234',
]);
echo "ID: " . $payment['id'] . "\n";

// Confirmer
$result = payhubRequest('POST', "/api/v1/payment-intents/{$payment['id']}/confirm");
echo "Status: " . $result['status'] . "\n";

JavaScript (Node.js / Fetch)

import crypto from 'crypto';

const API_KEY = 'YOUR_API_KEY';
const API_SECRET = 'YOUR_API_SECRET';
const BASE_URL = 'https://pay.example.com';

async function payhubRequest(method, path, body = null) {
    const timestamp = Math.floor(Date.now() / 1000);
    const nonce = crypto.randomBytes(16).toString('hex');
    const bodyStr = body ? JSON.stringify(body) : '';
    const message = `${timestamp}.${method}.${path}.${bodyStr}`;
    const signature = crypto.createHmac('sha256', API_SECRET)
        .update(message).digest('hex');

    const res = await fetch(BASE_URL + path, {
        method,
        headers: {
            'X-API-KEY': API_KEY,
            'X-SIGNATURE': signature,
            'X-TIMESTAMP': String(timestamp),
            'X-NONCE': nonce,
            'Content-Type': 'application/json',
        },
        body: bodyStr || undefined,
    });
    return res.json();
}

// Creer un paiement
const payment = await payhubRequest('POST', '/api/v1/payment-intents', {
    amount: 5000,
    currency: 'XAF',
    provider: 'mtn_momo',
    customer_phone: '+242064000000',
});
console.log('Payment ID:', payment.id);

// Confirmer
const result = await payhubRequest('POST',
    `/api/v1/payment-intents/${payment.id}/confirm`);
console.log('Status:', result.status);

Python (requests)

import hmac, hashlib, time, secrets, json, requests

API_KEY = 'YOUR_API_KEY'
API_SECRET = 'YOUR_API_SECRET'
BASE_URL = 'https://pay.example.com'

def payhub_request(method, path, body=None):
    timestamp = str(int(time.time()))
    nonce = secrets.token_hex(16)
    body_str = json.dumps(body) if body else ''
    message = f'{timestamp}.{method}.{path}.{body_str}'
    signature = hmac.new(
        API_SECRET.encode(), message.encode(), hashlib.sha256
    ).hexdigest()

    headers = {
        'X-API-KEY': API_KEY,
        'X-SIGNATURE': signature,
        'X-TIMESTAMP': timestamp,
        'X-NONCE': nonce,
        'Content-Type': 'application/json',
    }
    resp = requests.request(method, BASE_URL + path,
                            headers=headers, data=body_str or None)
    return resp.json()

# Creer un paiement
payment = payhub_request('POST', '/api/v1/payment-intents', {
    'amount': 5000,
    'currency': 'XAF',
    'provider': 'mtn_momo',
    'customer_phone': '+242064000000',
})
print(f"Payment ID: {payment['id']}")

# Confirmer
result = payhub_request('POST',
    f"/api/v1/payment-intents/{payment['id']}/confirm")
print(f"Status: {result['status']}")

Base URL

Remplacez https://pay.example.com par l'URL de votre instance PayHub. En developpement local : http://localhost:8888

Credentials

Actualiser les credentials sandbox

Re-genere un nouvel API User + API Key via l'API MTN MoMo

Minimum 8 caracteres