🦁

Stripeを用いた決済処理の実装方法②

2023/07/16に公開

背景

前回の記事で、Stripeを用いた「Checkoutセッションの作成から決済ページへリダイレクトする実装方法」を紹介しました。今回はその続きで、Stripeの決済完了後の処理の実装方法を解説していきたいと思います。

これからStripeの決済完了後の処理を実装しようとしている方の参考になれば幸いです。

 

決済処理の流れ(前回の続き)

前回は、決済処理の大まかな流れを紹介しました。今回は、決済処理完了後の流れを記載いたします。大まかな流れは下記の通りです。

①顧客が購入を完了する準備ができると、アプリケーションは新しい Checkout セッション(Webサイトの購入者に対する単一のアクティブなセッションまたはエンゲージメントのこと)を作成します。(前回実装済み)

②Checkout セッションは、顧客を Stripe がオンラインで提供する決済ページにリダイレクトする URL を提供します。(前回実装済み)

③顧客は決済ページに決済情報を入力し、取引を完了します。(前回実装済み)

④取引終了後、Webhook は checkout.session.completed イベントを使用して注文のフルフィルメントを実行します。(今回はここから実装。)

⑤Webhook のイベントを検知し、注文データを作成・保存。

 

ここで出てきた Webhook について解説します。

Webhookとは、Webアプリケーション間でのリアルタイムのデータ送信や通知を可能にする仕組みです。
アプリケーションが特定のイベント(今回の場合、checkout.session.completed イベント)が発生した場合に事前に指定されたURL(エンドポイント。今回の場合、localhost:3010/api/v1/webhooks )に対してデータを送信します。このURLは、データを受け取る側のアプリケーションが提供します。データの送信方法は、通常はHTTPプロトコルを使用してPOSTリクエストを送信する形式です。

ここで、決済完了後の処理の実装方法を紹介する前に、Stripe CLIを使用し、StripeのWebhookをローカルサーバーに転送し、Webhook のイベントログをモニタリングする方法を紹介します。
Strip の Webhook イベントは、stripe listen コマンドを用いることでモニタリングすることができます。
(stripe listen コマンドについてはこちらのリンクを参照。)

コマンドは下記の通りです。

ターミナル
$ stripe listen --forward-to localhost:3010/api/v1/webhooks

 
次に、④の実装方法を解説していきます。

 

注文のフルフィルメントを実行する処理を作成

こちらのリンクを参考に、注文のフルフィルメントを実行する処理を実装することができます。

まず、Stripeから送信されるイベント(今回の場合、checkout.session.completed イベント)を受信するためのAPI(rails でいうcontrollerのこと)を作成します。

実装内容は下記の通りです。

app/controllers/api/v1/webhooks_controller.rb
class Api::V1::WebhooksController < ApplicationController

  def create
    payload = request.body.read
    sig_header = request.env['HTTP_STRIPE_SIGNATURE']
    endpoint_secret = Rails.application.credentials.dig(:stripe, :endpoint_secret)
    event = nil

    begin
      event = Stripe::Webhook.construct_event(
        payload, sig_header, endpoint_secret
      )

    rescue JSON::ParserError, Stripe::SignatureVerificationError => e
      Rails.logger.debug e
      status 400
      return
    end

    case event.type
    when 'checkout.session.completed'
      session = event.data.object
      user = User.find(session.client_reference_id)
      cart_items = user.cart.cart_items.not_order_confirm
      return unless user

    # ここに注文内容をデータベースに保存する処理を実装予定

    end
  end
end

上記のコードについて解説します。

create アクションにより、StripeのWebhookから送信されたデータを受け取り、処理を実行することができます。

request.body.read は、POSTリクエストのボディからデータを読み取ります。StripeからのWebhookのペイロード(データ)がここで取得されます。

request.env['HTTP_STRIPE_SIGNATURE'] は、リクエストヘッダーからStripeの署名を取得します。この署名は、Stripeからの通知が正当なものであることを検証するために使用されます。

(Stripe の署名についてはこちらのリンク参照。)

Rails.application.credentials.dig(:stripe, :endpoint_secret) は、Stripeの認証用の秘密鍵(secret_key)を取得します。この認証用の秘密鍵(secret_key)は、Stripeからの通知の検証に使用されます。

Stripe::Webhook.construct_event メソッドは、Stripeのライブラリを使用して、ペイロード、署名、認証用の秘密鍵(secret_key)を元に event オブジェクトを構築します。event オブジェクトは、Stripeからの通知に関連する情報を含んでいます。

受け取った event.type が checkout.session.completed の時、Stripeの event オブジェクトからこのあと作成すべき注文データに必要なセッション情報(session)、ユーザー情報(user)、ユーザーが持つカートアイテム(cart_items)を取得します。

つまり、上記のコードでは、StripeのWebhookを受け取り、決済処理が完了した場合に注文情報を作成するための準備を行なっています。

また、ルーティングは、下記のように設定しています。

config/routes.rb
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do

      -- 省略 --

      resources :webhooks, only: [:create]
    end
  end
end

 

注文データを作成・保存

最後に、注文データ(Order)を作成する処理を実装していきます。実装結果が以下の通りです。

app/controllers/api/v1/webhooks_controller.rb
class Api::V1::WebhooksController < ApplicationController

  def create
    payload = request.body.read
    sig_header = request.env['HTTP_STRIPE_SIGNATURE']
    endpoint_secret = Rails.application.credentials.dig(:stripe, :endpoint_secret)
    event = nil

    begin
      event = Stripe::Webhook.construct_event(
        payload, sig_header, endpoint_secret
      )
    rescue JSON::ParserError, Stripe::SignatureVerificationError => e
      Rails.logger.debug e
      status 400
      return
    end

    case event.type
    when 'checkout.session.completed'
      session = event.data.object
      user = User.find(session.client_reference_id)
      cart_items = user.cart.cart_items.not_order_confirm
      return unless user

      # 注文データの作成
      ApplicationRecord.transaction do
        order = Order.create_order(session)
        session_with_expand = Stripe::Checkout::Session.retrieve({ id: session.id, expand: ['line_items'] })
        OrderAddress.create_order_address(session, session_with_expand, order)
        CartItem.invalidate_cart_items(cart_items)
      end

      render json: { session: session }, status: :ok
    end
  end
end
app/models/cart_item.rb
class CartItem < ApplicationRecord
  belongs_to :cart
  belongs_to :item

  scope :not_order_confirm, -> { where(invalidated_at: nil) }

  class << self
    def invalidate_cart_items(cart_items)
      cart_items.each do |cart_item|
        cart_item.update(invalidated_at: Time.zone.now)
      end
    end
  end
end

上記のコードについて解説します。

ApplicationRecord.transaction do ~ end のトランザクション処理の中で、Order(注文データ),OrderAddress(お届け先データ)を作成しています。

また、CartItemには時刻が入ると論理削除されるような invalidated_at カラムを用意しています。つまり、注文確定したらカート内の商品(CartItem)が論理削除されるようにしています。

以上が決済完了後の実装方法の解説になります。

 

まとめ

今回は、Stripeの決済完了後の処理を実装する方法を解説しました。ドキュメントだけだとちょっと難しい部分がありましたが、いろんな方々の実装した記事を参考にすることでなんとか実装することができました。今後は、他の支払い方法での決済を実装できるかトライしていきたいです。

Discussion