以前、Laravel Cashierを使用してサブスクリプションを実装する方法について説明しました。しかし、Laravel Cashierは単純なStripeのラッパークラスではありません。そのため、例えばクレジットカードの決済に失敗したとき、どのようにハンドリングされるのか、しっかりと把握しておく必要があります。
そこで、今回はLaravel Cashierが実際にどのようなテーブルを持ち、またStripeでサブスクリプションの自動更新が失敗した場合の仕組みについて説明したいと思います。
Laravel CashierはStripeのラッパーではありますが、自分でテーブルも持っています。Laravel Cashierのインストール後に、 php artisan migrate
を実行すると、vendor/larave/cashier/database/migrations
配下にあるマイグレーションが実行されます。
Subscriptionの作成時はStripeのAPIをコールしますが、課金状況のチェックなどは、基本的にテーブルの内容を見にいきます。
そのため、Laravel Cashierで使用されるテーブルが、どのような動きをするのか、把握しておくことが大切です。
ユーザテーブルには、次のように追加されます。
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(); });
これらのカラムは、ユーザがSubscriptionに課金し、申し込んだときに作成されます。
cus_****
)が入ります。subscriptionsテーブルは、ユーザがSubscriptionに課金し、申し込まれる度に作成されます。
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']); });
User
クラスが、 Billable
トレイトを使用すると、 $user->subscriptions
で取得できます。)$user->subscribed('サブスクリプション名')
といった形で使用します。(この場合は、サブスクリプション中であるかどうかをbooleanで返します。)sub_***
の形式です。)$subscription->active()
など、課金状況を確認するときに使用されるカラムです。plan_***
もしくは price_***
の形式です。)cancelNow()
メソッドでキャンセルした場合は、現在日時が入ります。サブスクリプションに紐づく課金アイテムが入ります。
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']); });
注意点としては、複数のアイテムがある場合は、 subscriptions
テーブルの、 stripe_plan
カラムと quantity
プランはnullになります。
si_***
形式です。)plan_***
もしくは price_***
の形式です。)実際には、利用者のクレジットカードのエラーにより、うまく自動更新できないことがあります。その場合は、Webhookを使用して、Stripe側からコールしてもらいます。
Laravel Cashierインストールすると、次のルーティングが追加されます。
+--------+-----------+------------------------+------------------+------------------------------------------------------------------------+---------------------------------------------------+ | Domain | Method | URI | Name | Action | Middleware | +--------+-----------+------------------------+------------------+------------------------------------------------------------------------+---------------------------------------------------+ | | POST | stripe/webhook | cashier.webhook | Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook | | +--------+-----------+------------------------+------------------+------------------------------------------------------------------------+---------------------------------------------------+
このパスで、以下のWebhookを受け取ることができます。
customer.subscription.updated
customer.subscription.deleted
customer.updated
customer.deleted
invoice.payment_action_required
Stripeの管理画面で、これらのイベントを送信されるように設定します。
[Stripe管理画面]>[開発者]>[Webhook]>[アカウントからイベントを受信するイベント]>[+エンドポイントを追加] を開き、実際のアプリケーションのURLに、イベントが送信されるように設定します。
また、このWebhookはwebのルーティングに追加されるため、CSRF保護の対象外に設定する必要があります。 VerifyCsrfToken
クラスで、対象外に設定します。
class VerifyCsrfToken extends Middleware { /** * The URIs that should be excluded from CSRF verification. * * @var array */ protected $except = [ 'stripe/*', ]; }
これで完了です。Stripeから決済失敗のWebhookが3回連続で届くと、先ほどの subscriptions
テーブルが更新され、課金が無効になります。
いかがでしたか。課金情報の問い合わせのために、何度もStripeにAPIリクエストすることなく、Laravel Cashierは動作しますが、Webhookをしっかり実装しないと、自動更新の失敗を検知することができません。
Laravel Cashierを使用するときは、必ずWebhookもセットで設定しましょう。