评论

微信支付V3版本小程序支付 php签名,验签,数据解密代码分享

微信支付v3版 php解密解密代码

数据解密需要用到sodium扩展 大部分php版本需要安装

证书序列号可以在这里查看https://myssl.com/cert_decode.html

我用的php7.4版本

直接上代码:

 <?php


//微信原生支付
class Wxpay
{


    private static $appid = 'xxxxxxxxxxxxxx'; //appid
    private static $mchid = 'xxxxxxxxxxxxxx'; //商户号
    private static $mch_apiv3key = 'xxxxxxxxxxxxxx'; //商户平台apiv3密钥(商户平台手动生成)
    private static $serial_no = 'xxxxxxxxxxxxxx';//商户API证书序列号serial_no
    private static $wx_serial_no = 'xxxxxxxxxxxxxx';//平台证书序列号
    


    /*
     * 支付(小程序支付)
     * @param type $sn        订单编号
     * @param type $money  金额
     * @param type $openid  用户小程序openid
     * @return type
     */


    public static function getPayParam($sn, $money, $openid)
    {
        $url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi';
        $notify_url = url('/api/weixin/notify');


        $data = [];
        $data['appid'] = self::$appid;
        $data['mchid'] = self::$mchid; //商户号
        $data['description'] = 'xxx'; //描述?
        $data['out_trade_no'] = $sn; //商户系统内部订单号
        $data['time_expire'] = date('Y-m-d') . 'T' . date('H:i:s', (time() + 1800)) . '+08:00'; //订单失效时间2018-06-08T10:34:56+08:00
        $data['notify_url'] = $notify_url; //异步通知接口地址
        $data['amount'] = ['total' => $money * 100, 'currency' => 'CNY']; //金额
        $data['payer'] = ['openid' => $openid]; //用户


        $re = self::wxCurl($url, $data, 'POST');


        if (!isset($re['prepay_id'])) {
            exit('参数获取失败');
        }


        $result = [];
        $result['appId'] = self::$appid;
        $result['timeStamp'] = (string)time();
        $result['nonceStr'] = uniqid();
        $result['package'] = 'prepay_id=' . $re['prepay_id'];
        $result['signType'] = 'RSA';
        $result['paySign'] = self::getPaySign($result);


        return $result;
    }


    /**
     * 查询订单
     * @param type $sn
     */
    public static function select($sn, $return = false)
    {
        $mchid = self::$mchid; //商户号
        $url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/' . $sn . '?mchid=' . $mchid;
        $re = self::wxCurl($url, [], 'GET');
        if ($return) {
            return $re;
        }
        if (isset($re['trade_state']) && $re['trade_state'] == 'SUCCESS') {
            return true;
        }
        return false;
    }


    /**
     * 关闭订单
     * @param type $sn
     */
    public static function close($sn)
    {
        $mchid = self::$mchid; //商户号
        $url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/' . $sn . '/close';
        $re = self::wxCurl($url, ['mchid' => $mchid], 'POST');
        return true;
    }


    /**
     * 退款
     * @param type $sn
     */
    public static function refund($order_sn, $refund_sn, $total, $refund, $msg = '退款')
    {
        $url = 'https://api.mch.weixin.qq.com/v3/refund/domestic/refunds';
        $data = [];
        $data['notify_url'] = url('ag/weixin/notify_refund');
        $data['out_trade_no'] = $order_sn; //订单号
        $data['out_refund_no'] = $refund_sn; //退款单号
        $data['reason'] = $msg;
        $data['amount'] = ['refund' => $refund * 100, 'total' => $total * 100, 'currency' => 'CNY'];
        $re = self::wxCurl($url, $data, 'POST');
        return $re;
    }


    //请求
    public static function wxCurl($url, $data = [], $method = 'GET')
    {
        $Authorization = self::getReSign($url, $data, $method);
        $header = [
            'Content-Type: application/json',
            'Accept: application/json',
            'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.63',
            'Authorization: ' . $Authorization
        ];


        $redata = $data ? json_encode($data) : '';


        //CURL
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_FAILONERROR, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        //有post参数-设置   
        if ($method=='POST') {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $redata);
        }
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        $result = curl_exec($ch);
        curl_close($ch);


        return $result ? json_decode($result, true) : [];
    }


    //后端请求签名
    public static function getReSign($url, $data, $method = 'GET')
    {
        $url_parts = parse_url($url);
        $canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
        $http_method = $method;
        $timestamp = time();
        $nonce = uniqid();
        $body = $data ? json_encode($data) : '';
        $mchid = self::$mchid; //商户id
        $serial_no = self::$serial_no; //证书编号
        $private_key = self::getPrivateKey(BASE_PATH . 'cert/apiclient_key.pem'); //商户私钥


        $message = $http_method . "\n" .
            $canonical_url . "\n" .
            $timestamp . "\n" .
            $nonce . "\n" .
            $body . "\n";


        openssl_sign($message, $raw_sign, $private_key, 'sha256WithRSAEncryption');
        $sign = base64_encode($raw_sign);


        $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"', $mchid, $nonce, $timestamp, $serial_no, $sign);
        return 'WECHATPAY2-SHA256-RSA2048 ' . $token;
    }


    //前端小程序签名
    public static function getPaySign($result)
    {
        $private_key = self::getPrivateKey(BASE_PATH . 'cert/apiclient_key.pem'); //商户私钥
        $message = $result['appId'] . "\n" .
            $result['timeStamp'] . "\n" .
            $result['nonceStr'] . "\n" .
            $result['package'] . "\n";
        openssl_sign($message, $raw_sign, $private_key, 'sha256WithRSAEncryption');
        $sign = base64_encode($raw_sign);
        return $sign;
    }


    //验证签名
    public static function checkSign()
    {
        $header = $_SERVER;//请求头
        $serial_no = $header['wechatpay-serial'] ?? ''; //微信平台序列号
        $timeStamp = $header['wechatpay-timestamp'] ?? '';
        $nonce = $header['wechatpay-nonce'] ?? '';
        $body = file_get_contents('php://input');//获取原始请求体
        $wx_sign = $header['wechatpay-signature'] ?? '';
        $wx_serial_no = self::$wx_serial_no; //保存的平台序列号(从下载的平台证书里面获取)
        if (!$serial_no || $wx_serial_no != $serial_no) {//证书过期
            return false;
        }


        $message = $timeStamp . "\n" .
            $nonce . "\n" .
            $body . "\n";


        $wx_sign = base64_decode($wx_sign);
        $public_key = self::getPublicKey(BASE_PATH . 'cert/wx_public_cert.pem'); //平台公钥
        $res = openssl_verify($message, $wx_sign, $public_key, OPENSSL_ALGO_SHA256);
        if ($res == 1) {
            return true;
        }
        return false;
    }


    //获取私钥
    public static function getPrivateKey($filepath)
    {
        return openssl_get_privatekey(file_get_contents($filepath));
    }


    //获取公钥
    public static function getPublicKey($filepath)
    {
        return openssl_pkey_get_public(file_get_contents($filepath));
    }


    //加密数据(基本用不上)
    public static function getEncrypt($str)
    {
//$str是待加密字符串 
        $public_key_path = BASE_PATH . 'cert/wx_public_cert.pem'; //'平台证书路径';
        $public_key = file_get_contents($public_key_path);
        $encrypted = '';
        if (openssl_public_encrypt($str, $encrypted, $public_key, OPENSSL_PKCS1_OAEP_PADDING)) {
            //base64编码 
            $sign = base64_encode($encrypted);
        } else {
            throw new Exception('encrypt failed');
        }
        return $sign;
    }


    //解密数据(下载证书用的)
    public static function decryptToString($ciphertext, $associatedData, $nonceStr)
    {
        $aesKey =self::$mch_apiv3key; //商户apiv3密钥解密
        $str = base64_decode($ciphertext);
        if (strlen($str) <= 16) {
            return '';
        }
        // ext-sodium (default installed on >= PHP 7.2) 如果没有该函数需要安装sodium扩展
        return sodium_crypto_aead_aes256gcm_decrypt($str, $associatedData, $nonceStr, $aesKey);
    }


    //下载平台证书
    public static function downCert()
    {
        $url = 'https://api.mch.weixin.qq.com/v3/certificates';
        $re = self::wxCurl($url, [], 'GET');
        if (!isset($re['data'])) {
           exit('获取证书失败');
        }
        $ciphertext = $re['data'][0]['encrypt_certificate']['ciphertext'];
        $associatedData = $re['data'][0]['encrypt_certificate']['associated_data'];
        $nonceStr = $re['data'][0]['encrypt_certificate']['nonce'];
        $data = self::decryptToString($ciphertext, $associatedData, $nonceStr);
        if (!$data) {
            exit('获取证书解密失败');
        }
        //保存平台证书 (https://myssl.com/cert_decode.html)获取证书序列号
        file_put_contents(BASE_PATH . '/cert/wx_public_cert.pem', $data);
        return $data;
    }


}


最后一次编辑于  2021-10-11  
点赞 3
收藏
评论

4 个评论

  • Stars.
    Stars.
    2021-07-18
    大佬  下载证书这步我这为啥一直报错  解密这步  return \sodium_crypto_aead_aes256gcm_decrypt($str, $associatedData, $nonceStr, $aesKey);一直返回false
    
    2021-07-18
    赞同
    回复 4
    • 王某
      王某
      2021-07-27
      我也是 解密一直返回false  您这边解决了嘛?
      2021-07-27
      回复
    • Stars.
      Stars.
      2021-08-05回复王某
      没有解决  你这边解决了没
      2021-08-05
      回复
    • 🇨 🇭 🇦 🇸 🇪
      🇨 🇭 🇦 🇸 🇪
      2021-09-13
      我也是一直false ,只能查查订单勉强了
      2021-09-13
      回复
    • 机器翻译官
      机器翻译官
      2021-09-17
      解密 检查sodium_crypto_aead_aes256gcm_decrypt函数有没有,没有需要安装sodium扩展, 密钥是商户平台里面的apiv3密钥 不是api密钥,或者重置密钥试试
      2021-09-17
      回复
  • 🍗
    🍗
    2021-06-10
    verify为什么用的是OPENSSL_ALGO_SHA256而不是sha256WithRSAEncryption
    


    2021-06-10
    赞同
    回复
  • 小生蚝
    小生蚝
    2021-06-02

    谢谢大佬哇!解决了我好多问题hhh

    2021-06-02
    赞同
    回复
  • T.
    T.
    2021-05-12

    感谢大佬

    2021-05-12
    赞同
    回复
登录 后发表内容