dz-delivery / sdk
Unified PHP SDK for Algerian delivery carriers (Yalidine, ZRExpress, Maystro, Ecotrack/DHD, GuepEx) — SDK livraison Algérie: parcels, tracking, fees, wilayas. Laravel-friendly.
Requires
- php: >=8.2
- ext-json: *
- guzzlehttp/guzzle: ^7.8
Requires (Dev)
- phpunit/phpunit: ^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Unified PHP client for Algerian delivery carriers — Yalidine, GuepEx, ZRExpress, Maystro, Ecotrack (DHD, Anderson, World Express, …).
🇩🇿 SDK PHP livraison Algérie : créer, suivre et tarifer vos colis avec le même code pour chaque transporteur. Docs : https://developing-off.github.io/dz-delivery/ · TypeScript / Node.js :
@dz-delivery/core
Implements the shared contract in
docs/CONTRACT.md; provider mappings are in
docs/PROVIDERS.md.
| Provider | Key | Status |
|---|---|---|
| ZRExpress | zrexpress |
contrat unifié (getStopDesks, listParcels, …) + extras (bulk, labels, webhooks) |
| Yalidine | yalidine |
contrat unifié, checked against the developer docs |
| Maystro Delivery | maystro |
createParcel, getFees, getWilayas, getCommunes, getStopDesks — track / cancelParcel throw NOT_SUPPORTED (webhooks only) |
| Ecotrack (DHD, Anderson, World Express, …) | ecotrack |
contrat unifié — one provider for every courier on the Ecotrack platform (courier or baseUrl) |
Requires PHP ≥ 8.2 and Guzzle 7.
Install
composer require dz-delivery/sdk
Full guide (configuration, every method with input/output tables, errors, Laravel,
Maystro webhooks, testing): docs/guide-php.md.
Usage
use DzDelivery\ClientFactory; use DzDelivery\Errors\ShippingError; $client = ClientFactory::create('zrexpress', [ 'apiKey' => getenv('ZREXPRESS_API_KEY'), 'tenant' => getenv('ZREXPRESS_TENANT'), // 'baseUrl' => ..., 'timeoutMs' => 15000, 'includeRaw' => true, ]); // Extras ZR (bulk / labels): new \DzDelivery\Providers\ZRExpressProvider([...]) // Webhooks: \DzDelivery\Providers\ZRExpress\Webhook::verify($rawBody, $headers, $secret) // → docs/ZREXPRESS.md // Yalidine also needs the origin wilaya (for createParcel and getFees): // ClientFactory::create('yalidine', ['apiId' => ..., 'apiToken' => ..., 'fromWilayaId' => 16]); // GuepEx (same API): add 'baseUrl' => YalidineProvider::GUEPEX_DEFAULT_BASE_URL // Ecotrack couriers (DHD, Conexlog, ...): token + 'courier' key or the tenant 'baseUrl': // ClientFactory::create('ecotrack', ['token' => getenv('ECOTRACK_TOKEN'), 'courier' => 'dhd']); // Maystro only needs a store token (sent as `Authorization: Token <apiToken>`): // ClientFactory::create('maystro', ['apiToken' => getenv('MAYSTRO_API_TOKEN')]); // It has no tracking API: map webhook statuses with StatusMapper::maystro($payload['status']). try { $parcel = $client->createParcel([ 'recipientName' => 'Client Test', 'phone' => '0550123456', // normalized per carrier (+213… for ZRExpress) 'address' => '12 rue des Tests', 'wilayaId' => 16, // provider id or official code 1–58, or 'wilayaName' 'communeName' => 'Bab Ezzouar', // or 'communeId' 'productName' => 'T-shirt', // or 'productList' => [['name' => ..., 'quantity' => 1]] 'price' => 2500, // amount collected from the recipient (DZD) 'orderId' => 'ORD-1001', ]); $result = $client->track($parcel->trackingNumber); echo $result->parcel->status; // pending | created | in_transit | delivered | returned | cancelled | unknown foreach ($result->events as $event) { echo "$event->at $event->status $event->label\n"; } // weight in kg; length/width/height in cm (Yalidine bills the volumetric weight when higher) $quote = $client->getFees(['toWilayaId' => 16, 'isStopDesk' => false, 'weight' => 2]); $wilayas = $client->getWilayas(); $communes = $client->getCommunes(16); // Stop desks / pickup points: use $desk->id as 'stopDeskId' with 'isStopDesk' => true. // ZRExpress: prefer wilaya-only (commune filter often empty). Maystro: communeId required. $desks = $client->getStopDesks(['wilayaId' => 16]); // Account parcels + status (ZRExpress). No event history — use track() for that. $page = $client->listParcels(['page' => 1, 'pageSize' => 50]); // Connectivity / credentials probe (always hits the network) $health = $client->healthCheck(); // { ok: true, provider, latencyMs } $client->cancelParcel($parcel->trackingNumber); } catch (ShippingError $e) { // $e->code: AUTH_FAILED | VALIDATION_ERROR | NOT_FOUND | RATE_LIMITED | NOT_SUPPORTED // | PROVIDER_ERROR | NETWORK_ERROR | UNKNOWN echo "$e->code: {$e->getMessage()} (HTTP $e->httpStatus)"; // RATE_LIMITED: $e->retryAfter = seconds to wait (from the Retry-After header), when sent. }
Inputs accept either arrays or the model objects (ParcelInput, FeeQuery, StopDeskQuery). All returned
models expose public readonly camelCase properties and an optional raw native payload
(disable with 'includeRaw' => false).
A runnable example lives in examples/php/quickstart.php.
Development
composer install
composer test
Tests use Guzzle's MockHandler (no network) and the shared JSON fixtures in
docs/fixtures/, so they must be run from the monorepo. Pass a
Guzzle handler through the handler option to mock HTTP in your own tests:
$stack = GuzzleHttp\HandlerStack::create(new GuzzleHttp\Handler\MockHandler([...])); $client = ClientFactory::create('zrexpress', ['apiKey' => 'k', 'tenant' => 't', 'handler' => $stack]);
Layout
src/
ClientFactory.php
Contracts/ShippingProviderInterface.php
Models/ ParcelInput, ProductItem, Parcel, ParcelStatus, TrackingEvent, TrackResult,
FeeQuery, FeeQuote, Location, StopDeskQuery, StopDesk, ClientOptions
Errors/ShippingError.php
Http/HttpClient.php
Providers/ ZRExpressProvider, YalidineProvider, MaystroProvider
Support/ StatusMapper, Phone, Text, Dates
tests/