微信支付V3版本的 签名生成,验签,解密,统一下单的简单封装

微信支付 V3版本的 签名生成,验签,解密,统一下单的简单封装

暂时看了文档只完成了这部分,感觉封装的不是特别完美,希望有大佬指点一下,想着封装好一个SDK直接在其他项目中使用,不太会

V3Base

namespace app\lib\pay\weixinV3\lib;


use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use WechatPay\GuzzleMiddleware\Util\PemUtil;
use WechatPay\GuzzleMiddleware\WechatPayMiddleware;

class V3Base
{
    protected $client = null;
    protected $weChatPayConfig = [];

    public function __construct()
    {
        $this->weChatPayConfig = config("pay.weixin");
        //商户相关配置,
        $merchantId = $this->weChatPayConfig['merchantId']; // 商户号
        $merchantSerialNumber = $this->weChatPayConfig['merchantSerialNumber']; // 商户API证书序列号
        $merchantPrivateKey = PemUtil::loadPrivateKey($this->weChatPayConfig['merchantPrivateKey']); // 商户私钥文件路径
        // 微信支付平台配置
        $wechatpayCertificate = PemUtil::loadCertificate($this->weChatPayConfig['platformCert']); // 微信支付平台证书文件路径
        // 构造一个WechatPayMiddleware
        $wechatpayMiddleware = WechatPayMiddleware::builder()
            ->withMerchant($merchantId, $merchantSerialNumber, $merchantPrivateKey) // 传入商户相关配置
            ->withWechatPay([$wechatpayCertificate]) // 可传入多个微信支付平台证书,参数类型为array
            ->build();

        // 将WechatPayMiddleware添加到Guzzle的HandlerStack中
        $stack = HandlerStack::create();
        $stack->push($wechatpayMiddleware, 'wechatpay');

        // 创建Guzzle HTTP Client时,将HandlerStack传入,接下来,正常使用Guzzle发起API请求,WechatPayMiddleware会自动地处理签名和验签
        $client = new Client(['handler' => $stack, 'verify' => false]);
        $this->client = $client;
    }

    /**
     * Desc:签名生成 @serial_no 商户API证书serial_no,用于声明所使用的证书,第一次需要执行官方jar包获取
     * User: XiaoYu
     * Date: 2021/3/31
     * Time: 8:52
     * @param $url 请求的接口
     * @param $http_method 请求方式
     * @param $timestamp 时间戳
     * @param $nonce 随机串
     * @param string $body 报文主体
     * @return array
     */
    public function signGeneration($url, $http_method, $timestamp, $nonce, $body = "")
    {
        $url_parts = parse_url($url);
        $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
        $message = $http_method . "\n" .
            $canonical_url . "\n" .
            $timestamp . "\n" .
            $nonce . "\n" .
            $body . "\n";

        openssl_sign($message, $raw_sign, PemUtil::loadPrivateKey($this->weChatPayConfig['merchantPrivateKey']), 'sha256WithRSAEncryption');
        $sign = base64_encode($raw_sign);
        $schema = 'WECHATPAY2-SHA256-RSA2048';
        $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
            $this->weChatPayConfig['merchantId'], $nonce, $timestamp, $this->weChatPayConfig['serial_no'], $sign);
        $header = [
            "Content-Type" => "application/json",
            "Accept" => "application/json",
            "User-Agent" => "*/*",
            "Authorization" => $schema . ' ' . $token
        ];
        return $header;
    }
}

获取平台证书

namespace app\lib\pay\weixinV3\lib;


use app\lib\Str;

/**
 * 平台证书
 * @package app\lib\pay\weixinV3\lib
 */
class Certificates extends V3Base
{
    /**
     * Desc: 获取平台证书  定时每12小时执行-》替换证书号以及serial_no
     * User: XiaoYu
     * Date: 2021/3/31
     * Time: 9:58
     * @return string
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    public function getCertificates()
    {
        $url = "https://api.mch.weixin.qq.com/v3/certificates";
        $method = "GET";
        $timestamp = time();
        $nonce = Str::createNoncestr();
        $resp = $this->client->request(
            $method,
            $url,
            [
                'headers' => $this->signGeneration($url, $method, $timestamp, $nonce)
            ]
        );
        $cert = json_decode($resp->getBody()->getContents(), true);
        $encrypt_certificate = $cert['data'][0]['encrypt_certificate'];
        $AesUtil = new AesUtil($this->weChatPayConfig['apiV3']);
        $weChatPayPem = $AesUtil->decryptToString($encrypt_certificate['associated_data'], $encrypt_certificate['nonce'], $encrypt_certificate['ciphertext']);
        return $weChatPayPem;
    }
}

回调报文解密

namespace app\lib\pay\weixinV3\lib;

use InvalidArgumentException;

/**
 * 回调报文解密
 * @package app\lib\pay\weixinV3\lib
 */
class AesUtil
{
    /**
     * AES key
     *
     * @var string
     */
    private $aesKey;

    const KEY_LENGTH_BYTE = 32;
    const AUTH_TAG_LENGTH_BYTE = 16;

    /**
     * Constructor
     */
    public function __construct($aesKey)
    {
        if (strlen($aesKey) != self::KEY_LENGTH_BYTE) {
            throw new InvalidArgumentException('无效的ApiV3Key,长度应为32个字节');
        }
        $this->aesKey = $aesKey;
    }

    /**
     * Decrypt AEAD_AES_256_GCM ciphertext
     *
     * @param string $associatedData AES GCM additional authentication data
     * @param string $nonceStr AES GCM nonce
     * @param string $ciphertext AES GCM cipher text
     *
     * @return string|bool      Decrypted string on success or FALSE on failure
     */
    public function decryptToString($associatedData, $nonceStr, $ciphertext)
    {
        $ciphertext = \base64_decode($ciphertext);
        if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) {
            return false;
        }

        // ext-sodium (default installed on >= PHP 7.2)
        if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') &&
            \sodium_crypto_aead_aes256gcm_is_available()) {
            return \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->aesKey);
        }

        // ext-libsodium (need install libsodium-php 1.x via pecl)
        if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') &&
            \Sodium\crypto_aead_aes256gcm_is_available()) {
            return \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->aesKey);
        }

        // openssl (PHP >= 7.1 support AEAD)
        if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) {
            $ctext = substr($ciphertext, 0, -self::AUTH_TAG_LENGTH_BYTE);
            $authTag = substr($ciphertext, -self::AUTH_TAG_LENGTH_BYTE);

            return \openssl_decrypt($ctext, 'aes-256-gcm', $this->aesKey, \OPENSSL_RAW_DATA, $nonceStr,
                $authTag, $associatedData);
        }

        throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
    }
}

统一下单


namespace app\lib\pay\weixinV3\lib;

use app\lib\Str;
use GuzzleHttp\Exception\RequestException;
use think\Exception;

/**
 * 统一下单接口-》按照官方文档自行组装数据
 * Class UnifiedOrder
 * @package app\lib\pay\weixinV3\lib
 */
class UnifiedOrder extends V3Base
{
    public function UnifiedOrder($data)
    {
        switch ($data['trade_type']) {
            case "JSAPI":
                $url = "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
                break;
            case "APP":
                $url = "https://api.mch.weixin.qq.com/v3/pay/transactions/app";
                break;
            case "NATIVE":
                $url = "https://api.mch.weixin.qq.com/v3/pay/transactions/native";
                break;
            case "H5":
                $url = "https://api.mch.weixin.qq.com/v3/pay/transactions/app";
                break;
            default:
                throw new Exception("该模式暂时未接入");
        }
        unset($data['trade_type']);
        $data['mchid'] = $this->weChatPayConfig['merchantId'];
        $data['notify_url'] = $this->weChatPayConfig['notify'];
        $data['appid'] = $this->weChatPayConfig['appid'];
        $body = json_encode($data);
        try {
            $resp = $this->client->request(
                'POST',
                $url, //请求URL
                [
                    // JSON请求体
                    'json' => $data,
                    'headers' => $this->signGeneration($url, "POST", time(), Str::createNoncestr(), $body)
                ]
            );
            $statusCode = $resp->getStatusCode();
            if ($statusCode == 200) { //处理成功
                return ["code_url" => $resp->getBody()->getContents()];
            } else if ($statusCode == 204) { //处理成功,无返回Body
                return ["result" => "success"];
            }
        } catch (RequestException $e) {
            throw $e;
        }
    }
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值