微信支付-微信JSAPI支付

时间:2022-09-24 20:10:21

关于微信,支付宝支付都做过了,但是很少有时间去写个博客,笔记啥的。话不多少,直接上代码吧!

此版本基于tp5(thinkphp5)的一个简易的微信支付类,目前可以正常支付哦,退款没有做,哈哈~~ 如下:

Pay.php(/baby/extend/wx/Pay.php)

<?php

namespace wx;
class Pay
{
    /**
     * pay config
     * @var array
     */
    private $_payCfg = array(
        'appId' => 'your appId',
        'appSecret' => 'your appSecret',
        'mchId' => 'your mchId',#商户号
        'mchSecret' => 'your mchSecret',#商户号secret
    );

    /**
     * redirect_url
     * @var
     */
    private $_redirectUrl;

    public function __construct($redirectUrl = NULL)
    {
        if (!empty($redirectUrl)) {
            $this->_redirectUrl = $redirectUrl;
        }
    }

    /**
     * 通过redirectUri获取授权信息
     * @return mixed
     */
    public function getAuthInfo()
    {
        //通过appId获取code
        $redirectUri = urlencode($this->_redirectUrl);
        $codeUrl = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=' . $this->_payCfg['appId'] . '&redirect_uri=' . $redirectUri . '&response_type=code&scope=snsapi_base&state=YQJ#wechat_redirect';

        if (empty($_REQUEST['code'])) {
            header('Location:' . $codeUrl);
            exit;
        }

        //通过code换取网页授权信息
        $curl = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid=' . $this->_payCfg['appId'] . '&secret=' . $this->_payCfg['appSecret'] . '&code=' . $_REQUEST['code'] . '&grant_type=authorization_code';
        $res = $this->_curlGetReq($curl);
        $authInfo = '';
        if ($res['tag']) {
            if (is_object($res['msg'])) {
                $formatRes = (array)$res['msg'];
                $authInfo = array(
                    'accessToken' => !empty($formatRes['access_token']) ? $formatRes['access_token'] : '',
                    'expiresIn' => !empty($formatRes['expires_in']) ? $formatRes['expires_in'] : '',
                    'refreshToken' => !empty($formatRes['refresh_token']) ? $formatRes['refresh_token'] : '',
                    'openId' => !empty($formatRes['openid']) ? $formatRes['openid'] : '',
                    'scope' => !empty($formatRes['snsapi_base']) ? $formatRes['snsapi_base'] : '',
                );
            }
        }
        return $authInfo;
    }

    /**
     * 下单支付
     * @param $param
     * @return array|string
     */
    public function toPay($param)
    {
        $body = empty($param['body']) ? 'xoxoxo' : $param['body'];
        $orderSn = empty($param['orderSn']) ? $this->generateOrderNum() : $param['orderSn'];
        $totalFee = empty($param['fee']) ? 0.01 : $param['fee'];
        $openId = empty($param['openId']) ? '' : $param['openId'];

        //统一下单参数构造
        $unifiedOrder = array(
            'appid' => $this->_payCfg['appId'],
            'mch_id' => $this->_payCfg['mchId'],
            'nonce_str' => $this->getNonceStr(),
            'body' => $body,
            'out_trade_no' => $orderSn,
            'total_fee' => $totalFee*100,
            'spbill_create_ip' => $this->getClientIp(),
            'notify_url' => 'https://www.baidu.com',//todo 你的支付回调url
            'trade_type' => 'JSAPI',
            'openid' => $openId
        );
        $unifiedOrder['sign'] = $this->makeSign($unifiedOrder);

        //请求数据,统一下单
        $xmlData = $this->toXml($unifiedOrder);
        $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';
        $res = $this->postXmlCurl($url, $xmlData);
        if (!$res) {
            return array('status' => 0, 'msg' => "Can't connect the server");
        }
        $content = $this->toArr($res);

        if (!empty($content) && is_array($content)) {
            if (!empty($content['result_code'])) {
                if ($content['result_code'] == 'FAIL') {
                    return array('status' => 0, 'msg' => $content['err_code'] . ':' . $content['err_code_des']);
                }
            }
            if (!empty($content['return_code'])) {
                if ($content['return_code'] == 'FAIL') {
                    return array('status' => 0, 'msg' => $content['return_msg']);
                }
            }

            $time = time();
            settype($time, "string");
            $resData = array(
                'appId' => strval($content['appid']),
                'nonceStr' => strval($content['nonce_str']),
                'package' => 'prepay_id=' . strval($content['prepay_id']),
                'signType' => 'MD5',
                'timeStamp' => $time
            );
            $resData['paySign'] = $this->makeSign($resData);
        }
        return json_encode($resData);
    }

    /**
     * 产生随机字符串,不长于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;
    }

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

    /**
     * 格式化参数格式化成url参数
     * @param $unifiedOrder
     * @return string
     * @internal param $unifiedOrder
     */
    public function toUrlParams($unifiedOrder)
    {
        $buff = "";
        foreach ($unifiedOrder as $k => $v) {
            if ($k != "sign" && $v != "" && !is_array($v)) {
                $buff .= $k . "=" . $v . "&";
            }
        }
        $buff = trim($buff, "&");
        return $buff;
    }

    /**
     * 输出xml字符
     * @param $unifiedOrder
     * @return string
     */
    public function toXml($unifiedOrder)
    {
        if (!is_array($unifiedOrder) || count($unifiedOrder) <= 0) exit('数组异常');

        $xml = "<xml>";
        foreach ($unifiedOrder as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }

    /**
     * 将xml转为array
     * @param $xml
     * @return mixed
     */
    public function toArr($xml)
    {
        //将XML转为array,禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
    }

    /**
     * curl get request
     * @param $url
     * @return array
     */
    public function _curlGetReq($url)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_TIMEOUT, 10);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        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);

        $res = curl_exec($ch);
        $error = curl_error($ch);
        curl_close($ch);

        if ($res === false) {
            return array('tag' => false, 'msg' => $error);
        }
        return array(
            'tag' => true,
            'msg' => json_decode($res)
        );
    }

    /**
     * 以post方式提交xml到对应的接口url
     * @param $xml
     * @param $url
     * @return mixed
     */
    public function postXmlCurl($url, $xml)
    {
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, 10);
        //如果有配置代理这里就设置代理
        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);

        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行curl
        $data = curl_exec($ch);
        //返回结果
        if ($data) {
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            curl_close($ch);
            exit("curl出错,错误码:$error");
        }
    }

    /**
     * 获取ip
     * @return bool
     */
    public function getClientIp()
    {
        $ip = false;
        if (!empty($_SERVER["HTTP_CLIENT_IP"]) && '127.0.0.1' != $_SERVER["HTTP_CLIENT_IP"]) {
            $ip = $_SERVER["HTTP_CLIENT_IP"];
        }
        if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
            $ips = explode(", ", $_SERVER['HTTP_X_FORWARDED_FOR']);
            if ($ip) {
                array_unshift($ips, $ip);
                $ip = FALSE;
            }
            for ($i = 0; $i < count($ips); $i++) {
                //echo $ips[$i].'<br>';
                if (!preg_match('/^(10|172\.16|192\.168)\./', $ips[$i])) {
                    $ip = $ips[$i];
                    break;
                }
            }
        }
        //@author jzzmly 2015-8-11 验证从HTTP变量里拿到的ip信息,防止伪造非法字符攻击
        return (empty($ip) || preg_match('/[^0-9^\.]+/', $ip)) ? $_SERVER['REMOTE_ADDR'] : $ip;
        //return ($ip ? $ip : $_SERVER['REMOTE_ADDR']);
    }

    /**
     * 生成16位订单号
     * @return string
     */
    public function generateOrderNum()
    {
        return $order_number = date('Ymd') . substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
    }
}
简单的支付类已经OK了,现在差调用层了,代码如下:

Test.php(/baby/application/index/controller/Test.php)

<?php
namespace app\index\controller;
use think\Controller;
use think\Request;
class Test extends Controller
{

    public function index(){
        $redirectUri = 'https://www.baidu.com/test.html';
        $payObj = new \wx\Pay($redirectUri);

        $authInfo = $payObj->getAuthInfo();
        if(!empty($authInfo['openId'])){
            $payInfo = array(
                'body'=>'xoxoxoxo',
                'fee'=>0.01,
                'openId'=>$authInfo['openId'],
            );

            $payRes = $payObj->toPay($payInfo);

            $this->assign('payRes',$payRes);

        }
        return $this->fetch('index/test');
    }
    //异步通知没有做,后续补充~
    public function notify(){
        exit('回调');
    }
}
调用层的代码也已经OK了,最后就差模板了,如下:

test.html(/baby/application/index/view/index/test.html)

<html>
<head>
	<meta http-equiv="content-type" content="text/html;charset=utf-8"/>
	<meta name="viewport" content="width=device-width, initial-scale=1"/>
	<title>微信JSAPI支付</title>
	<script type="text/javascript">
        function jsApiCall()
        {
            WeixinJSBridge.invoke(
                'getBrandWCPayRequest',{$payRes},
                function(res){
                    if(res.err_msg == "get_brand_wcpay_request:ok"){
                        alert("支付成功!");
                        window.location.href="http://www.baidu.com";
                    }else if(res.err_msg == "get_brand_wcpay_request:cancel"){
                        alert("用户取消支付!");
                    }else{
                        alert("支付失败!");
                    }
                }
            );
        }

        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/>
<font color="#9ACD32"><b><span style="color:#f00;font-size:50px;margin-left:40%;">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>


 关键在微信公众号平台要配置好,说实在的,初次去配置,确实费劲,通读下微信官方文档吧!有问题@me.