このリポジトリはPHP Webアプリフレームワークである、LaravelのLTSバージョンである5.5の公式英文ドキュメントを日本語へ翻訳しています。
This project is maintained by okinaka
Laravel CashierはStripeとBraintreeによるサブスクリプション(定期課金)サービスの読みやすく、スラスラと記述できるインターフェイスを提供します。これにより書くのが恐ろしくなるような、サブスクリプション支払いのための決まりきったコードのほとんどが処理できます。基本的なサブスクリプション管理に加え、Cashierはクーポン、サブスクリプションの変更、サブスクリプション数、キャンセル猶予期間、さらにインボイスのPDF発行まで行います。
{note} サブスクリプションを提供せず、「一回だけ」の支払いを取り扱う場合は、Cashierを使用してはいけません。StripeかBraintreeのSDKを直接使用してください。
最初に、Stripe向けのChashierパッケージを依存パッケージへ追加します。
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->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;
}
最後に、Stripeキーをservices.php
設定ファイルへ設定します。Stripe APIキーはStripeのコントロールパネルから取得します。
'stripe' => [
'model' => App\User::class,
'key' => env('STRIPE_KEY'),
'secret' => env('STRIPE_SECRET'),
],
多くの操作において、StripeとBraintreeで実装しているCashierの機能は同じものです。両方のサービスはクレジットカードでの支払いを提供していますが、BraintreeはPayPalでの支払いもサポートしています。しかしながら、Braintreeには、Stripeが提供してる機能のいくつかが欠けています。以下の点を考慮し、StripeとBraintreeのどちらを使うのか決めてください。
increment
(追加)とdecrement
(減少)メソッドをサポートしていません。これはCashierではなく、Braintreeの制限です。最初に、Braintree向けのCashierパッケージを追加します。
composer require "laravel/cashier-braintree":"~2.0"
次にconfig/app.php
設定ファイルへ、Laravel\Cashier\CashierServiceProvider
サービスプロバイダを登録します。
Laravel\Cashier\CashierServiceProvider::class
CashierをBraintreeで使用する前に、plan-credit
ディスカウントをBraintreeのコントロールパネルで定義する必要があります。このディスカウントは、年払いから月払い、もしくは月払いから年払いの変更時に代金を確実に按分するために使用されます。
ディスカウント値は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
トレイをモデルに追加してください。
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(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
メソッドは2つの文字列を引数に取ります。通貨と通貨記号です。
use Laravel\Cashier\Cashier;
Cashier::useCurrency('eur', '€');
サブスクリプションを作成するには最初にbillableなモデルのインスタンスを取得しますが、通常はApp\User
のインスタンスでしょう。モデルインスタンスが獲得できたら、モデルのサブスクリプションを作成するために、newSubscription
メソッドを使います。
$user = User::find(1);
$user->newSubscription('main', 'premium')->create($stripeToken);
newSubscription
メソッドの最初の引数は、サブスクリプションの名前です。アプリケーションでサブスクリプションを一つしか取り扱わない場合、main
かprimary
と名づけましょう。2つ目の引数には、ユーザーが購入したStripe/Braintreeのプランを指定します。この値はStripe/Braintreeのプランの識別子です。
create
メソッドはStripeクレジットカード/ソーストークンを引数にとり、サブスクリプションを開始すると同時に、カスタマーIDと関連する支払い情報をデータベースに保存します。
ユーザーに関する詳細情報を追加したい場合は、create
メソッドの第2引数に渡すことができます。
$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')) {
// このユーザーは支払っていない顧客
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
メソッドは、この期間中、まだ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} 購入数は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);
使用期間による支払いの按分を行わずに、サブスクリプション数を変更する場合は、noProrate
メソッドを使ってください。
$user->subscription('main')->noProrate()->updateQuantity(10);
サブスクリプション数の詳細については、Stripeドキュメントを読んでください。
ユーザーが支払うサブスクリプションに対する税率を指定するには、BillableモデルへtaxPercentage
メソッドを実装し、小数点以下が1桁以内で、0から100までの数値を返します。
public function taxPercentage() {
return 20;
}
taxPercentage
メソッドにより、モデルごとに税率を適用できるため、多くの州や国に渡るユーザーベースで税率を決める場合に便利です。
{note}
taxPercentage
メソッドは、サブスクリプションの課金時のみに適用されます。Cashierで「一回のみ」の支払いを行う場合は、税率を自分で適用する必要があります。
サブスクリプションをキャンセルするにはcancel
メソッドをユーザーのサブスクリプションに対して使ってください。
$user->subscription('main')->cancel();
サブスクリプションがキャンセルされるとCashierは自動的に、データベースのends_at
カラムをセットします。このカラムはいつからsubscribed
メソッドがfalse
を返し始めればよいのか、判定するために使用されています。例えば、顧客が3月1日にキャンセルしたが、そのサブスクリプションが3月5日に終了するようにスケジュールされていれば、subscribed
メソッドは3月5日になるまでtrue
を返し続けます。
ユーザーがサブスクリプションをキャンセルしたが、まだ「猶予期間」が残っているかどうかを調べるには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);
このメソッドはデータベースのサブスクリプションレコードへ、試用期間の終了日を設定すると同時に、Stripe/Braintreeへこの期日が過ぎるまで、顧客へ課金を始めないように指示します。
{note} 顧客のサブスクリプションが試用期間の最後の日までにキャンセルされないと、期限が切れると同時に課金されます。そのため、ユーザーに試用期間の終了日を通知しておくべきでしょう。
ユーザーが使用期間中であるかを判定するには、ユーザーインスタンスに対し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),
]);
{note} モデル定義の
trial_ends_at
に対する、日付ミューテタを付け加えるのを忘れないでください。
既存のサブスクリプションと関連付けが行われていないので、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($stripeToken);
StripeとBraintree、両方共にWebフックによりアプリケーションへ様々なイベントを通知できます。StripeのWebフックを処理するには、CashierのWebフックコントローラへのルートを定義する必要があります。このコントローラは通知されるWebフックリクエストをすべて処理し、正しいコントローラメソッドをディスパッチします。
Route::post(
'stripe/webhook',
'\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook'
);
{note} ルートを登録したら、Stripeコントロールパネル設定のWebフックURLも、合わせて設定してください。
このコントローラはデフォルトで、(Stripeの設定により決まる)課金の失敗が多すぎる場合、サブスクリプションを自動的にキャンセル処理します。しかしながら、処理したいWebフックイベントをどれでも処理できるようにするために、このコントローラを拡張する方法を以降で学びます。
StripeのWebフックでは、Laravelの CSRFバリデーションをバイパスする必要があるため、VerifyCsrfToken
ミドルウェアのURIを例外としてリストしておくか、ルート定義をweb
ミドルウェアグループのリストから外しておきましょう。
protected $except = [
'stripe/*',
];
Cashierは課金の失敗時にサブスクリプションを自動的に処理しますが、他にもStripeのWebフックイベントを処理したい場合は、Webフックコントローラをシンプルに拡張します。メソッド名はCashierの命名規則に沿う必要があります。メソッドはhandle
のプレフィックスで始まり、処理したいStripeのWebフックの名前を「キャメルケース」にします。たとえば、invoice.payment_succeeded
Webフックを処理する場合は、handleInvoicePaymentSucceeded
メソッドをコントローラに追加します。
<?php
namespace App\Http\Controllers;
use Laravel\Cashier\Http\Controllers\WebhookController as CashierController;
class WebhookController extends CashierController
{
/**
* StripeのWebフック処理
*
* @param array $payload
* @return Response
*/
public function handleInvoicePaymentSucceeded($payload)
{
// イベントの処理
}
}
顧客のクレジットカードが有効期限切れだったら? 心配いりません。Cashierは顧客のサブスクリプションを簡単にキャンセルできるWebフックを用意しています。前記と同じように、コントローラのルートを指定するだけです。
Route::post(
'stripe/webhook',
'\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook'
);
これだけです! 課金の失敗はコントローラにより捉えられ、処理されます。コントローラはStripeによりサブスクリプションが不能だと判断されると(通常は課金に3回失敗時)、その顧客のサブスクリプションをキャンセルします。
StripeとBraintree、両方共にWebフックによりアプリケーションへ様々なイベントを通知できます。BraintreeのWebフックを処理するには、CashierのWebフックコントローラへのルートを定義する必要があります。このコントローラは通知されるWebフックリクエストをすべて処理し、正しいコントローラメソッドをディスパッチします。
Route::post(
'braintree/webhook',
'\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook'
);
{note} ルートを登録したら、Braintreeコントロールパネル設定のWebフックURLも、合わせて設定してください。
このコントローラはデフォルトで、(Braintreeの設定により決まる)課金の失敗が多すぎる場合、サブスクリプションを自動的にキャンセル処理します。しかしながら、処理したいWebフックイベントをどれでも処理できるようにするために、このコントローラを拡張する方法を以降で学びます。
BraintreeのWebフックでは、Laravelの CSRFバリデーションをバイパスする必要があるため、VerifyCsrfToken
ミドルウェアのURIを例外としてリストしておくか、ルート定義をweb
ミドルウェアグループのリストから外しておきましょう。
protected $except = [
'braintree/*',
];
Braintreeは課金の失敗時にサブスクリプションを自動的に処理しますが、他にもStripeのWebフックイベントを処理したい場合は、Webフックコントローラをシンプルに拡張します。メソッド名はCashierの命名規則に沿う必要があります。メソッドはhandle
のプレフィックスで始まり、処理したいStripeのWebフックの名前を「キャメルケース」にします。たとえば、dispute_opened
Webフックを処理する場合は、handleDisputeOpened
メソッドをコントローラに追加します。
<?php
namespace App\Http\Controllers;
use Braintree\WebhookNotification;
use Laravel\Cashier\Http\Controllers\WebhookController as CashierController;
class WebhookController extends CashierController
{
/**
* Braintree Webフックの処理
*
* @param WebhookNotification $webhook
* @return Response
*/
public function handleDisputeOpened(WebhookNotification $notification)
{
// イベントの処理
}
}
顧客のクレジットカードが有効期限切れだったら? 心配いりません。Cashierは顧客のサブスクリプションを簡単にキャンセルできるWebフックコントローラを含んでいます。コントローラのルートを指定するだけです。
Route::post(
'braintree/webhook',
'\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook'
);
これだけです。支払いが失敗すると、コントローラにより捉えられ、処理されます。Braintreeが購入に失敗したと判断すると(通常は支払いに3回失敗した場合)、その顧客のサブスクリプションはキャンセルされます。BraintreeのコントロールパネルでWebフックのURIを設定する必要があることを忘れないで下さい。
{note} Stripeを使用している場合、
charge
メソッドにはアプリケーションで使用している通貨の最低単位で金額を指定しますが、Braintreeのcharge
メソッドには、ドル単位の金額を指定します。
すでに何かを購入している顧客のクレジットカードに、「一回だけ」の課金をしたい場合は、billableモデルのインスタンスに対し、charge
メソッドを使います。
// Stripeはセント単位で課金する
$user->charge(100);
// Braintreeはドル単位で課金する
$user->charge(1);
charge
メソッドは第2引数に配列を受け付け、裏で作成されるStripe/Braintreeの課金作成に対するオプションを指定できます。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,
]);
{note}
invoiceFor
メソッドは、課金失敗時にリトライするStripeインボイスを生成します。リトライをしてほしくない場合は、最初に課金に失敗した時点で、Stripe APIを使用し、生成したインボイスを閉じる必要があります。
invoices
メソッドにより、billableモデルのインボイスの配列を簡単に取得できます。
$invoices = $user->invoices();
// 結果にペンディング中のインボイスも含める
$invoices = $user->invoicesIncludingPending();
顧客へインボイスを一覧表示するとき、そのインボイスに関連する情報を表示するために、インボイスのヘルパメソッドを表示に利用できます。ユーザーが簡単にダウンロードできるように、テーブルで全インボイスを一覧表示する例を見てください。
<table>
@foreach ($invoices as $invoice)
<tr>
<td></td>
<td></td>
<td><a href="/user/invoice/">Download</a></td>
</tr>
@endforeach
</table>
ルートやコントローラの中でdownloadInvoice
メソッドを使うと、そのインボイスのPDFダウンロードを生成できます。このメソッドはブラウザへダウンロードのHTTPレスポンスを正しく行う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',
]);
});