今回は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と調べていただきアカウントを作成してください
作成ができたらテスト環境用の箱(サンドボックス)を作成します。
作成が完了したらこのような画面になるかと思います。
image.png
ここまできたら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のここに書いてあります。
image.png

STRIPE_WEBHOOK_SECRETの前に設定が必要です。
stripeの左下の開発者→webhook→概要→送信先を追加を押すと
image.png
このような画面が出てきます。
下部でイベントを追加するのですが、今回はとりあえずこの三つを選んでください

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が発行されます。
画像の黄色文字の箇所です。
image.png

こちらのurlを下記のように語尾に付け足した形でstripeのエンドポイントに登録します

 https://renewable~~~~~~~~~~~ngrok-free.dev/stripe/webhook

作成が完了するとこのような画面になります
image.png
この画面右下の画像の箇所がSTRIPE_WEBHOOK_SECRETにいれる値になります
image.png

コントローラーとルート作成

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');