書籍「Laravelの教科書」Laravel13対応サポートガイド公開中 → ダウンロードはこちら

Laravel8にSTRIPEでサブスクリプション機能を付ける10ステップ

LaravelとStripe API連携

LaravelはCachierパッケージをインストールして、Stripeと連携が可能です。

両者とも優れたシステムですが、連携の手順は意外とメンドウ。

10個のステップでひとつずつ、ステップを解説していきますね。

Laravelにサブスク申込機能を取り入れたい方は、参考にしてみてください。

Laravel8にSTRIPEでサブスクリプション機能を付ける10ステップ

10ステップは下記のとおりです。

  1. Stripeのテストアカウント作成
  2. Stripeの商品作成
  3. Laravelのプロジェクト作成
  4. LaravelにCachierパッケージをインストール
  5. Laravelのマイグレーション実施
  6. Laravelの.envファイルにStripeのAPIを設定
  7. UserモデルファイルにBillable追加
  8. ルート設定
  9. コントローラーの作成
  10. フォームの作成

①Stripeのテストアカウント作成

まずはSTRIPEでテストアカウントを作ります。

必要なのはメールとURLぐらい。

なお、テストアカウントは後から本番用に移行できますが、その際には本番用アカウント作成が必要となります。

本番アカウント用のちょっと面倒な質問への対応は、こちらの記事で解説しているので困ったら参考にしてくださいね。

②Stripeの商品作成

Stripeのアカウント作成後、商品を作成します。

左側のメニューより【商品】を選択。

【商品を追加】ボタンを押して、サブスクプランを作成しておきます。

価格欄で【継続】を選択しましょう。

③Laravelのプロジェクト作成

次にLaravelのプロジェクトを準備します。

下記のコマンドで新たにプロジェクトを作成できます。

composer create-project --prefer-dist laravel/laravel newproject

【newproject】にはお好きな名前をいれてください。

なお、初めてLaravelを使う場合には、事前にインストールが必要なものがいくつかあります。

詳しくは、Laravelのインストールと初期設定を解説した下記記事をご覧ください。

④LaravelにCachierパッケージをインストール

次にLaravelにCachierパッケージをインストールします。

composer require laravel/cashier

2020年11月11日現在、バージョン12をインストールできます。

“memory size exhausted” エラーが出た場合には、php.iniでメモリの容量設定を変更してください。

詳しくはこちらの記事の「メモリ不足エラーになった場合」をご覧ください。

⑤Laravelのマイグレーション実施

Cachierインストール後、マイグレーションを行います。

php artisan migrate

これによって、データベースのユーザーテーブルに4個のカラムが自動で追加されます。

Cashierインストール後、ユーザーテーブルに4つのカラム追加

さらにsubscriptionsテーブルとsubscription_itemテーブルが作成されます。

subscriptionsテーブル

subscription_itemsテーブル

今後サブスクリプションを作成・更新すると、追加されたカラム・テーブルへ自動で値が入っていきます。

⑥Laravelの.envファイルにStripeのAPIを設定

次にStripeのキーをLaravelに埋め込みます。

公開可能キー・シークレットキー・商品APIキーの3つを埋め込んでいきましょう。

Stripe側でキーを取得

Stripeにログインすると、【ホーム】メニューに【テストAPIキーの取得】が表示されます。

【公開可能キー】と【シークレットキー】をそれぞれコピーします。

さらに商品ページへ移動し、作成した商品の【API ID】をコピーしておきます。

Laravel側にキーを貼り付ける

Laravelの.envファイルを開き、この3個のキーを貼り付けます。

さらにapp/configのservices.phpファイルに、上記の情報を設定しておきます。

    'stripe' => [
        'pb_key'=>env('STRIPE_KEY'),
        'st_key'=>env('STRIPE_SECRET'),
        'basic_plan_id'=>env('STRIPE_BASIC_ID'),
    ],

.envファイルとconfigへのキーの設定については、こちらの記事で解説しています。

「なぜこんなふうに設定するんだろう?」と思ったら、目を通してみてください。

キーを変更してもうまく反映されなかったりした場合の対策はこちら。

⑦UserモデルファイルにBillable追加

app\Models の User.phpファイルを開き、use Laravel\Cashier\Billable; と use Billable; を追加します。

use Laravel\Cashier\Billable;

class User extends Authenticatable
{
    use Billable;
}

これによって、サブスク作成を実行するメソッドが使えるようになります。

⑧ルート設定

次に、Laravelプロジェクト内の routes/web.phpファイルに、ルート設定を記述します。

特にStripeだから特別、ということはなく、通常どおりでOK。

フォームを表示するルート設定と、フォームを投稿後のルート設定のふたつを準備しておきます。

Route::get('/subscription', 'StripeController@subscription')->name('stripe.subscription');
Route::post('/subscription/afterpay', 'StripeController@afterpay')->name('stripe.afterpay');

ひとことメモ

Laravel8ではコントローラーのパスが通っていないので、コントローラー名の前にパスを入れねばなりません。

あらかじめ app/ProvidersのRouteServiceProvider.php の29行目を有効にしておいてください。

⑨コントローラーの作成

次にコントローラーを作成します。

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

php artisan make:controller StripeController

app/Http/Controllers に StripeController.phpが作成されます。

ファイルを開き、上のほうに下記を入れておきます。

namespace App\Http\Controllers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
use Laravel\Cashier\Cashier;
use Stripe\Stripe;
use Stripe\Charge;
use App\Models\User;

次にフォーム表示用と、フォーム投稿後用のコントローラーを作ります。

フォーム表示用コントローラー

フォーム表示用のコントローラーは下記のとおり。

    public function subscription(Request $request){
      $user=Auth::user();
        return view('post.subscription',  [
          'intent' => $user->createSetupIntent()
      ]);
    }

ここで ‘intent’ => $user->createSetupIntent() の箇所は、Setup Intentを作成するために入れています。

こうしておくことで、ビュー側で支払い方法(payment method)に関する情報を集められます。

サブスク作成時には、この支払方法情報が必要になります。

フォーム投稿後用コントローラー

フォーム投稿後用のコントローラーは下記のとおり。

最後の【ルート設定】には、処理後に表示させたいページのルートを入れておいてください。

特になれけば、return back(); としておくと良いでしょう。

    public function afterpay(Request $request){
        // ログインユーザーを$userとする
        $user=Auth::user();

        // またStripe顧客でなければ、新規顧客にする
        $stripeCustomer = $user->createOrGetStripeCustomer();

        // フォーム送信の情報から$paymentMethodを作成する
        $paymentMethod=$request->input('stripePaymentMethod');

        // プランはconfigに設定したbasic_plan_idとする
        $plan=config('services.stripe.basic_plan_id');
        
        // 上記のプランと支払方法で、サブスクを新規作成する
        $user->newSubscription('default', $plan)
        ->create($paymentMethod);

        // 処理後に'ルート設定'にページ移行
        return redirect()->route('ルート設定');
    }

上記では、createOrGetStripeCustomer()やnewSubscription()といったメソッドを使っています。

Cashierを入れたことで、こういったメソッドが使えるようになりました。

公式マニュアルには、その他にも色々なメソッドが紹介されているので、興味があればチェックしてみてくださいね。

⑩フォームの作成

最後にフォームの作成にとりかかります。

CSSファイルの作成

CSSは、Stripeに準備されているものを使います。

public/cssフォルダ内の stripe.cssファイルに保存しておきます。

【stripe.css】

.MyCardElement {
	height: 40px;
	padding: 10px 12px;
	width: 100%;
	color: #32325d;
	background-color: white;
	border: 1px solid transparent;
	border-radius: 4px;
  
	box-shadow: 0 1px 3px 0 #e6ebf1;
	-webkit-transition: box-shadow 150ms ease;
	transition: box-shadow 150ms ease;
  }
  
  .MyCardElement--focus {
	box-shadow: 0 1px 3px 0 #cfd7df;
  }
  
  .MyCardElement--invalid {
	border-color: #fa755a;
  }
  
  .MyCardElement--webkit-autofill {
	background-color: #fefde5 !important;
  }

なおStripeはきれいなテンプレートを色々と準備してくれています。

他のテンプレート例は、Stripeのサイトをご覧ください。

作成したstripe.cssのリンクは、Laravelにデフォルトで準備されているテンプレートlayouts/app.blade.phpファイルの<head></head>の間にセットしておきます。

app.blade.phpファイルの上部に app.cssのリンクが設置されているかと思いますが、その下辺りでOKです。

【app.blade.php】

    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
    <link href="{{ asset('css/stripe.css') }}" rel="stylesheet">

blade.phpファイルのHTML部分の作成

次にresources/viewsにpostフォルダを作り、subscription.blade.phpファイルを作成します。

フォーム部分、つまりHTML部分はこんな感じです。

【subscription.blade.php】

{{-- ヘッダー部分の設定 --}}
@extends('layouts.app')
@section('content')

<div class="container py-3">
  <h3 class="mb-3">ご登録フォーム</h3>

{{-- フォーム部分 --}}
<form action="{{route('stripe.afterpay')}}" method="post" id="payment-form">
  @csrf
  
    <label for="exampleInputEmail1">お名前</label>
    <input type="test" class="form-control col-sm-5" id="card-holder-name" required>

    <label for="exampleInputPassword1">カード番号</label>
    <div class="form-group MyCardElement col-sm-5" id="card-element"></div>
 
    <div id="card-errors" role="alert" style='color:red'></div>

    <button class="btn btn-primary" id="card-button" data-secret="{{ $intent->client_secret }}">送信する</button>

</form>
</div>

layouts/app.blade.phpをテンプレートとして使うために、@extends(layouts.app)を入れています。

こうすることで、上記のようなヘッダーをページに表示できます。

@extendsと@sectionの使い方は、こちらで解説しています。

もうひとつ、ここで重要なポイントがあります。

resources/layouts の app.blade.php ファイルを開きます。

{{ asset(‘js/app.js’) }}  ファイルへのリンクをtitleの下あたりに変えておきます。

こうしないと、stripeのjavascriptと競合してしまうためです。

詳しくはこちら。

stripe.jsの挿入

HTMLの後にJavascript部分を入れていきます。

まずは、こちらのstripe.jsをいれます。

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

ヘッダ部分にいれるとエラーになったりもするので、HTMLの記述が終わったところにいれておきます。

このコードを入れておくことで、Stripeがあやしげな行動を検知できるようにもなります。

Stripe側では、詐欺を防ぐため、決済ページだけでなく、できれば全てのページに本コードをいれておくよう推奨しています。

To best leverage Stripe’s advanced fraud functionality, include this script on every page, not just the checkout page.

*参照元:Stripe公式マニュアル

Javascript部分の作成

最後に subscription.blade.phpファイルにJavascriptを入れていきます。

<script>

	// HTMLの読み込み完了後に実行するようにする
	window.onload = my_init;
	function my_init() {

		// Configに設定したStripeのAPIキーを読み込む  
		const stripe = Stripe("{{ config('services.stripe.pb_key') }}");
		const elements = stripe.elements();

		var style = {
			base: {
			color: "#32325d",
			fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
			fontSmoothing: "antialiased",
			fontSize: "16px",
			"::placeholder": {
			color: "#aab7c4"
			}
		},
		invalid: {
			color: "#fa755a",
			iconColor: "#fa755a"
		}
		};
		
		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) => {
			// formのsubmitボタンのデフォルト動作を無効にする
			e.preventDefault();
			const { setupIntent, error } = await stripe.confirmCardSetup(
				clientSecret, {
					payment_method: {
					card: cardElement,
					billing_details: { name: cardHolderName.value }
					}
				}
			);
			
			if (error) {
			// エラー処理
			console.log('error');
			
			} else {
			// 問題なければ、stripePaymentHandlerへ
			stripePaymentHandler(setupIntent);
			}
		});
	}
	
	function stripePaymentHandler(setupIntent) {
	var form = document.getElementById('payment-form');
	var hiddenInput = document.createElement('input');
	hiddenInput.setAttribute('type', 'hidden');
	hiddenInput.setAttribute('name', 'stripePaymentMethod');
	hiddenInput.setAttribute('value', setupIntent.payment_method);
	form.appendChild(hiddenInput);
	// フォームを送信
	form.submit();
	}
</script>
@endsection

かなり長くなりましたが^^;

これで出来上がりです。

テストをする

最後にテストをしてみましょう。

ユーザーとしてログインした後、作成した決済ページを開きます。

下記のルールで、テスト用のカード番号・数字を入れます。

  • カード番号:4242 4242 4242 4242
  • 日付:将来の日付を適当に入れる
  • CVC: 3桁の数字を適当に入れる

送信後、無事にデータが送信されているかチェックします。

Stripeにログインし、左側のメニューから【支払い】をクリック。

送信した商品の支払いが行われているかチェックしてください。

支払い処理が成功していれば、【顧客】にも、ユーザーが登録されているはずです。

さいごに

おつかれさまでした!

LaravelとStripeの連携、結構メンドウですよね。

じゅんこ
じゅんこ

正直、もっと簡単に連携できると思っていました。

ただ連携させられれば、できることは色々とあります。

関連記事で解説しているので、LaravelとStripe連携にご興味があれば、ご一読ください。

じゅんこ
じゅんこ

すてきな決済システムを作っていきましょうー

★2021年5月8日内容修正

修正前のコードでは、うまくいくときと、いかない場合がありました。

コード実行のタイミングが、その時々で変わってしまっていたためです。

そこでsubscription.blade.phpファイルの中に、javaScript初期化処理をページ読み込み完了後に行うようにしました。

(本件についてコメントいただき、ありがとうございました!)

 

★2021年8月31日内容修正

動作には変化はありませんが、下記コードの括弧を修正しました。

Stripe(“{{ config(‘services.stripe.pb_key’) }}”)

LaravelとStripe API連携

生成AIを「安全に」使うための研修・セミナー

お問い合わせ

生成AIの業務活用で気になるのは、効率だけでなくセキュリティです。 実践的な活用に加え、セキュリティ対策もお伝えします。

詳細を見てみる
 

生成AIを「安全に」使うための研修・セミナー

お問い合わせ

生成AIの業務活用で気になるのは、効率だけでなくセキュリティです。 実践的な活用に加え、セキュリティ対策もお伝えします。

詳細を見てみる

【Laravelの教科書・プレゼント】

Junko
Laravelの使い方を覚えたい!と思ったら、ぜひ、役立ててほしいです。 基礎編は無料でプレゼント中です♪
ひつじプログラマ
会員制サイトをいちから作っていくよ。ボタンをクリックして詳細を見てね。
Laravelの教科書の詳細を見る

最新版Laravel13への対応に対応。Laravel8からの各バージョンのテキストもご覧になれます♪

【無料プレゼント】

「LaravelでWebアプリをいちから作れるようになりたい!」

そんなLaravel初心者のあなたへ【Laravelの教科書】基礎編プレゼント中! 会員制フォーラムサイトを学習しながら作れます。

詳細はこちらをクリック

最新のLaravel13に対応。Laravel8からの各バージョンのテキストもご覧になれます♪

Laravelの本を書きました。


ひつじが目印♪

Laravelの使い方を分かりやすく解説した本を書きました。 書店やAmazon等のオンラインショップにて販売中
最新版Laravel13対応用サポートガイド(PDF)あり

書籍の詳細を見てみる

Laravelの本書きました。


ひつじが目印♪
クリックするとamazonページへ。
最新版Laravel13対応用
サポートガイド(PDF)あり

書籍の詳細を見てみる
シェアする
Twitter始めました。

コメント

  1. 柳田昂也 より:

    はじめまして! いつも記事を参考にさせて頂いております、ありがとうございます!

    「Laravel8にSTRIPEでサブスクリプション機能を付ける10ステップ」の記事拝見させていただきました。
    そこで記事の通り、実装してみたところ
    “resource_missing
    This customer has no attached payment source or default payment method.”というエラーが出てつまずいています。
    開発環境(localhost:8000など)の影響でstripeとうまく連携できていないこともあるのかなと考えました。

    こちらは「php artisan serve」 で立ち上げて開発してますでしょうか…?

    恐縮ですがご回答いただたら嬉しいです!

    • Junko Junko より:

      柳田さん、コメントありがとうございます^^

      記事通りに試して頂いたのにエラーになってしまったとのこと、残念です。

      こちら試してみましたが php artisan serveでサーバーを立ち上げた環境でも動作しました。

      いただいたエラーメッセージで検索してみましたが、人によって解決方法が様々で、イマイチ原因が分かりません。

      エラーメッセージをそのまま読むと、payment methodがちゃんと設定されていない感じですよね。

      .envやconfigの設定が間違えていないか、subscriptionメソッドの設定が正しく入っているか、ビューファイルから情報がちゃんと受け渡されているか。

      このあたりが原因の可能性があります。

      以前、paymentMethodのエラーについて書いた記事があります。
      本ブログの記事に沿ってコードを入れて頂いているので該当しないと思いますが、念の為、記事のリンク貼りますね。
      ↓↓
      https://biz.addisteria.com/laravel-stripe-paymentmethod/

      • 柳田昂也 より:

        ご回答ありがとうございます!!また、詳しいアドバイスまで恐れ入ります…!

        貼っていただいた参考記事のように、今自分が開発している環境(laravel や cashier、その他)のバージョンの関係があるのかもしれません。

        お忙しいところありがとうございました。
        再度、config設定やバージョンを見直して原因調査していきます!

        • Junko Junko より:

          柳田さん、

          その後 新しい環境で試したところ、わたしのほうでもエラーを再現しました!
          記事内のコードを書き直したので、よろしければ、再度お試しください。
          コメント、ありがとうございました。

          • 柳田昂也 より:

            わざわざありがとうございます!!大変助かります!
            私の方でも実装してみたいと思います!

  2. YunaSuzuki より:

    初めまして。とても詳しく書かれている本稿が大変役に立っております。
    1点、Intentを作成する箇所「$user->createSetupIntent()」で、私の環境だと下記エラーが出ました。
    Stripe\Exception\AuthenticationException
    No API key provided. Set your API key when constructing the StripeClient instance, or provide it on a per-request basis using the `api_key` key in the $opts argument.
    原因についてネットで調べましたがイマイチ解決策が分からず…
    もしご存知でしたらお教えいただけませんでしょうか?

    • Junko Junko より:

      こんにちは!
      こちらAPIキーが正しくセットされていない可能性があります。
      Stripe側でちゃんと取得されているとすると、.envまたはconfigでの
      設定が間違えていないか、確認してみてください。

      また、設定変更がLaravel側に反映されていない可能性も。
      php artisan config:clear
      を行ってみてください。
      キャッシュクリアについては、別記事にて解説してます:https://biz.addisteria.com/cache_clear/

  3. あさひ より:

    基礎編からお世話になり、いつも拝見し勉強させていただいています!

    いま、こちらの記事を参考に(というかほぼコピペさせていただいたんですが、、笑)ページをつくっているのですが、テストをしてみるとカード内容の入力フォームが、枠になっているのに入力できない状態になっております。

    どのような原因が考えられるか教えていただけないでしょうか。よろしくお願いいたします。

    • Junko Junko より:

      コメントありがとうございます。嬉しいです!
      またご質問の件ですが、もしかしたら広告ブロック機能(AdBlock)を使っていらっしゃったりしますか?
      もしそうでしたら、一度止めて、試してみてください。

      • あさひ より:

        お返事いただいて、ありがとうございます!

        確認してみたのですが、広告ブロック機能等は使っておらず、なかなか原因がつかめずにいます。teratailにも質問してみたのですが未解決です。

        もう一度自分でも考えてみますので、もしなにかほかにも原因になりそうなものがあれば、教えていただけると幸いです。よろしくお願いいたします!

        • Junko Junko より:

          JavaScriptが効いていないのが原因だと推測されます。

          このあたりがあやしいですが、他のものも含めて、
          ちゃんと入っているか、また入れる位置も正しいかチェックしてみてください。

        • Junko Junko より:

          あ、コード入れた部分、コメントから消えちゃってました。
          括弧部分全角にしておきますね。
          <script src=”https://js.stripe.com/v3/”></script>
          ↑↑
          これが抜けていないか、正しい場所にあるか確認してみてください

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