diff --git a/core/app/models/spree/payment.rb b/core/app/models/spree/payment.rb index c4eaa2bdecf..114e5bb0503 100644 --- a/core/app/models/spree/payment.rb +++ b/core/app/models/spree/payment.rb @@ -140,6 +140,20 @@ def credit_allowed amount - (offsets_total.abs + refunds.sum(:amount)) end + # The total amount this payment can be credited in cents. + # + # For currencies which do not support decimal places, it will return the + # full value. + # + # @return [Fixnum] The amount of this payment minus offets and refunds + # in cents. + def credit_allowed_in_cents + Spree::Money.new( + credit_allowed, + currency: currency + ).money.cents + end + # @return [Boolean] true when this payment can be credited def can_credit? credit_allowed > 0 diff --git a/core/app/models/spree/payment_method/store_credit.rb b/core/app/models/spree/payment_method/store_credit.rb index 469b5596474..800c5742770 100644 --- a/core/app/models/spree/payment_method/store_credit.rb +++ b/core/app/models/spree/payment_method/store_credit.rb @@ -67,7 +67,16 @@ def credit(amount_in_cents, auth_code, gateway_options) currency = gateway_options[:currency] || store_credit.currency originator = gateway_options[:originator] - store_credit.credit(amount_in_cents / 100.0.to_d, auth_code, currency, action_originator: originator) + if amount_in_cents > 0 + store_credit.credit( + ::Money.new(amount_in_cents, currency).to_d, + auth_code, + currency, + action_originator: originator + ) + else + ActiveMerchant::Billing::Response.new(true, '') + end end handle_action(action, :credit, auth_code) @@ -78,13 +87,18 @@ def cancel(auth_code) store_credit = store_credit_event.try(:store_credit) if store_credit_event.nil? || store_credit.nil? - return false + ActiveMerchant::Billing::Response.new(false, '', {}, {}) elsif store_credit_event.capture_action? - store_credit.credit(store_credit_event.amount, auth_code, store_credit.currency) + amount = if store_credit_event.originator_type == 'Spree::Payment' + store_credit_event.originator.credit_allowed_in_cents + else + (store_credit_event.amount * 100).round + end + credit(amount, auth_code, { originator: store_credit_event.originator }) elsif store_credit_event.authorization_action? store_credit.void(auth_code) else - return false + ActiveMerchant::Billing::Response.new(false, '', {}, {}) end end diff --git a/core/spec/models/spree/payment_method/store_credit_spec.rb b/core/spec/models/spree/payment_method/store_credit_spec.rb index 8f8d0247e43..50531c8f961 100644 --- a/core/spec/models/spree/payment_method/store_credit_spec.rb +++ b/core/spec/models/spree/payment_method/store_credit_spec.rb @@ -218,6 +218,19 @@ end end + context "when attempting to credit for zero" do + let(:credit_amount) { 0 } + + it "does not credit the store credit" do + expect_any_instance_of(Spree::StoreCredit).to_not receive(:credit) + subject + end + + it "returns a success response" do + expect(subject.success?).to be true + end + end + context "when the store credit isn't credited successfully" do before { allow_any_instance_of(Spree::StoreCredit).to receive_messages(credit: false) } @@ -255,15 +268,41 @@ let(:auth_code) { "1-SC-20141111111111" } let(:captured_amount) { 10.0 } + shared_examples "a spree payment method" do + it "returns an ActiveMerchant::Billing::Response" do + expect(subject).to be_instance_of(ActiveMerchant::Billing::Response) + end + end + context "capture event found" do - let!(:store_credit_event) { create(:store_credit_capture_event, - authorization_code: auth_code, - amount: captured_amount, - store_credit: store_credit) } + let!(:store_credit_event) do + create( + :store_credit_capture_event, + authorization_code: auth_code, + amount: captured_amount, + store_credit: store_credit, + originator: originator + ) + end + let(:payment) { create(:payment, order: order, amount: 5) } + let(:originator) { nil } - it "creates a store credit for the same amount that was captured" do - expect_any_instance_of(Spree::StoreCredit).to receive(:credit).with(captured_amount, auth_code, store_credit.currency) - subject + it_behaves_like "a spree payment method" + + context "when originator is nil" do + it "refunds the event amount" do + expect { subject }.to change{ store_credit.reload.amount_remaining }. + from(140).to(150) + end + end + + context "when the originator is the payment" do + let(:originator) { payment } + + it "refunds the payment credit allowed amount" do + expect { subject }.to change{ store_credit.reload.amount_remaining }. + from(140).to(145) + end end end @@ -285,9 +324,9 @@ Spree::PaymentMethod::StoreCredit.new.cancel('INVALID') end - it "returns false" do - expect(subject).to be false - end + it_behaves_like "a spree payment method" + + it { expect(subject.success?).to be(false) } end end end diff --git a/core/spec/models/spree/payment_spec.rb b/core/spec/models/spree/payment_spec.rb index 0cb7e958abd..c82572a156b 100644 --- a/core/spec/models/spree/payment_spec.rb +++ b/core/spec/models/spree/payment_spec.rb @@ -617,6 +617,53 @@ end end + describe "#credit_allowed_in_cents" do + subject { payment.credit_allowed_in_cents } + + let(:currency) { 'USD' } + let(:order) { create :order, currency: currency } + let(:payment) do + create :payment, amount: 100, order: order, state: 'completed' + end + + context "when there are no offsets" do + context "when the currency is USD" do + it "it returns the payment amount in cents" do + expect(subject).to eq(10000) + end + end + + context "when the currency is JPY" do + let(:currency) { 'JPY' } + it "it returns the total amount" do + expect(subject).to eq(100) + end + end + end + + context "when there are offsets" do + before do + payment.offsets.create!( + amount: -80, + order: order, + source: payment, + state: 'completed' + ) + end + + it "is the difference between offsets total and payment amount in cents" do + expect(subject).to eq(2000) + end + + context "when the currency is JPY" do + let(:currency) { 'JPY' } + it "it returns the total amount minus offsets" do + expect(subject).to eq(20) + end + end + end + end + describe "#can_credit?" do it "is true if credit_allowed > 0" do allow(payment).to receive(:credit_allowed).and_return(100)