You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Shopify Scripts开发:如何排除VIP标签客户参与分层折扣?

问题描述

已编写Shopify脚本为带「VIP」标签的客户提供30%折扣,现推出分层产品折扣活动,要求该活动不适用于「VIP」标签客户,两项促销独立运行,VIP客户不可同时享受双重折扣。已在配置中将customer_tag_match_type设为:exclude、customer_tags设为["VIP"],但需在TieredProductDiscountByProductSpendCampaign类中添加客户标签检查逻辑,求具体实现方式。

现有脚本代码
# ================================ Customizable Settings 
================================
# ================================================================
# Product Discounts by Customer Tag
#
# ================================================================
DISCOUNTS_FOR_VIP_TAG = [
  {
    customer_tag_match_type: :include,
    customer_tags: ["VIP"],
    product_selector_match_type: :include,
    product_selector_type: :all,
    product_selectors: nil,
    discount_type: :percent,
    discount_amount: 30,
    discount_message: "Enjoy 30% off as a VIP",
  },
]
# ================================ Customizable Settings 
================================
# ================================================================
# Tiered Product Discount by Product Spend Threhsold
#
# ================================================================
PRODUCT_DISCOUNT_TIERS = [
  {
    customer_tag_match_type: :exclude,
    customer_tags: ["VIP"],
    tiers: [
      {
        threshold: 200,
        discount_type: :percent,
        discount_amount: 15,
        discount_message: 'Spend $200 and get 15% off!',
      },
      {
        threshold: 400,
        discount_type: :percent,
        discount_amount: 20,
        discount_message: 'Spend $400 and get 20% off!',
      },
      {
        threshold: 600,
        discount_type: :percent,
        discount_amount: 25,
        discount_message: 'Spend $600 and get 25% off!',
      },
    ],
  },
]
# ================================ Script Code (do not edit) 
================================
# ================================================================
# CustomerTagSelector
#
# Finds whether the supplied customer has any of the entered tags.
# ================================================================
class CustomerTagSelector
  def initialize(match_type, tags)
    @comparator = match_type == :include ? 'any?' : 'none?'
    @tags = tags.map { |tag| tag.downcase.strip }
  end

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

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

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

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

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

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

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

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

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

  def all(line_item)
    true
  end
end

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

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

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

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

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

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

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

      next unless customer_tag_selector.match?(cart.customer)

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

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

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

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

  def run(cart)
    @campaigns.each do |campaign|  
      if campaign[:product_selector_type] == :all
        total_applicable_cost = cart.subtotal_price
        applicable_items = cart.line_items
      else
        product_selector = ProductSelector.new(
          campaign[:product_selector_match_type],
          campaign[:product_selector_type],
          campaign[:product_selectors],
        )

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

        next if applicable_items.nil?

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

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

      next if applicable_tier.nil?

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

      applicable_items.each do |line_item|
        discount_applicator.apply(line_item)
      end
    end
  end
end

CAMPAIGNS = [
  TieredProductDiscountByProductSpendCampaign.new(PRODUCT_DISCOUNT_TIERS),
  DiscountForCustomerTagCampaign.new(DISCOUNTS_FOR_VIP_TAG),
]

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

Output.cart = Input.cart
解决方案

只需在TieredProductDiscountByProductSpendCampaign的run方法中,添加客户标签检查逻辑,复用已有的CustomerTagSelector类即可实现需求。具体修改如下:

  1. 在遍历每个campaign的开头,先检查当前客户是否符合该分层折扣的客户标签规则;
  2. 如果客户不符合规则(即VIP客户),直接跳过该campaign的后续执行逻辑。

修改后的TieredProductDiscountByProductSpendCampaign类完整代码:

class TieredProductDiscountByProductSpendCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart)
    # 先判断是否有客户标签数据,无则直接返回
    return unless cart.customer&.tags

    @campaigns.each do |campaign|  
      # 添加客户标签检查逻辑
      customer_tag_selector = CustomerTagSelector.new(campaign[:customer_tag_match_type], campaign[:customer_tags])
      next unless customer_tag_selector.match?(cart.customer)

      if campaign[:product_selector_type] == :all
        total_applicable_cost = cart.subtotal_price
        applicable_items = cart.line_items
      else
        product_selector = ProductSelector.new(
          campaign[:product_selector_match_type],
          campaign[:product_selector_type],
          campaign[:product_selectors],
        )

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

        next if applicable_items.nil? || applicable_items.empty?

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

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

      next if applicable_tier.nil?

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

      applicable_items.each do |line_item|
        discount_applicator.apply(line_item)
      end
    end
  end
end

修改说明

  • 新增return unless cart.customer&.tags:确保只有存在客户标签数据时才执行后续逻辑;
  • 新增customer_tag_selector实例化及next unless customer_tag_selector.match?(cart.customer):检查客户是否符合当前分层折扣的标签规则,VIP客户会因为match_type: :exclude而不通过检查,直接跳过该campaign;
  • 优化next if applicable_items.nil?为next if applicable_items.nil? || applicable_items.empty?:避免空数组导致后续计算错误。

这样修改后,VIP客户只会触发DiscountForCustomerTagCampaign的30%折扣,不会触发分层产品折扣,实现两项促销独立运行的需求。

内容的提问来源于stack exchange,提问作者user2498890

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 18:34:56