Help us understand the problem. What is going on with this article?

[Rails]Pay.jpを利用したクレジット決済機能実装 ② ~モデルの作成・クレジットカード登録~

はじめに

例によって、某プログラミングスクールの最終課題である、フリマアプリのクローンサイト作成において、購入機能実装時にPay.jpを利用したので、健忘録としてここに記す。
今回は主にクレジットカード登録機能の実装に取り掛かります。
(お待たせいたしました、、、)

バージョン情報

ruby '2.5.1'
Rails '5.2.4.2'

実装の流れ

  1. 実装の準備・APIの導入
  2. モデルの作成・クレジットカード登録 ← 今回の実装内容
  3. クレジットカード詳細表示・削除
  4. クレジットカード購入(決済)機能

前提条件として

今回の実装機能のダイジェスト

クレジットカード登録の様子

Image from Gyazo

  1. まず、新規登録画面で特定のカード情報が入力される
  2. 入力された情報は、トークン化処理(第3者にカード情報が漏れないように暗号化)する。(jQueryのpayjp.jsで実装)
  3. トークン化された情報は、自アプリの任意の自テーブルで登録される
  4. 登録が完了したら(今回の例では)createビューが表示される
  5. もし、登録途中でエラーがあれば、その旨のflashメッセージが表示される。

注意!

クレジットカード登録のテストの際には以下のサイトのテスト用のクレジット番号を必ずお使いください!
テストカード
有効期限は現在以降の年と月であればなんでもOK
CVC(セキュリティコード)はなんでもOK!
間違ってもご自身のカード番号は使わないように

前置きはこれくらいで、いよいよ実装!

いよいよ実装! まずはモデルの作成

(こちらは主旨に若干外れてしまいますので、さらっと行きます。参考程度でお願いします)
今回は以下のテーブルを作成しました。

credit_cardsテーブル

Column Type Options
user_id references null: false,foreign_key: true
customer_id string null: false
card_id string null: false

credit_cardsマイグレーションファイル

db/migrate/###_create_credit_cards.rb
class CreateCreditCards < ActiveRecord::Migration[5.2]
  def change
    create_table :credit_cards do |t|
      t.references    :user,        null: false, foreign_key: true
      t.string       :customer_id,  null: false
      t.string       :card_id,      null: false
      t.timestamps 
    end
  end
end

こちらをrails db:migrate

ターミナル
$ rails db:migrate

(もしDBのテーブル作成に不安があれば、(私が参考にした)こちらのサイトをご覧ください)

ちなみにカード情報そのものを保存することは禁止されています。
カード情報非通過化対応のお願い

アソシエーションはこんな感じ

models/credit_card.rb
class CreditCard < ApplicationRecord
  belongs_to :user
end

(今回は1人1枚のみクレジットカード登録できるように設定(has_oneで))

models/user.rb
# 今回の実装に不要なアソシエーションは省略
class User < ApplicationRecord
  has_one :credit_card, dependent: :destroy
end

コントローラーを作る!

今回は(credit_cardsモデルもあることだし)credit_cardsコントローラーを作って、そちらにアクションを記載していきます。

ターミナル
$ rails g controller Credit_cards

今回はクレジットカード新規登録ということで、先に以下のようにコントローラーにnewアクションcreateアクションを追記

(newアクション後のトークン作成処理をまだ行なっていないため、createアクションの内容が分かりにくいかもしれませんが、分からなければ一旦置いておいてOK!)

controllers/credit_cards_controller.rb
class CreditCardsController < ApplicationController
  require "payjp" #PAYJPとやり取りするために、payjpをロード

  def new
    # 後ほど、showアクション(登録クレジットカード詳細表示機能)実装時に追記します。
  end

  def create
    # 前回credentials.yml.encに記載したAPI秘密鍵を呼び出します。
    Payjp.api_key = Rails.application.credentials.dig(:payjp, :PAYJP_SECRET_KEY)

    # 後ほどトークン作成処理を行いますが、そちらの完了の有無でフラッシュメッセージを表示させます。
    if params["payjp_token"].blank?
      redirect_to action: "new", alert: "クレジットカードを登録できませんでした。"
    else
    # 無事トークン作成された場合のアクション(こっちが本命のアクション)
    # まずは生成したトークンから、顧客情報と紐付け、PAY.JP管理サイトに登録
      customer = Payjp::Customer.create(
        email: current_user.email,
        card: params["payjp_token"],
        metadata: {user_id: current_user.id} #最悪なくてもOK!
      )
      # 今度はトークン化した情報を自アプリのCredit_cardsテーブルに登録!
      @card = CreditCard.new(user_id: current_user.id, customer_id: customer.id, card_id: customer.default_card)
      # 無事、トークン作成とともにcredit_cardsテーブルに登録された場合、createビューが表示されるように条件分岐
      if @card.save
        #もしcreateビューを作成しない場合はredirect_toなどで表示ビューを指定
      else
        redirect_to action: "create"
      end
    end
  end

end

しれっと記載してますが、フラッシュメッセージ表示機能も実装しています。
(なので、フラッシュメッセージ機能実装しないとうまく動かないかも)

参考にした記事: 【Rails】flashメッセージを使用して簡易メッセージを表示させる詳しい方法と解説

ついでにルーティングも設定しておく

config/routes.rb
Rails.application.routes.draw do

# したの方に追記
  resources :credit_cards, only: [:new, :create] do
  end
end

コントローラーの記載、ルーティングは完了!

ビューを記載(基本的にレイアウトなどはお好みで、、)

ビューの表示例

Image from Gyazo

newアクション のviewを記載

ちなみに部分テンプレートを使用してます(ファイル名を参照!)。
基本的にはこの次の、jQueryのpayjp.js記述時のid名との照らし合わせ用として、ご確認ください。

views/credit_cards/new/_new.html.haml
.card_add 
  .title クレジットカード情報入力

  -#form_tagメソッドでcreateアクションに入力情報を渡します。
  = form_tag(credit_cards_path, method: :post, id: "charge-form", class: "details", name: "inputForm") do
    .number-details
      .card-number カード番号
      %span.must_check 必須
    .number
      = text_field_tag 'payment_card_no', "", class: 'cardnumber', id: "payment_card_no", placeholder: '半角数字のみ', type: "text", maxlength: "16"
    -# 事前にassets/imagesにcardsファイルを作成、中に各カード会社の画像を入れておき、呼び出す。
    .image
      = image_tag(image_path('cards/visa.png'), class: 'visa')
      = image_tag(image_path('cards/master.png'), class: 'master')
      = image_tag(image_path('cards/jcb.png'), class: 'jcb')
      = image_tag(image_path('cards/amex.png'), class: 'amex')
      = image_tag(image_path('cards/diners.png'), class: 'diners')
      = image_tag(image_path('cards/discover.png'), class: 'discover')

    .expirationdate
      .expirationdate__details
        .date 有効期限
        %span.must_check 必須
      .expirationdate__choice
        .month
          %select#payment_card_month.card-default{name: "payment_card_month", type: "text"}
            %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
          .month-details.year
          %select#payment_card_year.card-default{name: "payment_card_year", type: "text"}
            %option{value: "2020"} 20
            %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
            %option{value: "2030"} 30
          .year-details.securitycode
      .securitycode__details
        .securitycode-details__title セキュリティコード
        %span.must_check 必須
      .securitycode__cardsecurity
        = text_field_tag  "payment_card_cvc", "", type: 'text', class: 'securitycode__cardsecurity__form', id: "payment_card_cvc", maxlength: "4", placeholder: 'カード背面4桁もしくは3桁の番号'
      .card-question カード裏面の番号とは?
    .add 
      = submit_tag "登録する", class: "add__btn", id: "payment_card_submit-button"

scssは省略で、、、

一番重要! トークン化処理のためのpayjp.js記載

実装は提供されている
クレジットカード情報をトークン化するpayjp.jsの使い方
を参考に
ですが正直言ってだいぶ分かりにくいです、、、

ちなみにjQueryを利用してますので、未設定の場合はこちらを参考に導入お願いします!
参考にさせていただいた記事: RailsでjQueryを使えるようにする方法

assets/javascripts/payjp.js
window.addEventListener('DOMContentLoaded', function(){

  //id名が"payment_card_submit-button"というボタンが押されたら取得
  let submit = document.getElementById("payment_card_submit-button");

  Payjp.setPublicKey('pk_test_###'); //公開鍵の記述(ご自身の公開鍵コードを記述しよう!)

    submit.addEventListener('click', function(e){ //ボタンが押されたらトークン作成開始。

    e.preventDefault(); //ボタンを1度無効化

    let card = { //入力されたカード情報を取得(id名の記載ミスに注意!)
        number: document.getElementById("payment_card_no").value,
        cvc: document.getElementById("payment_card_cvc").value,
        exp_month: document.getElementById("payment_card_month").value,
        exp_year: document.getElementById("payment_card_year").value
    };

    Payjp.createToken(card, function(status, response) {  // トークンを生成
      if (status === 200) { //成功した場合(status === 200はリクエストが成功している状況です。)
        //データを自サーバにpostしないようにremoveAttr("name")で削除
        $(".number").removeAttr("name");
        $(".cvc").removeAttr("name");
        $(".exp_month").removeAttr("name");
        $(".exp_year").removeAttr("name"); 
        $("#charge-form").append(
          $('<input type="hidden" name="payjp_token">').val(response.id)
        ); //取得したトークンを送信できる状態にします
        document.inputForm.submit();
        alert("登録が完了しました"); //正常処理完了確認用。createビューがあればつけなくてもOKかな
      } else {
        alert("カード情報が正しくありません。"); //エラー確認用
      }
    });
  });
});

ちなみにこちらの実装が一番難しいと思うので、複数のサイトを比較して実装を進めることをおすすめします。

参考にさせていただいた記事:
Railsフリマアプリ payjpを使ってクレジットカードを登録・削除
Pay.jpをRailsで!【登録・削除・購入】
Payjpでクレジットカード登録と削除機能を実装する(Rails)
[HowTo]Pay.jpを用いたクレジットカードの登録機能実装について/カスタムフォーム版

これにより、クレジットカード新規登録、登録内容入力(newメソッド)、入力内容をトークン化(payjp.jsでの処理)、トークン化された入力内容がuser_idと紐づいてcredit_cardsテーブルに登録される(createメソッド)処理が実装されました!

おまけ createビューの記述

最後に、問題なく登録が完了された場合のcreateビューを記述して完成です。
(特にこだわり等なければ、redirect_to等でマイページやトップページに遷移させても問題ないです。)

createビューの表示例

Image from Gyazo

createアクションのviewを記載

(例によって部分テンプレートを使っています。)

views/credit_cards/create/_create.html.haml
.create
  .create__frame
    .create__frame__message
      登録が完了しました。
    %ul.create__frame__btns
      -# マイページに戻るパスを指定
      %li.my_page_link
        = link_to "マイページに戻る", users_path(current_user.id), class: "my_page_link__btn"
      -# のちに実装する、登録クレジットカード詳細表示(showアクション)に行くパスを指定
      %li.credit_cards_index_link
        = link_to "登録カード一覧", credit_card_path(current_user.id), class: "credit_cards_index_link__btn"

クレジットカード新規登録機能実装 完了!!

最後に

以上でクレジットカードの新規登録機能の実装は完成です!
さらに発展させると、ユーザー登録時にクレジットカード登録機能を差し込むこともできそうですね。

あと、何度も言いますが、登録テスト時には必ず、テストカードの番号での登録をお願いしますね!

次回は、「クレジットカード編集・削除 機能」の実装を行います。

クレジットカード編集・削除 機能

参考リンク

Shunsuke_i_21
こんにちは プログラミング学習半年目の新米エンジニアです。 初心者の目線を忘れず、エラーにぶち当たった際の手助けになれるよう、尽力致します。 ご指導ご鞭撻の程、宜しくお願い致します。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした