Last active
March 20, 2023 22:35
-
-
Save CodeBrotha/4a9977adb7efbe76dbfe4fdf9d8dbae9 to your computer and use it in GitHub Desktop.
Shopify Scripts - Combined Line Item Script
This file contains 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
# ================================================================ | |
# ================================================================ | |
# LINE ITEM COMBO SCRIPT | |
# | |
# This script runs multiple line item scripts. | |
# | |
# Currently contains: | |
# | |
# 1: PRODUCT QUANTITY LIMITS | |
# (Limits line item quantities per order) | |
# 2: LINE ITEM PROPERTY DISCOUNT | |
# (Applies discount to each line item based on a line item property's value.) | |
# ================================================================ | |
# ================================================================ | |
# ================================================================ | |
# PRODUCT QUANTITY LIMITS | |
# ================================================================ | |
# ================ Customizable Settings ================ | |
# If the quantity of any matching items is greater than the | |
# entered threshold, the excess items are removed from the cart. | |
# It should be noted that there will be no notice to the customer | |
# when this happens. | |
# | |
# - 'enable' determines whether the campaign will run. Can be: | |
# - 'true' to run | |
# - 'false' to not run | |
# - '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 | |
# - ':all' for all products | |
# - 'product_selectors' is a list of identifiers (from above) | |
# for qualifying products. Product/Variant ID lists should | |
# only contain numbers (ie. no quotes). If ':all' is used, | |
# this can also be 'nil'. | |
# - 'variant_level_limit' determines whether the below limit | |
# is applied on a variant, or a total quantity, level. For | |
# example, can I have X number of individual matching items, | |
# or can I only have X number total of matching items? | |
# Can be: | |
# - 'true' to limit at a variant level | |
# - 'false' to limit total quantity | |
# - 'quantity_allowed' is the number of products allowed | |
# ================================================================ | |
## Check if any line items have a tag that starts with "restrict:qty-limit" | |
## If so create a restricted quantity campaign for that tag | |
restricted_qty_campaigns = [ | |
{ | |
product_selector_match_type: :include, | |
product_selector_type: :all, | |
variant_level_limit: true, | |
quantity_allowed: 100, | |
}, | |
{ | |
product_selector_match_type: :include, | |
product_selector_type: :tag, | |
product_selectors: ["special-tag"], | |
variant_level_limit: true, | |
quantity_allowed: 10, | |
} | |
] | |
has_restrict_qty_tag = false | |
## Give your quantity limit tags the same prefix, example "restrict:qty-5" or "restrict:qty-10" etc. | |
## Then you can easily use the following to capture any quantity limit tag with the same prefix as shown here. | |
Input.cart.line_items.each do |item| | |
item.variant.product.tags.each do |tag| | |
if tag.start_with?('restrict:qty') | |
next if restricted_qty_campaigns.any? {|h| h[:product_selectors] and h[:product_selectors].include? tag} | |
has_restrict_qty_tag = true | |
restrict_qty_tag = tag | |
split_tag = tag.split('-', 2) | |
restrict_qty = split_tag.last.to_i | |
puts 'restrict:qty tag found' | |
puts 'restrict_qty_tag is ' + restrict_qty_tag | |
puts 'restrict_qty is = ' + split_tag.last | |
## If the tag's restrict quantity is the same as that of an existing campaign, combine with the existing campaign. | |
if restricted_qty_campaigns.any? {|h| h[:quantity_allowed] == restrict_qty} | |
existing_hash = restricted_qty_campaigns.find { |h| h[:quantity_allowed] == restrict_qty } | |
restrict_qty_tag_array = [restrict_qty_tag] | |
existing_hash[:product_selectors] += restrict_qty_tag_array | |
else | |
restricted_qty_campaign = [ | |
{ | |
product_selector_match_type: :include, | |
product_selector_type: :tag, | |
product_selectors: [restrict_qty_tag], | |
variant_level_limit: true, | |
quantity_allowed: restrict_qty, | |
} | |
] | |
restricted_qty_campaigns += restricted_qty_campaign | |
end | |
end | |
end | |
end | |
puts 'restricted_qty_campaigns is ' + restricted_qty_campaigns.to_s | |
QUANTITY_LIMITS = { | |
enable: true, | |
campaigns: restricted_qty_campaigns | |
} | |
# ================================================================ | |
# 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 | |
def all(line_item) | |
true | |
end | |
end | |
# ================================================================ | |
# ProductQuantityLimitCampaign | |
# | |
# If the quantity of any matching items is greater than the | |
# entered threshold, the excess items are removed from the cart. | |
# ================================================================ | |
class ProductQuantityLimitCampaign | |
def initialize(enable, campaigns) | |
@enable = enable | |
@campaigns = campaigns | |
end | |
def run(cart) | |
return unless @enable | |
@campaigns.each do |campaign| | |
product_selector = ProductSelector.new( | |
campaign[:product_selector_match_type], | |
campaign[:product_selector_type], | |
campaign[:product_selectors] | |
) | |
if campaign[:variant_level_limit] | |
applicable_items = {} | |
cart.line_items.each_with_index do |line_item, idx| | |
next unless product_selector.match?(line_item) | |
id = idx | |
if applicable_items[id].nil? | |
applicable_items[id] = { | |
items: [], | |
total_quantity: 0 | |
} | |
end | |
applicable_items[id][:items].push(line_item) | |
applicable_items[id][:total_quantity] += line_item.quantity | |
end | |
next if applicable_items.nil? | |
applicable_items.each do |id, info| | |
next unless info[:total_quantity] > campaign[:quantity_allowed] | |
num_to_remove = info[:total_quantity] - campaign[:quantity_allowed] | |
self.loop_items(cart, info[:items], num_to_remove) | |
end | |
else | |
applicable_items = cart.line_items.select { |line_item| product_selector.match?(line_item) } | |
next if applicable_items.nil? | |
total_quantity = applicable_items.map(&:quantity).reduce(0, :+) | |
next unless total_quantity > campaign[:quantity_allowed] | |
num_to_remove = total_quantity - campaign[:quantity_allowed] | |
self.loop_items(cart, applicable_items, num_to_remove) | |
end | |
end | |
end | |
def loop_items(cart, line_items, num_to_remove) | |
line_items.each do |line_item| | |
if line_item.quantity > num_to_remove | |
split_line_item = line_item.split(take: num_to_remove) | |
break | |
else | |
index = cart.line_items.find_index(line_item) | |
cart.line_items.delete_at(index) | |
num_to_remove -= line_item.quantity | |
end | |
break if num_to_remove <= 0 | |
end | |
end | |
end | |
# ================================================================ | |
# END PRODUCT QUANTITY LIMITS | |
# ================================================================ | |
# ================================================================ | |
# RUN THE CAMPAIGNS | |
# ================================================================ | |
CAMPAIGNS = [ | |
ProductQuantityLimitCampaign.new( | |
QUANTITY_LIMITS[:enable], | |
QUANTITY_LIMITS[:campaigns], | |
), | |
] | |
CAMPAIGNS.each do |campaign| | |
campaign.run(Input.cart) | |
end | |
# ================================================================ | |
# END RUN THE CAMPAIGNS | |
# ================================================================ | |
# ================================================================ | |
# LINE ITEM PROPERTY DISCOUNT | |
# ================================================================ | |
## This script applies discount to each line item based on a line item property's value. | |
## | |
# ================================================================ | |
# ================================================================ | |
Input.cart.line_items.each do |item| | |
# Set the line item property in Shopify theme When adding item to cart | |
# | |
# line item property is a key/value pair. Example: {"_someCoolPromo": "1"} | |
# | |
# In this example: | |
# "_someCoolPromo" is the key we look for here and must ALWAYS start with an underscore. | |
# "1" is the value and respresents a value of $1.00 ("1.25" for $1.25, "1.5" for $1.50 etc.) | |
# | |
# ================================================================ | |
## Check if line item has the specific line item property and if so apply the relevant discount (if greater than sale discount) | |
if item.properties.has_key?("_lineItemPropertyKeyHere") && (item.properties["_lineItemPropertyKeyHere"].to_f > 0.00) | |
line_item_discount_value = Float(item.properties["_lineItemPropertyKeyHere"]) | |
discount_amount = Money.new(cents: 100) * line_item_discount_value | |
discount_message = "Some Cool Promo" | |
## Check if line item is currently on sale and if so get the sale discount | |
if !item.variant.compare_at_price.nil? && item.variant.compare_at_price.cents > 0 | |
## Item has compare_at_price, check if is on sale | |
if item.variant.compare_at_price > item.variant.price | |
## Item is on sale, apply bundle item discount based on item.variant.compare_at_price | |
sale_discount = item.variant.compare_at_price - item.variant.price | |
if line_item_discount_value > (Float(sale_discount.cents.to_s) / 100) | |
new_line_price = [item.variant.compare_at_price - (discount_amount * item.quantity), Money.zero].max | |
item.change_line_price(new_line_price, { message: discount_message }) | |
end | |
else | |
## Item is not on sale, apply line item discount based on item.line_price | |
new_line_price = [item.line_price - (discount_amount * item.quantity), Money.zero].max | |
item.change_line_price(new_line_price, { message: discount_message }) | |
end | |
else | |
## Item does not have compare_at_price, apply line item discount based on item.line_price | |
new_line_price = [item.line_price - (discount_amount * item.quantity), Money.zero].max | |
item.change_line_price(new_line_price, { message: discount_message }) | |
end | |
end | |
end | |
# ================================================================ | |
# END LINE ITEM PROPERTY DISCOUNT | |
# ================================================================ | |
Output.cart = Input.cart |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment