[A]Sync Chainable WeChatPay v2&v3's OpenAPI SDK for PHP
基于 Guzzle HTTP Client 的微信支付 PHP 开发库。
当前版本为 1.4.10
版。
项目版本遵循 语义化版本号。
如果你使用的版本 <=v1.3.2
,升级前请参考 升级指南。
项目支持的环境如下:
我们推荐使用目前处于 Active Support 阶段的 PHP 8 和 Guzzle 7。
推荐使用 PHP 包管理工具 Composer 安装 SDK:
composer require wechatpay/wechatpay
以下是 微信支付 API v3 的指引。如果你是 API v2 的使用者,请看 README_APIv2。
商户 API 证书,是用来证实商户身份的。证书中包含商户号、证书序列号、证书有效期等信息,由证书授权机构(Certificate Authority ,简称 CA)签发,以防证书被伪造或篡改。详情见 什么是商户API证书?如何获取商户API证书? 。
商户 API 私钥。你申请商户 API 证书时,会生成商户私钥,并保存在本地证书文件夹的文件 apiclient_key.pem 中。为了证明 API 请求是由你发送的,你应使用商户 API 私钥对请求进行签名。
不要把私钥文件暴露在公共场合,如上传到 Github,写在 App 代码中等。
微信支付平台证书。微信支付平台证书是指:由微信支付负责申请,包含微信支付平台标识、公钥信息的证书。你需使用微信支付平台证书中的公钥验证 API 应答和回调通知的签名。
通用的 composer 命令,像安装依赖包一样 下载平台证书 文件,供SDK初始化使用。
证书序列号。每个证书都有一个由 CA 颁发的唯一编号,即证书序列号。
微信支付平台公钥,是微信支付平台的公钥,用于应答及回调通知的数据签名,可在 微信支付商户平台 -> 账户中心 -> API安全 直接下载。
微信支付平台公钥ID,是微信支付平台公钥的唯一标识,可在 微信支付商户平台 -> 账户中心 -> API安全 直接查看。
<?php
require_once('vendor/autoload.php');
use WeChatPay\Builder;
use WeChatPay\Crypto\Rsa;
// 设置参数
// 商户号
$merchantId = '190000****';
// 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
$merchantPrivateKeyFilePath = 'file:///path/to/merchant/apiclient_key.pem';
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
// 「商户API证书」的「证书序列号」
$merchantCertificateSerial = '3775B6A45ACD588826D15E583A95F5DD********';
// 从本地文件中加载「微信支付平台证书」或者「微信支付平台公钥」,用来验证微信支付应答的签名
$platformCertificateOrPublicKeyFilePath = 'file:///path/to/wechatpay/certificate_or_publickey.pem';
$platformPublicKeyInstance = Rsa::from($platformCertificateOrPublicKeyFilePath, Rsa::KEY_TYPE_PUBLIC);
// 「微信支付平台证书」的「证书序列号」或者是「微信支付平台公钥ID」
// 「平台证书序列号」及/或「平台公钥ID」可以从 商户平台 -> 账户中心 -> API安全 直接查询到
$platformCertificateSerialOrPublicKeyId = '7132D72A03E93CDDF8C03BBD1F37EEDF********';
// 构造一个 APIv3 客户端实例
$instance = Builder::factory([
'mchid' => $merchantId,
'serial' => $merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
$platformCertificateSerialOrPublicKeyId => $platformPublicKeyInstance,
],
]);
// 发送请求
$resp = $instance->chain('v3/certificates')->get(
/** @see https://docs.guzzlephp.org/en/stable/request-options.html#debug */
// ['debug' => true] // 调试模式
);
echo (string) $resp->getBody(), PHP_EOL;
使用客户端提供的 get
、put
、post
、patch
或 delete
方法发送同步请求。以 ```
$resp = $instance
->chain('v3/pay/transactions/native')
->post(['json' => [
'mchid' => '1900006XXX',
'out_trade_no' => 'native12177525012014070332333',
'appid' => 'wxdace645e0bc2cXXX',
'description' => 'Image形象店-深圳腾大-QQ公仔',
'notify_url' => 'https://weixin.qq.com/',
'amount' => [
'total' => 1,
'currency' => 'CNY'
],
]]);
echo $resp->getStatusCode(), PHP_EOL; echo (string) $resp->getBody(), PHP_EOL;
После успешного запроса вы получите объект ответа `GuzzleHttp\Psr7\Response`.
Для дальнейшего изучения того, как получить доступ к информации в ответе, обратитесь к документации Guzzle: «Using Response» (https://docs.guzzlephp.org/en/stable/quickstart.html#using-responses).
### Асинхронный запрос
Чтобы отправить асинхронный запрос, используйте методы `getAsync`, `putAsync`, `postAsync`, `patchAsync` или `deleteAsync`, предоставляемые клиентом. Пример — запрос на возврат средств (https://pay.weixin.qq.com/docs/merchant/apis/native-payment/create.html):
```php
$promise = $instance
->chain('v3/refund/domestic/refunds')
->postAsync([
'json' => [
'transaction_id' => '1217752501201407033233368018',
'out_refund_no' => '1217752501201407033233368018',
'amount' => [
'refund' => 888,
'total' => 888,
'currency' => 'CNY',
],
],
])
->then(static function($response) {
// Нормальная логика обратного вызова
echo (string) $response->getBody(), PHP_EOL;
return $response;
})
->otherwise(static function($e) {
// Обработка ошибок
echo $e->getMessage(), PHP_EOL;
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
echo (string) $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
}
echo $e->getTraceAsString(), PHP_EOL;
});
// Синхронное ожидание
$promise->wait();
Методы [get|post|put|patch|delete]Async
возвращают обещания Guzzle (Guzzle Promises
). Вы можете сделать две вещи:
then()
для обработки полученного Psr\Http\Message\ResponseInterface
и, при необходимости, передайте его следующему then()
.otherwise()
для обработки исключений.Наконец, используйте wait()
для ожидания завершения выполнения запроса.
Большинству разработчиков рекомендуется использовать синхронный режим, поскольку он более понятен.
Если вы опытный разработчик с опытом асинхронного программирования, то в некоторых сценариях последовательного вызова API объединение нескольких операций через then()
может быть элегантным решением. Например, потоковая загрузка файлов транзакционных отчётов в виде цепочки функций (https://developers.weixin.qq.com/community/pay/article/doc/000ec4521086b85fb81d6472a51013).
URI Template (https://www.rfc-editor.org/rfc/rfc6570.html) — это способ выражения переменных в URI. WeChat Pay API использует этот метод для представления номеров или идентификаторов заказов в URL Path.
# Использование номера заказа WeChat для запроса заказа
GET /v3/pay/transactions/id/{transaction_id}
# Использование торгового номера продавца для запроса заказа
GET /v3/pay/transactions/out-trade-no/{out_trade_no}
Используя цепочку URI Templates (chain URI Template
), вы можете легко писать URL, как код, и легко вводить пути и передавать параметры URL. После настройки пакета описания интерфейса вы также можете включить подсказки IDE (https://github.com/TheNorthMemory/wechatpay-openapi).
Основные элементы цепочки — сегменты в URI Path (segments
), которые соединяются знаком ->
. Правила соединения следующие:
v3->pay->transactions->native
.chain()
. Например, chain('v3/pay/transactions/native')
-
)
camelCase
). Например, merchant-service
можно записать как merchantService
.{foo-bar}
. Например, {merchant-service}
.chain()
, что может привести к ошибкам в обработке регистра.
_variable_name_
для поддержки подсказок IDE. Например, v3->pay->transactions->id->_transaction_id_
.{'{variable_name}'}
для написания. Например, v3->pay->transactions->id->{'{transaction_id}'}
HTTP METHOD
запроса как последнее действие в цепочке. Например, v3->pay->transactions->native->post([ ... ])
query
. ```
$inWechatpayTimestamp = ''; // 请根据实际情况获取
$inWechatpaySerial = ''; // 请根据实际情况获取
$inWechatpayNonce = ''; // 请根据实际情况获取$inBody = ''; // 请根据实际情况获取,例如: file_get_contents('php://input');
$apiv3Key = ''; // 在商户平台上设置的APIv3密钥
// 根据通知的平台证书序列号,查询本地平台证书文件,
// 假定为 /path/to/wechatpay/inWechatpaySerial.pem
$platformPublicKeyInstance = Rsa::from('file:///path/to/wechatpay/inWechatpaySerial.pem', Rsa::KEY_TYPE_PUBLIC);
// 检查通知时间偏移量,允许5分钟之内的偏移 $timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp); $verifiedStatus = Rsa::verify( // 构造验签名串 Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody), $inWechatpaySignature, $platformPublicKeyInstance ); if ($timeOffsetStatus && $verifiedStatus) { // 转换通知的JSON文本消息为PHP Array数组 $inBodyArray = (array)json_decode($inBody, true); // 使用PHP7的数据解构语法,从Array中解构并赋值变量 ['resource' => [ 'ciphertext' => $ciphertext, 'nonce' => $nonce, 'associated_data' => $aad ]] = $inBodyArray; // 加密文本消息解密 $inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad); // 把解密后的文本转换为PHP Array数组 $inBodyResourceArray = (array)json_decode($inBodyResource, true); // print_r($inBodyResourceArray);// 打印解密后的结果 }
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.