tourze/wechat-work-server-bundle
Symfony bundle integrating WeChat Work (企业微信) server-side APIs. Provides callback verification, event and message handling, and a ready-to-use API client wired through Symfony DI, so you can receive events, send messages, and manage contacts without boilerplate.
Install and pin. composer require tourze/wechat-work-server-bundle:^0.1.0. The jump from 0.0.2 to 0.1.0 is a pre-1.0 minor, which under Composer semantics is treated as breaking: the caret constraint ^0.1.0 will not pick up 0.0.x, and 0.2.0 will not be picked up either. Pin what you test against and read the compare link (0.0.2...0.1.0) yourself before upgrading — the release notes carry no description of what actually changed.
Check the foundation first. This is a bundle — it is built against Symfony's kernel, DI container, and config-tree conventions, not Laravel's. Before writing any application code, confirm which underlying transport/crypto package it depends on (a WeChat Work kernel or SDK of some kind) and whether you can use those classes standalone. In a Laravel app the realistic path is: pull in the bundle for its server-side callback handling (signature verification, AES decryption, XML parsing, message/event dispatch), and either run it through a Symfony-kernel bridge or instantiate the handler services manually in a Laravel service provider. Do not assume php artisan vendor:publish will produce anything useful — Symfony bundles ship config trees, not Laravel publishable config.
Where to look first, in order:
*Bundle.php) — its loadExtension/prependExtension tells you the config keys and which services are auto-registered.First use case: stand up the callback URL verification. WeCom sends a GET with msg_signature, timestamp, nonce, echostr; you must verify the signature, AES-decrypt echostr, and return the plaintext. Get that green in the WeCom admin console before touching message handling — it validates token, EncodingAESKey, and CorpID config in one shot.
Route wiring. Register the callback as a POST (and GET for verification) route inside your WeCom-facing middleware group. Explicitly exclude it from VerifyCsrfToken, from Authenticate/session middleware, and from any middleware that rewrites the request body. The handler needs the raw request body and the raw query string.
Config, not .env magic. Whatever the bundle calls its config (token, encoding AES key, corp ID, agent ID), funnel it through config/services.php + .env and construct the handler explicitly. Symfony bundles generally won't read env() the way a Laravel package would, so treat config as an adapter you write, not something the package handles for you.
Dispatch on MsgType / Event, then get out of the request cycle. The standard shape:
$message = $handler->parse($rawBody, $query); // verifies signature + decrypts
if ($messages->seen($message->msgId)) { // idempotency guard
return response('', 200);
}
ProcessWecomMessage::dispatch($message); // queue it
return response('', 200); // respond fast, always
Keep the controller handler thin. WeCom's event vocabulary (text/image/event: enter_agent, change_contact, approval, task_card, …) grows, and you want one match on the type mapping to jobs, not business logic inline.
Passive vs. active replies. If the bundle supports returning an encrypted reply body synchronously, use that only for trivial cases (echo, canned help text). Everything else should go async and be delivered through the WeCom push/客服 message API using a cached access token — do not fetch a fresh access_token per message, WeCom rate-limits and invalidates aggressively. Put the token behind a cache key with TTL slightly under the returned expires_in.
Persistence/logging. Persist raw inbound payloads (encrypted or decrypted, your call) alongside your parsed record. When WeCom support asks "did you receive event X at 14:32", you want the answer to be a SELECT, not a log grep.
0.0.2 → 0.1.0 is the release where the author felt free to break things. Until you've read the diff, treat this as an API you don't control: wrap the bundle's handler behind your own small interface so a future 0.2.0 doesn't ripple through your controllers.EncodingAESKey is 43 characters, base64-decoded with a trailing =. The most common integration failure is a key pasted with/without padding or with whitespace. Also confirm the CorpID used in decrypt matches the one configured — a mismatch fails silently-ish.MsgId-keyed idempotency guard you will process duplicates and double-send replies. This is the single most common production bug in WeCom integrations.$request->all() and JSON-parsing middleware destroy the payload the signature covers. Use $request->getContent().0.1.0 with an unreadable changelog, there is no stability contract protecting you.How can I help you explore Laravel packages today?