PAYATHON 2026

Laravel 从支付页面重定向后用户被登出怎么办?

支付老李

结论

遇到「支付回调首次访问显示未登录,刷新后又恢复登录」的情况,通常不是 session 文件被清除,而是浏览器从银行域名返回时没有携带 Laravel 的 session cookie。

应先检查 Laravel 的 SameSite、Secure、域名及 HTTP/HTTPS 配置。支付回调也不应依赖 auth middleware。正确的做法是通过订单号或支付流水号验证交易,再将用户跳转到站内结果页。

为什么刷新后登录状态会恢复

Laravel 的 file session 将 session 数据保存在服务器端。只有浏览器发送类似 laravel_session 的 cookie,Laravel 才能找到对应的 session 文件。

支付过程涉及跨站跳转:

你的站点 → 银行站点 → 你的站点

如果 cookie 设置为 SameSite=Strict,或者设置了 SameSite=None 却没有同时启用 Secure,浏览器可能不会在银行返回后的第一次请求中发送该 cookie。auth middleware 因此认为用户尚未登录,并将其重定向到登录页。

用户刷新页面后,这次请求已经属于站内请求,浏览器会重新发送 cookie,于是登录状态看起来又「自动恢复」了。

这也能解释为什么修改 framework/sessions 权限或直接使用 $_SESSION 没有效果。问题通常不在于服务器能否写入 session 文件,而在于浏览器是否发送了 cookie。

调整 Laravel session 配置

可以先在 .env 中使用以下配置:

SESSION_DRIVER=file
SESSION_SECURE_COOKIE=true
SESSION_SAME_SITE=lax

银行通过顶层 GET 重定向返回时,lax 通常可以正常工作。

同时确认 config/session.php 使用了对应的环境变量:

'secure' => env('SESSION_SECURE_COOKIE', true),

'same_site' => env('SESSION_SAME_SITE', 'lax'),

修改配置后需要清除缓存。如果主机允许执行 Artisan,可以运行:

php artisan optimize:clear

旧版 Laravel 如果不支持 optimize:clear,可以分别执行:

php artisan config:clear
php artisan cache:clear

如果 shared hosting 无法直接执行命令,可通过主机控制面板提供的终端运行,或按照现有部署方式重新生成配置缓存。

银行使用 POST 或嵌入页面返回时

如果银行不是通过普通的顶层 GET 跳转,而是使用跨站 POST、iframe 或其他嵌入方式访问回调地址,SameSite=Lax 仍有可能阻止浏览器发送 cookie。此时可以考虑:

SESSION_SECURE_COOKIE=true
SESSION_SAME_SITE=none

SameSite=None 必须与 HTTPS 和 Secure 配合使用,否则现代浏览器通常会拒绝该 cookie。不要为了兼容支付回调而关闭 HTTPS,也不要随意放宽 cookie 策略。

检查域名和 HTTPS 是否一致

支付前后的地址应使用相同的站点形式,不要混用:

https://example.com
https://www.example.com
http://example.com

建议在 .env 中设置唯一的规范地址:

APP_URL=https://example.com
SESSION_DOMAIN=example.com
SESSION_SECURE_COOKIE=true

如果确实需要在主域名与子域名之间共享 cookie,可以根据 Laravel 版本和实际部署情况测试:

SESSION_DOMAIN=.example.com

如果应用始终使用同一个主机名,也可以不设置 SESSION_DOMAIN,让浏览器创建 host-only cookie。这样通常更不容易出现配置错误。

还要检查反向代理、CDN 或 cPanel 的 HTTPS 转发配置,避免 Laravel 将 HTTPS 请求误判为 HTTP。否则,系统生成的 cookie 属性和重定向地址都可能不正确。

支付回调不要依赖登录 session

即使 session 配置没有问题,支付验证接口也不应将「用户仍处于登录状态」作为处理支付结果的必要条件。银行回调可能延迟或重复发送,也可能由银行服务器直接发起请求,这些请求不会携带用户浏览器中的 cookie。

可以将流程拆成两个接口:

Route::match(['get', 'post'], '/payment/callback', [
    PaymentController::class,
    'verify',
])->name('payment.callback');

Route::get('/payment/result/{payment}', [
    PaymentController::class,
    'result',
])->middleware('auth')->name('payment.result');

发起支付前,先在数据库中保存支付记录:

$payment = Payment::create([
    'user_id' => auth()->id(),
    'cart_id' => $cart->id,
    'reference' => (string) Str::uuid(),
    'status' => 'pending',
]);

$returnUrl = route('payment.callback', [
    'reference' => $payment->reference,
]);

return redirect()->away($bankUrl);

收到回调后,根据本地支付编号查找记录,并使用银行提供的签名、交易号或服务器端查询接口验证支付结果:

public function verify(Request $request)
{
    $payment = Payment::where(
        'reference',
        $request->input('reference')
    )->firstOrFail();

    // 按银行文档验证签名、金额、商户号和交易状态。
    $verified = $this->bankGateway->verify($request->all(), $payment);

    if ($verified) {
        $payment->update([
            'status' => 'paid',
            'paid_at' => now(),
        ]);
    }

    if (auth()->check() && auth()->id() === $payment->user_id) {
        return redirect()->route('payment.result', $payment);
    }

    return redirect()->route('login')
        ->with('status', '支付结果已记录,请登录后查看。');
}

reference 只能用于定位本地支付记录,不能单独作为支付成功的依据。系统必须验证银行签名,并核对金额、币种、商户号、订单号及交易状态。回调还需要具备幂等性,防止银行重复通知导致重复扣减库存或重复记账。

修正重定向写法

在控制器中直接返回重定向响应即可:

return redirect()->away($go);

不建议手动调用:

redirect()->to($go)->send();

send() 会立即发送响应,却不会像正常的控制器返回值那样自然结束应用流程,可能导致后续代码或中间件的行为难以判断。

在浏览器开发者工具的 Network 面板中依次检查:

  1. 离开站点前是否存在 laravel_session cookie。
  2. 银行返回后的第一次请求是否携带该 cookie。
  3. cookie 是否因 SameSite、Secure 或 Domain 不匹配而被阻止。
  4. 回调地址是否从 HTTPS 降级为 HTTP。
  5. 支付前后是否切换了 www、子域名或端口。
  6. SESSION_LIFETIME 是否过短,以及服务器时间是否正确。

不要长期将 storage/framework/sessions 的权限设置为 777。只需确保运行 PHP 的用户具有必要的读写权限。过宽的权限既不能解决浏览器 cookie 问题,也会增加安全风险。

备注:内容仅供参考。