Laravel 集成 GoPay REST API 时无法设置请求头
结论
Laravel 通常不会修改或拦截 GoPay SDK 通过 cURL 设置的出站请求头。Laravel 的 Request、中间件和 header() 主要处理浏览器发给应用的入站请求,以及应用返回给浏览器的响应。它们不会自动介入 SDK 单独创建的 cURL 请求。
同一段代码在普通 PHP 脚本中可以运行,在 Laravel 中却失败,常见原因包括:
- 两段代码运行在不同的 PHP 环境中,例如 CLI 与 PHP-FPM 加载了不同的
php.ini。 - Laravel 加载了不同版本的 GoPay SDK 或依赖包。
.env、配置缓存或命名空间导致 SDK 读取了错误的配置。- 获取 access token 的请求已经失败,后续请求因而无法生成
Authorization: Bearer ...。 - 实际请求经过代理或重定向,
Authorization在转发时被删除。 - 检查的是 Laravel 的入站请求头或响应头,并非 SDK 发出的 cURL 请求头。
排查时不必先尝试让 Laravel 修改请求头。应依次检查运行环境、SDK 版本、token 请求,以及 cURL 实际发送的数据。
首先处理已经暴露的凭据
示例中包含 clientId 和 clientSecret。如果它们是真实凭据,应立即在 GoPay 后台撤销或更换。不要继续使用,也不要提交到 Git 仓库。
在 Laravel 中,应通过环境变量保存这些配置:
GOPAY_GOID=your_goid
GOPAY_CLIENT_ID=your_client_id
GOPAY_CLIENT_SECRET=your_client_secret
GOPAY_PRODUCTION=false
然后在配置文件中读取:
// config/services.php
return [
'gopay' => [
'goid' => env('GOPAY_GOID'),
'client_id' => env('GOPAY_CLIENT_ID'),
'client_secret' => env('GOPAY_CLIENT_SECRET'),
'production' => env('GOPAY_PRODUCTION', false),
],
];
控制器应使用 config(),不要直接在业务代码中调用 env():
$gopay = \GoPay\payments([
'goid' => config('services.gopay.goid'),
'clientId' => config('services.gopay.client_id'),
'clientSecret' => config('services.gopay.client_secret'),
'isProductionMode' => (bool) config('services.gopay.production'),
'scope' => \GoPay\Definition\TokenScope::ALL,
'language' => \GoPay\Definition\Language::CZECH,
], [
'logger' => new \GoPay\Http\Log\PrintHttpRequest(),
]);
修改 .env 后,如果项目启用了配置缓存,需要执行:
php artisan config:clear
php artisan cache:clear
需要在生产环境重新生成配置缓存时,再执行:
php artisan config:cache
区分入站请求头与 SDK 的出站请求头
下面的代码只能查看浏览器或客户端发给 Laravel 的请求头:
request()->headers->all();
下面的函数通常只能查看 PHP 准备返回给浏览器的响应头:
headers_list();
这两种方式都无法证明 GoPay SDK 发出的 cURL 请求是否包含 Authorization、Accept 或 Content-Type。
GoPay SDK 创建的是一条独立的 HTTP 请求链:
浏览器 -> Laravel -> GoPay SDK/cURL -> GoPay API
Laravel 中间件处理左侧的请求。SDK 的请求头位于右侧,需要通过 SDK 日志、cURL verbose 输出、代理抓包或 GoPay 返回的错误信息来检查。
先确认 token 是否获取成功
支付请求中的 Authorization 通常依赖 SDK 事先取得 access token。如果认证请求失败,后续支付请求就无法携带有效的 Bearer token。
调用接口后,除了支付数据,还应记录完整的状态和响应体。具体方法取决于项目安装的 GoPay SDK 版本,例如:
$response = $gopay->createPayment($paymentData);
logger()->debug('GoPay response', [
'status_code' => method_exists($response, 'statusCode')
? $response->statusCode()
: null,
'json' => method_exists($response, 'json')
? $response->json()
: null,
]);
if (method_exists($response, 'hasSucceed') && !$response->hasSucceed()) {
logger()->error('GoPay request failed', [
'status_code' => $response->statusCode(),
'response' => $response->json(),
]);
}
不要记录完整的 Authorization、clientSecret 或 access token。需要检查请求头时,应对敏感值进行脱敏:
Authorization: Bearer ********
认证阶段需要重点检查以下错误:
401 Unauthorizedinvalid_client- DNS 解析失败
- SSL certificate problem
- Connection timeout
- 无法连接 GoPay sandbox
- 请求被发送到错误的生产或测试地址
isProductionMode => false 表示使用测试环境,因此 client ID、secret 和 GoID 也必须属于对应的测试环境。混用生产凭据和测试地址会导致认证失败,很容易被误判为”请求头没有设置”。
比较 Web 与 CLI 的 PHP 环境
普通脚本经常通过命令行运行,而 Laravel controller 通常由 PHP-FPM 或 Apache PHP 模块执行,两者可能加载完全不同的配置。
先在命令行中检查:
php --ini
php -m | grep curl
php -i | grep -E "cURL support|curl.cainfo|openssl.cafile"
php -v
然后临时在 Laravel 路由或控制器中记录 Web 环境:
logger()->debug('PHP web environment', [
'php_version' => PHP_VERSION,
'php_sapi' => PHP_SAPI,
'loaded_ini' => php_ini_loaded_file(),
'curl_loaded' => extension_loaded('curl'),
'curl_version' => extension_loaded('curl') ? curl_version() : null,
'curl_cainfo' => ini_get('curl.cainfo'),
'openssl_cafile' => ini_get('openssl.cafile'),
]);
比较以下项目:
- PHP 版本
PHP_SAPI- 实际加载的
php.ini - cURL 扩展是否启用
- libcurl 和 OpenSSL 版本
- CA 证书路径
open_basedir- 网络代理相关环境变量
- PHP-FPM 用户是否有权读取 CA 文件
如果 CLI 启用了 cURL 扩展,而 PHP-FPM 没有,就需要为 Web 使用的 PHP 安装或启用该扩展,并重启 PHP-FPM 或 Web Server。只重启 Artisan 命令不会生效。
检查 Composer 实际加载的 SDK
两个项目都调用了 GoPay\payments(),并不代表它们使用的 SDK 完全相同。可以执行以下命令进行比较:
composer show | grep -i gopay
composer show gopay/payments-sdk-php
composer why gopay/payments-sdk-php
包名应以项目的实际安装结果为准。
还要确认 Laravel 加载的是 Composer 提供的类,而不是项目中的同名 facade、辅助函数或旧版手动复制文件。可以通过反射确认实际加载的文件:
$reflection = new ReflectionClass(\GoPay\Api::class);
logger()->debug('Loaded GoPay class', [
'file' => $reflection->getFileName(),
]);
这里的类名也应按照实际安装的 SDK 进行调整。需要确认的是运行时加载的文件路径和版本。
控制器顶部还应明确导入相关类型:
use GoPay\Definition\BankSwiftCode;
use GoPay\Definition\Currency;
use GoPay\Definition\Payment\PaymentInstrument;
use GoPay\Definition\Payment\Recurrence;
如果导入路径与当前 SDK 版本不一致,PHP 可能解析到错误的类,也可能在真正发起请求前就已经失败。
直接验证 Laravel 运行环境能否通过 cURL 发送请求头
要区分 SDK 问题和 PHP-FPM/cURL 环境问题,可以在同一个 Web 运行环境中进行最小化诊断。测试地址应使用自己控制的调试端点,不要将 GoPay secret 或真实 token 发送到第三方回显服务。
$ch = curl_init('https://your-controlled-endpoint.example/debug');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode(['test' => true]),
CURLOPT_HTTPHEADER => [
'Accept: application/json',
'Content-Type: application/json',
'Authorization: Bearer test-token',
],
CURLINFO_HEADER_OUT => true,
]);
$body = curl_exec($ch);
$sentHeaders = curl_getinfo($ch, CURLINFO_HEADER_OUT);
$errorNumber = curl_errno($ch);
$errorMessage = curl_error($ch);
$statusCode = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
logger()->debug('cURL diagnostic', [
'headers' => preg_replace(
'/Authorization:.*\R/i',
"Authorization: Bearer ********\r\n",
$sentHeaders
),
'status_code' => $statusCode,
'curl_errno' => $errorNumber,
'curl_error' => $errorMessage,
'body' => $body,
]);
如果日志中能看到这三个请求头,就说明 Laravel 没有阻止 cURL 设置 header。接下来应检查 SDK 版本、认证流程和配置值。
如果这项测试也失败,则需要检查 PHP-FPM 的 cURL、SSL、CA 证书、代理设置和网络权限。
检查代理与重定向
Authorization 是敏感请求头。当请求跨主机重定向时,cURL、HTTP 客户端或中间代理可能不会将它继续发送到新主机。这是正常的安全策略。
需要检查以下项目:
- GoPay URL 是否配置成旧地址。
- HTTP 地址是否重定向到 HTTPS。
- 测试环境是否重定向到另一个域名。
- 服务器是否通过公司代理访问外网。
- Nginx、Apache、WAF 或容器 sidecar 是否改写了请求。
- 环境中是否设置了
HTTP_PROXY、HTTPS_PROXY或NO_PROXY。
可以记录最终 URL、HTTP 状态码和重定向次数,但不要输出凭据。如果出现 301、302、307 或 308,应直接使用最终的官方 HTTPS API 地址,不要依赖重定向。
Content-Type 并非所有请求都必须包含
不同请求阶段使用的 header 可能不同:
- 获取 token 的请求可能使用 Basic Authentication 和表单编码。
- 创建支付的请求通常发送 JSON,因此需要相应的
Content-Type。 - 没有请求体的 GET 请求不一定设置
Content-Type。 Authorization可能由CURLOPT_USERPWD生成,不一定直接出现在 SDK 的 header 数组中。
因此,只检查 SDK 内部传给 CURLOPT_HTTPHEADER 的数组,无法完整判断最终请求是否带有认证信息。应检查 cURL 实际发出的请求,或者服务端实际收到的请求。
建议的排查顺序
- 立即更换已经公开的
clientSecret。 - 记录 GoPay 返回的 HTTP 状态码、响应体和 cURL 错误,但不要记录敏感值。
- 确认 token 是否获取成功,并核对测试环境与测试凭据是否匹配。
- 比较 CLI 与 PHP-FPM 使用的 PHP、cURL、OpenSSL 和
php.ini。 - 比较普通脚本与 Laravel 项目安装的 GoPay SDK 版本。
- 清除 Laravel 配置缓存,确认
config()读取到的值不是空值。 - 在 Laravel Web 环境中执行最小 cURL 诊断。
- 检查代理、重定向和最终请求 URL。
如果仍然无法定位问题,最有用的补充信息不是完整 token,而是脱敏后的 SDK 请求日志、GoPay 响应体、HTTP 状态码、curl_errno()、curl_error()、CLI 与 Web 分别加载的 php.ini 路径,以及两个项目实际安装的 SDK 版本。
备注:内容仅供参考。
