Payjpでクレジットカード登録と削除機能を実装する(Rails)

## はじめに

この記事は2019年はじめにTECHCAMPで勉強していたときに書いたものですが
おかげさまで未だに多くの方に観ていただいています。
いくつか情報が古いものがあるかもしれませんが、
もしお気づきの点があればご指摘いただければ修正しますので
コメント欄か編集リクエストに記載ください！:pray:
（今後見てくださる方の時間の節約にもなりますので）

## 実装する機能と前提条件
今回、Payjp(Pay.jp)を利用して**入力フォームを直接ページ内に設置**します。
実装するものとしては下記のとおりです。

- <font color="Red">__自作のカード情報入力フォーム__</font>
- <font color="Red">__カード情報とユーザーの紐づけ__</font>
- <font color="Red">__ユーザーとカードの登録と削除__</font>
- <font color="gray">__商品の購入__（[次回の記事参照](https://qiita.com/takachan_coding/items/d21c0d2621368c9b0d9b)）</font>

イメージとしてはこんな感じの入力フォームです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224314/8d06bd53-0bd1-914e-b73e-9f997ae50962.png)

もしSDKを使う場合は別記事をご覧いただくか[PAY.JP API 利用ガイド | PAY.JP](https://pay.jp/docs/cardtoken)を触るとイメージをつかみやすいと思います。

## バージョン情報

- ruby 2.3.1
- Rails 5.0.7

## 前提条件

- hamlでの記載（gem 'haml-rails'）
- deviseが導入済みでログインができている

→Devise未導入や何もない状態からスタートする場合は、
[『Devise導入の設定手順 ~haml使用/pay.jp導入の前準備~ (Rails)』](https://qiita.com/takachan_coding/items/c7584f7ac165d22c5526)を先に実施ください。


## 1.Payjpのアカウントを作成しよう
[Payjpのサイト](https://pay.jp)でアカウントを作成します。

## 2.APIを確認しよう
[ダッシュボードのAPI](https://pay.jp/d/settings)より確認ができます。
今回はテストモードでの実装なので、__テスト秘密鍵__と__テスト公開鍵__を使用します。
<img width="1007" alt="スクリーンショット 2019-04-14 16.31.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224314/4e41420a-5569-3ed8-3bac-2fb3d9588b36.png">

## 3.payjpのgemを設置しよう
下記をgemfileに記載しbundle installを実施します。

```Gemfile
gem 'payjp'
```

## 4.payjp.jsを読み込めるようにしよう

`%script{src: "https://js.pay.jp/", type: "text/javascript"}`を下記の通り追記します。

```app/views/layouts/application.html.haml
%html
  %head
    %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
    %title payjptest
    %script{src: "https://js.pay.jp/", type: "text/javascript"}
    -# このscriptを記載
    = csrf_meta_tags
    = stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload'
    = javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
  %body
    = yield
```

## 5.テーブルを作成しよう

下記コマンドでpayjpのデータを保管するテーブルを作成します。

```
rails g model Card user_id:integer customer_id:string card_id:string
```

テーブルのカラムの紐づけは下記の通りです。

- user_id ... Userテーブルのid
- customer_id ... payjpの顧客id
- card_id ... payjpのデフォルトカードid

 （デフォルトカードidはトークンとは違います。ここの理解で結構時間をとってしまいました…）

作成したマイグレーションファイルは下記のとおりです。nullはお好みでつけてください。

```db/migrate/20190400000000_create_cards.rb
class CreateCards < ActiveRecord::Migration[5.0]
  def change
    create_table :cards do |t|
      t.integer :user_id, null: false
      t.string :customer_id, null: false
      t.string :card_id, null: false
      t.timestamps
    end
  end
end
```

マイグレーションを実施します。

```
$ rails db:migrate
```

ちなみにカード情報そのものを保存することは禁止されていますので、
payjpに保管されている情報を顧客idやカードidで呼び出すことで情報取得や支払いなどに対応します。
[カード情報非通過化対応のお願い](http://payjp-announce.hatenablog.com/entry/2017/11/10/182738)

## 6.コントローラーを作成しよう

下記コマンドでコントローラーを作成します。
後ほどビューも2つ（new,show）作るのでそれも付け加えておきます。

```
$ rails g controller card new show
```

作成したコントローラーの内容を変更します。

```app/controllers/card_controller.rb
class CardController < ApplicationController

  require "payjp"

  def new
    card = Card.where(user_id: current_user.id)
    redirect_to action: "show" if card.exists?
  end

  def pay #payjpとCardのデータベース作成を実施します。
    Payjp.api_key = ENV["PAYJP_PRIVATE_KEY"]
    if params['payjp-token'].blank?
      redirect_to action: "new"
    else
      customer = Payjp::Customer.create(
      description: '登録テスト', #なくてもOK
      email: current_user.email, #なくてもOK
      card: params['payjp-token'],
      metadata: {user_id: current_user.id}
      ) #念の為metadataにuser_idを入れましたがなくてもOK
      @card = Card.new(user_id: current_user.id, customer_id: customer.id, card_id: customer.default_card)
      if @card.save
        redirect_to action: "show"
      else
        redirect_to action: "pay"
      end
    end
  end

  def delete #PayjpとCardデータベースを削除します
    card = Card.where(user_id: current_user.id).first
    if card.blank?
    else
      Payjp.api_key = ENV["PAYJP_PRIVATE_KEY"]
      customer = Payjp::Customer.retrieve(card.customer_id)
      customer.delete
      card.delete
    end
      redirect_to action: "new"
  end
  
  def show #Cardのデータpayjpに送り情報を取り出します
    card = Card.where(user_id: current_user.id).first
    if card.blank?
      redirect_to action: "new" 
    else
      Payjp.api_key = ENV["PAYJP_PRIVATE_KEY"]
      customer = Payjp::Customer.retrieve(card.customer_id)
      @default_card_information = customer.cards.retrieve(card.card_id)
    end
  end
end
```

コントローラ内の`ENV["PAYJP_PRIVATE_KEY"]`は環境変数で__テスト秘密鍵__を設定し読み込みます。
私は[dotenv](https://qiita.com/closer/items/f8d8ba00ae86d7051764)を利用していますので、下記のような形で記載しました。

```rb:.env
PAYJP_PRIVATE_KEY = 'sk_test_000000000000000000000000'
PAYJP_KEY = 'pk_test_00000000000000000000000'
```


## 7.カードの登録画面を作成しよう
今回は登録画面と確認兼削除画面の2つを作成します。デザインはアレンジしてください。
尚、ルーティングがまだなので画面の表示は`『10.カードを登録してみよう』`までお待ち下さい。

__登録画面__

```app/view/card/new.html.haml
= form_tag(pay_card_index_path, method: :post, id: 'charge-form',  name: "inputForm") do
  %label カード番号
  = text_field_tag "number", "", class: "number", placeholder: "半角数字のみ" ,maxlength: "16", type: "text", id: "card_number"
  %br
  %label 有効期限
  %select#exp_month{name: "exp_month", type: "text"}
    %option{value: ""} --
    %option{value: "1"}01
    %option{value: "2"}02
    %option{value: "3"}03
    %option{value: "4"}04
    %option{value: "5"}05
    %option{value: "6"}06
    %option{value: "7"}07
    %option{value: "8"}08
    %option{value: "9"}09
    %option{value: "10"}10
    %option{value: "11"}11
    %option{value: "12"}12
  %span 月/
  %select#exp_year{name: "exp_year", type: "text"}
    %option{value: ""} --
    %option{value: "2019"}19
    %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
  %span 年
  %br
  %label セキュリティコード
  = text_field_tag "cvc", "", class: "cvc", placeholder: "カード背面3~4桁の番号", maxlength: "4", id: "cvc"
  #card_token
  = submit_tag "追加する", id: "token_submit"
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224314/8d06bd53-0bd1-914e-b73e-9f997ae50962.png)

最初、= sectionを使っていたのですがうまく行かなかったため
%optionが連続発生し駄長なコードになっているのはご了承ください…

__確認兼削除画面__

```app/view/card/show.html.haml
%label 登録クレジットカード情報
%br
= "**** **** **** " + @default_card_information.last4
%br
- exp_month = @default_card_information.exp_month.to_s
- exp_year = @default_card_information.exp_year.to_s.slice(2,3)
= exp_month + " / " + exp_year
= form_tag(card_delete_path, method: :post, id: 'charge-form',  name: "inputForm") do
  %input{ type: "hidden", name: "card_id", value: "" }
  %button 削除する
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224314/fdc2ee98-611e-9ffe-9c48-a88a8f27b3dd.png)

## 8.Payjpにデータを送りトークンを取得しよう

提供されている[pay.jpのサンプル](https://payjp.hatenablog.com/entry/2017/12/05/134933)を参照し一部アレンジしております。

jQueryを使用するので、railsに未設定の場合は設定をしてください。
[設定方法はこちら](https://qiita.com/ngron/items/95846bd630a723e00038)！

```app/assets/javascripts/payjp.js
document.addEventListener(
  "DOMContentLoaded", e => {
    if (document.getElementById("token_submit") != null) { //token_submitというidがnullの場合、下記コードを実行しない
      Payjp.setPublicKey("pk_test_79ae2d2743199a76f3eb36aa"); //ここに公開鍵を直書き
      let btn = document.getElementById("token_submit"); //IDがtoken_submitの場合に取得されます
      btn.addEventListener("click", e => { //ボタンが押されたときに作動します
        e.preventDefault(); //ボタンを一旦無効化します
        let card = {
          number: document.getElementById("card_number").value,
          cvc: document.getElementById("cvc").value,
          exp_month: document.getElementById("exp_month").value,
          exp_year: document.getElementById("exp_year").value
        }; //入力されたデータを取得します。
        Payjp.createToken(card, (status, response) => {
          if (status === 200) { //成功した場合
            $("#card_number").removeAttr("name");
            $("#cvc").removeAttr("name");
            $("#exp_month").removeAttr("name");
            $("#exp_year").removeAttr("name"); //データを自サーバにpostしないように削除
            $("#card_token").append(
              $('<input type="hidden" name="payjp-token">').val(response.id)
            ); //取得したトークンを送信できる状態にします
            document.inputForm.submit();
            alert("登録が完了しました"); //確認用
          } else {
            alert("カード情報が正しくありません。"); //確認用
          }
        });
      });
    }
  },
  false
);
```

## 9.ルートを作成しよう

今回はshow,pay,new,deleteの4つのメゾットがあるので下記の通り追記します。
一部はresoursesで設定してもよいかと思います。

```config/routes.rb
resources :card, only: [:new, :show] do
  collection do
    post 'show', to: 'card#show'
    post 'pay', to: 'card#pay'
    post 'delete', to: 'card#delete'
  end
end
```

## 10.カードを登録してみよう

http://localhost:3000/card/new にアクセスして登録できるか確認してみましょう。

その時、下記の[テストカード](https://pay.jp/docs/testcard)で登録するようにしてください。
それ以外を打ち込んだ場合はトークンが発行できずはねられてしまいます。
<img width="274" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/224314/11cb43bf-4a83-0a01-a58b-9dcb18b54a5d.png">

以上でカード登録から削除まで一通り実装できました。
次回はこれを使って商品支払いを実装します。

## 参考

- https://pay.jp/docs/api/?ruby
- [RailsでjQueryを使えるようにする方法｜Qiita](https://qiita.com/ngron/items/95846bd630a723e00038)