youzanyun/open-sdk
PHP SDK for the Youzan Cloud open platform. It wraps Youzan's OAuth flow and REST endpoints in a clean client, letting PHP and Laravel apps authenticate shops and call trading, customer, and marketing APIs without hand-rolling token handling, request signing, or response parsing.
Install and wire it up. The package is framework-agnostic (no service provider, no facade, nothing to publish), so Laravel integration is entirely on you:
composer require youzanyun/open-sdk
Add a config block to config/services.php:
'youzan' => [
'client_id' => env('YOUZAN_CLIENT_ID'),
'client_secret' => env('YOUZAN_CLIENT_SECRET'),
'redirect_uri' => env('YOUZAN_REDIRECT_URI'),
'sign_secret' => env('YOUZAN_SIGN_SECRET'), // push-message signing key
],
Where to look first. Read vendor/youzanyun/open-sdk/src/ before reading any blog post — the three files that matter are Client.php (the HTTP entry point for every API), Token.php (OAuth / self-use token issuance and refresh), and Auth.php (authorization URL builder). The method signatures on Client::get() / Client::post() have shifted between major versions, so confirm the exact argument order (API name, access token, params, API version) straight from the source you installed rather than from the README.
First use case. Get an access token, then make one read-only call and dump the raw response. For a self-use (自用型) app this is a single token call; for a tool-type (工具型) app you need the OAuth redirect first:
use Youzan\Open\Token;
$token = new Token(
config('services.youzan.client_id'),
config('services.youzan.client_secret')
);
$resp = $token->getSelfToken(); // self-use app
// $resp = $token->getAccessToken($code, config('services.youzan.redirect_uri')); // tool-type app
// $resp['access_token'], $resp['refresh_token'], $resp['expires_in']
Then tinker a real call — youzan.shop.get or a store listing is the cheapest way to prove credentials, permissions and network egress all work before you build anything on top.
One thing to verify on day one: whether your app has been granted the API scopes in the Youzan console. Credentials can be perfect and every call still returns a permission error — better to discover that in tinker than after building a sync job.
Wrap the SDK behind one service class. Never let Youzan\Open\Client leak into controllers or jobs. A thin YouzanService that resolves credentials from config, attaches the right token for a given shop, and normalises the response envelope gives you one place to add logging, retries and a swappable fake for tests.
Persist tokens, don't re-fetch them. Create a youzan_shops (or youzan_tokens) table with kdt_id, access_token, refresh_token, expires_at. Access tokens are long-lived but not permanent; the refresh token is the thing you actually lose sleep over. Store the new pair every time you refresh — assume rotation.
Refresh on a schedule, guarded by a lock. A console command running a few times a day, wrapped in Cache::lock("youzan:token:{$kdtId}", 30), prevents two queue workers from refreshing simultaneously and burning each other's refresh token. Refresh when expires_at is inside a comfortable window (a day, not a minute) so a failed run still leaves you headroom. Register it in routes/console.php / the scheduler.
Multi-store is the default assumption. Every call is scoped to a shop (kdt_id), so a fleet of stores means a fleet of tokens. Resolve the token per job payload rather than per application — a job that carries shop_id and looks up the token inside handle() is far more robust than one that captures a token at dispatch time.
OAuth callback for tool-type apps. Two routes: a redirect that builds the authorize URL via Auth, and a callback that exchanges code for a token, resolves the shop, upserts the token row, and redirects back to your UI. Treat the callback as untrusted input — verify state against the session.
Push messages. Youzan pushes events (orders, refunds, item changes) to an HTTPS endpoint you configure. Your route should: read the raw body, verify the signature using the signing secret, return the required success JSON immediately, and dispatch a job to do the real work. Keep the controller under a few milliseconds — a slow handler triggers retries and duplicates.
Idempotency and rate limiting. Youzan enforces per-API QPS limits, and push messages are at-least-once. Use Redis::throttle() on outbound calls and dedupe inbound events on a natural key (order ID + event type, or the push's own identifier) before touching your database.
Testing. Because the SDK does its own transport, Http::fake() won't help you. Either mock your YouzanService interface in feature tests, or (better) build a small fake transport that returns canned envelopes so you can exercise your parsing and error branches for real.
HTTP 200 does not mean success. The gateway returns 200 with a JSON body where code === 0 means OK and anything else is an error, often with the useful detail buried in a gw_err_resp object. Every call site must check code, not the status. Write one unwrap() helper that throws a typed exception on non-zero codes and returns data — otherwise you'll be debugging silent no-ops in production.
Capture trace_id. It's the only thing Youzan support will ask for. Log it alongside the API name, shop ID and duration for every call.
Don't retry writes blindly. Retries are safe for reads and dangerous for *.add / *.update style calls — you'll create duplicates. Retry on connection errors and 5xx for reads; for writes, either skip the retry or key it on an idempotency field the API supports.
Set explicit timeouts and a proxy if needed. The SDK's HTTP layer has no Laravel-style timeout config, and Chinese endpoints can be slow or blocked from some regions. Decide where the egress IP lives before launch — this is the single most common cause of "it works locally" failures with this family of SDKs.
The last release is from 2022. Newer Youzan APIs may have no typed wrapper, and behaviour may have drifted from the docs. This is mostly fine because Client::get() / Client::post() let you call any API by its dotted name with a raw params array — treat the SDK as an auth + transport layer rather than a full API client, and you'll never be blocked by its release cadence.
Signature verification must hash the raw body, not a re-serialised array. Laravel's JSON decoding/re-encoding will change whitespace and key order and invalidate the hash. Grab $request->getContent() before any middleware touches it, and remember that the test-mode flag in the payload should route to your sandbox handling, not your production writes.
Extension points are limited to what you build. There's no middleware pipeline, no events, no driver abstraction. The practical answer is to keep the SDK one layer deep: config in, YouzanService as the boundary, typed exceptions and normalised arrays out. Everything you'd normally want from a first-party package — retry policy, metrics, stubs for tests — becomes your code, and it's worth writing once at the start rather than sprinkling try/catch around the codebase later.
How can I help you explore Laravel packages today?