基本思路:
1、用户扫码进入我们的系统页面(自己定义的一个用户输入金额的页面)
通过获取CODE然后获取openid
2、用户输完金额后,点击支付按钮,进入统一支付接口
获取微信支付的相应参数
3、调用微信支付JS SDK
下面上代码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <!-- 控制浏览器缓存 --> <meta http-equiv="Cache-Control" content="no-store" /> <!-- 优先使用 IE 最新版本和 Chrome --> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> <link rel="stylesheet" type="text/css" href="/luo/public/weui/dist/style/weui.css"> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> <script src="http://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script> <title>微信安全支付</title> <script src="http://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script> <script> wx.config({ debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId: 'wx4fbc93aa6fb594cf', // 必填,公众号的唯一标识 timestamp: '{{$sign['timestamp']}}', // 必填,生成签名的时间戳 nonceStr: '{{$sign['noncestr']}}', // 必填,生成签名的随机串 signature: '{{$sign['sign']}}',// 必填,签名 jsApiList: [ 'chooseImage','updateAppMessageShareData','onMenuShareAppMessage','uploadImage','previewImage','scanQRCode' ] // 必填,需要使用的JS接口列表 }); </script> <style> * { margin: 0; padding: 0; box-sizing: border-box; } html, body { height: 100%; overflow: hidden; } .clearfix:after { content: "\200B"; display: block; height: 0; clear: both; } .clearfix { *zoom: 1; } /*IE/7/6*/ .shuru div::-webkit-scrollbar { width: 0; height: 0; -webkit-transition: 1s; } .shuru div::-webkit-scrollbar-thumb { background-color: #a7afb4; background-clip: padding-box; min-height: 28px; } .shuru div::-webkit-scrollbar-thumb:hover { background-color: #525252; background-clip: padding-box; min-height: 28px; } .shuru div::-webkit-scrollbar-track-piece { background-color: #ccd0d2; } .wrap { position: relative; margin: auto; max-width: 640px; min-width: 320px; width: 100%; height: 100%; background: #F0EFF5; overflow: hidden; } .layer-content { position: absolute; left: 50%; bottom: -200px; width: 100%; max-width: 640px; height: auto; z-index: 12; -webkit-transform: translateX(-50%); transform: translateX(-50%); } /* 输入表单 */ .edit_cash { display: block; margin-top: 15px; padding: 15px; margin: 0 auto; width: 90%; border: 1px solid #CFCFCF; border-radius: 10px; background-color: #fff; } .edit_cash p { font-size: 14px; color: #8D8D8F; } .shuru { position: relative; margin-bottom: 10px; } .shuru div { border: none; width: 100%; height: 50px; font-size: 25px; line-height: 50px; border-bottom: 1px solid #CFCFCF; text-indent: 30px; outline: none; white-space: pre; overflow-x: scroll; } .shuru span { position: absolute; top: 5px; font-size: 25px; } .submit { display: block; margin: 20px auto 0; width: 90%; height: 40px; font-size: 16px; color: #fff; border-radius: 3px; background: #80D983; border: 1px solid #47D14C; font-weight: 600; } /* 键盘 */ .form_edit { width: 100%; background: #D1D4DD; } .form_edit> div { margin-bottom: 2px; margin-right: 0.5%; float: left; width: 33%; height: 45px; text-align: center; color: #333; line-height: 45px; font-size: 18px; font-weight: 600; background-color: #fff; border-radius: 5px; } .form_edit> div:nth-child(3n) { margin-right: 0; } .form_edit> div:last-child { background-color: #DEE1E9; } </style> </head> <body> @csrf <div class="wrap"> <form action="" class="edit_cash"> <p>消费总额</p> <div class="shuru"> <span>¥</span> <div id="div"></div> </div> <p>可询问工作人员应缴费用总额</p> </form> <input type="button" value="支 付" class="submit" /> </div> <div class="layer-content"> <div class="form_edit clearfix"> <div class="num">1</div> <div class="num">2</div> <div class="num">3</div> <div class="num">4</div> <div class="num">5</div> <div class="num">6</div> <div class="num">7</div> <div class="num">8</div> <div class="num">9</div> <div class="num">.</div> <div class="num">0</div> <div id="remove">删除</div> </div> </div> <script src="https://cdn.bootcss.com/layer/3.1.0/layer.js"></script> <script> $(function(){ // 监听#div内容变化,改变支付按钮的颜色 $('#div').bind('DOMNodeInserted', function(){ if($("#div").text()!="" || $("#div").text()>'0'){ $('.submit').removeClass('active'); $('.submit').attr('disabled', false); $(".submit").css("background-color","#47D14C"); }else{ $('.submit').addClass('active'); $('.submit').attr('disabled', true); $(".submit").css("background-color","#80D983"); } }); $('#div').trigger('DOMNodeInserted'); $('.shuru').click(function(e){ $('.layer-content').animate({ bottom: 0 }, 200) e.stopPropagation(); }) $('.wrap').click(function(){ $('.layer-content').animate({ bottom: '-200px' }, 200) }) $('.form_edit .num').click(function(){ var oDiv = document.getElementById("div"); oDiv.innerHTML += this.innerHTML; }) $('#remove').click(function(){ var oDiv = document.getElementById("div"); var oDivHtml = oDiv.innerHTML; oDiv.innerHTML = oDivHtml.substring(0,oDivHtml.length-1); Inserted(); }); function weixinpay(data){ wx.chooseWXPay({ timestamp: data.timestamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符 nonceStr: data.nonceStr, // 支付签名随机串,不长于 32 位 package: data.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*) signType: data.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5' paySign: data.paySign, // 支付签名 success: function (res) { } }); } $('input[type="button"]').click(function(){ var money = $("#div").text(); var zz = /^\d+(\.\d{1,2})?$/; var i = layer.load(2); if (zz.test(money) && money != 0) { $.ajax({ url:"/luo/public/index.php/pay_do", type:'get', dataType:'json', data:{openid:'{{$openid}}',money:money}, success:function(data){ layer.close(i); if(data.status == 1){ weixinpay(data); }else{ alert('请求失败!') } } }); } else { layer.close(i); alert('请输入正确金额') } }); }) </script> </body> </html>PHP后台代码:
<?php namespace App\Http\Controllers; use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Routing\Controller as BaseController; use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Http\Request; class IndexController extends Controller { private $arr; public function __construct() { $url = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://"; $url = "$url$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"; $ticket=$this -> getTicket(); $timestamp = time(); $nonceStr = uniqid(); $params = [ 'noncestr' => $nonceStr, 'jsapi_ticket' =>$ticket, 'timestamp' => $timestamp, 'url' => $url ]; ksort($params); $str = urldecode(http_build_query($params)); $sign = sha1($str); $this -> arr = ["timestamp"=>$timestamp,"noncestr"=>$nonceStr,"sign"=>$sign,'appid'=> self::APPID]; } public function index(){ $url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx4fbc93aa6fb594cf&redirect_uri=http://dtxb.zjyhj.cn/luo/public/index.php/pay&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect'; header('location:'.$url); } public function pay(Request $request){ $code = $request -> get('code'); $url = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid=wx4fbc93aa6fb594cf&secret=6be5c84bdd0cb2567c64f51aefe8b5d7&code='.$code.'&grant_type=authorization_code'; $data = $this -> curlRequest($url); $arr = json_decode($data,true); $openid = $arr['openid']; return view('pay',['openid'=>$openid,'sign' => $this->arr]); } public function pay_do(Request $request){ $openid = $request -> get('openid'); $money = $request -> get('money'); $out_trade_no = date('Ymd').time().rand(10000,99999); $arr = $this -> getQrUrl($out_trade_no,$money * 100 , $openid); if($arr){ $info['appid'] = self::APPID; $info['package'] = "prepay_id=".$arr['prepay_id']; $info['timestamp'] = time(); $info['nonceStr'] = uniqid(); $info['signType'] = "MD5"; $params = [ 'appId' => $info['appid'], 'package' =>$info['package'], 'timeStamp' => $info['timestamp'], 'nonceStr' => $info['nonceStr'], 'signType' => $info['signType'] ]; $sign = $this -> getSign($params); $info['paySign'] = $sign; $info['status'] = 1; return $info; }else{ return ['status'=>2]; } } public function getQrUrl($out_trade_no,$money,$openid){ //调用统一下单API $params = [ 'appid'=> self::APPID, 'mch_id'=> self::MCHID, 'nonce_str'=>uniqid(), 'body'=> '支付测试', 'out_trade_no'=> $out_trade_no, 'total_fee'=> $money,//2分 'spbill_create_ip'=>$_SERVER['REMOTE_ADDR'], 'notify_url'=> self::NOTIFY, 'trade_type'=>'JSAPI', 'openid'=> $openid, ]; $arr = $this->unifiedorder($params); if($arr){ return $arr; }else{ return false; } } public function curlRequest($url,$data = ''){ $ch = curl_init(); $params[CURLOPT_URL] = $url; //请求url地址 $params[CURLOPT_HEADER] = false; //是否返回响应头信息 $params[CURLOPT_RETURNTRANSFER] = true; //是否将结果返回 $params[CURLOPT_FOLLOWLOCATION] = true; //是否重定向 $params[CURLOPT_TIMEOUT] = 30; //超时时间 if(!empty($data)){ $params[CURLOPT_POST] = true; $params[CURLOPT_POSTFIELDS] = $data; } $params[CURLOPT_SSL_VERIFYPEER] = false;//请求https时设置,还有其他解决方案 $params[CURLOPT_SSL_VERIFYHOST] = false;//请求https时,其他方案查看其他博文 curl_setopt_array($ch, $params); //传入curl参数 $content = curl_exec($ch); //执行 curl_close($ch); //关闭连接 return $content; } public function notify(){ } }Controller类:
<?php namespace App\Http\Controllers; use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Routing\Controller as BaseController; use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use DB; class Controller extends BaseController { const KEY = ''; //支付秘钥需要更改成自己的 const APPID = ''; //APPID需要更改为自己的 const MCHID = ''; //商户号需要更改成自己的 const SECRET = ''; //开发者密码需要更改为自己的 const UOURL = 'https://api.mch.weixin.qq.com/pay/unifiedorder'; //无需更改 统一下单API地址 const NOTIFY = ''; //支付通知地址需要更改成你自己服务器的地址 public function __construct() { } public function getToken(){ //公众号id和密匙 $appid=self::APPID; $secret=self::SECRET; $token = DB::table('token') -> where('k','token') -> first(); if(empty($token) || time() - $token -> time > 3600){ //获取全局access_token $url="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=".$appid."&secret=".$secret.""; $data=$this->curlRequest($url); //print_r($data); $data=json_decode($data,true); $token=$data['access_token']; if(empty($token)){ DB::table('token') -> insert(['k'=>'token','v'=>$token]); }else{ DB::table('token') -> where('k','token') -> update(['v'=>$token]); } } return $token; } public function getTicket(){ $ticket = DB::table('token') -> where('k','ticket') -> first(); if(empty($ticket) || time() - $ticket -> time > 3600){ $token=$this ->getToken(); $url="https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=".$token."&type=jsapi"; $js_api=file_get_contents($url); //print_r($js_api); $js_api=json_decode($js_api,true); $ticket=$js_api['ticket']; if(empty($ticket)){ DB::table('token') -> insert(['k'=>'ticket','v'=>$ticket]); }else{ DB::table('token') -> where('k','ticket') -> update(['v'=>$ticket]); } } return $ticket; } //获取签名 public function getSign($arr){ //去除数组的空值 array_filter($arr); if(isset($arr['sign'])){ unset($arr['sign']); } //排序 ksort($arr); //组装字符 $str = $this->arrToUrl($arr) . '&key=' . self::KEY; //使用md5 加密 转换成大写 return strtoupper(md5($str)); } //获取带签名的数组 public function setSign($arr){ $arr['sign'] = $this->getSign($arr); return $arr; } //校验签名 public function checkSign($arr){ //生成新签名 $sign = $this->getSign($arr); //和数组中原始签名比较 if($sign == $arr['sign']){ return true; }else{ return false; } } //数组转URL字符串 不带key public function arrToUrl($arr){ return urldecode(http_build_query($arr)); } //记录到文件 public function logs($file,$data){ $data = is_array($data) ? print_r($data,true) : $data; file_put_contents('./logs/' .$file, $data); } public function getPost(){ return file_get_contents('php://input'); } //Xml 文件转数组 public function XmlToArr($xml) { if($xml == '') return ''; libxml_disable_entity_loader(true); $arr = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); return $arr; } //数组转XML public function ArrToXml($arr) { if(!is_array($arr) || count($arr) == 0) return ''; $xml = "<xml>"; foreach ($arr as $key=>$val) { if (is_numeric($val)){ $xml.="<".$key.">".$val."</".$key.">"; }else{ $xml.="<".$key."><![CDATA[".$val."]]></".$key.">"; } } $xml.="</xml>"; return $xml; } //post 字符串到接口 public function postStr($url,$postfields){ $ch = curl_init(); $params[CURLOPT_URL] = $url; //请求url地址 $params[CURLOPT_HEADER] = false; //是否返回响应头信息 $params[CURLOPT_RETURNTRANSFER] = true; //是否将结果返回 $params[CURLOPT_FOLLOWLOCATION] = true; //是否重定向 $params[CURLOPT_POST] = true; $params[CURLOPT_SSL_VERIFYPEER] = false;//禁用证书校验 $params[CURLOPT_SSL_VERIFYHOST] = false; $params[CURLOPT_POSTFIELDS] = $postfields; curl_setopt_array($ch, $params); //传入curl参数 $content = curl_exec($ch); //执行 curl_close($ch); //关闭连接 return $content; } //统一下单 public function unifiedorder($params){ //获取到带签名的数组 $params = $this->setSign($params); //数组转xml $xml = $this->ArrToXml($params); //发送数据到统一下单API地址 $data = $this->postStr(self::UOURL, $xml); $arr = $this->XmlToArr($data); if($arr['result_code'] == 'SUCCESS' && $arr['return_code'] == 'SUCCESS'){ return $arr; } } }