finecho/logistics
finecho/logistics is a Laravel package for express and logistics tracking, with a driver-based API for querying shipment status through popular courier services. Configure credentials, send tracking requests, and handle responses cleanly in PHP.
This is a small Laravel wrapper around Chinese carrier-tracking aggregators (快递100 / 快递鸟-style APIs) — it takes a tracking number plus a carrier code and returns the tracking timeline. Treat it as a thin HTTP adapter, not a logistics platform.
Minimal setup:
composer require finecho/logisticscomposer.json → extra.laravel for auto-discovery. Given the 2021 last release, it likely targets Laravel 8/9; you may need a fork or --ignore-platform-reqs on Laravel 10/11 (see Gotchas).php artisan vendor:publish --provider="Finecho\Logistics\LogisticsServiceProvider" (or --tag=logistics)..env — the config will expect a customer/customer id and an API key for whichever aggregator driver is the default.Finecho\Logistics\Facades\Logistics), pass tracking number + carrier code, render the returned trace array as a vertical timeline.Where to look first: read src/ end-to-end — it's small enough to finish in 15 minutes, which is the cheapest way to confirm the actual method names, the driver contract, and the response shape before you write anything on top of it. Then look at config/logistics.php for the driver list and credential keys, and the service provider for bindings/facade alias.
Never call the package directly from controllers. Wrap it in App\Services\LogisticsTracker (or an action class) that exposes track(Shipment $shipment): TrackingResult. Given the package's age, this is the single highest-leverage decision you'll make — it makes the eventual swap to AfterShip/17track/your own HTTP client a one-file change.
Persist, don't proxy. Create shipments and shipment_events tables. On each query, upsert events keyed by (shipment_id, occurred_at, description_hash) so re-polling is idempotent. Your UI then reads from your DB and never blocks on a vendor call.
Normalize status codes. Vendor responses use Chinese status strings/numbers (已签收, 派送中, 退回). Map them into your own enum — pending | picked_up | in_transit | out_for_delivery | delivered | exception | returned — inside the wrapper. Everything downstream (notifications, "delivered" triggers, SLA reports) should key off your enum, never off the raw vendor value.
Cache hard and poll on a schedule. These APIs are quota-limited and often billed per query. Cache by logistics:{carrier}:{tracking_no} with a conditional TTL: ~30–60 min while in transit, 24h+ once delivered, and a terminal no-retry state for "no tracking info yet". Then run a queued/scheduled job that refreshes only open shipments, rather than querying on every page view.
Queue the calls. Dispatch a job when a shipment is created and again on a schedule. Set an explicit HTTP timeout and retryUntil/backoff; vendor endpoints routinely hang for 10–30s.
Carrier code mapping is its own table. Your internal courier (sf, zto) does not equal the vendor's code (shunfeng, zhongtong), and codes differ between 快递100 and 快递鸟. Keep a carriers table with internal_code, vendor_code, vendor_driver columns so a driver change doesn't require a code deploy.
Driver failover. If the package exposes a manager (Logistics::driver('x')) with a default config key, use it: primary aggregator in config, secondary as fallback in your wrapper when the first returns a quota or signature error. If push/callback (订阅) mode is supported, prefer it over polling — it cuts query costs dramatically and gives fresher events; mount a signature-verifying route and upsert into shipment_events.
Test against a fake, not the network. Use Http::fake() with recorded vendor payloads (Chinese strings, empty traces, error envelopes) and assert on your normalized DTO. Never assert on the package's raw array shape.
illuminate/support version pins to block installation on modern Laravel; fork it, loosen the constraint, and vendor the fork under a VCS repository entry. Don't plan on upstream fixes for changed vendor signing schemes.extra.laravel. If Logistics:: isn't resolvable, register the provider in bootstrap/providers.php (or config/app.php) and add the facade alias manually.result/status/message fields explicitly and translate Chinese error strings into typed exceptions (QuotaExceeded, InvalidSignature, NoTrackingInfo) so your retry logic can distinguish "retry later" from "never retry".Cache::lock("logistics:{$no}") and serve stale data on lock contention.utf8mb4; mangled Chinese in traces is almost always a connection charset issue, not the package.config/logistics.php is cleaner than patching the vendor's — and your LogisticsTracker wrapper means you can do it incrementally, one carrier at a time.How can I help you explore Laravel packages today?