# wechatpay-php: the official PHP SDK for WeChat Pay APIv3

> wechatpay-php is the official PHP SDK for WeChat Pay, wrapping the APIv3 endpoints in a Guzzle client that signs every request and verifies every response, with a chainable URI template, sensitive-data encryption helpers and notification verification. It suits a PHP merchant integration, and it only handles the protocol, not the business logic around it.

**wechatpay-apiv3/wechatpay-php** — 微信支付 APIv3 的官方 PHP Library，同时也支持 APIv2

- Repository: https://github.com/wechatpay-apiv3/wechatpay-php
- Stars: 602 · Forks: 104
- Language: PHP
- License: Apache-2.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/wechatpay-apiv3-wechatpay-php

## A protocol client, not a payment abstraction

wechatpay-php is described as a synchronous and asynchronous chainable WeChat Pay v2 and v3 OpenAPI SDK for PHP, built on the Guzzle HTTP client. The scope of that sentence matters, because the library sits lower in the stack than most people expect.

It is an API client. It knows how to build a request to a WeChat Pay endpoint, how to sign that request with your merchant private key, how to verify the signature on the response WeChat Pay sends back, how to decrypt sensitive fields, and how to verify and decrypt a callback notification. It does not know what a refund is for, when to issue one, what your order model looks like, or how to reconcile a payment against your own ledger.

The feature list is four items and each is about protocol mechanics. There is a Guzzle HTTP client for APIv2 and APIv3 that supports synchronous or asynchronous request sending and automatically performs request signing and response signature verification. There is a chainable URI Template. There is encryption and decryption for sensitive information. And there is signature verification and decryption for callback notifications.

So the value is that you do not have to reimplement WeChat Pay's signature scheme, its certificate model or its AES-GCM payload handling. The value is not that it makes payments easy.

The licence is Apache-2.0 and the package is published on Packagist as wechatpay/wechatpay. The project description notes that the current version is 1.5.0 and that the project follows semantic versioning, and that users on versions at or below v1.3.2 should read the upgrade guide in UPGRADING.md before upgrading.

## The certificate model, and why there are two kinds of public key

The concepts section of the project documentation is where the integration decisions actually get made, and it is worth reading slowly because the naming is easy to mix up.

There is the merchant API certificate, which proves the merchant's identity. It contains the merchant number, the certificate serial number and the certificate validity period, and it is issued by a certificate authority so that it cannot be forged or altered. There is the merchant API private key, generated when you apply for that certificate and stored locally in the file apiclient_key.pem. That key is what signs your requests, and the documentation is emphatic that it must never be exposed publicly, whether uploaded to GitHub or written into application code.

On the platform side there are two ways for WeChat Pay to prove who it is, and this is the part that changed recently and catches integrators out.

The first is the WeChat Pay platform certificate, applied for by WeChat Pay and containing the platform identifier and public key information. You use its public key to verify API responses and callback notification signatures. Its serial number is a unique identifier issued by the certificate authority, and the serial number can be parsed from the certificate file or looked up in the merchant platform under the API security section.

The second is the WeChat Pay public key, which also signs responses and callback notifications, downloaded from the merchant platform, together with the WeChat Pay public key ID, its unique identifier, viewed in the same place.

The Builder factory takes both, keyed by serial number and by public key ID respectively, which is why the certs array in the configuration holds two entries.

The documentation also mentions a built-in CLI tool to download platform certificate files, the same way you would install any Composer dependency, so you can fetch them at setup time rather than pasting them in by hand.

## Building the client and making the first call

Installation is a single Composer command:

```shell
composer require wechatpay/wechatpay
```

The client is then constructed with a Builder factory, and the configuration is a set of credentials rather than an options object with defaults:

```php
$instance = Builder::factory([
    'mchid'      => $merchantId,
    'serial'     => $merchantCertificateSerial,
    'privateKey' => $merchantPrivateKeyInstance,
    'certs'      => [
        $platformCertificateSerial => $onePlatformPublicKeyInstance,
        $platformPublicKeyId       => $twoPlatformPublicKeyInstance,
    ],
]);
```

The private key and the platform public key are loaded from local files through Rsa::from, which takes a file URL and a key type, KEY_TYPE_PRIVATE or KEY_TYPE_PUBLIC.

The first call in the documentation is the one that fetches the WeChat Pay platform certificates, and it is a good first request to make because it verifies your whole credential setup:

```php
    $resp = $instance->chain('v3/certificates')->get(
    );
```

The chain method takes a URI template, and that is where the chainable part earns its name. You write the endpoint path as a template and the client substitutes the parameters, so the same call is expressed once rather than assembled from string concatenation.

If that request fails with an error body whose code is RESOURCE_NOT_EXISTS, the documentation explains what it means: there is no usable platform certificate, and the merchant can only operate in WeChat Pay public key mode. In that case the platform certificate serial and the platform public key instance are not needed at initialization at all, which is the state the documentation describes the initialization as not requiring.

The error handling pattern in the example is also the pattern to copy: catch the exception, print the message, and if it is a Guzzle RequestException with a response, print the status code, reason phrase and body, then the stack trace.

## Synchronous and asynchronous calls, and the response object

Requests come in two flavours, and the difference is which Guzzle method is called rather than which client you build.

For synchronous requests the client offers get, put, post, patch and delete. The documented example is a native payment order, and the shape is a chain of calls ending in a body:

```php
$promise = $instance
->chain('v3/refund/domestic/refunds')
->postAsync([
    'json' => [
        'transaction_id' => '1217752501201407033233368018',
        'out_refund_no'  => '1217752501201407033233368018',
        'amount'         => [
            'refund'   => 888,
            'total'    => 888,
            'currency' => 'CNY',
        ],
    ],
])
```

The asynchronous variants are getAsync, putAsync, postAsync, patchAsync and deleteAsync, and they return a promise you resolve with a then callback. The response object you get back on success is a GuzzleHttp\Psr7\Response, and the documentation points at Guzzle's own response documentation for how to read the status code and body.

The design point is that both flavours share the same client and the same chain syntax, and the signing and verification happen regardless of which you use. That is what makes it safe to move a slow endpoint to the asynchronous form without changing anything about the credentials.

There is a separate README_APIv2.md for merchants using the older API version, and the start of the documentation says the APIv3 guidance is the main path with APIv2 users directed to that separate file. The library supports both, and the two are documented separately rather than interleaved, which is the right call given how much the credential model differs between them.

## The repository layout, and what the Makefile generates

The tree shows a library with a real test suite and a static analysis setup, which is what you would expect from a package that signs financial requests.

Alongside src/ and tests/ there is a bin/ directory for the command-line tooling the documentation refers to for downloading platform certificates, a CHANGELOG.md, a SECURITY.md, and the UPGRADING.md that the project status section points at. There is a composer.json, a phpunit.xml.dist for the test runner, and four separate PHPStan configurations: phpstan.neon.dist for the default, and phpstan.v7.1.neon, phpstan.v8.2.neon and phpstan.v8.4.neon for the three PHP versions the analysis is pinned to.

The Makefile is where the interesting part sits, because the test fixtures are cryptographic material and generating them is a job in itself. The default target chains them:

```make
all: stats keygen x509crt test clean
```

The keygen target generates a 2048-bit RSA private key with openssl and derives the PKCS#1 and SPKI forms from it, writes a random 16-byte password as hex, and produces an encrypted PKCS#8 key using that password file. The x509crt target then creates a self-signed certificate with a random serial number, using an openssl command with a subject of /C=CN/ST=Shanghai/O=WeChatPay Community/CN=WeChatPay Community CI, valid for a single day, and prints the certificate text at the end.

The reason for generating these at build time rather than committing them is that they are test credentials, and generating them keeps a private key out of the repository. The clean target removes them afterwards.

The stats target runs PHPStan with a memory limit of 200M, and the test target runs PHPUnit:

## Where wechatpay-php is the wrong tool

The first limit is language. This is a PHP SDK, and while the API is a REST API and you could call it from anywhere, the signing, verification and decryption logic here is in PHP. A Node or Python service integrating with the same merchant account would be implementing the same protocol against the same certificates, and the SDK's value would not transfer.

The second is that it is an API client, so all the surrounding work is yours. Refund policy, order state, idempotency on your side, reconciliation against a bank statement, and what happens when a callback arrives twice are all outside this library. For a merchant with a finance team, that surrounding work is usually larger than the integration work.

The third is the credential surface. The library requires a merchant private key on disk, plus platform certificates or the public key and its ID, plus serial numbers. The documentation is explicit that the private key must not be exposed, and a PHP application that reads a key file at startup has to be deployed somewhere the file permissions mean something. For a deployment model where secrets come from a vault or a key management service, this SDK does not meet you there; you would fetch the key into a temp file at startup or implement the signing yourself.

The fourth is the API version split. Supporting v2 and v3 in one package sounds convenient, but the documentation treats them as two separate documents with a separate README_APIv2.md, because the credential model is genuinely different. A team that only needs one version inherits the other one's surface anyway.

The fifth is the release cadence against a payment API. The recent tags are v1.4.11 from 2024-12-27, v1.4.12 from 2025-01-26 and v1.5.0 from 2026-09-18, with the last push to the repository on 2026-09-18. That is a library that moves in step with the platform rather than continuously, which is reasonable for a payment SDK but does mean you should check the changelog when the platform announces something new, rather than expecting the SDK to have absorbed it already.

## Conclusion

wechatpay-php is worth using for a PHP merchant integration because it is the official SDK rather than a community wrapper, so its handling of the certificate and public-key material matches what WeChat Pay expects, and because the chainable client keeps the request-signing and response-verification steps invisible to the calling code. It is the wrong choice if your integration is not in PHP, and it is the wrong choice if you want a payment abstraction rather than an API client. Before you build on it, check the three things the project documents: that the current version is 1.5.0 and follows semantic versioning, so a minor release will not change your API but a major one will; that the supported environments are Guzzle 7.0 with PHP 7.2.5 or newer, or Guzzle 6.5 with PHP 7.1.2 or newer, and the recommendation is PHP 8 and Guzzle 7; and that a merchant on versions at or below v1.3.2 must read the upgrade guide in UPGRADING.md first, because the certificate and public-key model changed between those lines.

## FAQ

### How do I install the WeChat Pay PHP SDK?

Use Composer and run composer require wechatpay/wechatpay. The package is published on Packagist as wechatpay/wechatpay under the Apache-2.0 licence, and it requires Guzzle 7.0 with PHP 7.2.5 or newer, or Guzzle 6.5 with PHP 7.1.2 or newer, with PHP 8 and Guzzle 7 recommended.

### How do I create a WeChat Pay APIv3 client in PHP?

Load your merchant private key and the platform public key with Rsa::from, then pass them to Builder::factory with the merchant id, the certificate serial number and a certs array keyed by the platform certificate serial and the platform public key ID. The returned client exposes chain for URI templates and the get, put, post, patch and delete methods, plus their async variants.

### What happens if the platform certificate request fails with RESOURCE_NOT_EXISTS?

It means there is no usable platform certificate available and the merchant can only run in WeChat Pay public key mode. In that case you do not need to read or configure the platform certificate serial number or the platform public key instance when initializing the client.

## Sources

- [Issues](https://github.com/wechatpay-apiv3/wechatpay-php/issues)
- [License: Apache-2.0](https://github.com/wechatpay-apiv3/wechatpay-php/blob/main/LICENSE)
- [README](https://github.com/wechatpay-apiv3/wechatpay-php/blob/main/README.md)
- [Releases](https://github.com/wechatpay-apiv3/wechatpay-php/releases)
- [wechatpay-apiv3/wechatpay-php on GitHub](https://github.com/wechatpay-apiv3/wechatpay-php)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/wechatpay-apiv3-wechatpay-php
