Code haml rails Ruby

【Ruby】【rails】payjpにてフリマアプリ商品購入機能の実装

2020年8月10日


メル○リのようなフリーマーケットサイトを開発する中で、商品購入機能を実装しました。
本記事では、その実装手順について解説していきます。

完成画面

  • ページの遷移

    商品一覧から商品選択

    商品詳細画面へ遷移し購入ボタンを押下

    商品購入確認画面へ遷移し購入ボタンで購入完了

  • 商品購入確認画面の遷移

    クレカの登録有無で表示を場合分けしています。クレカが未登録の場合はその場で登録します。
    商品購入確認画面で「登録してください」ボタンを押下

    カード登録画面へ遷移し「登録する」ボタンを押下

    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です。

CATEGORIES & TAGS

Code, haml, rails, Ruby,

Author:


comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

関連記事

むるし

インフラ系エンジニア。備忘録で色々書いていきます。
現在テックキャンプでフルコミット中。

年収訴求

CodeCamp

縛りなしWiFi

%d人のブロガーが「いいね」をつけました。