Not sure if you already got this working or not but this code should do want you wanted.
It will apply the tier discount based on how many items are in the cart with the ‘men’ or ‘women’ tag.
Tier price will show on the cart page and discount message will show on each eligible item on the checkout page.
class Campaign
def initialize(condition, *qualifiers)
@condition = (condition.to_s + '?').to_sym
@qualifiers = PostCartAmountQualifier ? [] : [] rescue qualifiers.compact
@line_item_selector = qualifiers.last unless @line_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 @li_match_type.nil?
cart.line_items.send(@li_match_type) { |item| qualifier.match?(item) }
else
qualifier.match?(cart, @line_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)
@discount.apply_final_discount if @discount && @discount.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 TieredDiscount < Campaign
def initialize(condition, customer_qualifier, cart_qualifier, line_item_selector, discount_type, tier_type, discount_tiers)
super(condition, customer_qualifier, cart_qualifier)
@line_item_selector = line_item_selector
@discount_type = discount_type
@tier_type = tier_type
@discount_tiers = discount_tiers.sort_by {|tier| tier[:discount].to_f }
end
def init_discount(amount, message)
case @discount_type
when :fixed
return FixedTotalDiscount.new(amount, message, :split)
when :percent
return PercentageDiscount.new(amount, message)
when :per_item
return FixedItemDiscount.new(amount, message)
end
end
def run(cart)
return unless qualifies?(cart)
applicable_items = cart.line_items.select { |item| @line_item_selector.nil? || @line_item_selector.match?(item) }
case @tier_type
when :customer_tag
return if cart.customer.nil?
customer_tags = cart.customer.tags.map(&:downcase)
qualified_tiers = @discount_tiers.select { |tier| customer_tags.include?(tier[:tier].downcase) }
when :cart_subtotal
cart_total = cart.subtotal_price
qualified_tiers = @discount_tiers.select { |tier| cart_total >= Money.new(cents: tier[:tier].to_i * 100) }
when :discountable_total
discountable_total = applicable_items.reduce(Money.zero) { |total, item| total + item.line_price }
qualified_tiers = @discount_tiers.select { |tier| discountable_total >= Money.new(cents: tier[:tier].to_i * 100) }
when :discountable_total_items
discountable_quantity = applicable_items.reduce(0) { |total, item| total + item.quantity }
qualified_tiers = @discount_tiers.select { |tier| discountable_quantity >= tier[:tier].to_i }
when :cart_items
cart_quantity = cart.line_items.reduce(0) { |total, item| total + item.quantity }
qualified_tiers = @discount_tiers.select { |tier| cart_quantity >= tier[:tier].to_i }
end
if @tier_type == :line_quantity
applicable_items.each do |item|
qualified_tiers = @discount_tiers.select { |tier| item.quantity >= tier[:tier].to_i }
next if qualified_tiers.empty?
discount_amount = qualified_tiers.last[:discount].to_f
discount_message = qualified_tiers.last[:message]
discount = init_discount(discount_amount, discount_message)
discount.apply(item)
discount.apply_final_discount if discount.respond_to?(:apply_final_discount)
end
else
return if qualified_tiers.empty?
discount_amount = qualified_tiers.last[:discount].to_f
discount_message = qualified_tiers.last[:message]
@discount = init_discount(discount_amount, discount_message)
applicable_items.each { |item| @discount.apply(item) }
end
end
end
class PercentageDiscount
def initialize(percent, message)
@discount = (100 - percent) / 100.0
@message = message
end
def apply(line_item)
line_item.change_line_price(line_item.line_price * @discount, message: @message)
end
end
class FixedTotalDiscount
def initialize(amount, message, behaviour = :to_zero)
@amount = Money.new(cents: amount * 100)
@message = message
@discount_applied = Money.zero
@all_items = []
@is_split = behaviour == :split
end
def apply(line_item)
if @is_split
@all_items << line_item
else
return unless @discount_applied < @amount
discount_to_apply = [(@amount - @discount_applied), line_item.line_price].min
line_item.change_line_price(line_item.line_price - discount_to_apply, {message: @message})
@discount_applied += discount_to_apply
end
end
def apply_final_discount
return if @all_items.length == 0
total_items = @all_items.length
total_quantity = 0
total_cost = Money.zero
@all_items.each do |item|
total_quantity += item.quantity
total_cost += item.line_price
end
@all_items.each_with_index do |item, index|
discount_percent = item.line_price.cents / total_cost.cents
if total_items == index + 1
discount_to_apply = Money.new(cents: @amount.cents - @discount_applied.cents.floor)
else
discount_to_apply = Money.new(cents: @amount.cents * discount_percent)
end
item.change_line_price(item.line_price - discount_to_apply, {message: @message})
@discount_applied += discount_to_apply
end
end
end
class FixedItemDiscount
def initialize(amount, message)
@amount = Money.new(cents: amount * 100)
@message = message
end
def apply(line_item)
per_item_price = line_item.variant.price
per_item_discount = [(@amount - per_item_price), @amount].max
discount_to_apply = [(per_item_discount * line_item.quantity), line_item.line_price].min
line_item.change_line_price(line_item.line_price - discount_to_apply, {message: @message})
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 ProductTagSelector < Selector
def initialize(match_type, match_condition, tags)
@match_condition = match_condition
@invert = match_type == :does_not
@tags = tags.map(&:downcase)
end
def match?(line_item)
product_tags = line_item.variant.product.tags.to_a.map(&:downcase)
case @match_condition
when :match
return @invert ^ ((@tags & product_tags).length > 0)
else
return @invert ^ partial_match(@match_condition, product_tags, @tags)
end
end
end
CAMPAIGNS = [
TieredDiscount.new(
:all,
nil,
nil,
ProductTagSelector.new(
:does,
:match,
["men", "women"]
),
:percent,
:discountable_total_items,
[{:tier => "3", :discount => "10", :message => "YOU SAVED 10% BY BUYING 3 OR MORE"}, {:tier => "5", :discount => "15", :message => "YOU SAVED 15% BY BUYING 5 OR MORE"}, {:tier => "7", :discount => "20", :message => "YOU SAVED 20% BY BUYING 7 OR MORE"}]
)
].freeze
CAMPAIGNS.each do |campaign|
campaign.run_with_hooks(Input.cart)
end
Output.cart = Input.cart
Code was made from the Shopify Scripts Creator on Github, if you haven’t checked it out it’s the best way to create all your scripts.