Last active
August 18, 2017 05:35
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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