3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

payjpを使用したカード機能の搭載

Last updated at Posted at 2020-10-05

カード機能を搭載しました。

使用gem
devise
PAY.JP for Ruby
~~Gon gem~~←最終的に使用しませんでした。

まずはカード登録機能

ですが、カード番号などをECサイト運営社が持つことは法律で禁じられているらしいです。
ですのでDBにそれらの情報は登録できません。
代わりにpayjpのIDを登録することになります。

カード登録の流れ
・エンドユーザーがカード番号等を入力しsubmitをポチ
・payjpにデータが送信されカードトークンが帰ってくる。
・帰ってきたカードトークンからカスタマー(持ち主)をcreate
・createされたカスタマー情報からカスタマーIDとカードIDをDBへ登録

私の場合、アソシエーションは

user has_one :card
card belongs_to :user

cardsテーブルは

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

では早速、Gemfileに以下を追記して bundle install しましょう。

Gemfile
gem 'payjp'
# gem 'gon'←最終的に使用しませんでした。

ここでgonとは
rubyの変数をJQuery(javascript)に渡せるgemです。

間違っているかもしれませんが
JQuery(javascript)や他のコードに、直接キー(公開キーを除く)を書き込むことは、キーの漏洩リスクが上がると考えています。
テストキーならいいかもしれませんが、リリース後などの本番ではテストキーは使えません。
credentials.ymlに書く方法もありますが、個人のキーはあくまで個人のものなので、個人での管理が望ましいかと考えています。

installが終了したらgonを使えるようにします
application.html.hamlの
javascript_include_tagの上に追記

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"]
    @api_key = ENV["PAYJP_PUBLIC_KEY"]
end

カード関連のjsは全てのページで読み込む必要性が無いため
今回は
card/new.html.hamlに直接以下を書きました。

app/views/card/new.html.haml
:javascript
  #let payjp = Payjp(gon.api_key);
  let payjp = Payjp("#{@api_key}");

これで環境変数をJQueryで読み込んでいるのでキーを晒さずに済みそうです。
共同開発をしている場合、必要な人は各人payjpに登録して環境変数に書き込みましょう。

newアクションが上記でできているので登録フォームを作ります。

app/views/card/new.html.haml
= form_with url: card_index_path,local:true,id:"cardNew__form" do |f| //card_index_pathはcardcontrollerのcreate action へのpath
    %input#cardNew__form--inputHidden{type:"hidden",name:"payjp-token"}
    #cardNew__form--input
    #cardNew__form--errMessage
    = f.submit "登録する",id:"cardNew__form--submit"

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

:javascript //ここからJQueryです。

  //let payjp = Payjp(gon.api_key); //先程のgonです。
  let payjp = Payjp("#{@api_key}"); //viewファイル内なので、この記述でJQにルビーの変数を渡せます。
  let elements = payjp.elements();
  let cardElement = elements.create('card');

  cardElement.mount('#cardNew__form--input');
//ここまででPAYJPが予め用意した入力欄ができます。カードのワンポイントがかわゆいので私はこのまま使用します^^

  $(".cardNew").on("click","#cardNew__form--submit",
    function(e){
      e.preventDefault();
      payjp.createToken(cardElement).then(function(response){ //ここでpayjpにカード情報を送ってカードトークンをいただきます。
        if(response.error){
          $('#cardNew__form--errMessage').text(response.error.message);
        }else{
          $("#cardNew__form--inputHidden").attr("value",response.id); //response.idがカードトークンです。ここでinput name="payjp-token"に値をわたします。
          $("#cardNew__form").submit();
        }
      });
    }
  );

カード番号:424242424242
↑有効期限は未来、CVCは何でも良いみたいです。
テストカード

これでsubmitを押せば、createアクションへトークンが送られます。
card_index_pathはcreateアクションへのパスです。
うまく見えない人は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( #ここで先程のカードトークンと紐付いたカスタマーをクリエイトします。
        card: params["payjp-token"]
      )
      @card = Card.new(
        user_id: current_user.id,
        customer_id: customer.id, #先程のカスタマーのidです。
        card_id: customer.default_card #先程のカスタマーに紐付いたカードのidです。
      )
      if @card.save
        redirect_to new_card_path
      else
        render :new
      end
    end
  end

ここまででDBへカードの登録ができるかと思います。

以下は試していないので動くかどうか分かりませんが、
もっと簡単に済ませたい人はこちらから

=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}"}

をコピペしてdata-keyにご自身のパブリックキー
@api_keyをnew controllerで作ってください。パブリックなので直接書き込んでも構いません。
urlは先程と同じ。idは任意。classは試してないのでわかりません。
先程のnew controllerはgon仕様になっているので、そこを

@api_key = ENV["PAYJP_PUBLIC_KEY"]

にすればOKだと思います。
payjp-tokenパラメーターの値としてトークンを送信するみたいです。
これだけで、質の高いカード入力画面が出来上がるようです。
すごいですね。

参考一次ソース
PAY.JP Announcement
payjp.js v2 リファレンス
payjp.js v2 ガイド
顧客を作成する

参考ブログ
【Rails】PAYJPを用いた決済機能の実装手順を簡単にまとめてみた
Payjpでクレジットカード登録と削除機能を実装する(Rails)

次にカードでのお支払い

流れとしては

商品詳細ページで購入をポチ
購入確認ページで購入を確定をポチ
カード決済される
paymentsテーブルに決済IDと何を誰が買ったのかをcreate
itemsテーブルのstock欄を減らしてupdate

アソシエーション

item has_many :payments
payment belogs_to :item

paymentsテーブル

***create_payments.rb
      t.string      :charge_id, null: false
      t.references  :user,      foreign_key: true
      t.references  :buyer,     foreign_key: {to_table: :users}
      t.integer     :item_id,   null: false
      t.integer     :quantity,  null: false
      t.integer     :payment,   null: false
views/payments/new.html.haml

= form_with url:item_payments_path,local:true,id:"new_form" do |f|
  = f.number_field :quantity
  = f.submit "購入する" #なんか怖いので金額などのデータは飛ばさないようにしてます。購入数だけです。
payments_controller.rb
  def create
    item = Item.find(params[:item_id])                              #ルーティングはネストしてitemのidを含むようにしてください。
    item.stock -= params[:quantity].to_i                            #itemのstockから購入数を引きます。後はupdateするだけ
    payment = Payment.new(payment_params)
    Payjp.api_key = ENV["PAYJP_SECRET_KEY"] 
    charge = Payjp::Charge.create( #ここで決済します。
      amount: item.price.to_i * params[:quantity].to_i,             #決済額
      customer: Card.find_by(user_id: current_user.id).customer_id, #お客様
      currency: 'jpy'                                               #日本円
    )
    payment.charge_id = charge.id                                   #先程生成したpaymentインスタンスに決済IDを入れています。別に必要はありません。お好みで^^
    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

こんな感じです。
わかりやすくするため、色々と省略しています。
ルーティングのネストがわからない人は

config/routes.rb
  resources :items do
    resources :payments,only: [:new,:create]
  end

こんな感じです。

たぶんこれで動くと思います。

参考一次ソース
payjp.js APIガイド
PAY.JP chargeオブジェクト
↑省略しましたが、カードの下4桁やVIZAとか表示させたい人へ(retrieveとか使ってください。)

参考ブログ
Rails で Payjp を使って決済システムを導入する

登録したカードを削除する。

自分の場合
ルーティング

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

なので、上記のviewを変更しました。
変わったのは上部のifからelseと
JQueryのgonの定義判定のみです。
@api_keyの判定のみです。

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 "削除","/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 "登録する",id:"cardNew__form--submit"



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

:javascript
  if(typeof gon != 'undefined'){
  if("#{@api_key}"){

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

    let payjp = Payjp("#{@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
3
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?