[TOC]
### **1、简介**
[Laravel](http://laravelacademy.org/tags/laravel "View all posts in Laravel") [Cashier](http://laravelacademy.org/tags/cashier "View all posts in Cashier") 为通过 [Stripe](https://stripe.com/) 实现[订阅](http://laravelacademy.org/tags/%e8%ae%a2%e9%98%85 "View all posts in 订阅")[支付](http://laravelacademy.org/tags/%e6%94%af%e4%bb%98 "View all posts in 支付")服务提供了一个优雅平滑的接口。它封装了几乎所有你恐惧编写的样板化的订阅支付代码。除了基本的订阅管理外,Cashier还支持处理[优惠券](http://laravelacademy.org/tags/%e4%bc%98%e6%83%a0%e5%88%b8 "View all posts in 优惠券")、订阅升级/替换、订阅“数量”、取消宽限期,甚至生成[PDF](http://laravelacademy.org/tags/pdf "View all posts in PDF")[发票](http://laravelacademy.org/tags/%e5%8f%91%e7%a5%a8 "View all posts in 发票")。
#### **1.1 安装&配置**
##### **Composer**
首先,添加 Cashier 包到 `composer.json` 文件并运行 `composer update` 命令:
~~~
"laravel/cashier": "~6.0"
~~~
##### **服务提供者**
接下来,在 `config/app.php` 配置文件中注册[服务提供者](http://laravelacademy.org/post/2900.html):`Laravel\Cashier\CashierServiceProvider`。
##### **迁移**
使用 Cashier 之前,我们需要准备好数据库。我们需要添加一个字段到 `users` 表,还要创建新的 `subscriptions` 表来处理所有用户订阅:
~~~
Schema::table('users', function ($table) {
$table->string('stripe_id')->nullable();
$table->string('card_brand')->nullable();
$table->string('card_last_four')->nullable();
});
Schema::create('subscriptions', function ($table) {
$table->increments('id');
$table->integer('user_id');
$table->string('name');
$table->string('stripe_id');
$table->string('stripe_plan');
$table->integer('quantity');
$table->timestamp('trial_ends_at')->nullable();
$table->timestamp('ends_at')->nullable();
$table->timestamps();
});
~~~
创建好迁移后,只需简单运行 `migrate` 命令,相应修改就会更新到数据库。
##### **设置模型**
接下来,添加 `Billable` trait 到 `User` 模型类:
~~~
use Laravel\Cashier\Billable;
class User extends Authenticatable{
use Billable;
}
~~~
##### **[Stripe](http://laravelacademy.org/tags/stripe "View all posts in Stripe") 键**
最后,在配置文件 `config/services.php` 中设置 Stripe 键:
~~~
'stripe' => [
'model' => 'User',
'secret' => env('STRIPE_API_SECRET'),
],
~~~
### **2、订阅实现**
#### **2.1 创建订阅**
要创建一个订阅,首先要获取一个账单模型的实例,通常是 `App\User` 的实例。获取到该模型实例之后,你可以使用`newSubscription` 方法来创建该模型的订阅:
~~~
$user = User::find(1);
$user->newSubscription('main', 'monthly')->create($creditCardToken);
~~~
第一个传递给 `newSubscription` 方法的参数是该订阅的名字,如果应用只有一个订阅,可以将其称作 `main` 或`primary`,第二个参数用于指定用户订阅的 Stripe [计划](http://laravelacademy.org/tags/%e8%ae%a1%e5%88%92 "View all posts in 计划"),该值对应 Stripe 中相应计划的 id。
`create` 方法会自动创建这个 Stripe 订阅,同时更新数据库中 Stripe 的客户 ID(即 `users` 表中的 `stripe_id`)和其它相关的账单信息。如果你的订阅计划有[试用期](http://laravelacademy.org/tags/%e8%af%95%e7%94%a8%e6%9c%9f "View all posts in 试用期"),试用期结束时间也会自动被设置到数据库相应字段。
##### **额外的用户信息**
如果你想要指定额外的客户信息,你可以将其作为第二个参数传递给 `create` 方法:
~~~
$user->newSubscription('main', 'monthly')->create($creditCardToken, [
'email' => $email,
'description' => 'Our First Customer'
]);
~~~
要了解更多 Stripe 支持的字段,可以查看 Stripe 关于[创建消费者的文档](https://stripe.com/docs/api#create_customer)。
##### **优惠券**
如果你想要在创建订阅的时候使用优惠券,可以使用 `withCoupon` 方法:
~~~
$user->newSubscription('main', 'monthly')
->withCoupon('code')
->create($creditCardToken);
~~~
#### **2.2 检查订阅状态**
用户订阅你的应用后,你可以使用各种便利的方法来简单检查订阅状态。首先,如果用户有一个有效的订阅,则`subscribed` 方法返回`true`,即使订阅现在出于试用期:
~~~
if ($user->subscribed('main')) {
//
}
~~~
`subscribed` 方法还可以用于[路由中间件](http://laravelacademy.org/post/2803.html),基于用户订阅状态允许你对路由和控制器的访问进行过滤:
~~~
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` 方法,该方法在为还处于试用期的用户显示警告信息很有用:
~~~
if ($user->->subscription('main')->onTrial()) {
//
}
~~~
`onPlan` 方法可用于判断用户是否基于 Stripe ID 订阅了给定的计划:
~~~
if ($user->onPlan('monthly')) {
//
}
~~~
##### **已取消的订阅状态**
要判断用户是否曾经是有效的订阅者,但现在取消了订阅,可以使用 `cancelled` 方法:
~~~
if ($user->subscription('main')->cancelled()) {
//
}
~~~
你还可以判断用户是否曾经取消过订阅,但现在仍然在“宽限期”直到完全失效。例如,如果一个用户在3月5号取消了一个实际有效期到3月10号的订阅,该用户处于“宽限期”直到3月10号。注意 `subscribed` 方法在此期间仍然返回`true`。
~~~
if ($user->subscription('main')->onGracePeriod()) {
//
}
~~~
#### **2.3 修改订阅**
用户订阅应用后,偶尔想要改变到新的订阅计划,要将用户切换到新的订阅,使用 `swap` 方法。例如,我们可以轻松切换用户到 `premium` 订阅:
~~~
$user = App\User::find(1);
$user->subscription('main')->swap('stripe-plan-id');
~~~
如果用户在试用,试用期将会被维护。还有,如果订阅存在数量,数量也可以被维护。切换订阅计划后,
可以使用 `invoice` 方法立即给用户开发票:
~~~
$user->subscription('main')->swap('stripe-plan-id');$user->invoice();
~~~
#### **2.4 订阅数量**
有时候订阅也会被数量影响,例如,应用中每个账户每月需要付费$10,要简单增加或减少订阅数量,使用`incrementQuantity` 和 `decrementQuantity` 方法:
~~~
$user = User::find(1);
$user->subscription('main')->incrementQuantity();
// Add five to the subscription's current quantity...
$user->subscription('main')->incrementQuantity(5);
$user->subscription('main')->decrementQuantity();
// Subtract five to the subscription's current quantity...
$user->subscription('main')->decrementQuantity(5);
~~~
你也可以使用 `updateQuantity` 方法指定数量:
~~~
$user->subscription('main')->updateQuantity(10);
~~~
想要了解更多订阅数量信息,查阅相关[Stripe文档](https://stripe.com/docs/guides/subscriptions#setting-quantities)。
#### **2.5 订阅税金**
在 Cashier 中,提供 `tax_percent` 值发送给 Stripe 很简单。要指定用户支付订阅的税率,实现账单模型的`getTaxPercent` 方法,并返回一个在0到100之间的数值,不要超过两位小数:
~~~
public function getTaxPercent() {
return 20;
}
~~~
这将使你可以在模型基础上使用税率,对跨越不同国家的用户很有用。
#### **2.6 取消订阅**
要取消订阅,可以调用用户订阅上的 `cancel` 方法:
~~~
$user->subscription('main')->cancel();
~~~
当订阅被取消时,Cashier 将会自动设置数据库中的 `subscription_ends_at` 字段。该字段用于了解 `subscribed` 方法什么时候开始返回 `false`。例如,如果客户3月1号份取消订阅,但订阅直到3月5号才会结束,那么 `subscribed`方法继续返回 `true` 直到3月5号。
你可以使用 `onGracePeriod` 方法判断用户是否已经取消订阅但仍然在“宽限期”:
~~~
if ($user->subscription('main')->onGracePeriod()) {
//
}
~~~
#### **2.7 恢复订阅**
如果用户已经取消订阅但想要恢复该订阅,可以使用 `resume` 方法,前提是该用户必须在宽限期内:
~~~
$user->subscription('main')->resume();
~~~
如果该用户取消了一个订阅然后在订阅失效之前恢复了这个订阅,则不会立即支付该账单,取而代之的,他们的订阅只是被重新激活,并回到正常的支付周期。
### **3、处理 Stripe [Webhook](http://laravelacademy.org/tags/webhook "View all posts in Webhook")**
#### **3.1 订阅失败处理**
如果客户的[信用卡](http://laravelacademy.org/tags/%e4%bf%a1%e7%94%a8%e5%8d%a1 "View all posts in 信用卡")失效怎么办?不用担心—— Cashier 自带了 Webhook 控制器,该控制器可以很方便地为你取消客户订阅。只需要定义如下控制器路由:
~~~
Route::post('stripe/webhook', 'Laravel\Cashier\WebhookController@handleWebhook');
~~~
就是这样!失败的支付将会被该控制器捕获和处理。当 Stripe 判断订阅失败(正常情况下尝试支付失败三次后)时该控制器将会取消客户的订阅。不要忘了:你需要在 Stripe 控制面板设置中配置相应的 webhook URI,否则不能正常工作。
由于 Stripe webhooks 需要通过 Laravel 的[CSRF验证](http://laravelacademy.org/post/2784.html#csrf-attack),所以我们将该 URI 置于 `VerifyCsrfToken` 中间件排除列表中:
~~~
protected $except = [
'stripe/*',
];
~~~
#### **3.2 其它Webhooks**
如果你有额外想要处理的 Stripe webhook 事件,只需简单继承 Webhook 控制器, 你的方法名应该和 Cashier 期望的约定一致,尤其是方法应该以“handle”开头并以驼峰命名法命名。例如,如果你想要处理`invoice.payment_succeeded` webhook,你应该添加 `handleInvoicePaymentSucceeded` 方法到控制器:
~~~
<?php
namespace App\Http\Controller;
use Laravel\Cashier\WebhookController as BaseController;
class WebhookController extends BaseController{
/**
* 处理 stripe webhook.
*
* @param array $payload
* @return Response
*/
public function handleInvoicePaymentSucceeded($payload)
{
// 处理该事件
}
}
~~~
### **4、一次性付款**
如果你想要使用订阅客户的信用卡一次性结清账单,可以使用账单模型实例上的 `charge` 方法,该方法接收付款金额(应用使用的货币的最小单位对应的金额数值)作为参数,例如,下面的例子使用信用卡支付100美分,或1美元:
~~~
$user->charge(100);
~~~
`charge` 方法接收一个数组作为第二个参数,允许你传递任何你想要传递的底层 Stripe 账单创建参数:
~~~
$user->charge(100, [
'source' => $token,
'receipt_email' => $user->email,]
);
~~~
如果支付失败 `charge` 方法将返回 `false`,这通常表明付款被拒绝:
~~~
if ( ! $user->charge(100)) {
// The charge was denied...
}
~~~
如果支付成功,该方法将会返回一个完整的 Stripe 响应。
### **5、发票**
你可以使用 `invoices` 方法轻松获取账单模型的发票数组:
~~~
$invoices = $user->invoices();
~~~
当列出客户发票时,你可以使用发票的辅助函数来显示相关的发票信息。例如,你可能想要在表格中列出每张发票,从而方便用户下载它们:
~~~
<table>
@foreach ($invoices as $invoice)
<tr>
<td>{{ $invoice->dateString() }}</td>
<td>{{ $invoice->dollars() }}</td>
<td><a href="/user/invoice/{{ $invoice->id }}">Download</a></td>
</tr>
@endforeach
</table>
~~~
#### **生成PDF发票**
在生成PDF分票之前,需要安装 PHP 库 `dompdf`:
~~~
composer require dompdf/dompdf
~~~
在路由或控制器中,使用 `downloadInvoice` 方法生成发票的 PDF 下载,该方法将会自动生成相应的 HTTP 响应发送下载到浏览器:
~~~
Route::get('user/invoice/{invoice}', function ($invoiceId) {
return Auth::user()->downloadInvoice($invoiceId, [
'vendor' => 'Your Company',
'product' => 'Your Product',
]);
});
~~~
- 序言
- 发行版本说明
- 升级指南
- 贡献代码
- 开始
- 安装
- 配置
- Laravel Homestead
- 基础
- HTTP 路由
- HTTP 中间件
- HTTP 控制器
- HTTP 请求
- HTTP 响应
- 视图
- Blade 模板引擎
- 架构
- 一次请求的生命周期
- 应用目录结构
- 服务提供者
- 服务容器
- 门面(Facades)
- 数据库
- 起步
- 查询构建器
- 迁移
- 填充数据
- Eloquent ORM
- 起步
- 关联关系
- 集合
- 访问器&修改器
- 序列化
- 服务
- 用户认证
- 用户授权
- Artisan Console
- 订阅支付实现:Laravel Cashier
- 缓存
- 集合
- 集成前端资源:Laravel Elixir
- 加密
- 错误&日志
- 事件
- 文件系统/云存储
- 哈希
- 辅助函数
- 本地化
- 邮件
- 包开发
- 分页
- Redis
- 队列
- Session
- Envoy Task Runner
- 任务调度
- 测试
- 验证
- 新手入门指南
- 简单任务管理系统
- 带用户功能的任务管理系统