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类即可实现需求。具体修改如下:
- 在遍历每个
campaign的开头,先检查当前客户是否符合该分层折扣的客户标签规则; - 如果客户不符合规则(即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
相关产品推荐
相关产品推荐

