[GO] Equipped with a card function using payjp

Equipped with a card function.

** Use gem ** devise PAY.JP for Ruby Gon gem

First of all, card registration function

However, it seems that it is prohibited by law for EC site operators to have card numbers. Therefore, such information cannot be registered in the DB. Instead, you will need to register your payjp ID.

** Card registration flow ** ・ The end user enters the card number etc. and clicks submit ・ Data will be sent to payjp and the card token will be returned. ・ Create a customer (owner) from the returned card token ・ Register customer ID and card ID in DB from created customer information

In my case, the association is

user has_one :card
card belongs_to :user

cards table

migrate/***create_cards.rb


      t.references  :user,        foreign_key: true,index:{unique: true}
      t.string      :customer_id, null: false
      t.string      :card_id,     null: false

Now, let's add the following to the Gemfile and bundle install.

Gemfile


gem 'payjp'
gem 'gon'

** Here is gon ** It is a gem that can pass ruby variables to JQuery (javascript).

** It may be wrong, but ** I think that css and JQuery (javascript) can be seen by the verification tool of the browser. So, I think it's a good idea to embed the key directly in JQuery (javascript). A test key may be fine, but you can't use a test key in production. There is also a way to write it in credentials.yml, but since personal keys are personal, I think it is desirable to manage them individually.

After the install is finished, you can use gon. application.html.haml Added above javascript_include_tag

ruby:app/views/layouts/application.html.haml


= include_gon
= javascript_include_tag 'application'

app/controllers/card_controller.rb


def new
    gon.api_key = ENV["PAYJP_PUBLIC_KEY"]
end

Because there is no need to load card-related js on every page This time I wrote the following directly in card / new.html.haml.

ruby:app/views/card/new.html.haml


:javascript
  let payjp = Payjp(gon.api_key);

Now that the environment variables are being read by JQuery, it seems that you don't have to expose the key. If you are co-developing, register each person who needs it in payjp and write it in the environment variable.

Since the new action is made above, create a registration form.

javascript:app/views/card/new.html.haml


= form_with url: card_index_path,local:true,id:"cardNew__form" do |f| //card_index_path is the path to the card controller's create action
    %input#cardNew__form--inputHidden{type:"hidden",name:"payjp-token"}
    #cardNew__form--input
    #cardNew__form--errMessage
    = f.submit "to register",id:"cardNew__form--submit"

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

:javascript //Here is JQuery.

  let payjp = Payjp(gon.api_key); //This is gon.
  let elements = payjp.elements();
  let cardElement = elements.create('card');

  cardElement.mount('#cardNew__form--input');
//Up to this point, the input fields prepared by PAYJP have been created. One point of the card is cute so I will use it as it is^^

  $(".cardNew").on("click","#cardNew__form--submit",
    function(e){
      e.preventDefault();
      payjp.createToken(cardElement).then(function(response){ //Here, send the card information to payjp and get the card token.
        if(response.error){
          $('#cardNew__form--errMessage').text(response.error.message);
        }else{
          $("#cardNew__form--inputHidden").attr("value",response.id); //response.id is the card token. Here input name="payjp-token"Give the value to.
          $("#cardNew__form").submit();
        }
      });
    }
  );

Card number: 424242424242 ↑ The expiration date is in the future, and CVC seems to be anything. Test card

Now press submit and the token will be sent to the create action. card_index_path is the path to the create action. If you don't see it well, please adjust it with CSS.

app/controllers/card_controller.rb


  def create
    if params["payjp-token"].blank?
      render :new
    else
      Payjp.api_key = ENV["PAYJP_SECRET_KEY"]
      customer = Payjp::Customer.create( #Here, create a customer who is associated with the card token.
        card: params["payjp-token"]
      )
      @card = Card.new(
        user_id: current_user.id,
        customer_id: customer.id, #This is the customer's id.
        card_id: customer.default_card #This is the id of the card associated with the customer.
      )
      if @card.save
        redirect_to new_card_path
      else
        render :new
      end
    end
  end

I think that you can register the card in the DB up to this point. If you want to make it easier, go to here

=form_with url: card_index_path,local:true,id:"new_form" do |f|
    %script{src:"https://checkout.pay.jp/",class:"payjp-button","data-key":"#{@api_key}"}

Copy and paste to the empty data-key to your public key Create @ape_key with new controller. The url is the same as before. id is arbitrary. I haven't tried class so I don't know. As for the controller, the new one is gon specification, so

@api_key = ENV["PAYJP_PUBLIC_KEY"]

I think it's OK if you set it to. It seems to send a token as the value of the payjp-token parameter. With this alone, it seems that a high-quality card input screen is completed. It's amazing.

Reference primary source PAY.JP Announcement payjp.js v2 reference payjp.js v2 Guide Create a customer

Reference blog [Rails] I briefly summarized the procedure for implementing the payment function using PAYJP Implementing credit card registration and deletion function with Payjp (Rails)

Then pay by credit card

As a flow

Click to purchase on the product details page Confirm purchase on the purchase confirmation page Credit card payment Create a payment ID and who bought it in the payments table Reduce the stock field in the items table and update

association

item has_many :payments
payment belogs_to :item

payments table

***create_payments.rb


      t.string      :charge_id, null: false
      t.references  :user,      foreign_key: true
      t.references  :item,      foreign_key: true
      t.integer     :quantity,  null: false

haml:views/payments/new.html.haml



= form_with url:item_payments_path,local:true,id:"new_form" do |f|
  = f.number_field :quantity
  = f.submit "To buy" #I'm afraid of something, so I try not to skip data such as the amount of money. Only the number of purchases.

payments_controller.rb


  def create
    item = Item.find(params[:item_id])                              #Nest the routing to include the id of the item.
    item.stock -= params[:quantity].to_i                            #Subtract the number of purchases from the item stock. All you have to do is update
    payment = Payment.new(payment_params)
    Payjp.api_key = ENV["PAYJP_SECRET_KEY"] 
    charge = Payjp::Charge.create( #Settle here.
      amount: item.price.to_i * params[:quantity].to_i,             #Settlement amount
      customer: Card.find_by(user_id: current_user.id).customer_id, #Customer
      currency: 'jpy'                                               #Japanese yen
    )
    payment.charge_id = charge.id                                   #I put the payment ID in the payment instance I generated earlier. No need to separate. as you like^^
    if payment.save && item.update(stock: item.stock)
      redirect_to root_path
    else
      render "new"
    end
  end

  private
  def payment_params
    params.permit(
      :item_id,
      :quantity,
    ).merge(user_id: current_user.id)
  end

It is like this. Various parts have been omitted for the sake of clarity. If you don't know the routing nesting

config/routes.rb


  resources :items do
    resources :payments,only: [:new,:create]
  end

It is like this.

I think this will probably work.

Reference primary source payjp.js API Guide [PAY.JP charge object](https://pay.jp/docs/api/#charge%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82 % AF% E3% 83% 88) ↑ I omitted it, but for those who want to display the last 4 digits of the card or VIZA (please use retrieve)

Reference blog Introduce a payment system using Payjp with Rails

Delete the registered card.

In my case routing

config/routes.rb


resources :card,only: [:new,:create,:destroy]
card_index POST    /card(.:format)        card#create
new_card   GET     /card/new(.:format)    card#new
card       DELETE  /card/:id(.:format)    card#destroy

So I changed the above view. What has changed is from if to else at the top It is only the definition judgment of gon of JQuery.

view/card/new.html.haml


  - if current_user.card
    #cardNew__form
      #cardNew__form--type= @card.brand.upcase
      #cardNew__form--num= "**** **** **** #{@card.last4}"
      #cardNew__form--name= @card.name
      =link_to "Delete","/card/#{current_user.card.id}"
  - else
    = form_with url: card_index_path,local:true,id:"cardNew__form" do |f|
      %input#cardNew__form--inputHidden{type:"hidden",name:"payjp-token"}
      #cardNew__form--input
      #cardNew__form--errMessage
      = f.submit "to register",id:"cardNew__form--submit"



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

:javascript
  if(typeof gon != 'undefined'){

    const style = {
      base: {
        backgroundColor:'#ffffff',
        '::placeholder': {
          color: '#bbbbbb',
        }
      },
      invalid: {
        color: 'red',
      }
    }

    let payjp = Payjp(gon.api_key);
    let elements = payjp.elements();
    let cardElement = elements.create('card', {style: style});

    cardElement.mount('#cardNew__form--input');

    $(".cardNew").on("click","#cardNew__form--submit",
      function(e){
        e.preventDefault();
        payjp.createToken(cardElement).then(function(response){
          if(response.error){
            $('#cardNew__form--errMessage').text(response.error.message);
          }else{
            $("#cardNew__form--inputHidden").attr("value",response.id);
            $("#cardNew__form").submit();
          }
        });
      }
    );
  }

card_controller.rb


  def new
    if current_user.card
      Payjp.api_key = ENV["PAYJP_SECRET_KEY"]
      customer = Payjp::Customer.retrieve(current_user.card.customer_id)
      @card = customer.cards.retrieve(current_user.card.card_id)
    else
      gon.api_key = ENV["PAYJP_PUBLIC_KEY"]
    end
  end

Recommended Posts

Equipped with a card function using payjp
Using a printer with Debian 10
Using a webcam with Raspberry Pi
Create a Python function decorator with Class
A memorandum of using Python's input function
[Piyopiyokai # 1] Let's play with Lambda: Creating a Lambda function
Evaluate CNN performance with a custom merit function
[Chat De Tornado] Make a chat using WebSocket with Tornado
I tried using a database (sqlite3) with kivy
A story about using Resona's software token with 1Password
[Practice] Make a Watson app with Python! # 2 [Translation function]
[Python] Create a ValueObject with a complete constructor using dataclasses
Make a function to describe Japanese fonts with OpenCV
Register a ticket with redmine API using python requests
I can't exe a project using PyWebView with PyInstaller
Create a Todo app with Django ⑤ Create a task editing function
Using a Python program with fluentd's exec_filter Output Plugin
Create a company name extractor with python using JCLdic
Using a python program with fluentd's exec Output Plugin
A4 size with python-pptx
Differentiate a two-variable function
Decorate with a decorator
I got a Value Error when using JUMAN ++ with PyKNP
Let's create a function for parametrized test using frame object
Create a Mastodon bot with a function to automatically reply with Python
Associate Python Enum with a function and make it Callable
How to print characters as a table with Python's print function
A story about installing matplotlib using pip with an error
To return char * in a callback function using ctypes in Python
A memo of writing a basic function in Python using recursion
Finding pi with a three-line function [Python / Monte Carlo method]
[Linux] Write a deployment tool using rsync with a shell script
Try running a function written in Python using Fn Project
Try encryption / decryption using OpenSSL key with Python3 pow function