dz-delivery

dz-deliverySDK livraison Algérie

Un seul SDK pour créer, suivre et tarifer des colis avec Yalidine, ZRExpress, Maystro, Ecotrack (DHD…) — TypeScript et PHP, même contrat.

Démarrage

Installer

Packages publics · npm @dz-delivery/core · Composer dz-delivery/sdk.

TypeScript · Node ≥ 18
npm install @dz-delivery/core
PHP ≥ 8.2 · Guzzle 7
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.

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);

Configuration

Clients & credentials

Un client par transporteur. Options communes : baseUrl, timeoutMs (15s), includeRaw (true).

CléConfigNotes
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…
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' });

ShippingProvider

API unifiée

Même interface pour tous les transporteurs. Pas un wrapper 1:1 — les opérations dont une boutique a besoin.

  1. 01CréercreateParcel
  2. 02Suivretrack
  3. 03TarifergetFees
  4. 04LieuxgetWilayas · getCommunes · getStopDesks
  5. 05Lister (compte)listParcels
  6. 06AnnulercancelParcel
  7. 07SantéhealthCheck

createParcel

Valide côté client avant HTTP. Accepte codes wilaya 1–58, ids transporteur, ou noms. price = montant COD en DZD.

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

track

Retourne le colis courant + historique d’événements (plus ancien → plus récent). Maystro : NOT_SUPPORTED (webhooks).

const { parcel, events } = await shipping.track(trackingNumber);
// events: [{ status, label, at, raw? }, …]

getFees

Une cotation pour destination + mode. Yalidine utilise fromWilayaId du client.

const quote = await shipping.getFees({
  toWilayaId: 16,
  toCommuneId: '1630', // utile Maystro / Yalidine
  isStopDesk: false,
  weight: 2,
});
// { deliveryFee, stopDeskFee?, returnFee?, currency: 'DZD' }

getWilayas · getCommunes · getStopDesks

Lieux mis en cache sur la durée de vie du client. Stop desks : passer desk.id comme stopDeskId.

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,
});

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.

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 });

cancelParcel · healthCheck

cancelParcel accepte tracking ou id interne. healthCheck sonde les credentials (wilayas, hors cache) → { ok, provider, latencyMs }.

await shipping.cancelParcel(parcel.trackingNumber);
const health = await shipping.healthCheck();

Contrat

Modèles principaux

Champs camelCase en TS et PHP. Montants en DZD, poids en kg, dates ISO 8601.

ParcelInput (création)

ChampRequisDescription
recipientName, phone, address, priceouiIdentité + COD
wilayaId ou wilayaNameun des deuxCode 1–58, id, ou nom
communeId ou communeNameun des deuxId ou nom dans la wilaya
productName ou productListun des deuxDescription produit(s)
isStopDesk + stopDeskIdsi relaisstopDeskId = id de getStopDesks
orderId, remark, weight, dimsnonRéf. marchand, note, kg/cm
stockTypenonZRExpress : none (défaut) / local / warehouse

Parcel · TrackingEvent

ModèleChamps
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.

CodeQuand
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
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;
}

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 ouiouiouioui
track ouiouiwebhooksoui
getFees ouiouiouioui
getWilayas / getCommunes ouiouiouioui
getStopDesks hubscenterscommuneIdoui
listParcels ouibientôtwebhooksbientôt
cancelParcel ouisi En prép.nonsi non validé
healthCheck ouiouiouioui
bulk / labels / webhooks verify extras———

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.

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);
});

Labels

HTML via URL SAS. Auth : X-Api-Key + X-Tenant (comme le reste du SDK).

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

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.

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
]);
DomaineSDKNotes
create / track / fees / lieux / list / cancel / healthcontratShippingProvider
Webhooks receivehelpersverify + parse
LabelsgenerateLabelsmultiple / individual
Bulk create / deletecreateParcelsBulk / deleteParcelsBulkorderId requis en create
Update / pickup bags / catalog / claimsbacklogprochain lot

Particularités

Notes par transporteur

ZRExpress

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.

Yalidine / GuepEx

fromWilayaId obligatoire à la construction. Annulation seulement en « En préparation ». GuepEx = même API, autre baseUrl.

Maystro

Pas d’API track/cancel — configurer les webhooks Maystro et mapper avec mapMaystroStatus. getStopDesks exige communeId.

Ecotrack

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.