はじめに
本記事は第五章 商品カテゴリ機能のつづきです。
Railsを用いてフリマアプリの根幹機能である商品の購入機能を実装します。
本章がこのロードマップの最終章となります。
フリマアプリ開発完全ロードマップ
章 | 内容 | 難易度 | 所要時間 | 主要技術 |
---|---|---|---|---|
序 章 | AWS自動デプロイ | ★☆☆☆☆ | ★★★☆☆ | capistrano |
第一章 | データベース設計 | ★☆☆☆☆ | ★☆☆☆☆ | |
第二章 | マークアップ作業 | ★★☆☆☆ | ★★★★★ | |
第三章 | ユーザ登録/ログイン機能 | ★★★★☆ | ★★☆☆☆ | devise |
第四章 | 商品出品/編集/詳細表示/削除 | ★★★★☆ | ★★★☆☆ | carrierwave |
第五章 | 商品カテゴリ機能 | ★★★★★ | ★★★☆☆ | ancestry |
第六章 | 商品購入機能 | ★★★★★ | ★★★★☆ | Payjp |
記事を読むにあたっての前提条件
この記事には私が作成した大量のコードが載っていますが、
コードレビューのための記事ではありませんので、「コードが汚い!」などのコメントはご遠慮願います。
リファクタリングについては、編集リクエストで私に直接お知らせ頂ければ幸いです。
ご理解のほど、何卒よろしくお願いいたします。
1.gem"payjp"の導入
今回の購入機能実装ではクレジットカードを登録するためのpayjp
というgemを使います。
gem'payjp'
bundle install
また、payjpの利用にはpayjpのアカウント登録が必須なので登録しておいて下さい。
https://pay.jp/
2.cardsコントローラおよびモデルの作成
購入機能の実装にあたり、新たにcardsコントローラ
およびCardモデル
を作成します。
rails g controller cards
rails g model card
class CreateCards < ActiveRecord::Migration[5.2]
def change
create_table :cards do |t|
t.references :user , null: false, foreign_key: true
t.string :card_id , null: false
t.string :customer_id, null: false
t.timestamps
end
end
end
rails db:migrate
続いて、既存モデルとの紐付けをしておきます。
belongs_to :user
has_one :card
3.カード登録/照会画面および商品購入確認/完了画面を作成
以下のビューが必要になるので、新たに作成します。
・商品購入確認画面
・商品購入完了画面
・カード登録画面
・カード照会画面
まずはそれぞれに対応したルーティングを定義しましょう。
Rails.application.routes.draw do
devise_for :users, controllers: {
registrations: 'users/registrations',
}
devise_scope :user do
get 'addresses', to: 'users/registrations#new_address'
post 'addresses', to: 'users/registrations#create_address'
end
$date = Time.now.in_time_zone('Tokyo').to_s
root "products#index"
resources :products do
collection do
get "set_images"
get "set_parents"
get "set_children"
get "set_grandchildren"
end
# 編集箇所ここから
member do
get "buy"
get "pay"
end
# 編集箇所ここまで
end
# 編集箇所ここから
resources :cards, only: [:new, :create, :show, :destroy] do
collection do
post 'show', to: 'card#show'
end
end
# 編集箇所ここまで
resources :users, only: :show
end
ビュー表示用にコントローラも以下のように編集しておきます。
class CardsController < ApplicationController
def new
@card = Card.new
end
def show
@default_card_information = {number: "4242424242424242", exp_month: "12", exp_year: "2020"}
end
end
showビューの表示にダミーデータが必要なので登録しておきます。
そして、ルーティングに対応するビューを作成します。
カード登録画面
- contents = ["マイページ" ,"いいね一覧","出品する" ,"出品した商品","購入した商品","評価一覧","発送元・お届け先変更","支払い方法","ログアウト" ]
- links = [user_path(current_user),"" ,new_product_path,"" ,"" ,"" ,"" ,"" ,destroy_user_session_path]
- method = ["get" ,"" ,"get" ,"" ,"" ,"" ,"" ,"" ,"delete" ]
= render "layouts/header"
.show-users-wrapper
.show-users-wrapper__center
.show-users-wrapper__center__left-content
- contents.each.with_index(0) do |content,i|
= link_to links[i], method: method[i],class: "show-users-wrapper__center__left-content__box" do
= content
= form_with model: @card, url: cards_path do |f|
.show-users-wrapper__center__right-content
.card-wrapper
.card-wrapper__title
クレジットカード情報登録
.card-wrapper__content-title
カード番号
%span{class:"require"}
必須
= f.text_field :number, {class: "card-wrapper__input-text", placeholder: "ハイフンなし、半角数字のみ", value:"4242424242424242"}
.card-wrapper__content-title
有効期限
%span{class:"require"}
必須
- month = [["月",""]]
- for num in 1..12
- month << num
= f.select :exp_month, month, {}, {class: "card-wrapper__input-text input-half"}
- year = [["年",""]]
- for num in 0..20
- year << 2040-num
= f.select :exp_year, year, {}, {class: "card-wrapper__input-text input-half"}
.card-wrapper__content-title
セキュリティーコード
%span{class:"require"}
必須
= f.text_field :cvc, {class: "card-wrapper__input-text", placeholder: "3〜4文字、半角数字のみ"}
= f.submit "登録する", {class: "card-wrapper__input-text submit-btn"}
= render "layouts/footer"
@import "cards";
.card-wrapper{
padding: 20px;
height: 457px;
&__title{
text-align: center;
font-weight: bold;
}
&__content-title{
margin-top: 20px;
.require{
padding: 3px;
color: white;
font-size: 12px;
border-radius: 4px;
background-color: $frema;
}
}
&__input-text{
height: 44px;
line-height: 44px;
font-size: 16px;
padding: 0 16px;
width: 460px;
margin-top: 10px;
border-radius: 4px;
}
.input-half{
width: 217px;
margin-right: 10px;
}
.submit-btn{
color: white;
margin-top: 30px;
border-radius: 0px;
background-color: $frema;
border: 0px solid #000;
}
.disabled{
background-color: #f0f0f0;
}
img{
margin-top: 20px;
margin-left: 130px;
width: 200px;
}
}
カード照会画面
- contents = ["マイページ" ,"いいね一覧","出品する" ,"出品した商品","購入した商品","評価一覧","発送元・お届け先変更","支払い方法","ログアウト" ]
- links = [user_path(current_user),"" ,new_product_path,"" ,"" ,"" ,"" ,"" ,destroy_user_session_path]
- method = ["get" ,"" ,"get" ,"" ,"" ,"" ,"" ,"" ,"delete" ]
= render "layouts/header"
.show-users-wrapper
.show-users-wrapper__center
.show-users-wrapper__center__left-content
- contents.each.with_index(0) do |content,i|
= link_to links[i], method: method[i],class: "show-users-wrapper__center__left-content__box" do
= content
.show-users-wrapper__center__right-content
.card-wrapper
.card-wrapper__title
クレジットカード情報照会
= image_tag "visa-card.png"
.card-wrapper__content-title
カード番号
%input{type: "text", class: "card-wrapper__input-text disabled", disabled: true, value: "**** **** **** " + "#{@default_card_information[:number].last(4)}"}
.card-wrapper__content-title
有効期限
%input{type: "text", class: "card-wrapper__input-text input-half disabled", disabled: true, value: @default_card_information[:exp_month]}
%input{type: "text", class: "card-wrapper__input-text input-half disabled", disabled: true, value: @default_card_information[:exp_year]}
= render "layouts/footer"
カード照会画面のCSSはカード登録画面と共通です。
商品購入確認画面
マークアップに入る前に、商品詳細画面から購入確認画面へリンクできるように設定しておきます。
= link_to buy_product_path(@product), class:"show-wrapper__main__buy-button" do
商品購入へ進む
products_controller.rb
にbuyアクションを定義し、
カード表示用のダミー情報を与えておきます。
# buyを追加
before_action :set_product, only: [:show, :edit, :update, :destroy, :buy]
def buy
@default_card_information = {number: "4242424242424242", exp_month: "12", exp_year: "2020"}
end
以下、マークアップコードです。
= render "layouts/header"
.buy-wrapper
.buy-wrapper__main
.buy-wrapper__main__title
.buy-wrapper__main__title__text.center
購入内容の確認
.border
.buy-wrapper__main__contents
= image_tag "#{@product.images[0].image}", class: "buy-wrapper__main__contents__left"
.buy-wrapper__main__contents__right
= @product.name
%br
= "¥#{@product.price.to_s(:delimited)} (税込)"
%br
= @product.postage
.border
.buy-wrapper__main__title
.buy-wrapper__main__title__text
支払い金額
.buy-wrapper__main__title__text
= "¥#{@product.price.to_s(:delimited)}"
.buy-wrapper__main__title
.buy-wrapper__main__title__text
支払い方法
- if Card.where(user: current_user).exists?
.buy-wrapper__main__title__text.light.space
= "**** **** **** " + "#{@default_card_information[:number].last(4)}"
= image_tag "visa-card.png"
- else
= link_to new_card_path, class: "link-blue" do
登録して下さい
.buy-wrapper__main__title
.buy-wrapper__main__title__text
配送先
.buy-wrapper__main__title__text.light.width
= "〒#{Address.find_by(user: current_user).post_number}"
%br
= Address.find_by(user: current_user).prefecture
= Address.find_by(user: current_user).city
= Address.find_by(user: current_user).address
- unless Address.find_by(user: current_user).apartment == nil
= Address.find_by(user: current_user).apartment
- if Card.where(user: current_user).exists?
= link_to pay_product_path(@product), class: "buy-wrapper__main__pay-btn" do
購入を確定する
= render "layouts/footer"
@import "products_buy";
.buy-wrapper{
background-color: #eee;
padding: 40px 0;
&__main{
width: 720px;
padding-bottom: 30px;
background-color: white;
margin: 0 auto;
&__title{
margin: 0 auto;
font-size: 20px;
font-weight: bold;
padding: 20px 0px;
width: 300px;
display: flex;
justify-content: space-between;
.light{
font-size: 14px;
padding-top: 6px;
position: relative;
font-weight: normal;
}
.space{
padding-right: 56px;
}
.width{
width: 170px;
font-size: 12px;
}
.center{
margin: 0 auto;
}
img{
position: absolute;
top: 0;
right: 0;
height: 30px;
}
}
&__contents{
width: 300px;
margin: 0 auto;
padding: 20px 0;
display: flex;
align-items: center;
justify-content: space-around;
&__left{
width: 140px;
height: 100px;
object-fit: cover;
}
&__right{
width: 140px;
font-size: 14px;
line-height: 24px;
}
}
&__pay-btn{
display: block;
margin: 0 auto;
text-align: center;
color: white;
padding: 10px;
width: 300px;
background-color: $frema;
}
.border{
border-bottom: 1px solid #eee;
}
}
}
商品購入完了画面
= render "layouts/header"
.new-wrapper
.new-wrapper__main
.new-wrapper__main__create
商品の購入が完了しました
%br
= link_to root_path,class: "link-blue" do
トップページに戻る
= render "layouts/footer"
4.カード登録機能の実装
まずはpayjpの公開鍵・秘密鍵を設定していきます。
Payjpの公式ホームページにアクセスして、左のリストからAPI
を選び、
テスト用秘密鍵
とテスト用公開鍵
をコピーします。
そして、ターミナルで以下を実行。
// OSがCatalina以降の方は、以下を実行して下さい
sudo vim ~/.zshrc
// OSがMojave以前の方は、以下を実行して下さい
sudo vim ~/.bash_profile
そして、payjpの公開鍵と秘密鍵を以下のように書き込みます。
export PAYJP_PRIVATE_KEY='sk_test_xxxxxxxxxxxxxxxxxxxxxxxx'
export PAYJP_PUBLIC_KEY='pk_test_xxxxxxxxxxxxxxxxxxxxxxxx'
編集の際は、i
キーで編集モードに切り替えて編集、完了の際はesc
キーを2回押して:wq
で上書き保存です。編集後に、以下を実行します。
// OSがCatalina以降の方は、以下を実行して下さい
source vim ~/.zshrc
// OSがMojave以前の方は、以下を実行して下さい
source vim ~/.bash_profile
これで準備完了。次はpayjpトークンの発行
を行います。
トークン
とは、1回だけ使える一時パスワードのようなものです。顧客に入力してもらったカードの情報をそのままデータベースに保存してしまうと、ハッキングされて情報を盗まれる恐れがあるので、payjpのセキュリティーガチガチサーバに保存しておいて、その情報をトークンで借りてくる、という形を取ります。
トークンの発行はカードの登録時と支払いの発生時に行います。
つまりcards#new
cards#create
とproducts#buy
products#pay
です。
まずはcards#new
cards#create
、つまりカード登録のアクションを定義していきます。
def new
@card = Card.new
card = Card.where(user_id: current_user.id)
if card.exists?
redirect_to card_path(card[0].id)
end
end
def create
Payjp.api_key = ENV['PAYJP_PRIVATE_KEY']
token = Payjp::Token.create({
card: {
number: params[:card][:number],
cvc: params[:card][:cvc],
exp_month: params[:card][:exp_month],
exp_year: params[:card][:exp_year]
}},
{'X-Payjp-Direct-Token-Generate': 'true'}
)
if token.blank?
redirect_to new_card_path
else
customer = Payjp::Customer.create(card: token)
card = Card.new(user_id: current_user.id, customer_id: customer.id, card_id: customer.default_card)
card.save!
if card.save!
redirect_to card_path(card)
else
redirect_to new_card_path
end
end
end
token = Payjp::Token.create
という記述でトークンを発行できます。
あとは、トークン発行に必要なparamsをビューで与えてあげればOKです。
- contents = ["マイページ" ,"いいね一覧","出品する" ,"出品した商品","購入した商品","評価一覧","発送元・お届け先変更","支払い方法","ログアウト" ]
- links = [user_path(current_user),"" ,new_product_path,"" ,"" ,"" ,"" ,"" ,destroy_user_session_path]
- method = ["get" ,"" ,"get" ,"" ,"" ,"" ,"" ,"" ,"delete" ]
= render "layouts/header"
.show-users-wrapper
.show-users-wrapper__center
.show-users-wrapper__center__left-content
- contents.each.with_index(0) do |content,i|
= link_to links[i], method: method[i],class: "show-users-wrapper__center__left-content__box" do
= content
= form_with model: @card, url: cards_path do |f|
.show-users-wrapper__center__right-content
.card-wrapper
.card-wrapper__title
クレジットカード情報登録
.card-wrapper__content-title
カード番号
%span{class:"require"}
必須
= f.text_field :number, {class: "card-wrapper__input-text", placeholder: "ハイフンなし、半角数字のみ", value:"4242424242424242"}
.card-wrapper__content-title
有効期限
%span{class:"require"}
必須
- month = [["月",""]]
- for num in 1..12
- month << num
= f.select :exp_month, month, {}, {class: "card-wrapper__input-text input-half"}
- year = [["年",""]]
- for num in 0..20
- year << 2040-num
= f.select :exp_year, year, {}, {class: "card-wrapper__input-text input-half"}
.card-wrapper__content-title
セキュリティーコード
%span{class:"require"}
必須
= f.text_field :cvc, {class: "card-wrapper__input-text", placeholder: "3〜4文字、半角数字のみ"}
= f.submit "登録する", {class: "card-wrapper__input-text submit-btn"}
= render "layouts/footer"
これでカード登録が可能となるので、カードを1つ登録しておいて下さい。
また、先ほど作成したshowおよびbuyのビューがダミーのままなので、
登録したカードの情報が表示できるように修正しておきます。
def show
card = Card.find_by(user_id: current_user.id)
if card.blank?
redirect_to action: "create"
else
Payjp.api_key = ENV['PAYJP_PRIVATE_KEY']
customer = Payjp::Customer.retrieve(card.customer_id)
@default_card_information = Payjp::Customer.retrieve(card.customer_id).cards.data[0]
end
end
- contents = ["マイページ" ,"いいね一覧","出品する" ,"出品した商品","購入した商品","評価一覧","発送元・お届け先変更","支払い方法","ログアウト" ]
- links = [user_path(current_user),"" ,new_product_path,"" ,"" ,"" ,"" ,"" ,destroy_user_session_path]
- method = ["get" ,"" ,"get" ,"" ,"" ,"" ,"" ,"" ,"delete" ]
= render "layouts/header"
.show-users-wrapper
.show-users-wrapper__center
.show-users-wrapper__center__left-content
- contents.each.with_index(0) do |content,i|
= link_to links[i], method: method[i],class: "show-users-wrapper__center__left-content__box" do
= content
.show-users-wrapper__center__right-content
.card-wrapper
.card-wrapper__title
クレジットカード情報照会
= image_tag "visa-card.png"
.card-wrapper__content-title
カード番号
%input{type: "text", class: "card-wrapper__input-text disabled", disabled: true, value: "**** **** **** " + "#{@default_card_information.last4}"}
.card-wrapper__content-title
有効期限
%input{type: "text", class: "card-wrapper__input-text input-half disabled", disabled: true, value: @default_card_information.exp_month}
%input{type: "text", class: "card-wrapper__input-text input-half disabled", disabled: true, value: @default_card_information.exp_year}
= render "layouts/footer"
def buy
unless @product.soldout
card = Card.where(user_id: current_user.id)
if card.exists?
@card = Card.find_by(user_id: current_user.id)
Payjp.api_key = ENV['PAYJP_PRIVATE_KEY']
customer = Payjp::Customer.retrieve(@card.customer_id)
@default_card_information = Payjp::Customer.retrieve(@card.customer_id).cards.data[0]
end
else
redirect_to product_path(@product)
end
end
= render "layouts/header"
.buy-wrapper
.buy-wrapper__main
.buy-wrapper__main__title
.buy-wrapper__main__title__text.center
購入内容の確認
.border
.buy-wrapper__main__contents
= image_tag "#{@product.images[0].image}", class: "buy-wrapper__main__contents__left"
.buy-wrapper__main__contents__right
= @product.name
%br
= "¥#{@product.price.to_s(:delimited)} (税込)"
%br
= @product.postage
.border
.buy-wrapper__main__title
.buy-wrapper__main__title__text
支払い金額
.buy-wrapper__main__title__text
= "¥#{@product.price.to_s(:delimited)}"
.buy-wrapper__main__title
.buy-wrapper__main__title__text
支払い方法
- if Card.where(user: current_user).exists?
.buy-wrapper__main__title__text.light.space
-# 修正箇所ここから
= "**** **** **** " + "#{@default_card_information.last4}"
-# 修正箇所ここまで
= image_tag "visa-card.png"
- else
= link_to new_card_path, class: "link-blue" do
登録して下さい
.buy-wrapper__main__title
.buy-wrapper__main__title__text
配送先
.buy-wrapper__main__title__text.light.width
= "〒#{Address.find_by(user: current_user).post_number}"
%br
= Address.find_by(user: current_user).prefecture
= Address.find_by(user: current_user).city
= Address.find_by(user: current_user).address
- unless Address.find_by(user: current_user).apartment == nil
= Address.find_by(user: current_user).apartment
- if Card.where(user: current_user).exists?
= link_to pay_product_path(@product), class: "buy-wrapper__main__pay-btn" do
購入を確定する
= render "layouts/footer"
5.機能修正
購入機能の実装作業に入る前に、2点修正を加えておきます。
1. 売り切れ商品を購入できなくする
2. 自分で出品した商品を購入できないようにする
上記をまとめてやっていきます。
まずは売り切れ判定を行うために、productモデルにsoldoutカラムを追加します。
rails g migration add_column_product_soldout
class AddColumnProductSoldout < ActiveRecord::Migration[5.2]
def change
add_column :products, :soldout, :boolean, null: false, default: false
end
end
rails db:migrate
productsのindexとshowにそれぞれsoldout表示ができるようにしておきます。
.top-main__products
- @products[0..4].each do |product|
= link_to product_path(product.id) do
.top-main__products__product
-# 編集箇所ここから
- if product.soldout == true
.soldout
SOLD OUT
-# 編集箇所ここまで
- product.images.each.with_index(1) do |image,i|
- if i == 1
= image_tag("#{image.image}")
.top-main__products__product__price
= "¥#{product.price.to_s(:delimited)}"
.top-main__products__product__text
= product.name
.soldout{
letter-spacing: 1px;
width: 100%;
position: absolute;
font-size: 9px;
text-align: center;
padding: 2px;
background-color: $frema;
font-weight: bold;
color: white;
}
下記のshow.html.haml
は修正が二箇所あります。
= render "layouts/header"
.show-wrapper{action: request.path}
.show-wrapper__main
.show-wrapper__main__product-name
= @product.name
%br
- if @product.user_id == current_user.id
= link_to edit_product_path(@product), class:"link-blue", data: {turbolinks: false} do
商品を編集する
- if @product.user_id == current_user.id
= link_to product_path(@product), method: "delete", class:"link-blue" do
商品を削除する
.show-wrapper__main__contents
.show-wrapper__main__contents__left
-# 編集箇所ここから
- if @product.soldout == true
.soldout
SOLD OUT
-# 編集箇所ここまで
- @product.images.each.with_index do |image,i|
- if i == 0
= image_tag "#{image.image}", class:"show-wrapper__main__contents__left__image"
.show-wrapper__main__contents__left__icons
- @product.images.each.with_index do |image,i|
- if i == 0
= image_tag "#{image.image}", class:"show-wrapper__main__contents__left__icons__icon", index: i
- else
= image_tag "#{image.image}", class:"show-wrapper__main__contents__left__icons__icon non-active", index: i
.show-wrapper__main__contents__right
.show-wrapper__main__contents__right__table
.show-wrapper__main__contents__right__table__left
出品者
.show-wrapper__main__contents__right__table__right
= User.find(@product.user_id).nickname
.show-wrapper__main__contents__right__table
.show-wrapper__main__contents__right__table__left
カテゴリー
.show-wrapper__main__contents__right__table__right
= link_to "#", class: "link-blue" do
= @product.category.parent.parent.name
%br
%i{class: "fas fa-chevron-right"}
= @product.category.parent.name
%br
%i{class: "fas fa-chevron-right"}
= @product.category.name
.show-wrapper__main__contents__right__table
.show-wrapper__main__contents__right__table__left
ブランド
.show-wrapper__main__contents__right__table__right
- unless @product.brand.blank?
= @product.brand
.show-wrapper__main__contents__right__table
.show-wrapper__main__contents__right__table__left
商品のサイズ
.show-wrapper__main__contents__right__table__right
- unless @product.size.blank?
= @product.size
.show-wrapper__main__contents__right__table
.show-wrapper__main__contents__right__table__left
商品の状態
.show-wrapper__main__contents__right__table__right
= @product.condition
.show-wrapper__main__contents__right__table
.show-wrapper__main__contents__right__table__left
配送料の負担
.show-wrapper__main__contents__right__table__right
= @product.postage
.show-wrapper__main__contents__right__table
.show-wrapper__main__contents__right__table__left
配送の方法
.show-wrapper__main__contents__right__table__right
らくらくフリマ便
.show-wrapper__main__contents__right__table
.show-wrapper__main__contents__right__table__left
配送元地域
.show-wrapper__main__contents__right__table__right
= @product.region
.show-wrapper__main__contents__right__table
.show-wrapper__main__contents__right__table__left
発送日の目安
.show-wrapper__main__contents__right__table__right
= @product.shipping_days
.show-wrapper__main__price
%h1
= "¥#{@product.price.to_s(:delimited)}"
%span{class: "show-wrapper__main__price__tax"} (税込)
- if @product.postage == "送料込み(出品者負担)"
送料込み
- else
送料別
-# 編集箇所ここから
- if @product.soldout == false && @product.user_id != current_user.id
= link_to buy_product_path(@product), class:"show-wrapper__main__buy-button" do
商品購入へ進む
-# 編集箇所ここまで
.show-wrapper__main__explanation
= br(@product.explanation)
.show-wrapper__main__bottom
.show-wrapper__main__bottom__btn-wrapper
.show-wrapper__main__bottom__btn-wrapper__btn
%i{class: "far fa-heart"}
いいね!
0
.show-wrapper__main__bottom__btn-wrapper__btn
%i{class: "far fa-flag"}
不適切な商品の報告
= link_to "#", class:"show-wrapper__main__bottom__link link-blue" do
%i{class: "fas fa-lock"}
あんしん、あんぜんへの取り組み
.show-wrapper__main
.show-wrapper__main__comment-wrapper
.show-wrapper__main__comment-wrapper__name
令和の明智光秀
.show-wrapper__main__comment-wrapper__icon
= image_tag "user_icon.png"
.show-wrapper__main__comment-wrapper__comment
= image_tag "bubble.png"
= br("こんにちは!\nこちら3000円にお値下げは可能でしょうか?")
%br
%i{class: "far fa-clock"} 3時間前
%i{class: "far fa-flag"}
.show-wrapper__main__comment-wrapper
.show-wrapper__main__comment-wrapper__name
令和の織田信長(本物)
.show-wrapper__main__comment-wrapper__icon
= image_tag "user_icon.png"
.show-wrapper__main__comment-wrapper__comment
= image_tag "bubble.png"
= br("令和の明智光秀さん\nOKです!3000円に値下げしましたので、よろしくお願いします。")
%br
%i{class: "far fa-clock"} 3時間前
%i{class: "far fa-flag"}
.show-wrapper__main__comment-input
.show-wrapper__main__comment-input__caution
相手のことを考え丁寧なコメントを心がけましょう。不快な言葉遣いなどは利用制限や退会処分となることがあります。
%textarea{class: "show-wrapper__main__comment-input__textfield"}
%input{type: "submit", class: "show-wrapper__main__comment-input__submit", value: "コメントを投稿する"}
.show-wrapper__main
.show-wrapper__main__sns-icons
%i{class: "fab fa-facebook-square"}
%i{class: "fab fa-twitter"}
%i{class: "fab fa-line"}
%i{class: "fab fa-pinterest"}
.show-wrapper__others
= link_to "#", class: "show-wrapper__others__title link-blue" do
= "令和の織田信長(本物)"
さんのその他の出品
.show-wrapper__others__products
- for num in 1..6
.show-wrapper__others__products__product
= image_tag "1616794_s.jpg", class: "show-wrapper__others__products__product__image"
.show-wrapper__others__products__product__bottom
.show-wrapper__others__products__product__bottom__text
商品サンプル商品サンプル商品サンプル商品サンプル商品サンプル商品サンプル商品サンプル
.show-wrapper__others__products__product__bottom__price
= "¥#{1980.to_s(:delimited)}"
%span{class: "show-wrapper__others__products__product__bottom__price__like"}
%i{class: "far fa-heart"}
0
= render "layouts/footer"
.soldout{
letter-spacing: 1px;
width: 100%;
position: absolute;
text-align: center;
padding: 2px;
background-color: $frema;
font-weight: bold;
color: white;
}
6.購入機能の実装
最後に、購入機能の実装です。
いよいよ実装も大詰めです。頑張っていきましょう!
products_controller.rb
にpayアクションを定義します。
# payを追加
before_action :set_product, only: [:show, :edit, :update, :destroy, :buy, :pay]
def pay
unless @product.soldout
@card = Card.find_by(user_id: current_user.id)
@product.soldout = true
@product.save!
Payjp.api_key = ENV['PAYJP_PRIVATE_KEY']
@charge = Payjp::Charge.create(
amount: @product.price,
customer: @card.customer_id,
currency: 'jpy'
)
else
redirect_to product_path(@product)
end
end
Payjp::Charge.create
という記述によって支払いが行われます。
あとは、作成した画面で商品を実際に購入してみましょう。
商品が売り切れ表示に変わり、Payjp公式サイトに売上が登録されれば実装完了です!
お疲れ様でした!
これでフリマアプリの機能実装を行うことができました!
ここまで読んでいただき、本当にありがとうございました。
他にも検索機能やいいね機能など、実装できる機能は沢山ありますので、是非実装を進めてみて下さい。
章 | 内容 | 難易度 | 所要時間 | 主要技術 |
---|---|---|---|---|
序 章 | AWS自動デプロイ | ★☆☆☆☆ | ★★★☆☆ | capistrano |
第一章 | データベース設計 | ★☆☆☆☆ | ★☆☆☆☆ | |
第二章 | マークアップ作業 | ★★☆☆☆ | ★★★★★ | |
第三章 | ユーザ登録/ログイン機能 | ★★★★☆ | ★★☆☆☆ | devise |
第四章 | 商品出品/編集/詳細表示/削除 | ★★★★☆ | ★★★☆☆ | carrierwave |
第五章 | 商品カテゴリ機能 | ★★★★★ | ★★★☆☆ | ancestry |
第六章 | 商品購入機能 | ★★★★★ | ★★★★☆ | Payjp |
あとがき
この記事のここをもっと解説してほしい!などありましたら、コメント欄でお伝えください。
また、コメント欄での暴言や誹謗中傷の書き込み等はご遠慮ください。マジで凹むので。
いつも記事を読んで頂いている皆さま、LGTMを押していただいている皆さま、本当にありがとうございます!!
有益な記事を執筆するモチベーションに繋がりますので、この記事が役に立ったなと思って頂けたら、
是非LGTMボタンのクリックやSNSでのシェアをよろしくお願いします。