Rails初学者の躓きメモ〜PAY.JPの402エラーが１行の修正で解決〜

# はじめに
フリマアプリを作成中。

PAY.JPを使ってユーザーにクレジットカード情報を登録させたい！
と実装を試みたのですが、"402"というエラーレスポンスが返されて躓きました。
(PAY.JPのAPIリファレンスによると、"402"はカード認証エラーとのこと。)

脳みそ擦り切れてだいぶ時間をかけてしまった部分なのでメモとして投稿します。

初投稿故、色々読みづらい部分もあるかと思いますが、ご了承ください。

因みに、ここではPAY.JPの実装手順については割愛しますが、
やり方としては次の記事を参考にしました。
https://qiita.com/takachan_coding/items/f7e70794b9ca03b559dd
https://qiita.com/wrtenniss/items/75dc631778506f8bce16

# バージョン情報
- Ruby  2.5.1
- Rails 5.2.3

# 大まかに記述したコード(修正前)
### まず、コントローラーを作成。
カード情報をPAY.JPとDBに保存させたり削除させたり、
あとPAY.JPから情報を引っ張ってきてビューに表示させるように記述。

```Ruby:cards_controller.rb
class CardsController < ApplicationController
  require "payjp"
  before_action :set_card, only: [:confirmation, :destroy, :show]

  def confirmation
    redirect_to action: "show",id: current_user.id if @card.present?
  end

  def new
  end

  def create
    Payjp.api_key = ENV["PAYJP_PRIVATE_KEY"] #秘密鍵
    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_id: current_user.id, customer_id: customer.id, card_id: customer.default_card)
      if @card.save
        redirect_to action: "show", id: current_user.id
      else
        redirect_to action: "new"
      end
    end
  end

  def destroy
    if @card.present?
      customer = Payjp::Customer.retrieve(@card.customer_id)
      customer.delete
      @card.delete
      redirect_to action: "confirmation"
    end
  end

  def show
    if @card.blank?
      redirect_to action: "confirmation"
    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

  private

  def set_card
    @card = Card.find_by(user_id: current_user.id)
  end
end
```

### 次に、payjp.jsを記述。
token_submitのIDを持つsubmitボタンが押されることでトークンが作成される処理を記述。
 
```JavaScript:payjp.js
document.addEventListener(
  "DOMContentLoaded", e => {
    if (document.getElementById("token_submit") != null) { 
      Payjp.setPublicKey("pk_test_************************"); //自身の公開鍵を直置き
      let btn = document.getElementById("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, function(status, response) {
          if (status === 200) { 
            $("#card_number").removeAttr("name");
            $("#cvc").removeAttr("name");
            $("#exp_month").removeAttr("name");
            $("#exp_year").removeAttr("name");
            $("#card_token").append(
              $('<input type="hidden" name="payjp-token">').val(response.id)
            ); 
            document.inputForm.submit();
            alert("登録が完了しました"); //確認用
          } else {
            alert("カード情報が正しくありません。"); //確認用
          }
        });
      });
    }
  };
  false
);
```
### カード情報を入力してもらうビューを作成。
payjp.jsはカード情報のトークン化に特化したライブラリです。
なので、入力フォーム画面は自身で作成しないといけないようです。

ここではカード登録に必要な有効期限(月・年)とセキュリティコードを
ユーザーが入力出来るようにしました。

```Ruby:new.html.haml
= render "shared/header"
.wrapper
  .container-mypage
    = render "users/side_bar"
    
    .user-card__content
      %h2 クレジットカード情報入力
      = form_tag(cards_path, method: :post, action: "create", id: 'card__form', class: "card__form", name: "inputForm") do
        .card__form__content
          .card__form__content--number.form_space
            = label_tag "card_number", "カード番号"
            %span 必須
            = text_field_tag "number", "", class: "number", id: "card_number", placeholder: "半角数字のみ", maxlength: "16", value: "4242424242424242"
            %ul.card__form__content--number--cards
              %li
                = image_tag"credit_cards/visa.svg", size: "40x20"
              %li
                = image_tag"credit_cards/master-card.svg", size: "34x20"
              %li
                = image_tag"credit_cards/saison-card.svg", size: "30x20"
              %li
                = image_tag"credit_cards/jcb.svg", size: "32x20"
              %li
                = image_tag"credit_cards/american_express.svg", size: "32x20"
              %li
                = image_tag"credit_cards/dinersclub.svg", size: "32x20"
              %li
                = image_tag"credit_cards/discover.svg", size: "32x20"
          .card__form__content--expired.form_space
            = label_tag "card_expired", "有効期限"
            %span 必須
            .card__form__content--expired--month
              .card__form__content--expired--month__select-box
                %select{name: "exp_month", class: "select--item", id: "exp_month", type: "text"}
                  = (1..12).each do |month|
                    %option{value: month, class: "select--item--month"}
                      = month
                = fa_icon("angle-down", class: "fa")
              %span 月
            .card__form__content--expired--year
              .card__form__content--expired--year__select-box
                %select{name: "exp_year", class: "select--item", id: "exp_year", type: "text"}
                  = (20..30).each do |year|
                    - full_year = 20 + year
                    %option{value: full_year,class: "select--item--year"}
                      = year
                = fa_icon("angle-down", class: "fa")
              %span 年
          .card__form__content--security
            = label_tag "security", "セキュリティコード"
            %span 必須
            = text_field_tag "security", "", id:"cvc", class:"security", name: "cvc", placeholder: "カード背面4桁もしくは3桁の番号", value: "123"
          .card__form__content--help.form_space
            = fa_icon("question-circle", class: "fa")
            カード裏面の番号とは？
          .card__form__content--btn{id: "card_token"}
            = submit_tag "次へ進む", id:"token_submit", class: "btn-red"
= render "shared/footer"

```

↓因みに完成後のビューがこちら。割と綺麗に出来ました。
<img width="1440" alt="card_new_view.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/554116/56eb8b91-3c37-7dac-6c56-ae336fa5ecfe.png">

あとはroute.rbで適当なルートを追記してっと。

よし！これで登録できるだろう！
・・・と思ったんですが、カード情報を入力してsubmitボタンを押したところ、
payjp.jsで記述した「カード情報が正しくありません」のアラートが出てきました。
ここからコードとにらめっこする時間が続きます・・・

# デベロッパーツールで確認
コードを見ても何を間違えているかが解らなかったため、
デベロッパーツール(Macショートカット: ⌘ + option + I)で確認。
すると、以下のエラーメッセージに遭遇。

<img width="1440" alt="402エラー.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/554116/9adec093-1392-7b9f-a736-b07bd31e2ec6.png">

error: (code: "invalid_expiry_year", message: "invalid expiration year", params: "card[exp_year]", status: 402,...)

ほうほう、どうやら"year"の部分で何かしら間違っているらしい。
そこで、リファレンスで"year"の部分を読み進めていくと・・・

[PAY.JP API リファレンス トークンを作成]
https://pay.jp/docs/api/#%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%82%92%E4%BD%9C%E6%88%90

データ型がString(文字列型)で指定されてる！！

というわけで、次のとおり修正。

# いざ、修正タイム！
下記の部分のみ修正。

```Ruby:new.html.haml
   .card__form__content--expired--year
     .card__form__content--expired--year__select-box
       %select{name: "exp_year", class: "select--item", id: "exp_year", type: "text"}
         = (20..30).each do |year|
           - full_year = "20" + year.to_s #文字列に変換
           %option{value: full_year,class: "select--item--year"}
             = year
       = fa_icon("angle-down", class: "fa")
         %span 年
```

to_sメソッドを使って整数型を文字列型に変更。

すると！無事！実装することが！出来ました！やったー！
久々に脳汁が滲み出てきました。

しかし、この１行修正するのに何時間かかったんだか・・・

# 終わりに
やはり、リファレンスはしっかり読んだ方がいいですね・・・
初学者としては「リファレンスに書いてあることムズイ」という感じですが・・・笑

リファレンスしっかり把握できるぐらいスキルを向上させていきます！