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

[商品価格に関しましては、リンクが作成された時点と現時点で情報が変更されている場合がございます。]

現場で使える Ruby on Rails 5速習実践ガイド [ 大場寧子 ]
価格:3828円(税込、送料無料) (2022/5/10時点)

CATEGORIES & TAGS

Code, haml, rails, Ruby,

Author:


comment

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

関連記事

カテゴリー

むるし

フリーランスのインフラ系エンジニア。
備忘録で色々書いていきます。
お問い合わせは↓
mo-gyu@murci.net
LPIC303 Security
%d人のブロガーが「いいね」をつけました。