Laravelを使用したStripeのCheckoutでの決済済みかどうかの判定の実装
前回と前々回の記事の続きです。
概要
今回はCheckoutで決済した後の決済、未決済の判定について紹介していきたいと思います。
バックエンドはLaravelを使用しています。
決済、未決済の判定をする方法は以下のように2種類あり、今回はそれらについて解説しています。
・StripeのPaymentIntentの検索APIを使用し判定する方法
・Webhookを使用して判定する方法
結論、2種類の方法のうちWebhookのほうが早いので、そちらをお勧めします。
StripeのPaymentIntentの検索APIを使用し判定する方法
PaymentIntentの検索APIを使用し、PaymentIntentのstatusがsucceededだと決済が完了していることがわかります。
この方法は、Webhookに比べると遅いです。
決済して10秒くらい経ってもstatusがかわらないことがあるので、リトライ処理を作ったほうがいいです。
PaymentIntentとは
「支払いされたか、支払い方法はどうしたか」など支払いに関する情報が保存されているオブジェクトです。
これはCheckoutを使用した場合、決済をした後に作成されます。
なぜPaymentIntentの検索APIを使用したか
Checkoutのセッションでも決済、未決済判定ができますが、
セッションIDをDBに保存しておくと考えたときに、
「決済前に2人セッションを作成したら、決済されるセッションと決済されないセッションができるので、どちらをDBに保存すればいいのかわからない」
という懸念点があります。
また、セッションの検索APIは、セッションIDでしか検索することができません。
よって、別の方法を考える必要がありました。
そこでMetadataという、自由に構造化情報を設定できるパラメーターを使用して検索できるPaymentIntentの検索APIを使用した。
実装
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を使う方法より、早く支払い完了であることがわかるのでお勧めです。
準備
- Webhookのendpoint_secretの取得
本番または開発環境
https://dashboard.stripe.com/test/webhooks/create?endpoint_location=hosted
ローカル環境
- 受信するイベントを絞り込む
本番または開発環境のみで可能で、エンドポイント作成の時の「リッスンするイベントの選択」で
「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