Laravel集成Stripe:无法通过Checkout Session支付指定已有发票
解决Stripe指定未结发票支付问题(Laravel环境)
问题背景
- 技术栈:Laravel + Stripe PHP SDK
- 场景:存在一张订阅生成的未结Stripe发票,ID为
in_1PVBjnSE4D7cjIupbEfXsQ4w - 需求:
- 生成客户支付该指定发票的URL
- 客户完成支付后跳转至指定页面
- 问题:跳转功能正常,但原有未结发票未被支付,反而生成了等额新发票
- 尝试方案:用Stripe Checkout Session实现跳转,但无法引导支付指定发票
原错误代码:
public function createCheckoutSession(Request $request, $invoiceId) { $firmId = session('firmdatas.id'); $firmData = Firmregistration::findOrFail($firmId); $invoice = FirmInvoices::findOrFail($invoiceId); $stripeObj = new \Stripe\StripeClient(config('services.stripe.secret')); $subscription = $stripeObj->subscriptions->retrieve($invoice->stripe_subscription_id); try { $session = $stripeObj->checkout->sessions->create([ 'payment_method_types' => ['card'], 'line_items' => [[ 'price_data' => [ 'currency' => 'usd', 'product_data' => [ 'name' => 'Invoice Payment', ], 'unit_amount' => $invoice->amount_due, ], 'quantity' => 1, ]], 'subscription_data' => [ 'items' => [ ['plan' => $subscription->items->data[0]->id], // Assuming only one item in the subscription ], ], 'mode' => 'payment', 'customer' => $firmData->stripe_id, 'payment_intent_data' => [ 'description' => "Payment for Invoice #{$invoice->number}", 'metadata' => [ 'invoice_id' => $invoice->invoice_id, ], ], 'success_url' => route('admin.subscription.invoice.paymentsuccess', [ 'invoiceid' => $invoice->invoice_id, 'amount_paid' => $invoice->amount_due, // Stripe will update the actual amount paid after payment 'receipt_url' => '', // Placeholder, will be updated below ]), 'cancel_url' => route('admin.subscription.view'), ]); // Redirect to the Checkout Session URL return redirect()->away($session->url); } catch (\Exception $e) { Log::error('Error creating Stripe Checkout Session: ' . $e->getMessage()); return view('errors.500'); } }
错误原因
原代码核心问题:
- 使用
mode: 'payment'结合subscription_data,会触发Stripe创建新的订阅周期并生成新发票,而非支付已有未结发票 - 通过
line_items手动构建金额,完全脱离了目标发票的关联逻辑
解决方案
要支付指定的未结发票,需直接基于该发票创建Checkout Session,无需手动构建商品或订阅数据。修改后的代码如下:
public function createCheckoutSession(Request $request, $invoiceId) { $firmId = session('firmdatas.id'); $firmData = Firmregistration::findOrFail($firmId); $invoice = FirmInvoices::findOrFail($invoiceId); $stripeObj = new \Stripe\StripeClient(config('services.stripe.secret')); try { // 验证目标Stripe发票状态为未结(open) $stripeInvoice = $stripeObj->invoices->retrieve($invoice->stripe_invoice_id); if ($stripeInvoice->status !== 'open') { abort(400, '该发票无法支付'); } $session = $stripeObj->checkout->sessions->create([ 'payment_method_types' => ['card'], // 直接关联目标Stripe发票ID 'invoice' => $stripeInvoice->id, 'mode' => 'payment', 'customer' => $firmData->stripe_id, 'success_url' => route('admin.subscription.invoice.paymentsuccess', [ 'invoiceid' => $invoice->invoice_id, 'stripe_invoice_id' => $stripeInvoice->id, ]), 'cancel_url' => route('admin.subscription.view'), ]); return redirect()->away($session->url); } catch (\Exception $e) { Log::error('Error creating Stripe Checkout Session for invoice payment: ' . $e->getMessage()); return view('errors.500'); } }
关键说明
- 发票状态验证:必须确保Stripe发票处于
open状态,已支付/已过期的发票无法创建支付会话 - 关联发票参数:通过
invoice字段直接指定目标Stripe发票ID,Stripe会自动加载发票金额、明细等信息,避免手动构建的误差 - Webhook处理:支付完成后,务必监听Stripe的
invoice.paidwebhook事件,更新本地FirmInvoices的支付状态(如标记为已支付、记录支付时间等) - 客户匹配:确保
customer参数与发票所属客户一致,否则会报错
内容的提问来源于stack exchange,提问作者surya kumar
相关产品推荐
相关产品推荐

