Personalized checkout and custom promotions with Shopify Scripts
Hi all,
I would like to give the customer a total discount of X when they buy 3, 5 or 7 products in a certain category. The products where the discount is valid all have a certain tag: men or women.
I'm now using the code below, but the code is not working. Does anyone know how to fix this?
Thanks in advance!!
DISCOUNTS_BY_QUANTITY = { 3 => 10, 5 => 15, 7 => 20, } tags_needed = ['men', 'women'] Input.cart.line_items.each do |line_item| next unless tags_needed.include?(line_item.variant.product.tags) quantity, discount = DISCOUNTS_BY_QUANTITY.find do |quantity, _| line_item.quantity >= quantity end next unless discount # message = "#{discount}% off when buying at least #{quantity}." message = 'YOU SAVED TIER_AMOUNT BY BUYING 3 OR MORE' line_item.change_line_price( line_item.line_price * (Decimal.new(1) - discount.to_d / 100), message: message, ) end Output.cart = Input.cart
Hi Roberto,
The tag comparer in your script will only pass flow if all of the tags defined in the script are present on the product. So in this case the product would need to have both a men and women tag.
Try the following out instead.
DISCOUNTS_BY_QUANTITY = { 3 => 10, 5 => 15, 7 => 20, } tags_needed = ['men', 'women'] Input.cart.line_items.each do |line_item| next if (tags_needed & line_item.variant.product.tags).empty? quantity, discount = DISCOUNTS_BY_QUANTITY.find do |quantity, _| line_item.quantity >= quantity end next unless discount # message = "#{discount}% off when buying at least #{quantity}." message = 'YOU SAVED TIER_AMOUNT BY BUYING 3 OR MORE' line_item.change_line_price( line_item.line_price * (Decimal.new(1) - discount.to_d / 100), message: message, ) end Output.cart = Input.cart
Cheers,
Elliott
Thanks, now the discount is given on one specific product.
But I would like to give discounts, no matter if they 5 the same products or 5 different products (with the tag men or tag women).
Lastly, preferably with the discount at the end of the cart with a total discount and not a discount per product.
Not sure if you already got this working or not but this code should do want you wanted.
It will apply the tier discount based on how many items are in the cart with the 'men' or 'women' tag.
Tier price will show on the cart page and discount message will show on each eligible item on the checkout page.
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 TieredDiscount < Campaign def initialize(condition, customer_qualifier, cart_qualifier, line_item_selector, discount_type, tier_type, discount_tiers) super(condition, customer_qualifier, cart_qualifier) @line_item_selector = line_item_selector @discount_type = discount_type @tier_type = tier_type @discount_tiers = discount_tiers.sort_by {|tier| tier[:discount].to_f } end def init_discount(amount, message) case @discount_type when :fixed return FixedTotalDiscount.new(amount, message, :split) when :percent return PercentageDiscount.new(amount, message) when :per_item return FixedItemDiscount.new(amount, message) end end def run(cart) return unless qualifies?(cart) applicable_items = cart.line_items.select { |item| @line_item_selector.nil? || @line_item_selector.match?(item) } case @tier_type when :customer_tag return if cart.customer.nil? customer_tags = cart.customer.tags.map(&:downcase) qualified_tiers = @discount_tiers.select { |tier| customer_tags.include?(tier[:tier].downcase) } when :cart_subtotal cart_total = cart.subtotal_price qualified_tiers = @discount_tiers.select { |tier| cart_total >= Money.new(cents: tier[:tier].to_i * 100) } when :discountable_total discountable_total = applicable_items.reduce(Money.zero) { |total, item| total + item.line_price } qualified_tiers = @discount_tiers.select { |tier| discountable_total >= Money.new(cents: tier[:tier].to_i * 100) } when :discountable_total_items discountable_quantity = applicable_items.reduce(0) { |total, item| total + item.quantity } qualified_tiers = @discount_tiers.select { |tier| discountable_quantity >= tier[:tier].to_i } when :cart_items cart_quantity = cart.line_items.reduce(0) { |total, item| total + item.quantity } qualified_tiers = @discount_tiers.select { |tier| cart_quantity >= tier[:tier].to_i } end if @tier_type == :line_quantity applicable_items.each do |item| qualified_tiers = @discount_tiers.select { |tier| item.quantity >= tier[:tier].to_i } next if qualified_tiers.empty? discount_amount = qualified_tiers.last[:discount].to_f discount_message = qualified_tiers.last[:message] discount = init_discount(discount_amount, discount_message) discount.apply(item) discount.apply_final_discount if discount.respond_to?(:apply_final_discount) end else return if qualified_tiers.empty? discount_amount = qualified_tiers.last[:discount].to_f discount_message = qualified_tiers.last[:message] @discount = init_discount(discount_amount, discount_message) applicable_items.each { |item| @discount.apply(item) } end 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 class FixedTotalDiscount def initialize(amount, message, behaviour = :to_zero) @amount = Money.new(cents: amount * 100) @message = message @discount_applied = Money.zero @all_items = [] @is_split = behaviour == :split end def apply(line_item) if @is_split @all_items << line_item else return unless @discount_applied < @amount discount_to_apply = [(@amount - @discount_applied), line_item.line_price].min line_item.change_line_price(line_item.line_price - discount_to_apply, {message: @message}) @discount_applied += discount_to_apply end end def apply_final_discount return if @all_items.length == 0 total_items = @all_items.length total_quantity = 0 total_cost = Money.zero @all_items.each do |item| total_quantity += item.quantity total_cost += item.line_price end @all_items.each_with_index do |item, index| discount_percent = item.line_price.cents / total_cost.cents if total_items == index + 1 discount_to_apply = Money.new(cents: @amount.cents - @discount_applied.cents.floor) else discount_to_apply = Money.new(cents: @amount.cents * discount_percent) end item.change_line_price(item.line_price - discount_to_apply, {message: @message}) @discount_applied += discount_to_apply end end end class FixedItemDiscount def initialize(amount, message) @amount = Money.new(cents: amount * 100) @message = message end def apply(line_item) per_item_price = line_item.variant.price per_item_discount = [(@amount - per_item_price), @amount].max discount_to_apply = [(per_item_discount * line_item.quantity), line_item.line_price].min line_item.change_line_price(line_item.line_price - discount_to_apply, {message: @message}) 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 ProductTagSelector < Selector def initialize(match_type, match_condition, tags) @match_condition = match_condition @invert = match_type == :does_not @tags = tags.map(&:downcase) end def match?(line_item) product_tags = line_item.variant.product.tags.to_a.map(&:downcase) case @match_condition when :match return @invert ^ ((@tags & product_tags).length > 0) else return @invert ^ partial_match(@match_condition, product_tags, @tags) end end end CAMPAIGNS = [ TieredDiscount.new( :all, nil, nil, ProductTagSelector.new( :does, :match, ["men", "women"] ), :percent, :discountable_total_items, [{:tier => "3", :discount => "10", :message => "YOU SAVED 10% BY BUYING 3 OR MORE"}, {:tier => "5", :discount => "15", :message => "YOU SAVED 15% BY BUYING 5 OR MORE"}, {:tier => "7", :discount => "20", :message => "YOU SAVED 20% BY BUYING 7 OR MORE"}] ) ].freeze CAMPAIGNS.each do |campaign| campaign.run_with_hooks(Input.cart) end Output.cart = Input.cart
Code was made from the Shopify Scripts Creator on Github, if you haven't checked it out it's the best way to create all your scripts.
Hi :(
DISCOUNTS_BY_QUANTITY = {
5 => 5,
3 => 3,
}
tags_needed = ['Makeup']
Input.cart.line_items.each do |line_item|
#next if line_item.variant.product.gift_card?
next if (tags_needed & line_item.variant.product.tags).empty?
quantity, discount = DISCOUNTS_BY_QUANTITY.find do |quantity, _|
line_item.quantity >= quantity
end
next unless discount
#message = "#{discount}% off when buying at least #{quantity}."
#message = "#{discount}% descuento por #{line_item.quantity} con #{quantity}"
if line_item.quantity == 3
message = "Add 2 More Pack and Get a 5% Discount"
elsif line_item.quantity == 4
message = "Add 1 More Pack and Get a 5% Discount"
else line_item.quantity >= 5
message = "Great you have your full discount!"
end
line_item.change_line_price(
line_item.line_price * (Decimal.new(1) - discount.to_d / 100),
message: message,
)
end
Output.cart = Input.cart
I want a message to appear when the product quantity is 1 but the discount does not apply !!
Hi Roberto,
Messages only appear when a discount is actually applied. If the customer isn't eligible for the discount, then no message would appear.
I hope that is helpful,
Matthew
ok I understand and if I want a message to appear before applying a discount, since the discount will be applied only until it reaches the amount of 3 !! So I want to encourage you to add 2 more products to reach the amount of 3 !!
Portrait of Stephen positioned next to an image of planet Earth, with the Stephen's World ...
By JasonH Mar 18, 2024Digital marketers and app developers have tracked activity in apps and websites for yea...
By Ollie Mar 13, 2024February was an exciting month with Shopify Editions, informative webinars, and more! F...
By JasonH Mar 7, 2024