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

#実装する機能

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

+ <font color="Red">クレジットカード情報入力フォーム</font>
+ <font color="Red">カード情報とユーザー情報の紐づけ</font>
+ <font color="Red">サブスク決済（定期課金）の機能</font>

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

[![Image from Gyazo](https://i.gyazo.com/bb1191674a0eb7cdc488afe5fdef9395.png)](https://gyazo.com/bb1191674a0eb7cdc488afe5fdef9395)

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

#前提条件
+ deviseが導入済みでログインができている

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

[Payjpのサイト](https://pay.jp/ "Pay.jp")でアカウントを作成します。

#2.APIを確認しよう
ダッシュボードのAPIより確認ができます。
今回はテストモードでの実装なので、テスト秘密鍵とテスト公開鍵を使用します。
![https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_224314_4e41420a-5569-3ed8-3bac-2fb3d9588b36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543993/26a30d30-8f77-1ace-e964-456bd49b14d3.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543993/723879c7-111b-32df-4e3d-597d57aa7bdc.png)

変数subscriptionの中身は下記のように定期課金IDと顧客IDとプランIDを持ってます。
![スクリーンショット 2021-01-08 19.47.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543993/21ac29a9-0431-c3d5-85cf-8fb858bfdfa2.png)

コンソールで見るとこんな感じ。
![スクリーンショット 2021-01-08 20.00.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543993/58a7107b-80c3-a829-d84d-954382537561.png)

プラン詳細画面でも設定したプラン通りになっているのがわかります。
![スクリーンショット 2021-01-08 20.07.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/543993/2d50bb6e-e640-b0b4-4338-fc35e2732a5b.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まとめ](https://qiita.com/Toman1223/items/8633142312bfa886d50b "activehash")

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

jQueryを使用するので、railsに未設定の場合は設定をしてください。
[設定方法はこちら](https://qiita.com/ngron/items/95846bd630a723e00038 "Qiita")！

```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.テストカードで決済してみよう

こちらの[テストカード](https://pay.jp/docs/testcard "test")で登録するようにしてください。
それ以外を打ち込んだ場合はトークンが発行できずはねられてしまいます。


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