pay.jpの導入

#Pay.jpの導入方法
##クレジットカード決済の代行サービスで、簡単なオープンAPIで導入する事ができる
#Why
##コード量も非常に少なく、Javascriptで簡単にフォーム送信までできるのでご紹介します（割愛しながらの説明になります）
#アプリケーションの作成
```java:ターミナル
% cd ~/projects(好きなように)
% rails _6.0.0_ new payjp_practice -d mysql
% cd payjp_practice
% rails db:create
```
#Orderモデル作成
```java:ターミナル
% rails g model order
```
#Ordersテーブル作成
```java:db/migrate/**************_create_orders.rb
class CreateOrders < ActiveRecord::Migration[6.0]
  def change
    create_table :orders do |t|
      t.integer :price  ,null: false
      t.timestamps
    end
  end
end
```
#忘れずに！
```java:ターミナル
% rails db:migrate
```
#バリデーション
```java:app/models/order.rb
class Order < ApplicationRecord
  validates :price, presence: true
end
```
#ルーティング
```java:config/routes.rb
Rails.application.routes.draw do
  root to: 'orders#index'
  resources :orders, only:[:create]
end
```
#ordersコントローラー
```java:ターミナル
% rails g controller orders
```
#
```java:app/controllers/orders_controller.rb
class OrdersController < ApplicationController

  def index
  end

  def create
  end

end
```
#ビューを作成
```java:app/views/orders/index.html.erb
<%= form_with  model: @order, id: 'charge-form', class: 'card-form',local: true do |f| %>
  <div class='form-wrap'>
    <%= f.label :price, "金額" %>
    <%= f.text_field :price, class:"price", placeholder:"例)2000" %>
  </div>
  <%= f.submit "購入" ,class:"button" %>
<% end %>
```
#CSS記述
```java:app/assets/stylesheets/style.css
.card-form{
  width: 500px;
}

.form-wrap{
  display: flex;
  flex-direction: column;
}

.exp_month{
  resize:none;
}

.exp_year{
  resize:none;
}

.input-expiration-date-wrap{
  display: flex;
}


.button{
  margin-top: 30px;
  height: 30px;
  width: 100px;
}
```
#コントローラー編集
```java:app/controllers/orders_controller.rb
class OrdersController < ApplicationController

  def index
    @order = Order.new
  end

  def create
    @order = Order.new(order_params)
    if @order.valid?
      @order.save
      return redirect_to root_path
    else
      render 'index'
    end
  end

  private

  def order_params
    params.require(:order).permit(:price)
  end

end
```
#部分テンプレート
```java:app/views/orders/index.html.erb
<%= form_with  model: @order, id: 'charge-form', class: 'card-form',local: true do |f| %>
  <%= render 'layouts/error_messages', model: @order %>
  <div class='form-wrap'>
<%# 省略 %>
```
#ビュー記述
```java:app/views/orders/index.html.erb
<%= form_with  model: @order, id: 'charge-form', class: 'card-form',local: true do |f| %>
  <%= render 'layouts/error_messages', model: @order %>
  <div class='form-wrap'>
    <%= f.label :price, "金額" %>
    <%= f.text_field :price, class:"price", placeholder:"例)2000" %>
  </div>
  <div class='form-wrap'>
    <%= f.label :number,  "カード番号" %>
    <%= f.text_field :number, class:"number", placeholder:"カード番号(半角英数字)", maxlength:"16" %>
  </div>
  <div class='form-wrap'>
    <%= f.label :cvc ,"ＣＶＣ" %>
    <%= f.text_field :cvc, class:"cvc", placeholder:"カード背面4桁もしくは3桁の番号", maxlength:"3" %>
  </div>
  <div class='form-wrap'>
    <p>有効期限</p>
    <div class='input-expiration-date-wrap'>
      <%= f.text_field :exp_month, class:"exp_month", placeholder:"例)3" %>
      <p>月</p>
      <%= f.text_field :exp_year, class:"exp_year", placeholder:"例)24" %>
      <p>年</p>
    </div>
  </div>
  <%= f.submit "購入" ,class:"button" %>
<% end %>
```
#turbolinks削除&コード追加
```java:app/views/layouts/application.html.erb
<%# 省略 %>
    <%= stylesheet_link_tag 'application', media: 'all'  %>
    <%= javascript_pack_tag 'application' %>
<%# 省略 %>
```
#
```java:app/javascript/packs/application.js
// 省略
require("@rails/ujs").start()
// require("turbolinks").start() // コメントアウトする
require("@rails/activestorage").start()
require("channels")
// 省略
```
#payjp.js読み込み
```java:app/views/layouts/application.html.erb
<%# 省略 %>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <script type="text/javascript" src="https://js.pay.jp/v1/"></script>
    <%= stylesheet_link_tag 'application', media: 'all' %>
    <%= javascript_pack_tag 'application' %>
<%# 省略 %>
```
#トークン化準備
```java:app/javascript/packs/application.js
// 省略
require("@rails/ujs").start()
// require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
require("../card")
// 省略
```
#イベント発火
```java:app/javascript/card.js
const pay = () => {
  const form = document.getElementById("charge-form");
  form.addEventListener("submit", (e) => {
    e.preventDefault();
    console.log("フォーム送信時にイベント発火")
  });
};

window.addEventListener("load", pay);
```
#公開鍵設定
```java:app/javascript/card.js
const pay = () => {
  Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵
  const form = document.getElementById("charge-form");
  form.addEventListener("submit", (e) => {
    e.preventDefault();
    console.log("フォーム送信時にイベント発火")
  });
};

window.addEventListener("load", pay);
```
#フォームの情報取得
```java:app/javascript/card.js
const pay = () => {
  Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵
  const form = document.getElementById("charge-form");
  form.addEventListener("submit", (e) => {
    e.preventDefault();

    const formResult = document.getElementById("charge-form");
    const formData = new FormData(formResult);

    const card = {
      number: formData.get("order[number]"),
      cvc: formData.get("order[cvc]"),
      exp_month: formData.get("order[exp_month]"),
      exp_year: `20${formData.get("order[exp_year]")}`,
    };
  });
};

window.addEventListener("load", pay);
```
#カードの情報トークン化
```java:app/javascript/card.js
const pay = () => {
  Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵
  const form = document.getElementById("charge-form");
  form.addEventListener("submit", (e) => {
    e.preventDefault();

    const formResult = document.getElementById("charge-form");
    const formData = new FormData(formResult);

    const card = {
      number: formData.get("order[number]"),
      cvc: formData.get("order[cvc]"),
      exp_month: formData.get("order[exp_month]"),
      exp_year: `20${formData.get("order[exp_year]")}`,
    };

    Payjp.createToken(card, (status, response) => {
      if (status == 200) {
        const token = response.id;
        console.log(token)
      }
    });
  });
};

window.addEventListener("load", pay);
```
##一度pay.jpが用意しているテスト用のカード情報を入力してチェックしておきましょう！
カード番号	4242424242424242（16桁）
CVC	123
有効期限	登録時より未来（04/25など）
#トークンの値をフォームに含める
```java:app/javascript/card.js
const pay = () => {
  Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵
  const form = document.getElementById("charge-form");
  form.addEventListener("submit", (e) => {
    e.preventDefault();

    const formResult = document.getElementById("charge-form");
    const formData = new FormData(formResult);

    const card = {
      number: formData.get("order[number]"),
      cvc: formData.get("order[cvc]"),
      exp_month: formData.get("order[exp_month]"),
      exp_year: `20${formData.get("order[exp_year]")}`,
    };

    Payjp.createToken(card, (status, response) => {
      if (status == 200) {
        const token = response.id;
        const renderDom = document.getElementById("charge-form");
        const tokenObj = `<input value=${token} name='token'>`;
        renderDom.insertAdjacentHTML("beforeend", tokenObj);
        debugger;
      }
    });
  });
};

window.addEventListener("load", pay);
```
#トークンの値を非表示
```java:app/javascript/card.js
const pay = () => {
  Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵
  const form = document.getElementById("charge-form");
  form.addEventListener("submit", (e) => {
    e.preventDefault();

    const formResult = document.getElementById("charge-form");
    const formData = new FormData(formResult);

    const card = {
      number: formData.get("order[number]"),
      cvc: formData.get("order[cvc]"),
      exp_month: formData.get("order[exp_month]"),
      exp_year: `20${formData.get("order[exp_year]")}`,
    };

    Payjp.createToken(card, (status, response) => {
      if (status == 200) {
        const token = response.id;
        const renderDom = document.getElementById("charge-form");
        const tokenObj = `<input value=${token} name='token' type="hidden"> `;
        renderDom.insertAdjacentHTML("beforeend", tokenObj);
        debugger;
      }
    });
  });
};

window.addEventListener("load", pay);
```
#クレジットカードの情報を削除
```java:app/javascript/card.js
const pay = () => {
  Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵
  const form = document.getElementById("charge-form");
  form.addEventListener("submit", (e) => {
    e.preventDefault();

    const formResult = document.getElementById("charge-form");
    const formData = new FormData(formResult);

    const card = {
      number: formData.get("order[number]"),
      cvc: formData.get("order[cvc]"),
      exp_month: formData.get("order[exp_month]"),
      exp_year: `20${formData.get("order[exp_year]")}`,
    };

    Payjp.createToken(card, (status, response) => {
      if (status == 200) {
        const token = response.id;
        const renderDom = document.getElementById("charge-form");
        const tokenObj = `<input value=${token} name='token' type="hidden"> `;
        renderDom.insertAdjacentHTML("beforeend", tokenObj);
      }

      document.getElementById("order_number").removeAttribute("name");
      document.getElementById("order_cvc").removeAttribute("name");
      document.getElementById("order_exp_month").removeAttribute("name");
      document.getElementById("order_exp_year").removeAttribute("name");
    });
  });
};

window.addEventListener("load", pay);
```
#フォームの情報をサーバーサイドに送信
```java:app/javascript/card.js
const pay = () => {
  Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵
  const form = document.getElementById("charge-form");
  form.addEventListener("submit", (e) => {
    e.preventDefault();

    const formResult = document.getElementById("charge-form");
    const formData = new FormData(formResult);

    const card = {
      number: formData.get("order[number]"),
      cvc: formData.get("order[cvc]"),
      exp_month: formData.get("order[exp_month]"),
      exp_year: `20${formData.get("order[exp_year]")}`,
    };

    Payjp.createToken(card, (status, response) => {
      if (status == 200) {
        const token = response.id;
        const renderDom = document.getElementById("charge-form");
        const tokenObj = `<input value=${token} name='token' type="hidden"> `;
        renderDom.insertAdjacentHTML("beforeend", tokenObj);
      }

      document.getElementById("order_number").removeAttribute("name");
      document.getElementById("order_cvc").removeAttribute("name");
      document.getElementById("order_exp_month").removeAttribute("name");
      document.getElementById("order_exp_year").removeAttribute("name");

      document.getElementById("charge-form").submit();
    });
  });
};

window.addEventListener("load", pay);
```
#ストロングパラメーター編集
```java:app/controllers/orders_controller.rb
#省略

  private

  def order_params
    params.require(:order).permit(:price).merge(token: params[:token])
  end

end
```
#Orderモデルに追記
```java:app/models/order.rb
class Order < ApplicationRecord
  attr_accessor :token
  validates :price, presence: true
end
```
#Gem導入
```java:Gemfile
# 省略
gem 'payjp'
```
#決済処理の記述とリファクタリング
```java:app/controllers/orders_controller.rb
class OrdersController < ApplicationController

  def index
    @order = Order.new
  end

  def create
    @order = Order.new(order_params)
    if @order.valid?
      pay_item
      @order.save
      return redirect_to root_path
    else
      render 'index'
    end
  end

  private

  def order_params
    params.require(:order).permit(:price).merge(token: params[:token])
  end

  def pay_item
    Payjp.api_key = "sk_test_***********"  # 自身のPAY.JPテスト秘密鍵を記述しましょう
    Payjp::Charge.create(
      amount: order_params[:price],  # 商品の値段
      card: order_params[:token],    # カードトークン
      currency: 'jpy'                 # 通貨の種類（日本円）
    )
  end

end
```
#バリデーション
```java:app/models/order.rb
class Order < ApplicationRecord
  attr_accessor :token
  validates :price, presence: true
  validates :token, presence: true
end
```
#環境変数（Mac Catalina以降の場合）
```java:ターミナル
% vim ~/.zshrc
# iを押してインサートモードに移行し、下記を追記する。既存の記述は消去しない。
export PAYJP_SECRET_KEY='sk_test_************'
export PAYJP_PUBLIC_KEY='pk_test_************'
# 編集が終わったらescキーを押してから:wqと入力して保存して終了
```
#
```java:ターミナル
# 編集した.zshrcを読み込み直して、追加した環境変数を使えるようにする
% source ~/.zshrc
```
#秘密鍵代入した環境変数の呼び込み
```java:app/controllers/orders_controller.rb
#省略
def pay_item
   Payjp.api_key = ENV["PAYJP_SECRET_KEY"]
   Payjp::Charge.create(
     amount: order_params[:price],
     card: order_params[:token],
     currency:'jpy'
   )
end
```
#JavaScriptで環境変数の呼び込み
```java:ターミナル
% touch config/initializers/webpacker.rb
```
#
```java:config/initializers/webpacker.rb
Webpacker::Compiler.env["PAYJP_PUBLIC_KEY"] = ENV["PAYJP_PUBLIC_KEY"]
```
#
```java:app/javascript/card.js
const pay = () => {
 Payjp.setPublicKey(process.env.PAYJP_PUBLIC_KEY);
  // 省略
```
#まとめ
##簡単と言っておきながら意外と記述は多かったかもしれません。ですが、APIの中でも比較的簡単な決済機能の導入なので抑えておくといいかもしれません。オリジナルでカラム等追加する場合がほとんどだと思いますので、ゆっくり順に書くことをお勧めします！以上！