メル○リのようなフリーマーケットサイトを開発する中で、商品購入機能を実装しました。
本記事では、その実装手順について解説していきます。
完成画面
-
ページの遷移
商品一覧から商品選択
↓
商品詳細画面へ遷移し購入ボタンを押下
↓
商品購入確認画面へ遷移し購入ボタンで購入完了 -
商品購入確認画面の遷移
クレカの登録有無で表示を場合分けしています。クレカが未登録の場合はその場で登録します。
商品購入確認画面で「登録してください」ボタンを押下
↓
カード登録画面へ遷移し「登録する」ボタンを押下
↓
payjpのデフォルト登録モーダルが開くのでカード情報を入力
↓
カード一覧画面へ遷移するので「支払い方法を選択する」ボタンを押下
↓
商品購入確認画面へ戻る
実装手順
以下の手順で実装していきます。
❶ cardモデル作成
❷ 商品購入確認ページフロント実装
❸ cards/itemsコントローラ作成
❹ ルーティング作成
❺ 環境変数設定
❻ 動作確認
❶ cardモデル作成
$ rails g model card
マイグレーションファイル
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
$ rails db:migrate
cardテーブルのデータは、カード登録時に自動生成されるため、cardモデルへのアソシエーションは不要です。
❷ 商品購入確認ページフロント実装
app/views/items/purchase.html.haml
.purchase
.purchase__header
.purchase__header__logo
= link_to root_path ,method: :get,class:"purchase__header__logo__link" do
= image_tag src="logo.png", size: "170x50"
.purchase__body
.purchase__body__title
購入内容の確認
.purchase__body__item
.purchase__body__item__img
= image_tag("#{@item.item_imgs[0].url}", size: "80x80")
.purchase__body__item__right
.purchase__body__item__right__name
= @item.name
%ul.purchase__body__item__right__object
送料込み(税込)
%li.purchase__body__item__right__object__price
= "¥#{@item.price.to_s(:delimited, delimiter: ',')}"
.purchase__body__price
.purchase__body__price__word
支払金額
.purchase__body__price__price
= "¥#{@item.price.to_s(:delimited, delimiter: ',')}"
.purchase__body__way-to-pay
%ul.purchase__body__way-to-pay__title
%li
支払い方法
.purchase__body__way-to-pay__credit
- if @card
= render template: 'items/cards/create'
- else
= link_to new_item_card_path(@item) ,method: :get,class:"purchase__body__way-to-pay__credit__link" do
※登録してください
略
.purchase__body__bottom-to-buy
.purchase__body__bottom-to-buy__object
= link_to pay_item_path(@item), class: "purchase__body__bottom-to-buy__object__link", method: :post do
.button
購入する
略
ハイライト箇所にて、クレカの登録有無で場合分けを行います。クレカが未登録ならcreateテンプレートを表示、登録済であればカード一覧を表示します。
app/views/items/cards/create.html.haml
.customer-card
%h2 クレジットカード情報
.customer-card__content
%figure.form_space
= image_tag"cards/master_logo.svg",size: "50x30"
.customer-card__content__number
%h3
= "**** **** **** " + @default_card_information.last4
.customer-card__content__expired
- exp_month = @default_card_information.exp_month.to_s
- exp_year = @default_card_information.exp_year.to_s.slice(2,3)
%h3
= exp_month + " / " + exp_year
.customer-card__content__item
= link_to purchase_item_path(@item) ,method: :get, class:"customer-card__content__item__link" do
支払い方法を選択する
.customer-card__delete
= link_to item_card_path(@item.id, 1), method: :delete, id: 'charge-form', name: "inputForm", class:"customer-card__delete__link"do
削除する 〉
カード一覧のビューです。
ハイライト箇所にて支払い方法を選択すると、引数の@itemでidが元の購入確認画面へ引き継がれます。
app/views/items/cards/new.html.haml
略
.purchase__body
.purchase__body__title
= form_with url: item_cards_path(@item.id), method: :post do
%script.payjp-button{"data-key" => Rails.application.credentials[:payjp_key], src: "https://checkout.pay.jp/"}
カード登録はpayjpの外部APIにて行うため、リンク先「https://checkout.pay.jp/」を記述します。
❸ cards/itemsコントローラ作成
商品のidに紐づくカード決済を行うため、cardsコントローラは、itemsコントローラからネストする形で作成します。
$ rails g controller Items
$ rails g controller Items::Cards
app/controllers/items/cards_controller.rb
class Items::CardsController < ApplicationController
before_action :set_item, only: [:show, :create, :new, :destroy]
before_action :set_card, only: [:show, :destroy, :new]
require "payjp"
def new
redirect_to action: :create if @card.present?
end
def create #payjpとCardのデータベース作成を実施します。
Payjp.api_key = Rails.application.credentials[:payjp_private_key]
if params['payjp-token'].blank?
redirect_to action: :new
else
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)
if @card.save
customer = Payjp::Customer.retrieve(@card.customer_id)
@default_card_information = customer.cards.retrieve(@card.card_id)
flash[:notice] = 'クレジットカードの登録ができました'
else
redirect_to action: :new
flash[:notice] = 'クレジットカードの登録に失敗しました'
end
end
end
def destroy #PayjpとCardデータベースを削除します
if @card.blank?
else
Payjp.api_key = Rails.application.credentials[:payjp_private_key]
customer = Payjp::Customer.retrieve(@card.customer_id)
customer.delete
@card.delete
end
redirect_to action: "new"
end
def set_item
@item = Item.find(params[:item_id])
puts "params[:item_id] = #{params[:item_id]}"
end
def set_card
@card = Card.find_by(user_id: current_user.id)
end
end
-
それぞれのメソッドの役割
- new :
cardオブジェクトが存在しないならcreateアクションを実行 - create :
鍵をpayjpへ送り、トークンを受領
トークンを使用してcustomerとcardオブジェクト作成
cardはpayjpのデフォルトカードを使用 - destroy :
customerとcardオブジェクト削除
メソッドの名前ですが、独自の名前(payとか)でうまく動作しなかったため、7つのアクションから選択します。
app/controllers/items_controller.rb
class ItemsController < ApplicationController
before_action :set_parents, only: [:index, :new, :create]
before_action :set_item, only: [:show, :purchase, :pay, :card_show]
before_action :set_card, only: [:purchase, :pay, :card_show]
略
def show
@items = Item.includes(:item_imgs).where(id: params[:id])
@item = Item.find_by(id: params[:id])
@category_grandchild = Category.find_by(id: @item.category_id)
@category_child = @category_grandchild.parent
@category_parent = @category_child.parent
end
def purchase
Payjp.api_key = Rails.application.credentials[:payjp_private_key]
if not @card.blank?
customer = Payjp::Customer.retrieve(@card.customer_id)
@default_card_information = customer.cards.retrieve(@card.card_id)
end
end
def pay
Payjp.api_key = Rails.application.credentials[:payjp_private_key]
Payjp::Charge.create(
:amount => @item.price, #支払金額を入力(itemテーブル等に紐づけても良い)
:customer => @card.customer_id, #顧客ID
:currency => 'jpy', #日本円
)
@item.trading_status = 1
@item.buyer_id = current_user.id
@item.save
redirect_to action: :done
end
def done
end
def set_item
@item = Item.find_by(id:params[:id])
end
def set_card
@card = Card.find_by(user_id: current_user.id)
end
private
def set_parents
@parents = Category.where(ancestry: nil)
end
def item_params
params.require(:item).permit(
:name, :introduction, :category_id,
:brand_id, :item_condition_id, :postage_payer_id,
:prefecture_code, :preparation_day_id, :postage_type_id,
:price, :item_imgs_attributes:[:url]
)
end
end
購入機能に関係ないコードは省略しています。
❹ ルーティング作成
config/routes.rb
Rails.application.routes.draw do
略
root 'items#index'
resources :items, except: [:show] do
collection do
get :search
end
end
resources :items do
scope module: :items do
resources :cards, only:[:new, :create, :show, :destroy]
end
collection do
get 'done', to:'items#done'
end
member do
get "purchase"
post "pay"
end
end
略
ハイライト箇所:scope module でitemsの中にcardをネストさせます。
関連するルーティングは以下のようになります。
$ rails routes
略
item_cards POST /items/:item_id/cards(.:format) items/cards#create
new_item_card GET /items/:item_id/cards/new(.:format) items/cards#new
item_card GET /items/:item_id/cards/:id(.:format) items/cards#show
DELETE /items/:item_id/cards/:id(.:format) items/cards#destroy
done_items GET /items/done(.:format) items#done
purchase_item GET /items/:id/purchase(.:format) items#purchase
pay_item POST /items/:id/pay(.:format) items#pay
略
❺ 環境変数設定
payjpからもらう秘密鍵と公開鍵を環境変数へ記述します。
dotenvによる.env記載ではデプロイ時にうまく動作しなかったため、credential.ymlを使用します。
$ EDITOR=vim bin/rails credentials:edit
config/credentials.yml.emc
payjp_private_key: sk_test_xxxxxxxxxxxxxxxx
payjp_key: pk_test_xxxxxxxxxxxxxxxxxxxx
:wqで保存
❻ 動作確認
完成画面のように遷移できればOKです。
|
|
|
|