もふもふ技術部

IT技術系mofmofメディア

【Rails】Stripe決済で複数のユーザープランを実装する

Stripe決済第三弾です。
今回は前々回実装したサブスクと、前回実装した買い切りを組み合わせて複数のプランをユーザーが選択できるようにしてみます。
下準備は以下の記事を参考にしてみてください。

www.mof-mof.co.jp

決済を実装する

今回用意するプラン

  • スタンダードプラン
    • ¥100/月
    • ¥800/買い切り
  • プレミアムプラン
    • ¥1,000/月
    • ¥8,000/買い切り

料金を作成する

こちら で料金を作成します。
前回までの記事を参考に商品と料金を作成します。

www.mof-mof.co.jp

商品カタログページを見て、以下のようになっていればOKです。

商品カタログページ

プランのマスターデータを作る

各料金の price_~~~ を保存する場所がないので、作成していきます。
DBに保存でもいいのですが、今回は4つだけなので ActiveHash を使います。

Gemをインストールします。

Gemfile

gem "active_hash"

PlanとPlanPriceを作成します。

app/models/plan.rb

class Plan < ActiveHash::Base
  include ActiveHash::Associations
  has_many :plan_prices

  self.data = [
    { id: 1, code: 'free', name: "無料会員"},
    { id: 2, code: 'standard', name: "スタンダード会員"},
    { id: 3, code: 'premium', name: "プレミアム会員"}
  ]
end

app/models/plan_price.rb

class PlanPrice < ActiveHash::Base
  include ActiveHash::Associations
  belongs_to :plan

  self.data = [
    { id: 1, plan_id: 2, price_id: "price_~~~~", mode: "subscription" }, # サブスクのスタンダードプラン
    { id: 2, plan_id: 2, price_id: "price_~~~~", mode: "payment" }, # 買い切りのスタンダードプラン
    { id: 3, plan_id: 3, price_id: "price_~~~~", mode: "subscription" }, # サブスクのプレミアムプラン
    { id: 4, plan_id: 3, price_id: "price_~~~~", mode: "payment" }, # 買い切りのプレミアムプラン
  ]
end

usersテーブルにカラムを追加します。

$ bundle exec rails g migration plan_id_to_users

db/20240912000000_plan_id_to_users.rb

class PlanIdToUsers < ActiveRecord::Migration[7.1]
  def change
    add_column :users, :plan_id, :integer, default: 1
  end
end

※ 前回まで使用していた premium カラムは不要です。

app/models/user.rb 以下を追記

extend ActiveHash::Associations::ActiveRecordExtensions
belongs_to :plan

プレミアムプランへの動線を作る

ルーティングを追加します。

config/routes.rb

resources :plans, only: [:index]
コントローラーを作成します。

app/controllers/plans_controller.rb

class PlansController < ApplicationController
  def index; end
end
viewsを作成します。

app/viewa/plans/index.html.erb

<%= button_to checkouts_path(plan_price_id: 1), data: { turbo: false } do %>
  スタンダードプラン(¥100/月)に申し込む
<% end %>
<%= button_to checkouts_path(plan_price_id: 2), data: { turbo: false } do %>
  スタンダードプラン(¥800/買い切り)に申し込む
<% end %>
<%= button_to checkouts_path(plan_price_id: 3), data: { turbo: false } do %>
  プレミアムプラン(¥1,000/月)に申し込む
<% end %>
<%= button_to checkouts_path(plan_price_id: 4), data: { turbo: false } do %>
  プレミアムプラン(¥8,000/買い切り)に申し込む
<% end %>

全然DRYになってないですが、4つなのでよしとします。

Stripeで決済する

ルーティングを追加します。

config/routes.rb

resources :checkouts, only: [:create]
コントローラーを作成します。

app/controllers/checkouts_controller.rb

class CheckoutsController < ApplicationController
  def create
    create_customer if current_user.customer_id.blank?

    plan_price = PlanPrice.find_by(id: params[:plan_price_id])
    session = Stripe::Checkout::Session.create(
      customer: current_user.customer_id,
      mode: plan_price.mode,
      payment_method_types: ["card"],
      line_items: [
        {
          quantity: 1,
          price: plan_price.price_id
        }
      ],
      success_url: plans_url,
      cancel_url: plans_url
    )

    redirect_to session.url, allow_other_host: true
  end

  private

  def create_customer
    customer = Stripe::Customer.create({
      name: current_user.name,
      email: current_user.email,
    })
    current_user.update(customer_id: customer.id)
  end
end

ここまでで決済ができるようになっています。
それぞれのボタンを押してみると、それぞれの価格でStripeのカード情報入力ページへ遷移すると思います。

Stripeのカード情報入力ページ

ここで使用できるテストカードは こちら を参考にしてください。

Webhookを使用できるようにする

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

ローカル環境でWebhookを使用するためにStripe CLIをインストールします。

$ brew install stripe/stripe-cli/stripe
CLIにログインします。
$ stripe login
ローカル環境にイベントを送信します。
 stripe listen --forward-to localhost:3000/webhooks

「Your webhook signing secret is whsec_~~」 と表示されるかと思います。
ここに表示されている、 whsec_~~~ はWebhook用のsecretなのでコピーしましょう。

環境変数を追記します。
$ rails credentials:edit -e development 

エディタが開いたら以下を参考にWebhook用のsecretを設定します。

stripe:
  publishable_key: pk_test_~~~~
  secret_key: sk_test_~~~~
  endpoint_secret: whsec_~~~~

Stripeで決済した情報を保存する

Stripeで決済した情報を保存できるようにWebhookを実装します。

ルーティングを追加します。

config/routes.rb

resources :webhooks, only: [:create]
コントローラーを作成します。

app/controllers/webhooks_controller.rb

class WebhooksController < ApplicationController
  skip_before_action :verify_authenticity_token
  skip_before_action :signed_in_user

  def create
    payload = request.body.read
    sig_header = request.env['HTTP_STRIPE_SIGNATURE']
    endpoint_secret = Rails.application.credentials.dig(:stripe, :endpoint_secret)
    event = nil

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

    case event.type
    when 'checkout.session.completed'
      session = event.data.object
      user = User.find_by(customer_id: session.customer)
      return unless user

      ApplicationRecord.transaction do
        line_item = Stripe::Checkout::Session.list_line_items(session.id).data.first
        plan = PlanPrice.find_by(price_id: line_item.price.id).plan
        Payment.create!(
          user_id: user.id,
          subscription_id: session.subscription,
          product_id: line_item.price.product,
          price_id: line_item.price.id,
          customer_id: session.customer,
          price: line_item.price.unit_amount,
          plan_id: plan.id
        )
        user.update!(plan_id: plan.id)
      rescue => e
        p e
      end
    end
  end
end

決済情報を保存したあとは、ユーザーがどのプランの会員になったか判別して保存します。

終わり

これで実装完了です。
サブスクのプランと買い切りのプラン、料金の違うプランなどを並べることができました。
選択肢が増えるとユーザーに使ってもらいやすくなりそうですね!