配置并獲取商戶appId、證書靶溜、v3密鑰狂塘、證書序列號(hào)等
官方有接入指引,按照要求申請(qǐng)即可順利獲壤谈濉:https://pay.weixin.qq.com/index.php/core/cert/api_cert#/api-cert-manage
appid: wx*******************
v3密鑰:GM****************************
商戶號(hào):16******************
證書序列號(hào):12*********************************
composer安裝wechatpay官方推薦sdk
官方倉庫:https://github.com/wechatpay-apiv3/wechatpay-php/tree/main
進(jìn)入項(xiàng)目目錄又谋,并運(yùn)行安裝命令:
cd path_to_project/tp8
composer require wechatpay/wechatpay
下載微信官方公鑰證書:
注意:前面下載的證書對(duì)是商戶自己的拼缝,這里需要使用官方工具才能下載微信的公鑰證書,用于返回?cái)?shù)據(jù)的驗(yàn)證簽名
cd path_to_project/tp8
# 查看使用幫助
php vendor/bin/CertificateDownloader.php -V
Usage: 微信支付平臺(tái)證書下載工具 [-hV]
-f=<privateKeyFilePath> -k=<apiV3key> -m=<merchantId>
-s=<serialNo> -o=[outputFilePath] -u=[baseUri]
Options:
-m, --mchid=<merchantId> 商戶號(hào)
-s, --serialno=<serialNo> 商戶證書的序列號(hào)
-f, --privatekey=<privateKeyFilePath>
商戶的私鑰文件
-k, --key=<apiV3key> ApiV3Key
-o, --output=[outputFilePath]
下載成功后保存證書的路徑彰亥,可選參數(shù)咧七,默認(rèn)為臨時(shí)文件目錄夾
-u, --baseuri=[baseUri] 接入點(diǎn),默認(rèn)為 https://api.mch.weixin.qq.com/
-V, --version Print version information and exit.
-h, --help Show this help message and exit.
帶上參數(shù)執(zhí)行命令剩愧,獲取微信公鑰證書:
cd path_to_project/tp8
# 這里要帶上前面申請(qǐng)的證書相對(duì)路徑及v3密鑰猪叙,商戶號(hào),序列號(hào)等參數(shù)
# php vendor/bin/CertificateDownloader.php -k ${apiV3key} -m ${mchId} -f ${mchPrivateKeyFilePath} -s ${mchSerialNo} -o ${outputFilePath}
php vendor/bin/CertificateDownloader.php -m 16****************** -f "certs/apiclient_key.pem" -k "GM****************************" -s "12*********************************" -o "certs/"
如果提示報(bào)錯(cuò):
- Trying [240e:e1:aa00:4000::94]:443...
- Connected to api.mch.weixin.qq.com (240e:e1:aa00:4000::94) port 443
- ALPN: curl offers http/1.1
- SSL certificate problem: unable to get local issuer certificate
- Closing connection
cURL error 60: SSL certificate problem: unable to get local issuer certificate (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) for https://api.mch.weixin.qq.com/v3/certificates
需要進(jìn)入到vendor/guzzlehttp/guzzle/src/Client.php仁卷,修改配置:
private function configureDefaults(array $config): void {
$defaults = [
'allow_redirects' => RedirectMiddleware::$defaultSettings,
'http_errors' => true,
'decode_content' => true,
// verify = ture 修改成 false穴翩,等證書下載完成再改回來
'verify' => false,
'cookies' => false,
'idn_conversion' => false,
];
...
}
再次執(zhí)行命令:
php vendor/bin/CertificateDownloader.php -m 16****************** -f "certs/apiclient_key.pem" -k "GM****************************" -s "12*********************************" -o "certs/"
集成到thinkphp8
1、 在app\model目錄下新建WxpayModel.php
<?php
namespace app\model;
use WeChatPay\Builder;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Util\PemUtil;
class WxpayModel {
// 設(shè)置參數(shù)
public function Pay() {
// 商戶號(hào)
$merchantId = '1***********************';
// 從本地文件中加載「商戶API私鑰」锦积,「商戶API私鑰」會(huì)用來生成請(qǐng)求的簽名 // 商戶私鑰芒帕,用來發(fā)送數(shù)據(jù)簽名
$merchantPrivateKeyFilePath = 'file://D:/projects/tp8/certs/apiclient_key.pem';
$merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
// 「商戶API證書」的「證書序列號(hào)」
$merchantCertificateSerial = '1**********************************************************';
// 從本地文件中加載「微信支付平臺(tái)證書」,用來驗(yàn)證微信支付應(yīng)答的簽名 // 微信公鑰丰介,用來驗(yàn)證返回?cái)?shù)據(jù)的簽名
$platformCertificateFilePath = 'file://D:/projects/tp8/certs/cert.pem';
$platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
// 從「微信支付平臺(tái)證書」中獲取「證書序列號(hào)」
$platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
// 構(gòu)造一個(gè) APIv3 客戶端實(shí)例
$instance = Builder::factory([
'mchid' => $merchantId,
'serial' => $merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
$platformCertificateSerial => $platformPublicKeyInstance,
],
]);
try {
$resp = $instance
->chain('v3/pay/transactions/native') // 這里使用的native支付背蟆,其它接口參考官方文檔,如jsapi
->post(['json' => [
'mchid' => '1**************',
'out_trade_no' => 'native12177525012014070332333',
'appid' => 'wx*******************',
'description' => 'Image形象店-深圳騰大-QQ公仔',
'notify_url' => 'https://weixin.qq.com/',
'amount' => [
'total' => 1,
'currency' => 'CNY',
],
]]);
echo $resp->getStatusCode(), PHP_EOL;
echo $resp->getBody(), PHP_EOL;
} catch (\Exception $e) {
// 進(jìn)行錯(cuò)誤處理
echo $e->getMessage(), PHP_EOL;
if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
$r = $e->getResponse();
echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
}
echo $e->getTraceAsString(), PHP_EOL;
}
}
}
2哮幢、 到app\controllers目錄下Index.php添加測試喚起
<?php
namespace app\controller;
use app\model\WxpayModel;
class Index {
public function index() {
$wxpayModel = new WxpayModel();
$wxpayModel->pay();
}
}
瀏覽器訪問項(xiàng)目http://localhost/index/index.html
正解返回支付鏈接:
{"code_url":"weixin://wxpay/bizpayurl?pr=gUTJqa5zz"}
至此带膀,demo通了。
-
回調(diào)通知處理
使用微信公鑰對(duì)接口數(shù)據(jù)進(jìn)行簽名驗(yàn)證:
官方文檔有步驟說明:
- 從請(qǐng)求頭部
Headers
橙垢,拿到Wechatpay-Signature
垛叨、Wechatpay-Nonce
、Wechatpay-Timestamp
柜某、Wechatpay-Serial
及Request-ID
嗽元,商戶側(cè)Web
解決方案可能有差異,請(qǐng)求頭可能大小寫不敏感喂击,請(qǐng)根據(jù)自身應(yīng)用來定剂癌;- 獲取請(qǐng)求
body
體的JSON
純文本;- 檢查通知消息頭標(biāo)記的
Wechatpay-Timestamp
偏移量是否在5分鐘之內(nèi)翰绊;- 調(diào)用
SDK
內(nèi)置方法佩谷,構(gòu)造驗(yàn)簽名串然后經(jīng)Rsa::verfify
驗(yàn)簽;- 消息體需要解密的监嗜,調(diào)用
SDK
內(nèi)置方法解密谐檀;- 如遇到問題,請(qǐng)拿Request-ID聯(lián)系官方在線技術(shù)支持秤茅;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Formatter;
use \think\facade\App;
// 提取需要的頭部信息
$inWechatpaySignature = isset($_SERVER['HTTP_WECHATPAY_SIGNATURE']) ? $_SERVER['HTTP_WECHATPAY_SIGNATURE'] : '';
$inWechatpayTimestamp = isset($_SERVER['HTTP_WECHATPAY_TIMESTAMP']) ? $_SERVER['HTTP_WECHATPAY_TIMESTAMP'] : '';
$inWechatpaySerial = isset($_SERVER['HTTP_WECHATPAY_SERIAL']) ? $_SERVER['HTTP_WECHATPAY_SERIAL'] : '';
$inWechatpayNonce = isset($_SERVER['HTTP_WECHATPAY_NONCE']) ? $_SERVER['HTTP_WECHATPAY_NONCE'] : '';
// 還有一個(gè)request_id稚补,主要用于技術(shù)支持,如果沒問題框喳,就不需要
// 這里要重點(diǎn)注意:獲取請(qǐng)求`body`體的`JSON`純文本课幕,不能使用thinkphp框架的方法input()厦坛、$_POST、$_GET等
$inBody = file_get_contents('php://input');
$apiv3Key = 'Dg56*************************';// 在商戶平臺(tái)上設(shè)置的APIv3密鑰
// 獲取應(yīng)用實(shí)例
$app = app();
// 這里使用微信平臺(tái)的公鑰進(jìn)行驗(yàn)簽乍惊,如果數(shù)據(jù)庫存的是文件路徑:
// $platformPublicKeyInstance = Rsa::from('file://' . $app->getRootPath() . $config['provider_public_key'], Rsa::KEY_TYPE_PUBLIC);
// 這里使用微信平臺(tái)的公鑰進(jìn)行驗(yàn)簽杜秸,如果數(shù)據(jù)庫存的是公鑰串:
$platformPublicKeyInstance = Rsa::from($config['provider_public_key'], Rsa::KEY_TYPE_PUBLIC);
// 檢查通知時(shí)間偏移量,允許5分鐘之內(nèi)的偏移
$timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
$verifiedStatus = Rsa::verify(
// 構(gòu)造驗(yàn)簽名串
Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),
$inWechatpaySignature,
$platformPublicKeyInstance
);
if ($timeOffsetStatus && $verifiedStatus) {
// 轉(zhuǎn)換通知的JSON文本消息為PHP Array數(shù)組
$inBodyArray = (array)json_decode($inBody, true);
$inBodyArray = json_decode($inBody, true);
$ciphertext = $inBodyArray['resource']['ciphertext'];
$nonce = $inBodyArray['resource']['nonce'];
$aad = $inBodyArray['resource']['associated_data'];
// 加密文本消息解密
$inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad);
// 把解密后的文本轉(zhuǎn)換為PHP Array數(shù)組
$inBodyResourceArray = (array)json_decode($inBodyResource, true);
// print_r($inBodyResourceArray);// 打印解密后的結(jié)果
return ['status' => true, 'data' => $inBodyResourceArray];
} else {
return ['status' => false, 'data' => []];
}
重點(diǎn)注意:獲取請(qǐng)求body
體的JSON
純文本润绎,不能使用thinkphp框架的方法input()撬碟、$_POST、$_GET等
最后根據(jù)驗(yàn)證簽名的結(jié)果和解密出的數(shù)據(jù)執(zhí)行后續(xù)操作即可莉撇。