なにこれ
某スクールのカリキュラムでフリマアプリを開発しました。
そこで購入機能を実装したので、payjpの導入〜商品購入までの流れを備忘録として書きます。
細かく解説はしないで、ざっと流れを説明する記事です。
コードに縦棒が混じってますが、ご了承下さい。
payjpをインストール
gem 'payjp'
をbundle installします
ユーザーのカード情報を保存するためのcardsテーブルを作成
なぜこのテーブルを作るのか
payjpはセキュリティの観点で、payjp側でカード番号などを管理する仕組みになっており、
開発者側の環境では暗号化されてます。
暗号化されたカード情報は保存する必要があるためです。
| class CreateCards < ActiveRecord::Migration[5.2] |
|:--|
| def change |
| create_table :cards do |t| |
| t.references :user ,foreign_key: true, null: false |
| t.string :customer_id, null: false |
| t.string :card_id, null: false |
| t.timestamps |
| end |
| end |
| end |
次にアソシエーションを組みます。
| class Card < ApplicationRecord |
|:--|
| belongs_to :user, optional: true |
| end |
ルーティングを設定します。
今回はカード一覧、カード新規登録、カード作成、カード削除をするので、4つ設定します。
商品が購入した時に呼び出されるアクションが欲しいので、posts(商品投稿)にpayアクションを追加しています。
pay/:idで「どの番号の商品か」と言うのを分からせてます。
例)post.id[1]で購入が押されたらパラメーターでpost.id[1]が送られる
それで該当商品を購入済み状態に変更できます!
| resources :cards, only: [:index, :new, :create, :destroy] |
| resources :posts do |
| collection do |
| post 'pay/:id'=> 'posts#pay' |
| end |
| end |
次にcredentials.yml.encにpayjpのシークレットキーの情報を追加します。
credentials.ymlの説明は省略します。
credentialsを編集できるようにするためには、どのエディタで編集するのかをあらかじめ設定する必要があります。
まずは、ターミナルからVSCodeを起動できるよう設定を行います。
VSCodeで、「Command + Shift + P」を同時に押してコマンドパレットを開きます。
続いて、「shell」と入力しましょう。
メニューに、「PATH内に'code'コマンドをインストールします」という項目が表示されるので、それをクリックします。
この操作を行うことで、ターミナルから「code」と打つことでVSCodeを起動できるようになりました。
以下のコマンドでcredentials.ymlを開きます。
EDITOR='code --wait' rails credentials:edit
開いたら以下の内容を記述。
SK_test_XXXXXXの内容は、payjpに会員登録後に「API」という項目から確認できます。
payjp:
PAYJP_SECRET_KEY: sk_test_XXXXXXXXXXXXXXXXX
追加したらタブの✗を押して保存します。
control + K だと保存されないです。
cardsコントローラーを作成、編集します。
class CardsController < ApplicationController
skip_before_action :verify_authenticity_token
before_action :set_payjp_key, except: :new
def index
if current_user.card.present?
@cards = Card.where(user_id: current_user.id)
end
end
def new
end
def create
if params[:payjp_token]
customer = Payjp::Customer.create(card: params[:payjp_token])
@card = Card.new(user_id: current_user.id, customer_id: customer.id, card_id: customer.default_card)
@card.save
redirect_to cards_path
end
end
def destroy
card = Card.find(params[:id])
customer = Payjp::Customer.retrieve(card.customer_id)
customer.delete
card.delete
redirect_to cards_path
end
private
def set_payjp_key
Payjp.api_key = Rails.application.credentials.payjp[:PAYJP_SECRET_KEY]
end
end
createアクションのif params[:payjp_token]
は後述しますが、
パラメーターで[:payjp_token]というデータを送っております。
customer = Payjp::Customer.create(card: params[:payjp_token])
でpayjpのサーバーと通信してるみたいです。
通信した結果を変数customerに代入してる。といった流れです。
@card = Card.new(user_id: current_user.id, customer_id: customer.id, card_id: customer.default_card)
これでカードの新規登録を行います。内容は暗号化されたものです
privateの中のset_payjp_keyメソッドとは?
Payjp.api_key = Rails.application.credentials.payjp[:PAYJP_SECRET_KEY]
この記述で、Payjp.api_keyという変数?に、先程追加したcredentials.ymlの中身を呼び出してます。
この記述がないと、createアクションでpayjpのサーバーを呼び出すことができず、エラーが起きます。
beforeアクションでskip_before_action :verify_authenticity_token
とあります。
これは超重要で、端的に言うと「これがないとpayjpにハッシュ(データ)を送ることができません。」
サーバーサイドの準備は終わったので、次にビューを触っていきます。
application.html.hamlにscriptを追加してpayjpを読み込ませます。
%body
%script{src: "https://js.pay.jp/", type: "text/javascript"}
= render 'layouts/notifications'
= yield
次にカードの新規登録ページを作成します。
クラスは個人で変更して下さい。
%formを使っている理由は、payjpのjsを呼び出すためです。
form_withを使うやり方が分かりませんでした。
.show-main__registration-field
%form.card-form{method: :post, action: "/cards", id: "chargeForm"}
.show-main__registration-field--text
クレジットカード情報登録
.show-main__registration-field__container
.show-main__registration-field__container__number
%label.show-main__registration-field__container__number--text
カード番号
%span.form-require
必須
%input{type: "text", placeholder: "半角数字のみ", class: "show-main__registration-field__container__number--input", id: "card-num-input"}
.show-main__registration-field__container__expiration
%label.show-main__registration-field__container__expiration--text
有効期限
%span.form-require
必須
.show-main__registration-field__container__expiration--input
%select#month-select
%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
%select#year-select
%option{value: ""} --
%option{value: "2021"}21
%option{value: "2022"}22
%option{value: "2023"}23
%option{value: "2024"}24
%option{value: "2025"}25
%option{value: "2026"}26
.show-main__registration-field__container__security
%label.show-main__registration-field__container__security--text
セキュリティコード
%span.form-require
必須
%input{class: "show-main__registration-field__container__security--input", id: "security-code-input", name: "security-code", type: "text", placeholder: "カード背面4桁もしくは3桁の番号"}
.show-main__registration-field__container__security--information
%span.show-main__registration-field__container__security--information-link
=link_to root_path do
%i.fas.fa-question-circle
カードの裏面の番号とは?
%input#add-card-btn{type: 'submit', value: "クレジットカードの登録", class: "show-main__registration-field__container--submit"}
先程formで送信したデータをcontrollerに送れる形式であるpayjp_token
に変更する処理をjsでします。
このあたりは正直公式のコピペなので、解説できないです。
「こういうもの」としか受け取れないです。
パブリックキーはがっつり本文に書いちゃってokです。
正しいテストカードの情報じゃないとif (status === 200) {
によってエラーが出るようになってます。
ちなみに、カードのテスト番号は、以下です
カードナンバー【4242424242424242】
有効期限【12/21】
名前【YUI ARAGAKI】
$(function () {
// パブリックキーを書いてpayjpと通信できる状態にする
Payjp.setPublicKey('pk_test_62873b159b61e41f8452494b');
// 投稿ボタンを定義
const card_btn = $('#add-card-btn');
if (card_btn != null) {
// 投稿ボタンが押されたら発火
card_btn.click(function (e) {
e.preventDefault();
$(function () {
// カードの情報を定数cardに代入。
const card = {
number: $('#card-num-input').val(),
exp_month: $('#month-select').val(),
exp_year: $('#year-select').val(),
cvc: $('#security-code-input').val(),
}
form = $("#chargeForm")
// この記述でtokenを呼び出してます。
Payjp.createToken(card, function (status, response) {
if (status === 200) { //成功した場合。statusが200だと通信成功らしいです。
// inputをappendしています valueは生成したデータで。
form.append($('<input name="payjp_token" type="hidden">').val(response.id));
// これでcreateアクションが呼ばれます。
form.submit();
// これが出たらカード情報が登録されます
alert("カード情報を登録しました");
} else {
// カード情報が正しくないor入力漏れがあるとこちらが表示されます
alert("正しいカード情報を入力してください");
}
})
});
});
}
});
次にカード一覧を作成します。
これも公式を移したので解説する部分は少ないです。
payjpは自分でコードを書いたりしないで、公式に頼るのが正解だと思いました。
= render @cards
= link_to "削除する", card_path(card), method: :delete, class: 'btn', data: { confirm: '削除してよろしいですか?' }
- customer = Payjp::Customer.retrieve(card.customer_id)
- @default_card_information = customer.cards.retrieve(card.card_id)
= "**** **** **** " + @default_card_information.last4
- 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
次は商品詳細ページでpayjpを使った購入リンクを作成します。
postsコントローラーのpayアクションを呼び出す記述をしています。
scriptは公式。
= form_tag(action: :pay, method: :post) do
%script.payjp-button{:src => "https://checkout.pay.jp", :type => "text/javascript" ,"data-text" => "購入する" ,"data-key" => "pk_test_62873b159b61e41f8452494b"}
コントローラーを記述をします!
cahrgeまで全て公式に書いてあります。
@post.updateでboolean型に指定したpurchasedというカラムをtrueにしてます。
つまり、購入済みの状態にしています。
def pay
@post = Post.find(params[:id])
Payjp.api_key = Rails.application.credentials.payjp[:PAYJP_SECRET_KEY]
charge = Payjp::Charge.create(
amount: @post.price,
card: params['payjp-token'],
currency: 'jpy'
)
@post.update(purchased: true)
redirect_to root_path, notice: '購入しました!'
end
以上でpayjpのカード登録〜商品購入までの流れは終了です!
レビュー機能をつけるなら、カラムの設定とかテーブル追加したり色々やると思いますが、
自分たちのチームは必須実装を終わらせると言う目的だったので、そこらへんは触ってないです!
追加実装までやるチームはすごいと思いました。