【Ruby on Rails】Stripeのサブスクリプションで試したことをまとめてみた

f:id:kuracux:20200625113934p:plain

スタメンでエンジニアをしている田中です。 今回は決済プラットフォームであるStripeのサブスクリプションについて、Rubyで実際にコードを書きながら調査をしたので、そのまとめを記述していこうと思います。

目次

Stripeのサブスクリプションについて

Stripeのサブスクリプションは一度作成すると定期的に自動で決済が行われるようになります。 決済金額や支払間隔はStripeのダッシュボードから設定することが可能です。また、API経由で設定することも可能です。

サブスクリプションの作成についても、同様にAPI経由で作成することが可能となっています。本記事では、API経由でサブスクリプションを作成する方法について説明します。

準備

Stripeのgemをインストールします。

gem 'stripe' 

StripeのAPIを利用する際にはapi_keyの設定が必要です。 Stripeのダッシュボードにテスト用のsecret_keyがあるので、initializer配下に以下を配置しておきましょう。

Stripe.api_key = 'secret_key'

参考: https://github.com/stripe/stripe-ruby

サブスクリプションの生成

即時にサブスクリプションの契約を行う場合はStripe::Subscription.createを使用します。

Stripe::Subscription.create({
  customer: 'customer_id',
  items: [
    {
      price: 'price_id',
      quantity: 1,
    }
  ],
  default_tax_rates: ['tax_id'],
})

参考: https://stripe.com/docs/api/subscriptions/create https://stripe.com/docs/billing/subscriptions/examples

テスト用のクレジットカード

Stripeでは様々なケースのテスト用のクレジットカードを用意しています。 各種ブランド・地域、3Dセキュア対応のカードや異常系を想定したカードなど様々です。 以下のページにて一覧で掲載されているので確認してみてください。 https://stripe.com/docs/testing

サブスクリプションの開始時刻の設定

即時に決済を行う場合であれば、先程説明したStripe::Subscription.createを使用すればよいのですが、事前登録のようにサブスクリプションの開始日を未来日で設定したい場合については、以下のようにStripe::SubscriptionSchedule.createを利用することで未来日を指定することができます。

Stripe::SubscriptionSchedule.create({
  customer: 'customer_id',
  start_date: 1592699528, # unixtime
  phases: [
    {
      plans: [price: 'price_id', quantity: 1],
      default_tax_rates: ['tax_id']
    },
  ],
})

参考: https://stripe.com/docs/api/subscription_schedules/create

注意点として、サブスクリプションが有効になるタイミングと決済が行われるタイミングには1時間の時間差があります。そのため、決済時のWebhookを利用して処理をしようとする際にはこの時間差を考慮する必要があります。具体的には以下の通りです。

  • start_dateで指定した時刻がサブスクリプションが有効になる時刻
  • start_date + 1時間後が決済が行われる時刻

参考: https://stripe.com/docs/billing/subscriptions/overview#subscription-events

トライアル期間の設定

サブスクリプションによくあるビジネスモデルとして一定期間の無料トライアル後に決済を行うケースがあります。Stripeに関しては、即時にサブスクリプションを開始する場合(Stripe::Subscription)とサブスクリプション開始日を指定する場合(Stripe::SubscriptionSchedule)のどちらについても、パラメータとしてtrial_endを渡すことで対応することが出来ます。

Stripe::Subscription.create({
  customer: 'customer_id',
  items: [{ price: 'price_id' }],
  default_tax_rates: ['tax_id'],
  trial_end: 1593268745, # unixtime
})
Stripe::SubscriptionSchedule.create({
  customer: 'customer_id',
  start_date: 1592699528, # unixtime
  phases: [
    {
      plans: [price: 'price_id', quantity: 1],
      default_tax_rates: ['tax_id'],
      trial_end: 1593268745, # unixtime
    },
  ],
})

注意点としては、サブスクリプションの開始時刻を設定する場合と同様、トライアル終了後1時間後に決済が行われます。時系列としては以下のとおりです。

即時にサブスクリプションを開始する場合

  • trial_endで指定した日時がトライアル終了日時となる
  • trial_endで指定した日時 + 1時間後に決済が行われる

サブスクリプション開始日を指定する場合

  • start_dateで指定した日時がトライアル開始日時となる
  • start_dateで指定した日時 + 1時間後にトライアルの決済処理が行われる。しかし、トライアル期間のため0円のインボイスが作成される
  • trial_endで指定した日時がトライアル終了日時となる
  • trial_endで指定した日時 + 1時間後に決済が行われる。こちらの決済ではプランに設定された金額が請求される

参考: https://stripe.com/docs/billing/subscriptions/trials

Webhookでイベントの取得

概要

RailsアプリケーションにWebhookのエンドポイントを作成することで、決済の成功・失敗やサブスクリプションの作成といったイベントの通知をStripeから受け取ることができます。本項では、ローカルでの検証方法やエンドポイントの作成方法をご紹介します。

ローカルでの検証方法

StripeではWebhookの検証のためにStripe CLIを提供しています。
以下の手順を参考にインストールしてください。
参考: https://stripe.com/docs/stripe-cli

インストール後、stripe loginを実行してStripeにログインすることで各種コマンドが実行できます。

Webhookを受け取る場合はstripe listenコマンドで受け取ることが出来ます。
また、ローカル環境のエンドポイントの指定や取得したいイベントを指定して起動することも出来ます。
詳細は下記をご参照ください。
https://stripe.com/docs/stripe-cli/webhooks

イベント一覧

取得できるイベントの一覧は下記をご参照ください。
https://stripe.com/docs/api/events/types

エンドポイントの作成

Webhookを受け取るエンドポイントは、以下の2つの処理を行います。

  1. requestの検証
  2. イベントの種類による処理の振り分け

1に関しては、後述する署名の検証を行います。
2に関しては、イベントに応じて行いたい処理を記述してください。(例えば、決済完了処理を受け取ってデータを作成する・ユーザーのステータスを更新する等)

payload = request.body.read
event = nil

begin
  event = Stripe::Event.construct_from(
    JSON.parse(payload, symbolize_names: true)
  )
rescue JSON::ParserError => e
  # Invalid payload
  status 400
  return
end

# Handle the event
case event.type
  when 'payment_intent.succeeded'
  payment_intent = event.data.object # contains a Stripe::PaymentIntent
  # Then define and call a method to handle the successful payment intent.
  # handle_payment_intent_succeeded(payment_intent)
  when 'payment_method.attached'
  payment_method = event.data.object # contains a Stripe::PaymentMethod
  # Then define and call a method to handle the successful attachment of a PaymentMethod.
  # handle_payment_method_attached(payment_method)
  # ... handle other event types
else
  # Unexpected event type
  status 400
  return
end

status 200

参考: https://stripe.com/docs/webhooks/build

署名の検証

StripeのWebhookは署名の検証を行うことが出来ます。 本番環境または検証環境であればStripeのダッシュボードの「開発者」 → 「Webhook」からエンドポイントを設定すると、Webhook用のシークレットキーが発行されます。 ローカル環境であれば、Stripe CLIにてstripe listenコマンド実行時にシークレットキーが発行されるので、そちらを設定してください。

payload = request.body.read
sig_header = request.env['HTTP_STRIPE_SIGNATURE']
endpoint_secret = 'webhookのsecret_key'
event = nil

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

参考: https://stripe.com/docs/webhooks/signatures

まとめ

Stripeのサブスクリプションの基本的な作成から、スケジュールやトライアル、処理後のWebhookといった決済サービスを作成する上で必要なところをまとめてみました。Stripeはドキュメントもしっかりとまとめられており、開発者フレンドリーなプラットフォームだと感じました。まだまだ扱いきれていないこともあるので、今後も機会があればまとめていきたいと思います。

最後に、株式会社スタメンでは一緒に働くエンジニアを募集しています。ご興味のある方はぜひエンジニア採用サイトをご覧ください。