Cashier 交易工具包
简介
Laravel Cashier 提供了直观、流畅的接口来接入 Stripe’s and Braintree’s 订阅付费服务。它可以处理几乎所有你写起来非常头疼的付费订阅代码。除了提供基本的订阅管理之外,Cashier 还可以帮你处理优惠券,交换订阅、订阅“数量”、取消宽限期,甚至还可以生成 pdf 文档。
{注意} 如果你只是“一次性”的收费并且不提供订阅,你就不应该使用 Cashier。可以直接使用 Stripe 和 Braintree 的 SDK。
配置
Stripe
Composer
首先, 将 Stripe 的 Cashier 包添加到您的依赖项中:
composer require "laravel/cashier":"~7.0"
数据库迁移
在使用 Cashier 之前,我们需要 准备数据库。我们需要向你的 users 表中添加几个列,并创建一个新的 subscriptions 表来保存所有客户的订阅:
Schema::table('users', function ($table) {
$table->string('stripe_id')->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('stripe_id');
$table->string('stripe_plan');
$table->integer('quantity');
$table->timestamp('trial_ends_at')->nullable();
$table->timestamp('ends_at')->nullable();
$table->timestamps();
});
一旦迁移文件建立好后,运行 Artisan 的 migrate 命令。
Billable 模型
接下来,添加 Billable trait 到模型定义。这个 trait 提供了多个方法以便执行常用支付任务,例如创建订阅、使用优惠券以及更新信用卡信息:
use Laravel\Cashier\Billable;
class User extends Authenticatable
{
use Billable;
}
API Keys
最后,在配置文件 services.php 中配置 Stripe 的 key,你可以在Stripe 官网个人中心的控制面板中获取这些 Stripe API key 信息:
'stripe' => [
'model' => App\User::class,
'key' => env('STRIPE_KEY'),
'secret' => env('STRIPE_SECRET'),
],
Braintree
Braintree 注意事项
对于很多操作,Stripe 和 Braintree 实现 Cashier 的功能都是一样的,两者都提供了通过信用卡进行订阅支付的功能,但是 Braintree 还支持通过 PayPal 支付。不过,Braintree 也缺失一些 Stripe 支持的功能,在决定使用 Stripe 还是 Braintree 之前,需要考虑以下几点:
- Braintree 支持 PayPal 而 Stripe 不支持。
- Braintree 不支持
increment和decrement方法,这是 Braintree 的限制,而不是 Cashier 限制。 - Braintree 不支持基于百分比的折扣。这是 Braintree 的限制,而不是 Cashier 限制。
Composer
首先,将 Braintree 的 Cashier 包添加到您的依赖项中:
composer require "laravel/cashier-braintree":"~2.0"
服务提供者
接下来, 在 config/app.php 配置文件中,注册 Laravel\Cashier\CashierServiceProvider 服务提供者:
Laravel\Cashier\CashierServiceProvider::class
信用卡优惠计划
在使用 Cashier 之前,你需要首先在 Braintree 控制面板中定义一个 plan-credit 折扣。这个折扣会根据用户选择的支付选项匹配合适的折扣比例,比如选择年付还是月付。
在 Braintree 控制面板中配置的折扣总额可以随意填写,Cashier 会在每次使用优惠券的时候根据我们自己的定制覆盖该默认值。由于 Braintree 不支持在订阅频率上来匹配折扣比例,所以这一优惠券是必需的。
数据库迁移
开始使用 Cashier 之前, 我们需要 准备一下数据库。我们需要在数据库的 users 表中新增几个列,以及创建一个新的 subscriptions 表来存储客户的订阅信息:
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();
});
一旦迁移文件建立好后,运行 Artisan 的 migrate 命令。
Billable 模型
下一步, 添加 Billable trait 到你的模型定义中:
use Laravel\Cashier\Billable;
class User extends Authenticatable
{
use Billable;
}
API Keys
下一步, 你应该在 services.php 文件中配置以下选项:
'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 服务提供者的 boot 方法中,添加以下的 Braintree SDK 调用:
\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)作为默认货币。你可以通过在服务提供者的 boot 方法中调用 Cashier::useCurrency 方法来更改默认的货币。这个 useCurrency 方法接受两个字符串参数:货币和货币符号:
use Laravel\Cashier\Cashier;
Cashier::useCurrency('eur', '€');
订阅
创建订阅
创建订阅,首先需要获取到一个 Billable 模型实例,这通常是 App\User 的一个实例。一旦您获取了模型实例,您可以使用 newSubscription 方法创建模型的订阅:
$user = User::find(1);
$user->newSubscription('main', 'premium')->create($stripeToken);
newSubscription 方法的第一个参数应该是订阅的名称。如果您的应用程序只提供一个订阅,那么您可以将其设置为 main or primary。第二个参数是用户订阅的 Stripe / Braintree 计划。这个值应该与 Stripe 或 Braintree 中的标识符对应。
create 方法接受一个 Stripe 信用卡 / 源令牌,它将开始订阅,并使用客户 ID 和其他相关的账单信息更新数据库。
用户其他的详细信息
如果您想要指定用户其他的详细信息,您可以通过将它们作为第二个参数传递给 create 方法:
$user->newSubscription('main', 'monthly')->create($stripeToken, [
'email' => $email,
]);
要了解更多关于 Stripe 或 Braintree 支持的额外字段,请查看 Stripe 的 内容创建客户文档 或对应的 Braintree 文档。
优惠券
如果您想在创建订阅时使用优惠券,您可以使用 withCoupon 方法:
$user->newSubscription('main', 'monthly')
->withCoupon('code')
->create($stripeToken);
检查订阅状态
一旦用户在您的应用程序订阅了,您可以使用各种方便的方法轻松地检查他们的订阅状态。首先,如果用户有一个激活的订阅,那么 subscribed 的方法将返回 true ,即使订阅当前处于试用阶段:
if ($user->subscribed('main')) {
//
}
这个 subscribed 方法还可以在 路由中间件 使用,允许您根据用户的订阅状态对路由和控制器进行访问。
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()) {
//
}
基于给定的 Stripe / Braintree 计划 ID,可以使用 subscribedToPlan 方法来确定用户是否订阅了该计划。在本例中,我们将确定用户的 main 订阅是否激活了 monthly 计划:
if ($user->subscribedToPlan('monthly', 'main')) {
//
}
取消订阅状态
为了确定用户是否曾经订阅,但是已经取消了他们的订阅,您可以使用 cancelled 方法:
if ($user->subscription('main')->cancelled()) {
//
}
您还可以确定用户是否已经取消了订阅,但是仍然处于订阅的「宽限期」,直到订阅完全过期为止。例如,如果用户在 3 月 5 日取消了原定于 3 月 10 日到期的订阅,那么用户将在 3 月 10 日之前进行「宽限期」。请注意,在此期间 subscribed 方法仍然返回 true:
if ($user->subscription('main')->onGracePeriod()) {
//
}
修改订阅计划
用户在您的应用程序中订阅了之后,他们可能会偶尔想要更改一个新的订阅计划。要将一个用户切换到一个新的订阅,需将订阅计划的标识符传递给 swap 方法:
$user = App\User::find(1);
$user->subscription('main')->swap('provider-plan-id');
如果用户在试用期,试用期的期限会被保留。另外,如果订阅的数量存在「份额」,那么该份额也将保持。
如果你想在更改用户订阅计划的时候取消用户当前订阅的试用期,可以使用 skipTrial 方法:
$user->subscription('main')
->skipTrial()
->swap('provider-plan-id');
订阅量
{note} 订阅量仅由 Cashier 的 Stripe 支持。Braintree 没有一个对应于 Stripe 的「数量」的特性。
有些时候订阅是会受「数量」影响的。举个例子,你的应用程序的付费方式可能是每个账户 $10 / 月。你可以使用 incrementQuantity 和 decrementQuantity 方法轻松的增加或减少你的订阅量:
$user = User::find(1);
$user->subscription('main')->incrementQuantity();
// 对当前的订阅量加5 ...
$user->subscription('main')->incrementQuantity(5);
$user->subscription('main')->decrementQuantity();
// 对当前的订阅量减5 ...
$user->subscription('main')->decrementQuantity(5);
或者, 你可以使用 updateQuantity 方法设定一个特定的数量:
$user->subscription('main')->updateQuantity(10);
noProrate 方法可用于更新订阅的数量,而不会对收费进行定价:
$user->subscription('main')->noProrate()->updateQuantity(10);
要获得更多关于订阅量的信息, 请参考 Stripe 文档.
订阅税额
在计费模式上实现 taxPercentage 方法,并且返回一个 0 到 100 不超过2位小数的数字,用来指定用户在订阅中支付的税率百分比。
public function taxPercentage() {
return 20;
}
taxPercentage 方法使你能够在模型的基础上应用税率,这对于一个跨越多个国家和税率的用户群可能有帮助。
{note}
taxPercentage方法只适用于付费订阅模式。如果你用 charges 来做「一次性」收费,你需要同时手工指定税率。
取消订阅
在用户订阅上调用 cancel 方法用来取消订阅:
$user->subscription('main')->cancel();
当一个订阅被取消时,Cashier 将会自动的在你的数据库中设置 ends_at 列。这个列经常被用来知道何时 subscribed 字段应该开始返回 false 。例如,如果客户在 3 月 1 日取消订阅,但是订阅计划直到 3 月 5 日才结束,subscribed 方法将会继续返回 true 一直到 3 月 5 日。
你可以使用 onGracePeriod 方法确定用户是否确定订阅,但是仍然存在一个「宽限期」:
if ($user->subscription('main')->onGracePeriod()) {
//
}
如果你想马上取消订阅,请在用户的订阅中调用 cancelNow 方法:
$user->subscription('main')->cancelNow();
恢复订阅
如果一个用已经取消订阅,你可以使用 resume 方法在你希望去恢复它的时候。用户 必须 仍然在他们的宽限期内才可以恢复订阅:
$user->subscription('main')->resume();
如果用户取消订阅,然后在订阅完全过期前恢复该订阅,他们将不会被立即计费。相反,他们的订阅将会被重新激活,需要按照原来的支付流程再次进行支付。
更新信用卡
updateCard 方法可用于更新用户的信用卡信息,该方法接受一个 Stripe 令牌并设置一个新的信用卡作为默认支付源:
$user->updateCard($stripeToken);
试用订阅
有信用卡的情况
如果你想给你的顾客提供试用期,同时收集支付方法信息,那么你应该在创建订阅使用 trialDays 方法:
$user = User::find(1);
$user->newSubscription('main', 'monthly')
->trialDays(10)
->create($stripeToken);
该方法会在数据库订阅记录上设置订阅期结束时间,以便告知 Sripe / Braintree 在此之前不要计算用户的账单信息。
{note} 如果顾客没有在试用期结束前取消订阅,订阅会被自动结算,所以你应该确保告知你的用户他们的试用结束期。
trialUntil 方法允许提供 DateTime 实例指定试用结束期:
use Carbon\Carbon;
$user->newSubscription('main', 'monthly')
->trialUntil(Carbon::now()->addDays(10))
->create($stripeToken);
你可以使用用户实例的onTrial 方法或者订阅实例的 onTrial 方法判断用户是否处于试用期。下面两个示例等价:
if ($user->onTrial('main')) {
//
}
if ($user->subscription('main')->onTrial()) {
//
}
没有信用卡的情况
如果你不想在提供试用期的时候收集用户支付方式信息,只需设置用户记录的 trial_ends_at 列为期望的试用期结束日期即可,这通常在用户注册期间完成:
$user = User::create([
// Populate other user properties...
'trial_ends_at' => now()->addDays(10),
]);
{note} 确保已添加
trial_ends_at日期修改器 到模型定义。
Cashier 把这种类型的引用引申为「一般体验」,因为它没有关联任何已存在的订阅。如果当前的日期没有超过 trail_ends_at 值, User 实例的 onTrial 方法将会返回 true :
if ($user->onTrial()) {
// 用户在他们的试用期内...
}
如果你希望明确的知道用户处于「一般」试用期,并且还未创建实际的订阅,那么你可以使用 onGenericTrial 方法:
if ($user->onGenericTrial()) {
// 用户在他们「一般」试用期...
}
如果你准备给用户创建实际的订阅,通常你可以使用 newSubsription 方法:
$user = User::find(1);
$user->newSubscription('main', 'monthly')->create($stripeToken);
处理 Stripe Webhooks
Stripe 和 Braintree 都可以通过 webhook 通知应用各种各样的事件。去处理 Stripe webhooks,需要定义一个去 Cashier webhook控制器的路由。这个控制器可以处理所有输入 webhook 的请求并将他们分发到合适的控制器方法:
Route::post(
'stripe/webhook',
'\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook'
);
{note} 一旦已经注册了路由,确保在 Stripe 控制面板配置了 webhook URL
默认情况下,这个控制器将会自动对支付失败次数过多(这个次数可以在 Stripe 设置中定义)的订阅进行取消;此外,我们很快会发现,你可以扩展这控制器去处理任何你想要处理的 webhook 事件。
Webhooks & CSRF 保护
因为 Stripe webhooks 需要绕过 Laraval 的 CSRF 保护,确保在你的 VerifyCsrfToken 中间件列出 URI ,或者将其置于 web 中间件组之外:
protected $except = [
'stripe/*',
];
定义 Webhook 事件处理程序
Cashier 对于失败支付自动进行取消订阅,但是如果你有其他的 Stripe webhook 事件希望去处理,可以扩展 Webhook 控制器。你的方法名应该与Cashier 期望的约定相符,更具体的说,你希望处理Stripe webhook 的方法应该以 handle 和「大小写」名为前缀。举例来说,如果你希望处理 invoice.payment_succeeded webhook,你应该在控制器添加 handleInvoicePaymentSucceeded 方法:
<?php
namespace App\Http\Controllers;
use Laravel\Cashier\Http\Controllers\WebhookController as CashierController;
class WebhookController extends CashierController
{
/**
* Handle a Stripe webhook.
*
* @param array $payload
* @return Response
*/
public function handleInvoicePaymentSucceeded($payload)
{
// 处理事件
}
}
接下来,在 routes/web.php 文件中定义去 Cashier 控制器的路由
Next, define a route to your Cashier controller within your routes/web.php file:
Route::post(
'stripe/webhook',
'\App\Http\Controllers\WebhookController@handleWebhook'
);
订阅失败
如果用户的信用卡过期怎么办?不用担心 - Cashier 包含了一个 Webhook 控制器可以轻松为你取消用户的订阅。正如上文所述,所有你需要做的只是将路由指向控制器:
Route::post(
'stripe/webhook',
'\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook'
);
就是这样!失败的支付将会被控制器捕获和处理,该控制器会在 Stripe 判断订阅失败后(通常尝试支付失败3次及以上)取消用户的订阅。
处理 Braintree Webhooks
Stripe 和 Braintree 都可以通过 webhooks 通知应用各种各样的事件。去处理 Braintree webhooks,需要定义一个去 Cashier webhook 控制器的路由。这个控制器可以处理所有输入 webhook 的请求并将它们分发到合适的路由器方法:
Route::post(
'braintree/webhook',
'\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook'
);
{note} 一旦已经注册了路由,确保在 Braintree 控制器面板配置了 webhook URL。
默认情况下,这个控制器将会自动对支付失败次数过多(这个次数可以在 Braintree 设置中定义)的订阅进行取消;此外,我们很快会发现,你可以扩展这控制器去处理任何你想要处理的 webhook 事件。
Webhooks & CSRF 保护
因为 Braintree webhooks 需要绕过 Laravel 的 CSRF 保护,确保在你的 VerifyCsrfToken 中间件列出 URI ,或者将其置于 web 中间件组之外:
protected $except = [
'braintree/*',
];
定义 Webhook 事件处理器
Cashier 对于失败支付自动进行取消订阅,但是如果你有其他的 Braintree webhook 事件希望去处理,可以扩展 Webhook 控制器。你的方法名应该与Cashier 期望的约定相符,更具体的说,你希望处理Braintree webhook 的方法应该以 handle 和「大小写」名为前缀。举例来说,如果你希望处理 dispute_opened webhook,你应该在控制器添加 handleDisputeOpened 方法:
<?php
namespace App\Http\Controllers;
use Braintree\WebhookNotification;
use Laravel\Cashier\Http\Controllers\WebhookController as CashierController;
class WebhookController extends CashierController
{
/**
* Handle a Braintree webhook.
*
* @param WebhookNotification $webhook
* @return Response
*/
public function handleDisputeOpened(WebhookNotification $notification)
{
// 处理事件
}
}
失败的订阅
如果用户的信用卡过期怎么办?不用担心 - Cashier 包含了一个 Webhook 控制器可以轻松为你取消用户的订阅。只需要将路由指向控制器:
Route::post(
'braintree/webhook',
'\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook'
);
就是这样!失败的支付将会被控制器捕获和处理,该控制器会在 Braintree 判断订阅失败后(通常尝试支付失败3次及以上)取消用户的订阅。 不要忘记:你需要在 Braintree 控制器面板配置你的 webhook URI。
一次性支付
简单支付
{note} 使用 Stripe 时,
charge方法接收你想支付于 应用程序使用的货币的最小单位 的金额。然而,当使用 Braintree 时,你应该将全部的美元金额传入charge方法:
如果你想对订阅客户的信用卡收取「一次性」费用,可以在可计费模型实例上使用 charge 方法:
// Stripe 接收分为单位的费用...
$user->charge(100);
// Braintree 接收美元为单位的费用...
$user->charge(1);
charge 方法接受一个数组作为它的第二个参数,允许你创建支付时将任何你想要的选项传递给底层的 Stripe / Braintree 。 有关在创建支付时可用的选项,请参阅 Stripe 或 Braintree 文档:
$user->charge(100, [
'custom_option' => $value,
]);
如果支付失败,charge 方法将会抛出异常。如果支付成功,该方法将会返回完整的 Stripe / Braintree 响应:
try {
$response = $user->charge(100);
} catch (Exception $e) {
//
}
费用与发票
有时你可能需要支付一次性费用同时也需要生成费用发票,以便可以向客户提供 PDF 收据。 invoiceFor 方法可以让你做到这一点。 例如,向客户开具 5.00 美元的「一次性费用」发票:
// Stripe 接收分为单位的费用...
$user->invoiceFor('One Time Fee', 500);
// Braintree 接收美元为单位的费用...
$user->invoiceFor('One Time Fee', 5);
该发票会立即通过用户信用卡支付。 invoiceFor 方法接收一个数组作为第三个参数,允许你在创建支付将任何你想要的选项传递给底层的 Stripe / Braintree :
$user->invoiceFor('One Time Fee', 500, [
'custom-option' => $value,
]);
如果你使用 Braintree 作为你的账单提供者,你在调用 invoiceFor 方法时必须包含 description 选项:
$user->invoiceFor('One Time Fee', 500, [
'description' => '发票描述写在这里',
]);
{note}
invoiceFor方法将会创建 Stripe 发票,该发票将会在支付失败后重试。如果你不想失败后重试,你需要在第一次支付失败后调用 Stripe API 关闭它。
发票
你可以使用 invoices 方法轻松获取账单模型的发票数组:
$invoices = $user->invoices();
// 结果包含处理中的发票...
$invoices = $user->invoicesIncludingPending();
当列出顾客发票清单时,你可以使用发票辅助函数来显示相关的发票信息。例如,你可能想在表格中列出每张发票,从而方便用户下载它们:
<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 发票
从路由或控制器内,使用 downloadInvoice 方法生成一个发票的 PDF 下载。这个方法会自动给浏览器生成合适的 HTTP 下载响应:
use Illuminate\Http\Request;
Route::get('user/invoice/{invoice}', function (Request $request, $invoiceId) {
return $request->user()->downloadInvoice($invoiceId, [
'vendor' => 'Your Company',
'product' => 'Your Product',
]);
});
点击查看所有 Laravel 中文文档 文章: https://codercto.com/courses/l/3.html
Data Structures and Algorithm Analysis in Java
Mark A. Weiss / Pearson / 2006-3-3 / USD 143.00
As the speed and power of computers increases, so does the need for effective programming and algorithm analysis. By approaching these skills in tandem, Mark Allen Weiss teaches readers to develop wel......一起来看看 《Data Structures and Algorithm Analysis in Java》 这本书的介绍吧!