11
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Payjpでサブスク機能(定期課金)を実装する(Rails)

Last updated at Posted at 2020-11-30

#実装する機能

今回、Payjp(Pay.jp)を利用して入力フォームを作ります。
実装するものとしては下記のとおりです。

  • クレジットカード情報入力フォーム
  • カード情報とユーザー情報の紐づけ
  • サブスク決済(定期課金)の機能

イメージとしてはこんな感じです。

Image from Gyazo

#バージョン情報
ruby 2.5.1
Rails 5.2.4.4

#前提条件

  • deviseが導入済みでログインができている

#1.Payjpのアカウントを作成しよう

Payjpのサイトでアカウントを作成します。

#2.APIを確認しよう
ダッシュボードのAPIより確認ができます。
今回はテストモードでの実装なので、テスト秘密鍵とテスト公開鍵を使用します。
https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_224314_4e41420a-5569-3ed8-3bac-2fb3d9588b36.png

#3.環境変数を設定しよう
環境変数は credentials.yml.enc に設定します。
ターミナルで $ EDITOR='code --wait' rails credentials:edit と打ち込むとvscodeで編集が可能です。

config/credentials.yml
payjp:
  PAYJP_PRIVATE_KEY: sk_test_*******************

セットするのは、秘密鍵のみでオッケーです。yamlファイルはインデントが崩れると読み込めなくなるのでご注意ください。
※環境変数の設定は、環境により異なります。

#4.payjpのgemを設置しよう
下記をgemfileに記載しbundle installを実施します。

gem 'payjp'

#5.payjp.jsを読み込めるようにしよう

<script src="https://js.pay.jp/" type="text/javascript"></script>を下記の通り追記します。

app/views/layouts/application.html

<head>
    <title>appname</title>

    <script src="https://js.pay.jp/" type="text/javascript"></script> #追記

    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>


#6.Userテーブルを編集しよう

新たにカラムを追加しよう。(subscription_idカラムとpremiumカラム)
ユーザーがプレミアム会員かどうかの判別するためにpremiumカラムを追加。
ユーザーが定期課金(プレミアム会員になったら)したら、subscription_idに値を持つ。

db/migrate/devise_create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at
      t.datetime :remember_created_at

      t.string :subscription_id  #追記
      t.boolean :premium, default: false, null: false #追記

      t.timestamps null: false
    end
    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
  end
end

#7.Cardテーブルを用意しよう

コマンドにて下記を実行

$ rails g model Card

user_id(ユーザーと紐づけるため)
customer_id(payjpの顧客id)
card_id(payjpのデフォルトカードid)

db/migrate/create_cards.rb
class CreateCards < ActiveRecord::Migration[5.2]
  def change
    create_table :cards do |t|
      t.integer :user_id
      t.string :customer_id
      t.string :card_id
      t.timestamps
    end
  end
end

ここまで来たら一旦、マイグレーションを実施します。

$ rails db:migrate

#8.アソシエーションを組もう
ユーザーが複数のカード情報を持っていて、カードは一人のユーザーに紐づいている。

app/models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
        :recoverable, :rememberable, :validatable

  has_many :cards, dependent: :destroy
end

app/models/card.rb
class Card < ApplicationRecord
  belongs_to :user
end

#9.コントローラーを作成しよう

下記コマンドでコントローラーを作成します。

$ rails g controller card

作成したコントローラーの内容を変更します。

app/controllers/card_controller.rb
class CardsController < ApplicationController

  require 'payjp'

  before_action :set_api_key

  def plan #定期課金プラン
    Payjp::Plan.create(
      :amount => 1000,
      :interval => 'month',
      :billing_day => 27,
      :currency => 'jpy',
    )
  end

  def create #カード登録メソッド
    if params['payjp-token'].blank?
      redirect_to action: "new"
      # トークンが取得出来てなければループ
    else
      user_id = current_user.id
      customer = Payjp::Customer.create(
      card: params['payjp-token']
      # params['payjp-token'](response.id)からcustomerを作成
      ) 
      @card = Card.new(user_id: user_id, customer_id: customer.id, card_id: customer.default_card)
      if @card.save
        pay #カード情報を保存できたらpayアクションを呼び出す。
      else
        flash[:alert] = 'カード情報を登録できませんでした'
        redirect_to action: "new"
      end
    end
  end

  def pay
    card = Card.where(user_id: current_user.id).first
    Payjp.api_key = Rails.application.credentials.payjp[:PAYJP_PRIVATE_KEY]
    subscription = Payjp::Subscription.create( #サブスク情報を作成して変数subscriptionに代入
    :customer => card.customer_id, 
    :plan => plan, #plamアクションで定義した情報を呼び出す
    metadata: {user_id: current_user.id}
    )
    current_user.update(subscription_id: subscription.id, premium: true)
    #userテーブルのsubscription_idに値を持たせ、premiumカラムをtrueにして、user情報をアップデート
    flash[:alert] = '定期課金に登録できました'
    redirect_to "/"
  end

  def cancel
    Payjp.api_key = Rails.application.credentials.payjp[:PAYJP_PRIVATE_KEY]
    subscription = Payjp::Subscription.retrieve(current_user.subscription_id)
    subscription.cancel
    current_user.update(premium: false) #キャンセルしたユーザーは、premiumカラムをfalseにする
    flash[:alert] = '定期課金を解除できました'
    redirect_to "/" 
  end

  def set_api_key
    Payjp.api_key = Rails.application.credentials[:payjp][:PAYJP_PRIVATE_KEY]
  end

end

ポイントは、Payjp::Subscription.createで定期課金情報を作成して、変数subscriptionに代入するところです。
スクリーンショット 2021-01-08 19.40.25.png

変数subscriptionの中身は下記のように定期課金IDと顧客IDとプランIDを持ってます。
スクリーンショット 2021-01-08 19.47.27.png

コンソールで見るとこんな感じ。
スクリーンショット 2021-01-08 20.00.31.png

プラン詳細画面でも設定したプラン通りになっているのがわかります。
スクリーンショット 2021-01-08 20.07.28.png

定期課金情報を作成してしまえば、課金日ごとに売上も月々で自動的に計上されます。

#10.ルートを作成しよう

config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  resources :cards, only:[:index, :new, :create,:destroy,:show] do
  end
end

#11.カードの登録画面&決済画面を作成しよう

今回はカード登録と同時に決済がされる画面を作成します。
完成したらhttp://localhost:3000/cards/newにアクセスしましょう。

app/views/cards/new.html

<p>クレジットード情報入力</p>

<%= form_with model: @card, url: cards_path, local: true, html: { name: 'inputForm' } do |f| %>
  <p>カード番号</p>
  <%= f.text_field :card_number, id: "card_number",class: "card_form",maxlength: '16' %>
  <p>有効期限</p>
  <%= f.collection_select :exp_month , Month.all, :id, :name , {},class: "card-check-box",id: "exp_month" %>
  <%= f.collection_select :exp_year, Year.all, :id, :name, {}, class: "card-check-box",id: "exp_year"  %>
  <p>セキュリティコード</p>
  <%= f.password_field :cvc, id: 'cvc',class: "card_form" ,maxlength: '6',autocomplete: "on" %>
  <div class="card-btn" id="card_token"></div>
  <%= f.submit 'サブスク決済をする', id: 'token_submit',class: 'card-btn' %>
<% end %>

※有効期限のセレクトボックスにはactivehashを用いています。
詳しくは、こちら→active_hashまとめ

#12.Payjpにデータを送りトークンを取得しよう

jQueryを使用するので、railsに未設定の場合は設定をしてください。
設定方法はこちら

app/assets/javascripts/payjp.js
document.addEventListener(
  "DOMContentLoaded", e => {
    if (document.getElementById("token_submit") != null) {
      // "token_submit"というidをもつhtmlがあるページか?つまりカード作成ページか
      Payjp.setPublicKey("pk_test_bf8f6b07458f0197ea990312"); 
      let btn = document.getElementById("token_submit");  // 送信ボタンをbtnに格納
      btn.addEventListener("click", e => {  // 送信ボタンがクリックされたとき
        e.preventDefault(); // デフォルトのブラウザの動きをいったんとめる(createアクションへの遷移を)
        let card = {  // cardに入力された値をハッシュで格納
          number: document.getElementById("card_number").value,
          cvc: document.getElementById("cvc").value,
          exp_month: document.getElementById("exp_month").value,
          exp_year: document.getElementById("exp_year").value
        }; 
        Payjp.createToken(card, (status, response) => {
          // カード情報をpayjpに送りカードトークンを(response.id)を受け取る。
          if (status === 200) {  // 正常な値の場合 
            $("#card_number").removeAttr("name");
            $("#cvc").removeAttr("name");
            $("#exp_month").removeAttr("name");
            $("#exp_year").removeAttr("name"); 
            // name属性を削除することにより、dataベースに送るのを防ぐ。
            $("#card_token").append(
              $('<input type="hidden" name="payjp-token">').val(response.id)
              // <input type="hidden" name="payjp-token" value= response.id>が#card_tokenに追加される。
            ); 
            if(!confirm('この内容でよろしかったですか?')){
                /* キャンセルの時の処理 */
                return false;
            }else{
                /* OKの時の処理  今回は特に処理がないので空*/
            }
            document.inputForm.submit(); // inputFormのsubmitを発動。(上記で停止していた)
          } else {
            alert("カード情報が正しくありません。");
          }
        });
      });
    }
  },
  false
);

#13.テストカードで決済してみよう

こちらのテストカードで登録するようにしてください。
それ以外を打ち込んだ場合はトークンが発行できずはねられてしまいます。

以上でカード登録からサブスク決済まで一通り実装できました。
プランなどを自由に組み替えてコントローラーで処理を分ければ、決済機能だけのアプリを作ることもできそうです。

11
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?