Personalized checkout and custom promotions with Shopify Scripts
Hi there. I want to make a discount pack where users choose 1 'buy' item and 1 more in order to get a discount (not random ones, but using exact Product IDs instead) -> they get 20% off EACH item.
I want to make the code so that the second item can vary and there are several combinations. Right now the way it works is that I can also get a discount on 2 x of the same product (which is a no-go). I assume the changes should be somewhere in here (perhaps an if statement?):
CAMPAIGNS = [
BuyXGetX.new(
:all,
nil,
nil,
ProductIdSelector.new(
:is_one,
["2631126679652", "2631121043556"]
),
ProductIdSelector.new(
:is_one,
["2631121043556", "2631126679652"]
),
PercentageDiscount.new(
20,
"Discount applied!"
),
1,
1,
0
)
].freeze
_____
The whole snippet:
class Campaign
def initialize(condition, *qualifiers)
@condition = (condition.to_s + '?').to_sym
@qualifiers = PostCartAmountQualifier ? [] : [] rescue qualifiers.compact
@Anonymous_item_selector = qualifiers.last unless @Anonymous_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, @Anonymous_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)
@Anonymous_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 @Anonymous_item_selector.nil?
applicable_buy_items = cart.line_items
else
applicable_buy_items = cart.line_items.select { |item| @Anonymous_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 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 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,
nil,
ProductIdSelector.new(
:is_one,
["2631126679652", "2631121043556"]
),
ProductIdSelector.new(
:is_one,
["2631121043556", "2631126679652"]
),
PercentageDiscount.new(
20,
"Discount applied!"
),
1,
1,
0
)
].freeze
CAMPAIGNS.each do |campaign|
campaign.run_with_hooks(Input.cart)
end
Output.cart = Input.cart
We recently spoke with Zopi developers @Zopi about how dropshipping businesses can enha...
By JasonH Oct 23, 2024A big shout out to all of the merchants who participated in our AMA with 2H Media: Holi...
By Jacqui Oct 21, 2024We want to take a moment to celebrate the incredible ways you all engage with the Shopi...
By JasonH Oct 15, 2024