イントロダクション
LaravelキャッシャーはStripeとBraintreeによるサブスクリプション(定期課金)サービスの読みやすく、スラスラと記述できるインターフェイスを提供します。これにより書くことが恐ろしくなるような、サブスクリプション支払いのための決まりきったコードのほとんどが処理できます。基本的なサブスクリプション管理に加え、キャッシャーはクーポン、サブスクリプションの変更、サブスクリプション数、キャンセル猶予期間、さらにインボイスのPDF発行まで行います。
Stripe設定
Composer
最初に、composer.json
ファイルに、Cashierパッケージを追加し、composer update
コマンドを実行します。
"laravel/cashier": "~6.0"
サービスプロバイダ
次にapp
設定ファイルへ、Laravel\Cashier\CashierServiceProvider
サービスプロバイダを登録します。
データベースマイグレーション
キャッシャーを使用する前に、データベースを準備する必要があります。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
トレイをモデルに追加してください。
use Laravel\Cashier\Billable;
class User extends Authenticatable
{
use Billable;
}
プロバイダキー
次に、services.php
設定ファイルで、Stripeキーを設定してください。
'stripe' => [
'model' => App\User::class,
'secret' => env('STRIPE_SECRET'),
],
Braintree設定
Braintree Caveats
多くの操作において、StripeとBraintreeで実装しているCashierの機能は同じものです。両方のサービスはクレジットカードでの支払いを提供していますが、BraintreeはPayPalでの支払いもサポートしています。しかしながら、Braintreeには、Stripeが提供してる機能のいくつかが欠けています。以下の点を考慮し、StripeとBraintreeのどちらを使うのか決めてください。
- BraintreeはPayPalをサポートしていますが、Stripeはしていません。
- Braintreeはサブスクリプション数の
increment
(追加)とdecrement
(減少)メソッドをサポートしていません。これはCashierではなく、Braintreeの制限です。 - Braintreeはパーセンテージをもとにしたディスカウントはサポートしていません。これはCashierではなく、Braintreeの制限です。
Composer
最初に、composer.json
ファイルに、Braintree向けのCashierパッケージを追加し、composer update
コマンドを実行します。
"laravel/cashier-braintree": "~1.0"
サービスプロバイダ
次にapp
設定ファイルへ、Laravel\Cashier\CashierServiceProvider
サービスプロバイダを登録します。
クレジットクーポンのプラン
CashierをBraintreeで使用する前に、plan-credit
ディスカウントをBraintreeのコントロールパネルで定義する必要があります。このディスカウントは、年払いから月払い、もしくは月払いから年払いの変更時に代金を確実に按分するために使用されます。Braintreeコントロールパネルで設定するディスカウントは、好きな値が指定でき、クーポンの適用時ごとに、Cashierは指定した値を自身のカスタム値でオーバーライドします。
データベースマイグレーション
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
トレイトをモデルへ追加してください。
use Laravel\Cashier\Billable;
class User extends Authenticatable
{
use Billable;
}
プロバイダキー
次に、以下のオプションを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呼び出しコードをAppServiceProvider
サービスプロバイダのboot
メソッドに追加します。
\Braintree_Configuration::environment(env('BRAINTREE_ENV'));
\Braintree_Configuration::merchantId(env('BRAINTREE_MERCHANT_ID'));
\Braintree_Configuration::publicKey(env('BRAINTREE_PUBLIC_KEY'));
\Braintree_Configuration::privateKey(env('BRAINTREE_PRIVATE_KEY'));
サブスクリプション
サブスクリプション作成
サブスクリプションを作成するには最初にbillableなモデルのインスタンスを取得しますが、通常はApp\User
のインスタンスでしょう。モデルインスタンスが獲得できたら、モデルのサブスクリプションを作成するために、newSubscription
メソッドを使います。
$user = User::find(1);
$user->newSubscription('main', 'monthly')->create($creditCardToken);
newSubscription
メソッドの最初の引数は、サブスクリプションの名前です。アプリケーションでサブスクリプションを一つしか取り扱わない場合、main
かprimary
と名づけましょう。2つ目の引数には、ユーザが購入したStripe/Braintreeのプランを指定します。この値はStripe/Braintreeのプランの識別子です。
create
メソッドは自動的にStripeのサブスクリプションを作成すると同時に、StripeのカスタマーIDと関連する支払い情報をデータベースに保存します。
ユーザ詳細情報の指定
ユーザに関する詳細情報を追加したい場合は、create
メソッドの第2引数に渡すことができます。
$user->newSubscription('main', 'monthly')->create($creditCardToken, [
'email' => $email,
]);
Stripe/Braintreeがサポートしている追加のフィールドについての更なる情報は、Stripeの顧客の作成ドキュメントか、Braintreeのドキュメントを確認してください。
クーポン
サブスクリプションの作成時に、クーポンを適用したい場合は、withCoupon
メソッドを使用してください。
$user->newSubscription('main', 'monthly')
->withCoupon('code')
->create($creditCardToken);
サブスクリプション状態の確認
ユーザがアプリケーションで何かを購入したら、バラエティー豊かで便利なメソッドでサブスクリプション状況を簡単にチェックできます。まず初めに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
プランであるかを確認しています。
if ($user->subscribedToPlan('monthly', 'main')) {
//
}
キャンセルしたサブスクリプションの状態
ユーザが一度アクティブな購入者になってから、サブスクリプションをキャンセルしたことを調べるには、cancelled
メソッドを使用します。
if ($user->subscription('main')->cancelled()) {
//
}
また、ユーザがサブスクリプションをキャンセルしているが、まだ完全に期限が切れる前の「猶予期間」中であるかを調べることもできます。例えば、ユーザが3月5日にサブスクリプションをキャンセルし、3月10日に無効になる場合、そのユーザは3月10日までは「猶予期間」中です。subscribed
メソッドは、この期間中、まだture
を返すことに注目して下さい。
if ($user->subscription('main')->onGracePeriod()) {
//
}
プラン変更
アプリケーションの購入済みユーザが新しいサブスクリプションプランへ変更したくなるのはよくあるでしょう。ユーザを新しいサブスクリプションに変更するには、swap
メソッドを使用します。例としてユーザをささっとpremium
サブスクリプションプランへ変更してみましょう。
$user = App\User::find(1);
$user->subscription('main')->swap('provider-plan-id');
ユーザが試用期間中の場合、試用期間は継続します。また、そのプランに「購入数」が存在している場合、購入個数も継続します。
$user->subscription('main')->swap('provider-plan-id');
プランを変更するが、変更しようとするプランの試用期間を飛ばしたい場合は、skipTrial
メソッドを使用します。
$user->subscription('main')
->skipTrial()
->swap('provider-plan-id');
購入数
注意: 購入数はStripe版のCashierでのみサポートしています。Braintreeには、Stripeの"quantity"(購入数)にあたる機能がありません。
購入数はサブスクリプションに影響をあたえることがあります。たとえば、あるアプリケーションで「ユーザごと」に毎月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);
サブスクリプション数の詳細については、Stripeドキュメントを読んでください。
サブスクリプションの税金
キャッシャーを使えばStripe/Braintreeへ「税率(tax_percent
)」を送るのも簡単です。サブスクリプションに対する顧客の支払いの税率を指定するには、billableなモデルにtaxPercentage
メソッドを実装し、小数点以下の桁数が1桁以内で0から100までの数値を返します。
public function taxPercentage() {
return 20;
}
これによりモデルごとに税率を適用できるため、多くの州や国に渡るユーザベースで税率を決める場合に便利です。
サブスクリプションキャンセル
サブスクリプションをキャンセルするにはcancel
メソッドをユーザのサブスクリプションに対して使ってください。
$user->subscription('main')->cancel();
サブスクリプションがキャンセルされるとキャッシャーは自動的に、データベースのends_at
カラムをセットします。このカラムはいつからsubscribed
メソッドがfalse
を返し始めればよいのか、判定するために使用されています。例えば、顧客が3月1日にキャンセルしたが、そのサブスクリプションが3月5日に終了するようにスケジュールされていれば、subscribed
メソッドは3月5日になるまでtrue
を返し続けます。
ユーザがサブスクリプションをキャンセルしたが、まだ「猶予期間」が残っているかどうかを調べるにはonGracePeriod
メソッドを使います。
if ($user->subscription('main')->onGracePeriod()) {
//
}
サブスクリプション再開
ユーザがキャンセルしたサブスクリプションを、再開したいときには、resume
メソッドを使用してください。サブスクリプションを再開するには、そのユーザに有効期間が残っている必要があります。
$user->subscription('main')->resume();
ユーザがサブスクリプションをキャンセルし、それからそのサブスクリプションを再開する場合、そのサブスクリプションの有効期日が完全に切れていなければすぐに課金されません。そのサブスクリプションはシンプルに再度有効になり、元々の支払いサイクルにより課金されます
サブスクリプションのトレイト
カードの事前登録あり
顧客へ試用期間を提供し、支払情報を事前に登録してもらう場合、サブスクリプションを作成するときにtrialDays
メソッドを使ってください。
$user = User::find(1);
$user->newSubscription('main', 'monthly')
->trialDays(10)
->create($creditCardToken);
このメソッドはデータベースのサブスクリプションレコードへ、試用期間の終了日を設定すると同時に、Stripe/Braintreeへこの期日が過ぎるまで、顧客へ課金を始めないように指示します。
注意: 顧客のサブスクリプションが試用期間の最後の日までにキャンセルされないと、期限が切れると同時に課金されます。そのため、ユーザに試用期間の終了日を通知しておくべきでしょう。
ユーザが使用期間中であるかを判定するには、ユーザインスタンスに対しonTrial
メソッドを使うか、サブスクリプションインスタンスに対してonTrial
を使用してください。次の2つの例は、同じ目的を達します。
if ($user->onTrial('main')) {
//
}
if ($user->subscription('main')->onTrial()) {
//
}
カードの事前登録なし
事前にユーザの支払い方法の情報を登録してもらうことなく、試用期間を提供する場合は、そのユーザのレコードのtrial_ends_at
に、試用の最終日を設定するだけです。典型的な使い方は、ユーザ登録時に設定する方法でしょう。
$user = User::create([
// 他のユーザプロパティの設定…
'trial_ends_at' => Carbon::now()->addDays(10),
]);
既存のサブスクリプションと関連付けが行われていないので、Cashierでは、このタイプの試用を「包括的な試用(generic
trial)」と呼んでいます。User
インスタンスに対し、onTrial
メソッドがtrue
を返す場合、現在の日付はtrial_ends_at
の値を過ぎていません。
if ($user->onTrial()) {
// ユーザは試用期間中
}
特に、ユーザが「包括的な試用」期間中であり、まだサブスクリプションが作成されていないことを調べたい場合は、onGenericTrial
メソッドが使用できます。
if ($user->onGenericTrial()) {
// ユーザは「包括的」な試用期間中
}
ユーザに実際のサブスクリプションを作成する準備ができたら、通常はnewSubscription
メソッドを使います。
$user = User::find(1);
$user->newSubscription('main', 'monthly')->create($creditCardToken);
StripeのWebフック処理
サブスクリプション不可
顧客のクレジットカードが有効期限切れだったら? 心配いりません。キャッシャーは顧客のサブスクリプションを簡単にキャンセルできるWebフックコントローラを用意しています。コントローラのルートを指定するだけです。
Route::post(
'stripe/webhook',
'\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook'
);
これだけです! 課金の失敗はコントローラにより捉えられ、処理されます。コントローラはStripeによりサブスクリプションが不能だと判断されると(通常は課金に3回失敗時)、その顧客のサブスクリプションをキャンセルします。Stripeのコントロールパネル設定でWebフックURIを設定する必要があります。
StripeのWebフックでは、Laravelの CSRFバリデーションをバイパスする必要があるため、VerifyCsrfToken
ミドルウェアのURIを例外としてリストしておくか、ルート定義をweb
ミドルウェアグループのリストから外しておきましょう。
protected $except = [
'stripe/*',
];
その他のWebフック
処理したい追加のStripe
Webフックイベントがある場合、Webフックコントローラを拡張するだけです。メソッド名はCashierが期待する命名規則に従う必要があります。つまりメソッド名はhandle
で始まり、取り扱いたいStripeのWebフックを続けます。たとえばinvoice.payment_succeeded
Webフックを使用したいのなら、コントローラにはhandleInvoicePaymentSucceeded
メソッドを追加してください。
<?php
namespace App\Http\Controllers;
use Laravel\Cashier\Http\Controllers\WebhookController as BaseController;
class WebhookController extends BaseController
{
/**
* StripeのWebフック処理
*
* @param array $payload
* @return Response
*/
public function handleInvoicePaymentSucceeded($payload)
{
// イベントを処理する
}
}
BraintreeのWebフック処理
サブスクリプション不可
顧客のクレジットカードが有効期限切れだったら? 心配いりません。キャッシャーは顧客のサブスクリプションを簡単にキャンセルできるWebフックコントローラを含んでいます。コントローラのルートを指定するだけです。
Route::post(
'braintree/webhook',
'\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook'
);
これだけです。支払いが失敗すると、コントローラにより捉えられ、処理されます。Braintreeが購入に失敗したと判断すると(通常は支払いに3回失敗した場合)、その顧客のサブスクリプションはキャンセルされます。BraintreeのコントロールパネルでWebフックのURIを設定する必要があることを忘れないで下さい。
BraintreeのWebフックは、LaravelのCSRFバリデーションをバイパスする必要があるため、VerifyCsrfToken
ミドルウェアの例外リストへURIをのせるか、web
ミドルウェアグループのリスト外でルートを定義します。
protected $except = [
'braintree/*',
];
その他のWebフック
処理したい追加のBraintree
Webフックイベントがある場合、Webフックコントローラを拡張するだけです。メソッド名はBraintreeが期待する命名規則に従う必要があります。特にメソッド名はhandleで始まり、取り扱いたいBraintreeのWebフックを続けます。たとえばdispute_opened
Webフックを使用したいのなら、コントローラにはhandleDisputeOpened
メソッドを追加してください。
<?php
namespace App\Http\Controllers;
use Braintree\WebhookNotification;
use Laravel\Cashier\Http\Controllers\WebhookController as BaseController;
class WebhookController extends BaseController
{
/**
* BraintreeのWebフック処理
*
* @param WebhookNotification $webhook
* @return Response
*/
public function handleDisputeOpened(WebhookNotification $notification)
{
// イベントの処理
}
}
一回だけの課金
課金のみ
注意: Stripeを使用している場合、
charge
メソッドにはアプリケーションで使用している通貨の最低単位で金額を指定しますが、Braintreeの場合にはドル単位の金額を指定します。
すでに何かを購入している顧客のクレジットカードに、「一回だけ」の課金をしたい場合は、billableモデルのインスタンスに対し、charge
メソッドを使います。
// Stripeはセント単位で課金する
$user->charge(100);
// Braintreeはドル単位で課金する
$user->charge(1);
charge
メソッドは第2引数に配列を受け付け、裏で作成されるStripe/Braintreeの課金に対するオプションを指定できます。
$user->charge(100, [
'custom_option' => $value,
]);
charge
メソッドは、課金に失敗した場合に例外を投げます。課金に成功すると、メソッドはtripe/Braintreeレスポンスをそのまま返します。
try {
$response = $user->charge(100);
} catch (Exception $e) {
//
}
インボイス付き課金
一回だけ課金をしつつ、顧客へ発行するPDFのレシートとしてインボイスも生成したいことがあります。invoiceFor
メソッドは、まさにそのために存在しています。例として、「一回だけ」の料金を5ドル課金してみましょう。
// Stripeはセント単位で課金する
$user->invoiceFor('One Time Fee', 500);
// Braintreeはドル単位で課金する
$user->invoiceFor('One Time Fee', 5);
金額は即時にユーザのクレジットカードへ課金されます。invoiceFor
メソッドは第3引数として配列を受け付け、裏で作成されるStripe/Braintreeの課金オプションを指定できます。
$user->invoiceFor('One Time Fee', 500, [
'custom-option' => $value,
]);
注意:
invoiceFor
メソッドは、課金失敗時にリトライするStripeインボイスを生成します。リトライをしてほしくない場合は、最初に課金に失敗した時点で、Stripe APIを使用し、生成したインボイスを閉じる必要があります。
インボイス
invoices
メソッドにより、billableモデルのインボイスの配列を簡単に取得できます。
$invoices = $user->invoices();
顧客へインボイスを一覧表示するとき、そのインボイスに関連する情報を表示するために、インボイスのヘルパメソッドを表示に利用できます。ユーザが簡単にダウンロードできるように、テーブルで全インボイスを一覧表示する例を見てください。
<table>
@foreach ($invoices as $invoice)
<tr>
<td>{{ $invoice->date()->toFormattedDateString() }}</td>
<td>{{ $invoice->total() }}</td>
<td><a href="/user/invoice/{{ $invoice->id }}">ダウンロード</a></td>
</tr>
@endforeach
</table>
インボイスPDF生成
インボイスPDFを生成する前に、dompdf
PHPライブラリーをインストールしておく必要があります。
composer require dompdf/dompdf
ルートやコントローラの中でdownloadInvoice
メソッドを使うと、そのインボイスのPDFダウンロードを生成できます。このメソッドはブラウザへダウンロードのHTTPレスポンスを正しく行うHTTPレスポンスを生成します。
Route::get('user/invoice/{invoice}', function ($invoiceId) {
return Auth::user()->downloadInvoice($invoiceId, [
'vendor' => 'Your Company',
'product' => 'Your Product',
]);
});