Introduction of pay.jp

How to install Pay.jp

It is a credit card payment agency service that can be introduced with a simple open API.

Why

The amount of code is very small, and you can easily submit a form with Javascript, so I will introduce it (I will omit it)

Creating an application

Terminal


% cd ~/projects(as you like)
% rails _6.0.0_ new payjp_practice -d mysql
% cd payjp_practice
% rails db:create

Order model creation

Terminal


% rails g model order

Create Orders table

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

Don't forget!

Terminal


% rails db:migrate

Validation

app/models/order.rb


class Order < ApplicationRecord
  validates :price, presence: true
end

routing

config/routes.rb


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

orders controller

Terminal


% rails g controller orders

app/controllers/orders_controller.rb


class OrdersController < ApplicationController

  def index
  end

  def create
  end

end

Create a view

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, "Amount of money" %>
    <%= f.text_field :price, class:"price", placeholder:"Example)2000" %>
  </div>
  <%= f.submit "Purchase" ,class:"button" %>
<% end %>

CSS description

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

Controller editing

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

Partial template

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'>
<%#abridgement%>

View description

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, "Amount of money" %>
    <%= f.text_field :price, class:"price", placeholder:"Example)2000" %>
  </div>
  <div class='form-wrap'>
    <%= f.label :number,  "card number" %>
    <%= f.text_field :number, class:"number", placeholder:"card number(Half-width alphanumeric characters)", maxlength:"16" %>
  </div>
  <div class='form-wrap'>
    <%= f.label :cvc ,"CVC" %>
    <%= f.text_field :cvc, class:"cvc", placeholder:"4-digit or 3-digit number on the back of the card", maxlength:"3" %>
  </div>
  <div class='form-wrap'>
    <p>expiration date</p>
    <div class='input-expiration-date-wrap'>
      <%= f.text_field :exp_month, class:"exp_month", placeholder:"Example)3" %>
      <p>Month</p>
      <%= f.text_field :exp_year, class:"exp_year", placeholder:"Example)24" %>
      <p>Year</p>
    </div>
  </div>
  <%= f.submit "Purchase" ,class:"button" %>
<% end %>

remove turbolinks & add code

java:app/views/layouts/application.html.erb


<%#abridgement%>
    <%= stylesheet_link_tag 'application', media: 'all'  %>
    <%= javascript_pack_tag 'application' %>
<%#abridgement%>

app/javascript/packs/application.js


//abridgement
require("@rails/ujs").start()
// require("turbolinks").start() //Comment out
require("@rails/activestorage").start()
require("channels")
//abridgement

Read payjp.js

java:app/views/layouts/application.html.erb


<%#abridgement%>
    <%= 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' %>
<%#abridgement%>

Tokenization preparation

app/javascript/packs/application.js


//abridgement
require("@rails/ujs").start()
// require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
require("../card")
//abridgement

Event firing

app/javascript/card.js


const pay = () => {
  const form = document.getElementById("charge-form");
  form.addEventListener("submit", (e) => {
    e.preventDefault();
    console.log("Event fired when submitting form")
  });
};

window.addEventListener("load", pay);

Public key setting

app/javascript/card.js


const pay = () => {
  Payjp.setPublicKey("pk_test_******************"); // PAY.JP test public key
  const form = document.getElementById("charge-form");
  form.addEventListener("submit", (e) => {
    e.preventDefault();
    console.log("Event fired when submitting form")
  });
};

window.addEventListener("load", pay);

Get form information

app/javascript/card.js


const pay = () => {
  Payjp.setPublicKey("pk_test_******************"); // PAY.JP test public key
  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);

Card information tokenization

app/javascript/card.js


const pay = () => {
  Payjp.setPublicKey("pk_test_******************"); // PAY.JP test public key
  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);

Let's check by entering the test card information prepared by pay.jp once!

Card number 4242424242424242 (16 digits) CVC 123 Expiration date Future from registration (04/25, etc.)

Include the token value in the form

app/javascript/card.js


const pay = () => {
  Payjp.setPublicKey("pk_test_******************"); // PAY.JP test public key
  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);

Hide token value

app/javascript/card.js


const pay = () => {
  Payjp.setPublicKey("pk_test_******************"); // PAY.JP test public key
  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);

Delete credit card information

app/javascript/card.js


const pay = () => {
  Payjp.setPublicKey("pk_test_******************"); // PAY.JP test public key
  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);

Submit form information to server side

app/javascript/card.js


const pay = () => {
  Payjp.setPublicKey("pk_test_******************"); // PAY.JP test public key
  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);

Strong parameter editing

app/controllers/orders_controller.rb


#abridgement

  private

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

end

Added to Order model

app/models/order.rb


class Order < ApplicationRecord
  attr_accessor :token
  validates :price, presence: true
end

Introducing Gem

Gemfile


#abridgement
gem 'payjp'

Payment processing description and refactoring

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_***********"  #Own PAY.Write the JP test private key
    Payjp::Charge.create(
      amount: order_params[:price],  #Product price
      card: order_params[:token],    #Card token
      currency: 'jpy'                 #Currency type (Japanese Yen)
    )
  end

end

Validation

app/models/order.rb


class Order < ApplicationRecord
  attr_accessor :token
  validates :price, presence: true
  validates :token, presence: true
end

Environment variables (for Mac Catalina and later)

Terminal


% vim ~/.zshrc
#Press i to switch to insert mode and add the following. Do not delete the existing description.
export PAYJP_SECRET_KEY='sk_test_************'
export PAYJP_PUBLIC_KEY='pk_test_************'
#After editing, press the esc key:Type wq to save and exit

Terminal


#Edited.Reload zshrc to use the added environment variables
% source ~/.zshrc

Invitation of environment variables assigned private key

app/controllers/orders_controller.rb


#abridgement
def pay_item
   Payjp.api_key = ENV["PAYJP_SECRET_KEY"]
   Payjp::Charge.create(
     amount: order_params[:price],
     card: order_params[:token],
     currency:'jpy'
   )
end

Calling environment variables with JavaScript

Terminal


% 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);
  //abridgement

Summary

Although it was easy, there may have been a lot of descriptions. However, it may be good to suppress it because it introduces a relatively simple payment function among APIs. In most cases, you will add columns etc. in the original, so it is recommended to write slowly in order! that's all!

Recommended Posts

Introduction of pay.jp
[Rails] Introduction of PAY.JP
Introduction of milkode
Introduction of Docker --Part 1--
Introduction of user authentication
[Rails] Introduction of devise Basics
[Docker] Introduction of basic Docker Instruction
[Rails] Introduction of Rubocop by beginners
From introduction to use of ActiveHash
From introduction to usage of byebug
Summary about the introduction of Device
Lombok ① Introduction
Introduction (self-introduction)
Introduction memo of automatic test using Jenkins
[In-house study session] Introduction of "Readable Code"
Personal application production 2 Introduction of WEB fonts
Introduction of Docker Hub and commands Self-learning ①
Introduction of New Generation Java Programming Guide (Java 10)
Output of the book "Introduction to Java"
[Java] Introduction
Introduction and precautions of gem Friendly Id
12 of Array
Introduction (editing)
Introduction of IDOM engineer's development environment (physics)
Introduction and usage explanation of Font Awesome
Introduction of New Generation Java Programming Guide (Java 11)
[Vue.js] Implementation of menu function Vue.js introduction rails6
Introduction of New Generation Java Programming Guide (Java 12)
[Rails] Introduction of pry-rails ~ How to debug binding.pry
[Docker] Introduction to docker compose Basic summary of docker-compose.yml
[Ruby on Rails] Until the introduction of RSpec
Introduction and basic usage of Simple Calendar gem
How to use enum (introduction of Japanese notation)
Introduction of Rspec and Japanese localization of error messages
[Introduction to Java] Basics of java arithmetic (for beginners)
Introduction and introduction of management screen generation gem Administrate