ตัวอย่างสคริปต์สินค้าเฉพาะรายการ

สคริปต์สินค้าเฉพาะรายการมีผลต่อสินค้าในตะกร้าสินค้า โดยสามารถเปลี่ยนราคาและคุณสมบัติของสินค้าเหล่านั้นได้ สคริปต์เหล่านี้จะใช้งานทุกครั้งที่มีการเปลี่ยนลักษณะตะกร้าสินค้า เช่น การเพิ่มหรือลบสินค้า การเปลี่ยนที่อยู่ที่จัดส่ง หรือการเพิ่มรหัสส่วนลด

หากต้องการใช้เทมเพลตในหน้านี้ ให้สร้างสคริปต์ใหม่ด้วยเทมเพลตเปล่า

ขั้นตอน:

  1. จากส่วน Shopify admin ให้ไปที่แอป > Script Editor
  2. คลิกสร้างสคริปต์
  3. คลิกที่สินค้าเฉพาะรายการ
  4. เลือกเทมเพลตเปล่า แล้วคลิกสร้างสคริปต์
  5. ในส่วนซอร์สโค้ดของ Ruby ให้ลบบรรทัดโค้ดเริ่มต้น: Output.cart = Input.cart
  6. คัดลอกสคริปต์จากหน้านี้แล้ววางในส่วนซอร์สโค้ดของ Ruby
  7. แก้ไขส่วนการตั้งค่าแบบปรับแต่งเองของสคริปต์เพื่อให้ทำงานในร้านค้าของคุณ
  8. ทดสอบสคริปต์ของคุณ หากต้องการข้อมูลเพิ่มเติม ให้ไปที่การทดสอบและแก้จุดบกพร่อง Shopify Scripts
  9. หลังจากการทดสอบ: - คลิกบันทึกแบบร่างเพื่อบันทึกแบบร่างที่ยังไม่ได้เผยแพร่ของสคริปต์ หรือ
    • คลิกบันทึกและเผยแพร่เพื่อสร้างและเผยแพร่สคริปต์

ส่วนลดตะกร้าสินค้าแบบแบ่งระดับตามค่าใช้จ่าย

ใช้สคริปต์นี้เพื่อเสนอส่วนลดเป็นจำนวนเงินที่เพิ่มขึ้นตามมูลค่ารวมของสินค้าในตะกร้าสินค้า ส่วนลดนี้จะแบ่งให้เท่ากันมากที่สุดสำหรับสินค้าแต่ละรายการในตะกร้าสินค้า

ตัวอย่างเช่น คุณสามารถใช้สคริปต์นี้เพื่อเสนอส่วนลด $25 สำหรับยอดรวมหากลูกค้าใช้จ่ายมากกว่า $150 ส่วนลด $50 เมื่อพวกเขาใช้จ่ายมากกว่า $300 หรือส่วนลด $75 เมื่อพวกเขาใช้จ่ายมากกว่า $400

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

ตั้งระดับส่วนลดตามการใช้จ่าย

ใช้สคริปต์นี้เพื่อเสนอส่วนลดเป็นเปอร์เซ็นต์ที่เพิ่มขึ้นตามมูลค่ารวมของสินค้าในตะกร้าสินค้า

ตัวอย่างเช่น คุณสามารถใช้สคริปต์นี้เพื่อเสนอส่วนลด 10% ให้ลูกค้าหากพวกเขาใช้จ่าย $30 ขึ้นไป ส่วนลด 15% หากพวกเขาใช้จ่าย $50 ขึ้นไป หรือส่วนลด 20% หากพวกเขาใช้จ่าย $100 ขึ้นไป

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

ตั้งระดับส่วนลดตามจำนวน

ใช้สคริปต์นี้เพื่อเสนอส่วนลดหลายรายการในครั้งเดียวสำหรับสินค้าเฉพาะรายการ โดยใช้เปอร์เซ็นต์ส่วนลดที่เพิ่มขึ้นตามจำนวนสินค้าเหล่านั้นในตะกร้าสินค้า

ตัวอย่างเช่น คุณสามารถใช้สคริปต์นี้เพื่อเสนอส่วนลด 10% ของคำสั่งซื้อหากลูกค้าซื้อหมวก 2 ใบขึ้นไป หรือส่วนลด 15% หากซื้อหมวก 5 ใบขึ้นไป

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

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

ส่วนลดแบบแบ่งระดับตามค่าใช้จ่ายสินค้า

ใช้สคริปต์นี้เสนอเปอร์เซ็นต์ส่วนลดที่เพื่มขึ้นตามมูลค่ารวมของสินค้าในตะกร้าสินค้าบางรายการ

ตัวอย่างเช่น คุณสามารถใช้สคริปต์นี้เพื่อเสนอส่วนลด 10% หากลูกค้าใช้จ่าย $30 ขึ้นไป ส่วนลด 15% หากพวกเขาใช้จ่าย $50 ขึ้นไป และส่วนลด 20% หากพวกเขาใช้จ่าย $100 ขึ้นไป แต่เฉพาะสินค้าที่ตรงกับแถบป้ายที่ระบุเท่านั้น

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

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

ส่วนลดสินค้า

ใช้สคริปต์นี้เพื่อเสนอส่วนลดสำหรับสินค้าเฉพาะรายการ

ตัวอย่างเช่น คุณสามารถใช้สคริปต์นี้เพื่อเสนอส่วนลด 10% สำหรับสินค้าที่มีแถบป้าย discounted

# ================================ Customizable Settings ================================
# ================================================================
# Discount by Product
#
# Any matching product 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
# - ':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 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 product 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

ส่วนลดสินค้าตามรหัสส่วนลด

ใช้สคริปต์นี้เพื่อเสนอส่วนลดสำหรับสินค้าเฉพาะรายการหากมีการใช้รหัสส่วนลดที่กำหนด

ตัวอย่างเช่น คุณสามารถใช้สคริปต์นี้ในการเสนอส่วนลด 10% สำหรับสินค้าที่มีแท็ก discounted หากพวกเขาใช้รหัสส่วนลด DISCOUNT_10

# ================================ Customizable Settings ================================
# ================================================================
# Product Discount by Discount Code
#
# If any "matching" discount codes are used, any "matching" product
# 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
# - ':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 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" product
# 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

ซื้อสินค้าจำนวนหนึ่งเพื่อรับส่วนลดขั้นที่สอง

ใช้สคริปต์นี้เพื่อเสนอส่วนลดสำหรับสินค้าเฉพาะรายการหากมีการซื้อสินค้ามากกว่าจำนวนที่กำหนด

ตัวอย่างเช่น คุณสามารถใช้สคริปต์นี้เพื่อเสนอให้ลูกค้าสามารถซื้อสินค้า 3 ชิ้นที่มีแถบป้าย discount และรับส่วนลด 50% สำหรับสินค้าชิ้นที่ 4 ที่มีแถบป้าย 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 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 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 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 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 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

ส่วนลดซื้อหนึ่งแถมหนึ่ง

ใช้สคริปต์นี้เพื่อเสนอส่วนลดสินค้าเป็นเปอร์เซ็นต์เมื่อลูกค้าซื้อสินค้าต่างๆ ตามจำนวนที่กำหนด

ตัวอย่างเช่น คุณสามารถใช้สคริปต์นี้เพื่อเสนอส่วนลดราคาหมวก 10% หากลูกค้าซื้อเสื้อยืด 2 ตัว

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

ซื้อสินค้าเฉพาะรายการตามจำนวนที่กำหนด

ใช้สคริปต์นี้เพื่อเสนอสินค้าจำนวนหนึ่งตามราคาที่เลือก

ตัวอย่างเช่น คุณสามารถใช้สคริปต์นี้เพื่อเสนอเสื้อยืด 2 ตัวในราคา $20

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

ส่วนลดของลูกค้าที่มีแถบป้าย

ใช้สคริปต์นี้เสนอส่วนลดแก่ลูกค้าด้วยแท็กบางรายการ

ตัวอย่างเช่น คุณสามารถใช้สคริปต์นี้เพื่อเสนอส่วนลด 20% หากลูกค้ามีแถบป้าย 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

ส่วนลดสินค้าโดยการตลาดของลูกค้า

ใช้สคริปต์นี้เพื่อเสนอส่วนลดสำหรับสินค้าเฉพาะรายการให้แก่ลูกค้าที่ยอมรับการทำการตลาด

ตัวอย่างเช่นคุณสามารถใช้สคริปต์นี้เพื่อเสนอส่วนลด 10% สำหรับลูกค้าทุกรายที่ยอมรับการทำการตลาด

# ================================ Customizable Settings ================================
# ================================================================
# Product Discount by Customer Marketing
#
# If the customer aceepts marketing, any matching product(s) 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
# - ':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 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 aceepts marketing, any matching product(s) 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 product(s) 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
# - ':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 buyers!'
  }
]

# ================================ 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 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 product(s) 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

จำกัดจำนวนสินค้า

ใช้สคริปต์นี้เพื่อใช้ขีดจำกัดจำนวนกับสินค้าที่ต้องการ

ตัวอย่างเช่น คุณสามารถใช้สคริปต์นี้เพื่อป้องกันไม่ให้ลูกค้าซื้อ "สินค้า X" มากกว่า 1 ชิ้นในคำสั่งซื้อเดียว

# ================================ 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
# - ':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 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
# (limited on a variant level). It should be noted that there will
# be no notice to the customer when this happens.
# ================================================================
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.product.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

พร้อมเริ่มต้นการขายด้วย Shopify แล้วหรือยัง

ทดลองใช้งานฟรี