لائن آئٹم سکرپٹ کی مثالیں

لائن آئٹم سکرپٹ کارٹ میں موجود آئٹم کو متاثر کرتے ہیں، اور ان کی خصوصیات اور قیمتوں کو تبدیل کر سکتے ہیں۔ یہ سکرپٹ ہر اس وقت رن ہوتے ہیں جب کارٹ کی کوئی خصوصیت تبدیل ہوتی ہے، جیسے کہ کسی آئٹم کو شامل کرنا یا ہٹانا، شپنگ کا پتہ تبدیل کرنا، یا ڈسکاؤنٹ کوڈ شامل کرنا۔

اس صفحے پر موجود ٹیمپلیٹ استعمال کرنے کے لیے، بلینک ٹیمپلیٹ کے ساتھ ایک نیا سکرپٹ بنائیں۔

اقدامات:

  1. اپنے Shopify ایڈمن سے، Apps > Script Editor پر جائیں۔

  2. Create script پر کلک کریں۔

  3. Line items پر کلک کریں۔

  4. Blank template منتخب کریں، پھر Create script پر کلک کریں۔

  5. Ruby source code سیکشن میں، کوڈ کی ڈیفالٹ لائن کو حذف کریں: Output.cart = Input.cart

  6. اس صفحے سے ایک سکرپٹ کاپی کریں اور اسے Ruby source code سیکشن میں پیسٹ کریں۔

  7. اپنے سٹور میں کام کرنے کے لیے سکرپٹ کے Customizable Settings سیکشن میں ترمیم کریں۔

  8. اپنے سکرپٹ کی جانچ کریں۔ مزید معلومات کے لیے، Shopify Scripts کی ٹیسٹنگ اور ڈیبگنگ دیکھیں۔

  9. ٹیسٹنگ کے بعد:

    • سکرپٹ کا غیر شائع شدہ ڈرافٹ محفوظ کرنے کے لیے Save draft پر کلک کریں، یا
    • سکرپٹ بنانے اور شائع کرنے کے لیے Save and publish پر کلک کریں۔

اخراجات کے لحاظ سے ٹیرڈ کارٹ ڈسکاؤنٹ

کارٹ میں موجود آئٹم کی کل مالیت کے مطابق بڑھنے والا رقم کا ڈسکاؤنٹ پیش کرنے کے لیے اس سکرپٹ کا استعمال کریں۔ یہ ڈسکاؤنٹ کارٹ میں موجود انفرادی آئٹم پر ہر ممکن حد تک یکساں طور پر پھیلایا جاتا ہے۔

مثال کے طور پر، اگر گاہک $150 سے زیادہ خرچ کرتے ہیں تو ان کے کل بل پر $25 کا ڈسکاؤنٹ پیش کریں، اگر وہ $300 سے زیادہ خرچ کرتے ہیں تو $50 کا ڈسکاؤنٹ، یا اگر وہ $400 سے زیادہ خرچ کرتے ہیں تو $75 کا ڈسکاؤنٹ دیں۔

# ================================ Customizable Settings ================================
# ================================================================
# Tiered Cart Discounts by Spend Threshold
#
# If the cart total is greater than (or equal to) an entered
# threshold, the associated discount is applied to the cart. The
# discount will be spread, as evenly as possible, across all items.
#
#   - 'threshold' is the spend amount needed to qualify
#   - 'discount_amount' is the dollar discount to apply to the
#     cart
#   - 'discount_message' is the message to show when a discount
#     is applied
# ================================================================
SPENDING_THRESHOLDS = [
  {
    threshold: 150,
    discount_amount: 25,
    discount_message: 'Spend $150 and get $25 off!',
  },
  {
    threshold: 300,
    discount_amount: 50,
    discount_message: 'Spend $300 and get $50 off!',
  },
  {
    threshold: 400,
    discount_amount: 75,
    discount_message: 'Spend $400 and get $75 off!',
  },
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# DollarDiscountApplicator
#
# Applies the entered discount to the supplied line item.
# ================================================================
class DollarDiscountApplicator
  def initialize(discount_message)
    @discount_message = discount_message
  end

  def apply(line_item, discount_amount)
    new_line_price = line_item.line_price - discount_amount
    line_item.change_line_price(new_line_price, message: @discount_message)
  end
end

# ================================================================
# TieredCartDiscountBySpendCampaign
#
# If the cart total is greater than (or equal to) an entered
# threshold, the associated discount is applied to the cart. The
# discount will be spread, as evenly as possible, across all items.
# ================================================================
class TieredCartDiscountBySpendCampaign
  def initialize(tiers)
    @tiers = tiers.sort_by { |tier| tier[:threshold] }.reverse
  end

  def run(cart)
    applicable_tier = @tiers.find { |tier| cart.subtotal_price >= (Money.new(cents: 100) * tier[:threshold]) }
    return if applicable_tier.nil?

    discount_applicator = DollarDiscountApplicator.new(applicable_tier[:discount_message])
    discount_amount = applicable_tier[:discount_amount]
    items = cart.line_items.sort_by { |line_item| line_item.variant.price }
    self.loop_items(cart, items, discount_amount, discount_applicator)
  end

  def loop_items(cart, line_items, discount_amount, discount_applicator)
    avg_discount = (discount_amount.to_f * (1 / line_items.map(&:quantity).reduce(0, :+))).round(2)
    avg_discount = Money.new(cents: 100) * avg_discount
    discount_amount = Money.new(cents: 100) * discount_amount

    line_items.each_with_index do |line_item, index|
      break if discount_amount <= Money.zero

      line_discount = avg_discount * line_item.quantity

      if discount_amount < line_discount || index == (line_items.size - 1)
        discount_update = line_item.line_price > discount_amount ? discount_amount : line_item.line_price
      else
        discount_update = line_item.line_price > line_discount ? line_discount : line_item.line_price
      end

      discount_amount -= discount_update
      discount_applicator.apply(line_item, discount_update)
    end
  end
end

CAMPAIGNS = [
  TieredCartDiscountBySpendCampaign.new(SPENDING_THRESHOLDS),
]

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

Output.cart = Input.cart

اخراجات کے لحاظ سے ٹیرڈ ڈسکاؤنٹ

ان کے کارٹ میں موجود آئٹمز کی کل مالیت کے لحاظ سے بڑھنے والا فیصد ڈسکاؤنٹ پیش کرنے کے لیے اس سکرپٹ کو استعمال کریں۔

مثال کے طور پر، اگر کسٹمرز $30 یا اس سے زیادہ خرچ کرتے ہیں تو انہیں 10% ڈسکاؤنٹ، اگر وہ $50 یا اس سے زیادہ خرچ کرتے ہیں تو 15% ڈسکاؤنٹ، یا اگر وہ $100 یا اس سے زیادہ خرچ کرتے ہیں تو 20% ڈسکاؤنٹ پیش کریں۔

# ================================ Customizable Settings ================================
# ================================================================
# Tiered Discounts by Spend Threshold
#
# If the cart total is greater than (or equal to) an entered
# threshold, the associated discount is applied to each item.
#
#   - 'threshold' is the spend amount needed 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
# ================================================================
SPENDING_THRESHOLDS = [
  {
    threshold: 30,
    discount_type: :percent,
    discount_amount: 10,
    discount_message: 'Spend $30 and get 10% off!',
  },
  {
    threshold: 50,
    discount_type: :percent,
    discount_amount: 15,
    discount_message: 'Spend $50 and get 15% off!',
  },
  {
    threshold: 100,
    discount_type: :percent,
    discount_amount: 20,
    discount_message: 'Spend $100 and get 20% off!',
  },
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# DiscountApplicator
#
# Applies the entered discount to the supplied line item.
# ================================================================
class DiscountApplicator
  def initialize(discount_type, discount_amount, discount_message)
    @discount_type = discount_type
    @discount_message = discount_message

    @discount_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 @discount_type == :percent
      line_item.line_price * @discount_amount
    else
      [line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
    end

    line_item.change_line_price(new_line_price, message: @discount_message)
  end
end

# ================================================================
# TieredDiscountBySpendCampaign
#
# If the cart total is greater than (or equal to) an entered
# threshold, the associated discount is applied to each item.
# ================================================================
class TieredDiscountBySpendCampaign
  def initialize(tiers)
    @tiers = tiers.sort_by { |tier| tier[:threshold] }.reverse
  end

  def run(cart)
    applicable_tier = @tiers.find { |tier| cart.subtotal_price >= (Money.new(cents: 100) * tier[:threshold]) }
    return if applicable_tier.nil?

    discount_applicator = DiscountApplicator.new(
      applicable_tier[:discount_type],
      applicable_tier[:discount_amount],
      applicable_tier[:discount_message]
    )

    cart.line_items.each do |line_item|
      next if line_item.variant.product.gift_card?
      discount_applicator.apply(line_item)
    end
  end
end

CAMPAIGNS = [
  TieredDiscountBySpendCampaign.new(SPENDING_THRESHOLDS),
]

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

Output.cart = Input.cart

مقدار کے لحاظ سے پروڈکٹ کا درجہ بند ڈسکاؤنٹ

کسی مخصوص پروڈکٹ پر بلک ڈسکاؤنٹ پیش کرنے کے لیے اس سکرپٹ کو استعمال کریں، جس میں ایک ایسا فیصد ڈسکاؤنٹ ہو جو کارٹ میں موجود ان پروڈکٹس کی تعداد کے لحاظ سے بڑھتا ہو۔

مثال کے طور پر، اگر کسٹمرز 2 یا اس سے زیادہ ٹوپیاں خریدتے ہیں تو انہیں ہر ٹوپی پر 10% ڈسکاؤنٹ، یا اگر وہ 5 یا اس سے زیادہ ٹوپیاں خریدتے ہیں تو 15% ڈسکاؤنٹ پیش کریں۔

# ================================ 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_DISCOUNT_TIERS = [
  {
    product_selector_match_type: :include,
    product_selector_type: :tag,
    product_selectors: ["your_tag"],
    tiers: [
      {
        quantity: 2,
        discount_type: :percent,
        discount_amount: 10,
        discount_message: '10% off for 2+',
      },
      {
        quantity: 5,
        discount_type: :percent,
        discount_amount: 15,
        discount_message: '15% off for 5+',
      },
    ],
  },
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
class ProductSelector
  def initialize(match_type, selector_type, selectors)
    @match_type = match_type
    @comparator = match_type == :include ? 'any?' : 'none?'
    @selector_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)
    @discount_type = discount_type
    @discount_message = discount_message

    @discount_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 @discount_type == :percent
      line_item.line_price * @discount_amount
    else
      [line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
    end

    line_item.change_line_price(new_line_price, message: @discount_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) }

      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 }

      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|
        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

پروڈکٹ پر خرچ کے لحاظ سے پروڈکٹ کا درجہ بند ڈسکاؤنٹ

کارٹ میں موجود مخصوص آئٹمز کی کل مالیت کے لحاظ سے بڑھنے والا فیصد ڈسکاؤنٹ پیش کرنے کے لیے اس سکرپٹ کو استعمال کریں۔

مثال کے طور پر، اگر کسٹمرز $30 یا اس سے زیادہ خرچ کرتے ہیں تو 10% ڈسکاؤنٹ، $50 یا اس سے زیادہ پر 15%، اور $100 یا اس سے زیادہ پر 20% ڈسکاؤنٹ پیش کریں، لیکن صرف ان آئٹمز کے لیے جو مخصوص ٹیگ سے مماثل ہوں۔

# ================================ Customizable Settings ================================
# ================================================================
# Tiered Product Discount by Product Spend Threhsold
#
# If the total amount spent on matching items is greather 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:
#     - 'threshold' is the minimum dollar amount needed 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_DISCOUNT_TIERS = [
  {
    product_selector_match_type: :exclude,
    product_selector_type: :tag,
    product_selectors: ["your_tag", "another_tag"],
    tiers: [
      {
        threshold: 100,
        discount_type: :percent,
        discount_amount: 10,
        discount_message: 'Spend $100 or more, and get 10% off!',
      },
    ],
  },
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
class ProductSelector
  def initialize(match_type, selector_type, selectors)
    @match_type = match_type
    @comparator = match_type == :include ? 'any?' : 'none?'
    @selector_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)
    @discount_type = discount_type
    @discount_message = discount_message

    @discount_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 @discount_type == :percent
      line_item.line_price * @discount_amount
    else
      [line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
    end

    line_item.change_line_price(new_line_price, message: @discount_message)
  end
end

# ================================================================
# TieredProductDiscountByProductSpendCampaign
#
# If the total amount spent on matching items is greather than (or
# equal to) an entered threshold, the associated discount is
# applied to each matching item.
# ================================================================
class TieredProductDiscountByProductSpendCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart)
    @campaigns.each do |campaign|
      if campaign[:product_selector_type] == :all
        total_applicable_cost = cart.subtotal_price
        applicable_items = cart.line_items
      else
        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) }

        next if applicable_items.nil?

        total_applicable_cost = applicable_items.map(&:line_price).reduce(Money.zero, :+)
      end

      tiers = campaign[:tiers].sort_by { |tier| tier[:threshold] }.reverse
      applicable_tier = tiers.find { |tier|  total_applicable_cost >= (Money.new(cents: 100) * tier[:threshold]) }

      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|
        discount_applicator.apply(line_item)
      end
    end
  end
end

CAMPAIGNS = [
  TieredProductDiscountByProductSpendCampaign.new(PRODUCT_DISCOUNT_TIERS),
]

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

Output.cart = Input.cart

پروڈکٹ ڈسکاؤنٹ

مخصوص آئٹمز پر ڈسکاؤنٹ پیش کرنے کے لیے اس سکرپٹ کو استعمال کریں۔

مثال کے طور پر، discounted کے ٹیگ کردہ آئٹمز پر کسٹمرز کو 10% ڈسکاؤنٹ پیش کریں۔

# ================================ Customizable Settings ================================
# ================================================================
# Discount by Product
#
# Any matching item will be discounted by the entered amount.
#
#   - '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'.
#   - '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_DISCOUNTS = [
  {
    product_selector_match_type: :include,
    product_selector_type: :tag,
    product_selectors: ["your_tag"],
    discount_type: :percent,
    discount_amount: 10,
    discount_message: '10% off tagged products!'
  }
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
class ProductSelector
  def initialize(match_type, selector_type, selectors)
    @match_type = match_type
    @comparator = match_type == :include ? 'any?' : 'none?'
    @selector_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)
    @discount_type = discount_type
    @discount_message = discount_message

    @discount_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 @discount_type == :percent
      line_item.line_price * @discount_amount
    else
      [line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
    end

    line_item.change_line_price(new_line_price, message: @discount_message)
  end
end

# ================================================================
# ProductDiscountCampaign
#
# Any matching item will be discounted by the entered amount.
# ================================================================
class ProductDiscountCampaign
  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],
      )

      discount_applicator = DiscountApplicator.new(
        campaign[:discount_type],
        campaign[:discount_amount],
        campaign[:discount_message]
      )

      cart.line_items.each do |line_item|
        next unless product_selector.match?(line_item)
        discount_applicator.apply(line_item)
      end
    end
  end
end

CAMPAIGNS = [
  ProductDiscountCampaign.new(PRODUCT_DISCOUNTS),
]

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

Output.cart = Input.cart

ڈسکاؤنٹ کوڈ کے لحاظ سے پروڈکٹ ڈسکاؤنٹ

اگر کوئی مخصوص ڈسکاؤنٹ کوڈ استعمال کیا جاتا ہے تو مخصوص آئٹمز پر ڈسکاؤنٹ پیش کرنے کے لیے یہ سکرپٹ استعمال کریں۔

مثال کے طور پر، اگر کسٹمرز DISCOUNT_10 ڈسکاؤنٹ کوڈ استعمال کرتے ہیں تو انہیں discounted کے ٹیگ کردہ آئٹمز پر 10% ڈسکاؤنٹ پیش کریں۔

# ================================ Customizable Settings ================================
# ================================================================
# Product Discount by Discount Code
#
# If any matching discount codes are used, any matching items
# will be discounted by the entered amount.
#
#   - 'discount_code_match_type' determines whether the below
#     strings should be an exact or partial match. Can be:
#       - ':exact' for an exact match
#       - ':partial' for a partial match
#   - 'discount_codes' is a list of strings to identify discount
#     codes
#   - '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'.
#   - '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_DISCOUNTS_BY_DISCOUNT_CODE = [
  {
    discount_code_match_type: :exact,
    discount_codes: ["TESTCODE1", "TESTCODE2"],
    product_selector_match_type: :include,
    product_selector_type: :tag,
    product_selectors: ["your_tag"],
    discount_type: :percent,
    discount_amount: 10,
    discount_message: '10% off tagged products!'
  }
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# DiscountCodeSelector
#
# Finds whether the supplied discount code matches any of the
# entered codes.
# ================================================================
class DiscountCodeSelector
  def initialize(match_type, discount_codes)
    @comparator = match_type == :exact ? '==' : 'include?'
    @discount_codes = discount_codes.map { |discount_code| discount_code.upcase.strip }
  end

  def match?(discount_code)
    @discount_codes.any? { |code| discount_code.code.upcase.send(@comparator, code) }
  end
end

# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
class ProductSelector
  def initialize(match_type, selector_type, selectors)
    @match_type = match_type
    @comparator = match_type == :include ? 'any?' : 'none?'
    @selector_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)
    @discount_type = discount_type
    @discount_message = discount_message

    @discount_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 @discount_type == :percent
      line_item.line_price * @discount_amount
    else
      [line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
    end

    line_item.change_line_price(new_line_price, message: @discount_message)
  end
end

# ================================================================
# ProductDiscountByCodeCampaign
#
# If any matching discount codes are used, any matching items
# will be discounted by the entered amount.
# ================================================================
class ProductDiscountByCodeCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart)
    return if cart.discount_code.nil?

    @campaigns.each do |campaign|
      discount_code_selector = DiscountCodeSelector.new(
        campaign[:discount_code_match_type],
        campaign[:discount_codes]
      )

      next unless discount_code_selector.match?(cart.discount_code)

      product_selector = ProductSelector.new(
        campaign[:product_selector_match_type],
        campaign[:product_selector_type],
        campaign[:product_selectors],
      )

      discount_applicator = DiscountApplicator.new(
        campaign[:discount_type],
        campaign[:discount_amount],
        campaign[:discount_message]
      )

      cart.line_items.each do |line_item|
        next unless product_selector.match?(line_item)
        discount_applicator.apply(line_item)
      end
    end
  end
end

CAMPAIGNS = [
  ProductDiscountByCodeCampaign.new(PRODUCT_DISCOUNTS_BY_DISCOUNT_CODE),
]

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

Output.cart = Input.cart

کسی پروڈکٹ کی مخصوص مقدار خریدیں، دوسری مقدار ڈسکاؤنٹ پر حاصل کریں

اگر کوئی مخصوص مقدار سے زیادہ خریدی جائے تو مخصوص آئٹم پر ڈسکاؤنٹ پیش کرنے کے لیے یہ سکرپٹ استعمال کریں۔

مثال کے طور پر، کسٹمرز کو discount ٹیگ والے تین آئٹمز خریدنے کی پیشکش کریں اور انہیں discount ٹیگ والے چوتھے آئٹم پر 50% ڈسکاؤنٹ دیں۔

# ================================ Customizable Settings ================================
# ================================================================
# Buy X, Get Y For Z Discount
#
# Buy a certain number of matching items, get a certain number
# of the same matching items with the entered discount applied. For
# example:
#
#   "Buy 2 products tagged with 'tag', get another product
#    tagged with 'tag' for 10% off"
#
#   - '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'.
#   - 'quantity_to_buy' is the number of products needed to
#     qualify
#   - 'quantity_to_discount' is the number of products to discount
#   - '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
#
# Something to note for the case of running multiple offers is
# that there shouldn't be any overlap between product selection
# as this can lead to extra discounting. For example, you should
# NOT offer "Buy 1 Product X, get 1 50% off", as well as "Buy 2
# Product X, get 1 free"
# ================================================================
BUY_X_GET_Y_FOR_Z = [
  {
    product_selector_match_type: :include,
    product_selector_type: :all,
    product_selectors: nil,
    quantity_to_buy: 1,
    quantity_to_discount: 1,
    discount_type: :percent,
    discount_amount: 50,
    discount_message: 'Buy one item, get the second 50% off!',
  },
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
class ProductSelector
  def initialize(match_type, selector_type, selectors)
    @match_type = match_type
    @comparator = match_type == :include ? 'any?' : 'none?'
    @selector_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)
    @discount_type = discount_type
    @discount_message = discount_message

    @discount_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 @discount_type == :percent
      line_item.line_price * @discount_amount
    else
      [line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
    end

    line_item.change_line_price(new_line_price, message: @discount_message)
  end
end

# ================================================================
# BuyXGetYForZCampaign
#
# Buy a certain number of matching items, get a certain number
# of the same matching items with the entered discount applied.
# ================================================================
class BuyXGetYForZCampaign
  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],
      )

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

      next if eligible_items.nil?

      eligible_items = eligible_items.sort_by { |line_item| -line_item.variant.price }
      quantity_to_buy = campaign[:quantity_to_buy]
      quantity_to_discount = campaign[:quantity_to_discount]
      bundle_size = quantity_to_buy + quantity_to_discount
      number_of_bundles = (eligible_items.map(&:quantity).reduce(0, :+) / bundle_size).floor
      number_of_discountable_items = number_of_bundles * quantity_to_discount

      next unless number_of_discountable_items > 0

      discount_applicator = DiscountApplicator.new(
        campaign[:discount_type],
        campaign[:discount_amount],
        campaign[:discount_message]
      )

      self.loop_items(
        discount_applicator, cart, eligible_items, number_of_discountable_items, quantity_to_buy, quantity_to_discount
      )
    end
  end

  def loop_items(discount_applicator, cart, line_items, num_to_discount, quantity_to_buy, quantity_to_discount)
    surplus = 0
    bundle_size = quantity_to_buy + quantity_to_discount

    line_items.each do |line_item|
      line_quantity = line_item.quantity + surplus

      if line_quantity > quantity_to_buy
        bundles_per_line = (line_quantity / bundle_size).floor
        take_quantity = bundles_per_line * quantity_to_discount
        surplus += (line_quantity - (bundle_size * bundles_per_line))

        if line_item.quantity > take_quantity
          discount_item = line_item.split(take: take_quantity)
          discount_applicator.apply(discount_item)
          position = cart.line_items.find_index(line_item)
          cart.line_items.insert(position + 1, discount_item)
          num_to_discount -= take_quantity
        else
          discount_applicator.apply(line_item)
          num_to_discount -= line_item.quantity
        end
      else
        surplus += line_quantity
      end

      break if num_to_discount <= 0
    end
  end
end

CAMPAIGNS = [
  BuyXGetYForZCampaign.new(BUY_X_GET_Y_FOR_Z),
]

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

Output.cart = Input.cart

ایک بنڈل خریدیں، ڈسکاؤنٹ پر ایک اضافی پروڈکٹ حاصل کریں

اگر انہوں نے کئی دیگر آئٹمز خریدے ہوں تو مخصوص آئٹم پر ڈسکاؤنٹ پیش کرنے کے لیے اس سکرپٹ کو استعمال کریں۔

مثال کے طور پر، اگر کسٹمرز ٹوپی، ٹی شرٹ، اور دھوپ کا چشمہ بھی خریدتے ہیں تو انہیں جرابوں کا ایک مفت جوڑا پیش کریں۔

# ================================ Customizable Settings ================================
# ================================================================
# Buy Products VWX, get Product Y for Z Discount
#
# Buy a specific bundle of items, get another item at a discount.
# For example:
#
#   "Buy a t-shirt, a hat, and sunglasses, get a free pair of socks"
#
#   - 'bundle_items' is a list of the items that comprise the
#     bundle, where:
#     - 'product_id' is the ID of the product
#     - 'quantity_needed' is the quantity necessary to complete
#       the bundle
#   - 'quantity_to_buy' is the number of bundles needed to qualify
#     for a discount product
#   - 'discount_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
#   - 'discount_product_selector_type' determines how qualifying
#     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
#   - 'discount_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'.
#   - 'quantity_to_discount' is the number of items to discount
#     per offer
#   - '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
# ================================================================
BUNDLE_DISCOUNTS = [
  {
    bundle_items: [
      {
        product_id: 1234567890987,
        quantity_needed: 1
      },
      {
        product_id: 1234567890986,
        quantity_needed: 1
      },
    ],
    quantity_to_buy: 1,
    discount_product_selector_match_type: :include,
    discount_product_selector_type: :product_id,
    discount_product_selectors: [1234567890123],
    quantity_to_discount: 1,
    discount_type: :percent,
    discount_amount: 10,
    discount_message: "Buy Product VWX, get Product Y for 10% off",
  },
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# BundleSelector
#
# Finds any items that are part of the entered bundle and saves
# them.
# ================================================================
class BundleSelector
  def initialize(bundle_items)
    @bundle_items = bundle_items.reduce({}) do |acc, bundle_item|
      acc[bundle_item[:product_id]] = {
        cart_items: [],
        quantity_needed: bundle_item[:quantity_needed],
        total_quantity: 0,
      }

      acc
    end
  end

  def build(cart)
    cart.line_items.each do |line_item|
      next unless @bundle_items[line_item.variant.product.id]

      @bundle_items[line_item.variant.product.id][:cart_items].push(line_item)
      @bundle_items[line_item.variant.product.id][:total_quantity] += line_item.quantity
    end

    @bundle_items
  end
end

# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
class ProductSelector
  def initialize(match_type, selector_type, selectors)
    @match_type = match_type
    @comparator = match_type == :include ? 'any?' : 'none?'
    @selector_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)
    @discount_type = discount_type
    @discount_message = discount_message

    @discount_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 @discount_type == :percent
      line_item.line_price * @discount_amount
    else
      [line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
    end

    line_item.change_line_price(new_line_price, message: @discount_message)
  end
end

# ================================================================
# DiscountLoop
#
# Loops through the supplied line items and discounts the supplied
# number of items by the supplied discount.
# ================================================================
class DiscountLoop
  def initialize(discount_applicator)
    @discount_applicator = discount_applicator
  end

  def loop_items(cart, line_items, num_to_discount)
    line_items.each_with_index do |line_item|
      break if num_to_discount <= 0

      if line_item.quantity > num_to_discount
        split_line_item = line_item.split(take: num_to_discount)
        @discount_applicator.apply(split_line_item)
        position = cart.line_items.find_index(line_item)
        cart.line_items.insert(position + 1, split_line_item)
        break
      else
        @discount_applicator.apply(line_item)
        num_to_discount -= line_item.quantity
      end
    end
  end
end

# ================================================================
# BundleDiscountCampaign
#
# If the entered bundle is present, the entered discount is
# applied to the entered product.
# ================================================================
class BundleDiscountCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart)
    @campaigns.each do |campaign|
      bundle_selector = BundleSelector.new(campaign[:bundle_items])
      bundle_items = bundle_selector.build(cart)

      next if bundle_items.any? do |product_id, product_info|
        product_info[:total_quantity] < product_info[:quantity_needed]
      end

      num_bundles = bundle_items.map do |product_id, product_info|
        (product_info[:total_quantity] / product_info[:quantity_needed])
      end

      num_bundles = num_bundles.min.floor

      product_selector = ProductSelector.new(
        campaign[:discount_product_selector_match_type],
        campaign[:discount_product_selector_type],
        campaign[:discount_product_selectors],
      )

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

      next if discount_items.nil?

      discount_applicator = DiscountApplicator.new(
        campaign[:discount_type],
        campaign[:discount_amount],
        campaign[:discount_message]
      )

      discount_loop = DiscountLoop.new(discount_applicator)
      discount_loop.loop_items(cart, discount_items, (campaign[:quantity_to_discount] * num_bundles))
    end
  end
end

CAMPAIGNS = [
  BundleDiscountCampaign.new(BUNDLE_DISCOUNTS),
]

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

Output.cart = Input.cart

بنڈل ڈسکاؤنٹ

جب پروڈکٹس کا ایک مخصوص سیٹ کارٹ میں شامل کیا جائے تو ڈسکاؤنٹ پیش کرنے کے لیے یہ سکرپٹ استعمال کریں۔

مثال کے طور پر، ٹی شرٹ، ٹوپی، اور دھوپ کا چشمہ خریدنے پر کسٹمرز کو 20% ڈسکاؤنٹ پیش کریں۔

# ================================ Customizable Settings ================================
# ================================================================
# Buy Products WXY, get Z Discount
#
# Buy a specific bundle of products, get that bundle at a
# discount. For example:
#
#   "Buy a t-shirt, a hat, and sunglasses, get 20% off each"
#
#   - 'bundle_items' is a list of the items that comprise the
#     bundle, where:
#     - 'product_id' is the ID of the product
#     - 'quantity_needed' is the quantity necessary to complete
#       the bundle
#   - '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
# ================================================================
BUNDLE_DISCOUNTS = [
  {
    bundle_items: [
      {
        product_id: 1234567890987,
        quantity_needed: 1
      },
      {
        product_id: 1234567890986,
        quantity_needed: 1
      },
    ],
    discount_type: :percent,
    discount_amount: 10,
    discount_message: "Buy Product X and Product Y, get 10% off!",
  },
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# BundleSelector
#
# Finds any items that are part of the entered bundle and saves
# them.
# ================================================================
class BundleSelector
  def initialize(bundle_items)
    @bundle_items = bundle_items.reduce({}) do |acc, bundle_item|
      acc[bundle_item[:product_id]] = {
        cart_items: [],
        quantity_needed: bundle_item[:quantity_needed],
        total_quantity: 0,
      }

      acc
    end
  end

  def build(cart)
    cart.line_items.each do |line_item|
			next if line_item.line_price_changed?
      next unless @bundle_items[line_item.variant.product.id]

      @bundle_items[line_item.variant.product.id][:cart_items].push(line_item)
      @bundle_items[line_item.variant.product.id][:total_quantity] += line_item.quantity
    end

    @bundle_items
  end
end

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

    @discount_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 @discount_type == :percent
      line_item.line_price * @discount_amount
    else
      [line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
    end

    line_item.change_line_price(new_line_price, message: @discount_message)
  end
end

# ================================================================
# DiscountLoop
#
# Loops through the supplied line items and discounts the supplied
# number of items by the supplied discount.
# ================================================================
class DiscountLoop
  def initialize(discount_applicator)
    @discount_applicator = discount_applicator
  end

  def loop_items(cart, line_items, num_to_discount)
    line_items.each_with_index do |line_item|
      break if num_to_discount <= 0

      if line_item.quantity > num_to_discount
        split_line_item = line_item.split(take: num_to_discount)
        @discount_applicator.apply(split_line_item)
        position = cart.line_items.find_index(line_item)
        cart.line_items.insert(position + 1, split_line_item)
        break
      else
        @discount_applicator.apply(line_item)
        num_to_discount -= line_item.quantity
      end
    end
  end
end

# ================================================================
# BundleDiscountCampaign
#
# If the entered bundle is present, the entered discount is
# applied to each item in the bundle.
# ================================================================
class BundleDiscountCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart)
    @campaigns.each do |campaign|
      bundle_selector = BundleSelector.new(campaign[:bundle_items])
      bundle_items = bundle_selector.build(cart)

      next if bundle_items.any? do |product_id, product_info|
        product_info[:total_quantity] < product_info[:quantity_needed]
      end

      num_bundles = bundle_items.map do |product_id, product_info|
        (product_info[:total_quantity] / product_info[:quantity_needed])
      end

      num_bundles = num_bundles.min.floor

      discount_applicator = DiscountApplicator.new(
        campaign[:discount_type],
        campaign[:discount_amount],
        campaign[:discount_message]
      )

      discount_loop = DiscountLoop.new(discount_applicator)

      bundle_items.each do |product_id, product_info|
        discount_loop.loop_items(
          cart,
          product_info[:cart_items],
          (product_info[:quantity_needed] * num_bundles),
        )
      end
    end
  end
end

CAMPAIGNS = [
  BundleDiscountCampaign.new(BUNDLE_DISCOUNTS),
]

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

Output.cart = Input.cart

ایک پر ایک مفت (BOGO) ڈسکاؤنٹ

جب کوئی کسٹمر کسی دوسری پروڈکٹ کی مقررہ تعداد خریدتا ہے تو کسی آئٹم پر فیصد ڈسکاؤنٹ پیش کرنے کے لیے یہ سکرپٹ استعمال کریں۔

مثال کے طور پر، اگر کسٹمرز 2 ٹی شرٹس خریدتے ہیں تو انہیں ایک ٹوپی پر 10% ڈسکاؤنٹ پیش کریں۔

# ================================ Customizable Settings ================================
# ================================================================
# Buy V of Product W, Get X of Product Y for Z Discount
#
# Buy a certain number of matching items, get a certain number of
# a different set of matching items with the entered discount
# applied. For example:
#
#   "Buy 2 t-shirts, get 1 hat for 10% off"
#
#   - 'buy_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
#   - 'buy_product_selector_type' determines how eligible products
#     will be identified. Can be:
#       - ':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
#   - 'buy_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'.
#   - 'quantity_to_buy' is the number of products needed to
#     qualify
#   - 'get_selector_match_type' is the same idea as the "Buy"
#     version above
#   - 'get_product_selector_type' is the same idea as the "Buy"
#     version above
#   - 'get_product_selectors' is the same idea as the "Buy"
#     version above
#   - 'quantity_to_discount' is the number of products to discount
#   - 'allow_incomplete_bundle' determines whether a portion of
#     the items to discount can be discounted, or all items
#     need to be present. Can be:
#       - 'true'
#       - 'false'
#   - '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
# ================================================================
BUYVOFW_GETXOFY_FORZ = [
  {
    buy_product_selector_match_type: :include,
    buy_product_selector_type: :tag,
    buy_product_selectors: ["your_tag", "another_tag"],
    quantity_to_buy: 1,
    get_product_selector_match_type: :include,
    get_product_selector_type: :tag,
    get_product_selectors: ["your_other_tag", "a_different_tag"],
    quantity_to_discount: 1,
    allow_incomplete_bundle: false,
    discount_type: :percent,
    discount_amount: 100,
    discount_message: 'Buy a Product X, get a Product Y free!',
  },
  {
    buy_product_selector_match_type: :include,
    buy_product_selector_type: :product_id,
    buy_product_selectors: [1234567890987, 1234567890986],
    quantity_to_buy: 1,
    get_product_selector_match_type: :include,
    get_product_selector_type: :product_id,
    get_product_selectors: [1234567890985, 1234567890984],
    quantity_to_discount: 1,
    allow_incomplete_bundle: false,
    discount_type: :dollar,
    discount_amount: 10,
    discount_message: 'Buy a Product X, get $10 off a Product Y!',
  },
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
class ProductSelector
  def initialize(match_type, selector_type, selectors)
    @match_type = match_type
    @comparator = match_type == :include ? 'any?' : 'none?'
    @selector_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)
    @discount_type = discount_type
    @discount_message = discount_message

    @discount_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 @discount_type == :percent
      line_item.line_price * @discount_amount
    else
      [line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
    end

    line_item.change_line_price(new_line_price, message: @discount_message)
  end
end

# ================================================================
# DiscountLoop
#
# Loops through the supplied line items and discounts the supplied
# number of items by the supplied discount.
# ================================================================
class DiscountLoop
  def initialize(discount_applicator)
    @discount_applicator = discount_applicator
  end

  def loop_items(cart, line_items, num_to_discount)
    line_items.each do |line_item|
      break if num_to_discount <= 0

      if line_item.quantity > num_to_discount
        split_line_item = line_item.split(take: num_to_discount)
        @discount_applicator.apply(split_line_item)
        position = cart.line_items.find_index(line_item)
        cart.line_items.insert(position + 1, split_line_item)
        break
      else
        @discount_applicator.apply(line_item)
        num_to_discount -= line_item.quantity
      end
    end
  end
end

# ================================================================
# BuyVofWGetXofYForZCampaign
#
# Buy a certain number of matching items, get a certain number of
# a different set of matching items with the entered discount
# applied.
# ================================================================
class BuyVofWGetXofYForZCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart)
    @campaigns.each do |campaign|
      buy_product_selector = ProductSelector.new(
        campaign[:buy_product_selector_match_type],
        campaign[:buy_product_selector_type],
        campaign[:buy_product_selectors],
      )

      get_product_selector = ProductSelector.new(
        campaign[:get_product_selector_match_type],
        campaign[:get_product_selector_type],
        campaign[:get_product_selectors],
      )

      buy_items = []
      get_items = []

      cart.line_items.each do |line_item|
        buy_items.push(line_item) if buy_product_selector.match?(line_item)
        get_items.push(line_item) if get_product_selector.match?(line_item)
      end

      next if buy_items.empty? || get_items.empty?

      get_items = get_items.sort_by { |line_item| line_item.variant.price }
      quantity_to_buy = campaign[:quantity_to_buy]
      quantity_to_discount = campaign[:quantity_to_discount]
      buy_offers = (buy_items.map(&:quantity).reduce(0, :+) / quantity_to_buy).floor

      if campaign[:allow_incomplete_bundle]
        number_of_bundles = buy_offers
      else
        get_offers = (get_items.map(&:quantity).reduce(0, :+) / quantity_to_discount).floor
        number_of_bundles = [buy_offers, get_offers].min
      end

      number_of_discountable_items = number_of_bundles * quantity_to_discount

      next unless number_of_discountable_items > 0

      discount_applicator = DiscountApplicator.new(
        campaign[:discount_type],
        campaign[:discount_amount],
        campaign[:discount_message]
      )

      discount_loop = DiscountLoop.new(discount_applicator)
      discount_loop.loop_items(cart, get_items, number_of_discountable_items)
    end
  end
end

CAMPAIGNS = [
  BuyVofWGetXofYForZCampaign.new(BUYVOFW_GETXOFY_FORZ),
]

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

Output.cart = Input.cart

مخصوص رقم میں پروڈکٹ کی مخصوص مقدار خریدیں

متعدد پروڈکٹس کو منتخب کردہ قیمت پر پیش کرنے کے لیے یہ سکرپٹ استعمال کریں۔

مثال کے طور پر، کسٹمرز کو $20 میں 2 ٹی شرٹس پیش کریں۔

# ================================ Customizable Settings ================================
# ================================================================
# Buy X of Product Y for $Z
#
# Buy a certain number of matching items for a specific price.
# For example:
#
#   "Buy 2 t-shirts for $20"
#
#   - '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'.
#   - 'quantity_to_buy' is the number of products needed to
#     qualify
#   - 'final_price` is the amount to charge for all products that
#     are part of the offer
#   - 'discount_message' is the message to show when a discount
#     is applied
# ================================================================
BUY_X_GET_Y_FOR_Z = [
  {
    product_selector_match_type: :include,
    product_selector_type: :tag,
    product_selectors: ["your_tag"],
    quantity_to_buy: 2,
    final_price: 100,
    discount_message: 'Buy 2 for $20',
  },
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
class ProductSelector
  def initialize(match_type, selector_type, selectors)
    @match_type = match_type
    @comparator = match_type == :include ? 'any?' : 'none?'
    @selector_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

# ================================================================
# DollarDiscountApplicator
#
# Applies the entered discount to the supplied line item.
# ================================================================
class DollarDiscountApplicator
  def initialize(discount_message)
    @discount_message = discount_message
  end

  def apply(line_item, discount_amount)
    new_line_price = line_item.line_price - discount_amount
    line_item.change_line_price(new_line_price, message: @discount_message)
  end
end

# ================================================================
# BuyXOfYForZCampaign
#
# Buy a certain number of matching items for a specific price.
# ================================================================
class BuyXOfYForZCampaign
  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],
      )

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

      next if eligible_items.nil?

      eligible_item_count = eligible_items.map(&:quantity).reduce(0, :+)
      quantity_to_buy = campaign[:quantity_to_buy]
      number_of_offers = (eligible_item_count / quantity_to_buy).floor

      next unless number_of_offers > 0

      number_of_discountable_items = number_of_offers * quantity_to_buy
      total_offer_price = Money.new(cents: 100) * (number_of_offers * campaign[:final_price])
      discount_applicator = DollarDiscountApplicator.new(campaign[:discount_message])

      self.loop_items(cart, eligible_items, number_of_discountable_items, total_offer_price, discount_applicator)
    end
  end

  def loop_items(cart, line_items, num_to_discount, total_price, discount_applicator)
    current_price = Money.zero
    avg_price = total_price * (1 / num_to_discount)

    line_items = line_items.sort_by { |line_item| line_item.variant.price }

    line_items.each do |line_item|
      break if num_to_discount <= 0

      if line_item.quantity > num_to_discount
        split_line_item = line_item.split(take: num_to_discount)
        discount_amount = split_line_item.line_price - (total_price - current_price)
        discount_applicator.apply(split_line_item, discount_amount)
        position = cart.line_items.find_index(line_item)
        cart.line_items.insert(position + 1, split_line_item)
        break
      elsif line_item.quantity == num_to_discount
        discount_amount = line_item.line_price - (total_price - current_price)
        discount_applicator.apply(line_item, discount_amount)
        break
      else
        if line_item.variant.price <= avg_price
          current_price += line_item.line_price
        else
          discount_amount = (line_item.variant.price - avg_price) * line_item.quantity
          current_price += (line_item.line_price - discount_amount)
          discount_applicator.apply(line_item, discount_amount)
        end

        num_to_discount -= line_item.quantity
      end
    end
  end
end

CAMPAIGNS = [
  BuyXOfYForZCampaign.new(BUY_X_GET_Y_FOR_Z),
]

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

Output.cart = Input.cart

خریداری کے ساتھ مفت تحفہ

اگر کارٹ کا کل بل ایک مخصوص رقم سے زیادہ ہو تو مخصوص پروڈکٹ پر ڈسکاؤنٹ پیش کرنے کے لیے یہ سکرپٹ استعمال کریں۔

مثال کے طور پر، اگر کسٹمرز $75 سے زیادہ خرچ کرتے ہیں تو انہیں ایک مفت تحفہ پیش کریں۔

# ================================ Customizable Settings ================================
# ================================================================
# Spend $X, get Product Y for Z Discount
#
# If the cart total is greater than (or equal to) the entered
# threshold (less the discounted amount), the entered number of
# matching items is discounted by the entered amount.
#
#   - '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'.
#   - 'threshold' is the dollar amount needed to spend to qualify
#   - 'quantity_to_discount' is the number of items to discount
#     if qualified
#   - '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
# ================================================================
SPENDX_GETY_FORZ = [
  {
    product_selector_match_type: :include,
    product_selector_type: :product_id,
    product_selectors: [1234567890123],
    threshold: 75,
    quantity_to_discount: 1,
    discount_type: :percent,
    discount_amount: 100,
    discount_message: 'Spend $75 or more and get a free Product X!',
  },
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
class ProductSelector
  def initialize(match_type, selector_type, selectors)
    @match_type = match_type
    @comparator = match_type == :include ? 'any?' : 'none?'
    @selector_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)
    @discount_type = discount_type
    @discount_message = discount_message

    @discount_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 @discount_type == :percent
      line_item.line_price * @discount_amount
    else
      [line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
    end

    line_item.change_line_price(new_line_price, message: @discount_message)
  end
end

# ================================================================
# DiscountLoop
#
# Loops through the supplied line items and discounts the supplied
# number of items by the supplied discount.
# ================================================================
class DiscountLoop
  def initialize(discount_applicator)
    @discount_applicator = discount_applicator
  end

  def loop_items(cart, line_items, num_to_discount)
    line_items.each do |line_item|
      break if num_to_discount <= 0

      if line_item.quantity > num_to_discount
        split_line_item = line_item.split(take: num_to_discount)
        @discount_applicator.apply(split_line_item)
        position = cart.line_items.find_index(line_item)
        cart.line_items.insert(position + 1, split_line_item)
        break
      else
        @discount_applicator.apply(line_item)
        num_to_discount -= line_item.quantity
      end
    end
  end
end

# ================================================================
# SpendXGetYForZCampaign
#
# If the cart total is greater than (or equal to) the entered
# threshold (less the discounted amount), the entered number of
# matching items is discounted by the entered amount.
# ================================================================
class SpendXGetYForZCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart)
    @campaigns.each do |campaign|
      threshold = Money.new(cents: 100) * campaign[:threshold]

      next if cart.subtotal_price < threshold

      product_selector = ProductSelector.new(
        campaign[:product_selector_match_type],
        campaign[:product_selector_type],
        campaign[:product_selectors],
      )

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

      next if eligible_items.nil?

      eligible_items = eligible_items.sort_by { |line_item| line_item.variant.price }
      num_to_discount = campaign[:quantity_to_discount]
      cart_total = cart.subtotal_price

      eligible_items.each do |line_item|
        break if num_to_discount <= 0

        if line_item.quantity > num_to_discount
          cart_total -= line_item.variant.price * num_to_discount
          break
        else
          cart_total -= line_item.line_price
          num_to_discount -= line_item.quantity
        end
      end

      next if cart_total < threshold

      discount_applicator = discount_applicator = DiscountApplicator.new(
        campaign[:discount_type],
        campaign[:discount_amount],
        campaign[:discount_message]
      )

      discount_loop = DiscountLoop.new(discount_applicator)
      discount_loop.loop_items(cart, eligible_items, campaign[:quantity_to_discount])
    end
  end
end

CAMPAIGNS = [
  SpendXGetYForZCampaign.new(SPENDX_GETY_FORZ),
]

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

Output.cart = Input.cart

کسٹمر ٹیگ کے لحاظ سے پروڈکٹ ڈسکاؤنٹ

مخصوص ٹیگز والے کسٹمرز کو مخصوص پروڈکٹس پر ڈسکاؤنٹ پیش کرنے کے لیے یہ سکرپٹ استعمال کریں۔

مثال کے طور پر، اگر کسٹمر کے پاس VIP ٹیگ ہے تو 20% ڈسکاؤنٹ پیش کریں۔

# ================================ Customizable Settings ================================
# ================================================================
# Product Discounts by Customer Tag
#
# If we have a matching customer (by tag), the entered discount
# will be applied to any matching items.
#
#   - 'customer_tag_match_type' determines whether we look for the customer
#     to be tagged with any of the entered tags or not. Can be:
#       - ':include' to check if the customer is tagged
#       - ':exclude' to make sure the customer isn't tagged
#   - 'customer_tags' is a list of tags to identify qualified
#     customers
#   - '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'.
#   - '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
# ================================================================
DISCOUNTS_FOR_CUSTOMER_TAG = [
  {
    customer_tag_match_type: :include,
    customer_tags: ["VIP"],
    product_selector_match_type: :include,
    product_selector_type: :all,
    product_selectors: nil,
    discount_type: :percent,
    discount_amount: 20,
    discount_message: "Discount for VIP customers!",
  },
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# CustomerTagSelector
#
# Finds whether the supplied customer has any of the entered tags.
# ================================================================
class CustomerTagSelector
  def initialize(match_type, tags)
    @comparator = match_type == :include ? 'any?' : 'none?'
    @tags = tags.map { |tag| tag.downcase.strip }
  end

  def match?(customer)
    customer_tags = customer.tags.map { |tag| tag.downcase.strip }
    (@tags & customer_tags).send(@comparator)
  end
end

# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
class ProductSelector
  def initialize(match_type, selector_type, selectors)
    @match_type = match_type
    @comparator = match_type == :include ? 'any?' : 'none?'
    @selector_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)
    @discount_type = discount_type
    @discount_message = discount_message

    @discount_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 @discount_type == :percent
      line_item.line_price * @discount_amount
    else
      [line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
    end

    line_item.change_line_price(new_line_price, message: @discount_message)
  end
end

# ================================================================
# DiscountForCustomerTagCampaign
#
# If we have a matching customer (by tag), the entered discount
# is applied to any matching items.
# ================================================================
class DiscountForCustomerTagCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart)
    return unless cart.customer&.tags

    @campaigns.each do |campaign|
      customer_tag_selector = CustomerTagSelector.new(campaign[:customer_tag_match_type], campaign[:customer_tags])

      next unless customer_tag_selector.match?(cart.customer)

      product_selector = ProductSelector.new(
        campaign[:product_selector_match_type],
        campaign[:product_selector_type],
        campaign[:product_selectors]
      )

      discount_applicator = DiscountApplicator.new(
        campaign[:discount_type],
        campaign[:discount_amount],
        campaign[:discount_message]
      )

      cart.line_items.each do |line_item|
        next unless product_selector.match?(line_item)
        discount_applicator.apply(line_item)
      end
    end
  end
end

CAMPAIGNS = [
  DiscountForCustomerTagCampaign.new(DISCOUNTS_FOR_CUSTOMER_TAG),
]

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

Output.cart = Input.cart

کسٹمر مارکیٹنگ کے لحاظ سے پروڈکٹ ڈسکاؤنٹ

مارکیٹنگ قبول کرنے والے کسٹمرز کو مخصوص پروڈکٹس پر ڈسکاؤنٹ پیش کرنے کے لیے یہ سکرپٹ استعمال کریں۔

مثال کے طور پر، مارکیٹنگ قبول کرنے والے کسی بھی کسٹمر کے لیے تمام پروڈکٹس پر 10% ڈسکاؤنٹ پیش کریں۔

# ================================ Customizable Settings ================================
# ================================================================
# Product Discount by Customer Marketing
#
# If the customer accepts marketing, any matching  items are
# discounted by the entered amount.
#
#   - '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'.
#   - '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_DISCOUNTS_BY_CUSTOMER_MARKETING = [
  {
    product_selector_match_type: :include,
    product_selector_type: :all,
    product_selectors: nil,
    discount_type: :percent,
    discount_amount: 10,
    discount_message: '10% off for subscribed customers!'
  }
]

# ================================ Script Code (do not edit) ===============================
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
class ProductSelector
  def initialize(match_type, selector_type, selectors)
    @match_type = match_type
    @comparator = match_type == :include ? 'any?' : 'none?'
    @selector_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)
    @discount_type = discount_type
    @discount_message = discount_message

    @discount_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 @discount_type == :percent
      line_item.line_price * @discount_amount
    else
      [line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
    end

    line_item.change_line_price(new_line_price, message: @discount_message)
  end
end

# ================================================================
# ProductDiscountByCustomerMarketingCampaign
#
# If the customer accepts marketing, any matching items are
# discounted by the entered amount.
# ================================================================
class ProductDiscountByCustomerMarketingCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart)
    return if cart.customer.nil?

    @campaigns.each do |campaign|
      next unless cart.customer.accepts_marketing?

      product_selector = ProductSelector.new(
        campaign[:product_selector_match_type],
        campaign[:product_selector_type],
        campaign[:product_selectors]
      )

      discount_applicator = DiscountApplicator.new(
        campaign[:discount_type],
        campaign[:discount_amount],
        campaign[:discount_message]
      )

      cart.line_items.each do |line_item|
        next unless product_selector.match?(line_item)
        discount_applicator.apply(line_item)
      end
    end
  end
end

CAMPAIGNS = [
  ProductDiscountByCustomerMarketingCampaign.new(PRODUCT_DISCOUNTS_BY_CUSTOMER_MARKETING),
]

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

Output.cart = Input.cart

کسٹمر کے آرڈرز کی تعداد کے لحاظ سے پروڈکٹ ڈسکاؤنٹ

آرڈرز کی مخصوص تعداد رکھنے والے کسٹمرز کے لیے مخصوص پروڈکٹس پر ڈسکاؤنٹ پیش کرنے کے لیے یہ سکرپٹ استعمال کریں۔

مثال کے طور پر، کسی بھی ایسے کسٹمر کے لیے 10% ڈسکاؤنٹ پیش کریں جس کا کوئی آرڈر نہ ہو۔

# ================================ Customizable Settings ================================
# ================================================================
# Product Discount by Order Count
#
# If the customer has made a matching number of orders, any
# matching items are discounted by the entered amount.
#
#   - 'order_count_match_type' determines how we compare the
#     customer's order count to the entered limit. Can be:
#       - ':greater_than' to ensure that the customer's order count
#         is greater than the entered limit
#       - ':greater_than_equal' to ensure that the customer's order
#         count is greater than, or equal to, the entered limit
#       - ':less_than' to ensure that the customer's order count is
#         less than the entered limit
#       - ':less_than_equal' to ensure that the customer's order
#         count is less than, or equal to, the entered limit
#   - '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'.
#   - '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_DISCOUNTS_BY_ORDER_COUNT = [
  {
    order_count_match_type: :less_than,
    order_count_limit: 1,
    product_selector_match_type: :include,
    product_selector_type: :all,
    product_selectors: nil,
    discount_type: :percent,
    discount_amount: 10,
    discount_message: '10% off for first time customers!'
  }
]

# ================================ Script Code (do not edit) ===============================
# ================================================================
# OrderCountSelector
#
# Finds whether the customer has made a certain number of orders
# ================================================================
class OrderCountSelector
  def initialize(match_type, limit)
    @match_type = match_type
    @limit = limit
  end

  def match?(customer)
    if self.respond_to?(@match_type)
      self.send(@match_type, customer, @limit)
    else
      raise RuntimeError.new('Invalid order count match type')
    end
  end

  def greater_than(customer, limit)
    customer.orders_count > limit
  end

  def greater_than_equal(customer, limit)
    customer.orders_count >= limit
  end

  def less_than(customer, limit)
    customer.orders_count < limit
  end

  def less_than_equal(customer, limit)
    customer.orders_count <= limit
  end
end

# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
class ProductSelector
  def initialize(match_type, selector_type, selectors)
    @match_type = match_type
    @comparator = match_type == :include ? 'any?' : 'none?'
    @selector_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)
    @discount_type = discount_type
    @discount_message = discount_message

    @discount_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 @discount_type == :percent
      line_item.line_price * @discount_amount
    else
      [line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
    end

    line_item.change_line_price(new_line_price, message: @discount_message)
  end
end

# ================================================================
# ProductDiscountByOrderCountCampaign
#
# If the customer has made a matching number of orders, any
# matching items are discounted by the entered amount.
# ================================================================
class ProductDiscountByOrderCountCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart)
    return if cart.customer.nil?

    @campaigns.each do |campaign|
      order_count_selector = OrderCountSelector.new(
        campaign[:order_count_match_type],
        campaign[:order_count_limit]
      )

      next unless order_count_selector.match?(cart.customer)

      product_selector = ProductSelector.new(
        campaign[:product_selector_match_type],
        campaign[:product_selector_type],
        campaign[:product_selectors]
      )

      discount_applicator = DiscountApplicator.new(
        campaign[:discount_type],
        campaign[:discount_amount],
        campaign[:discount_message]
      )

      cart.line_items.each do |line_item|
        next unless product_selector.match?(line_item)
        discount_applicator.apply(line_item)
      end
    end
  end
end

CAMPAIGNS = [
  ProductDiscountByOrderCountCampaign.new(PRODUCT_DISCOUNTS_BY_ORDER_COUNT),
]

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

Output.cart = Input.cart

ڈسکاؤنٹ کوڈز غیر فعال کریں

چیک آؤٹ کے عمل میں ڈسکاؤنٹ کوڈز کا استعمال غیر فعال کرنے کے لیے یہ سکرپٹ استعمال کریں۔

مثال کے طور پر، جب آپ کے سٹور پر سیل لگی ہو تو کسٹمرز کو ڈسکاؤنٹ کوڈز استعمال کرنے سے روکیں۔

# ================================ Customizable Settings ================================
# ================================================================
# Disable Discount Code Use
#
# Any discount codes will be rejected with the entered message.
#
#   - 'REJECTION_MESSAGE' is the message to show when a discount
#     code is rejected
# ================================================================
REJECTION_MESSAGE = 'Discount codes cannot be used during this sale'

# ================================ Script Code (do not edit) ================================
# ================================================================
# DisableDiscountCodesCampaign
#
# Any discount codes will be rejected with the entered message.
# ================================================================
class DisableDiscountCodesCampaign
  def initialize(rejection_message)
    @rejection_message = rejection_message
  end

  def run(cart)
    return if cart.discount_code.nil?

    cart.discount_code.reject(message: @rejection_message)
  end
end

CAMPAIGNS = [
  DisableDiscountCodesCampaign.new(REJECTION_MESSAGE),
]

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

Output.cart = Input.cart

پروڈکٹ کے لیے ڈسکاؤنٹ کوڈز غیر فعال کریں

اگر کارٹ میں مخصوص پروڈکٹس موجود ہوں تو چیک آؤٹ کے عمل میں ڈسکاؤنٹ کوڈز کا استعمال غیر فعال کرنے کے لیے یہ سکرپٹ استعمال کریں۔

مثال کے طور پر، اگر discounted کے ٹیگ والی پروڈکٹ کارٹ میں موجود ہو تو کسٹمرز کو ڈسکاؤنٹ کوڈز استعمال کرنے سے روکیں۔

# ================================ Customizable Settings ================================
# ================================================================
# Disable Discount Code(s) For Products
#
# If any matching discount codes are used, and any matching items
# are in the cart, the discount code is rejected with the entered
# message.
#
#   - 'discount_code_match_type' determines whether the below
#     strings should be an exact or partial match. Can be:
#       - ':exact' for an exact match
#       - ':partial' for a partial match
#   - 'discount_codes' is a list of strings to identify discount
#     codes
#   - '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'.
#   - 'rejection_message' is the message to show when a discount
#     code is rejected
# ================================================================
REJECT_DISCOUNT_CODE_FOR_PRODUCTS = [
  {
    discount_code_match_type: :exact,
    discount_codes: ["TESTCODE1", "TESTCODE2"],
    product_selector_match_type: :include,
    product_selector_type: :tag,
    product_selectors: ["discounted"],
    rejection_message: "Discount codes can't be used with 'discounted' products"
  }
]

# ================================ Script Code (do not edit) ================================
# ================================================================
# DiscountCodeSelector
#
# Finds whether the supplied discount code matches any of the
# entered codes.
# ================================================================
class DiscountCodeSelector
  def initialize(match_type, discount_codes)
    @comparator = match_type == :exact ? '==' : 'include?'
    @discount_codes = discount_codes.map { |discount_code| discount_code.upcase.strip }
  end

  def match?(discount_code)
    @discount_codes.any? { |code| discount_code.code.upcase.send(@comparator, code) }
  end
end

# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
class ProductSelector
  def initialize(match_type, selector_type, selectors)
    @match_type = match_type
    @comparator = match_type == :include ? 'any?' : 'none?'
    @selector_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

# ================================================================
# DisableDiscountCodesForProductsCampaign
#
# If any matching discount codes are used, and any matching items
# are in the cart, the discount code is rejected with the entered
# message.
# ================================================================
class DisableDiscountCodesForProductsCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart)
    return if cart.discount_code.nil?

    @campaigns.each do |campaign|
      discount_code_selector = DiscountCodeSelector.new(
        campaign[:discount_code_match_type],
        campaign[:discount_codes]
      )

      next unless discount_code_selector.match?(cart.discount_code)

      product_selector = ProductSelector.new(
        campaign[:product_selector_match_type],
        campaign[:product_selector_type],
        campaign[:product_selectors],
      )

      next unless cart.line_items.any? { |line_item| product_selector.match?(line_item) }

      cart.discount_code.reject(message: campaign[:rejection_message])
    end
  end
end

CAMPAIGNS = [
  DisableDiscountCodesForProductsCampaign.new(REJECT_DISCOUNT_CODE_FOR_PRODUCTS),
]

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

Output.cart = Input.cart

پروڈکٹ کی مقداروں کو محدود کریں

مخصوص پروڈکٹس پر مقدار کی حدیں لاگو کرنے کے لیے یہ سکرپٹ استعمال کریں۔

مثال کے طور پر، کسٹمرز کو ایک ہی آرڈر میں 1 'پروڈکٹ X' سے زیادہ خریدنے سے روکیں۔

# ================================ Customizable Settings ================================
# ================================================================
# Product Quantity Limits
#
# If the quantity of any matching items is greater than the
# entered threshold, the excess items are removed from the cart.
# It should be noted that there will be no notice to the customer
# when this happens.
#
#   - 'enable' determines whether the campaign will run. Can be:
#       - 'true' to run
#       - 'false' to not run
#   - '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'.
#   - 'variant_level_limit' determines whether the below limit
#     is applied on a variant, or a total quantity, level. For
#     example, can I have X number of individual matching items,
#     or can I only have X number total of matching items?
#     Can be:
#       - 'true' to limit at a variant level
#       - 'false' to limit total quantity
#   - 'quantity_allowed' is the number of products allowed
# ================================================================
QUANTITY_LIMITS = {
  enable: true,
  campaigns: [
    {
      product_selector_match_type: :include,
      product_selector_type: :tag,
      product_selectors: ["limited"],
      variant_level_limit: true,
      quantity_allowed: 2,
    },
  ]
}

# ================================ Script Code (do not edit) ================================
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
class ProductSelector
  def initialize(match_type, selector_type, selectors)
    @match_type = match_type
    @comparator = match_type == :include ? 'any?' : 'none?'
    @selector_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

# ================================================================
# ProductQuantityLimitCampaign
#
# If the quantity of any matching items is greater than the
# entered threshold, the excess items are removed from the cart.
# ================================================================
class ProductQuantityLimitCampaign
  def initialize(enable, campaigns)
    @enable = enable
    @campaigns = campaigns
  end

  def run(cart)
    return unless @enable

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

      if campaign[:variant_level_limit]
        applicable_items = {}

        cart.line_items.each do |line_item|
          next unless product_selector.match?(line_item)

          id = line_item.variant.id

          if applicable_items[id].nil?
            applicable_items[id] = {
              items: [],
              total_quantity: 0
            }
          end

          applicable_items[id][:items].push(line_item)
          applicable_items[id][:total_quantity] += line_item.quantity
        end

        next if applicable_items.nil?

        applicable_items.each do |id, info|
          next unless info[:total_quantity] > campaign[:quantity_allowed]

          num_to_remove = info[:total_quantity] - campaign[:quantity_allowed]
          self.loop_items(cart, info[:items], num_to_remove)
        end
      else
        applicable_items = cart.line_items.select { |line_item| product_selector.match?(line_item) }

        next if applicable_items.nil?

        total_quantity = applicable_items.map(&:quantity).reduce(0, :+)

        next unless total_quantity > campaign[:quantity_allowed]

        num_to_remove = total_quantity - campaign[:quantity_allowed]
        self.loop_items(cart, applicable_items, num_to_remove)
      end
    end
  end

  def loop_items(cart, line_items, num_to_remove)
    line_items.each do |line_item|
      if line_item.quantity > num_to_remove
        split_line_item = line_item.split(take: num_to_remove)
        break
      else
        index = cart.line_items.find_index(line_item)
        cart.line_items.delete_at(index)
        num_to_remove -= line_item.quantity
      end

      break if num_to_remove <= 0
    end
  end
end

CAMPAIGNS = [
  ProductQuantityLimitCampaign.new(
    QUANTITY_LIMITS[:enable],
    QUANTITY_LIMITS[:campaigns],
  ),
]

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

Output.cart = Input.cart