Laravel支付网关回调POST请求报错:不支持GET方法如何解决
解决方案
问题根因
- 你混淆了支付网关的两类回调:
- 服务器异步通知(notify):是网关服务器后台主动发POST请求到你的服务端,不会经过用户浏览器,用于你可靠更新订单状态
- 用户同步跳转(return):是用户支付完成后,网关引导用户浏览器用GET方式跳转回你的站点,仅用于给用户展示支付结果,不可靠,不能用来更新订单状态
- 你把两类回调的地址都填成了同一个POST路由,用户跳转时走GET请求自然会报方法不支持的错误
- 放在
web.php的路由默认带CSRF校验,网关发来的POST请求没有携带你的站点CSRF令牌,会被中间件拦截,所以你拿不到POST参数
修复步骤
1. 拆分两个回调路由
首先建议把异步通知路由放到routes/api.php里(api路由默认关闭CSRF校验,前缀自动带/api,最终地址是/api/gateway/confirm_payment/{order_id}):
// api.php 中添加,给网关服务器做异步POST回调使用 Route::post('gateway/confirm_payment/{order_id}', 'Website\GatewayController@confirm_payment'); // web.php 中添加,给用户支付完成后跳转使用 Route::get('gateway/payment_return/{order_id}', 'Website\GatewayController@payment_return');
如果一定要把异步路由放在web.php,需要在app/Http/Middleware/VerifyCsrfToken.php中把该路由加入CSRF白名单:
protected $except = [ 'gateway/confirm_payment/*', ];
2. 调整控制器异步通知逻辑
use Illuminate\Http\Request; public function confirm_payment(Request $request, $order_id){ $merchant_key = 'xxxxxx'; $merchant_salt = 'xxxxxx'; // 不要用原生$_POST,直接用Laravel的Request对象获取参数 $hash = base64_encode( hash_hmac('sha256', $request->merchant_oid.$merchant_salt.$request->status.$request->total_amount, $merchant_key, true) ); if( $hash != $request->hash) { // 签名校验失败直接返回错误,按网关要求返回指定失败字符串即可 return 'fail'; } if( $request->status == 'success' ) { // 这里不要用dd(),dd会终止请求且输出调试信息,网关会认为回调失败 // 处理订单成功逻辑:更新订单状态、入账等 // 必须按网关要求返回指定的成功标识,比如大部分网关要求输出success return 'success'; }else{ // 处理支付失败逻辑 return 'success'; // 即使支付失败也要返回成功标识,避免网关重复回调 } } // 新增同步跳转的处理方法 public function payment_return(Request $request, $order_id){ // 这里可以查询订单当前状态,给用户展示支付结果即可,不要在这里做订单状态更新 $order = \App\Models\Order::find($order_id); return view('payment.result', compact('order')); }
3. 网关后台配置调整
把网关后台的异步通知地址填/api/gateway/confirm_payment/订单id的格式,同步跳转地址填/gateway/payment_return/订单id的格式即可。
注意事项
- 异步通知是网关服务器直接发起请求,和用户浏览器无关,所以不要在该逻辑里使用session、auth、cookie等依赖用户会话的功能
- 必须按网关文档要求返回指定的回调成功响应,否则网关会重复发起多次回调
- 同步跳转的参数仅做参考,订单状态必须以异步通知的结果为准
内容的提问来源于stack exchange,提问作者Mahlika
相关产品推荐
相关产品推荐

