【Stripe決済】laravel + JavaScriptでStripeクレジットカード決済をやってみた

## はじめに
決済機能を実装するにあたり、StripeAPIを使用して実装することになった。

今回の記事はstripeAPIに関しての決済備忘録になる。
忘れないようにメモしとく。

## ざっくりな環境
+ Docker
+ Laravel11
+ php8.3
+ MySQL8.0
+ JQuery
+ HTML
+ CSS
+ Bootstrap

## 流れ
まず流れから把握しておく。

+ **フロント側でPaymentMethodIDを作成する**
Stripe(多分他の決済機能も一緒)はクレジットカード情報を送信したり、取得したりするのはセキュリティ的に良くないため、カード情報を直接保存せず、トークン化された情報を使って、セキュリティを確保するらしい。これにより、PCI DSS（Payment Card Industry Data Security Standard）に準拠した安全な方法でカード情報を管理ができるようになるみたいやな。
ここでまあまあハマった。
<br>

+ **フロント側で作成したPaymentMethodIDを使用してサーバー側が決済**
上記記述のPaymentMethodIDをもとに決済をサーバー側で実装する。
こっちはすんなりいった。
<br>

+ **前提**
StripeはデフォルトでElementsを生成してくれる便利な機能がある。
でもフォームはカスタムしたレイアウトにしたいと思ってElementsをなるべく使用しない方向で考えていたけど厳しかったから断念して大人しく用意されているElementsを使用した。
特に有効期限とかセレクトボックスにしてみたかったりしたけど断念（多分できない）。
なんとなくオリジナルフォームにできるように頑張った結果を今回は記載しておく。


## JavaScriptでフロント側を実装
+ **PaymentMethodIDを作成してFormをサブミット**

```index.html
<form id="credit" method="POST" action="{{ ここで決済実装URLを指定 }}">
    <div id="card_number"></div>
    <div id="card_expiry"></div>
    <div id="card_security"></div>
    <div id="card_errors"></div>
    <button type="submit">決済</button>
</form>

    <script>
        // あらかじめLaravelで設定したStripeのAPIキー(public_key)を呼び出す。
        const stripe_public_key = "{{ config('stripe.stripe.key') }}";
        const stripe = Stripe(stripe_public_key);
        const elements = stripe.elements();

        // カード情報の入力フィールドを作成
        var cardNumber = elements.create('cardNumber');
        cardNumber.mount('#card_number');
        var cardExpiry = elements.create('cardExpiry');
        cardExpiry.mount('#card_expiry');
        var cardCvc = elements.create('cardCvc');
        cardCvc.mount('#card_security');

        // フォームの送信処理
        var form = document.getElementById('credit');
        form.addEventListener('submit', async function(event) {
            event.preventDefault();

            // フォーム送信ボタンを無効化して二重送信を防止
            document.querySelector('button[type="submit"]').disabled = true;

            try {
                // PaymentMethodを作成
                const result = await stripe.createPaymentMethod({
                    type: 'card',
                    card: cardNumber, // カード情報を渡す
                });

                if (result.error) {
                    // エラーメッセージを表示
                    var displayError = document.getElementById('card_errors');
                    displayError.textContent = result.error.message;

                    // 送信ボタンを再度有効化
                    document.querySelector('button[type="submit"]').disabled = false;
                } else {
                    // PaymentMethod IDをサーバーに送信
                    console.log(result.paymentMethod.id);
                    stripeSubmit(result.paymentMethod.id);
                }
            } catch (error) {
                console.error('Error creating payment method:', error);

                // 送信ボタンを再度有効化
                document.querySelector('button[type="submit"]').disabled = false;
            }
        });

        // PaymentMethod IDをフォームに追加して送信
        function stripeSubmit(paymentMethodId) {
            var form = document.getElementById('credit_submit');
            // クレジット情報埋め込み
            var hiddenInput = document.createElement('input');
            hiddenInput.setAttribute('type', 'hidden');
            hiddenInput.setAttribute('name', 'payment_method_id');
            hiddenInput.setAttribute('value', paymentMethodId);
            form.appendChild(hiddenInput);
            // フォームを手動で送信
            form.submit();
        }

    </script>

```

## Laravelで決済実装
+ **Stripeのライブラリをインストール**
```shell
composer require stripe/stripe-php
```
<br>

+ **実際にPaymentMethodIDを使用して決済実装**

```IndexController.php
    public function CreditExe(Request $request)
    {
        // StripeAPIキーの秘密鍵を呼び出す
        Stripe::setApiKey(config('stripe.stripe.secret'));

        // 実装決済
        $payment = PaymentIntent::create([
            // 値段
            "amount" => 5000,
            // 通過設定
            "currency" => 'jpy',
            // フロントで作成したPaymentMethodID
            "payment_method" => $request->input('payment_method_id'),
            // 支払い確定
            "confirm" => true,
            // リダイレクト防止
            "automatic_payment_methods" => [
                'enabled' => true,
                'allow_redirects' => 'never', // リダイレクトを無効にする
            ],
        ]);

        // リダイレクト
        return redirect()->route('sample')->with('success', 'お支払いが完了しました。');
    }
```

特に気をつけることはなさそう。

+ **"confirm" => true,**
PaymentIntentの作成と同時に支払いが即座に確定されるらしい。

<br>

+ **PaymentMethodIDについて**
PaymentMethodIDは使い回しが可能なため、このトークンをDBに保存しておくとクレジット情報をシステムに保持していなくても２回目以降は自動決済が可能にすることができる。そしてこのトークンからカードブランドや下四桁のカード情報も引っ張ってくれるみたいなので、本当にDBでクレジットカード情報を保持する必要がない。

## まとめ
いやぁ最初は何事も大変！