LaravelはCachierパッケージをインストールして、Stripeと連携が可能です。
両者とも優れたシステムですが、連携の手順は意外とメンドウ。
10個のステップでひとつずつ、ステップを解説していきますね。
Laravelにサブスク申込機能を取り入れたい方は、参考にしてみてください。
Laravel8にSTRIPEでサブスクリプション機能を付ける10ステップ
10ステップは下記のとおりです。
- Stripeのテストアカウント作成
- Stripeの商品作成
- Laravelのプロジェクト作成
- LaravelにCachierパッケージをインストール
- Laravelのマイグレーション実施
- Laravelの.envファイルにStripeのAPIを設定
- UserモデルファイルにBillable追加
- ルート設定
- コントローラーの作成
- フォームの作成
①Stripeのテストアカウント作成
まずはSTRIPEでテストアカウントを作ります。
必要なのはメールとURLぐらい。
なお、テストアカウントは後から本番用に移行できますが、その際には本番用アカウント作成が必要となります。
本番アカウント用のちょっと面倒な質問への対応は、こちらの記事で解説しているので困ったら参考にしてくださいね。
②Stripeの商品作成
Stripeのアカウント作成後、商品を作成します。
左側のメニューより【商品】を選択。
【商品を追加】ボタンを押して、サブスクプランを作成しておきます。
価格欄で【継続】を選択しましょう。
③Laravelのプロジェクト作成
次にLaravelのプロジェクトを準備します。
下記のコマンドで新たにプロジェクトを作成できます。
1 |
composer create-project --prefer-dist laravel/laravel newproject |
【newproject】にはお好きな名前をいれてください。
なお、初めてLaravelを使う場合には、事前にインストールが必要なものがいくつかあります。
詳しくは、Laravelのインストールと初期設定を解説した下記記事をご覧ください。
④LaravelにCachierパッケージをインストール
次にLaravelにCachierパッケージをインストールします。
1 |
composer require laravel/cashier |
2020年11月11日現在、バージョン12をインストールできます。
“memory size exhausted” エラーが出た場合には、php.iniでメモリの容量設定を変更してください。
詳しくはこちらの記事の「メモリ不足エラーになった場合」をご覧ください。
⑤Laravelのマイグレーション実施
Cachierインストール後、マイグレーションを行います。
1 |
php artisan migrate |
これによって、データベースのユーザーテーブルに4個のカラムが自動で追加されます。
さらにsubscriptionsテーブルとsubscription_itemテーブルが作成されます。
今後サブスクリプションを作成・更新すると、追加されたカラム・テーブルへ自動で値が入っていきます。
⑥Laravelの.envファイルにStripeのAPIを設定
次にStripeのキーをLaravelに埋め込みます。
公開可能キー・シークレットキー・商品APIキーの3つを埋め込んでいきましょう。
Stripe側でキーを取得
Stripeにログインすると、【ホーム】メニューに【テストAPIキーの取得】が表示されます。
【公開可能キー】と【シークレットキー】をそれぞれコピーします。
さらに商品ページへ移動し、作成した商品の【API ID】をコピーしておきます。
Laravel側にキーを貼り付ける
Laravelの.envファイルを開き、この3個のキーを貼り付けます。
さらにapp/configのservices.phpファイルに、上記の情報を設定しておきます。
1 2 3 4 5 |
'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; を追加します。
1 2 3 4 5 6 |
use Laravel\Cashier\Billable; class User extends Authenticatable { use Billable; } |
これによって、サブスク作成を実行するメソッドが使えるようになります。
⑧ルート設定
次に、Laravelプロジェクト内の routes/web.phpファイルに、ルート設定を記述します。
特にStripeだから特別、ということはなく、通常どおりでOK。
フォームを表示するルート設定と、フォームを投稿後のルート設定のふたつを準備しておきます。
1 2 |
Route::get('/subscription', 'StripeController@subscription')->name('stripe.subscription'); Route::post('/subscription/afterpay', 'StripeController@afterpay')->name('stripe.afterpay'); |
ひとことメモ
Laravel8ではコントローラーのパスが通っていないので、コントローラー名の前にパスを入れねばなりません。
あらかじめ app/ProvidersのRouteServiceProvider.php の29行目を有効にしておいてください。
⑨コントローラーの作成
次にコントローラーを作成します。
下記コマンドを実行してください。
1 |
php artisan make:controller StripeController |
app/Http/Controllers に StripeController.phpが作成されます。
ファイルを開き、上のほうに下記を入れておきます。
1 2 3 4 5 6 7 |
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; |
次にフォーム表示用と、フォーム投稿後用のコントローラーを作ります。
フォーム表示用コントローラー
フォーム表示用のコントローラーは下記のとおり。
1 2 3 4 5 6 |
public function subscription(Request $request){ $user=Auth::user(); return view('post.subscription', [ 'intent' => $user->createSetupIntent() ]); } |
ここで 'intent' => $user->createSetupIntent() の箇所は、Setup Intentを作成するために入れています。
こうしておくことで、ビュー側で支払い方法(payment method)に関する情報を集められます。
サブスク作成時には、この支払方法情報が必要になります。
フォーム投稿後用コントローラー
フォーム投稿後用のコントローラーは下記のとおり。
最後の【ルート設定】には、処理後に表示させたいページのルートを入れておいてください。
特になれけば、 return back(); としておくと良いでしょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
.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】
1 2 |
<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】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
{{-- ヘッダー部分の設定 --}} @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をいれます。
1 |
<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を入れていきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
<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’) }}”)
コメント
はじめまして! いつも記事を参考にさせて頂いております、ありがとうございます!
「Laravel8にSTRIPEでサブスクリプション機能を付ける10ステップ」の記事拝見させていただきました。
そこで記事の通り、実装してみたところ
“resource_missing
This customer has no attached payment source or default payment method.”というエラーが出てつまずいています。
開発環境(localhost:8000など)の影響でstripeとうまく連携できていないこともあるのかなと考えました。
こちらは「php artisan serve」 で立ち上げて開発してますでしょうか…?
恐縮ですがご回答いただたら嬉しいです!
柳田さん、コメントありがとうございます^^
記事通りに試して頂いたのにエラーになってしまったとのこと、残念です。
こちら試してみましたが php artisan serveでサーバーを立ち上げた環境でも動作しました。
いただいたエラーメッセージで検索してみましたが、人によって解決方法が様々で、イマイチ原因が分かりません。
エラーメッセージをそのまま読むと、payment methodがちゃんと設定されていない感じですよね。
.envやconfigの設定が間違えていないか、subscriptionメソッドの設定が正しく入っているか、ビューファイルから情報がちゃんと受け渡されているか。
このあたりが原因の可能性があります。
以前、paymentMethodのエラーについて書いた記事があります。
本ブログの記事に沿ってコードを入れて頂いているので該当しないと思いますが、念の為、記事のリンク貼りますね。
↓↓
https://biz.addisteria.com/laravel-stripe-paymentmethod/
ご回答ありがとうございます!!また、詳しいアドバイスまで恐れ入ります…!
貼っていただいた参考記事のように、今自分が開発している環境(laravel や cashier、その他)のバージョンの関係があるのかもしれません。
お忙しいところありがとうございました。
再度、config設定やバージョンを見直して原因調査していきます!
柳田さん、
その後 新しい環境で試したところ、わたしのほうでもエラーを再現しました!
記事内のコードを書き直したので、よろしければ、再度お試しください。
コメント、ありがとうございました。
わざわざありがとうございます!!大変助かります!
私の方でも実装してみたいと思います!
初めまして。とても詳しく書かれている本稿が大変役に立っております。
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.原因についてネットで調べましたがイマイチ解決策が分からず…
もしご存知でしたらお教えいただけませんでしょうか?
こんにちは!
こちらAPIキーが正しくセットされていない可能性があります。
Stripe側でちゃんと取得されているとすると、.envまたはconfigでの
設定が間違えていないか、確認してみてください。
また、設定変更がLaravel側に反映されていない可能性も。
php artisan config:clear
を行ってみてください。
キャッシュクリアについては、別記事にて解説してます:https://biz.addisteria.com/cache_clear/
基礎編からお世話になり、いつも拝見し勉強させていただいています!
いま、こちらの記事を参考に(というかほぼコピペさせていただいたんですが、、笑)ページをつくっているのですが、テストをしてみるとカード内容の入力フォームが、枠になっているのに入力できない状態になっております。
どのような原因が考えられるか教えていただけないでしょうか。よろしくお願いいたします。
コメントありがとうございます。嬉しいです!
またご質問の件ですが、もしかしたら広告ブロック機能(AdBlock)を使っていらっしゃったりしますか?
もしそうでしたら、一度止めて、試してみてください。
お返事いただいて、ありがとうございます!
確認してみたのですが、広告ブロック機能等は使っておらず、なかなか原因がつかめずにいます。teratailにも質問してみたのですが未解決です。
もう一度自分でも考えてみますので、もしなにかほかにも原因になりそうなものがあれば、教えていただけると幸いです。よろしくお願いいたします!
JavaScriptが効いていないのが原因だと推測されます。
このあたりがあやしいですが、他のものも含めて、
ちゃんと入っているか、また入れる位置も正しいかチェックしてみてください。
あ、コード入れた部分、コメントから消えちゃってました。
括弧部分全角にしておきますね。
<script src=”https://js.stripe.com/v3/”></script>
↑↑
これが抜けていないか、正しい場所にあるか確認してみてください