PHP SDK
xaalis-php (in integrations/sdk-php) wraps the API for PHP 8.1+ servers. No runtime dependencies beyond
ext-curl and ext-json. Not on Packagist yet: use it from the monorepo, through Composer’s PSR-4 autoload
(Xaalis\ → src/) or the bundled autoload.php.
require __DIR__ . '/vendor/autoload.php';
$xaalis = new Xaalis\Client(getenv('XAALIS_SECRET_KEY'), ['base_url' => 'http://localhost:4000']);
$payment = $xaalis->payments->create( [ 'amount' => 15000, // int, XOF — never a float or a string 'description' => 'Commande #1042', 'client_reference' => 'order_1042', 'success_url' => 'https://shop.example/merci', 'metadata' => ['order_id' => '1042'], ], ['idempotency_key' => 'payment:order_1042'],);
header('Location: ' . $payment['checkout_url']); // hosted checkoutResponses are associative arrays with the same fields as the API (payment object).
| Service | Methods |
|---|---|
payments |
create, retrieve, list, all (generator over every page) |
payouts |
create, retrieve, list, all |
balance |
retrieve |
account |
retrieve, update(['webhook_url' => 'https://…']) |
webhookDeliveries |
list(['limit' => 20]) |
testHelpers |
simulatePayment($id, 'succeeded' | 'failed') — test keys only |
Xaalis\Webhook::constructEvent($raw, $header, $secret) |
verify and parse a webhook |
Options
Section titled “Options”| Option | Default | |
|---|---|---|
base_url |
https://api.xaalis.sn |
a live key over http:// is refused |
timeout |
30 |
seconds per attempt |
max_retries |
2 |
retries after the first attempt |
http |
curl | fn($method, $url, $headers, $body, $timeout) returning ['status', 'body', 'headers'] — for tests |
The key must match sk_test_… / sk_live_… (40 characters after the prefix); it never appears in exception
messages or in var_dump($xaalis). $xaalis->testMode is true for test keys.
What it does for you
Section titled “What it does for you”- Idempotency: every
createsends anIdempotency-Key— a random UUID v4 unless you passidempotency_key— and reuses it on every retry. Pass your own (your order id) to stay safe across process restarts too. See Idempotency. - Retries: network errors,
429and5xx, with exponential backoff andRetry-After— only when replaying is safe:GET,PATCH, or a create carrying its key.4xxis never retried. - Integers only:
amountmust be a PHPint;150.5,15000.0and"15000"throw\InvalidArgumentExceptionbefore anything is sent.
Errors
Section titled “Errors”use Xaalis\Exception\ApiException;use Xaalis\Exception\ConnectionException;
try { $xaalis->payouts->create(['amount' => 5000, 'provider' => 'wave', 'recipient' => ['phone' => '+221770000001']]);} catch (ApiException $e) { if ($e->getErrorCode() === 'insufficient_funds') { // show "balance too low" } else { throw $e; }} catch (ConnectionException $e) { // unknown outcome: retry later with the SAME idempotency_key}| Exception | When |
|---|---|
Xaalis\Exception\XaalisException |
base class of the three below |
ApiException |
the API answered an error: getStatus(), getErrorCode() (stable, see Errors), getMessage(), getDetails() |
ConnectionException |
network failure or timeout after the retries |
SignatureException |
a webhook failed verification — answer 400 |
Webhooks
Section titled “Webhooks”$raw = file_get_contents('php://input'); // the exact bytes, never a re-encoded arraytry { $event = Xaalis\Webhook::constructEvent($raw, $_SERVER['HTTP_XAALIS_SIGNATURE'] ?? null, getenv('XAALIS_WEBHOOK_SECRET'));} catch (Xaalis\Exception\SignatureException $e) { http_response_code(400); exit;}
if ($event['livemode'] !== $expectLive || alreadyProcessed($event['id'])) { // test + live share one URL; at-least-once http_response_code(200); exit;}if ($event['type'] === 'payment.succeeded') { $payment = $event['data']['object']; // check $payment['amount'] and $payment['metadata'] against your order, then fulfil it once}http_response_code(200);constructEvent rejects a missing or malformed header, a timestamp more than 300 s away from now (replays), and a
signature that doesn’t match any v1 (compared with hash_equals).
integrations/sdk-php/tests/docker-test.sh runs php -l and the test suite in php:8.3-cli. With ADMIN_TOKEN
in the repo-root .env it also runs a live flow against the local API: create a merchant, a 15 000 payment (fee
225), simulate success, check the balance is 14 775, and check a 99 000 000 payout fails with insufficient_funds.
For WooCommerce stores, use the WooCommerce plugin instead — it doesn’t need Composer.