Access a community of over 900,000 Shopify Merchants and Partners and engage in meaningful conversations with your peers.
I've been seeing production errors my every script I run in the Script Editor. Everything tested fine but I'm unable to resolve this. Any help would be much obliged.
Below is the script I am running.
class Campaign def initialize(condition, *qualifiers) @condition = (condition.to_s + '?').to_sym @qualifiers = PostCartAmountQualifier ? [] : [] rescue qualifiers.compact @line_item_selector = qualifiers.last unless @line_item_selector qualifiers.compact.each do |qualifier| is_multi_select = qualifier.instance_variable_get(:@conditions).is_a?(Array) if is_multi_select qualifier.instance_variable_get(:@conditions).each do |nested_q| @post_amount_qualifier = nested_q if nested_q.is_a?(PostCartAmountQualifier) @qualifiers << qualifier end else @post_amount_qualifier = qualifier if qualifier.is_a?(PostCartAmountQualifier) @qualifiers << qualifier end end if @qualifiers.empty? end def qualifies?(cart) return true if @qualifiers.empty? @unmodified_line_items = cart.line_items.map do |item| new_item = item.dup new_item.instance_variables.each do |var| val = item.instance_variable_get(var) new_item.instance_variable_set(var, val.dup) if val.respond_to?(:dup) end new_item end if @post_amount_qualifier @qualifiers.send(@condition) do |qualifier| is_selector = false if qualifier.is_a?(Selector) || qualifier.instance_variable_get(:@conditions).any? { |q| q.is_a?(Selector) } is_selector = true end rescue nil if is_selector raise "Missing line item match type" if @li_match_type.nil? cart.line_items.send(@li_match_type) { |item| qualifier.match?(item) } else qualifier.match?(cart, @line_item_selector) end end end def run_with_hooks(cart) before_run(cart) if respond_to?(:before_run) run(cart) after_run(cart) end def after_run(cart) @discount.apply_final_discount if @discount && @discount.respond_to?(:apply_final_discount) revert_changes(cart) unless @post_amount_qualifier.nil? || @post_amount_qualifier.match?(cart) end def revert_changes(cart) cart.instance_variable_set(:@line_items, @unmodified_line_items) end end class BuyXGetX < Campaign def initialize(condition, customer_qualifier, cart_qualifier, buy_item_selector, get_item_selector, discount, buy_x, get_x, max_sets) super(condition, customer_qualifier, cart_qualifier) @line_item_selector = buy_item_selector @get_item_selector = get_item_selector @discount = discount @buy_x = buy_x @get_x = get_x @max_sets = max_sets == 0 ? nil : max_sets end def run(cart) raise "Campaign requires a discount" unless @discount return unless qualifies?(cart) return unless cart.line_items.reduce(0) {|total, item| total += item.quantity } >= @buy_x applicable_buy_items = nil eligible_get_items = nil discountable_sets = 0 # Find the items that qualify for buy_x if @line_item_selector.nil? applicable_buy_items = cart.line_items else applicable_buy_items = cart.line_items.select { |item| @line_item_selector.match?(item) } end # Find the items that qualify for get_x if @get_item_selector.nil? eligible_get_items = cart.line_items else eligible_get_items = cart.line_items.select {|item| @get_item_selector.match?(item) } end # Check if cart qualifies for discounts and limit the discount sets purchased_quantity = applicable_buy_items.reduce(0) { |total, item| total += item.quantity } discountable_sets = (@max_sets ? [purchased_quantity / @buy_x, @max_sets].min : purchased_quantity / @buy_x).to_i return if discountable_sets < 1 discountable_quantity = (discountable_sets * @get_x).to_i # Apply the discounts (sort to discount lower priced items first) eligible_get_items = eligible_get_items.sort_by { |item| item.variant.price } eligible_get_items.each do |item| break if discountable_quantity == 0 if item.quantity <= discountable_quantity @discount.apply(item) discountable_quantity -= item.quantity else new_item = item.split({ take: discountable_quantity }) @discount.apply(new_item) cart.line_items << new_item discountable_quantity = 0 end end end end class ConditionalDiscount < Campaign def initialize(condition, customer_qualifier, cart_qualifier, line_item_selector, discount, max_discounts) super(condition, customer_qualifier, cart_qualifier) @line_item_selector = line_item_selector @discount = discount @items_to_discount = max_discounts == 0 ? nil : max_discounts end def run(cart) raise "Campaign requires a discount" unless @discount return unless qualifies?(cart) applicable_items = cart.line_items.select { |item| @line_item_selector.nil? || @line_item_selector.match?(item) } applicable_items = applicable_items.sort_by { |item| item.variant.price } applicable_items.each do |item| break if @items_to_discount == 0 if (!@items_to_discount.nil? && item.quantity > @items_to_discount) discounted_items = item.split(take: @items_to_discount) @discount.apply(discounted_items) cart.line_items << discounted_items @items_to_discount = 0 else @discount.apply(item) @items_to_discount -= item.quantity if !@items_to_discount.nil? end end end end class Qualifier def partial_match(match_type, item_info, possible_matches) match_type = (match_type.to_s + '?').to_sym if item_info.kind_of?(Array) possible_matches.any? do |possibility| item_info.any? do |search| search.send(match_type, possibility) end end else possible_matches.any? do |possibility| item_info.send(match_type, possibility) end end end def compare_amounts(compare, comparison_type, compare_to) case comparison_type when :greater_than return compare > compare_to when :greater_than_or_equal return compare >= compare_to when :less_than return compare < compare_to when :less_than_or_equal return compare <= compare_to when :equal_to return compare == compare_to else raise "Invalid comparison type" end end end class CartHasItemQualifier < Qualifier def initialize(quantity_or_subtotal, comparison_type, amount, item_selector) @quantity_or_subtotal = quantity_or_subtotal @comparison_type = comparison_type @amount = quantity_or_subtotal == :subtotal ? Money.new(cents: amount * 100) : amount @item_selector = item_selector end def match?(cart, selector = nil) raise "Must supply an item selector for the #{self.class}" if @item_selector.nil? case @quantity_or_subtotal when :quantity total = cart.line_items.reduce(0) do |total, item| total + (@item_selector&.match?(item) ? item.quantity : 0) end when :subtotal total = cart.line_items.reduce(Money.zero) do |total, item| total + (@item_selector&.match?(item) ? item.line_price : Money.zero) end end compare_amounts(total, @comparison_type, @amount) end end class CustomerTagQualifier < Qualifier def initialize(match_type, match_condition, tags) @match_condition = match_condition @invert = match_type == :does_not @tags = tags.map(&:downcase) end def match?(cart, selector = nil) return true if cart.customer.nil? && @invert return false if cart.customer.nil? customer_tags = cart.customer.tags.to_a.map(&:downcase) case @match_condition when :match return @invert ^ ((@tags & customer_tags).length > 0) else return @invert ^ partial_match(@match_condition, customer_tags, @tags) end end end class Selector def partial_match(match_type, item_info, possible_matches) match_type = (match_type.to_s + '?').to_sym if item_info.kind_of?(Array) possible_matches.any? do |possibility| item_info.any? do |search| search.send(match_type, possibility) end end else possible_matches.any? do |possibility| item_info.send(match_type, possibility) end end end end class ProductIdSelector < Selector def initialize(match_type, product_ids) @invert = match_type == :not_one @product_ids = product_ids.map { |id| id.to_i } end def match?(line_item) @invert ^ @product_ids.include?(line_item.variant.product.id) end end class ProductVendorSelector < Selector def initialize(match_type, vendors) @invert = match_type != :is_one @vendors = vendors.map(&:downcase) end def match?(line_item) @invert ^ @vendors.include?(line_item.variant.product.vendor.downcase) end end class PercentageDiscount def initialize(percent, message) @discount = (100 - percent) / 100.0 @message = message end def apply(line_item) line_item.change_line_price(line_item.line_price * @discount, message: @message) end end CAMPAIGNS = [ BuyXGetX.new( :all, nil, CartHasItemQualifier.new( :quantity, :greater_than_or_equal, 1, ProductIdSelector.new( :is_one, ["2423846273082", "2423850860602", "2424427282490", "4379124105274", "4379125776442"] ) ), ProductIdSelector.new( :is_one, ["2423846273082", "2423850860602", "2424427282490", "4379124105274", "4379125776442"] ), ProductIdSelector.new( :is_one, ["610077245498"] ), PercentageDiscount.new( 100, "" ), 1, 1, 0 ), ConditionalDiscount.new( :all, CustomerTagQualifier.new( :does, :start_with, ["pro"] ), CartHasItemQualifier.new( :quantity, :greater_than, 0, ProductVendorSelector.new( :is_one, ["Aprés Nail", "Apres Nail"] ) ), ProductVendorSelector.new( :is_one, ["Apres Nail", "Aprés Nail"] ), PercentageDiscount.new( 20, "Pro Member Discount!" ), 0 ) ].freeze CAMPAIGNS.each do |campaign| campaign.run_with_hooks(Input.cart) end Output.cart = Input.cart
Hi, did you resolve this? I have recently posted a message about "InstructionQuotaExceeded" where customers are created very large carts (entering 1million in the qty etc) & I am interested in ways to detect & prevent such errors. I wondered if you were seeing a similar problem if you're code worked in testing?
Seeing a similar issue myslef. Did you ever get resoltion to this?
Hi everyone,
I worked around this problem by placing some boundary conditions in my code after experimenting with cart sizes & working out where the limits get exceeded.
I assume these attempts are trying to get free stuff or trying to break the checkout, so if the numbers are too high my code simply ignores the cart & doesn't even attempt to apply a discount...
Here's my main class that handles this before applying the discount code (which I have left out for simplicity).
class PromotionCampaign
def initialize(cart)
@cart = cart
@Max_items = 20
@Max_qty = 80
end
def run!
if(@cart.line_items.count <= @Max_items)
if(@cart.line_items.map {|cart_line| cart_line.quantity }.inject(0, :+)<=@max_qty)
discounted_products = ProductBuyXForX.new(@cart).run!
end
end
@cart
end
end
Hope that is useful to someone!
User | RANK |
---|---|
3 | |
2 | |
2 | |
1 | |
1 |