📘

Laravelを使用したStripeのCheckoutでの決済済みかどうかの判定の実装

2024/01/12に公開

前回と前々回の記事の続きです。
https://zenn.dev/ota_rg/articles/5bb03b17198f58
https://zenn.dev/ota_rg/articles/836c891b481c23

概要

今回はCheckoutで決済した後の決済、未決済の判定について紹介していきたいと思います。
バックエンドはLaravelを使用しています。

決済、未決済の判定をする方法は以下のように2種類あり、今回はそれらについて解説しています。
StripeのPaymentIntentの検索APIを使用し判定する方法
Webhookを使用して判定する方法

結論、2種類の方法のうちWebhookのほうが早いので、そちらをお勧めします。

StripeのPaymentIntentの検索APIを使用し判定する方法

PaymentIntentの検索APIを使用し、PaymentIntentのstatusがsucceededだと決済が完了していることがわかります。
https://stripe.com/docs/api/payment_intents/object#payment_intent_object-status

この方法は、Webhookに比べると遅いです。
決済して10秒くらい経ってもstatusがかわらないことがあるので、リトライ処理を作ったほうがいいです。

PaymentIntentとは

「支払いされたか、支払い方法はどうしたか」など支払いに関する情報が保存されているオブジェクトです。
これはCheckoutを使用した場合、決済をした後に作成されます。

https://stripe.com/docs/api/payment_intents

なぜPaymentIntentの検索APIを使用したか

Checkoutのセッションでも決済、未決済判定ができますが、
セッションIDをDBに保存しておくと考えたときに、
「決済前に2人セッションを作成したら、決済されるセッションと決済されないセッションができるので、どちらをDBに保存すればいいのかわからない」
という懸念点があります。

また、セッションの検索APIは、セッションIDでしか検索することができません。
よって、別の方法を考える必要がありました。

そこでMetadataという、自由に構造化情報を設定できるパラメーターを使用して検索できるPaymentIntentの検索APIを使用した。

https://stripe.com/docs/api/payment_intents/search

実装

metadataの設定

以下のようにCheckoutセッション作成時のpayment_intent_data['metadata']
のパラメーターに自由にmetadataを設定することができます。

$checkout = $stripe->checkout->sessions->create([
            'mode'                   => 'payment', // 支払いモード
            'payment_method_types'   => ['card'],
	            ・
		・
		・
		・
            // PaymentIntentのMetadata設定
            'payment_intent_data'    => [
                'metadata' => [
                    'reference_report_request_id' => 2,
            
                ],
            ],
        ],[
            'idempotency_key' => 'test',
        ]);

サンプルコード

支払いが完了していて、顧客とメタデータのitem_idで絞り込んだPaymentIntentを検索するコードです。
PaymentIntentのstatusがsucceededだと支払いが完了しています。

public function getSession(Request $request): JsonResponse
    {
        $config = config('define.stripe.token');
        $stripe = new StripeClient($config);
        //PaymentIntentsの検索
        $paymentIntents=$stripe->paymentIntents->search([
            'query' => 'status:\'succeeded\' AND customer:\'customer_id\' AND metadata[\'item_id\']:\'1\'',
        ]);
        return response()->json($paymentIntents);
}

重要なところの解説

PaymentIntentsの検索は$stripe->paymentIntents->search()で行っていますが、以下のクエリの書き方に注意する。

  • =が:になる
  • 'が「'」になる

Webhookを使用した方法

StripeのWebhookを使用し、支払い完了イベントである「payment_intent.succeeded」を受け取ったら支払いが完了していることがわかります。

この方法はPaymentIntent検索APIを使う方法より、早く支払い完了であることがわかるのでお勧めです。

準備

ローカル環境
https://dashboard.stripe.com/test/webhooks/create?endpoint_location=local

  • 受信するイベントを絞り込む
    本番または開発環境のみで可能で、エンドポイント作成の時の「リッスンするイベントの選択」で
    「payment_intent.succeeded」を選択することで、支払い成功以外のイベントを受信しなくなる

実装

サンプルコード

public function webhook()
    {
        // 参考:https://stripe.com/docs/payments/checkout/fulfill-orders

        Stripe::setApiKey(env('STRIPE_SECRET'));

        // StripeのWebhookのendpoint_secret
        $endpoint_secret = 'endpoint_secret';

        $payload = @file_get_contents('php://input');
        $sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];
        $event = null;

        try {
            $event = Webhook::constructEvent(
                $payload, $sig_header, $endpoint_secret
            );
        } catch(\UnexpectedValueException $e) {
            // Invalid payload
            http_response_code(400);
            exit();
        } catch(\Stripe\Exception\SignatureVerificationException $e) {
            // Invalid signature
            http_response_code(400);
            exit();
        }

        if($event->type==='payment_intent.succeeded'){
	    $payment_intent = event.data.object
            //支払いが完了した後に行いたい処理をかく
        }

        http_response_code(200);
    }

重要なところの解説

以下のようにpayment_intent.succeededのイベントを受け取るとevent.data.objectでPaymentIntentを取得することができる。
PaymentIntentに事前にメタデータを設定することで、自前DBの商品IDやユーザーIDで誰のものかを特定することができる。

	if($event->type==='payment_intent.succeeded'){
	    $payment_intent = event.data.object
            //支払いが完了した後に行いたい処理をかく
        }

終わりに

今回はLaravelを使用したStripeのCheckoutでの決済済みかどうかの判定の解説をしました。
PaymentIntentのステータスが変わる時間にはムラがあるということが、実装してから分かったので、その対応策を考えたり、リトライ処理を仕込むのが大変でした。
皆さんは最初からWebhookを使いましょう。

Discussion