更新日:
【Rails】 PAYJPを使ってクレジットカード決済を導入しよう!
PAY.JPとはクレジットカード決済代行サービスです。payjpというgemを使うことでRailsのアプリに簡単にクレジットカード決済機能を付けることができます。
作成したアプリにクレジットカード決済機能を付けたいがどうしたら良いかわからない方が多くいらっしゃるかと思います。
そんな時に便利なのがPAY.JPというクレジットカード決済代行サービスです。
このサービスを使ってRailsのアプリにクレジットカード決済機能を付ける流れをサンプルアプリを作成しながら確認していきましょう。
PAY.JPとは
クレジットカードを使って買い物をする際には、下記のような流れで決済が行われています。
①ユーザーがクレジットカードを使って商品を購入
②加盟店がクレジットカード会社に手数料を支払い、入金される
③クレジットカード会社からユーザーに請求が行き、ユーザーの口座から引き落とされる
このような流れで決済が行われます。
もし個人でクレジットカード決済を行うのであれば、クレジットカード会社とのやりとりを自分で行わなければなりません。
またクレジットカード情報という非常に大切な個人情報を管理する必要があるので、これらを個人で管理するのはかなり大変です。
そこで登場するのがクレジットカード決済を代行してくれるサービスです。
その中の一つが今回使用するPAY.JPというサービスになります。
代行サービスを使うことによりこれらの大変な作業を簡単に扱えるようになります。
PAY.JPの登録方法
それではまずPAY.JPのサービスを使えるようにするためにPAY.JPにアカウントを作成しましょう。
公式サイトへアクセスしてメニューから「申し込む」をクリックしましょう。
すると新規登録フォームが表示されるので、こちらから登録を行いましょう。
その後、ログイン画面に遷移するのでログインし、下記の画面になれば登録は完了です。
PAY.JPのAPIの使い方
payjpを使ってECサイトを作ろうの章でアプリケーションを作成しながらPAY.JPのAPIの使い方を解説しますが、ここではまずAPIの使い方の簡単な流れを見ていきましょう。
gemのインストール 〜 購入までの流れ
まずは基本的な使い方の流れを解説します。
1. gemのインストール
PAY.JPをRailsのアプリで使うにはPAY.JPが用意しているpayjp
というgemが必要になります。
まずはこのgemをインストールします。
1
gem 'payjp'
2. HTMLのheadタグ内にPAY.JP側で用意しているjsを呼び出す記述をする
PAY.JPのAPIを使用するにはPAY.JP側で用意しているpayjp.js
というJavaScriptのライブラリが必要です。
そのため、下記のコードをheadタグ内に記述します。
Railsだとapplication.html.erb
に追記することになります。
1
<script type="text/javascript" src="https://js.pay.jp/v1/"></script>
3.トークンを取得するjsを用意する
カード情報をPAY.JPに送り、トークンを取得する部分はJavaScriptを使って行います。
今回のサンプルアプリではpayjp.jsのv1を使うので公式サイトを参考に下記のようなjsファイルを作成します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const payjp = () => {
Payjp.setPublicKey("公開鍵の値");
const form = document.getElementById("入力フォームのid");
form.addEventListener("submit", function(e) {
e.preventDefault();
const card = {
number: フォームに入力されたカード番号の値,
name: フォームに入力されたカード名義人の値
cvc: フォームに入力されたセキュリティコードの値,
exp_month: フォームに入力された月の値,
exp_year: `20${フォームに入力された年の値}`,
};
Payjp.createToken(card, function(status, response) {
if (status === 200) {
const token = response.id;
const tokenObj = `<input value=${token} name='token_id' type="hidden">`;
const cardForm = document.getElementById("入力フォームのid");
cardForm.insertAdjacentHTML("beforeend", tokenObj);
document.getElementById("カード番号の入力フォームのid").removeAttribute("name");
document.getElementById("カード名義人の入力フォームのid").removeAttribute("name");
document.getElementById("セキュリティコードの入力フォームのid").removeAttribute("name");
document.getElementById("月の入力フォームのid").removeAttribute("name");
document.getElementById("年の入力フォームのid").removeAttribute("name");
document.getElementById("入力フォームのid").submit();
} else {
alert("カード情報が正しくありません")
}
});
});
};
window.addEventListener("load", payjp);
4. 各idの保存をする
次にコントローラーのアクション内で顧客id、トークンidを保存するコードを記述します。
下のコードはcardsテーブルに保存する際の記述です。
1
2
3
4
5
6
7
8
9
10
11
12
13
def アクション名
Payjp.api_key = ENV["秘密鍵の値"]
customer = Payjp::Customer.create(
description: 'test',
card: params[:token_id]
)
card = Card.new(
顧客idを保存するカラム名: customer.id,
トークンidを保存するカラム名: params[:token_id],
user_id: current_user.id
)
end
5. カード情報の取得
ユーザーのマイページなどで登録したカード情報を表示したい場合、PAY.JP側からカード情報を取得する必要があります。
その時のコントローラーで記述するコードの一例を紹介します。
1
2
3
4
5
6
7
def アクション名
Payjp.api_key = "秘密鍵の値" # PAY.JPに秘密鍵を使ってアクセス
card = Card.find_by(user_id: current_user.id) # cardsテーブルからレコードを取得
customer = Payjp::Customer.retrieve(card.customer_id) # 顧客idを元に、顧客情報を取得
@card = customer.cards.first # 登録したカード情報の取得
end
取得できるカード情報の一例です。
プロパティ名 | プロパティ値 | 例 |
---|---|---|
brand | カードのブランド名 | Visa |
exp_month | カード有効期限の月 | 10 |
exp_year | カード有効期限の年 | 2024 |
last4 | カード番号の末尾4桁 | 4242 |
name | カード名義人 | TARO YAMADA |
例えばカード番号の末尾4桁を表示させたければ下記のように記述します。
1
<%= @card.last4 %>
6. 購入する方法
登録したカードを使って商品を購入するコードの一例を紹介します。
1
2
3
4
5
6
7
8
9
def アクション名
Payjp.api_key = "秘密鍵の値" # PAY.JPに秘密鍵をセット
customer_id = current_user.card.customer_id # 顧客idを取得
Payjp::Charge.create( # PAY.JPに購入価格と顧客id、通過の種類を渡す
amount: "商品の価格の値",
customer: "顧客id",
currency: 'jpy'
)
end
これが大まかな流れになります。
この記事では実際にECサイトを作りながら、さらに具体的な使い方を解説していきます。
クレジットカードまわりのAPIの基本的な使い方
他にもPAY.JPでは複数枚のカードの追加や、カード情報の編集・削除なども行うことができます。
クレジットカードの追加
顧客情報に新たなカードを作成するには下記のように記述をします。
※参照: PAY.JPのAPI
1
2
3
4
5
Payjp.api_key = '秘密鍵'
customer = Payjp::Customer.retrieve('顧客id')
customer.cards.create(
card: 'トークンid'
)
カード情報の更新
登録したカード情報を更新するには下記のように記述をします。
1
2
3
4
5
Payjp.api_key = '秘密鍵'
customer = Payjp::Customer.retrieve('顧客id')
card = customer.cards.retrieve('カードid')
card.name = "PAY TARO"
card.save
カードidはカードを作成した際のcardオブジェクトのidの値になります。
1
2
3
4
5
6
Payjp.api_key = '秘密鍵'
customer = Payjp::Customer.retrieve('顧客id')
card = customer.cards.create(
card: 'トークンid'
)
card_id = card.id # カードidの取得
実際アプリに導入する際にはcard_idも保存しておくと良さそうです。
なお以前はexp_month , exp_year , cvc の指定ができましたが非通過対応により既に当該パラメーターは利用出来ないようになっています。
詳しくは カード情報非通過化対応のお願い をご覧ください。
※参照: PAY.JPのAPI
カード情報の削除
登録したカード情報を削除するには下記のように記述をします。
※参照: PAY.JPのAPI
1
2
3
4
Payjp.api_key = '秘密鍵'
customer = Payjp::Customer.retrieve('顧客id')
card = customer.cards.retrieve('カードid')
card.delete
このようにカードを追加したり編集・削除することもできます。
皆さんでも工夫してさらに高機能なクレジットカード決済機能をつけてみましょう。
本番利用申請の仕方
PAY.JPを実際のアプリで利用するには本番利用申請を行う必要があります。
詳しくは公式サイトのヘルプページを参照してください。
payjpを使ってECサイトを作ろう
前述した通りRailsでPAY.JPのサービスを使うにはpayjp
というgemが必要になります。
この章ではサンプルアプリを使いながらpayjpの使い方を解説していきます。
サンプルアプリケーションを用意しよう
それでは今回使用するサンプルアプリを作成していきましょう。
ターミナルで下記のコマンドを実行してください。
1
2
rails _6.0.0_ new payjp_app -d mysql
cd payjp_app
これでアプリケーションの雛形を作成できました。
今回は下記のような機能を持ったアプリを作成します。
- ユーザー認証機能
- ユーザーはクレジットカードを1枚登録できる
- ユーザーは出品されている商品をカードで購入できる
- 1度購入した商品は購入できない
ER図は下記のようになります。
それぞれのテーブルは下記の役割を担っています。
テーブル名 | 説明 |
---|---|
usersテーブル | ユーザー情報管理 |
productsテーブル | 商品情報管理 |
cardsテーブル | カード情報管理 |
ordersテーブル | 購入情報管理 |
環境変数の定義
PAY.JPのサービスを使うにはPAY.JPへの認証に必要な「秘密鍵」と、クレジットカードのトークンを生成するために必要な「公開鍵」の2つの鍵情報が必要になります。
鍵にはテストとしてPAY.JPを利用する場合に使う「テスト鍵」と、実際にPAY.JP側に料金を払い本番環境でPAY.JPを利用する「本番鍵」があります。
テスト鍵では、本番の支払い処理を行うサーバーへは接続されず、実際の支払い情報として計上されることもありません。
今回はテストとして使うため、「テスト秘密鍵」と「テスト公開鍵」を利用します。
ダッシュボードの左のメニューのAPI
をクリックすると「テスト秘密鍵」と「テスト公開鍵」を確認することができます。
これは外部に漏らしたくない情報なので環境変数として定義します。
環境変数を定義する方法はいろいろありますが、今回はdotenv-railsというgemを使い設定していきます。
まずはGemfileの最後に下記のコードを追記してbundle installコマンドを実行しましょう。
1
gem 'dotenv-rails'
次に下記のコマンドを実行し、環境変数を定義するファイルを作成します。
1
touch .env
それでは作成した.env
ファイルに環境変数を定義しましょう。
それぞれの鍵の値は先ほどPAY.JPのAPIのページで確認した値を代入します。
1
2
3
4
5
6
PAYJP_SECRET_KEY="テスト秘密鍵の値"
PAYJP_PUBLIC_KEY="テスト公開鍵の値"
# (例)値は架空の値です。
PAYJP_SECRET_KEY="sk_test_11b724efa6ec040des9a5h72"
PAYJP_PUBLIC_KEY="pk_test_16c0azc53ecdb076897k8c9c"
rails cコマンドでコンソールモードにして実際に値が取得できるか確認してみましょう。
nilになってしまった場合は環境変数名が間違っていないか確認、またはexitでコンソールを抜け出して、再度rails cをしましょう。
1
2
3
rails c
irb(main):001:0> ENV['PAYJP_SECRET_KEY']
=> "sk_test_11b724efa6ec040des9a5h72"
.envファイル
は大事な情報が書かれたファイルなのでGithubに間違ってもアップロードしてはいけません。
そのため.envファイル
は、Git管理下から除外する為に必ずgitignoreファイルに指定する必要があります。
gitignoreファイルの一番下に下記のコードを追記しましょう。
1
/.env
これで環境変数の定義は完了です。
ユーザー認証機能を実装しよう
それではまずユーザー認証機能から実装していきましょう。
今回ユーザー認証機能はdeviseというgemを使い実装をします。
gemをインストール
gemfileの一番下に下記のコードを追記します。
1
gem 'devise'
その後、ターミナルで下記のコマンドを実行し、deviseをインストールします。
1
2
bundle install
rails g devise:install
以下のようにターミナルに出力されればインストールに成功しています。
userモデルの作成
次にuserモデルを作成します。
ターミナルで下記のコマンドを実行します。
1
rails g devise user
usersテーブルの作成
上記のコマンドでモデルファイルと一緒にusersテーブルを作成するマイグレーションファイルが作成されます。
デフォルトだとemailカラムとpasswordカラムの記述しかないので、nameカラムを追加します。
下記のようにマイグレーションファイルを編集をします。
1
2
3
4
5
6
7
8
9
Class DeviseCreateUsers < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
## Database authenticatable
t.string :name, null: false
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
#下記省略
次にデータベースを作成するのですが、文字コードを編集する必要があるのでconfigフォルダ内にあるdatabase.yml
を下記のように編集します。
1
2
3
4
5
6
7
8
9
default: &default
adapter: mysql2
encoding: utf8
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: root
password:
socket: /tmp/mysql.sock
// 略
encoding
の部分を「utf8mb4」から「utf8」に変更しました。
次に下記のコマンドでこのアプリのデータベースとusersテーブルを作成します。
1
rails db:create db:migrate
これでusersテーブルを作成することができました。
ビューファイルの編集
次にユーザー認証に必要なビューファイルを編集します。
deviseで用意されているビューファイルを編集するため、ターミナルで下記のコマンドを実行し、ビューファイルを作成します。
1
rails g devise:views
次に作成された新規登録フォームを編集します。
app/views/devise/registrations/new.html.erb
を下記のように編集します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<h2>Sign up</h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div>
<div class="field">
<%= f.label :password %>
<% if @minimum_password_length %>
<em>(<%= @minimum_password_length %> characters minimum)</em>
<% end %><br />
<%= f.password_field :password, autocomplete: "new-password" %>
</div>
<div class="field">
<%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
</div>
<div class="actions">
<%= f.submit "Sign up" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
rails sコマンドでサーバーを立ち上げて、localhost:3000/users/sign_upにアクセスし下記の画面が表示されるのを確認しましょう。
ストロングパラメーターの設定
次にストロングパラメーターを定義します。
deviseでストロングパラメーターを定義する際はapplication_controller
に追記をする必要があります。
application_controller.rb
を下記のように編集します。
1
2
3
4
5
6
7
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
end
end
実際に先ほどのフォームから登録をし、下記の画面が表示されるか確認をしましょう。
商品情報を登録しよう
次に購入する商品を登録する準備をしていきます。
productモデルの作成
rails g modelコマンドを使いproductモデルを作成します。
ターミナルで下記のコマンドを実行します。
1
rails g model product
これでproductモデルが作成されました。
productsテーブルの作成
上記のコマンドでproductsテーブルを作成するマイグレーションファイルが作成されます。
マイグレーションファイルを下記のように編集をします。
1
2
3
4
5
6
7
8
9
class CreateProducts < ActiveRecord::Migration[5.2]
def change
create_table :products do |t|
t.string :name
t.integer :price
t.timestamps
end
end
end
次に下記のコマンドでproductsテーブルを作成します。
1
rails db:migrate
これでproductsテーブルが作成されました。
seedファイルでデータのセット
続いて購入する商品の情報をproductsテーブルに保存します。
dbフォルダ内にあるseeds.rbファイル
を下記のように編集します。
最初からコメントアウトされているコードは消してしまいましょう。
1
2
3
4
5
6
7
products = [
{ name: '鉛筆', price: 100 },
{ name: 'ボールペン', price: 200 },
{ name: '消しゴム', price: 50 },
{ name: 'ノート', price: 100 }
]
Product.create(products)
次にターミナルで下記のコマンドを実行してください。
1
rails db:seed
productsテーブルを確認し、下のように値がセットされているか確認をしましょう。
productsコントローラーの作成
次に下記のコマンドを実行し、productsコントローラーを作成します。
1
rails g controller products index
これでproductsコントローラーが作成されました。
indexアクションの編集
次にproductsコントローラーのindexアクションを編集します。
products_controller.rbを下記のように編集します。
1
2
3
4
5
class ProductsController < ApplicationController
def index
@products = Product.all
end
end
これでindexアクションを定義できました。
ルートの設定
では先ほど作成したproductsコントローラーのindexアクションをルートとして定義しましょう。
configフォルダ内にあるroutes.rb
を下記のように編集します。
1
2
3
4
Rails.application.routes.draw do
root 'products#index'
devise_for :users
end
編集前は'products/index'
と/
なっているので#
に変更するのを忘れないようにしてください。
ビューファイルの作成
次に先ほど登録した商品の一覧画面を作成します。
indexアクションをルートとして定義したのでトップページのビューファイルになります。
appフォルダにあるviewsフォルダのproductsフォルダ内にあるindex.html.erb
というファイルを編集します。
1
2
3
4
5
6
7
8
<% if user_signed_in?%>
<p><%= link_to "ログアウト", destroy_user_session_path, method: :delete %></p>
<% else %>
<p><%= link_to "新規登録", new_user_registration_path %> <%= link_to "ログイン", new_user_session_path %></p>
<% end %>
<% @products.each do |product| %>
<p>商品名:<%= product.name%> 価格:<%= product.price %></p>
<% end %>
編集後、localhost:3000/にアクセスして下記のように商品の一覧が表示されるか確認をしましょう。
これでサンプルアプリの準備は完了です。
gemをインストールしよう
それではサンプルアプリにクレジットカード機能を付けていきましょう。
まずはRailsアプリでPAY.JPのサービスを使えるようにするためpayjp
というgemをインストールします。
Gemfileの末尾に下記のコードを追記します。
1
gem 'payjp'
その後、bundle installコマンド
でインストールをしましょう。
payjpを使う準備をしよう
次にPAY.JP側で用意しているJavaScriptを呼び出す記述をします。
application.html.erb
に下記のコードを追記します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html>
<head>
<title>PayjpApp</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<script type="text/javascript" src="https://js.pay.jp/v1/"></script>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<%= yield %>
</body>
</html>
これで準備は整いました。
決済までの流れを確認しよう
それでは実際に決済が行われるまでの流れを確認していきましょう。
①〜② カード情報をPAY.JPに送りトークンを生成する
カード情報は個人情報になるため、アプリ側のデータベースで管理することは法律で禁じられています。
そのためカード情報はPAY.JP側で管理をしてもらい、トークンを発行してもらいます。
トークンとはPAY.JPで保管されているカード番号やCVCなどのセキュアなデータを隠しつつも、カードと同じように扱うことができるものです。
③ 取得したトークンidをRailsアプリへ送信する
ユーザーは取得したトークンのidをRailsアプリへ送信します。
トークンidを受け取ったRailsアプリはトークンidを保存します。
④〜⑤ PAY.JPにトークンidを登録し、顧客情報を作成
商品を購入するには発行されたトークンが必要になります。
一度使用したトークンは再び使用することはできませんが、 顧客情報にトークンidを登録すれば、顧客idを支払い手段として用いることで、何度でも同じカードで支払い処理ができるようになります。
そのためPAY.JPにトークンidを送り、顧客情報を管理するcustomerオブジェクトを作成し顧客情報を受け取ります。
その後、作成したcustomerオブジェクトのid(顧客id)も保存します。
⑥〜⑦ 顧客idなどの情報をPAY.JPに送信し、決済処理をする
決済を行うためには顧客idや購入金額などの情報が必要なので、それらをPAY.JPに送信します。
送られてきた情報からPAY.JP側でカード情報と照らし合わせて決済処理を行います。
カード登録画面を作成しよう
それではカード情報をPAY.JPに送るためにカード情報を入力するフォームを作成します。
入力フォームはcardsコントローラーのnewアクションで表示させます。
cardモデルを作成
まずは下記のコマンドでcardモデルを作成します。
1
rails g model card
これでcardモデルが作成されました。
アソシエーションの定義
次に作成したcardモデルとuserモデルとのアソシエーションを定義します。
1人のユーザーは1枚のカードを登録できるので2つのテーブルの関係は1対1になります。
まずはcardモデルから定義していきます。
1
2
3
class Card < ApplicationRecord
belongs_to :user
end
次はuserモデルです。
1
2
3
4
5
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_one :card, dependent: :destroy
end
今回は外部キー制約を付けているので、ユーザーが消去されたらそのユーザーのカード情報も削除するためdependent: :destroy
を付けます。
cardsテーブルの作成
先ほどのコマンドでcardsテーブルを作成するマイグレーションファイルも作成されました。
マイグレーションファイルを下記のように編集します。
1
2
3
4
5
6
7
8
9
10
class CreateCards < ActiveRecord::Migration[5.2]
def change
create_table :cards do |t|
t.string :customer_id, null: false
t.string :token_id, null: false
t.references :user, foreign_key: true
t.timestamps
end
end
end
編集後、下記のコマンドを実行します。
1
rails db:migrate
これでカード情報を保存するcardsテーブルが作成されました。
cardsコントローラーの作成
次に下記のコマンドでcardsコントローラーを作成します。
1
rails g controller cards new
これでcardsコントローラーを作成できました。
カード情報はユーザーがログインしていないと登録できなくさせるため、cardsコントローラーに下記のコードを追記します。
1
2
3
4
5
6
class CardsController < ApplicationController
before_action :authenticate_user!, only: [:new, :create]
def new
end
end
authenticate_user!メソッドはdeviseで用意されているヘルパーメソッドで、ログインしていない時にはログインフォームへ遷移させるメソッドです。
before_actionメソッドを使い、各アクションが動く前にこのメソッドを実行しています。
ルーティングの編集
cardsコントローラーを作成したのでルーティングを編集します。
1
2
3
4
5
Rails.application.routes.draw do
root 'products#index'
devise_for :users
resources :cards, only: [:new, :create]
end
ビューファイルの作成
次にカード情報を入力してもらうフォームを作成します。
new.html.erb
を下記のように記述します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<h1>クレジットカード登録フォーム</h1>
<%= form_with url: cards_path, id: "card_form", local: true do |f| %>
<div>
<%= f.label :number, "カード番号" %>
<%= f.text_field :number %>
</div>
<div>
<%= f.label :name, "カード名義人" %>
<%= f.text_field :name %>
</div>
<div>
<%= f.label :cvc, "セキュリティコード" %>
<%= f.text_field :cvc %>
</div>
<div>
<%= f.label :exp_month, "有効期限(月)" %>
<%= f.text_field :exp_month %>
</div>
<div>
<%= f.label :exp_year, "有効期限(年)" %>
<%= f.text_field :exp_year %>
</div>
<div>
<%= f.submit "送信" %>
</div>
<% end %>
ログイン状態でlocalhost:3000/cards/newにアクセスし、下記の画面になるか確認をしましょう。
カード情報をPAY.JPに送りトークン化しよう
カード情報をPAY.JPに送り、トークン化する部分はJavaScriptを使って行います。
今回はcard.js
という名前のファイルを作成し、コードを記述していきます。
js内で環境変数を使えるようにしよう
その前にcard.js内で最初に定義した環境変数を使用するので、javascript内で環境変数を使えるようにします。
下記のコマンドでwebpacker.rb
ファイルを作成します。
1
touch config/initializers/webpacker.rb
作成したらwebpacker.rb
を下記のように編集します。
1
Webpacker::Compiler.env["PAYJP_PUBLIC_KEY"] = ENV["PAYJP_PUBLIC_KEY"]
これでjs内で環境変数を使えるようになりました。
card.jsを作成しよう
それではjsを作成していきます。
下記のコマンドでapp/javascriptフォルダ
内にcard.js
というファイルを作成しましょう。
1
touch app/javascript/card.js
card.jsを読み込む記述をしよう
次に作成したcard.jsを読み込む記述をapp/javascript/packs/application.js
にしていきます。
turbolinks機能を使うとjsがうまく読み込めない時があるので、turbolinks機能をオフにするため2行目をコメントアウトします。
1
2
3
4
5
require("@rails/ujs").start()
// require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
require("../card")
card.jsを編集しよう
公式サイトを参考に、作成したファイルを下記のように編集をします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const payjp = () => {
Payjp.setPublicKey(process.env.PAYJP_PUBLIC_KEY);
const form = document.getElementById("card_form");
form.addEventListener("submit", function(e) {
e.preventDefault();
const card = {
number: document.getElementById("number").value,
name: document.getElementById("name").value,
cvc: document.getElementById("cvc").value,
exp_month: document.getElementById("exp_month").value,
exp_year: `20${document.getElementById("exp_year").value}`,
};
Payjp.createToken(card, function(status, response) {
if (status === 200) {
const token = response.id;
const tokenObj = `<input value=${token} name='token_id' type="hidden">`;
const cardForm = document.getElementById("card_form");
cardForm.insertAdjacentHTML("beforeend", tokenObj);
document.getElementById("number").removeAttribute("name");
document.getElementById("name").removeAttribute("name");
document.getElementById("cvc").removeAttribute("name");
document.getElementById("exp_month").removeAttribute("name");
document.getElementById("exp_year").removeAttribute("name");
document.getElementById("card_form").submit();
} else {
alert("カード情報が正しくありません")
}
});
});
};
window.addEventListener("load", payjp);
それではこのjsのそれぞれのコードが何をしているか細かく見ていきましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
const payjp = () => {
Payjp.setPublicKey(process.env.PAYJP_PUBLIC_KEY);
const form = document.getElementById("card_form");
form.addEventListener("submit", function(e) {
e.preventDefault();
const card = {
number: document.getElementById("number").value,
name: document.getElementById("name").value,
cvc: document.getElementById("cvc").value,
exp_month: document.getElementById("exp_month").value,
exp_year: `20${document.getElementById("exp_year").value}`,
};
# 略
PAY.JPにクレジットカード情報を送り、トークン化するには公開鍵が必要でした。
この部分ではprocess.env.PAYJP_PUBLIC_KEY;
で環境変数を読み込んでPayjp.setPublicKey
でPYA.JPに公開鍵を設定しています。
そして入力フォームの送信ボタンを押した際にe.preventDefault();
によってsubmitイベントを止めています。
次に下記のコードの部分です。
1
2
3
4
5
6
7
8
9
10
11
12
13
const payjp = () => {
Payjp.setPublicKey(process.env.PAYJP_PUBLIC_KEY);
const form = document.getElementById("card_form");
form.addEventListener("submit", function(e) {
e.preventDefault();
const card = {
number: document.getElementById("number").value,
name: document.getElementById("name").value,
cvc: document.getElementById("cvc").value,
exp_month: document.getElementById("exp_month").value,
exp_year: `20${document.getElementById("exp_year").value}`,
};
# 略
この部分ではcardオブジェクトを作成し、プロパティ値をセットしています。
この情報がクレジットカード情報としてPAY.JPに送られます。
getElementById
の引数にはそれぞれのフォームのidの値を入れます。
ここでのプロパティ名はPAY.JP側が要求するものにしておかないとエラーになります。
例えば下記のようなコードだとエラーになります。
1
2
3
4
5
6
7
const card = {
card_number: document.getElementById("number").value,
card_name: document.getElementById("name").value,
card_cvc: document.getElementById("cvc").value,
card_exp_month: document.getElementById("exp_month").value,
card_exp_year: `20${document.getElementById("exp_year").value}`,
};
PAY.JP側は「number」、「name」、「cvc」、「exp_month」、「exp_year」といったプロパティ名としてカード情報を管理するため、「card_number」のようなこちら側で設定した名前だとエラーになるので気をつけましょう。
次に下記のコードです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const payjp = () => {
Payjp.setPublicKey(process.env.PAYJP_PUBLIC_KEY);
const form = document.getElementById("card_form");
form.addEventListener("submit", function(e) {
e.preventDefault();
const card = {
number: document.getElementById("number").value,
name: document.getElementById("name").value,
cvc: document.getElementById("cvc").value,
exp_month: document.getElementById("exp_month").value,
exp_year: `20${document.getElementById("exp_year").value}`,
};
Payjp.createToken(card, function(status, response) {
if (status === 200) {
const token = response.id;
const tokenObj = `<input value=${token} name='token_id' type="hidden">`;
const cardForm = document.getElementById("card_form");
cardForm.insertAdjacentHTML("beforeend", tokenObj);
document.getElementById("number").removeAttribute("name");
document.getElementById("name").removeAttribute("name");
document.getElementById("cvc").removeAttribute("name");
document.getElementById("exp_month").removeAttribute("name");
document.getElementById("exp_year").removeAttribute("name");
document.getElementById("card_form").submit();
} else {
alert("カード情報が正しくありません")
}
});
});
};
window.addEventListener("load", payjp);
Payjp.createToken(card, function(status, response) {
の部分でcardオブジェクトの情報をPAY.JP側に送信し、status
とresponse
を受け取ります。
status
にはHTTPステータスコードが、response
にはtokenオブジェクトが格納されています。
公式サイトによるとtokenオブジェクトは下記のような構造になっています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
"card": {
"address_city": null,
"address_line1": null,
"address_line2": null,
"address_state": null,
"address_zip": null,
"address_zip_check": "unchecked",
"brand": "Visa",
"country": null,
"created": 1442290383,
"customer": null,
"cvc_check": "passed",
"exp_month": 2,
"exp_year": 2024,
"fingerprint": "e1d8225886e3a7211127df751c86787f",
"id": "car_e3ccd4e0959f45e7c75bacc4be90",
"livemode": false,
"metadata": {},
"last4": "4242",
"name": null,
"object": "card"
},
"created": 1442290383,
"id": "tok_5ca06b51685e001723a2c3b4aeb4",
"livemode": false,
"object": "token",
"used": false
}
tokenオブジェクトのidの値でPAY.JPに登録したカード情報を利用するので、response.id
でトークンのidを取得しています。
次に取得したトークンidをパラメータに含める記述を見てみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const payjp = () => {
Payjp.setPublicKey(process.env.PAYJP_PUBLIC_KEY);
const form = document.getElementById("card_form");
form.addEventListener("submit", function(e) {
e.preventDefault();
const card = {
number: document.getElementById("number").value,
name: document.getElementById("name").value,
cvc: document.getElementById("cvc").value,
exp_month: document.getElementById("exp_month").value,
exp_year: `20${document.getElementById("exp_year").value}`,
};
Payjp.createToken(card, function(status, response) {
if (status === 200) {
const token = response.id;
const tokenObj = `<input value=${token} name='token_id' type="hidden">`;
const cardForm = document.getElementById("card_form");
cardForm.insertAdjacentHTML("beforeend", tokenObj);
document.getElementById("number").removeAttribute("name");
document.getElementById("name").removeAttribute("name");
document.getElementById("cvc").removeAttribute("name");
document.getElementById("exp_month").removeAttribute("name");
document.getElementById("exp_year").removeAttribute("name");
document.getElementById("card_form").submit();
} else {
alert("カード情報が正しくありません")
}
});
});
};
window.addEventListener("load", payjp);
const tokenObj = <input value=${token} name='token_id' type="hidden">;
の記述によってparamsにトークンidの値を追加しています。
このフォームは表示させたくないので、type="hidden"
で非表示にしています、
このままではparamsにカード情報が入り、サーバーサイドに送信されてしまいます。
サーバーサイドに重要な個人情報を送らないためにトークン化しました。
ですのでカード情報がparamsに含まれないようにする必要があります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const payjp = () => {
Payjp.setPublicKey(process.env.PAYJP_PUBLIC_KEY);
const form = document.getElementById("card_form");
form.addEventListener("submit", function(e) {
e.preventDefault();
const card = {
number: document.getElementById("number").value,
name: document.getElementById("name").value,
cvc: document.getElementById("cvc").value,
exp_month: document.getElementById("exp_month").value,
exp_year: `20${document.getElementById("exp_year").value}`,
};
Payjp.createToken(card, function(status, response) {
if (status === 200) {
const token = response.id;
const tokenObj = `<input value=${token} name='token_id' type="hidden">`;
const cardForm = document.getElementById("card_form");
cardForm.insertAdjacentHTML("beforeend", tokenObj);
document.getElementById("number").removeAttribute("name");
document.getElementById("name").removeAttribute("name");
document.getElementById("cvc").removeAttribute("name");
document.getElementById("exp_month").removeAttribute("name");
document.getElementById("exp_year").removeAttribute("name");
document.getElementById("card_form").submit();
} else {
alert("カード情報が正しくありません")
}
});
});
};
window.addEventListener("load", payjp);
上のコードではremoveAttribute("name");
でidで指定したinputタグのname属性を削除することによりparamsに含まれないようにしています。
最後に送信とエラーハンドリングの部分です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const payjp = () => {
Payjp.setPublicKey(process.env.PAYJP_PUBLIC_KEY);
const form = document.getElementById("card_form");
form.addEventListener("submit", function(e) {
e.preventDefault();
const card = {
number: document.getElementById("number").value,
name: document.getElementById("name").value,
cvc: document.getElementById("cvc").value,
exp_month: document.getElementById("exp_month").value,
exp_year: `20${document.getElementById("exp_year").value}`,
};
Payjp.createToken(card, function(status, response) {
if (status === 200) {
const token = response.id;
const tokenObj = `<input value=${token} name='token_id' type="hidden">`;
const cardForm = document.getElementById("card_form");
cardForm.insertAdjacentHTML("beforeend", tokenObj);
document.getElementById("number").removeAttribute("name");
document.getElementById("name").removeAttribute("name");
document.getElementById("cvc").removeAttribute("name");
document.getElementById("exp_month").removeAttribute("name");
document.getElementById("exp_year").removeAttribute("name");
document.getElementById("card_form").submit();
} else {
alert("カード情報が正しくありません")
}
});
});
};
window.addEventListener("load", payjp);
document.getElementById("card_form").submit();
によってjs側でsubmitをしています。
正しい情報を送信していない時にはトークンを取得できないのでアラートを出しています。
顧客idとトークンidを保存しよう
次にユーザーから送られてきたトークンidを保存するコードを記述していきます。
createアクションを編集しよう
idを保存するためcreateアクションを編集していきます。
まずはPAY.JPに秘密鍵を送信し、アクセスできるようにします。
一度使用したトークンは再び使用することはできませんが、 顧客の情報にカード情報(トークンid)を登録すれば、顧客id(customer_id)を支払い手段として用いることで、何度でも同じカードで支払い処理ができるようになります。
そのため、Payjp::Customer.create
でPayjp::Customerクラスから顧客情報を管理するcustomerオブジェクトを作成します。
引数にはテストか本番かを判別するdescriptionの値とparamsで送られてきたトークンidの値を入れます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CardsController < ApplicationController
before_action :authenticate_user!, only: [:new, :create]
def new
end
def create
Payjp.api_key = ENV["PAYJP_SECRET_KEY"]
customer = Payjp::Customer.create(
description: 'test',
card: params[:token_id]
)
end
end
次に作成したcustomerオブジェクトのidとparamsで送られてきたトークンidをcardsテーブルに保存します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class CardsController < ApplicationController
before_action :authenticate_user!, only: [:new, :create]
def new
end
def create
Payjp.api_key = ENV["PAYJP_SECRET_KEY"]
customer = Payjp::Customer.create(
description: 'test',
card: params[:token_id]
)
card = Card.new(
customer_id: customer.id,
token_id: params[:token_id],
user_id: current_user.id
)
end
end
次に保存に成功した時と失敗した時のエラーハンドリングを行います。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class CardsController < ApplicationController
before_action :authenticate_user!, only: [:new, :create]
def new
end
def create
Payjp.api_key = ENV["PAYJP_SECRET_KEY"]
customer = Payjp::Customer.create(
description: 'test',
card: params[:token_id]
)
card = Card.new(
customer_id: customer.id,
token_id: params[:token_id],
user_id: current_user.id
)
if card.save
redirect_to root_path
else
redirect_to new_card_path
end
end
end
ではlocalhost:3000/cards/newからカード情報を入力してみます。
今回はテスト環境で行うため、PAY.JP側で用意しているテストカードを使います。
下記の表の値を使いカード情報を入力し、「送信」を押してみましょう。
情報 | 入力する内容 |
---|---|
カード番号 | 4242424242424242 |
カード名義人 | 何でもOK (例)TARO YAMADA |
cvc | 3桁の数字 |
有効期限 | 現在より未来の値(例)(月)02 (年)23 |
その後、データベースに保存されるか確認をしましょう。
エラーの原因を解決しよう
もし「カード情報が正しくありません」というエラーメッセージが出てしまったら、何らかの問題があります。
その際はcard.js
の13行目と14行目の間に下記のコードを追記します。
1
2
3
Payjp.createToken(card, (status, response) => {
console.log(status)
if (status === 200) {
エラーメッセージが出たということはPAY.JP側から返ってくるHTTPステータスコードが200以外であるためです。
返ってくるステータスコードを確認するとどの情報が間違っているかを知ることができます。
公式サイトによるとステータスコードは下記のようになっています。
主なものだけ紹介します。
ステータスコード | 意味 |
---|---|
200 | リクエスト成功 |
400 | カードオブジェクトのプロパティ名の間違い |
401 | 公開鍵が正しくない |
402 | カード情報が正しくない |
404 | application.html.erbのpayjpのjsのリンクが存在しない |
検証モードのコンソールを確認しましょう。
よくあるのが402エラーです。
カード情報が16桁で正しく入力されているか、テストカードの番号が入力されているか、有効期限は未来のものになっているかなど確認しましょう。
カードを1枚しか登録できないようにしよう
今回のサンプルアプリではカードは1枚のみ登録できるようにしたいのですが、今のままだと複数枚登録できてしまいます。
1枚しか登録できないようnewアクション内に下記のコードを追記します。
※PAY.JPでは複数枚のクレジットカードを登録することも可能です
1
2
3
4
5
6
7
class CardsController < ApplicationController
before_action :authenticate_user!, only: [:new, :create]
def new
redirect_to root_path if user_signed_in? && current_user.card
end
# 略
この記述によりユーザーがカードを作っていたらルートパスに戻すことができます。
マイページにカード情報を表示させよう
次にユーザーのマイページにカード情報を表示させましょう。
マイページはusersコントローラーのshowアクションで表示させます。
ルーティングの定義
まずはusersコントローラーのルーティングから定義しましょう。
routes.rb
を下記のように編集します。
1
2
3
4
5
6
Rails.application.routes.draw do
root 'products#index'
devise_for :users
resources :cards, only: [:new, :create]
resources :users, only: :show
end
必ずdevise_for :users
の記述より下に記述してください。
usersコントローラーの作成
次にコントローラーを作成します。
下記のコマンドでusersコントローラーを作成しましょう。
1
rails g controller users show
これでコントローラーが作成できました。
次にshowアクションを定義します。
1
2
3
4
5
6
7
8
9
class UsersController < ApplicationController
def show
Payjp.api_key = ENV["PAYJP_SECRET_KEY"] # PAY.JPに秘密鍵を使ってアクセス
card = Card.find_by(user_id: current_user.id) # cardsテーブルからユーザーのカード情報を取得
customer = Payjp::Customer.retrieve(card.customer_id) # 顧客idを元に、顧客情報を取得
@card = customer.cards.first # cards.firstで登録した最初のカード情報を取得
end
end
まずはcardsテーブルからログインしているユーザーのカード情報をfind_byメソッドを使い取得します。
次に先ほど作成した顧客情報をPAY.JP側から取得するためpayjp
のgemで用意されているretrieveメソッド
を使います。
引数には顧客idの値を渡します。
customer.cards
でcustomerオブジェクトに格納されているカード情報を取得できます。
さらにfirstメソッド
を使うことで最初のカード情報を取得することができます。
取得したcardオブジェクトの中身は下記のようになっています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#<Payjp::Card:0x3fd1e9428df8 id=car_*****************************>
JSON: {
"id": "car_****************************",
"address_city": null,
"address_line1": null,
"address_line2": null,
"address_state": null,
"address_zip": null,
"address_zip_check": "unchecked",
"brand": "Visa",
"country": null,
"created": 1618992858,
"customer": "cus_****************************",
"cvc_check": "passed",
"exp_month": 2,
"exp_year": 2023,
"fingerprint": "********************************",
"last4": "4242",
"livemode": false,
"metadata": {},
"name": null,
"object": "card",
"three_d_secure_status": null
}
このような情報が確認できます。
カード番号の末尾4桁はlast4
というプロパティの値となっているので、この値を取得したければcard.last4
で取得ができます。
マイページのビューファイルの作成
次にマイページのビューファイルを作成します。
app/views/users/show.html.erb
を下記のように編集しましょう。
1
2
3
4
5
6
<h1><%= current_user.name %>さんのページ</h1>
<h2>カード情報</h2>
<p>カード番号:**** **** **** <%= @card.last4%></p>
<p>カード名義人: <%= @card.name %></p>
<p>有効期限:<%= @card.exp_month %> / <%= @card.exp_year %></p>
<p>ブランド:<%= @card.brand %></p>
編集後、localhost:3000/users/1/にアクセスし、下記のようにカード情報が表示されているか確認をしましょう。
このように登録したカード情報が表示されています。
カードを登録していない時の条件分岐
ただしこのままだとクレジットカードを登録していないとエラーになってしまいます。
クレジットカードを登録している時といないときで条件分岐をさせましょう。
1
2
3
4
5
6
7
8
9
10
class UsersController < ApplicationController
def show
Payjp.api_key = ENV["PAYJP_SECRET_KEY"] # PAY.JPに秘密鍵を使ってアクセス
card = Card.find_by(user_id: current_user.id) # cardsテーブルからユーザーのカード情報を入手
if card.present?
customer = Payjp::Customer.retrieve(card.customer_id) # 顧客トークンを元に、顧客情報を入手
@card = customer.cards.first
end
end
end
今回はpresent?メソッドを使い、cardオブジェクトの中身を確認しました。
ビューファイルも編集します。
1
2
3
4
5
6
7
8
9
10
<h1><%= current_user.name %>さんのページ</h1>
<h2>カード情報</h2>
<% if @card.present? %>
<p>カード番号:**** **** **** <%= @card.last4%></p>
<p>カード名義人: <%= @card.name %></p>
<p>有効期限:<%= @card.exp_month %> / <%= @card.exp_year %></p>
<p>ブランド:<%= @card.brand %></p>
<% else %>
<p>カードが登録されていません</p>
<% end %>
クレジットカードが登録されていない時は下記のように表示されます。
それでは最後にトップページに「マイページ」、「クレジットカード登録ページ」へのリンクを貼りましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
<% if user_signed_in?%>
<p>
<%= link_to "ログアウト", destroy_user_session_path, method: :delete %> <%= link_to "マイページ", user_path(current_user) %>
<% if current_user.card.blank? %>
<%= link_to "クレジットカード登録", new_card_path %>
<% end %>
</p>
<% else %>
<p><%= link_to "新規登録", new_user_registration_path %> <%= link_to "ログイン", new_user_session_path %></p>
<% end %>
<% @products.each do |product| %>
<p>商品名:<%= product.name%> 価格:<%= product.price %></p>
<% end %>
このアプリではクレジットカードは1枚しか登録できないようにするため、クレジットカードの登録フォームへのリンクはカードを持っていない状態のみ表示させるようにします。
今回はblank?メソッドを使い、ログインしているユーザーのカードがnilだったらクレジットカードの登録フォームのリンクを表示させるようにしました。
クレジットカードを使い、商品を購入しよう
それでは登録したクレジットカードを使って商品を購入してみましょう。
orderモデルの作成
まずはorderモデルを作成します。
ターミナルで下記のコマンドを実行します。
1
rails g model order
これでorderモデルが作成されました。
アソシエーションの定義
次に作成したorderモデルとuserモデル・productモデルとのアソシエーションを定義します。
ユーザーは複数の商品を購入できるのでuserモデルとの関係性は1対多となります。
また今回は1つの商品は1度しか購入できないためproductモデルとの関係性は1対1になります。
まずはorderモデルから定義していきます。
1
2
3
4
class Order < ApplicationRecord
belongs_to :user
belongs_to :product
end
次はuserモデルです。
1
2
3
4
5
6
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_one :card, dependent: :destroy
has_many :orders, dependent: :destroy
end
最後にproductモデルです。
1
2
3
class Product < ApplicationRecord
has_one :order, dependent: :destroy
end
今回も外部キー制約を付けているので、ユーザーや商品が消去されたら購買情報も削除するためdependent: :destroyを付けます。
ordersテーブルの作成
先ほどのコマンドでordersテーブルを作成するマイグレーションファイルが作成されます。
マイグレーションファイルを下記のように編集をします。
1
2
3
4
5
6
7
8
9
class CreateOrders < ActiveRecord::Migration[6.0]
def change
create_table :orders do |t|
t.references :user, foreign_key: true
t.references :product, foreign_key: true
t.timestamps
end
end
end
次に下記のコマンドでordersテーブルを作成します。
1
rails db:migrate
これでordersテーブルが作成されました。
ordersコントローラーの作成
次に下記のコマンドでcardsコントローラーを作成します。
1
rails g controller orders
今回はordersテーブルに商品を購入したユーザーのidを保存するため、ユーザーがログインしていない時には購入できないようにします。
カードの登録の時に使ったauthenticate_user!メソッド
をここでも使いましょう。
ordersコントローラーを下記のように編集します。
1
2
3
4
5
class OrdersController < ApplicationController
before_action :authenticate_user!, only: :create
def create
end
end
ルーティングの編集
ordersコントローラーを作成したのでルーティングを編集します。
1
2
3
4
5
6
7
8
9
Rails.application.routes.draw do
root 'products#index'
devise_for :users
resources :cards, only: [:new, :create]
resources :users, only: :show
resources :products, only: :index do
resources :orders, only: :create
end
end
今回はURLにproductレコードのidを入れたいため、ルーティングをネストしました。
トップページの編集
ではトップページに購入のリンクを追加します。
app/views/products/index.html.erb
を下記のように編集します。
1
2
3
4
5
6
7
8
9
10
11
12
13
<% if user_signed_in?%>
<p>
<%= link_to "ログアウト", destroy_user_session_path, method: :delete %> <%= link_to "マイページ", user_path(current_user) %>
<% if current_user.card.blank? %>
<%= link_to "クレジットカード登録", new_card_path %>
<% end %>
</p>
<% else %>
<p><%= link_to "新規登録", new_user_registration_path %> <%= link_to "ログイン", new_user_session_path %></p>
<% end %>
<% @products.each do |product| %>
<p>商品名:<%= product.name%> 価格:<%= product.price %> <%= link_to "購入", product_orders_path(product.id), method: :post %></p>
<% end %>
購入はordersコントローラーのcreateアクションが行うので、product_orders_path
としました。
また、createアクション内でproductレコードのidの値が必要になるのでparamsに含まれるよう、引数にproduct.id
の値を渡しています。
createアクションを動かすhttpメソッドはpostなのでmethod: :post
も記述します。
1
2
Prefix Verb URI Pattern Controller#Action
product_orders POST /products/:product_id/orders(.:format) orders#create
このように「購入ボタン」を表示することができました。
createアクションの定義
それではordersコントローラーのcreateアクションを定義していきます。
下記のように編集をします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class OrdersController < ApplicationController
before_action :authenticate_user!, only: :create
def create
return redirect_to new_card_path unless current_user.card.present?
product = Product.find(params[:product_id]) # 購入する商品のレコードを取得
Payjp.api_key = ENV["PAYJP_SECRET_KEY"] # PAY.JPに秘密鍵を設定
customer_id = current_user.card.customer_id # 顧客idを取得
Payjp::Charge.create( # PAY.JPに購入価格と顧客id、通貨の種類を渡す
amount: product.price,
customer: customer_id,
currency: 'jpy'
)
current_user.orders.create(product_id: product.id) # 購入履歴テーブルに保存
redirect_to root_path
end
end
カードが登録されていないと購入できないようにするため、return redirect_to new_card_path unless current_user.card.present?
でカードを登録していないときはカード登録フォームへ戻す記述をします。
1.product = Product.find(params[:product_id])
で購入する商品のレコードを取得します。
2.次にPayjp.api_key = ENV["PAYJP_SECRET_KEY"]
でPAY.JPに秘密鍵を定義します。
3.customer_id = current_user.card.customer_id
で顧客idを取得します。
4.Payjp::Charge.create
で決済情報を作成します。
決済情報の作成に必要な値は商品の価格と顧客id、そしてどの国の通貨かを表すcurrencyの値です。
顧客idを決済時に使用することで、登録したカードの情報を持ったトークンを使って購入することができます。
トークンidを使って購入する場合は下記のようになります。
1
2
3
4
5
Payjp::Charge.create(
amount: product.price,
card: 'トークンid',
currency: 'jpy'
)
上の場合だと一度使用したトークンは再び使用することはできなくなるため、購入するたびにカード情報を登録し新たなトークンを作成する必要が出てしまい非常に面倒です。
ですがここで顧客idを支払い手段として用いることで、何度でも一度登録したカードで決済処理ができるようになります。
決済処理が終わったら、current_user.orders.create(product_id: product.id)
でordersテーブルに購入したユーザーのidと売れた商品のidを保存します。
実際に購入をクリックしてordersテーブルに保存されるか確認をしましょう。
PAY.JPの売り上げのページでも売上が保存されているか確認してみましょう。
このように購入した商品の金額が保存されています。
売却済みの商品は購入できないようにしよう
購入するとトップページに戻りますが、このままだと購入済みの商品も購入できる状態になっています。
購入済みの商品は購入できないよう1つでも購入されていたら「売れ切れ」、売れていなかったら「購入」のリンクが表示されるようヘルパーメソッドをapp/helpers/products_helper.rb
に定義します。
1
2
3
4
5
6
7
8
9
module ProductsHelper
def product_order(product)
if Order.exists?(product_id: product.id)
"売り切れ"
else
link_to "購入", product_orders_path(product.id), method: :post
end
end
end
売れているか売れていないかを判断するにはordersテーブルのproduct_idカラムに商品のidが保存されているレコードがあれば売れていると判断できます。
上のコードではexists?メソッドを使い、ordersテーブルのproduct_idカラムに売れているかを調べたい商品のidが存在するかを調べ、売却済みかを判断しています。
他にもアソシエーションを利用してレコードを取得し、取得したレコードの中身で判断するという方法もあります。
1
2
3
4
5
6
7
8
9
module ProductsHelper
def product_order(product)
if product.order.present? # アソシエーションでレコードを取得し、present?メソッド取得できているか確認
"売り切れ"
else
link_to "購入", product_orders_path(product.id), method: :post
end
end
end
次に作成したヘルパーメソッドをビューファイルで使います。
1
2
3
4
5
6
7
8
9
10
11
12
13
<% if user_signed_in?%>
<p>
<%= link_to "ログアウト", destroy_user_session_path, method: :delete %> <%= link_to "マイページ", user_path(current_user) %>
<% if current_user.card.blank? %>
<%= link_to "クレジットカード登録", new_card_path %>
<% end %>
</p>
<% else %>
<p><%= link_to "新規登録", new_user_registration_path %> <%= link_to "ログイン", new_user_session_path %></p>
<% end %>
<% @products.each do |product| %>
<p>商品名:<%= product.name%> 価格:<%= product.price %> <%= product_order(product) %></p>
<% end %>
このように先ほど購入した商品は「売れ切れ」と表示され、再度購入することができなくなりました。
これでPAY.JPサービスを使ってクレジットカードを登録し、商品を購入する機能が実装できました。
今回のサンプルアプリは基礎的な部分の実装なので、1枚のカードを登録し、購入するという部分のみを詳しく解説いたしました。
PAY.JPのAPIの使い方で紹介した通り、クレジットカードは複数のカードを登録したり、カード情報を更新、削除などもできます。
今回作成したECサイトをベースにし、いろいろな機能を加えてECサイトをカスタマイズしていってください。
この記事のまとめ
- PAY.JPはクレジットカード決済を代行してくれるサービスです
- RailsアプリでPAY.JPのサービスを使うにはpayjpというgemが必要です
- payjpを使うことにより簡単にクレジットカード決済機能を実装することができます