719-286-0751 [email protected]

Shopify Scripts Part III: Tiered Pricing and Combining Line Item Scripts

This is part III in our Shopify Scripts series, you can find the previous posts here and here. We will be building upon the first post mainly, where we introduced wholesale pricing to our store via a line item script. In this post we will explore adding another line item script feature, a tiered pricing discount such as “Buy X of Z, get Y% off”. The difficulty in adding this to our existing store is that, as stated in our first post, Shopify only allows 1 of each type of script on your store. This means we can’t just write another script, and add it to the mix (this is likely for the best, as things could get out of hand fast).

 

Before we begin

If this is the first post in this series you are reading, I would strongly recommend at least reviewing the first few sections of post 1 so that you are aware of the setup and limitations. We are not going to be going very in depth on the development process in this post either, as that was covered in post 1.

 

Getting started

If you open your script editor, you’ll notice underĀ Line Items there is an example script. Shopify provides one here as well that is quite a bit more dense, but is far more customizable and is based on tags. While I would prefer the second script overall, it doesn’t really allow changes to campaigns without going into the script editor and modifying code which is not ideal for non-technical clients. Additionally, we will also need to find a way to combine this with our existing whole sale discounts. While the second script does include something of a “campain combiner” near the bottom, the issue is that campaigns would be stacked. This may be fine for your business, but likely there are some businesses where stacking a 10% wholesale discount on top of a “Buy 10 get 20% off” would not be what we want to happen.

 

Tiered Discount Campaign

I’m going to present my version of a tiered discount campaign. Similar to our first post, this campaign will utilize tags. I setup tags with the following format: TIERED_{{QTY}}_{{DISCOUNT_PERCENT}} — so a product that has the tags: tiered_5_10 and tiered_10_20 will be 10% at 5-9 and 20% off above quantity of 10! We can stack as many of these as we want. The code I came up with is here:

class TieredPricingCampaign
  def initialize(product_tag)
    @product_tag = product_tag.downcase
  end
  
  def run(line_item, cart)
    # start with no price adjustment
    final_amount, message = nil, nil
    # look at each product tag
    line_item.variant.product.tags.each do |tag|
      products_tag = tag.downcase
      # if the tag matches our campaign, then extract the qty / discount amount
      if (products_tag.split(@product_tag)[0] === '')
        needed_qty, discount = products_tag.split(@product_tag)[1].split('_')
        # if they bought enough, then we calculate the new price
        if line_item.quantity >= needed_qty.to_i
          new_amount = Money.new(cents: line_item.line_price.cents * ((100 - discount.to_i) / 100))
          new_message = "Buy #{needed_qty}, get #{discount}% off!"
          # if this is a better deal than whatever we have so far, use it instead
          if !final_amount || final_amount.cents > new_amount.cents
            final_amount = new_amount
            message = new_message
          end
        end
      end
    end
    # if we didn't find any matching tags, then we return nil
    return final_amount, message
  end
  
end

Now, you’ll notice this doesn’t actually change the price of the item. This would not be too hard to change (just updated price instead of return the values), however I have written it this way because we are going to be combining it with our wholesale campaign. Additionally, this campaign handles multiple pricing tiers and will apply the one with the highest quantity / greatest discount that it finds!

 

Combining campaigns

Now that we have a tiered discount campaign setup and working, we need a way to combine campaigns. The solution I came up with is to have each campaign have a run function that either returns [nil, nil] indicating it didn’t find any discount to apply, or a new price and a message. Our discount combiner will then compare these values and see which offers the best deal, and apply that one to the particular line item. The code for this is as follows:

# Runs each campaign, whichever returns the lowest price is applied to the line item
class MaximumDiscountCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end
  
  def run(cart)
    cart.line_items.each do |line_item|
      min_price = line_item.line_price
      final_message = nil
      @campaigns.each do |campaign|
        new_price, message = campaign.run(line_item, cart)
        if new_price
          if new_price.cents < min_price.cents
            min_price = new_price
            final_message = message
          end
        end
      end
      if (min_price.cents < line_item.line_price.cents && final_message)
        line_item.change_line_price(min_price, message: final_message)
      end
    end
  end
end

As you can see, if we have a customer with a wholesale discountĀ and they hit a quantity discount, whichever is greater will be the one that is applied. You’ll notice that each campaigns run function also accepts the whole cart object, this is because it’s the only way to access a customer. In the future, this could also prove useful for BOGO campaigns, where the price of one item depends on the presence of another item in the cart.

 

Putting it all together

Now, we need to combine this with our wholesale campaign and test it out. The full code of the script on my store follows:

WHOLESALE_TAG = 'WHOLESALE_'
TIERED_PRICING_TAG = 'TIERED_'

class WholesalePricingCampaign
  def initialize(wholesale_tag)
    @wholesale_tag = wholesale_tag.downcase
  end

  def run (line_item, cart)
    wholesale_tag = @wholesale_tag
    discount_amount = 0
    if cart.customer
      cart.customer.tags.each do |tag|
        if tag.downcase.start_with?(wholesale_tag)
          discount = tag.downcase.split(wholesale_tag)[1].to_i
          if discount > discount_amount
            discount_amount = discount
          end
        end
      end

      return Money.new(cents: line_item.line_price.cents * ((100 - discount_amount) / 100)), "Wholesale discount (#{discount_amount}%)"
    end
  end
end

class TieredPricingCampaign
  def initialize(product_tag)
    @product_tag = product_tag.downcase
  end
  
  def run(line_item, cart)
    # start with no price adjustment
    final_amount, message = nil, nil
    # look at each product tag
    line_item.variant.product.tags.each do |tag|
      products_tag = tag.downcase
      # if the tag matches our campaign, then extract the qty / discount amount
      if (products_tag.split(@product_tag)[0] === '')
        needed_qty, discount = products_tag.split(@product_tag)[1].split('_')
        # if they bought enough, then we calculate the new price
        if line_item.quantity >= needed_qty.to_i
          new_amount = Money.new(cents: line_item.line_price.cents * ((100 - discount.to_i) / 100))
          new_message = "Buy #{needed_qty}, get #{discount}% off!"
          # if this is a better deal than whatever we have so far, use it instead
          if !final_amount || final_amount.cents > new_amount.cents
            final_amount = new_amount
            message = new_message
          end
        end
      end
    end
    # if we didn't find any matching tags, then we return nil
    return final_amount, message
  end
  
end

# Runs each campaign, whichever returns the lowest price is applied to the line item
class MaximumDiscountCampaign
  def initialize(campaigns)
    @campaigns = campaigns
  end
  
  def run(cart)
    cart.line_items.each do |line_item|
      min_price = line_item.line_price
      final_message = nil
      @campaigns.each do |campaign|
        new_price, message = campaign.run(line_item, cart)
        if new_price
          if new_price.cents < min_price.cents
            min_price = new_price
            final_message = message
          end
        end
      end
      if (min_price.cents < line_item.line_price.cents && final_message)
        line_item.change_line_price(min_price, message: final_message)
      end
    end
  end
end


CAMPAIGNS = [
  WholesalePricingCampaign.new(WHOLESALE_TAG),
  TieredPricingCampaign.new(TIERED_PRICING_TAG)
]

campaign = MaximumDiscountCampaign.new(CAMPAIGNS)

campaign.run Input.cart

Output.cart = Input.cart

To test this out, I have my wholesale customer from before that will receive 10% store wide by default. I added the following tags to my store’s “Bangle Bracelet” tiered_5_20, tiered_10_25, tiered_15_27 — which means in english: buy 5-9 get 20% off, buy 10-14 get 25% off, and buy 15+ get 27% off.

Here’s a quick screencap of my cart in various scenarios to show how this plays out —

 

As you can see, when the items are below 5 the standard wholesale pricing kicks in. Once we get above that, there is a better discount from the tiered pricing settings and the script applies that discount instead (and prevents stacking)!

 

Need help with Shopify?

We’ve had a lot of experience building websites in Shopify. If you need help, head over to the Cadence Labs contact page, or email us at [email protected].

Submit a Comment

Your email address will not be published. Required fields are marked *

Install our webapp on your iPhone! Tap and then Add to homescreen.
Share This