支付方式失效但用户仍订阅的问题修复求助
解决方案
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
- 登录Stripe控制台创建Webhook,添加事件:
customer.subscription.updated(订阅状态变更触发)invoice.payment_failed(支付失败触发)
- 设置Webhook URL为
https://你的域名/stripe/webhook - 获取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
相关产品推荐
相关产品推荐

