From b498c2863292dbbb57818355f836b67c69bc1335 Mon Sep 17 00:00:00 2001 From: Rohan Mitchell Date: Thu, 30 Apr 2015 15:38:01 +1000 Subject: [PATCH] Payments can be refunded --- app/models/spree/payment_decorator.rb | 37 +++++++++++++ spec/models/spree/payment_spec.rb | 80 +++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/app/models/spree/payment_decorator.rb b/app/models/spree/payment_decorator.rb index 5095ae57e9..1a80f0269f 100644 --- a/app/models/spree/payment_decorator.rb +++ b/app/models/spree/payment_decorator.rb @@ -11,5 +11,42 @@ module Spree actions end alias_method_chain :actions, :pin_payment_adaptations + + + def refund!(refund_amount=nil) + protect_from_connection_error do + check_environment + + refund_amount = calculate_refund_amount(refund_amount) + + if payment_method.payment_profiles_supported? + response = payment_method.refund((refund_amount * 100).round, source, response_code, gateway_options) + else + response = payment_method.refund((refund_amount * 100).round, response_code, gateway_options) + end + + record_response(response) + + if response.success? + self.class.create({ :order => order, + :source => self, + :payment_method => payment_method, + :amount => refund_amount.abs * -1, + :response_code => response.authorization, + :state => 'completed' }, :without_protection => true) + else + gateway_error(response) + end + end + end + + + private + + def calculate_refund_amount(refund_amount=nil) + refund_amount ||= credit_allowed >= order.outstanding_balance.abs ? order.outstanding_balance.abs : credit_allowed.abs + refund_amount.to_f + end + end end diff --git a/spec/models/spree/payment_spec.rb b/spec/models/spree/payment_spec.rb index 04cf7b4c6f..8a781e7700 100644 --- a/spec/models/spree/payment_spec.rb +++ b/spec/models/spree/payment_spec.rb @@ -44,5 +44,85 @@ module Spree end end end + + describe "refunding" do + let(:payment) { create(:payment) } + let(:success) { double(:success? => true, authorization: 'abc123') } + let(:failure) { double(:success? => false) } + + it "always checks the environment" do + payment.payment_method.stub(:refund) { success } + payment.should_receive(:check_environment) + payment.refund! + end + + describe "calculating refund amount" do + it "returns the parameter amount when given" do + payment.send(:calculate_refund_amount, 123).should === 123.0 + end + + it "refunds up to the value of the payment when the outstanding balance is larger" do + payment.stub(:credit_allowed) { 123 } + payment.stub(:order) { double(:order, outstanding_balance: 1000) } + payment.send(:calculate_refund_amount).should == 123 + end + + it "refunds up to the outstanding balance of the order when the payment is larger" do + payment.stub(:credit_allowed) { 1000 } + payment.stub(:order) { double(:order, outstanding_balance: 123) } + payment.send(:calculate_refund_amount).should == 123 + end + end + + describe "performing refunds" do + before do + payment.stub(:calculate_refund_amount) { 123 } + payment.payment_method.should_receive(:refund).and_return(success) + end + + it "performs the refund without payment profiles" do + payment.payment_method.stub(:payment_profiles_supported?) { false } + payment.refund! + end + + it "performs the refund with payment profiles" do + payment.payment_method.stub(:payment_profiles_supported?) { true } + payment.refund! + end + end + + it "records the response" do + payment.stub(:calculate_refund_amount) { 123 } + payment.payment_method.stub(:refund).and_return(success) + payment.should_receive(:record_response).with(success) + payment.refund! + end + + it "records a payment on success" do + payment.stub(:calculate_refund_amount) { 123 } + payment.payment_method.stub(:refund).and_return(success) + payment.stub(:record_response) + + expect do + payment.refund! + end.to change(Payment, :count).by(1) + + p = Payment.last + p.order.should == payment.order + p.source.should == payment + p.payment_method.should == payment.payment_method + p.amount.should == -123 + p.response_code.should == success.authorization + p.state.should == 'completed' + end + + it "logs the error on failure" do + payment.stub(:calculate_refund_amount) { 123 } + payment.payment_method.stub(:refund).and_return(failure) + payment.stub(:record_response) + payment.should_receive(:gateway_error).with(failure) + payment.refund! + end + end end end