From a82f7ced44108878ad587f5bae0206632f7b5c93 Mon Sep 17 00:00:00 2001
From: Szymon Iwacz <szymon@iwacz.pl>
Date: Thu, 1 Oct 2020 16:21:50 +0200
Subject: [PATCH] Fix order outstanding balance when store credit is without
 auto capture (#10516)

* Fix order outstanding_balance when store credit is without auto capture

* rubocop fix

* Do not use .map{ _1 } syntax
---
 core/app/models/spree/order/payments.rb      | 10 +++-
 core/spec/models/spree/order/payment_spec.rb | 60 ++++++++++++++++++++
 2 files changed, 69 insertions(+), 1 deletion(-)

diff --git a/core/app/models/spree/order/payments.rb b/core/app/models/spree/order/payments.rb
index ccabe5a94ef..2420af9bd10 100644
--- a/core/app/models/spree/order/payments.rb
+++ b/core/app/models/spree/order/payments.rb
@@ -52,7 +52,7 @@ def process_payments_with(method)
 
             payment.public_send(method)
 
-            if payment.completed? && payment_total != total
+            if payment.completed? && payment_total != total_without_pending_store_credits
               self.payment_total += payment.amount
             end
           end
@@ -60,6 +60,14 @@ def process_payments_with(method)
           result = !!Spree::Config[:allow_checkout_on_gateway_error]
           errors.add(:base, e.message) && (return result)
         end
+
+        # Pending store credits are not added to `self.payment_total`.
+        # It can cause a situation where the amount of the credit card payment reduced with store credits
+        # may be added twice to `self.payment_total` causing wrong `order.outstanding_balance`
+        # calculations and thus an incorrect payment state.
+        def total_without_pending_store_credits
+          total - payments.map { |p| p.amount if p.source.is_a?(Spree::StoreCredit) && p.pending? }.sum(&:to_f)
+        end
       end
     end
   end
diff --git a/core/spec/models/spree/order/payment_spec.rb b/core/spec/models/spree/order/payment_spec.rb
index d00c2f6c8b2..3915760938f 100644
--- a/core/spec/models/spree/order/payment_spec.rb
+++ b/core/spec/models/spree/order/payment_spec.rb
@@ -27,6 +27,66 @@ module Spree
         expect(payment_2).to be_completed
       end
 
+      context 'processes all checkout payments along with store credits' do
+        context 'with store credits payment method auto capture turned on' do
+          it 'order should be paid' do
+            store_credit = create(:store_credit_payment, amount: 50)
+            payment = create(:payment, amount: 50)
+
+            expect(order).to receive(:unprocessed_payments).and_return([store_credit, payment]).at_least(:once)
+
+            order.process_payments!
+            updater.update_payment_state
+            expect(order.payment_state).to eq('paid')
+
+            expect(payment).to be_completed
+            expect(store_credit).to be_completed
+          end
+        end
+
+        context 'with store credits payment method auto capture turned off' do
+          let!(:payment) { create(:payment, amount: payment_amount) }
+          let!(:store_credit_payment) do
+            create(
+              :store_credit_payment,
+              amount: store_credit_amount,
+              payment_method: create(:store_credit_payment_method, auto_capture: false)
+            )
+          end
+
+          before do
+            payments = [store_credit_payment, payment]
+            expect(order).to receive(:payments).and_return(payments).at_least(:once)
+            expect(order.payments).to receive(:valid).and_return(payments)
+
+            order.process_payments!
+            updater.update_payment_state
+            expect(payment).to be_completed
+            expect(store_credit_payment).to be_pending
+          end
+
+          context 'order payment state should be balance due' do
+            let!(:payment_amount) { 70.00 }
+            let!(:store_credit_amount) { 30.00 }
+
+            it do
+              expect(order.payment_state).to eq('balance_due')
+              expect(order.outstanding_balance).to eq(30.00)
+            end
+          end
+
+          context 'order payment state should be balance due' do
+            let!(:payment_amount) { 90.00 }
+            let!(:store_credit_amount) { 10.00 }
+
+            it do
+              expect(order.payment_state).to eq('balance_due')
+              expect(order.outstanding_balance).to eq(10.00)
+            end
+          end
+        end
+      end
+
       it 'does not go over total for order' do
         payment_1 = create(:payment, amount: 50)
         payment_2 = create(:payment, amount: 50)