Aller au contenu

PHP

Le package wajub/wajub-php, ses ressources et son intégration dans une application Laravel.

wajub/wajub-php est le client Wajub de votre serveur : avec votre clé secrète, il crée des paiements, lit leur statut et vérifie les signatures des webhooks, avec ou sans framework.

wajub/wajub-php

Stable · GA

Packagist

Version
2.0.1
Runtime
PHP 8.4+, Guzzle 8.2+, ext-json

Couvre

  • Paiements
  • Facturation
  • Transferts
  • Sync
  • Shield
  • Taxes

Installer

Un package et deux dépendances
composer require wajub/wajub-php

Cré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 simple
<?php

use Wajub\Wajub;

$wajub = new Wajub([
    'api_key' => getenv('WAJUB_API_KEY'),
    'webhook_secret' => getenv('WAJUB_WEBHOOK_SECRET'),
]);
CléFonction
api_keyVotre clé sk. ou sk_test.. Obligatoire, une valeur vide génère une erreur
webhook_secretSecret whsec_, uniquement nécessaire pour webhooks->constructEvent(). WAJUB_WEBHOOK_SECRET par défaut
http_clientVotre propre GuzzleHttp\Client, pour un proxy ou un handler simulé dans les tests. Donnez à un vrai client base_uri https://api.wajub.com et http_errors false
max_network_retriesNombre de nouvelles tentatives après un échec temporaire
timeoutDélai d'expiration d'une requête en secondes, si vous ne transmettez pas http_client

Dans une application Laravel

Associez-le une fois comme singleton, puis injectez-le.

<?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.

config/services.php
'wajub' => [
    'secret' => env('WAJUB_API_KEY'),
    'webhook_secret' => env('WAJUB_WEBHOOK_SECRET'),
],

Premier appel

Créer un paiement
$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.

Lire le résultat
$payment->id;                   // trx_test_8kQ2mW9vB4nL6hR1cY3d
$payment->status;               // pending
$payment->authorization_url;    // https://pay.wajub.com/test_xxxxx
$payment->authorization_token;  // test_xxxxx

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

PropriétéMéthodes
$wajub->globalping, channels, countries, currencies
$wajub->paymentscreate, retrieve, list, cancel, process, processSplit, listRefunds
$wajub->customerscreate, retrieve, update, delete, list, block, unblock, activate, deactivate, listTaxIds, createTaxId, deleteTaxId
$wajub->refundscreate, retrieve, list
$wajub->transferscreate, retrieve, list
$wajub->beneficiariescreate, retrieve, update, delete, list
$wajub->linkscreate, retrieve, update, delete, list
$wajub->invoicescreate, retrieve, update, delete, list, send, markPaid, cancel
$wajub->accountscreate, retrieve, update, delete, list, regenerateToken
$wajub->webhookEndpointscreate, retrieve, update, delete, list, rotateSecret
$wajub->balanceretrieve
$wajub->eventslist, retrieve, resend
$wajub->disputeslist, retrieve, submitEvidence, accept, close, sendMessage
$wajub->identityresolve, validate
$wajub->taxgetSettings, updateSettings, rates, calculate, reports, listCodes, retrieveCode, listRegistrations, createRegistration, retrieveRegistration, updateRegistration, deleteRegistration, jurisdictions, thresholds, thresholdAlerts
$wajub->shieldgetSettings, updateSettings, stats, listBlocklist, addToBlocklist, removeFromBlocklist
$wajub->webhooksconstructEvent
  • La vérification de signature de webhooks s'exécute en local, sans appel HTTP. Toutes les autres ressources appellent l'API REST.
  • links, invoices, tax et shield ne fonctionnent qu'en live. Une clé sandbox reçoit 403 This feature is only available in live mode. sur chacune de leurs méthodes.
  • refunds et transfers ne proposent que la création, la lecture et la liste.

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.

Une page ou toutes les pages
$page = $wajub->payments->list(['status' => 'succeeded', '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 les champs de pagination de la liste, décrits dans Pagination.

Idempotence et nouvelles tentatives

Les échecs temporaires sont réessayés pour vous ; max_network_retries en change le nombre. Pour qu'une création puisse aussi être répétée sans risque de votre côté, après un redémarrage ou dans une tâche en file d'attente réessayée, transmettez votre propre clé d'idempotence, le numéro de commande par exemple.

Un numéro de commande constitue la meilleure clé
use Wajub\RequestOptions;

$wajub->payments->create(
    $params,
    new RequestOptions(idempotencyKey: "order-{$order->id}"),
);

Agir pour un compte connecté

Un appel pour un vendeur
use Wajub\RequestOptions;

$wajub->payments->create(
    ['amount' => 25000, 'currency' => 'XAF', 'email' => $buyer->email],
    new RequestOptions(sync: $seller->wajub_account_id),
);

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.

Un contrôleur Laravel
<?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);
    }
}

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.

bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
    $middleware->validateCsrfTokens(except: ['webhooks/wajub']);
})

Le nom de l'événement se trouve dans event, pas dans type. Si vos horloges dérivent, constructEvent accepte la tolérance en secondes comme quatrième argument. Consultez Vérification de signature.

Erreurs

Intercepter d'abord l'erreur précise
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\ExceptionCause
AuthenticationException401
PermissionException403
NotFoundException404
InvalidRequestException400 et 422
RateLimitException429, avec retryAfter en secondes
WajubErrorTout autre statut et classe parente de toutes les erreurs ci-dessus
ApiConnectionExceptionAucune réponse, problème réseau ou délai d'expiration
WebhookSignatureVerificationErrorÉchec de vérification d'un webhook

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.

Un test PHPUnit sans réseau
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/test_xxxxx',
        'authorization_token' => 'test_xxxxx',
        '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('test_xxxxx', $payment->authorization_token);

Débiter sans la page hébergée

Requête push Mobile Money
$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 Mobile Money. Le payeur doit encore approuver sur son téléphone. Le résultat arrive donc dans le webhook.

Effectuer un payout

Un transfert vers un numéro de téléphone
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

Deux terminaux
wajub listen --forward-to localhost:8000/webhooks/wajub
wajub trigger payment.succeeded

Consultez la CLI pour plus de détails.

Que pensez-vous de ce contenu ?