废话简述

  前段时间搞了个jsapi  v3的支付功能,挺费时间的,细节很多,以下是个人的经验分享,包含代码过程。

开发前提

  根据我自身的开发需求,我选择了jsapi v3的支付方式,根据这个方式要做以下准备。

  1)要有一个认证好的服务号,有一个注册认证好的商户号。

  2)服务号要先申请微信支付功能     

       3)在微信支付平台开通jsapi支付功能

 

 

 

  4)在微信支付平台的产品中心  关联服务号

 

 

  5)在微信支付平台搞定如下东西,记得做好保存工作

 

代码过程

  1)前端代码,vue-cli。 getzhifu函数是传递数据给后台并获取到发起支付的数据。zhifu函数正式发起支付,请注意参数的大小写问题。本人的参考地址是     https://www.jianshu.com/p/f71648a8a2ad    //

  /**
  //记得安装weixin-jsapi,并导入
  //npm install weixin-jsapi --save-dev
  //import wx from 'weixin-jsapi'
  */
  // 后台获取支付需要的数据
    getzhifu:async function(){
        let a='';
        var openid=this.openid;
        var updata={
         "body": this.data.pname,
         "out_trade_no":this.ocode,
         "total_fee":this.total_fee,
         "notify_url":'回调地址',
         "openid":openid,
         "trade_type":'JSAPI'
        };
        console.log(updata);
        await axios.post('获取支付数据的后台接口',qs.stringify(updata)).then(function(res){
            a= res.data;
        }).catch(function (error) {
            a= error;
        });
        console.log('aaa');
        console.log(a);
        if(a.return_code == 'SUCCESS'){
            this.timeStamp=a.timeStamp, 
            this.nonceStr=a.nonceStr, // 支付签名随机串,不长于 32 位         
            this.package=a.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)     
            this.signType=a.signType, // 签名方式,默认为'SHA1',这里用'RSA'  
            this.paySign=a.paySign,
            this.$nextTick(()=>{
                this.zhifu();
            })  
        }
    },
    // 发起支付
    zhifu:function(){
        let that=this;
        wx.config({ 
            debug: true, // 这里一般在测试阶段先用ture,等打包给后台的时候就改回false, 
            appId: that.Appid, // 必填,公众号的唯一标识 
            timestamp: that.timeStamp, // 必填,生成签名的时间戳 ,注意是小写的s    
            nonceStr: that.nonceStr, // 必填,生成签名的随机串 
            signature: that.paySign, // 必填,签名 
            jsApiList: ['chooseWXPay'] // 必填,需要使用的JS接口列表,如果你的业务需求和我一样,照填即可
        })
        wx.ready(() => {  
            wx.checkJsApi({     
            jsApiList: ['chooseWXPay'],     
            success:function(res){ 
                console.log("seccess") 
                console.log('hskdjskjk', res) 
            }, 
            fail:function(res){      
                console.log("fail");       
                console.log(res) }   
            })   
            wx.chooseWXPay({  
                nonceStr: that.nonceStr, // 支付签名随机串,不长于 32 位         
                package: that.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)     
                signType: that.signType, // v3支付一般用 'RSA'
                paySign: that.paySign, // 支付签名
                timestamp: that.timeStamp, //  注意参数是小写的s   
                success: function (res) {  // 支付成功后的回调函数       
                     alert(res)   
                },     
                fail: function (res) {       
                     alert("支付失败");       
                     alert(res.errMsg);  
                }   
            }) 
        }) 
        wx.error(err => { 
            alert(err) 
        })
    },

  

  2)后端代码,tp5。本人的参考地址是:https://blog.csdn.net/qq_43278317/article/details/119249920

  我的做法是封装支付代码在extent目录下(目前只封装了jsapi v3的,尴尬),在业务控制器中调取。

  业务控制器的方法

// 获取前端数据,生成支付数据返回前端。
    public function getzhifu(){
        Hook::listen('my_cors');//我这边需要这个做跨域的,不需要可注释
        $request=new request();
        $body=$request->post('body');
        $out_trade_no=$request->post('out_trade_no');
        $total_fee=$request->post('total_fee');
        $notify_url=$request->post('notify_url');
        $openid=$request->post('openid');
        $spbill_create_ip=$_SERVER["REMOTE_ADDR"];
        $trade_type=$request->post('trade_type');
        $total_fee = '0.01';   
        $wxpay  = new Wxpay();//这个要先在控制器的头部引入先,//use Wxpay;
        $date = [
            'out_trade_no'  => $out_trade_no,
            'total_fee'     => $total_fee,
            'body'          => $body,
            'spbill_create_ip' => $spbill_create_ip,
            'notify_url' => $notify_url,
            'trade_type' => $trade_type,
            'openid'=>$openid,
        ];
        $res =$wxpay->start($date);
     if($res){exit(json_encode($res,JSON_UNESCAPED_UNICODE))} 
  }

 

  extent目录下的代码

<?php
class Wxpay{
    /**
     * 微信公共配置方法
     */
    /*备注:apiclient_cert.p12是商户证书文件,除PHP外的开发均使用此证书文件*/
     
     
    #微信基础信息
    // $wxMerchantId = "商户号";                                               //商户号
    // $wxMerchantApiCertificate = __DIR__ . "/cert/apiclient_cert.pem";       //商户证书
    // $wxMerchantApiPrivateKey = __DIR__ . "/cert/apiclient_key.pem";         //商户私钥
    // $wxApiV3Key = "微信API v3密钥";                                         //微信API v3密钥
    // $wxApiSerialNo = "商户API证书序列号";                                   //商户API证书序列号
    
//这里的参数命名过长了,可以优化一下,我就不优化了 private $config =[ 'wxMerchantId'=>"", //商户号 'wxMerchantApiCertificate' =>"", //商户证书 'wxMerchantApiPrivateKey' =>"", //商户私钥 'wxApiV3Key' => "", //微信API v3密钥,32位 'wxApiSerialNo' => "", //商户API证书序列号 'appid'=>'' //服务号appid ]; //这里的参数也可以发到上面$config去,我就不放了 private $common=[ //'url'=>"https://api.mch.weixin.qq.com/v3/certificates", 'xdurl'=>'https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi',//统一下单地址 'params'=>[], ];
//这里是记录生成签名后的几个参数,用来返回给接口的,返回不止这几个,还有prepay_id参数值package,签名方式signtype和支付签名paySign

    private $backdata=[
        'timestamp'=>'',
        'nonce_str'=>'',
        'signType'=>'RSA',
        ];

    /**
     *     #############微信构造签名流程#############
     * 生成签名
     * @param string $method 请求方法 POST 或者GET
     * @param string $url 请求URL
     * @param string $request 请求中的请求报文主体
     * @param $wxMerchantId商户号
     * @param $certKey商户私钥
     * @param $wxApiSerialNo商户API证书序列号
     * @return string
     */
    public function start($fdata){
         $url=$this->common['xdurl'];
         $wxMerchantId=$this->config['wxMerchantId'];//商户号
         $wxMerchantApiPrivateKey=$this->config['wxMerchantApiPrivateKey'];//商户私钥
         $wxApiSerialNo=$this->config['wxApiSerialNo'];//商户API证书序列号

         $hdata = array();
         $time = time();
         $hdata['appid'] = $this->config['appid'];
         $hdata['mchid'] = $wxMerchantId;
         $hdata['description'] = $fdata['body'];//商品描述
         $hdata['out_trade_no'] = $fdata['out_trade_no'];//支付订单编号
         $hdata['notify_url'] = $fdata['notify_url'];//回调接口
         $hdata['amount']['total'] = intval($fdata['total_fee']*100);
         $hdata['payer']['openid'] = $fdata['openid'];//用户付款的openID
         $hdata = json_encode($hdata); //转json串
        
         $authorization=$this->RequestSign("POST",$url,$hdata,$wxMerchantId,$wxMerchantApiPrivateKey,$wxApiSerialNo);
        $xdurl=$this->common['xdurl'];
        $token=$authorization;
        $header  = array(
            'Content-Type:'.'application/json; charset=UTF-8',
            'Accept:application/json',
            'User-Agent:*/*',
            'Authorization: WECHATPAY2-SHA256-RSA2048 '.$token
        );
        $ret =$this->curl_post_https($xdurl,$hdata,$header);
        // var_dump($ret);exit;
        $ret = json_decode($ret, true);
        // var_dump($ret);exit;
        if (isset($ret['prepay_id'])) { //拉起预支付成功
            $res['return_code']='FAIL';
            $res['timeStamp'] =  $this->backdata['timestamp'];//时间戳
            $res['nonceStr'] = $this->backdata['nonceStr'];//随机字符串
            $res['signType'] = $this->backdata['signType'];//签名算法,暂支持 RSA
            $res['package'] = 'prepay_id=' . $ret['prepay_id'];//统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=*
            $res['paySign'] = $this->getWechartSign($this->config['appid'],$res['timeStamp'],$res['nonceStr'],$res['package'],$wxMerchantApiPrivateKey);//签名
            // var_dump($res);exit;
            if($res['paySign']){$res['return_code']='SUCCESS';}
            return $res; //返回给前端需要的串
        }
        return $ret;

    }
    
    public function RequestSign($method = "POST", $url = "", $request = array(), $wxMerchantId, $certKey, $wxApiSerialNo)
    {
        #截取获取当前请求地址【去除域名】
        $url_parts = parse_url($url);
        $path = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
        #获取当前时间戳
        $timeStamp = time();
        $this->backdata['timestamp']=$timeStamp;
        #生成一个随机字符串
        $nonceStr = $this->getNonceStr();
        $this->backdata['nonceStr']=$nonceStr;
        #构造签名串
        // $requestSign = sprintf("%s\n%s\n%s\n%s\n%s\n",$method,$path,$timeStamp,$nonceStr,$request);
        $requestSign = "POST"."\n".$path."\n".$timeStamp."\n".$nonceStr."\n".$request."\n";
        // echo $requestSign;
        #计算计算签名值
        $sign = $this->calculateSignatureValue($requestSign, $certKey);
        // echo $sign;exit;
        #设置HTTP头获取Authorization
        $token = $this->createToken($wxMerchantId, $nonceStr, $timeStamp, $wxApiSerialNo, $sign);
        // echo $token;exit;
        #返回
        return $token;
    }
     
    /**
     * 计算签名值
     * @param $requestSign
     * @param $certKey
     * @return string
     * 使用商户私钥对待签名串进行SHA256 with RSA签名,并对签名结果进行Base64编码得到签名值
     */
    public function calculateSignatureValue($requestSign, $certKey)
    {
        // $raw_sign="";
        $certKey = file_get_contents($certKey);
        // echo $certKey;exit;
        openssl_sign($requestSign, $raw_sign, $certKey, 'sha256WithRSAEncryption');
        // openssl_sign($requestSign, $raw_sign, $certKey, 'sha256');
        $sign = base64_encode($raw_sign);
        return $sign;
    }
     
    /**
     * 获取token
     * @param $merchant_id
     * @param $nonce
     * @param $timestamp
     * @param $serial_no
     * @param $sign
     * @return string
     */
    public function createToken($merchant_id, $nonce, $timestamp, $serial_no, $sign)
    {
        $schema = 'WECHATPAY2-SHA256-RSA2048';
        $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
            $merchant_id, $nonce, $timestamp, $serial_no, $sign);
        return $token;
    }
     
    /**
     * 产生随机字符串,不长于32位
     * @param int $length
     * @return string
     */
    public function getNonceStr($length = 32)
    {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }
     
     
    /**
     * post请求
     * @param $url
     * @param $authorization
     * @return mixed
     */
    public function curlPostWithWx($url, $authorization)
    {
        $paramsString = json_encode($this->common['params']);
        // 初始化curl
        $ch = curl_init();
        // 设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        // post数据
        curl_setopt($ch, CURLOPT_POST, 1);
        // post的变量
        curl_setopt($ch, CURLOPT_POSTFIELDS, $paramsString);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                'Content-Type: application/json; charset=utf-8',
                'Content-Length: ' . strlen($paramsString),
                'Authorization: ' . "WECHATPAY2-SHA256-RSA2048 " . $authorization,
                'Accept: application/json',
                'User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'
            )
        );
        // 运行curl,结果以jason形式返回
        $res = curl_exec($ch);
        curl_close($ch);
        // 取出数据
        $data = json_decode($res, true);
        return $data;
    }
     
    /**
     * get请求
     * @param $url
     * @param $authorization
     * @return mixed
     */
    public function curlGetWithWx($url, $authorization)
    {
        // $paramsString = json_encode($this->common['params']);
        // 初始化curl
        $ch = curl_init();
        // 设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, 60);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
                'Content-Type: application/json; charset=utf-8',
                // 'Content-Length: ' . strlen($paramsString),
                'Authorization: ' . "WECHATPAY2-SHA256-RSA2048 " . $authorization,
                'Accept: application/json',
                'User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'
            )
        );
        // 运行curl,结果以jason形式返回
        $res = curl_exec($ch);
        curl_close($ch);
        // 取出数据
        $data = json_decode($res, true);
        //返回
        return $data;
    }
    public function curl_post_https($url,$data,$header){ // 模拟提交数据函数
        $curl = curl_init(); // 启动一个CURL会话
        curl_setopt($curl, CURLOPT_URL, $url); // 要访问的地址
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); // 对认证证书来源的检查
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2); // 从证书中检查SSL加密算法是否存在
        curl_setopt($curl, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); // 模拟用户使用的浏览器
        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转
        curl_setopt($curl, CURLOPT_AUTOREFERER, 1); // 自动设置Referer
        curl_setopt($curl, CURLOPT_POST, 1); // 发送一个常规的Post请求
        curl_setopt($curl, CURLOPT_POSTFIELDS, $data); // Post提交的数据包
        curl_setopt($curl, CURLOPT_TIMEOUT, 30); // 设置超时限制防止死循环
        curl_setopt($curl, CURLOPT_HEADER, 0); // 显示返回的Header区域内容
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); // 获取的信息以文件流的形式返回
     
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
        $tmpInfo = curl_exec($curl); // 执行操作
        if (curl_errno($curl)) {
            echo 'Errno'.curl_error($curl);//捕抓异常
        }
        curl_close($curl); // 关闭CURL会话
        return $tmpInfo; // 返回数据,json格式
    }
    
        /*
     * 统一下单调起支付的签名
     * */
    public function getWechartSign($appid, $timeStamp, $noncestr, $prepay_id, $sy)
    {
        $str = $appid . "\n" . $timeStamp . "\n" . $noncestr . "\n" . $prepay_id . "\n";
        $str2 = $this->calculateSignatureValue($str, $sy);
        return $str2;
    }
}

  

完成!成功在前端调起支付。