微信支付
微信支付的开发需要有一个微信公众号并且开通了微信支付才能正常进行,申请微信支付需要有公司资质。对于手上没有支付商户号的同学,可以下载测试账号体验基本的支付功能。
测试账号
首先,我们需要访问 https://pay.weixin.qq.com/wiki/doc/api/index.html 官方文档。选择扫码支付
下载 PHP 的SDK
解压缩文件并从 WxPay.Config.php
文件中找出 app_id
、mch_id
和 key
三个测试账号:
支付时测试账号:
'app_id' => 'wx426b3015555a46be',
'mch_id' => '1900009851',
'key' => '8934e7d15453e97507ef794cf7b0519d',
微信扫码支付流程
微信扫码支付流程:
发起支付
我们定义一个微信支付控制器并添加两个方法:
- pay:发起支付
- notify:接收通知
<?php
namespace controllers;
use Yansongda\Pay\Pay;
class WxpayController
{
protected $config = [
'app_id' => 'wx426b3015555a46be', // 公众号 APPID
'mch_id' => '1900009851',
'key' => '8934e7d15453e97507ef794cf7b0519d',
'notify_url' => 'http://requestbin.fullcontact.com/r6s2a1r6',
];
public function pay()
{
$order = [
'out_trade_no' => time(),
'total_fee' => '1', // **单位:分**
'body' => 'test body - 测试',
// 'openid' => 'onkVf1FjWS5SBIixxxxxxx',
];
$pay = Pay::wechat($this->config)->scan($order);
echo $pay->return_code , '<hr>';
echo $pay->return_msg , '<hr>';
echo $pay->appid , '<hr>';
echo $pay->result_code , '<hr>';
echo $pay->code_url , '<hr>';
}
public function notify()
{
$pay = Pay::wechat($this->config);
try{
$data = $pay->verify(); // 是的,验签就这么简单!
if($data->result_code == 'SUCCESS' && $data->return_code == 'SUCCESS')
{
echo '共支付了:'.$data->total_fee.'分';
echo '订单ID:'.$data->out_trade_no;
}
} catch (Exception $e) {
var_dump( $e->getMessage() );
}
$pay->success()->send();
}
}
访问 wxpay/pay 方法发起支付,成功之后,微信服务器会返回支付码:
我们可以使用支付码生成二维码,我们可以先使用一个网站来生成:
现在使用手机扫码即可实现微信支付。
注意:支付时使用的是真的钱!!!!!所以,测试时请使用1分!!!
生成二维码
拿到支付码之后,我们需要把支付码转成二维码图片。
1、安装扩展
composer require endroid/qr-code
2、生成二维码
use Endroid\QrCode\QrCode;
......
public function qrcode()
{
$qrCode = new QrCode('weixin://wxpay/bizpayurl?pr=hpVOrJ1');
header('Content-Type: '.$qrCode->getContentType());
echo $qrCode->writeString();
}
通知的调试
因为发送的通知都是在后台进行的,所以我们看不到任何的输出结果,这不便于我们对代码进行调试。
在这种情况下,为了调试,我们可以将要打印的信息写到日志文件中,然后通过日志文件来查看打印的信息。
$log = new \libs\Log('wxpay.log');
$log.log(file_get_contents('php://input'));
代码说明:
file_get_contents('php://input')
: 可以用来接收原始数据。当我们接收的数据是XML
或者JSON
等非正常表单中的数据时,我们需要使用这种方式才能接收到数据。
示例、使用日志记录接收到的通知:
public function notify()
{
$log = new \libs\Log('wxpay');
// 记录日志
$log->log('接收到微信的消息');
$pay = Pay::wechat($this->config);
try{
$data = $pay->verify(); // 是的,验签就这么简单!
// 记录日志
$log->log('验证成功,接收的数据是:' . file_get_contents('php://input'));
if($data->result_code == 'SUCCESS' && $data->return_code == 'SUCCESS')
{
// 记录日志
$log->log('支付成功');
// 更新订单状态
$order = new \models\Order;
// 获取订单信息
$orderInfo = $order->findBySn($data->out_trade_no);
if($orderInfo['status'] == 0)
{
// 开启事务
$order->startTrans();
// 设置订单为已支付状态
$ret1 = $order->setPaid($data->out_trade_no);
// 更新用户余额
$user = new \models\User;
$ret2 = $user->addMoney($orderInfo['money'], $orderInfo['user_id']);
// 判断
if($ret1 && $ret2)
{
// 提交事务
$order->commit();
}
else
{
// 回事事务
$order->rollback();
}
}
}
}
catch (Exception $e) {
// 记录日志
$log->log('验证失败!' . $e->getMessage());
var_dump( $e->getMessage() );
}
$pay->success()->send();
}
支付成功之后,我们可以查看日志文件中的内容进行调试:
测试成功之后就可以注释掉记录日志的代码了。
扩展:接收原始数据
平时我们使用的 $_POST
和 $_GET
接收的数据都是经过PHP处理过的数据,但是 PHP 无法自动处理 XML 和 JSON 这些格式的数据,所以会导致使用 $_GET
和 $_POST
无法接收这种数据。
要接收 XML
和 JSON
格式的数据需要使用 php://input
。
示例:接收未经处理的原始数据
$data = file_get_contents('php://input'); // 接收原始的数据
检查支付状态
我们希望在微信支付成功之后,网页就知道已经支付成功了,然后自动关闭页面。
难点:网站使用的HTTP协议,这种协议不能实现服务器主动向浏览器发数据的功能。
实现服务器推送数据到浏览器的方案:
1、客户端定期(每1秒)向服务器发送 AJAX 获取订单的状态 (实现简单、效果最差)
2、轮询+长连接(减少发送的频繁,让服务器多停留一会儿,占用服务器资源,阻塞进程)
3、comet 推送服务器(需要搭建服务器,性能很好)
4、用第三方的推送服务
5、websocket协议 (基于HTTP协议之上,可以长久连接的协议,并且可以双向推送数据,适合开发网页游戏、聊天室之类实时双向通信的程序)(旧的浏览器不支持、搭建websock 服务器)
使用轮询实现推送
接下来我们使用每秒执行 1 次 AJAX 的方案来查询订单状态,并在检测到订单已支付成功时关闭页面。
首先,我们使用一个页面来显示二维码图片。
1、创建 qrcode 控制器用来生成二维码
<?php
namespace controllers;
use Endroid\QrCode\QrCode;
class QrcodeController
{
// 把一个字符串生成 二维码图片并显示
public function qrcode()
{
$str = $_GET['code'];
$qrCode = new QrCode($str);
header('Content-Type: '.$qrCode->getContentType());
echo $qrCode->writeString();
}
}
2、创建支付视图
views/users/wxpay.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>扫码完成支付</h1>
<img src="/qrcode/qrcode?code=<?=$code?>">
</body>
</html>
<script src="/simditor-2.3.6/scripts/jquery.min.js"></script>
<script>
var sn = "<?=$sn?>";
function check()
{
$.ajax({
type:"GET",
url:"/user/orderStatus?sn="+sn,
success:function(data)
{
if(data == 1)
{
alert("支付成功!");
// 关闭页面
window.close();
}
else
{
// 1秒之后执行一次
setTimeout(check, 1000);
}
}
});
}
// 1秒之后执行一次
setTimeout(check, 1000);
</script>
3、在支付时显示支付视图
WxpayController.php
// 调用微信接口进行支付
public function pay()
{
// 接收订单编号
$sn = $_POST['sn'];
// 取出订单信息
$order = new \models\Order;
// 根据订单编号取出订单信息
$data = $order->findBySn($sn);
if($data['status'] == 0)
{
// 调用微信接口
$ret = Pay::wechat($this->config)->scan([
'out_trade_no' => $data['sn'],
'total_fee' => $data['money'] * 100, // 单位:分
'body' => '智聊系统用户充值 :'.$data['money'].'元',
]);
if($ret->return_code == 'SUCCESS' && $ret->result_code == 'SUCCESS')
{
// 加载视图,并把支付码的字符串发到页面中
view('users.wxpay', [
'code' => $ret->code_url,
'sn' => $sn,
]);
}
}
else
{
die('订单状态不允许支付~');
}
}
4、服务器提供一个查询订单状态的接口
UserController.php
public function orderStatus()
{
$sn = $_GET['sn'];
$model = new Order;
$info = $model->findBySn($sn);
echo $info['status'];
}
添加使用长连接
由于每秒执行 AJAX ,连接的频繁太快,所以我们可以在服务器端添加些代码,让服务器延迟返回数据,保持长连接,以减少AJAX连接的频繁。
修改查询订单的接口:
controllers/UserController.php
public function orderStatus()
{
$sn = $_GET['sn'];
// 获取的次数
$try = 10;
$model = new Order;
do
{
// 查询订单信息
$info = $model->findBySn($sn);
// 如果订单未支付就等待1秒,并减少尝试的次数,如果已经支付就退出循环
if($info['status'] == 0)
{
sleep(1);
$try--;
}
else
break;
}while($try>0); // 如果尝试的次数到达指定的次数就退出循环
echo $info['status'];
}