Transactions
Reussies
Taux de succes
Erreurs
Emails envoyes
Emails reussis
SMS envoyes
SMS reussis
Transactions journalieres (30j)
Par provider
txns
Aucune donnee
Revenus par devise
Aucune donnee
Top tenants
Aucune donnee
tenant(s)
| Nom | Slug | Statut | Cree le | Actions |
|---|---|---|---|---|
|
Slug:
Cles API
REVOQUEE
Secret:
·
Aucun provider configure
Notifications Client
Email SMS Aucun canal actif
Aucune notification configuree
| ID | Tenant | Montant | Statut | Provider | Client | Date | |
|---|---|---|---|---|---|---|---|
|
Payment Intent
Charges
Remboursements
Logs
| Date | Action | Methode | Chemin | Code | Provider | Duree | Erreur |
|---|---|---|---|---|---|---|---|
Taux de conversion par provider
Revenus par provider / devise
| Provider | Devise | Tx | Reussis | Volume |
|---|---|---|---|---|
Tendance mensuelle (12 mois)
Distribution horaire
Top tenants
| Tenant | Total | Reussis | Taux | Volume |
|---|---|---|---|---|
Gerez les comptes administrateurs et leurs permissions.
| Nom | Role | Statut | Derniere connexion | Actions | |
|---|---|---|---|---|---|
Rapports automatiques envoyes par email.
Commande cron : php bin/console app:reports:send
| Nom | Frequence | Type | Destinataire | Tenant | Dernier envoi | Actions |
|---|---|---|---|---|---|---|
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
Message ID:
Provider:
Message ID:
Provider:
Services
Base de donnees
Redis
PHP
Documentation interactive OpenAPI/Swagger disponible
Explorez tous les endpoints avec les schemas de requete/reponse
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-KEY | Cle API du tenant |
| X-SIGNATURE | HMAC-SHA256 de timestamp.method.path.body |
| X-TIMESTAMP | Timestamp Unix (tolerance : 5 min) |
| X-NONCE | Valeur unique anti-rejeu (UUID recommande) |
// 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-Key | Cle 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.
1. Creer un PaymentIntent
// 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
// 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
4. Rembourser
// Requete (optionnel: remboursement partiel)
{
"amount": 2000,
"reason": "Client insatisfait"
}
// Reponse 200
{
"id": "01912345-...",
"status": "refunded",
"refund_amount": 2000
}
Providers supportes
MTN Mobile Money
Congo-Brazzaville (XAF)
provider: "mtn_momo"
Airtel Money
Congo-Brazzaville (XAF)
provider: "airtel_money"
PayPal
International (USD, EUR...)
provider: "paypal"
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
// 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
{
"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
Types d'erreurs courants
| error | Description |
|---|---|
| auth_failed | Cle API ou signature HMAC invalide |
| invalid_nonce | Nonce deja utilise (rejeu detecte) |
| validation_error | Champs requis manquants ou invalides |
| invalid_transition | Changement d'etat non autorise |
| provider_error | Erreur retournee par le provider de paiement |
| idempotency_conflict | Cle d'idempotence reutilisee avec un payload different |
| rate_limited | Trop 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