Move payment action logic to payment

At closer inspection, almost all logic around which payment actions to
display involves only the state of the payment. So I moved the logic
there.

We now have one list of all possible actions supported by the UX. Then
payment methods can declare a list of supported actions. If that's
conditional, they can implement the conditions themselves. The payment
model itself then still filters the actions based on its state.
This commit is contained in:
Maikel Linke
2026-02-17 16:16:31 +11:00
parent 956c4a27c2
commit fdd22bc097
6 changed files with 82 additions and 91 deletions

View File

@@ -17,31 +17,6 @@ module Spree
%w{capture_and_complete_order void credit resend_authorization_email}
end
# Indicates whether its possible to capture the payment
def can_capture_and_complete_order?(payment)
return false if payment.requires_authorization?
payment.pending? || payment.checkout?
end
# Indicates whether its possible to void the payment.
def can_void?(payment)
!payment.void?
end
# Indicates whether its possible to credit the payment. Note that most gateways require that the
# payment be settled first which generally happens within 12-24 hours of the transaction.
def can_credit?(payment)
return false unless payment.completed?
return false unless payment.order.payment_state == 'credit_owed'
payment.credit_allowed.positive?
end
def can_resend_authorization_email?(payment)
payment.requires_authorization?
end
def payment_source_class
CreditCard
end

View File

@@ -11,6 +11,13 @@ module Spree
localize_number :amount
# All possible actions offered to shop owners.
ACTIONS = %w[
capture_and_complete_order
void
credit
resend_authorization_email
].freeze
IDENTIFIER_CHARS = (('A'..'Z').to_a + ('0'..'9').to_a - %w(0 1 I O)).freeze
delegate :line_items, to: :order
@@ -152,11 +159,33 @@ module Spree
end
def actions
return [] unless payment_method.respond_to?(:actions)
supported = ACTIONS & payment_method.actions.to_a
supported.select { public_send("can_#{it}?") }
end
payment_method.actions.select do |action|
payment_method.__send__("can_#{action}?", self)
end
# Indicates whether its possible to capture the payment
def can_capture_and_complete_order?
return false if requires_authorization?
pending? || checkout?
end
# Indicates whether its possible to void the payment.
def can_void?
!void?
end
# Indicates whether its possible to credit the payment. Note that most gateways require that the
# payment be settled first which generally happens within 12-24 hours of the transaction.
def can_credit?
return false unless completed?
return false unless order.payment_state == "credit_owed"
credit_allowed.positive?
end
def can_resend_authorization_email?
requires_authorization?
end
def resend_authorization_email!

View File

@@ -7,16 +7,6 @@ module Spree
%w{capture_and_complete_order void}
end
# Indicates whether its possible to capture the payment
def can_capture_and_complete_order?(payment)
['checkout', 'pending'].include?(payment.state)
end
# Indicates whether its possible to void the payment.
def can_void?(payment)
payment.state != 'void'
end
def capture(*_args)
ActiveMerchant::Billing::Response.new(true, "", {}, {})
end

View File

@@ -25,10 +25,6 @@ module Spree
%w{void}
end
def can_void?(payment)
payment.state == "completed"
end
# Name of the view to display during checkout
def method_type
"check" # empty view

View File

@@ -23,51 +23,4 @@ RSpec.describe Spree::Gateway do
it "raises an error in test env" do
expect { gateway.imaginary_method('foo') }.to raise_error StandardError
end
describe "#can_capture?" do
it "should be true if payment is pending" do
payment = build_stubbed(:payment, created_at: Time.zone.now)
allow(payment).to receive(:pending?) { true }
expect(gateway.can_capture_and_complete_order?(payment)).to be_truthy
end
it "should be true if payment is checkout" do
payment = build_stubbed(:payment, created_at: Time.zone.now)
allow(payment).to receive_messages pending?: false,
checkout?: true
expect(gateway.can_capture_and_complete_order?(payment)).to be_truthy
end
end
describe "#can_void?" do
it "should be true if payment is not void" do
payment = build_stubbed(:payment)
allow(payment).to receive(:void?) { false }
expect(gateway.can_void?(payment)).to be_truthy
end
end
describe "#can_credit?" do
it "should be false if payment is not completed" do
payment = build_stubbed(:payment)
allow(payment).to receive(:completed?) { false }
expect(gateway.can_credit?(payment)).to be_falsy
end
it "should be false when order payment_state is not 'credit_owed'" do
payment = build_stubbed(:payment,
order: create(:order, payment_state: 'paid'))
allow(payment).to receive(:completed?) { true }
expect(gateway.can_credit?(payment)).to be_falsy
end
it "should be false when credit_allowed is zero" do
payment = build_stubbed(:payment,
order: create(:order, payment_state: 'credit_owed'))
allow(payment).to receive_messages completed?: true,
credit_allowed: 0
expect(gateway.can_credit?(payment)).to be_falsy
end
end
end

View File

@@ -855,7 +855,8 @@ RSpec.describe Spree::Payment do
describe "available actions" do
context "for most gateways" do
let(:payment) { build_stubbed(:payment, source: build_stubbed(:credit_card)) }
let(:payment) { build_stubbed(:payment, payment_method:) }
let(:payment_method) { Spree::Gateway::StripeSCA.new }
it "can capture and void" do
expect(payment.actions).to match_array %w(capture_and_complete_order void)
@@ -872,6 +873,53 @@ RSpec.describe Spree::Payment do
end
end
end
describe "#can_capture_and_complete_order?" do
it "should be true if payment is pending" do
payment = build_stubbed(:payment, created_at: Time.zone.now)
allow(payment).to receive(:pending?) { true }
expect(payment.can_capture_and_complete_order?).to be_truthy
end
it "should be true if payment is checkout" do
payment = build_stubbed(:payment, created_at: Time.zone.now)
allow(payment).to receive_messages pending?: false,
checkout?: true
expect(payment.can_capture_and_complete_order?).to be_truthy
end
end
describe "#can_void?" do
it "should be true if payment is not void" do
payment = build_stubbed(:payment)
allow(payment).to receive(:void?) { false }
expect(payment.can_void?).to be_truthy
end
end
describe "#can_credit?" do
it "should be false if payment is not completed" do
payment = build_stubbed(:payment)
allow(payment).to receive(:completed?) { false }
expect(payment.can_credit?).to be_falsy
end
it "should be false when order payment_state is not 'credit_owed'" do
payment = build_stubbed(:payment,
order: create(:order, payment_state: 'paid'))
allow(payment).to receive(:completed?) { true }
expect(payment.can_credit?).to be_falsy
end
it "should be false when credit_allowed is zero" do
payment = build_stubbed(:payment,
order: create(:order, payment_state: 'credit_owed'))
allow(payment).to receive_messages completed?: true,
credit_allowed: 0
expect(payment.can_credit?).to be_falsy
end
end
end
describe "refund!" do