Skip to content

Commit

Permalink
Add a default implementation for PaymentMethod#try_void
Browse files Browse the repository at this point in the history
We use to replicate this behavior on all the payment extensions
that add a payment integration. Still, the logic is pretty similar
all the times.

By adding this default behavior we can save some time integrating
new payment methods, unelss something special is needed.

The default try_void method added will just check if the void attempt
is successful, otherwise it will return false. This will determine if
Solidus will emit a refund along with an order cancellation.

The method is a bit complex because void can have different arity, based
on if the payment method support payment profiles or not. See [1] for
more information.

[1]: https://github.com/solidusio/solidus/blob/16d1e3704fbbe630a584c919de1a640a6ab6c6f8/core/app/models/spree/payment/processing.rb#L74-L81
  • Loading branch information
kennyadsl committed Jan 9, 2023
1 parent 16d1e37 commit efdc735
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 10 deletions.
27 changes: 17 additions & 10 deletions core/app/models/spree/payment_method.rb
Original file line number Diff line number Diff line change
Expand Up @@ -173,16 +173,23 @@ def supports?(_source)
# Return +false+ or +nil+ if the void is not possible anymore - because it was already processed by the bank.
# Solidus will refund the amount of the payment in this case.
#
# @return [ActiveMerchant::Billing::Response] with +true+ if the void succeeded
# @return [ActiveMerchant::Billing::Response] with +false+ if the void failed
# @return [false] if it can't be voided at this time
#
def try_void(_payment)
raise ::NotImplementedError,
"You need to implement `try_void` for #{self.class.name}. In that " \
'return a ActiveMerchant::Billing::Response object if the void succeeds '\
'or `false|nil` if the void is not possible anymore. ' \
'Solidus will refund the amount of the payment then.'
# This default implementation will void the payment if void succeed,
# otherwise it returns false.
#
# @api public
# @param payment [Spree::Payment] the payment to void
# @return [ActiveMerchant::Billing::Response|FalseClass]
def try_void(payment)
void_attempt = case gateway.method(:void).arity
when -2
void(nil, { originator: payment })
when -3
void(nil, nil, { originator: payment })
end

return void_attempt if void_attempt.success?

false
end

def store_credit?
Expand Down
75 changes: 75 additions & 0 deletions core/spec/models/spree/payment_method_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,81 @@ def gateway_class
end
end

describe "#try_void" do
let(:payment_method) { TestPaymentMethodWithoutTryVoid.new }
let(:payment) { create(:payment, payment_method: payment_method) }

class TestPaymentMethodWithoutTryVoid < Spree::PaymentMethod
# We are intentionally not defining try_void on this payment method.
# In this way the following specs will use the default implementation
# on Spree::PaymentMethod.

def gateway_class
PaymentGateway
end
end

context "when the payment gateway void has arity = -3" do
class PaymentGateway
def initialize(options)
end

def void(_response_code, _source, gateway_options = {})
payment = gateway_options[:originator]

if payment.completed?
ActiveMerchant::Billing::Response.new(false, "Can't void a completed payment", {}, test: true)
else
ActiveMerchant::Billing::Response.new(true, "Payment correctly voided", {}, test: true)
end
end
end

context "when the payment is already captured" do
it "returns false" do
allow(payment).to receive(:completed?).and_return true
expect(payment.payment_method.try_void(payment)).to be_falsey
end
end

context "when the payment is not yet captured" do
it "returns the success response" do
expect(payment.payment_method.try_void(payment)).to be_success
end
end
end

context "when the payment gateway void has arity = -2" do
class PaymentGateway
def initialize(options)
end

def void(_response_code, gateway_options = {})
payment = gateway_options[:originator]

if payment.completed?
ActiveMerchant::Billing::Response.new(false, "Can't void a completed payment", {}, test: true)
else
ActiveMerchant::Billing::Response.new(true, "Payment correctly voided", {}, test: true)
end
end
end

context "when the payment is already captured" do
it "returns false" do
allow(payment).to receive(:completed?).and_return true
expect(payment.payment_method.try_void(payment)).to be_falsey
end
end

context "when the payment is not yet captured" do
it "returns the success response" do
expect(payment.payment_method.try_void(payment)).to be_success
end
end
end
end

describe 'model_name.human' do
context 'PaymentMethod itself' do
it "returns i18n value" do
Expand Down

0 comments on commit efdc735

Please sign in to comment.