Laravel 如何获取 POST callback 请求数据
结论
POST 路由本身没有问题。接收 callback 时,要根据请求的 Content-Type,使用 $request->all()、$request->input() 或 $request->getContent() 读取数据。
第三方 callback 收不到数据,常见原因有两个:
- 第三方发送的是表单数据,代码却把请求体当作 JSON 解码。
- callback 路由受 Laravel CSRF 校验保护。第三方请求没有 CSRF token,因此返回
419。
不建议用 dd() 调试线上 callback。dd() 会立即终止请求,第三方可能因此判断回调处理失败。更合适的方式是将数据写入日志,并返回明确的 2xx 响应。
接收并记录 callback 数据
可以先记录解析后的参数、原始请求体和请求头,以确认第三方实际发送的数据格式:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class PackController extends Controller
{
public function callback(Request $request)
{
Log::info('Payment callback received', [
'content_type' => $request->header('Content-Type'),
'data' => $request->all(),
'raw_body' => $request->getContent(),
]);
return response('OK', 200);
}
}
日志通常位于:
storage/logs/laravel.log
如果 Laravel 配置了其他日志 channel,则以项目的日志配置为准。
根据请求格式读取数据
表单格式
如果 callback 的 Content-Type 是:
application/x-www-form-urlencoded
或者:
multipart/form-data
可以使用:
$data = $request->all();
读取单个字段时,可以使用:
$orderId = $request->input('order_id');
$status = $request->input('status');
也可以只读取预期字段:
$data = $request->only([
'order_id',
'status',
'price_amount',
'price_currency',
]);
字段名称要以 callback 服务实际发送的参数为准。
JSON 格式
如果 Content-Type 是:
application/json
Laravel 通常也能直接解析:
$data = $request->all();
或者:
$data = $request->json()->all();
如果需要读取未经处理的原始 JSON,可以使用:
$rawBody = $request->getContent();
$data = json_decode($rawBody, true);
if (json_last_error() !== JSON_ERROR_NONE) {
Log::warning('Invalid callback JSON', [
'error' => json_last_error_msg(),
'raw_body' => $rawBody,
]);
return response('Invalid JSON', 400);
}
原来的代码:
json_decode($request->getContent(), true)
只有当请求体确实是 JSON 时才会得到数组。如果对方发送的是 URL 编码表单,json_decode() 会返回 null。
处理 CSRF 校验
定义在 routes/web.php 中的 POST 路由通常会经过 CSRF 中间件。第三方服务器无法提供当前 Laravel 应用生成的 CSRF token,所以 callback 可能返回 419 Page Expired。
只需为这个 callback 地址排除 CSRF 校验,不要关闭整个应用的 CSRF 保护。
如果 Laravel 项目通过 VerifyCsrfToken 中间件配置 CSRF,可以加入:
protected $except = [
'callback',
];
部分项目的中间件文件位于:
app/Http/Middleware/VerifyCsrfToken.php
较新的 Laravel 项目可能会在 bootstrap/app.php 中配置例外路径,例如:
use Illuminate\Foundation\Configuration\Middleware;
->withMiddleware(function (Middleware $middleware) {
$middleware->validateCsrfTokens(except: [
'callback',
]);
})
具体写法取决于 Laravel 版本和项目结构。
也可以将第三方 callback 定义在 routes/api.php 中,但 API 路由可能会自动添加 /api 前缀。例如:
use App\Http\Controllers\PackController;
use Illuminate\Support\Facades\Route;
Route::post('/callback', [PackController::class, 'callback'])
->name('payment.callback');
此时,实际地址可能是:
https://example.com/api/callback
是否包含 /api 前缀,可以查看项目的路由配置,或运行:
php artisan route:list
关于请求方法错误
callback 路由定义为:
Route::post('/callback', [PackController::class, 'callback']);
这表示它只接受 POST 请求。直接在浏览器地址栏访问 /callback 时,浏览器发送的是 GET 请求,因此可能出现 405 Method Not Allowed。这并不代表第三方使用 POST 请求时,callback 路由的配置有问题。
本地可以这样测试:
curl -X POST https://example.com/callback \
-H 'Content-Type: application/json' \
-d '{"order_id":"123","status":"paid"}'
如果对方发送的是表单数据,可以这样测试:
curl -X POST https://example.com/callback \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'order_id=123&status=paid'
推荐的处理方式
完成调试后,可以将 callback 方法整理为:
public function callback(Request $request)
{
$data = $request->all();
Log::info('Payment callback received', [
'data' => $data,
]);
// 校验 callback 的真实性
// 校验必要字段
// 根据订单号查询订单
// 幂等地更新支付状态
return response('OK', 200);
}
还需要注意以下几点:
- 不要在正式 callback 中使用
dd(),否则代码无法执行到后面的return。 - 应尽快返回第三方要求的成功状态码,通常是
200,具体仍要以对方当前的 callback 文档为准。 - callback 可能因网络重试而重复发送,因此订单更新逻辑必须具备幂等性。
- 不要只根据请求中的
status确认付款。应按照服务商提供的签名、token、订单查询接口或其他验证机制,确认请求的真实性。 - 生产环境中不要长期记录完整的支付信息、密钥、签名或个人数据。排查结束后,应减少日志字段。
- callback URL 必须能被第三方服务器公开访问。
localhost或只能从内网访问的地址,通常无法接收线上 callback。
备注:内容仅供参考。
