[Java] [Rails] Introducing pay.jp (custom view to registration)

7 minute read

At first

Implemented the credit card registration function required when purchasing products, etc. using payjp. The procedure from customizing the view to registration is left as a memorandum.

Development environment

  • Rails 5.2.4.2 ・Ruby 2.5.1

What is pay.jp

https://pay.jp/docs/started To put it simply, it is a service that handles credit card registration to payment. Using the payjp gem will help you implement the feature.

https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_513632_19d75450-6adb-d809-1541-e37e81b1650f.png

By the way, saving the card information itself in the DB is prohibited. The information stored in payjp is called by the customer ID or the card ID so that the information can be obtained and payments can be made. The point is that you pass the information of the card and customer made by payjp and the id you got, so please save the id in the DB and refer to it from payjp. I think that is the image. In terms of security, it is very dangerous to store credit card information in the DB, so I think it is in this form. (See URL below) http://payjp-announce.hatenablog.com/entry/2017/11/10/182738

Before implementing the function

・Check the API key after obtaining the PAY.JP account.

https://pay.jp/

After registering and logging in, click the API page to see your test private key and test public key. Screenshots 2020-06-09 19.12.21.png

###・Add gem

gem'payjp'
gem'dotenv-rails'

Allows you to use pay.jp and dotenv. dotenv is a gem that allows you to read environment variables as files. dotenv, as there is a process in which environment variables must be read later, add it first. After adding, bundle install.

After installation is complete, create an .env file in your home directory.

Screenshot 2020-06-10 17.58.06.png In the .env file, enter the private key and public key that you confirmed earlier.

PAYJP_PRIVATE_KEY ='sk_test_****************:'
PAYJP_KEY ='pk_test_****************************'

Add the following description to the .gitgnore file so that it will be dangerous if important information such as payjp key is leaked so that it does not go up to git.

/.env

You can also enter the code directly in the environment variable. https://qiita.com/daisukeshimizu/items/c01f29f8398cc7f5c396

Fixed ###view application.html.haml.

%script{src: "https://js.pay.jp/", type: "text/javascript"}

Add this sentence so that payjp.js can be read by the head tag.

 !!!
%html
  %head
    %meta{content: "text/html; charset=UTF-8", "http-equiv": "Content-Type"}/
    %title EcApp
    %script{src: "https://js.pay.jp/", type: "text/javascript"}
    = csrf_meta_tags
    = csp_meta_tag
    = stylesheet_link_tag'application', media:'all','data-turbolinks-track':'reload'
    = javascript_include_tag'application','data-turbolinks-track':'reload'
  %body
    = yield

payjp.js is a library specialized only for tokenizing card information. The payjp.js library is hosted at https://js.pay.jp, so this domain is read and used, so it does not work locally. By reading this, you can use methods such as token acquisition from payjp.

Implementation

Creating ###1.view

  <script type="text/javascript" src="https://checkout.pay.jp" class="payjp-button" data-key="public key"></script>

There is also a method to add one line of script tag and use the view prepared by payjp in advance, but I will omit it because I want to customize the view this time.

Screenshot 2020-06-09 19.45.21.png Although it is in the process of being created, the view will be like this.

= render partial: "items/header"
.main
  %h2 new member registration
  = form_with url: creditcards_path, method: :post, id:'charge-form', html: {name: "inputForm" }do |f|
    .main__form
      .main__form__registration
        Credit card registration page
      .main__form__field
        %label.main__form__field__label Card number
        = f.text_field :number, name: "number", id: "card_number", type: "text", placeholder:'half-width numbers only', class:'main__form__field__box', maxlength: "16"
        =image_tag "material/credit/credit.png", class:"main__form__field__credit-image"
      .main__form__field2
        %label.main__form__field__label expiration date
        = f.select :exp_month, [["--","--"],["01",1],["02",02],["03",03],["04",04 ],["05",05],["06",6],["07",07],["08",8],["09",9],["10",10], ["11",11],["12",12]],{}, class:'credit-box', name: "exp_month", id:"exp_month"
        %span.month month
        = f.select :exp_year, [["--","--"],["2019",2019],["2020",2020],["2021",2021],["2022",2022 ],["2023",2023],["2024",2024],["2025",2025],["2026",2026],["2027",2027],["2028",2028], ["2029",2029]], {}, class:'credit-box', name: "exp_year", id:"exp_year"
        %span.year year
      .main__form__field
        %label.main__form__field__label security code
        = f.text_field :cvc, name: "cvc", id:"cvc", type: "text", placeholder: '4 or 3 digit number on back of card', class: "main__form__field__box", maxlength: "4"
      .main__form__field
        = f.submit'register', id: "token_submit", class: "main__form__field__submit-btn"

After this, the value received in the form with payjp.js is assigned to [payjpToken] as token data and the data is sent to payjp. It is mandatory to specify id and name attributes to form elements other than form and submit button respectively.

Create the token data to be sent to ###2.payjp with Javascript Since jQuery is used, set it if it is not set. https://qiita.com/ngron/items/95846bd630a723e00038 I think this article will be helpful.

Once you can use jQuery, In the payjp.js file, create the token data to be sent to payjp, and use the token as a key to register credit card information etc. After having credit card information entered, we will implement it by utilizing Javascript to create a “token” based on that information.

$(document).on('turbolinks:load', function() {
  var form = $("#charge-form"); //Substitute the id “charge-form” for form.Payjp.setPublicKey('pk_test_839895c840d4f91f7e75df7e'); // Directly write the public key so that you can refer to it.
  $(document).on("click", "#token_submit", function(e) {// Fires when e is pressed.
    e.preventDefault(); // Stop rails process first, then process js first.
    form.find("input[type=submit]").prop("disabled", true);
    var card = {// Get the credit card information entered in the card variable based on id, and assign it to the card variable. ..
        number: $("#card_number").val(),
        cvc: $("#cvc").val(),
        exp_month: $("#exp_month").val(),
        exp_year: $("#exp_year").val(),
    };
    Payjp.createToken(card, function(s, response) {// Generate a token. The card information from earlier will be returned as an encrypted token called token.
      if (response.error) {// if the value was an error
        alert('The card information is incorrect');
      }
      else {/ if no error
        $("#card_number").removeAttr("name");
        $("#cvc").removeAttr("name");
        $("#exp_month").removeAttr("name");
        $("#exp_year").removeAttr("name"); // Delete the value as it is not saved in the DB.
        var token = response.id;
        alert("Registration is complete");
        form.append($('<input type="hidden" name="payjpToken"/>').val(token)); //Since the token is saved in db, insert the token created in js into form There is.
        form.get(0).submit(); // We are getting the data we inserted earlier in the form.
      }
    });
  });
});

https://payjp.hatenablog.com/entry/2017/12/05/134933 Arranged by referring to the sample payjp.js provided by pay.jp.

Payjp.setPublicKey(‘pk_test_xxxxxxxxxxxx’); If you do not write this, communication with the Payjp server is not performed and the token will not be issued, so enter the API key you confirmed earlier. *Since the specification is such that the button cannot be pressed without reloading when an error still occurs, we plan to add a modified one in the future.

3. Creating models and tables

class CreateCards <ActiveRecord::Migration[5.2]
  def change
    create_table :cards do |t|
      t.references :user, foreign_key: true, null: false
      t.string :customer_id, null: false
      t.string :card_id, null: false
      t.timestamps
    end
  end
end

This is a table to create credit_card model and save PAY.JP information. ・User_id … id of User table ・Customer_id … customer id of payjp ・Card_id … id of payjp default card The name corresponding to the column sent from payjp will be used so that it can be registered in the DB.

class CreditCard <ApplicationRecord
  #association
  belongs_to :user

  #Validation
  validates :user_id, :customer_id, :card_id, presence: true
end

4. Create routing.


Rails.application.routes.draw do

  #devise routing
  devise_for :users, controllers: {
    registrations:'users/registrations'
  }
  devise_scope :user do
    post'users/sign_up', to:'users/registrations#create'
    get'addresses', to:'users/registrations#new_address'
    post'addresses', to:'users/registrations#create_address'
    get'creditcards', to:'users/registrations#new_credit_card'
    post'creditcards', to:'users/registrations#pay'
  end

Since I want to register in the same flow as new registration, I decided to put it together in the registrations controller. Please note that the description will change when creating a credit_card controller.

5. Controller correction.


class Users::RegistrationsController <Devise::RegistrationsController

  def create_address
    @user = User.new(session["devise.regist_data"]["user"])
    @address = Address.new(address_params)
    unless @address.valid?
      flash.now[:alert] = @address.errors.full_messages
      render :new_address and return
    end
    @user.addresses.build(@address.attributes)
    @user.save
    session["devise.regist_data"]["user"].clear
    sign_in(:user, @user)
    redirect_to creditcards_path # Skip to new action for credit card registration
  end

  def new_credit_card
    card = CreditCard.where(user_id: current_user.id)
    Assign a hash containing the id of the logged-in user to the user_id column of #credit card in card.
  end

  require "payjp" # Allowed to get API key.

  def pay
    Payjp.api_key = ENV["PAYJP_PRIVATE_KEY"] We authenticate with the private key to get the value sent from #payjp.
    if params['payjpToken'].blank?
      render :new_credit_card #If the payjpToken created with JS is empty, start over.
    else
      customer = Payjp::Customer.create(card: params['payjpToken'],) The acquired value is assigned to the #customer variable.
      @creditcard = CreditCard.new(user_id: current_user.id, customer_id: customer.id, card_id: customer.default_card)
      if @creditcard.save
        redirect_to root_path
      else
        redirect_to action: "new_credit_card"
      end
    end
  end

The official article was easy to understand for getting the value from pay.jp, so I used it as a reference. https://pay.jp/docs/api/?ruby#payjp-api

After registering the address in the wizard format, I am redirecting to credit card registration, so I am filling in from the create_address method. The article also describes the wizard-based implementation of the registration function using devise, so I hope it will be helpful. https://qiita.com/Nosuke0808/items/00a8cac860abd68e2688

As an aside, I tried to implement it and in the implementation in the wizard format I use render, but I thought it would be better to switch the screen with redirect_to. Currently, if you reload the page while registering the address, a routing error will occur. The way the user sees is the same with render and redirect_to, and it will be useless if it reloads with an error, so save it in the DB at the stage where registration is completed and redirect it to the next page. I felt that I’m looking for a method that will not cause an error when reloading using render in the future, but I plan to rewrite it with redirect_to.

That’s all for the credit card registration using payjp. After implementation, the data created with js could not be sent to pay.jp, data acquisition from pay.jp did not work well, and it took a long time to implement. It’s not difficult if you can understand it, so I hope that it will be a little reference for those who are going to implement payjp.

References

https://techtechmedia.com/payjp-rails/ https://pay.jp/docs/started http://payjp-announce.hatenablog.com/entry/2017/11/10/182738 https://qiita.com/daisukeshimizu/items/c01f29f8398cc7f5c396https://payjp.hatenablog.com/entry/2017/12/05/134933 https://pay.jp/docs/api/?ruby#payjp-api