前回の記事までで、Stripeで定期課金を実装するための基本的な知識をまとめました。 今回は、Ruby on Railsでこれを実現するための実装例を紹介します。
目次
- Stripe入門(1): 定期課金を実装する流れや基本知識について
- Stripe入門(2): Checkoutによるトークンの取得と、顧客の作成方法
- Stripe入門(3): プランの作成と、顧客に定期課金を行なう方法
- Stripe入門(4): Railsで定期課金を行なうための実装例
実装するもの
今回は、次のような特徴をもつオンラインメディアを実装してみたいと思います。
- 記事は誰でも閲覧できるものと有料会員限定のものがある
- 非会員が限定記事を閲覧しようとすると決済ボタンが表示される
- 有料会員に対しては購読のキャンセルボタンを表示する
- プランや記事は管理画面から追加する
なお、ユーザ登録にはDeviseを用いますが、この部分については本筋ではないので説明を省略します。 また、管理画面はActiveAdminやAdminiなどを用いてすでに構築済みであるとします。 こちらも、まだの場合は実装を行なってください。
実装の流れ
実装は次のような流れで行ないます。
- プランを作成する
- ビューを作成する
- プランを購読する(顧客の作成がまだの場合はこれも作成する)
- 購読をキャンセルする
- 有効期限を更新する
実装の際にはクライアントライブラリの導入や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やドキュメントがとても分かりやすく、スムーズに開発を進めることができます。
定期課金の参考になれば嬉しいです。