跳转到主要内容

概述

Laravel Cashier (Stripe) 是用于在 Laravel 中使用 Stripe 计费功能的官方扩展包。你可以通过统一的 API 完成订阅的创建、状态检查、取消、单次支付、下载发票以及处理 Webhook。

安装与配置

首先安装 Cashier,并创建所需的数据库表。
composer require laravel/cashier
php artisan vendor:publish --tag="cashier-migrations"
php artisan migrate
然后在 App\Models\User 等计费对象模型上添加 Billable trait。
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Cashier\Billable;

class User extends Authenticatable
{
    use Billable;
}
.env 中配置 Stripe 密钥。
STRIPE_KEY=your-stripe-key
STRIPE_SECRET=your-stripe-secret
STRIPE_WEBHOOK_SECRET=your-stripe-webhook-secret

顾客管理

如果 Stripe Customer 可能还未创建,可以使用 createOrGetStripeCustomer()
$stripeCustomer = $user->createOrGetStripeCustomer();
如果要显式创建,则使用 createAsStripeCustomer()
$stripeCustomer = $user->createAsStripeCustomer();

订阅

新建订阅

通过 newSubscription()create() 开始订阅。 $paymentMethodId 传入从 Stripe.js 等获取到的 Payment Method ID。
$user->newSubscription('default', 'price_monthly')
    ->create($paymentMethodId);
price_monthly 只是示例。实际使用时请传入你在 Stripe 后台创建的 Price ID。

状态检查

使用 subscribed() 可以判断订阅是否有效。
if ($user->subscribed('default')) {
    // Active subscription...
}

取消与恢复

$user->subscription('default')->cancel();

if ($user->subscription('default')->onGracePeriod()) {
    // The user is on the grace period...
}

$user->subscription('default')->resume();

单次支付(Charge)

charge() 可以进行一次性收费。金额以货币的最小单位传入(例如 USD 时,100 代表 $1.00)。 这里 $paymentMethodId 同样使用 Stripe 上创建的 Payment Method ID。
$payment = $user->charge(100, $paymentMethodId);
如果支付失败,charge() 会抛出异常。

Payment Element

使用 Stripe 的 Payment Element,可以统一处理卡、Apple Pay、Google Pay、iDEAL 等多种支付方式。

在订阅中使用

先创建 Setup Intent 并传给视图。
return view('subscribe', [
    'intent' => $user->createSetupIntent()
]);
在 Blade 视图中挂载 Payment Element,并传入 Setup Intent 的 client_secret
<div id="payment-element"></div>
<button id="submit">Subscribe</button>

<script src="https://js.stripe.com/v3/"></script>
<script>
    const stripe = Stripe('stripe-public-key');

    const elements = stripe.elements({
        clientSecret: '{{ $intent->client_secret }}'
    });

    const paymentElement = elements.create('payment');

    paymentElement.mount('#payment-element');

    document.getElementById('submit').addEventListener('click', async () => {
        const { error } = await stripe.confirmSetup({
            elements,
            confirmParams: {
                return_url: '{{ route("subscription.complete") }}',
            },
        });

        if (error) {
            // 向用户展示错误信息...
        }
    });
</script>
Stripe 会在跳转回来的 URL 上附加 setup_intent 参数。使用它获取 Payment Method ID,然后创建订阅。
use Illuminate\Http\Request;

Route::get('/subscription/complete', function (Request $request) {
    $setupIntent = $request->user()->findSetupIntent(
        $request->setup_intent
    );

    $paymentMethod = $setupIntent->payment_method;

    $request->user()
        ->newSubscription('default', 'price_xxx')
        ->create($paymentMethod);

    return redirect('/dashboard');
})->name('subscription.complete');

在单次支付中使用

对于单次支付,使用 pay() 创建 Payment Intent。Order 模型需要包含 user_idamountstatusstripe_payment_intent_id 字段。
use App\Models\Order;
use Illuminate\Http\Request;

Route::post('/pay', function (Request $request) {
    $amount = 1000;

    $payment = $request->user()->pay($amount);

    $order = Order::create([
        'user_id' => $request->user()->id,
        'amount' => $amount,
        'status' => 'pending',
        'stripe_payment_intent_id' => $payment->id,
    ]);

    return view('checkout', [
        'clientSecret' => $payment->client_secret,
        'order' => $order,
    ]);
});
挂载 Payment Element 并确认支付。
<div id="payment-element"></div>
<button id="submit">Pay Now</button>

<script src="https://js.stripe.com/v3/"></script>
<script>
    const stripe = Stripe('stripe-public-key');

    const elements = stripe.elements({
        clientSecret: '{{ $clientSecret }}'
    });

    const paymentElement = elements.create('payment');

    paymentElement.mount('#payment-element');

    document.getElementById('submit').addEventListener('click', async () => {
        const { error } = await stripe.confirmPayment({
            elements,
            confirmParams: {
                return_url: '{{ route("payment.complete") }}',
            },
        });

        if (error) {
            // 向用户展示错误信息...
        }
    });
</script>
跳转回来后,通过 payment_intent 参数查找 Order,并在确认 Payment Intent 属于当前已认证用户且状态为 succeeded 之后再完成订单。
use App\Models\Order;
use Illuminate\Http\Request;

Route::get('/payment/complete', function (Request $request) {
    $order = Order::where('user_id', $request->user()->id)
        ->where('stripe_payment_intent_id', $request->payment_intent)
        ->firstOrFail();

    $paymentIntent = $request->user()
        ->stripe()
        ->paymentIntents
        ->retrieve($request->payment_intent);

    if ($paymentIntent->customer === $request->user()->stripe_id &&
        $paymentIntent->status === 'succeeded') {
        $order->update(['status' => 'paid']);

        // 完成订单的处理...
    }

    return redirect('/dashboard');
})->name('payment.complete');

发票

使用 invoices() 可以获取发票列表。
$invoices = $user->invoices();
下载 PDF 需要先安装 dompdf/dompdf,然后使用 downloadInvoice()$invoiceId 传入通过 invoices() 得到的发票 ID。
composer require dompdf/dompdf
return $user->downloadInvoice($invoiceId);

Webhook 配置

Cashier 会自动为 Stripe Webhook 注册路由,默认使用 /stripe/webhook。请在 Stripe 后台中配置该 URL。 可以通过 cashier:webhook 命令创建 Webhook。
php artisan cashier:webhook
需要将 stripe/* 从 CSRF 保护中排除。
->withMiddleware(function (Middleware $middleware): void {
    $middleware->preventRequestForgery(except: [
        'stripe/*',
    ]);
})
.env 中配置 STRIPE_WEBHOOK_SECRET 后,Cashier 的签名校验中间件就能对 Webhook 请求进行校验。
最后修改于 2026年7月13日