You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

支付方式失效但用户仍订阅的问题修复求助

解决方案

1. 修复取消订阅的500错误

问题根源是用户可能无活跃订阅、订阅已取消,直接调用cancel()会抛出异常。修改代码增加判断和异常处理:

public function cancel()
{
    $user = auth()->user();

    try {
        $subscription = $user->subscription('plan');
        if ($subscription->exists && !$subscription->cancelled()) {
            $subscription->cancel();
            return redirect()->back()->with("success", "Your subscription has been canceled.");
        }
        return redirect()->back()->with("error", "No active subscription to cancel.");
    } catch (\Exception $e) {
        return redirect()->back()->with("error", "Failed to cancel subscription: " . $e->getMessage());
    }
}

2. 修复订阅状态校验逻辑

当前subscribed()仅检查用户是否有订阅记录,不区分是否处于有效活跃状态(比如支付失败导致的过期、欠费状态)。改用active()方法判断真实有效订阅:

public function index()
{
    $user = auth()->user();
    $isActiveSubscriber = $user->subscription('plan')?->active() ?? false;

    // 非有效订阅用户,且有已发布内容则批量设为未发布
    if (!$isActiveSubscriber && $user->memorials->where('status', 'published')->count() > 0) {
        $user->memorials()->where('status', 'published')->update(['status' => 'unpublished']);
    }

    return view('memorials');
}

注:active()返回true的场景包括订阅处于试用期、正常付费期;返回false的场景包括订阅已取消、已过期、支付失败(past_due)。

3. 处理支付失效的自动同步(核心)

Laravel Cashier不会自动同步Stripe端的订阅状态变化,必须配置Stripe Webhook监听事件,实时更新本地状态并处理权限:

步骤a:配置Stripe Webhook

  1. 登录Stripe控制台创建Webhook,添加事件:
    • customer.subscription.updated(订阅状态变更触发)
    • invoice.payment_failed(支付失败触发)
  2. 设置Webhook URL为https://你的域名/stripe/webhook
  3. 获取Webhook签名密钥,写入.env:STRIPE_WEBHOOK_SECRET=你的密钥

步骤b:创建Webhook处理控制器

生成Cashier Webhook控制器:

php artisan cashier:webhook

修改app/Http/Controllers/StripeWebhookController.php:

<?php

namespace App\Http\Controllers;

use Laravel\Cashier\Http\Controllers\WebhookController as CashierController;

class StripeWebhookController extends CashierController
{
    // 处理支付失败事件
    public function handleInvoicePaymentFailed($payload)
    {
        $user = $this->getUserByStripeId($payload['data']['object']['customer']);

        if ($user && !$user->subscription('plan')?->active()) {
            $user->memorials()->where('status', 'published')->update(['status' => 'unpublished']);
            // 可添加用户通知逻辑,提醒更新支付信息
        }

        return $this->successMethod();
    }

    // 处理订阅状态更新事件
    public function handleCustomerSubscriptionUpdated($payload)
    {
        $user = $this->getUserByStripeId($payload['data']['object']['customer']);

        if ($user) {
            $subscription = $user->subscription('plan');
            $subscription->syncStripeSubscription();

            // 订阅失效时下架内容
            if (!$subscription->active()) {
                $user->memorials()->where('status', 'published')->update(['status' => 'unpublished']);
            }
            // 订阅恢复时可自动上架内容(可选)
            else {
                $user->memorials()->where('status', 'unpublished')->update(['status' => 'published']);
            }
        }

        return $this->successMethod();
    }
}

步骤c:注册Webhook路由

在routes/web.php添加:

Route::post('/stripe/webhook', [\App\Http\Controllers\StripeWebhookController::class, 'handleWebhook'])->name('stripe.webhook');

4. 优化订阅创建逻辑(可选)

简化重复代码,提升可读性:

public function subscribe(Request $request)
{
    $user = auth()->user();
    $paymentMethod = $request->payment_method;
    $planId = $request->plan;
    $coupon = $request->coupon;

    match($coupon) {
        'Free60' => $user->newSubscription('plan', $planId)->trialDays(60)->create($paymentMethod),
        '1YrEA' => $user->newSubscription('plan', 'monthly-4_99')->trialDays(365)->create($paymentMethod),
        'LAT-44' => $user->newSubscription('plan', 'monthly-4_99')->withCoupon($coupon)->create($paymentMethod),
        default => $user->newSubscription('plan', $planId)->create($paymentMethod),
    };

    $successMsg = match($planId) {
        'yearly' => 'You are now subscribed to a yearly plan.',
        default => 'You are now subscribed to a monthly plan.'
    };

    if ($planId == 'yearly') {
        $user->update(['free_qrplaque' => 'yes']);
    }

    return response([
        'success_url'=> redirect()->intended('memorials')->with('success', $successMsg)->getTargetUrl()
    ]);
}

内容的提问来源于stack exchange,提问作者Jake Scervino

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 08:31:15