Laravel 的收费系统 Cashier

简介

Laravel Cashier 提供了直观、流畅的接口来接入 Stripe'sBraintree's 订阅付费服务。它可以处理几乎所有你写起来非常头疼付费订阅代码。除了提供基本的订阅管理之外,Cashier 还可以帮你处理优惠券,更改订阅计划, 按量计费订阅, 已取消订阅宽限期管理, 甚至生成发票的 PDF 文件。

{note} 如果你只是需要提供一次性的收费服务,建议直接使用 Stripe 和 Braintree 的 SDK,而无需使用 Cashier。

配置

Stripe

Composer

首先,将 Cashier Stripe 扩展包添加到 composer.json 文件,然后运行 composer update 命令:

"laravel/cashier": "~7.0"

Service 服务提供者

接下来,需要注册 Laravel\Cashier\CashierServiceProvider 服务提供者 到你的 config/app.php 配置文件。

数据库迁移

在使用 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->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 Artisan 命令。

Billable 模型

现在,需要添加 Billable trait 到你的模型定义。Billable trait 提供了丰富的方法去允许你完成常见的支付任务,比如创建订阅,使用优惠券以及更新信用卡信息。

use Laravel\Cashier\Billable;

class User extends Authenticatable
{
    use Billable;
}

API Keys

最后,你需要在 services.php 配置文件中设置你的 Stripe key。你可以在 Stripe 的控制面板获取到相关的 API keys。

'stripe' => [
    'model'  => App\User::class,
    'key' => env('STRIPE_KEY'),
    'secret' => env('STRIPE_SECRET'),
],

Braintree

Braintree 注意事项

对于大多数操作,Stripe 和 Braintree 在 Cashier 的实现方法是一致的,都提供对信用卡订阅计费的支持。但是 Braintree 是通过 Paypal 进行支付的,所以相对 Stripe 来说会缺少一些功能的支持。在选择该使用 Stripe 还是 Braintree 的时候需要留意这些区别。

  • Braintree 支持 PayPal,但是 Stripe 不支持。
  • Braintree 并不支持对 subscriptions 使用 incrementdecrement 方法。这是 Braintree 本身的限制,并不是 Cahier 的限制。
  • Braintree 并不支持基于百分比的折扣。 这也是 Braintree 本身的限制,并不是 Cahier 的限制。

Composer

首先,将 Cashier Braintree 扩展包添加到 composer.json 文件,然后运行 composer update 命令:

"laravel/cashier-braintree": "~2.0"

服务提供者

接下来,需要注册 Laravel\Cashier\CashierServiceProvider 服务提供者 到你的 config/app/php 配置文件。

信用卡优惠计划

在使用 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->integer('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();
});

创建好数据库迁移文件之后,需要运行一下 migrate Artisan 命令。

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'),
],

然后你需要将 Braintree SDK 添加到你的 AppServiceProviderboot 方法中:

\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', '€');

订阅列表

创建订阅

创建订阅,首先需要获取到一个 billabel 模型实例,一般情况下就是 App\User 实例。然后你可以使用 newSubscription 方法来创建该模型的订阅:

$user = User::find(1);

$user->newSubscription('main', 'monthly')->create($stripeToken);

newSubscription 方法的第一个参数应该是订阅的名称。如果你的应用程序只是提供一个简单的订阅,你可以简单的设置为 main 或者 primary。第二个参数需要指定用户的 Stripe / Braintree 订阅计划。这里的值应该和在 Stripe 或 Braintree 的中的对应值保持一致。

create 方法会创建订阅并且更新客户 ID 和相关的支付信息到数据库。

Additional User Details

如果你需要添加额外的客户细节信息,你可以在 create 方法的第二个参数中进行传递:

$user->newSubscription('main', 'monthly')->create($stripeToken, [
    'email' => $email,
]);

查阅一下 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')) {
        // 用户并没有完成支付...
        return redirect('billing');
    }

    return $next($request);
}

如果你想知道用户是否在一个常识周期内,你可以使用 onTrial 方法。该方法可以用来提示用户他们还在试用期之内:

if ($user->subscription('main')->onTrial()) {
    //
}

subscribedToPlan 方法可以基于给定的 Stripe / Braintree 计划 ID 来判断用户是否有订阅该计划。下面的示例,我们在判断用户的 main 订阅是否成功激活订阅了 monthly 计划。