How can I simplify a line item script for tier structure?

Hi all,

I’m using a line item script to set properties on line items based on a tier structure. Initially, i used a demo script from Shopify’s documentation to add a tier pricing discount. During my development, it made more sense to create a custom solution so setting properties on those line items. I managed to update that demo script to execute just that. The problem though, is that various parts of the tier pricing logic (eg: a tier pricing discount message) remains an obligated part for it to function (i remove the discount message with javascript in the front). On top of that it’s still using the bulk logic of a tier pricing discount which is probably way too complex for what my goal is. Is there anyone here who is able to update my script to just do the following:

  • Set a "_is_tier_product => “yes” property on all products in the cart, starting from a quantity of 3, except for those who are tagged with “no-tier”

  • Set a "_is_tier_product => “no” property on all remaining products where the tier isn’t active on (eg, less than 3 matching products)

My current working script:

# ================================ Customizable Settings ================================
# ================================================================
# Tiered Product Discount by Quantity
#
# If the total quantity of matching items is greater than (or
# equal to) an entered threshold, the associated discount is
# applied to each matching item.
#
#   - 'product_selector_match_type' determines whether we look for
#     products that do or don't match the entered selectors. Can
#     be:
#       - ':include' to check if the product does match
#       - ':exclude' to make sure the product doesn't match
#   - 'product_selector_type' determines how eligible products
#     will be identified. Can be either:
#       - ':tag' to find products by tag
#       - ':type' to find products by type
#       - ':vendor' to find products by vendor
#       - ':product_id' to find products by ID
#       - ':variant_id' to find products by variant ID
#       - ':subscription' to find subscription products
#       - ':all' for all products
#   - 'product_selectors' is a list of identifiers (from above) for
#     qualifying products. Product/Variant ID lists should only
#     contain numbers (ie. no quotes). If ':all' is used, this
#     can also be 'nil'.
#   - 'tiers' is a list of tiers where:
#     - 'quantity' is the minimum quantity you need to buy to
#       qualify
#     - 'discount_type' is the type of discount to provide. Can be
#       either:
#         - ':percent'
#         - ':dollar'
#     - 'discount_amount' is the percentage/dollar discount to
#       apply (per item)
#     - 'discount_message' is the message to show when a discount
#       is applied
# ================================================================

# Product list: edit when changing this file! When a free product is added, make sure to also add the motivator_hidden_product ID

# 6688120864907 > Gift card
# 7009234321547 > Alpha Shaker

PRODUCT_DISCOUNT_TIERS = [
  {
    product_selector_match_type: :exclude,
    product_selector_type: :tag,
    product_selectors: ["no-tier"],
    tiers: [
      {
        quantity: 3,
        discount_type: :percent,
        discount_amount: 0,
        discount_message: '10% discount',
      }
    ],
  },
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
class ProductSelector
  def initialize(match_type, selector_type, selectors)
    _type = match_type
    @comparator = match_type == :include ? 'any?' : 'none?'
    _type = selector_type
    @selectors = selectors
  end

  def match?(line_item)
    if self.respond_to?(@selector_type)
      self.send(@selector_type, line_item)
    else
      raise RuntimeError.new('Invalid product selector type')
    end
  end

  def tag(line_item)
    product_tags = line_item.variant.product.tags.map { |tag| tag.downcase.strip }
    @selectors = @selectors.map { |selector| selector.downcase.strip }
    (@selectors & product_tags).send(@comparator)
  end

  def type(line_item)
    @selectors = @selectors.map { |selector| selector.downcase.strip }
    (@match_type == :include) == @selectors.include?(line_item.variant.product.product_type.downcase.strip)
  end

  def vendor(line_item)
    @selectors = @selectors.map { |selector| selector.downcase.strip }
    (@match_type == :include) == @selectors.include?(line_item.variant.product.vendor.downcase.strip)
  end

  def product_id(line_item)
    (@match_type == :include) == @selectors.include?(line_item.variant.product.id)
  end

  def variant_id(line_item)
    (@match_type == :include) == @selectors.include?(line_item.variant.id)
  end

  def subscription(line_item)
    !line_item.selling_plan_id.nil?
  end

  def all(line_item)
    true
  end
end

# ================================================================
# DiscountApplicator
#
# Applies the entered discount to the supplied line item.
# ================================================================
class DiscountApplicator
  def initialize(discount_type, discount_amount, discount_message)
    _type = discount_type
    _message = discount_message

    _amount = if discount_type == :percent
      1 - (discount_amount * 0.01)
    else
      Money.new(cents: 100) * discount_amount
    end
  end

  def apply(line_item)
    new_line_price = if _type == :percent
      line_item.line_price * _amount
      #puts new_line_price
      #line_item.line_price * line_item.line_price
    else
      [line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
    end

    line_item.change_line_price(new_line_price, message: _message)
  end
end

# ================================================================
# TieredProductDiscountByQuantityCampaign
#
# If the total quantity of matching items is greater than (or
# equal to) an entered threshold, the associated discount is
# applied to each matching item.
# ================================================================
class TieredProductDiscountByQuantityCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart)
    @campaigns.each do |campaign|
      product_selector = ProductSelector.new(
        campaign[:product_selector_match_type],
        campaign[:product_selector_type],
        campaign[:product_selectors],
      )

      applicable_items = cart.line_items.select { |line_item| product_selector.match?(line_item) }
      non_applicable_items = cart.line_items

      next if applicable_items.nil?

      total_applicable_quantity = applicable_items.map(&:quantity).reduce(0, :+)
      tiers = campaign[:tiers].sort_by { |tier| tier[:quantity] }.reverse
      applicable_tier = tiers.find { |tier| tier[:quantity] <= total_applicable_quantity }
      
      non_applicable_items.each do |line_item|
        new_properties = line_item.properties
        new_properties.merge!({"_is_tier_product" => "no"})
        line_item.change_properties(new_properties, message: "Property was added successfully")
      end

      next if applicable_tier.nil?

      discount_applicator = DiscountApplicator.new(
        applicable_tier[:discount_type],
        applicable_tier[:discount_amount],
        applicable_tier[:discount_message]
      )
      
      applicable_items.each do |line_item|
        new_properties = line_item.properties
        new_properties.merge!({"_is_tier_product" => "yes"})
        line_item.change_properties(new_properties, message: "Property was added successfully")
      end
      
      #if Input.cart.discount_code
        #Input.cart.discount_code.reject(
          #message: "Kann nicht genutzt werden, da eine andere Aktion aktiv ist"
        #)
      #end
      
      applicable_items.each do |line_item|
        discount_applicator.apply(line_item)
      end
    end
  end
end

CAMPAIGNS = [
  TieredProductDiscountByQuantityCampaign.new(PRODUCT_DISCOUNT_TIERS),
]

CAMPAIGNS.each do |campaign|
  campaign.run(Input.cart)
end

Output.cart = Input.cart