【Stripeコンビニ決済】RailsでWebhookハンドラを実装する
こんちには、株式会社スペースマーケットでエンジニアをしているtchmrです。
先日公開したStripeコンビニ決済の記事で割愛したWebhookハンドラについて書きたいと思います。
Stripeには任意のエンドポイントに対して特定のイベントを送信するWebhook機能があります。
これを利用することでStripe内のデータ更新結果を自社のアプリケーションに取り込むといったことができます。
本記事では以下の例をご紹介します。
購入者がコンビニで支払を完了 → Stripe(支払いステータス更新) → Webhook連携 → 自社アプリケーション(支払いステータス更新)
StripeのWebhookハンドラを作成する
大まかな手順は以下のとおりです。
- StripeでWebhookの設定を行う
- ルーティング設定を行う
- ルーティングに対応した処理を記述する
3-1. コントローラ作成
3-2. サービスクラス作成
1. StripeでWebhookの設定を行う
まずは、Stripeのダッシュボード(あるいはAPI)からWebhookの設定を行う必要があります。
ここでの留意点は以下のとおりです。
- 本番/テスト環境それぞれで独立したWebhookを作成することができます。
- Stripeダッシュボードで本番/テスト環境を切り替えられます
- リッスン対象のアカウント
- 連結アカウントではなく本体アカウントの方を選択します。
- リッスン対象のイベント
- charge.created(支払情報の作成イベント)
-
payment_intent.created
も検討しましたが、連携されるevent情報からPIに紐づくTransferの情報が上手く取得できなかったため上記を選択しました。この点が特に問題がないアプリケーションならこちらを採用しても良いかもしれません。
-
- charge.created(支払情報の作成イベント)
2. ルーティング設定を行う
1.でWebhook設定時にエンドポイントを設定しています。
これに対応するルーティングをアプリケーション側で作成します。
3. 処理を記述する
コントローラの作成
対応するコントローラを作成していきます。
以下、作成にあたり意識したいポイントです。
- コントローラは見通しの良い状態にしておく
- Webhookごとにコントローラを分離
- 複数のWebhookを扱う場合でも独立性、可読性を高く保ちやすいです。
- 処理はサービスクラスに切り出す
- コントローラではサービスを呼び出すだけとすることでシンプルな作りにしやすいです。
- Webhookごとにコントローラを分離
サンプルコード
class Webhook::KonbiniPaymentSuccessController
# コンビニ決済 支払成功イベントWebhook処理
def update
result = ::Konbini::UpdateStatusAfterPaymentSuccessService.call(event: @event, object: @object)
if result.present?
render json: { message: result[:message] }, status: result[:status] and return
end
render json: { message: '正常に処理が完了しました。' }, status: 200
rescue StandardError => e
Rails.logger.error(e.message)
render json: { message: e.message }, status: 500
end
...省略
end
jsonをrenderすることでStripeダッシュボードのWebhookログにレスポンスを表示することができます。
サービスクラスの作成
連携されたWebhookを元にステータスを更新する処理を記述します。
class Konbini::UpdateStatusAfterPaymentSuccessService
attr_accessor :event
attr_accessor :object
def self.call(event:, object:)
new(event: event, object: object).call
end
def initialize(event:, object:)
self.event = event
self.object = object
end
# コンビニ支払い 支払成功時の更新処理(Webhook受信時)
def call
# リクエストの検証(実装省略)
validated_result = validate_event_data
if validated_result.present?
return { message: validated_result[:message], status: validated_result[:status] }
end
konbini_payment = KonbiniPayment.find_by(payment_intent_id: object[:payment_intent])
return return { message: 'リソースが存在しません', status: 404 } if konbini_payment.blank?
reservation = konbini_payment.reservation
reservation.status = '支払済'
konbini_payment.status = '支払済'
reservation.save!
stripe_konbini_payment.save!
nil
end
private
def validate_event_data
# 必要に応じて入力値の検証を実施
...
end
end
テスト
テスト環境では、コンビニ支払い用のPaymentIntentを作成して一定時間(デフォルト3分)経過するとコンビニでの支払いが完了したこととなり上記で設定したWebhookが連携されます。
メールアドレスに特定の文字列を含むことで即時支払等にすることもできます。
また、連携したWebhookはStripeダッシュボード上から閲覧や再送することができるのでテストの際に役立ちます。
最後に
スペースマーケットでは一緒に働いてくれる仲間を募集中です。
少しでも興味を持っていただけたらぜひ採用ページをご覧ください。
スペースを簡単に貸し借りできるサービス「スペースマーケット」のエンジニアによる公式ブログです。 弊社採用技術スタックはこちら -> whatweuse.dev/company/spacemarket
Discussion