#はじめに
某スクールのチーム開発にてpay.jpを活用したクレジットカード登録を担当させて頂きましたのでアウトプットも兼ねてまとめたいです。
ちなみにコードについてはまだまだ素人の為拙い部分も目立つかも知れない為、参考にされる方はご了承ください。
##Payjpとは何か?
クレジットカードを登録、変更、購入を行ってくれる便利なAPIです。
フリマアプリ、ECサイトでクレジットカードを使って商品の購入する時クレジットカード情報が保存されるところになります。
ちなみにカード情報そのものをECサイト上に保存することは禁止されている為ご注意下さい。
http://payjp-announce.hatenablog.com/entry/2017/11/10/182738
現在はWEBサイトに通販も交えたサイトが多く、ネットでのECサービスほぼ全てに実装されている機能で、身分証明も兼ねているため、Webエンジニアになられる方は学んでおいた方が良いかもしれません。
##前提条件
今回のPayjp登録機能実装ですが、前提条件として
・hamlでの記載(gem 'haml-rails')
・deviseが導入済みでログインができている
その為、何もない状態から機能を実装する場合
事前準備として以下のサイトが参考になるかと思います。
https://qiita.com/takachan_coding/items/c7584f7ac165d22c5526
##1.下準備を行いましょう
#####1-1 PAY.JPのアカウントを取得しましょう。
https://pay.jp/
上記URLでPayjpのアカウントを新規登録しましょう。
上記のAPIのダッシュボードで確認してみましょう。
今回はテストモードでの実装なので、テスト秘密鍵とテスト公開鍵を使用します。
#####1-3 Payjpのgemをインストールします。
gem 'payjp'
忘れずにbundle installをしましょう。
④ payjp.jsを読み込めるようにしましょう
application.html.hamlのtitleの直下に以下の記述を行いましょう。
%script{src: "https://js.pay.jp/", type: "text/javascript"}
以上で下準備は完了です。
##2.テーブルを作成しましょう。
下記コマンドをターミナル上で入力する事でPayjpのデータを保管する為のテーブルを作成できます。
$ rails g model Card user_id:integer customer_id:string card_id:string
下記のようなテーブルができていますので確認して下さい。
class CreateCards < ActiveRecord::Migration[5.0]
def change
create_table :cards do |t|
t.integer :user_id
t.string :customer_id
t.string :card_id
t.timestamps
end
end
end
テーブルの説明は下記の通りになります。
・user_id ... Userテーブルのid
・customer_id ... payjpの顧客id
・card_id ... payjpのデフォルトカードid
ちなみにカード情報そのものをECサイトのDB上に保存することは禁止されていますので、カード番号、有効期限、セキュリティコードのカラムは作らないようにしましょう。
##3.ビューとそれに対するCSSを2つ作成しましょう
= form_tag(cards_path, method: :post, id: 'charge-form') do |f|
.payment-text__box
.payment-text__contents
%label カード番号
%span 必須
= text_field_tag "card_number", "", class: "number", placeholder: "半角数字のみ" ,maxlength: "16", type: "text", id: "card_number"
%ul.card
%li=image_tag "image-visa.png",size: "49x20"
%li=image_tag "image-mastercard.png",size: "34x20"
%li=image_tag "image-SAISON.png",size: "30x20"
%li=image_tag "image-JCB.png",size: "32x20"
%li=image_tag "image-AMERICAN.png",size: "21x20"
%li=image_tag "image-Diners.png",size: "32x20"
%li=image_tag "image-DISCOVER.png",size: "32x20"
.payment-expiration_date
%label 有効期限
%span.required 必須
.expiration_date
%select#exp_month{name: "exp_month", type: "text"}
%option{value: ""} --
%option{value: "1"}01
%option{value: "2"}02
%option{value: "3"}03
%option{value: "4"}04
%option{value: "5"}05
%option{value: "6"}06
%option{value: "7"}07
%option{value: "8"}08
%option{value: "9"}09
%option{value: "10"}10
%option{value: "11"}11
%option{value: "12"}12
.exp-text 月
%select#exp_year{name: "exp_year", type: "text"}
%option{value: ""} --
%option{value: "2021"}21
%option{value: "2022"}22
%option{value: "2023"}23
%option{value: "2024"}24
%option{value: "2025"}25
%option{value: "2026"}26
%option{value: "2027"}27
%option{value: "2028"}28
%option{value: "2029"}29
.exp-text 年
.payment-text__contents
%label セキュリティコード
%span 必須
= text_field_tag "cvc", "", class: "cvc", placeholder: "カード背面3~4桁の番号", maxlength: "4", id: "cvc"
#card_token
= submit_tag "登録する", id: "token_submit",class:"red_btn","data-turbolinks": false
.toppage_btn
= link_to 'トップページに戻る',root_path,class:"toppage_btn"
%section.main-contents
.single-main
%section.chapter__container
%h2.chapter__head 支払い方法
.payment__main
.payment__list
.payment__list__content
%h3.sub__head クレジットカード
.card__payment__list
%form.card__payment__content
.card__number
カード番号
%br
= "**** **** **** " + @default_card_information.last4
.expire__date
有効期限
%br
- exp_month = @default_card_information.exp_month.to_s
- exp_year = @default_card_information.exp_year.to_s.slice(2,3)
= exp_month + " / " + exp_year
= form_tag(card_path(current_user.id), method: :delete, id:'charge-form') do
%input{ type: "hidden", name: "card_id", value: "" }
%button.delete__button 削除する
.toppage_btn
= link_to 'トップページに戻る',root_path,class:"toppage_btn"
上記のコードですが
new.html.hamlがカード登録画面
show.html.hamlがカード確認画面になります。
CSSも一応載せますが上手く反映されない可能性もありますのでご了承下さい。
.header-payment {
height: 128px;
background: #f5f5f5;
text-align: center;
display: block;
box-sizing: border-box;
font-size: 12px;
color: #888;
font-weight: 600;
h1 {
margin: 40px 0 0;
display: inline-block;
}
img {
width: 185px;
height: 49px;
line-height: 49px;
}
}
.payment-box {
font-family: 'Source Sans Pro', Helvetica , Arial, '游ゴシック体', 'YuGothic', 'メイリオ', 'Meiryo', sans-serif;
background: #f5f5f5;
}
.payment-main {
width: 650px;
margin: 0 auto;
height: 620px;
background: #fff;
}
.payment-container {
font-size: 24px;
padding: 8px 24px;
text-align: center;
h2 {
font-weight: bold;
line-height: 1.4;
}
}
.payment-text {
margin-top: 25px;
border-top: 1px solid #f5f5f5;
&__box {
max-width: 400px;
margin: 0 auto;
#token_submit.red_btn{
margin-top: 30px;
background-color: red;
color: white;
}
.toppage_btn{
height: 50px;
color:white;
text-align: center;
background-color: lightblue;
margin-top: 30px;
padding-top: 12px;
width: 100%;
text-decoration: none;
font-size: 18px;
border-radius: 2%;
}
}
&__number {
position: relative;
margin: 8px 0 0;
}
.text {
text-align: right;
color: #0099e8;
i {
display: inline-block;
width: 14px;
height: 14px;
border-radius: 50%;
background: #0099e8;
color: #fff;
line-height: 14px;
font-size: 12px;
text-align: center;
span {
text-align: right;
color: #0099e8;
}
}
}
&__contents {
margin: 0;
margin: 24px 0 0;
p {
margin: 8px 0 0;
line-height: 1.5;
color: #888;
}
label {
font-weight: 600;
display: inline-block;
}
span {
background: #ea352d;
margin: 10px 0 0 10px;
padding: 2px 4px;
border-radius: 2px;
color: #fff;
font-size: 12px;
vertical-align: top;
}
textarea {
width: 100%;
margin: 8px 0 0;
height: 48px;
padding: 10px 16px 8px;
border-radius: 4px;
border: 1px solid #ccc;
background: #fff;
line-height: 1.5;
font-size: 16px;
img {
width: 49px;
height: 20px;
}
}
.password {
margin: 16px 0 0;
}
.card {
margin: 8px 0 0;
font-size: 0;
}
li {
display: inline-block;
margin: 0 0 0 8px;
vertical-align: middle;
}
}
}
.payment-expiration_date {
margin-top: 30px;
label {
font-weight: 600;
display: inline-block;
vertical-align: top;
}
span.required {
background: #ea352d;
margin-left: 10px;
padding: 2px 4px;
border-radius: 2px;
color: #fff;
font-size: 12px;
vertical-align: top;
p {
margin: 8px 0 0;
line-height: 1.5;
color: #888;
font-weight: 600;
}
}
.expiration_date {
display: flex;
margin-top: 10px;
#exp_month{
width: 45%;
}
#exp_year{
width: 45%;
}
.exp-text{
margin-left: 5px;
margin-right: 5px;
padding-top: 10px;
font-size: 20px;
}
}
select {
width: calc(100% - 38px);
z-index: 2;
height: 48px;
padding: 0 16px;
border-radius: 4px;
border: 1px solid #ccc;
background: 0;
font-size: 16px;
line-height: 1.5;
cursor: pointer;
}
span {
margin: 20px 0 0;
}
}
.main-contents {
float: right;
width: 700px;
margin: 0;
background: #fff;
.chapter__container {
background: #fff;
width: 700px;
margin: 0 auto;
.chapter__head {
font-size: 24px;
padding: 8px 24px;
border-bottom: 1px solid #f5f5f5;
text-align: center;
font-weight: bold;
}
.payment__main{
padding: 64px;
border-top: 1px solid #f5f5f5;
.payment__list {
max-width: 320px;
margin: 0 auto;
.sub__head {
font-size: 16px;
font-weight: bold;
}
}
.card__payment__list{
max-width: 320px;
margin: 0 auto;
padding: 24px 0;
border-bottom: 1px solid #eee;
.card__number {
margin: 8px 0 0;
font-size: 16px;
}
.expire__date {
margin: 8px 0 0;
font-size: 16px;
}
.delete__button {
position: relative;
bottom: 0px;
padding: 4px 6px;
border-radius: 3px;
border: 1px solid red;
color: white;
background-color: red;
}
.toppage_btn{
height: 50px;
color:white;
text-align: center;
background-color: lightblue;
margin-top: 30px;
padding-top: 12px;
width: 100%;
text-decoration: none;
font-size: 18px;
border-radius: 2%;
}
}
}
}
}
また、トップページの好きな場所にこんな感じのlink_toメソッドを追記しましょう
= link_to new_card_path do
クレジットカード登録
また、これらに対してのルーティングにつきましては後ほど説明します。
##4.コントローラーを作成しましょう。
$ rails g controller cards
作成したコントローラーの内容を変更します。
class CardsController < ApplicationController
require 'payjp'
before_action :set_card, only:[:index, :show, :destroy]
def index
if @card.blank?
#登録された情報がない場合にカード登録画面に移動
flash[:alert] = 'カードを登録してください'
render :new
else
Payjp.api_key = ENV["PAYJP_SECRET_KEY"]
customer = Payjp::Customer.retrieve(@card.customer_id)
@default_card_information = customer.cards.retrieve(@card.card_id)
end
end
def new
@card = Card.where(user_id: current_user.id)
redirect_to card_path(current_user.id) if @card.exists?
end
def create
Payjp.api_key = ENV["PAYJP_SECRET_KEY"]
if params['payjp-token'].blank?
render :new
else
customer = Payjp::Customer.create(
card: params['payjp-token'],
metadata: {user_id: current_user.id}
)
@card = Card.new(user_id: current_user.id, customer_id: customer.id, card_id: customer.default_card)
if @card.save
flash.now[:notice] = "カードの登録に成功しました"
redirect_to root_path
else
flash[:alert] = 'カードの登録に失敗しました'
render :new
end
end
end
def show
if @card.blank?
# 登録された情報がない場合にカード登録画面に移動
redirect_to new_card_path
else
Payjp.api_key = ENV["PAYJP_SECRET_KEY"]
customer = Payjp::Customer.retrieve(@card.customer_id)
@default_card_information = customer.cards.retrieve(@card.card_id)
end
end
def destroy #PayjpとCardデータベースを削除
if @card.blank?
render :new
else
Payjp.api_key = ENV["PAYJP_SECRET_KEY"]
customer = Payjp::Customer.retrieve(@card.customer_id)
customer.delete
@card.delete
end
flash[:notice] = 'カードが削除されました'
redirect_to root_path
end
private
def card_params
params.require(:card).permit(:user_id, :customer_id, :card_id).merge(user_id: current_user.id)
end
def set_card
@card = Card.find_by(user_id: current_user.id)
end
end
コントローラ内のENV["PAYJP_SECRET_KEY"]は環境変数でテスト秘密鍵を設定し読み込みます。
##5 Payjpにデータを送る為の事前準備を行いましょう
#####5-1. 1章の下準備を行いましょうの1-2で確認しましたテストキーをターミナルに入力しましょう
$ vim ~/.bash_profile
vimの細かい使い方はこちらで確認して下さい
https://qiita.com/hide/items/5bfe5b322872c61a6896
上記コマンドを入力すると入力できる画面に入りますので
iをクリックして入力
下記画像のようにPayjpテストモードの画面でテスト秘密鍵とテスト公開鍵を入力して下さい。
入力が終わりましたら
$ source ~/.bash_profile
で保存しましょう。
5-2. secret.ymlに以下の通りテスト秘密鍵とテスト公開鍵を登録しましょう。
development:
payjp_secret_key: <%= ENV["PAYJP_SECRET_KEY"] %>
payjp_public_key: <%= ENV["PAYJP_PUBLIC_KEY"] %>
production:
payjp_secret_key: <%= ENV["PAYJP_SECRET_KEY"] %>
payjp_public_key: <%= ENV["PAYJP_PUBLIC_KEY"] %>
##6 Payjpにデータを送りトークンを取得しよう
提供されているpay.jp.isのサンプルを参照し、アレンジを加えています。
jQueryを使用していますので設定をしていない人はこちらを参考に設定して下さい。
$(document).on('turbolinks:load', function(){
// payjp.jsの初期化
Payjp.setPublicKey('pk_test_fe63a9d675fba31924799f7e');
// ボタンのイベントハンドリング
const btn = document.getElementById("token_submit");
btn.addEventListener('click', (e) => {
e.preventDefault();
// カード情報生成
const card = {
number: $('#card_number').val(),
exp_month: $('#exp_month').val(),
exp_year: $('#exp_year').val(),
cvc: $('#cvc').val(),
};
// トークン生成
Payjp.createToken(card, (status, response) => {
if (status === 200) { //成功した場合
$("#card_number").removeAttr("name");
$("#exp_month").removeAttr("name");
$("#exp_year").removeAttr("name");
$("#cvc").removeAttr("name");
$("#charge-form").append(
$('<input type="hidden" name="payjp-token">').val(response.id)
); //取得したトークンを送信できる状態にします
$("#charge-form").submit();
alert("登録が完了しました"); //確認用
} else {
alert("カード情報が正しくありません。"); //確認用
}
});
});
})
##7.ルートを作成しよう
今回はshow,pay,new,deleteの4つのメゾットがあるので下記のように設定しましょう。
resources :cards, only: [:create, :new, :show, :destroy]
##8.カードを登録してみよう
http://localhost:3000/cards/new にアクセスして登録できるか確認してみましょう。
その時、カード番号は必ずテストカードで登録するようにしてください。
それ以外を打ち込んだ場合はトークンが発行できずはねられてしまいます。
また、くれぐれも自身のカード番号、セキュリティコードは入力しないようにして下さい。
カード入力の例
カード番号:4242424242424242
有効期限: 12月 22年
CVC(セキュリティコード):222
名前:HIDE
以上でカード登録から削除まで一通り実装できました。
初めてまともな記事を投稿する事になりますので拙い文章やコードだったと思いますが
今後ともよろしくお願いします。