tourze/wechat-official-account-menu-bundle
A Symfony bundle for managing custom menus on WeChat Official Accounts. It integrates menu creation and configuration into your Symfony application, helping developers define and publish official account menu structures through the familiar bundle workflow. Suitable for projects already using Symfon...
tourze/wechat-official-account-menu-bundle 0.0.1 is an initial release with no changelog — the release notes are literally just a compare link. Treat it as pre-alpha: there is no upgrade path, no deprecation policy, and the public API can change in any 0.0.x bump.
First, confirm what you actually installed. Despite the "bundle" suffix (Tourze publishes Symfony bundles), check composer.json before assuming Laravel compatibility:
composer show tourze/wechat-official-account-menu-bundle
If type is symfony-bundle and it requires symfony/http-kernel / symfony/dependency-injection, you cannot composer require it into a Laravel app and expect a service provider to be auto-discovered. Your minimal first steps:
src/ before writing any code. Look for the menu value objects (typically Button, SubButton, Menu), the service that calls https://api.weixin.qq.com/cgi-bin/menu/create, and how it obtains an access_token. That service boundary is the only thing you actually care about."tourze/wechat-official-account-menu-bundle": "0.0.1" — and commit the lock file. Do not use ^0.0.1; a 0.0.2 can rename classes.WechatMenu class in app/Services over Laravel's Http facade. Bridging a Symfony bundle into Laravel means manually registering its Extension and Bundle::build() container definitions, which is rarely worth it for a menu-payload builder.Your first real use case is almost always the same: publish a static click/view menu with one level of sub-buttons for a single official account, in a queued job, so a marketing change doesn't need a deploy.
Wrap the client, don't sprinkle it. Whatever the package exposes, put a single Laravel service in front of it so the eventual 0.1.0 rewrite touches one file:
final class WechatMenuService
{
public function publish(Menu $menu): void
{
// delegate to the bundle's menu client, or POST the payload yourself
Http::post('https://api.weixin.qq.com/cgi-bin/menu/create', [
'access_token' => $this->token(),
'button' => $menu->toArray(),
])->throw()->json();
}
}
Build the menu declaratively from config, not from code. Menus change for business reasons, not engineering ones. A config/wechat-menu.php array mapped into the package's value objects gives you a menu that ops can amend without touching PHP:
'buttons' => [
['type' => 'view', 'name' => 'Shop', 'url' => env('WECHAT_SHOP_URL')],
['type' => 'click', 'name' => 'Support', 'event_key' => 'SUPPORT'],
['name' => 'More', 'sub_button' => [...]],
],
Publish via a job, gated by environment. Menu creation is a full replacement of the live menu. Run it from php artisan wechat:menu:publish on deploy, or a queued job triggered by a config change — never inline in a request. Guard with app()->isProduction() so local/staging runs against the test-account credentials, never the production account.
Cache the access token aggressively and centrally. WeChat issues a token valid ~7200s and rate-limits issuance; two concurrent calls refreshing it will invalidate each other (error 40001). Put token acquisition behind Cache::remember('wechat:access_token', 7000, ...) and wrap the refresh in Cache::lock().
Integrate with your existing event handling. If you already handle click events from WeChat's message push, keep event_key values in the same enum used by your webhook controller — that's the seam where the menu definition and the message handler must agree, and it's the thing most likely to silently break.
Test against a WeChat test account (测试号). The test sandbox supports the menu APIs with a separate appid/secret and doesn't expose your real followers. Point .env.testing at it.
composer.json constraints against your Symfony/Laravel versions manually; do not trust that it installs cleanly.extra.laravel.providers. Expect no auto-discovery, no Laravel config publishing, and possibly container-definition code that references services you must register yourself.menu/create overwrites everything, including any personalized/conditional menus configured in the WeChat admin console. If a human has ever edited the menu in the MP console, your deploy will silently delete their work. Agree on one source of truth.40033/40017 response usually means you exceeded one.40001 (invalid credential) is almost always a stale token invalidated by another process or by the user resetting the secret in the console. 40164 means your server IP is not in the account's IP whitelist — this bites immediately in Docker/CI where egress IPs differ.type: view requires a valid ICP-filed domain matching the account's configured business domain, otherwise the menu saves but the link 404s inside WeChat's browser.{"errcode":0,"errmsg":"ok"} on success with HTTP 200. Any HTTP-client throw() on status alone won't catch failures — you must inspect errcode. Add that check to your wrapper.CredentialInterface-style abstraction is common in Tourze packages) and whether it ships a MenuProvider. If 0.1.0 introduces a provider interface, that's your migration hook — build the config-driven menu behind an interface now so you can adopt it.How can I help you explore Laravel packages today?