Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stripe stock check #7779

Merged
23 changes: 20 additions & 3 deletions app/controllers/checkout_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def edit
# This is only required because of spree_paypal_express. If we implement
# a version of paypal that uses this controller, and more specifically
# the #action_failed method, then we can remove this call
OrderCheckoutRestart.new(@order).call
reset_order_to_cart
rescue Spree::Core::GatewayError => e
rescue_from_spree_gateway_error(e)
end
Expand Down Expand Up @@ -82,7 +82,7 @@ def load_order
@order = current_order

redirect_to(main_app.shop_path) && return if redirect_to_shop?
redirect_to_cart_path && return unless valid_order_line_items?
handle_invalid_stock && return unless valid_order_line_items?

before_address
setup_for_current_state
Expand All @@ -100,7 +100,10 @@ def valid_order_line_items?
distributes_order_variants?(@order)
end

def redirect_to_cart_path
def handle_invalid_stock
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice renaming 👍

cancel_incomplete_payments if valid_payment_intent_provided?
reset_order_to_cart

respond_to do |format|
format.html do
redirect_to main_app.cart_path
Expand All @@ -112,6 +115,20 @@ def redirect_to_cart_path
end
end

def cancel_incomplete_payments
# The checkout could not complete due to stock running out. We void any pending (incomplete)
# Stripe payments here as the order will need to be changed and resubmitted (or abandoned).
@order.payments.incomplete.each do |payment|
payment.void!
payment.adjustment&.update_columns(eligible: false, state: "finalized")
end
flash[:notice] = I18n.t("checkout.payment_cancelled_due_to_stock")
end

def reset_order_to_cart
OrderCheckoutRestart.new(@order).call
end

def setup_for_current_state
method_name = :"before_#{@order.state}"
__send__(method_name) if respond_to?(method_name, true)
Expand Down
5 changes: 3 additions & 2 deletions app/models/spree/payment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@ class Payment < ApplicationRecord
scope :from_credit_card, -> { where(source_type: 'Spree::CreditCard') }
scope :with_state, ->(s) { where(state: s.to_s) }
scope :completed, -> { with_state('completed') }
scope :incomplete, -> { where(state: %w(checkout pending requires_authorization)) }
scope :pending, -> { with_state('pending') }
scope :failed, -> { with_state('failed') }
scope :valid, -> { where('state NOT IN (?)', %w(failed invalid)) }
scope :valid, -> { where.not(state: %w(failed invalid)) }
scope :authorization_action_required, -> { where.not(cvv_response_message: nil) }
scope :requires_authorization, -> { with_state("requires_authorization") }
scope :with_payment_intent, ->(code) { where(response_code: code) }
Expand All @@ -72,7 +73,7 @@ class Payment < ApplicationRecord
transition from: [:processing, :pending, :checkout, :requires_authorization], to: :completed
end
event :void do
transition from: [:pending, :completed, :checkout], to: :void
transition from: [:pending, :completed, :requires_authorization, :checkout], to: :void
end
# when the card brand isnt supported
event :invalidate do
Expand Down
1 change: 1 addition & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1315,6 +1315,7 @@ en:
message_html: "I agree to the seller's %{terms_and_conditions_link} and the platform %{tos_link}."
terms_and_conditions: "Terms and Conditions"
failed: "The checkout failed. Please let us know so that we can process your order."
payment_cancelled_due_to_stock: "Payment cancelled: the checkout could not be completed due to stock issues."
shops:
hubs:
show_closed_shops: "Show closed shops"
Expand Down
58 changes: 46 additions & 12 deletions spec/controllers/checkout_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@
let(:shipping_method) { distributor.shipping_methods.first }

before do
order.line_items << create(:line_item,
variant: order_cycle.variants_distributed_by(distributor).first)
order.contents.add(order_cycle.variants_distributed_by(distributor).first)

allow(controller).to receive(:current_distributor).and_return(distributor)
allow(controller).to receive(:current_order_cycle).and_return(order_cycle)
Expand Down Expand Up @@ -92,19 +91,54 @@
allow(OrderCycleDistributedVariants).to receive(:new).and_return(order_cycle_distributed_variants)
end

it "redirects when some items are out of stock" do
allow(order).to receive_message_chain(:insufficient_stock_lines, :empty?).and_return false
context "running out of stock" do
it "redirects when some items are out of stock" do
allow(order).to receive_message_chain(:insufficient_stock_lines, :empty?).and_return false

get :edit
expect(response).to redirect_to cart_path
end
get :edit
expect(response).to redirect_to cart_path
end

it "redirects when some items are not available" do
allow(order).to receive_message_chain(:insufficient_stock_lines, :empty?).and_return true
expect(order_cycle_distributed_variants).to receive(:distributes_order_variants?).with(order).and_return(false)

get :edit
expect(response).to redirect_to cart_path
end

context "after redirecting back from Stripe" do
let(:order) { create(:order_with_totals_and_distribution) }
let!(:payment) { create(:payment, state: "pending", amount: order.total, order: order) }
let!(:transaction_fee) {
create(:adjustment, state: "open", amount: 10, order: order, adjustable: payment)
}

it "redirects when some items are not available" do
allow(order).to receive_message_chain(:insufficient_stock_lines, :empty?).and_return true
expect(order_cycle_distributed_variants).to receive(:distributes_order_variants?).with(order).and_return(false)
before do
allow(order).to receive_message_chain(:insufficient_stock_lines, :empty?).and_return(false)
allow(order_cycle_distributed_variants).to receive(:distributes_order_variants?).
with(order).and_return(true)
allow(controller).to receive(:valid_payment_intent_provided?) { true }
order.save
allow(order).to receive_message_chain(:payments, :completed) { [] }
allow(order).to receive_message_chain(:payments, :incomplete) { [payment] }
allow(payment).to receive(:adjustment) { transaction_fee }
end

it "cancels the payment and resets the order to cart" do
expect(payment).to receive(:void!).and_call_original

spree_post :edit

expect(response).to redirect_to cart_path
expect(flash[:notice]).to eq I18n.t('checkout.payment_cancelled_due_to_stock')

get :edit
expect(response).to redirect_to cart_path
expect(order.state).to eq "cart"
expect(payment.state).to eq "void"
expect(transaction_fee.reload.eligible).to eq false
expect(transaction_fee.state).to eq "finalized"
end
end
end

describe "when items are available and in stock" do
Expand Down