-
-
Save ihorduchenko/eb4ab1c7fec0b7e4d8a055000bd2db91 to your computer and use it in GitHub Desktop.
Shopify Script Discounting example
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
# ================================ Customizable Settings ================================ | |
# ================================================================ | |
# Tiered Discounts by Quantity | |
# | |
# A list of discount tier offers where: | |
# - '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 | |
# - ':id' to find products by ID | |
# - ':type' to find products by type | |
# - 'product_selector' is a list of tags or IDs to identify | |
# qualifying products | |
# - 'tiers' is a list of tiers where: | |
# - 'quantity' is the minimum quantity you need to buy to | |
# qualify | |
# - 'discount_type' is the type of discount to provide. Can be | |
# either: | |
# - ':percent' | |
# - ':dollar' | |
# - 'discount_amount' is the percentage/dollar discount to | |
# apply (per item) | |
# - 'discount_message' is the message to show when a discount | |
# is applied | |
# ================================================================ | |
PRODUCT_DISCOUNT_TIERS = [ | |
{ | |
product_selector_match_type: :include, | |
product_selector_type: :type, | |
product_selectors: ["Socks"], | |
tiers: [ | |
{ | |
quantity: 3, | |
discount_type: :dollar, | |
discount_amount: 8.333, | |
discount_message: '3 pairs of Socks for $59!', | |
}, | |
], | |
}, | |
] | |
# ================================================================ | |
# Bundle Cart Discounts (ie. 'Buy a bib and a jersey, and get 15% | |
# off all items) | |
# | |
# A list of bundle discounts where: | |
# - 'bundle_items' is a list of the items that comprise the | |
# bundle, where: | |
# - 'product_id' is the ID of the product | |
# - 'quantity_needed' is the quantity necessary to complete | |
# the bundle | |
# - 'discount_type' is the type of discount to provide. Can be | |
# either: | |
# - ':percent' | |
# - ':dollar' | |
# - 'discount_amount' is the percentage/dollar discount to | |
# apply (per item) | |
# - 'discount_message' is the message to show when a discount | |
# is applied | |
# ================================================================ | |
BUNDLE_DISCOUNTS = [ | |
{ | |
bundle_items: [ | |
{ | |
product_selector_match_type: :include, | |
product_selector_type: :type, | |
product_selectors: ["Men's Bib Shorts", "Women's Bib Shorts"], | |
quantity_needed: 1 | |
}, | |
{ | |
product_selector_match_type: :include, | |
product_selector_type: :type, | |
product_selectors: ["Jersey", "Men's Jersey", "Women's Jersey"], | |
quantity_needed: 1 | |
}, | |
], | |
discount_type: :percent, | |
discount_amount: 15, | |
discount_message: "15% off when buying a bib and a jersey!", | |
}, | |
] | |
# ================================ Script Code (do not edit) ================================ | |
# ================================================================ | |
# ProductSelector | |
# | |
# Finds matching products - either by product ID or tag | |
# ================================================================ | |
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 @selector_type == :tag | |
product_tags = line_item.variant.product.tags.map { |tag| tag.downcase.strip } | |
@selectors = @selectors.map { |selector| selector.downcase.strip } | |
(@selectors & product_tags).send(@comparator) | |
elsif @selector_type == :id | |
if @match_type == :include | |
@selectors.include?(line_item.variant.product.id) | |
else | |
[email protected]?(line_item.variant.product.id) | |
end | |
else | |
@selectors = @selectors.map { |selector| selector.downcase.strip } | |
if @match_type == :include | |
@selectors.include?(line_item.variant.product.product_type.downcase.strip) | |
else | |
[email protected]?(line_item.variant.product.product_type.downcase.strip) | |
end | |
end | |
end | |
end | |
# ================================================================ | |
# BundleSelector | |
# | |
# Finds any items that are part of the entered bundle and saves | |
# them | |
# ================================================================ | |
class BundleSelector | |
def initialize(bundle_items) | |
@bundle_items = bundle_items.reduce([]) do |acc, bundle_item| | |
product_selector = ProductSelector.new( | |
bundle_item[:product_selector_match_type], | |
bundle_item[:product_selector_type], | |
bundle_item[:product_selectors], | |
) | |
bundle_item_info = { | |
product_selector: product_selector, | |
quantity_needed: bundle_item[:quantity_needed], | |
total_quantity: 0, | |
} | |
acc.push(bundle_item_info) | |
acc | |
end | |
end | |
def build(cart) | |
cart.line_items.each do |line_item| | |
@bundle_items.each do |bundle_item| | |
next unless bundle_item[:product_selector].match?(line_item) | |
bundle_item[:total_quantity] += line_item.quantity | |
end | |
end | |
@bundle_items | |
end | |
end | |
# ================================================================ | |
# DiscountApplicator | |
# | |
# Applies the entered discount to the supplied line item | |
# ================================================================ | |
class DiscountApplicator | |
def initialize(discount_type, discount_amount, discount_message) | |
@discount_type = discount_type | |
@discount_message = discount_message | |
@discount_amount = if discount_type == :percent | |
1 - (discount_amount * 0.01) | |
else | |
Money.new(cents: 100) * discount_amount | |
end | |
end | |
def apply(line_item) | |
new_line_price = if @discount_type == :percent | |
line_item.line_price * @discount_amount | |
else | |
[line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max | |
end | |
line_item.change_line_price(new_line_price, message: @discount_message) | |
end | |
end | |
# ================================================================ | |
# TieredPricingCampaign | |
# | |
# Finds any products of the entered type and applies the | |
# associated tier discount | |
# ================================================================ | |
class TieredPricingCampaign | |
def initialize(discounts) | |
@discounts = discounts | |
end | |
def run(cart) | |
@discounts.each do |discount| | |
product_selector = ProductSelector.new( | |
discount[:product_selector_match_type], | |
discount[:product_selector_type], | |
discount[:product_selectors], | |
) | |
tiers = discount[:tiers].sort_by { |tier| tier[:quantity] }.reverse | |
applicable_items = cart.line_items.select { |line_item| product_selector.match?(line_item) } | |
next if applicable_items.nil? | |
total_applicable_quantity = applicable_items.map(&:quantity).reduce(0, :+) | |
applicable_tier = tiers.find { |tier| tier[:quantity] <= total_applicable_quantity } | |
next if applicable_tier.nil? | |
discount_applicator = DiscountApplicator.new( | |
applicable_tier[:discount_type], | |
applicable_tier[:discount_amount], | |
applicable_tier[:discount_message] | |
) | |
applicable_items.each do |line_item| | |
next if line_item.discounted? | |
discount_applicator.apply(line_item) | |
end | |
end | |
end | |
end | |
# ================================================================ | |
# BundleCartDiscountCampaign | |
# | |
# If the entered bundle is present, the entered discount is | |
# applied to each item in the cart | |
# ================================================================ | |
class BundleCartDiscountCampaign | |
def initialize(discounts) | |
@discounts = discounts | |
end | |
def run(cart) | |
@discounts.each do |discount| | |
bundle_selector = BundleSelector.new(discount[:bundle_items]) | |
bundle_items = bundle_selector.build(cart) | |
next if bundle_items.any? do |product_info| | |
product_info[:total_quantity] < product_info[:quantity_needed] | |
end | |
discount_applicator = DiscountApplicator.new( | |
discount[:discount_type], | |
discount[:discount_amount], | |
discount[:discount_message] | |
) | |
cart.line_items.each do |line_item| | |
next if line_item.discounted? | |
discount_applicator.apply(line_item) | |
end | |
end | |
end | |
end | |
CAMPAIGNS = [ | |
TieredPricingCampaign.new(PRODUCT_DISCOUNT_TIERS), | |
BundleCartDiscountCampaign.new(BUNDLE_DISCOUNTS), | |
] | |
CAMPAIGNS.each do |campaign| | |
campaign.run(Input.cart) | |
end | |
Output.cart = Input.cart |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment