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

Youzan Api Bundle Laravel Package

tourze/youzan-api-bundle

A Symfony bundle for integrating the Youzan API into your PHP application, offering service configuration and convenient access to Youzan client services for building e-commerce, shop, and order integrations with less boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

Mind the packaging first. Despite the name, tourze/youzan-api-bundle is a Symfony bundle from the Tourze family, not a Laravel package. There is no YouzanServiceProvider, no config/youzan.php, and no package auto-discovery. In practice you get two usable layers:

  1. Plain PHP client/service classes under Tourze\YouzanBundle\... that you can new up or bind in a Laravel service provider yourself.
  2. Symfony DI wiring (services.yaml, autoconfiguration, #[AsAlias]-style attributes) that Laravel will ignore entirely.

Most Laravel teams should consume layer 1 only.

Install and inspect.

composer require tourze/youzan-api-bundle:0.1.0
ls vendor/tourze/youzan-api-bundle/src

Read src/ before writing any code — the bundle is small and the real API surface is the handful of classes there. Check whether a DependencyInjection/ folder exists; if it does, that is Symfony-only and safe to skip.

Configure credentials. Youzan (有赞) uses client_id / client_secret for self-use ("自用型") apps, plus a kdt_id (shop ID) for most shop-scoped endpoints. Put these in config/services.php and .env:

// config/services.php
'youzan' => [
    'client_id'     => env('YOUZAN_CLIENT_ID'),
    'client_secret' => env('YOUZAN_CLIENT_SECRET'),
    'kdt_id'        => env('YOUZAN_KDT_ID'),
    'grant_id'      => env('YOUZAN_GRANT_ID'), // tool-type apps only
],

Bind it in a provider. This is the step the bundle won't do for you:

// app/Providers/YouzanServiceProvider.php
public function register(): void
{
    $this->app->singleton(YouzanClient::class, fn () => new YouzanClient(
        clientId: config('services.youzan.client_id'),
        clientSecret: config('services.youzan.client_secret'),
        // ...whatever the constructor actually requires; check src/
    ));
}

First use case: pull a single order or the shop's item list and dump it. Youzan's API is POST https://open.youzan.com/api/oauthentry/{method}/{version} with an access_token query param — the bundle wraps the token exchange and request signing. Start with one read-only call (youzan.trade.get or youzan.items.onsale.get) to confirm auth works before touching writes.

Where to look first, in order: src/ → the bundle's README → the GitHub repo's tests/ (usually the most accurate documentation Tourze ships) → Youzan's official docs at doc.youzan.cn for method semantics.

Implementation Patterns

Token lifecycle is the whole job. Youzan access tokens are short-lived (roughly a week for self-use apps) and come with a refresh token. Do not fetch a token per request — that alone will get you rate-limited. Cache it:

$token = Cache::remember('youzan:token', now()->addHours(20), function () use ($client) {
    return $client->authenticate(); // returns token + refresh_token + expires_in
});

Store the refresh token separately in the cache and in the DB, because a refresh invalidates the previous one — losing it means a manual re-auth. A scheduled command that refreshes proactively at ~80% of TTL is the pattern that ages well.

Wrap the client, don't scatter it. The bundle exposes raw API methods; your domain shouldn't. One thin adapter:

final class YouzanGateway
{
    public function __construct(private YouzanClient $client) {}

    public function order(string $tid): OrderData
    {
        return OrderData::fromYouzan($this->client->call('youzan.trade.get', [
            'tid' => $tid,
        ]));
    }
}

Then map Youzan's payload into your own DTOs. Youzan returns nested response.trade / response.items shapes with a gw_err_resp on failure — normalising that once beats handling it at 30 call sites.

Push webhooks into jobs, immediately. Youzan's message service posts to your endpoint and expects a fast, empty 200. Your controller should verify, persist raw, dispatch, and return:

public function __invoke(Request $request)
{
    abort_unless($this->verifySignature($request), 403);

    YouzanMessage::create($request->all());

    ProcessYouzanMessage::dispatch($request->input('id'))
        ->onQueue('youzan');

    return response('success');
}

Idempotency key on Youzan's message id is mandatory — they retry aggressively on any non-200 or timeout, and duplicates are routine.

Rate-limit client-side. Youzan enforces per-app QPS (single digits for many methods). Route all calls through a queued job with RateLimited middleware or a Redis token bucket. Backfill/sync commands must be throttled and resumable — record a cursor, not an offset, so a mid-run failure doesn't replay.

Multi-shop tenancy. If you serve more than one shop, the client must be shop-scoped, not a singleton. Bind it as scoped() in Laravel, resolved from the current tenant, caching tokens under youzan:token:{kdt_id}.

Testing. No Http::fake() integration exists, so point the client's base URL at a local stub, or bind a FakeYouzanClient implementing the same interface in tests. Record real fixtures once — Youzan's response shapes vary by API version and are not worth inventing.

Gotchas and Tips

The 0.1.0 changelog is empty. The release notes are a bare compare link between 0.0.3 and 0.1.0 — no migration guide, no BC notes. Under pre-1.0 semver, a minor bump is exactly where breaking changes land. Before upgrading, read the diff yourself:

composer require tourze/youzan-api-bundle:0.1.0 --dry-run
# then, if already installed:
git -C vendor/tourze/youzan-api-bundle log --oneline 0.0.3..0.1.0

Pin the exact version ("0.1.0", no caret) until you've diffed it. Tourze packages move fast and refactor class namespaces freely.

Don't rely on the bundle for HTTP concerns. It is a thin binding over Guzzle. No retry policy, no circuit breaker, no meaningful exception hierarchy in all likelihood. Wrap calls in retry(3, fn ($e) => $e instanceof ConnectionException, 200) and translate Youzan's gw_err_resp codes into your own exceptions before they reach controllers.

Money is in fen (分), integers. Youzan returns amounts as integer cents in most fields, but a few legacy endpoints use decimal strings. Do not blindly /(100) — check per field and store minor units, converting only at the presentation boundary. This bites everyone once.

Signature verification has two modes. Youzan supports both plaintext and encrypted (AES) message pushes depending on the app's config; the encrypted variant needs your client_secret as key and a base64 decode before decrypt. If webhooks "work" but payloads are garbage, you're almost certainly on the encrypted mode without decrypting. Also verify the msg_signature over the correct concatenation — Youzan's is not the same formula as WeChat's despite the naming similarity.

Symfony leftovers will confuse you. services.yaml, Resources/config/, and PHP attributes like #[AsTaggedItem] are inert in Laravel. Don't try to make them work; don't install symfony/dependency-injection just to satisfy them. If the package's composer.json requires Symfony components beyond http-client, that's a dependency-weight signal — check the tree with composer why symfony/* before shipping.

Extension point is the client class itself. There's no event system or plugin hook here. Custom behaviour goes in a decorator around the bundle's client implementing your own interface, so you can swap to a hand-rolled Guzzle wrapper later if the package stalls. Given 0.0.x → 0.1.0 velocity with no documentation, having that escape hatch is the single most valuable architectural decision you'll make with this dependency.

Debugging: log full request/response pairs (redacting access_token and client_secret) behind a config flag. Youzan's error messages are terse and sometimes in Chinese; having the raw body is usually faster than reading their docs.

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.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment