前回の記事までで、Stripeで定期課金を実装するための基本的な知識をまとめました。 今回は、Ruby on Railsでこれを実現するための実装例を紹介します。

目次

実装するもの

今回は、次のような特徴をもつオンラインメディアを実装してみたいと思います。

  • 記事は誰でも閲覧できるものと有料会員限定のものがある
  • 非会員が限定記事を閲覧しようとすると決済ボタンが表示される
  • 有料会員に対しては購読のキャンセルボタンを表示する
  • プランや記事は管理画面から追加する

なお、ユーザ登録にはDeviseを用いますが、この部分については本筋ではないので説明を省略します。 また、管理画面はActiveAdminAdminiなどを用いてすでに構築済みであるとします。 こちらも、まだの場合は実装を行なってください。

実装の流れ

実装は次のような流れで行ないます。

  1. プランを作成する
  2. ビューを作成する
  3. プランを購読する(顧客の作成がまだの場合はこれも作成する)
  4. 購読をキャンセルする
  5. 有効期限を更新する

実装の際にはクライアントライブラリの導入やAPIキーの設定が必要となります。 詳しくは公式ドキュメントなどをご覧ください。

1. プランを作成する

それではまず、有料会員向けのプランから作成していきます。 これは、たとえばBasicやPremiumなどで、プランに応じてコンテンツの閲覧権限を変えたりします。

プランは料金(amount)、課金のスパン(interval)、名前(name)を持ちます。 また、Stripe側でプランを識別するためのID(stripe_plan_id)も格納します。

アプリケーション側のプランの作成は管理画面から行ないますが、プラン作成時のコールバックでStripe側にこれを反映させます。 次のように、モデルの#after_createでStripe側にプランを作成します。

# == Schema Information
#
# Table name: plans
#
#  id             :integer          not null, primary key
#  amount         :integer          not null
#  interval       :string           not null
#  name           :string           not null
#  stripe_plan_id :string
#  created_at     :datetime         not null
#  updated_at     :datetime         not null
#

class Plan < ApplicationRecord
  after_create :create_stripe_plan

  def create_stripe_plan
    Stripe::Plan.create(
      amount: amount,
      currency: 'jpy',
      id: stripe_id,
      interval: interval,
      name: name
    )
    update(stripe_plan_id: stripe_id)
  end

  private

  def stripe_id
    name.parameterize.underscore
  end
end

2. 記事のビュー

次に、記事ページのビューを以下に示します。 記事が限定記事でないか、限定記事かつ有料会員の場合はコンテンツが表示され、それ以外の場合はCheckoutによる決済ボタンが表示されます。

Post#<%= @post.id %> (<%= @post.scope %>)

<% if @post.everyone? || (@post.member? && current_user.member?) %>
  <%= @post.content %>
<% else %>
  <p>有料会員になると、このコンテンツを閲覧できるようになります。</p>
  <%= form_tag subscription_path do %>
    <script
      src="https://checkout.stripe.com/checkout.js" class="stripe-button"
      data-key="pk_test_xxxxxxxxxxxxxxxxxxxxxxxx"
      data-name="Demo Site"
      data-description="Widget"
      data-image="https://stripe.com/img/documentation/checkout/marketplace.png"
      data-locale="auto"
      data-currency="jpy"
      data-panel-label="Subscribe"
      data-email="<%= current_user.email %>">
    </script>
  <% end %>
<% end %>

<% if current_user.member? %>
  <%= link_to '有料会員を解約する', subscription_path, method: :delete %>
<% end %>

Postモデルには、その記事が限定記事かどうかを表す:scopeを定義します。

class Post < ApplicationRecord
  enum scope: { everyone: 0, member: 10 }
end

また、ユーザが有料会員かどうかは、次の#member?で判断します。 決済が完了した際に、その決済の有効期限をexpires_atに格納する予定なので、これを元に判断しています。

class User < ApplicationRecord
  def member?
    expires_at && (expires_at > Time.now)
  end
end

3. 購読する

上記のビューでCheckoutにカード情報を入力し送信すると、次の#createにトークンが渡されます。 これを元にプランを購読させます。

顧客が未作成の場合はあわせて作成しており、そもそも購読済みの場合は処理をスキップしています。

class SubscriptionsController < ApplicationController
  before_action :authenticate_user!

  def create
    # (1) 購読済みの場合は戻る
    if current_user.stripe_subscription_id
      redirect_to :back
    end

    # (2) 顧客が未作成の場合は作成する
    unless current_user.stripe_customer_id
      current_user.create_customer(params[:stripeToken])
    end

    # (3) Basicプランを購読させる
    current_user.create_subscription('basic')

    redirect_to :back
  end
end

実際の購読処理などはUserモデルで行なっています。 コントローラに直接実装するのではなくモデルに切り出すことで、ユニットテストがしやすくなります。

class User < ApplicationRecord
  def create_customer(token)
    customer = Stripe::Customer.create(email: email, source: token)
    update(stripe_customer_id: customer.id)
  end

  def create_subscription(plan)
    subscription = Stripe::Subscription.create(
      customer: stripe_customer_id,
      plan: plan
    )
    update(stripe_subscription_id: subscription.id)
    update_subscription(subscription)
  end

  def update_subscription(subscription)
    update(expires_at: Time.zone.at(subscription.current_period_end))
  end
end

4. 購読をキャンセルする

有料会員に対しては、購読をキャンセルするボタンも提供します。 また、退会時には自動でキャンセルするよう、モデルにコールバックも設定します。

class SubscriptionsController < ApplicationController
  def destroy
    current_user.delete_subscription
    redirect_to :back
  end
end
class User < ApplicationRecord
  before_destroy :delete_subscription

  def delete_subscription
    Stripe::Subscription.retrieve(stripe_subscription_id).delete
    update(expires_at: nil, stripe_subscription_id: nil)
  end
end

5. 有効期限を更新する

月次の決済が完了すると、StripeからWebhookによる通知が飛んできます。 これを元にユーザを特定し、そのユーザの有効期限を更新します。

class Api::SubscriptionsController < ApplicationController
  protect_from_forgery except: :create

  def create
    event = Stripe::Event.retrieve(params[:id])

    case event.type
    when 'customer.subscription.updated'
      subscription = event.data.object
      user = User.find_by(stripe_subscription_id: subscription.id)
      user.update_subscription(subscription)
    end

    head :ok
  end
end

参考記事

おわりに

以上でStripeに関する実装は完了となります。 実装してみると分かるのですが、Stripeは他の決済サービスに比べてAPIやドキュメントがとても分かりやすく、スムーズに開発を進めることができます。

定期課金の参考になれば嬉しいです。