From c031b0e52b6d6133c120cee5793f342a544fd0ee Mon Sep 17 00:00:00 2001 From: Rob Harrington Date: Fri, 6 Oct 2017 12:16:13 +1100 Subject: [PATCH] Clear shipments and payments after failed payment at checkout --- app/controllers/checkout_controller.rb | 13 ++++ app/models/spree/order_decorator.rb | 7 +- .../requests/checkout/failed_checkout_spec.rb | 70 +++++++++++++++++++ 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 spec/requests/checkout/failed_checkout_spec.rb diff --git a/app/controllers/checkout_controller.rb b/app/controllers/checkout_controller.rb index 62c91df4f4..817be578b0 100644 --- a/app/controllers/checkout_controller.rb +++ b/app/controllers/checkout_controller.rb @@ -15,6 +15,10 @@ class CheckoutController < Spree::CheckoutController include EnterprisesHelper 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 #update_failed method, then we can remove this call + restart_checkout end def update @@ -137,6 +141,7 @@ class CheckoutController < Spree::CheckoutController def update_failed clear_ship_address + restart_checkout respond_to do |format| format.html do render :edit @@ -155,6 +160,14 @@ class CheckoutController < Spree::CheckoutController end end + def restart_checkout + return if @order.state == 'cart' + @order.restart_checkout! # resets state to 'cart' + @order.shipments.with_state(:pending).destroy_all + @order.payments.with_state(:checkout).destroy_all + @order.reload + end + def skip_state_validation? true end diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb index 0166fc10c1..31c14c0225 100644 --- a/app/models/spree/order_decorator.rb +++ b/app/models/spree/order_decorator.rb @@ -376,11 +376,14 @@ Spree::Order.class_eval do # object_params sets the payment amount to the order total, but it does this before # the shipping method is set. This results in the customer not being charged for their # order's shipping. To fix this, we refresh the payment amount here. - def charge_shipping! + def charge_shipping_and_payment_fees! update_totals return unless payments.any? payments.first.update_attribute :amount, total end end -Spree::Order.state_machine.after_transition to: :payment, do: :charge_shipping! +Spree::Order.state_machine.after_transition to: :payment, do: :charge_shipping_and_payment_fees! +Spree::Order.state_machine.event :restart_checkout do + transition :to => :cart, unless: :completed? +end diff --git a/spec/requests/checkout/failed_checkout_spec.rb b/spec/requests/checkout/failed_checkout_spec.rb new file mode 100644 index 0000000000..6246beadf0 --- /dev/null +++ b/spec/requests/checkout/failed_checkout_spec.rb @@ -0,0 +1,70 @@ +require 'spec_helper' + +describe "checking out an order that initially fails", type: :request do + include ShopWorkflow + + let!(:shop) { create(:enterprise) } + let!(:order_cycle) { create(:simple_order_cycle) } + let!(:exchange) { create(:exchange, order_cycle: order_cycle, sender: order_cycle.coordinator, receiver: shop, incoming: false, pickup_time: "Monday") } + let!(:address) { create(:address) } + let!(:order) { create(:order, distributor: shop, order_cycle: order_cycle) } + let!(:line_item) { create(:line_item, order: order, quantity: 3, price: 5.00) } + let!(:payment_method) { create(:bogus_payment_method, distributor_ids: [shop.id], environment: Rails.env) } + let!(:check_payment_method) { create(:payment_method, distributor_ids: [shop.id], environment: Rails.env) } + let!(:shipping_method) { create(:shipping_method, distributor_ids: [shop.id]) } + let!(:shipment) { create(:shipment, order: order, shipping_method: shipping_method) } + let(:params) do + { format: :json, order: { + shipping_method_id: shipping_method.id, + payments_attributes: [{payment_method_id: payment_method.id}], + bill_address_attributes: address.attributes.slice("firstname", "lastname", "address1", "address2", "phone", "city", "zipcode", "state_id", "country_id"), + ship_address_attributes: address.attributes.slice("firstname", "lastname", "address1", "address2", "phone", "city", "zipcode", "state_id", "country_id") + } } + end + + before do + order.reload.update_totals + set_order order + end + + context "when shipping and payment fees apply" do + let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) } + + before do + payment_method.calculator = calculator.dup + payment_method.save! + check_payment_method.calculator = calculator.dup + check_payment_method.save! + shipping_method.calculator = calculator.dup + shipping_method.save! + end + + it "clears shipments and payments before rendering the checkout" do + put update_checkout_path, params + + # Checking out a BogusGateway without a source fails at :payment + # Shipments and payments should then be cleared before rendering checkout + expect(response.status).to be 400 + expect(flash[:error]).to eq I18n.t(:payment_processing_failed) + order.reload + expect(order.shipments.count).to be 0 + expect(order.payments.count).to be 0 + expect(order.adjustment_total).to eq 0 + + # Add another line item to change the fee totals + create(:line_item, order: order, quantity: 3, price: 5.00) + + # Use a check payment method, which should work + params[:order][:payments_attributes][0][:payment_method_id] = check_payment_method.id + put update_checkout_path, params + + expect(response.status).to be 200 + order.reload + expect(order.total).to eq 36 + expect(order.adjustment_total).to eq 6 + expect(order.item_total).to eq 30 + expect(order.shipments.count).to eq 1 + expect(order.payments.count).to eq 1 + end + end +end