支付封装合集(微信)1【更新至2020.11.17】

最近几日对于h5的支付做了调试,发现调用其实非常简单,这里提示几点在调试过程中出现的错误

1 config中的 key 是微信商户的key ,填写的时候需要注意。如果填写的是微信公众号的key,在其他传递的参数都是正确的情况下,爆出的是签名错误。

2 前端调用如果是h5页面,使用 window.location.href = 返回的链接 直接做链接跳转 ,后可加 redirect_url 参数,指定在支付成功后的跳转页面。但是加参数的跳转链接 需要使用 encodeURI 做处理,如图

 3 如果是前后端分离,前端使用的是vue的话  建议前端在支付部分直接做 完全性的 h5页面处理  进行链接跳转,请勿在页面中使用任何有关vue的东西,这是比较简单的针对vue 微信h5支付 做处理的方式

  至于vue直接处理微信h5支付 前端尝试未通过,有兴趣的可以联合前端查看处理下,也欢迎反馈问题,这里会非常感谢,并标明提供的相关信息。

以上就是微信h5支付在处理过程中需要注意的几点,而相对于支付宝的h5,虽然同微信h5都是直接根据返回的链接做跳转 但是不需要做什么麻烦的处理 相对来说还是比较方便的

之前在csdn 上,为了方便修改更新现在移动到这里,那边的会直接删除

从工作至今四年 终于将自己封装的写个差不多了 其中有借鉴各种支付方式 可有雷同 但一些内容为自己整理封装后的效果 希望多多提议

目前测试通过无误的有 app wx 其他间隔时间过长 不太确定 会在之后不断调整 力求完美

<?php
namespace pay;
/**
 * 微信支付
 */
class  Wxpay
{
    // 需了解一个应用对应一个appid 需要多个共存时就需要多个设置
    private $config = [
        'app_appid'=>'*********',
        'wx_appid'=>'******',
    
        'mobile_appid'=>'*****',
        'mch_id'=>'*********',
        'key'=>'******',
    ];
    // 设置证书 退款时需要
    private $cert=[
        'apiclient_cert'=>'',
        'apiclient_key'=>'',
    ];
    private $appid='';
    private $calltype = 'app';//调用端口 app wx mobile  pc wx_small
    private $unifiedorder = 'https://api.mch.weixin.qq.com/pay/unifiedorder';//微信支付接口
    private $refund = 'https://api.mch.weixin.qq.com/secapi/pay/refund';//微信退款提交接口
    private $refundquery = 'https://api.mch.weixin.qq.com/pay/refundquery';//微信退款查询接口
    public function __construct($payTypeInfo=[]){
        if($payTypeInfo)
        {
            foreach ($payTypeInfo as $k => $val) {
                if($k=='calltype')
                {
                    $this->calltype=$val['val']!=''?$val['val']:$this->calltype;
                }else{
                    $this->config[$k]=$val['val'];
                }
            } 
        }
        $appname=$this->calltype.'_appid';
        $this->appid=$this->config[$appname];
        // 线上 linux
        // $this->cert['apiclient_cert']=EXTEND_PATH.'pay/wxcert/apiclient_cert.pem';
        // $this->cert['apiclient_key']=EXTEND_PATH.'pay/wxcert/apiclient_key.pem';
        
        // 本地
       // $this->cert['apiclient_cert']=EXTEND_PATH.'paywxcertapiclient_cert.pem';
       // $this->cert['apiclient_key']=EXTEND_PATH.'paywxcertapiclient_key.pem';
    }
    public function index($data)
    {
        // 端口检测
        $res=$this->calltypeCheck($data);
        if($res['code']==0){ return $res; }
        // 初次提交进行数据判断
        // FLog($data);
        $res=$this->wxresult($this->postdata($data),$this->config['key']);
        FLog($res);
        if($res['return_code']=='FAIL')
        {
            return ['code'=>0,'msg'=>$res['return_msg']];
        }
        if($res['result_code']=='FAIL')
        {
            return ['code'=>0,'msg'=>$res['err_code'].' '.$res['err_code_des']];
        }
        $calltype=$this->calltype;
        return $this->$calltype($data,$res);
    }
    // 微信公众号
    public function wx($data,$res)
    {
        $newjsdata=[
            'appId'=>$this->appid,
            'nonceStr'=>'"'.rand(1000,9999).'"',
            'package'=>"prepay_id=".$res['prepay_id'],//
            'signType'=>"MD5",
            'timeStamp'=>'"'.time().'"',
            'paySign'=>'',
        ];
        $newjsdata=getsign($newjsdata,$this->config['key'],'paySign','key','1');
        return ['code'=>1,'msg'=>'请支付','data'=>['str'=>$newjsdata]];
    }
    // 微信小程序
    public function wx_small($data,$res)
    {
        $nonceStr=(string)rand(1000,9999);
        $timeStamp=(string)time();
        $newjsdata=[
            'appId'=>$this->appid,
            'nonceStr'=>$nonceStr,
            'package'=>"prepay_id=".$res['prepay_id'],//
            'signType'=>"MD5",
            'timeStamp'=>$timeStamp,
            'paySign'=>'',
        ];

        $newjsdata=getsign($newjsdata,$this->config['key'],'paySign','key',1);

        $returndata['time']=$timeStamp;
        $returndata['order']=$newjsdata;

        return ['code'=>1,'msg'=>'请支付','data'=>$returndata];
    }
    // app支付
    public function app($data,$res)
    {  
        $newdata=[
            'appid'=>$res['appid'],
            'partnerid'=>$res['mch_id'],
            'prepayid'=>$res['prepay_id'],
            'package'=>"Sign=WXPay",//
            'noncestr'=>rand(1000,9999),
            'timestamp'=>time(),
        ];
        $newdata=getsign($newdata,$this->config['key'],'sign','key','1');
        // 外壳app的操作
        // $buff = "";
        // foreach ($newdata as $k => $v) {
        //     if($v != "" && !is_array($v)){
        //         $buff .= $k . "=" . $v . "&";
        //     }
        // }
        // $buff = trim($buff, "&");
        // $url='http://www.ruidao888.com/home/order/index.html?type=wxpay&'.$buff;
        return ['code'=>1,'msg'=>'请支付','data'=>['type'=>'app','str'=>$newdata]];
    }

    public function mobile($data,$res)
    {
        //$web_link='http://pnceshi.zzyuyou.com/web/index/app_download';
        //$url=$res['mweb_url'].'&redirect_url='.urlencode($web_link);
    //以上两行为测试支付成功后指定返回的页面,一般写在前端比较好,这里只做事例查看
        $url=$res['mweb_url'];
        return ['code'=>1,'msg'=>'请支付','data'=>['type'=>'app','url'=>$url]];
    }
    
    public function postdata($data)
    {
        $trade_type=$this->calltypeD('trade_type');
        $postdata=[
            'appid'=>$this->appid,
            'mch_id'=>$this->config['mch_id'],
            'nonce_str'=>rand(10000,99999),
            'body'=>$data['subject'],
            'attach'=>$data['attach'],//对什么表进行操作
            'out_trade_no'=>$data['pay_sn'],
            'total_fee'=>$data['total']*100,
            'spbill_create_ip'=>request()->ip(),
            'notify_url'=>$data['notify_url'],
            'trade_type'=>$trade_type,//
        ];
        
        $postdata=$this->ReArr_postdata($postdata,$data);
        // dump($postdata);
        // die;
        return $postdata;
    }
    // 重组数据
    public function ReArr_postdata($postdata,$data)
    {
        if($this->calltype=='wx' || $this->calltype=='wx_small')
        {
            /*微信公众号小程序必须*/
            $postdata['openid']=$data['openid'];
        }else if($this->calltype=='app')
        {
            // 正常字段就行
        }else if($this->calltype=='mobile')
        {
            $ccD=cc('web_link,web_name');
            $scene_info=[
                'type'=>'Wap',//场景类型
                'wap_url'=>$ccD['web_link'],//WAP网站URL地址
                'wap_name'=>$ccD['web_name'],
            ];
        //好像之前跟现在的不太一样 不过对号入座,改成这个也是没有问题的
            $h5_info=[
                'h5_info'=>$scene_info
            ];
            $postdata['scene_info']=json_encode($h5_info);
        }else if($this->calltype=='jsapi')
        {
            $ccD=cc('web_link,web_name');
            $scene_info=[
                'type'=>'Wap',//场景类型
                'wap_url'=>$ccD['web_link'],//WAP网站URL地址
                'wap_name'=>$ccD['web_name'],
            ];
            $postdata['scene_info']=json_encode($scene_info);
        }
        $postdata['body']=trim($postdata['body']);
        $postdata['total_fee']=(int)$postdata['total_fee'];
        return $postdata;
    }
    // 根据类型获取想要的信息
    public function calltypeD($field)
    {
        /**
         * JSAPI--JSAPI支付(或小程序支付) 测试通过
         * NATIVE--Native支付
         * APP--app支付   测试通过
         * MWEB--H5支付
         * MICROPAY--付款码支付
         */
        $D=[
            'wx'=>['trade_type'=>'JSAPI'],
            'wx_small'=>['trade_type'=>'JSAPI'],//小程序
            'app'=>['trade_type'=>'APP'],
            'mobile'=>['trade_type'=>'MWEB'],
        ];
        return isset($D[$this->calltype])?$D[$this->calltype][$field]:'';
    }
    // 端口检测
    public function calltypeCheck($data)
    {
        // 对配置信息进行检测
        if($this->appid=='') { return ['code'=>0,'msg'=>'请先配置 appid']; }
        if($this->config['mch_id']=='') { return ['code'=>0,'msg'=>'请先配置 mch_id']; }
        if($this->config['key']=='') { return ['code'=>0,'msg'=>'请先配置 key']; }
        
        if($this->calltype=='wx' && (!isset($data['openid']) || $data['openid']==''))
        {
            return ['code'=>0,'msg'=>'缺少微信openid,请先获取'];
        }
        return ['code'=>1,'msg'=>'检测通过'];
    }
    // 退款申请并查询
    public function refund_and_query($data)
    {
        $res=$this->refund($data);
        if($res['code']==0)
        {
            return $res;
        }
        $res2=$this->refundquery($res['refund_data']);
        return $res2;
    }
    // 退款申请
    public function refund($data)
    {
        $out_refund_no = date('YmdHis').rand(1000, 9999);//订单号每次随机
        $postdata=[
            'appid'=>$this->appid,
            'mch_id'=>$this->config['mch_id'],
            'nonce_str'=>rand(10000,99999),
            'out_trade_no'=>$data['out_trade_no'],
            'out_refund_no'=>$out_refund_no,
            'total_fee'=>$data['total_fee']*100,
            'refund_fee'=>$data['refund_fee']*100,
        ];
        $res=$this->wxresult_refund($postdata,$this->config['key'],$this->cert);
        // echo var_dump($res);
        // die;
        if($res['return_code']=='FAIL')
        {
            return ['code'=>0,'msg'=>$res['return_msg']];
        }
        if($res['result_code']=='FAIL')
        {
            return ['code'=>0,'msg'=>$res['err_code'].' '.$res['err_code_des']];
        }
        return ['code'=>1,'msg'=>'申请成功','refund_data'=>$res];
    }
    // 退款查询
    public function refundquery($data)
    {
        // 查询是否退款成功
        $s_postdata=[
            'appid'=>$this->appid,
            'mch_id'=>$this->config['mch_id'],
            'nonce_str'=>rand(10000,99999),
            'sign_type'=>'MD5',
            'refund_id'=>$data['refund_id'],//退款单号
        ];
        $res2=$this->wxresult_refund($s_postdata,$this->config['key'],$this->cert,$this->refundquery);
        if($res2['return_code']=='FAIL')
        {
            return ['code'=>0,'msg'=>$res2['return_msg']];
        }
        if($res2['result_code']=='FAIL')
        {
            return ['code'=>0,'msg'=>$res2['err_code'].' '.$res2['err_code_des']];
        }
        return ['code'=>1,'msg'=>'修改成功','refund_data'=>$res2];
    }
    private function wxresult_refund($data,$key,$cert,$url='') {
        $url=$url==''?$this->refund:$url;
        return postXmlSSLCurl(arrayToXml(getsign($data,$key)),$cert,$url); 
    }
    private function wxresult($data,$key) {
        return $this->wxCur(arrayToXml(getsign($data,$key))); 
    }
    private function wxCur($data,$url="") {
        $url=$url==''?$this->unifiedorder:$url;
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL,$url);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        $response=curl_exec($ch);
        if($response)
        {
           curl_close($ch); 
           return json_decode(json_encode(simplexml_load_string($response, 'simplexmlelement', LIBXML_NOCDATA)),true);
        }else{
            $error = curl_errno($ch);
            // echo "curl出错,错误码:$error"."<br>"; 
            // echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
            curl_close($ch);
            return ['return_code'=>'FAIL','return_msg'=>"curl出错,错误码:$error"];
        }
    }
}



function postXmlSSLCurl($xml,$cert,$url,$second=30)
{
    // echo '2222222222222';
    // dump($xml);
    // die;
    
    $ch = curl_init();
    //超时时间
    curl_setopt($ch,CURLOPT_TIMEOUT,$second);
    //这里设置代理,如果有的话
    //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
    //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
    curl_setopt($ch,CURLOPT_URL, $url);
    curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
    curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE);
    //设置header
    curl_setopt($ch,CURLOPT_HEADER,FALSE);
    //要求结果为字符串且输出到屏幕上
    curl_setopt($ch,CURLOPT_RETURNTRANSFER,TRUE);
    //设置证书
    //使用证书:cert 与 key 分别属于两个.pem文件
    //默认格式为PEM,可以注释
    curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
    curl_setopt($ch,CURLOPT_SSLCERT, $cert['apiclient_cert']);
    //默认格式为PEM,可以注释
    curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
    curl_setopt($ch,CURLOPT_SSLKEY, $cert['apiclient_key']);
    //post提交方式
    curl_setopt($ch,CURLOPT_POST, true);
    curl_setopt($ch,CURLOPT_POSTFIELDS,$xml);
    $response=curl_exec($ch);
    if($response)
    {
       curl_close($ch); 
       return json_decode(json_encode(simplexml_load_string($response, 'simplexmlelement', LIBXML_NOCDATA)),true);
    }else{
        $error = curl_errno($ch);
        // echo "curl出错,错误码:$error"."<br>"; 
        // echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
        curl_close($ch);
        return ['return_code'=>'FAIL','return_msg'=>"curl出错,错误码:$error"];
    }
}
function getsign($data,$key,$newkey='sign',$linkstr='key',$ksorts=1) {
    if($ksorts==1){
        ksort($data);
    }
    $buff = "";
    foreach ($data as $k => $v) {
        if($k != $newkey && $v != "" && !is_array($v)){
            $buff .= $k . "=" . $v . "&";
        }
    }
    $buff = trim($buff, "&");
    $buff=$buff."&".$linkstr."=".$key;
    $sign=strtoupper(MD5($buff));
    $data[$newkey]=$sign;
    return $data;
}

// 签名验证   对比
// function makeSign($data)
// {
//     $config_key='z4YoADhVnknugpG8BN2S4bvJZIV9s7QC';
//     // 去空
//     $data=array_filter($data);
//     //签名步骤一:按字典序排序参数
//     ksort($data);
//     //将数组转成url形式
//     $string_a=http_build_query($data);
//     $string_a=urldecode($string_a);
//     //签名步骤二:在string后加入KEY
//     $string_sign_temp=$string_a."&key=".$config_key;
//     //签名步骤三:MD5加密
//     $sign = md5($string_sign_temp);
//     // 签名步骤四:所有字符转为大写
//     $result=strtoupper($sign);
//     return $result;
// }


function arrayToXml($arr) {
    // dump($arr);
    // die;
    $xml = "<xml>";
    foreach ($arr as $key=>$val)
    {
        $xml.="<".$key.">".$val."</".$key.">";
    }
    $xml.="</xml>";
    return $xml;
}     
原文地址:https://www.cnblogs.com/exo5/p/13992669.html