微信支付(Native)

571 阅读1分钟

微信支付接入

  1. 准备公众号/小程序
  2. 开通微信支付,等待审核通过
  3. 微信支付-产品中心-AppID账号管理,关联公众号/小程序,关联成功后可获取到10位"商户号"(mchId)

1669710125910.jpg

  1. 微信支付-账户中心-API安全,设置APIV3密钥,密钥包括了32位的API V3秘钥(apiV3Key)
  2. 微信支付-账户中心-API安全,申请API证书(包括apiclient_cert.pem和apiclient_key.pem),申请完成后点击”管理证书“,获取证书序列号(mchSetialNo)
  3. 产品中心-我的产品,开通相关权限(Native支付)
  4. 下载微信支付平台证书
composer require wechatpay/wechatpay

php -f vendor\wechatpay\wechatpay\bin\CertificateDownloader.php -- -k ${apiV3Key} -m ${mchId} -f apiclient_key.pem  -s ${mchSetialNo} -o .

注1: 如果出现错误: SSL certificate problem: unable to get local issuer certificate 修改CertificateDownloader.php,增加 'verify' => false

private function job(array $opts): void
{
    ...
    $instance = Builder::factory([
        'mchid'      => $opts['mchid'],
        'serial'     => $opts['serialno'],
        'privateKey' => \file_get_contents((string)$opts['privatekey']),
        'certs'      => &$certs,
        'base_uri'   => (string)($opts['baseuri'] ?? self::DEFAULT_BASE_URI),
        'verify'     => false
    ]);
    ...

参考文档 pay.weixin.qq.com/wiki/doc/ap…

PHP接口

<?php

namespace app\base;

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

class WxPay
{
    public static function getClient()
    {
        // 设置参数
        $config = \Yii::$app->params['wxConfig'];

        // 商户号
        $merchantId = $config['merchant']['mchId'];

        // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
        $merchantPrivateKeyFilePath = "file://" . $config['merchant']['mchKeyFile'];
        $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);

        // 「商户API证书」的「证书序列号」
        $merchantCertificateSerial = $config['merchant']['mchSetialNo'];

        // 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名
        $platformCertificateFilePath = "file://" . $config['merchant']['wxKeyFile'];
        $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);

        // 从「微信支付平台证书」中获取「证书序列号」
        $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);

        // 构造一个 APIv3 客户端实例
        $instance = Builder::factory([
            'mchid' => $merchantId,
            'serial' => $merchantCertificateSerial,
            'privateKey' => $merchantPrivateKeyInstance,
            'certs' => [
                $platformCertificateSerial => $platformPublicKeyInstance,
            ],
            'verify'     => false
        ]);
        return $instance;
    }


    public static function getResponse()
    {
        $config = \Yii::$app->params['wxConfig'];

        $headers = \Yii::$app->request->getHeaders();
        $inWechatpaySignature = $headers['Wechatpay-Signature'];// 请根据实际情况获取
        $inWechatpayTimestamp = $headers['Wechatpay-Timestamp'];// 请根据实际情况获取
        $inWechatpaySerial = $headers['Wechatpay-Serial'];// 请根据实际情况获取
        $inWechatpayNonce = $headers['Wechatpay-Nonce'];// 请根据实际情况获取
        $inBody = \Yii::$app->request->getRawBody();// 请根据实际情况获取,例如: file_get_contents('php://input');

        $apiv3Key = $config['merchant']['apiV3Key'];;// 在商户平台上设置的APIv3密钥

        // 根据通知的平台证书序列号,查询本地平台证书文件,
        $platformCertificateFilePath = 'file://'.$config['merchant']['wxKeyFile'];
        $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, 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);// 打印解密后的结果

            return $inBodyResourceArray;
        }

        return null;
    }
}