すでにメンバーの場合は

無料会員登録

GitHubアカウントで登録 Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

登録がまだの方はこちらから

Pikawakaにログイン

GitHubアカウントでログイン Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

Rails

【Rails】 PAYJPを使ってクレジットカード決済を導入しよう!

ぴっかちゃん
ぴっかちゃん

PAY.JPとはクレジットカード決済代行サービスです。payjpというgemを使うことでRailsのアプリに簡単にクレジットカード決済機能を付けることができます。

作成したアプリにクレジットカード決済機能を付けたいがどうしたら良いかわからない方が多くいらっしゃるかと思います。
そんな時に便利なのがPAY.JPというクレジットカード決済代行サービスです。

このサービスを使ってRailsのアプリにクレジットカード決済機能を付ける流れをサンプルアプリを作成しながら確認していきましょう。

PAY.JPとは

クレジットカードを使って買い物をする際には、下記のような流れで決済が行われています。

①ユーザーがクレジットカードを使って商品を購入
商品を購入

②加盟店がクレジットカード会社に手数料を支払い、入金される
手数料と入金

③クレジットカード会社からユーザーに請求が行き、ユーザーの口座から引き落とされる
請求と引き落とし

このような流れで決済が行われます。
もし個人でクレジットカード決済を行うのであれば、クレジットカード会社とのやりとりを自分で行わなければなりません。
またクレジットカード情報という非常に大切な個人情報を管理する必要があるので、これらを個人で管理するのはかなり大変です。

そこで登場するのがクレジットカード決済を代行してくれるサービスです。
その中の一つが今回使用するPAY.JPというサービスになります。
代行サービスを使うことによりこれらの大変な作業を簡単に扱えるようになります。

PAY.JPの登録方法

それではまずPAY.JPのサービスを使えるようにするためにPAY.JPにアカウントを作成しましょう。
公式サイトへアクセスしてメニューから「申し込む」をクリックしましょう。

payjp公式サイト

すると新規登録フォームが表示されるので、こちらから登録を行いましょう。

登録画面

その後、ログイン画面に遷移するのでログインし、下記の画面になれば登録は完了です。

ダッシュボード

PAY.JPのAPIの使い方

payjpを使ってECサイトを作ろうの章でアプリケーションを作成しながらPAY.JPのAPIの使い方を解説しますが、ここではまずAPIの使い方の簡単な流れを見ていきましょう。

gemのインストール 〜 購入までの流れ

まずは基本的な使い方の流れを解説します。

1. gemのインストール

PAY.JPをRailsのアプリで使うにはPAY.JPが用意しているpayjpというgemが必要になります。
まずはこのgemをインストールします。

Gemfile | payjpの追加
1
gem 'payjp'

2. HTMLのheadタグ内にPAY.JP側で用意しているjsを呼び出す記述をする

PAY.JPのAPIを使用するにはPAY.JP側で用意しているpayjp.jsというJavaScriptのライブラリが必要です。
そのため、下記のコードをheadタグ内に記述します。
Railsだとapplication.html.erbに追記することになります。

app/views/layouts/application.html.erb | jsを呼び出すコードの記述
1
<script type="text/javascript" src="https://js.pay.jp/v1/"></script>

3.トークンを取得するjsを用意する

カード情報をPAY.JPに送り、トークンを取得する部分はJavaScriptを使って行います。
今回のサンプルアプリではpayjp.jsのv1を使うので公式サイトを参考に下記のようなjsファイルを作成します。

card.js | 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テーブルに保存する際の記述です。

コントローラー | 各idの保存
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図は下記のようになります。

ER図

それぞれのテーブルは下記の役割を担っています。

テーブル名 説明
usersテーブル ユーザー情報管理
productsテーブル 商品情報管理
cardsテーブル カード情報管理
ordersテーブル 購入情報管理

環境変数の定義

PAY.JPのサービスを使うにはPAY.JPへの認証に必要な「秘密鍵」と、クレジットカードのトークンを生成するために必要な「公開鍵」の2つの鍵情報が必要になります。

鍵にはテストとしてPAY.JPを利用する場合に使う「テスト鍵」と、実際にPAY.JP側に料金を払い本番環境でPAY.JPを利用する「本番鍵」があります。
テスト鍵では、本番の支払い処理を行うサーバーへは接続されず、実際の支払い情報として計上されることもありません。

今回はテストとして使うため、「テスト秘密鍵」と「テスト公開鍵」を利用します。
ダッシュボードの左のメニューのAPIをクリックすると「テスト秘密鍵」と「テスト公開鍵」を確認することができます。

API

これは外部に漏らしたくない情報なので環境変数として定義します。

環境変数を定義する方法はいろいろありますが、今回はdotenv-railsというgemを使い設定していきます。
まずはGemfileの最後に下記のコードを追記してbundle installコマンドを実行しましょう。

Gemfile
1
gem 'dotenv-rails'

次に下記のコマンドを実行し、環境変数を定義するファイルを作成します。

ターミナル | .envファイルの作成
1
touch .env

それでは作成した.envファイルに環境変数を定義しましょう。
それぞれの鍵の値は先ほどPAY.JPのAPIのページで確認した値を代入します。

.env | 環境変数の定義
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ファイルの一番下に下記のコードを追記しましょう。

gitignore | .envを追記
1
/.env

これで環境変数の定義は完了です。

ユーザー認証機能を実装しよう

それではまずユーザー認証機能から実装していきましょう。
今回ユーザー認証機能はdeviseというgemを使い実装をします。

gemをインストール

gemfileの一番下に下記のコードを追記します。

Gemfile | deviseのインストール
1
gem 'devise'

その後、ターミナルで下記のコマンドを実行し、deviseをインストールします。

ターミナル | deviseのインストール
1
2
bundle install
rails g devise:install

以下のようにターミナルに出力されればインストールに成功しています。

deviseインストール

userモデルの作成

次にuserモデルを作成します。
ターミナルで下記のコマンドを実行します。

ターミナル | userモデルの作成
1
rails g devise user

usersテーブルの作成

上記のコマンドでモデルファイルと一緒にusersテーブルを作成するマイグレーションファイルが作成されます。
デフォルトだとemailカラムとpasswordカラムの記述しかないので、nameカラムを追加します。
下記のようにマイグレーションファイルを編集をします。

db/migrate/**********_devise_create_users.rb | マイグレーションファイルの編集
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を下記のように編集します。

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テーブルを作成します。

ターミナル | データベースとusersテーブルの作成
1
rails db:create db:migrate

これでusersテーブルを作成することができました。

ビューファイルの編集

次にユーザー認証に必要なビューファイルを編集します。
deviseで用意されているビューファイルを編集するため、ターミナルで下記のコマンドを実行し、ビューファイルを作成します。

ターミナル | ビューの作成
1
rails g devise:views

次に作成された新規登録フォームを編集します。
app/views/devise/registrations/new.html.erbを下記のように編集します。

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を下記のように編集します。

app/controllers/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モデルを作成します。
ターミナルで下記のコマンドを実行します。

ターミナル | productモデルの作成
1
rails g model product

これでproductモデルが作成されました。

productsテーブルの作成

上記のコマンドでproductsテーブルを作成するマイグレーションファイルが作成されます。
マイグレーションファイルを下記のように編集をします。

db/migrate/**********_create_products.rb | マイグレーションファイルの編集
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テーブルを作成します。

ターミナル | productsテーブルの作成
1
rails db:migrate

これでproductsテーブルが作成されました。

seedファイルでデータのセット

続いて購入する商品の情報をproductsテーブルに保存します。
dbフォルダ内にあるseeds.rbファイルを下記のように編集します。
最初からコメントアウトされているコードは消してしまいましょう。

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)

次にターミナルで下記のコマンドを実行してください。

ターミナル | seedファイルの実行
1
rails db:seed

productsテーブルを確認し、下のように値がセットされているか確認をしましょう。

productsテーブル

productsコントローラーの作成

次に下記のコマンドを実行し、productsコントローラーを作成します。

ターミナル | productsコントローラーの作成
1
rails g controller products index

これでproductsコントローラーが作成されました。

indexアクションの編集

次にproductsコントローラーのindexアクションを編集します。
products_controller.rbを下記のように編集します。

app/controllers/products_controller.rb | indexアクションの追加
1
2
3
4
5
class ProductsController < ApplicationController
  def index
@products = Product.all
end end

これでindexアクションを定義できました。

ルートの設定

では先ほど作成したproductsコントローラーのindexアクションをルートとして定義しましょう。
configフォルダ内にあるroutes.rbを下記のように編集します。

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というファイルを編集します。

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の末尾に下記のコードを追記します。

Gemfile | payjpの追加
1
gem 'payjp'

その後、bundle installコマンドでインストールをしましょう。

payjpを使う準備をしよう

次にPAY.JP側で用意しているJavaScriptを呼び出す記述をします。
application.html.erbに下記のコードを追記します。

app/views/layouts/application.html.erb | jsを読み込ませるコードの追記
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に送りトークンを生成する

1〜2
カード情報は個人情報になるため、アプリ側のデータベースで管理することは法律で禁じられています。
そのためカード情報はPAY.JP側で管理をしてもらい、トークンを発行してもらいます。
トークンとはPAY.JPで保管されているカード番号やCVCなどのセキュアなデータを隠しつつも、カードと同じように扱うことができるものです。

③ 取得したトークンidをRailsアプリへ送信する

3

ユーザーは取得したトークンのidをRailsアプリへ送信します。
トークンidを受け取ったRailsアプリはトークンidを保存します。

④〜⑤ PAY.JPにトークンidを登録し、顧客情報を作成

4〜5
商品を購入するには発行されたトークンが必要になります。
一度使用したトークンは再び使用することはできませんが、 顧客情報にトークンidを登録すれば、顧客idを支払い手段として用いることで、何度でも同じカードで支払い処理ができるようになります。
そのためPAY.JPにトークンidを送り、顧客情報を管理するcustomerオブジェクトを作成し顧客情報を受け取ります。
その後、作成したcustomerオブジェクトのid(顧客id)も保存します。

⑥〜⑦ 顧客idなどの情報をPAY.JPに送信し、決済処理をする

6<br>
7
決済を行うためには顧客idや購入金額などの情報が必要なので、それらをPAY.JPに送信します。
送られてきた情報からPAY.JP側でカード情報と照らし合わせて決済処理を行います。

ポイント
  1. クレジットカード情報は個人情報になるため、管理することはできない。
  2. そのためPAY.JP側でクレジットカード情報を管理してもらう。
  3. クレジットカード情報を利用するにはPAY.JPから発行してもらったトークンが必要。
  4. トークンは、カード番号やCVCなどのセキュアなデータを隠しつつも、カードと同じように扱うことができる。
  5. 一度使用したトークンは再利用できないが、顧客情報にトークンのidを登録すると顧客idで何度でも同じカードで支払い処理ができる。

カード登録画面を作成しよう

それではカード情報をPAY.JPに送るためにカード情報を入力するフォームを作成します。
入力フォームはcardsコントローラーのnewアクションで表示させます。

cardモデルを作成

まずは下記のコマンドでcardモデルを作成します。

ターミナル | cardモデルの作成
1
rails g model card

これでcardモデルが作成されました。

アソシエーションの定義

次に作成したcardモデルとuserモデルとのアソシエーションを定義します。
1人のユーザーは1枚のカードを登録できるので2つのテーブルの関係は1対1になります。

まずはcardモデルから定義していきます。

app/models/card.rb | アソシエーションの定義
1
2
3
class Card < ApplicationRecord
belongs_to :user
end

次はuserモデルです。

app/models/user.rb | アソシエーションの定義
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テーブルを作成するマイグレーションファイルも作成されました。
マイグレーションファイルを下記のように編集します。

db/migrate/**********_create_cards.rb | マイグレーションファイルの編集
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

編集後、下記のコマンドを実行します。

ターミナル | cardsテーブルの作成
1
rails db:migrate

これでカード情報を保存するcardsテーブルが作成されました。

cardsコントローラーの作成

次に下記のコマンドでcardsコントローラーを作成します。

ターミナル | cardsコントローラーの作成
1
rails g controller cards new

これでcardsコントローラーを作成できました。
カード情報はユーザーがログインしていないと登録できなくさせるため、cardsコントローラーに下記のコードを追記します。

app/controllers/card_controller.rb | コードの追記
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コントローラーを作成したのでルーティングを編集します。

config/routes.rb | 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を下記のように記述します。

apps/views/cards/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ファイルを作成します。

ターミナル | webpacker.rbファイルの作成
1
touch config/initializers/webpacker.rb

作成したらwebpacker.rbを下記のように編集します。

config/initializers/webpacker.rb | 環境変数の追加 -->
1
Webpacker::Compiler.env["PAYJP_PUBLIC_KEY"] = ENV["PAYJP_PUBLIC_KEY"]

これでjs内で環境変数を使えるようになりました。

card.jsを作成しよう

それではjsを作成していきます。
下記のコマンドでapp/javascriptフォルダ内にcard.jsというファイルを作成しましょう。

ターミナル | card.jsファイルの作成
1
touch app/javascript/card.js

card.jsを読み込む記述をしよう

次に作成したcard.jsを読み込む記述をapp/javascript/packs/application.jsにしていきます。
turbolinks機能を使うとjsがうまく読み込めない時があるので、turbolinks機能をオフにするため2行目をコメントアウトします。

app/javascript/packs/application.js | card.jsを読み込む記述 -->
1
2
3
4
5
require("@rails/ujs").start()
// require("turbolinks").start()
require("@rails/activestorage").start() require("channels")
require("../card")

card.jsを編集しよう

公式サイトを参考に、作成したファイルを下記のように編集をします。

app/javascript/card.js | 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のそれぞれのコードが何をしているか細かく見ていきましょう。

app/javascript/card.js | card.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イベントを止めています。

次に下記のコードの部分です。

app/javascript/card.js | card.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}`,
};
#

この部分ではcardオブジェクトを作成し、プロパティ値をセットしています。
この情報がクレジットカード情報としてPAY.JPに送られます。
getElementByIdの引数にはそれぞれのフォームのidの値を入れます。

フォームのid

ここでのプロパティ名はPAY.JP側が要求するものにしておかないとエラーになります。
例えば下記のようなコードだとエラーになります。

app/javascript/card.js | card.jsの説明 -->
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」のようなこちら側で設定した名前だとエラーになるので気をつけましょう。

次に下記のコードです。

app/javascript/card.js | 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);

Payjp.createToken(card, function(status, response) {の部分でcardオブジェクトの情報をPAY.JP側に送信し、statusresponseを受け取ります。
statusにはHTTPステータスコードが、responseにはtokenオブジェクトが格納されています。

トークンの流れ

公式サイトによると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をパラメータに含める記述を見てみましょう。

app/javascript/card.js | 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);

const tokenObj = <input value=${token} name='token_id' type="hidden">;の記述によってparamsにトークンidの値を追加しています。
このフォームは表示させたくないので、type="hidden"で非表示にしています、

このままではparamsにカード情報が入り、サーバーサイドに送信されてしまいます。
サーバーサイドに重要な個人情報を送らないためにトークン化しました。
ですのでカード情報がparamsに含まれないようにする必要があります。

app/javascript/card.js | 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);

上のコードではremoveAttribute("name");でidで指定したinputタグのname属性を削除することによりparamsに含まれないようにしています。

最後に送信とエラーハンドリングの部分です。

app/javascript/card.js | 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);

document.getElementById("card_form").submit();によってjs側でsubmitをしています。
正しい情報を送信していない時にはトークンを取得できないのでアラートを出しています。

顧客idとトークンidを保存しよう

次にユーザーから送られてきたトークンidを保存するコードを記述していきます。

トークンidの保存

createアクションを編集しよう

idを保存するためcreateアクションを編集していきます。
まずはPAY.JPに秘密鍵を送信し、アクセスできるようにします。

一度使用したトークンは再び使用することはできませんが、 顧客の情報にカード情報(トークンid)を登録すれば、顧客id(customer_id)を支払い手段として用いることで、何度でも同じカードで支払い処理ができるようになります。
そのため、Payjp::Customer.createでPayjp::Customerクラスから顧客情報を管理するcustomerオブジェクトを作成します。
引数にはテストか本番かを判別するdescriptionの値とparamsで送られてきたトークンidの値を入れます。

顧客情報の作成

app/controllers/cards_controller.rb |
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テーブルに保存します。

app/controllers/cards_controller.rb |
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

次に保存に成功した時と失敗した時のエラーハンドリングを行います。

app/controllers/cards_controller.rb |
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行目の間に下記のコードを追記します。

app/javascript/card.js | card.jsの編集 -->
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では複数枚のクレジットカードを登録することも可能です

app/controllers/cards_controller.rb | newアクションに追記
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を下記のように編集します。

config/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コントローラーを作成しましょう。

ターミナル | usersコントローラーの作成
1
rails g controller users show

これでコントローラーが作成できました。
次にshowアクションを定義します。

app/controllers/users_controller.rb |
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オブジェクトの中身は下記のようになっています。

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を下記のように編集しましょう。

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/にアクセスし、下記のようにカード情報が表示されているか確認をしましょう。

マイページ

このように登録したカード情報が表示されています。

カードを登録していない時の条件分岐

ただしこのままだとクレジットカードを登録していないとエラーになってしまいます。
クレジットカードを登録している時といないときで条件分岐をさせましょう。

app/controllers/users_controller.rb |
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オブジェクトの中身を確認しました。

ビューファイルも編集します。

app/views/users/show.html.erb | マイページのビューファイルの編集
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 %>

クレジットカードが登録されていない時は下記のように表示されます。

マイページ

それでは最後にトップページに「マイページ」、「クレジットカード登録ページ」へのリンクを貼りましょう。

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 %></p> <% end %>

このアプリではクレジットカードは1枚しか登録できないようにするため、クレジットカードの登録フォームへのリンクはカードを持っていない状態のみ表示させるようにします。

今回はblank?メソッドを使い、ログインしているユーザーのカードがnilだったらクレジットカードの登録フォームのリンクを表示させるようにしました。

クレジットカードを使い、商品を購入しよう

それでは登録したクレジットカードを使って商品を購入してみましょう。

orderモデルの作成

まずはorderモデルを作成します。
ターミナルで下記のコマンドを実行します。

ターミナル | orderモデルの作成
1
rails g model order

これでorderモデルが作成されました。

アソシエーションの定義

次に作成したorderモデルとuserモデル・productモデルとのアソシエーションを定義します。
ユーザーは複数の商品を購入できるのでuserモデルとの関係性は1対多となります。
また今回は1つの商品は1度しか購入できないためproductモデルとの関係性は1対1になります。

まずはorderモデルから定義していきます。

app/models/order.rb | アソシエーションを定義
1
2
3
4
class Order < ApplicationRecord
belongs_to :user
belongs_to :product
end

次はuserモデルです。

app/models/user.rb | アソシエーションの定義
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モデルです。

app/models/product.rb | アソシエーションの定義
1
2
3
class Product < ApplicationRecord
has_one :order, dependent: :destroy
end

今回も外部キー制約を付けているので、ユーザーや商品が消去されたら購買情報も削除するためdependent: :destroyを付けます。

ordersテーブルの作成

先ほどのコマンドでordersテーブルを作成するマイグレーションファイルが作成されます。
マイグレーションファイルを下記のように編集をします。

db/migrate/**********_create_orders.rb | マイグレーションファイルの編集
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テーブルを作成します。

ターミナル | ordersテーブルの作成
1
rails db:migrate

これでordersテーブルが作成されました。

ordersコントローラーの作成

次に下記のコマンドでcardsコントローラーを作成します。

ターミナル | ordersコントローラーの作成
1
rails g controller orders

今回はordersテーブルに商品を購入したユーザーのidを保存するため、ユーザーがログインしていない時には購入できないようにします。
カードの登録の時に使ったauthenticate_user!メソッドをここでも使いましょう。
ordersコントローラーを下記のように編集します。

app/controllers/orders_controller.rb | authenticate_user!メソッドの追記
1
2
3
4
5
class OrdersController < ApplicationController
before_action :authenticate_user!, only: :create
def create
end
end

ルーティングの編集

ordersコントローラーを作成したのでルーティングを編集します。

config/routes.rb | ルーティングの編集 -->
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を下記のように編集します。

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アクションを定義していきます。
下記のように編集をします。

app/controllers/orders_controller.rb | 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を使って購入する場合は下記のようになります。

app/controllers/orders_controller.rb | createアクションの定義
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テーブルに保存されるか確認をしましょう。

ordersテーブルの確認

PAY.JPの売り上げのページでも売上が保存されているか確認してみましょう。

売り上げの確認

このように購入した商品の金額が保存されています。

ポイント
  1. 購入にはトークンid、またはトークンidを登録した顧客情報のidが必要です。
  2. トークンidは一度しか使えないため、その都度クレジットカード情報をトークン化する必要があります。
  3. トークンidを登録した顧客情報のidは何度でも使うことができます。

売却済みの商品は購入できないようにしよう

購入するとトップページに戻りますが、このままだと購入済みの商品も購入できる状態になっています。
購入済みの商品は購入できないよう1つでも購入されていたら「売れ切れ」、売れていなかったら「購入」のリンクが表示されるようヘルパーメソッドをapp/helpers/products_helper.rbに定義します。

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が存在するかを調べ、売却済みかを判断しています。

他にもアソシエーションを利用してレコードを取得し、取得したレコードの中身で判断するという方法もあります。

app/helpers/products_helper.rb | ヘルパーメソッドの定義
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

次に作成したヘルパーメソッドをビューファイルで使います。

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 %> <%= product_order(product) %></p>
<% end %>

このように先ほど購入した商品は「売れ切れ」と表示され、再度購入することができなくなりました。

売れ切れの確認

これでPAY.JPサービスを使ってクレジットカードを登録し、商品を購入する機能が実装できました。

今回のサンプルアプリは基礎的な部分の実装なので、1枚のカードを登録し、購入するという部分のみを詳しく解説いたしました。
PAY.JPのAPIの使い方で紹介した通り、クレジットカードは複数のカードを登録したり、カード情報を更新、削除などもできます。
今回作成したECサイトをベースにし、いろいろな機能を加えてECサイトをカスタマイズしていってください。

この記事のまとめ

  • PAY.JPはクレジットカード決済を代行してくれるサービスです
  • RailsアプリでPAY.JPのサービスを使うにはpayjpというgemが必要です
  • payjpを使うことにより簡単にクレジットカード決済機能を実装することができます