Personalized checkout and custom promotions with Shopify Scripts
Using script editor we can set a discount for product with the following (just partial)
PRODUCT_DISCOUNT_TIERS = [
{
product_selector_match_type: :include,
product_selector_type: :type,
product_selectors: ["mytype"],
tiers: [
{
quantity: 1,
discount_type: :dollar,
discount_amount: 0.01,
discount_message: 'you just saved a penny!',
},
.....
I would like to replicate this on multiple product types. However I can only publish on script at a time. My original idea was to do a script for each product discount I'd like. Sometimes I'm going to use a type other times a tag. How would I be able to accomplish this?
Solved! Go to the solution
This is an accepted solution.
After reviewing this I noticed that PRODUCT_DISCOUNT_TIERS is an array. I was able to add more discount products with a different type by simply creating a different object in the array.
PRODUCT_DISCOUNT_TIERS = [
{
product_selector_match_type: :include,
product_selector_type: :type,
product_selectors: ["mytype"],
tiers: [
{
quantity: 1,
discount_type: :dollar,
discount_amount: 0.01,
discount_message: 'you just saved a penny!',
}....
},
{ // new object with product_selector etc here...
}
We support this kind of thing using the Playwright app. I used it to quickly generate an example below. Hopefully that's a helpful starting point for you. If for some reason copying and pasting the script doesn't work, I've attached it as well.
Let me know if that works for you!
Best,
Matthew
######################################################################## ## Create Shopify scripts without writing code at playwrightapp.com ## ######################################################################## 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 ProductTypeSelector < Selector def initialize(match_type, product_types) @invert = match_type == :not_one @product_types = product_types.map(&:downcase) end def match?(line_item) @invert ^ @product_types.include?(line_item.variant.product.product_type.downcase) end end CAMPAIGNS = [ TieredDiscount.new( :all, nil, nil, ProductTypeSelector.new(:is_one, ["MyTypeA"]), :percent, :line_quantity, [ { :tier => "1", :discount => "0.01", :message => "You saved a penny" }, { :tier => "2", :discount => "0.02", :message => "You saved two pennies" } ] ), TieredDiscount.new( :all, nil, nil, ProductTypeSelector.new(:is_one, ["MyTypeB"]), :percent, :line_quantity, [ { :tier => "1", :discount => "1", :message => "You saved a dollar" }, { :tier => "2", :discount => "2", :message => "You saved two dollars" } ] ) ].freeze CAMPAIGNS.each do |campaign| campaign.run_with_hooks(Input.cart) end Output.cart = Input.cart
Thanks for the great help. I was looking for something more along the lines of any documentation on how to achieve it or some direction on how I can do it. As a developer I'd like to learn how to get it done and not pay the $49 a month your service charges.
This is an accepted solution.
After reviewing this I noticed that PRODUCT_DISCOUNT_TIERS is an array. I was able to add more discount products with a different type by simply creating a different object in the array.
PRODUCT_DISCOUNT_TIERS = [
{
product_selector_match_type: :include,
product_selector_type: :type,
product_selectors: ["mytype"],
tiers: [
{
quantity: 1,
discount_type: :dollar,
discount_amount: 0.01,
discount_message: 'you just saved a penny!',
}....
},
{ // new object with product_selector etc here...
}
As 2024 wraps up, the dropshipping landscape is already shifting towards 2025's trends....
By JasonH Nov 27, 2024Hey Community! It’s time to share some appreciation and celebrate what we have accomplis...
By JasonH Nov 14, 2024In today’s interview, we sat down with @BSS-Commerce to discuss practical strategies f...
By JasonH Nov 13, 2024