Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
ef56df09a1 Bump webmock from 3.26.1 to 3.26.2
Bumps [webmock](https://github.com/bblimke/webmock) from 3.26.1 to 3.26.2.
- [Release notes](https://github.com/bblimke/webmock/releases)
- [Changelog](https://github.com/bblimke/webmock/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bblimke/webmock/compare/v3.26.1...v3.26.2)

---
updated-dependencies:
- dependency-name: webmock
  dependency-version: 3.26.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-25 09:38:33 +00:00
19 changed files with 115 additions and 274 deletions

View File

@@ -959,7 +959,7 @@ GEM
activesupport
faraday (~> 2.0)
faraday-follow_redirects
webmock (3.26.1)
webmock (3.26.2)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)

View File

@@ -52,7 +52,7 @@ module Spree
# (we can't use respond_override because Spree no longer uses respond_with)
def fire
event = params[:e]
return unless event
return unless event && @payment.payment_source
# capture_and_complete_order will complete the order, so we want to try to redeem VINE
# voucher first and exit if it fails

View File

@@ -22,10 +22,10 @@ class PaymentMailer < ApplicationMailer
end
end
def refund_available(amount, payment, taler_order_status_url)
def refund_available(payment, taler_order_status_url)
@order = payment.order
@shop = @order.distributor.name
@amount = amount
@amount = payment.display_amount
@taler_order_status_url = taler_order_status_url
I18n.with_locale valid_locale(@order.user) do

View File

@@ -197,10 +197,6 @@ module Spree
can [:admin, :index, :destroy], :oidc_setting
can [:admin, :create], Voucher
can [:admin, :destroy], EnterpriseRole do |enterprise_role|
enterprise_role.enterprise.owner_id == user.id
end
end
def add_product_management_abilities(user)

View File

@@ -63,6 +63,35 @@ module Spree
"XXXX-XXXX-XXXX-#{last_digits}"
end
def actions
%w{capture_and_complete_order void credit resend_authorization_email}
end
def can_resend_authorization_email?(payment)
payment.requires_authorization?
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
# Allows us to use a gateway_payment_profile_id to store Stripe Tokens
def has_payment_profile?
gateway_customer_profile_id.present? || gateway_payment_profile_id.present?

View File

@@ -13,35 +13,6 @@ module Spree
preference :server, :string, default: 'live'
preference :test_mode, :boolean, default: false
def actions
%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

@@ -152,10 +152,11 @@ module Spree
end
def actions
return [] unless payment_method.respond_to?(:actions)
return [] unless payment_source.respond_to?(:actions)
payment_method.actions.select do |action|
payment_method.__send__("can_#{action}?", self)
payment_source.actions.select do |action|
!payment_source.respond_to?("can_#{action}?") ||
payment_source.__send__("can_#{action}?", self)
end
end
@@ -165,6 +166,11 @@ module Spree
PaymentMailer.authorize_payment(self).deliver_later
end
def payment_source
res = source.is_a?(Payment) ? source.source : source
res || payment_method
end
def ensure_correct_adjustment
revoke_adjustment_eligibility if ['failed', 'invalid', 'void'].include?(state)
return if adjustment.try(:finalized?)

View File

@@ -18,27 +18,15 @@ module Spree
# - backend_url: https://backend.demo.taler.net/instances/sandbox
# - api_key: sandbox
class Taler < PaymentMethod
# Demo backend instances will use the KUDOS currency.
DEMO_PREFIX = "https://backend.demo.taler.net/instances"
preference :backend_url, :string
preference :api_key, :password
def actions
%w[credit void]
%w{void}
end
def can_void?(payment)
# The source can be another payment. Then this is an offset payment
# like a credit record. We can't void a refund.
payment.source == self && payment.state == "completed"
end
def can_credit?(payment)
return false unless payment.completed?
return false unless payment.order.payment_state == 'credit_owed'
payment.credit_allowed.positive?
payment.state == "completed"
end
# Name of the view to display during checkout
@@ -80,23 +68,6 @@ module Spree
ActiveMerchant::Billing::Response.new(success, message)
end
def credit(money, response_code, gateway_options)
amount = money / 100 # called with cents
payment = gateway_options[:payment]
taler_order = taler_order(id: response_code)
status = taler_order.fetch("order_status")
raise "Unsupported action" if status != "paid"
taler_amount = "KUDOS:#{amount}"
taler_order.refund(refund: taler_amount, reason: "credit")
spree_money = Spree::Money.new(amount, currency: payment.currency).to_s
PaymentMailer.refund_available(spree_money, payment, taler_order.status_url).deliver_later
ActiveMerchant::Billing::Response.new(true, "Refund initiated")
end
def void(response_code, gateway_options)
payment = gateway_options[:payment]
taler_order = taler_order(id: response_code)
@@ -111,8 +82,7 @@ module Spree
amount = taler_order.fetch("contract_terms")["amount"]
taler_order.refund(refund: amount, reason: "void")
spree_money = payment.money.to_s
PaymentMailer.refund_available(spree_money, payment, taler_order.status_url).deliver_later
PaymentMailer.refund_available(payment, taler_order.status_url).deliver_later
ActiveMerchant::Billing::Response.new(true, "Refund initiated")
end
@@ -126,7 +96,7 @@ module Spree
def create_taler_order(payment)
# We are ignoring currency for now so that we can test with the
# current demo backend only working with the KUDOS currency.
taler_amount = "#{currency(payment)}:#{payment.amount}"
taler_amount = "KUDOS:#{payment.amount}"
urls = Rails.application.routes.url_helpers
fulfillment_url = urls.payment_gateways_confirm_taler_url(payment_id: payment.id)
taler_order.create(
@@ -143,12 +113,6 @@ module Spree
id:,
)
end
def currency(payment)
return "KUDOS" if preferred_backend_url.starts_with?(DEMO_PREFIX)
payment.order.currency
end
end
end
end

View File

@@ -14,6 +14,7 @@ module Checkout
apply_strong_parameters
set_pickup_address
set_address_details
set_payment_amount
set_existing_card
@order_params
@@ -57,6 +58,12 @@ module Checkout
end
end
def set_payment_amount
return unless @order_params[:payments_attributes]
@order_params[:payments_attributes].first[:amount] = order.outstanding_balance.amount
end
def set_existing_card
return unless existing_card_selected?

View File

@@ -30,19 +30,17 @@ module OrderManagement
other_permitted_producer_ids = EnterpriseRelationship.joins(:parent)
.permitting(distributor.id).with_permission(:add_to_order_cycle)
.merge(Enterprise.is_primary_producer)
.select(:parent_id)
.pluck(:parent_id)
Enterprise.where(id: distributor.id)
.select(:id)
.or(Enterprise.where(id: other_permitted_producer_ids))
# Append to the potentially gigantic array instead of using union, which creates a new array
# The db IN statement won't care if there's a duplicate.
other_permitted_producer_ids << distributor.id
end
def self.outgoing_exchange_variant_ids(distributor)
# DISTINCT is not required here since this subquery is used within an IN clause,
# where duplicate values do not impact the result.
ExchangeVariant.joins(:exchange)
ExchangeVariant.select("DISTINCT exchange_variants.variant_id").joins(:exchange)
.where(exchanges: { incoming: false, receiver_id: distributor.id })
.select(:variant_id)
.pluck(:variant_id)
end
end
end

View File

@@ -56,7 +56,7 @@ RSpec.describe PaymentMailer do
payment = build(:payment)
payment.order.distributor = build(:enterprise, name: "Carrot Castle")
link = "https://taler.example.com/order/1"
mail = PaymentMailer.refund_available(payment.money.to_s, payment, link)
mail = PaymentMailer.refund_available(payment, link)
expect(mail.subject).to eq "Refund from Carrot Castle"
expect(mail.body).to include "Your payment of $45.75 to Carrot Castle is being refunded."

View File

@@ -21,6 +21,53 @@ RSpec.describe Spree::CreditCard do
let(:credit_card) { described_class.new }
context "#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(credit_card.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(credit_card.can_capture_and_complete_order?(payment)).to be_truthy
end
end
context "#can_void?" do
it "should be true if payment is not void" do
payment = build_stubbed(:payment)
allow(payment).to receive(:void?) { false }
expect(credit_card.can_void?(payment)).to be_truthy
end
end
context "#can_credit?" do
it "should be false if payment is not completed" do
payment = build_stubbed(:payment)
allow(payment).to receive(:completed?) { false }
expect(credit_card.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(credit_card.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(credit_card.can_credit?(payment)).to be_falsy
end
end
context "#valid?" do
it "should validate presence of number" do
credit_card.attributes = valid_credit_card_attributes.except(:number)

View File

@@ -1,7 +1,6 @@
# frozen_string_literal: true
RSpec.describe Spree::Gateway do
subject(:gateway) { test_gateway.new }
let(:test_gateway) do
Class.new(Spree::Gateway) do
def provider_class
@@ -16,58 +15,13 @@ RSpec.describe Spree::Gateway do
it "passes through all arguments on a method_missing call" do
expect(Rails.env).to receive(:local?).and_return(false)
gateway = test_gateway.new
expect(gateway.provider).to receive(:imaginary_method).with('foo')
gateway.imaginary_method('foo')
end
it "raises an error in test env" do
gateway = test_gateway.new
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

@@ -12,8 +12,8 @@ RSpec.describe Spree::PaymentMethod::Taler do
let(:backend_url) { "https://backend.demo.taler.net/instances/sandbox" }
let(:token_url) { "#{backend_url}/private/token" }
describe "#external_payment_url" do
it "creates an order reference and retrieves a URL to pay at", vcr: true do
describe "#external_payment_url", vcr: true do
it "creates an order reference and retrieves a URL to pay at" do
order = create(:order_ready_for_confirmation, payment_method: taler)
url = subject.external_payment_url(order:)
@@ -23,26 +23,6 @@ RSpec.describe Spree::PaymentMethod::Taler do
payment = order.payments.last.reload
expect(payment.response_code).to match "20...[0-9A-Z-]{17}$"
end
it "creates the Taler order with the right currency" do
order = create(:order_ready_for_confirmation, payment_method: taler)
backend_url = "https://taler.example.com"
token_url = "https://taler.example.com/private/token"
order_url = "https://taler.example.com/private/orders"
taler = Spree::PaymentMethod::Taler.new(
preferred_backend_url: "https://taler.example.com",
preferred_api_key: "sandbox",
)
stub_request(:post, token_url).to_return(body: { token: "1234" }.to_json)
stub_request(:post, order_url)
.with(body: /"amount":"AUD:10.0"/)
.to_return(body: { order_id: "one" }.to_json)
url = taler.external_payment_url(order:)
expect(url).to eq "#{backend_url}/orders/one"
end
end
describe "#purchase" do
@@ -76,46 +56,6 @@ RSpec.describe Spree::PaymentMethod::Taler do
end
end
describe "#credit" do
let(:order_endpoint) { "#{backend_url}/private/orders/taler-order-8" }
let(:refund_endpoint) { "#{order_endpoint}/refund" }
let(:taler_refund_uri) {
"taler://refund/backend.demo.taler.net/instances/sandbox/taler-order-8/"
}
it "starts the refund process" do
order_status = { order_status: "paid" }
stub_request(:get, order_endpoint).to_return(body: order_status.to_json)
stub_request(:post, refund_endpoint).to_return(body: { taler_refund_uri: }.to_json)
order = create(:completed_order_with_totals)
order.payments.create(
amount: order.total, state: :completed,
payment_method: taler,
response_code: "taler-order-8",
)
expect {
response = taler.credit(100, "taler-order-8", { payment: order.payments[0] })
expect(response.success?).to eq true
}.to enqueue_mail(PaymentMailer, :refund_available)
end
it "raises an error if payment hasn't been taken yet" do
order_status = { order_status: "claimed" }
stub_request(:get, order_endpoint).to_return(body: order_status.to_json)
order = create(:completed_order_with_totals)
order.payments.create(
amount: order.total, state: :completed,
payment_method: taler,
response_code: "taler-order-8",
)
expect {
taler.credit(100, "taler-order-8", { payment: order.payments[0] })
}.to raise_error StandardError, "Unsupported action"
end
end
describe "#void" do
let(:order_endpoint) { "#{backend_url}/private/orders/taler-order-8" }
let(:refund_endpoint) { "#{order_endpoint}/refund" }

View File

@@ -855,8 +855,7 @@ RSpec.describe Spree::Payment do
describe "available actions" do
context "for most gateways" do
let(:payment) { build_stubbed(:payment, payment_method:) }
let(:payment_method) { Spree::Gateway::StripeSCA.new }
let(:payment) { build_stubbed(:payment, source: build_stubbed(:credit_card)) }
it "can capture and void" do
expect(payment.actions).to match_array %w(capture_and_complete_order void)

View File

@@ -157,6 +157,8 @@ RSpec.describe Spree::Admin::PaymentsController do
context "with no payment source" do
it "redirect to payments page" do
allow(payment).to receive(:payment_source).and_return(nil)
put(
"/admin/orders/#{order.number}/payments/#{order.payments.first.id}/fire?e=void",
params: {},

View File

@@ -885,47 +885,6 @@ RSpec.describe '
end
end
end
describe "removing enterprise managers" do
let(:existing_user) { create(:user) }
before do
distributor1.users << existing_user
login_as logged_in_user
visit edit_admin_enterprise_path(distributor1)
scroll_to(:bottom)
within ".side_menu" do
find(:link, "Users").trigger("click")
end
end
context "as the enterprise owner" do
let(:logged_in_user) { distributor1.owner }
it 'removes the manager as enterprise owner' do
expect(page).to have_content existing_user.email
within "#manager-#{existing_user.id}" do
accept_confirm do
page.find("a.icon-trash").click
end
end
expect(page).not_to have_content existing_user.email
end
end
context "as the enterprise manager" do
let(:logged_in_user) { existing_user }
it "is unable delete any other manager" do
expect(page).to have_content existing_user.email
within('.edit_enterprise') do
expect(page).not_to have_selector('a.icon-trash')
end
end
end
end
end
context "changing package" do

View File

@@ -25,7 +25,7 @@ RSpec.describe "Admin -> Order -> Payments" do
login_as distributor.owner
end
it "allows to void a Taler payment" do
it "allows to refund a Taler payment" do
order_status = {
order_status: "paid",
contract_terms: {
@@ -51,34 +51,4 @@ RSpec.describe "Admin -> Order -> Payments" do
expect(page).not_to have_link "Void"
end
end
it "allows to credit a Taler payment" do
order_status = {
order_status: "paid",
contract_terms: {
amount: "KUDOS:2",
}
}
order_endpoint = "https://taler.example.com/private/orders/taler-id-1"
refund_endpoint = "https://taler.example.com/private/orders/taler-id-1/refund"
stub_request(:get, order_endpoint).to_return(body: order_status.to_json)
stub_request(:post, refund_endpoint).to_return(body: "{}")
visit spree.admin_order_payments_path(order.number)
within row_containing("Taler") do
expect(page).to have_text "COMPLETED"
expect(page).to have_link "Credit"
click_link class: "icon-credit"
expect(page).to have_text "COMPLETED"
expect(page).not_to have_link "Credit"
end
# Our payment system creates a new payment to show the credit.
within row_containing("$-9.75") do
expect(page).not_to have_link "Void"
end
end
end

View File

@@ -370,7 +370,6 @@ RSpec.describe "As a consumer, I want to checkout my order" do
Spree::PaymentMethod::Taler.create!(
name: "Taler",
environment: "test",
preferred_backend_url: "https://taler.example.com/",
distributors: [distributor]
)
end