tourze/wechat-mini-program-bundle
A PHP bundle that simplifies integrating WeChat Mini Program capabilities into backend applications. It provides service configuration and API wiring for mini program authentication, messaging, and related workflows, reducing boilerplate for developers building WeChat Mini Program-connected PHP serv...
This is a Symfony bundle, not a Laravel package — that is the single most important thing to know before you install it. There is no ServiceProvider, no Facade, no php artisan vendor:publish. It ships a *Bundle class and a DependencyInjection/ extension intended to be registered in config/bundles.php of a Symfony app. In Laravel you will be wiring its services into the container by hand.
Confirm the baseline first:
composer show tourze/wechat-mini-program-bundle --all # check PHP/Symfony constraints
2.0.0 is a major bump from 1.0.1 with an empty release body (just an auto-generated compare link), so treat namespaces, service IDs, and the configuration tree as unknown until you read them. Before writing any code:
vendor/tourze/wechat-mini-program-bundle/src/ — find the bundle class and list the concrete API/service classes. These are what you will actually consume.src/DependencyInjection/Configuration.php — this is the authoritative list of config keys and their defaults. It is your replacement for a config/wechat.php stub.src/Resources/config/*.yaml (or PHP service definitions) — service IDs, constructor arguments, and which HTTP client is expected (symfony/http-client or PSR-18).composer.json of the package itself — note whether it pulls tourze/wechat-mini-program as the underlying SDK. If it does, you can often depend on that core library directly and skip the bundle wiring entirely.First use case: the mini-program login handshake. wx.login() on the client yields a short-lived code; your server exchanges it via code2Session for openid + session_key. That single call is 90% of what most teams need on day one. Get that working through a thin adapter class, then add subscribe messages / QR codes / content-security checks on top.
Bridge the bundle into Laravel explicitly. Register the underlying client as a singleton in AppServiceProvider, reading credentials from config/services.php:
$this->app->singleton(WeChatMiniProgramClient::class, fn () => new WeChatMiniProgramClient(
appId: config('services.wechat_mp.app_id'),
secret: config('services.wechat_mp.secret'),
));
Never new the client at the call site — the access-token cache and HTTP client must be shared.
Wrap it, don't sprinkle it. Put every call behind your own MiniProgramService with methods named for your domain (loginWithCode, sendSubscribeMessage, generateUnlimitedQr). A 1.0 → 2.0 major bump with no documented migration is exactly the scenario where leaking vendor class names into 30 controllers becomes a week of work. The wrapper is your upgrade seam.
Login → token issuance. The canonical flow:
$session = $this->miniProgram->code2Session($code); // openid, session_key, unionid
$user = User::firstOrCreate(['openid' => $session->openid]);
Cache::put("wx:sk:{$user->id}", $session->session_key, now()->addDays(7));
return ['token' => $user->createToken('mp')->plainTextToken];
Persist session_key server-side only, in the cache, keyed per user. It is a symmetric key used to decrypt encryptedData / getPhoneNumber payloads — it must never reach the client.
Push anything network-bound to a queue. Subscribe messages, msgSecCheck, and QR generation are all remote calls with multi-second tails. Dispatch a job, use $tries = 3 with backoff(), and rely on Laravel's failed_jobs for the WeChat error code payload.
Test by faking HTTP, not by mocking the bundle. Since the bundle delegates to an HTTP client, Http::fake(['api.weixin.qq.com/*' => Http::response([...])]) gives you far more upgrade-resilient tests than mocking vendor classes whose names may change between majors.
Adapter per environment. Point at a local stub in local so a dev without WeChat credentials can still run the login flow; only production should hit api.weixin.qq.com.
composer update. Diff the tree between tags (git diff 1.0.1 2.0.0 -- src/ in the package repo, or diff vendor/ before and after) and pin ^2.0 only after the login flow passes. Assume renamed namespaces and changed service IDs until proven otherwise.config:cache and secrets. Symfony-style bundles often read from a container parameter bag; Laravel's config() is a different mechanism entirely. Keep credentials in env() via config/services.php and verify with php artisan config:cache && php artisan tinker — a cached config is a very common "works locally, dead in prod" cause.access_token per app with ~2000 fetches/day. Cache it centrally with a TTL slightly shorter than the returned expires_in, and wrap in Cache::lock() — concurrent web workers otherwise burn the quota and get everyone throttled.40001, 42001, 40014 mean the cached token is stale or was invalidated by another process. Intercept these, flush the cache, retry exactly once. Retrying in a loop makes it worse.session_key rotates. Every fresh wx.login() invalidates the prior key. Decryption failures are usually a stale key or a user who logged in on a second device — re-prompt rather than debugging crypto.-1 / 45009 are not your bug. -1 is "system busy, try later" (retry with backoff); 45009 is API quota exhaustion. Log the raw errcode/errmsg — the bundle may normalize errors into exceptions and lose the code.vendor/. Composer will erase your edits on the next deploy.session_key-in-logs leak. Framework-neutral bundles often don't mark these values sensitive; Laravel's exception handler will happily serialize them into laravel.log. Sanitize at your wrapper boundary before you log the session object.How can I help you explore Laravel packages today?