Thinkphp8 集成微信支付sdk

配置并獲取商戶appId、證書靶溜、v3密鑰狂塘、證書序列號(hào)等

官方有接入指引,按照要求申請(qǐng)即可順利獲壤谈濉:https://pay.weixin.qq.com/index.php/core/cert/api_cert#/api-cert-manage

申請(qǐng)商戶證書.png

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/"
下載微信公鑰證書.png

集成到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通了。

  1. 回調(diào)通知處理

使用微信公鑰對(duì)接口數(shù)據(jù)進(jìn)行簽名驗(yàn)證:
官方文檔有步驟說明:

  1. 從請(qǐng)求頭部Headers橙垢,拿到Wechatpay-Signature垛叨、Wechatpay-NonceWechatpay-Timestamp柜某、Wechatpay-SerialRequest-ID嗽元,商戶側(cè)Web解決方案可能有差異,請(qǐng)求頭可能大小寫不敏感喂击,請(qǐng)根據(jù)自身應(yīng)用來定剂癌;
  2. 獲取請(qǐng)求body體的JSON純文本;
  3. 檢查通知消息頭標(biāo)記的Wechatpay-Timestamp偏移量是否在5分鐘之內(nèi)翰绊;
  4. 調(diào)用SDK內(nèi)置方法佩谷,構(gòu)造驗(yàn)簽名串然后經(jīng)Rsa::verfify驗(yàn)簽;
  5. 消息體需要解密的监嗜,調(diào)用SDK內(nèi)置方法解密谐檀;
  6. 如遇到問題,請(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ù)操作即可莉撇。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末呢蛤,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子棍郎,更是在濱河造成了極大的恐慌其障,老刑警劉巖,帶你破解...
    沈念sama閱讀 223,126評(píng)論 6 520
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涂佃,死亡現(xiàn)場離奇詭異励翼,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)辜荠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,421評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門汽抚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人伯病,你說我怎么就攤上這事造烁。” “怎么了狱从?”我有些...
    開封第一講書人閱讀 169,941評(píng)論 0 366
  • 文/不壞的土叔 我叫張陵膨蛮,是天一觀的道長叠纹。 經(jīng)常有香客問我季研,道長,這世上最難降的妖魔是什么誉察? 我笑而不...
    開封第一講書人閱讀 60,294評(píng)論 1 300
  • 正文 為了忘掉前任与涡,我火速辦了婚禮,結(jié)果婚禮上持偏,老公的妹妹穿的比我還像新娘驼卖。我一直安慰自己,他們只是感情好鸿秆,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,295評(píng)論 6 398
  • 文/花漫 我一把揭開白布酌畜。 她就那樣靜靜地躺著,像睡著了一般卿叽。 火紅的嫁衣襯著肌膚如雪桥胞。 梳的紋絲不亂的頭發(fā)上恳守,一...
    開封第一講書人閱讀 52,874評(píng)論 1 314
  • 那天,我揣著相機(jī)與錄音贩虾,去河邊找鬼催烘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛缎罢,可吹牛的內(nèi)容都是我干的伊群。 我是一名探鬼主播,決...
    沈念sama閱讀 41,285評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼策精,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼舰始!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起咽袜,我...
    開封第一講書人閱讀 40,249評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤蔽午,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后酬蹋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體及老,經(jīng)...
    沈念sama閱讀 46,760評(píng)論 1 321
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,840評(píng)論 3 343
  • 正文 我和宋清朗相戀三年范抓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了骄恶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,973評(píng)論 1 354
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡匕垫,死狀恐怖僧鲁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情象泵,我是刑警寧澤寞秃,帶...
    沈念sama閱讀 36,631評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站偶惠,受9級(jí)特大地震影響春寿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜忽孽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,315評(píng)論 3 336
  • 文/蒙蒙 一绑改、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧兄一,春花似錦厘线、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,797評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至骂束,卻和暖如春耳璧,著一層夾襖步出監(jiān)牢的瞬間硝全,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,926評(píng)論 1 275
  • 我被黑心中介騙來泰國打工楞抡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伟众,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,431評(píng)論 3 379
  • 正文 我出身青樓召廷,卻偏偏與公主長得像凳厢,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子竞慢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,982評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容