Skip to content

Instantly share code, notes, and snippets.

@CodeBrotha
Last active August 18, 2017 05:35
Show Gist options
  • Select an option

  • Save CodeBrotha/84ea92d5d270df4f0004bda17c86f499 to your computer and use it in GitHub Desktop.

Select an option

Save CodeBrotha/84ea92d5d270df4f0004bda17c86f499 to your computer and use it in GitHub Desktop.
Shopify Plus - Shopify Script - Buy X products of the same vendor, get 1 free.
# Buy X products of the same vendor, get 1 free.
class VendorSelector
def initialize(vendor, tag)
@vendor = vendor
@tag = tag
end
# Returns whether a or not a line item matches the selector.
#
# Example
# -------
# Given `VendorSelector.new("Vendor Name Here", "your tag here")` and
# a line_item with a variant with vendor = "Vendor Name Here" AND tag = "your tag here"
#
# selector.match?(line_item) # returns true
#
def match?(line_item)
(line_item.variant.product.vendor == @vendor) && (line_item.variant.product.tags.include?(@tag))
end
end
# DiscountPercentage
# ==================
#
# Example
# -------
# * 100% off the discounted line item
#
class DiscountPercentage
# Initializes the discount.
#
# Arguments
# ---------
#
# * percent
# The percentage that the item will be discounted.
#
# * message
# The console message to show for the discount.
#
def initialize(percent, message)
# Calculate the percentage, making sure decimal values are used for precision.
#
@percent = Decimal.new(percent) / 100.0
@message = message
end
# Applies the discount on a line item.
#
# Example
# -------
# Given `DiscountPercentage.new(10, "Great discount")` and the following line item:
#
# * Quantity = 3, Price = 10
#
# The discount will give $1 off per quantity, for a total of $3 off.
#
def apply(line_item)
# Calculate the discount for this line item.
line_discount = line_item.line_price * @percent
# Calculate the discounted line price.
new_line_price = line_item.line_price - line_discount
# Apply the new line price to this line item.
# The given message may be displayed in cart pages and confirmation emails to describe the applied discount.
#
line_item.change_line_price(new_line_price, message: @message)
# Print a debugging line to the console.
puts "Discounted line item with variant #{line_item.variant.id} by #{line_discount}."
end
end
# LowestPricePartitioner
# ====================
#
# The `LowestPricePartitioner` is used when a specific quantity of items are discounted.
# It discounts the lowest priced items of the specifed quantity of items.
#
# Example
# -------
# Given `LowestPricePartitioner.new(2,1)` and a cart containing the following line items:
#
# * (A) Quantity = 2, Price = 5
# * (B) Quantity = 3, Price = 10
#
# The partitioner will:
#
# * Sort them by ascending price (A, B)
# * Count the total items to be discounted (1)
# * Take 1 of A to be discounted
#
# The items to be discounted will be (before discount):
#
# * (A) Quantity = 1, Price = 5
#
class LowestPricePartitioner
# Initializes the partitioner.
#
# Arguments
# ---------
#
# * paid_item_count
# The number of items to skip before selecting items to discount.
#
# * discounted_item_count
# The number of items to return for discounting.
#
# Example
# -------
# To create a campaign such as "Buy five, the 6th item is discounted":
#
# LowestPricePartitioner.new(5,1)
#
def initialize(paid_item_count, discounted_item_count)
@paid_item_count = paid_item_count
@discounted_item_count = discounted_item_count
end
# Partitions the items and returns the items that are to be discounted.
#
# Arguments
# ---------
#
# * cart
# The cart to which split items will be added (typically Input.cart).
#
# * line_items
# The selected items that are applicable for the campaign.
#
# Example
# -------
#
# To create a campaign such that for all items under $5, the 3rd one is discounted:
#
# selected_items = Input.cart.line_items.select{|item| item.variant.price < Money.new(cents: 5_00)}
# partitioner = LowestPricePartitioner.new(2,1)
# items_to_discount = partitioner.partition(Input.cart, selected_items)
#
# After this, the campaign has to apply discounts to `items_to_discount`.
#
def partition(cart, applicable_line_items)
# Sort the items by price from low to high.
sorted_items = applicable_line_items.sort_by{|line_item| line_item.variant.price}
# Find the total quantity of items.
total_applicable_quantity = sorted_items.map(&:quantity).reduce(0, :+)
# Find the quantity of items that must be discounted.
discounted_items_remaining = Integer(total_applicable_quantity / (@paid_item_count + @discounted_item_count) * @discounted_item_count)
# Create an array of items to return.
discounted_items = []
# Loop over all the items and find those to be discounted.
sorted_items.each do |line_item|
# Exit the loop if all discounted items have been found.
break if discounted_items_remaining == 0
# The item will be discounted.
discounted_item = line_item
if line_item.quantity > discounted_items_remaining
# If the item has more quantity than what must be discounted, split it.
discounted_item = line_item.split(take: discounted_items_remaining)
# Insert the newly-created item in the cart, right after the original item.
position = cart.line_items.find_index(line_item)
cart.line_items.insert(position + 1, discounted_item)
end
# Decrement the items left to be discounted.
discounted_items_remaining -= discounted_item.quantity
# Add the item to be returned.
discounted_items.push(discounted_item)
end
# Return the items to be discounted.
discounted_items
end
end
# BuyGetCampaign
# ==============
#
# Example campaigns
# -----------------
#
# * Buy five, get one free
# * Buy one, get one 50% off
# * Buy two items and get a third for $5 off
#
class BuyGetCampaign
# Initializes the campaign.
#
# Arguments
# ---------
#
# * selector
# The selector finds eligible items for this campaign.
#
# * discount
# The discount changes the prices of the items returned by the partitioner.
#
# * partitioner
# The partitioner takes all applicable items, and returns only those that are to be discounted.
# In a "Buy five items, get the sixth for free" campaign,
# the partitioner would skip five items and return the sixth item.
#
def initialize(selector, discount, partitioner)
@selector = selector
@discount = discount
@partitioner = partitioner
end
# Runs the campaign on the given cart.
#
# Example
# -------
# To run the campaign on the input cart:
#
# campaign.run(Input.cart)
#
def run(cart)
applicable_items = cart.line_items.select do |line_item|
@selector.match?(line_item)
end
discounted_items = @partitioner.partition(cart, applicable_items)
discounted_items.each do |line_item|
@discount.apply(line_item)
end
end
end
# Use an array to keep track of the discount campaigns desired.
# The CAMPAIGNS settings here give every 6th item with the same vendor "Vendor Name Here" for free. (Change these settings accordingly.)
CAMPAIGNS = [
BuyGetCampaign.new(
VendorSelector.new("Vendor Name Here", "your tag here"),
DiscountPercentage.new(100, "6TH ITEM IS FREE!"),
LowestPricePartitioner.new(5,1),
)
]
# Iterate through each of the discount campaigns.
CAMPAIGNS.each do |campaign|
# Apply the campaign onto the cart.
campaign.run(Input.cart)
end
# In order to reflect the changes to the line items, the output of the script needs to be specified.
Output.cart = Input.cart
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment