Cashier 交易工具包
Cashier 交易工具包
Laravel Cashier
簡(jiǎn)介
Laravel Cashier 提供了直觀、流暢的接口來(lái)接入 Stripe's 和 Braintree's 的付費(fèi)訂閱服務(wù)。它可以處理幾乎讓您頭疼的付費(fèi)訂閱代碼。除了提供基本的訂閱管理之外,Cashier 還可以幫您處理優(yōu)惠券、交換訂閱、訂閱 “數(shù)量”、取消寬限期,甚至還可以生成 PDF 發(fā)票。
{注意} 如果您只是需要 “一次性” 的收費(fèi)并且不提供訂閱,就不應(yīng)該使用 Cashier。建議您使用 Stripe 和 Braintree 的 SDK。
升級(jí) Cashier
當(dāng)您從舊版本升級(jí)到最新版本的 Cashier 前,建議您優(yōu)先閱讀 Cashier 升級(jí)指南.
配置
Stripe
Composer
首先,將 Stripe 的 Cashier 包添加到您的項(xiàng)目依賴項(xiàng)中:
composer require laravel/cashier
數(shù)據(jù)庫(kù)遷移
在使用 Cashier 之前,需要 準(zhǔn)備數(shù)據(jù)庫(kù)。 Cashier 需要向您的 users
表中添加幾個(gè)列,并創(chuàng)建一個(gè)新的 subscriptions
表來(lái)保存所有客戶的訂閱:
Schema::table('users', function ($table) { $table->string('stripe_id')->nullable()->collation('utf8mb4_bin'); $table->string('card_brand')->nullable(); $table->string('card_last_four', 4)->nullable(); $table->timestamp('trial_ends_at')->nullable(); }); Schema::create('subscriptions', function ($table) { $table->increments('id'); $table->unsignedInteger('user_id'); $table->string('name'); $table->string('stripe_id')->collation('utf8mb4_bin'); $table->string('stripe_plan'); $table->integer('quantity'); $table->timestamp('trial_ends_at')->nullable(); $table->timestamp('ends_at')->nullable(); $table->timestamps(); });
一旦遷移文件建立好后,運(yùn)行 Artisan 的 migrate
命令。
Billable 模型
接下來(lái),添加 Billable
Trait 到您的模型定義。這個(gè) Trait 提供了多個(gè)方法以便執(zhí)行常用支付任務(wù),例如創(chuàng)建訂閱、使用優(yōu)惠券以及更新信用卡信息:
use Laravel\Cashier\Billable;class User extends Authenticatable{ use Billable; }
API Keys
最后,在配置文件 services.php
中配置 Stripe 的 Key,您可以在 Stripe 官網(wǎng)個(gè)人控制面板中獲取這些 Stripe API Key 信息:
'stripe' => [ 'model' => App\User::class, 'key' => env('STRIPE_KEY'), 'secret' => env('STRIPE_SECRET'), ],
Braintree
Braintree 注意事項(xiàng)
在很多情況下,Stripe 和 Braintree 實(shí)現(xiàn) Cashier 的功能都是一樣的,兩者都提供了通過(guò)信用卡進(jìn)行訂閱支付的功能, Braintree 還額外支持通過(guò) PayPal 支付。但 Braintree 也缺失一些 Stripe 支持的功能,在決定使用 Stripe 或 Braintree 之前,您需要考慮以下幾點(diǎn):
- Braintree 支持 PayPal 而 Stripe 不支持。
- Braintree 不支持
increment
和decrement
方法,這是 Braintree 的限制,而不是 Cashier 限制。 - Braintree 不支持基于百分比的折扣。這是 Braintree 的限制,而不是 Cashier 限制。
Composer
首先,將 Braintree 的 Cashier 包添加到您項(xiàng)目的依賴項(xiàng)中:
composer require "laravel/cashier-braintree":"~2.0"
信用卡優(yōu)惠計(jì)劃
在使用 Cashier 之前,你需要首先在 Braintree 控制面板中定義一個(gè) plan-credit
折扣。這個(gè)折扣會(huì)根據(jù)用戶選擇的支付選項(xiàng)匹配合適的折扣比例,比如選擇年付或者月付。
在 Braintree 控制面板中配置的折扣總額可以隨意填寫,Cashier 會(huì)在每次使用優(yōu)惠券的時(shí)候根據(jù)您的配置來(lái)覆蓋該默認(rèn)值。由于 Braintree 不支持使用訂閱頻率來(lái)匹配折扣比例,所以這個(gè)優(yōu)惠券是必需的。
數(shù)據(jù)庫(kù)遷移
開始使用 Cashier 之前,需要 準(zhǔn)備數(shù)據(jù)庫(kù)。Cashier 需要在您的數(shù)據(jù)庫(kù) users
表中新增幾個(gè)列,以及創(chuàng)建一個(gè)新的 subscriptions
表來(lái)存儲(chǔ)客戶的訂閱信息:
Schema::table('users', function ($table) { $table->string('braintree_id')->nullable(); $table->string('paypal_email')->nullable(); $table->string('card_brand')->nullable(); $table->string('card_last_four')->nullable(); $table->timestamp('trial_ends_at')->nullable(); }); Schema::create('subscriptions', function ($table) { $table->increments('id'); $table->unsignedInteger('user_id'); $table->string('name'); $table->string('braintree_id'); $table->string('braintree_plan'); $table->integer('quantity'); $table->timestamp('trial_ends_at')->nullable(); $table->timestamp('ends_at')->nullable(); $table->timestamps(); });
一旦遷移文件建立好后,運(yùn)行 Artisan 的 migrate
命令。
Billable 模型
然后,添加 Billable
Trait 到你的模型定義中:
use Laravel\Cashier\Billable; class User extends Authenticatable{ use Billable; }
API Keys
緊接著,您應(yīng)該在 services.php
文件中配置以下選項(xiàng):
'braintree' => [ 'model' => App\User::class, 'environment' => env('BRAINTREE_ENV'), 'merchant_id' => env('BRAINTREE_MERCHANT_ID'), 'public_key' => env('BRAINTREE_PUBLIC_KEY'), 'private_key' => env('BRAINTREE_PRIVATE_KEY'), ],
最后,您必須向 AppServiceProvider
服務(wù)提供者的 boot
方法中,添加以下的 Braintree SDK 調(diào)用:
\Braintree_Configuration::environment(config('services.braintree.environment')); \Braintree_Configuration::merchantId(config('services.braintree.merchant_id')); \Braintree_Configuration::publicKey(config('services.braintree.public_key')); \Braintree_Configuration::privateKey(config('services.braintree.private_key'));
貨幣配置
Cashier 使用美元(USD)作為默認(rèn)貨幣。您可以通過(guò)在服務(wù)提供者的 boot
方法中調(diào)用 Cashier::useCurrency
方法來(lái)更改默認(rèn)的貨幣。這個(gè) useCurrency
方法接受兩個(gè)字符串參數(shù):貨幣和貨幣符號(hào):
use Laravel\Cashier\Cashier; Cashier::useCurrency('eur', '€');
訂閱
創(chuàng)建訂閱
創(chuàng)建訂閱,首先需要獲取到一個(gè) Billable 模型實(shí)例,這通常是 App\User
的一個(gè)實(shí)例。一旦您獲取了模型實(shí)例,您可以使用 newSubscription
方法創(chuàng)建模型的訂閱:
$user = User::find(1); $user->newSubscription('main', 'premium')->create($stripeToken);
newSubscription
方法的第一個(gè)參數(shù)應(yīng)該是訂閱的名稱。如果您的應(yīng)用程序只提供一個(gè)訂閱,那么您可以將其設(shè)置為 main
or primary
。第二個(gè)參數(shù)是用戶訂閱的 Stripe / Braintree 計(jì)劃。這個(gè)值應(yīng)該與 Stripe 或 Braintree 中的標(biāo)識(shí)符對(duì)應(yīng)。
create
方法接受一個(gè) Stripe 信用卡 / 源令牌,它將開始訂閱,并使用客戶 ID 和其他相關(guān)的賬單信息更新數(shù)據(jù)庫(kù)。
用戶其他的詳細(xì)信息
如果您想要指定用戶其他的詳細(xì)信息,您可以通過(guò)將它們作為第二個(gè)參數(shù)傳遞給 create
方法:
$user->newSubscription('main', 'monthly')->create($stripeToken, [ 'email' => $email, ]);
要了解更多關(guān)于 Stripe 或 Braintree 支持的額外字段,請(qǐng)查看 Stripe 的 內(nèi)容創(chuàng)建客戶文檔 或?qū)?yīng)的 Braintree 文檔。
優(yōu)惠券
如果您想在創(chuàng)建訂閱時(shí)使用優(yōu)惠券,您可以使用 withCoupon
方法:
$user->newSubscription('main', 'monthly') ->withCoupon('code') ->create($stripeToken);
檢查訂閱狀態(tài)
一旦用戶在您的應(yīng)用程序訂閱了,您可以使用各種方便的方法輕松地檢查他們的訂閱狀態(tài)。首先,如果用戶有一個(gè)激活的訂閱,那么 subscribed
的方法將返回 true
,即使訂閱當(dāng)前處于試用階段:
if ($user->subscribed('main')) { // }
這個(gè) subscribed
方法還可以在 路由中間件 使用,允許您根據(jù)用戶的訂閱狀態(tài)對(duì)路由和控制器進(jìn)行訪問:
public function handle($request, Closure $next){ if ($request->user() && ! $request->user()->subscribed('main')) { // This user is not a paying customer... return redirect('billing'); } return $next($request); }
如果您想要確定用戶是否仍然處于試用階段,您可以使用 onTrial
方法。這個(gè)方法對(duì)于向用戶顯示他們?nèi)匀惶幱谠囉闷诘木媸呛苡杏玫模?/p>
if ($user->subscription('main')->onTrial()) { // }
基于給定的 Stripe / Braintree 計(jì)劃 ID,可以使用 subscribedToPlan
方法來(lái)確定用戶是否訂閱了該計(jì)劃。在本例中,我們將確定用戶的 main
訂閱是否激活了 monthly
計(jì)劃:
if ($user->subscribedToPlan('monthly', 'main')) { // }
recurring
方法可用于確定用戶當(dāng)前是否已訂閱,并且不再處于試用階段:
if ($user->subscription('main')->recurring()) { // }
取消訂閱狀態(tài)
為了確定用戶是否曾經(jīng)訂閱,但是已經(jīng)取消了他們的訂閱,您可以使用 cancelled
方法:
if ($user->subscription('main')->cancelled()) { // }
您還可以確定用戶是否已經(jīng)取消了訂閱,但是仍然處于訂閱的「寬限期」,直到訂閱完全過(guò)期為止。例如,如果用戶在 3 月 5 日取消了原定于 3 月 10 日到期的訂閱,那么用戶將在 3 月 10 日之前進(jìn)行「寬限期」。請(qǐng)注意,在此期間 subscribed
方法仍然返回 true
:
if ($user->subscription('main')->onGracePeriod()) { // }
如果要確定用戶取消訂閱的時(shí)間是否已不在其 “寬限期” 內(nèi),可以使用 ended
方法:
if ($user->subscription('main')->ended()) { // }
修改訂閱計(jì)劃
用戶在您的應(yīng)用程序中訂閱了之后,他們可能會(huì)偶爾想要更改一個(gè)新的訂閱計(jì)劃。要將一個(gè)用戶切換到一個(gè)新的訂閱,需將訂閱計(jì)劃的標(biāo)識(shí)符傳遞給 swap
方法:
$user = App\User::find(1); $user->subscription('main')->swap('provider-plan-id');
如果用戶在試用期,試用期的期限會(huì)被保留。另外,如果訂閱的數(shù)量存在「份額」,那么該份額也將保持。
如果你想在更改用戶訂閱計(jì)劃的時(shí)候取消用戶當(dāng)前訂閱的試用期,可以使用 skipTrial
方法:
$user->subscription('main') ->skipTrial() ->swap('provider-plan-id');
訂閱量
{注意} 訂閱量?jī)H由 Cashier 的 Stripe 支持。Braintree 沒有一個(gè)對(duì)應(yīng)于 Stripe 的「數(shù)量」的特性。
有些時(shí)候訂閱是會(huì)受「數(shù)量」影響的。舉個(gè)例子,你的應(yīng)用程序的付費(fèi)方式可能是每個(gè)賬戶 $10 / 月。你可以使用 incrementQuantity
和 decrementQuantity
方法輕松地增加或減少你的訂閱量:
$user = User::find(1); $user->subscription('main')->incrementQuantity(); // 對(duì)當(dāng)前的訂閱量加5... $user->subscription('main')->incrementQuantity(5); $user->subscription('main')->decrementQuantity(); // 對(duì)當(dāng)前的訂閱量減5... $user->subscription('main')->decrementQuantity(5);
或者,你可以使用 updateQuantity
方法設(shè)定一個(gè)特定的數(shù)量:
$user->subscription('main')->updateQuantity(10);
noProrate
方法可用于更新訂閱的數(shù)量,而不會(huì)對(duì)收費(fèi)進(jìn)行定價(jià):
$user->subscription('main')->noProrate()->updateQuantity(10);
要獲得更多關(guān)于訂閱量的信息,請(qǐng)參考 Stripe 文檔.
訂閱稅額
在計(jì)費(fèi)模式上實(shí)現(xiàn) taxPercentage
方法,并且返回一個(gè) 0 到 100 不超過(guò) 2 位小數(shù)的數(shù)字,用來(lái)指定用戶在訂閱中支付的稅率百分比。
public function taxPercentage() { return 20; }
taxPercentage
方法使你能夠在模型的基礎(chǔ)上應(yīng)用稅率,這對(duì)于一個(gè)跨越多個(gè)國(guó)家和稅率的用戶群可能有幫助。
{注意}
taxPercentage
方法只適用于付費(fèi)訂閱模式。如果你用 charges 來(lái)做「一次性」收費(fèi),你需要同時(shí)手工指定稅率。
同步稅率百分比
當(dāng)更改 taxPercentage
方法返回的硬編碼值時(shí),用戶的任何現(xiàn)有訂閱的稅率設(shè)置將保持不變。如果要用返回的 taxPercentage
值更新現(xiàn)有訂閱的稅率,應(yīng)在用戶的訂閱實(shí)例上調(diào)用 syncTaxPercentage
方法:
$user->subscription('main')->syncTaxPercentage();
訂閱錨定日期
{注意} Cashier 中只有 Stripe 支持修改訂閱錨定日期。
默認(rèn)情況下,計(jì)費(fèi)周期錨定是創(chuàng)建訂閱的日期,如果使用試用期,則是試用結(jié)束的日期。如果要修改賬單錨定日期,可以使用 anchorBillingCycleOn
方法:
use App\User;use Carbon\Carbon; $user = User::find(1); $anchor = Carbon::parse('first day of next month'); $user->newSubscription('main', 'premium') ->anchorBillingCycleOn($anchor->startOfDay()) ->create($stripeToken);
有關(guān)管理訂閱計(jì)費(fèi)周期的詳細(xì)信息,請(qǐng)參閱 Stripe 計(jì)費(fèi)周期文檔
取消訂閱
在用戶訂閱上調(diào)用 cancel
方法用來(lái)取消訂閱:
$user->subscription('main')->cancel();
當(dāng)一個(gè)訂閱被取消時(shí),Cashier 將會(huì)自動(dòng)在你的數(shù)據(jù)庫(kù)中設(shè)置 ends_at
列。這個(gè)列經(jīng)常被用來(lái)獲悉 subscribed
字段何時(shí)應(yīng)該開始返回 false
。例如,如果客戶在 3 月 1 日取消訂閱,但是訂閱計(jì)劃直到 3 月 5 日才結(jié)束,subscribed
方法將會(huì)繼續(xù)返回 true
一直到 3 月 5 日。
你可以使用 onGracePeriod
方法確定用戶是否確定訂閱,但是仍然存在一個(gè)「寬限期」:
if ($user->subscription('main')->onGracePeriod()) { // }
如果你想馬上取消訂閱,請(qǐng)?jiān)谟脩舻挠嗛喼姓{(diào)用 cancelNow
方法:
$user->subscription('main')->cancelNow();
恢復(fù)訂閱
如果一個(gè)用已經(jīng)取消訂閱,你可以在你希望恢復(fù)它的時(shí)候使用 resume
方法。用戶 必須 仍然在他們的寬限期內(nèi)才可以恢復(fù)訂閱:
$user->subscription('main')->resume();
如果用戶已取消訂閱,然后在訂閱寬限期前恢復(fù)該訂閱,他們將不會(huì)被立即計(jì)費(fèi)。相反,他們的訂閱將會(huì)被重新激活,需要按照原來(lái)的支付流程再次進(jìn)行支付。
試用訂閱
以信用卡訂閱
如果你想給你的顧客提供試用期,同時(shí)收集支付方法信息,那么你應(yīng)該在創(chuàng)建訂閱使用 trialDays
方法:
$user = User::find(1);$user->newSubscription('main', 'monthly') ->trialDays(10) ->create($stripeToken);
該方法會(huì)在數(shù)據(jù)庫(kù)訂閱記錄上設(shè)置訂閱期結(jié)束時(shí)間,以便告知 Sripe / Braintree 在此之前不要計(jì)算用戶的賬單信息。
{注意} 如果顧客沒有在試用期結(jié)束前取消訂閱,訂閱會(huì)被自動(dòng)結(jié)算,所以你應(yīng)該確保告知你的用戶他們的試用結(jié)束期。
trialUntil
方法允許提供 DateTime
實(shí)例指定試用結(jié)束期:
use Carbon\Carbon;$user->newSubscription('main', 'monthly') ->trialUntil(Carbon::now()->addDays(10)) ->create($stripeToken);
你可以使用用戶實(shí)例的 onTrial
方法或者訂閱實(shí)例的 onTrial
方法判斷用戶是否處于試用期。下面兩個(gè)示例等價(jià):
if ($user->onTrial('main')) { // } if ($user->subscription('main')->onTrial()) { // }
非信用卡訂閱
如果你不想在提供試用期的時(shí)候收集用戶支付方式信息,只需設(shè)置用戶記錄的 trial_ends_at
列為期望的試用期結(jié)束日期即可,這通常在用戶注冊(cè)期間完成:
$user = User::create([ // Populate other user properties... 'trial_ends_at' => now()->addDays(10), ]);
{注意} 確保已添加
trial_ends_at
日期修改器 到模型定義。
Cashier 把這種類型的引用稱為「一般體驗(yàn)」,因?yàn)樗鼪]有關(guān)聯(lián)任何已存在的訂閱。如果當(dāng)前的日期沒有超過(guò) trail_ends_at
值, User
實(shí)例的 onTrial
方法將會(huì)返回 true
:
if ($user->onTrial()) { // 用戶在他們的試用期內(nèi)... }
如果你希望明確的知道用戶處于「一般」試用期,并且還未創(chuàng)建實(shí)際的訂閱,那么你可以使用 onGenericTrial
方法:
if ($user->onGenericTrial()) { // 用戶在他們「一般」試用期... }
如果你準(zhǔn)備給用戶創(chuàng)建實(shí)際的訂閱,通常你可以使用 newSubsription
方法:
$user = User::find(1); $user->newSubscription('main', 'monthly')->create($stripeToken);
客戶
創(chuàng)建客戶
有時(shí),您可能希望在未訂閱的情況下創(chuàng)建 Stripe 客戶。您可以使用 createAsStripeCustomer
方法完成此操作:
$user->createAsStripeCustomer();
一旦在 Stripe 中創(chuàng)建了客戶,您可以稍后開始訂閱。
{提示} 在 Braintree 中創(chuàng)建客戶使用的是
createAsBraintreeCustomer
方法。
銀行卡
接收信用卡
可計(jì)費(fèi)模型實(shí)例上的 cards
方法返回 Laravel\Cashier\Card
實(shí)例的集合:
$cards = $user->cards();
要檢索默認(rèn)卡,可以使用 defaultCard
方法;
$card = $user->defaultCard();
確定卡號(hào)存檔
您可以使用 hasCardOnFile
方法檢查客戶是否在其帳戶上儲(chǔ)存了信用卡:
if ($user->hasCardOnFile()) { // }
更新信用卡
updateCard
方法可用于更新用戶的信用卡信息,該方法接受一個(gè) Stripe 令牌并設(shè)置一個(gè)新的信用卡作為默認(rèn)支付源:
$user->updateCard($stripeToken);
將您的卡信息與客戶的默認(rèn)卡信息進(jìn)行 Stripe 同步,可以使用 updateCardFromStripe
方法:
$user->updateCardFromStripe();
刪除信用卡
要?jiǎng)h除卡,應(yīng)首先使用 cards
方法檢索客戶的卡。然后,可以調(diào)用要?jiǎng)h除的卡實(shí)例上的 delete
方法:
foreach ($user->cards() as $card) { $card->delete(); }
{注意} 如果要?jiǎng)h除默認(rèn)卡,請(qǐng)確保使用
updateCardFromStripe
方法將新的默認(rèn)卡與數(shù)據(jù)庫(kù)同步。
deleteCards
方法將刪除應(yīng)用程序存儲(chǔ)的用戶的所有卡信息:
$user->deleteCards();
{注意} 如果用戶已有訂閱,則應(yīng)考慮阻止他們刪除最后剩余的付款方式。
處理 Stripe Webhooks
Stripe 和 Braintree 都可以通過(guò) webhook 通知應(yīng)用各種各樣的事件。要處理 Stripe webhook,需要定義一個(gè) Cashier 的 webhook 控制器的路由。這個(gè)控制器可以處理所有進(jìn)入 webhook 的請(qǐng)求并將他們分發(fā)到合適的控制器方法中:
Route::post( 'stripe/webhook', '\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook' );
{注意} 一旦注冊(cè)了路由,確保在您的 Stripe 控制面板配置了 webhook URL。
默認(rèn)情況下,這個(gè)控制器將會(huì)自動(dòng)對(duì)支付失敗次數(shù)過(guò)多(這個(gè)次數(shù)可以在 Stripe 設(shè)置中定義)的訂閱進(jìn)行取消;此外,我們很快會(huì)發(fā)現(xiàn),你可以擴(kuò)展這個(gè)控制器去處理任何你想要處理的 webhook 事件。
{注意} 請(qǐng)確保使用 Cashier 的 webhook 驗(yàn)簽 中間件來(lái)保護(hù)傳入請(qǐng)求。
Webhooks & CSRF 保護(hù)
因?yàn)?Stripe webhooks 需要繞過(guò) Laraval 的 CSRF 保護(hù),請(qǐng)確保在你的 VerifyCsrfToken
中間件含有 URI ,或者將其置于 web
中間件組之外:
protected $except = [ 'stripe/*', ];
定義 Webhook 事件處理程序
Cashier 對(duì)于失敗支付自動(dòng)進(jìn)行取消訂閱,但是如果你有其他的 Stripe webhook 事件希望去處理,可以擴(kuò)展 Webhook 控制器。你的方法名應(yīng)該與 Cashier 期望的約定相符,更具體的說(shuō),你希望處理 Stripe webhook 的方法應(yīng)該以 handle
和 「駝峰」 名為前綴。舉例來(lái)說(shuō),如果你希望處理 invoice.payment_succeeded
的 webhook,你應(yīng)該在控制器添加 handleInvoicePaymentSucceeded
方法:
<?php namespace App\Http\Controllers; use Laravel\Cashier\Http\Controllers\WebhookController as CashierController; class WebhookController extends CashierController{ /** * Handle invoice payment succeeded. * * @param array $payload * @return \Symfony\Component\HttpFoundation\Response */ public function handleInvoicePaymentSucceeded($payload) { // 此處處理事件 } }
接下來(lái),在 routes/web.php
文件中定義 Cashier 控制器的路由:
Route::post( 'stripe/webhook', '\App\Http\Controllers\WebhookController@handleWebhook' );
訂閱失敗
如果用戶的信用卡過(guò)期怎么辦?不用擔(dān)心 - Cashier 包含了一個(gè) Webhook 控制器可以輕松為你取消用戶的訂閱。正如上文所述,你需要做的只是將路由指向控制器:
Route::post( 'stripe/webhook', '\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook' );
就是這樣!失敗的支付將會(huì)被控制器捕獲并處理,該控制器會(huì)在 Stripe 判斷訂閱失敗后(通常嘗試支付失敗 3 次及以上)取消用戶的訂閱。
Webhook 驗(yàn)簽
為了保護(hù) Webhook,你需要使用 Stripe 的 webhook 簽名。為了方便起見,Cashier 包含一個(gè)中間件,用于驗(yàn)證傳入 Stripe webhook 的請(qǐng)求是否有效。
如果要啟用 Webhook 驗(yàn)證,請(qǐng)確保在 services
配置文件中設(shè)置了 stripe.webhook.secret
的值。 Webhook 的 secret
可以從 Stripe 用戶控制面板中找到。
處理 Braintree Webhooks
Stripe 和 Braintree 都可以通過(guò) webhooks 通知應(yīng)用各種各樣的事件。要處理 Braintree webhooks,需要定義一個(gè) Cashier webhook 控制器的路由。這個(gè)控制器可以處理所有傳入 webhook 的請(qǐng)求并將它們分發(fā)到合適的路由器方法中:
Route::post( 'braintree/webhook', '\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook' );
{注意} 一旦注冊(cè)了路由,確保在 Braintree 控制器面板配置了 webhook URL。
默認(rèn)情況下,這個(gè)控制器將會(huì)自動(dòng)對(duì)支付失敗次數(shù)過(guò)多(這個(gè)次數(shù)可以在 Braintree 設(shè)置中定義)的訂閱進(jìn)行取消;此外,我們很快會(huì)發(fā)現(xiàn),你可以擴(kuò)展這控制器去處理任何你想要處理的 webhook 事件。
Webhooks & CSRF 保護(hù)
因?yàn)?Braintree webhooks 需要繞過(guò) Laravel 的 CSRF 保護(hù),請(qǐng)確保在你的 VerifyCsrfToken
中間件列表中含有 URI ,或者將其置于 web
中間件組之外:
protected $except = [ 'braintree/*', ];
定義 Webhook 事件處理程序
Cashier 對(duì)于失敗支付自動(dòng)進(jìn)行取消訂閱,但是如果你有其他的 Braintree webhook 事件希望去處理,可以擴(kuò)展 Webhook 控制器。你的方法名應(yīng)該與 Cashier 期望的約定相符,更具體的說(shuō),你希望處理 Braintree webhook 的方法應(yīng)該以 handle
和「駝峰」名為前綴。舉例來(lái)說(shuō),如果你希望處理 dispute_opened
這個(gè) webhook,你應(yīng)該在控制器添加 handleDisputeOpened
方法:
<?php namespace App\Http\Controllers; use Braintree\WebhookNotification; use Laravel\Cashier\Http\Controllers\WebhookController as CashierController; class WebhookController extends CashierController{ /** * Handle a new dispute. * * @param \Braintree\WebhookNotification $webhook * @return \Symfony\Component\HttpFoundation\Responses */ public function handleDisputeOpened(WebhookNotification $webhook) { // 此處處理時(shí)事件... } }
訂閱失敗
如果用戶的信用卡過(guò)期怎么辦?不用擔(dān)心 - Cashier 包含了一個(gè) Webhook 控制器可以輕松為你取消用戶的訂閱。只需要將路由指向控制器中:
Route::post( 'braintree/webhook', '\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook' );
就是這樣!失敗的支付將會(huì)被控制器捕獲和處理,該控制器會(huì)在 Braintree 判斷訂閱失敗后(通常嘗試支付失敗 3 次及以上)取消用戶的訂閱。 不要忘記:在你的 Braintree 控制器面板中配置 webhook URI。
一次性支付
簡(jiǎn)單支付
{注意} 當(dāng)使用 Stripe 時(shí),
charge
方法接收你想支付于 應(yīng)用程序使用的貨幣的最小單位 的金額。然而,當(dāng)使用 Braintree 時(shí),你應(yīng)該將全部的美元金額傳入charge
方法:
如果你想對(duì)訂閱客戶的信用卡收取「一次性」費(fèi)用,可以在可計(jì)費(fèi)模型實(shí)例上使用 charge
方法:
// Stripe 接收分為單位的費(fèi)用... $stripeCharge = $user->charge(100); // Braintree 接收美元為單位的費(fèi)用... $user->charge(1);
charge
方法接受一個(gè)數(shù)組作為它的第二個(gè)參數(shù),允許你創(chuàng)建支付時(shí)將任何你想要的選項(xiàng)傳遞給底層的 Stripe / Braintree 。 有關(guān)在創(chuàng)建支付時(shí)可用的選項(xiàng),請(qǐng)參閱 Stripe 或 Braintree 文檔:
$user->charge(100, [ 'custom_option' => $value, ]);
如果支付失敗,charge
方法將會(huì)拋出異常。如果支付成功,該方法將會(huì)返回完整的 Stripe / Braintree 響應(yīng):
try { $response = $user->charge(100); } catch (Exception $e) { // }
費(fèi)用與發(fā)票
有時(shí)你可能需要支付一次性費(fèi)用同時(shí)也需要生成費(fèi)用發(fā)票,以便可以向客戶提供 PDF 文件格式的收據(jù)。 invoiceFor
方法可以讓你做到這一點(diǎn)。 例如,向客戶開具 5.00 美元的「一次性費(fèi)用」發(fā)票:
// Stripe 接收分為單位的費(fèi)用... $user->invoiceFor('One Time Fee', 500); // Braintree 接收美元為單位的費(fèi)用... $user->invoiceFor('One Time Fee', 5);
該發(fā)票會(huì)立即通過(guò)用戶信用卡支付。 invoiceFor
方法接收一個(gè)數(shù)組作為第三個(gè)參數(shù),允許你在創(chuàng)建支付時(shí)將任何你想要的選項(xiàng)傳遞給底層的 Stripe / Braintree :
$user->invoiceFor('Stickers', 500, [ 'quantity' => 50, ], [ 'tax_percent' => 21, ]);
如果你使用 Braintree 作為你的賬單提供者,你在調(diào)用 invoiceFor
方法時(shí)必須包含 description
選項(xiàng):
$user->invoiceFor('One Time Fee', 500, [ 'description' => 'your invoice description here', ]);
{注意}
invoiceFor
方法將會(huì)創(chuàng)建 Stripe 發(fā)票,該發(fā)票將會(huì)在支付失敗后重試。如果你不想失敗后重試,你需要在第一次支付失敗后調(diào)用 Stripe API 關(guān)閉它。
關(guān)于退款
如果您需要處理退款,您可以使用 refund
方法。此方法接受 Stripe charge ID 作為其唯一參數(shù):
$stripeCharge = $user->charge(100); $user->refund($stripeCharge->id);
發(fā)票
您可以使用 invoices
方法輕松獲取賬單模型的發(fā)票數(shù)組:
$invoices = $user->invoices(); // 結(jié)果包含處理中的發(fā)票... $invoices = $user->invoicesIncludingPending();
當(dāng)列出客戶發(fā)票清單時(shí),可以使用發(fā)票輔助函數(shù)來(lái)顯示相關(guān)的發(fā)票信息。例如,您可能希望在表格中列出每張發(fā)票,從而方便客戶下載它們:
<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>
生成 PDF 發(fā)票
在路由或控制器中,使用 downloadInvoice
方法生成一個(gè)發(fā)票的 PDF 下載。這個(gè)方法會(huì)自動(dòng)給瀏覽器生成合適的 HTTP 下載響應(yīng):
use Illuminate\Http\Request; Route::get('user/invoice/{invoice}', function (Request $request, $invoiceId) { return $request->user()->downloadInvoice($invoiceId, [ 'vendor' => 'Your Company', 'product' => 'Your Product', ]); });