Laravel Cashier
介绍
Laravel Cashier 提供了一个对 Stripe 订阅计费服务的表达性、流畅的接口。它处理了几乎所有你不想写的订阅计费样板代码。除了基本的订阅管理,Cashier 还可以处理优惠券、更换订阅、订阅“数量”、取消宽限期,甚至生成发票 PDF。
配置
Composer
首先,将 Cashier 包添加到你的 composer.json
文件中:
"laravel/cashier": "~2.0"
服务提供者
接下来,在你的 app
配置文件中注册 Laravel\Cashier\CashierServiceProvider
。
迁移
在使用 Cashier 之前,我们需要在你的数据库中添加几个列。别担心,你可以使用 cashier:table
Artisan 命令创建一个迁移来添加必要的列。例如,要将列添加到用户表中,使用 php artisan cashier:table users
。创建迁移后,只需运行 migrate
命令。
模型设置
接下来,将 BillableTrait 和适当的日期变换器添加到你的模型定义中:
use Laravel\Cashier\BillableTrait;
use Laravel\Cashier\BillableInterface;
class User extends Eloquent implements BillableInterface {
use BillableTrait;
protected $dates = ['trial_ends_at', 'subscription_ends_at'];
}
Stripe Key
最后,在你的一个引导文件中设置你的 Stripe key:
User::setStripeKey('stripe-key');
订阅计划
一旦你有了一个模型实例,你可以很容易地将该用户订阅到一个给定的 Stripe 计划:
$user = User::find(1);
$user->subscription('monthly')->create($creditCardToken);
如果你想在创建订阅时应用优惠券,可以使用 withCoupon
方法:
$user->subscription('monthly')
->withCoupon('code')
->create($creditCardToken);
subscription
方法将自动创建 Stripe 订阅,并更新你的数据库中的 Stripe 客户 ID 和其他相关的计费信息。如果你的计划在 Stripe 中配置了试用期,试用期结束日期也会自动设置在用户记录上。
如果你的计划有一个未在 Stripe 中配置的试用期,你必须在订阅后手动设置试用期结束日期:
$user->trial_ends_at = Carbon::now()->addDays(14);
$user->save();
指定额外的用户详情
如果你想指定额外的客户详情,可以通过 create
方法的第二个参数传递它们:
$user->subscription('monthly')->create($creditCardToken, [
'email' => $email, 'description' => '我们的第一个客户'
]);
要了解 Stripe 支持的其他字段,请查看 Stripe 的 客户创建文档。
无需提前提供信用卡
如果你的应用程序提供无需信用卡的免费试用期,请将模型上的 cardUpFront
属性设置为 false
:
protected $cardUpFront = false;
在账户创建时,务必在模型上设置试用期结束日期:
$user->trial_ends_at = Carbon::now()->addDays(14);
$user->save();
更换订阅
要将用户更换到新的订阅,请使用 swap
方法:
$user->subscription('premium')->swap();
如果用户在试用期内,试用期将正常保持。此外,如果订阅存在“数量”,该数量也将保持。
订阅数量
有时订阅会受到“数量”的影响。例如,你的应用程序可能会对账户上的每个用户每月收取 $10。要轻松增加或减少你的订阅数量,请使用 increment
和 decrement
方法:
$user = User::find(1);
$user->subscription()->increment();
// 在订阅的当前数量上增加五个...
$user->subscription()->increment(5);
$user->subscription()->decrement();
// 在订阅的当前数量上减少五个...
$user->subscription()->decrement(5);
取消订阅
取消订阅就像散步一样简单:
$user->subscription()->cancel();
当订阅被取消时,Cashier 会自动在你的数据库中设置 subscription_ends_at
列。此列用于知道何时 subscribed
方法应开始返回 false
。例如,如果客户在 3 月 1 日取消订阅,但订阅计划在 3 月 5 日结束,subscribed
方法将继续返回 true
直到 3 月 5 日。
恢复订阅
如果用户已取消其订阅并且你希望恢复它,请使用 resume
方法:
$user->subscription('monthly')->resume($creditCardToken);
如果用户取消订阅,然后在订阅完全过期之前恢复该订阅,他们将不会立即被计费。他们的订阅将被重新激活,并将在原始计费周期中计费。
检查订阅状态
要验证用户是否订阅了你的应用程序,请使用 subscribed
命令:
if ($user->subscribed())
{
//
}
subscribed
方法是路由过滤器的一个很好的候选者:
Route::filter('subscribed', function()
{
if (Auth::user() && ! Auth::user()->subscribed())
{
return Redirect::to('billing');
}
});
你还可以使用 onTrial
方法确定用户是否仍在其试用期内(如果适用):
if ($user->onTrial())
{
//
}
要确定用户是否曾是活跃订阅者,但已取消其订阅,可以使用 cancelled
方法:
if ($user->cancelled())
{
//
}
你还可以确定用户是否已取消其订阅,但仍在其“宽限期”内,直到订阅完全过期。例如,如果用户在 3 月 5 日取消了计划在 3 月 10 日结束的订阅,用户将在 3 月 10 日之前处于“宽限期”。请注意,在此期间 subscribed
方法仍返回 true
。
if ($user->onGracePeriod())
{
//
}
everSubscribed
方法可用于确定用户是否曾在你的应用程序中订阅过计划:
if ($user->everSubscribed())
{
//
}
onPlan
方法可用于根据其 ID 确定用户是否订阅了给定计划:
if ($user->onPlan('monthly'))
{
//
}
处理支付失败
如果客户的信用卡过期怎么办?不用担心 - Cashier 包含一个 Webhook 控制器,可以轻松地为你取消客户的订阅。只需将一个路由指向控制器:
Route::post('stripe/webhook', 'Laravel\Cashier\WebhookController@handleWebhook');
就是这样!失败的支付将由控制器捕获和处理。控制器将在三次支付失败后取消客户的订阅。此示例中的 stripe/webhook
URI 仅供参考。你需要在 Stripe 设置中配置 URI。
处理其他 Stripe Webhooks
如果你有其他 Stripe webhook 事件想要处理,只需扩展 Webhook 控制器。你的方法名称应符合 Cashier 的预期约定,具体来说,方法应以 handle
和你想处理的 Stripe webhook 名称为前缀。例如,如果你想处理 invoice.payment_succeeded
webhook,你应该在控制器中添加一个 handleInvoicePaymentSucceeded
方法。
class WebhookController extends Laravel\Cashier\WebhookController {
public function handleInvoicePaymentSucceeded($payload)
{
// 处理事件
}
}
NOTE
除了更新数据库中的订阅信息外,Webhook 控制器还将通过 Stripe API 取消订阅。
发票
你可以使用 invoices
方法轻松检索用户发票的数组:
$invoices = $user->invoices();
在为客户列出发票时,你可以使用这些辅助方法来显示相关的发票信息:
{{ $invoice->id }}
{{ $invoice->dateString() }}
{{ $invoice->dollars() }}
使用 downloadInvoice
方法生成发票的 PDF 下载。是的,真的这么简单:
return $user->downloadInvoice($invoice->id, [
'vendor' => '你的公司',
'product' => '你的产品',
]);