7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

はじめに

フリマアプリを作成中。

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から情報を引っ張ってきてビューに表示させるように記述。

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ボタンが押されることでトークンが作成される処理を記述。

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はカード情報のトークン化に特化したライブラリです。
なので、入力フォーム画面は自身で作成しないといけないようです。

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

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"

↓因みに完成後のビューがこちら。割と綺麗に出来ました。
card_new_view.png

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

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

デベロッパーツールで確認

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

402エラー.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(文字列型)で指定されてる!!

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

いざ、修正タイム!

下記の部分のみ修正。

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メソッドを使って整数型を文字列型に変更。

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

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

終わりに

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

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

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?