Personalized checkout and custom promotions with Shopify Scripts
I have a Shopify Script that applies a special pricing rule. For instance, if a customer orders 3 units of a device tagged as "3 For 10", the total price for those items will be £20. This rule is designed to work dynamically, so if they add 6 units to the cart, the total price would adjust to £20 accordingly.
However, there is an issue when the customer adds 9 different flavors of items, each tagged as "3 For 10". In this case, the discount doesn't apply at all, and an error message "InstructionQuotaExceeded" is encountered. I'm seeking assistance to fix this problem.
Please find the script below:
BUY_X_GET_Y_FOR_Z = [
{
product_selector_match_type: :include,
product_selector_type: :tag,
product_selectors: ["2 For 10"],
quantity_to_buy: 2,
final_price: 10,
},
{
product_selector_match_type: :include,
product_selector_type: :tag,
product_selectors: ["4 For 10"],
quantity_to_buy: 4,
final_price: 10,
},
{
product_selector_match_type: :include,
product_selector_type: :tag,
product_selectors: ["5 For 20"],
quantity_to_buy: 5,
final_price: 20,
},
{
product_selector_match_type: :include,
product_selector_type: :tag,
product_selectors: ["5 For 50"],
quantity_to_buy: 5,
final_price: 50,
},
{
product_selector_match_type: :include,
product_selector_type: :tag,
product_selectors: ["3 For 60"],
quantity_to_buy: 3,
final_price: 60,
},
{
product_selector_match_type: :include,
product_selector_type: :tag,
product_selectors: ["3 For 15"],
quantity_to_buy: 3,
final_price: 15,
},
{
product_selector_match_type: :include,
product_selector_type: :tag,
product_selectors: ["3 For 10"],
quantity_to_buy: 3,
final_price: 10,
},
{
product_selector_match_type: :include,
product_selector_type: :tag,
product_selectors: ["3 For 12"],
quantity_to_buy: 3,
final_price: 12,
},
]
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 all(line_item)
true
end
end
# ================================================================
# DollarDiscountApplicator
#
# Applies the entered discount to the supplied line item.
# ================================================================
class DollarDiscountApplicator
def initialize(discount_message)
@discount_message = discount_message
end
def apply(line_item, discount_amount)
new_line_price = line_item.line_price - discount_amount
line_item.change_line_price(new_line_price, message: @discount_message)
end
end
# ================================================================
# BuyXOfYForZCampaign
#
# Buy a certain number of matching items for a specific price.
# ================================================================
class BuyXOfYForZCampaign
def initialize(campaigns)
@campaigns = campaigns
end
def run(cart)
@campaigns.each do |campaign|
product_selector = ProductSelector.new(
campaign[:product_selector_match_type],
campaign[:product_selector_type],
campaign[:product_selectors],
)
eligible_items = cart.line_items.select { |line_item| product_selector.match?(line_item) }
next if eligible_items.nil?
eligible_item_count = eligible_items.map(&:quantity).reduce(0, :+)
quantity_to_buy = campaign[:quantity_to_buy]
number_of_offers = (eligible_item_count / quantity_to_buy).floor
next unless number_of_offers > 0
number_of_discountable_items = number_of_offers * quantity_to_buy
total_offer_price = Money.new(cents: 100) * (number_of_offers * campaign[:final_price])
discount_applicator = DollarDiscountApplicator.new(campaign[:discount_message])
self.loop_items(cart, eligible_items, number_of_discountable_items, total_offer_price, discount_applicator)
end
end
def loop_items(cart, line_items, num_to_discount, total_price, discount_applicator)
current_price = Money.zero
avg_price = total_price * (1 / num_to_discount)
line_items = line_items.sort_by { |line_item| line_item.variant.price }
line_items.each do |line_item|
break if num_to_discount <= 0
if line_item.quantity > num_to_discount
split_line_item = line_item.split(take: num_to_discount)
discount_amount = split_line_item.line_price - (total_price - current_price)
discount_applicator.apply(split_line_item, discount_amount)
position = cart.line_items.find_index(line_item)
cart.line_items.insert(position + 1, split_line_item)
break
elsif line_item.quantity == num_to_discount
discount_amount = line_item.line_price - (total_price - current_price)
discount_applicator.apply(line_item, discount_amount)
break
else
if line_item.variant.price <= avg_price
current_price += line_item.line_price
else
discount_amount = (line_item.variant.price - avg_price) * line_item.quantity
current_price += (line_item.line_price - discount_amount)
discount_applicator.apply(line_item, discount_amount)
end
num_to_discount -= line_item.quantity
end
end
end
end
CAMPAIGNS = [
BuyXOfYForZCampaign.new(BUY_X_GET_Y_FOR_Z),
]
CAMPAIGNS.each do |campaign|
campaign.run(Input.cart)
end
Output.cart = Input.cart
Hey Community! As the holiday season unfolds, we want to extend heartfelt thanks to a...
By JasonH Dec 6, 2024Dropshipping, a high-growth, $226 billion-dollar industry, remains a highly dynamic bus...
By JasonH Nov 27, 2024Hey Community! It’s time to share some appreciation and celebrate what we have accomplis...
By JasonH Nov 14, 2024