评论

如何使用wechatpay-php基础能力来做支付通知对接

回调通知受限于开发者/商户所使用的`WebServer`有很大差异,这里只给出开发指导步骤,供参考实现。

APIv3回调通知

  1. 从请求头部Headers,拿到Wechatpay-SignatureWechatpay-NonceWechatpay-TimestampWechatpay-SerialRequest-ID,商户侧Web解决方案可能有差异,请求头可能大小写不敏感,请根据自身应用来定;
  2. 获取请求body体的JSON纯文本;
  3. 检查通知消息头标记的Wechatpay-Timestamp偏移量是否在5分钟之内;
  4. 调用SDK内置方法,构造验签名串然后经Rsa::verfify验签;
  5. 消息体需要解密的,调用SDK内置方法解密;
  6. 如遇到问题,请拿Request-ID点击这里,联系官方在线技术支持;

样例代码如下:

use WeChatPay\Util\PemUtil;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Formatter;

$inWechatpaySignature = '';// 请根据实际情况获取
$inWechatpayTimestamp = '';// 请根据实际情况获取
$inWechatpaySerial = '';// 请根据实际情况获取
$inWechatpayNonce = '';// 请根据实际情况获取
$inBody = '';// 请根据实际情况获取,例如: file_get_contents('php://input');

$apiv3Key = '';// 在商户平台上设置的APIv3密钥

// 根据通知的平台证书序列号,查询本地平台证书文件,
// 假定为 `/path/to/wechatpay/inWechatpaySerial.pem`
$certInstance = PemUtil::loadCertificate('/path/to/wechatpay/inWechatpaySerial.pem');

// 检查通知时间偏移量,允许5分钟之内的偏移
$timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
$verifiedStatus = Rsa::verify(
    // 构造验签名串
    Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),
    $inWechatpaySignature, 
    $certInstance
);
if ($timeOffsetStatus && $verifiedStatus) {
    $inBodyArray = (array)json_decode($inBody, true);
    ['resource' => [
        'ciphertext' => $ciphertext, 
        'nonce' => $nonce, 
        'associated_data' => $aad
    ]] = $inBodyArray;
    $inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad);
    $inBodyResourceArray = (array)json_decode($inBodyResource, true);
    // print_r($inBodyResourceArray);// 打印解密后的结果
}

APIv2回调通知

  1. 从请求头Headers获取Request-ID,商户侧Web解决方案可能有差异,请求头的Request-ID可能大小写不敏感,请根据自身应用来定;
  2. 获取请求body体的XML纯文本;
  3. 调用SDK内置方法,根据签名算法做本地数据签名计算,然后与通知文本的signHash::equals对比验签;
  4. 消息体需要解密的,调用SDK内置方法解密;
  5. 如遇到问题,请拿Request-ID点击这里,联系官方在线技术支持;

样例代码如下:

use WeChatPay\Transformer;
use WeChatPay\Crypto\Hash;
use WeChatPay\Crypto\AesEcb;
use WeChatPay\Formatter;

$inBody = '';// 请根据实际情况获取,例如: file_get_contents('php://input');

$apiv2Key = '';// 在商户平台上设置的APIv2密钥

$inBodyArray = Transformer::toArray($inBody);

// 部分通知体无`sign_type`,部分`sign_type`默认为`MD5`,部分`sign_type`默认为`HMAC-SHA256`
// 部分通知无`sign`字典
// 请根据官方开发文档确定
['sign_type' => $signType, 'sign' => $sign] = $inBodyArray;

$calculated = Hash::sign(
    $signType ?? Hash::ALGO_MD5,// 如没获取到`sign_type`,假定默认为`MD5`
    Formatter::queryStringLike(Formatter::ksort($inBodyArray)), 
    $apiv2Key
);

$signatureStatus = Hash::equals($calculated, $sign);

if ($signatureStatus) {
    // 如需要解密的
    ['req_info' => $reqInfo] = $inBodyArray;
    $inBodyReqInfoXml = AesEcb::decrypt($reqInfo, Hash::md5($apiv2Key));
    $inBodyReqInfoArray = Transformer::toArray($inBodyReqInfoXml);
    // print_r($inBodyReqInfoArray);// 打印解密后的结果
}
最后一次编辑于  2021-08-22  
点赞 3
收藏
评论

8 个评论

  • 今天睡地板
    今天睡地板
    2021-08-21

    $inBody = '';// 请根据实际情况获取,例如: file_get_contents('php://input');

    这$inBody 如果获取的是 file_get_contents('php://input') 那么 下面提交的时候 还需要

    $inBody = "$inWechatpayTimestamp\n$inWechatpayNonce\n$inBody\n";

    研究了我一天。妹的! 说又不说清楚 还留点坑给别人挖。

    $apiv3Key = ''; 这个代码没用。

    2021-08-21
    赞同 2
    回复 4
    • 今天睡地板
      今天睡地板
      2021-08-21
      说的是V3版本。
      2021-08-21
      回复
    • 今天睡地板
      今天睡地板
      2021-08-21
      说错了 $apiv3Key = 这个解密的时候要用。一直钻研验签去了
      2021-08-21
      回复
    • 北望沣渭
      北望沣渭
      2021-08-22
      阿西吧,刚翻了文档看了下,还真错了,文章已修正,用下面的函数来拼接「构造验签名串」
      2021-08-22
      回复
    • 辻弍
      辻弍
      2022-03-31
      赞同,文档不清楚,我嘞去
      2022-03-31
      回复
  • 蔡婷
    蔡婷
    发表于移动端
    2021-08-21
    点赞评论收藏🥁🥁🥁
    2021-08-21
    赞同 2
    回复
  • 青寒
    青寒
    2021-08-21

    点个赞

    2021-08-21
    赞同 2
    回复
  • 今天睡地板
    今天睡地板
    2021-08-21

    大哥 你这两个参数没用到啊

    $inWechatpaySerial = '';

    $inWechatpayNonce = '';

    确定这样能行吗? 我试了不行哦

    应该要调用 ClientJsonTrait::verifier() 才行吧 我看代码 只有这里有验签的。

    2021-08-21
    赞同 2
    回复 2
    • 今天睡地板
      今天睡地板
      2021-08-21
      经过研究 下面4楼回答正确可用。
      2021-08-21
      回复
    • 北望沣渭
      北望沣渭
      2021-08-22
      `ClientJsonTrait::verifier()`是主动请求对服务端返回值验签;与「支付通知」是被动接受,对接受的内容进行验签;有类似的地方,逻辑类似。
      2021-08-22
      回复
  • 哄哄
    哄哄
    2023-05-20

     $inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad);

    V3回调 这个解密不出来,是什么原因呢?

    2023-05-20
    赞同
    回复
  • 零度 火焰
    零度 火焰
    2022-03-15

    $verifiedStatus = Rsa::verify(

                // 构造验签名串

                Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),

                $inWechatpaySignature, 

                $certInstance

            );

    这里一种报Argument 2 passed to WeChatPay\Crypto\Rsa::verify() must be of the type string, null given

    2022-03-15
    赞同
    回复 1
    • 北望沣渭
      北望沣渭
      2022-03-15
      $inWechatpaySignature 没有获取到?
      2022-03-15
      回复
  • Mr. Yang
    Mr. Yang
    2021-10-29

    // 根据通知的平台证书序列号,查询本地平台证书文件,

    这inWechatpaySerial.pem没明白在那里生成,是指那个平台证书序列号?那个本地平台?

    2021-10-29
    赞同
    回复 5
    • 北望沣渭
      北望沣渭
      2021-10-29
      对,是「平台证书」通知头上仅会带上「平台证书」的序列号,得自己先期下载了「平台证书」,按序列号找对应的「平台证书」进行验签
      2021-10-29
      回复
    • Mr. Yang
      Mr. Yang
      2021-10-29回复北望沣渭
      您好,老师; 是通过 命令提示符 php C:\inetpub\wwwroot\demo\teng.mall\vendor\wechatpay\wechatpay\bin\CertificateDownloader.php -- -k ${tnegChao91520323MA6DLCB4XY201606} -m ${15111507**} -f ${C:\inetpub\wwwroot\demo\teng.mall\application\20211028_cert\apiclient_cert.p12} -s ${28CD7B3DA9D8658E19FEF06ADC88C56400A954**} 下载平台证书吗?使用的是API证书序列号和API证书apiclient_cert.p12
      2021-10-29
      回复
    • 北望沣渭
      北望沣渭
      2021-10-29
      ${} 都去掉吧,这个特指变量,完整命令形如:
      2021-10-29
      回复
    • Mr. Yang
      Mr. Yang
      2021-10-30回复北望沣渭
      命令 执行了CertificateDownloader.php 没如何反应(提示),要嘛直接以记事本的方式打开此文件。
      2021-10-30
      回复
    • Mr. Yang
      Mr. Yang
      2021-10-30
      php、composer  都试过,同样的问题。
      2021-10-30
      回复
  • 无朽
    无朽
    2021-10-28

    $inWechatpaySignature = '';// 请根据实际情况获取

    $inWechatpayTimestamp = '';// 请根据实际情况获取

    $inWechatpaySerial = '';// 请根据实际情况获取

    $inWechatpayNonce = '';// 请根据实际情况获取、


    这四个参数是啥,要填啥

    2021-10-28
    赞同
    回复 1
    • 北望沣渭
      北望沣渭
      2021-10-28
      这四个都在请求头上,不是填,是获取;你是服务端,微信是客户端,TA在请求你的server时,带了这四个头headers信息,你想办法拿到即可。
      2021-10-28
      回复
登录 后发表内容