跳到內容

Laravel Cashier (Stripe)

簡介

Laravel Cashier StripeStripe 的訂閱計費服務提供富有表現力、流暢的介面。它幾乎處理了所有您不願意的樣板訂閱計費程式碼。除了基本的訂閱管理外,Cashier 還可以處理優惠券、交換訂閱、訂閱「數量」、取消寬限期,甚至產生發票 PDF。

升級 Cashier

升級到新版本的 Cashier 時,請務必仔細查看升級指南

為防止重大變更,Cashier 使用固定的 Stripe API 版本。Cashier 15 使用 Stripe API 版本 2023-10-16。Stripe API 版本將在次要版本更新,以便使用新的 Stripe 功能和改進。

安裝

首先,使用 Composer 套件管理器安裝 Stripe 的 Cashier 套件

1composer require laravel/cashier

安裝套件後,使用 vendor:publish Artisan 命令發布 Cashier 的遷移

1php artisan vendor:publish --tag="cashier-migrations"

然後,遷移您的資料庫

1php artisan migrate

Cashier 的遷移將在您的 users 表格中新增幾個欄位。它們還將建立一個新的 subscriptions 表格來保存您所有客戶的訂閱,以及一個 subscription_items 表格用於具有多個價格的訂閱。

如果您願意,您也可以使用 vendor:publish Artisan 命令發布 Cashier 的設定檔

1php artisan vendor:publish --tag="cashier-config"

最後,為了確保 Cashier 正確處理所有 Stripe 事件,請記得設定 Cashier 的 webhook 處理

Stripe 建議任何用於儲存 Stripe 識別碼的欄位都應區分大小寫。因此,當使用 MySQL 時,您應確保 stripe_id 欄位的欄位定序設定為 utf8_bin。有關此方面的更多資訊,請參閱 Stripe 文件

設定

可計費模型

在使用 Cashier 之前,請將 Billable trait 新增到您的可計費模型定義中。通常,這將是 App\Models\User 模型。此 trait 提供了各種方法,讓您可以執行常見的計費任務,例如建立訂閱、套用優惠券和更新付款方式資訊

1use Laravel\Cashier\Billable;
2 
3class User extends Authenticatable
4{
5 use Billable;
6}

Cashier 假設您的可計費模型將是 Laravel 隨附的 App\Models\User 類別。如果您希望變更此設定,您可以透過 useCustomerModel 方法指定不同的模型。此方法通常應在您的 AppServiceProvider 類別的 boot 方法中呼叫

1use App\Models\Cashier\User;
2use Laravel\Cashier\Cashier;
3 
4/**
5 * Bootstrap any application services.
6 */
7public function boot(): void
8{
9 Cashier::useCustomerModel(User::class);
10}

如果您使用的是 Laravel 提供的 App\Models\User 模型以外的模型,則需要發布和更改提供的Cashier 遷移,以符合您的替代模型的表格名稱。

API 金鑰

接下來,您應該在應用程式的 .env 檔案中設定您的 Stripe API 金鑰。您可以從 Stripe 控制面板檢索您的 Stripe API 金鑰

1STRIPE_KEY=your-stripe-key
2STRIPE_SECRET=your-stripe-secret
3STRIPE_WEBHOOK_SECRET=your-stripe-webhook-secret

您應確保在應用程式的 .env 檔案中定義了 STRIPE_WEBHOOK_SECRET 環境變數,因為此變數用於確保傳入的 webhook 確實來自 Stripe。

貨幣設定

預設的 Cashier 貨幣是美元 (USD)。您可以透過在應用程式的 .env 檔案中設定 CASHIER_CURRENCY 環境變數來變更預設貨幣

1CASHIER_CURRENCY=eur

除了設定 Cashier 的貨幣外,您還可以指定在發票上顯示貨幣值時要使用的地區設定。在內部,Cashier 使用 PHP 的 NumberFormatter 類別 來設定貨幣地區設定

1CASHIER_CURRENCY_LOCALE=nl_BE

為了使用 en 以外的地區設定,請確保在您的伺服器上安裝並設定了 ext-intl PHP 擴充功能。

稅務設定

感謝 Stripe Tax,可以自動計算 Stripe 產生的所有發票的稅金。您可以透過在應用程式的 App\Providers\AppServiceProvider 類別的 boot 方法中調用 calculateTaxes 方法來啟用自動稅金計算

1use Laravel\Cashier\Cashier;
2 
3/**
4 * Bootstrap any application services.
5 */
6public function boot(): void
7{
8 Cashier::calculateTaxes();
9}

啟用稅金計算後,任何新訂閱和產生的任何一次性發票都將收到自動稅金計算。

為了使此功能正常運作,您客戶的帳單詳細資訊(例如客戶的姓名、地址和稅務 ID)需要同步到 Stripe。您可以使用 Cashier 提供的客戶資料同步稅務 ID 方法來完成此操作。

記錄

Cashier 允許您指定在記錄嚴重 Stripe 錯誤時要使用的記錄通道。您可以透過在應用程式的 .env 檔案中定義 CASHIER_LOGGER 環境變數來指定記錄通道

1CASHIER_LOGGER=stack

透過 API 呼叫 Stripe 產生的例外狀況將透過應用程式的預設記錄通道記錄。

使用自訂模型

您可以透過定義自己的模型並擴充相應的 Cashier 模型,自由擴充 Cashier 內部使用的模型

1use Laravel\Cashier\Subscription as CashierSubscription;
2 
3class Subscription extends CashierSubscription
4{
5 // ...
6}

定義模型後,您可以指示 Cashier 透過 Laravel\Cashier\Cashier 類別使用您的自訂模型。通常,您應該在應用程式的 App\Providers\AppServiceProvider 類別的 boot 方法中告知 Cashier 您的自訂模型

1use App\Models\Cashier\Subscription;
2use App\Models\Cashier\SubscriptionItem;
3 
4/**
5 * Bootstrap any application services.
6 */
7public function boot(): void
8{
9 Cashier::useSubscriptionModel(Subscription::class);
10 Cashier::useSubscriptionItemModel(SubscriptionItem::class);
11}

快速入門

銷售產品

在使用 Stripe Checkout 之前,您應該在 Stripe 儀表板中定義具有固定價格的產品。此外,您應該設定 Cashier 的 webhook 處理

透過您的應用程式提供產品和訂閱計費可能會令人望而生畏。但是,感謝 Cashier 和 Stripe Checkout,您可以輕鬆建立現代、穩健的付款整合。

為了向客戶收取非週期性、單次收費產品的費用,我們將使用 Cashier 將客戶導向 Stripe Checkout,他們將在其中提供付款詳細資訊並確認購買。透過 Checkout 付款後,客戶將被重新導向到您在應用程式中選擇的成功 URL

1use Illuminate\Http\Request;
2 
3Route::get('/checkout', function (Request $request) {
4 $stripePriceId = 'price_deluxe_album';
5 
6 $quantity = 1;
7 
8 return $request->user()->checkout([$stripePriceId => $quantity], [
9 'success_url' => route('checkout-success'),
10 'cancel_url' => route('checkout-cancel'),
11 ]);
12})->name('checkout');
13 
14Route::view('/checkout/success', 'checkout.success')->name('checkout-success');
15Route::view('/checkout/cancel', 'checkout.cancel')->name('checkout-cancel');

如您在上面的範例中所見,我們將使用 Cashier 提供的 checkout 方法將客戶重新導向到 Stripe Checkout 以取得給定的「價格識別碼」。使用 Stripe 時,「價格」是指 特定產品的已定義價格

如有必要,checkout 方法將自動在 Stripe 中建立客戶,並將該 Stripe 客戶記錄連接到應用程式資料庫中的相應使用者。完成結帳會話後,客戶將被重新導向到專用的成功或取消頁面,您可以在其中向客戶顯示資訊訊息。

向 Stripe Checkout 提供元資料

在銷售產品時,通常會透過您自己的應用程式定義的 CartOrder 模型來追蹤已完成的訂單和購買的產品。當將客戶重新導向到 Stripe Checkout 以完成購買時,您可能需要提供現有的訂單識別碼,以便在客戶重新導向回您的應用程式時,您可以將已完成的購買與相應的訂單相關聯。

為了完成此操作,您可以向 checkout 方法提供 metadata 陣列。讓我們想像一下,當使用者開始結帳流程時,會在我們的應用程式中建立待處理的 Order。請記住,此範例中的 CartOrder 模型僅為說明用途,並非由 Cashier 提供。您可以根據自己應用程式的需求自由實作這些概念

1use App\Models\Cart;
2use App\Models\Order;
3use Illuminate\Http\Request;
4 
5Route::get('/cart/{cart}/checkout', function (Request $request, Cart $cart) {
6 $order = Order::create([
7 'cart_id' => $cart->id,
8 'price_ids' => $cart->price_ids,
9 'status' => 'incomplete',
10 ]);
11 
12 return $request->user()->checkout($order->price_ids, [
13 'success_url' => route('checkout-success').'?session_id={CHECKOUT_SESSION_ID}',
14 'cancel_url' => route('checkout-cancel'),
15 'metadata' => ['order_id' => $order->id],
16 ]);
17})->name('checkout');

如您在上面的範例中所見,當使用者開始結帳流程時,我們會將所有購物車/訂單關聯的 Stripe 價格識別碼提供給 checkout 方法。當然,您的應用程式負責在客戶新增這些項目時將這些項目與「購物車」或訂單相關聯。我們也透過 metadata 陣列向 Stripe Checkout 會話提供訂單的 ID。最後,我們已將 CHECKOUT_SESSION_ID 模板變數新增至 Checkout 成功路由。當 Stripe 將客戶重新導向回您的應用程式時,此模板變數將自動填入 Checkout 會話 ID。

接下來,讓我們建立 Checkout 成功路由。這是使用者透過 Stripe Checkout 完成購買後將被重新導向到的路由。在此路由中,我們可以檢索 Stripe Checkout 會話 ID 和關聯的 Stripe Checkout 實例,以便存取我們提供的元資料並相應地更新客戶的訂單

1use App\Models\Order;
2use Illuminate\Http\Request;
3use Laravel\Cashier\Cashier;
4 
5Route::get('/checkout/success', function (Request $request) {
6 $sessionId = $request->get('session_id');
7 
8 if ($sessionId === null) {
9 return;
10 }
11 
12 $session = Cashier::stripe()->checkout->sessions->retrieve($sessionId);
13 
14 if ($session->payment_status !== 'paid') {
15 return;
16 }
17 
18 $orderId = $session['metadata']['order_id'] ?? null;
19 
20 $order = Order::findOrFail($orderId);
21 
22 $order->update(['status' => 'completed']);
23 
24 return view('checkout-success', ['order' => $order]);
25})->name('checkout-success');

有關 Checkout 會話物件包含的資料 的更多資訊,請參閱 Stripe 的文件。

銷售訂閱

在使用 Stripe Checkout 之前,您應該在 Stripe 儀表板中定義具有固定價格的產品。此外,您應該設定 Cashier 的 webhook 處理

透過您的應用程式提供產品和訂閱計費可能會令人望而生畏。但是,感謝 Cashier 和 Stripe Checkout,您可以輕鬆建立現代、穩健的付款整合。

若要了解如何使用 Cashier 和 Stripe Checkout 銷售訂閱,讓我們考慮一個簡單的訂閱服務情境,該服務具有基本月度 (price_basic_monthly) 和年度 (price_basic_yearly) 方案。這兩種價格可以歸類在我們 Stripe 儀表板中的「基本」產品 (pro_basic) 下。此外,我們的訂閱服務可能會提供專家方案作為 pro_expert

首先,讓我們探索客戶如何訂閱我們的服務。當然,您可以想像客戶可能會在我們應用程式的定價頁面上點擊「訂閱」按鈕以選擇基本方案。此按鈕或連結應將使用者導向至 Laravel 路由,該路由會為他們選擇的方案建立 Stripe Checkout 會話

1use Illuminate\Http\Request;
2 
3Route::get('/subscription-checkout', function (Request $request) {
4 return $request->user()
5 ->newSubscription('default', 'price_basic_monthly')
6 ->trialDays(5)
7 ->allowPromotionCodes()
8 ->checkout([
9 'success_url' => route('your-success-route'),
10 'cancel_url' => route('your-cancel-route'),
11 ]);
12});

如您在上面的範例中所見,我們會將客戶重新導向到 Stripe Checkout 會話,該會話將允許他們訂閱我們的基本方案。成功結帳或取消後,客戶將被重新導向回我們提供給 checkout 方法的 URL。若要了解他們的訂閱何時實際開始(因為某些付款方式需要幾秒鐘才能處理),我們還需要設定 Cashier 的 webhook 處理

現在客戶可以開始訂閱了,我們需要限制應用程式的某些部分,以便只有訂閱的使用者才能存取它們。當然,我們始終可以透過 Cashier 的 Billable trait 提供的 subscribed 方法來判斷使用者目前的訂閱狀態

1@if ($user->subscribed())
2 <p>You are subscribed.</p>
3@endif

我們甚至可以輕鬆判斷使用者是否訂閱了特定產品或價格

1@if ($user->subscribedToProduct('pro_basic'))
2 <p>You are subscribed to our Basic product.</p>
3@endif
4 
5@if ($user->subscribedToPrice('price_basic_monthly'))
6 <p>You are subscribed to our monthly Basic plan.</p>
7@endif

建立訂閱中介層

為了方便起見,您可能希望建立一個中介層,以判斷傳入的請求是否來自訂閱使用者。定義此中介層後,您可以輕鬆地將其指派給路由,以防止未訂閱的使用者存取該路由

1<?php
2 
3namespace App\Http\Middleware;
4 
5use Closure;
6use Illuminate\Http\Request;
7use Symfony\Component\HttpFoundation\Response;
8 
9class Subscribed
10{
11 /**
12 * Handle an incoming request.
13 */
14 public function handle(Request $request, Closure $next): Response
15 {
16 if (! $request->user()?->subscribed()) {
17 // Redirect user to billing page and ask them to subscribe...
18 return redirect('/billing');
19 }
20 
21 return $next($request);
22 }
23}

定義中介層後,您可以將其指派給路由

1use App\Http\Middleware\Subscribed;
2 
3Route::get('/dashboard', function () {
4 // ...
5})->middleware([Subscribed::class]);

允許客戶管理其帳單方案

當然,客戶可能想要將其訂閱方案變更為其他產品或「層級」。允許這樣做的最簡單方法是將客戶導向 Stripe 的 客戶帳單入口網站,該入口網站提供託管的使用者介面,允許客戶下載發票、更新付款方式和變更訂閱方案。

首先,在您的應用程式中定義一個連結或按鈕,將使用者導向至 Laravel 路由,我們將使用該路由來啟動帳單入口網站會話

1<a href="{{ route('billing') }}">
2 Billing
3</a>

接下來,讓我們定義啟動 Stripe 客戶帳單入口網站會話並將使用者重新導向至入口網站的路由。redirectToBillingPortal 方法接受使用者在退出入口網站時應返回的 URL

1use Illuminate\Http\Request;
2 
3Route::get('/billing', function (Request $request) {
4 return $request->user()->redirectToBillingPortal(route('dashboard'));
5})->middleware(['auth'])->name('billing');

只要您設定了 Cashier 的 webhook 處理,Cashier 就會透過檢查來自 Stripe 的傳入 webhook 自動保持應用程式的 Cashier 相關資料庫表格同步。因此,舉例來說,當使用者透過 Stripe 的客戶帳單入口網站取消訂閱時,Cashier 將收到相應的 webhook,並在您的應用程式資料庫中將訂閱標記為「已取消」。

客戶

檢索客戶

您可以使用 Cashier::findBillable 方法依 Stripe ID 檢索客戶。此方法將傳回可計費模型的實例

1use Laravel\Cashier\Cashier;
2 
3$user = Cashier::findBillable($stripeId);

建立客戶

有時,您可能希望在不開始訂閱的情況下建立 Stripe 客戶。您可以使用 createAsStripeCustomer 方法完成此操作

1$stripeCustomer = $user->createAsStripeCustomer();

在 Stripe 中建立客戶後,您可以稍後開始訂閱。您可以提供選用的 $options 陣列,以傳遞 Stripe API 支援的任何其他客戶建立參數

1$stripeCustomer = $user->createAsStripeCustomer($options);

如果您想要傳回可計費模型的 Stripe 客戶物件,可以使用 asStripeCustomer 方法

1$stripeCustomer = $user->asStripeCustomer();

如果您想要檢索給定可計費模型的 Stripe 客戶物件,但不確定可計費模型是否已是 Stripe 中的客戶,則可以使用 createOrGetStripeCustomer 方法。如果 Stripe 中尚不存在客戶,此方法將在 Stripe 中建立一個新客戶

1$stripeCustomer = $user->createOrGetStripeCustomer();

更新客戶

有時,您可能希望直接使用其他資訊更新 Stripe 客戶。您可以使用 updateStripeCustomer 方法完成此操作。此方法接受 Stripe API 支援的客戶更新選項陣列

1$stripeCustomer = $user->updateStripeCustomer($options);

餘額

Stripe 允許您貸記或借記客戶的「餘額」。稍後,此餘額將在新的發票中貸記或借記。若要檢查客戶的總餘額,您可以使用可計費模型上提供的 balance 方法。balance 方法將傳回客戶貨幣中餘額的格式化字串表示

1$balance = $user->balance();

若要貸記客戶的餘額,您可以向 creditBalance 方法提供一個值。如果您願意,也可以提供描述

1$user->creditBalance(500, 'Premium customer top-up.');

debitBalance 方法提供值將借記客戶的餘額

1$user->debitBalance(300, 'Bad usage penalty.');

applyBalance 方法將為客戶建立新的客戶餘額交易。您可以使用 balanceTransactions 方法檢索這些交易記錄,這可能有助於提供客戶審閱的貸項和借項記錄

1// Retrieve all transactions...
2$transactions = $user->balanceTransactions();
3 
4foreach ($transactions as $transaction) {
5 // Transaction amount...
6 $amount = $transaction->amount(); // $2.31
7 
8 // Retrieve the related invoice when available...
9 $invoice = $transaction->invoice();
10}

稅務 ID

Cashier 提供了一種管理客戶稅務 ID 的簡單方法。例如,taxIds 方法可用於檢索指派給客戶的所有 稅務 ID 作為集合

1$taxIds = $user->taxIds();

您也可以依識別碼檢索客戶的特定稅務 ID

1$taxId = $user->findTaxId('txi_belgium');

您可以透過向 createTaxId 方法提供有效的 類型 和值來建立新的稅務 ID

1$taxId = $user->createTaxId('eu_vat', 'BE0123456789');

createTaxId 方法會立即將增值稅 ID 新增至客戶的帳戶。增值稅 ID 的驗證也由 Stripe 完成;但是,這是一個非同步程序。您可以透過訂閱 customer.tax_id.updated webhook 事件並檢查 增值稅 ID 的 verification 參數 來接收驗證更新的通知。有關處理 webhook 的更多資訊,請參閱有關定義 webhook 處理器的文件

您可以使用 deleteTaxId 方法刪除稅務 ID

1$user->deleteTaxId('txi_belgium');

與 Stripe 同步客戶資料

通常,當應用程式的使用者更新其姓名、電子郵件地址或 Stripe 也儲存的其他資訊時,您應將更新告知 Stripe。這樣做,Stripe 的資訊副本將與您的應用程式同步。

為了自動化此操作,您可以在可計費模型上定義一個事件接聽器,以回應模型的 updated 事件。然後,在您的事件接聽器中,您可以調用模型上的 syncStripeCustomerDetails 方法

1use App\Models\User;
2use function Illuminate\Events\queueable;
3 
4/**
5 * The "booted" method of the model.
6 */
7protected static function booted(): void
8{
9 static::updated(queueable(function (User $customer) {
10 if ($customer->hasStripeId()) {
11 $customer->syncStripeCustomerDetails();
12 }
13 }));
14}

現在,每次更新您的客戶模型時,其資訊都會與 Stripe 同步。為了方便起見,Cashier 將在首次建立客戶時自動將您的客戶資訊與 Stripe 同步。

您可以透過覆寫 Cashier 提供的各種方法來自訂用於將客戶資訊同步到 Stripe 的欄位。例如,您可以覆寫 stripeName 方法來自訂在 Cashier 將客戶資訊同步到 Stripe 時應視為客戶「姓名」的屬性

1/**
2 * Get the customer name that should be synced to Stripe.
3 */
4public function stripeName(): string|null
5{
6 return $this->company_name;
7}

同樣地,您可以覆寫 stripeEmailstripePhonestripeAddressstripePreferredLocales 方法。當更新 Stripe 客戶物件時,這些方法會將資訊同步到其對應的客戶參數。如果您希望完全控制客戶資訊同步程序,您可以覆寫 syncStripeCustomerDetails 方法。

帳單入口網站

Stripe 提供一種設定帳單入口網站的簡單方法,以便您的客戶可以管理其訂閱、付款方式並查看其帳單歷史記錄。您可以透過從控制器或路由調用可計費模型上的 redirectToBillingPortal 方法,將使用者重新導向至帳單入口網站

1use Illuminate\Http\Request;
2 
3Route::get('/billing-portal', function (Request $request) {
4 return $request->user()->redirectToBillingPortal();
5});

預設情況下,當使用者完成管理其訂閱時,他們將能夠透過 Stripe 帳單入口網站中的連結返回應用程式的 home 路由。您可以透過將 URL 作為引數傳遞給 redirectToBillingPortal 方法,來提供使用者應返回的自訂 URL

1use Illuminate\Http\Request;
2 
3Route::get('/billing-portal', function (Request $request) {
4 return $request->user()->redirectToBillingPortal(route('billing'));
5});

如果您想要產生帳單入口網站的 URL,而不產生 HTTP 重新導向回應,您可以調用 billingPortalUrl 方法

1$url = $request->user()->billingPortalUrl(route('billing'));

付款方式

儲存付款方式

為了使用 Stripe 建立訂閱或執行「一次性」收費,您需要儲存付款方式並從 Stripe 檢索其識別碼。用於完成此操作的方法會因您是否計劃將付款方式用於訂閱或單次收費而異,因此我們將在下面檢查兩者。

訂閱的付款方式

當儲存客戶的信用卡資訊以供訂閱將來使用時,必須使用 Stripe「設定意圖」API 來安全地收集客戶的付款方式詳細資訊。「設定意圖」向 Stripe 指示收取客戶付款方式費用的意圖。Cashier 的 Billable trait 包含 createSetupIntent 方法,可輕鬆建立新的設定意圖。您應該從將呈現表單以收集客戶付款方式詳細資訊的路由或控制器調用此方法

1return view('update-payment-method', [
2 'intent' => $user->createSetupIntent()
3]);

在您建立設定意圖並將其傳遞到視圖後,您應將其密碼附加到將收集付款方式的元素。例如,請考慮此「更新付款方式」表單

1<input id="card-holder-name" type="text">
2 
3<!-- Stripe Elements Placeholder -->
4<div id="card-element"></div>
5 
6<button id="card-button" data-secret="{{ $intent->client_secret }}">
7 Update Payment Method
8</button>

接下來,可以使用 Stripe.js 程式庫將 Stripe Element 附加到表單,並安全地收集客戶的付款詳細資訊

1<script src="https://js.stripe.com/v3/"></script>
2 
3<script>
4 const stripe = Stripe('stripe-public-key');
5 
6 const elements = stripe.elements();
7 const cardElement = elements.create('card');
8 
9 cardElement.mount('#card-element');
10</script>

接下來,可以使用 Stripe 的 confirmCardSetup 方法 來驗證卡片,並從 Stripe 檢索安全的「付款方式識別碼」

1const cardHolderName = document.getElementById('card-holder-name');
2const cardButton = document.getElementById('card-button');
3const clientSecret = cardButton.dataset.secret;
4 
5cardButton.addEventListener('click', async (e) => {
6 const { setupIntent, error } = await stripe.confirmCardSetup(
7 clientSecret, {
8 payment_method: {
9 card: cardElement,
10 billing_details: { name: cardHolderName.value }
11 }
12 }
13 );
14 
15 if (error) {
16 // Display "error.message" to the user...
17 } else {
18 // The card has been verified successfully...
19 }
20});

在 Stripe 驗證卡片後,您可以將產生的 setupIntent.payment_method 識別碼傳遞到您的 Laravel 應用程式,在其中可以將其附加到客戶。可以將付款方式新增為新的付款方式,或用於更新預設付款方式。您也可以立即使用付款方式識別碼來建立新的訂閱

如果您想了解有關設定意圖和收集客戶付款詳細資訊的更多資訊,請查看 Stripe 提供的此概述

單次收費的付款方式

當然,當對客戶的付款方式進行單次收費時,我們只需要使用一次付款方式識別碼。由於 Stripe 的限制,您可能無法將客戶的已儲存預設付款方式用於單次收費。您必須允許客戶使用 Stripe.js 程式庫輸入其付款方式詳細資訊。例如,請考慮以下表單

1<input id="card-holder-name" type="text">
2 
3<!-- Stripe Elements Placeholder -->
4<div id="card-element"></div>
5 
6<button id="card-button">
7 Process Payment
8</button>

在定義此類表單後,可以使用 Stripe.js 程式庫將 Stripe Element 附加到表單,並安全地收集客戶的付款詳細資訊

1<script src="https://js.stripe.com/v3/"></script>
2 
3<script>
4 const stripe = Stripe('stripe-public-key');
5 
6 const elements = stripe.elements();
7 const cardElement = elements.create('card');
8 
9 cardElement.mount('#card-element');
10</script>

接下來,可以使用 Stripe 的 createPaymentMethod 方法 來驗證卡片,並從 Stripe 檢索安全的「付款方式識別碼」

1const cardHolderName = document.getElementById('card-holder-name');
2const cardButton = document.getElementById('card-button');
3 
4cardButton.addEventListener('click', async (e) => {
5 const { paymentMethod, error } = await stripe.createPaymentMethod(
6 'card', cardElement, {
7 billing_details: { name: cardHolderName.value }
8 }
9 );
10 
11 if (error) {
12 // Display "error.message" to the user...
13 } else {
14 // The card has been verified successfully...
15 }
16});

如果卡片驗證成功,您可以將 paymentMethod.id 傳遞到您的 Laravel 應用程式並處理單次收費

檢索付款方式

可計費模型實例上的 paymentMethods 方法傳回 Laravel\Cashier\PaymentMethod 實例的集合

1$paymentMethods = $user->paymentMethods();

預設情況下,此方法將傳回所有類型的付款方式。若要檢索特定類型的付款方式,您可以將 type 作為引數傳遞給此方法

1$paymentMethods = $user->paymentMethods('sepa_debit');

若要檢索顧客的預設付款方式,可以使用 defaultPaymentMethod 方法

1$paymentMethod = $user->defaultPaymentMethod();

您可以使用 findPaymentMethod 方法,檢索附加至可計費模型的特定付款方式

1$paymentMethod = $user->findPaymentMethod($paymentMethodId);

付款方式存在性

若要判斷可計費模型是否已附加預設付款方式至其帳戶,請調用 hasDefaultPaymentMethod 方法

1if ($user->hasDefaultPaymentMethod()) {
2 // ...
3}

您可以使用 hasPaymentMethod 方法,判斷可計費模型是否已至少附加一種付款方式至其帳戶

1if ($user->hasPaymentMethod()) {
2 // ...
3}

此方法將判斷可計費模型是否完全沒有付款方式。若要判斷模型是否存在特定類型的付款方式,您可以將 type 作為引數傳遞給此方法

1if ($user->hasPaymentMethod('sepa_debit')) {
2 // ...
3}

更新預設付款方式

updateDefaultPaymentMethod 方法可用於更新顧客的預設付款方式資訊。此方法接受 Stripe 付款方式識別碼,並將新的付款方式指定為預設帳單付款方式

1$user->updateDefaultPaymentMethod($paymentMethod);

若要將您的預設付款方式資訊與 Stripe 中顧客的預設付款方式資訊同步,您可以使用 updateDefaultPaymentMethodFromStripe 方法

1$user->updateDefaultPaymentMethodFromStripe();

顧客的預設付款方式僅能用於開立發票和建立新的訂閱。由於 Stripe 的限制,預設付款方式可能無法用於單次扣款。

新增付款方式

若要新增付款方式,您可以在可計費模型上調用 addPaymentMethod 方法,並傳遞付款方式識別碼

1$user->addPaymentMethod($paymentMethod);

若要瞭解如何檢索付款方式識別碼,請查閱付款方式儲存文件

刪除付款方式

若要刪除付款方式,您可以在您想要刪除的 Laravel\Cashier\PaymentMethod 實例上調用 delete 方法

1$paymentMethod->delete();

deletePaymentMethod 方法將從可計費模型中刪除特定的付款方式

1$user->deletePaymentMethod('pm_visa');

deletePaymentMethods 方法將刪除可計費模型的所有付款方式資訊

1$user->deletePaymentMethods();

預設情況下,此方法將刪除所有類型的付款方式。若要刪除特定類型的付款方式,您可以將 type 作為引數傳遞給此方法

1$user->deletePaymentMethods('sepa_debit');

如果使用者有有效的訂閱,您的應用程式不應允許他們刪除其預設付款方式。

訂閱

訂閱提供了一種為您的顧客設定定期付款的方式。由 Cashier 管理的 Stripe 訂閱支援多種訂閱價格、訂閱數量、試用期等等。

建立訂閱

若要建立訂閱,首先檢索您的可計費模型實例,這通常會是 App\Models\User 的實例。一旦您檢索到模型實例,您可以使用 newSubscription 方法來建立模型的訂閱

1use Illuminate\Http\Request;
2 
3Route::post('/user/subscribe', function (Request $request) {
4 $request->user()->newSubscription(
5 'default', 'price_monthly'
6 )->create($request->paymentMethodId);
7 
8 // ...
9});

傳遞給 newSubscription 方法的第一個引數應該是訂閱的內部類型。如果您的應用程式僅提供單一訂閱,您可以將其命名為 defaultprimary。此訂閱類型僅供內部應用程式使用,不應向使用者顯示。此外,它不應包含空格,並且在建立訂閱後絕不應更改。第二個引數是使用者訂閱的特定價格。此值應對應於 Stripe 中的價格識別碼。

create 方法接受Stripe 付款方式識別碼或 Stripe PaymentMethod 物件,將開始訂閱,並使用可計費模型的 Stripe 顧客 ID 和其他相關帳單資訊更新您的資料庫。

直接將付款方式識別碼傳遞給 create 訂閱方法也會自動將其新增至使用者的已儲存付款方式。

透過發票電子郵件收取定期付款

您可以指示 Stripe 每次顧客的定期付款到期時,都向顧客發送發票電子郵件,而不是自動收取顧客的定期付款。然後,顧客可以在收到發票後手動付款。透過發票收取定期付款時,顧客無需預先提供付款方式

1$user->newSubscription('default', 'price_monthly')->createAndSendInvoice();

顧客在訂閱被取消之前必須支付發票的時間長度由 days_until_due 選項決定。預設情況下,此值為 30 天;但是,如果您願意,可以為此選項提供特定值

1$user->newSubscription('default', 'price_monthly')->createAndSendInvoice([], [
2 'days_until_due' => 30
3]);

數量

如果您想在建立訂閱時為價格設定特定的數量,您應該在建立訂閱之前調用訂閱建立器上的 quantity 方法

1$user->newSubscription('default', 'price_monthly')
2 ->quantity(5)
3 ->create($paymentMethod);

其他詳細資訊

如果您想指定 Stripe 支援的其他顧客訂閱選項,您可以將它們作為第二個和第三個引數傳遞給 create 方法

1$user->newSubscription('default', 'price_monthly')->create($paymentMethod, [
2 'email' => $email,
3], [
4 'metadata' => ['note' => 'Some extra information.'],
5]);

優惠券

如果您想在建立訂閱時套用優惠券,您可以使用 withCoupon 方法

1$user->newSubscription('default', 'price_monthly')
2 ->withCoupon('code')
3 ->create($paymentMethod);

或者,如果您想套用Stripe 促銷代碼,您可以使用 withPromotionCode 方法

1$user->newSubscription('default', 'price_monthly')
2 ->withPromotionCode('promo_code_id')
3 ->create($paymentMethod);

給定的促銷代碼 ID 應為指派給促銷代碼的 Stripe API ID,而不是面向顧客的促銷代碼。如果您需要根據給定的面向顧客的促銷代碼查找促銷代碼 ID,您可以使用 findPromotionCode 方法

1// Find a promotion code ID by its customer facing code...
2$promotionCode = $user->findPromotionCode('SUMMERSALE');
3 
4// Find an active promotion code ID by its customer facing code...
5$promotionCode = $user->findActivePromotionCode('SUMMERSALE');

在上面的範例中,傳回的 $promotionCode 物件是 Laravel\Cashier\PromotionCode 的實例。此類別裝飾底層的 Stripe\PromotionCode 物件。您可以透過調用 coupon 方法來檢索與促銷代碼相關的優惠券

1$coupon = $user->findPromotionCode('SUMMERSALE')->coupon();

優惠券實例允許您確定折扣金額,以及優惠券代表固定折扣還是百分比折扣

1if ($coupon->isPercentage()) {
2 return $coupon->percentOff().'%'; // 21.5%
3} else {
4 return $coupon->amountOff(); // $5.99
5}

您也可以檢索目前套用至顧客或訂閱的折扣

1$discount = $billable->discount();
2 
3$discount = $subscription->discount();

傳回的 Laravel\Cashier\Discount 實例裝飾底層的 Stripe\Discount 物件實例。您可以透過調用 coupon 方法來檢索與此折扣相關的優惠券

1$coupon = $subscription->discount()->coupon();

如果您想將新的優惠券或促銷代碼套用至顧客或訂閱,您可以透過 applyCouponapplyPromotionCode 方法來執行此操作

1$billable->applyCoupon('coupon_id');
2$billable->applyPromotionCode('promotion_code_id');
3 
4$subscription->applyCoupon('coupon_id');
5$subscription->applyPromotionCode('promotion_code_id');

請記住,您應該使用指派給促銷代碼的 Stripe API ID,而不是面向顧客的促銷代碼。在給定時間,只能將一個優惠券或促銷代碼套用至顧客或訂閱。

有關此主題的更多資訊,請查閱 Stripe 文件中關於優惠券促銷代碼的說明。

新增訂閱

如果您想為已擁有預設付款方式的顧客新增訂閱,您可以調用訂閱建立器上的 add 方法

1use App\Models\User;
2 
3$user = User::find(1);
4 
5$user->newSubscription('default', 'price_monthly')->add();

從 Stripe 控制面板建立訂閱

您也可以從 Stripe 控制面板本身建立訂閱。這樣做時,Cashier 將同步新新增的訂閱,並將其類型指派為 default。若要自訂指派給控制面板建立的訂閱的訂閱類型,請定義 webhook 事件處理器

此外,您只能透過 Stripe 控制面板建立一種訂閱類型。如果您的應用程式提供多種使用不同類型的訂閱,則只能透過 Stripe 控制面板新增一種訂閱類型。

最後,您應始終確保每個應用程式提供的訂閱類型僅新增一個有效訂閱。如果顧客有兩個 default 訂閱,即使兩者都將與您的應用程式資料庫同步,Cashier 也只會使用最近新增的訂閱。

檢查訂閱狀態

一旦顧客訂閱了您的應用程式,您可以輕鬆地使用各種便捷的方法檢查其訂閱狀態。首先,如果顧客擁有有效訂閱,即使訂閱目前處於試用期內,subscribed 方法也會傳回 truesubscribed 方法將訂閱類型作為其第一個引數接受

1if ($user->subscribed('default')) {
2 // ...
3}

subscribed 方法也是路由中介層的絕佳候選,允許您根據使用者的訂閱狀態篩選對路由和控制器的存取

1<?php
2 
3namespace App\Http\Middleware;
4 
5use Closure;
6use Illuminate\Http\Request;
7use Symfony\Component\HttpFoundation\Response;
8 
9class EnsureUserIsSubscribed
10{
11 /**
12 * Handle an incoming request.
13 *
14 * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
15 */
16 public function handle(Request $request, Closure $next): Response
17 {
18 if ($request->user() && ! $request->user()->subscribed('default')) {
19 // This user is not a paying customer...
20 return redirect('/billing');
21 }
22 
23 return $next($request);
24 }
25}

如果您想判斷使用者是否仍在試用期內,您可以使用 onTrial 方法。此方法可用於判斷是否應向使用者顯示他們仍在試用期內的警告

1if ($user->subscription('default')->onTrial()) {
2 // ...
3}

subscribedToProduct 方法可用於根據給定的 Stripe 產品識別碼判斷使用者是否訂閱了給定的產品。在 Stripe 中,產品是價格的集合。在此範例中,我們將判斷使用者的 default 訂閱是否有效訂閱了應用程式的「premium」產品。給定的 Stripe 產品識別碼應對應於您的產品在 Stripe 控制面板中的其中一個產品識別碼

1if ($user->subscribedToProduct('prod_premium', 'default')) {
2 // ...
3}

透過將陣列傳遞給 subscribedToProduct 方法,您可以判斷使用者的 default 訂閱是否有效訂閱了應用程式的「basic」或「premium」產品

1if ($user->subscribedToProduct(['prod_basic', 'prod_premium'], 'default')) {
2 // ...
3}

subscribedToPrice 方法可用於判斷顧客的訂閱是否對應於給定的價格 ID

1if ($user->subscribedToPrice('price_basic_monthly', 'default')) {
2 // ...
3}

recurring 方法可用於判斷使用者目前是否已訂閱且不再處於試用期內

1if ($user->subscription('default')->recurring()) {
2 // ...
3}

如果使用者有兩個相同類型的訂閱,subscription 方法將始終傳回最新的訂閱。例如,使用者可能具有兩個類型為 default 的訂閱記錄;但是,其中一個訂閱可能是舊的、過期的訂閱,而另一個訂閱是當前有效的訂閱。最新的訂閱將始終傳回,而較舊的訂閱則保留在資料庫中以供歷史記錄查閱。

已取消訂閱狀態

若要判斷使用者是否曾經是有效訂閱者但已取消訂閱,您可以使用 canceled 方法

1if ($user->subscription('default')->canceled()) {
2 // ...
3}

您也可以判斷使用者是否已取消訂閱,但仍處於「寬限期」內,直到訂閱完全到期。例如,如果使用者在 3 月 5 日取消了原定於 3 月 10 日到期的訂閱,則使用者在 3 月 10 日之前都處於「寬限期」。請注意,subscribed 方法在此期間仍會傳回 true

1if ($user->subscription('default')->onGracePeriod()) {
2 // ...
3}

若要判斷使用者是否已取消訂閱且不再處於「寬限期」內,您可以使用 ended 方法

1if ($user->subscription('default')->ended()) {
2 // ...
3}

未完成和逾期狀態

如果訂閱在建立後需要次要付款操作,則訂閱將標記為 incomplete。訂閱狀態儲存在 Cashier 的 subscriptions 資料庫表格的 stripe_status 欄位中。

同樣地,如果在交換價格時需要次要付款操作,則訂閱將標記為 past_due。當您的訂閱處於這些狀態中的任何一種時,在顧客確認付款之前,它將不會處於有效狀態。可以使用可計費模型或訂閱實例上的 hasIncompletePayment 方法來判斷訂閱是否具有未完成的付款

1if ($user->hasIncompletePayment('default')) {
2 // ...
3}
4 
5if ($user->subscription('default')->hasIncompletePayment()) {
6 // ...
7}

當訂閱有未完成的付款時,您應將使用者導向 Cashier 的付款確認頁面,並傳遞 latestPayment 識別碼。您可以使用訂閱實例上可用的 latestPayment 方法來檢索此識別碼

1<a href="{{ route('cashier.payment', $subscription->latestPayment()->id) }}">
2 Please confirm your payment.
3</a>

如果您希望訂閱在處於 past_dueincomplete 狀態時仍被視為有效,您可以使用 Cashier 提供的 keepPastDueSubscriptionsActivekeepIncompleteSubscriptionsActive 方法。通常,這些方法應在您的 App\Providers\AppServiceProviderregister 方法中調用

1use Laravel\Cashier\Cashier;
2 
3/**
4 * Register any application services.
5 */
6public function register(): void
7{
8 Cashier::keepPastDueSubscriptionsActive();
9 Cashier::keepIncompleteSubscriptionsActive();
10}

當訂閱處於 incomplete 狀態時,在付款確認之前無法變更。因此,當訂閱處於 incomplete 狀態時,swapupdateQuantity 方法將拋出例外。

訂閱範圍

大多數訂閱狀態也可用作查詢範圍,以便您可以輕鬆地在資料庫中查詢處於給定狀態的訂閱

1// Get all active subscriptions...
2$subscriptions = Subscription::query()->active()->get();
3 
4// Get all of the canceled subscriptions for a user...
5$subscriptions = $user->subscriptions()->canceled()->get();

完整的可用範圍列表如下

1Subscription::query()->active();
2Subscription::query()->canceled();
3Subscription::query()->ended();
4Subscription::query()->incomplete();
5Subscription::query()->notCanceled();
6Subscription::query()->notOnGracePeriod();
7Subscription::query()->notOnTrial();
8Subscription::query()->onGracePeriod();
9Subscription::query()->onTrial();
10Subscription::query()->pastDue();
11Subscription::query()->recurring();

變更價格

在顧客訂閱您的應用程式後,他們有時可能想更改為新的訂閱價格。若要將顧客交換到新的價格,請將 Stripe 價格識別碼傳遞給 swap 方法。當交換價格時,假設使用者想要重新啟用他們先前取消的訂閱。給定的價格識別碼應對應於 Stripe 控制面板中可用的 Stripe 價格識別碼

1use App\Models\User;
2 
3$user = App\Models\User::find(1);
4 
5$user->subscription('default')->swap('price_yearly');

如果顧客正在試用,試用期將會保留。此外,如果訂閱存在「數量」,則該數量也將被保留。

如果您想交換價格並取消顧客目前正在進行的任何試用期,您可以調用 skipTrial 方法

1$user->subscription('default')
2 ->skipTrial()
3 ->swap('price_yearly');

如果您想交換價格並立即向顧客開立發票,而不是等待他們的下一個帳單週期,您可以使用 swapAndInvoice 方法

1$user = User::find(1);
2 
3$user->subscription('default')->swapAndInvoice('price_yearly');

按比例收費

預設情況下,Stripe 在價格之間交換時會按比例收取費用。noProrate 方法可用於更新訂閱的價格,而無需按比例收取費用

1$user->subscription('default')->noProrate()->swap('price_yearly');

有關訂閱按比例收費的更多資訊,請查閱Stripe 文件

swapAndInvoice 方法之前執行 noProrate 方法對按比例收費沒有任何影響。始終會開立發票。

訂閱數量

有時訂閱會受到「數量」的影響。例如,專案管理應用程式可能會對每個專案每月收取 10 美元。您可以使用 incrementQuantitydecrementQuantity 方法輕鬆地增加或減少您的訂閱數量

1use App\Models\User;
2 
3$user = User::find(1);
4 
5$user->subscription('default')->incrementQuantity();
6 
7// Add five to the subscription's current quantity...
8$user->subscription('default')->incrementQuantity(5);
9 
10$user->subscription('default')->decrementQuantity();
11 
12// Subtract five from the subscription's current quantity...
13$user->subscription('default')->decrementQuantity(5);

或者,您可以使用 updateQuantity 方法設定特定數量

1$user->subscription('default')->updateQuantity(10);

noProrate 方法可用於更新訂閱的數量,而無需按比例收取費用

1$user->subscription('default')->noProrate()->updateQuantity(10);

有關訂閱數量的更多資訊,請查閱Stripe 文件

具有多個產品的訂閱的數量

如果您的訂閱是具有多個產品的訂閱,您應該將您想要增加或減少數量的價格 ID 作為第二個引數傳遞給增加/減少方法

1$user->subscription('default')->incrementQuantity(1, 'price_chat');

具有多個產品的訂閱

具有多個產品的訂閱允許您將多個帳單產品指派給單一訂閱。例如,假設您正在建置一個客戶服務「服務台」應用程式,該應用程式的基本訂閱價格為每月 10 美元,但提供額外每月 15 美元的即時聊天附加產品。具有多個產品的訂閱的資訊儲存在 Cashier 的 subscription_items 資料庫表格中。

您可以透過將價格陣列作為第二個引數傳遞給 newSubscription 方法,為給定的訂閱指定多個產品

1use Illuminate\Http\Request;
2 
3Route::post('/user/subscribe', function (Request $request) {
4 $request->user()->newSubscription('default', [
5 'price_monthly',
6 'price_chat',
7 ])->create($request->paymentMethodId);
8 
9 // ...
10});

在上面的範例中,顧客的 default 訂閱將附加兩個價格。這兩個價格都將在各自的帳單間隔內收費。如有必要,您可以使用 quantity 方法來指示每個價格的特定數量

1$user = User::find(1);
2 
3$user->newSubscription('default', ['price_monthly', 'price_chat'])
4 ->quantity(5, 'price_chat')
5 ->create($paymentMethod);

如果您想將另一個價格新增至現有的訂閱,您可以調用訂閱的 addPrice 方法

1$user = User::find(1);
2 
3$user->subscription('default')->addPrice('price_chat');

上面的範例將新增新價格,顧客將在其下一個帳單週期為其付費。如果您想立即向顧客開帳單,您可以使用 addPriceAndInvoice 方法

1$user->subscription('default')->addPriceAndInvoice('price_chat');

如果您想新增具有特定數量的價格,您可以將數量作為 addPriceaddPriceAndInvoice 方法的第二個引數傳遞

1$user = User::find(1);
2 
3$user->subscription('default')->addPrice('price_chat', 5);

您可以使用 removePrice 方法從訂閱中移除價格

1$user->subscription('default')->removePrice('price_chat');

您不能移除訂閱上的最後一個價格。相反地,您應該直接取消訂閱。

交換價格

您也可以變更附加至具有多個產品的訂閱的價格。例如,假設顧客擁有 price_basic 訂閱和 price_chat 附加產品,並且您想將顧客從 price_basic 升級到 price_pro 價格

1use App\Models\User;
2 
3$user = User::find(1);
4 
5$user->subscription('default')->swap(['price_pro', 'price_chat']);

當執行上面的範例時,底層具有 price_basic 的訂閱項目將被刪除,而具有 price_chat 的訂閱項目將被保留。此外,還會為 price_pro 建立新的訂閱項目。

您還可以透過將鍵/值對的陣列傳遞給 swap 方法來指定訂閱項目選項。例如,您可能需要指定訂閱價格數量

1$user = User::find(1);
2 
3$user->subscription('default')->swap([
4 'price_pro' => ['quantity' => 5],
5 'price_chat'
6]);

如果您想交換訂閱上的單個價格,您可以使用訂閱項目本身的 swap 方法來執行此操作。如果您想保留訂閱其他價格上的所有現有元數據,此方法特別有用

1$user = User::find(1);
2 
3$user->subscription('default')
4 ->findItemOrFail('price_basic')
5 ->swap('price_pro');

按比例收費

預設情況下,當從具有多個產品的訂閱新增或移除價格時,Stripe 將按比例收取費用。如果您想在不按比例收費的情況下進行價格調整,您應該將 noProrate 方法鏈接到您的價格操作

1$user->subscription('default')->noProrate()->removePrice('price_chat');

數量

如果您想更新個別訂閱價格的數量,您可以透過將價格 ID 作為方法的附加引數傳遞,來使用現有的數量方法

1$user = User::find(1);
2 
3$user->subscription('default')->incrementQuantity(5, 'price_chat');
4 
5$user->subscription('default')->decrementQuantity(3, 'price_chat');
6 
7$user->subscription('default')->updateQuantity(10, 'price_chat');

當訂閱有多個價格時,Subscription 模型上的 stripe_pricequantity 屬性將為 null。若要存取個別價格屬性,您應該使用 Subscription 模型上可用的 items 關係。

訂閱項目

當訂閱有多個價格時,它將在您的資料庫的 subscription_items 表格中儲存多個訂閱「項目」。您可以透過訂閱上的 items 關係存取這些項目

1use App\Models\User;
2 
3$user = User::find(1);
4 
5$subscriptionItem = $user->subscription('default')->items->first();
6 
7// Retrieve the Stripe price and quantity for a specific item...
8$stripePrice = $subscriptionItem->stripe_price;
9$quantity = $subscriptionItem->quantity;

您也可以使用 findItemOrFail 方法檢索特定價格

1$user = User::find(1);
2 
3$subscriptionItem = $user->subscription('default')->findItemOrFail('price_chat');

多個訂閱

Stripe 允許您的顧客同時擁有多個訂閱。例如,您可能經營一家健身房,提供游泳訂閱和舉重訂閱,並且每個訂閱可能有不同的價格。當然,顧客應該能夠訂閱其中一個或兩個方案。

當您的應用程式建立訂閱時,您可以將訂閱類型提供給 newSubscription 方法。類型可以是代表使用者正在啟動的訂閱類型的任何字串

1use Illuminate\Http\Request;
2 
3Route::post('/swimming/subscribe', function (Request $request) {
4 $request->user()->newSubscription('swimming')
5 ->price('price_swimming_monthly')
6 ->create($request->paymentMethodId);
7 
8 // ...
9});

在此範例中,我們為顧客啟動了每月游泳訂閱。但是,他們可能希望稍後交換為年度訂閱。當調整顧客的訂閱時,我們可以簡單地交換 swimming 訂閱上的價格

1$user->subscription('swimming')->swap('price_swimming_yearly');

當然,您也可以完全取消訂閱

1$user->subscription('swimming')->cancel();

基於用量的計費

基於用量的計費允許您根據顧客在帳單週期內對產品的使用量向他們收費。例如,您可以根據顧客每月發送的簡訊或電子郵件數量向他們收費。

若要開始使用用量計費,您首先需要在您的 Stripe 控制面板中使用基於用量的計費模型計量器建立新的產品。建立計量器後,儲存相關的事件名稱和計量器 ID,您將需要它們來報告和檢索用量。然後,使用 meteredPrice 方法將計量價格 ID 新增至顧客訂閱

1use Illuminate\Http\Request;
2 
3Route::post('/user/subscribe', function (Request $request) {
4 $request->user()->newSubscription('default')
5 ->meteredPrice('price_metered')
6 ->create($request->paymentMethodId);
7 
8 // ...
9});

您也可以透過Stripe Checkout開始計量訂閱

1$checkout = Auth::user()
2 ->newSubscription('default', [])
3 ->meteredPrice('price_metered')
4 ->checkout();
5 
6return view('your-checkout-view', [
7 'checkout' => $checkout,
8]);

報告用量

當您的顧客使用您的應用程式時,您將向 Stripe 報告其用量,以便可以準確地向他們收費。若要報告計量事件的用量,您可以使用您的 Billable 模型上的 reportMeterEvent 方法

1$user = User::find(1);
2 
3$user->reportMeterEvent('emails-sent');

預設情況下,會將 1 的「用量數量」新增至帳單週期。或者,您可以傳遞特定量的「用量」以新增至顧客在帳單週期內的用量

1$user = User::find(1);
2 
3$user->reportMeterEvent('emails-sent', quantity: 15);

若要檢索顧客的計量器事件摘要,您可以使用 Billable 實例的 meterEventSummaries 方法

1$user = User::find(1);
2 
3$meterUsage = $user->meterEventSummaries($meterId);
4 
5$meterUsage->first()->aggregated_value // 10

有關計量器事件摘要的更多資訊,請參閱 Stripe 的計量器事件摘要物件文件

若要列出所有計量器,您可以使用 Billable 實例的 meters 方法

1$user = User::find(1);
2 
3$user->meters();

訂閱稅

您可以使用 Stripe Tax 自動計算稅金,而不是手動計算稅率

若要指定使用者在訂閱時支付的稅率,您應在您的可計費模型上實作 taxRates 方法,並傳回包含 Stripe 稅率 ID 的陣列。您可以在您的 Stripe 控制面板中定義這些稅率

1/**
2 * The tax rates that should apply to the customer's subscriptions.
3 *
4 * @return array<int, string>
5 */
6public function taxRates(): array
7{
8 return ['txr_id'];
9}

taxRates 方法讓您可以逐個顧客地套用稅率,這對於跨越多個國家和稅率的使用者群可能很有幫助。

如果您提供具有多個產品的訂閱,您可以透過在您的可計費模型上實作 priceTaxRates 方法,為每個價格定義不同的稅率

1/**
2 * The tax rates that should apply to the customer's subscriptions.
3 *
4 * @return array<string, array<int, string>>
5 */
6public function priceTaxRates(): array
7{
8 return [
9 'price_monthly' => ['txr_id'],
10 ];
11}

taxRates 方法僅適用於訂閱費用。如果您使用 Cashier 進行「一次性」收費,您需要在當時手動指定稅率。

同步稅率

當變更 taxRates 方法傳回的硬編碼稅率 ID 時,使用者的任何現有訂閱的稅務設定將保持不變。如果您希望使用新的 taxRates 值更新現有訂閱的稅值,您應該在使用者的訂閱實例上調用 syncTaxRates 方法

1$user->subscription('default')->syncTaxRates();

這也將同步具有多個產品的訂閱的任何項目稅率。如果您的應用程式提供具有多個產品的訂閱,您應確保您的可計費模型實作了上面討論的 priceTaxRates 方法。

免稅

Cashier 還提供 isNotTaxExemptisTaxExemptreverseChargeApplies 方法來判斷顧客是否免稅。這些方法將調用 Stripe API 以判斷顧客的免稅狀態

1use App\Models\User;
2 
3$user = User::find(1);
4 
5$user->isTaxExempt();
6$user->isNotTaxExempt();
7$user->reverseChargeApplies();

這些方法也適用於任何 Laravel\Cashier\Invoice 物件。但是,當在 Invoice 物件上調用時,這些方法將判斷發票建立時的免稅狀態。

訂閱錨定日期

預設情況下,帳單週期錨點是訂閱建立的日期,或者,如果使用試用期,則是試用期結束的日期。如果您想修改帳單錨點日期,您可以使用 anchorBillingCycleOn 方法

1use Illuminate\Http\Request;
2 
3Route::post('/user/subscribe', function (Request $request) {
4 $anchor = Carbon::parse('first day of next month');
5 
6 $request->user()->newSubscription('default', 'price_monthly')
7 ->anchorBillingCycleOn($anchor->startOfDay())
8 ->create($request->paymentMethodId);
9 
10 // ...
11});

有關管理訂閱帳單週期的更多資訊,請查閱Stripe 帳單週期文件

取消訂閱

若要取消訂閱,請在使用者的訂閱上調用 cancel 方法

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

當訂閱被取消時,Cashier 將自動在您的 subscriptions 資料庫表格中設定 ends_at 欄位。此欄位用於了解 subscribed 方法何時應開始傳回 false

例如,如果顧客在 3 月 1 日取消了訂閱,但訂閱原定於 3 月 5 日結束,則 subscribed 方法將繼續傳回 true,直到 3 月 5 日。這樣做是因為通常允許使用者繼續使用應用程式,直到其帳單週期結束。

您可以使用 onGracePeriod 方法判斷使用者是否已取消訂閱,但仍處於「寬限期」內

1if ($user->subscription('default')->onGracePeriod()) {
2 // ...
3}

如果您想立即取消訂閱,請在使用者的訂閱上調用 cancelNow 方法

1$user->subscription('default')->cancelNow();

如果您想立即取消訂閱,並為任何剩餘的未開發票計量用量或新的/待處理的按比例收費發票項目開立發票,請在使用者的訂閱上調用 cancelNowAndInvoice 方法

1$user->subscription('default')->cancelNowAndInvoice();

您也可以選擇在特定時間點取消訂閱

1$user->subscription('default')->cancelAt(
2 now()->addDays(10)
3);

最後,您應始終在刪除相關使用者模型之前取消使用者訂閱

1$user->subscription('default')->cancelNow();
2 
3$user->delete();

恢復訂閱

如果顧客已取消訂閱,並且您希望恢復訂閱,您可以調用訂閱上的 resume 方法。顧客必須仍在「寬限期」內才能恢復訂閱

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

如果顧客取消訂閱,然後在訂閱完全到期之前恢復該訂閱,則顧客不會立即被收費。相反地,他們的訂閱將被重新啟用,他們將在原始帳單週期中被收費。

訂閱試用期

預先提供付款方式

如果您想在仍預先收集付款方式資訊的情況下為您的顧客提供試用期,您應該在建立訂閱時使用 trialDays 方法

1use Illuminate\Http\Request;
2 
3Route::post('/user/subscribe', function (Request $request) {
4 $request->user()->newSubscription('default', 'price_monthly')
5 ->trialDays(10)
6 ->create($request->paymentMethodId);
7 
8 // ...
9});

此方法將在資料庫中的訂閱記錄上設定試用期結束日期,並指示 Stripe 在此日期之後才開始向顧客收費。當使用 trialDays 方法時,Cashier 將覆寫 Stripe 中為價格配置的任何預設試用期。

如果顧客的訂閱在試用期結束日期之前未取消,他們將在試用期到期後立即被收費,因此您應務必通知您的使用者他們的試用期結束日期。

trialUntil 方法允許您提供 DateTime 實例,以指定試用期應何時結束

1use Carbon\Carbon;
2 
3$user->newSubscription('default', 'price_monthly')
4 ->trialUntil(Carbon::now()->addDays(10))
5 ->create($paymentMethod);

您可以使用使用者實例的 onTrial 方法或訂閱實例的 onTrial 方法來判斷使用者是否處於試用期內。以下兩個範例是等效的

1if ($user->onTrial('default')) {
2 // ...
3}
4 
5if ($user->subscription('default')->onTrial()) {
6 // ...
7}

您可以使用 endTrial 方法立即結束訂閱試用

1$user->subscription('default')->endTrial();

若要判斷現有的試用期是否已過期,您可以使用 hasExpiredTrial 方法

1if ($user->hasExpiredTrial('default')) {
2 // ...
3}
4 
5if ($user->subscription('default')->hasExpiredTrial()) {
6 // ...
7}

在 Stripe/Cashier 中定義試用天數

您可以選擇在 Stripe 控制面板中定義您的價格收到的試用天數,或者始終使用 Cashier 明確地傳遞它們。如果您選擇在 Stripe 中定義您的價格的試用天數,您應該知道,除非您明確調用 skipTrial() 方法,否則新的訂閱(包括過去曾有訂閱的顧客的新訂閱)將始終收到試用期。

不預先提供付款方式

如果您想在不預先收集使用者的付款方式資訊的情況下提供試用期,您可以將使用者記錄上的 trial_ends_at 欄位設定為您想要的試用期結束日期。這通常在使用者註冊期間完成

1use App\Models\User;
2 
3$user = User::create([
4 // ...
5 'trial_ends_at' => now()->addDays(10),
6]);

請務必為您的可計費模型類別定義中的 trial_ends_at 屬性新增日期轉換

Cashier 將此類型的試用期稱為「通用試用期」,因為它未附加到任何現有的訂閱。如果目前日期未超過 trial_ends_at 的值,則可計費模型實例上的 onTrial 方法將傳回 true

1if ($user->onTrial()) {
2 // User is within their trial period...
3}

當您準備好為使用者建立實際訂閱時,您可以像往常一樣使用 newSubscription 方法

1$user = User::find(1);
2 
3$user->newSubscription('default', 'price_monthly')->create($paymentMethod);

若要檢索使用者的試用期結束日期,您可以使用 trialEndsAt 方法。如果使用者正在試用,此方法將傳回 Carbon 日期實例,如果使用者沒有試用,則傳回 null。如果您想取得特定訂閱(而非預設訂閱)的試用期結束日期,您也可以傳遞選用的訂閱類型參數

1if ($user->onTrial()) {
2 $trialEndsAt = $user->trialEndsAt('main');
3}

如果您想明確知道使用者是否處於「通用」試用期內,並且尚未建立實際訂閱,您也可以使用 onGenericTrial 方法

1if ($user->onGenericTrial()) {
2 // User is within their "generic" trial period...
3}

延長試用期

extendTrial 方法允許您在訂閱建立後延長訂閱的試用期。如果試用期已過期,並且顧客已經在為訂閱付費,您仍然可以為他們提供延長的試用期。在試用期內花費的時間將從顧客的下一個發票中扣除

1use App\Models\User;
2 
3$subscription = User::find(1)->subscription('default');
4 
5// End the trial 7 days from now...
6$subscription->extendTrial(
7 now()->addDays(7)
8);
9 
10// Add an additional 5 days to the trial...
11$subscription->extendTrial(
12 $subscription->trial_ends_at->addDays(5)
13);

處理 Stripe Webhooks

您可以使用Stripe CLI來協助在本地開發期間測試 webhook。

Stripe 可以透過 webhook 通知您的應用程式各種事件。預設情況下,指向 Cashier 的 webhook 控制器的路由由 Cashier 服務提供者自動註冊。此控制器將處理所有傳入的 webhook 請求。

預設情況下,Cashier webhook 控制器將自動處理取消具有過多付款失敗次數的訂閱(由您的 Stripe 設定定義)、顧客更新、顧客刪除、訂閱更新和付款方式變更;但是,正如我們很快就會發現的,您可以擴展此控制器以處理您喜歡的任何 Stripe webhook 事件。

為了確保您的應用程式可以處理 Stripe webhook,請務必在 Stripe 控制面板中配置 webhook URL。預設情況下,Cashier 的 webhook 控制器會回應 /stripe/webhook URL 路徑。您應在 Stripe 控制面板中啟用的所有 webhook 的完整列表為

  • customer.subscription.created
  • customer.subscription.updated
  • customer.subscription.deleted
  • customer.updated
  • customer.deleted
  • payment_method.automatically_updated
  • invoice.payment_action_required
  • invoice.payment_succeeded

為了方便起見,Cashier 包含 cashier:webhook Artisan 命令。此命令將在 Stripe 中建立一個 webhook,以監聽 Cashier 所需的所有事件

1php artisan cashier:webhook

預設情況下,建立的 webhook 將指向由 APP_URL 環境變數和 Cashier 包含的 cashier.webhook 路由定義的 URL。如果您想使用不同的 URL,您可以在調用命令時提供 --url 選項

1php artisan cashier:webhook --url "https://example.com/stripe/webhook"

建立的 webhook 將使用您的 Cashier 版本相容的 Stripe API 版本。如果您想使用不同的 Stripe 版本,您可以提供 --api-version 選項

1php artisan cashier:webhook --api-version="2019-12-03"

建立後,webhook 將立即啟用。如果您希望建立 webhook 但在您準備好之前使其保持停用狀態,您可以在調用命令時提供 --disabled 選項

1php artisan cashier:webhook --disabled

請確保使用 Cashier 包含的webhook 簽名驗證中介層保護傳入的 Stripe webhook 請求。

Webhook 和 CSRF 保護

由於 Stripe webhook 需要繞過 Laravel 的CSRF 保護,您應確保 Laravel 不會嘗試驗證傳入 Stripe webhook 的 CSRF 令牌。為了達成此目的,您應該在應用程式的 bootstrap/app.php 檔案中,將 stripe/* 排除在 CSRF 保護之外。

1->withMiddleware(function (Middleware $middleware) {
2 $middleware->validateCsrfTokens(except: [
3 'stripe/*',
4 ]);
5})

定義 Webhook 事件處理器

Cashier 會自動處理因扣款失敗和其他常見 Stripe webhook 事件而造成的訂閱取消。但是,如果您有其他想要處理的 webhook 事件,可以透過監聽 Cashier 發送的下列事件來完成:

  • Laravel\Cashier\Events\WebhookReceived
  • Laravel\Cashier\Events\WebhookHandled

這兩個事件都包含 Stripe webhook 的完整酬載。例如,如果您希望處理 invoice.payment_succeeded webhook,您可以註冊一個監聽器來處理該事件。

1<?php
2 
3namespace App\Listeners;
4 
5use Laravel\Cashier\Events\WebhookReceived;
6 
7class StripeEventListener
8{
9 /**
10 * Handle received Stripe webhooks.
11 */
12 public function handle(WebhookReceived $event): void
13 {
14 if ($event->payload['type'] === 'invoice.payment_succeeded') {
15 // Handle the incoming event...
16 }
17 }
18}

驗證 Webhook 簽名

為了保護您的 webhook 安全,您可以使用Stripe 的 webhook 簽章。為了方便起見,Cashier 自動包含一個中介層,用於驗證傳入的 Stripe webhook 請求是否有效。

若要啟用 webhook 驗證,請確保您的應用程式 .env 檔案中已設定 STRIPE_WEBHOOK_SECRET 環境變數。webhook secret 可以從您的 Stripe 帳戶儀表板中取得。

單次收費

簡單收費

如果您想要對客戶進行一次性扣款,您可以使用可計費模型實例上的 charge 方法。您需要提供付款方式識別碼作為 charge 方法的第二個參數。

1use Illuminate\Http\Request;
2 
3Route::post('/purchase', function (Request $request) {
4 $stripeCharge = $request->user()->charge(
5 100, $request->paymentMethodId
6 );
7 
8 // ...
9});

charge 方法接受一個陣列作為其第三個參數,讓您可以將任何您希望的選項傳遞給底層的 Stripe 扣款建立操作。關於建立扣款時可用的選項的更多資訊,可以在Stripe 文件中找到。

1$user->charge(100, $paymentMethod, [
2 'custom_option' => $value,
3]);

您也可以在沒有底層客戶或使用者的情況下使用 charge 方法。為了達成此目的,請在應用程式可計費模型的新實例上調用 charge 方法。

1use App\Models\User;
2 
3$stripeCharge = (new User)->charge(100, $paymentMethod);

如果扣款失敗,charge 方法將會拋出例外。如果扣款成功,方法將會傳回 Laravel\Cashier\Payment 的實例。

1try {
2 $payment = $user->charge(100, $paymentMethod);
3} catch (Exception $e) {
4 // ...
5}

charge 方法接受以您的應用程式所使用貨幣的最小面額表示的付款金額。例如,如果客戶以美元付款,金額應以美分指定。

發票收費

有時您可能需要進行一次性扣款並向客戶提供 PDF 帳單。invoicePrice 方法可讓您做到這一點。例如,讓我們為一位客戶開立五件新襯衫的帳單。

1$user->invoicePrice('price_tshirt', 5);

帳單將立即向使用者的預設付款方式扣款。invoicePrice 方法也接受一個陣列作為其第三個參數。此陣列包含帳單項目的帳單選項。該方法接受的第四個參數也是一個陣列,應包含帳單本身的帳單選項。

1$user->invoicePrice('price_tshirt', 5, [
2 'discounts' => [
3 ['coupon' => 'SUMMER21SALE']
4 ],
5], [
6 'default_tax_rates' => ['txr_id'],
7]);

invoicePrice 類似,您可以使用 tabPrice 方法為多個項目(每個帳單最多 250 個項目)建立一次性扣款,方法是將它們加入客戶的「待結帳項目」,然後向客戶開帳單。例如,我們可以為一位客戶開立五件襯衫和兩個馬克杯的帳單。

1$user->tabPrice('price_tshirt', 5);
2$user->tabPrice('price_mug', 2);
3$user->invoice();

或者,您可以使用 invoiceFor 方法對客戶的預設付款方式進行「一次性」扣款。

1$user->invoiceFor('One Time Fee', 500);

儘管 invoiceFor 方法可供您使用,但建議您使用具有預定義價格的 invoicePricetabPrice 方法。這樣做,您將可以在您的 Stripe 儀表板中獲得關於每個產品銷售的更好的分析和數據。

invoiceinvoicePriceinvoiceFor 方法將建立一個 Stripe 帳單,該帳單將重試失敗的扣款嘗試。如果您不希望帳單重試失敗的扣款,您需要在第一次扣款失敗後使用 Stripe API 關閉它們。

建立付款意圖

您可以透過在可計費模型實例上調用 pay 方法來建立新的 Stripe payment intent。調用此方法將建立一個 payment intent,該 intent 包裝在 Laravel\Cashier\Payment 實例中。

1use Illuminate\Http\Request;
2 
3Route::post('/pay', function (Request $request) {
4 $payment = $request->user()->pay(
5 $request->get('amount')
6 );
7 
8 return $payment->client_secret;
9});

建立 payment intent 後,您可以將客戶端密鑰返回到您的應用程式前端,以便使用者可以在他們的瀏覽器中完成付款。若要閱讀更多關於使用 Stripe payment intent 建立完整付款流程的資訊,請查閱Stripe 文件

當使用 pay 方法時,在您的 Stripe 儀表板中啟用的預設付款方式將對客戶可用。或者,如果您只想允許使用某些特定的付款方式,您可以使用 payWith 方法。

1use Illuminate\Http\Request;
2 
3Route::post('/pay', function (Request $request) {
4 $payment = $request->user()->payWith(
5 $request->get('amount'), ['card', 'bancontact']
6 );
7 
8 return $payment->client_secret;
9});

paypayWith 方法接受以您的應用程式所使用貨幣的最小面額表示的付款金額。例如,如果客戶以美元付款,金額應以美分指定。

退款

如果您需要退還 Stripe 扣款,您可以使用 refund 方法。此方法接受 Stripe payment intent ID 作為其第一個參數。

1$payment = $user->charge(100, $paymentMethodId);
2 
3$user->refund($payment->id);

發票

檢索發票

您可以使用 invoices 方法輕鬆檢索可計費模型的帳單陣列。invoices 方法傳回 Laravel\Cashier\Invoice 實例的集合。

1$invoices = $user->invoices();

如果您想在結果中包含待處理的帳單,您可以使用 invoicesIncludingPending 方法。

1$invoices = $user->invoicesIncludingPending();

您可以使用 findInvoice 方法依 ID 檢索特定的帳單。

1$invoice = $user->findInvoice($invoiceId);

顯示帳單資訊

當列出客戶的帳單時,您可以使用帳單的方法來顯示相關的帳單資訊。例如,您可能希望在表格中列出每個帳單,讓使用者可以輕鬆下載它們。

1<table>
2 @foreach ($invoices as $invoice)
3 <tr>
4 <td>{{ $invoice->date()->toFormattedDateString() }}</td>
5 <td>{{ $invoice->total() }}</td>
6 <td><a href="/user/invoice/{{ $invoice->id }}">Download</a></td>
7 </tr>
8 @endforeach
9</table>

即將到期的發票

若要檢索客戶的即將到來的帳單,您可以使用 upcomingInvoice 方法。

1$invoice = $user->upcomingInvoice();

同樣地,如果客戶有多個訂閱,您也可以檢索特定訂閱的即將到來的帳單。

1$invoice = $user->subscription('default')->upcomingInvoice();

預覽訂閱發票

使用 previewInvoice 方法,您可以在進行價格變更之前預覽帳單。這將讓您確定當給定的價格變更發生時,客戶的帳單會是什麼樣子。

1$invoice = $user->subscription('default')->previewInvoice('price_yearly');

您可以將價格陣列傳遞給 previewInvoice 方法,以便預覽具有多個新價格的帳單。

1$invoice = $user->subscription('default')->previewInvoice(['price_yearly', 'price_metered']);

產生發票 PDF

在產生帳單 PDF 之前,您應該使用 Composer 安裝 Dompdf 程式庫,這是 Cashier 的預設帳單渲染器。

1composer require dompdf/dompdf

從路由或控制器中,您可以使用 downloadInvoice 方法來產生給定帳單的 PDF 下載。此方法將自動產生下載帳單所需的正確 HTTP 回應。

1use Illuminate\Http\Request;
2 
3Route::get('/user/invoice/{invoice}', function (Request $request, string $invoiceId) {
4 return $request->user()->downloadInvoice($invoiceId);
5});

預設情況下,帳單上的所有資料都來自客戶和儲存在 Stripe 中的帳單資料。檔案名稱基於您的 app.name 設定值。但是,您可以透過提供一個陣列作為 downloadInvoice 方法的第二個參數來自訂部分資料。此陣列可讓您自訂公司和產品詳細資訊等資訊。

1return $request->user()->downloadInvoice($invoiceId, [
2 'vendor' => 'Your Company',
3 'product' => 'Your Product',
4 'street' => 'Main Str. 1',
5 'location' => '2000 Antwerp, Belgium',
6 'phone' => '+32 499 00 00 00',
7 'email' => '[email protected]',
8 'url' => 'https://example.com',
9 'vendorVat' => 'BE123456789',
10]);

downloadInvoice 方法也允許透過其第三個參數使用自訂檔案名稱。此檔案名稱將自動附加 .pdf 後綴。

1return $request->user()->downloadInvoice($invoiceId, [], 'my-invoice');
2 
3<a name="custom-invoice-render"></a>
4#### Custom Invoice Renderer
5 
6Cashier also makes it possible to use a custom invoice renderer. By default, Cashier uses the `DompdfInvoiceRenderer` implementation, which utilizes the [dompdf](https://github.com/dompdf/dompdf) PHP library to generate Cashier's invoices. However, you may use any renderer you wish by implementing the `Laravel\Cashier\Contracts\InvoiceRenderer` interface. For example, you may wish to render an invoice PDF using an API call to a third-party PDF rendering service:
7 
8use Illuminate\Support\Facades\Http;
9use Laravel\Cashier\Contracts\InvoiceRenderer;
10use Laravel\Cashier\Invoice;
11 
12class ApiInvoiceRenderer implements InvoiceRenderer
13{
14 /**
15 * Render the given invoice and return the raw PDF bytes.
16 */
17 public function render(Invoice $invoice, array $data = [], array $options = []): string
18 {
19 $html = $invoice->view($data)->render();
20 
21 return Http::get('https://example.com/html-to-pdf', ['html' => $html])->get()->body();
22 }
23}

一旦您實作了帳單渲染器契約,您應該更新您的應用程式 config/cashier.php 設定檔案中的 cashier.invoices.renderer 設定值。此設定值應設定為您的自訂渲染器實作的類別名稱。

結帳

Cashier Stripe 也支援 Stripe Checkout。Stripe Checkout 透過提供預先建立的託管付款頁面,消除了實作自訂頁面以接受付款的麻煩。

以下文件包含關於如何開始將 Stripe Checkout 與 Cashier 一起使用的資訊。若要了解更多關於 Stripe Checkout 的資訊,您也應該考慮查看 Stripe 關於 Checkout 的自有文件

產品結帳

您可以使用可計費模型上的 checkout 方法,對已在您的 Stripe 儀表板中建立的現有產品執行結帳。checkout 方法將啟動新的 Stripe Checkout 會話。預設情況下,您需要傳遞 Stripe 價格 ID。

1use Illuminate\Http\Request;
2 
3Route::get('/product-checkout', function (Request $request) {
4 return $request->user()->checkout('price_tshirt');
5});

如果需要,您也可以指定產品數量。

1use Illuminate\Http\Request;
2 
3Route::get('/product-checkout', function (Request $request) {
4 return $request->user()->checkout(['price_tshirt' => 15]);
5});

當客戶訪問此路由時,他們將被重新導向到 Stripe 的 Checkout 頁面。預設情況下,當使用者成功完成或取消購買時,他們將被重新導向到您的 home 路由位置,但您可以使用 success_urlcancel_url 選項指定自訂的回調 URL。

1use Illuminate\Http\Request;
2 
3Route::get('/product-checkout', function (Request $request) {
4 return $request->user()->checkout(['price_tshirt' => 1], [
5 'success_url' => route('your-success-route'),
6 'cancel_url' => route('your-cancel-route'),
7 ]);
8});

當定義您的 success_url 結帳選項時,您可以指示 Stripe 在調用您的 URL 時將結帳會話 ID 作為查詢字串參數新增。若要執行此操作,請將字串 {CHECKOUT_SESSION_ID} 加入您的 success_url 查詢字串。Stripe 將以實際的結帳會話 ID 取代此佔位符。

1use Illuminate\Http\Request;
2use Stripe\Checkout\Session;
3use Stripe\Customer;
4 
5Route::get('/product-checkout', function (Request $request) {
6 return $request->user()->checkout(['price_tshirt' => 1], [
7 'success_url' => route('checkout-success').'?session_id={CHECKOUT_SESSION_ID}',
8 'cancel_url' => route('checkout-cancel'),
9 ]);
10});
11 
12Route::get('/checkout-success', function (Request $request) {
13 $checkoutSession = $request->user()->stripe()->checkout->sessions->retrieve($request->get('session_id'));
14 
15 return view('checkout.success', ['checkoutSession' => $checkoutSession]);
16})->name('checkout-success');

促銷代碼

預設情況下,Stripe Checkout 不允許使用者可兌換的促銷代碼。幸運的是,有一個簡單的方法可以為您的 Checkout 頁面啟用這些代碼。若要執行此操作,您可以調用 allowPromotionCodes 方法。

1use Illuminate\Http\Request;
2 
3Route::get('/product-checkout', function (Request $request) {
4 return $request->user()
5 ->allowPromotionCodes()
6 ->checkout('price_tshirt');
7});

單次收費結帳

您也可以為尚未在您的 Stripe 儀表板中建立的臨時產品執行簡單的扣款。若要執行此操作,您可以使用可計費模型上的 checkoutCharge 方法,並向其傳遞可扣款金額、產品名稱和可選的數量。當客戶訪問此路由時,他們將被重新導向到 Stripe 的 Checkout 頁面。

1use Illuminate\Http\Request;
2 
3Route::get('/charge-checkout', function (Request $request) {
4 return $request->user()->checkoutCharge(1200, 'T-Shirt', 5);
5});

當使用 checkoutCharge 方法時,Stripe 將始終在您的 Stripe 儀表板中建立新的產品和價格。因此,我們建議您預先在您的 Stripe 儀表板中建立產品,並改用 checkout 方法。

訂閱結帳

將 Stripe Checkout 用於訂閱需要您在 Stripe 儀表板中啟用 customer.subscription.created webhook。此 webhook 將在您的資料庫中建立訂閱記錄,並儲存所有相關的訂閱項目。

您也可以使用 Stripe Checkout 來啟動訂閱。在使用 Cashier 的訂閱建構器方法定義您的訂閱後,您可以調用 checkout 方法。當客戶訪問此路由時,他們將被重新導向到 Stripe 的 Checkout 頁面。

1use Illuminate\Http\Request;
2 
3Route::get('/subscription-checkout', function (Request $request) {
4 return $request->user()
5 ->newSubscription('default', 'price_monthly')
6 ->checkout();
7});

與產品結帳一樣,您可以自訂成功和取消 URL。

1use Illuminate\Http\Request;
2 
3Route::get('/subscription-checkout', function (Request $request) {
4 return $request->user()
5 ->newSubscription('default', 'price_monthly')
6 ->checkout([
7 'success_url' => route('your-success-route'),
8 'cancel_url' => route('your-cancel-route'),
9 ]);
10});

當然,您也可以為訂閱結帳啟用促銷代碼。

1use Illuminate\Http\Request;
2 
3Route::get('/subscription-checkout', function (Request $request) {
4 return $request->user()
5 ->newSubscription('default', 'price_monthly')
6 ->allowPromotionCodes()
7 ->checkout();
8});

不幸的是,Stripe Checkout 在開始訂閱時不支援所有訂閱帳單選項。在訂閱建構器上使用 anchorBillingCycleOn 方法、設定按比例分攤行為或設定付款行為在 Stripe Checkout 會話期間將不起作用。請查閱 Stripe Checkout Session API 文件以查看哪些參數可用。

Stripe Checkout 和試用期

當然,您可以在建立將使用 Stripe Checkout 完成的訂閱時定義試用期。

1$checkout = Auth::user()->newSubscription('default', 'price_monthly')
2 ->trialDays(3)
3 ->checkout();

但是,試用期必須至少為 48 小時,這是 Stripe Checkout 支援的最短試用時間。

訂閱和 Webhook

請記住,Stripe 和 Cashier 透過 webhook 更新訂閱狀態,因此當客戶在輸入他們的付款資訊後返回應用程式時,訂閱可能尚未啟用。為了處理這種情況,您可能希望顯示一則訊息,告知使用者他們的付款或訂閱正在等待處理中。

收集稅務 ID

Checkout 也支援收集客戶的稅務 ID。若要在結帳會話中啟用此功能,請在建立會話時調用 collectTaxIds 方法。

1$checkout = $user->collectTaxIds()->checkout('price_tshirt');

當調用此方法時,將向客戶提供一個新的核取方塊,讓他們可以指示他們是否以公司身分購買。如果是,他們將有機會提供他們的稅務 ID 號碼。

如果您已在應用程式的服務提供者中設定自動稅務收集,則此功能將自動啟用,無需調用 collectTaxIds 方法。

訪客結帳

使用 Checkout::guest 方法,您可以為沒有「帳戶」的應用程式訪客啟動結帳會話。

1use Illuminate\Http\Request;
2use Laravel\Cashier\Checkout;
3 
4Route::get('/product-checkout', function (Request $request) {
5 return Checkout::guest()->create('price_tshirt', [
6 'success_url' => route('your-success-route'),
7 'cancel_url' => route('your-cancel-route'),
8 ]);
9});

與為現有使用者建立結帳會話類似,您可以使用 Laravel\Cashier\CheckoutBuilder 實例上可用的其他方法來自訂訪客結帳會話。

1use Illuminate\Http\Request;
2use Laravel\Cashier\Checkout;
3 
4Route::get('/product-checkout', function (Request $request) {
5 return Checkout::guest()
6 ->withPromotionCode('promo-code')
7 ->create('price_tshirt', [
8 'success_url' => route('your-success-route'),
9 'cancel_url' => route('your-cancel-route'),
10 ]);
11});

訪客結帳完成後,Stripe 可以發送 checkout.session.completed webhook 事件,因此請確保設定您的 Stripe webhook 以實際將此事件發送到您的應用程式。一旦 webhook 在 Stripe 儀表板中啟用,您就可以使用 Cashier 處理 webhook。webhook 酬載中包含的物件將是一個 checkout 物件,您可以檢查該物件以完成客戶的訂單。

處理付款失敗

有時,訂閱或單次扣款的付款可能會失敗。當這種情況發生時,Cashier 將拋出 Laravel\Cashier\Exceptions\IncompletePayment 例外,告知您發生了這種情況。在捕獲此例外後,您有兩種處理方式的選項。

首先,您可以將您的客戶重新導向到 Cashier 隨附的專用付款確認頁面。此頁面已經有一個相關的具名路由,該路由透過 Cashier 的服務提供者註冊。因此,您可以捕獲 IncompletePayment 例外,並將使用者重新導向到付款確認頁面。

1use Laravel\Cashier\Exceptions\IncompletePayment;
2 
3try {
4 $subscription = $user->newSubscription('default', 'price_monthly')
5 ->create($paymentMethod);
6} catch (IncompletePayment $exception) {
7 return redirect()->route(
8 'cashier.payment',
9 [$exception->payment->id, 'redirect' => route('home')]
10 );
11}

在付款確認頁面上,將提示客戶再次輸入他們的信用卡資訊,並執行 Stripe 要求的任何其他操作,例如「3D 安全」確認。確認付款後,使用者將被重新導向到上面指定的 redirect 參數提供的 URL。重新導向後,message (字串) 和 success (整數) 查詢字串變數將被加入到 URL 中。付款頁面目前支援以下付款方式類型:

  • 信用卡
  • 支付寶 (Alipay)
  • Bancontact
  • BECS 直接扣款
  • EPS
  • Giropay
  • iDEAL
  • SEPA 直接扣款

或者,您可以允許 Stripe 為您處理付款確認。在這種情況下,您可以在您的 Stripe 儀表板中設定 Stripe 的自動帳單電子郵件,而不是重新導向到付款確認頁面。但是,如果捕獲到 IncompletePayment 例外,您仍然應該告知使用者他們將收到一封包含進一步付款確認指示的電子郵件。

使用 Billable trait 的模型上的 chargeinvoiceForinvoice 方法可能會拋出付款例外。當與訂閱互動時,SubscriptionBuilder 上的 create 方法,以及 SubscriptionSubscriptionItem 模型上的 incrementAndInvoiceswapAndInvoice 方法可能會拋出不完整付款例外。

可以使用可計費模型或訂閱實例上的 hasIncompletePayment 方法來判斷現有的訂閱是否具有不完整的付款。

1if ($user->hasIncompletePayment('default')) {
2 // ...
3}
4 
5if ($user->subscription('default')->hasIncompletePayment()) {
6 // ...
7}

您可以透過檢查例外實例上的 payment 屬性來取得不完整付款的特定狀態。

1use Laravel\Cashier\Exceptions\IncompletePayment;
2 
3try {
4 $user->charge(1000, 'pm_card_threeDSecure2Required');
5} catch (IncompletePayment $exception) {
6 // Get the payment intent status...
7 $exception->payment->status;
8 
9 // Check specific conditions...
10 if ($exception->payment->requiresPaymentMethod()) {
11 // ...
12 } elseif ($exception->payment->requiresConfirmation()) {
13 // ...
14 }
15}

確認付款

某些付款方式需要額外的資料才能確認付款。例如,SEPA 付款方式在付款過程中需要額外的「授權」資料。您可以使用 withPaymentConfirmationOptions 方法將此資料提供給 Cashier。

1$subscription->withPaymentConfirmationOptions([
2 'mandate_data' => '...',
3])->swap('price_xxx');

您可以查閱 Stripe API 文件,以查看確認付款時接受的所有選項。

強力客戶認證

如果您的企業或您的客戶之一位於歐洲,您將需要遵守歐盟的強力客戶認證 (SCA) 法規。這些法規由歐盟於 2019 年 9 月實施,旨在防止付款詐欺。幸運的是,Stripe 和 Cashier 已準備好建構符合 SCA 標準的應用程式。

在開始之前,請查看 Stripe 關於 PSD2 和 SCA 的指南,以及他們關於新 SCA API 的文件

需要額外確認的付款

SCA 法規通常需要額外的驗證才能確認和處理付款。當這種情況發生時,Cashier 將拋出 Laravel\Cashier\Exceptions\IncompletePayment 例外,告知您需要額外的驗證。關於如何處理這些例外的更多資訊,可以在關於處理失敗付款的文件中找到。

由 Stripe 或 Cashier 呈現的付款確認畫面可以針對特定銀行或發卡銀行的付款流程進行客製化,並且可以包含額外的卡片確認、臨時小額扣款、單獨的裝置認證或其他形式的驗證。

不完整和逾期狀態

當付款需要額外確認時,訂閱將保持 incompletepast_due 狀態,如其 stripe_status 資料庫欄位所示。一旦付款確認完成,並且您的應用程式透過 webhook 收到 Stripe 的完成通知,Cashier 將自動啟用客戶的訂閱。

有關 incompletepast_due 狀態的更多資訊,請參閱我們關於這些狀態的額外文件

非會話付款通知

由於 SCA 法規要求客戶即使在他們的訂閱處於啟用狀態時也偶爾驗證他們的付款詳細資訊,因此當需要非會話付款確認時,Cashier 可以向客戶發送通知。例如,這可能在訂閱續訂時發生。可以透過將 CASHIER_PAYMENT_NOTIFICATION 環境變數設定為通知類別來啟用 Cashier 的付款通知。預設情況下,此通知已停用。當然,Cashier 包含您可以為此目的使用的通知類別,但如果需要,您可以自由提供您自己的通知類別。

1CASHIER_PAYMENT_NOTIFICATION=Laravel\Cashier\Notifications\ConfirmPayment

為了確保非會話付款確認通知被傳遞,請驗證您的應用程式已設定 Stripe webhook,並且 invoice.payment_action_required webhook 已在您的 Stripe 儀表板中啟用。此外,您的 Billable 模型也應該使用 Laravel 的 Illuminate\Notifications\Notifiable trait。

即使客戶手動進行需要額外確認的付款,也會發送通知。不幸的是,Stripe 無法知道付款是手動完成還是「非會話」完成的。但是,如果客戶在已經確認付款後訪問付款頁面,他們只會看到「付款成功」訊息。客戶將不會被允許意外地確認相同的付款兩次並產生意外的第二次扣款。

Stripe SDK

Cashier 的許多物件都是 Stripe SDK 物件的包裝器。如果您想直接與 Stripe 物件互動,您可以方便地使用 asStripe 方法檢索它們。

1$stripeSubscription = $subscription->asStripeSubscription();
2 
3$stripeSubscription->application_fee_percent = 5;
4 
5$stripeSubscription->save();

您也可以使用 updateStripeSubscription 方法直接更新 Stripe 訂閱。

1$subscription->updateStripeSubscription(['application_fee_percent' => 5]);

如果您想直接使用 Stripe\StripeClient 客戶端,您可以調用 Cashier 類別上的 stripe 方法。例如,您可以使用此方法來存取 StripeClient 實例,並從您的 Stripe 帳戶檢索價格列表。

1use Laravel\Cashier\Cashier;
2 
3$prices = Cashier::stripe()->prices->all();

測試

當測試使用 Cashier 的應用程式時,您可以模擬對 Stripe API 的實際 HTTP 請求;但是,這需要您部分地重新實作 Cashier 自己的行為。因此,我們建議允許您的測試命中實際的 Stripe API。雖然這比較慢,但它提供了更多的信心,讓您確信您的應用程式正在按預期工作,並且任何慢速測試都可以放置在它們自己的 Pest / PHPUnit 測試群組中。

在測試時,請記住 Cashier 本身已經有一個很棒的測試套件,因此您應該只專注於測試您自己的應用程式的訂閱和付款流程,而不是每個底層的 Cashier 行為。

若要開始,請將您的 Stripe secret 的測試版本新增到您的 phpunit.xml 檔案中。

1<env name="STRIPE_SECRET" value="sk_test_<your-key>"/>

現在,每當您在測試時與 Cashier 互動時,它都會將實際的 API 請求發送到您的 Stripe 測試環境。為了方便起見,您應該預先在您的 Stripe 測試帳戶中填寫您在測試期間可能使用的訂閱/價格。

為了測試各種帳單情境,例如信用卡拒絕和失敗,您可以使用 Stripe 提供的各種測試卡號和令牌

Laravel 是最具生產力的方式來
建構、部署和監控軟體。