首先得在微信公众号的公众号设置里,把微信支付的授权目录填上
然后你还得在商户号里,开通微信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>
效果演示: