payjpを介してクレジットカード登録機能を実装する。

#前書き
某プログラミングスクールの最終課題として存在するフリーマーケットアプリの作成。その中でも特に実装が難しいと噂に聞くのがクレジットカードの登録、利用という過程を踏む商品の購入機能だと言われています。
かくいう僕もこの機能を実装するにあたってとても苦労しました。特にクレジットカードの登録はかなり躓きました・・・
qiitaには色々なpayjpに関する記事がありますし、それ以外にも色々な記事を当たりましたがうまくいかず、最終的にスクールの質問システムでなんとか解決しました。本当に親身になって解決してもらったんですが、途中から理解が追い付かず置いてきぼり状態になってしまったので、改めてここで振り返りながら、どのように実装したか説明していきます！

#まずは準備
payjpを介したクレジットカードの利用を行うためには準備が必要です。ここではその過程を簡単に振り返ります。
##準備1
payjpにユーザー登録します。ログインしてAPIをみるとテストの公開鍵と秘密鍵が確認できます。
##準備2
payjpのgemを読み込みましょう。

```:gemfile
gem payjp
```
これでbundle installすればOKです。
##準備3
カードを登録するテーブルを作りましょう。
マイグレーションファイルは例として以下のようになります。

```ruby:create_card.rb
class CreateCards < ActiveRecord::Migration[5.2]
  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
```
payjpを通してカード番号などを保管するので、カード番号や使用期限などのカラムは作らないようにしましょう。マイグレーションし、アソシエーションはuserテーブルと組んでおきましょう。ここでは1対1で組んでおきます。

#ここからが本題！
これで最低限の準備は整いました。いよいよ本題です。コントローラー作り、そして登録フォーム作りに入っていきます。
##コントローラーの作成
まずはコントローラーを見ていきます。

```ruby:cards_controller.rb
class CardsController < ApplicationController
  before_action :set_card

  require "payjp"

  def index
  end

  def new
    @card = Card.new
  end


  def create
    Payjp.api_key = ENV['PAYJP_SECRET_KEY']#ENVファイルなどに環境変数としてpayjpの鍵を登録しておく必要があります。
    if params['payjp-token'].blank?
      redirect_to action: :new
    else
      customer = Payjp::Customer.create(
        email: current_user.email,
        card: params['payjp-token'],
        metadata: {user_id: current_user.id} 
      )
      @card = Card.new(user: current_user, customer_id: customer.id, card_id: customer.default_card)
      if @card.save
        redirect_to card_path(current_user),notice: 'クレジットカード情報を登録しました。'
      else
        redirect_to action: :new
      end
    end
  end

  def show
    if @card.present?
      Payjp.api_key = ENV['PAYJP_SECRET_KEY']
      customer = Payjp::Customer.retrieve(@card.customer_id)
      @card_information = customer.cards.retrieve(@card.card_id)
    end
  end

  def destroy
    customer = Payjp::Customer.retrieve(@card.customer_id)
    customer.delete
    if @card.destroy
      redirect_to action: index, notice: "削除しました"
    else
      redirect_to action: :index, alert: "削除できませんでした"
    end
  end


  private
  def set_card
    @card = Card.where(user_id: current_user.id).first if Card.where(user_id: current_user.id).present?
  end
end
```
カード情報を参照、削除できるようにshow、destroyアクションも作成しています。
payjp-tokenが多く登場しますが、payjpはカード情報のトークン化を行っており、入力した情報がpayjpに送られ、トークンが返されてそれを受け取ることで顧客情報とカードを紐付けたり、決済を行うとシステムのようです。これを入れることでトークンを取得し、カード情報の取得、削除が行えるようです。
このアクションに基づいてルーティングも行っておきましょう。

##いよいよ登録フォームの実装！
まずはフォームを作ります。

```haml
.creditinport
    .creditinport__title
      クレジットカード情報入力
    .creditinport__credit-card
    .creditinport__credit-card__inner
      = form_with model: @card, method: :post, html: { name: "inputForm", id: "card_form"} do |f|
        = f.label :カード番号, class: 'label'
        %span.required 必須
        %br
        = f.text_field :number, type: 'text', class: 'input-number', placeholder: '半角数字のみ', maxlength: "16"
        .card-deadline
          = f.label :有効期限, class: 'deadline'
          %span.required 必須
          %br
          .card-deadline__month
            = f.select :exp_month, [["01",1],["02",2],["03",3],["04",4],["05",5],["06",6],["07",7],["08",8],["09",9],["10",10],["11",11],["12",12]],{} , class: 'input-deadline'
            %span.month 月
          .card-deadline__year
            = f.select :exp_year, [["20",2020],["21",2021],["22",2022],["23",2023],["24",2024],["25",2025],["26",2026],["27",2027],["28",2028],["29",2029]],{} , class: 'input-deadline'
            %span.year 年
        .card-code
          = f.label :セキュリティコード, class: 'label'
          %span.required 必須
          %br
          = f.text_field :cvc, type: 'text', class: 'input-number', placeholder: 'カード背面4桁もしくは3桁の番号', maxlength: "4"
        .add_card#card_token
          = f.submit '追加する', class: 'add-btn', id: 'token_submit' 
```

そしてもう一つ必要なこととして、payjpにデータを送信し、トークンを返すためにjavascriptを作動させる必要があります。僕が最も詰まってしまったところはこの部分です。完成図は以下の通りです。ちなみにjqueryを使用しています。

```javascript:payjp.js
document.addEventListener(
  'turbolinks:load', e => {　//僕の場合tubrolinksがないとまともに動作しませんでした。 
    if ($("#card_form") != null) {
      Payjp.setPublicKey("pk_test_ここにはそれぞれの公開鍵を入れます。");
      const submitBtn = $("#token_submit")　//フォームの追加する部分のIDをここに入れます。
      submitBtn.on("click", (e) => {　//submitbtnにクリックイベントを発火
        e.preventDefault();
        let card = {
          number: document.getElementById("card_number").value,
          cvc: document.getElementById("card_cvc").value,
          exp_month: document.getElementById("card_exp_month").value,
          exp_year: document.getElementById("card_exp_year").value　//カード番号、セキュリティコード、有効期限を登録
        };
        Payjp.createToken(card, (status, response) => {
          if (status === 200) {　//statusが200の場合は通信成功。httpstatusを表し、400以上だと何らかの障害があることになります。
            $("#card_number").removeAttr("name");
            $("#card_cvc").removeAttr("name");
            $("#card_exp_month").removeAttr("name");
            $("#card_exp_year").removeAttr("name"); //送信したデータが自分のデータベースに登録されないよう削除
            $("#card_token").append(
              $('<input type="hidden" name="payjp-token">').val(response.id)
            );//トークンを送信できるようにする。
            alert("登録が完了しました");
            $("#card_form")[0].submit();
          } else {
            alert("カード情報が正しくありません。");
          }
        });
      });
    }
  }
);
```
僕の場合はクリックイベントが発火できずに苦戦しました。長い記事になりましたが、少しでも参考になれば幸いです。初学者などで間違いや意見などあれば教えていただけると幸いです！