LoginSignup
3
9

More than 3 years have passed since last update.

pay.jpの導入

Last updated at Posted at 2020-10-05

Pay.jpの導入方法

クレジットカード決済の代行サービスで、簡単なオープンAPIで導入する事ができる

Why

コード量も非常に少なく、Javascriptで簡単にフォーム送信までできるのでご紹介します(割愛しながらの説明になります)

アプリケーションの作成

ターミナル
% cd ~/projects(好きなように)
% rails _6.0.0_ new payjp_practice -d mysql
% cd payjp_practice
% rails db:create

Orderモデル作成

ターミナル
% rails g model order

Ordersテーブル作成

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

忘れずに!

ターミナル
% rails db:migrate

バリデーション

app/models/order.rb
class Order < ApplicationRecord
  validates :price, presence: true
end

ルーティング

config/routes.rb
Rails.application.routes.draw do
  root to: 'orders#index'
  resources :orders, only:[:create]
end

ordersコントローラー

ターミナル
% rails g controller orders
app/controllers/orders_controller.rb
class OrdersController < ApplicationController

  def index
  end

  def create
  end

end

ビューを作成

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記述

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;
}

コントローラー編集

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

部分テンプレート

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'>
<%# 省略 %>

ビュー記述

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 ,"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削除&コード追加

app/views/layouts/application.html.erb
<%# 省略 %>
    <%= stylesheet_link_tag 'application', media: 'all'  %>
    <%= javascript_pack_tag 'application' %>
<%# 省略 %>
app/javascript/packs/application.js
// 省略
require("@rails/ujs").start()
// require("turbolinks").start() // コメントアウトする
require("@rails/activestorage").start()
require("channels")
// 省略

payjp.js読み込み

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' %>
<%# 省略 %>

トークン化準備

app/javascript/packs/application.js
// 省略
require("@rails/ujs").start()
// require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
require("../card")
// 省略

イベント発火

app/javascript/card.js
const pay = () => {
  const form = document.getElementById("charge-form");
  form.addEventListener("submit", (e) => {
    e.preventDefault();
    console.log("フォーム送信時にイベント発火")
  });
};

window.addEventListener("load", pay);

公開鍵設定

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);

フォームの情報取得

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);

カードの情報トークン化

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など)

トークンの値をフォームに含める

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);

トークンの値を非表示

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);

クレジットカードの情報を削除

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);

フォームの情報をサーバーサイドに送信

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);

ストロングパラメーター編集

app/controllers/orders_controller.rb
#省略

  private

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

end

Orderモデルに追記

app/models/order.rb
class Order < ApplicationRecord
  attr_accessor :token
  validates :price, presence: true
end

Gem導入

Gemfile
# 省略
gem 'payjp'

決済処理の記述とリファクタリング

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

バリデーション

app/models/order.rb
class Order < ApplicationRecord
  attr_accessor :token
  validates :price, presence: true
  validates :token, presence: true
end

環境変数(Mac Catalina以降の場合)

ターミナル
% vim ~/.zshrc
# iを押してインサートモードに移行し下記を追記する既存の記述は消去しない
export PAYJP_SECRET_KEY='sk_test_************'
export PAYJP_PUBLIC_KEY='pk_test_************'
# 編集が終わったらescキーを押してから:wqと入力して保存して終了
ターミナル
# 編集した.zshrcを読み込み直して追加した環境変数を使えるようにする
% source ~/.zshrc

秘密鍵代入した環境変数の呼び込み

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で環境変数の呼び込み

ターミナル
% touch config/initializers/webpacker.rb
config/initializers/webpacker.rb
Webpacker::Compiler.env["PAYJP_PUBLIC_KEY"] = ENV["PAYJP_PUBLIC_KEY"]
app/javascript/card.js
const pay = () => {
 Payjp.setPublicKey(process.env.PAYJP_PUBLIC_KEY);
  // 省略

まとめ

簡単と言っておきながら意外と記述は多かったかもしれません。ですが、APIの中でも比較的簡単な決済機能の導入なので抑えておくといいかもしれません。オリジナルでカラム等追加する場合がほとんどだと思いますので、ゆっくり順に書くことをお勧めします!以上!

3
9
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
3
9