Eksempler på varelinjescripts

Varelinjescripts påvirker varer i en indkøbskurv og kan ændre deres egenskaber og priser. Disse scripts kører, hver gang en egenskab for en indkøbskurv ændres, f.eks. hvis der tilføjes eller fjernes en vare, hvis leveringsadressen ændres, eller hvis der tilføjes en rabatkode.

Hvis du vil bruge skabelonerne på denne side, skal du oprette et nyt script med en tom skabelon.

Fremgangsmåde:

  1. Gå til Apps > Script Editor i din Shopify-administrator.
  2. Klik på Opret script.
  3. Klik på Varelinjer.
  4. Vælg Tom skabelon, og klik derefter på Opret script.
  5. I afsnittet Ruby-kildekode skal du slette standardkodelinjen: Output.cart = Input.cart
  6. Kopiér et script fra denne side, og indsæt det i afsnittet Ruby-kildekode.
  7. Rediger afsnittet Indstillinger, der kan tilpasses for det script, der skal fungere i din butik.
  8. Test dit script. Du kan få mere at vide i Test og fejlfinding af Shopify-scripts.
  9. Efter testen: - Klik på Gem kladde for at gemme en ikke udgivet kladde af scriptet, eller
    • Klik på Gem og udgiv for at oprette og udgive scriptet.

Differentieret rabat på indkøbskurv efter forbrug

Brug dette script til at tilbyde en rabat på et beløb, som stiger i forhold til den samlede værdi af varer i deres indkøbskurv. Denne rabat spredes så jævnt som muligt ud over de individuelle varer i indkøbskurven.

Du kan f.eks. bruge dette script til at tilbyde kunder en rabat på 25 USD på deres samlede køb, hvis de bruger mere end 150 USD, 50 USD i rabat, hvis de bruger mere end 300 USD, eller 75 USD i rabat, hvis de bruger mere end 400 USD.

# ================================ 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 the cart. The
# discount will be spread, as evenly as possible, across all line
# 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

# ================================================================
# TieredDiscountBySpendCampaign
#
# 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 line
# items.
# ================================================================
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 = 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 = [
  TieredDiscountBySpendCampaign.new(SPENDING_THRESHOLDS),
]

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

Output.cart = Input.cart

Differentieret rabat efter forbrug

Brug dette script til at tilbyde en procentrabat, der stiger i forhold til den samlede værdi af varerne i deres indkøbskurv.

Du kan f.eks. bruge dette script til at tilbyde kunder 10 % rabat, hvis de bruger 30 USD eller mere, 15 % rabat, hvis de bruger 50 USD eller mere, eller 20 % rabat, hvis de bruger 100 USD eller mere.

# ================================ 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 in
# the cart.
#
# - '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 in
# the cart.
# ================================================================
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

Differentieret rabat efter antal

Brug dette script til at tilbyde en mængderabat på et bestemt produkt med en procentrabat, der stiger i forhold til antallet af disse produkter i indkøbskurven.

Du kan f.eks. bruge dette script til at tilbyde kunder 10 % rabat på deres ordre, hvis de køber 2 hatte eller flere, eller 15 % rabat, hvis de køber 5 hatte eller flere.

# ================================ Customizable Settings ================================
# ================================================================
# Tiered 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
# - ':all' for all products
# - 'product_selector' 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 @selector_type == :tag
      product_tags = line_item.variant.product.tags.map { |tag| tag.downcase.strip }
      @selectors = @selectors.map { |selector| selector.downcase.strip }
      (@selectors & product_tags).send(@comparator)
    elsif @selector_type == :type
      @selectors = @selectors.map { |selector| selector.downcase.strip }
      (@match_type == :include) == @selectors.include?(line_item.variant.product.product_type.downcase.strip)
    elsif @selector_type == :vendor
      @selectors = @selectors.map { |selector| selector.downcase.strip }
      (@match_type == :include) == @selectors.include?(line_item.variant.product.vendor.downcase.strip)
    elsif @selector_type == :product_id
      (@match_type == :include) == @selectors.include?(line_item.variant.product.id)
    elsif @selector_type == :variant_id
      (@match_type == :include) == @selectors.include?(line_item.variant.id)
    elsif @selector_type == :all
      true
    else
      raise RuntimeError.new('Invalid product selector type')
    end
  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

# ================================================================
# TieredPricingCampaign
#
# 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 TieredPricingCampaign
  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 = [
  TieredPricingCampaign.new(PRODUCT_DISCOUNT_TIERS),
]

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

Output.cart = Input.cart

Differentieret rabat efter produktforbrug

Brug dette script til at tilbyde en procentrabat, der øges i forhold til den samlede værdi af bestemte varer i indkøbskurven.

Du kan f.eks. bruge dette script til at tilbyde kunder 10 % rabat, hvis de bruger 30 USD eller mere, 15 % rabat, hvis de bruger 50 USD eller mere, og 20 % rabat, hvis de bruger 100 USD eller mere, men kun på varer, der matcher et bestemt tag.

# ================================ Customizable Settings ================================
# ================================================================
# Tiered Discount by 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
# - 'product_selectors' is a list of identifiers (from above) for
# qualifying products. Product/Variant ID lists should only
# contain numbers (ie. no quotes).
# - '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 @selector_type == :tag
      product_tags = line_item.variant.product.tags.map { |tag| tag.downcase.strip }
      @selectors = @selectors.map { |selector| selector.downcase.strip }
      (@selectors & product_tags).send(@comparator)
    elsif @selector_type == :type
      @selectors = @selectors.map { |selector| selector.downcase.strip }
      (@match_type == :include) == @selectors.include?(line_item.variant.product.product_type.downcase.strip)
    elsif @selector_type == :vendor
      @selectors = @selectors.map { |selector| selector.downcase.strip }
      (@match_type == :include) == @selectors.include?(line_item.variant.product.vendor.downcase.strip)
    elsif @selector_type == :product_id
      (@match_type == :include) == @selectors.include?(line_item.variant.product.id)
    elsif @selector_type == :variant_id
      (@match_type == :include) == @selectors.include?(line_item.variant.id)
    else
      raise RuntimeError.new('Invalid product selector type')
    end
  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

# ================================================================
# TieredPricingCampaign
#
# 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 TieredPricingCampaign
  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],
      )

      tiers = campaign[:tiers].sort_by { |tier| tier[:threshold] }.reverse
      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, :+)
      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 = [
  TieredPricingCampaign.new(PRODUCT_DISCOUNT_TIERS),
]

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

Output.cart = Input.cart

Tagget produktrabat

Brug dette script til at tilbyde en rabat på varer med bestemte tags.

Du kan f.eks. bruge dette script til at tilbyde kunder 10 % rabat på varer, der er tagget med discounted.

# ================================ Customizable Settings ================================
# ================================================================
# Discount by Product Tag
#
# Any product that is tagged (or not, depending on the setting
# below) with the entered tag(s) will be discounted by the entered
# amount.
#
# - 'product_tag_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_tags' is a list of product tags for qualified
# products
# - '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_TAG_DISCOUNTS = [
  {
    product_tag_match_type: :include,
    product_tags: ["your_tag", "another_tag"],
    discount_type: :percent,
    discount_amount: 10,
    discount_message: '10% off tagged products!'
  }
]

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

  def match?(line_item)
    product_tags = line_item.variant.product.tags.map { |tag| tag.downcase.strip }
    (product_tags & @tags).send(@comparator)
  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

# ================================================================
# ProductTagDiscountCampaign
#
# Any "matching" product (by tag) will be discounted by the entered
# amount.
# ================================================================
class ProductTagDiscountCampaign
  def initialize(discounts)
    @discounts = discounts
  end

  def run(cart)
    @discounts.each do |discount|
      product_tag_selector = ProductTagSelector.new(
        discount[:product_tag_match_type],
        discount[:product_tags]
      )

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

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

CAMPAIGNS = [
  ProductTagDiscountCampaign.new(PRODUCT_TAG_DISCOUNTS),
]

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

Output.cart = Input.cart

Taggede kunderabatter

Brug dette script til at tilbyde en rabat til kunder med bestemte tags.

Du kan f.eks. bruge dette script til at tilbyde 20 % rabat, hvis kunden har tagget VIP.

# ================================ Customizable Settings ================================
# ================================================================
# Discounts by Customer Tag
#
# If a customer is tagged (or not, depending on the setting below)
# with the entered tag(s), the entered discount will be applied to
# each item in the cart.
#
# - '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
# - '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"],
    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

# ================================================================
# 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.
# ================================================================
class DiscountForCustomerTagCampaign
  def initialize(discounts)
    @discounts = discounts
  end

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

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

      next unless customer_tag_selector.match?(cart.customer)

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

      cart.line_items.each do |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

Køb en bestemt mængde af et produkt, og få en anden mængde med rabat.

Brug dette script til at tilbyde en rabat på en bestemt vare, hvis der købes mere end en bestemt mængde.

Du kan f.eks. bruge dette script til at give kunderne mulighed for at købe tre varer, som er tagget med discount, og modtage 50 % rabat på en fjerde vare, der er tagget med discount.

# ================================ 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
# - This should only be a number (ie. no quotes)
# - ':variant_id' to find products by variant ID
# - This should only be a number (ie. no quotes)
# - ':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 @selector_type == :tag
      product_tags = line_item.variant.product.tags.map { |tag| tag.downcase.strip }
      @selectors = @selectors.map { |selector| selector.downcase.strip }
      (@selectors & product_tags).send(@comparator)
    elsif @selector_type == :type
      @selectors = @selectors.map { |selector| selector.downcase.strip }
      (@match_type == :include) == @selectors.include?(line_item.variant.product.product_type.downcase.strip)
    elsif @selector_type == :vendor
      @selectors = @selectors.map { |selector| selector.downcase.strip }
      (@match_type == :include) == @selectors.include?(line_item.variant.product.vendor.downcase.strip)
    elsif @selector_type == :product_id
      (@match_type == :include) == @selectors.include?(line_item.variant.product.id)
    elsif @selector_type == :variant_id
      (@match_type == :include) == @selectors.include?(line_item.variant.id)
    elsif @selector_type == :all
      true
    else
      raise RuntimeError.new('Invalid product selector type')
    end
  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

Køb et bundt, og få et ekstra produkt med rabat

Brug dette script til at tilbyde en rabat på en bestemt vare, hvis kunden har købt en række andre varer.

Du kan f.eks. bruge dette script til at tilbyde kunder et gratis par strømper, hvis de også køber en hat, en T-shirt og et par solbriller.

# ================================ Customizable Settings ================================
# ================================================================
# Buy Products VWX, get Product Y for Z Discount
#
# Buy a specific bundle of products, get another product 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
# - ':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 @selector_type == :tag
      product_tags = line_item.variant.product.tags.map { |tag| tag.downcase.strip }
      @selectors = @selectors.map { |selector| selector.downcase.strip }
      (@selectors & product_tags).send(@comparator)
    elsif @selector_type == :type
      @selectors = @selectors.map { |selector| selector.downcase.strip }
      (@match_type == :include) == @selectors.include?(line_item.variant.product.product_type.downcase.strip)
    elsif @selector_type == :vendor
      @selectors = @selectors.map { |selector| selector.downcase.strip }
      (@match_type == :include) == @selectors.include?(line_item.variant.product.vendor.downcase.strip)
    elsif @selector_type == :product_id
      (@match_type == :include) == @selectors.include?(line_item.variant.product.id)
    elsif @selector_type == :variant_id
      (@match_type == :include) == @selectors.include?(line_item.variant.id)
    elsif @selector_type == :all
      true
    else
      raise RuntimeError.new('Invalid product selector type')
    end
  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

Pakkerabat

Brug dette script til at tilbyde en rabat, når der lægges et bestemt sæt produkter i indkøbskurven.

Du kan f.eks. bruge dette script til at tilbyde kunder 20 % rabat på købet af en T-shirt, en hat og et par solbriller.

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

Køb én og få én gratis (BOGO)-rabat

Brug dette script til at tilbyde en procentrabat på en vare, når en kunde køber et bestemt antal af et andet produkt.

Du kan f.eks. bruge dette script til at tilbyde kunder 10 % rabat på en hat, hvis de køber 2 T-shirts.

# ================================ 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
# - ':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: :id,
    buy_product_selectors: [1234567890987, 1234567890986],
    quantity_to_buy: 1,
    get_product_selector_match_type: :include,
    get_product_selector_type: :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 @selector_type == :tag
      product_tags = line_item.variant.product.tags.map { |tag| tag.downcase.strip }
      @selectors = @selectors.map { |selector| selector.downcase.strip }
      (@selectors & product_tags).send(@comparator)
    elsif @selector_type == :type
      @selectors = @selectors.map { |selector| selector.downcase.strip }
      (@match_type == :include) == @selectors.include?(line_item.variant.product.product_type.downcase.strip)
    elsif @selector_type == :vendor
      @selectors = @selectors.map { |selector| selector.downcase.strip }
      (@match_type == :include) == @selectors.include?(line_item.variant.product.vendor.downcase.strip)
    elsif @selector_type == :product_id
      (@match_type == :include) == @selectors.include?(line_item.variant.product.id)
    elsif @selector_type == :variant_id
      (@match_type == :include) == @selectors.include?(line_item.variant.id)
    elsif @selector_type == :all
      true
    else
      raise RuntimeError.new('Invalid product selector type')
    end
  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

Køb et bestemt antal af et produkt for et bestemt beløb

Brug dette script til at tilbyde en række produkter til en bestemt pris.

Du kan f.eks. bruge dette script til at tilbyde kunderne 2 T-shirts for 20 USD.

# ================================ 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
# - ':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 @selector_type == :tag
      product_tags = line_item.variant.product.tags.map { |tag| tag.downcase.strip }
      @selectors = @selectors.map { |selector| selector.downcase.strip }
      (@selectors & product_tags).send(@comparator)
    elsif @selector_type == :type
      @selectors = @selectors.map { |selector| selector.downcase.strip }
      (@match_type == :include) == @selectors.include?(line_item.variant.product.product_type.downcase.strip)
    elsif @selector_type == :vendor
      @selectors = @selectors.map { |selector| selector.downcase.strip }
      (@match_type == :include) == @selectors.include?(line_item.variant.product.vendor.downcase.strip)
    elsif @selector_type == :product_id
      (@match_type == :include) == @selectors.include?(line_item.variant.product.id)
    elsif @selector_type == :variant_id
      (@match_type == :include) == @selectors.include?(line_item.variant.id)
    elsif @selector_type == :all
      true
    else
      raise RuntimeError.new('Invalid product selector type')
    end
  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

Gratis gave ved køb

Brug dette script til at tilbyde en rabat på et bestemt produkt, hvis indkøbskurvens samlede pris er større end et bestemt beløb.

Du kan f.eks. bruge dette script til at tilbyde kunder en gratis gave, hvis de bruger mere end 75 USD.

# ================================ 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
# - ':all' for all products
# - 'product_selector' 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 @selector_type == :tag
      product_tags = line_item.variant.product.tags.map { |tag| tag.downcase.strip }
      @selectors = @selectors.map { |selector| selector.downcase.strip }
      (@selectors & product_tags).send(@comparator)
    elsif @selector_type == :type
      @selectors = @selectors.map { |selector| selector.downcase.strip }
      (@match_type == :include) == @selectors.include?(line_item.variant.product.product_type.downcase.strip)
    elsif @selector_type == :vendor
      @selectors = @selectors.map { |selector| selector.downcase.strip }
      (@match_type == :include) == @selectors.include?(line_item.variant.product.vendor.downcase.strip)
    elsif @selector_type == :product_id
      (@match_type == :include) == @selectors.include?(line_item.variant.product.id)
    elsif @selector_type == :variant_id
      (@match_type == :include) == @selectors.include?(line_item.variant.id)
    elsif @selector_type == :all
      true
    else
      raise RuntimeError.new('Invalid product selector type')
    end
  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

Deaktiver rabatkoder

Brug dette script til at deaktivere brugen af rabatkoder i betalingsprocessen.

Du kan f.eks. bruge dette script til at forhindre kunder i at bruge rabatkoder, når der er udsalg i din butik.

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

Er du klar til at begynde at sælge med Shopify?

Prøv det gratis