From c67d47a773ea65eacc34ff1c64f14de1d17421ca Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Fri, 27 Feb 2026 15:07:58 +1100 Subject: [PATCH] Check if internal payment can be voided Add extra security, we don't want to void a credit payment that is not completed, otherwise we would be refunding credit that was not used. A credit payment should not be in a non completed state, but you never know. --- app/models/spree/payment/processing.rb | 3 +++ config/locales/en.yml | 1 + spec/models/spree/payment_spec.rb | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/app/models/spree/payment/processing.rb b/app/models/spree/payment/processing.rb index 3a2ec14f26..d389e99146 100644 --- a/app/models/spree/payment/processing.rb +++ b/app/models/spree/payment/processing.rb @@ -146,6 +146,9 @@ module Spree def internal_void! return true if void? + # We should only void complete payment, otherwise we will be refunding credit that was + # not used in the first place. + return gateway_error(Spree.t(:internal_payment_not_voidable)) if state != "completed" options = { customer_id: order.customer_id, payment_id: id, order_number: order.number } response = payment_method.void( diff --git a/config/locales/en.yml b/config/locales/en.yml index fa6a7d84a9..446c5b30a2 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -4196,6 +4196,7 @@ en: items_cannot_be_shipped: "Items cannot be shipped" gateway_config_unavailable: "Gateway config unavailable" gateway_error: "Payment failed" + internal_payment_not_voidable: Payment not voidable more: "More" new_adjustment: "New adjustment" new_tax_category: "New Tax Category" diff --git a/spec/models/spree/payment_spec.rb b/spec/models/spree/payment_spec.rb index f7b2bb1a3e..1665cad4c2 100644 --- a/spec/models/spree/payment_spec.rb +++ b/spec/models/spree/payment_spec.rb @@ -944,6 +944,13 @@ RSpec.describe Spree::Payment do end describe "internal_void!" do + let(:payment) do + payment = create(:payment, :completed) + payment.order = order + payment.payment_method = payment_method + payment + end + let(:order) { create(:order, customer:) } let(:customer) { create(:customer) } let(:payment_method) { @@ -1018,6 +1025,17 @@ RSpec.describe Spree::Payment do payment.internal_void! end end + + context "when payment not voidable" do + it "raises an error" do + payment.update(state: "pending") + expect(payment_method).not_to receive(:void) + + expect { + payment.internal_void! + }.to raise_error(Spree::Core::GatewayError, "Payment not voidable") + end + end end describe "applying transaction fees" do