Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Express Laravel Package

flex/express

Flex Express brings Express-style routing and middleware to Laravel/PHP, making it easy to define clean endpoints and build small APIs or web apps with minimal boilerplate. Its expressive syntax keeps setup light and development fast.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require flex/express:^1.0.2

Pin to ^1.0.2 at minimum — that release (see Gotchas) refreshes the bundled 快递公司代码 table, and older pins will send stale carrier codes to the vendors.

The package is a thin SDK around two upstream aggregators — 快递鸟 (KdNiao) and 快递100 (Kuaidi100) — so the first thing to do is decide which provider you actually have credentials for. They are not interchangeable: 快递鸟 needs an EBusinessID + AppKey (and pays per query unless you're on the free 即时查询 quota), while 快递100 needs a customer + secret (MD5-signed) or the newer 授权 key. You will typically only configure one.

  1. Publish the config (the tag name varies by version — check the service provider or run php artisan vendor:publish and pick the flex/express entry):
php artisan vendor:publish --provider="Flex\Express\ExpressServiceProvider"
  1. Fill in .env / config/express.php:
return [
    'default' => env('EXPRESS_DRIVER', 'kdniao'),

    'kdniao' => [
        'ebusiness_id' => env('KDNIAO_EBUSINESS_ID'),
        'app_key'       => env('KDNIAO_APP_KEY'),
        'sandbox'       => env('KDNIAO_SANDBOX', false),
    ],

    'kuaidi100' => [
        'customer' => env('KUAIDI100_CUSTOMER'),
        'secret'   => env('KUAIDI100_SECRET'),
    ],
];
  1. First use case — a single tracking query from a controller or artisan tinker:
use Flex\Express\Facades\Express;

$result = Express::query('SF', 'SF1234567890', '13800000000');

The carrier code (SF, YTO, ZTO, JD, …) must match the vendor's expected string. The package ships a company-code table (which 1.0.2 updated) — grep src/ and any bundled codes/company array or JSON before assuming it's absent, and read the driver class (KdNiao, Kuaidi100) next; that file is the real documentation, since the README is sparse.

Implementation Patterns

Bind a thin interface, not the SDK. Because the package talks to two vendors with different response shapes, wrap it immediately:

interface TrackingService
{
    public function track(string $carrier, string $number, ?string $phone = null): TrackingResult;
}

Map both drivers' payloads into one DTO (status, events[], delivered_at). This is the single highest-value thing you can do — it lets you swap providers when one's quota runs out, and it stops vendor JSON from leaking into your controllers.

Normalise carrier codes at the boundary. Even though the package carries a code list, put your own lookup in front of it — an enum or carrier_codes config keyed by your internal carrier slug, whose values are validated against the package's table:

// config/carriers.php
'sf' => ['vendor' => 'SF', 'phone_required' => true],
'jd' => ['vendor' => 'JD', 'phone_required' => true],

This gives you a single place to re-map when the vendor renames a company (百世/极兔/德邦 have all churned codes historically) and means a package upgrade — like 1.0.2 — can't silently change what you send. Re-diff your mappings against the package table after every bump.

Query-through vs. push (subscribe). Both vendors offer 订阅推送, which is what you want in production — you register a tracking number once and the vendor POSTs status changes to your webhook. Use the SDK for the subscribe call, but implement the callback endpoint yourself (validate their signature, then dispatch a job). Do not let the vendor callback do DB writes inline; 快递鸟 retries and you will get duplicates.

Polling as a fallback. For one-off admin lookups ("where is this order?") a synchronous Express::query() is fine. For anything covering many orders, push the query into a queued job:

class RefreshTracking implements ShouldQueue
{
    public function handle(TrackingService $tracking): void
    {
        $this->order->tracking = $tracking->track($this->order->carrier, $this->order->tracking_no);
        $this->order->save();
    }
}

Cache terminal states (delivered, returned) so you never burn a paid query on a parcel that already arrived.

Where to wire it in. Typical integration points: an OrderObserver/OrderPaid listener that subscribes the tracking number right after fulfilment, a scheduled command (everyThirtyMinutes) that polls non-terminal shipments, and a webhook route excluded from CSRF.

Multi-driver resolution. If the config exposes a default key, you can usually select a driver per-call. Read the driver factory in the package source before assuming the method name — releases around 2022 exposed it inconsistently as Express::driver('kdniao') or via the facade's __call.

Gotchas and Tips

  • Upgrading to 1.0.2 changes carrier codes. The release notes are a single line — "update express company code" — i.e. the bundled 快递公司代码 table was corrected (community PR from @GStepOne). It's a patch bump, so no API change is expected, but anything you persisted or hard-coded that came from the old table should be audited: stored vendor codes on existing subscriptions, seeded test fixtures, and any SF-Express/shunfeng-style strings you copied out of src/. Diff the code table between 1.0.1 and 1.0.2 before rolling out, and re-run one live query per carrier you actually support.
  • Still effectively unmaintained. 1.0.2 is a community code-list patch, not a feature release; before it the last release was 2022-09-23, with ~31 stars and zero dependents. Treat it as a copy-paste reference rather than a maintained dependency: verify it installs and passes tests on your PHP version (PHP 8.2/8.3 deprecation notices around dynamic properties and strlen(null) are likely). Pin the version in composer.json and consider forking if you need more than code-table updates.
  • Signature encoding is the #1 failure mode. 快递鸟 signs requestData with MD5 and then URL-encodes it before base64, in that order. 快递100 signs param + customer + key with MD5 and uppercases it. If you're getting errCode 100 / status 500, it's almost always the sign, not your credentials.
  • Vendor error codes are hidden in the body. Both APIs return HTTP 200 for failures. 快递鸟 returns Success: false + Reason; 快递100 returns result: false + message. If your SDK wrapper only checks the HTTP status you will silently store empty tracking data. Log the raw response body on every non-terminal path.
  • Phone number is mandatory for 顺丰 / 京东 / 极兔 (virtual numbers now). 快递鸟's free 即时查询 does not support 顺丰 at all — you'll need the paid 订阅 route or a different carrier. Plan for this in your UX: ask for the recipient phone when the carrier is SF.
  • Sandbox vs. production. 快递鸟 has a sandbox endpoint (http://sandboxapi.kdniao.com:8080/...) — make sure the flag actually flips the base URL; some versions hard-code the production host in the driver. Verify with a known test number before going live, and re-verify after each bump, since code-list patches occasionally ride along with other small edits.
  • Timeouts and retries. Neither driver configures a sane timeout by default; Guzzle's default is unlimited. Set 5–10s, retry with backoff in your job, not in the SDK (a retried subscribe is fine, a retried query is money).
  • Extension points are thin. Practically the only clean extension is replacing the driver class in the service provider binding, or wrapping the facade. If you need a third aggregator, this package won't help — but its KdNiao/Kuaidi100 classes are a useful reference for the signing logic, which is the tedious part, and its company-code table is a reasonable starting point for your own.
  • Don't trust the carrier code mapping blindly. 1.0.2 improves it, but it is still a static list maintained by whoever sends a PR. Keep your own carrier → vendor_code table; shipping providers (Shopify, 店匠, custom WMS) emit codes like shunfeng / SF-Express, and neither the SDK nor the bundled table will normalise those for you.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource