Laravel × Stripeでサブスクリプションを作成する方法

Laravel

「Laravelを使ってStripeのサブスクリプション機能を実装したいけど、方法がわからない。」

「サブスクリプション機能を作りたいけど、そもそもStripeに触ったことがなく、何から始めていいのかもわからない。」

このような方に向けて記事を作成します。

今回はPHPフレームワークであるLaravelを使って、Stripe上にサブスクリプションを作成する方法をチュートリアル形式でご紹介します。

また、LaravelにはLaravel CashierというStripe専用の便利なライブラリがあるため、こちらを使用していきます。

筆者の経験
✔️プログラミング歴3年ほど
✔️現役のWebエンジニア
✔️実務でLaravelを使ったStripe決済機能を担当していた

それでは、早速見ていきましょう!

Stripe準備

Stripeアカウントを作成する

Stripeを利用するには、Stripeアカウントがないと始まりません。

まずは、以下のURLからStripeアカウントを作成しましょう。

Stripeアカウント新規作成

以下の画面からメールアドレスや氏名など必要な情報を入力します。

必要な情報を入力し、Stripeアカウントが作成できたら、以下のURLからStripeにログインできます。

Stripeログイン画面

ビジネス詳細情報の入力を求められることがありますが、テスト用としてStripeを利用するだけなら入力の必要はありません。

ダッシュボードに行くと、以下のような画面になります。

Stripeの商品を作成する

次にStripeで販売したい商品を作成していきます。

Stripeダッシュボード画面から、メニューの[その他] > [商品カタログ]をクリックします。

以下のような商品カタログページに移動します。

この画面で「商品の追加」ボタンをクリックします。

以下のような入力画面が出てくるので、適当な商品を入力しておきます。

入力が終わったら、「次へ」ボタンをクリックし、商品の詳細情報を入力していきます。

サブスクリプションを作成する場合は、最初に「継続」を選択してください。

また、請求サイクルは月次や週次などを選択できます。

全て入力が終わったら、「次へ」ボタンをクリックして、「商品を追加」ボタンをクリックします。

これで、商品が一つできました!

Laravel

Laravelプロジェクト作成

ここまででStripe上に商品を作るところまで完了しました。

それでは、実際にLaravelからどうStripeと連携させるのかについて解説していきます。

まずは、Laravelプロジェクトを作成していきます。

今回はバージョン9.x系です。

composer create-project laravel/laravel=9.x stripe_test --prefer-dist

これで「stripe_test」というLaravelプロジェクトのフォルダーが出来上がりました。

Laravel Cashierインストール

LaravelにはStripe専用のライブラリである、Laravel Cashierというライブラリがあります。

こちらを利用することで、

  • 面倒な処理を省ける
  • 自動的にWebhookのセキュリティ機能が備わっている
  • ドキュメントが用意されている

などの利点があります。

そこで、今回はこちらのライブラリを使っていきます。

公式ドキュメントは以下です。

Laravel 9.x Laravel Cashier (Stripe)

詰まった時などは目を通しておくと良いでしょう。

まずはインストールしましょう。

composer require laravel/cashier

次に、マイグレーションをかけておきます。

php artisan migrate

すると、Stripe用のテーブル、カラムとして以下が作成されます。

  • subscriptionsテーブル
  • subscription_itemsテーブル
  • usersテーブル
    • stripe_idカラム
    • pm_typeカラム
    • pm_last_fourカラム
    • trial_ends_atカラム

データベースの詳しい内容はまた別記事でご紹介します。

今は、Stripeを使う上で必要なテーブルやカラムだと思っておけば大丈夫です。

次に、UseモデルにBillableトレイトを追加しておきます。

use Laravel\Cashier\Billable;

class User extends Authenticatable
{
    use Billable;
}

これをなぜ追加するかというと、Billableトレイトを追加することでサブスクリプションの作成やクーポン適用、支払い方法の更新などのLaravel Cashierで用意されているメソッドをUserモデルから使えるようになります

.envにAPIキーを設定

次に、テスト用のAPIキーを環境変数に設定していきます。

Stripeダッシュボードに戻って、上のメニューにある「開発者」を開きましょう。

開発者用画面のタブの中に「APIキー」があるはずです。

これをクリックして、APIキーとシークレットキーを確認します。

確認ができたら、Laravelに戻り、.envファイルに以下の環境変数を設定しておきます。

STRIPE_KEY=your-stripe-key
STRIPE_SECRET=your-stripe-secret

ビューの作成

フロント側では、Stripe.jsを使ってフォームを作成していきます。

Stripeには、Stripe側で用意されているフォームを使う「Stripe Checkout」と画面を自作する「Stripe Elements」があります。

本記事では、Stripe Elementsを使ってフォームを自作していきます。

まずは、フォームを作ります。

resources/views/stripe.blade.php

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>Accept a payment</title>
    <meta name="description" content="A demo of a payment on Stripe" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" href="stripe.css" />
    <script src="https://js.stripe.com/v3/"></script>
    <script src="stripe.js" defer></script>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
  </head>
  <body>
    <div class="container">
        <div class="card w-50 mt-5 m-auto">
            <div class="card-body">
                <form action="{{ route('stripe.payment') }}" method="POST" id="stripe-form">
                    @csrf
                    <div class="mb-3 row">
                        <label for="card-holder-name" class="col-sm-2 col-form-label">名前</label>
                        <div class="col-sm-10">
                            <input id="card-holder-name" type="text" class="form-control">
                        </div>
                    </div>

                    <!-- ストライプ要素のプレースホルダ -->
                    <div id="card-element" class="my-4"></div>

                    <button id="card-button" data-secret="{{ $intent->client_secret }}" class="btn btn-primary">
                        支払いをする
                    </button>
                </form>
            </div>
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
  </body>
</html>

次に、わかりやすいように決済成功画面も用意しておきましょう。

resources/views/payment-success.blade.php

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>Success a payment</title>
    <meta name="description" content="A demo of a payment on Stripe" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
  </head>
  <body>
    決済成功です!
  </body>
</html>

大事なのは以下です。

<script src="https://js.stripe.com/v3/"></script>

headタグ内でStripe.jsを読み込まないと、Stripe用のフォームが生成できません。

必ず記述しておきましょう。

また、CSSフレームワークにはBootstrapを利用しています。

詳細は割愛しますが、興味がある方は以下の公式を見てください。

BootStrap

最後に、JavaScriptを書いていきます。

const stripe = Stripe(テスト用APIキー);

var style = {
    base: {
        fontSize: "16px",
        color: "#424770",
        letterSpacing: "0.025em",
        fontFamily: "Roboto, Source Code Pro, monospace, SFUIDisplay",
        "::placeholder": {
            color: "#aab7c4"
        }
    },
    invalid: {
        color: "#9e2146"
    },
};


const elements = stripe.elements();
const cardElement = elements.create('card', {style: style, hidePostalCode: true});

cardElement.mount('#card-element');

const cardHolderName = document.getElementById('card-holder-name');
const cardButton = document.getElementById('card-button');
const clientSecret = cardButton.dataset.secret;

cardButton.addEventListener('click', async (e) => {
    const { setupIntent, error } = await stripe.confirmCardSetup(
        clientSecret, {
            payment_method: {
                card: cardElement,
                billing_details: { name: cardHolderName.value }
            }
        }
    );

    if (error) {
        // "error.message"をコンソールに表示する
        console.log(error.message);
    } else {
        // カードが正常に検証された場合
        // setupIntent.payment_methodをサーバーに送信する
        console.log('カードが正常です');
        const form = document.getElementById('stripe-form');
        const hiddenInput = document.createElement('input');
        hiddenInput.setAttribute('type', 'hidden');
        hiddenInput.setAttribute('name', 'payment_method');
        hiddenInput.setAttribute('value', setupIntent.payment_method);
        form.appendChild(hiddenInput);
        form.submit();
    }
});

長いですが、大事なのは以下の2点だけです。

まず、以下でStripe用のフォームを生成しています。

const elements = stripe.elements();
const cardElement = elements.create('card', {style: style, hidePostalCode: true});
cardElement.mount('#card-element');

種類は’card’としておき、styleオブジェクトには指定された形式でフォームのスタイルを指定しています。

次に、支払いボタンが押された時の挙動ですが、stripe.confirmCardSetupで支払い方法の情報に問題がないか確認しつつStripeと連携するためのセットアップを行います。

検証が完了した後に、以下でフォームの内容をサーバー側に渡しています。

else {
    // カードが正常に検証された場合
    // setupIntent.payment_methodをサーバーに送信する
    console.log('カードが正常です');
    const form = document.getElementById('stripe-form');
    const hiddenInput = document.createElement('input');
    hiddenInput.setAttribute('type', 'hidden');
    hiddenInput.setAttribute('name', 'payment_method');
    hiddenInput.setAttribute('value', setupIntent.payment_method);
    form.appendChild(hiddenInput);
    form.submit();
}

Stripe.jsの詳細については、以下の公式を参照しておくと良いでしょう。

Stripe.js

ルーティングの設定

ルーティングは以下の3本です。

Route::get('/stripe', 'App\Http\Controllers\StripeController@intent'); // stripeフォーム画面
Route::get('/payment-success', function () {
    return view('payment-success');
})->name('payment_success'); // 決済成功画面
Route::post('/payment', 'App\Http\Controllers\StripeController@payment')->name('stripe.payment'); // 決済処理

コントローラーの作成

まずは、ファイルを作成します。

php artisan make:controller StripeController

次に、フォーム画面用のメソッドを作成していきます。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Models\User;

class StripeController extends Controller
{
    public function intent()
    {
        $user = User::find(1);
        return view('stripe', [
            'intent' => $user->createSetupIntent()
        ]);
    }
}

今回はあくまでデモなので、ユーザーID:1で固定しています。

以下で支払い方法保存のためのセットアップインテントを作成し、ビューに渡しています。

'intent' => $user->createSetupIntent()

セットアップインテントとは、フォームに入力された支払い方法(クレカ情報)を安全に収集し保存するためのオブジェクトです。

これがないとStripe.jsはフォームを生成しません。

次に、サブスクリプションの決済の処理を追加します。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Models\User;

class StripeController extends Controller
{
		...

    public function payment(Request $request)
    {
        // サブスクリプションを作成する
        $user = User::find(1);
        $user->newSubscription('main', config('stripe.price_id'))->create($request->payment_method);
        return redirect()->route('payment_success');
    }
}

Stripeと連携し、Stripe上にサブスクリプションを作成する処理は以下です。

$user->newSubscription('main', config('stripe.price_id'))->create($request->payment_method);

newSubscriptionメソッドの第一引数にはサブスクリプション名を入れます。

第二引数にはStripeの価格IDが入ります。

Stripeの価格IDに関しては、Stripe上で商品を作成する際に自動的に割り振られています。

Stripeにログインし、商品カタログ画面に入って確認することができます。

こちらはデモで行う分には第二引数に直書きでもいいですが、本番利用を考えている場合は.envなど安全な場所に書いておきましょう。

最後にcreateメソッドでリクエストから受け取ったクレカ情報を引数に入れて実行すれば、Stripe上でサブスクリプションが作成されます。

これで一通り、Stripeの決済システムが完成しました!

テスト

最後に、ちゃんと動作するかを確認してみます。

/stripeにアクセスして、Stripeフォームにクレカ情報を入力後に「支払いをする」ボタンを押します。

テスト環境の場合は、テスト用のクレカ番号などが用意されています。

Stripeのテスト

今回はこちらを使っていきます。

ボタン押下後は無事に決済成功画面へ遷移していました。

最後に、Stripeと連携できているか確認してみます。

Stripeに入って、[顧客] > [顧客詳細]へ進んで「サブスクリプション」と記載のある場所で確認できます。

問題なく、顧客にサブスクリプションが作られていました。

最後に

お疲れ様でした!

いかがでしたでしょうか?

長い道のりでしたが、Stripeを使えば自前で決済システムを構築するよりは簡単に決済機能を実装することができます。

また、今回は紹介しませんでしたが、Stripeには他にもローコードでより簡単に決済機能を実装できるStripe Checkoutというものも用意されています。

こちらを使うと、ほとんどコードを書かなくても決済機能が実装できてしまいます。

こちらはまたの機会に記事を作成したいと思います。

それでは、今回は以上になります。

ご清聴ありがとうございました!

コメント

タイトルとURLをコピーしました