もふもふ技術部

IT技術系mofmofメディア

【Rails】Stripeでサブスク決済

近年、サブスクのサービスが増えましたね。
今回はユーザーの上位プランをサブスクで決済できるようにしたいと思います。
プレミアム会員を月額100円で実装してみます!

下準備

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

Gemfile

gem "stripe"

Stripeのアカウントを作成します。

stripe.com

開発環境で動作確認するだけであれば、アカウントを作成するだけですぐに使えます!

環境変数を追加します。

https://dashboard.stripe.com/test/apikeys からAPIキーを持ってきます。
credentials に保存しましょう。

$ rails credentials:edit -e development 

エディタが開いたら以下を参考にAPIキーを設定します。

stripe:
  publishable_key: pk_test_~~~~
  secret_key: sk_test_~~~~

次に、 config/initializers/stripe.rb を作成し、以下の内容で保存します。

Stripe.api_key = Rails.application.credentials.dig(:stripe, :secret_key)
Stripe.api_version = '2024-06-20'

※ APIのバージョン情報は こちら で確認できます。

ユーザー認証を作っておきましょう。

www.mof-mof.co.jp

こちらの記事などを参考に、ユーザー認証を準備します。
deviseを使ったものでも大丈夫です。

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

$ bundle exec rails g migration stripe_to_users

db/20240910000000_stripe_to_users.rb

class StripeToUsers < ActiveRecord::Migration[7.1]
  def change
    add_column :users, :premium, :boolean, default: false
    add_column :users, :customer_id, :string
  end
end

customer_id はStripeの顧客IDを保存するために使用します。

Paymentモデルを追加します。

$ bundle exec rails g model payment

db/20240910000000_create_payments.rb

class CreatePayments < ActiveRecord::Migration[7.1]
  def change
    create_table :payments do |t|
      t.references :user
      t.string :price_id
      t.string :subscription_id
      t.string :product_id
      t.string :customer_id
      t.integer :price

      t.timestamps
    end
  end
end

※ Stripeで決済した情報を保存するために使用します。必要に応じて、カラムを足しましょう。

決済を実装する

料金を作成する

こちら で料金を作成します。
料金の作成から全てRailsで実装することもできますが、今回は月額100円の料金プランだけなので手動で作成します。
「+商品を追加」ボタンを押し、以下の画像のように入力して登録します。

商品追加画面

一覧に「プレミアムプラン」が作成されたかと思います。
プレミアムプランを選択すると「料金」という項目が表示されます。 先ほど作成した「¥100」を選択します。

画面右上にある 「price_~~~」は後で使用するのでコピーしておきましょう。

料金の詳細

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

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

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, data: { turbo: false } do %>
  プレミアムプラン(¥100/月)に申し込む
<% end %>

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?

    price_id = 'price_~~~' #先ほどコピーした値
    session = Stripe::Checkout::Session.create(
      customer: current_user.customer_id,
      mode: "subscription",
      payment_method_types: ["card"],
      line_items: [
        {
          quantity: 1,
          price: price_id
        }
      ],
      success_url: root_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

ここまでで決済ができるようになっています。
実際に「プレミアムプラン(¥100/月)に申し込む」ボタンを押してみると、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
        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,
        )
        user.update!(premium: true)
      rescue => e
        p e
      end
    end
  end
end

今回は checkout.session.completed のイベントをトリガーして決済した情報を保存しました。 他にも invoice.payment_succeeded などがあり、領収書(PDF)のURLを取得することができます。
イベントごとにデータを保存することができるので、痒いところに手が届く感じがしますね!

決済情報を保存したあとは、ユーザーがプレミアム会員になった旨を保存して完成です。

終わり

これでサブスク決済ができるようになりました。
複数のプランがある場合や、プレミアム会員だけ特定の機能が使えるようにするなどの実装は本記事では割愛します。

pr_~~price_~~sub_~~~ などStripe独自のIDは種類が多いので最初はこんがらがってしまうかもしれません。
また、サブスク決済の日本語記事が少ない&少々古かったため、APIが動かなくて苦戦してしまいました。