Laravel Cashier (Stripe)
- 簡介
- 升級 Cashier
- 安裝
- 設定
- 快速入門
- 客戶
- 付款方式
- 訂閱
- 訂閱試用
- 處理 Stripe Webhook
- 單次收費
- 結帳
- 發票
- 處理付款失敗
- 加強客戶身份驗證 (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 套件
composer require laravel/cashier
安裝套件後,使用 vendor:publish
Artisan 命令發佈 Cashier 的遷移
php artisan vendor:publish --tag="cashier-migrations"
然後,遷移您的資料庫
php artisan migrate
Cashier 的遷移會將幾個資料行新增到您的 users
資料表。它們也會建立一個新的 subscriptions
資料表來儲存所有客戶的訂閱,以及一個 subscription_items
資料表來儲存具有多種價格的訂閱。
如果您願意,也可以使用 vendor:publish
Artisan 命令發佈 Cashier 的設定檔
php artisan vendor:publish --tag="cashier-config"
最後,為了確保 Cashier 正確處理所有 Stripe 事件,請記住設定 Cashier 的 Webhook 處理。
Stripe 建議用於儲存 Stripe 識別碼的任何資料行都應區分大小寫。因此,當使用 MySQL 時,您應確保 stripe_id
資料行的資料行定序設定為 utf8_bin
。有關此方面的更多資訊,請參閱 Stripe 文件。
設定
可計費模型
在使用 Cashier 之前,請將 Billable
特徵新增到您的可計費模型定義中。通常,這會是 App\Models\User
模型。此特徵提供各種方法,讓您可以執行常見的計費工作,例如建立訂閱、套用優惠券和更新付款方式資訊
use Laravel\Cashier\Billable; class User extends Authenticatable{ use Billable;}
Cashier 假設您的可計費模型會是 Laravel 隨附的 App\Models\User
類別。如果您想變更此設定,可以透過 useCustomerModel
方法指定不同的模型。此方法通常應在 AppServiceProvider
類別的 boot
方法中呼叫
use App\Models\Cashier\User;use Laravel\Cashier\Cashier; /** * Bootstrap any application services. */public function boot(): void{ Cashier::useCustomerModel(User::class);}
如果您使用的模型不是 Laravel 提供的 App\Models\User
模型,您需要發佈和變更提供的Cashier 遷移,以符合您的替代模型的資料表名稱。
API 金鑰
接下來,您應在應用程式的 .env
檔案中設定您的 Stripe API 金鑰。您可以從 Stripe 控制面板擷取您的 Stripe API 金鑰
STRIPE_KEY=your-stripe-keySTRIPE_SECRET=your-stripe-secretSTRIPE_WEBHOOK_SECRET=your-stripe-webhook-secret
您應確保在應用程式的 .env
檔案中定義了 STRIPE_WEBHOOK_SECRET
環境變數,因為此變數用於確保傳入的 Webhook 實際上來自 Stripe。
貨幣設定
預設的 Cashier 貨幣是美元 (USD)。您可以透過在應用程式的 .env
檔案中設定 CASHIER_CURRENCY
環境變數來變更預設貨幣
CASHIER_CURRENCY=eur
除了設定 Cashier 的貨幣外,您也可以指定一個地區設定,以便在格式化金額值以顯示在發票上時使用。在內部,Cashier 會使用 PHP 的 NumberFormatter
類別來設定貨幣地區設定
CASHIER_CURRENCY_LOCALE=nl_BE
若要使用 en
以外的地區設定,請確保您的伺服器已安裝並設定 ext-intl
PHP 擴充功能。
稅務設定
感謝 Stripe Tax,可以自動計算 Stripe 產生的所有發票的稅金。您可以透過在應用程式的 App\Providers\AppServiceProvider
類別的 boot
方法中呼叫 calculateTaxes
方法來啟用自動稅金計算
use Laravel\Cashier\Cashier; /** * Bootstrap any application services. */public function boot(): void{ Cashier::calculateTaxes();}
一旦啟用稅金計算,任何新的訂閱和任何產生的一次性發票都將收到自動稅金計算。
為了使此功能正常運作,您的客戶的帳單詳細資訊(例如客戶的姓名、地址和稅務 ID)需要與 Stripe 同步。您可以使用 Cashier 提供的客戶資料同步和稅務 ID 方法來完成此作業。
記錄
Cashier 允許您指定在記錄嚴重的 Stripe 錯誤時要使用的記錄通道。您可以透過在應用程式的 .env
檔案中定義 CASHIER_LOGGER
環境變數來指定記錄通道
CASHIER_LOGGER=stack
由對 Stripe 的 API 呼叫產生的例外狀況將透過您應用程式的預設記錄通道記錄。
使用自訂模型
您可以自由地透過定義自己的模型並擴充對應的 Cashier 模型來擴充 Cashier 內部使用的模型
use Laravel\Cashier\Subscription as CashierSubscription; class Subscription extends CashierSubscription{ // ...}
定義模型後,您可以透過 Laravel\Cashier\Cashier
類別指示 Cashier 使用您的自訂模型。通常,您應在應用程式的 App\Providers\AppServiceProvider
類別的 boot
方法中通知 Cashier 您的自訂模型
use App\Models\Cashier\Subscription;use App\Models\Cashier\SubscriptionItem; /** * Bootstrap any application services. */public function boot(): void{ Cashier::useSubscriptionModel(Subscription::class); Cashier::useSubscriptionItemModel(SubscriptionItem::class);}
快速入門
銷售產品
在使用 Stripe Checkout 之前,您應在 Stripe 儀表板中定義具有固定價格的產品。此外,您應設定 Cashier 的 Webhook 處理。
透過您的應用程式提供產品和訂閱計費可能令人卻步。然而,感謝 Cashier 和 Stripe Checkout,您可以輕鬆建立現代、穩健的付款整合。
為了向顧客收取非週期性、單次收費產品的費用,我們將利用 Cashier 引導顧客至 Stripe Checkout,讓他們提供付款詳細資訊並確認購買。一旦透過 Checkout 完成付款,顧客將被重新導向至您應用程式中選擇的成功 URL。
use Illuminate\Http\Request; Route::get('/checkout', function (Request $request) { $stripePriceId = 'price_deluxe_album'; $quantity = 1; return $request->user()->checkout([$stripePriceId => $quantity], [ 'success_url' => route('checkout-success'), 'cancel_url' => route('checkout-cancel'), ]);})->name('checkout'); Route::view('/checkout/success', 'checkout.success')->name('checkout-success');Route::view('/checkout/cancel', 'checkout.cancel')->name('checkout-cancel');
如您在上面的範例中所見,我們將利用 Cashier 提供的 checkout
方法,將顧客重新導向至 Stripe Checkout 以處理指定的「價格識別碼」。在使用 Stripe 時,「價格」指的是特定產品的定義價格。
如有必要,checkout
方法會自動在 Stripe 中建立顧客,並將該 Stripe 顧客記錄連結到您應用程式資料庫中的對應使用者。完成結帳流程後,顧客將被重新導向至專用的成功或取消頁面,您可以在其中向顧客顯示資訊訊息。
向 Stripe Checkout 提供 Meta Data
在銷售產品時,通常會透過您應用程式定義的 Cart
和 Order
模型來追蹤已完成的訂單和購買的產品。當將顧客重新導向至 Stripe Checkout 以完成購買時,您可能需要提供現有的訂單識別碼,以便在顧客重新導向回您的應用程式時,將已完成的購買與對應的訂單關聯起來。
為了實現這一點,您可以向 checkout
方法提供一個 metadata
陣列。假設當使用者開始結帳流程時,我們的應用程式中建立了一個待處理的 Order
。請記住,本範例中的 Cart
和 Order
模型僅為說明用途,並非由 Cashier 提供。您可以根據自己應用程式的需求自由實作這些概念。
use App\Models\Cart;use App\Models\Order;use Illuminate\Http\Request; Route::get('/cart/{cart}/checkout', function (Request $request, Cart $cart) { $order = Order::create([ 'cart_id' => $cart->id, 'price_ids' => $cart->price_ids, 'status' => 'incomplete', ]); return $request->user()->checkout($order->price_ids, [ 'success_url' => route('checkout-success').'?session_id={CHECKOUT_SESSION_ID}', 'cancel_url' => route('checkout-cancel'), 'metadata' => ['order_id' => $order->id], ]);})->name('checkout');
如您在上面的範例中所見,當使用者開始結帳流程時,我們會將所有購物車/訂單關聯的 Stripe 價格識別碼提供給 checkout
方法。當然,您的應用程式負責在顧客新增商品時,將這些商品與「購物車」或訂單關聯起來。我們也透過 metadata
陣列將訂單的 ID 提供給 Stripe Checkout 會話。最後,我們在結帳成功路由中新增了 CHECKOUT_SESSION_ID
範本變數。當 Stripe 將顧客重新導向回您的應用程式時,此範本變數將自動填入結帳會話 ID。
接下來,讓我們建立結帳成功路由。這是使用者透過 Stripe Checkout 完成購買後將被重新導向的路由。在此路由中,我們可以檢索 Stripe Checkout 會話 ID 和關聯的 Stripe Checkout 實例,以便存取我們提供的 meta data 並相應地更新顧客的訂單。
use App\Models\Order;use Illuminate\Http\Request;use Laravel\Cashier\Cashier; Route::get('/checkout/success', function (Request $request) { $sessionId = $request->get('session_id'); if ($sessionId === null) { return; } $session = Cashier::stripe()->checkout->sessions->retrieve($sessionId); if ($session->payment_status !== 'paid') { return; } $orderId = $session['metadata']['order_id'] ?? null; $order = Order::findOrFail($orderId); $order->update(['status' => 'completed']); return view('checkout-success', ['order' => $order]);})->name('checkout-success');
有關結帳會話物件包含的資料的更多資訊,請參閱 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 會話。
use Illuminate\Http\Request; Route::get('/subscription-checkout', function (Request $request) { return $request->user() ->newSubscription('default', 'price_basic_monthly') ->trialDays(5) ->allowPromotionCodes() ->checkout([ 'success_url' => route('your-success-route'), 'cancel_url' => route('your-cancel-route'), ]);});
如您在上面的範例中所見,我們會將顧客重新導向至 Stripe Checkout 會話,讓他們可以訂閱我們的基本方案。在成功結帳或取消後,顧客將被重新導向回我們提供給 checkout
方法的 URL。為了知道他們的訂閱何時真正開始 (因為某些付款方式需要幾秒鐘才能處理),我們還需要設定 Cashier 的 webhook 處理。
現在顧客可以開始訂閱了,我們需要限制應用程式的某些部分,讓只有訂閱的使用者才能存取它們。當然,我們隨時可以透過 Cashier 的 Billable
特性提供的 subscribed
方法來判斷使用者目前的訂閱狀態。
@if ($user->subscribed()) <p>You are subscribed.</p>@endif
我們甚至可以輕鬆判斷使用者是否訂閱了特定的產品或價格。
@if ($user->subscribedToProduct('pro_basic')) <p>You are subscribed to our Basic product.</p>@endif @if ($user->subscribedToPrice('price_basic_monthly')) <p>You are subscribed to our monthly Basic plan.</p>@endif
建立已訂閱的中介層
為了方便起見,您可能希望建立一個中介層,以判斷傳入的請求是否來自已訂閱的使用者。一旦定義了此中介層,您可以輕鬆地將其指派給一個路由,以防止未訂閱的使用者存取該路由。
<?php namespace App\Http\Middleware; use Closure;use Illuminate\Http\Request;use Symfony\Component\HttpFoundation\Response; class Subscribed{ /** * Handle an incoming request. */ public function handle(Request $request, Closure $next): Response { if (! $request->user()?->subscribed()) { // Redirect user to billing page and ask them to subscribe... return redirect('/billing'); } return $next($request); }}
一旦定義了中介層,您可以將其指派給一個路由。
use App\Http\Middleware\Subscribed; Route::get('/dashboard', function () { // ...})->middleware([Subscribed::class]);
允許顧客管理他們的計費方案
當然,顧客可能想要將他們的訂閱方案變更為其他產品或「層級」。最簡單的方法是引導顧客至 Stripe 的顧客計費入口網站,該入口網站提供了一個託管的使用者介面,讓顧客可以下載發票、更新他們的付款方式以及變更訂閱方案。
首先,在您的應用程式中定義一個連結或按鈕,將使用者導向至一個 Laravel 路由,我們將使用該路由來啟動計費入口網站會話。
<a href="{{ route('billing') }}"> Billing</a>
接下來,讓我們定義一個啟動 Stripe 顧客計費入口網站會話並將使用者重新導向至入口網站的路由。redirectToBillingPortal
方法接受使用者在退出入口網站時應返回的 URL。
use Illuminate\Http\Request; Route::get('/billing', function (Request $request) { return $request->user()->redirectToBillingPortal(route('dashboard'));})->middleware(['auth'])->name('billing');
只要您已設定 Cashier 的 webhook 處理,Cashier 就會透過檢查來自 Stripe 的傳入 webhook,自動保持您應用程式中與 Cashier 相關的資料庫表格同步。因此,舉例來說,當使用者透過 Stripe 的顧客計費入口網站取消其訂閱時,Cashier 將收到對應的 webhook,並在您應用程式的資料庫中將訂閱標記為「已取消」。
客戶
擷取客戶
您可以使用 Cashier::findBillable
方法,依據 Stripe ID 檢索顧客。此方法將傳回可計費模型的實例。
use Laravel\Cashier\Cashier; $user = Cashier::findBillable($stripeId);
建立客戶
有時,您可能希望在不開始訂閱的情況下建立 Stripe 顧客。您可以使用 createAsStripeCustomer
方法來實現這一點。
$stripeCustomer = $user->createAsStripeCustomer();
一旦在 Stripe 中建立了顧客,您可以在稍後開始訂閱。您可以提供一個選用的 $options
陣列,以傳遞任何其他Stripe API 支援的顧客建立參數。
$stripeCustomer = $user->createAsStripeCustomer($options);
如果您想傳回可計費模型的 Stripe 顧客物件,可以使用 asStripeCustomer
方法。
$stripeCustomer = $user->asStripeCustomer();
如果您想檢索給定可計費模型的 Stripe 顧客物件,但不確定該可計費模型是否已是 Stripe 中的顧客,可以使用 createOrGetStripeCustomer
方法。如果 Stripe 中尚不存在顧客,此方法將在 Stripe 中建立新的顧客。
$stripeCustomer = $user->createOrGetStripeCustomer();
更新客戶
有時,您可能希望直接使用其他資訊來更新 Stripe 顧客。您可以使用 updateStripeCustomer
方法來實現這一點。此方法接受一個Stripe API 支援的顧客更新選項陣列。
$stripeCustomer = $user->updateStripeCustomer($options);
餘額
Stripe 允許您為顧客的「餘額」充值或扣款。稍後,此餘額將在新的發票上充值或扣款。若要檢查顧客的總餘額,您可以使用可計費模型上提供的 balance
方法。balance
方法將傳回以顧客貨幣表示的格式化字串餘額。
$balance = $user->balance();
若要為顧客的餘額充值,您可以向 creditBalance
方法提供一個值。如果需要,您也可以提供描述。
$user->creditBalance(500, 'Premium customer top-up.');
向 debitBalance
方法提供一個值將從顧客的餘額中扣款。
$user->debitBalance(300, 'Bad usage penalty.');
applyBalance
方法將為顧客建立新的顧客餘額交易。您可以使用 balanceTransactions
方法來檢索這些交易記錄,這對於提供顧客可以檢閱的充值和扣款記錄非常有用。
// Retrieve all transactions...$transactions = $user->balanceTransactions(); foreach ($transactions as $transaction) { // Transaction amount... $amount = $transaction->amount(); // $2.31 // Retrieve the related invoice when available... $invoice = $transaction->invoice();}
稅務 ID
Cashier 提供了一種管理顧客稅務 ID 的簡單方法。例如,可以使用 taxIds
方法來檢索指派給顧客的所有稅務 ID,並以集合的形式呈現。
$taxIds = $user->taxIds();
您也可以透過其識別碼檢索顧客的特定稅務 ID。
$taxId = $user->findTaxId('txi_belgium');
您可以透過向 createTaxId
方法提供有效的類型和值來建立新的稅務 ID。
$taxId = $user->createTaxId('eu_vat', 'BE0123456789');
createTaxId
方法將立即將增值稅 ID 新增至顧客的帳戶。Stripe 也會驗證增值稅 ID;但是,這是一個非同步的流程。您可以透過訂閱 customer.tax_id.updated
webhook 事件並檢查增值稅 ID 的 verification
參數來接收驗證更新的通知。有關處理 webhook 的更多資訊,請參閱有關定義 webhook 處理器的文件。
您可以使用 deleteTaxId
方法來刪除稅務 ID。
$user->deleteTaxId('txi_belgium');
將客戶資料與 Stripe 同步
通常,當您應用程式的使用者更新其姓名、電子郵件地址或其他 Stripe 也會儲存的資訊時,您應該通知 Stripe 這些更新。這樣做,Stripe 儲存的資訊將與您的應用程式保持同步。
若要自動執行此操作,您可以在可計費模型上定義一個事件監聽器,以對模型的 updated
事件做出反應。然後,在您的事件監聽器中,您可以調用模型上的 syncStripeCustomerDetails
方法。
use App\Models\User;use function Illuminate\Events\queueable; /** * The "booted" method of the model. */protected static function booted(): void{ static::updated(queueable(function (User $customer) { if ($customer->hasStripeId()) { $customer->syncStripeCustomerDetails(); } }));}
現在,每次更新您的顧客模型時,其資訊都會與 Stripe 同步。為了方便起見,Cashier 會在初始建立顧客時自動將顧客的資訊與 Stripe 同步。
您可以透過覆寫 Cashier 提供的一系列方法來自訂用於將顧客資訊同步至 Stripe 的欄位。例如,您可以覆寫 stripeName
方法,以自訂當 Cashier 將顧客資訊同步至 Stripe 時,應被視為顧客「姓名」的屬性。
/** * Get the customer name that should be synced to Stripe. */public function stripeName(): string|null{ return $this->company_name;}
類似地,您可以覆寫 stripeEmail
、stripePhone
、stripeAddress
和 stripePreferredLocales
方法。當更新 Stripe 顧客物件時,這些方法會將資訊同步至其對應的顧客參數。如果您希望完全控制顧客資訊同步流程,您可以覆寫 syncStripeCustomerDetails
方法。
帳單入口網站
Stripe 提供一個簡單的方法來設定計費入口網站,讓您的顧客可以管理他們的訂閱、付款方式並檢視他們的帳單歷史記錄。您可以透過從控制器或路由中調用可計費模型上的 redirectToBillingPortal
方法,將您的使用者重新導向至計費入口網站。
use Illuminate\Http\Request; Route::get('/billing-portal', function (Request $request) { return $request->user()->redirectToBillingPortal();});
預設情況下,當使用者完成管理其訂閱時,他們可以透過 Stripe 計費入口網站中的連結返回您應用程式的 home
路由。您可以將使用者應該返回的自訂 URL 作為 redirectToBillingPortal
方法的引數傳遞。
use Illuminate\Http\Request; Route::get('/billing-portal', function (Request $request) { return $request->user()->redirectToBillingPortal(route('billing'));});
如果您想要產生計費入口網站的 URL 而不產生 HTTP 重新導向回應,您可以調用 billingPortalUrl
方法。
$url = $request->user()->billingPortalUrl(route('billing'));
付款方式
儲存付款方式
為了使用 Stripe 建立訂閱或執行「一次性」收費,您需要儲存付款方式並從 Stripe 檢索其識別碼。用於實現此目的的方法會因您是否打算將付款方式用於訂閱或單次收費而異,因此我們將在下面檢查兩者。
訂閱的付款方式
當儲存顧客的信用卡資訊以供訂閱日後使用時,必須使用 Stripe 的「設定意圖」API 來安全地收集顧客的付款方式詳細資訊。「設定意圖」向 Stripe 表示收取顧客付款方式的意圖。Cashier 的 Billable
特性包含 createSetupIntent
方法,可輕鬆建立新的設定意圖。您應該從將呈現收集顧客付款方式詳細資訊表單的路由或控制器中調用此方法。
return view('update-payment-method', [ 'intent' => $user->createSetupIntent()]);
在您建立 Setup Intent 並將其傳遞給視圖後,您應該將其密鑰附加到將收集付款方式的元素。例如,考慮這個「更新付款方式」表單
<input id="card-holder-name" type="text"> <!-- Stripe Elements Placeholder --><div id="card-element"></div> <button id="card-button" data-secret="{{ $intent->client_secret }}"> Update Payment Method</button>
接著,可以使用 Stripe.js 程式庫將 Stripe Element 附加到表單,並安全地收集客戶的付款詳細資訊
<script src="https://js.stripe.com/v3/"></script> <script> const stripe = Stripe('stripe-public-key'); const elements = stripe.elements(); const cardElement = elements.create('card'); cardElement.mount('#card-element');</script>
接下來,可以使用 Stripe 的 confirmCardSetup
方法來驗證卡片,並從 Stripe 取得安全的「付款方式識別碼」
const cardHolderName = document.getElementById('card-holder-name');const cardButton = document.getElementById('card-button');const clientSecret = cardButton.dataset.secret; cardButton.addEventListener('click', async (e) => { const { setupIntent, error } = await stripe.confirmCardSetup( clientSecret, { payment_method: { card: cardElement, billing_details: { name: cardHolderName.value } } } ); if (error) { // Display "error.message" to the user... } else { // The card has been verified successfully... }});
在 Stripe 驗證卡片後,您可以將產生的 setupIntent.payment_method
識別碼傳遞到您的 Laravel 應用程式,並將其附加到客戶。付款方式可以新增為新的付款方式或用於更新預設付款方式。您也可以立即使用付款方式識別碼來建立新的訂閱。
如果您想了解有關 Setup Intents 和收集客戶付款詳細資訊的更多資訊,請查閱 Stripe 提供的此概述。
單次收費的付款方式
當然,當針對客戶的付款方式進行單次收費時,我們只需要使用一次付款方式識別碼。由於 Stripe 的限制,您可能無法將客戶儲存的預設付款方式用於單次收費。您必須允許客戶使用 Stripe.js 程式庫輸入其付款方式詳細資訊。例如,請考慮以下表單
<input id="card-holder-name" type="text"> <!-- Stripe Elements Placeholder --><div id="card-element"></div> <button id="card-button"> Process Payment</button>
在定義這樣的表單之後,可以使用 Stripe.js 程式庫將 Stripe Element 附加到表單,並安全地收集客戶的付款詳細資訊
<script src="https://js.stripe.com/v3/"></script> <script> const stripe = Stripe('stripe-public-key'); const elements = stripe.elements(); const cardElement = elements.create('card'); cardElement.mount('#card-element');</script>
接下來,可以使用 Stripe 的 createPaymentMethod
方法來驗證卡片,並從 Stripe 取得安全的「付款方式識別碼」
const cardHolderName = document.getElementById('card-holder-name');const cardButton = document.getElementById('card-button'); cardButton.addEventListener('click', async (e) => { const { paymentMethod, error } = await stripe.createPaymentMethod( 'card', cardElement, { billing_details: { name: cardHolderName.value } } ); if (error) { // Display "error.message" to the user... } else { // The card has been verified successfully... }});
如果卡片驗證成功,您可以將 paymentMethod.id
傳遞到您的 Laravel 應用程式並處理單次收費。
擷取付款方式
可計費模型實例上的 paymentMethods
方法會傳回 Laravel\Cashier\PaymentMethod
實例的集合
$paymentMethods = $user->paymentMethods();
預設情況下,此方法將傳回每種類型的付款方式。若要擷取特定類型的付款方式,您可以將 type
作為引數傳遞給該方法
$paymentMethods = $user->paymentMethods('sepa_debit');
若要擷取客戶的預設付款方式,可以使用 defaultPaymentMethod
方法
$paymentMethod = $user->defaultPaymentMethod();
您可以使用 findPaymentMethod
方法擷取附加到可計費模型的特定付款方式
$paymentMethod = $user->findPaymentMethod($paymentMethodId);
付款方式存在性
若要判斷可計費模型是否在其帳戶上附加了預設付款方式,請調用 hasDefaultPaymentMethod
方法
if ($user->hasDefaultPaymentMethod()) { // ...}
您可以使用 hasPaymentMethod
方法判斷可計費模型是否在其帳戶上附加了至少一種付款方式
if ($user->hasPaymentMethod()) { // ...}
此方法將判斷可計費模型是否有任何付款方式。若要判斷模型是否存在特定類型的付款方式,您可以將 type
作為引數傳遞給該方法
if ($user->hasPaymentMethod('sepa_debit')) { // ...}
更新預設付款方式
updateDefaultPaymentMethod
方法可用於更新客戶的預設付款方式資訊。此方法接受 Stripe 付款方式識別碼,並將新的付款方式指定為預設帳單付款方式
$user->updateDefaultPaymentMethod($paymentMethod);
若要將您的預設付款方式資訊與客戶在 Stripe 中的預設付款方式資訊同步,您可以使用 updateDefaultPaymentMethodFromStripe
方法
$user->updateDefaultPaymentMethodFromStripe();
客戶的預設付款方式只能用於開立發票和建立新的訂閱。由於 Stripe 的限制,它不能用於單次收費。
新增付款方式
若要新增新的付款方式,您可以在可計費模型上呼叫 addPaymentMethod
方法,並傳遞付款方式識別碼
$user->addPaymentMethod($paymentMethod);
若要了解如何擷取付款方式識別碼,請查閱付款方式儲存文件。
刪除付款方式
若要刪除付款方式,您可以在您想要刪除的 Laravel\Cashier\PaymentMethod
實例上呼叫 delete
方法
$paymentMethod->delete();
deletePaymentMethod
方法將從可計費模型中刪除特定的付款方式
$user->deletePaymentMethod('pm_visa');
deletePaymentMethods
方法將刪除可計費模型的所有付款方式資訊
$user->deletePaymentMethods();
預設情況下,此方法將刪除每種類型的付款方式。若要刪除特定類型的付款方式,您可以將 type
作為引數傳遞給該方法
$user->deletePaymentMethods('sepa_debit');
如果使用者有活動訂閱,您的應用程式不應允許他們刪除其預設付款方式。
訂閱
訂閱提供了一種為您的客戶設定定期付款的方式。由 Cashier 管理的 Stripe 訂閱支援多個訂閱價格、訂閱數量、試用等功能。
建立訂閱
若要建立訂閱,首先擷取可計費模型的實例,這通常是 App\Models\User
的實例。擷取模型實例後,您可以使用 newSubscription
方法來建立模型的訂閱
use Illuminate\Http\Request; Route::post('/user/subscribe', function (Request $request) { $request->user()->newSubscription( 'default', 'price_monthly' )->create($request->paymentMethodId); // ...});
傳遞給 newSubscription
方法的第一個引數應為訂閱的內部類型。如果您的應用程式僅提供單一訂閱,您可以將其稱為 default
或 primary
。此訂閱類型僅供內部應用程式使用,並不打算向使用者顯示。此外,它不應包含空格,並且在建立訂閱後絕不應變更。第二個引數是使用者訂閱的特定價格。此值應對應於 Stripe 中的價格識別碼。
create
方法(接受Stripe 付款方式識別碼或 Stripe PaymentMethod
物件)將開始訂閱,並使用可計費模型的 Stripe 客戶 ID 和其他相關帳單資訊更新您的資料庫。
將付款方式識別碼直接傳遞給 create
訂閱方法也會自動將其新增到使用者儲存的付款方式中。
透過發票電子郵件收集定期付款
您可以指示 Stripe 每次客戶的定期付款到期時都向客戶發送發票電子郵件,而不是自動收集客戶的定期付款。然後,客戶可以在收到發票後手動支付發票。透過發票收集定期付款時,客戶不需要預先提供付款方式
$user->newSubscription('default', 'price_monthly')->createAndSendInvoice();
客戶在訂閱取消之前支付發票的時間長度由 days_until_due
選項決定。預設情況下,這是 30 天;但是,如果您願意,可以為此選項提供特定值
$user->newSubscription('default', 'price_monthly')->createAndSendInvoice([], [ 'days_until_due' => 30]);
數量
如果您想在建立訂閱時為價格設定特定的數量,您應該在建立訂閱之前在訂閱建構器上調用 quantity
方法
$user->newSubscription('default', 'price_monthly') ->quantity(5) ->create($paymentMethod);
其他詳細資訊
如果您想指定 Stripe 支援的其他客戶或訂閱選項,您可以將它們作為第二個和第三個引數傳遞給 create
方法
$user->newSubscription('default', 'price_monthly')->create($paymentMethod, [ 'email' => $email,], [ 'metadata' => ['note' => 'Some extra information.'],]);
優惠券
如果您想在建立訂閱時套用優惠券,您可以使用 withCoupon
方法
$user->newSubscription('default', 'price_monthly') ->withCoupon('code') ->create($paymentMethod);
或者,如果您想套用Stripe 促銷代碼,您可以使用 withPromotionCode
方法
$user->newSubscription('default', 'price_monthly') ->withPromotionCode('promo_code_id') ->create($paymentMethod);
給定的促銷代碼 ID 應該是分配給促銷代碼的 Stripe API ID,而不是客戶面對的促銷代碼。如果您需要根據給定的客戶面對促銷代碼找到促銷代碼 ID,您可以使用 findPromotionCode
方法
// Find a promotion code ID by its customer facing code...$promotionCode = $user->findPromotionCode('SUMMERSALE'); // Find an active promotion code ID by its customer facing code...$promotionCode = $user->findActivePromotionCode('SUMMERSALE');
在上面的範例中,傳回的 $promotionCode
物件是 Laravel\Cashier\PromotionCode
的實例。此類別裝飾了底層的 Stripe\PromotionCode
物件。您可以透過調用 coupon
方法來擷取與促銷代碼相關的優惠券
$coupon = $user->findPromotionCode('SUMMERSALE')->coupon();
優惠券實例允許您判斷折扣金額以及優惠券是否代表固定折扣或百分比折扣
if ($coupon->isPercentage()) { return $coupon->percentOff().'%'; // 21.5%} else { return $coupon->amountOff(); // $5.99}
您也可以擷取目前套用至客戶或訂閱的折扣
$discount = $billable->discount(); $discount = $subscription->discount();
傳回的 Laravel\Cashier\Discount
實例裝飾了底層的 Stripe\Discount
物件實例。您可以透過調用 coupon
方法來擷取與此折扣相關的優惠券
$coupon = $subscription->discount()->coupon();
如果您想將新的優惠券或促銷代碼套用至客戶或訂閱,您可以透過 applyCoupon
或 applyPromotionCode
方法進行
$billable->applyCoupon('coupon_id');$billable->applyPromotionCode('promotion_code_id'); $subscription->applyCoupon('coupon_id');$subscription->applyPromotionCode('promotion_code_id');
請記住,您應該使用分配給促銷代碼的 Stripe API ID,而不是客戶面對的促銷代碼。一次只能將一張優惠券或促銷代碼套用至客戶或訂閱。
有關此主題的更多資訊,請查閱 Stripe 關於優惠券和促銷代碼的文件。
新增訂閱
如果您想將訂閱新增至已具有預設付款方式的客戶,您可以在訂閱建構器上調用 add
方法
use App\Models\User; $user = User::find(1); $user->newSubscription('default', 'price_monthly')->add();
從 Stripe 控制面板建立訂閱
您也可以從 Stripe 控制面板本身建立訂閱。執行此操作時,Cashier 將同步新加入的訂閱,並將其指定為 default
類型。若要自訂分配給控制面板建立的訂閱的訂閱類型,請定義 webhook 事件處理常式。
此外,您只能透過 Stripe 控制面板建立一種訂閱類型。如果您的應用程式提供使用不同類型的多個訂閱,則只能透過 Stripe 控制面板新增一種訂閱類型。
最後,您應該始終確保每個由您的應用程式提供的訂閱類型僅新增一個活動訂閱。如果客戶有兩個 default
訂閱,則即使兩者都與您的應用程式資料庫同步,Cashier 也只會使用最近新增的訂閱。
檢查訂閱狀態
一旦客戶訂閱了您的應用程式,您可以使用各種便利的方法輕鬆檢查其訂閱狀態。首先,如果客戶具有活動訂閱,則 subscribed
方法會傳回 true
,即使訂閱目前處於試用期內。subscribed
方法接受訂閱的類型作為其第一個引數
if ($user->subscribed('default')) { // ...}
subscribed
方法也是路由中介軟體的絕佳候選者,可讓您根據使用者的訂閱狀態篩選對路由和控制器的存取
<?php namespace App\Http\Middleware; use Closure;use Illuminate\Http\Request;use Symfony\Component\HttpFoundation\Response; class EnsureUserIsSubscribed{ /** * Handle an incoming request. * * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next */ public function handle(Request $request, Closure $next): Response { if ($request->user() && ! $request->user()->subscribed('default')) { // This user is not a paying customer... return redirect('/billing'); } return $next($request); }}
如果您想判斷使用者是否仍在試用期內,您可以使用 onTrial
方法。此方法可用於判斷是否應該向使用者顯示他們仍在試用期內的警告
if ($user->subscription('default')->onTrial()) { // ...}
subscribedToProduct
方法可用於根據給定的 Stripe 產品識別碼判斷使用者是否訂閱了給定的產品。在 Stripe 中,產品是價格的集合。在此範例中,我們將判斷使用者的 default
訂閱是否已主動訂閱應用程式的「進階」產品。給定的 Stripe 產品識別碼應對應於您在 Stripe 控制面板中的其中一個產品識別碼
if ($user->subscribedToProduct('prod_premium', 'default')) { // ...}
透過將陣列傳遞給 subscribedToProduct
方法,您可以判斷使用者的 default
訂閱是否已主動訂閱應用程式的「基本」或「進階」產品
if ($user->subscribedToProduct(['prod_basic', 'prod_premium'], 'default')) { // ...}
subscribedToPrice
方法可用於判斷客戶的訂閱是否對應於給定的價格 ID
if ($user->subscribedToPrice('price_basic_monthly', 'default')) { // ...}
recurring
方法可用於判斷使用者目前是否已訂閱且不再處於試用期內
if ($user->subscription('default')->recurring()) { // ...}
如果使用者有兩個相同類型的訂閱,subscription
方法將永遠回傳最近期的訂閱。例如,使用者可能會有兩筆類型為 default
的訂閱紀錄;然而,其中一個訂閱可能是過期的舊訂閱,而另一個則是目前有效的訂閱。系統將永遠回傳最近期的訂閱,而較舊的訂閱則會保留在資料庫中以供歷史查閱。
已取消的訂閱狀態
若要判斷使用者是否曾經是有效的訂閱者但已取消訂閱,您可以使用 canceled
方法
if ($user->subscription('default')->canceled()) { // ...}
您也可以判斷使用者是否已取消訂閱,但仍在「寬限期」內,直到訂閱完全到期。例如,如果使用者在 3 月 5 日取消原定於 3 月 10 日到期的訂閱,則該使用者將處於「寬限期」直到 3 月 10 日。請注意,在此期間 subscribed
方法仍會回傳 true
if ($user->subscription('default')->onGracePeriod()) { // ...}
若要判斷使用者是否已取消訂閱且不再處於「寬限期」內,您可以使用 ended
方法
if ($user->subscription('default')->ended()) { // ...}
未完成和逾期狀態
如果訂閱在建立後需要二次付款操作,該訂閱將被標記為 incomplete
。訂閱狀態儲存在 Cashier 的 subscriptions
資料庫表格的 stripe_status
欄位中。
同樣地,如果在更換價格時需要二次付款操作,訂閱將被標記為 past_due
。當您的訂閱處於這些狀態之一時,在客戶確認付款之前將不會處於啟用狀態。若要判斷訂閱是否有未完成的付款,可以使用計費模型或訂閱實例上的 hasIncompletePayment
方法。
if ($user->hasIncompletePayment('default')) { // ...} if ($user->subscription('default')->hasIncompletePayment()) { // ...}
當訂閱有未完成的付款時,您應該將使用者導向 Cashier 的付款確認頁面,並傳遞 latestPayment
識別符。您可以使用訂閱實例上可用的 latestPayment
方法來檢索此識別符。
<a href="{{ route('cashier.payment', $subscription->latestPayment()->id) }}"> Please confirm your payment.</a>
如果您希望訂閱在 past_due
或 incomplete
狀態時仍被視為有效,您可以使用 Cashier 提供的 keepPastDueSubscriptionsActive
和 keepIncompleteSubscriptionsActive
方法。通常,這些方法應該在您 App\Providers\AppServiceProvider
的 register
方法中呼叫。
use Laravel\Cashier\Cashier; /** * Register any application services. */public function register(): void{ Cashier::keepPastDueSubscriptionsActive(); Cashier::keepIncompleteSubscriptionsActive();}
當訂閱處於 incomplete
狀態時,在付款確認之前無法變更。因此,當訂閱處於 incomplete
狀態時,swap
和 updateQuantity
方法將會拋出例外。
訂閱範圍
大多數訂閱狀態也可用作查詢範圍,以便您可以輕鬆地在資料庫中查詢處於特定狀態的訂閱。
// Get all active subscriptions...$subscriptions = Subscription::query()->active()->get(); // Get all of the canceled subscriptions for a user...$subscriptions = $user->subscriptions()->canceled()->get();
以下是可用範圍的完整清單
Subscription::query()->active();Subscription::query()->canceled();Subscription::query()->ended();Subscription::query()->incomplete();Subscription::query()->notCanceled();Subscription::query()->notOnGracePeriod();Subscription::query()->notOnTrial();Subscription::query()->onGracePeriod();Subscription::query()->onTrial();Subscription::query()->pastDue();Subscription::query()->recurring();
變更價格
在客戶訂閱您的應用程式後,他們可能偶爾會想變更為新的訂閱價格。若要將客戶更換為新的價格,請將 Stripe 價格的識別符傳遞給 swap
方法。更換價格時,假設使用者希望重新啟用先前已取消的訂閱。給定的價格識別符應對應於 Stripe 儀表板中可用的 Stripe 價格識別符。
use App\Models\User; $user = App\Models\User::find(1); $user->subscription('default')->swap('price_yearly');
如果客戶處於試用期,試用期將會保留。此外,如果訂閱存在「數量」,該數量也將會保留。
如果您想更換價格並取消客戶目前處於的任何試用期,您可以調用 skipTrial
方法。
$user->subscription('default') ->skipTrial() ->swap('price_yearly');
如果您想更換價格並立即向客戶開立發票,而不是等待他們的下一個計費週期,您可以使用 swapAndInvoice
方法。
$user = User::find(1); $user->subscription('default')->swapAndInvoice('price_yearly');
按比例計算
預設情況下,Stripe 在更換價格時會按比例計算費用。noProrate
方法可以用來更新訂閱的價格,而不按比例計算費用。
$user->subscription('default')->noProrate()->swap('price_yearly');
有關訂閱按比例計算的更多資訊,請參閱 Stripe 文件。
在 swapAndInvoice
方法之前執行 noProrate
方法將不會對按比例計算產生影響。始終會開立發票。
訂閱數量
有時訂閱會受到「數量」的影響。例如,專案管理應用程式可能會按每個專案每月收費 10 美元。您可以使用 incrementQuantity
和 decrementQuantity
方法來輕鬆增加或減少您的訂閱數量。
use App\Models\User; $user = User::find(1); $user->subscription('default')->incrementQuantity(); // Add five to the subscription's current quantity...$user->subscription('default')->incrementQuantity(5); $user->subscription('default')->decrementQuantity(); // Subtract five from the subscription's current quantity...$user->subscription('default')->decrementQuantity(5);
或者,您可以使用 updateQuantity
方法設定特定數量。
$user->subscription('default')->updateQuantity(10);
noProrate
方法可以用來更新訂閱的數量,而不按比例計算費用。
$user->subscription('default')->noProrate()->updateQuantity(10);
有關訂閱數量的更多資訊,請參閱 Stripe 文件。
具有多個產品的訂閱數量
如果您的訂閱是具有多個產品的訂閱,您應該將您希望增加或減少其數量的價格 ID 作為第二個引數傳遞給增加/減少方法。
$user->subscription('default')->incrementQuantity(1, 'price_chat');
具有多種產品的訂閱
具有多個產品的訂閱允許您將多個計費產品分配給單個訂閱。例如,假設您正在建構一個客戶服務「服務台」應用程式,其基本訂閱價格為每月 10 美元,但提供一個額外每月 15 美元的即時聊天附加產品。具有多個產品的訂閱資訊儲存在 Cashier 的 subscription_items
資料庫表格中。
您可以在將價格陣列作為第二個引數傳遞給 newSubscription
方法,為給定的訂閱指定多個產品。
use Illuminate\Http\Request; Route::post('/user/subscribe', function (Request $request) { $request->user()->newSubscription('default', [ 'price_monthly', 'price_chat', ])->create($request->paymentMethodId); // ...});
在上面的範例中,客戶將有兩個價格附加到他們的 default
訂閱。這兩個價格將按其各自的計費間隔收費。如有必要,您可以使用 quantity
方法來指示每個價格的特定數量。
$user = User::find(1); $user->newSubscription('default', ['price_monthly', 'price_chat']) ->quantity(5, 'price_chat') ->create($paymentMethod);
如果您想將另一個價格新增至現有的訂閱,您可以調用訂閱的 addPrice
方法。
$user = User::find(1); $user->subscription('default')->addPrice('price_chat');
上面的範例將新增新價格,並且客戶將在下一個計費週期為其付費。如果您想立即向客戶收費,您可以使用 addPriceAndInvoice
方法。
$user->subscription('default')->addPriceAndInvoice('price_chat');
如果您想新增具有特定數量的價格,您可以將該數量作為 addPrice
或 addPriceAndInvoice
方法的第二個引數傳遞。
$user = User::find(1); $user->subscription('default')->addPrice('price_chat', 5);
您可以使用 removePrice
方法從訂閱中移除價格。
$user->subscription('default')->removePrice('price_chat');
您不能移除訂閱上的最後一個價格。相反地,您應該直接取消訂閱。
更換價格
您也可以變更附加到具有多個產品的訂閱的價格。例如,假設客戶有一個具有 price_chat
附加產品的 price_basic
訂閱,並且您想將客戶從 price_basic
升級到 price_pro
價格。
use App\Models\User; $user = User::find(1); $user->subscription('default')->swap(['price_pro', 'price_chat']);
在執行上面的範例時,具有 price_basic
的基礎訂閱項目會被刪除,而具有 price_chat
的項目會被保留。此外,會為 price_pro
建立一個新的訂閱項目。
您也可以透過將鍵/值對陣列傳遞給 swap
方法來指定訂閱項目選項。例如,您可能需要指定訂閱價格數量。
$user = User::find(1); $user->subscription('default')->swap([ 'price_pro' => ['quantity' => 5], 'price_chat']);
如果您想在訂閱上更換單一價格,您可以使用訂閱項目本身的 swap
方法來完成。如果您想保留訂閱上其他價格的所有現有中繼資料,此方法特別有用。
$user = User::find(1); $user->subscription('default') ->findItemOrFail('price_basic') ->swap('price_pro');
按比例計算
預設情況下,Stripe 在從具有多個產品的訂閱新增或移除價格時,會按比例計算費用。如果您想在不按比例計算的情況下調整價格,您應該將 noProrate
方法連結到您的價格操作。
$user->subscription('default')->noProrate()->removePrice('price_chat');
數量
如果您想更新個別訂閱價格上的數量,您可以透過將價格 ID 作為方法的額外引數傳遞來使用現有的數量方法。
$user = User::find(1); $user->subscription('default')->incrementQuantity(5, 'price_chat'); $user->subscription('default')->decrementQuantity(3, 'price_chat'); $user->subscription('default')->updateQuantity(10, 'price_chat');
當訂閱有多個價格時,Subscription
模型上的 stripe_price
和 quantity
屬性將為 null
。若要存取個別的價格屬性,您應該使用 Subscription
模型上可用的 items
關係。
訂閱項目
當訂閱有多個價格時,它將在您資料庫的 subscription_items
表格中儲存多個訂閱「項目」。您可以使用訂閱上的 items
關係來存取這些項目。
use App\Models\User; $user = User::find(1); $subscriptionItem = $user->subscription('default')->items->first(); // Retrieve the Stripe price and quantity for a specific item...$stripePrice = $subscriptionItem->stripe_price;$quantity = $subscriptionItem->quantity;
您也可以使用 findItemOrFail
方法來檢索特定價格。
$user = User::find(1); $subscriptionItem = $user->subscription('default')->findItemOrFail('price_chat');
多個訂閱
Stripe 允許您的客戶同時擁有多個訂閱。例如,您可能會經營一間健身房,提供游泳訂閱和舉重訂閱,而且每個訂閱可能有不同的價格。當然,客戶應該可以訂閱其中一項或兩項方案。
當您的應用程式建立訂閱時,您可以將訂閱的類型提供給 newSubscription
方法。類型可以是任何代表使用者正在開始的訂閱類型的字串。
use Illuminate\Http\Request; Route::post('/swimming/subscribe', function (Request $request) { $request->user()->newSubscription('swimming') ->price('price_swimming_monthly') ->create($request->paymentMethodId); // ...});
在此範例中,我們為客戶開始了每月游泳訂閱。但是,他們可能稍後想更換為年度訂閱。當調整客戶的訂閱時,我們可以簡單地更換 swimming
訂閱上的價格。
$user->subscription('swimming')->swap('price_swimming_yearly');
當然,您也可以完全取消訂閱。
$user->subscription('swimming')->cancel();
基於用量的計費
依用量計費允許您根據客戶在計費週期內的產品使用量收費。例如,您可以根據客戶每月傳送的簡訊或電子郵件數量收費。
若要開始使用依用量計費,您首先需要在 Stripe 儀表板中建立一個新的產品,其中包含依用量計費模型和計量器。建立計量器後,請儲存相關聯的事件名稱和計量器 ID,您需要這些名稱和 ID 來報告和檢索使用量。然後,使用 meteredPrice
方法將計量價格 ID 新增至客戶訂閱。
use Illuminate\Http\Request; Route::post('/user/subscribe', function (Request $request) { $request->user()->newSubscription('default') ->meteredPrice('price_metered') ->create($request->paymentMethodId); // ...});
您也可以透過 Stripe Checkout 開始計量訂閱。
$checkout = Auth::user() ->newSubscription('default', []) ->meteredPrice('price_metered') ->checkout(); return view('your-checkout-view', [ 'checkout' => $checkout,]);
報告使用量
當您的客戶使用您的應用程式時,您會將他們的使用量報告給 Stripe,以便他們可以準確地計費。若要報告計量事件的使用量,您可以使用 Billable
模型上的 reportMeterEvent
方法。
$user = User::find(1); $user->reportMeterEvent('emails-sent');
預設情況下,會將「使用量」1 加入計費週期。或者,您可以傳遞特定的「使用量」數值,以加到客戶在計費週期內的使用量。
$user = User::find(1); $user->reportMeterEvent('emails-sent', quantity: 15);
若要檢索客戶針對某個計量器的事件摘要,您可以使用 Billable
實例的 meterEventSummaries
方法。
$user = User::find(1); $meterUsage = $user->meterEventSummaries($meterId); $meterUsage->first()->aggregated_value // 10
關於計量器事件摘要的更多資訊,請參閱 Stripe 的 計量器事件摘要物件文件。
若要列出所有計量器,您可以使用 Billable
實例的 meters
方法。
$user = User::find(1); $user->meters();
訂閱稅
您可以使用 Stripe Tax 自動計算稅金,而不是手動計算稅率。
若要指定使用者在訂閱時應支付的稅率,您應該在您的可計費模型上實作 taxRates
方法,並回傳包含 Stripe 稅率 ID 的陣列。您可以在 您的 Stripe 控制面板中定義這些稅率。
/** * The tax rates that should apply to the customer's subscriptions. * * @return array<int, string> */public function taxRates(): array{ return ['txr_id'];}
taxRates
方法可讓您根據個別客戶套用稅率,這對於跨越多個國家和稅率的使用者群體可能很有幫助。
如果您提供的訂閱包含多個產品,您可以在可計費模型上實作 priceTaxRates
方法,為每個價格定義不同的稅率。
/** * The tax rates that should apply to the customer's subscriptions. * * @return array<string, array<int, string>> */public function priceTaxRates(): array{ return [ 'price_monthly' => ['txr_id'], ];}
taxRates
方法僅適用於訂閱費用。如果您使用 Cashier 來產生「一次性」費用,您需要在那時手動指定稅率。
同步稅率
當變更 taxRates
方法回傳的硬編碼稅率 ID 時,使用者任何現有訂閱的稅務設定將保持不變。如果您希望使用新的 taxRates
值更新現有訂閱的稅務值,您應該在使用者訂閱實例上呼叫 syncTaxRates
方法。
$user->subscription('default')->syncTaxRates();
這也會同步具有多個產品的訂閱的任何項目稅率。如果您的應用程式提供的訂閱包含多個產品,您應該確保您的可計費模型實作了 上述討論的 priceTaxRates
方法。
稅務豁免
Cashier 也提供了 isNotTaxExempt
、isTaxExempt
和 reverseChargeApplies
方法來判斷客戶是否享有稅務豁免。這些方法將呼叫 Stripe API 來判斷客戶的稅務豁免狀態。
use App\Models\User; $user = User::find(1); $user->isTaxExempt();$user->isNotTaxExempt();$user->reverseChargeApplies();
這些方法也適用於任何 Laravel\Cashier\Invoice
物件。然而,當在 Invoice
物件上呼叫時,這些方法將判斷建立發票時的豁免狀態。
訂閱錨定日期
預設情況下,計費週期錨點是建立訂閱的日期,或者,如果使用了試用期,則是試用期結束的日期。如果您想修改計費錨點日期,可以使用 anchorBillingCycleOn
方法。
use Illuminate\Http\Request; Route::post('/user/subscribe', function (Request $request) { $anchor = Carbon::parse('first day of next month'); $request->user()->newSubscription('default', 'price_monthly') ->anchorBillingCycleOn($anchor->startOfDay()) ->create($request->paymentMethodId); // ...});
關於管理訂閱計費週期的更多資訊,請查閱 Stripe 計費週期文件。
取消訂閱
若要取消訂閱,請在使用者的訂閱上呼叫 cancel
方法。
$user->subscription('default')->cancel();
當訂閱被取消時,Cashier 會自動在您的 subscriptions
資料庫表格中設定 ends_at
欄位。此欄位用於判斷 subscribed
方法何時應開始回傳 false
。
例如,如果客戶在 3 月 1 日取消訂閱,但訂閱原定在 3 月 5 日結束,則 subscribed
方法將持續回傳 true
,直到 3 月 5 日。這樣做的原因是,通常允許使用者繼續使用應用程式直到他們的計費週期結束。
您可以使用 onGracePeriod
方法判斷使用者是否已取消訂閱,但仍在他們的「寬限期」內。
if ($user->subscription('default')->onGracePeriod()) { // ...}
如果您希望立即取消訂閱,請在使用者的訂閱上呼叫 cancelNow
方法。
$user->subscription('default')->cancelNow();
如果您希望立即取消訂閱,並為任何剩餘的未開立發票的計量使用量或新的/待定按比例計算的發票項目開立發票,請在使用者訂閱上呼叫 cancelNowAndInvoice
方法。
$user->subscription('default')->cancelNowAndInvoice();
您也可以選擇在特定的時間點取消訂閱。
$user->subscription('default')->cancelAt( now()->addDays(10));
最後,您應該始終在刪除相關使用者模型之前取消使用者訂閱。
$user->subscription('default')->cancelNow(); $user->delete();
恢復訂閱
如果客戶已取消訂閱,並且您希望恢復訂閱,您可以在訂閱上呼叫 resume
方法。客戶必須仍在他們的「寬限期」內才能恢復訂閱。
$user->subscription('default')->resume();
如果客戶取消訂閱,然後在訂閱完全到期之前恢復該訂閱,客戶將不會立即被收費。相反,他們的訂閱將被重新啟用,他們將按照原始的計費週期被收費。
訂閱試用
預先提供付款方式
如果您希望在預先收集付款方式資訊的同時為客戶提供試用期,您應該在建立訂閱時使用 trialDays
方法。
use Illuminate\Http\Request; Route::post('/user/subscribe', function (Request $request) { $request->user()->newSubscription('default', 'price_monthly') ->trialDays(10) ->create($request->paymentMethodId); // ...});
此方法將在資料庫中的訂閱記錄上設定試用期結束日期,並指示 Stripe 在此日期之後才開始向客戶收費。當使用 trialDays
方法時,Cashier 將覆寫在 Stripe 中為價格設定的任何預設試用期。
如果客戶的訂閱未在試用期結束日期之前取消,他們將在試用期結束後立即被收費,因此您應該確保通知您的使用者他們的試用期結束日期。
trialUntil
方法允許您提供一個 DateTime
實例,指定試用期應該何時結束。
use Carbon\Carbon; $user->newSubscription('default', 'price_monthly') ->trialUntil(Carbon::now()->addDays(10)) ->create($paymentMethod);
您可以使用使用者實例的 onTrial
方法或訂閱實例的 onTrial
方法,判斷使用者是否在他們的試用期內。下面的兩個範例是等效的。
if ($user->onTrial('default')) { // ...} if ($user->subscription('default')->onTrial()) { // ...}
您可以使用 endTrial
方法立即結束訂閱試用。
$user->subscription('default')->endTrial();
若要判斷現有的試用是否已過期,您可以使用 hasExpiredTrial
方法。
if ($user->hasExpiredTrial('default')) { // ...} if ($user->subscription('default')->hasExpiredTrial()) { // ...}
在 Stripe / Cashier 中定義試用天數
您可以選擇在 Stripe 控制面板中定義您的價格所接收的試用天數,或者始終使用 Cashier 明確傳遞它們。如果您選擇在 Stripe 中定義價格的試用天數,您應該知道新的訂閱,包括過去曾經訂閱過的客戶的新訂閱,除非您明確呼叫 skipTrial()
方法,否則始終會收到試用期。
不預先提供付款方式
如果您希望在不預先收集使用者付款方式資訊的情況下提供試用期,您可以在使用者記錄上的 trial_ends_at
欄位設定您想要的試用期結束日期。這通常在使用者註冊期間完成。
use App\Models\User; $user = User::create([ // ... 'trial_ends_at' => now()->addDays(10),]);
請務必在您的可計費模型的類別定義中,為 trial_ends_at
屬性新增一個 日期轉換。
Cashier 將此類試用稱為「通用試用」,因為它沒有附加到任何現有的訂閱。如果目前日期未超過 trial_ends_at
的值,可計費模型實例上的 onTrial
方法將回傳 true
。
if ($user->onTrial()) { // User is within their trial period...}
當您準備好為使用者建立實際訂閱時,您可以像往常一樣使用 newSubscription
方法。
$user = User::find(1); $user->newSubscription('default', 'price_monthly')->create($paymentMethod);
若要檢索使用者的試用期結束日期,您可以使用 trialEndsAt
方法。如果使用者在試用期內,此方法將回傳 Carbon 日期實例,否則回傳 null
。如果您想取得特定訂閱(而不是預設訂閱)的試用期結束日期,也可以傳遞一個選填的訂閱類型參數。
if ($user->onTrial()) { $trialEndsAt = $user->trialEndsAt('main');}
如果您想明確知道使用者是否在他們的「通用」試用期內,並且尚未建立實際訂閱,您也可以使用 onGenericTrial
方法。
if ($user->onGenericTrial()) { // User is within their "generic" trial period...}
延長試用期
extendTrial
方法允許您在建立訂閱後延長訂閱的試用期。如果試用期已過期,並且客戶已經在支付訂閱費用,您仍然可以為他們提供延長的試用期。試用期內花費的時間將從客戶的下一張發票中扣除。
use App\Models\User; $subscription = User::find(1)->subscription('default'); // End the trial 7 days from now...$subscription->extendTrial( now()->addDays(7)); // Add an additional 5 days to the trial...$subscription->extendTrial( $subscription->trial_ends_at->addDays(5));
處理 Stripe Webhook
您可以使用 Stripe CLI 來協助在本地開發期間測試網路鉤子。
Stripe 可以透過網路鉤子通知您的應用程式各種事件。預設情況下,指向 Cashier 網路鉤子控制器的路由會由 Cashier 服務提供者自動註冊。此控制器將處理所有傳入的網路鉤子請求。
預設情況下,Cashier 網路鉤子控制器會自動處理取消過多失敗收費的訂閱(如您的 Stripe 設定所定義)、客戶更新、客戶刪除、訂閱更新和付款方式變更;然而,我們很快就會發現,您可以擴充此控制器來處理您喜歡的任何 Stripe 網路鉤子事件。
為了確保您的應用程式可以處理 Stripe 網路鉤子,請務必在 Stripe 控制面板中設定網路鉤子 URL。預設情況下,Cashier 的網路鉤子控制器會回應 /stripe/webhook
URL 路徑。您應該在 Stripe 控制面板中啟用的所有網路鉤子的完整清單是
-
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 中建立一個網路鉤子,該網路鉤子會偵聽 Cashier 所需的所有事件。
php artisan cashier:webhook
預設情況下,建立的網路鉤子將指向由 APP_URL
環境變數和 Cashier 中包含的 cashier.webhook
路由定義的 URL。如果您想使用不同的 URL,您可以在呼叫命令時提供 --url
選項。
php artisan cashier:webhook --url "https://example.com/stripe/webhook"
建立的網路鉤子將使用您的 Cashier 版本相容的 Stripe API 版本。如果您想使用不同的 Stripe 版本,您可以提供 --api-version
選項。
php artisan cashier:webhook --api-version="2019-12-03"
建立後,網路鉤子將立即啟用。如果您希望建立網路鉤子,但在您準備好之前使其停用,您可以在呼叫命令時提供 --disabled
選項。
php artisan cashier:webhook --disabled
請確保使用 Cashier 包含的 網路鉤子簽名驗證中間件保護傳入的 Stripe 網路鉤子請求。
網路鉤子和 CSRF 保護
由於 Stripe 網路鉤子需要繞過 Laravel 的 CSRF 保護,因此您應該確保 Laravel 不會嘗試驗證傳入的 Stripe 網路鉤子的 CSRF 令牌。為此,您應該在應用程式的 bootstrap/app.php
檔案中從 CSRF 保護中排除 stripe/*
。
->withMiddleware(function (Middleware $middleware) { $middleware->validateCsrfTokens(except: [ 'stripe/*', ]);})
定義 Webhook 事件處理常式
Cashier 會自動處理失敗收費和其他常見 Stripe 網路鉤子事件的訂閱取消。但是,如果您有其他想要處理的網路鉤子事件,您可以透過偵聽 Cashier 分派的下列事件來實現。
-
Laravel\Cashier\Events\WebhookReceived
-
Laravel\Cashier\Events\WebhookHandled
這兩個事件都包含 Stripe webhook 的完整酬載。例如,如果您想處理 invoice.payment_succeeded
webhook,您可以註冊一個 監聽器 來處理該事件。
<?php namespace App\Listeners; use Laravel\Cashier\Events\WebhookReceived; class StripeEventListener{ /** * Handle received Stripe webhooks. */ public function handle(WebhookReceived $event): void { if ($event->payload['type'] === 'invoice.payment_succeeded') { // Handle the incoming event... } }}
驗證 Webhook 簽名
為了保護您的 webhook 安全,您可以使用 Stripe 的 webhook 簽名。為了方便起見,Cashier 會自動包含一個中介層,用於驗證傳入的 Stripe webhook 請求是否有效。
要啟用 webhook 驗證,請確保您的應用程式的 .env
檔案中已設定 STRIPE_WEBHOOK_SECRET
環境變數。webhook 的 secret
可以從您的 Stripe 帳戶儀表板中取得。
單次收費
簡單收費
如果您想對客戶進行一次性收費,您可以使用可計費模型實例上的 charge
方法。您需要提供付款方式識別碼作為 charge
方法的第二個參數。
use Illuminate\Http\Request; Route::post('/purchase', function (Request $request) { $stripeCharge = $request->user()->charge( 100, $request->paymentMethodId ); // ...});
charge
方法接受一個陣列作為其第三個參數,允許您將任何您希望的選項傳遞到底層的 Stripe 收費建立中。有關建立收費時可用的選項的更多資訊,請參閱 Stripe 文件。
$user->charge(100, $paymentMethod, [ 'custom_option' => $value,]);
您也可以在沒有底層客戶或使用者的情況下使用 charge
方法。要完成此操作,請在應用程式的可計費模型的新實例上調用 charge
方法。
use App\Models\User; $stripeCharge = (new User)->charge(100, $paymentMethod);
如果收費失敗,charge
方法將拋出例外。如果收費成功,該方法將返回 Laravel\Cashier\Payment
的實例。
try { $payment = $user->charge(100, $paymentMethod);} catch (Exception $e) { // ...}
charge
方法接受以應用程式使用的貨幣的最小單位表示的付款金額。例如,如果客戶以美元付款,金額應以美分指定。
包含發票的收費
有時您可能需要進行一次性收費並向客戶提供 PDF 發票。invoicePrice
方法可讓您做到這一點。例如,我們來為一位客戶開具五件新襯衫的發票。
$user->invoicePrice('price_tshirt', 5);
發票將立即向使用者的預設付款方式收費。invoicePrice
方法也接受一個陣列作為其第三個參數。此陣列包含發票項目的計費選項。該方法接受的第四個參數也是一個陣列,其中應包含發票本身的計費選項。
$user->invoicePrice('price_tshirt', 5, [ 'discounts' => [ ['coupon' => 'SUMMER21SALE'] ],], [ 'default_tax_rates' => ['txr_id'],]);
與 invoicePrice
類似,您可以使用 tabPrice
方法為多個項目(每個發票最多 250 個項目)建立一次性收費,將它們新增到客戶的「標籤」中,然後向客戶開具發票。例如,我們可以為客戶開具五件襯衫和兩個馬克杯的發票。
$user->tabPrice('price_tshirt', 5);$user->tabPrice('price_mug', 2);$user->invoice();
或者,您可以使用 invoiceFor
方法對客戶的預設付款方式進行「一次性」收費。
$user->invoiceFor('One Time Fee', 500);
雖然您可以使用 invoiceFor
方法,但建議您使用具有預定義價格的 invoicePrice
和 tabPrice
方法。這樣做,您將可以在 Stripe 儀表板中獲得更好的分析和資料,了解您的每個產品的銷售情況。
invoice
、invoicePrice
和 invoiceFor
方法將建立 Stripe 發票,該發票將重試失敗的計費嘗試。如果您不希望發票重試失敗的收費,您需要在第一次失敗的收費後使用 Stripe API 關閉它們。
建立付款意圖
您可以透過在可計費模型實例上調用 pay
方法來建立新的 Stripe 付款意圖。呼叫此方法將建立一個包裹在 Laravel\Cashier\Payment
實例中的付款意圖。
use Illuminate\Http\Request; Route::post('/pay', function (Request $request) { $payment = $request->user()->pay( $request->get('amount') ); return $payment->client_secret;});
建立付款意圖後,您可以將客戶端密碼返回到應用程式的前端,以便使用者可以在瀏覽器中完成付款。若要了解更多關於使用 Stripe 付款意圖建立完整付款流程的資訊,請參閱 Stripe 文件。
當使用 pay
方法時,您在 Stripe 儀表板中啟用的預設付款方式將可供客戶使用。或者,如果您只想允許使用某些特定的付款方式,您可以使用 payWith
方法。
use Illuminate\Http\Request; Route::post('/pay', function (Request $request) { $payment = $request->user()->payWith( $request->get('amount'), ['card', 'bancontact'] ); return $payment->client_secret;});
pay
和 payWith
方法接受以應用程式使用的貨幣的最小單位表示的付款金額。例如,如果客戶以美元付款,金額應以美分指定。
退款
如果您需要退還 Stripe 收費,您可以使用 refund
方法。此方法接受 Stripe 付款意圖 ID 作為其第一個參數。
$payment = $user->charge(100, $paymentMethodId); $user->refund($payment->id);
發票
擷取發票
您可以使用 invoices
方法輕鬆檢索可計費模型的發票陣列。invoices
方法返回 Laravel\Cashier\Invoice
實例的集合。
$invoices = $user->invoices();
如果您想在結果中包含待處理的發票,您可以使用 invoicesIncludingPending
方法。
$invoices = $user->invoicesIncludingPending();
您可以使用 findInvoice
方法透過其 ID 檢索特定的發票。
$invoice = $user->findInvoice($invoiceId);
顯示發票資訊
在列出客戶的發票時,您可以使用發票的方法來顯示相關的發票資訊。例如,您可能希望在表格中列出每個發票,允許使用者輕鬆下載其中任何一個。
<table> @foreach ($invoices as $invoice) <tr> <td>{{ $invoice->date()->toFormattedDateString() }}</td> <td>{{ $invoice->total() }}</td> <td><a href="/user/invoice/{{ $invoice->id }}">Download</a></td> </tr> @endforeach</table>
即將到來的發票
若要檢索客戶的即將到來的發票,您可以使用 upcomingInvoice
方法。
$invoice = $user->upcomingInvoice();
同樣地,如果客戶有多個訂閱,您也可以檢索特定訂閱的即將到來的發票。
$invoice = $user->subscription('default')->upcomingInvoice();
預覽訂閱發票
使用 previewInvoice
方法,您可以在進行價格變更之前預覽發票。這將允許您確定在進行給定價格變更時,客戶的發票看起來會是什麼樣子。
$invoice = $user->subscription('default')->previewInvoice('price_yearly');
您可以將價格陣列傳遞給 previewInvoice
方法,以便使用多個新價格預覽發票。
$invoice = $user->subscription('default')->previewInvoice(['price_yearly', 'price_metered']);
產生發票 PDF
在產生發票 PDF 之前,您應該使用 Composer 安裝 Dompdf 函式庫,這是 Cashier 的預設發票渲染器。
composer require dompdf/dompdf
從路由或控制器內,您可以使用 downloadInvoice
方法產生給定發票的 PDF 下載。此方法將自動產生下載發票所需的正確 HTTP 回應。
use Illuminate\Http\Request; Route::get('/user/invoice/{invoice}', function (Request $request, string $invoiceId) { return $request->user()->downloadInvoice($invoiceId);});
預設情況下,發票上的所有資料都來自 Stripe 中儲存的客戶和發票資料。檔案名稱基於您的 app.name
設定值。但是,您可以透過提供一個陣列作為 downloadInvoice
方法的第二個參數來自訂某些資料。此陣列允許您自訂諸如您的公司和產品詳細資訊之類的資訊。
return $request->user()->downloadInvoice($invoiceId, [ 'vendor' => 'Your Company', 'product' => 'Your Product', 'street' => 'Main Str. 1', 'location' => '2000 Antwerp, Belgium', 'phone' => '+32 499 00 00 00', 'url' => 'https://example.com', 'vendorVat' => 'BE123456789',]);
downloadInvoice
方法也允許透過其第三個參數使用自訂檔案名稱。此檔案名稱將自動加上 .pdf
後綴。
return $request->user()->downloadInvoice($invoiceId, [], 'my-invoice');
自訂發票渲染器
Cashier 還可以使用自訂發票渲染器。預設情況下,Cashier 使用 DompdfInvoiceRenderer
實作,它利用 dompdf PHP 函式庫來產生 Cashier 的發票。但是,您可以透過實作 Laravel\Cashier\Contracts\InvoiceRenderer
介面來使用您想要的任何渲染器。例如,您可能希望使用對第三方 PDF 渲染服務的 API 呼叫來渲染發票 PDF。
use Illuminate\Support\Facades\Http;use Laravel\Cashier\Contracts\InvoiceRenderer;use Laravel\Cashier\Invoice; class ApiInvoiceRenderer implements InvoiceRenderer{ /** * Render the given invoice and return the raw PDF bytes. */ public function render(Invoice $invoice, array $data = [], array $options = []): string { $html = $invoice->view($data)->render(); return Http::get('https://example.com/html-to-pdf', ['html' => $html])->get()->body(); }}
實作發票渲染器合約後,您應該更新應用程式 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。
use Illuminate\Http\Request; Route::get('/product-checkout', function (Request $request) { return $request->user()->checkout('price_tshirt');});
如果需要,您還可以指定產品數量。
use Illuminate\Http\Request; Route::get('/product-checkout', function (Request $request) { return $request->user()->checkout(['price_tshirt' => 15]);});
當客戶訪問此路由時,他們將被重新導向到 Stripe 的 Checkout 頁面。預設情況下,當使用者成功完成或取消購買時,他們將被重新導向到您的 home
路由位置,但您可以使用 success_url
和 cancel_url
選項指定自訂回呼 URL。
use Illuminate\Http\Request; Route::get('/product-checkout', function (Request $request) { return $request->user()->checkout(['price_tshirt' => 1], [ 'success_url' => route('your-success-route'), 'cancel_url' => route('your-cancel-route'), ]);});
當定義您的 success_url
結帳選項時,您可以指示 Stripe 在呼叫您的 URL 時將結帳會話 ID 作為查詢字串參數新增。要執行此操作,請將字串文字 {CHECKOUT_SESSION_ID}
新增到您的 success_url
查詢字串中。Stripe 將使用實際的結帳會話 ID 取代此預留位置。
use Illuminate\Http\Request;use Stripe\Checkout\Session;use Stripe\Customer; Route::get('/product-checkout', function (Request $request) { return $request->user()->checkout(['price_tshirt' => 1], [ 'success_url' => route('checkout-success').'?session_id={CHECKOUT_SESSION_ID}', 'cancel_url' => route('checkout-cancel'), ]);}); Route::get('/checkout-success', function (Request $request) { $checkoutSession = $request->user()->stripe()->checkout->sessions->retrieve($request->get('session_id')); return view('checkout.success', ['checkoutSession' => $checkoutSession]);})->name('checkout-success');
促銷代碼
預設情況下,Stripe Checkout 不允許 使用者可兌換的促銷代碼。幸運的是,有一個簡單的方法可以為您的 Checkout 頁面啟用這些代碼。要執行此操作,您可以調用 allowPromotionCodes
方法。
use Illuminate\Http\Request; Route::get('/product-checkout', function (Request $request) { return $request->user() ->allowPromotionCodes() ->checkout('price_tshirt');});
單次收費結帳
您還可以對未在 Stripe 儀表板中建立的臨時產品執行簡單的收費。要執行此操作,您可以使用可計費模型上的 checkoutCharge
方法,並將可收費金額、產品名稱和可選的數量傳遞給它。當客戶訪問此路由時,他們將被重新導向到 Stripe 的 Checkout 頁面。
use Illuminate\Http\Request; Route::get('/charge-checkout', function (Request $request) { return $request->user()->checkoutCharge(1200, 'T-Shirt', 5);});
當使用 checkoutCharge
方法時,Stripe 將始終在您的 Stripe 儀表板中建立新的產品和價格。因此,我們建議您預先在 Stripe 儀表板中建立產品,並改用 checkout
方法。
訂閱結帳
將 Stripe Checkout 用於訂閱需要您在 Stripe 儀表板中啟用 customer.subscription.created
webhook。此 webhook 將在您的資料庫中建立訂閱記錄,並儲存所有相關的訂閱項目。
您也可以使用 Stripe Checkout 來啟動訂閱。使用 Cashier 的訂閱建立器方法定義訂閱後,您可以呼叫 checkout
方法。當客戶訪問此路由時,他們將被重新導向到 Stripe 的 Checkout 頁面。
use Illuminate\Http\Request; Route::get('/subscription-checkout', function (Request $request) { return $request->user() ->newSubscription('default', 'price_monthly') ->checkout();});
就像產品結帳一樣,您可以自訂成功和取消 URL。
use Illuminate\Http\Request; Route::get('/subscription-checkout', function (Request $request) { return $request->user() ->newSubscription('default', 'price_monthly') ->checkout([ 'success_url' => route('your-success-route'), 'cancel_url' => route('your-cancel-route'), ]);});
當然,您也可以為訂閱結帳啟用促銷代碼。
use Illuminate\Http\Request; Route::get('/subscription-checkout', function (Request $request) { return $request->user() ->newSubscription('default', 'price_monthly') ->allowPromotionCodes() ->checkout();});
不幸的是,Stripe Checkout 在開始訂閱時不支援所有訂閱計費選項。在訂閱建立器上使用 anchorBillingCycleOn
方法、設定按比例收費行為或設定付款行為在 Stripe Checkout 會話期間不會有任何效果。請參閱 Stripe Checkout Session API 文件以查看哪些參數可用。
Stripe Checkout 和試用期
當然,您可以在建立將使用 Stripe Checkout 完成的訂閱時定義試用期。
$checkout = Auth::user()->newSubscription('default', 'price_monthly') ->trialDays(3) ->checkout();
但是,試用期必須至少為 48 小時,這是 Stripe Checkout 支援的最短試用時間。
訂閱和 Webhook
請記住,Stripe 和 Cashier 會透過 webhook 更新訂閱狀態,因此當客戶輸入付款資訊後返回應用程式時,訂閱可能尚未處於活動狀態。為了處理這種情況,您可能希望顯示一條訊息,通知使用者他們的付款或訂閱正在等待中。
收集稅務 ID
結帳功能也支援收集客戶的稅務識別號碼。若要在結帳環節啟用此功能,請在建立環節時調用 collectTaxIds
方法。
$checkout = $user->collectTaxIds()->checkout('price_tshirt');
當調用此方法時,客戶將看到一個新的核取方塊,允許他們指明是否以公司名義購買。如果是,他們將有機會提供他們的稅務識別號碼。
如果您已在應用程式的服務提供者中設定自動稅務收集,則此功能將自動啟用,無需調用 collectTaxIds
方法。
訪客結帳
使用 Checkout::guest
方法,您可以為應用程式中沒有「帳戶」的訪客啟動結帳環節。
use Illuminate\Http\Request;use Laravel\Cashier\Checkout; Route::get('/product-checkout', function (Request $request) { return Checkout::guest()->create('price_tshirt', [ 'success_url' => route('your-success-route'), 'cancel_url' => route('your-cancel-route'), ]);});
與為現有使用者建立結帳環節類似,您可以使用 Laravel\Cashier\CheckoutBuilder
實例上提供的其他方法來自訂訪客結帳環節。
use Illuminate\Http\Request;use Laravel\Cashier\Checkout; Route::get('/product-checkout', function (Request $request) { return Checkout::guest() ->withPromotionCode('promo-code') ->create('price_tshirt', [ 'success_url' => route('your-success-route'), 'cancel_url' => route('your-cancel-route'), ]);});
訪客結帳完成後,Stripe 可以發送一個 checkout.session.completed
webhook 事件,因此請務必設定您的 Stripe webhook,以便實際將此事件發送到您的應用程式。在 Stripe 控制面板中啟用 webhook 後,您可以使用 Cashier 處理 webhook。webhook 酬載中包含的物件將是一個 checkout
物件,您可以檢查該物件以完成客戶的訂單。
處理付款失敗
有時,訂閱或單次付款可能會失敗。當發生這種情況時,Cashier 將拋出一個 Laravel\Cashier\Exceptions\IncompletePayment
例外,通知您發生了這種情況。在捕獲此例外後,您有兩種處理方式。
首先,您可以將您的客戶重定向到 Cashier 附帶的專用付款確認頁面。此頁面已經有一個與 Cashier 的服務提供者註冊的相關命名路由。因此,您可以捕獲 IncompletePayment
例外,並將使用者重定向到付款確認頁面。
use Laravel\Cashier\Exceptions\IncompletePayment; try { $subscription = $user->newSubscription('default', 'price_monthly') ->create($paymentMethod);} catch (IncompletePayment $exception) { return redirect()->route( 'cashier.payment', [$exception->payment->id, 'redirect' => route('home')] );}
在付款確認頁面上,系統會提示客戶再次輸入他們的信用卡資訊,並執行 Stripe 要求的任何其他操作,例如「3D 安全」確認。確認付款後,使用者將被重定向到上方指定的 redirect
參數提供的 URL。在重定向時,message
(字串)和 success
(整數)查詢字串變數將被添加到 URL 中。付款頁面目前支援以下付款方式類型:
- 信用卡
- 支付寶
- Bancontact
- BECS 直接扣款
- EPS
- Giropay
- iDEAL
- SEPA 直接扣款
或者,您可以允許 Stripe 為您處理付款確認。在這種情況下,您可以不用重定向到付款確認頁面,而是在 Stripe 控制面板中設定 Stripe 的自動帳單電子郵件。但是,如果捕獲到 IncompletePayment
例外,您仍然應該通知使用者他們將收到一封包含進一步付款確認說明 的電子郵件。
在使用 Billable
trait 的模型上,以下方法可能會拋出付款例外:charge
、invoiceFor
和 invoice
。當與訂閱互動時,SubscriptionBuilder
上的 create
方法,以及 Subscription
和 SubscriptionItem
模型上的 incrementAndInvoice
和 swapAndInvoice
方法可能會拋出不完整的付款例外。
可以使用可計費模型或訂閱實例上的 hasIncompletePayment
方法來判斷現有訂閱是否具有不完整的付款。
if ($user->hasIncompletePayment('default')) { // ...} if ($user->subscription('default')->hasIncompletePayment()) { // ...}
您可以透過檢查例外實例上的 payment
屬性來得出不完整付款的特定狀態。
use Laravel\Cashier\Exceptions\IncompletePayment; try { $user->charge(1000, 'pm_card_threeDSecure2Required');} catch (IncompletePayment $exception) { // Get the payment intent status... $exception->payment->status; // Check specific conditions... if ($exception->payment->requiresPaymentMethod()) { // ... } elseif ($exception->payment->requiresConfirmation()) { // ... }}
確認付款
某些付款方式需要額外資料才能確認付款。例如,SEPA 付款方式在付款過程中需要額外的「授權」資料。您可以使用 withPaymentConfirmationOptions
方法將此資料提供給 Cashier。
$subscription->withPaymentConfirmationOptions([ 'mandate_data' => '...',])->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
資料庫欄位所示。一旦付款確認完成,並且您的應用程式透過 Stripe 的 webhook 收到完成通知,Cashier 將自動啟用客戶的訂閱。
有關 incomplete
和 past_due
狀態的更多資訊,請參閱我們關於這些狀態的其他文件。
非工作階段付款通知
由於 SCA 法規要求客戶即使在訂閱有效時也偶爾驗證其付款詳細資訊,因此當需要非工作階段付款確認時,Cashier 可以向客戶發送通知。例如,這可能發生在訂閱續訂時。可以將 CASHIER_PAYMENT_NOTIFICATION
環境變數設定為通知類別來啟用 Cashier 的付款通知。預設情況下,此通知已停用。當然,Cashier 包含一個您可以用於此目的的通知類別,但如果您需要,您可以自由提供自己的通知類別。
CASHIER_PAYMENT_NOTIFICATION=Laravel\Cashier\Notifications\ConfirmPayment
為了確保非工作階段付款確認通知已傳送,請驗證是否為您的應用程式設定了 Stripe webhook,並且在您的 Stripe 控制面板中啟用了 invoice.payment_action_required
webhook。此外,您的 Billable
模型也應使用 Laravel 的 Illuminate\Notifications\Notifiable
trait。
即使客戶手動進行需要額外確認的付款,也會發送通知。遺憾的是,Stripe 無法得知付款是手動還是「非工作階段」完成的。但是,如果客戶在確認付款後造訪付款頁面,他們只會看到「付款成功」訊息。客戶不會被允許意外地確認同一筆付款兩次並產生意外的第二次收費。
Stripe SDK
Cashier 的許多物件都是 Stripe SDK 物件的包裝器。如果您想直接與 Stripe 物件互動,可以使用 asStripe
方法方便地檢索它們。
$stripeSubscription = $subscription->asStripeSubscription(); $stripeSubscription->application_fee_percent = 5; $stripeSubscription->save();
您也可以使用 updateStripeSubscription
方法直接更新 Stripe 訂閱。
$subscription->updateStripeSubscription(['application_fee_percent' => 5]);
如果您想直接使用 Stripe\StripeClient
客戶端,則可以在 Cashier
類別上調用 stripe
方法。例如,您可以使用此方法存取 StripeClient
實例並從您的 Stripe 帳戶檢索價格清單。
use Laravel\Cashier\Cashier; $prices = Cashier::stripe()->prices->all();
測試
在測試使用 Cashier 的應用程式時,您可以模擬對 Stripe API 的實際 HTTP 請求;但是,這要求您部分重新實作 Cashier 自身的行為。因此,我們建議允許您的測試訪問實際的 Stripe API。雖然這比較慢,但它更能確保您的應用程式按照預期工作,並且任何慢速測試都可以放置在它們自己的 Pest / PHPUnit 測試群組中。
在測試時,請記住 Cashier 本身已經有一個出色的測試套件,因此您應該只專注於測試您自己的應用程式的訂閱和付款流程,而不是每個底層 Cashier 行為。
若要開始,請將您的 Stripe 密碼的測試版本添加到您的 phpunit.xml
檔案中。
<env name="STRIPE_SECRET" value="sk_test_<your-key>"/>
現在,當您在測試時與 Cashier 互動時,它將向您的 Stripe 測試環境發送實際的 API 請求。為了方便起見,您應該預先在您的 Stripe 測試帳戶中填入您在測試期間可能使用的訂閱/價格。
為了測試各種帳單情境,例如信用卡拒絕和失敗,您可以使用 Stripe 提供的各種測試卡號和代碼。