Skip to content

Commit

Permalink
Set transaction fee adjustments to ineligible if payment is invalid o…
Browse files Browse the repository at this point in the history
…r failed
  • Loading branch information
oeoeaio committed Sep 23, 2017
1 parent dcf27e8 commit 544a847
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 20 deletions.
23 changes: 17 additions & 6 deletions app/models/spree/payment_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ module Spree

def ensure_correct_adjustment
destroy_orphaned_paypal_payments if payment_method.is_a?(Spree::Gateway::PayPalExpress)
# Don't charge for invalid payments.
# PayPalExpress always creates a payment that is invalidated later.
# Unknown: What about failed payments?
if state == "invalid"
adjustment.andand.destroy
elsif adjustment

revoke_adjustment_eligibility if ['failed', 'invalid'].include?(state)

return if adjustment.try(:finalized?)

if adjustment
adjustment.originator = payment_method
adjustment.label = adjustment_label
adjustment.save
Expand Down Expand Up @@ -117,5 +117,16 @@ def destroy_orphaned_paypal_payments
orphaned_payments = order.payments.where(payment_method_id: payment_method_id, source_id: nil)
orphaned_payments.each(&:destroy)
end

# Don't charge fees for invalid or failed payments.
# This is called twice for failed payments, because the persistence of the 'failed'
# state is acheived through some trickery using an after_rollback callback on the
# payment model. See Spree::Payment#persist_invalid
def revoke_adjustment_eligibility
return unless adjustment.reload
return if adjustment.finalized?
adjustment.update_attribute(:eligible, false)
adjustment.finalize!
end
end
end
102 changes: 88 additions & 14 deletions spec/models/spree/payment_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -129,23 +129,23 @@ module Spree
let!(:order) { create(:order) }
let!(:line_item) { create(:line_item, order: order, quantity: 3, price: 5.00) }

# Mimicing call from PayPalController#confirm in spree_paypal_express
def create_new_paypal_express_payment(order, payment_method)
order.payments.create!({
:source => Spree::PaypalExpressCheckout.create({
:token => "123",
:payer_id => "456"
}, :without_protection => true),
:amount => order.total,
:payment_method => payment_method
}, :without_protection => true)
end

before do
order.update_totals
order.reload.update!
end

context "for paypal express payments with transaction fees" do
context "to paypal express payments" do
# Mimicing call from PayPalController#confirm in spree_paypal_express
def create_new_paypal_express_payment(order, payment_method)
order.payments.create!({
:source => Spree::PaypalExpressCheckout.create({
:token => "123",
:payer_id => "456"
}, :without_protection => true),
:amount => order.total,
:payment_method => payment_method
}, :without_protection => true)
end

let!(:payment_method) { Spree::Gateway::PayPalExpress.create!(name: "PayPalExpress", distributor_ids: [create(:distributor_enterprise).id], environment: Rails.env) }
let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) }

Expand Down Expand Up @@ -211,6 +211,80 @@ def create_new_paypal_express_payment(order, payment_method)
end
end
end

context "to Stripe payments" do
let(:shop) { create(:enterprise) }
let!(:payment_method) { create(:stripe_payment_method, distributor_ids: [create(:distributor_enterprise).id], preferred_enterprise_id: shop.id) }
let!(:payment) { create(:payment, order: order, payment_method: payment_method, amount: order.total) }
let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) }

before do
payment_method.calculator = calculator
payment_method.save!

allow(order).to receive(:pending_payments) { [payment] }
end

context "when the payment fails" do
let(:failed_response) { ActiveMerchant::Billing::Response.new(false, "This is an error message") }

before do
allow(payment_method).to receive(:purchase) { failed_response }
end

it "makes the transaction fee ineligible and finalizes it" do
# Decided to wrap the save process in order.process_payments!
# since that is the context it is usually performed in
order.process_payments!
expect(order.payments.count).to eq 1
expect(order.payments).to include payment
expect(payment.state).to eq "failed"
expect(payment.adjustment.eligible?).to be false
expect(payment.adjustment.finalized?).to be true
expect(order.adjustments.payment_fee.count).to eq 1
expect(order.adjustments.payment_fee.eligible).to_not include payment.adjustment
end
end

context "when the payment information is invalid" do
before do
allow(payment_method).to receive(:supports?) { false }
end

it "makes the transaction fee ineligible and finalizes it" do
# Decided to wrap the save process in order.process_payments!
# since that is the context it is usually performed in
order.process_payments!
expect(order.payments.count).to eq 1
expect(order.payments).to include payment
expect(payment.state).to eq "invalid"
expect(payment.adjustment.eligible?).to be false
expect(payment.adjustment.finalized?).to be true
expect(order.adjustments.payment_fee.count).to eq 1
expect(order.adjustments.payment_fee.eligible).to_not include payment.adjustment
end
end

context "when the payment is processed successfully" do
let(:successful_response) { ActiveMerchant::Billing::Response.new(true, "Yay!") }

before do
allow(payment_method).to receive(:purchase) { successful_response }
end

it "creates " do
# Decided to wrap the save process in order.process_payments!
# since that is the context it is usually performed in
order.process_payments!
expect(order.payments.count).to eq 1
expect(order.payments).to include payment
expect(payment.state).to eq "completed"
expect(payment.adjustment.eligible?).to be true
expect(order.adjustments.payment_fee.count).to eq 1
expect(order.adjustments.payment_fee.eligible).to include payment.adjustment
end
end
end
end
end
end

0 comments on commit 544a847

Please sign in to comment.