技术要点(具体开发代码,在(公正年报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)中实现