0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Stripe Checkout Webhookを使って、確保した在庫を戻す方法

Last updated at Posted at 2023-04-15

アプリケーションの流れ

  1. ユーザーが購入ボタンをクリック
  2. 在庫を確保
  3. stripe決済画面へ遷移
  4. ユーザーがアプリケーションから離脱
  5. 30分経っても決済がない場合、確保した在庫を戻す

チェックアウトセッションを作る

https://stripe.com/docs/payments/checkout/managing-limited-inventory
こちらのドキュメントを参考にしてセッションを作ります。

またline_itemmetadataを設定して、後で商品idとwebhookを使って在庫のDB処理をします。

CartController.blade.php
public function checkout()
    {
        $user = User::findOrFail(Auth::id());
        $products = $user->products;

        $lineItems = [];
        foreach ($products as $product) {
            $quantity = Stock::where('product_id', $product->id)->sum('quantity');

            if ($product->pivot->quantity > $quantity) {
                return redirect()->route('user.cart.index')->with(['message' => '在庫がなくなりました。', 'status' => 'alert']);
            } else {
                $lineItem = [
                    'quantity' => $product->pivot->quantity,
                    'price_data' => [
                        'currency' => 'jpy',
                        'unit_amount' => $product->price,
                        'product_data' => [
                            'name' => $product->name,
                            'description' => $product->information,
                            'metadata' => [
                                'product' => $product->id
                            ]
                        ],
                    ],
                ];
                array_push($lineItems, $lineItem);
            }
        }

        //Stripe決済画面に遷移する前に在庫を確保
        foreach($products as $product) {
            Stock::create([
                'product_id' => $product->id,
                'type' => \Constant::PRODUCT_LIST['reduce'],
                'quantity' => $product->pivot->quantity * -1,
            ]);
        }

        \Stripe\Stripe::setApiKey(env('STRIPE_SECRET_KEY'));
        header('Content-Type: application/json');

        $session = \Stripe\Checkout\Session::create([
            'line_items' => [$lineItems],
            'mode' => 'payment',
            'expires_at' => time() + (1800),
            'success_url' => route('user.cart.success'),
            'cancel_url' => route('user.cart.cancel'),
        ]);

        $publicKey = env('STRIPE_PUBLIC_KEY');

        return view('user.checkout', compact('session', 'publicKey'));
    }

ルーティング設定

web.php
use App\Http\Controllers\WebhookController;

Route::post('/stripe/webhook', [WebhookController::class, 'webhook']);

webhookの設定

stripeダッシュボード→開発者→webhook→エンドポイントの追加まで進み、エンドポイントURLとリッスンするイベントを設定します。

今回はStripeのWebhookを利用するにあたって、ローカルでHTTPSテスト環境をNGROKで構築しています。
NGROKで生成したURLを使用

エンドポイントには 「https://~~~~~~~~/stripe/webhook」と設定し、リッスンするイベントには「chekout.session.expired」を追加。

スクリーンショット 2023-04-15 11.09.29.png

webhookメソッドでDB処理

変数endpoint_secretにはエンドポイントを追加した際に署名シークレットというところに文字列があるのでそれを代入してください。
スクリーンショット 2023-04-15 12.09.53.png

line_itemsを取得するにはallLineItemsメソッドの引数にセッションIDを入れるとデータが取れます。

$stripe->checkout->sessions->allLineItems($session->id);

上記のままだとline_itemに設定したmetadataが取得できないので、allLineItemsメソッドの引数にもう一つ以下を加える。

$stripe->checkout->sessions->allLineItems($session->id, ['expand' => ['data.price.product']]);

在庫処理する全体像

WebhookController.php
<?php

namespace App\Http\Controllers;

use App\Models\Stock;
use Illuminate\Http\Request;

class WebhookController extends Controller
{
    public function webhook(Request $request)
    {
        \Stripe\Stripe::setApiKey(env('STRIPE_SECRET_KEY'));

        $endpoint_secret = env('STRIPE_WEBHOOK');

        $payload = $request->getContent();
        $sig_header = $request->header('stripe-signature');

        $event = null;
        try {
            $event = \Stripe\Webhook::constructEvent(
                $payload,
                $sig_header,
                $endpoint_secret
            );
        } catch (\UnexpectedValueException $e) {
            // Invalid payload.
            return response()->json('Invalid payload', 400);
        } catch (\Stripe\Exception\SignatureVerificationException $e) {
            // Invalid Signature.
            return response()->json('Invalid signature', 400);
        }

        //Stripe決済画面に遷移し、30分経っても決済がない場合、確保しておいた在庫を戻す
        if ($event->type == 'checkout.session.expired') {
            $session = $event->data->object;

            $stripe = new \Stripe\StripeClient(env('STRIPE_SECRET_KEY'));
            $line_items = $stripe->checkout->sessions->allLineItems($session->id, ['expand' => ['data.price.product']]);

            foreach ($line_items as $line_item) {
                $metadata = $line_item->price->product->metadata;
                $product_id = $metadata->product;
                $quantity = $line_item->quantity;

                Stock::create([
                    'product_id' => $product_id,
                    'type' => \Constant::PRODUCT_LIST['add'],
                    'quantity' => $quantity
                ]);
            }
        }

        return response()->json('ok', 200);
    }
}

CSRF除外設定

最後にwebhookの処理時にpostメソッドを使いますが、CSRF対策できないので、Laravelで特定のルート(stripe/webhook)だけを除外します。

app/Http/Middleware/VerifyCsrfToken.phpを開いて以下を設定

VerifyCsrfToken.php
protected $except = [
        'stripe/webhook'
    ];
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?