Hi Chris - I can share the script, however I found out shortly after that when using a blended store (under Shopify’s new B2B offering), when logged in as a B2B customer, shopify scripts are not supported, so if you’re using that then unfortunately you and I will have to wait for this functionality to roll out.
If you’re using a third party app for wholesale then this code would work for you, but bear in mind there are specific name based rules for our system (such as Royal Mail shipping rates and specific UPS shipping terms), so this will have to be amended to work in your store:
class Campaign
def initialize(condition, *qualifiers)
@condition = (condition.to_s + '?').to_sym
@qualifiers = PostCartAmountQualifier ? [] : [] rescue qualifiers.compact
_item_selector = qualifiers.last unless _item_selector
qualifiers.compact.each do |qualifier|
is_multi_select = qualifier.instance_variable_get(:@conditions).is_a?(Array)
if is_multi_select
qualifier.instance_variable_get(:@conditions).each do |nested_q|
@post_amount_qualifier = nested_q if nested_q.is_a?(PostCartAmountQualifier)
@qualifiers << qualifier
end
else
@post_amount_qualifier = qualifier if qualifier.is_a?(PostCartAmountQualifier)
@qualifiers << qualifier
end
end if @qualifiers.empty?
end
def qualifies?(cart)
return true if @qualifiers.empty?
@unmodified_line_items = cart.line_items.map do |item|
new_item = item.dup
new_item.instance_variables.each do |var|
val = item.instance_variable_get(var)
new_item.instance_variable_set(var, val.dup) if val.respond_to?(:dup)
end
new_item
end if @post_amount_qualifier
@qualifiers.send(@condition) do |qualifier|
is_selector = false
if qualifier.is_a?(Selector) || qualifier.instance_variable_get(:@conditions).any? { |q| q.is_a?(Selector) }
is_selector = true
end rescue nil
if is_selector
raise "Missing line item match type" if _match_type.nil?
cart.line_items.send(@li_match_type) { |item| qualifier.match?(item) }
else
qualifier.match?(cart, _item_selector)
end
end
end
def run_with_hooks(cart)
before_run(cart) if respond_to?(:before_run)
run(cart)
after_run(cart)
end
def after_run(cart)
.apply_final_discount if && .respond_to?(:apply_final_discount)
revert_changes(cart) unless @post_amount_qualifier.nil? || @post_amount_qualifier.match?(cart)
end
def revert_changes(cart)
cart.instance_variable_set(:@line_items, @unmodified_line_items)
end
end
class ConditionallyHideRates < Campaign
def initialize(condition, customer_qualifier, cart_qualifier, li_match_type, line_item_qualifier, rate_selector)
super(condition, customer_qualifier, cart_qualifier, line_item_qualifier)
_match_type = (li_match_type.to_s + '?').to_sym
@rate_selector = rate_selector
end
def run(rates, cart)
rates.delete_if { |rate| @rate_selector.match?(rate) } if qualifies?(cart)
end
end
class Qualifier
def partial_match(match_type, item_info, possible_matches)
match_type = (match_type.to_s + '?').to_sym
if item_info.kind_of?(Array)
possible_matches.any? do |possibility|
item_info.any? do |search|
search.send(match_type, possibility)
end
end
else
possible_matches.any? do |possibility|
item_info.send(match_type, possibility)
end
end
end
def compare_amounts(compare, comparison_type, compare_to)
case comparison_type
when :greater_than
return compare > compare_to
when :greater_than_or_equal
return compare >= compare_to
when :less_than
return compare < compare_to
when :less_than_or_equal
return compare <= compare_to
when :equal_to
return compare == compare_to
else
raise "Invalid comparison type"
end
end
end
class CustomerTagQualifier < Qualifier
def initialize(match_type, match_condition, tags)
_condition = match_condition
@invert = match_type == :does_not
= tags.map(&:downcase)
end
def match?(cart, selector = nil)
return true if cart.customer.nil? && @invert
return false if cart.customer.nil?
customer_tags = cart.customer.tags.to_a.map(&:downcase)
case _condition
when :match
return @invert ^ ((@tags & customer_tags).length > 0)
else
return @invert ^ partial_match(@match_condition, customer_tags, )
end
end
end
class Selector
def partial_match(match_type, item_info, possible_matches)
match_type = (match_type.to_s + '?').to_sym
if item_info.kind_of?(Array)
possible_matches.any? do |possibility|
item_info.any? do |search|
search.send(match_type, possibility)
end
end
else
possible_matches.any? do |possibility|
item_info.send(match_type, possibility)
end
end
end
end
class RateNameSelector < Selector
def initialize(match_type, match_condition, names)
_condition = match_condition
@invert = match_type == :does_not
@names = names.map(&:downcase)
end
def match?(shipping_rate)
name = shipping_rate.name.downcase
case _condition
when :match
return @invert ^ @names.include?(name)
else
return @invert ^ partial_match(@match_condition, name, @names)
end
end
end
CAMPAIGNS = [
ConditionallyHideRates.new(
:all,
CustomerTagQualifier.new(
:does,
:match,
["WHOLESALE"]
),
nil,
:any,
nil,
RateNameSelector.new(
:does,
:match,
["FREE Royal Mail 24 Tracked (On all orders placed before 3pm)", "FREE Royal Mail 48 Tracked (On all orders placed before 3pm)", "Royal Mail 24 Tracked (On all orders placed before 3pm)", "Royal Mail 48 Tracked (On all orders placed before 3pm)", "Royal Mail SATURDAY Tracked (On all orders placed before 3pm)", "UPS NEXT WORKING DAY (On all orders placed before 3pm)"]
)
),
ConditionallyHideRates.new(
:all,
CustomerTagQualifier.new(
:does,
:match,
["RETAIL"]
),
nil,
:any,
nil,
RateNameSelector.new(
:does,
:match,
["DHL - UP TO 100KG", "DHL - UP TO 30KG", "DHL - UP TO 40KG", "DHL - UP TO 50KG", "DHL - UP TO 60KG", "DHL - UP TO 70KG", "DHL - UP TO 80KG", "DHL - UP TO 90KG", "PalletForce 2+ Pallet", "PalletForce Single Pallet", "UPS - OVER 70KG", "UPS - UP TO 30KG", "UPS - UP TO 40KG", "UPS - UP TO 70KG"]
)
)
].freeze
CAMPAIGNS.each do |campaign|
campaign.run(Input.shipping_rates, Input.cart)
end
Output.shipping_rates = Input.shipping_rates