PAYATHON 2026

Laravel 5 subscription middleware 如何避免重定向循环

支付小周

结论

重定向循环的原因是 billing 路由也经过了 subscription middleware。未订阅用户访问 billing 时,中间件会再次执行 redirect('billing'),请求因此反复重定向。

最稳妥的做法是:不要把该中间件应用到 billing 路由。如果暂时无法调整路由结构,也可以在 middleware 中明确放行 billing 请求。

推荐方案:将 billing 排除在 middleware 保护范围之外

只把需要有效订阅才能访问的页面放入路由组,billing 页面仅使用登录认证 middleware:

Route::group(['middleware' => 'auth'], function () {
    Route::get('billing', 'BillingController@index');
    Route::post('billing', 'BillingController@store');

    Route::group(['middleware' => 'subscription'], function () {
        Route::get('dashboard', 'DashboardController@index');
        Route::get('reports', 'ReportController@index');
    });
});

这样处理后:

  • 未登录用户仍由 auth middleware 处理。
  • 已登录但没有有效订阅的用户可以访问 billing 页面。
  • dashboard、reports 等受保护页面只有订阅有效时才能访问。
  • billing 请求不会进入 subscription middleware,也就不会触发循环。

middleware 可以写得更直白一些:

public function handle($request, Closure $next)
{
    if (!\Auth::check()) {
        return $next($request);
    }

    $user = \Auth::user();
    $hospital = $user->hospital;

    $hasActiveSubscription = $hospital
        && $hospital->subscription === 'Active';

    if (!$hasActiveSubscription) {
        return redirect('billing');
    }

    return $next($request);
}

代码会先确认 $hospital 是否存在,避免用户未关联 hospital 时读取属性而引发异常。

备选方案:在 middleware 中放行 billing

如果该 middleware 已注册为全局 middleware,或者现有路由结构无法排除 billing,可以先判断当前请求是否指向 billing:

public function handle($request, Closure $next)
{
    if ($request->is('billing') || $request->is('billing/*')) {
        return $next($request);
    }

    if (\Auth::check()) {
        $user = \Auth::user();
        $hospital = $user->hospital;

        $hasActiveSubscription = $hospital
            && $hospital->subscription === 'Active';

        if (!$hasActiveSubscription) {
            return redirect('billing');
        }
    }

    return $next($request);
}

如果 billing 页面只有一个固定地址,可以简化为:

if ($request->is('billing')) {
    return $next($request);
}

如果还要放行支付回调、更新付款方式等子路径,再加入 billing/*。

原条件中的问题

原来的判断是:

if (
    \Auth::user()->hospital->subscription == null ||
    \Auth::user()->hospital->subscription !== 'Active'
) {
    return redirect('billing');
}

subscription !== 'Active' 已经包含 null 的情况,通常不必再单独判断:

if (\Auth::user()->hospital->subscription !== 'Active') {
    return redirect('billing');
}

不过,需要先确认 subscription 字段保存的确实是字符串 'Active'。如果 subscription 是 Eloquent 关联,返回值就是订阅模型对象,不能直接与字符串比较,而应检查模型中的状态字段。例如:

$subscription = \Auth::user()->hospital->subscription;

if (!$subscription || $subscription->status !== 'Active') {
    return redirect('billing');
}

应采用哪种写法,取决于 subscription 是数据库字段还是 Eloquent 关联。

注意事项

  • billing 页面的表单提交路由也必须放行,否则页面虽然能打开,保存付款信息时仍会被重定向。
  • 支付平台的 webhook 通常不依赖用户登录状态,也不应经过该 subscription middleware。
  • 建议将 middleware 用作路由 middleware,不要注册成全局 middleware,这样更容易控制保护范围。
  • 状态字符串必须与数据库中的实际值完全一致。使用严格比较时,'Active'、'active' 和 'ACTIVE' 是三个不同的值。
  • 如果未登录用户不能继续访问,应交给 auth middleware 统一处理,不要在 subscription middleware 中重复实现登录跳转。

备注:内容仅供参考。