Script when specific tags then bundle discount

New Member
2 0 0

Hi all, 

I would like to give the customer a total discount of X when they buy 3, 5 or 7 products in a certain category. The products where the discount is valid all have a certain tag: men or women. 

 

I'm now using the code below, but the code is not working. Does anyone know how to fix this?


Thanks in advance!!

 

DISCOUNTS_BY_QUANTITY = {
  3 => 10,
  5 => 15,
  7 => 20,
}

tags_needed = ['men', 'women']

Input.cart.line_items.each do |line_item|
 next unless tags_needed.include?(line_item.variant.product.tags)
 
  quantity, discount = DISCOUNTS_BY_QUANTITY.find do |quantity, _|
    line_item.quantity >= quantity
  end

next unless discount
 # message = "#{discount}% off when buying at least #{quantity}."
  message = 'YOU SAVED TIER_AMOUNT BY BUYING 3 OR MORE'
  line_item.change_line_price(
    line_item.line_price * (Decimal.new(1) - discount.to_d / 100),
    message: message,
  )
end

Output.cart = Input.cart
0 Likes
Shopify Partner
135 1 16

Hi Roberto,

 

The tag comparer in your script will only pass flow if all of the tags defined in the script are present on the product. So in this case the product would need to have both a men and women tag.

 

Try the following out instead.

 

DISCOUNTS_BY_QUANTITY = {
  3 => 10,
  5 => 15,
  7 => 20,
}

tags_needed = ['men', 'women']

Input.cart.line_items.each do |line_item|
 next if (tags_needed & line_item.variant.product.tags).empty?
 
  quantity, discount = DISCOUNTS_BY_QUANTITY.find do |quantity, _|
    line_item.quantity >= quantity
  end

  next unless discount
  # message = "#{discount}% off when buying at least #{quantity}."
  message = 'YOU SAVED TIER_AMOUNT BY BUYING 3 OR MORE'
  line_item.change_line_price(
    line_item.line_price * (Decimal.new(1) - discount.to_d / 100),
    message: message,
  )
end

Output.cart = Input.cart

Cheers,

Elliott

Feeling a bit lost? Contact elliot@mandelbrotian.com for help with theme setup, alterations, custom functionality, and app development.
0 Likes
New Member
2 0 0

Thanks, now the discount is given on one specific product. 
But I would like to give discounts, no matter if they 5 the same products or 5 different products (with the tag men or tag women). 


Lastly, preferably with the discount at the end of the cart with a total discount and not a discount per product. 

0 Likes
Highlighted
New Member
1 0 0

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.

0 Likes