Laravel Cashier(Stripe Checkout)更换银行卡操作步骤咨询
嘿,我刚好处理过类似的场景,用Laravel Cashier搭配Stripe Checkout来更新支付方式其实比你想的简单,而且完美契合你规避合规的需求——毕竟Stripe Checkout会帮你扛下大部分PCI合规的担子,不用自己碰敏感的银行卡信息。下面是一步步的详细操作,跟着来就行:
核心思路先理清
因为你用的是Stripe Checkout(而非Cashier默认的订阅管理页面),所以不能直接调用Cashier的updatePaymentMethod方法。核心逻辑是:通过Stripe Checkout的setup模式让用户在Stripe的安全页面输入新卡,Stripe会自动创建关联的支付方式,我们再通过Webhook回调把这个新支付方式设为用户订阅的默认支付方式。
步骤1:配置路由
先在routes/web.php里加两个路由:一个用来生成Checkout会话,另一个处理Stripe的Webhook回调:
Route::post('/user/update-payment-method', [UserController::class, 'createUpdatePaymentMethodSession']) ->middleware('auth'); Route::post('/stripe/update-payment-method-webhook', [UserController::class, 'handleUpdatePaymentMethodWebhook']) ->name('stripe.update-payment-method.webhook');
步骤2:生成Stripe Checkout会话
在UserController里写生成会话的方法,重点是指定mode为setup(仅用于设置支付方式,不创建订阅),并关联用户的Stripe Customer ID(Cashier已经帮你存在用户表的stripe_id字段里):
use Illuminate\Http\Request; use Laravel\Cashier\Cashier; public function createUpdatePaymentMethodSession(Request $request) { $user = $request->user(); // 如果你只针对订阅用户换卡,加这个判断 if (! $user->subscribed()) { return redirect()->back()->with('error', '你没有活跃的订阅'); } // 创建Stripe Checkout会话 $session = Cashier::stripe()->checkout->sessions->create([ 'mode' => 'setup', 'customer' => $user->stripe_id, 'success_url' => route('dashboard').'?payment_updated=1', // 成功后跳转的页面 'cancel_url' => route('dashboard').'?payment_updated=0', // 取消后跳转的页面 'payment_method_types' => ['card'], 'setup_intent_data' => [ // 存一些元数据,方便后续Webhook里识别用户和订阅 'metadata' => [ 'user_id' => $user->id, 'subscription_id' => $user->subscription()->stripe_id, ], ], ]); // 跳转到Stripe Checkout页面 return redirect()->away($session->url); }
步骤3:处理Stripe Webhook回调
当用户在Stripe页面完成支付方式设置后,Stripe会发送checkout.session.completed事件到你的Webhook地址,我们需要监听这个事件,把新支付方式设为订阅的默认支付方式:
use Stripe\Event; use Laravel\Cashier\Cashier; public function handleUpdatePaymentMethodWebhook(Request $request) { $payload = $request->getContent(); $sigHeader = $request->header('Stripe-Signature'); $webhookSecret = config('cashier.webhook_secret'); // 验证Webhook签名,防止伪造请求 try { $event = Cashier::stripe()->webhooks->constructEvent( $payload, $sigHeader, $webhookSecret ); } catch (\Exception $e) { return response()->json(['error' => $e->getMessage()], 403); } if ($event->type === 'checkout.session.completed') { $session = $event->data->object; // 从元数据里取出用户和订阅信息 $userId = $session->setup_intent->metadata['user_id']; $subscriptionId = $session->setup_intent->metadata['subscription_id']; $newPaymentMethodId = $session->setup_intent->payment_method; $user = \App\Models\User::find($userId); if (! $user) { return response()->json(['error' => '用户不存在'], 404); } $subscription = $user->subscription()->where('stripe_id', $subscriptionId)->first(); if (! $subscription) { return response()->json(['error' => '订阅不存在'], 404); } // 更新订阅的默认支付方式 $subscription->updateDefaultPaymentMethod($newPaymentMethodId); // 可选:删除旧的支付方式(如果需要清理的话) // $oldPaymentMethod = $user->defaultPaymentMethod(); // if ($oldPaymentMethod && $oldPaymentMethod->id !== $newPaymentMethodId) { // Cashier::stripe()->paymentMethods->detach($oldPaymentMethod->id); // } } return response()->json(['status' => 'success']); }
步骤4:前端添加触发按钮
在用户的仪表盘页面加一个按钮,触发上面的POST请求:
<form action="/user/update-payment-method" method="POST"> @csrf <button type="submit" class="btn btn-primary">更换银行卡</button> </form>
测试与注意事项
- 配置Stripe Webhook:登录Stripe仪表盘,添加你的Webhook地址(
https://你的域名/stripe/update-payment-method-webhook),并勾选checkout.session.completed事件,同时把Stripe提供的Webhook密钥填到config/cashier.php的webhook_secret里。 - 测试用卡:用Stripe测试卡
4242 4242 4242 4242(过期日期填未来的月份/年份,CVC随便填3位)来测试流程。 - 非订阅场景适配:如果用户没有订阅,只是想保存支付方式,去掉代码里的订阅相关判断,直接把支付方式关联到用户的Stripe Customer即可。
内容的提问来源于stack exchange,提问作者Jonathan Hyams




