Laravel Cashier (Stripe)
- 簡介
- 升級 Cashier
- 安裝
- 設定
- 快速入門
- 客戶
- 付款方式
- 訂閱
- 訂閱試用期
- 處理 Stripe Webhooks
- 單次收費
- 結帳
- 發票
- 處理付款失敗
- 強化客戶驗證 (SCA)
- Stripe SDK
- 測試
簡介
Laravel Cashier Stripe 為 Stripe 的訂閱計費服務提供富有表現力、流暢的介面。它幾乎處理了所有您不願意的樣板訂閱計費程式碼。除了基本的訂閱管理外,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 Authenticatable4{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-key2STRIPE_SECRET=your-stripe-secret3STRIPE_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(): void7{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 CashierSubscription4{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 提供元資料
在銷售產品時,通常會透過您自己的應用程式定義的 Cart
和 Order
模型來追蹤已完成的訂單和購買的產品。當將客戶重新導向到 Stripe Checkout 以完成購買時,您可能需要提供現有的訂單識別碼,以便在客戶重新導向回您的應用程式時,您可以將已完成的購買與相應的訂單相關聯。
為了完成此操作,您可以向 checkout
方法提供 metadata
陣列。讓我們想像一下,當使用者開始結帳流程時,會在我們的應用程式中建立待處理的 Order
。請記住,此範例中的 Cart
和 Order
模型僅為說明用途,並非由 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@endif4 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 Subscribed10{11 /**12 * Handle an incoming request.13 */14 public function handle(Request $request, Closure $next): Response15 {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 Billing3</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|null5{6 return $this->company_name;7}
同樣地,您可以覆寫 stripeEmail
、stripePhone
、stripeAddress
和 stripePreferredLocales
方法。當更新 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 Method8</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 Payment8</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
方法的第一個引數應該是訂閱的內部類型。如果您的應用程式僅提供單一訂閱,您可以將其命名為 default
或 primary
。此訂閱類型僅供內部應用程式使用,不應向使用者顯示。此外,它不應包含空格,並且在建立訂閱後絕不應更改。第二個引數是使用者訂閱的特定價格。此值應對應於 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' => 303]);
數量
如果您想在建立訂閱時為價格設定特定的數量,您應該在建立訂閱之前調用訂閱建立器上的 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.995}
您也可以檢索目前套用至顧客或訂閱的折扣
1$discount = $billable->discount();2 3$discount = $subscription->discount();
傳回的 Laravel\Cashier\Discount
實例裝飾底層的 Stripe\Discount
物件實例。您可以透過調用 coupon
方法來檢索與此折扣相關的優惠券
1$coupon = $subscription->discount()->coupon();
如果您想將新的優惠券或促銷代碼套用至顧客或訂閱,您可以透過 applyCoupon
或 applyPromotionCode
方法來執行此操作
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
方法也會傳回 true
。subscribed
方法將訂閱類型作為其第一個引數接受
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 EnsureUserIsSubscribed10{11 /**12 * Handle an incoming request.13 *14 * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next15 */16 public function handle(Request $request, Closure $next): Response17 {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_due
或 incomplete
狀態時仍被視為有效,您可以使用 Cashier 提供的 keepPastDueSubscriptionsActive
和 keepIncompleteSubscriptionsActive
方法。通常,這些方法應在您的 App\Providers\AppServiceProvider
的 register
方法中調用
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
狀態時,swap
和 updateQuantity
方法將拋出例外。
訂閱範圍
大多數訂閱狀態也可用作查詢範圍,以便您可以輕鬆地在資料庫中查詢處於給定狀態的訂閱
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 美元。您可以使用 incrementQuantity
和 decrementQuantity
方法輕鬆地增加或減少您的訂閱數量
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');
如果您想新增具有特定數量的價格,您可以將數量作為 addPrice
或 addPriceAndInvoice
方法的第二個引數傳遞
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_price
和 quantity
屬性將為 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(): array7{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 還提供 isNotTaxExempt
、isTaxExempt
和 reverseChargeApplies
方法來判斷顧客是否免稅。這些方法將調用 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): void13 {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->paymentMethodId6 );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
方法可供您使用,但建議您使用具有預定義價格的 invoicePrice
和 tabPrice
方法。這樣做,您將可以在您的 Stripe 儀表板中獲得關於每個產品銷售的更好的分析和數據。
invoice
、invoicePrice
和 invoiceFor
方法將建立一個 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});
pay
和 payWith
方法接受以您的應用程式所使用貨幣的最小面額表示的付款金額。例如,如果客戶以美元付款,金額應以美分指定。
退款
如果您需要退還 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 @endforeach9</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', 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 InvoiceRenderer13{14 /**15 * Render the given invoice and return the raw PDF bytes.16 */17 public function render(Invoice $invoice, array $data = [], array $options = []): string18 {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_url
和 cancel_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 的模型上的 charge
、invoiceFor
和 invoice
方法可能會拋出付款例外。當與訂閱互動時,SubscriptionBuilder
上的 create
方法,以及 Subscription
和 SubscriptionItem
模型上的 incrementAndInvoice
和 swapAndInvoice
方法可能會拋出不完整付款例外。
可以使用可計費模型或訂閱實例上的 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 呈現的付款確認畫面可以針對特定銀行或發卡銀行的付款流程進行客製化,並且可以包含額外的卡片確認、臨時小額扣款、單獨的裝置認證或其他形式的驗證。
不完整和逾期狀態
當付款需要額外確認時,訂閱將保持 incomplete
或 past_due
狀態,如其 stripe_status
資料庫欄位所示。一旦付款確認完成,並且您的應用程式透過 webhook 收到 Stripe 的完成通知,Cashier 將自動啟用客戶的訂閱。
有關 incomplete
和 past_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 提供的各種測試卡號和令牌。