PROGRAMMING

RailsでECサイトを作る!【決済機能の追加⑶】









前回に引き続き、ECサイトに決済機能を追加していきたいと思います!

今回やること
・購入が確定した時点で、購入履歴を保存するようにする
・購入履歴の保存と同時にカートを空にするトランザクション処理を設定する

こんな感じで今回もやっていきたいと思います!!

作成手順

モデルの作成

まずは購入履歴を表すPurcaseRecordを作成していきます。

$rails g model PurchaseRecord user:references

作成されたmigrationファイルは下記のように編集。

class CreatePurchaseRecords < ActiveRecord::Migration[5.2]
  def change
    create_table :purchase_records do |t|
      t.references :user,unique: true,foreign_key: true

      t.timestamps
    end
  end
end

purchaserecordテーブルはuserと一対一の関係のため、user_idのみをカラムに追加します。

オプションはunique: true,foreign_key: trueを追加しました。

unique: trueは、1人のユーザーが複数の履歴を作成できないようにしています。

foreign_key: true は外部キー制約です。これにより存在しないレコードと関連付けされないようにしてます。

migrationを実行していきます。

$ rails db:migrate

中間テーブルを作っていきます。

購入履歴とproductを関連づける中間テーブルなので、PurchaseRecordProductと名付けます。

$ rails g model PurchaseRecordProducts product:references purchaserecord:references

こちらもマイグレーションファイルが作成できたら、以下のように編集します。

class CreatePurchaseRecordProducts < ActiveRecord::Migration[5.2]
  def change
    create_table :purchase_record_products do |t|
      t.references :product, foreign_key: true
      t.references :purchase_record, foreign_key: true

      t.timestamps
    end
  end
end

purchaserecordとは違い、puchaserecordproductの中間テーブルなので、unique: trueは入れませんでした。理由としては、多対多の関係のため、たくさんのユーザーの購入履歴にはたくさんのproductが保存されるので、複数の履歴を作れるようにしなければいけないためです。

migrationを実行していきます。

$ rails db:migrate

モデルにリレーションを定義

class User < ApplicationRecord
  has_one :purchase_record, dependent: :destroy
end

class Product < ApplicationRecord
  has_many :purchase_record_products, dependent: :destroy
end

class PurchaseRecord < ApplicationRecord
  belongs_to :user
  has_many :purchase_record_products, dependent: :destroy
  has_many :products, through: :purchase_record_products
end 

class PurchaseRecordProduct < ApplicationRecord
  belongs_to :product
  belongs_to :purchase_record
end

上記のように関連性を作りました。

次は決済時と同時に購入履歴を作成していきます。

購入履歴の作成

Userモデルに以下の以下のようにコードを追加します。

class User < ApplicationRecord

    def prepare_cart
      cart || create_cart
    end
    
    def prepare_purchase_record
      purchase_record || create_purchase_record
    end
    
    def checkout
        purchase_record = prepare_purchase_record
        ids = product_ids.map{ |id| { product_id: id } }
        purchase_record.purchase_record_products.create!(ids)
    end
end
class ChargesController < ApplicationController
  
    def create
      Stripe.api_key = ENV.fetch('STRIPE_SECRET_KEY')
      token = params[:stripeToken]
      product_ids = params[:product_ids].map(&:to_i)
      products = current_user.cart.products.where(id: product_ids)
      total = products.sum(:price)
      cart_products = current_user.cart.cart_products.where(product_id: product_ids)
      cart_products.each(&:destroy!)
      current_user.checkout
            
      Stripe::Charge.create({
          amount: total,
          currency: 'jpy',
          description: 'Example charge',
          source: token,
      })
      redirect_to root_path, notice: '決済に成功しました'
      
    rescue Stripe::CardError => e
      flash[:error] = e.message
      redirect_to cart_path
    end
end

prepare_purchase_recordはカートの時と同様に、current_user.purchase_recordがなければ、購入履歴を作成し、作成されているのであれば購入履歴を新しく作成しないということをを意味します。そしてこれをuser.rbに書き込みます。Userモデルなので、current_userは不要です。

ids = product_ids.map{ |id| { product_id: id } }
purchase_record.purchase_record_products.create!(ids)

上記のコードでは、以下のようにproduct_idを複数作成することができるようになっています。

create!([{product_id: 1}, {product_id: 2}])

上記のようなarrayを作り、効率的にデータベースを操作できるためmapの処理を追加しました。

current_user.checkoutをapp/controllers/charges_controller.rbに追加してあげたら、購入履歴を作ることができました。

しかし、これだけではカートの中身は空になったけど、購入履歴が保存されないということが起きてしまうので、トランザクション処理を入れていきます。

トランザクション処理

トランザクション処理は以下のように追加すれば完了です。

 def checkout
      transaction do
        cart_products = cart.cart_products.where(product_id: product_ids)
        cart_products.each(&:destroy!)
        
        purchase_record = prepare_purchase_record
        ids = product_ids.map{ |id| { product_id: id } }
        purchase_record.purchase_record_products.create!(ids)
      end
    end

上記のように削除のコードをuser.rbに追加(currrent_userは省きます)し、transaction doも追加します。controllerの削除用のコードは消しておけば完了です。

しかし、決済をこのまましたら下記のエラーが出てしまいました。

日本語に訳すと、「検証に失敗しました:Purchaserecordが存在する必要があります」という意味です。つまり、Purchaserecordモデルに値が入っていないので、Validationで弾かれた感じですかね。このエラー文を検索してみると、optional: trueを追加する必要があるとのことです。

optional: trueとは外部キーのnilを許可するというコードです。

class PurchaseRecordProduct < ApplicationRecord
  belongs_to :product
  belongs_to :purchaserecord, optional: true
end

optional: trueを追加して無事に購入履歴が作成され、カートの中身が空になりました!

まとめ

これで決済機能が無事追加されました!

次はAdminアカウントの作成をします!

最後まで読んで頂き、ありがとうございました!

-PROGRAMMING

Copyright © Iseblog ,@2020 All Rights Reserved.