
前回に引き続き、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
とは違い、puchaserecord
とproduct
の中間テーブルなので、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アカウントの作成をします!
最後まで読んで頂き、ありがとうございました!