php-tp5-fastadmin开发H5页面,以微信公众号授权,进行JSAPI支付流程

技术要点(具体开发代码,在(公正年报H5)中实现

1、申请好公众号,获取AppID(公众号ID)AppSecret(公众号密钥)

 2、申请微信支付账户,获取(商户号)和(密钥)

 3、在微信支付账户中绑定公众号,然后在公众号中同意授权绑定,使两者产生关联

代码实现

一、h5页面,调用方法获取code

public function getGzhCode()
{
    $appid = 'xxx';//公众号appid
    $redirect_uri = urlencode('https://abcd/me'); // 用户同意授权后,能够跳转的回调链接地址
    // 获取code
    $url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=$appid&redirect_uri=$redirect_uri&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect";
    header('Location: ' . $url);
    exit();
}

二、在上面设置的回调页面me中(通过code获取access_token和openId),这个openId就是用户在公众号中的身份ID,支付的时候通过这个ID来实现

if (isset($_GET['code'])) {
    $appid = 'xxx';//公众号appid
    $appsecret = 'xxx';//公众号secret
    $code = $_GET['code'];//获取回调传过来的code,通过code再获取token以及openId
    $url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=$appid&secret=$appsecret&code=$code&grant_type=authorization_code";
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $result = curl_exec($ch);
    curl_close($ch);
    $data = json_decode($result, true);
    $openId = $data['openid']; // 用户的openid
    Session::set('openId',$openId);//保存openid到session中
    //判断用户是否已存在,不存在则先保存新用户记录    
    $userData = GznbUserModel::where('openId',$openId)->find();
    if(empty($userData)){
        $save['openId'] = $openId;
        GznbUserModel::create($save);
        $userData = GznbUserModel::where('openId',$openId)->find();
    }
    $this->assign('userData',$userData);
    return $this->view->fetch();
}

至此,已经完成了获取用户openId和保存记录,用户下次登录或支付时,通过这个openId来锁定身份

三、微信公众号-JSAPI支付实现

1、生成微信公众号-JSAPI支付参数,传递给H5页面做为config

public function form()
{
    if(request()->isPost()){
        //支付页面,提交订单,保存订单记录
        $openId = Session::get('openId');
        $status = 0;
        $trade_no = '';
        if($openId){
          $data = $this->request->param();
            $userData = GznbUserModel::where('openId',$openId)->find();
            if($userData){
                $data['userId'] = $userData['id'];
                $data['orderNumber'] = $this->getOrderNumber();
                //生成随机订单号,和回调时对应
                $data['trade_no'] = $data['orderNumber'];
                $res = GznbOrderModel::create($data);
                if($res){
                    $trade_no = $data['orderNumber'];
                    $status = 1;
                }
            }

        }
        return ['status'=>$status,'trade_no'=>$trade_no];
    }
        //支付订单参数
    $company = $this->request->param('company');
    $creditCode = $this->request->param('creditCode');
    $userName = $this->request->param('userName');
    $this->assign('company',$company);
    $this->assign('creditCode',$creditCode);
    $this->assign('userName',$userName);

    //生成微信公众号-JSAPI支付参数,传递给H5页面做为config
    $nonce_str = $this->nonce_str();//随机字符串
    $timestamp = time();
    //获取access_token
    $access_token = $this->getWxAccessToken();
    //获取ticket
    $ticket = $this->getTicket($access_token);
    $KEY = constant("KEYGZNB");
    $signData['jsapi_ticket'] = $ticket;
    $signData['noncestr'] = $nonce_str;
    $signData['timestamp'] = $timestamp;
    $signData['url'] = 'https://zzz/form';//支付订单页面
    $signature = $this->MakeSign($signData, $KEY);//生成签名
    $appId = constant("appidGZNB");
    $this->assign('appId',$appId);
    $this->assign('timestamp',$timestamp);
    $this->assign('nonce_str',$nonce_str);
    $this->assign('signature',$signature);
    return $this->view->fetch();
}

附(上面使用的方法)

/**
 * 获取小程序access_token
 */
public function getWxAccessToken(){
    $appId = constant("appidGZNB");
    $appSecret = constant("secretGZNB");
    $access_token = Cache::get('wx_access_token:'.$appId);
    if($access_token){
        return $access_token;
    }else{
        //1.请求url地址
        $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=".$appId."&secret=".$appSecret;
        $res = $this->curl($url);
        if(isset($res['errcode']) && $res['errcode']!=0){
            return ('获取access_token出错');
        }
        $access_token = $res['access_token'];
        Cache::set('wx_access_token:'.$appId,$access_token,5400);
        return $access_token;
    }
}
public function curl($url,$data = null){
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($curl, CURLOPT_TIMEOUT, 500);
    // 为保证第三方服务器与微信服务器之间数据传输的安全性,所有微信接口采用https方式调用,必须使用下面2行代码打开ssl安全校验。
    // 如果在部署过程中代码在此处验证失败,请到 http://curl.haxx.se/ca/cacert.pem 下载新的证书判别文件。
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($curl, CURLOPT_URL, $url);
    if (!empty($data)){
        curl_setopt($curl, CURLOPT_POST, 1);
        curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
    }

    $res = curl_exec($curl);
    curl_close($curl);
    $json_obj = json_decode($res,true);
    return $json_obj;
}

/**
 * 生成签名, $KEY就是支付key
 * @return 签名
 */
public function MakeSign($params, $KEY)
{
    //签名步骤一:按字典序排序数组参数
    ksort($params);
    $string = $this->ToUrlParams($params);  //参数进行拼接key=value&k=v
    //签名步骤二:在string后加入KEY
    $string = $string . "&key=" . $KEY;
    //签名步骤三:MD5加密
    $string = md5($string);
    //签名步骤四:所有字符转为大写
    $result = strtoupper($string);
    return $result;
}

/**
 * 将参数拼接为url: key=value&key=value
 * @param $params
 * @return string
 */
public function ToUrlParams($params)
{
    $string = '';
    if (!empty($params)) {
        $array = array();
        foreach ($params as $key => $value) {
            $array[] = $key . '=' . $value;
        }
        $string = implode("&", $array);
    }
    return $string;
}

H5-支付页面,获取传过来的参数,配置好config,然后通过下面方法进行支付

// 微信配置(传递过来的参数)
wx.config({
    debug: false,
    appId: '', // 必填,公众号的唯一标识
    timestamp:'', // 必填,生成签名的时间戳
    nonceStr: '', // 必填,生成签名的随机串
    signature: '', // 必填,签名
    jsApiList: ['chooseWXPay'] // 必填,需要使用的JS接口列表
});
var clickTimer = null;//设定几秒内不参重复提交
//提交支付订单,先获取订单参数,组合成一个formData对象进行传递
function submit(){   
    var  total = 318;
    var  typeName = $('.nav_item_type').text();
    var type=0;
    if(typeName == '补审作废'){
        type = 1;
    }else if(typeName == '其它'){
        type = 2;
        total = $('#total').val();
    }
    if($('#idCard').val() == ''){
        alert('请输入身份证号');
        $('#idCard').focus();
    }else if($('#phoneNum').val() == ''){
        alert('请输入手机号码');
        $('#phoneNum').focus();
    }else if(type==2 && (total=='' || total==0)) {
        alert('请输入金额');
        $('#total').focus();
    }else{
        // alert(total)
        var  idCard = $('#idCard').val();
        var  phoneNum = $('#phoneNum').val();
        var formData = new FormData();

        formData.append('company','{$company}');
        formData.append('creditCode','{$creditCode}');
        formData.append('userName','{$userName}');

        formData.append('type',type);
        formData.append('idCard',idCard);
        formData.append('phoneNum',phoneNum);
        formData.append('total',total);
        if(clickTimer === null){
            // 设置定时器,在6000毫秒(6秒)后允许再次点击
            clickTimer = setTimeout(function(){
                clickTimer = null;
            }, 3000);
            $.ajax({
                type:'POST',
                url:"{:url('form')}",
                data: formData, //传递的数据
                dataType : 'json', //传递数据的格式
                async:false, //这是重要的一步,防止重复提交的
                cache: false, //设置为false,上传文件不需要缓存。
                contentType: false,//设置为false,因为是构造的FormData对象,所以这里设置为false。
                processData: false,//设置为false,因为data值是FormData对象,不需要对数据做处理。
                success:function (data){
                    if(data.status==1){
                        getPaymentParams(total,data.trade_no);
                    }else{
                        alert('表单提交失败,请重新提交或联系管理员')

                    }
                }
            })
        }else {
            console.log('正在提交中,请勿重复点击。');
        }
    }
}
// 获取支付订单参数,
function getPaymentParams(total,trade_no) {
    $.ajax({
        type:'POST',
        url:"{:url('pay')}",
        data:{'total':total,'trade_no':trade_no},
        dataType:"json",
        success:function (data){
            invokeWeChatPay(data);
        }
    })
}

// 调用微信支付
function invokeWeChatPay(params) {
    wx.chooseWXPay({
        timestamp: params.timeStamp, // 支付签名时间戳
        nonceStr: params.nonceStr, // 支付签名随机串,不长于 32 位
        package: params.package, // 统一下单接口返回的 prepay_id 参数值,提交格式如:prepay_id=***
        signType: params.signType, // 签名类型,默认为'SHA1',使用新版支付需传入'MD5'
        paySign: params.paySign, // 支付签名
        success: function (res) {
            // 支付成功后的回调函数
            // alert('支付成功');
            window.location.href='{:url("order")}';
        },
        fail: function (res) {
            // 支付失败后的回调函数
            alert('支付失败');
        }
    });
}

//后台pay方法以及支付成功后回调notify

 //支付pay
    public function pay(){
        $trade_no = $this->request->param('trade_no');
        $total = $this->request->param('total');
//        $total = 0.01;
        $openId = Session::get('openId');
        $config = [
            'app_id' => constant("appidGZNB"),
            'mch_id' => constant("MCHIDGZNB"),
            'key' => constant("KEYGZNB"),
            'notify_url' => 'https://aaa/notify',
        ];
        $pay = Factory::payment($config);
        $attributes = [
            'openid' => $openId,
            'trade_type' => 'JSAPI',
            'body' => 'ab', //商品描述
            'out_trade_no' => $trade_no,
            'total_fee' => $total * 100,
            'notify_url' => 'https://aa/notify',
        ];
        $result = $pay->order->unify($attributes);
        if ($result['return_code'] == 'SUCCESS') {
            $prepay_id = $result['prepay_id'];
            $jsApiParam = $pay->jssdk->bridgeConfig($prepay_id);
            $array = json_decode($jsApiParam, true);
            // 返回 JSON 数据
            return ['timeStamp' => $array['timeStamp'],'nonceStr'=>$array['nonceStr'],'package'=>$array['package'],'signType'=>$array['signType'],'paySign'=>$array['paySign']];

        } else {
            echo $result['return_msg'];
        }
    }
    //支付后-回调
    public function notify(){
        $post = file_get_contents('php://input');
        $post_data = $this->xml_to_array($post);   //微信支付成功,返回回调地址url的数据:XML转数组Array
        $postSign = $post_data['sign'];
        $pay_no = $post_data['transaction_id'];

        unset($post_data['sign']);

        /* 微信官方提醒:
         *  商户系统对于支付结果通知的内容一定要做【签名验证】,
         *  并校验返回的【订单金额是否与商户侧的订单金额】一致,
         *  防止数据泄漏导致出现“假通知”,造成资金损失。
         */
        $KEY = constant("KEYGZNB");
        $user_sign = $this->MakeSign($post_data, $KEY);              //签名

        $where['trade_no'] = $post_data['out_trade_no'];
        $order = GznbOrderModel::where($where)->find();

        if ($post_data['return_code'] == 'SUCCESS' && $postSign == $user_sign) {
            /*
            * 首先判断,订单是否已经更新为ok,因为微信会总共发送8次回调确认
            * 其次,订单已经为ok的,直接返回SUCCESS
            * 最后,订单没有为ok的,更新状态为ok,返回SUCCESS
            */
            if ($order['status'] == 1) {
                $this->return_success();
            } else {
                $update['status'] = 1;
                $update['payTime'] = date("Y-m-d H:i");
                GznbOrderModel::where($where)->update($update);
            }
        } else {
            echo '微信支付失败';
        }
    }
    //    回调用 将xml转为array
    public function xml_to_array($xml)
    {
        if (!$xml) {
            return false;
        }
        //将XML转为array
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        return $data;
    }

具体开发代码,在(公正年报H5)中实现
 

你可能感兴趣的:(小例子,php,fastadmin,tp5,公众号支付,JSAPI)