- 最近做了个项目是关于微信网页开发的,今天记录下在做项目中的关于微信这块遇到的一些坑
- 关于微信这块,用的是EasyWeChat,提高了开发的效率.在看EasyWeChat这个文档的时候发现了有专门针对laravel 框架的包,所以就用了laravel-wechat
首先是安装这个composer包
composer require "overtrue/laravel-wechat:~3.0"
接着注册ServiceProvider,由于看github文档说明时,文档上有错误LaravelWeChat写成了小写Laravelwechat 在phpstorm中按ctr点击鼠标也能跳转方法,但是在运行项目的时候报找不到这个类,最后看了github上的issue有人遇到同样的问题,发现了这个大小写问题,此为第一个坑.
Overtrue\LaravelWeChat\ServiceProvider::class
- 接着是token验证,该配置的地方都没问题了,但是这个token验证老师失败,最后发现由于laravel 自带的CSRF 中间件要排除路由,和这个中间件平级,同时CSRF排除这个url
Route::any(\'/wechat\', \'WeChatController@serve\');
Route::group([\'middleware\' => \'web\'], funcation(){})
过滤url
class VerifyCsrfToken extends BaseVerifier
{
protected $except = [
//
\'wechat\', \'web/pay/callback\', \'web/pay/signCallback\'
];
}
- 关注回复相关代码
namespace App\Http\Controllers;
use App\Http\Controllers\Web\Controller;
use Illuminate\Http\Request;
use App\Http\Controllers\Web\UserController as User;
use App\Models\Setting;
use App\Models\WechatReply;
use EasyWeChat\Message\News;
class WechatController extends Controller
{
public function serve()
{
$app = app(\'wechat\');
$app->server->setMessageHandler(function($message) use ($app){
if ($message->MsgType==\'event\') {
$userOpenid = $message->FromUserName;
switch ($message->Event) {
case \'subscribe\':
$userInfo[\'openid\'] = $userOpenid;
$userService = $app->user; //获取用户服务
$user = $userService->get($userInfo[\'openid\']);
$userInfo[\'nickname\'] = $user[\'nickname\'];
$userInfo[\'headimgurl\'] = $user[\'headimgurl\'];
if (userAttention($userInfo)) {
return $this->reply(\'follow_keyword\');
}else{
return \'您的信息由于某种原因没有保存,请重新关注\';
}
break;
case \'unsubscribe\':
if (userCancelAttention($userOpenid)) {
return \'已取消关注\';
}
break;
default:
# code...
break;
}
} elseif ($message->MsgType==\'text\') { //关键字回复
$replay = WechatReply::where(\'keyword\', $message->Content)->first();
if ($replay) {
} else {
}
}
});
return $app->server->serve();
}
- 网页授权,EasyWeChat进行网页授权非常简单,这个项目中,我主要用了静默授权,只要将路由包含在这个中间件中,通过session就可以获得这个用户的openid
Route::group([\'middleware\' => \'wechat.oauth\'], function () {
});
$user = session(\'wechat.oauth_user
$openid = $user[\'id\'];
- 菜单的设置 ,通过注入的方式获得菜单的实例
use EasyWeChat\Foundation\Application;
use Illuminate\Http\Request;
class MenuController extends Controller
{
public $menu;
public function __construct(Application $app)
{
$this->menu = $app->menu;
}
public function create()
{
//这里进行菜单数组格式的组装
$this->menu->add($buttons);
}
- 模板消息 ,写一个全局辅助函数,来发送模板消息,获得模板消息应用实例
function sendTemplateMsg($templateId, $userId, $data, $url)
{
$user = \App\Models\User::where(\'id\', $userId)->first();
$app = new \EasyWeChat\Foundation\Application(config(\'wechat\'));
$notice = $app->notice;
$result = $notice->uses($templateId)->withUrl($url)->andData($data)->andReceiver($user->openid)->send();
return $result;
}
- 支付,支付这边用的是jssdk方式支付,最主要的是要获得统一下单的prepay_id.
前端页面这个$js要在控制器传到这个待支付界面
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js" type="text/javascript" charset="utf-8"></script> //微信的js库文件
wx.config(<?php echo $js->config(array(\'chooseWXPay\'), false) ?>);
点击支付调用此方法
function pay() {
$.ajax({
url: siteUrl + \'/web/pay/create\',
type: \'get\',
dataType: \'json\',
data: {
\'id\': "{{ $order->id }}",// 订单ID
\'coupon_id\': "{{ $coupon_id }}",// 优惠券ID
\'actual_payment_amount\': deposit // 实际支付金额
},
headers: {
\'X-CSRF-TOKEN\': $(\'meta[name="csrf-token"]\').attr(\'content\')
},
success: function (data) {
if (data[\'code\'] === 0) {
data = data[\'data\'];
wx.ready(function() {
wx.chooseWXPay({
timestamp: data[\'timestamp\'],
nonceStr: data[\'nonceStr\'],
package: data[\'package\'],
signType: data[\'signType\'],
paySign: data[\'paySign\'],
success: function (res) {
// 支付成功跳转新页面
window.location.href = siteUrl + "/web/order/paySuccess";
}
});
});
} else {
// 统一下单失败
alert(data[\'data\'][\'err_code_des\']);
}
}
});
}
create方法代码
use EasyWeChat\Foundation\Application;
use EasyWeChat\Payment\Order as WechatOrder;
use Illuminate\Support\Facades\Log;
class PayController extends Controller
{
private $app;
public function __construct()
{
$this->app = new Application(config(\'wechat\'));
}
// 微信统一下单
public function createOrder(Request $request)
{
$order = Order::find($request->id);
$attributes = [
\'trade_type\' => \'JSAPI\',
\'body\' => \'\',
\'detail\' => \'订单号: \' . $order->order_no,
\'out_trade_no\' => $order->order_no,
\'total_fee\' => $request->actual_payment_amount * 100, // 单位:分
\'openid\' => getOpenId(), // trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识,
\'notify_url\' => url(\'/web/pay/callback\'), //支付回调方法
\'attach\' => \'\' //这里是带给回调方法的一些字段,用于业务逻辑处理
];
$order = new WechatOrder($attributes);
$result = $this->app->payment->prepare($order);
if ($result->return_code == \'SUCCESS\' && $result->result_code == \'SUCCESS\') {
$prepayId = $result->prepay_id;
$config = $this->app->payment->configForJSSDKPayment($prepayId); // 返回数组
return apiReturn(SUCCESS, \'统一下单成功\', $config);
}
return apiReturn(ERROR, \'统一下单失败\', $result);
}
由于callback方法也是post类型所以在CSRF中间件中也要过滤,这也是有的人发现支付完了,但是没有调用callback方法
class VerifyCsrfToken extends BaseVerifier
{
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array
*/
protected $except = [
//
\'wechat\', \'web/pay/callback\'
];
}
callback方法
// 微信支付回调
public function callback(Request $request)
{
$response = $this->app->payment->handleNotify(function($notify, $successful) {
// 记录日志
Log::info(\'微信支付: \' . json_encode($notify));
// 使用通知里的 "微信支付订单号" 或者 "商户订单号" 去自己的数据库找到订单
$order = Order::where(\'order_no\', $notify->out_trade_no)->first();
// 检查订单是否已经更新过支付状态
if ($order->pay_time) {
return true;
}
// 用户是否支付成功
if ($successful) {
//业务逻辑代码,处理
$attach = json_decode($notify->attach);
apiReturn(SUCCESS, \'支付成功\', $notify);
} else {
apiReturn(SUCCESS, \'支付失败\', $notify);
}
$order->save(); // 保存订单
$this->sendPayMessage($order->id);
return true; // 返回处理完成
});
return $response;
}
- 分享朋友,分享朋友圈,本来以为点击页面中的某一个按钮能直接分享给朋友呢,其实不是的,要使用微信的发送朋友,使用jssdk可以自定义分享的地址
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js" type="text/javascript" charset="utf-8"></script>
wx.config(<?php echo $js->config(array(\'onMenuShareTimeline\', \'onMenuShareAppMessage\'), false) ?>);
wx.ready(function() {
wx.onMenuShareAppMessage({
title: \'\', // 分享标题
desc: \'\', // 分享描述
link: \'\', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: \'\', // 分享图标
type: \'link\', // 分享类型,music、video或link,不填默认为link
dataUrl: \'\', // 如果type是music或video,则要提供数据链接,默认为空
success: function () {
alert(\'分享成功\');
// 用户确认分享后执行的回调函数
},
cancel: function () {
// 用户取消分享后执行的回调函数
},
fail: function () {
alert(\'fail\');
}
});
wx.onMenuShareTimeline({
title: \'推荐拿红包\', // 分享标题
link: \'\', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: \'\', // 分享图标
success: function () {
// 用户确认分享后执行的回调函数
alert(\'分享成功\')
},
cancel: function () {
// 用户取消分享后执行的回调函数
alert(\'请重新分享\')
}
});
});
起初我没有用wx.ready包住这两段代码,结果是分享的是我当前界面,而不是自定义的url地址,但是微信开发文档写的是:
- wx.ready(function(){
// config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,则可以直接调用,不需要放在ready函数中。
});
我理解的是分享也是手动触发的应该也不需要放在ready中, 但是实际不行.