Let people choose which payment methods are available to customers on order cycles

This commit is contained in:
Cillian O'Ruanaidh
2022-10-07 17:14:11 +01:00
parent 5718f9f00c
commit 4e6d64c0a1
18 changed files with 789 additions and 340 deletions

View File

@@ -1,7 +1,5 @@
# frozen_string_literal: true
require 'open_food_network/available_payment_method_filter'
module EnterprisesHelper
def current_distributor
@current_distributor ||= current_order(false)&.distributor
@@ -18,18 +16,7 @@ module EnterprisesHelper
end
def available_payment_methods
return [] if current_distributor.blank?
payment_methods = current_distributor.payment_methods.available(:both).to_a
filter = OpenFoodNetwork::AvailablePaymentMethodFilter.new
filter.filter!(payment_methods)
applicator = OpenFoodNetwork::TagRuleApplicator.new(current_distributor,
"FilterPaymentMethods", current_customer&.tag_list)
applicator.filter!(payment_methods)
payment_methods
OrderAvailablePaymentMethods.new(current_order, current_customer).to_a
end
def managed_enterprises

View File

@@ -399,7 +399,7 @@ class Enterprise < ApplicationRecord
end
def ready_for_checkout?
shipping_methods.frontend.any? && payment_methods.available.any?
shipping_methods.frontend.any? && payment_methods.available.any?(&:configured?)
end
def self.find_available_permalink(test_permalink)

View File

@@ -24,6 +24,9 @@ class OrderCycle < ApplicationRecord
has_many :distributors, -> { distinct }, source: :receiver, through: :cached_outgoing_exchanges
has_many :order_cycle_schedules
has_many :schedules, through: :order_cycle_schedules
has_and_belongs_to_many :selected_distributor_payment_methods,
class_name: 'DistributorPaymentMethod',
join_table: 'order_cycles_distributor_payment_methods'
has_and_belongs_to_many :selected_distributor_shipping_methods,
class_name: 'DistributorShippingMethod',
join_table: 'order_cycles_distributor_shipping_methods'
@@ -152,12 +155,10 @@ class OrderCycle < ApplicationRecord
]
end
def attachable_payment_methods
Spree::PaymentMethod.available(:both).
joins("INNER JOIN distributors_payment_methods
ON payment_method_id = spree_payment_methods.id").
where("distributor_id IN (?)", distributor_ids).
distinct
def attachable_distributor_payment_methods
DistributorPaymentMethod.joins(:payment_method).
merge(Spree::PaymentMethod.available).
where("distributor_id IN (?)", distributor_ids)
end
def attachable_distributor_shipping_methods
@@ -177,6 +178,9 @@ class OrderCycle < ApplicationRecord
oc.schedule_ids = schedule_ids
oc.save!
exchanges.each { |e| e.clone!(oc) }
oc.selected_distributor_payment_method_ids = (
attachable_distributor_payment_methods.map(&:id) & selected_distributor_payment_method_ids
)
oc.selected_distributor_shipping_method_ids = (
attachable_distributor_shipping_methods.map(&:id) & selected_distributor_shipping_method_ids
)
@@ -293,6 +297,18 @@ class OrderCycle < ApplicationRecord
items.each { |li| scoper.scope(li.variant) }
end
def distributor_payment_methods
if simple? || selected_distributor_payment_methods.none?
attachable_distributor_payment_methods
else
attachable_distributor_payment_methods.where(
"distributors_payment_methods.id IN (?) OR distributor_id NOT IN (?)",
selected_distributor_payment_methods.map(&:id),
selected_distributor_payment_methods.map(&:distributor_id)
)
end
end
def distributor_shipping_methods
if simple? || selected_distributor_shipping_methods.none?
attachable_distributor_shipping_methods

View File

@@ -20,6 +20,8 @@ module Spree
after_initialize :init
scope :inactive_or_backend, -> { where("active = false OR display_on = 'back_end'") }
scope :production, -> { where(environment: 'production') }
scope :managed_by, lambda { |user|
@@ -57,6 +59,10 @@ module Spree
Rails.application.config.spree.payment_methods
end
def configured?
!stripe? || stripe_configured?
end
def provider_class
raise 'You must implement provider_class method for this gateway.'
end
@@ -71,6 +77,10 @@ module Spree
nil
end
def frontend?
active? && display_on != "back_end"
end
# The class that will process payments for this payment type, used for @payment.source
# e.g. CreditCard in the case of a the Gateway payment type
# nil means the payment method doesn't require a source e.g. check
@@ -120,5 +130,17 @@ module Spree
def distributor_validation
validates_with DistributorsValidator
end
def stripe?
type.ends_with?("StripeSCA")
end
def stripe_configured?
Spree::Config.stripe_connect_enabled &&
Stripe.publishable_key &&
preferred_enterprise_id.present? &&
preferred_enterprise_id > 0 &&
stripe_account_id.present?
end
end
end

View File

@@ -0,0 +1,37 @@
# frozen_string_literal: true
class OrderAvailablePaymentMethods
attr_reader :order, :customer
delegate :distributor,
:order_cycle,
to: :order
def initialize(order, customer = nil)
@order, @customer = order, customer
end
def to_a
return [] if distributor.blank?
payment_methods = payment_methods_before_tag_rules_applied
applicator = OpenFoodNetwork::TagRuleApplicator.new(distributor,
"FilterPaymentMethods", customer&.tag_list)
applicator.filter!(payment_methods)
payment_methods.uniq
end
private
def payment_methods_before_tag_rules_applied
if order_cycle.nil? || order_cycle.simple?
distributor.payment_methods
else
distributor.payment_methods.where(
id: order_cycle.distributor_payment_methods.select(:payment_method_id)
)
end.available.select(&:configured?)
end
end

View File

@@ -12,6 +12,9 @@ class OrderCycleForm
@user = user
@permissions = OpenFoodNetwork::Permissions.new(user)
@schedule_ids = order_cycle_params.delete(:schedule_ids)
@selected_distributor_payment_method_ids = order_cycle_params.delete(
:selected_distributor_payment_method_ids
)
@selected_distributor_shipping_method_ids = order_cycle_params.delete(
:selected_distributor_shipping_method_ids
)
@@ -27,6 +30,7 @@ class OrderCycleForm
order_cycle.schedule_ids = schedule_ids if parameter_specified?(:schedule_ids)
order_cycle.save!
apply_exchange_changes
attach_selected_distributor_payment_methods
attach_selected_distributor_shipping_methods
sync_subscriptions
true
@@ -49,16 +53,29 @@ class OrderCycleForm
return if exchanges_unchanged?
OpenFoodNetwork::OrderCycleFormApplicator.new(order_cycle, user).go!
# reload so outgoing exchanges are up-to-date for shipping/payment method validations
order_cycle.reload
end
def attach_selected_distributor_payment_methods
return if @selected_distributor_payment_method_ids.nil?
order_cycle.selected_distributor_payment_method_ids = selected_distributor_payment_method_ids
order_cycle.save!
end
def attach_selected_distributor_shipping_methods
return if @selected_distributor_shipping_method_ids.nil?
order_cycle.reload # so outgoing exchanges are up-to-date for shipping method validations
order_cycle.selected_distributor_shipping_method_ids = selected_distributor_shipping_method_ids
order_cycle.save!
end
def attachable_distributor_payment_method_ids
@attachable_distributor_payment_method_ids ||= order_cycle.attachable_distributor_payment_methods.map(&:id)
end
def attachable_distributor_shipping_method_ids
@attachable_distributor_shipping_method_ids ||= order_cycle.attachable_distributor_shipping_methods.map(&:id)
end
@@ -69,6 +86,19 @@ class OrderCycleForm
end
end
def selected_distributor_payment_method_ids
@selected_distributor_payment_method_ids = (
attachable_distributor_payment_method_ids &
@selected_distributor_payment_method_ids.reject(&:blank?).map(&:to_i)
)
if attachable_distributor_payment_method_ids.sort == @selected_distributor_payment_method_ids.sort
@selected_distributor_payment_method_ids = []
end
@selected_distributor_payment_method_ids
end
def selected_distributor_shipping_method_ids
@selected_distributor_shipping_method_ids = (
attachable_distributor_shipping_method_ids &

View File

@@ -17,7 +17,8 @@ module PermittedAttributes
:name, :orders_open_at, :orders_close_at, :coordinator_id,
:preferred_product_selection_from_coordinator_inventory_only,
:automatic_notifications,
{ schedule_ids: [], selected_distributor_shipping_method_ids: [], coordinator_fee_ids: [] }
{ schedule_ids: [], selected_distributor_payment_method_ids: [],
selected_distributor_shipping_method_ids: [], coordinator_fee_ids: [] }
]
end

View File

@@ -10,8 +10,6 @@
%fieldset.no-border-bottom
%legend{ align: 'center'}= t('.checkout_options')
= hidden_field_tag "order_cycle[selected_distributor_shipping_method_ids][]", ""
.row
.three.columns
&nbsp;
@@ -19,10 +17,12 @@
%table.checkout-options
%thead
%tr
%th{ colspan: 2 }= t('.shipping_methods')
%th{ colspan: 2 }
= t('.shipping_methods')
= hidden_field_tag "order_cycle[selected_distributor_shipping_method_ids][]", ""
- @order_cycle.distributors.each do |distributor|
- distributor_shipping_methods = @order_cycle.attachable_distributor_shipping_methods.where("distributor_id = ?", distributor.id).includes(:shipping_method)
%tr{ "data-controller": "select-all" }
%tr{ class: "distributor-#{distributor.id}-shipping-methods", "data-controller": "select-all" }
%td.text-center
- if distributor_shipping_methods.many?
%label
@@ -48,17 +48,36 @@
%p
= t('.no_shipping_methods')
%tr
%th{ colspan: 2 }= t('.payment_methods')
%tr
%td
%td
- if @order_cycle.attachable_payment_methods.available(:both).any?
%ul
- @order_cycle.attachable_payment_methods.available(:both).each do |payment_method|
%li= payment_method.name
- else
%p
= t('.no_payment_methods')
%th{ colspan: 2 }
= t('.payment_methods')
= hidden_field_tag "order_cycle[selected_distributor_payment_method_ids][]", ""
- @order_cycle.distributors.each do |distributor|
- distributor_payment_methods = @order_cycle.attachable_distributor_payment_methods.where("distributor_id = ?", distributor.id).includes(:payment_method)
%tr{ class: "distributor-#{distributor.id}-payment-methods", "data-controller": "select-all" }
%td.text-center
- if distributor_payment_methods.many?
%label
= check_box_tag nil, nil, nil, { "data-action": "change->select-all#toggleAll", "data-select-all-target": "all" }
= t(".select_all")
%td
%em= distributor.name
- distributor_payment_methods.each do |distributor_payment_method|
%p
%label{ class: ("disabled" if distributor_payment_methods.one? || !distributor_payment_method.payment_method.frontend?) }
= check_box_tag "order_cycle[selected_distributor_payment_method_ids][]",
distributor_payment_method.id,
@order_cycle.distributor_payment_methods.include?(distributor_payment_method),
id: "order_cycle_selected_distributor_payment_method_ids_#{distributor_payment_method.id}",
data: ({ "action" => "change->select-all#toggleCheckbox", "select-all-target" => "checkbox" } if distributor_payment_method.payment_method.frontend?)
= distributor_payment_method.payment_method.name
- distributor.payment_methods.inactive_or_backend.each do |payment_method|
%label.disabled
= check_box_tag nil, nil, false, disabled: true
= payment_method.name
= "(#{t('.back_end')})"
- if distributor.payment_methods.available.none?
%p
= t('.no_payment_methods')
%div#save-bar
%div.container

View File

@@ -1,30 +0,0 @@
# frozen_string_literal: true
module OpenFoodNetwork
class AvailablePaymentMethodFilter
def filter!(payment_methods)
if stripe_enabled?
payment_methods.to_a.reject! do |payment_method|
payment_method.type.ends_with?("StripeSCA") &&
stripe_configuration_incomplete?(payment_method)
end
else
payment_methods.to_a.reject! do |payment_method|
payment_method.type.ends_with?("StripeSCA")
end
end
end
private
def stripe_enabled?
Spree::Config.stripe_connect_enabled && Stripe.publishable_key
end
def stripe_configuration_incomplete?(payment_method)
payment_method.preferred_enterprise_id.nil? ||
payment_method.preferred_enterprise_id.zero? ||
payment_method.stripe_account_id.blank?
end
end
end

View File

@@ -1,175 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
describe EnterprisesHelper, type: :helper do
let(:user) { create(:user) }
let(:distributor) { create(:distributor_enterprise) }
let(:some_other_distributor) { create(:distributor_enterprise) }
before { allow(helper).to receive(:spree_current_user) { user } }
describe "loading available payment methods" do
let!(:pm1) { create(:payment_method, distributors: [distributor]) }
let!(:pm2) { create(:payment_method, distributors: [some_other_distributor]) }
context "when the order has no current_distributor" do
before do
allow(helper).to receive(:current_distributor) { nil }
end
it "returns an empty array" do
expect(helper.available_payment_methods).to eq []
end
end
context "when no tag rules are in effect" do
before { allow(helper).to receive(:current_distributor) { distributor } }
it "finds the payment methods for the current distributor" do
expect(helper.available_payment_methods).to_not include pm2
expect(helper.available_payment_methods).to include pm1
end
end
context "when FilterPaymentMethods tag rules are in effect" do
let(:customer) { create(:customer, user: user, enterprise: distributor) }
let!(:tag_rule) {
create(:filter_payment_methods_tag_rule,
enterprise: distributor,
preferred_customer_tags: "trusted",
preferred_payment_method_tags: "trusted")
}
let!(:default_tag_rule) {
create(:filter_payment_methods_tag_rule,
enterprise: distributor,
is_default: true,
preferred_payment_method_tags: "trusted")
}
let(:tagged_pm) { pm1 }
let(:untagged_pm) { pm2 }
before do
tagged_pm.update_attribute(:tag_list, 'trusted')
distributor.payment_methods = [tagged_pm, untagged_pm]
allow(helper).to receive(:current_distributor) { distributor }
end
context "with a preferred visiblity of 'visible', default visibility of 'hidden'" do
before {
tag_rule.update_attribute(:preferred_matched_payment_methods_visibility, 'visible')
}
before {
default_tag_rule.update_attribute(:preferred_matched_payment_methods_visibility, 'hidden')
}
context "when the customer is nil" do
it "applies default action (hide)" do
expect(helper.current_customer).to be nil
expect(helper.available_payment_methods).to include untagged_pm
expect(helper.available_payment_methods).to_not include tagged_pm
end
end
context "when the customer's tags match" do
before { customer.update_attribute(:tag_list, 'trusted') }
it "applies the action (show)" do
expect(helper.current_customer).to eq customer
expect(helper.available_payment_methods).to include tagged_pm, untagged_pm
end
end
context "when the customer's tags don't match" do
before { customer.update_attribute(:tag_list, 'something') }
it "applies the default action (hide)" do
expect(helper.current_customer).to eq customer
expect(helper.available_payment_methods).to include untagged_pm
expect(helper.available_payment_methods).to_not include tagged_pm
end
end
end
context "with a preferred visiblity of 'hidden', default visibility of 'visible'" do
before {
tag_rule.update_attribute(:preferred_matched_payment_methods_visibility, 'hidden')
}
before {
default_tag_rule.update_attribute(:preferred_matched_payment_methods_visibility,
'visible')
}
context "when the customer is nil" do
it "applies default action (show)" do
expect(helper.current_customer).to be nil
expect(helper.available_payment_methods).to include tagged_pm, untagged_pm
end
end
context "when the customer's tags match" do
before { customer.update_attribute(:tag_list, 'trusted') }
it "applies the action (hide)" do
expect(helper.current_customer).to eq customer
expect(helper.available_payment_methods).to include untagged_pm
expect(helper.available_payment_methods).to_not include tagged_pm
end
end
context "when the customer's tags don't match" do
before { customer.update_attribute(:tag_list, 'something') }
it "applies the default action (show)" do
expect(helper.current_customer).to eq customer
expect(helper.available_payment_methods).to include tagged_pm, untagged_pm
end
end
end
end
context "when Stripe payment methods are present" do
let!(:pm3) {
create(:stripe_sca_payment_method, distributors: [distributor],
preferred_enterprise_id: distributor.id)
}
let!(:pm4) {
create(:stripe_sca_payment_method, distributors: [distributor],
preferred_enterprise_id: some_other_distributor.id)
}
let(:available_payment_methods) { helper.available_payment_methods }
around do |example|
original_stripe_connect_enabled = Spree::Config[:stripe_connect_enabled]
example.run
Spree::Config.set(stripe_connect_enabled: original_stripe_connect_enabled)
end
before do
allow(helper).to receive(:current_distributor) { distributor }
end
context "and Stripe Connect is disabled" do
before { Spree::Config.set(stripe_connect_enabled: false) }
it "ignores Stripe payment methods" do
expect(available_payment_methods).to_not include pm3, pm4
end
end
context "and Stripe Connect is enabled" do
let!(:stripe_account) { create(:stripe_account, enterprise_id: distributor.id) }
before do
Spree::Config.set(stripe_connect_enabled: true)
Stripe.publishable_key = "some_key"
end
it "includes Stripe payment methods with a valid stripe accounts" do
expect(available_payment_methods).to include pm3
expect(available_payment_methods).to_not include pm4
end
end
end
end
end

View File

@@ -276,7 +276,7 @@ describe Enterprise do
expect(Enterprise.ready_for_checkout).not_to include e
end
it "does not show enterprises wchich only have backend shipping methods" do
it "does not show enterprises which only have backend shipping methods" do
create(:shipping_method, distributors: [e],
display_on: Spree::ShippingMethod::DISPLAY_ON_OPTIONS[:back_end])
create(:payment_method, distributors: [e])
@@ -354,6 +354,13 @@ describe Enterprise do
create(:payment_method, distributors: [e])
expect(e.reload).to be_ready_for_checkout
end
it "returns false for enterprises with payment methods that are available but not configured
correctly" do
create(:shipping_method, distributors: [e])
create(:stripe_sca_payment_method, distributors: [e])
expect(e.reload).not_to be_ready_for_checkout
end
end
describe "distributors_with_active_order_cycles" do

View File

@@ -404,7 +404,32 @@ describe OrderCycle do
expect(cloned_exchange_attributes).to match_array original_exchange_attributes
end
context "when it has preferred shipping methods which can longer be applied validly
context "when it has selected payment methods which can longer be applied validly
e.g. payment method is backoffice only" do
it "only attaches the valid ones to the clone" do
distributor = create(:distributor_enterprise)
distributor_payment_method_i = create(
:payment_method,
distributors: [distributor]
).distributor_payment_methods.first
distributor_payment_method_ii = create(
:payment_method,
distributors: [distributor],
display_on: "back_end"
).distributor_payment_methods.first
order_cycle = create(:distributor_order_cycle, distributors: [distributor])
order_cycle.selected_distributor_payment_methods = [
distributor_payment_method_i,
distributor_payment_method_ii
]
cloned_order_cycle = order_cycle.clone!
expect(cloned_order_cycle.distributor_payment_methods).to eq [distributor_payment_method_i]
end
end
context "when it has selected shipping methods which can longer be applied validly
e.g. shipping method is backoffice only" do
it "only attaches the valid ones to the clone" do
distributor = create(:distributor_enterprise)
@@ -637,6 +662,32 @@ describe OrderCycle do
end
end
describe "#attachable_distributor_payment_methods" do
it "includes distributor payment methods from the distributors on the order cycle" do
payment_method = create(:payment_method)
oc = create(:simple_order_cycle, distributors: [payment_method.distributors.first])
distributor_payment_method = payment_method.distributor_payment_methods.first
expect(oc.attachable_distributor_payment_methods).to eq([distributor_payment_method])
end
it "does not include backoffice only distributor payment methods" do
payment_method = create(:payment_method, display_on: "back_end")
enterprise = create(:enterprise, payment_methods: [payment_method])
oc = create(:simple_order_cycle, distributors: [enterprise])
expect(oc.attachable_distributor_payment_methods).to be_empty
end
it "does not include inactive distributor payment methods" do
payment_method = create(:payment_method, active: false)
enterprise = create(:enterprise, payment_methods: [payment_method])
oc = create(:simple_order_cycle, distributors: [enterprise])
expect(oc.attachable_distributor_payment_methods).to be_empty
end
end
describe "#attachable_distributor_shipping_methods" do
it "includes distributor shipping methods from the distributors on the order cycle" do
shipping_method = create(:shipping_method)
@@ -655,6 +706,80 @@ describe OrderCycle do
end
end
describe "#distributor_payment_methods" do
let(:distributor) { create(:distributor_enterprise) }
it "returns all attachable distributor payment methods if the order cycle is simple" do
oc = create(:sells_own_order_cycle, distributors: [distributor])
distributor_payment_method = create(
:payment_method,
distributors: [distributor]
).distributor_payment_methods.first
expect(oc.distributor_payment_methods).to eq [distributor_payment_method]
end
context "distributor order cycle i.e. non-simple" do
let(:oc) { create(:distributor_order_cycle, distributors: [distributor]) }
it "returns all attachable distributor payment methods if no distributor payment methods
have been selected specifically" do
distributor_payment_method = create(
:payment_method,
distributors: [distributor]
).distributor_payment_methods.first
expect(oc.selected_distributor_payment_methods).to be_empty
expect(oc.distributor_payment_methods).to eq [distributor_payment_method]
end
it "returns selected distributor payment methods if they have been specified" do
distributor_payment_method_i = create(
:payment_method,
distributors: [distributor]
).distributor_payment_methods.first
distributor_payment_method_ii = create(
:payment_method,
distributors: [distributor]
).distributor_payment_methods.first
oc.selected_distributor_payment_methods << distributor_payment_method_ii
expect(oc.distributor_payment_methods).to eq [distributor_payment_method_ii]
end
context "with multiple distributors" do
let(:other_distributor) { create(:distributor_enterprise) }
let(:oc) { create(:distributor_order_cycle, distributors: [distributor, other_distributor]) }
it "returns all attachable distributor payment methods for a distributor if no distributor
payment methods have been selected specifically for that distributor, even if
distributor payment methods have been selected specifically for a different distributor
on the order cycle" do
distributor_payment_method = create(
:payment_method,
distributors: [distributor]
).distributor_payment_methods.first
other_distributor_payment_method_i = create(
:payment_method,
distributors: [other_distributor]
).distributor_payment_methods.first
other_distributor_payment_method_ii = create(
:payment_method,
distributors: [other_distributor]
).distributor_payment_methods.first
oc.selected_distributor_payment_methods << other_distributor_payment_method_i
expect(oc.distributor_payment_methods).to eq [
distributor_payment_method,
other_distributor_payment_method_i
]
end
end
end
end
describe "#distributor_shipping_methods" do
let(:distributor) { create(:distributor_enterprise) }

View File

@@ -5,100 +5,166 @@ require 'spec_helper'
class Spree::Gateway::Test < Spree::Gateway
end
module Spree
describe PaymentMethod do
describe "#available" do
let(:enterprise) { create(:enterprise) }
describe Spree::PaymentMethod do
describe "#available" do
let(:enterprise) { create(:enterprise) }
before do
Spree::PaymentMethod.delete_all
[nil, 'both', 'back_end'].each do |display_on|
Spree::Gateway::Test.create(
name: 'Display Both',
display_on: display_on,
active: true,
environment: 'test',
description: 'foofah',
distributors: [enterprise]
)
end
expect(Spree::PaymentMethod.all.size).to eq 3
end
it "should return all methods available to front-end/back-end when no parameter is passed" do
expect(Spree::PaymentMethod.available.size).to eq 2
end
it "should return all methods available to front-end/back-end when display_on = :both" do
expect(Spree::PaymentMethod.available(:both).size).to eq 2
end
it "should return all methods available to back-end when display_on = :back_end" do
expect(Spree::PaymentMethod.available(:back_end).size).to eq 2
end
end
describe "#configured?" do
context "non-Stripe payment method" do
let(:payment_method) { build(:payment_method) }
it "returns true" do
expect(payment_method).to be_configured
end
end
context "Stripe payment method" do
let(:payment_method) { create(:stripe_sca_payment_method) }
around do |example|
original_stripe_connect_enabled = Spree::Config[:stripe_connect_enabled]
example.run
Spree::Config.set(stripe_connect_enabled: original_stripe_connect_enabled)
end
before do
Spree::PaymentMethod.delete_all
Spree::Config.set(stripe_connect_enabled: true)
Stripe.publishable_key = "some_key"
end
[nil, 'both', 'back_end'].each do |display_on|
Spree::Gateway::Test.create(
name: 'Display Both',
display_on: display_on,
active: true,
environment: 'test',
description: 'foofah',
distributors: [enterprise]
)
context "and Stripe Connect is enabled and a Stripe publishable key, account id, account
owner are all present" do
it "returns true" do
expect(payment_method).to be_configured
end
expect(Spree::PaymentMethod.all.size).to eq 3
end
it "should return all methods available to front-end/back-end when no parameter is passed" do
expect(Spree::PaymentMethod.available.size).to eq 2
context "and Stripe Connect is disabled" do
before { Spree::Config.set(stripe_connect_enabled: false) }
it "returns false" do
expect(payment_method).not_to be_configured
end
end
it "should return all methods available to front-end/back-end when display_on = :both" do
expect(Spree::PaymentMethod.available(:both).size).to eq 2
context "and a Stripe publishable key is not present" do
before { Spree::Config.set(stripe_connect_enabled: false) }
it "returns false" do
expect(payment_method).not_to be_configured
end
end
it "should return all methods available to back-end when display_on = :back_end" do
expect(Spree::PaymentMethod.available(:back_end).size).to eq 2
context "and a Stripe account owner is not present" do
before { payment_method.preferred_enterprise_id = nil }
it "returns false" do
expect(payment_method).not_to be_configured
end
end
end
it "orders payment methods by name" do
pm1 = create(:payment_method, name: 'ZZ')
pm2 = create(:payment_method, name: 'AA')
pm3 = create(:payment_method, name: 'BB')
context "and a Stripe account ID is not present" do
before do
StripeAccount.find_by(
enterprise_id: payment_method.preferred_enterprise_id
).update_column(:stripe_user_id, nil)
end
expect(PaymentMethod.by_name).to eq([pm2, pm3, pm1])
end
it "raises errors when required fields are missing" do
pm = PaymentMethod.new
pm.save
expect(pm.errors.to_a).to eq(["Name can't be blank", "At least one hub must be selected"])
end
it "generates a clean name for known Payment Method types" do
expect(Spree::PaymentMethod::Check.clean_name).to eq(I18n.t("spree.admin.payment_methods.providers.check"))
expect(Spree::Gateway::PayPalExpress.clean_name).to eq(I18n.t("spree.admin.payment_methods.providers.paypalexpress"))
expect(Spree::Gateway::StripeSCA.clean_name).to eq(I18n.t("spree.admin.payment_methods.providers.stripesca"))
expect(Spree::Gateway::BogusSimple.clean_name).to eq(I18n.t("spree.admin.payment_methods.providers.bogussimple"))
expect(Spree::Gateway::Bogus.clean_name).to eq(I18n.t("spree.admin.payment_methods.providers.bogus"))
end
it "computes the amount of fees" do
order = create(:order)
free_payment_method = create(:payment_method) # flat rate calculator with preferred_amount of 0
expect(free_payment_method.compute_amount(order)).to eq 0
flat_rate_payment_method = create(:payment_method,
calculator: ::Calculator::FlatRate.new(preferred_amount: 10))
expect(flat_rate_payment_method.compute_amount(order)).to eq 10
flat_percent_payment_method = create(:payment_method,
calculator: ::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10))
expect(flat_percent_payment_method.compute_amount(order)).to eq 0
product = create(:product)
order.contents.add(product.variants.first)
expect(flat_percent_payment_method.compute_amount(order)).to eq 2.0
end
describe "scope" do
describe "filtering to specified distributors" do
let!(:distributor_a) { create(:distributor_enterprise) }
let!(:distributor_b) { create(:distributor_enterprise) }
let!(:distributor_c) { create(:distributor_enterprise) }
let!(:payment_method_a) {
create(:payment_method, distributors: [distributor_a, distributor_b])
}
let!(:payment_method_b) { create(:payment_method, distributors: [distributor_b]) }
let!(:payment_method_c) { create(:payment_method, distributors: [distributor_c]) }
it "includes only unique records under specified distributors" do
result = described_class.for_distributors([distributor_a, distributor_b])
expect(result.length).to eq(2)
expect(result).to include(payment_method_a)
expect(result).to include(payment_method_b)
it "returns false" do
expect(payment_method).not_to be_configured
end
end
end
end
it "orders payment methods by name" do
pm1 = create(:payment_method, name: 'ZZ')
pm2 = create(:payment_method, name: 'AA')
pm3 = create(:payment_method, name: 'BB')
expect(Spree::PaymentMethod.by_name).to eq([pm2, pm3, pm1])
end
it "raises errors when required fields are missing" do
pm = Spree::PaymentMethod.new
pm.save
expect(pm.errors.to_a).to eq(["Name can't be blank", "At least one hub must be selected"])
end
it "generates a clean name for known Payment Method types" do
expect(Spree::PaymentMethod::Check.clean_name).to eq(I18n.t("spree.admin.payment_methods.providers.check"))
expect(Spree::Gateway::PayPalExpress.clean_name).to eq(I18n.t("spree.admin.payment_methods.providers.paypalexpress"))
expect(Spree::Gateway::StripeSCA.clean_name).to eq(I18n.t("spree.admin.payment_methods.providers.stripesca"))
expect(Spree::Gateway::BogusSimple.clean_name).to eq(I18n.t("spree.admin.payment_methods.providers.bogussimple"))
expect(Spree::Gateway::Bogus.clean_name).to eq(I18n.t("spree.admin.payment_methods.providers.bogus"))
end
it "computes the amount of fees" do
order = create(:order)
free_payment_method = create(:payment_method) # flat rate calculator with preferred_amount of 0
expect(free_payment_method.compute_amount(order)).to eq 0
flat_rate_payment_method = create(:payment_method,
calculator: ::Calculator::FlatRate.new(preferred_amount: 10))
expect(flat_rate_payment_method.compute_amount(order)).to eq 10
flat_percent_payment_method = create(:payment_method,
calculator: ::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10))
expect(flat_percent_payment_method.compute_amount(order)).to eq 0
product = create(:product)
order.contents.add(product.variants.first)
expect(flat_percent_payment_method.compute_amount(order)).to eq 2.0
end
describe "scope" do
describe "filtering to specified distributors" do
let!(:distributor_a) { create(:distributor_enterprise) }
let!(:distributor_b) { create(:distributor_enterprise) }
let!(:distributor_c) { create(:distributor_enterprise) }
let!(:payment_method_a) {
create(:payment_method, distributors: [distributor_a, distributor_b])
}
let!(:payment_method_b) { create(:payment_method, distributors: [distributor_b]) }
let!(:payment_method_c) { create(:payment_method, distributors: [distributor_c]) }
it "includes only unique records under specified distributors" do
result = described_class.for_distributors([distributor_a, distributor_b])
expect(result.length).to eq(2)
expect(result).to include(payment_method_a)
expect(result).to include(payment_method_b)
end
end
end
end

View File

@@ -82,11 +82,18 @@ describe "checking out an order with a Stripe SCA payment method", type: :reques
}
end
around do |example|
original_stripe_connect_enabled = Spree::Config[:stripe_connect_enabled]
example.run
Spree::Config.set(stripe_connect_enabled: original_stripe_connect_enabled)
end
before do
order_cycle_distributed_variants = double(:order_cycle_distributed_variants)
allow(OrderCycleDistributedVariants).to receive(:new) { order_cycle_distributed_variants }
allow(order_cycle_distributed_variants).to receive(:distributes_order_variants?) { true }
allow(Stripe).to receive(:publishable_key).and_return("some_token")
Spree::Config.set(stripe_connect_enabled: true)
Stripe.api_key = "sk_test_12345"
order.update(distributor_id: enterprise.id, order_cycle_id: order_cycle.id)
order.reload.update_totals

View File

@@ -0,0 +1,217 @@
# frozen_string_literal: true
require 'spec_helper'
describe OrderAvailablePaymentMethods do
context "when the order has no current_distributor" do
it "returns an empty array" do
order_cycle = create(:sells_own_order_cycle)
order = build(:order, distributor: nil, order_cycle: order_cycle)
expect(OrderAvailablePaymentMethods.new(order).to_a).to eq []
end
end
it "does not return 'back office only' payment method" do
distributor = create(:distributor_enterprise)
frontend_payment_method = create(:payment_method, distributors: [distributor])
backoffice_only_payment_method = create(:payment_method,
distributors: [distributor],
display_on: 'back_end')
order_cycle = create(:sells_own_order_cycle)
order = build(:order, distributor: distributor, order_cycle: order_cycle)
available_payment_methods = OrderAvailablePaymentMethods.new(order).to_a
expect(available_payment_methods).to eq [frontend_payment_method]
end
it "does not return payment methods which are not configured correctly" do
distributor = create(:distributor_enterprise)
frontend_payment_method = create(:payment_method, distributors: [distributor])
unconfigured_payment_method = create(:stripe_sca_payment_method,
distributors: [distributor],
display_on: 'back_end')
order_cycle = create(:sells_own_order_cycle)
order = build(:order, distributor: distributor, order_cycle: order_cycle)
available_payment_methods = OrderAvailablePaymentMethods.new(order).to_a
expect(available_payment_methods).to eq [frontend_payment_method]
end
context "when no tag rules are in effect" do
context "sells own order cycle i.e. simple" do
it "only returns the payment methods which are available on the order cycle
and belong to the order distributor" do
distributor_i = create(:distributor_enterprise)
distributor_ii = create(:distributor_enterprise)
distributor_iii = create(:distributor_enterprise)
payment_method_i = create(:payment_method, distributors: [distributor_i])
payment_method_ii = create(:payment_method, distributors: [distributor_ii])
payment_method_iii = create(:payment_method, distributors: [distributor_iii])
order_cycle = create(:sells_own_order_cycle, distributors: [distributor_i, distributor_ii])
order = build(:order, distributor: distributor_i, order_cycle: order_cycle)
available_payment_methods = OrderAvailablePaymentMethods.new(order).to_a
expect(available_payment_methods).to eq [payment_method_i]
end
end
context "distributor order cycle i.e. not simple" do
it "only returns the payment methods which are available on the order cycle
and belong to the order distributor" do
distributor_i = create(:distributor_enterprise, payment_methods: [])
distributor_ii = create(:distributor_enterprise, payment_methods: [])
payment_method_i = create(:payment_method, distributors: [distributor_i])
payment_method_ii = create(:payment_method, distributors: [distributor_i])
payment_method_iii = create(:payment_method, distributors: [distributor_ii])
payment_method_iv = create(:payment_method, distributors: [distributor_ii])
order_cycle = create(:distributor_order_cycle,
distributors: [distributor_i, distributor_ii])
order_cycle.selected_distributor_payment_methods << [
distributor_i.distributor_payment_methods.first,
distributor_ii.distributor_payment_methods.first,
]
order = build(:order, distributor: distributor_i, order_cycle: order_cycle)
available_payment_methods = OrderAvailablePaymentMethods.new(order).to_a
expect(available_payment_methods).to eq [payment_method_i]
end
end
end
context "when FilterPaymentMethods tag rules are in effect" do
let(:user) { create(:user) }
let(:distributor) { create(:distributor_enterprise) }
let(:other_distributor) { create(:distributor_enterprise) }
let!(:distributor_payment_method) { create(:payment_method, distributors: [distributor]) }
let!(:other_distributor_payment_method) do
create(:payment_method, distributors: [other_distributor])
end
let(:customer) { create(:customer, user: user, enterprise: distributor) }
let!(:tag_rule) {
create(:filter_payment_methods_tag_rule,
enterprise: distributor,
preferred_customer_tags: "local",
preferred_payment_method_tags: "local-delivery")
}
let!(:default_tag_rule) {
create(:filter_payment_methods_tag_rule,
enterprise: distributor,
is_default: true,
preferred_payment_method_tags: "local-delivery")
}
let!(:tagged_payment_method) { distributor_payment_method }
let!(:untagged_payment_method) { other_distributor_payment_method }
before do
tagged_payment_method.update_attribute(:tag_list, 'local-delivery')
distributor.payment_methods = [tagged_payment_method, untagged_payment_method]
end
context "with a preferred visiblity of 'visible', default visibility of 'hidden'" do
before {
tag_rule.update_attribute(:preferred_matched_payment_methods_visibility, 'visible')
}
before {
default_tag_rule.update_attribute(:preferred_matched_payment_methods_visibility,
'hidden')
}
let(:order_cycle) { create(:sells_own_order_cycle) }
let(:order) { build(:order, distributor: distributor, order_cycle: order_cycle) }
context "when the customer is nil" do
let(:available_payment_methods) { OrderAvailablePaymentMethods.new(order).to_a }
it "applies default action (hide)" do
expect(available_payment_methods).to include untagged_payment_method
expect(available_payment_methods).to_not include tagged_payment_method
end
end
context "when a customer is present" do
let(:available_payment_methods) { OrderAvailablePaymentMethods.new(order, customer).to_a }
context "and the customer's tags match" do
before do
customer.update_attribute(:tag_list, 'local')
end
it "applies the action (show)" do
expect(available_payment_methods).to include(
tagged_payment_method,
untagged_payment_method
)
end
end
context "and the customer's tags don't match" do
before do
customer.update_attribute(:tag_list, 'something')
end
it "applies the default action (hide)" do
expect(available_payment_methods).to include untagged_payment_method
expect(available_payment_methods).to_not include tagged_payment_method
end
end
end
end
context "with a preferred visiblity of 'hidden', default visibility of 'visible'" do
before {
tag_rule.update_attribute(:preferred_matched_payment_methods_visibility, 'hidden')
}
before {
default_tag_rule.update_attribute(:preferred_matched_payment_methods_visibility,
'visible')
}
let(:order_cycle) { create(:sells_own_order_cycle) }
let(:order) { build(:order, distributor: distributor, order_cycle: order_cycle) }
context "when the customer is nil" do
let(:available_payment_methods) { OrderAvailablePaymentMethods.new(order).to_a }
it "applies default action (show)" do
expect(available_payment_methods).to include(
tagged_payment_method,
untagged_payment_method
)
end
end
context "when a customer is present" do
let(:available_payment_methods) { OrderAvailablePaymentMethods.new(order, customer).to_a }
context "and the customer's tags match" do
before do
customer.update_attribute(:tag_list, 'local')
end
it "applies the action (hide)" do
expect(available_payment_methods).to include untagged_payment_method
expect(available_payment_methods).to_not include tagged_payment_method
end
end
context "and the customer's tags don't match" do
before do
customer.update_attribute(:tag_list, 'something')
end
it "applies the default action (show)" do
expect(available_payment_methods).to include(
tagged_payment_method,
untagged_payment_method
)
end
end
end
end
end
end

View File

@@ -135,7 +135,9 @@ describe OrderCycleForm do
let(:distributor) { order_cycle.coordinator }
let(:supplier) { create(:supplier_enterprise) }
let(:user) { distributor.owner }
let(:payment_method) { create(:payment_method, distributors: [distributor]) }
let(:shipping_method) { create(:shipping_method, distributors: [distributor]) }
let(:distributor_payment_method) { payment_method.distributor_payment_methods.first }
let(:distributor_shipping_method) { shipping_method.distributor_shipping_methods.first }
let(:variant) { create(:variant, product: create(:product, supplier: supplier)) }
let(:params) { { name: 'Some new name' } }
@@ -151,14 +153,14 @@ describe OrderCycleForm do
}
end
context "basic update i.e. without exchanges or shipping methods" do
context "basic update i.e. without exchanges or payment/shipping methods" do
it do
expect(form.save).to be true
expect(order_cycle.name).to eq 'Some new name'
end
end
context "updating basics, incoming exchanges, outcoming exchanges
context "updating basics, incoming exchanges, outcoming exchanges, payment_methods
and shipping methods simultaneously" do
before do
params.merge!(
@@ -171,21 +173,31 @@ describe OrderCycleForm do
enterprise_fee_ids: []
}],
outgoing_exchanges: [outgoing_exchange_params],
selected_distributor_payment_method_ids: [distributor_payment_method.id],
selected_distributor_shipping_method_ids: [distributor_shipping_method.id]
)
end
it "saves everything i.e. the basics, incoming and outgoing exchanges and shipping methods" do
it "saves everything i.e. the basics, incoming and outgoing exchanges, payment methods and
shipping methods" do
expect(form.save).to be true
expect(order_cycle.name).to eq 'Some new name'
expect(order_cycle.cached_incoming_exchanges.count).to eq 1
expect(order_cycle.cached_outgoing_exchanges.count).to eq 1
expect(order_cycle.distributor_payment_methods).to eq [distributor_payment_method]
expect(order_cycle.distributor_shipping_methods).to eq [distributor_shipping_method]
end
end
context "updating outgoing exchanges and shipping methods simultaneously but the shipping
method doesn't belong to the new or any existing order cycle distributor" do
context "updating outgoing exchanges and shipping methods simultaneously but the payment
and shipping methods don't belong to the new or any existing order cycle
distributor" do
let(:other_distributor_payment_method) do
create(
:payment_method,
distributors: [create(:distributor_enterprise)]
).distributor_payment_methods.first
end
let(:other_distributor_shipping_method) do
create(
:shipping_method,
@@ -196,6 +208,7 @@ describe OrderCycleForm do
before do
params.merge!(
outgoing_exchanges: [outgoing_exchange_params],
selected_distributor_payment_method_ids: [other_distributor_payment_method.id],
selected_distributor_shipping_method_ids: [other_distributor_shipping_method.id]
)
end
@@ -203,10 +216,59 @@ describe OrderCycleForm do
it "saves the outgoing exchange but ignores the shipping method" do
expect(form.save).to be true
expect(order_cycle.distributors).to eq [distributor]
expect(order_cycle.distributor_payment_methods).to be_empty
expect(order_cycle.distributor_shipping_methods).to be_empty
end
end
context "updating payment methods" do
context "and it's valid" do
it "saves the changes" do
distributor = create(:distributor_enterprise)
distributor_payment_method = create(
:payment_method,
distributors: [distributor]
).distributor_payment_methods.first
order_cycle = create(:distributor_order_cycle, distributors: [distributor])
form = OrderCycleForm.new(
order_cycle,
{ selected_distributor_payment_method_ids: [distributor_payment_method.id] },
order_cycle.coordinator
)
expect(form.save).to be true
expect(order_cycle.distributor_payment_methods).to eq [distributor_payment_method]
end
end
context "with a payment method which doesn't belong to any distributor on the order cycle" do
it "ignores it" do
distributor_i = create(:distributor_enterprise)
distributor_ii = create(:distributor_enterprise)
distributor_payment_method_i = create(
:payment_method,
distributors: [distributor_i]
).distributor_payment_methods.first
distributor_payment_method_ii = create(
:payment_method,
distributors: [distributor_ii]
).distributor_payment_methods.first
order_cycle = create(:distributor_order_cycle,
distributors: [distributor_i])
form = OrderCycleForm.new(
order_cycle,
{ selected_distributor_payment_method_ids: [distributor_payment_method_ii.id] },
order_cycle.coordinator
)
expect(form.save).to be true
expect(order_cycle.distributor_payment_methods).to eq [distributor_payment_method_i]
end
end
end
context "updating shipping methods" do
context "and it's valid" do
it "saves the changes" do

View File

@@ -22,6 +22,8 @@ describe '
let!(:distributor) {
create(:distributor_enterprise, name: 'My distributor', with_payment_and_shipping: true)
}
let!(:payment_method_i) { distributor.payment_methods.first }
let!(:payment_method_ii) { create(:payment_method, distributors: [distributor]) }
let!(:shipping_method_i) { distributor.shipping_methods.first }
let!(:shipping_method_ii) { create(:shipping_method, distributors: [distributor]) }
let(:oc) { OrderCycle.last }
@@ -44,6 +46,7 @@ describe '
shipping_method_i.update!(name: "Pickup - always available")
shipping_method_ii.update!(name: "Delivery - sometimes available")
payment_method_ii.update!(name: "Cash")
end
it "creating an order cycle with full interface", js: true do
@@ -68,6 +71,8 @@ describe '
add_supplier_with_fees
add_distributor_with_fees
select_distributor_shipping_methods
select_distributor_payment_methods
click_button 'Save and Back to List'
expect_all_data_saved
end
@@ -161,22 +166,50 @@ describe '
click_button 'Save and Next'
end
def select_distributor_payment_methods
within("tr.distributor-#{distributor.id}-payment-methods") do
expect(page).to have_checked_field "Select all"
expect(page).to have_checked_field "Check"
expect(page).to have_checked_field "Cash"
uncheck "Cash"
expect(page).to have_unchecked_field "Select all"
expect_checking_select_all_payment_methods_works
expect_unchecking_select_all_payment_methods_works
# Our final selection:
check "Check"
end
end
def select_distributor_shipping_methods
expect(page).to have_checked_field "Select all"
within("tr.distributor-#{distributor.id}-shipping-methods") do
expect(page).to have_checked_field "Select all"
expect(page).to have_checked_field "Pickup - always available"
expect(page).to have_checked_field "Delivery - sometimes available"
expect(page).to have_checked_field "Pickup - always available"
expect(page).to have_checked_field "Delivery - sometimes available"
uncheck "Delivery - sometimes available"
uncheck "Delivery - sometimes available"
expect(page).to have_unchecked_field "Select all"
expect(page).to have_unchecked_field "Select all"
expect_checking_select_all_shipping_methods_works
expect_unchecking_select_all_shipping_methods_works
expect_checking_select_all_shipping_methods_works
expect_unchecking_select_all_shipping_methods_works
# Our final selection:
check "Pickup - always available"
click_button 'Save and Back to List'
# Our final selection:
check "Pickup - always available"
end
end
def expect_checking_select_all_payment_methods_works
# Now test that the "Select all" input is doing what it's supposed to:
check "Select all"
expect(page).to have_checked_field "Check"
expect(page).to have_checked_field "Cash"
end
def expect_checking_select_all_shipping_methods_works
@@ -187,6 +220,13 @@ describe '
expect(page).to have_checked_field "Delivery - sometimes available"
end
def expect_unchecking_select_all_payment_methods_works
uncheck "Select all"
expect(page).to have_unchecked_field "Check"
expect(page).to have_unchecked_field "Cash"
end
def expect_unchecking_select_all_shipping_methods_works
uncheck "Select all"
@@ -208,6 +248,7 @@ describe '
expect_receival_instructions_saved
expect_pickup_time_and_instructions_saved
expect_distributor_shipping_methods_saved
expect_distributor_payment_methods_saved
end
def expect_opening_and_closing_times_saved
@@ -240,6 +281,10 @@ describe '
expect(exchange.tag_list).to eq(['wholesale'])
end
def expect_distributor_payment_methods_saved
expect(oc.distributor_payment_methods).to eq(payment_method_i.distributor_payment_methods)
end
def expect_distributor_shipping_methods_saved
expect(oc.distributor_shipping_methods).to eq(shipping_method_i.distributor_shipping_methods)
end

View File

@@ -272,6 +272,8 @@ describe '
expect_shipping_methods_to_be_checked_for(distributor_managed)
expect_shipping_methods_to_be_checked_for(distributor_permitted)
expect_payment_methods_to_be_checked_for(distributor_managed)
expect_payment_methods_to_be_checked_for(distributor_permitted)
click_button 'Save and Back to List'
order_cycle = OrderCycle.find_by(name: 'My order cycle')
@@ -286,6 +288,9 @@ describe '
expect(order_cycle.distributor_shipping_methods).to match_array(
order_cycle.attachable_distributor_shipping_methods
)
expect(order_cycle.distributor_payment_methods).to match_array(
order_cycle.attachable_distributor_payment_methods
)
end
context "editing an order cycle" do
@@ -724,6 +729,14 @@ describe '
private
def expect_payment_methods_to_be_checked_for(distributor)
distributor.distributor_payment_method_ids.each do |distributor_payment_method_id|
expect(page).to have_checked_field(
"order_cycle_selected_distributor_payment_method_ids_#{distributor_payment_method_id}"
)
end
end
def expect_shipping_methods_to_be_checked_for(distributor)
distributor.distributor_shipping_method_ids.each do |distributor_shipping_method_id|
expect(page).to have_checked_field(