Démarrage
Installer
Packages publics · npm @dz-delivery/core · Composer dz-delivery/sdk.
TypeScript · Node ≥ 18
Copier
npm install @dz-delivery/core
PHP ≥ 8.2 · Guzzle 7
Copier
composer require dz-delivery/sdk
Zéro dépendance runtime côté TypeScript (fetch natif). PHP : Guzzle uniquement.
Source de vérité : CONTRACT.md .
Premier colis
Quickstart
Créer un colis ZRExpress en quelques lignes.
TypeScript
PHP
import { createClient } from '@dz-delivery/core';
const shipping = createClient ('zrexpress' , {
apiKey: process.env.ZREXPRESS_API_KEY!,
tenant: process.env.ZREXPRESS_TENANT!,
});
const parcel = await shipping.createParcel ({
recipientName: 'Amine B.' ,
phone: '0550123456' ,
address: '12 rue Didouche Mourad' ,
wilayaId: 16 ,
communeName: 'Alger Centre' ,
productName: 'T-shirt' ,
price: 2500 ,
});
const { parcel: current, events } = await shipping.track (parcel.trackingNumber);
use DzDelivery\ClientFactory;
$shipping = ClientFactory::create ('zrexpress' , [
'apiKey' => getenv('ZREXPRESS_API_KEY' ),
'tenant' => getenv('ZREXPRESS_TENANT' ),
]);
$parcel = $shipping->createParcel ([
'recipientName' => 'Amine B.' ,
'phone' => '0550123456' ,
'address' => '12 rue Didouche Mourad' ,
'wilayaId' => 16 ,
'communeName' => 'Alger Centre' ,
'productName' => 'T-shirt' ,
'price' => 2500 ,
]);
$result = $shipping->track ($parcel->trackingNumber);
Configuration
Clients & credentials
Un client par transporteur. Options communes : baseUrl , timeoutMs (15s), includeRaw (true).
Clé Config Notes
zrexpress
apiKey, tenant
X-Api-Key + X-Tenant. Extras : createZRExpress
yalidine
apiId, apiToken, fromWilayaId
GuepEx : même provider + baseUrl GuepEx
maystro
apiToken
Authorization: Token … — pas de track/cancel
ecotrack
token + courier ou baseUrl
DHD, Anderson, World Express, 48Hr, EcoRapid, Conexlog…
TypeScript
PHP
import { createClient, createZRExpress, GUEPEX_DEFAULT_BASE_URL } from '@dz-delivery/core';
const zr = createClient ('zrexpress' , { apiKey, tenant });
const zrExtra = createZRExpress ({ apiKey, tenant }); // bulk / labels typés
const yalidine = createClient ('yalidine' , {
apiId, apiToken, fromWilayaId: 16 ,
});
const guepex = createClient ('yalidine' , {
apiId, apiToken, fromWilayaId: 16 , baseUrl: GUEPEX_DEFAULT_BASE_URL,
});
const maystro = createClient ('maystro' , { apiToken });
const dhd = createClient ('ecotrack' , { token, courier: 'dhd' });
use DzDelivery\ClientFactory;
use DzDelivery\Providers\ZRExpressProvider;
use DzDelivery\Providers\YalidineProvider;
$zr = ClientFactory::create ('zrexpress' , ['apiKey' => $key, 'tenant' => $tenant]);
$zrExtra = new ZRExpressProvider(['apiKey' => $key, 'tenant' => $tenant]);
$yalidine = ClientFactory::create ('yalidine' , [
'apiId' => $id, 'apiToken' => $token, 'fromWilayaId' => 16 ,
]);
$guepex = ClientFactory::create ('yalidine' , [
'apiId' => $id, 'apiToken' => $token, 'fromWilayaId' => 16 ,
'baseUrl' => YalidineProvider::GUEPEX_DEFAULT_BASE_URL,
]);
ShippingProvider
API unifiée
Même interface pour tous les transporteurs. Pas un wrapper 1:1 — les opérations dont une boutique a besoin.
01 Créer createParcel
02 Suivre track
03 Tarifer getFees
04 Lieux getWilayas · getCommunes · getStopDesks
05 Lister (compte) listParcels
06 Annuler cancelParcel
07 Santé healthCheck
createParcel
Valide côté client avant HTTP. Accepte codes wilaya 1–58 , ids transporteur, ou noms. price = montant COD en DZD.
TypeScript
PHP
const parcel = await shipping.createParcel ({
recipientName: 'Client' ,
phone: '0550123456' , // normalisé par carrier
phone2: '0770000000' ,
address: 'Cité 221 lots' ,
wilayaName: 'Alger' , // ou wilayaId: 16
communeName: 'Bab Ezzouar' ,
productName: 'T-shirt' , // ou productList: [{ name, quantity }]
price: 2500 ,
weight: 0.5 ,
orderId: 'ORD-1001' ,
remark: 'fragile' ,
isStopDesk: false ,
// stockType: 'none' | 'local' | 'warehouse' (ZRExpress)
});
// → { id, trackingNumber, status, provider, raw? }
$parcel = $shipping->createParcel ([
'recipientName' => 'Client' ,
'phone' => '0550123456' ,
'address' => 'Cité 221 lots' ,
'wilayaId' => 16 ,
'communeName' => 'Bab Ezzouar' ,
'productName' => 'T-shirt' ,
'price' => 2500 ,
'orderId' => 'ORD-1001' ,
]);
// $parcel->id · trackingNumber · status · provider
track
Retourne le colis courant + historique d’événements (plus ancien → plus récent). Maystro : NOT_SUPPORTED (webhooks).
TypeScript
PHP
const { parcel, events } = await shipping.track (trackingNumber);
// events: [{ status, label, at, raw? }, …]
$result = $shipping->track ($trackingNumber);
foreach ($result->events as $event) {
echo "$event->at $event->status $event->label\n" ;
}
getFees
Une cotation pour destination + mode. Yalidine utilise fromWilayaId du client.
TypeScript
PHP
const quote = await shipping.getFees ({
toWilayaId: 16 ,
toCommuneId: '1630' , // utile Maystro / Yalidine
isStopDesk: false ,
weight: 2 ,
});
// { deliveryFee, stopDeskFee?, returnFee?, currency: 'DZD' }
$quote = $shipping->getFees ([
'toWilayaId' => 16 ,
'toCommuneId' => 1630 ,
'isStopDesk' => false ,
'weight' => 2 ,
]);
getWilayas · getCommunes · getStopDesks
Lieux mis en cache sur la durée de vie du client. Stop desks : passer desk.id comme stopDeskId .
TypeScript
PHP
const wilayas = await shipping.getWilayas ();
const communes = await shipping.getCommunes (16 );
// ZRExpress : préférer wilaya seule (filtre commune souvent vide)
const desks = await shipping.getStopDesks ({ wilayaId: 16 });
// Maystro : communeId obligatoire
await shipping.createParcel ({
/* … */
isStopDesk: true ,
stopDeskId: desks[0 ].id,
});
$wilayas = $shipping->getWilayas ();
$communes = $shipping->getCommunes (16 );
$desks = $shipping->getStopDesks (['wilayaId' => 16 ]);
listParcels
Liste paginée des colis du compte + statut unifié. Pas d’historique d’événements — utiliser track() . ZRExpress via parcels/search ; autres : NOT_SUPPORTED pour l’instant. Préférer les webhooks en production.
TypeScript
PHP
const page = await zr.listParcels ({ page: 1 , pageSize: 50 });
// page.parcels · page.hasNext · page.totalCount
await zr.listParcels ({ orderId: 'ORD-1001' });
await zr.listParcels ({ keyword: '0550' , status: 'pending' });
await zr.listParcels ({ updatedSince: '2026-10-01T00:00:00+00:00' , allPages: true });
$page = $zr->listParcels (['page' => 1 , 'pageSize' => 50 ]);
$zr->listParcels (['orderId' => 'ORD-1001' ]);
cancelParcel · healthCheck
cancelParcel accepte tracking ou id interne. healthCheck sonde les credentials (wilayas, hors cache) → { ok, provider, latencyMs } .
TypeScript
PHP
await shipping.cancelParcel (parcel.trackingNumber);
const health = await shipping.healthCheck ();
$shipping->cancelParcel ($parcel->trackingNumber);
$health = $shipping->healthCheck ();
Contrat
Modèles principaux
Champs camelCase en TS et PHP. Montants en DZD, poids en kg, dates ISO 8601.
ParcelInput (création)
Champ Requis Description
recipientName, phone, address, priceoui Identité + COD
wilayaId ou wilayaNameun des deux Code 1–58, id, ou nom
communeId ou communeNameun des deux Id ou nom dans la wilaya
productName ou productListun des deux Description produit(s)
isStopDesk + stopDeskIdsi relais stopDeskId = id de getStopDesks
orderId, remark, weight, dimsnon Réf. marchand, note, kg/cm
stockTypenon ZRExpress : none (défaut) / local / warehouse
Parcel · TrackingEvent
Modèle Champs
Parcelid, trackingNumber, status, provider, raw?
TrackingEventstatus, label, at, raw?
FeeQuotedeliveryFee, stopDeskFee?, returnFee?, currency: 'DZD'
Locationid, name, parentId?, code?
Normalisation
Statuts unifiés
Un statut natif inconnu → unknown (jamais une erreur). Le libellé natif reste dans label / raw .
pending
created
in_transit
delivered
returned
cancelled
unknown
Mappers exportés : mapZRExpressStatus , mapYalidineStatus , mapMaystroStatus , mapEcotrackStatus (PHP : StatusMapper ).
ShippingError
Erreurs
Toute échec lève ShippingError . Ne catch pas sans lire code .
Code Quand
VALIDATION_ERRORInput invalide (souvent avant HTTP) ou 400/422
AUTH_FAILED401 / 403
NOT_FOUND404, wilaya/commune inconnue, colis absent
RATE_LIMITED429 — voir retryAfter
NOT_SUPPORTEDOpération absente chez le carrier
PROVIDER_ERROR5xx / réponse inattendue
NETWORK_ERRORTimeout, DNS, abort
UNKNOWNAutres
TypeScript
PHP
import { isShippingError } from '@dz-delivery/core';
try {
await shipping.createParcel (input);
} catch (err) {
if (isShippingError (err)) {
console.error(err.code, err.message, err.httpStatus, err.details);
}
throw err;
}
use DzDelivery\Errors\ShippingError;
try {
$shipping->createParcel ($input);
} catch (ShippingError $e) {
echo $e->code . ': ' . $e->getMessage ();
}
Compatibilité
Ce que chaque transporteur expose
NOT_SUPPORTED = l’API carrier n’a pas l’opération (pas un bug SDK).
Méthode
ZRExpress
Yalidine
Maystro
Ecotrack
createParcel
oui oui oui oui
track
oui oui webhooks oui
getFees
oui oui oui oui
getWilayas / getCommunes
oui oui oui oui
getStopDesks
hubs centers communeId oui
listParcels
oui bientôt webhooks bientôt
cancelParcel
oui si En prép. non si non validé
healthCheck
oui oui oui oui
bulk / labels / webhooks verify
— — —
ZR-only · 0.1.7+
ZRExpress extras
Hors contrat unifié : méthodes sur ZRExpressProvider + helpers webhooks. createClient('zrexpress') reste l’interface simple.
Matrice complète et backlog :
docs/ZREXPRESS.md .
Webhooks (receive)
Svix / Standard Webhooks — corps brut obligatoire (avant JSON.parse). Headers svix-id / svix-timestamp / svix-signature (aliases webhook-* ). Fenêtre ~5 min.
TypeScript
PHP
import { verifyZRExpressWebhook } from '@dz-delivery/core';
app.post('/webhooks/zrexpress' , express.raw({ type: '*/*' }), (req, res) => {
const event = verifyZRExpressWebhook (
req.body, req.headers, process.env.ZREXPRESS_WEBHOOK_SECRET!,
);
// event.type · parcelId · trackingNumber · status · raw
res.sendStatus(200 );
});
use DzDelivery\Providers\ZRExpress\Webhook;
$raw = file_get_contents('php://input' ); // pas Request::json()
$event = Webhook::verify ($raw, getallheaders(), getenv('ZREXPRESS_WEBHOOK_SECRET' ));
Labels
HTML via URL SAS. Auth : X-Api-Key + X-Tenant (comme le reste du SDK).
TypeScript
PHP
const multi = await zr.generateLabels (['16-AAA111-ZR' , '16-BBB222-ZR' ]);
// { fileUrl, failedTrackingNumbers } — max 250
const one = await zr.generateLabels (['16-AAA111-ZR' ], { mode: 'individual' });
// { labels: [{ trackingNumber, fileUrl }], failedTrackingNumbers } — max 100
$multi = $zr->generateLabels (['16-AAA111-ZR' ]);
$one = $zr->generateLabels (['16-AAA111-ZR' ], ['mode' => 'individual' ]);
Bulk create / delete
Création : max 100, orderId obligatoire par item. Suppression : max 200, UUID ou tracking (détection auto). Réponses 200 / 207 / 400 → résultat structuré, pas d’exception si le corps est un rapport bulk.
TypeScript
PHP
const created = await zr.createParcelsBulk ([
{ ...input, orderId: 'ORD-1' },
{ ...input, orderId: 'ORD-2' },
]);
// { successes: Parcel[], failures: [{ index, orderId?, error }] }
const deleted = await zr.deleteParcelsBulk ([
'8c1a4c53-0000-4000-8000-000000000001' , // UUID
'16-AAA111-ZR' , // tracking
]);
$created = $zr->createParcelsBulk ([
array_merge($input, ['orderId' => 'ORD-1' ]),
]);
$deleted = $zr->deleteParcelsBulk ([$uuid, '16-AAA111-ZR' ]);
Domaine SDK Notes
create / track / fees / lieux / list / cancel / health contrat ShippingProvider
Webhooks receive verify + parse
Labels multiple / individual
Bulk create / delete orderId requis en create
Update / pickup bags / catalog / claims backlog prochain lot
Particularités
Notes par transporteur
Montant ≤ 150 000 DZD (frais inclus). Téléphones en international (+213… ). Stop desks = hubs ; préfère wilaya seule. Sans stateId , le colis reste en OrderReceived jusqu’à passage « prêt à expédier » côté portail.
fromWilayaId obligatoire à la construction. Annulation seulement en « En préparation ». GuepEx = même API, autre baseUrl .
Pas d’API track/cancel — configurer les webhooks Maystro et mapper avec mapMaystroStatus . getStopDesks exige communeId .
Un provider, plusieurs coursiers via courier (dhd , anderson , worldexpress …) ou baseUrl HTTPS du tenant. Option autoValidate pour valider juste après création.
Mapping natif détaillé :
PROVIDERS.md .
Guide PHP (Laravel, Maystro webhooks) :
guide-php.md .
Questions fréquentes
FAQ
Comment utiliser l’API Yalidine en Node.js ou en PHP ?
Installez @dz-delivery/core (npm) ou dz-delivery/sdk (Composer), puis créez un client yalidine avec apiId , apiToken et votre wilaya d’origine. Guide Yalidine .
Comment intégrer ZR Express dans ma boutique ?
Un client zrexpress avec votre apiKey et votre tenant suffit pour créer, suivre, tarifer, lister et annuler des colis — plus les étiquettes, le bulk et les webhooks signés. Guide ZRExpress .
DHD, Anderson, World Express… sont-ils supportés ?
Oui : ce sont des transporteurs de la plateforme Ecotrack. Un seul provider ecotrack les couvre, avec une clé courier intégrée ou l’URL de votre tenant. Guide Ecotrack .
Puis-je changer de transporteur sans réécrire mon code ?
Oui. Tous les transporteurs renvoient les mêmes objets et les mêmes statuts normalisés (pending , in_transit , delivered …). Seule la configuration du client change.
Ça marche avec Laravel, WooCommerce ou Shopify ?
Le SDK PHP s’installe dans n’importe quel projet Composer (exemple Laravel dans le guide PHP ). Pour un backend Node.js ou une app Shopify, utilisez le package npm.
Est-ce gratuit ?
Oui, le SDK est open source sous licence MIT. Vous utilisez vos propres identifiants API chez chaque transporteur.
Aller plus loin
Références
Sources dans le dépôt et packages publiés.