开源项目

微信支付PHP类封装

2018-06-20  本文已影响119人  echo1028

一:类说明

Tool.php: 工具类,封装了xml转数组 、数组转xml、curl post请求 、curl get请求、获取用户openid等方法;

WxPayApi.php: 微信支付API列表类,例如统一下单接口等;

WxPayConfig.php: 微信配置类;

WxPayException: 异常处理类;

WxPayDataBase: 数据基类;

WxPayNotify:支付异步通知应答类;

二:代码如下

1.工具类:Tool.php

  <?php 
  namespace wexin\pay;
  class Tool
  {
        /**
     * 生成随机字符串,不长于32位
     *
     * @param int $length
     * @return string
     */
    public static 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;
    }/*}}}*/

    /**
     * array 转为 xml
     *
     * @param array $data | object
     * @return string
     * @throws WxPayException
     */
    public static function arrayToXml($data = [])
    {/*{{{*/
        if (!is_array($data) || count($data) == 0) {
            throw new WxPayException('数组数据异常!');
        }
        $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;
    }/*}}}*/

    /**
     * xml 转为 array
     *
     * @param string $xml
     * @return mixed
     * @throws WxPayException
     */
    public static function xmlToArray($xml = '')
    {/*{{{*/
        if (empty($xml))
            throw new WxPayException('xml数据异常!');
        // 将XML转为array
        // 禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        return $data;
    }/*}}}*/

    /**
     * 格式化参数为URL参数
     *
     * @param array $data
     * @return string
     */
    public static function toUrlParams($data = [])
    {/*{{{*/
        $buff = '';
        foreach ($data as $key => $val) {
            if ($key != 'sign' && $val != '' && !is_array($val)) {
                $buff .= $key . '=' . $val . '&';
            }
        }
        $buff = trim($buff, '&');
        return $buff;
    }/*}}}*/

    /**
     * 生成签名
     *
     * @param array $data
     * @return string
     */
    public static function makeSign($data = [])
    {/*{{{*/
        // 签名步骤一:按字典排序参数
        ksort($data);
        $string = self::toUrlParams($data);
        // 签名步骤二:在string后加入key
        $string = $string . '&key=' . WxPayConfig::KEY;
        // 签名步骤三:MD5加密
        $string = md5($string);
        // 签名步骤四:所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }/*}}}*/

    /**
     * 获取客户端IP地址
     *
     * @param int $type 返回类型 0 返回IP地址 1 返回IPV4地址数字
     * @param bool $adv 是否进行高级模式获取(有可能被伪装)
     * @return mixed
     */
    public static function getClientIP($type = 0, $adv = false)
    {/*{{{*/
        $type      = $type ? 1 : 0;
        static $ip = null;
        if (null !== $ip) {
            return $ip[$type];
        }

        if ($adv) {
            if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
                $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
                $pos = array_search('unknown', $arr);
                if (false !== $pos) {
                    unset($arr[$pos]);
                }
                $ip = trim(current($arr));
            } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
                $ip = $_SERVER['HTTP_CLIENT_IP'];
            } elseif (isset($_SERVER['REMOTE_ADDR'])) {
                $ip = $_SERVER['REMOTE_ADDR'];
            }
        } elseif (isset($_SERVER['REMOTE_ADDR'])) {
            $ip = $_SERVER['REMOTE_ADDR'];
        }
        // IP地址合法验证
        $long = sprintf("%u", ip2long($ip));
        $ip   = $long ? [$ip, $long] : ['0.0.0.0', 0];
        return $ip[$type];
    }/*}}}*/

    /**
     * 获取毫秒级时间戳
     *
     * @return array|string
     */
    public static function getMillisecond()
    {/*{{{*/
        $time  = explode (" ", microtime ());
        $time  = $time[1] . ($time[0] * 1000);
        $time2 = explode( ".", $time );
        $time  = $time2[0];
        return $time;
    }/*}}}*/
    
    /**
     * post方式提交数据到对应的接口
     *
     * @param string $data    需要post的xml数据
     * @param $url string    url
     * @param bool $useCert 是否需要证书,默认不需要
     * @param int $timeout   请求执行超时时间,默认30s
     * @return mixed
     * @throws WxPayException
     */
    public static function postCurl($data, $url, $useCert = false, $timeout = 30)
    {/*{{{*/
        $ch = curl_init();
        // 设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);

        // 如果有配置代理这里就设置代理
        if (WxPayConfig::CURL_PROXY_HOST != "0.0.0.0"
            && WxPayConfig::CURL_PROXY_PORT != 0) {
            curl_setopt($ch, CURLOPT_PROXY, WxPayConfig::CURL_PROXY_HOST);
            curl_setopt($ch, CURLOPT_PROXYPORT, WxPayConfig::CURL_PROXY_PORT);
        }
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); // 严格校验
        // 设置header
        curl_setopt($ch, CURLOPT_HEADER, false);
        // 要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        if ($useCert == true) {
            // 设置证书
            // 使用证书:cert 与 key 分别属于两个.pem文件
            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
            curl_setopt($ch, CURLOPT_SSLCERT, WxPayConfig::SSLCERT_PATH);
            curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
            curl_setopt($ch, CURLOPT_SSLKEY, WxPayConfig::SSLKEY_PATH);
        }
        // post方式提交数据
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        // 运行curl
        $result = curl_exec($ch);
        // 返回结果
        if ($result) {
            curl_close($ch);
            return $result;
        } else {
            $error = curl_errno($ch);
            curl_close($ch);
            throw new WxPayException('curl出错,错误码: ' . $error);
        }
    }/*}}}*/

    /**
     * curl GET提交
     *
     * @param $url
     * @param int $timeout
     * @return mixed
     */
    public static function getCurl($url, $timeout = 5)
    {/*{{{*/
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); // 设置超时
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        $result = curl_exec($ch);
        curl_close($ch);
        return $result;
 
    }/*}}}*/

    /**
     * 获取用户openid
     */
    public static function getOpenid()
    {
        // 通过code获取openid
        if (!isset($_GET['code'])) {
            // 触发微信返回code码
            $url     = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] . $_SERVER['QUERY_STRING'];
            $baseUrl = urlencode($url);
            $redirectUrl = self::createOauthUrlForCode($baseUrl);
            header("Location: $redirectUrl");
            exit();
        } else {
            // 获取code,通过code获取openid
            $code = $_GET['code'];
            // 构造获取openid的链接
            $url  = self::createOauthUrlForOpenid($code);
            // 发送get请求
            $data = self::getCurl($url);
            $data = json_decode($data, true);
            return $data['openid'];

        }
    }

    /**
     * 构造获取code的url连接
     *
     * @param string $redirectUrl 微信服务器回跳的url,需要url编码
     * @return string
     */
    public static function createOauthUrlForCode($redirectUrl)
    {
        $params['appid']         = WxPayConfig::APPID;
        $params['redirect_uri']  = $redirectUrl;
        $params['response_type'] = 'code';
        $params['scope']         = 'snsapi_base';
        $params['state']         = 'STATE#wechat_redirect';
        $queryString = self::toUrlParams($params);
        return 'https://open.weixin.qq.com/connect/oauth2/authorize?' . $queryString;
    }

    /**
     * 构造通过code获取openid的url链接
     *
     * @param $code
     * @return string
     */
    public static function createOauthUrlForOpenid($code)
    {
        $params['appid']      = WxPayConfig::APPID;
        $params['secret']     = WxPayConfig::APPSECRET;
        $params['code']       = $code;
        $params['grant_type'] = 'authorization_code';
        $queryString = self::toUrlParams($params);
        return 'https://api.weixin.qq.com/sns/oauth2/access_token?' . $queryString;
    }
  }

2.微信支付API列表类:WxPayApi.php

<?php
namespace wexin\pay;
class WxPayApi
{
     /**
     * 统一下单请求地址
     */
    const UNIFORM_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/unifiedorder';

    /**
     * 统一下单接口
     * 
     * @param $inputObj
     * @param int $timeOut
     * @return mixed
     * @throws WxPayException
     */
    public static function unifiedOrder($inputObj, $timeOut = 6)
    {
        // 检测必填参数
        if (!isset($inputObj->appid)) {
            throw new WxPayException('缺少统一下单接口必填参数公众号ID:appid!');
        } elseif (!isset($inputObj->mch_id)) {
            throw new WxPayException('缺少统一下单接口必填参数商户号:mch_id!');
        } elseif (!isset($inputObj->sub_mch_id)) {
            throw new WxPayException('缺少统一下单接口必填参数子商户号:sub_mch_id!');
        } elseif (!isset($inputObj->sign)) {
            throw new WxPayException('缺少统一下单接口必填参数签名:sign!');
        } elseif (!isset($inputObj->body)) {
            throw new WxPayException('缺少统一下单接口必填参数商品描述:body!');
        } elseif (!isset($inputObj->out_trade_no)) {
            throw new WxPayException('缺少统一下单接口必填参数商户订单号:out_trade_no!');
        } elseif (!isset($inputObj->total_fee)) {
            throw new WxPayException('缺少统一下单接口必填参数总金额:total_fee!');
        } elseif (!isset($inputObj->notify_url)) {
            throw new WxPayException('缺少统一下单接口必填参数通知地址:notify_url!');
        } elseif (!isset($inputObj->trade_type)) {
            throw new WxPayException('缺少统一下单接口必填参数交易类型:trade_type!');
        } elseif (!isset($inputObj->nonce_str)) {
            throw new WxPayException('缺少统一下单接口必填参数随机字符串:nonce_str!');
        } elseif (!isset($inputObj->spbill_create_ip)) {
            throw new WxPayException('缺少统一下单接口必填参数终端IP:spbill_create_ip!');
        }
        // 关联参数
        if ($inputObj->trade_type == 'JSAPI' &&  !isset($inputObj->openid)) {
            throw new WxPayException("统一下单接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!");
        }
        if($inputObj->trade_type == 'NATIVE' && !isset($inputObj->product_id)) {
            throw new WxPayException("统一下单接口中,缺少必填参数product_id!trade_type为NATIVE时,product_id为必填参数!");
        }
        
        // 格式化xml数据
        $xml = Tool::arrayToXml($inputObj->getValues());
        // 请求开始时间
//        $startTimeStamp = Tool::getMillisecond();
        // 发送请求
        $response = Tool::postCurl($xml, self::UNIFORM_ORDER_URL, false, $timeOut);
        $result   = Tool::xmlToArray($response);
        return $result;
    } 
}

3.配置类:WxPayConfig.php

<?php
namespace weixin\pay;
class WxPayConfig
{
      /**
     * 微信公众号信息配置
     *
     * APPID: 绑定支付的APPID,必须配置
     * MCHID: 商户号 必须配置
     * KEY: 商户支付密钥,必须配置
     * APPSECRET: 公众号secert (仅JSAPI支付的时候需要配置, 登录公众平台,进入开发者中心可设置) 
     * @var string 
     */
    const APPID = '';
    const MCHID = '';
    const KEY   = '';
    const APPSECRET = '';
    //=======【证书路径设置】==================================
    /**
     * 证书路径,绝对路径(仅退款、撤销订单时需要)
     */
    const SSLCERT_PATH = '../cert/apiclient_cert.pem';
    const SSLKEY_PATH  = '../cert/apiclient_key.pem';

    //=======【curl代理设置】===================================
    const CURL_PROXY_HOST = "0.0.0.0";
    const CURL_PROXY_PORT = 0;

    //=======【上报信息配置】===================================
    const REPORT_LEVENL = 1;

    //=======【支付异步通知URL】===================================
    const NOTIFY_URL = '';
}

4.异常类:WxPayException.php

<?php
namespace weixin\pay;
class WxPayException extends \Exception
{
     /**
     * @return string
     */
    public function errorMessage()
    {
        return $this->getMessage();
    }
}

5.数据基类:WxPayDataBase.php

<?php
namespace weixin\pay;
class WxPayDataBase
{
     /**
     * @var array
     */
    protected $values = [];

    /**
     * @param $name
     * @param $value
     */
    public function __set($name, $value)
    {
        $this->values[$name] = $value;
    }

    /**
     * __get 
     * 
     * @param mixed $name 
     * @access public
     * @return void
     */
    public function __get($name)
    {
        return $this->values[$name];
    }

    /**
     * __isset 
     * 
     * @param mixed $name 
     * @access public
     * @return void
     */
    public function __isset($name)
    {
        return array_key_exists($name, $this->values); 
    }

    /**
     * setSign 设置签名
     * 
     * @return void
     */
    public function setSign()
    {
        $sign = Tool::makeSign($this->values);
        $this->values['sign'] = $sign; 
    }

    /**
     * getValues 获取设置的值
     * 
     * @return array
     */
    public function getValues()
    {
        return $this->values;
    }
}

6.支付异步通知应答类: WxPayNotify.php

<?php
namespace weixin\pay;
/**
 * 微信支付异步通知应答
 *
 * Class WxPayNotify
 * @package weixin\pay
 */
class WxPayNotify extends WxPayDataBase
{

    /**
     * @param $xml
     */
    public function replayNotify($xml)
    {
        echo $xml;
    }
}

使用如下:

// 统一下单
<?php
 $WxPayData               = new WxPayDataBase();                          
 $WxPayData->appid        = WxPayConfig::APPID;                           
 $WxPayData->mch_id       = WxPayConfig::MCHID;                           
 $WxPayData->sub_mch_id   = '1508144001';                                 
 $WxPayData->body         = '商品';                                   
 $WxPayData->out_trade_no = Tool::getNonceStr();                          
 // 金额:分                                                              
 $WxPayData->total_fee    = 1;                                            
 $WxPayData->notify_url   = 'https://www.qq.com/Wx/WxPay/notify';  
 $WxPayData->trade_type   = 'JSAPI';                                      
 $WxPayData->openid       = '你的openid';               
 // 随机字符串                                                            
 $WxPayData->nonce_str        = Tool::getNonceStr();                      
 // 终端ip                                                                
 $WxPayData->spbill_create_ip = Tool::getClientIP();                      
 $WxPayData->setSign();                                                   
 try {                                                                    
     $result = WxPayApi::unifiedOrder($WxPayData);                                                                       
 } catch (WxPayException $e) {                                            
     die($e->getMessage());                                               
 }        

// 支付异步通知处理函数
function notify()
{
        $xml   = file_get_contents('php://input');   
        $array = Tool::xmlToArray($xml);
        // 记录日志
        file_put_contents('/Data/logs/app/store/notify.log', json_encode($array) . PHP_EOL, FILE_APPEND);
        // 商户应答
        $notify = new WxPayNotify();
        if ($array['result_code'] == 'SUCCESS' && $array['return_code'] == 'SUCCESS') {
            $result_sign = $array['sign'];
            unset($array['sign']);
            $sign = Tool::makeSign($array); 
            // 验证签名 
            if ($sign == $result_sign) {
                $notify->return_code = 'SUCCESS';
                $notify->return_msg  = 'OK';
                $xml = Tool::arrayToXml($notify->getValues());
                $notify->replayNotify();
            } else {
                $notify->return_code = 'FAIL';
                $notify->return_msg  = '签名错误';
                $xml = Tool::arrayToXml($notify->getValues());
                $notify->replayNotify();
            }
            
        } else {
                $notify->return_code = 'FAIL';
                $notify->return_msg  = '支付失败';
                $xml = Tool::arrayToXml($notify->getValues());
                $notify->replayNotify();
        }
}                                                                

jsapi发起支付

微信内H5调起支付

<!DOCTYPE html>
<html lang="en">
<head>
    <title>微信支付样例-支付</title>
    <script>
    function jsApiCall()
    {
        WeixinJSBridge.invoke(
            'getBrandWCPayRequest',
            {
                "appId": "",
                "timeStamp": "",
                "nonceStr": "",
                "package": "",
                "signType": "",
                "paySign": "",
            },
            function(res) {
                WeixinJSBridge.log(res.err_msg);
                alert(res.err_code+res.err_desc+res.err_msg);
            }
        );
    }   
    function callpay()
    {
    
        if (typeof WeixinJSBridge == "undefined"){
            if ( document.addEventListener ) {
                document.addEventListener('WeixinJSBridgeReady', jsApiCall, false);
            } else if (document.attachEvent) {
                document.attachEvent('WeixinJSBridgeReady', jsApiCall); 
                document.attachEvent('onWeixinJSBridgeReady', jsApiCall);
            }
        } else {
            jsApiCall();
        }
    }
    </script>
</head>
<body>
    <br/>
    <font color="#9ACD32"><b>该笔订单支付金额为<span style="color:#f00;font-size:50px">1分</span>钱</b></font><br/><br/>
    <div align="center">
        <button style="width:210px; height:50px; border-radius: 15px;background-color:#FE6714; border:0px #FE6714 solid; cursor: pointer;  color:white;  font-size:16px;" type="button" onclick="callpay()" >立即支付</button>
    </div>
</body>
</html>
上一篇下一篇

猜你喜欢

热点阅读