Setting up free shipping on initial orders with Shopify Scripts

If you are on Shopify Plus, you can use Shopify Scripts to give customers free shipping when they purchase a subscription from your store. This will apply to the initial order when a customer checks out with a subscription on your Shopify site. You can also set up free shipping for recurring orders from the Promotions page within Ordergroove. Note that free shipping set up in Ordergroove only applies to recurring subscription orders, and not the initial order when a customer purchases a subscription. See this article for more information about subscription shipping and delivery for Shopify.

Note: This article includes sample code for a script to enable free shipping. Engineering resources are required to modify this sample script so that it works properly with your site. Ordergroove is able to provide the sample code, however we cannot offer support on customizing the script so that it works for your store.

Follow these steps to set up the script for free shipping: 

  1. Open the Script Editor in your Shopify admin. If you do not have the Script Editor, you can add it to your store here

  2. Click "Create script" on the top right to create a new script.

  3. In the popup, select the Shipping rates tab and choose "Blank template", and click "Create script".
    mceclip0.png

  4. Give the script a descriptive title.
    mceclip1.png

  5. Select Code to open up the script code editor.
    mceclip3.png
  6. Paste the following sample code into the code editor. Please note that you may need to make changes to this script so that it works properly on your site. 
    # SCRIPT BASED ON
    # https://help.shopify.com/en/manual/checkout-settings/script-editor/examples/shipping-scripts
    # ================================ Customizable Settings ================================
    # ================================================================
    # Hide Rate(s) for Product/Country
    #
    # If the cart contains any matching items, and we have a matching
    # country, the entered rate(s) are hidden.
    #
    # - '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
    # - ':subscription' to find subscription products
    # - 'product_selectors' is a list of tags or IDs to identify
    # associated products
    # - 'country_code_match_type' determines whether we look for
    # countries that do, or don't, match the entered options, or
    # all countries. Can be:
    # - ':include' to look for countries that DO match
    # - ':exclude' to look for countries that DO NOT match
    # - ':all' to look for all countries
    # - 'country_codes' is a list of country code abbreviations
    # - ie. United States would be `US`
    # - 'rate_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
    # - ':all' for all rates
    # - 'rate_names' is a list of strings to identify rates
    # - if using ':all' above, this can be set to 'nil'
    # ================================================================
    HIDE_RATES_FOR_PRODUCT_AND_COUNTRY_PLUS_FREE_SHIPPING = [
    {
    product_selector_match_type: :include,
    product_selector_type: :subscription,
    product_selectors: [],
    country_code_match_type: :all,
    country_codes: [],
    rate_match_type: :exact,
    rate_names: [""],
    discount_type: :percent,
    discount_amount: 100,
    discount_message: "Free Subscription Shipping"
    },
    ]

    # ================================ 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 subscription(line_item)
    !line_item.selling_plan_id.nil?
    end
    end

    # ================================================================
    # CountrySelector
    #
    # Finds whether the supplied country code matches the entered
    # strings.
    # ================================================================
    class CountrySelector
    def initialize(match_type, countries)
    @match_type = match_type
    @countries = countries.map { |country| country.upcase.strip }
    end

    def match?(country_code)
    if @match_type == :all
    true
    else
    (@match_type == :include) == @countries.any? { |country| country_code.upcase.strip == country }
    end
    end
    end

    # ================================================================
    # RateNameSelector
    #
    # Finds whether the supplied rate name matches 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

    # ================================================================
    # HideRatesForProductCountryCampaign
    #
    # If the cart contains any matching items, and we have a matching
    # country, the entered rate(s) are hidden.
    # ================================================================
    class HideRatesForProductCountryCampaign
    def initialize(campaigns)
    @campaigns = campaigns
    end

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

    return if address.nil?

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

    country_selector = CountrySelector.new(campaign[:country_code_match_type], campaign[:country_codes])
    product_match = cart.line_items.any? { |line_item| product_selector.match?(line_item) }
    country_match = country_selector.match?(address.country_code)

    next unless product_match && country_match

    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

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

    shipping_rates.each do |shipping_rate|
    discount_applicator.apply(shipping_rate)
    end
    end
    end
    end

    class DiscountApplicator
    def initialize(discount_type, discount_amount, discount_message)
    @discount_type = discount_type
    @discount_message = discount_message

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

    def apply(shipping_rate)
    rate_discount = if @discount_type == :percent
    shipping_rate.price * @discount_amount
    else
    @discount_amount
    end

    shipping_rate.apply_discount(rate_discount, message: @discount_message)
    end
    end

    CAMPAIGNS = [
    HideRatesForProductCountryCampaign.new(HIDE_RATES_FOR_PRODUCT_AND_COUNTRY_PLUS_FREE_SHIPPING),
    ]

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

    Output.shipping_rates = Input.shipping_rates
  7. Click "Save and Publish" on the top right to save the script.

With this script in place, you'll see free shipping applied at checkout when there is a subscription item in the cart.
mceclip4.png

Comments

0 comments

Article is closed for comments.