PHP
Le package wajub/wajub-php, ses ressources et son intégration dans une application Laravel.
wajub/wajub-php constitue la partie serveur d'une intégration Wajub. Il conserve votre clé secrète,
crée des paiements, lit leur statut réel et vérifie les signatures des webhooks. Il repose sur
Guzzle, respecte PSR-4 et ne suppose aucun framework.
wajub/wajub-php
Stable · GAPackagist
- Version
- 2.0.0
- Runtime
- PHP 8.4+, Guzzle 8.2+, ext-json
Couvre
- Paiements
- Facturation
- Transferts
- Sync
- Shield
- Taxes
Installer
composer require wajub/wajub-phpCréer le client
Le constructeur accepte un tableau. api_key n'a aucune valeur de repli dans l'environnement,
lisez donc vous-même la variable. Seul webhook_secret retombe sur WAJUB_WEBHOOK_SECRET quand il
est omis.
<?php
use Wajub\Wajub;
$wajub = new Wajub([
'api_key' => getenv('WAJUB_API_KEY'),
'webhook_secret' => getenv('WAJUB_WEBHOOK_SECRET'),
]);| Clé | Fonction |
|---|---|
api_key | Votre clé sk. ou sk_test.. Obligatoire, une valeur vide génère une erreur |
webhook_secret | Secret whsec_, uniquement nécessaire pour webhooks->constructEvent(). WAJUB_WEBHOOK_SECRET par défaut |
idempotency_key_prefix | Préfixe de la clé générée. 'wajub' par défaut |
http_client | Votre propre GuzzleHttp\Client, pour un proxy ou un handler simulé dans les tests |
max_network_retries | Nouvelles tentatives sur les échecs temporaires. 2 par défaut |
timeout | Secondes transmises au client Guzzle par défaut. 30 par défaut |
Une clé manquante génère InvalidArgumentException: Wajub: api_key is required lors de la
construction. Une mauvaise configuration échoue donc au démarrage plutôt qu'au premier paiement.
Dans une application Laravel
Associez-le une fois comme singleton, puis injectez-le. Un seul pool de connexions Guzzle est ainsi conservé pendant tout le processus.
<?php
// app/Providers/AppServiceProvider.php
use Wajub\Wajub;
public function register(): void
{
$this->app->singleton(Wajub::class, fn () => new Wajub([
'api_key' => config('services.wajub.secret'),
'webhook_secret' => config('services.wajub.webhook_secret'),
]));
}Dans Laravel, ajoutez les deux clés à config/services.php au lieu d'appeler env() hors d'un
fichier de configuration. Sinon, php artisan config:cache vous donnera une clé nulle en production.
'wajub' => [
'secret' => env('WAJUB_API_KEY'),
'webhook_secret' => env('WAJUB_WEBHOOK_SECRET'),
],Premier appel
$payment = $wajub->payments->create([
'amount' => 25000,
'currency' => 'XAF',
'email' => 'amina@example.com',
'description' => 'Order 4172',
'reference' => 'order-4172',
'callback' => route('checkout.complete'),
]);
return redirect()->away($payment->authorization_url);Les paramètres sont transmis dans un tableau associatif avec les noms de champs exacts de l'API.
La réponse est un ApiObject. Chaque champ est donc une propriété et aucune donnée n'est perdue.
$payment->id; // trx_test_8kQ2mW9vB4nL6hR1cY3d
$payment->status; // pending
$payment->authorization_url; // https://pay.wajub.com/tok_xxxxx
$payment->authorization_token; // tok_xxxxx
// A field added by the API after this release is still readable.
$payment->settlement_batch_id;Les montants utilisent l'unité principale
25000 en XAF représente vingt-cinq mille francs. Une devise décimale utilise une valeur
décimale : 'amount' => 12.50 en GHS.
Toutes les ressources du client
| Property | Méthodes |
|---|---|
| $wajub->global | ping, channels, countries, currencies |
| $wajub->payments | create, initialize, retrieve, list, cancel, process, processSplit, listRefunds |
| $wajub->customers | create, retrieve, update, delete, list, block, unblock, activate, deactivate, listTaxIds, createTaxId, deleteTaxId |
| $wajub->refunds | create, retrieve, list |
| $wajub->transfers | create, retrieve, list |
| $wajub->beneficiaries | create, retrieve, update, delete, list |
| $wajub->links | create, retrieve, update, delete, list |
| $wajub->invoices | create, retrieve, update, delete, list, send, markPaid, cancel |
| $wajub->accounts | create, retrieve, update, delete, list, regenerateToken |
| $wajub->webhookEndpoints | create, retrieve, update, delete, list, rotateSecret |
| $wajub->balance | retrieve |
| $wajub->events | list, retrieve, resend |
| $wajub->disputes | list, retrieve, submitEvidence, accept, close, sendMessage |
| $wajub->identity | resolve, validate |
| $wajub->tax | getSettings, updateSettings, rates, calculate, reports, listCodes, retrieveCode, listRegistrations, createRegistration, retrieveRegistration, updateRegistration, deleteRegistration, jurisdictions, thresholds, thresholdAlerts |
| $wajub->shield | getSettings, updateSettings, stats, listBlocklist, addToBlocklist, removeFromBlocklist |
| $wajub->listen | config, auth |
| $wajub->webhooks | constructEvent |
webhookssignature verification runs locally, no HTTP call. All other resources call the merchant REST API.links,invoices,taxandshieldare live only. A sandbox key gets403 This feature is only available in live mode.on every one of their methods.refundsandtransfersare create, retrieve and list only. The shared CRUD base also exposesupdateanddeleteon them, but the API serves no such route.
Parcourir une liste paginée
list() renvoie un PagedResult qui implémente Iterator. Un foreach sur l'objet parcourt donc
la page actuelle, tandis que autoPagingIterator() parcourt toutes les pages.
$page = $wajub->payments->list(['status' => 'success', 'per_page' => 50]);
foreach ($page->data as $payment) {
echo $payment['id'].' '.$payment['amount'];
}
if ($page->hasMore) {
$page = $page->getNextPage();
}
// Or let it fetch the following pages for you.
foreach ($wajub->payments->list()->autoPagingIterator() as $payment) {
$this->reconcile($payment);
}$page->data contient de simples tableaux, pas des ApiObject. Les lignes utilisent donc l'accès
par tableau. $page->meta contient total, per_page, current_page et last_page lorsque l'endpoint les renvoie.
Idempotence et nouvelles tentatives
Chaque POST et PUT contient une valeur Idempotency-Key générée, qui sécurise les nouvelles
tentatives automatiques. Transmettez votre propre clé lorsqu'une valeur naturelle existe.
use Wajub\RequestOptions;
$wajub->payments->create(
$params,
new RequestOptions(idempotencyKey: "order-{$order->id}"),
);| Élément | Valeur |
|---|---|
| Statuts réessayés | 429, 500, 502, 503, 504 et tout échec réseau |
| Tentatives | 3 au total, un appel initial et deux nouvelles tentatives |
| Attente progressive | Exponentielle avec part d'aléa, en respectant Retry-After quand il est présent, jamais plus de 10 secondes |
| Délai d'expiration | 30 secondes par requête |
Une tâche en file d'attente constitue une meilleure nouvelle tentative
La création d'un paiement dans une requête web retient un worker PHP-FPM jusqu'à 30 secondes si l'API est lente. Trois appels suffisent à épuiser un petit pool. Exécutez l'appel dans une tâche en file d'attente avec la même clé d'idempotence. Une nouvelle tentative retrouvera le même paiement au lieu d'en créer un second.
Agir pour un compte connecté
use Wajub\RequestOptions;
$wajub->payments->create(
['amount' => 25000, 'currency' => 'XAF', 'email' => $buyer->email],
new RequestOptions(sync: $seller->wajub_account_id),
);sync devient l'en-tête X-Sync. La configuration figure dans Sync.
Webhooks
constructEvent vérifie la signature et renvoie l'événement analysé. Il nécessite le corps exact reçu.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Wajub\Exception\WebhookSignatureVerificationError;
use Wajub\Wajub;
class WajubWebhookController extends Controller
{
public function __invoke(Request $request, Wajub $wajub)
{
try {
$event = $wajub->webhooks->constructEvent(
$request->getContent(),
$request->header('X-Wajub-Signature', ''),
$request->header('X-Wajub-Timestamp', ''),
);
} catch (WebhookSignatureVerificationError) {
return response()->noContent(400);
}
if ($event['event'] === 'payment.succeeded') {
HandleWajubEvent::dispatch($event);
}
return response()->noContent(200);
}
}Utilisez getContent, jamais all ou json
La signature couvre {timestamp}.{raw body}. $request->all() et $request->json() analysent
tous deux le contenu, qui devrait ensuite être réencodé. L'ordre des clés et les espaces changent,
ce qui invalide le hash. $request->getContent() renvoie les octets tels qu'ils ont été livrés.
Une route de webhook Laravel nécessite deux autres précautions. Excluez-la de la protection CSRF,
car Wajub ne possède aucun jeton de session, et répondez rapidement. Accusez réception avec 200,
puis confiez le travail à une tâche en file d'attente. La livraison est réessayée si votre endpoint répond trop lentement.
->withMiddleware(function (Middleware $middleware) {
$middleware->validateCsrfTokens(except: ['webhooks/wajub']);
})Le nom de l'événement se trouve dans event, pas dans type. La tolérance est de 300 secondes par
défaut. constructEvent accepte un quatrième argument si le décalage de vos horloges est supérieur.
Consultez Vérification de signature.
Erreurs
use Wajub\Exception\InvalidRequestException;
use Wajub\Exception\RateLimitException;
use Wajub\Exception\WajubError;
try {
$wajub->payments->create($params);
} catch (InvalidRequestException $e) {
// $e->errors vaut ['amount' => 'The amount must be at least 25.']
return response()->json(['fields' => $e->errors], 422);
} catch (RateLimitException $e) {
return response()->noContent(503)
->header('Retry-After', (string) ($e->retryAfter ?? 5));
} catch (WajubError $e) {
Log::error('wajub failed', ['code' => $e->errorCode, 'status' => $e->httpStatus]);
throw $e;
}Classe dans Wajub\Exception | Cause |
|---|---|
AuthenticationException | 401 |
PermissionException | 403 |
NotFoundException | 404 |
InvalidRequestException | 400 et 422 |
RateLimitException | 429, avec retryAfter en secondes |
WajubError | Tout autre statut et classe parente de toutes les erreurs ci-dessus |
ApiConnectionException | Aucune réponse, problème réseau ou délai d'expiration |
WebhookSignatureVerificationError | Échec de vérification d'un webhook |
getCode renvoie toujours zéro
WajubError transmet uniquement le message à Exception. $e->getCode() vaut donc 0 pour
chaque échec Wajub. La véritable valeur se trouve dans la propriété en lecture seule $e->errorCode,
avec $e->httpStatus, $e->errors et $e->raw. La classe de base est aussi le seul nom de la
hiérarchie qui se termine par Error plutôt que Exception.
Tester sans appeler l'API
L'option http_client accepte tout client Guzzle. Un handler simulé permet donc d'utiliser tout le SDK hors ligne.
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use Wajub\Wajub;
$mock = new MockHandler([
new Response(201, [], json_encode([
'code' => 201,
'authorization_url' => 'https://pay.wajub.com/tok_test',
'authorization_token' => 'tok_test',
'transaction' => ['id' => 'trx_test', 'status' => 'pending', 'amount' => 25000],
])),
]);
$wajub = new Wajub([
'api_key' => 'sk_test.fake',
'http_client' => new Client(['handler' => HandlerStack::create($mock)]),
]);
$payment = $wajub->payments->create(['amount' => 25000, 'currency' => 'XAF']);
$this->assertSame('tok_test', $payment->authorization_token);Débiter sans la page hébergée
$payment = $wajub->payments->create([
'amount' => 25000,
'currency' => 'XAF',
'phone' => '+237670000000',
]);
$wajub->payments->process($payment->id, [
'channel' => 'cm.mtn',
'phone' => '+237670000000',
]);Un canal suit le format country.operator. cm.mtn désigne donc MTN Mobile Money au Cameroun. La
liste complète figure dans Moyens de paiement et canaux. Le payeur doit
encore approuver sur son téléphone. Le résultat arrive donc dans le webhook.
Effectuer un payout
use Wajub\RequestOptions;
$transfer = $wajub->transfers->create(
[
'amount' => 100000,
'currency' => 'XAF',
'beneficiary' => [
'name' => 'Amina Diallo',
'channel' => 'cm.mtn',
'phone' => '+237670000000',
],
'reference' => 'payout-892',
],
new RequestOptions(idempotencyKey: 'payout-892'),
);beneficiary accepte aussi l'identifiant ben_… d'un bénéficiaire enregistré. Préférez cette forme
dès que vous payez deux fois la même personne.
Développement local
wajub listen --forward-to localhost:8000/webhooks/wajub
wajub trigger payment.succeededConsultez la CLI pour plus de détails.
Pages associées
- Démarrage rapide des SDKsLe même premier appel dans cinq langages présentés côte à côte.
- WebhooksTous les événements et leurs garanties de livraison.
- Wajub.jsLa partie navigateur, montée avec le jeton renvoyé par ce SDK.
- IdempotenceCe que protège une clé et pendant combien de temps.
- Gestion des erreursLes échecs à réessayer et ceux à afficher.
- Vue d'ensemble de la référence APILes endpoints utilisés par toutes les méthodes ci-dessus.