目次
サブスクリプション(定期購読)の実装をサポートするライブラリであるLaravel Cashierを触ってみます!
内部的にはStripeを利用して決済処理を行っている様です。
Laravel Cashierをインストールする
公式ドキュメントに従って、必要な物をインストールする。
DBにテーブルを追加する必要があるので、php migrate
でマイグレーションを流す。
以下の様な3つのマイグレーションが流れる。
Schema::table('users', function (Blueprint $table) { $table->string('stripe_id')->nullable()->index(); $table->string('card_brand')->nullable(); $table->string('card_last_four', 4)->nullable(); $table->timestamp('trial_ends_at')->nullable(); });
Schema::create('subscriptions', function (Blueprint $table) { $table->bigIncrements('id'); $table->unsignedBigInteger('user_id'); $table->string('name'); $table->string('stripe_id'); $table->string('stripe_status'); $table->string('stripe_plan')->nullable(); $table->integer('quantity')->nullable(); $table->timestamp('trial_ends_at')->nullable(); $table->timestamp('ends_at')->nullable(); $table->timestamps(); $table->index(['user_id', 'stripe_status']); });
Schema::create('subscription_items', function (Blueprint $table) { $table->bigIncrements('id'); $table->unsignedBigInteger('subscription_id'); $table->string('stripe_id')->index(); $table->string('stripe_plan'); $table->integer('quantity'); $table->timestamps(); $table->unique(['subscription_id', 'stripe_plan']); });
MySQL WorkbenchでER図を生成するとこんな感じ。subscriptions
テーブルには定期購読の情報が保存される。subscription_items
テーブルには定期購読のプランの情報が保存される。
stripeからAPIキーを取得する
公開キーと秘密キーを取得して.env
に追加する。
.env
で通貨が設定できるらしい。en
以外のロケールを使用する場合は、ext-intl
拡張をインストールする必要があるらしい。
CASHIER_CURRENCY=ja_JP
stripeの顧客データ(Customer)を生成する
Billable
トレイトのcreateOrGetStripeCustomer()
メソッドで顧客データを生成できる
$stripeCustomer = $user->createOrGetStripeCustomer();
テストアプリではユーザ詳細画面を開いたら顧客データが生成される様にした。
実際に顧客データを生成してテーブルを確認してみる。user
テーブルのstripe_id
カラムにIDが記録されているのが分かる。
Stripe上にも顧客データが登録されている。
支払い方法を保存する
支払い方法(カード情報)を登録するフォームを作成する。
この辺は前にも調べたなぁ
テストカード番号が用意されているのでこれを使う
$user->updateDefaultPaymentMethod($paymentMethod);
動かしてみるとconsoleにエラーが発生していた。
APIキーが誤っているらしい。
POST https://api.stripe.com/v1/setup_intents/seti_xxxx/confirm 401
{
"error": {
"message": "Invalid API Key provided: aaa",
"type": "invalid_request_error"
}
}
フロントでStripeを初期化する時にパブリックキーを渡す必要があった。
const stripe = Stripe('pk_test_xxxxxxxxxxx');
改めて保存してみる。user
テーブルにカード情報が追加されていた。
といっても表示用の一部の情報しかないので、実際に決済する場合はstripeサーバからpayment_method
のIDを取得しないといけない。
定期購読(Subscription)する
購入フォームを作った。
ボタンが1つあるだけ。
バックエンドの処理はこんな感じ。paymentMethods()
メソッドで保存済みの支払い方法を取得して、newSubscription
メソッドで定期購読(Subscription)を作成している
$paymentMethods = $user->paymentMethods();
$paymentMethod = $paymentMethods[0];
$user->newSubscription('subscription-A', 'plan-A')->create($paymentMethod);
動かしてみるとエラーが発生した。
urlencode() expects parameter 1 to be string, object given
create
メソッドに、$paymentMethod
ではなく$paymentMethod->id
を渡す必要があった。
$user->newSubscription('subscription-A', 'plan-A')->create($paymentMethod->id);
更にエラーが発生した。plan-A
というプランは存在しないとの事。
No such plan: plan-A
先にstripeのダッシュボードでプランを作成しておく必要がある模様。
月額500円のプランを作った。
newSubscription
メソッドの第二引数を修正した。
$user->newSubscription('subscription-A', 'price_xxxx')->create($paymentMethod->id);
購入処理が完了したのでテーブルを確認してみる。description
テーブルに以下の様なレコードが追加されていた。
description_item
テーブルにも同様にレコードが作成されている。
stripe側にも支払いデータが登録されている。
顧客ページを確認してみると定期支払い情報が追加されていた。
次回のインボイスを見ると来月同日の請求になっている。
特に何も指定しないと定期購入は即座に課金されて、来月の同日に次の支払いが行われる模様。
図にするとこんな感じ。
プランの変更
月額1000円のプランを追加して、プランの切り替えを試してみる。
画面はこんな感じ。
バックエンドの処理はこれだけ。swap
メソッドに変更先のプランのIDを渡す。
$user->subscription('subscription-A')->swap($planId);
プラン変更後、Stripeのダッシュボードを確認すると、保留中のインボイスが増えた。
月額498円の免除と月額997円の新しい請求が保留されている。
デフォルトだと即時請求ではないらしい。(swapAndInvoice
メソッドを使えば即時請求になるとの事。)
保留されている請求は、次の「請求サイクル」で処理される。
Prorations(比例配分)という仕組みにより月額料金が日割り計算されている。
subscriptions
テーブルとsubscription_items
テーブルのstripe_plan
列が変わっている
(この画像だとモザイクで分からないが・・・)
請求サイクルについて
サブスクリプションを作成した日がアンカー日、つまり請求日となる。
トライアル期間(試用期間)を含む場合は、トライアル期間の最終日がアンカー日となる
定期購読のキャンセル
定期購読を解約してみる。
cancel
メソッドを使用して解約する。
$user->subscription('subscription-A')->cancel();
キャンセルするとdescription
テーブルのends_at
カラムに日時が登録された。
stripe側では、次の請求日に解約される状態になった。
解約してから次の請求日までの期間をGrace Period(猶予期間)と呼ぶらしい。
reduce
メソッドを試用すれば猶予期間中の契約を復元できる
直ちに定期購入を終了する
cancelNow
メソッドを使えば、猶予期間無しで解約できる
実際に試してDBを確認すると、subscriptions
テーブルのstripe_status
カラムがactive
からcanceled
に変わった。
stripeでもキャンセルされている。
数量指定をしてみる。
画面は定期購入画面を流用。
定期購読契約時にquantity
メソッドで数量を指定できる。
利用人数×月額n円の様な課金形態の場合に使用する
$user->newSubscription('subscription-A', $planId)->quantity($quantity)->create($paymentMethod->id);
月額1000円のプランを4個契約するとStripe上では以下の様になった。
subscription
テーブルとsubscription_items
テーブルには、新しいレコードが追加され、quantity
カラムに数量が記録されていた。
トライアル(試用期間)を設定してみる
こちらも定期購入画面を流用。
5日間の試用期間を追加してみた。
今日が5/27で、トライアル終了日(最初の請求日)が6/1になった。
今日を含めて5日間のトライアル期間になるみたい。
subscription
テーブルを確認すると、レコードが追加され、stripe_status
カラムにはtrialing
が、trial_ends_at
カラムにはトライアル終了日時が登録された。
トライアル(試用期間)を付与すると請求サイクルが後ろにずれる。
クーポンを使ってみる
まずはStripeのダッシュボードでクーポンを作成する。
半額になるクーポンを作成した。
IDが発行されるので控える。
画面にクーポンID入力欄を追加する
月額500円のプランを半額クーポンを使用して契約してみる。
ちゃんと半額の250円分の支払いが発生した。
内訳を見るとクーポンが適応されているのがわかる。
DBには特に変わった箇所は無く、クーポンに関する情報はStripe側でのみ管理されている模様。
税率を設定してみる
Stripeのダッシュボードから税率を作成する。
消費税という名目で8%で作成した。
あ、今はもう10%か・・・。
Billingトレイトを継承しているモデルにtaxRates
メソッドを追加して適応したい税率IDを返す。
public function taxRates() { return ['txr_xxxx']; }
実際に月額1000円のプランを定期購読してStripeで確認してみると1080円になっていた。
内訳にも消費税が記載されている。
複数プランの同時購読
月額500円のプランと月額1000円のプランを同時に購読してみる。
処理はこんな感じ。
$user->newSubscription('subscription-A', [ 'price_aaaa', 'price_bbbb' ])->create($paymentMethod->id);
Stripe上で確認すると複数のプランを同時に購読できている事がわかる。
subscriptions
テーブルは、これまで通り、1つのレコードが追加されている。
subscription_items
テーブルにはレコードが2つ追加された。
プラン毎にレコードが追加される仕様らしい。
所感
Stripeすげぇ
Webエンジニアをやっています
UX/UIデザインからプログラミング、DB設計、SEO、インフラ構築など幅広く対応してます
PHP/PHPUnit/Laravel/Vue/Nuxt/Docker/Terraform
ご連絡はTwitterのDMまで。