-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a default implementation for PaymentMethod#try_void #4843
Add a default implementation for PaymentMethod#try_void #4843
Conversation
efdc735
to
40eacea
Compare
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For consistency with other placed deciding which arity to use I would make it dependent on the result of payment_profiles_supported?
as in:
solidus/core/app/models/spree/payment/processing.rb
Lines 74 to 81 in 16d1e37
if payment_method.payment_profiles_supported? | |
# Gateways supporting payment profiles will need access to credit card object because this stores the payment profile information | |
# so supply the authorization itself as well as the credit card, rather than just the authorization code | |
response = payment_method.void(response_code, source, gateway_options) | |
else | |
# Standard ActiveMerchant void usage | |
response = payment_method.void(response_code, gateway_options) | |
end |
Not sure there's a way to abstract it or maybe stop having two signatures, but for the time being I would suggest we stick to the same check, so it will be easier to refactor in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, looks cleaner for sure, and I think we will notice tests failing on the try_void method if that implementation changes.
let(:payment) { create(:payment, payment_method: payment_method) } | ||
|
||
context "when the payment gateway void has arity = -3" do | ||
class PaymentGatewayVoidable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
caveat: the class
/ module
keywords will create a nesting scope related to the top-level, while the describe
blocks won't create one. This means that TestPaymentMethodWithoutTryVoid
, PaymentGateway
etc. are defined on the top-level (aka Object
).
A possible alternative is to use anonymous classes or a file-specific namespace in which to store these fixtures:
module SpreePaymentMethodSpec
class TestPaymentMethodWithoutTryVoid < Spree::PaymentMethod
attr_accessor :gateway_class, :payment_profiles_supported?
end
class PaymentGatewayWithoutSource
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
class PaymentGatewayWithSource
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
end
RSpec.describe Spree::PaymentMethod, type: :model do
# ...
describe "#try_void" do
let(:payment) { create(:payment, payment_method: payment_method) }
context "when the payment supports profiles" do
let(:payment_method) { SpreePaymentMethodSpec::TestPaymentMethodWithoutTryVoid.new(
gateway_class: SpreePaymentMethodSpec::PaymentGatewayWithSource,
payment_profiles_supported?: true
) }
# ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@elia done a first pass with anonymous classes. I added a preliminary commit to refactor the rest of the file, which was using the same approach I used (that's why I went that route). I have the feeling we are losing a bit of readability in specs, at least now it seems harder to imagine what real Payment Method/Gateway classes look like. I'm open to giving it another shot if that's too cryptic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's fine, any solution in this space has trade-offs and at least this isn't messing up with top-level constants 👍
to avoid polluting the global namespace, in fact those classes were defined on a global Object and avaiable in all the rest of the test suite after their first run. This will reduce the possibility of flacky specs.
40eacea
to
0896273
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice work
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. One thing worth mentioning: the self.name definition in the anonymous payment method classes in tests is needed for Rails 5.2. [1]: https://github.com/solidusio/solidus/blob/16d1e3704fbbe630a584c919de1a640a6ab6c6f8/core/app/models/spree/payment/processing.rb#L74-L81
0896273
to
a52c467
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Summary
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 payment processing for more information.
This PR comes from this conversation on PayPal Commerce Platform.
Checklist
Check out our PR guidelines for more details.
The following are mandatory for all PRs:
The following are not always needed (
cross them outif they are not):