[Rails] Introducing pay.jp (from view customization to registration)

At first

We have implemented the credit card registration function required when purchasing products using payjp. Keep a memo of the procedure from customizing the view to registering it.

Development environment

・ Rails 5.2.4.2 ・ Ruby 2.5.1

What is pay.jp

https://pay.jp/docs/started Simply put, it is a service that handles credit card registration and payment on your behalf. Helps implement the feature by using the payjp gem.

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

By the way, it is prohibited to save the card information itself in the DB. By calling the information stored in payjp with the customer id or card id, it is a mechanism to respond to information acquisition and payment. The point is that the card and customer information created by payjp and the associated id are passed, so save that id in the DB and refer to it from payjp. I think that is the image. In terms of security, it is extremely 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 feature implementation

・ After acquiring a PAY.JP account, check the API key.

https://pay.jp/

After registering and logging in, click on the API page to see your test private and test public keys. スクリーンショット 2020-06-09 19.12.21.png

・ Add gem

gem 'payjp'
gem 'dotenv-rails'

Make pay.jp and dotenv available. dotenv is a gem that allows environment variables to be read in a file. dotenv, there is a process that you have to read the environment variables later, so add it first. After adding, bundle install.

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

スクリーンショット 2020-06-10 17.58.06.png Enter the private and public keys you confirmed earlier in the .env file.
PAYJP_PRIVATE_KEY ='sk_test_****************:'
PAYJP_KEY ='pk_test_************************'

If important information such as payjp key is leaked, it is dangerous, so add the following description to the .gitgnore file so that it will 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 that specializes only in tokenizing card information. Since the payjp.js library is hosted at https://js.pay.jp, it will load and use this domain, so it will not work locally. By reading this, you will be able to use methods such as token acquisition from payjp.

Implementation

1. Create view

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

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

スクリーンショット 2020-06-09 19.45.21.png Although it is in the process of being created, it will look 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 the back of the 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, so It is mandatory to specify id and name attributes for form elements other than form and submit button, respectively.

2. Create token data to be sent to payjp with Javascript

Since jQuery is used, please 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 token data to be sent to payjp, and use that token as a key to register credit card information. After having your credit card information entered, we will implement it using Javascript to create a "token" based on that information.

$(document).on('turbolinks:load', function() {
 var form = $ ("# charge-form"); // Assign the one with id ”charge-form” to form.
 Payjp.setPublicKey ('pk_test_839895c840d4f91f7e75df7e'); // Write the public key directly so that you can refer to it.
 $ (document) .on ("click", "# token_submit", function (e) {// Works when e is pressed.
 e.preventDefault (); // First, stop the rails processing and perform the js processing first.
    form.find("input[type=submit]").prop("disabled", true);
 var card = {// Get the credit card information entered in the card variable based on the 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 token. The previous card information is returned as an encrypted token.
 if (response.error) {// If the value is an error
 alert ('Incorrect card information');
      }
 else {// if no error occurs
        $("#card_number").removeAttr("name");
        $("#cvc").removeAttr("name");
        $("#exp_month").removeAttr("name");
 $ ("# exp_year"). removeAttr ("name"); // Delete the value because it is not saved in the DB.
        var token = response.id;
 alert ("Registration completed");
 form.append ($ ('<input type = "hidden" name = "payjpToken" />'). val (token)); // Since the token is saved in db, insert the token created by js into form There is.
 form.get (0) .submit (); // gets the data inserted earlier in form.
      }
    });
  });
});

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

Payjp.setPublicKey('pk_test_xxxxxxxxxxxx'); If you do not describe this, communication with the Payjp server will not be performed and the token will not be issued, so enter the API key you confirmed earlier.

3. Creating a model and table

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 for creating a credit_card model and saving PAY.JP information. -User_id ... id of User table ・ Customer_id ... customer id of payjp ・ Card_id ... the id of the default card for payjp Make it a name corresponding to the column sent from payjp 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 a 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

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

5. Controller fix.


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 # Jump to new action for credit card registration
  end

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

 require "payjp" #Allow to get API key.

  def pay
 Payjp.api_key = ENV ["PAYJP_PRIVATE_KEY"] We are authenticating with the private key to get the value sent from #payjp.
    if params['payjpToken'].blank? 
 render: new_credit_card If the payjpToken created by #JS is from, try again.
    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

Since the official article was easy to understand for getting the value from pay.jp, I referred to it. https://pay.jp/docs/api/?ruby#payjp-api

After registering the address in the wizard format, I am redirect_toing the credit card registration, so I am filling in from the create_address method. I also posted a wizard-style implementation of the registration function using devise in the article, so I hope you find it helpful. https://qiita.com/Nosuke0808/items/00a8cac860abd68e2688

As an aside, when I implemented it, I used render in the wizard format implementation, but I thought it would be better to switch screens with redirect_to. Currently, if you reload the page while registering the address, a routing error will occur. The appearance of the user is the same whether it is render or redirect_to, and if it reloads and an error occurs, it will be useless, so when each registration is completed, save it in the DB and redirect to the next page. I felt that it was good. I'm looking for a way to avoid errors when reloading using render in the future, but I plan to rewrite it to redirect_to.

This completes the registration of the credit card using payjp. After implementing, the data created by js could not be sent to pay.jp, and the data acquisition from pay.jp did not go well, so it took a long time to implement. If you can understand it, it will not be difficult, so I hope it will be helpful for those who are going to implement payjp from now on.

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/c01f29f8398cc7f5c396 https://payjp.hatenablog.com/entry/2017/12/05/134933 https://pay.jp/docs/api/?ruby#payjp-api

Recommended Posts

[Rails] Introducing pay.jp (from view customization to registration)
Introducing Bootstrap to Rails 5
Introducing full calendar to Rails application
Introducing React to Rails with react-rails
From pulling docker-image of rails to launching
[Rails] How to convert from erb to haml
Introducing Pacemaker from CentOS 7 repository to RHEL7
[Rails] Assigning variables from controller to JavaScript
How to link Rails6 Vue (from environment construction)
Introducing New Relic to Rails apps on Heroku
Rails Tutorial Chapter 1 From Zero to Deployment [Try]
[Rails] Introducing jquery
[Rails] Introducing devise
[Rails] View sharing
[Rails] From test preparation to model unit testing [RSpec]
[Rails] Implement credit card registration / deletion function in PAY.JP
[Updated from time to time] Ruby on Rails Convenient methods
How to download images from AWS S3 (rails, carrierwave)
Rails Tutorial 4th Edition: Chapter 1 From Zero to Deployment
[Rails] How to edit and customize devise view and controller
Using PAY.JP API with Rails ~ Card Registration ~ (payjp.js v2)
[Ruby on Rails] From MySQL construction to database change
[Rails] How to load JavaScript in a specific view
[Rails] How to display an image in the view