💬

Rails|ECサイトの注文画面・確認画面・注文確定機能

2023/08/20に公開

要件

ECサイトの注文機能を作成しています。

カートに入れた商品(CartItem)の一覧表示、「注文情報入力進む」をクリック
→ 注文情報入力画面(支払い方法、お届け先を選択)、「確認画面へ進む」をクリック
→ カートに入れた商品の一覧表示、支払い方法、お届け先を表示、「注文を確定する」をクリック
→ 注文完了画面

マイページの「注文履歴一覧」をクリック
→ 注文履歴の一覧を表示、「詳細表示」をクリック
→ 注文履歴の詳細を表示

という流れにします。

開発環境

ruby 3.1.2p20
Rails 6.1.7.4
Cloud9
devise (4.9.2)
Memberモデル、Itemモデル、CartItemモデル、Addressモデルを作成済み
enum導入済み、ja.ymlファイル作成済み

モデルの作成

orderモデルを作成します。

$ rails g model order

ordersテーブルの中身はこちらの記事の通りです。
https://zenn.dev/airiin/articles/4691b219877e67

定義書の通りに、migrationファイルを編集します。

migrate_create_order_model.rb
    create_table :orders do |t|
      t.integer "member_id", null: false
      t.string "post_code", null: false
      t.text "address", null: false
      t.string "name", null: false
      t.integer "shipping_fee", null: false
      t.integer "total_price", null: false
      t.integer "pay_method", null: false
      t.integer "status", null: false
      t.timestamps
    end

db:migrateを実行します。

$ rails db:migrate

コントローラの作成

次にコントローラを作成します。

$ rails g controller orders new show index complete confirm

rails g controller コントローラ名 アクション名
このようにコマンドを実行することで、アクションとビューを同時に作成できます。

orders_controller.rb
class OrdersController < ApplicationController
    before_action :authenticate_member!, only: [:new, :confirm, :create, :indexm :show, :complete]
    
    def new
    end
    
    def confirm
      @cart_items = CartItem.where(member_id: current_member.id)
      @shipping_fee = 800 #送料は800円で固定
      @selected_pay_method = params[:order][:pey_method]
      
      #以下、商品合計額の計算
      ary = []
      @cart_items.each do |cart_item|
        ary << cart_item.item.price*cart_item.quantity
      end
      @cart_items_price = ary.sum
      
      @total_price = @shipping_fee + @cart_items_price
      @address_type = params[:order][:address_type]
      case @address_type
      when "member_address"
        @selected_address = current_member.post_code + " " + current_member.address + " " + current_member.family_name + current_member.first_name
      when "registered_address"
        unless params[:order][:registered_address_id] == ""
          selected = Address.find(params[:order][:registered_address_id]
          @selected_address = selected.post_code + " " + selected.address + " " + selected.name
	 else	 
	   render :new
	 end
      when "new_address"
        unless params[:order][:new_post_code] == "" && params[:order][:new_address] == "" && params[:order][:new_name] == ""
	  @selected_address = params[:order][:new_post_code] + " " + params[:order][:new_address] + " " + params[:order][:new_name]
	else
	  render :new
	end
      end     
    end
    
    def create
      @order = Order.new
      @order.member_id = current_member.id
      @order.shipping_fee = 800
      @cart_items = CartItem.where(member_id: current_member.id)
      ary = []
      @cart_items.each do |cart_item|
        ary << cart_item.item.price*cart_item.quantity
      end
      @cart_items_price = ary.sum
      @order.total_price = @order.shipping_fee + @cart_items_price
      @order.pay_method = params[:order][:pay_method]
      if @order.pay_method == "credit_card"
        @order.status = 1
      else
        @order.status = 0
      end
      
      address_type = params[:order][:address_type]
      case address_type
    when "member_address"
      @order.post_code = current_member.post_code
      @order.address = current_member.address
      @order.name = current_member.family_name + current_member.first_name
    when "registered_address"
      Addresse.find(params[:order][:registered_address_id])
      selected = Addresse.find(params[:order][:registered_address_id])
      @order.post_code = selected.post_code
      @order.address = selected.address
      @order.name = selected.name
    when "new_address"
      @order.post_code = params[:order][:new_post_code]
      @order.address = params[:order][:new_address]
      @order.name = params[:order][:new_name]
    end
    
    if @order.save
      if @order.status == 0
        @cart_items.each do |cart_item|
          OrderDetail.create!(order_id: @order.id, item_id: cart_item.item.id, price: cart_item.item.price, quantity: cart_item.quantity, making_status: 0)
        end
      else
        @cart_items.each do |cart_item|
          OrderDetail.create!(order_id: @order.id, item_id: cart_item.item.id, price: cart_item.item.price, quantity: cart_item.quantity, making_status: 1)
        end
      end
      @cart_items.destroy_all
      redirect_to complete_orders_path
    else
      render :items
    end
  end    
  end
    
    def index
      @orders = Order.where(member_id: current_member.id).order(created_at: :desc).
    end
    
    def show
      @order = Order.find(params[:id])
      @order_details= OrderDetail.where(order_id: @order.id)
    end 
    
    def complete
    end
    
end 

ビューの作成

注文情報入力画面

ここで支払い方法とお届け先を選択します。
ここで入力した情報は orders#confirm に送られます。

new.html.erb
<div class="container p-3">
  <div class="row">
  <%= render 'public/shared/header.html.erb' %>
  <div class="col-md-12">

    <h3>注文情報入力</h3>
    <%= form_with model: Order.new, url: confirm_orders_path, method: :get do |f|  %>

    <h4>支払い方法</h4>
     <%= f.radio_button :pay_method, :credit_card, checked: true %>
     <%= f.label :pay_method, Order.pay_methods_i18n[:credit_card], {value: :credit_card} %>
    <br>
    <%= f.radio_button :pay_method, :transfer %>
    <%= f.label :pay_method, Order.pay_methods_i18n[:transfer], {value: :transfer} %>

    <div>
    <h4>お届け先</h4>
    <%= f.radio_button :address_type, "member_address", checked: true %>
    <%= f.label :address_type, "ご自身の住所" %><%= current_member.post_code + " " + current_member.address + " " + current_member.family_name + current_member.first_name %>
    </div>

    <br>
    <%= f.radio_button :address_type, [:registered_address] %>
    <%= f.label :address_type, "登録済住所から選択" %>
    <%= f.select :registered_address_id, current_member.addresses.all.map { |m| [[m.post_code, m.address, m.name].join(" "), m.id] } ,include_blank: "登録してある配送先住所から選択" %>


    <br>
    <%= f.radio_button :address_type, "new_address" %>
    <%= f.label :address_type, "新しいお届け先" %>
    <table>
      <tr>
        <td>郵便番号(ハイフンなし)</td>
        <td><%= f.text_field :new_post_code %></td>
      </tr>
      <tr>
        <td>住所</td>
        <td><%= f.text_field :new_address %></td>
      </tr>
      <tr>
        <td>宛名</td>
        <td><%= f.text_field :new_name %></td>
      </tr>
    </table>

    <br>
    <%= f.submit "確認画面へ進む", class: "btn btn-info" %>
    <% end %>

  </div>

  <%= render 'public/shared/footer.html.erb' %>
  </div>
</div>

f.radio_button
ラジオボタンについての解説はこちら
https://zenn.dev/airiin/articles/b63b9d6ba9b0d6

Order.pay_methods_i18n[:credit_card]
enumを利用しています。credit_cardは0、transferは1に紐付けていて、ja.ymlに日本語訳を作成しています。そのため、この部分は「クレジットカード」と表示されます。

配送先住所について

各ラジオボタンに address_type を作成しています。
自分の住所 → member_address
自分の登録済み住所 → registered_address
新しい住所 → new_address
この値を使って、コントローラで条件分岐をさせています。

<%= f.select :registered_address_id, current_member.addresses.all.map { |m| [[m.post_code, m.address, m.name].join(" "), m.id] } ,include_blank: "登録してある配送先住所から選択" %>

プルダウンで登録済みの住所から選択できるようになっています。
:registered_address_idこのフォームから送付するデータは params[:order][:registered_address_id]で送られます。
current_member.addresses.all現在ログイン中のユーザーに紐づけられた全Addressデータを取得します。
mapメソッドを使って、各Addressのデータ(郵便番号、住所、宛名)を取り出して joinメソッドで繋げています。プルダウンの選択肢の表示用です。
m.id各アドレスのidもパラメータとして送っています。コントローラでデータを扱う用です。

注文確認画面

注文情報入力画面の内容を確認させます。

confirm.html.erb
<div class="container p-3">
  <div class="row">
  <%= render 'public/shared/header.html.erb' %>
  <div class="col-md-12">
    <h3>注文情報確認</h3>

    <table class="table">
      <tr>
        <td>商品名</td>
        <td>単価(税込)</td>
        <td>数量</td>
        <td>小計</td>
      </tr>
      <% @cart_items.each do |cart_item| %>
        <tr>
          <td>
            <%= image_tag cart_item.item.image, size: "100x80" %>
            <strong><%= cart_item.item.name %></strong>
          </td>
          <td><%= cart_item.item.price %></td>
          <td><%= cart_item.quantity %></td>
          <td><%= cart_item.item.price*cart_item.quantity %></td>
        </tr>
      <% end %>
    </table>

    <table class="table">
      <tr>
        <td>送料</td>
        <td><%= @shipping_fee %></td>
      </tr>
      <tr>
        <td>商品合計</td>
        <td><%= @cart_items_price %></td>
      </tr>
      <tr>
        <td>請求額</td>
        <td><%= @total_price %></td>
      </tr>
    </table>

    <h4>支払い方法</h4>

    <% if @selected_pay_method == "credit_card" %>
      クレジットカード
    <% else %>
      銀行振込
    <% end %>

    <h4>お届け先</h4>
    <%= @selected_address %>

    <%= form_with url: orders_path, method: :post do %>

      <%= hidden_field_tag 'order[pay_method]', @selected_pay_method %>
      <%= hidden_field_tag 'order[address_type]', @address_type %>

      <% if @address_type == "registered_address" %>
        <%= hidden_field_tag 'order[registered_address_id]', params[:order][:registered_address_id] %>
      <% elsif @address_type == "new_address" %>
        <%= hidden_field_tag 'order[new_post_code]', params[:order][:new_post_code] %>
        <%= hidden_field_tag 'order[new_address]', params[:order][:new_address] %>
        <%= hidden_field_tag 'order[new_name]', params[:order][:new_name] %>
      <% end %>
      <%= submit_tag "注文を確定する", class: "btn btn-success" %>
    <% end %>
  </div>

    <%= render 'public/shared/footer.html.erb' %>
  </div>
</div>

f.hidden_field
隠しフィールドを使って、データを送付します。

注文完了画面

complete.html.erb
<div class="container p-3">
  <div class="row">
    <%= render 'public/shared/header.html.erb' %>

  <div class="col-md-12">
    <h3>ご注文ありがとうございました!</h3>
  </div>

    <%= render 'public/shared/footer.html.erb' %>
  </div>
</div>

注文履歴一覧画面

index.html.erb
<div class="container p-3">
  <div class="row">
    <%= render 'public/shared/header.html.erb' %>

  <div class="col-md-12">
    <h3>注文履歴一覧</h3>
    <table class="table">
      <tr>
        <td>注文日</td>
        <td>配送先</td>
        <td>注文商品</td>
        <td>支払い金額</td>
        <td>ステータス</td>
        <td>注文詳細</td>
      </tr>
      <% @orders.each do |order| %>
    <tr>
      <td><%= order.created_at.strftime('%Y/%m/%d') %></td>
      <td><%= order.address %></td>
      <td>
        <ul class="list-unstyled">
          <% order.order_details.each do |order_detail|  %>
            <li><%= order_detail.item.name %></li>
          <% end %>
        </ul>
      </td>
      <td><%= order.total_price %></td>
      <td><%= order.status_i18n %></td>
      <td><%= link_to "表示する", order_path(order.id), class: "btn btn-info" %></td>
    </tr>
    <% end %>
    </table>

    <%= paginate @orders, theme: 'bootstrap-5' %>

  </div>

    <%= render 'public/shared/footer.html.erb' %>
  </div>
</div>

注文履歴詳細画面

show.html.erb
<div class="container p-3">
  <div class="row">
    <%= render 'public/shared/header.html.erb' %>

  <div class="col-md-12">
    <h3>注文履歴詳細</h3>

    <h4>注文情報</h4>
    <table class="table">
      <tr>
        <td>注文日</td>
        <td><%= @order.created_at.strftime('%Y/%m/%d') %></td>
      </tr>
      <tr>
        <td>配送先</td>
        <td><%= @order.post_code + " " + @order.address + " " + @order.name %></td>
      </tr>
      <tr>
        <td>支払い方法</td>
        <td><%= @order.pay_method_i18n %></td>
      </tr>
      <tr>
        <td>ステータス</td>
        <td><%= @order.status_i18n %></td>
      </tr>
    </table>

    <h4>請求情報</h4>
    <table class="table">
      <tr>
        <td>商品合計</td>
        <td><%= @order.total_price %></td>
      </tr>
      <tr>
        <td>配送料</td>
        <td><%= @order.shipping_fee %></td>
      </tr>
      <tr>
        <td>ご請求額</td>
        <td><%= @order.total_price + @order.shipping_fee %></td>
      </tr>
    </table>

    <h4>注文内容</h4>
    <table class="table">
      <tr>
        <td>商品</td>
        <td>単価(税込)</td>
        <td>個数</td>
        <td>小計</td>
      </tr>
      <% @order_details.each do |order_detail| %>
        <tr>
          <td><%= order_detail.item.name %></td>
          <td><%= order_detail.item.price %></td>
          <td><%= order_detail.quantity %></td>
          <td><%= order_detail.quantity * order_detail.item.price %></td>
        </tr>
      <% end %>
    </table>
  </div>
  <%= render 'public/shared/footer.html.erb' %>
  </div>
</div>

参考にさせていただいた記事等

https://github.com/SanEmu/naganocake

Discussion