Search by

dzwallet / laravel-sdk

dzwallet

PHP / Laravel SDK for the DzWallet payment API (CIB and Edahabia card payments through SATIM, payment links and QR codes).

Package info

github.com/dzwallet/laravel-sdk

pkg:composer/dzwallet/laravel-sdk

Statistics

Installs: 5

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.2.0 2026-11-09 10:21 UTC

This package is auto-updated.

Last update: 2026-11-09 10:23:03 UTC


README

PHP / Laravel client for the DzWallet payment API: accept CIB and Edahabia card payments (through SATIM) on your website, create payment links and QR codes, check payment status, and verify the signed payment notification (IPN).

  • PHP 8.1+, Laravel 10 / 11 / 12 (uses Laravel's own HTTP client)
  • Test mode never contacts a real gateway
  • Payments are never retried automatically, so a slow answer cannot create two payments

Money is only received when the status is success. Never mark an order as paid because the customer reached your success_url: use the signed IPN, or call paymentStatus() from your server.

Install

composer require dzwallet/laravel-sdk
php artisan vendor:publish --tag=dzwallet-config   # optional

Laravel finds the package by itself (no change to config/app.php). Requires PHP 8.1+ and Laravel 10, 11 or 12.

Configure

.env (keys are in your merchant dashboard: Setting → API Key):

DZWALLET_MODE=test                 # test | live
DZWALLET_PUBLIC_KEY=...            # the TEST public key in test mode, the LIVE one in live mode
DZWALLET_SECRET_KEY=...            # the matching secret key. Server side only, never in JavaScript or a repository

Test payments are signed with your test secret key and live payments with your live secret key, so switch both keys together with the mode. Live keys exist only after your identity (KYC) is verified.

Register your website domain in the merchant profile (Your website domains). ipn_url, success_url and cancel_url must belong to it.

Take a card payment

use DzWallet\Facades\DzWallet;
use DzWallet\DzWalletClient;

$order = Order::create([...]);                       // keep your own record first
$order->update(['identifier' => DzWalletClient::newIdentifier('ORD')]);   // max 20 characters, unique

$payment = DzWallet::initiatePayment([
    'identifier'  => $order->identifier,
    'amount'      => $order->total,                  // DZD
    'details'     => "Order #{$order->id}",          // max 100 characters
    'ipn_url'     => route('dzwallet.ipn'),
    'success_url' => route('orders.thanks', $order),
    'cancel_url'  => route('orders.cart'),
    'site_name'   => config('app.name'),
    'customer'    => [
        'first_name' => $user->first_name,
        'last_name'  => $user->last_name,
        'email'      => $user->email,
        'mobile'     => $user->phone,
    ],
    // optional: 'currency' => 'DZD' (default), 'gateway_methods' => ['Satim'] (default),
    // 'site_logo', 'checkout_theme' => 'light|dark', 'shipping_info', 'billing_info'
]);

return redirect()->away($payment->redirectUrl);

Receive the notification (IPN)

DzWallet calls your ipn_url after the gateway confirms the payment. Two ways to receive it.

1. Ready-made route. Set DZWALLET_IPN_ENABLED=true. The package registers POST /dzwallet/ipn (CSRF-exempt), verifies the signature on the raw body, rejects old timestamps, ignores events it already handled and fires an event:

use DzWallet\Events\PaymentNotificationReceived;
use Illuminate\Support\Facades\Event;

Event::listen(PaymentNotificationReceived::class, function ($event) {
    $ipn = $event->ipn;                               // signature already verified
    // $event->eventId is the unique event id (the same on every retry)
    $order = Order::where('identifier', $ipn->identifier)->first();

    if (!$order || !$ipn->isSuccess()) return;
    if ($order->isPaid()) return;                     // the same notification can arrive twice
    if (abs((float) $ipn->amount() - (float) $order->total) > 0.005) return;   // amount must match YOUR order

    $order->markPaid($ipn->trx());
});

2. Your own controller.

Route::post('/pay/ipn', function (Request $request) {
    $order = Order::where('identifier', $request->input('identifier'))->firstOrFail();

    // throws InvalidSignatureException when the message is not from DzWallet,
    // is for another order, or carries another amount
    $ipn = DzWallet::verifyIpn($request->all(), $order->identifier, $order->total);

    if ($ipn->isSuccess() && !$order->isPaid()) {
        $order->markPaid($ipn->trx());
    }
    return response('OK');       // answer quickly with HTTP 200
})->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class]);

Or protect a route with the middleware alias: ->middleware('dzwallet.signature') (rejects bad signatures with 403 and exposes $request->attributes->get('dzwallet.ipn') and $request->attributes->get('dzwallet.event_id')).

How notifications are signed (v2)

Every notification carries X-DzWallet-Signature: t=<unix time>,v1=<hex>, where v1 = HMAC-SHA256(secret, t + "." + raw body), plus X-DzWallet-Event-Id. The package:

  • verifies the signature on the exact raw body, so any changed field (status, amount, transaction) is rejected;
  • rejects a timestamp more than 5 minutes from your server clock (DZWALLET_IPN_TOLERANCE, seconds);
  • reads the event id from the signed body and, on the ready-made route, ignores an event it already handled (DZWALLET_IPN_DEDUPE, kept 7 days in your cache; the cache driver must support locks: file, database, redis, memcached);
  • falls back to the older signature field (uppercase HMAC of data.amount + identifier) so existing integrations keep working. Set DZWALLET_IPN_REQUIRE_V2=true to refuse it once you no longer need it.

Keep your own order update idempotent as well: the cache can be cleared, and a notification can be redelivered from the merchant dashboard (Webhook Log) with the same event id.

To verify by hand: [$ipn, $eventId] = DzWallet::verifyNotification($request); or DzWallet::verifyWebhook($request->getContent(), $request->header('X-DzWallet-Signature')).

Check a payment from your server

Use it when the IPN did not arrive, or on your thank-you page:

$status = DzWallet::paymentStatus($order->identifier);

$status->isPaid();     // true only for "success"
$status->status;       // initiated | pending | success | rejected | cancelled
$status->isFinal();
$status->trx;

Payment links and QR codes (live keys)

$link = DzWallet::createPaymentLink(2500, [
    'reference' => 'ORD1042',     // optional, max 15: letters, digits, _ -   (same reference + amount = same link)
    'days'      => 7,             // 1-30
    'note'      => 'Room 12',     // max 100
    'email'     => 'customer@example.dz',   // the link is emailed
    'ipn_url'   => 'https://shop.example.dz/pay/ipn',
]);

$link->url;  $link->printUrl;  $link->status;  $link->expiresAt;  $link->isOpen();

DzWallet::paymentLink('ORD1042');              // status
DzWallet::reactivatePaymentLink('ORD1042');    // same link and QR code, validity restarts

Errors

Exception When
InvalidArgumentException a parameter is wrong; nothing was sent
ConfigurationException a key is missing
ApiException DzWallet answered with an error (->errors, ->httpStatus, ->isRateLimited())
ConnectionException timeout / network. For initiatePayment the result is unknown: call paymentStatus() with the same identifier before creating another payment
InvalidSignatureException a notification is not authentic, or is for another order or amount

All extend DzWallet\Exceptions\DzWalletException. Limits: payment creation and status are limited to 60 requests per minute per IP (HTTP 429).

Security checklist

  • The secret key lives only in .env on the server.
  • Always pass the expected identifier and amount to verifyIpn().
  • Make the order update idempotent: ignore an order that is already paid.
  • Use HTTPS with a valid certificate on your ipn_url.
  • Test with DZWALLET_MODE=test first. In test mode you get a simulated payment page and a real signed IPN.

Tests

composer install
vendor/bin/phpunit

The suite uses Http::fake(): it never calls DzWallet and never creates a payment.

MIT licence.