Laravelで課金する方法(Laravel Cashier + Stripe)【ダウンロード可】

こんにちは。フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、前回の意外と簡単!Laravel で全文検索をつくるでは、Laravelが提供する公式パッケージ「Laravel Scout」で全文検索する機能をつくってみました。

しかし、Laravelには公式パッケージが他にも公開されていて、私達が開発する上で「うーん、ホントはほしいけど、自前でつくるのはちょっと怖いかも・・・」と考える機能も提供してくれています。

それは何かと言うと・・・・・・

課金システム

です。

つまり、毎月利用料を払うシステムで、身近な例で言うと「アマゾン・プライム」だとか、有料メルマガを思い浮かべるとわかりやすいかもしれません。

ということで、今回は「Laravel Cashier + Strip」で月額課金システムをつくってみます。

ぜひ皆さんのお役に立てましたら嬉しいです😊✨
(最後に、実際に開発したソースコード一式をダウンロードできますよ👍)

【追記:2020.08.25】Laravel Cashierが更新されていたので、Laravel 7.x + Laravel Cashier v12.2.0 で加筆修正しました。

「ここ数年、開発は phpstorm を
サブスクして使ってます👍」

開発環境: Laravel 7.x/Laravel Cashier v12.2.0/Vue 2.6

やりたいこと

まずはゴールとして以下の6つを実装することにします。

  • 月額課金ができる
  • 課金のプランは「プレミアム(1,000円/月)」と「ベーシック(500円/月)」の2種類
  • 課金のキャンセルができる
  • キャンセルしたものを元に戻すことができる
  • プランの変更ができる
  • クレジットカードの変更ができる

前提として

Laravelの「php artisan make:auth」でログイン機能が有効になっていて、テストユーザーが登録されているものとします。

※詳しくは、【Laravel5.6】インストール直後にやること3点をご覧ください。

また、決済サービスのStripeにアカウントを作成しておいてください。(👈 私はすでに昔作ってしまったのでスクリーンショットはありません。ごめんなさい😅 Stripeは日本語対応してますのでそれほど難しくないです)

※ ちなみに、ローカル環境でテストする場合でもstripeが提供するjsライブラリにはhttpsで接続している必要があります。もしローカル環境にhttpsを導入する場合は、コピペでOK!ローカル環境にHTTPSを導入する(nginx編)を参考にしてみてください。

Stripeの設定をする

それでは、ここからStripeにログインして設定をしていきます。
今回は説明なので、テスト・バージョンの設定になりますが、本番環境の場合はそれぞれ置き換えて実行してください。

APIキーを取得する

画面左側にある「開発者」というリンクをクリックします。

さらに「APIキー」をクリックします。

すると、「標準キー」の項目に「公開可能キー」「シークレットキー」が表示されます。

これがいわゆる「APIキー」です。
この2つを以下のように.envに登録しておきましょう。

STRIPE_KEY=************************
STRIPE_SECRET=***************************

ウェブフックのキーを取得する

続いてウェブフックのキーを取得します。
ウェブフックは課金やキャンセルが実行された直後にPOSTメソッドでLaravel側にアクセスしてくるもので、課金の失敗や各種データの変更を管理してくれることになります。

では、メニューから「Webhook」をクリックします。

画面右上にある「+ エンドポイントを追加」をクリック。

すると、入力フォームがポップアップされるので、

  • エンドポイントURL
  • 送信イベント(すべてのイベントを受信をクリック。お好みで選んでもOK)

を入力して「エンドポイントを追加」ボタンをクリックしてください。

クリックすると登録した情報が表示されますので、その中から署名シークレットにある「署名シークレット」をコピーしてください。

そして、その署名シークレットは.envに以下のように登録しておきましょう。

STRIPE_WEBHOOK_SECRET=****************************

【ご注意⚠】

なお、ローカル環境ではstripeからアクセスが届きませんので、うまくいきません。その場合、stripeStripe Cliというテストツールを用意してくれていますので、こちらをインストールして使ってください。

イメージとしては、次のとおりです。

stripeのウェブフック(Stripe Cliがウェブフックを仲介)ローカル環境

つまり、Stripe Cliは、stripe とローカル環境の「橋渡し」をしてくれる存在になります。

インストール方法は、ちなみに – 3:Stripe Clieをインストールする方法をご覧ください。

プランを追加する

課金のプラン「プレミアム」と「ベーシック」を追加します。
メニューの「商品」をクリックしてください。

そして、「+ 商品を追加」というボタンをクリック。

すると入力フォームが現れるので、まず商品名を入力します。

そして、その下には料金プランを入力する部分があるのでこれもお好みで入力してください。

入力が終わったらページ右上にある「商品を保存」ボタンをクリックすれば完了です。

続いて「ベーシック」プランも同様に追加したら、一覧に表示されている各商品名をクリックして詳細ページを表示させます。

するとそのページには以下のようにAPI IDが表示されていますので、「ベーシック」と「プレミアム」それぞれのIDを取得し、.envconfig/services.phpに登録しておいてください。

.env

STRIPE_BASIC_ID=*****************
STRIPE_PREMIUM_ID=*****************

config/services.php

'stripe' => [
    'model' => App\User::class,
    'key' => env('STRIPE_KEY'),
    'secret' => env('STRIPE_SECRET'),
    'webhook' => [
        'secret' => env('STRIPE_WEBHOOK_SECRET'),
        'tolerance' => env('STRIPE_WEBHOOK_TOLERANCE', 300),
    ],
    'plans' => [
        env('STRIPE_BASIC_ID') => 'ベーシック',
        env('STRIPE_PREMIUM_ID') => 'プレミアム'
    ]
],

インストール&準備をする

パッケージのインストール

では、続いてLaravelで課金機能を開発していきましょう。
まずは専用パッケージLaravel Cashierをインストールします。

composer require laravel/cashier

パッケージがインストールされたら、app/User.phpで課金機能が使えるようにBillableトレイトを追加します。

<?php

namespace App;

// 省略

use Laravel\Cashier\Billable;

class User extends Authenticatable
{
    use Notifiable, Billable;

    // 省略

これで、課金するためのメソッドが利用できるようになりました。

DBテーブルの準備

パッケージのインストールが完了したら、DBのテーブル環境を整えます。
以下のコマンドで実行してください。

php artisan migrate

※なお、旧バージョンではマイグレーションを自分でつくっていましたが現在はパッケージ内の「vendor/laravel/cashier/database/migrations」に含まれています。もし旧バージョンのテーブル作成方法をご覧になりたい方は、ちなみに – 2:旧バージョンのマイグレーションを参考にしてみてください。

コマンドを実行したらテーブルは以下のようになります。

課金ページをつくる

では、ここからは課金を実行するページを作っていきます。
構成としてはページは1つだけで、状態によってVueで表示を切り替えることになります。

また、データの保存や課金の実行はすべてAjax通信で行います。

ルートをつくる

では、routes/web.phpに課金ページのためのルートを追加してください。

Route::prefix('user')->middleware(['auth'])->group(function() {

    // 課金
    Route::get('subscription', 'User\SubscriptionController@index');
    Route::get('ajax/subscription/status', 'User\Ajax\SubscriptionController@status');
    Route::post('ajax/subscription/subscribe', 'User\Ajax\SubscriptionController@subscribe');
    Route::post('ajax/subscription/cancel', 'User\Ajax\SubscriptionController@cancel');
    Route::post('ajax/subscription/resume', 'User\Ajax\SubscriptionController@resume');
    Route::post('ajax/subscription/change_plan', 'User\Ajax\SubscriptionController@change_plan');
    Route::post('ajax/subscription/update_card', 'User\Ajax\SubscriptionController@update_card');

});

※ ログインしている人だけに表示するようauthミドルウェアを有効にしています。

なお、ウェブフックはこのままでは「CSRFブロック」が働いてアクセス拒否されてしまいますので、app/Http/Middleware/VerifyCsrfToken.phpを開いて解除しておいてください。

<?php

namespace App\Http\Middleware;

use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;

class VerifyCsrfToken extends Middleware
{
    // 省略

    protected $except = [
        'stripe/*', // 👈 追加
    ];
}

コントローラーをつくる

続いて、コントローラーです。

まず、ブラウザで表示するページのコントローラーを作ります。
以下のコマンドを実行してください。

php artisan make:controller User\\SubscriptionController

すると、app/Http/Controllers/User/SubscriptionController.phpが作成されるので、以下のように変更します。

<?php

namespace App\Http\Controllers\User;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class SubscriptionController extends Controller
{
    public function index(Request $request) {

        $user = $request->user();
        return view('user.subscription.index')->with([
            'intent' => $user->createSetupIntent()
        ]);

    }
}

【追記:2020.11.07】訪問ユーザーさんからご質問があった結果コードを修正しました。ありがとうございました!

続いて以下のコマンドでAjax用のコントローラーを作成してください。

php artisan make:controller User\\Ajax\\SubscriptionController

app/Http/Controllers/User/Ajax/SubscriptionController.phpの中身を以下のように変更します。

<?php

namespace App\Http\Controllers\User\Ajax;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class SubscriptionController extends Controller
{
    // 課金を実行
    public function subscribe(Request $request) {

        $user = $request->user();

        if(!$user->subscribed('main')) {

            $payment_method = $request->payment_method;
            $plan = $request->plan;
            $user->newSubscription('main', $plan)->create($payment_method);
            $user->load('subscriptions');

        }

        return $this->status();

    }

    // 課金をキャンセル
    public function cancel(Request $request) {

        $request->user()
            ->subscription('main')
            ->cancel();
        return $this->status();

    }

    // キャンセルしたものをもとに戻す
    public function resume(Request $request) {

        $request->user()
            ->subscription('main')
            ->resume();
        return $this->status();

    }

    // プランを変更する
    public function change_plan(Request $request) {

        $plan = $request->plan;
        $request->user()
            ->subscription('main')
            ->swap($plan);
        return $this->status();

    }

    // カードを変更する
    public function update_card(Request $request) {

        $payment_method = $request->payment_method;
        $request->user()
            ->updateDefaultPaymentMethod($payment_method);
        return $this->status();

    }

    // 課金状態を返す
    public function status() {

        $status = 'unsubscribed';
        $user = auth()->user();
        $details = [];

        if($user->subscribed('main')) { // 課金履歴あり

            if($user->subscription('main')->cancelled()) {  // キャンセル済み

                $status = 'cancelled';

            } else {    // 課金中

                $status = 'subscribed';

            }

            $subscription = $user->subscriptions->first(function($value){

                return ($value->name === 'main');

            })->only('ends_at', 'stripe_plan');

            $details = [
                'end_date' => ($subscription['ends_at']) ? $subscription['ends_at']->format('Y-m-d') : null,
                'plan' => \Arr::get(config('services.stripe.plans'), $subscription['stripe_plan']),
                'card_last_four' => $user->card_last_four
            ];

        }

        return [
            'status' => $status,
            'details' => $details
        ];

    }
}

やっていることは、上から

  • subscribe() ・・・ 新しい課金を登録
  • cancel() ・・・ すでに課金されているものをキャンセル
  • resume() ・・・ キャンセルされた課金を元に戻す
  • change_plan() ・・・ プラン変更
  • update_card() ・・・ クレジットカード変更

となります。

ここで重要なのが、subscribe()内の以下の部分です。

$user->load('subscriptions');

この行がないと、課金状態の更新がされないため常にunsubscribedステータスが返ることになってしまいます。(←ちょっとハマりました😅)

ちなみに、コードの中で「main」という課金名が出てきますが、今回課金システムはひとつだけを想定しているのでこの名前にしています。(もしくはprimarydefaultなどでもいいでしょう)

ビューをつくる

では、最後にビューです。
resources/views/user/subscription/index.blade.phpを作成して中身を以下のようにしてください。

<html>
<head>
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
    <style>

        body {

            padding: 15px;

        }

        #new-card, #update-card {

            border: 1px solid #ccc;
            padding: 8px;

        }

    </style>
</head>
<body>
<div id="app" class="container">
    <h1 class="mb-4">Stripeを使った月額課金・サンプル</h1>
    <div class="row">
        <div class="offset-3 col-6">
            <div class="card mb-4">
                <div class="card-body bg-light">
                    <div v-if="!isSubscribed">
                        <div class="form-group">
                            <select class="form-control" v-model="plan">
                                <option v-for="(value,key) in planOptions" :value="key" v-text="value"></option>
                            </select>
                        </div>
                        <div class="form-group">
                            <input type="text" class="form-control" v-model="cardHolderName" placeholder="名義人(半角ローマ字)">
                        </div>
                        <div class="form-group">
                            <div id="new-card" class="bg-white"></div>
                        </div>
                        <div class="form-group text-right">
                            <button
                                type="button"
                                class="btn btn-primary"
                                data-secret="{{ $intent->client_secret }}"
                                @click="subscribe">
                                課金する
                            </button>
                        </div>
                    </div>
                    <div v-else-if="isSubscribed">
                        <div v-if="isCancelled">
                            キャンセル済みです。(終了:<span v-text="details.end_date"></span>)
                            <button class="btn btn-info" type="button" @click="resume">元に戻す</button>
                        </div>
                        <!-- 課金中 -->
                        <div v-else>
                            <div class="mb-3">現在、課金中です。</div>
                            <button class="btn btn-warning" type="button" @click="cancel">キャンセル</button>
                            <hr>
                            <div class="form-group">
                                課金中のプラン: <span v-text="details.plan"></span>
                            </div>
                            <div class="form-group">
                                <select class="form-control" v-model="plan">
                                    <option v-for="(value,key) in planOptions" :value="key" v-text="value"></option>
                                </select><br>
                                <button class="btn btn-success" type="button" @click="changePlan">プランを変更する</button>
                            </div>
                            <hr>
                            <div class="form-group">
                                カード情報(下4桁): <span v-text="details.card_last_four"></span>
                            </div>
                            <div class="form-group">
                                <input type="text" class="form-control" v-model="cardHolderName" placeholder="名義人(半角ローマ字)">
                            </div>
                            <div class="form-group">
                                <div id="update-card" class="bg-white"></div><br>
                                <button
                                    type="button"
                                    class="btn btn-secondary"
                                    data-secret="{{ $intent->client_secret }}"
                                    @click="updateCard">
                                    クレジットカードを変更する
                                </button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="card">
                <div class="card-body">
                    <h5>テスト入力について</h5>
                    <hr>
                    <strong>名義人:</strong> 半角ローマ字ならなんでもOK<br>
                    <strong>カード番号:</strong> <a href="https://stripe.com/docs/testing#cards" target="_blank">テスト用のカード番号</a>に用意されています。なお、年/月は未来の日付ならいつでもOKで、CVCも数字ならなんでもOKです。
                </div>
            </div>
        </div>
    </div>
</div>
<script src="https://js.stripe.com/v3/"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js"></script>
<script>

    new Vue({
        el: '#app',
        data: {
            stripe: null,
            stripeCard: null,
            publicKey: '{{ config('services.stripe.key') }}',
            status: '',
            cardHolderName: '',
            details: {},
            plan: '',
            planOptions: {!! json_encode(config('services.stripe.plans')) !!}
        },
        methods: {
            async subscribe(e) {

                const paymentMethod = await this.getPaymentMethod(e.target);
                const url = '/user/ajax/subscription/subscribe';
                const params = {
                    payment_method: paymentMethod,
                    plan: this.plan
                };
                axios.post(url, params)
                    .then(response => {

                        location.reload();

                    });

            },
            cancel() {

                const url = '/user/ajax/subscription/cancel';
                axios.post(url)
                    .then(this.setStatus);

            },
            resume() {

                const url = '/user/ajax/subscription/resume';
                axios.post(url)
                    .then(this.setStatus);

            },
            changePlan() {

                const url = '/user/ajax/subscription/change_plan';
                const params = { plan: this.plan };
                axios.post(url, params)
                    .then(this.setStatus);

            },
            async updateCard(e) {

                const paymentMethod = await this.getPaymentMethod(e.target);
                const url = '/user/ajax/subscription/update_card';
                const params = { payment_method: paymentMethod };
                axios.post(url, params)
                    .then(response => {

                        location.reload();

                    });

            },
            setStatus(response) {

                this.status = response.data.status;
                this.details = response.data.details;

            },
            async getPaymentMethod(target) {

                const clientSecret = target.dataset.secret;
                const { setupIntent, error } = await this.stripe.confirmCardSetup(
                    clientSecret, {
                        payment_method: {
                            card: this.stripeCard,
                            billing_details: { name: this.cardHolderName }
                        }
                    }
                );

                if (error) {

                    console.log(error);

                } else {

                    return setupIntent.payment_method;

                }

            }
        },
        computed: {
            isSubscribed() {

                return (this.status === 'subscribed' || this.status === 'cancelled');

            },
            isCancelled() {

                return (this.status === 'cancelled');

            }
        },
        watch: {
            status(value) {

                Vue.nextTick(() => {

                    if(!this.isCancelled) {

                        const selector = (value === 'unsubscribed') ? '#new-card' : '#update-card';
                        this.stripeCard = this.stripe.elements().create('card', {
                            hidePostalCode: true
                        });
                        this.stripeCard.mount(selector);

                    }

                });

            }
        },
        mounted() {

            this.stripe = Stripe(this.publicKey);
            const url = '/user/ajax/subscription/status';
            axios.get(url)
                .then(this.setStatus);

        }
    });

</script>
</body>
</html>

少しコードが長くなってしまいましたが、やっていることは、以下になります。

  • Laravelから課金状態を Ajax で取得&その状態ごとに表示を切り替え
  • 課金登録とクレジットカードを変更する場合は stripeから Intent を取得してから Ajax 送信
  • それ以外の場合は単に Ajax で Laravel 側へ送信

そして、重要な部分はwatchです。この中では、statusが変更したと同時にstripe.jsのクレジットカード入力ボックスを作成していますが、状態によってターゲットをその都度切り替えています。

⚠ なお、「前提として」でも書きましたが、stripe.jshttpsでの接続が必須となっていますので気をつけてください。

また、viewportも必須とのことですので、忘れずに<head></head>タグ内に追加しておいてください。

<meta name="viewport" content="width=device-width, initial-scale=1" />

テストしてみる

では実際にテストしてみましょう❗

ログインして、「https://******/user/subscription」にアクセスすると以下のようなフォームが表示されます。

まずは月額課金に登録してみます。
入力フォームを以下のようにして「課金する」ボタンをクリックします。

すると・・・・・・
登録が完了して表示が切り替わります。

では、続いて登録したばかりですが「キャンセル」ボタンをクリックして課金を止めてみましょう。

するとキャンセルが実行されるので、またすぐに元に戻してみましょう。

完了すると表示が切り替わるので、次はプランの変更をします。

現在プレミアムで登録しているので「ベーシック」を選択し「プランを変更する」ボタンをクリックします。

すると・・・・・

プレミアム」と表示されていた部分がベーシックに変更になります。(ページをリロードしてもベーシックのままになっていることを確認してみてください)

では、次に登録されたクレジットカード番号を他のものに変更してみましょう。

現在は「4242…」から始まるVISAのテスト番号で登録しているので、masterカードに変更してみましょう。

これで送信すると・・・・・・
登録されているカードの下4桁が変更になりました。

成功です😊✨
お疲れ様でした❗

ちなみに – 1:テストのクレジットカード番号

Stripeでは以下のページで「テストのために使えるクレジットカード番号」をいくつか公開しています。

Test card numbers and tokens

なお、有効期限は未来の日付ならいつでもOKで、CVCはランダムな数字で問題ありません。

ちなみに – 2:旧バージョンのマイグレーション

現在はパッケージ内にマイグレーションが含まれていますので不要ですが、旧バージョンではテーブルを自分で作成していました。

加筆修正の際に削除しようかとも思いましたが、もしかすると必要としている方がいらっしゃるかもしれないのでこちらへ移動しました。

※つまり、最新版を使っている場合はこの作業は不要です。

まずはusersテーブルにクレジットカード情報(の一部)が保存できるように項目を追加します。
以下のコマンドでマイグレーション・ファイルを作成してください。

php artisan make:migration add_stripe_to_users_table

すると、database/migrations/****_**_**_******_add_stripe_to_users_table.phpが作成されるので中身を以下のように変更します(太字が変更した部分です)

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class AddStripeToUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('stripe_id')
                ->nullable()
                ->collation('utf8mb4_bin')
                ->after('remember_token');
            $table->string('card_brand')
                ->nullable()
                ->after('stripe_id');
            $table->string('card_last_four', 4)
                ->nullable()
                ->after('card_brand');
            $table->timestamp('trial_ends_at')
                ->nullable()
                ->after('card_last_four');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('stripe_id');
            $table->dropColumn('card_brand');
            $table->dropColumn('card_last_four');
            $table->dropColumn('trial_ends_at');
        });
    }
}

次に課金情報を保存しておくsubscriptionsテーブルを作成します。同じく以下のコマンドでマイグレーション・ファイルを作成してください。

php artisan make:migration create_subscriptions_table

そして、database/migrations/****_**_**_******_create_subscriptions_table.phpを開いて以下のようにします。

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateSubscriptionsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('subscriptions', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->unsignedBigInteger('user_id');
            $table->string('name');
            $table->string('stripe_id')->collation('utf8mb4_bin');
            $table->string('stripe_plan');
            $table->integer('quantity');
            $table->timestamp('trial_ends_at')->nullable();
            $table->timestamp('ends_at')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('subscriptions');
    }
}

これで必要なマイグレーションを2つ作成できました。

ちなみに – 3:Stripe Cliをインストールする方法

Stripe CLI をインストールする

基本的にインストールするにはStripeが提供する以下のGitHubページでダウンロードして実行します。

(最新パッケージDLページ)
https://github.com/stripe/stripe-cli/releases/latest

Windows の場合

自分の環境に該当するファイルをダウンロード&展開し.exeファイルを実行します。

Linuxの場合

需要は少ないでしょうが、Stripe CLIは私のような「Linux Love😉」な人間に向けてもパッケージを用意してくています。

こちらも該当パッケージをダウンロードして展開(コマンド例:tar -xvf stripe_X.X.X_linux_x86_64.tar.gz)、そしてこの状態で./stripeをコマンドで実行できるようになります。(つまり、通常のインストールというカンジではないですね)

なお、このままではどこからでもStripeコマンドが実行できるようにはなっていないので、次のような場所に移動しておくといいでしょう。

sudo mv stripe /usr/local/bin/stripe

移動後、stripe -vを実行して次のような表示が出たらうまくいっています。

stripe version 1.4.3

Stripe CLIにログインする

では、Stripe CLIからログインしましょう。

※ Linux環境で実行しましたが、widnowsでも似たカンジだと思います。もし違いがあったら、ぜひ情報提供お願いいたします。m_ _m

以下のコマンドを実行してください。

stripe login

すると、次のような表示になりますので、このまま「Enterキー」を押します。

認証ページがブラウザで表示されるので、内容を確認して「アクセスを許可」ボタンをクリックしてください。

すると、以下のような表示になりログイン作業は完了です。

【超意訳】
おけー❗あなたのIDが設定されたよ👍
でも90日だけ有効だから、またログインよろしく😊✨

では、念のためStripeのログインページでも接続ができているかチェックしておきましょう。

まず、ページ左にあるメニューの「開発者 > Webhook」をクリックします。

すると、ウェブフックの詳細ページが表示されるので「Stripe CLIのイベントを受信するデバイス」の項目をチェックしてみてください。

以下のようになっていればうまくいっています。

【追記:2021.03.11】
どうやらstripe listenを実行しないと表示されないようです。
次の項目をみてコマンド実行してから確認してください。m(_ _)m

続いて、「ウェブフックURL」をStripeに登録します。

登録の方法は、ウェブフックのキーを取得するを参考にしてみてください。

  • なお、STRIPE_WEBHOOK_SECRETを登録する部分は次の項目で実行するので飛ばしておいてください)
  • また、routeはすでにパッケージ側で登録してくれているので設定する必要はありません。もし独自のURLにしたい場合は「おまけ – 1:ウェブフックを独自のURLに設定する」をご覧ください。

ウェブフックを送信する

では、実際にテスト用のウェブフックを送信してみましょう。
以下のコマンドを実行してください。

stripe listen --forward-to example.test/stripe/webhook

example.test/stripe/webhookはローカル環境のURLです。適宜変更してください。

すると、以下のように専用の秘密鍵を表示してくれるので、この文字列を.envSTRIPE_WEBHOOK_SECRETとして登録します。

STRIPE_WEBHOOK_SECRET=******************************

この状態でウェブフックのテスト環境は完了しています。

後はブラウザでサブスクリプション登録するなどのイベントがあると以下のように自動で表示が追加されることになります。

おまけ – 1:ウェブフックを独自のURLに設定する

通常は問題ないと思いますが、何らかの理由でウェブフックを独自のURLにしたい場合も出てくるかと思います。

そんな場合は次のようにしてみてください。

コントローラーをつくる

まずLaravel CashierCashierControllerを継承したクラスをつくります。

今回はWebhookContollerをつくってみます。
以下のコマンドを実行してください。

php artisan make:controller WebhookController

するとファイルが作成されるので、中身を次のように変更してください。

/app/Http/Controllers/WebhookController.php

namespace App\Http\Controllers;

use Laravel\Cashier\Http\Controllers\WebhookController as CashierController;

class WebhookController extends CashierController
{
    public function receive($payload)
    {
        // ここで独自の処理ができます!
    }
}

ルートをつくる

あとは、このコントローラーを通常通りweb.phpに登録すれば完了です。

Route::post('custom/stripe/webhook', '\App\Http\Controllers\WebhookController@receive');

※なお、新しいルートにウェブフックを登録した場合は、app/Http/Middleware/VerifyCsrfToken.phpexceptCSRFブロックを解除しておきましょう。

protected $except = [
    'custom/stripe/*',
];

おまけ – 2:HTTPSへウェブフック送信する

https://******」へウェブフック送信をする場合は、コマンドを以下のように変更します。

stripe listen --forward-to https://example.test/custom/stripe/webhook --skip-verify

通常のコマンドとの違いは、URLに「https://」をつけていることと、「–skip-verify」オプションをつけていることです。

ダウンロードする

今回実際に開発したソースコード一式を以下からダウンロードすることができます。

Laravelで課金する方法(Laravel Cashier + Stripe)

※ ただし、stripeの設定や.env、パッケージのインストールなどはご自身で準備する必要がありますのでご注意ください。

開発のご依頼お待ちしております
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします! by 九保すこひ

おわりに

実は移転する前のブログでLaravel Cashierの記事を公開しているのですが、Laravel CashierだけでなくstripeのAPIバージョンもアップデートされるなど変更点が多くなったため今回は、より詳しく記事をお届けしました。

【追記:2020.08.26】さらにLaravel Cashierが更新されたので、再度記事を大幅にアップデートしました。

そして、前回と比較して一番いいなと思ったのは、「クレジットカードの入力ボックスを stripe.js が作成してくれる」という部分です。

以前はわざわざクレジットカード番号、有効期限(月と年)、CVCの入力ボックスを作って独自に送信する流れでしたが、今はstripe.jsが一手に引き受けてくれるようになっていてちょっと感動しました。(しかも、入力もしやすいですね😊✨)

ということで、現在は個人であっても課金システムを導入することが難しい時代ではなくなりました。

ぜひ皆さんもトライしてみてはいかがでしょうか。

ではでは〜!

「サブスクって言葉、浸透しましたね😊」

このエントリーをはてなブックマークに追加       follow us in feedly