Laravel 支付回调出现 MethodNotAllowedHttpException
结论
MethodNotAllowedHttpException 通常与支付验证代码无关。它表示 callback 请求使用的 HTTP 方法与 Laravel 路由允许的方法不一致。
例如,支付平台向回调地址发送 POST 请求,但路由只注册了 GET:
Route::get('/payment/callback', [PaymentController::class, 'callback']);
Laravel 会在请求进入控制器前将其拒绝,所以控制器中的 $_POST、SOAP 调用等代码都不会执行。
正确配置回调路由
如果支付平台通过 POST 发送回调,路由也应注册为 POST:
use App\Http\Controllers\PaymentController;
use Illuminate\Support\Facades\Route;
Route::post('/payment/callback', [PaymentController::class, 'callback'])
->name('payment.callback');
如果支付服务可能使用 GET 或 POST,确认服务商协议后,可以同时允许这两种方法:
Route::match(['get', 'post'], '/payment/callback', [
PaymentController::class,
'callback',
])->name('payment.callback');
不要为了省事直接使用 Route::any()。回调接口只需开放支付服务实际使用的请求方法。
使用 Laravel 的 Request 读取参数
在 Laravel 控制器中,应注入 Illuminate\Http\Request,不要直接访问 $_POST:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use SoapClient;
class PaymentController extends Controller
{
public function callback(Request $request)
{
$state = $request->input('State');
$refNum = $request->input('RefNum');
if ($state !== 'OK' || empty($refNum)) {
return response('Transaction Failed', 400);
}
$merchantCode = config('services.sep.merchant_code');
$soapClient = new SoapClient(
'https://verify.sep.ir/Payments/ReferencePayment.asmx?WSDL'
);
$result = $soapClient->VerifyTransaction($refNum, $merchantCode);
if ($result <= 0) {
return response('Transaction Failed', 400);
}
return response(
"Transaction Successful\nRef: {$refNum}\nRes: {$result}"
);
}
}
$request->input() 能读取表单参数和查询字符串参数,也便于进行验证、测试和后续维护。
检查 CSRF 中间件
支付平台属于站外服务,无法携带 Laravel 页面生成的 CSRF token。如果 callback 路由位于启用了 CSRF 校验的 web 中间件组中,还要根据当前 Laravel 项目的配置方式,将该路径排除在 CSRF 校验之外。
不同 Laravel 版本的配置位置不同。某些版本可以在应用配置中排除:
->withMiddleware(function ($middleware) {
$middleware->validateCsrfTokens(except: [
'payment/callback',
]);
})
较早的项目通常在 CSRF 中间件的 $except 中配置:
protected $except = [
'payment/callback',
];
CSRF 校验失败通常会显示 419 Page Expired 或 token mismatch,而不是 MethodNotAllowedHttpException。因此,先修正路由方法,再处理 CSRF 问题。
如何确认实际请求方法
使用下面的命令检查已注册的路由:
php artisan route:list
找到 callback 路径,确认其 Method 是否包含支付平台实际发送的 POST 或 GET。
还要注意以下几种情况:
- 浏览器跳转到回调页时通常使用
GET。 - 支付平台服务器发送交易结果时通常使用
POST。 - 部分支付服务会分别提供浏览器返回地址和服务器通知地址,最好为这两个地址单独定义路由。
例如:
Route::get('/payment/return', [PaymentController::class, 'returnPage']);
Route::post('/payment/callback', [PaymentController::class, 'callback']);
注意事项
不能只根据 State === 'OK' 判断支付成功。该字段来自外部请求,可能被伪造。必须调用支付服务的验证接口,并核对订单号、商户号、支付金额、交易状态以及该交易是否已经处理。
回调还应采用幂等设计。支付平台可能重复发送同一笔通知,因此需要使用 RefNum 或服务商提供的唯一交易号,避免重复入账。SOAP 请求也可能超时或抛出 SoapFault。生产环境中应捕获异常并记录日志,不要直接向客户端输出内部错误信息。
备注:内容仅供参考。
