今回はlaravelにて決済機能を作成したのでメモがてら共有します。
開発環境
- Windows
- laravel Ver.12
- ngrok
- xampp
流れ
・laravelインストール
↓
・ngrok登録
↓
・ストライプ設定
↓
・xデバックを登録
↓(↑ここまでが準備)
・コントローラーの作成
・ルート作成
・購入画面
・ストライプ決済画面
・決済後の画面(2種類) ・webhookにて裏で情報を受け取る
インストール(laravel ngrok) 準備編
laravelインストール
【前提条件】
・laravelの基礎学習が完了済み
・xamppインストール済み
laravelのインストールに関してはこちらの記事を参考にしてください
DB接続ができたら戻ってきてください
https://qiita.com/Engineering-seiya/items/f9358971fc3e830ddab4
ngrok設定
ngrokのインストール方法はこちらです
https://qiita.com/Wadachan69/items/590b5b9c1c916b59a54c
- ngrokがなぜ必要なのか
stripeにてwebhookを使います。webhookの場合帰ってくるURLがhttpsにてデータが帰ってくるため開発環境ではhttpしか使えないのでhttpsでデータの受け取りができないためngrokを使いhttpsのurlをローカルのurlに変換してくれます。
stripeの画面の設定方法は後ほどお伝えします。
ストライプ設定
stripeと調べていただきアカウントを作成してください
作成ができたらテスト環境用の箱(サンドボックス)を作成します。
作成が完了したらこのような画面になるかと思います。
ここまできたらokです。
xデバックのインストール
【xデバックとは】
xデバックはポイントを置いてその地点に値がきているのかだったり一個ずつ処理がどのように動いているのか追えるようになります。
・xデバックの設定はこの記事を参考にしてください
https://qiita.com/hitotch/items/7b2895f9822ded3fa7db
Stripe PHP SDKをインストール
これはlaravel側で簡単にstripeを使えるように整えてくれるセットです
必ずインストールしてください
composer require stripe/stripe-php
envファイルの設定
ここからが本題です。
envファイルを開きこの三つを一番下に付け足してください設定します
STRIPE_PUBLIC_KEY=
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
STRIPE_PUBLIC_KEYとSTRIPE_SECRET_KEYはstripeのここに書いてあります。
STRIPE_WEBHOOK_SECRETの前に設定が必要です。
stripeの左下の開発者→webhook→概要→送信先を追加を押すと
このような画面が出てきます。
下部でイベントを追加するのですが、今回はとりあえずこの三つを選んでください
checkout.session.completed
checkout.session.expired
payment_intent.payment_failed
これらはstripe画面で決済が完了した際に送られてくるデータは何がいいですかというやつで今回はカード決済なのでこの三つを選んでいます
‘checkout.session.completed’ カード決済が成功して購入完了
‘checkout.session.expired’ 支払わずにセッションが期限切れ
‘payment_intent.payment_failed’ カードが拒否されるなど決済失敗
次に進めるとエンドポイントの設定があります。
ここでngrokを起動させます。
起動させる場所はどこでもいいのでターミナルを開いて下記を実行します。
ngrok http 8000
起動させるとurlが発行されます。
画像の黄色文字の箇所です。
こちらのurlを下記のように語尾に付け足した形でstripeのエンドポイントに登録します
https://renewable~~~~~~~~~~~ngrok-free.dev/stripe/webhook
作成が完了するとこのような画面になります
この画面右下の画像の箇所がSTRIPE_WEBHOOK_SECRETにいれる値になります
コントローラーとルート作成
php artisan make:controller コントローラー名 --resource
今回作成するコントローラー名はPurchaseControllerです
コントローラーに下記のメソッドを追加します
stripeの決済画面に飛ばす処理
//PurchaseController
//↓画面上部で宣言
use Stripe\StripeClient;
//↑画面上部で宣言
public function purchase(Request $request)
{
$stripe = new StripeClient(config('stripe.secret'));
$checkout = $stripe->checkout->sessions->create([
'payment_method_types' => ['card'],
'line_items' => [[
'price_data' => [
'currency' => 'jpy',
'unit_amount' => 2000,
'product_data' => [
'name' => 'ahagate',
],
],
'quantity' => 1,
]],
'mode' => 'payment',
'success_url' => route('purchase.success'),
'cancel_url' => route('purchase.cancel'),
]);
return redirect($checkout->url); // ← これも無いと画面が動かない
}
このコントローラーに飛ばすためのルート
//決済画面へ
Route::post('/purchase', [PurchaseController::class, 'purchase'])->name('purchase');
次はwebhookの処理です
エンドポイントを設定する際に語尾に/stripe/webhookを付け足した形で登録したと思います。
なので/stripe/webhookのurlでコントローラーで処理できるようにルートとコントローラーを設定します
ルート
Route::post('/stripe/webhook', [PurchaseController::class, 'handle']);
コントローラー
public function handle(Request $request)
{
$payload = $request->getContent();
$signature = $request->header('Stripe-Signature');
$secret = config('stripe.webhook_secret');
// 例外を受け止める唯一の方法が try / catch
try {
$event = \Stripe\Webhook::constructEvent($payload, $signature, $secret);
} catch (\Exception $e) {
// 署名が合わない=本物じゃない、または secret が間違い
return response()->json(['error' => 'invalid signature'], 400);
}
if ($event->type === 'checkout.session.completed') {
// 決済成功の場合の処理を書く
}
if ($event->type === 'checkout.session.expired') {
// カゴ落ち処理(任意。何もしなくてもよい)
}
if ($event->type === 'payment_intent.payment_failed') {
// 決済失敗の記録(任意)
}
return response()->json(['status' => 'ok']);
}
あと一つ大事な設定があります
Stripeは外部サーバーで合言葉(CSRFトークン)を持っていないので、入口で弾かれて 419でエラーになってしまいます。
開発環境だとwebhookで値が受け取れない状態になってしまうので
bootstrap/app.php の withMiddleware の中に、除外設定を追加します。
いまはこうなっています
->withMiddleware(function (Middleware $middleware): void {
//
})
ここをこう変えます
->withMiddleware(function (Middleware $middleware): void {
$middleware->validateCsrfTokens(except: [
'stripe/webhook',
]);
})
View
今回laravel側で設定するviewは簡易的なものですが共有しときます
viewsファイル内にstripeファイルを作成し
下記を作成します
・決済ボタン
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ config('app.name', 'Laravel') }}</title>
</head>
<body>
<form action="{{ route('purchase') }}" method="POST">
@csrf
<button type="submit">購入する(Stripeへ)</button>
</form>
</body>
</html>
ルート
Route::get('/', function () {
return view('welcome');
});
・キャンセル用
<div>
<p>支払いがキャンセルされました</p>
</div>
ルート
Route::get('/purchase/cancel', [PurchaseController::class, 'cancel'])->name('purchase.cancel');
・決済成功用
<div>
<p>成功</p>
</div>
ルート
Route::get('/purchase/success', [PurchaseController::class, 'success'])->name('purchase.success');