概要
原著者の許諾を得て翻訳・公開いたします。
- 英語記事: How To Securely Handle Webhook Events From Stripe In Ruby On Rails Application?
- 原文公開日: 2019/12/04
- 著者: Mittal Patel -- RubyとJavaScript、問題解決と業務のオーナーシップを取ることを愛するRails開発者です。コーディング以外では音楽鑑賞、デザート賞味、料理も趣味です。
- サイト: BoTree Technologies -- Ruby on Railsによるエンタープライズアプリケーションの構築を手掛ける開発会社です。RPA、AI、Python、Django、JavaScript、ReactJSにも特化しています。
- 同社のRuby on Rails開発ページ: Ruby on Rails Web Development, Ruby on Rails Developers - BoTree Technologies
Rails: StripeのWebhookイベントをセキュアに扱う方法(翻訳)
StripeはWebペイメントサービスでその名を広く知られたソリューションであり、私たちは支払い処理のために構築した多くのRuby on RailsアプリケーションでStripeを大規模に採用しています。これらのアプリケーションはどれも、Stripeから届くいくつかの重要なイベントに関する通知を受け取って、ワークフローの中で対応する必要があります。
Stripeはwebhookイベントを送信することで、登録されているアプリケーションに対して、さまざまなイベントの発生時に公開エンドポイントに対してHTTP POSTリクエストの形で通知を送信します。このエンドポイント(ルーティング)は一般公開されるので、その気になれば誰でも悪意を持って呼び出せます。したがって、有害なユーザーからエンドポイントを守ることが非常に重要になってきます。Stripeが送信するStripe-Signature
ヘッダーはメッセージの真正性検証に利用され、これによって知らない第三者からのリクエストではなくStripeからのリクエストであることを確認します。
これはstripe_events gemを使うのが最も簡単です。
それでは実装してみましょう。とても興味深い内容になるはずです。
⚓1. gemのインストール(Gemfile)
Gemfileに以下のgemを追加します。
gem 'stripe_event'
⚓2. ルーティングの追加(routes.rb)
mount StripeEvent::Engine, at: '/any-path-you-want'
ここではわかりやすくするために、以下のパスを使うことにします。
mount StripeEvent::Engine, at: '/stripe/webhook' # このURLは変更可能
⚓3. development環境でのテスト
まずはwebhookをローカルでテストするために、サーバーをngrokで公開しましょう。ngrokで一般からアクセス可能なURLを作成できることはもう皆さんご存知ですよね。
私のngrok URLを仮にhttps://9123ab3f.ngrok.io/
だとしましょう(訳注: もちろん既に無効です)。
次に必要なのは、Stripeのwebhookを受けるhttps://9123ab3f.ngrok.io/stripe/webhook
のようなエンドポイントを追加することです。
それではStripeのwebhookをセットアップしましょう。
- Stripeアカウントにログインして、Stripeのダッシュボードに移動する
- Developers > Webhooks を開く(リンク)
- 自分のアカウントをtestモードに切り替える
- Stripe webhookに自分のEndPointを追加し、欲しいイベントを設定する(課金の作成や返金など)
- 設定するEndPointはngrokのURLです(
https://9123ab3f.ngrok.io/stripe/webhook
など)
liveモードのときには、自分のWebサイトのURLホストをEndPointに追加する必要があります。
webhookを作成したら、そのwebhookの署名用の秘密鍵をメモしておきます。これは後でコードに追加する必要があります。
⚓4. credentialの追加(config/initializers/stripe.rb)
Stripe.api_key = ENV['STRIPE_SECRET_KEY']
StripeEvent.signing_secret = ENV['STRIPE_SIGNING_SECRET']
StripeEvent.configure do |events|
events.subscribe 'charge.dispute.created', Stripe::EventHandler.new
end
ここではStripeイベントをひとつ設定します。このcharge.dispute.created
とStripe::EventHandler
は、webhookが呼び出された後のアフターエフェクトを扱うために作成したサービスです。
Stripeの支払いで何らかの課金が発生すると、Stripe::EventHandler
クラスを呼び出します。
StripeEvent.configure
ブロックでイベントを複数定義することもできます。詳しくは以下の記事をどうぞ。
参考: Handling BigCommerce Webhooks in Ruby on Rails application - BoTree Technologies
課金に関連する以下のようなイベントは、すべてcharge.dispute
で扱えます。
- Charge Created(課金の作成)
- Charge Updated(課金の更新)
- Charge Closed(課金のクローズ)
- Charge Funds Reinstated(課金額の再投入)
- Charge Funds Withdrawn(課金額の回収)
events.subscribe 'charge.dispute.', Stripe::EventHandler.new
上のコードは、いずれかのcharge.dispute
イベントが発火したときに、エンドポイントにリダイレクトしてStripe::EventHandler
クラスを呼び出すようStripに指示します。
⚓5. webhookリクエストを扱うサービス
services/stripeフォルダの下にevent_handler.rbというサービスをひとつ作成します(app/services/stripe/event_handler.rb)。
# Stripeモジュール
module Stripe
# stripeのメインクラスEventHandler
class EventHandler
def call(event)
method = 'handle_' + event.type.tr('.', '_')
send method, event
rescue JSON::ParserError => e
render json: { status: 400, error: 'Invalid payload' }
Raven.capture_exception(e)
rescue Stripe::SignatureVerificationError => e
render json: { status: 400, error: 'Invalid signature' }
Raven.capture_exception(e)
end
end
def handle_charge_dispute_created(event)
# your code goes here
end
end
# method = 'handle_' + event.type.tr('.', '_')
# event.type
# これはイベント名を取れる。この記事の場合は「charge.dispute.created」を返して
# ‘.’ を ‘_’に置き換えることで‘charge_dispute_created’にし、そこに'handle_’をくっつける
# 最終的にhandle_charge_dispute_createdというメソッド名になる
# send method, event
# これはメソッドの変数の値に基づいてメソッドを呼び出す。この記事の場合は
# ‘handle_charge_dispute_created’を呼び出す
# 他のイベントについても同様にイベントに基づいてこのメソッドをサービス内に定義し
# 自分のサービスに置く必要がある
Stripeのイベントに基づいてこのようにサービスを定義するときに、このコンセプトを継承するとコードをシンプルに書けます。
以下の2つのStripeイベントを扱わなければならなくなったとします。
- Charge dispute(課金の異議申し立て)
- Charge refund(課金の返金)
この場合、以下のようなサービスを定義してコードを切り離せます。
メインのservice(EventHandler)
でリクエストを扱います。
# app/services/stripe/event_handler.rb
# Stripe module
module Stripe
# stripe main class EventHandler
class EventHandler
def call(event)
method = 'handle_' + event.type.tr('.', '_')
send method, event
rescue JSON::ParserError => e
render json: { status: 400, error: 'Invalid payload' }
Raven.capture_exception(e)
rescue Stripe::SignatureVerificationError => e
render json: { status: 400, error: 'Invalid signature' }
Raven.capture_exception(e)
end
end
end
以下のサービスは、リクエストに応じて課金の異議申し立てイベントを扱います。
# app/services/stripe/dispute_event_handler.rb
# Stripe event handler for handling webhook
module Stripe
# This will inherite the eventHandler main class
class DisputeEventHandler < EventHandler
def handle_charge_dispute_created(event)
# your code goes here
end
end
end
以下のサービスは、リクエストに応じて課金の返金イベントを扱います。
# app/services/stripe/refund_event_handler.rb
# Stripe event handler for handling webhook
module Stripe
# This will inherite the eventHandler main class
class RefundEventHandler < EventHandler
def handle_charge_refund_created(event)
# your code goes here
end
end
end
目が離せませんね!皆さまもぜひ、自分のStripe webhookをセキュアにすることを検討しましょう。
お読みいただいた皆さまに感謝いたします。
25人を超えるRuby on Railsチームを擁する私たちBoTree Technologiesは、エンタープライズアプリケーションの構築をお引き受けいたします。
弊社では、RPA、AI、Python、Django、JavaScript、ReactJSにも特化しています。
- 無料でご相談に応じます -- 御社の成長をお助けいたします!