【Laravel5.8+Stripe11】Webhookの実装

f:id:nekorokkekun:20190827102831p:plain:w1000
こちらの連載記事では、LaravelとStripeを使用して企業サイト兼Eコマース(ECサイト)を作成していきます。

Laravelのプロジェクト作成からStripeの実装まで行い、最終的に単発決済・サブスクリプションの実装までを目指します。

シリーズ

【Laravel5.8+Stripe⓪】ECサイト作成チュートリアル概要 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe①】ベースプロジェクトの作成 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe②】メールフォームの実装 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe③】ユーザー認証機能のカスタマイズ - Laravelとねころっけくん5.8
【Laravel5.8+Stripe④】ディスカウントページを作成する - Laravelとねころっけくん5.8
【Laravel5.8+Stripe⑤】Laravel CasherとStripeを導入して管理者権限を設定する その1 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe⑥】Laravel CasherとStripeを導入して管理者権限を設定する その2 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe⑦】サブスクリプション決済の作成 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe⑧】請求書ダウンロード機能の実装 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe⑨】サブスクリプションプラン変更機能の実装 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe⑩】サブスクリプション中止機能の実装 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe11】Webhookの実装 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe12】クーポン機能を実装する - Laravelとねころっけくん5.8
【Laravel5.8+Stripe13】ショッピングカートの実装 - Laravelとねころっけくん5.8


ちなみにこちらの記事は、Easy E-Commerce Using Laravel and Stripeという書籍をもとに執筆しています。

今回はStripeのダッシュボード上からWebhookの設定を行なっていきます。

Webhookの概要

Webhookは、Stripe内でイベントが発生したときにサイトに通知する方法です。

例えば、新しい顧客がサブスクリプションに登録したとき、または既存の顧客のクレジットカードの有効期限が切れたときなど、あらゆるシチュエーションに合わせてWebhookを設定することが可能です。

Stripeのダッシュボードサイドメニュー>開発者>Webhook

からWebhookの設定を行うことができます。
f:id:nekorokkekun:20190825132419p:plain

Webhookの設定

まずはStripeのダッシュボード上からWebhookを実際に設定していきましょう。

先ほどの手順でWebhook設定ページに行くと、【+エンドポイントを追加】というボタンがあるため、そちらをクリックします。
f:id:nekorokkekun:20190825133112p:plain

モーダルウィンドウが表示されるため、
f:id:nekorokkekun:20190825133137p:plain

エンドポイント URLには、「使用されているドメイン/stripe/webhook」と入力します。

また今回はWebhookのテスト実装のため、イベントについては「すべてのイベントを受信」のリンクをクリックして起きましょう。

上記2点の設定が済んだら、「エンドポイントを追加」というボタンを押して、Webhookを作成します。

これでStripeに関わる何かしらのアクションをユーザーが取った際、自動的に「/stripe/webhook」というルートが投げられることになります。

Laravel側の設定

次にWebhookを発火させるための実装をLaravel側に行なっていきます。

まずはルーティングを行いましょう。これは先ほどStripeのダッシュボード上で設定したエンドポイントURLです。
/routes/web.php

Route::post('stripe/webhook','StripeController@handleWebhook');


次に、Webhook専用のControllerを作成しましょう。

$ php artisan make:controller StripeController

続けてControllerの中身を書き込んでいきましょう。
/app/Http/Controllers/StripeController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class StripeController extends \Laravel\Cashier\Http\Controllers\WebhookController
{
    public function handleInvoicePaymentFailed($payload)
    {
        $billable = $this->getBillable(
          $payload['data']['object']['customer']
        );
    
        if ($billable) {
            \Mail::send('emails.failed_charge', 
              compact('billable'), function($message)
        	{
        		$message->to(env('MAIL_FROM'), env('MAIL_NAME'));
            	$message->subject('WeDewLawns.com Payment Failed');
        	});
        }
    
        return new Response('Webhook Handled', 200);
    }
}

1つずつ解説をしていきます。

まずStripeControllerがextendsしているWebhookControllerですが、その本体は/vendor/laravel/cashier/src/Http/Controllers/WebhookController.phpです。

WebhookControllerを継承することで、Cashierのイベントシステムにフルアクセスできるようになり、独自のwebhookリスナーメソッドを作成できるようになります。

また新規作成したhandleInvoicePaymentFailedですが、こちらには命名規則があるのでご注意ください。

たとえば、invoice.payment_failedというWebhookのイベントをリッスンする場合、handleInvoicePaymentFailedという名前のメソッドを作成します。ハンドルを前に付け、ピリオドを削除し、文字を大文字にします。

CSRF対策とStripe Webhookの兼ね合い

セキュリティ上の理由から、LaravelではグローバルCSRFプロテクションが有効になっています。

基本的にはCSRFプロテクションがあることでセキュリティが担保されているのですが、WebhookはStripeサーバーから発信されるため、CSRFプロテクションに弾かれてしまいます。

StripeからのリクエストにCSRFトークンが付随しないことで生じる問題です。

そのため、以下のファイルにコードを記述する必要が出てきます。
/app/Http/Middleware/VerifyCsrfToken.php

    protected $except = [
        'stripe/webhook/*'
    ];

protected $exceptの配列に、CSRFプロテクション無しで通過させたいパスを書き込むことによって、限定的にCSRFプロテクションを免れることができます。

userの登録、ログイン、サブスクリプション登録、変更、キャンセル何かしらのアクションを行なってみた後でStripeのダッシュボード>開発者>Webhookを確認してみましょう。
f:id:nekorokkekun:20190825213426p:plain

上記の画像のようにWebhookが成功していたら、実装完了です。

migrationの修正

Laravel Cashierのダウングレードを行なった際、こちらの記事でご紹介したような機能が失われてしまったようです。

create_customer_columnsというusersテーブルにカラムを追加するための機能が自動的に備わっていることがわかります。

そのため、usersテーブルへのカラム追加と、新たにsubscriptionsテーブル、Subscriptionモデルを作成する必要が出てきました。

まずはusersテーブルへのカラム追加から。
/database/migrations/2014_10_12_000000_create_users_table.php

    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->string('email')->unique();
            $table->timestamp('email_verified_at')->nullable();
            $table->string('password');
            $table->string('stripe_subscription')->nullable();
            $table->string('stripe_plan')->nullable();
            $table->string('stripe_id')->nullable(); //追加
            $table->string('card_brand')->nullable(); //追加
            $table->string('card_last_four')->nullable(); //追加
            $table->rememberToken();
            $table->timestamps();
        });
    }

次にsubscriptionsテーブル、Subscriptionモデルを作成します。

$ php artisan make:model Subscription -m

/database/migrations/2019_08_25_105835_create_subscriptions_table.php

    public function up()
    {
        Schema::create('subscriptions', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('name');
            $table->string('stripe_id');
            $table->string('stripe_plan');
            $table->string('quantity');
            $table->timestamp('trial_ends_at');
            $table->timestamp('ends_at');
            $table->unsignedBigInteger('user_id');
            $table->foreign('user_id')->references('id')->on('users');
            $table->timestamps();
        });
    }

ここまでできたら、改めてmigrateを行います。

$ php artisan migrate:fresh

Laravel Cashierはグレードごとに扱いが異なるため、注意が必要です。

シリーズ

【Laravel5.8+Stripe⓪】ECサイト作成チュートリアル概要 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe①】ベースプロジェクトの作成 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe②】メールフォームの実装 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe③】ユーザー認証機能のカスタマイズ - Laravelとねころっけくん5.8
【Laravel5.8+Stripe④】ディスカウントページを作成する - Laravelとねころっけくん5.8
【Laravel5.8+Stripe⑤】Laravel CasherとStripeを導入して管理者権限を設定する その1 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe⑥】Laravel CasherとStripeを導入して管理者権限を設定する その2 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe⑦】サブスクリプション決済の作成 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe⑧】請求書ダウンロード機能の実装 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe⑨】サブスクリプションプラン変更機能の実装 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe⑩】サブスクリプション中止機能の実装 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe11】Webhookの実装 - Laravelとねころっけくん5.8
【Laravel5.8+Stripe12】クーポン機能を実装する - Laravelとねころっけくん5.8
【Laravel5.8+Stripe13】ショッピングカートの実装 - Laravelとねころっけくん5.8