はじめに
フリマアプリを作成中。
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から情報を引っ張ってきてビューに表示させるように記述。
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ボタンが押されることでトークンが作成される処理を記述。
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はカード情報のトークン化に特化したライブラリです。
なので、入力フォーム画面は自身で作成しないといけないようです。
ここではカード登録に必要な有効期限(月・年)とセキュリティコードを
ユーザーが入力出来るようにしました。
= 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"
あとはroute.rbで適当なルートを追記してっと。
よし!これで登録できるだろう!
・・・と思ったんですが、カード情報を入力してsubmitボタンを押したところ、
payjp.jsで記述した「カード情報が正しくありません」のアラートが出てきました。
ここからコードとにらめっこする時間が続きます・・・
デベロッパーツールで確認
コードを見ても何を間違えているかが解らなかったため、
デベロッパーツール(Macショートカット: ⌘ + option + I)で確認。
すると、以下のエラーメッセージに遭遇。
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(文字列型)で指定されてる!!
というわけで、次のとおり修正。
いざ、修正タイム!
下記の部分のみ修正。
.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行修正するのに何時間かかったんだか・・・
終わりに
やはり、リファレンスはしっかり読んだ方がいいですね・・・
初学者としては「リファレンスに書いてあることムズイ」という感じですが・・・笑
リファレンスしっかり把握できるぐらいスキルを向上させていきます!