在线文档教程

thinkphp微信H5支付整合

/app/index/model/Wxh5.php
<?php
namespace app\index\model;

use think\Db;
use think\Validate;
use think\Loader;
use think\Model;
class Wxh5 extends model
{
    public function __construct() {
        $this->config = $this->wxConfig();
    }
    /**
     * 获取秘钥配置
     * @return [type] 数组
     */
    public function wxConfig() {
        $config = [
            "appid"  => set('appid',3),  // 开放平台或商户平台APPID
            "mch_id" => set('mch_id',3),  // 商户平台 商户号
            "key"    => set('key',3),   // 商户平台 秘钥KEY
            "TOKEN"  => "",  // 此参数非必传 有的前端在jsapi 支付时会要求返回signature 参数  此参数即为此准备
        ];
        return $config;
    }
    public function index($param,$t="")
    {
        if ($t) {
            $order = [
                'out_trade_no'    => $param['out_trade_no'],
                'out_refund_no' => $param['out_refund_no'],
                'total_fee'        => intval($param['total_fee']*100),
                'refund_fee'    => intval($param['total_fee']*100)
            ];
            // 退款
            $unified_order=$this->curl_post_ssl($order);
        }else{
            $order = [
                'out_trade_no'  => $param['out_trade_no'],// 订单号
                'total_fee'     => intval($param['total_fee']*100),// 订单金额  以(分)为单位
                'body'          => $param['body'],// 商品描述
                'notify_url'    => $param['notify_url'], //回调地址
                'spbill_create_ip' => $param['spbill_create_ip'], //对应IP
                'trade_type'    => $param['trade_type']  //对应支付类型
            ];
            #统一下单 获取prepay_id
            $unified_order=$this->unifiedOrder($order);
        }
        #获取当前时间戳
        $data =  $unified_order;
        return $data;
    }
    /**
     * 统一下单
     * @param  array $order 订单 必须包含支付所需要的参数 body(产品描述)、total_fee(订单金额)、out_trade_no(订单号)、product_id(产品id)、trade_type(类型:JSAPI,NATIVE,APP)
     */
    public function unifiedOrder($order)
    {
        $config =[
            'appid'     => $this->config['appid'], //appid
            'mch_id'    => $this->config['mch_id'], //商户号ID
            'nonce_str' => $this->getNonceStr(),
        ];

        # 合并配置数据和订单数据
        $data=array_merge($order,$config);

        # 生成签名
        $sign=$this->makeSign($data);
        $data['sign']=$sign;
        #转换成xml
        $xml=$this->toXml($data);
        $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';  //接收xml数据的文件
        $header[] = "Content-type: text/xml";      //定义content-type为xml,注意是数组
        $ch = curl_init ($url);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 兼容本地没有指定curl.cainfo路径的错误
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        $response = curl_exec($ch);
        if(curl_errno($ch)){
            # 显示报错信息;终止继续执行
            die(curl_error($ch));
        }
        curl_close($ch);
        #转换成数组
        $result=$this->toArray($response);

        #显示错误信息
        if ($result['return_code']=='FAIL') 
        {
            die($result['return_msg']);
        }
        // var_dump($result);
        $result['sign']=$sign;
        $result['nonce_str']=$this->getNonceStr();
        return $result;
    }
    /**
     * 微信支付退款发起请求
     */
    public function curl_post_ssl($order, $second=30,$aHeader=array()){
        $config =[
            'appid'     => $this->config['appid'], //appid
            'mch_id'    => $this->config['mch_id'], //商户号ID
            'nonce_str' => $this->getNonceStr(),
        ];
        # 合并配置数据和订单数据
        $data=array_merge($order,$config);

        # 生成签名
        $sign=$this->makeSign($data);
        $data['sign']=$sign;
        #转换成xml
        $xml=$this->toXml($data);
        // return $data;
        $url = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
        $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_SSLCERT, 'cert' . DIRECTORY_SEPARATOR . 'apiclient_cert.pem');
        curl_setopt($ch, CURLOPT_SSLKEY, 'cert' . DIRECTORY_SEPARATOR . 'apiclient_key.pem');
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        $data = curl_exec($ch);
        $result=$this->toArray($data);
        //返回结果
        if ($result) {
            curl_close($ch);
            return $result;
        } else {
            return false;
        }
    }
    /**
     * 生成签名
     * @return 签名,本函数不覆盖sign成员变量,如要设置签名需要调用SetSign方法赋值
     */
    public function makeSign($data)
    {
        # 去空
        $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=".$this->config['key'];
        #签名步骤三:MD5加密
        $sign = md5($string_sign_temp);
        # 签名步骤四:所有字符转为大写
        $result=strtoupper($sign);
        return $result;
    }
    /**
     * 将一个数组转换为 XML 结构的字符串
     * @param array $arr 要转换的数组
     * @param int $level 节点层级, 1 为 Root.
     * @return string XML 结构的字符串
     */
    public function array2xml($arr, $level = 1) {
        $s = $level == 1 ? "<xml>" : '';
        foreach($arr as $tagname => $value) {
            if (is_numeric($tagname)) {
                $tagname = $value['TagName'];
                unset($value['TagName']);
            }
            if(!is_array($value)) {
                $s .= "<{$tagname}>".(!is_numeric($value) ? '<![CDATA[' : '').$value.(!is_numeric($value) ? ']]>' : '')."</{$tagname}>";
            } else {
                $s .= "<{$tagname}>" . $this->array2xml($value, $level + 1)."</{$tagname}>";
            }
        }
        $s = preg_replace("/([\x01-\x08\x0b-\x0c\x0e-\x1f])+/", ' ', $s);
        return $level == 1 ? $s."</xml>" : $s;
    }
    /**
     * 将xml转为array
     * @param  string $xml xml字符串
     * @return array       转换得到的数组
     */
    public function toArray($xml){   
        #禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $result= json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);        
        return $result;
    }

    /**
     * 
     * 产生随机字符串,不长于32位
     * @param int $length
     * @return 产生的随机字符串
     */
    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;
    }

    /**
     * 输出xml字符
     * @throws WxPayException
    **/
    public function toXml($data)
    {
        $xml = "<xml>";
        foreach ($data as $key=>$val){
            if (is_numeric($val)){
                $xml.="<".$key.">".$val."</".$key.">";
            }else{
                $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
            }
        }
        $xml.="</xml>";
        return $xml; 
    }

    /**
     * 验证
     * @return array 返回数组格式的notify数据
     */
    public function notify()
    {
        // 获取xml
        $xml=file_get_contents('php://input', 'r'); 
        # 转成php数组
        $data=$this->toArray($xml);
        # 保存原sign
        $data_sign=$data['sign'];
        # sign不参与签名
        unset($data['sign']);
        $sign=$this->makeSign($data);
        # 判断签名是否正确  判断支付状态
        if ($sign===$data_sign && $data['return_code']=='SUCCESS' && $data['result_code']=='SUCCESS') 
        {
            $result=$data;
        }else{
            $result=false;
        }

        # 返回状态给微信服务器
        if ($result) 
        {
            $str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
        }else{
            $str='<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名失败]]></return_msg></xml>';
        }
        return $result;
    }
}
?>

下单

use app\index\model\Wxh5;
$title = '充值';
        $total_fee = 0.01;

            $out_trade_no=date('YmdHis').rand(1000,9999);

        $notify_url = http().$_SERVER['HTTP_HOST'].url('index/chongzhi/notifywx'); //回调
        $wxpay  = new Wxh5();
        $trade_type = 'MWEB';
        $date = [
            'out_trade_no'  => $out_trade_no,
            'total_fee'     => $total_fee,
            'body'          => $title,
            'description'   => $title,
            'spbill_create_ip' => $_SERVER["REMOTE_ADDR"],
            'notify_url' => $notify_url,
            'trade_type' => $trade_type,
            
        ];
        
        $data['title'] = $title;
        $data['id'] = $out_trade_no;
        $data['zhifu'] = '微信';
        $data['tid'] = 0;
        $data['model'] = '';
        $data['trade_no'] = 0;
        $data['status'] = 0;
        $data['jiage'] = $total_fee;
        $data['add_time'] = time();
        $data['errorcode'] = 0;
        $data['uid'] = input('uid');
        $data['ip'] = $_SERVER["REMOTE_ADDR"];
        Db::name('dingdan')->insert($data);
        $res = $wxpay->index($date);
        $url = $res['mweb_url'];
        echo '<meta http-equiv="refresh" content="0;url='.$url.'">';