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].