mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-27 01:43:22 +00:00
Lock variants during checkout to avoid race condition
It was possible that several people bought the same variant even though there wasn't enough stock for everybody. That resulted in negative stock.
This commit is contained in:
@@ -3,6 +3,8 @@ require 'open_food_network/address_finder'
|
||||
class CheckoutController < Spree::CheckoutController
|
||||
layout 'darkswarm'
|
||||
|
||||
prepend_around_filter :lock_variants, only: :update
|
||||
|
||||
prepend_before_filter :check_hub_ready_for_checkout
|
||||
prepend_before_filter :check_order_cycle_expiry
|
||||
prepend_before_filter :require_order_cycle
|
||||
@@ -76,6 +78,33 @@ class CheckoutController < Spree::CheckoutController
|
||||
|
||||
private
|
||||
|
||||
# We need locks to avoid a race condition on stock checking. Otherwise we end
|
||||
# up with negative stock when many people check out at the same time. This
|
||||
# implementation makes a checkout wait for all other checkouts containing one
|
||||
# of the order`s variants.
|
||||
#
|
||||
# There are many places in which stock is stored in the database. Row locking
|
||||
# on variant level ensures that there are no conflicts even when an item is
|
||||
# sold through different shops.
|
||||
#
|
||||
# Ordering the variants by id prevents deadlocks. Plucking the id sends the
|
||||
# locking query without building Spree::Variant objects.
|
||||
def lock_variants
|
||||
# The before action `load_order` handles this error state:
|
||||
return yield if current_order.nil?
|
||||
|
||||
ActiveRecord::Base.transaction do
|
||||
# Prevent another checkout from completing the order at the same time:
|
||||
current_order.lock!
|
||||
|
||||
# Prevent any other checkouts of the same line items at the same time:
|
||||
variant_ids = current_order.line_items.select(:variant_id)
|
||||
Spree::Variant.where(id: variant_ids).order(:id).lock.pluck(:id)
|
||||
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def set_default_bill_address
|
||||
if params[:order][:default_bill_address]
|
||||
new_bill_address = @order.bill_address.clone.attributes
|
||||
|
||||
Reference in New Issue
Block a user