Search by

uften / courier

uften

A modern, strictly-typed Laravel package for integrating Algerian shipping providers (Yalidine, Maystro, Procolis/ZR Express, Ecotrack) with a unified adapter-based API.

Package info

github.com/uften/courier

pkg:composer/uften/courier

Statistics

Installs: 139

Dependents: 0

Suggesters: 0

Stars: 10

Open Issues: 0

v1.3.0 2026-07-31 23:33 UTC

This package is auto-updated.

Last update: 2026-09-30 02:44:02 UTC


README

A modern, strictly-typed Laravel package for integrating most Algerian shipping providers — built on PHP 8.4, backed by enums, readonly DTOs, and a clean adapter pattern.

PHP 8.4 Laravel Tests License: MIT

Why uften/courier?

Every Algerian courier has a different API shape, different field names, and different tracking status strings ("Livré", "delivered", "SORTIE EN LIVRAISON" …). Integrating more than one means writing glue code over and over.

uften/courier solves this with three principles:

  1. Unified DTOs — CreateOrderData, OrderData, RateData, LabelData. One shape, every provider.
  2. Canonical status enum — TrackingStatus with 12 values. Each adapter normalises its raw strings or IDs into this dictionary. Your code only ever sees TrackingStatus::DELIVERED.
  3. Swappable adapters — swap providers with a single enum change. Zero application-code changes.

Supported Providers — 96 total

Yalidine engine (6 providers)

Provider Enum Base URL Logo
Yalidine Provider::YALIDINE api.yalidine.app yalidine.png
Yalitec Provider::YALITEC api.yalitec.me yalitec.png
Easy and Speed Provider::EASY_AND_SPEED api.easyandspeed.app easy-and-speed.png
Economiqua Provider::ECONOMIQUA api.economiqua.app economiqua.png
GuepEx Express Provider::GUEPEX api.guepex.app guepex.png
We Can Services Provider::WE_CAN api.wecanservices.me we-can.png

Maystro (standalone)

Provider Enum Base URL
Maystro Delivery Provider::MAYSTRO backend.maystro-delivery.com/api

Procolis engine

Provider Enum Base URL
Procolis Provider::PROCOLIS procolis.com/api_v1
ZR Express Provider::ZREXPRESS procolis.com/api_v1

Zimou Express (delivery router)

Provider Enum Base URL
Zimou Express Provider::ZIMOU zimou.express/api

ZR Express NEW (standalone — new platform)

Provider Enum Base URL
ZR Express NEW Provider::ZREXPRESS_NEW api.zrexpress.app

Ecotrack engine — 23 providers sharing one API surface

Provider Enum Subdomain
Ecotrack (generic) Provider::ECOTRACK ecotrack.dz
Anderson Delivery Provider::ANDERSON anderson.ecotrack.dz
Areex Provider::AREEX areex.ecotrack.dz
BA Consult Provider::BA_CONSULT bacexpress.ecotrack.dz
Conexlog (UPS) Provider::CONEXLOG app.conexlog-dz.com
Coyote Express Provider::COYOTE_EXPRESS coyoteexpressdz.ecotrack.dz
DHD Provider::DHD dhd.ecotrack.dz
Distazero Provider::DISTAZERO distazero.ecotrack.dz
48Hr Livraison Provider::E48HR 48hr.ecotrack.dz
FRET.Direct Provider::FRETDIRECT fret.ecotrack.dz
GOLIVRI Provider::GOLIVRI golivri.ecotrack.dz
Mono Hub Provider::MONO_HUB mono.ecotrack.dz
MSM Go Provider::MSM_GO msmgo.ecotrack.dz
Negmar Express Provider::NEGMAR_EXPRESS negmar.ecotrack.dz
Packers Provider::PACKERS packers.ecotrack.dz
Prest Provider::PREST prest.ecotrack.dz
RB Livraison Provider::RB_LIVRAISON rblivraison.ecotrack.dz
Rex Livraison Provider::REX_LIVRAISON rex.ecotrack.dz
Rocket Delivery Provider::ROCKET_DELIVERY rocket.ecotrack.dz
Salva Delivery Provider::SALVA_DELIVERY salvadelivery.ecotrack.dz
Speed Delivery Provider::SPEED_DELIVERY speeddelivery.ecotrack.dz
TSL Express Provider::TSL_EXPRESS tsl.ecotrack.dz
WorldExpress Provider::WORLDEXPRESS worldexpress.ecotrack.dz

Supported Methods

Method Yalidine / Yalitec Maystro Procolis / ZR Express ZR Express NEW Zimou Express Ecotrack engine (all 23)
testCredentials() ✅ ✅ ✅ ✅ ✅ ✅
metadata() ✅ ✅ ✅ ✅ ✅ ✅
getRates() ✅ * ❌ ✅ ✅‡ ❌ ✅
getCreateOrderValidationRules() ✅ ✅ ✅ ✅ ✅ ✅
createOrder() ✅ ✅ ✅ ✅ ✅ ✅
getOrder() ✅ ✅ ✅ ✅ ** ✅ *** ✅
cancelOrder() ➖ ➖ ➖ ✅ ✅ ➖
getLabel() ✅ ✅ ❌ ✅ ✅ ✅
createProduct() + ❌ ✅ ❌ ❌ ❌ ❌

* Yalidine getRates() requires $fromWilayaId.
** getOrder() accepts either the Zimou integer package ID or the tracking_code string.
*** ZR Express NEW getOrder() accepts either a UUID parcel ID or a tracking number (e.g. 16-JUKYSI-ZR). cancelOrder() also supported (maps to DELETE /parcels/{id}).

  • Maystro-only method, not part of the ProviderAdapter contract — type-hint MaystroAdapter directly.
    ➖ = planned / unknown support. ‡ ZR Express NEW getOrder() accepts UUID or tracking number. cancelOrder() supported. getLabel() returns LabelType::HTML_URL (Azure Blob SAS URL to an HTML label file — SAS tokens expire, do not cache). getRates() returns commune-level prices; RateData::$toWilayaId is 0 for all entries.

Requirements

  • PHP 8.4+
  • Laravel 12, 13

Installation

composer require uften/courier

Publish the config:

php artisan vendor:publish --tag=courier-config

Publish carrier logo assets:

php artisan vendor:publish --tag=courier-assets

Carrier logos will be published to public/vendor/courier/logo/ and can be retrieved programmatically using $provider->getLogoUrl() or $provider->getLogoFileName().

Configuration

Add your credentials to .env:

# Yalidine engine
YALIDINE_API_TOKEN=your-api-id
YALIDINE_API_KEY=your-api-key
YALITEC_API_TOKEN=your-api-id
YALITEC_API_KEY=your-api-key

# Maystro
MAYSTRO_API_TOKEN=your-token

# Procolis / ZR Express
PROCOLIS_ID=your-id
PROCOLIS_TOKEN=your-token
ZREXPRESS_ID=your-id
ZREXPRESS_TOKEN=your-token

# ZR Express NEW (new platform — NOT the legacy Procolis one)
ZREXPRESS_NEW_TENANT_ID=your-tenant-uuid
ZREXPRESS_NEW_API_KEY=your-api-secret-key
# Optional — only needed if getLabel() returns 401 with X-Api-Key alone:
ZREXPRESS_NEW_BEARER_TOKEN=your-jwt-token

# Zimou Express
ZIMOU_API_TOKEN=your-bearer-token

# Ecotrack-engine providers (one token per carrier account)
ECOTRACK_API_TOKEN=your-token
DHD_API_TOKEN=your-token
CONEXLOG_API_TOKEN=your-token
# ... see config/courier.php for the full list

Usage

Resolve a provider

use Uften\Courier\Facades\Courier;
use Uften\Courier\Enums\Provider;

$zimou    = Courier::provider(Provider::ZIMOU);
$yalidine = Courier::provider(Provider::YALIDINE);
$dhd      = Courier::via('dhd');

// Runtime credentials override
$zimou = Courier::provider(Provider::ZIMOU, ['token' => 'my-token']);

Create an order

use Uften\Courier\Data\CreateOrderData;
use Uften\Courier\Enums\DeliveryType;

$order = Courier::provider(Provider::ZIMOU)->createOrder(
    new CreateOrderData(
        orderId:            'MY-ORD-069',
        firstName:          'Mohammed',
        lastName:           'A. ALLAL',
        phone:              '0669096909',
        address:            '69 Rue Hattab Amar',
        toWilayaId:         '09',
        toCommune:          'Blida',
        productDescription: 'Smartphone Samsung Galaxy S25',
        price:              120000.0,
        deliveryType:       DeliveryType::HOME,
        weight:             1.5,
        quantity:           1, // optional — number of items
    )
);

echo $order->trackingNumber; // Zimou's own tracking code, e.g. "ZM-ABC123"
echo $order->raw['id']; // Zimou's own package ID
echo $order->status->value;  // "pending"
echo $order->notes;          // "Via: Yalidine | Partner tracking: YALI-99999"

Zimou: requesting "Flexible" delivery

Zimou supports three delivery tiers: Express (default for HOME), Flexible (cheaper, slower), and Point relais (STOP_DESK). To request Flexible, embed the hint in the notes field:

new CreateOrderData(
    // ...
    deliveryType: DeliveryType::HOME,
    notes: 'zimou_delivery_type:Flexible|Leave at door if absent',
)

The adapter strips the prefix and uses "Flexible" for the API call. The remaining text after | is sent as the observation note.

Track a shipment

use Uften\Courier\Enums\TrackingStatus;

// By Zimou integer package ID
$order = Courier::provider(Provider::ZIMOU)->getOrder('2632165');

// Status is always canonical
if ($order->status->value === TrackingStatus::DELIVERED) { /* ... */ }
if ($order->status->isTerminal()) { /* stop polling */ }

// Access the partner carrier details (not always available)
echo $order->raw['tracking_partner_company'];        // "Yalidine"
echo $order->raw['delivery_company_tracking_code'];  // "YALI-99999"

ZR Express NEW — territory UUIDs

ZR Express NEW identifies delivery addresses by UUID territory IDs (not integer wilaya codes).

The cityTerritoryId (wilaya level) is auto-resolved from toWilayaId using a dynamic search or built-in map. You only need to supply the district UUID:

new CreateOrderData(
    toWilayaId: 9,   // Blida → auto-resolved to UUID
    notes: 'zr_district:8d0b6cd9-7712-47d2-9ea4-460246494c32',
    // ...
)

If you have the UUID for the city, you can pass it directly to toWilayaId:

new CreateOrderData(
    toWilayaId: 'f72674e2-45a8-444d-b65b-6c905e94b123', // Blida UUID
    // district defaults to the same UUID if zr_district is omitted
)

Otherwise, the full notes format still works: "zr_city:{uuid}|zr_district:{uuid}|optional note"

ZR Express NEW — cancelOrder

cancelOrder() is fully supported (maps to DELETE /api/v1/parcels/{id}):

// Pass the UUID parcel ID (from $order->raw['id'])
Courier::provider(Provider::ZREXPRESS_NEW)->cancelOrder('8c1a4c53-9d1a-4bb0-9b44-e9c0c2f90111');

// Or pass a tracking number — the adapter resolves the UUID internally
Courier::provider(Provider::ZREXPRESS_NEW)->cancelOrder('16-JUKYSI-ZR');

Access provider metadata (no API call needed)

$meta = Provider::ZIMOU->metadata();
echo $meta->title;       // "Zimou Express"
echo $meta->website;     // "https://zimou.express"
echo $meta->apiDocs;     // "https://zimou.express/api/docs"

// All 29 providers at once — perfect for building provider-selection UIs
$all = Courier::allMetadata(); // array<string, ProviderMetadata>

Get shipping rates

// Zimou returns your account's configured prices
$rates = Courier::provider(Provider::ZIMOU)->getRates();

foreach ($rates as $rate) {
    echo "{$rate->toWilayaName}: {$rate->homeDeliveryPrice} DZD (home) / {$rate->stopDeskPrice} DZD (stop desk)";
}

Fetch a label

use Uften\Courier\Enums\LabelType;

$label = Courier::provider(Provider::ZIMOU)->getLabel('2632165');

// Zimou returns PDF_URL
return redirect($label->url);

TrackingStatus Dictionary

Case Value Meaning
PENDING pending Created, not yet collected
PICKED_UP picked_up Collected from sender
IN_TRANSIT in_transit Moving between hubs
OUT_FOR_DELIVERY out_for_delivery With delivery agent
DELIVERED delivered Successfully delivered ✓
FAILED_DELIVERY failed_delivery Attempt failed
RETURNING returning Heading back to sender
RETURNED returned Back at sender
CANCELLED cancelled Cancelled before shipment
READY_FOR_PICKUP ready_for_pickup At stop desk / relay
EXCEPTION exception Lost, damaged, blocked
UNKNOWN unknown Unmapped raw status

Error Handling

use Uften\Courier\Exceptions\{
    AuthenticationException,
    CourierException,
    OrderNotFoundException,
    UnsupportedOperationException
};

try {
    $order = Courier::provider(Provider::ZIMOU)->getOrder($orderId);
} catch (OrderNotFoundException $e) {       // order number not found
} catch (AuthenticationException $e) {      // bad credentials
} catch (UnsupportedOperationException $e) { // feature not supported
} catch (CourierException $e) {             // any other provider error
    // For Zimou: also thrown when the API returns {"error": 1, "message": "..."}
}

Extending: Custom Adapter at Runtime

Courier::extend(Provider::ZIMOU, function (?array $credentials) {
    return new MyCustomZimouAdapter($credentials);
});

Testing

composer test           # Pest suite
composer test:coverage  # with Xdebug
composer format         # Laravel Pint

Disclaimer

  • Not officially affiliated with or endorsed by any shipping provider.
  • Verify all providers are authorised by ARPCE before use.

Contributing

See CONTRIBUTING.md.

Credits

Thanks to Piteur Studio for providing the primary endpoints for this package.

Changelog

See CHANGELOG.md.

License

MIT — see LICENSE.md.