カード機能を搭載しました。
使用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テーブルは
t.references :user, foreign_key: true#,index:{unique: true}
t.string :customer_id, null: false
t.string :card_id, null: false
では早速、Gemfileに以下を追記して bundle install しましょう。
gem 'payjp'
# gem 'gon'←最終的に使用しませんでした。
ここでgonとは
rubyの変数をJQuery(javascript)に渡せるgemです。
間違っているかもしれませんが
JQuery(javascript)や他のコードに、直接キー(公開キーを除く)を書き込むことは、キーの漏洩リスクが上がると考えています。
テストキーならいいかもしれませんが、リリース後などの本番ではテストキーは使えません。
credentials.ymlに書く方法もありますが、個人のキーはあくまで個人のものなので、個人での管理が望ましいかと考えています。
installが終了したらgonを使えるようにします
application.html.hamlの
javascript_include_tagの上に追記
# = include_gon ←最終的に使用しませんでした。
= javascript_include_tag 'application'
def new
# gon.api_key = ENV["PAYJP_PUBLIC_KEY"]
@api_key = ENV["PAYJP_PUBLIC_KEY"]
end
カード関連のjsは全てのページで読み込む必要性が無いため
今回は
card/new.html.hamlに直接以下を書きました。
:javascript
#let payjp = Payjp(gon.api_key);
let payjp = Payjp("#{@api_key}");
これで環境変数をJQueryで読み込んでいるのでキーを晒さずに済みそうです。
共同開発をしている場合、必要な人は各人payjpに登録して環境変数に書き込みましょう。
newアクションが上記でできているので登録フォームを作ります。
= 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で調整してください。
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テーブル
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
= form_with url:item_payments_path,local:true,id:"new_form" do |f|
= f.number_field :quantity
= f.submit "購入する" #なんか怖いので金額などのデータは飛ばさないようにしてます。購入数だけです。
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
こんな感じです。
わかりやすくするため、色々と省略しています。
ルーティングのネストがわからない人は
resources :items do
resources :payments,only: [:new,:create]
end
こんな感じです。
たぶんこれで動くと思います。
参考一次ソース
payjp.js APIガイド
PAY.JP chargeオブジェクト
↑省略しましたが、カードの下4桁やVIZAとか表示させたい人へ(retrieveとか使ってください。)
参考ブログ
Rails で Payjp を使って決済システムを導入する
登録したカードを削除する。
自分の場合
ルーティング
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の判定のみです。
- 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();
}
});
}
);
}
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