tourze/cainiao-pickup-bundle
A PHP bundle that integrates Cainiao pickup services into your application, offering configuration and service wiring for parcel pickup workflows. Simplifies connecting your project to Cainiao logistics endpoints with minimal setup.
Install
composer require tourze/cainiao-pickup-bundle
This is the very first tagged release (0.0.1), so treat it as experimental: there is no changelog body, no upgrade guide, and no stability guarantee. Pin the version exactly ("0.0.1") until you've verified behaviour in your own environment.
Where to look first (in this order, because documentation is absent):
composer.json — check type. A *-bundle package from this vendor is conventionally a Symfony bundle, not a Laravel service provider. Note the autoload.psr-4 namespace; that's what you'll reference in config/app.php or your own provider.src/ — identify the entry point (a *Bundle, *ServiceProvider, or a manager/factory class) and the client/service classes it exposes.config/, Resources/config/, or DependencyInjection/ directory — this is where credentials and endpoint options are declared. If there's no publishable config, you'll be constructing the client manually.README / tests — tests are often the only accurate documentation of expected input/output shapes at this stage.Registration. If the package ships a Laravel ServiceProvider, add it to bootstrap/providers.php (Laravel 11+) or config/app.php. If it only ships a Symfony Bundle class, you'll need a thin adapter provider:
// app/Providers/CainiaoPickupServiceProvider.php
public function register(): void
{
$this->app->singleton(\Tourze\CainiaoPickup\SomeClient::class, fn ($app) => new \Tourze\CainiaoPickup\SomeClient(
config('services.cainiao.app_key'),
config('services.cainiao.app_secret'),
));
}
First use case. Don't start with order creation. Start with a read-only call — a pickup-station/network lookup or a shipment query — executed in php artisan tinker with sandbox credentials. That proves auth, signing, and HTTP transport work before you write anything to a live system.
Keep the vendor client behind your own boundary. The package API is 0.0.x and will move. Wrap it:
final class CainiaoPickupGateway
{
public function __construct(private readonly SomeClient $client) {}
public function stationsNear(string $city, string $district): array
{
$raw = $this->client->queryStations($city, $district);
return collect($raw['data'] ?? [])
->map(fn (array $s) => new PickupStationDto(
id: $s['stationCode'],
name: $s['stationName'],
address: $s['address'],
))
->all();
}
}
Controllers and jobs depend on CainiaoPickupGateway, never on the package classes. When 0.0.2 renames something, you patch one file.
Config in config/services.php, secrets in .env.
'cainiao' => [
'app_key' => env('CAINIAO_APP_KEY'),
'app_secret' => env('CAINIAO_APP_SECRET'),
'sandbox' => env('CAINIAO_SANDBOX', true),
],
Never let the package read env() directly — config caching will silently break it.
Async writes with idempotency. Pickup-order creation is a side-effecting external call: dispatch it on a queue job with $tries = 3, backoff() growing, and a client-supplied idempotency/business key derived from your own order UUID. Without that, a retry after a timeout produces duplicate pickup orders — the single most common production incident with this class of integration.
final class CreatePickupOrder implements ShouldQueue
{
public int $tries = 3;
public array $backoff = [10, 60, 300];
public function handle(CainiaoPickupGateway $gateway): void
{
$gateway->createOrder($this->order, idempotencyKey: $this->order->uuid);
}
}
Cache reference data aggressively. Station lists, district codes, and carrier lists change on the order of weeks. Cache::remember("cainiao.stations.{$city}", now()->addDay(), ...) cuts latency and — more importantly — keeps you off rate limits during traffic spikes.
Testing. Two layers:
CainiaoPickupGateway implementation, or Http::fake() if the package uses Laravel's HTTP client (verify in src/ — if it uses Guzzle/Symfony HttpClient directly, Http::fake() won't intercept and you'll need to mock the client class).Roll out behind a flag. The package is pre-1.0. Put the pickup flow behind Feature::active('cainiao-pickup') with a manual fallback path.
0.0.1 means the public API is not yet stable. In semver terms every patch bump may break you. Constrain tightly ("tourze/cainiao-pickup-bundle": "0.0.1"), review the diff on each bump, and never composer update this package as part of a routine sweep. The empty release note is itself a signal: assume no changelog discipline and read the commit history on GitHub instead.
Symfony-vs-Laravel friction is the likeliest integration blocker. If it's a Symfony bundle, expect: Bundle class rather than ServiceProvider; Symfony Config component definitions instead of Laravel config arrays; constructor-injected services that need explicit container bindings; and no vendor:publish support. Budget time for a hand-written adapter provider — that's normal, not a red flag.
Business errors usually arrive with HTTP 200. Alibaba-family APIs commonly return transport success with an error code in the body (success: false, errorCode, subMsg). If you only ->throw() on status codes, failures will pass silently. Check the body code explicitly and throw your own domain exception.
Signature/auth failures are almost always one of three things: clock skew (sign the timestamp you actually send), character-encoding mismatch (UTF-8 vs GBK on address fields), or a secret with trailing whitespace from a copy-paste into .env. When you see "invalid signature", check these before touching the package.
Debugging: log the outbound request (with the signature and secret redacted) and the full raw response at debug level, in one structured entry keyed by your order UUID. Log::debug('cainiao.request', [...]) plus a catch that re-logs the raw body on parse failure will save you hours versus stack traces from deep inside the vendor code. If the package offers a debug/verbose option, enable it in local only.
Extension points to prefer over forking: bind an interface in your provider and decorate the client (log, retry, metrics) rather than editing vendor files. If a method is missing entirely, subclass or compose rather than patching — you'll need to upgrade off 0.0.1 eventually.
Sanitize before persisting. Raw gateway responses frequently embed API keys or session tokens. Strip them before writing to your activity_log, exception tracker, or queue payloads.
How can I help you explore Laravel packages today?