Global Logistics Aggregation (global-logistics)
Global Logistics Aggregation is a composer package with a unified facade for domestic express and international logistics tracking queries (PHP 8.2+, PSR-4, framework-agnostic).
Project introduction
global-logistics collapses tracking queries for 209 express/postal carriers worldwide into a single facade: your application just passes in a tracking number, and the package works out the domestic/international channel and the carrier on its own — you never have to care about the differences between carriers (signing, OAuth2, XML/JSON, status mapping).
| Metric | Value |
|—-|—-|
| Carriers integrated | 209 (45 domestic + 164 international) |
| Tracking-number detection rules | 187 (order-sensitive, first match wins) |
| International coverage | The big four (DHL / FedEx / UPS / USPS) + national postal S10 systems (Europe, Latin America and the Caribbean, Africa and the Middle East, Asia-Pacific) |
| Unified status semantics | 7 TrackStatus values (including exception / returned) |
| Tests | 1663 cases / 6662 assertions, all green |
| Environment | PHP 8.2+, PSR-4 / PSR-18, no framework binding; works out of the box with Laravel / ThinkPHP / Hyperf / Webman / Yii 2 |
Project description
Built for e-commerce, warehousing, ERP and similar business systems, it converges the official APIs of “domestic express + international logistics” into a single facade:
- One entry point:
Logistics::track($trackingNo)automatically identifies the domestic/international channel and the carrier — no need to know who owns the tracking number - One data model: every carrier returns the same
Tracking/TrackingEventstructure, so the business layer only talks to one shape - One status semantic: the wildly varying raw statuses of carriers are mapped onto a unified
TrackStatusenum (7 values) - Global coverage: 164 international carriers, including DHL / FedEx / UPS / USPS and the national postal S10 systems (Europe, Latin America and the Caribbean, Africa and the Middle East, Asia-Pacific)
- Zero hardcoded credentials: every carrier’s credentials are injected through configuration, keeping code and secrets completely separate
Features
Integrated carriers (209)
Domestic: SF Express, ZTO Express, YTO Express, J&T Express, Yunda Express, STO Express, JD Logistics, EMS, BEST Express, Deppon Logistics, Kuayue Express, ANE Logistics, Cainiao Express, China Post, Suning Logistics, UC Express, Yimidida, ZJS Express, TTK Express, ZTO Freight, Daniao (Cainiao Direct Delivery), China Railway Express, Shunxin Jetta, SURE Express, Xinfeng Logistics, Lianhaotong, RRS Logistics, Fengwang Express, BEST Freight, Yunda Freight, YTO Freight, Zengyi Express, China Civil Aviation Express, Hoau, Jiaji Express, Longbang Express, A Plus Express, Suteng Logistics, China Railway Logistics, China Post Logistics, Zengyi Express, Quanfeng Express, Guotong Express, Yuancheng Express, Xinbang Logistics
International: DHL, FedEx, UPS, USPS, Royal Mail, Canada Post, Australia Post, Japan Post, Aramex, GLS, DPD, PostNL, Cainiao International, Correios (Brazil), Evri, 4PX, Hongkong Post, Kerry Express, Korea Post, La Poste (France), NZ Post, Poste Italiane, Russian Post, Singapore Post, Swiss Post, Yodel, Yuntu Logistics, Yanwen Logistics, SF International, TNT, ONTRAQ, Purolator, bpost (Belgian Post), Correos (Spanish Post), Delhivery (India), InPost (Poland parcel lockers), Omniva (Estonia), Posti (Finland), Bring (Norway), Austrian Post, Thailand Post, Chunghwa Post (Taiwan), PostNord (Sweden/Denmark), CTT (Portugal), An Post (Ireland), Poczta Polska (Poland), India Post, Pos Malaysia (Malaysia), Emirates Post (UAE), Magyar Posta (Hungary), Česká pošta (Czech Republic), ELTA (Greece), Viettel Post (Vietnam), ZTO International, YTO International, J&T International, Winit, Ukrposhta (Ukraine), Turkey PTT (Turkey), Israel Post (Israel), Egypt Post (Egypt), Saudi Post (Saudi Arabia), South African Post (South Africa), Correos de México (Mexico), Correo Argentino (Argentina), Correos de Chile (Chile), Pos Indonesia (Indonesia), PHLPost (Philippines), Pakistan Post (Pakistan), Kazpost (Kazakhstan), Poșta Română (Romania), Hrvatska pošta (Croatia), Slovak Post (Slovakia), Pošta Slovenije (Slovenia), Pošta Srbije (Serbia), Bulgarian Posts (Bulgaria), Lietuvos paštas (Lithuania), Latvijas Pasts (Latvia), Íslandspóstur (Iceland), MaltaPost (Malta), POST Luxembourg (Luxembourg), Cyprus Post (Cyprus), Poșta Moldovei (Moldova), Posta Shqiptare (Albania), Belpochta (Belarus), Makedonska Pošta (North Macedonia), BH Pošta (Bosnia and Herzegovina), Deutsche Post (Germany), Montenegro Post (Montenegro), Andorra Post (Andorra), La Poste Monaco (Monaco), Liechtenstein Post (Liechtenstein), Poste San Marino (San Marino), Poste Vaticane (Vatican), Royal Gibraltar Post (Gibraltar), Jersey Post (Jersey), Guernsey Post (Guernsey), Isle of Man Post (Isle of Man), Posta Faroe Islands (Faroe Islands), Post Greenland (Greenland), Post Åland (Åland Islands), 4-72 (Colombia), Serpost (Peru), Correo Uruguayo (Uruguay), Correo Paraguayo (Paraguay), Correos de Bolivia (Bolivia), Correos del Ecuador (Ecuador), Ipostel (Venezuela), Correos de Costa Rica (Costa Rica), Correos de Panamá (Panama), INPOSDOM (Dominican Republic), Correo de Guatemala (Guatemala), HonduCorreo (Honduras), Correos de El Salvador (El Salvador), Correos de Nicaragua (Nicaragua), Correos de Cuba (Cuba), Jamaica Post (Jamaica), TTPOST (Trinidad and Tobago), Barbados Post (Barbados), Bahamas Post (Bahamas), Suriname Post (Suriname), Guyana Post (Guyana), Barid Al-Maghrib (Morocco), Algérie Poste (Algeria), La Poste Tunisienne (Tunisia), Posta Kenya (Kenya), NIPOST (Nigeria), Ethiopia Post (Ethiopia), Ghana Post (Ghana), Tanzania Post (Tanzania), Uganda Post (Uganda), Rwanda Post (Rwanda), Zampost (Zambia), Zimpost (Zimbabwe), Mozambique Post (Mozambique), Correios de Angola (Angola), La Poste Sénégalaise (Senegal), La Poste de Côte d’Ivoire (Côte d’Ivoire), Cameroon Post (Cameroon), Mauritius Post (Mauritius), Qatar Post (Qatar), Kuwait Post (Kuwait), Bahrain Post (Bahrain), Bangladesh Post (Bangladesh), Nepal Post (Nepal), Sri Lanka Post (Sri Lanka), Myanmar Post (Myanmar), Cambodia Post (Cambodia), Laos Post (Laos), Mongolia Post (Mongolia), Georgian Post (Georgia), Azərpoçt (Azerbaijan), HayPost (Armenia), Uzbekistan Post (Uzbekistan), Kyrgyz Post (Kyrgyzstan), Tajikistan Post (Tajikistan), Turkmenistan Post (Turkmenistan), Afghanistan Post (Afghanistan), Bhutan Post (Bhutan), Maldives Post (Maldives), Brunei Post (Brunei), Papua New Guinea Post (Papua New Guinea), Fiji Post (Fiji), Samoa Post (Samoa)
Unified status enum (GlobalLogistics\Support\TrackStatus)
PENDING (awaiting pickup) → IN_TRANSIT (in transit) → OUT_FOR_DELIVERY (out for delivery) → DELIVERED (signed for); exceptions map to EXCEPTION, returns to RETURNED, and anything unrecognizable to UNKNOWN.
Core capabilities
- Automatic tracking-number detection (187 regex rules, order-sensitive, domestic rules take priority)
- Unified tracking queries (
Logistics::track()) plus explicit channel calls (domestic()/international()) - A unified exception hierarchy (authentication failure / tracking number not found / network error / carrier not registered / API error)
- HTTP infrastructure: PSR-18 client, automatic OAuth2 token acquisition and caching, automatic retry on failure
- Framework auto-discovery: works out of the box with Laravel / ThinkPHP 8 / Hyperf / Webman / Yii 2
- Callback signature verification (SF Express example:
verifyCallbackSignature())
Usage
Installation
1 | composer require erikwang2013/global-logistics |
Configuration
1 |
|
Framework projects (Laravel, etc.) can use the
config/logistics.phptemplate directly — see “Framework integration”.If
configure()is never called, the facade initializes itself with an empty configuration (only carriers that need no credentials are supported).
Configuration reference
Top-level options
| Key | Type | Default | Description |
|---|---|---|---|
http_client |
a PSR-18 client or null |
null |
Custom HTTP client; null builds Guzzle automatically |
max_retries |
int | 2 |
Number of retries after a single failed request |
registry |
array | built-in registry of 209 carriers | Custom “channel → code → adapter class” registry (advanced) |
detector_rules |
array | built-in 187 rules | Custom tracking-number detection rules (advanced) |
Credential field cheat sheet
Each carrier authenticates differently, so field names differ too. The common mappings are:
| Field | Meaning | Typical carriers |
|---|---|---|
partner_id + checkword |
SF-style signed account and check code | sf, sf-international |
company_id + secret |
ZTO-style company account and secret | zto, zto-freight, zto-intl |
app_key + app_secret |
Generic signing key pair | yto, yd, jt, debon, ky, etc. |
ebusiness_id + app_key |
Kdniao API (electronic waybills) | lht, rrs, sure, xf and the various freight carriers |
client_id + client_secret |
OAuth2 client credentials (token acquired and refreshed automatically) | dhl, fedex, ups, royal-mail, swiss-post, yodel |
user_id / api_key / key |
An account or key issued by the carrier | usps, postnl, gls, saudi-post, etc. |
endpoint |
Custom API endpoint (optional; leave it as '' to use the built-in official address) |
Supported by all carriers |
Which fields a given carrier needs is determined by the keys of its entry in
config/logistics.php; carriers that need no credentials can simply use an empty array.
Obtaining credentials
- Domestic express: contact the carrier’s sales or integration contact to apply for a “tracking query / electronic waybill API” account (SF Fengqiao, ZTO Open Platform, etc.)
- International express: DHL / FedEx / UPS and others let you register a developer account on their developer portal to obtain OAuth2 credentials; USPS, PostNL and others issue API Keys on their websites
- National postal operators: most offer a public tracking API that needs no credentials; where credentials are required, apply at the postal operator’s developer center
Carriers that need no credentials
The following carriers work as-is — just leave their configuration as an empty array: domestic sto (STO Express), jd (JD Logistics); international japan-post (Japan Post) and most postal operators (turkey-post, israel-post, egypt-post, south-african-post, phl-post, pakistan-post, kazpost, etc.). See config/logistics.php for the full list.
Credential security
Inject credentials through environment variables or your framework’s .env rather than hardcoding them into the repository:
1 | Logistics::configure([ |
Querying tracking information
1 | // Automatically identifies the channel (domestic/international) and the carrier |
Error handling
All exceptions extend GlobalLogistics\Exceptions\LogisticsException, so they can be caught in one place:
| Exception | Scenario |
|---|---|
CarrierNotFoundException |
The carrier cannot be identified from the tracking number |
TrackingNotFoundException |
The tracking number is valid but the carrier has no tracking record for it |
AuthException |
Authentication failure (bad credentials, etc.) |
NetworkException |
HTTP network error (implements the PSR-18 NetworkExceptionInterface) |
LogisticsException |
Other API/parsing errors |
1 | use GlobalLogistics\Exceptions\LogisticsException; |
Callback signature verification (subscription push)
Once the carrier has enabled its subscription API, here is how to handle the callback (SF Express as the example):
1 | use GlobalLogistics\Logistics; |
Architecture
Directory structure
1 | global-logistics/ |
Responsibilities of each layer
Logistics(src/Logistics.php): the static facade; it holds the global configuration, detector and factory, and initializes itself with an empty configuration when nothing has been configuredDetector(src/Detector.php+src/Resources/detector-rules.php): a regex rule table where the first match in order wins, returning aDetection(channel + carrier code); rule order is sensitive (for example, STO numbers starting with 77 must be matched before the plain 13-digit rule)CarrierFactory(src/CarrierFactory.php+src/Resources/carrier-registry.php): instantiates adapters from the “channel → code → adapter class” registry, injectingConfigand the HTTP client in a uniform way- Carrier adapters (
src/Carriers/): implementCarrierInterfaceand absorb each carrier’s protocol differences (signing, OAuth2, XML/JSON, status mapping); they all follow one template structure (ENDPOINTconstant +STATUS_MAP+mapEvent()), which makes adding carriers straightforward - HTTP layer (
src/Http/):OAuthTokenClientis a PSR-18 decorator that fetches the token lazily, caches it for the process (expiring 60s early) and refreshes plus retries once on a 401;RetryingClientretries failed requests according tomax_retries - Model layer (
src/Models/):Tracking/TrackingEventare immutable objects;Order/OrderRequest/Labelare reserved for ordering and waybill capabilities Config(src/Config.php): dot-notation value lookup ($config->get('dhl.client_id'))- Exception hierarchy (
src/Exceptions/):LogisticsExceptionis the base class, with 4 scenario-specific subclasses
Adding a new carrier
- Create the adapter class (following the
src/Carriers/Domestic/Yto.phptemplate): implementCarrierInterfaceand do the status mapping inmapEvent() - Registry: add the “channel → code → class” entry to
src/Resources/carrier-registry.php - Tracking-number rules: add a regex to
src/Resources/detector-rules.php(mind the ordering — domestic rules come first) - Add a fixture and an adapter test (mock HTTP, no real credentials needed)
Framework integration
After composer require erikwang2013/global-logistics the package is auto-discovered per framework, with no manual registration; the configuration template is consistently an array keyed by carrier code at the top level (see config/logistics.php; the structure matches the argument to Logistics::configure()).
Laravel
- Auto-registration: composer package discovery via
extra.laravel.providers, nothing to configure - Publish the config:
php artisan vendor:publish --tag=global-logistics(generatesconfig/logistics.php) - Usage:
\GlobalLogistics\Logistics::track('SF1234567890')
ThinkPHP 8
- Auto-registration: composer
extra.think.services(generatesvendor/services.phpat install time; you can also regenerate it withphp think service:discover) - Configuration: have
config/logistics.phpin your application return an array of the same structure (it overrides the package defaults) - Usage:
\GlobalLogistics\Logistics::track('SF1234567890')
Hyperf
- Auto-discovery: composer
extra.hyperf.configpoints at the ConfigProvider - Publish the config:
php bin/hyperf.php vendor:publish(publishes toconfig/autoload/logistics.php) - Usage:
\GlobalLogistics\Logistics::track('SF1234567890')
Webman
- Auto-install: the webman project template ships composer hooks such as
post-package-install, so on install/update the configuration is copied toconfig/plugin/erikwang2013/global-logistics/and on uninstall it is removed automatically - Reading the config:
config('plugin.erikwang2013.global-logistics.app.sf.partner_id') - Usage:
\GlobalLogistics\Logistics::track('SF1234567890')
Yii 2
- Auto-registration: the package type
yii2-extensionplus composerextra.bootstrap, executed on every application bootstrap - Configuration: add
'logistics' => [...array of the same structure...]to your application configuration’sparams - Usage:
\GlobalLogistics\Logistics::track('SF1234567890') - If this package’s entry does not appear in
vendor/yiisoft/extensions.php(rare), runcomposer dump-autoloadto rebuild it
Development
1 | composer install |
The full test suite runs without real credentials (adapter tests go through mock HTTP + fixtures; framework integration tests use the real framework classes).

