rails7でstripeのcheckoutにて決済を実装しました。詰まったところや設計をまとめました。
やりたいこと
今回の実装内容は下記
- rails7で決済を実装したい
- フォームなどはつくりたくないのでstripeのcheckout機能で決済ページはすでに構築済みのものを使う
- クレジットカード決済
- 単発決済
- webhookで決済完了をstripeから受け取りdbに反映
全体の流れ
- stripeにてアカウント作成、商品登録
- gemの導入
- config/initializers/stripe.rbにStripeのAPIキーを設定
- 決済ボタンをおすとticket_payment viewへ遷移。そのページは表示させず、javascriptでcheckoutへredirect
- sucsess viewへ遷移
- 決済後にサーバーにwebhookから受け取った情報をpayment_historyに反映
stripeのcheckoutとは?
stripeのcheckoutは決済画面がすでにできていて、そこに遷移させることで決済できる仕組みです。
Checkout の仕組み
https://stripe.com/docs/payments/checkout/how-checkout-works
決済の結果をwebhookで受け取って、dbに反映させることも可能。もっと簡単なものにPayment Linksがあるんですが、こちらはただリンクを生成し、決済するもので決済の結果をサービスのdbに反映させる必要がある場合はこちらのcheckoutがおすすめです。
stripeの商品登録
stripe管理画面にてまずは商品を作成します。

商品タブから商品を追加ボタンをクリック

商品情報を設定します。
ここでprice_idを発行します。
gemの導入
gemfile
gem 'stripe'
上記でstripe gemを導入
APIキーの設定
APIキー周りを設定。config/initializers/stripe.rbにstripe.rbファイルを作成し、下記を記載。
config/initializers/stripe.rb
Stripe.api_key = ENV['STRIPE_SECRET_KEY']
checkoutページへの遷移
次にcheckoutページへの遷移を実装します。
https://stripe.com/docs/checkout/quickstart
stripeのクイックスタートでは
- postで/create-checkout-sessionにリクエスト
- create-checkout-sessionメソッドからredirect
ですが、railsのturbo link周りの仕様でうまくいかなかったので
- ticket_payment viewに遷移
- javascriptでredirect
で実装しました。
ticket_paymentの作成、遷移
- ticket_payment viewを作成します。
- そこへgetで遷移させます。
ticket_payment viewに下記を記載。
<script>
var stripe = Stripe('pk_test_'); // ()内には公開可能キーを記述
stripe.redirectToCheckout({
sessionId: '<%= @session.id %>'
}).then(function (result) {
});
</script>
controller
def ticket_payment
@session = Stripe::Checkout::Session.create({
customer: current_user.stripe_customer_id,
line_items: [{
price: 'price_',
quantity: params[:ticket_count].to_i
}],
mode: 'payment',
success_url: request.base_url + '/admin/ticket_payment_success',
cancel_url: request.base_url + '/admin/setting'
})
end
controllerのメソッドは上記。priceにstripeの設定画面で登録した商品のidを入力。
決済後にサーバーにwebhookから受け取った情報をpayment_historyに反映
決済後にwebhookから受け取った情報を反映させます。今回反映させるのは
- 購入したチケット数を反映
- 決済履歴を残す
後者はstripeで確認すればいいといえば良いのですが、サービス内で購入数などをのちのち表示できたほうが良いなと思い、一応実装。
webhook controllerを下記で作成。
class WebhookController < ActionController::API
def webhook_event
webhook_secret = ENV["WEBHOOK_SECRET"]
payload = request.body.read
if !webhook_secret.empty?
# Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured.
sig_header = request.env['HTTP_STRIPE_SIGNATURE']
event = nil
begin
event = Stripe::Webhook.construct_event(
payload, sig_header, webhook_secret
)
rescue JSON::ParserError => e
# Invalid payload
status 400
return
rescue Stripe::SignatureVerificationError => e
# Invalid signature
puts '⚠️ Webhook signature verification failed.'
status 400
return
end
else
data = JSON.parse(payload, symbolize_names: true)
event = Stripe::Event.construct_from(data)
end
# Get the type of webhook event sent
event_type = event['type']
data = event['data']
data_object = data['object']
case event.type
when 'checkout.session.completed'
event_checkout_session_completed(data)
else
puts "Unhandled event type: \#{event.type}"
puts event.type
end
render status: 200, json: { status: 200, message: "Success" }
end
private
def event_checkout_session_completed(data)
session = data.object
user = User.find_by(stripe_customer_id: session.customer)
begin
ActiveRecord::Base.transaction do
stripe_session = Stripe::Checkout::Session.retrieve({
id: session.id,
expand: ['line_items']
})
# Get the total quantity of items purchased
total_quantity = stripe_session.line_items.data.sum(&:quantity)
amount_total = session.amount_total
# チケット購入履歴を作成
payment_history = PaymentHistory.create(amount: amount_total)
payment_history.save!
# チケットカウントを増やす
user.increment!(:ticket_count, total_quantity)
end
rescue => e
puts e
end
end
end
webhook_eventメソッド
- 最初に、環境変数からWebhookの秘密鍵(
WEBHOOK_SECRET
)を取得します。 - 続いて、リクエストボディを取得し、それが空でないかどうかをチェックします。
- Webhookの秘密鍵が空でない場合(つまり、Webhookの署名検証が必要な場合)、ヘッダからStripeの署名を取得し、それとペイロード、秘密鍵を使ってイベントの検証と構築を行います。
- 何らかの理由でペイロードが無効(
JSON::ParserError
)または署名が無効(Stripe::SignatureVerificationError
)な場合、それをログに出力し、400のHTTPステータスコードを返して処理を終了します。 - 秘密鍵が空の場合、ペイロードをJSONオブジェクトに変換し、そのデータからStripeのイベントオブジェクトを構築します。
- イベントタイプとデータを抽出し、チェックアウトセッションが完了したかどうかを確認します(
checkout.session.completed
)。その場合、専用のメソッド(event_checkout_session_completed
)を呼び出します。 - 処理が完了したら、200のHTTPステータスコードと”Success”メッセージを含むJSONレスポンスを返します。
event_checkout_session_completedメソッド
のプライベートメソッドは、チェックアウトセッションの完了イベントを処理します。
- セッションデータを取得し、そのセッションから顧客情報を取り出します。顧客情報を元に、データベースからユーザーを検索します。
- トランザクションを開始し、エラーが発生した場合はそれをキャッチします。
- Stripe APIを使用して、ラインアイテム情報を含むチェックアウトセッションを取得します。
- ラインアイテムから購入した合計数量を算出します。
- 支払い(payment_intent)と合計金額をログに出力します。
- 支払い履歴(
PaymentHistory
)オブジェクトを作成し、保存します。 - ユーザーが購入したチケット数を、ユーザーのチケットカウントに追加します。
このコントローラーは、StripeのWebhookを利用して購入情報を処理し、ユーザーの購入履歴とチケット数を更新する役割を果たしています。
total_quantity = stripe_session.line_items.data.sum(&:quantity)
Rubyにおける.sum(&:quantity)
は、Enumerableモジュールのsum
メソッドを利用しています。
まず、&:quantity
はRubyのシンボルをProcオブジェクトに変換する省略形です。これは{ |item| item.quantity }
と同等で、それぞれのアイテムに対してquantity
メソッドを呼び出します。つまり、&:quantity
は各アイテムからquantity属性を取得するための手段です。
そして、sum
メソッドはその取得した属性(この場合はquantity)の合計を計算します。
なので、stripe_session.line_items.data.sum(&:quantity)
はstripe_sessionのline_itemsデータ(配列と想定)のそれぞれのアイテムからquantityを取り出し、その総和(つまり、全アイテムのquantityの合計)を計算します。
具体的には、購入した商品の個数の合計を計算していると考えられます。