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

Wechat Laravel Package

overtrue/wechat

Overtrue/WeChat (EasyWeChat) is a widely used PHP SDK for building WeChat integrations. It provides clean APIs for official accounts, mini programs, payments, open platforms, enterprise WeChat, and more, handling tokens, messages, menus, and OAuth so you can focus on your application logic.

Deep Wiki
Context7

Getting Started

Install the SDK with Composer. For Laravel, use the framework bridge instead of wiring everything by hand:

composer require overtrue/wechat
# Laravel
composer require overtrue/laravel-wechat
php artisan vendor:publish --provider="EasyWeChat\Laravel\ServiceProvider"

Start by looking at config/wechat.php (Laravel) or your application config array. The key objects you will use daily are:

  • getClient() — raw HTTP calls to WeChat APIs: users, menus, template messages, QR codes, etc.
  • getServer() — webhook/callback handling for messages and events.
  • getAccessToken() — token inspection/refresh when debugging.
  • getUtils() — JSSDK config, mini program codeToSession, encryption helpers.
  • getOAuth() / getAuth() — web authorization and mini program login.
  • getValidator() / getDecryptor() on the payment application — callback signature verification and resource decryption.

A minimal official-account server endpoint, which is usually the first thing to get working:

use EasyWeChat\OfficialAccount\Application;

$app = new Application([
    'app_id'  => env('WECHAT_APP_ID'),
    'secret'  => env('WECHAT_SECRET'),
    'token'   => env('WECHAT_TOKEN'),
    'aes_key' => env('WECHAT_AES_KEY'), // if using safe mode
]);

Route::any('/wechat/server', function () use ($app) {
    $server = $app->getServer();

    $server->with(function ($message) {
        return 'received';
    });

    $response = $server->serve();

    return response($response->getBody()->getContents(), $response->getStatusCode())
        ->withHeaders($response->getHeaders());
});

Configure that URL in the WeChat official account console first. The initial GET verification request is handled by serve(); if that fails, nothing else will work.

Implementation Patterns

Use one application instance per platform. Official account, mini program, payment, open platform, and WeChat Work are separate SDK applications with separate credentials, tokens, and sometimes separate caches. Register them as singletons in a service provider and inject the specific application class rather than passing around raw config arrays.

$this->app->singleton(\EasyWeChat\OfficialAccount\Application::class, function () {
    return new \EasyWeChat\OfficialAccount\Application(config('wechat.official_account.default'));
});

Treat getClient() as the primary integration surface. Most WeChat APIs do not need dedicated SDK methods; you call the endpoint and handle the JSON response. This is the normal day-to-day workflow for user management, menus, customer service messages, and template messages.

$response = $app->getClient()->get('/cgi-bin/user/get', [
    'query' => ['openid' => $openId],
]);

$user = $response->toArray();

Use the server object for all inbound webhooks. Register message and event listeners rather than parsing XML/JSON yourself. Keep handlers fast: acknowledge WeChat immediately, then dispatch jobs for database writes, notifications, or external API calls.

$server = $app->getServer();

$server->addMessageListener('text', function ($message) {
    return 'You said: ' . $message->Content;
});

$server->addEventListener('subscribe', function ($event) {
    dispatch(new SyncWechatSubscriber($event->FromUserName));
});

For official-account OAuth, redirect and exchange the code server-side. Persist openid and unionid against your local user record. Do not trust user profile data as current; WeChat profile snapshots can be stale unless you refresh them.

$oauth = $app->getOAuth();

// Redirect
return redirect($oauth->redirect($callbackUrl));

// Callback
$user = $oauth->userFromCode($request->get('code'));
$openId = $user->getId();
$unionId = $user->getOriginal()['unionid'] ?? null;

For mini programs, exchange code for session_key only on the server. Never send session_key to the client. Use the SDK encryptor for encrypted phone number or user data payloads.

$session = $miniApp->getUtils()->codeToSession($code);
// Persist session_key in a short-lived server-side session/cache.

$decrypted = $miniApp->getEncryptor()->decrypt(
    $session['session_key'],
    $iv,
    $encryptedData
);

Payment is a separate application and a separate mental model. Create the order server-side, return the prepay_id or signed client parameters, and handle the asynchronous callback through getValidator() and getDecryptor(). Always verify the signature against the raw request body before decrypting.

$pay = new \EasyWeChat\Pay\Application([
    'mch_id'      => env('WECHAT_MCH_ID'),
    'private_key' => env('WECHAT_PAY_PRIVATE_KEY'),
    'certificate' => env('WECHAT_PAY_CERTIFICATE'),
    'secret_key'  => env('WECHAT_PAY_V3_KEY'),
]);

// Create a JSAPI transaction.
$response = $pay->getClient()->post('/v3/pay/transactions/jsapi', [
    'json' => [
        'appid'        => env('WECHAT_APP_ID'),
        'mchid'        => env('WECHAT_MCH_ID'),
        'description'  => 'Order #123',
        'out_trade_no' => $orderNo,
        'notify_url'   => route('wechat.pay.notify'),
        'amount'       => ['total' => 100, 'currency' => 'CNY'],
        'payer'        => ['openid' => $openId],
    ],
]);

// Callback: verify first, decrypt second.
$validator = $pay->getValidator();
$validator->validate($headers, $rawBody);

$decryptor = $pay->getDecryptor();
$message = $decryptor->decrypt($rawBody);

Build JSSDK config through getUtils(). Do not hand-roll nonce/timestamp/signature logic; pass the exact current page URL, including query string, and the list of JS APIs you actually use.

$config = $app->getUtils()->buildJsSdkConfig(
    $request->fullUrl(),
    ['chooseImage', 'previewImage'],
    false
);

Configure cache and logging explicitly in production. Access tokens are cached by the SDK, but the default cache is not suitable for multi-process or multi-server deployments. Use Redis or Laravel's shared cache so every worker sees the same token. Add a logger to capture SDK HTTP failures, but scrub secrets and authorization headers.

Test with mocked HTTP clients, not live WeChat calls. Inject a fake PSR-18/Guzzle client or use the SDK's configurable HTTP client with a mock handler. Keep the callback payload fixtures from WeChat in tests and assert signature verification and decryption separately from business logic.

Gotchas and Tips

  • Version drift is the biggest source of broken tutorials. EasyWeChat 6.x uses EasyWeChat\OfficialAccount\Application, EasyWeChat\MiniApp\Application, and EasyWeChat\Pay\Application. Older examples using Factory::officialAccount() are for 4.x/5.x and will not work unchanged.
  • The official account token and mini program token are not interchangeable. Keep separate app instances and separate cache namespaces, even when both use the same WeChat account identity.
  • Access-token errors usually mean stale cache or wrong IP. 40001 / 42001 often means the cached token was invalidated or another process refreshed it. 40164 means your server IP is not in the official account or mini program IP whitelist. Clear the cache and check the whitelist before changing code.
  • Concurrent token refreshes can race. In high-concurrency deployments, use a shared cache and a lock around token refresh if you see frequent token invalidation. The SDK caches tokens, but it does not provide a distributed refresh lock by default.
  • Server responses are PSR-7 responses, not Laravel responses. $server->serve() returns a PSR-7 response. Convert the body, status, and headers when returning from Laravel/Symfony, or use the Laravel bridge's conventions. Forgetting this produces empty or malformed replies to WeChat.
  • Use the raw request body for payment verification. Signature verification is over the exact bytes WeChat sent. Do not parse and re-encode JSON before passing the body to getValidator(). In Laravel use $request->getContent().
  • Never disable payment callback signature verification or platform-certificate checks. Cache and rotate platform certificates correctly; validate the Wechatpay-Serial header against your configured certificate set.
  • Make payment callbacks idempotent. WeChat retries notifications. Key processing by out_trade_no / transaction ID and return a success response quickly. Do not perform long-running fulfillment inside the callback request.
  • Safe mode requires matching token and aes_key. If messages arrive encrypted but aes_key is missing or wrong, the server will fail to decode them. If you change encryption settings in the WeChat console, update the app config and clear cached configuration.
  • JSSDK signatures use the exact page URL. A URL with a different query string, fragment handling, or HTTP/HTTPS mismatch will produce invalid signature. Build the signature from the same URL the browser reports.
  • WeChat HTTP APIs are inconsistent about JSON vs query parameters. Some endpoints expect JSON bodies, others expect query strings. Check the endpoint documentation and use the correct json or query option with getClient().
  • Log SDK requests and responses during integration, then reduce log level. In production, log failures and token errors, not full payloads containing openid, phone numbers, or payment data.
  • Use setCache(), setLogger(), and the configurable HTTP client as extension points. These are the clean places to integrate Laravel's cache, Monolog, and Guzzle middleware/retries rather than editing vendor code.
  • Open platform and WeChat Work have their own callback and token flows. Do not reuse the official-account server handler or token cache for them. In particular, open platform component verification tickets arrive repeatedly and must be cached and handled reliably.
  • Local development needs a public HTTPS callback URL. Use a tunnel such as ngrok for official account and payment callbacks, and remember that mini program request domains and payment authorization directories must be configured in the WeChat consoles.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource