PHP微信公众号支付,JSAPI支付方法,ThinkPHP5+微信支付
JSAPI支付:是指在微信内置浏览器内调用微信支付模块支付,比如可用于微信公众号内的微信商城之类的。
首先得在微信公众号的公众号设置里,把微信支付的授权目录填上
然后你还得在商户号里,开通微信JSAPI支付的功能
然后这里的授权目录也得填上
然后按照微信文档的时序图,大概分3步
项目结构:
配置文件:
Jsapi.php代码:
<?php namespace app\index\controller; use app\common\controller\HomeBase; use tools\WxPay; use think\Config; use phpqrcode\ApiQrcode; use think\Request; use think\Cache; use tools\RetJosn; /** * JSAPI支付DEMO * Class Index * @package app\index\controller */ class Jsapi extends HomeBase { /** * 首页 */ public function index() { $redirect_uri = urlencode('http://wxpay.ngrok2.xiaomiqiu.cn'.url('Jsapi/index2')); $url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid='.Config::get('wx_pay')['appid'].'&redirect_uri='.$redirect_uri.'&response_type=code&scope=snsapi_base&state=123#wechat_redirect'; $this->redirect($url); } /** * 授权后跳转到此 */ public function index2(Request $request){ $arr = $request->get(); $code = $arr['code']; $url = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid='.Config::get('wx_pay')['appid'].'&secret='.Config::get('wx_pay')['secret'].'&code='.$code.'&grant_type=authorization_code'; $ret_json = file_get_contents("$url"); $ret_arr = json_decode($ret_json,true); $openid = $ret_arr['openid']; $this->assign('openid',$openid); return $this->fetch(); } /** * 下单获取支付参数 * @param Request $request * @param WxPay $wxpay * @return \think\response\Json */ public function getUrl(Request $request,WxPay $wxpay) { $pid = $request->get('id'); $openid = $request->get('openid'); //调用统一下单API $params = [ 'appid' => Config::get('wx_pay')['appid'], 'mch_id' => Config::get('wx_pay')['mchid'], 'nonce_str' => md5(time()), 'body' => '订单号:'.$pid, 'out_trade_no' => $pid, 'total_fee' => 2, 'spbill_create_ip' => $_SERVER['SERVER_ADDR'], 'notify_url' => Config::get('wx_pay')['notify'], 'trade_type' => 'JSAPI', 'product_id' => $pid, 'openid' => $openid ]; $arr = $wxpay->unifiedorder($params); $wxpay->logs('logs.txt',$arr); if (isset($arr['prepay_id'])) { //重新签名 $data = [ 'appId' => $arr['appid'], 'timeStamp' => time(), 'nonceStr' => md5(time()), 'package' => 'prepay_id='.$arr['prepay_id'], 'signType' => 'MD5' ]; $data = $wxpay->setSign($data); $data['paySign'] = $data['sign']; unset($data['sign']); Cache::set('send' . $params['out_trade_no'], $params['total_fee'], 3600); return RetJosn::successJson('OK',$data); } else { return RetJosn::errorJson('err'); } } /** * 接收腾讯推送支付通知 * @param WxPay $wxpay */ public function backOrder(WxPay $wxpay) { try { // 获取腾讯传回来的通知数据 $xml = $wxpay->getPost(); // 将XML格式的数据转换为数组 $arr = $wxpay->XmlToArr($xml); $wxpay->logs('logs.txt', '1'); // 验证签名 if ($wxpay->checkSign($arr)) { Cache::set('back'.$arr['out_trade_no'],$arr['total_fee'],3600); } $wxpay->logs('logs.txt', '2'); $params = [ 'return_code' => 'SUCCESS', 'return_msg' => 'OK' ]; echo $wxpay->ArrToXml($params); } catch (\Exception $e) { $wxpay->logs('logs.txt', $e->getMessage()); exit(); } } /** * 查询支付状态 * @param Request $request * @return type */ public function checkSuccess(Request $request) { $pid = $request->get('id'); if (Cache::get('send' . $pid) == Cache::get('back' . $pid)) { return RetJosn::successJson('支付成功'); } else { return RetJosn::errorJson(Cache::get('send' . $pid) . '|' . Cache::get('back' . $pid)); } } }
微信支付类:
<?php /* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ namespace tools; use think\Config; /** * Description of WxPay * * @author admin */ class WxPay { /** * 获取签名 * @param type $arr * @return type */ public function getSign($arr) { //去除数组的空值 array_filter($arr); if(isset($arr['sign'])){ unset($arr['sign']); } //排序 ksort($arr); //组装字符 $str = $this->arrToUrl($arr) . '&key=' . Config::get('wx_pay')['key']; //使用md5 加密 转换成大写 return strtoupper(md5($str)); } /** * 校验签名 * @param type $arr * @return boolean */ public function checkSign($arr){ //生成新签名 $sign = $this->getSign($arr); //和数组中原始签名比较 if($sign == $arr['sign']){ return true; }else{ return false; } } /** * 获取带签名的数组 * @param array $arr * @return type */ public function setSign($arr) { $arr['sign'] = $this->getSign($arr); return $arr; } /** * 数组转URL字符串 不带key * @param type $arr * @return type */ public function arrToUrl($arr) { return urldecode(http_build_query($arr)); } /** * 记录到文件 * @param type $file * @param type $data */ public function logs($file,$data) { $data = is_array($data) ? print_r($data,true) : $data; file_put_contents('./public/paylogs/' .$file, $data); } /** * 接收POST推送 * @return type */ public function getPost() { return file_get_contents('php://input'); } /** * Xml 文件转数组 * @param type $xml * @return string */ 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 * @param type $arr * @return string */ 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请求 * @param type $url * @param type $postfields * @return type */ 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; } /** * 统一下单 * @param type $params * @return boolean */ public function unifiedorder($params) { //获取到带签名的数组 $params = $this->setSign($params); //数组转xml $xml = $this->ArrToXml($params); //发送数据到统一下单API地址 $data = $this->postStr(Config::get('wx_pay')['uourl'], $xml); $arr = $this->XmlToArr($data); if($arr['result_code'] == 'SUCCESS' && $arr['return_code'] == 'SUCCESS'){ return $arr; }else{ $this->logs('error.txt', $data); return false; } } }
前端页面:
<!DOCTYPE html> <!-- To change this license header, choose License Headers in Project Properties. To change this template file, choose Tools | Templates and open the template in the editor. --> <html> <head> <title>TODO supply a title</title> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1" /> </head> <body> <h1 id="openid"></h1> <div class="btn-box"> <button onclick="getUrl()">下单</button> </div> <div id="info"> </div> </body> <script src="__STATIC__/default/js/jquery-1.9.1.min.js"></script> <script> var openid = ''; $(function () { localStorage.getItem('openid'); if (localStorage.getItem('openid')) { openid = localStorage.getItem('openid'); } else { openid = '{$openid}'; localStorage.setItem('opendi',openid); } $('#openid').html(openid); }); /*验证码生成*/ function getUrl() { var id = getId(10); $.ajax({ url:"{:url('Jsapi/getUrl')}?id=" + id + '&openid=' + openid, dataType:'json', success:function(res) { if (res.code == 200) { alert('1'+JSON.stringify(res)); var data = res.data; wakeup(res.data); checkSuccess(id); } } }); } function wakeup(data){ WeixinJSBridge.invoke( 'getBrandWCPayRequest', data, function(res){ if(res.err_msg == "get_brand_wcpay_request:ok" ){ // 使用以上方式判断前端返回,微信团队郑重提示: //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。 alert('支付成功'); } }); } /*生成唯一Id*/ function getId(length){ var tmp = Date.parse( new Date() ).toString(); tmp = tmp.substr(0,length); return tmp; } /*轮询支付状态*/ function checkSuccess(id){ var interval = window.setInterval(function(){ $.ajax({ url:"{:url('Jsapi/checkSuccess')}?id=" + id, dataType:'json', success:function(res) { if (res.code == 200) { $('#info').html('订单号:'+id+','+res.msg); clearInterval(interval); } } }) },2000) } </script> </html>
效果演示: