Shipping Script to hide shipping options based on product tags

Solved
M0w45
Excursionist
17 0 2

Hi there,

I am currently running a script that hides shipping sources & names based products in the cart. 

I currently have (3) scenarios: 

  • if all items in cart contain the product tag "status-special-order", then it hides some of my shipping options.
  • if any of the items in the cart contain the product tag "status-special-order", then it hides some of my shipping options.
  • if none of the items in the cart contain the product tag "status-special-order", then it hides some of my shipping options.

This script is working perfectly fine but I can't seem to make it work when trying to add an extra loop to check the province_code. 

I would need to include a condition so that if province_code = ["BC","AB","ON","SK","MB","NB","NS","PE","QC"] then it runs the script below, but if province_code does not match any of these codes, then it only shows (2) shipping options. I can't seem to make it work, and any help would be much appreciated

Thank you so much!

HIDE_RATES_FOR_ONLY_SPECIAL_ORDER = [
  {
    product_selector_match_type: :include,
    product_selector_match_scope: :all,
    product_selector_type: :tag,
    product_selectors: ["status-special-order"],
    rate_match_type: :exact,
    rate_names: ["Free Shipping ($35 min.)","$5.99 Flat Rate","Expedited Parcel","Xpresspost","Priority","Send me the stocked item(s) now and the special item(s) when available.","I can wait for all items to be available and help offset carbon footprint."],
  },
]

HIDE_RATES_FOR_MIXED_ORDER = [
  {
    product_selector_match_type: :include,
    product_selector_match_scope: :any,
    product_selector_type: :tag,
    product_selectors: ["status-special-order"],
    rate_match_type: :exact,
    rate_names: ["Free Shipping ($35 min.)","$5.99 Flat Rate","Expedited Parcel","Xpresspost","Priority","Send me the special order item(s) when available."],
  },
]

HIDE_RATES_FOR_STOCK_ORDER = [
  {
    product_selector_match_type: :exclude,
    product_selector_match_scope: :any,
    product_selector_type: :tag,
    product_selectors: ["status-special-order"],
    rate_match_type: :exact,
    rate_names: ["Send me the stocked item(s) now and the special item(s) when available.","I can wait for all items to be available and help offset carbon footprint.","Send me the special order item(s) when available."],
  },
]


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

# ================================================================
# RateNameSelector
# Finds whether the supplied rate names match any of the entered names.
# ================================================================
class RateNameSelector
  def initialize(match_type, rate_names)
    @match_type = match_type
    @comparator = match_type == :exact ? '==' : 'include?'
    @rate_names = rate_names&.map { |rate_name| rate_name.downcase.strip }
  end

  def match?(shipping_rate)
    if @match_type == :all
      true
    else
      @rate_names.any? { |name| shipping_rate.name.downcase.send(@comparator, name) }
    end
  end
end

# ================================================================
# HideRatesForProduct
# If the cart contains any matching items, the entered rate(s) are hidden.
# ================================================================
class HideRatesForProduct
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart, shipping_rates)
    address = cart.shipping_address
    campaign_satisified = false

    return if address.nil?

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

      if campaign[:product_selector_match_scope] == :all
        product_match = cart.line_items.all? { |line_item| product_selector.match?(line_item) }
      else
        product_match = cart.line_items.any? { |line_item| product_selector.match?(line_item) }
      end

      next unless product_match

      rate_name_selector = RateNameSelector.new(
        campaign[:rate_match_type],
        campaign[:rate_names],
      )

      shipping_rates.each do |shipping_rate|
        if rate_name_selector.match?(shipping_rate)
          campaign_satisified = true
        end
      end

      shipping_rates.delete_if do |shipping_rate|
        rate_name_selector.match?(shipping_rate)
      end
    end

    return campaign_satisified
  end
end

CAMPAIGNS = [
  HideRatesForProduct.new(HIDE_RATES_FOR_ONLY_SPECIAL_ORDER),
  HideRatesForProduct.new(HIDE_RATES_FOR_MIXED_ORDER),
  HideRatesForProduct.new(HIDE_RATES_FOR_STOCK_ORDER)
]

CAMPAIGNS.each do |campaign|
  break if campaign.run(Input.cart, Input.shipping_rates)
end

Output.shipping_rates = Input.shipping_rates
0 Likes

Hi @M0w45,

Which shipping options would you like to show if the province_code doesn't match that list?

Thanks,
Matthew

Playwright | Create Shopify Scripts without writing code | https://playwrightapp.com
- Was my reply helpful? Please Like and Accept Solution.
- Need help with a custom Shopify script? Email me at matthew@playwrightapp.com

M0w45
Excursionist
17 0 2

Hi @playwright-matt ,

If the province_code does not match the list, I'd like to show the following shipping options only: "Expedited Parcel","Xpresspost","Priority"

Thank you!

0 Likes

This script is getting fancy! Try this and see if it works for you:

 

 

 

 

HIDE_RATES_IF_NOT_PROVINCE_CODE = [
  {
    province_code_match_type: :exact,
    province_codes: ["BC","AB","ON","SK","MB","NB","NS","PE","QC"],
    rate_match_type: :exact,
    rate_names: ["Free Shipping ($35 min.)",
      "$5.99 Flat Rate",
      "Send me the special order item(s) when available.",
      "I can wait for all items to be available and help offset carbon footprint.",
      "Send me the special order item(s) when available."],
  },
]

HIDE_RATES_FOR_ONLY_SPECIAL_ORDER = [
  {
    product_selector_match_type: :include,
    product_selector_match_scope: :all,
    product_selector_type: :tag,
    product_selectors: ["status-special-order"],
    rate_match_type: :exact,
    rate_names: ["Free Shipping ($35 min.)",
      "$5.99 Flat Rate",
      "Expedited Parcel",
      "Xpresspost",
      "Priority",
      "Send me the stocked item(s) now and the special item(s) when available.",
      "I can wait for all items to be available and help offset carbon footprint."],
  },
]

HIDE_RATES_FOR_MIXED_ORDER = [
  {
    product_selector_match_type: :include,
    product_selector_match_scope: :any,
    product_selector_type: :tag,
    product_selectors: ["status-special-order"],
    rate_match_type: :exact,
    rate_names: ["Free Shipping ($35 min.)",
      "$5.99 Flat Rate",
      "Expedited Parcel",
      "Xpresspost",
      "Priority",
      "Send me the special order item(s) when available."],
  },
]

HIDE_RATES_FOR_STOCK_ORDER = [
  {
    product_selector_match_type: :exclude,
    product_selector_match_scope: :any,
    product_selector_type: :tag,
    product_selectors: ["status-special-order"],
    rate_match_type: :exact,
    rate_names: ["Send me the stocked item(s) now and the special item(s) when available.",
      "I can wait for all items to be available and help offset carbon footprint.",
      "Send me the special order item(s) when available."],
  },
]

# ================================================================
# 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?'
    @selectors = selectors
    @Selector_type = selector_type  # this line is getting edited by the forum system, it should  begin with @ and then the word 'selector_type' and then be equal to 'selector_type' (all lowercase)
  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
end

# ================================================================
# ProvinceCodeSelector
#
# Finds whether the supplied province code matches any of the entered
# strings.
# ================================================================
class ProvinceCodeSelector
  def initialize(match_type, province_codes)
    @comparator = match_type == :exact ? '==' : 'include?'
    @province_codes = province_codes.map { |province_code| province_code.upcase.strip }
  end

  def match?(province_code)
    @province_codes.any? { |pc| province_code.to_s.upcase.strip.send(@comparator, pc) }
  end
end

# ================================================================
# RateNameSelector
# Finds whether the supplied rate names match any of the entered names.
# ================================================================
class RateNameSelector
  def initialize(match_type, rate_names)
    @match_type = match_type
    @comparator = match_type == :exact ? '==' : 'include?'
    @rate_names = rate_names&.map { |rate_name| rate_name.downcase.strip }
  end

  def match?(shipping_rate)
    if @match_type == :all
      true
    else
      @rate_names.any? { |name| shipping_rate.name.downcase.send(@comparator, name) }
    end
  end
end

# ================================================================
# HideRatesForProduct
# If the cart contains any matching items, the entered rate(s) are hidden.
# ================================================================
class HideRatesForProduct
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart, shipping_rates)
    address = cart.shipping_address
    campaign_satisified = false

    return if address.nil?

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

      if campaign[:product_selector_match_scope] == :all
        product_match = cart.line_items.all? { |line_item| product_selector.match?(line_item) }
      else
        product_match = cart.line_items.any? { |line_item| product_selector.match?(line_item) }
      end

      next unless product_match

      rate_name_selector = RateNameSelector.new(
        campaign[:rate_match_type],
        campaign[:rate_names],
      )

      shipping_rates.each do |shipping_rate|
        if rate_name_selector.match?(shipping_rate)
          campaign_satisified = true
          puts "matched product campaign"
        end
      end

      shipping_rates.delete_if do |shipping_rate|
        rate_name_selector.match?(shipping_rate)
      end
    end

    return campaign_satisified
  end
end

# ================================================================
# HideRatesForProvinceCodeCampaign
#
# If the cart's shipping address zip/province/country match the
# entered settings, the entered rate(s) are hidden.
# ================================================================
class HideRatesIfNotProvince
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart, shipping_rates)
    address = cart.shipping_address
    campaign_satisified = false

    return if address.nil?

    @campaigns.each do |campaign|
      province_code_selector = ProvinceCodeSelector.new(campaign[:province_code_match_type], campaign[:province_codes])

      province_not_found = !province_code_selector.match?(address.province_code)

      next unless province_not_found
      
      campaign_satisified = true
      puts "matched missing province code campaign"

      rate_name_selector = RateNameSelector.new(
        campaign[:rate_match_type],
        campaign[:rate_names],
      )

      shipping_rates.delete_if do |shipping_rate|
        rate_name_selector.match?(shipping_rate)
      end
    end

    return campaign_satisified
  end
end

CAMPAIGNS = [
  HideRatesIfNotProvince.new(HIDE_RATES_IF_NOT_PROVINCE_CODE),
  HideRatesForProduct.new(HIDE_RATES_FOR_ONLY_SPECIAL_ORDER),
  HideRatesForProduct.new(HIDE_RATES_FOR_MIXED_ORDER),
  HideRatesForProduct.new(HIDE_RATES_FOR_STOCK_ORDER)
]

CAMPAIGNS.each do |campaign|
  break if campaign.run(Input.cart, Input.shipping_rates)
end

Output.shipping_rates = Input.shipping_rates

 

 

 

 

 

 

 

Playwright | Create Shopify Scripts without writing code | https://playwrightapp.com
- Was my reply helpful? Please Like and Accept Solution.
- Need help with a custom Shopify script? Email me at matthew@playwrightapp.com

M0w45
Excursionist
17 0 2

Thank you @playwright-matt for sharing this.

I gave it a try and got an error starting line 73:

[Error] nil is not a symbol
Test Script:73:in ProductSelector.match?
Test Script:147:in HideRatesForProduct.run
shopify/std_lib/core/list.rb:41:in List.each
Test Script:147:in HideRatesForProduct.run
Test Script:139:in HideRatesForProduct.run
Test Script:224:in Object.call
Test Script:223

Hope this helps

0 Likes

This is an accepted solution.

Something keeps happening with that "@selector_type" variable name. It was getting capitalized for some reason. I've removed that:

 

 

HIDE_RATES_IF_NOT_PROVINCE_CODE = [
  {
    province_code_match_type: :exact,
    province_codes: ["BC","AB","ON","SK","MB","NB","NS","PE","QC"],
    rate_match_type: :exact,
    rate_names: ["Free Shipping ($35 min.)",
      "$5.99 Flat Rate",
      "Send me the special order item(s) when available.",
      "I can wait for all items to be available and help offset carbon footprint.",
      "Send me the special order item(s) when available."],
  },
]

HIDE_RATES_FOR_ONLY_SPECIAL_ORDER = [
  {
    product_selector_match_type: :include,
    product_selector_match_scope: :all,
    product_selector_type: :tag,
    product_selectors: ["status-special-order"],
    rate_match_type: :exact,
    rate_names: ["Free Shipping ($35 min.)",
      "$5.99 Flat Rate",
      "Expedited Parcel",
      "Xpresspost",
      "Priority",
      "Send me the stocked item(s) now and the special item(s) when available.",
      "I can wait for all items to be available and help offset carbon footprint."],
  },
]

HIDE_RATES_FOR_MIXED_ORDER = [
  {
    product_selector_match_type: :include,
    product_selector_match_scope: :any,
    product_selector_type: :tag,
    product_selectors: ["status-special-order"],
    rate_match_type: :exact,
    rate_names: ["Free Shipping ($35 min.)",
      "$5.99 Flat Rate",
      "Expedited Parcel",
      "Xpresspost",
      "Priority",
      "Send me the special order item(s) when available."],
  },
]

HIDE_RATES_FOR_STOCK_ORDER = [
  {
    product_selector_match_type: :exclude,
    product_selector_match_scope: :any,
    product_selector_type: :tag,
    product_selectors: ["status-special-order"],
    rate_match_type: :exact,
    rate_names: ["Send me the stocked item(s) now and the special item(s) when available.",
      "I can wait for all items to be available and help offset carbon footprint.",
      "Send me the special order item(s) when available."],
  },
]

# ================================================================
# 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  # this line is getting edited by the forum system, it should  begin with @ and then the word 'selector_type' and then be equal to 'selector_type' (all lowercase)
    @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
end

# ================================================================
# ProvinceCodeSelector
#
# Finds whether the supplied province code matches any of the entered
# strings.
# ================================================================
class ProvinceCodeSelector
  def initialize(match_type, province_codes)
    @comparator = match_type == :exact ? '==' : 'include?'
    @province_codes = province_codes.map { |province_code| province_code.upcase.strip }
  end

  def match?(province_code)
    @province_codes.any? { |pc| province_code.to_s.upcase.strip.send(@comparator, pc) }
  end
end

# ================================================================
# RateNameSelector
# Finds whether the supplied rate names match any of the entered names.
# ================================================================
class RateNameSelector
  def initialize(match_type, rate_names)
    @match_type = match_type
    @comparator = match_type == :exact ? '==' : 'include?'
    @rate_names = rate_names&.map { |rate_name| rate_name.downcase.strip }
  end

  def match?(shipping_rate)
    if @match_type == :all
      true
    else
      @rate_names.any? { |name| shipping_rate.name.downcase.send(@comparator, name) }
    end
  end
end

# ================================================================
# HideRatesForProduct
# If the cart contains any matching items, the entered rate(s) are hidden.
# ================================================================
class HideRatesForProduct
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart, shipping_rates)
    address = cart.shipping_address
    campaign_satisified = false

    return if address.nil?

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

      if campaign[:product_selector_match_scope] == :all
        product_match = cart.line_items.all? { |line_item| product_selector.match?(line_item) }
      else
        product_match = cart.line_items.any? { |line_item| product_selector.match?(line_item) }
      end

      next unless product_match

      rate_name_selector = RateNameSelector.new(
        campaign[:rate_match_type],
        campaign[:rate_names],
      )

      shipping_rates.each do |shipping_rate|
        if rate_name_selector.match?(shipping_rate)
          campaign_satisified = true
          puts "matched product campaign"
        end
      end

      shipping_rates.delete_if do |shipping_rate|
        rate_name_selector.match?(shipping_rate)
      end
    end

    return campaign_satisified
  end
end

# ================================================================
# HideRatesForProvinceCodeCampaign
#
# If the cart's shipping address zip/province/country match the
# entered settings, the entered rate(s) are hidden.
# ================================================================
class HideRatesIfNotProvince
  def initialize(campaigns)
    @campaigns = campaigns
  end

  def run(cart, shipping_rates)
    address = cart.shipping_address
    campaign_satisified = false

    return if address.nil?

    @campaigns.each do |campaign|
      province_code_selector = ProvinceCodeSelector.new(campaign[:province_code_match_type], campaign[:province_codes])

      province_not_found = !province_code_selector.match?(address.province_code)

      next unless province_not_found
      
      campaign_satisified = true
      puts "matched missing province code campaign"

      rate_name_selector = RateNameSelector.new(
        campaign[:rate_match_type],
        campaign[:rate_names],
      )

      shipping_rates.delete_if do |shipping_rate|
        rate_name_selector.match?(shipping_rate)
      end
    end

    return campaign_satisified
  end
end

CAMPAIGNS = [
  HideRatesIfNotProvince.new(HIDE_RATES_IF_NOT_PROVINCE_CODE),
  HideRatesForProduct.new(HIDE_RATES_FOR_ONLY_SPECIAL_ORDER),
  HideRatesForProduct.new(HIDE_RATES_FOR_MIXED_ORDER),
  HideRatesForProduct.new(HIDE_RATES_FOR_STOCK_ORDER)
]

CAMPAIGNS.each do |campaign|
  break if campaign.run(Input.cart, Input.shipping_rates)
end

Output.shipping_rates = Input.shipping_rates

 

 

Playwright | Create Shopify Scripts without writing code | https://playwrightapp.com
- Was my reply helpful? Please Like and Accept Solution.
- Need help with a custom Shopify script? Email me at matthew@playwrightapp.com

M0w45
Excursionist
17 0 2

Hi @playwright-matt , I just gave it a try and still getting the exact same errors. 

I also compared this script to the previous version you sent, and they are identical. Was this meant to be?

Thanks again

0 Likes

This is an accepted solution.

Thanks for your patience on this. Very weird, I think the post is being formatted or changed by community forum system.

Screen Shot 2021-04-21 at 2.29.20 PM.png

Just make this line say:

@selector_type = selector_type

(basically just change the capital S for a lowercase s)

Playwright | Create Shopify Scripts without writing code | https://playwrightapp.com
- Was my reply helpful? Please Like and Accept Solution.
- Need help with a custom Shopify script? Email me at matthew@playwrightapp.com

I found out what was happening, the forum things I am trying to "mention" someone by typing "@" and then some name. I am not sure why it picks that particular line, but it does. 

Geez!

Playwright | Create Shopify Scripts without writing code | https://playwrightapp.com
- Was my reply helpful? Please Like and Accept Solution.
- Need help with a custom Shopify script? Email me at matthew@playwrightapp.com

M0w45
Excursionist
17 0 2

Hahaha great find @playwright-matt ! 

I did make the correction, and was able to test it. Everything seems to be working just fine now.

Once again, thank you very much for your help

0 Likes