商品項目指令碼範例
商品項目指令碼會影響購物車中的商品項目,且會變更其屬性與價格。每次購物車屬性變更時,會執行這些指令碼,例如新增或移除商品項目,變更運送地址或新增折扣代碼。
若要在此頁面使用範本,請使用空白範本建立新的指令碼。
步驟:
- 在 Shopify 管理介面 中,前往應用程式 > Script Editor。
- 按一下建立指令碼。
- 點擊「商品項目」。
- 選取空白範本,然後按一下建立指令碼。
- 在 Ruby 來源程式碼區段中,刪除預設程式碼行:
Output.cart = Input.cart
- 從此頁面複製一個指令碼,貼入 Ruby 來源程式碼區段。
- 編輯此指令碼的自訂設定區段,以便您的商店啟用此功能。
- 測試您的指令碼。如需更多資訊,請參閱「對 Shopify 指令碼進行測試和偵錯」。
- 測試之後:
- 按一下「儲存草稿」,儲存尚未發佈的指令碼草稿,或
- 按一下「儲存並發佈」,建立並發佈指令碼
此頁面上
以消費金額計算的分級購物車折扣
使用此程式碼提供金額折扣,該折扣依據其購物車中商品的總價值而增加。此折扣會盡可能平均分攤於購物車的各個商品。
舉例來說,在顧客消費超過 $150 美元時,提供 $25 美元的總額折扣;消費超過 $300 美元時,提供 $50 美元的折扣;或在其消費超過 $400 美元時,提供 $75 美元的折扣。
# ================================ Customizable Settings ================================
# ================================================================
# Tiered Cart Discounts by Spend Threshold
#
# If the cart total is greater than (or equal to) an entered
# threshold, the associated discount is applied to the cart. The
# discount will be spread, as evenly as possible, across all items.
#
# - 'threshold' is the spend amount needed to qualify
# - 'discount_amount' is the dollar discount to apply to the
# cart
# - 'discount_message' is the message to show when a discount
# is applied
# ================================================================
SPENDING_THRESHOLDS = [
{
threshold: 150,
discount_amount: 25,
discount_message: 'Spend $150 and get $25 off!',
},
{
threshold: 300,
discount_amount: 50,
discount_message: 'Spend $300 and get $50 off!',
},
{
threshold: 400,
discount_amount: 75,
discount_message: 'Spend $400 and get $75 off!',
},
]
# ================================ Script Code (do not edit) ================================
# ================================================================
# 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
# ================================================================
# TieredCartDiscountBySpendCampaign
#
# If the cart total is greater than (or equal to) an entered
# threshold, the associated discount is applied to the cart. The
# discount will be spread, as evenly as possible, across all items.
# ================================================================
class TieredCartDiscountBySpendCampaign
def initialize(tiers)
@tiers = tiers.sort_by { |tier| tier[:threshold] }.reverse
end
def run(cart)
applicable_tier = @tiers.find { |tier| cart.subtotal_price >= (Money.new(cents: 100) * tier[:threshold]) }
return if applicable_tier.nil?
discount_applicator = DollarDiscountApplicator.new(applicable_tier[:discount_message])
discount_amount = applicable_tier[:discount_amount]
items = cart.line_items.sort_by { |line_item| line_item.variant.price }
self.loop_items(cart, items, discount_amount, discount_applicator)
end
def loop_items(cart, line_items, discount_amount, discount_applicator)
avg_discount = (discount_amount.to_f * (1 / line_items.map(&:quantity).reduce(0, :+))).round(2)
avg_discount = Money.new(cents: 100) * avg_discount
discount_amount = Money.new(cents: 100) * discount_amount
line_items.each_with_index do |line_item, index|
break if discount_amount <= Money.zero
line_discount = avg_discount * line_item.quantity
if discount_amount < line_discount || index == (line_items.size - 1)
discount_update = line_item.line_price > discount_amount ? discount_amount : line_item.line_price
else
discount_update = line_item.line_price > line_discount ? line_discount : line_item.line_price
end
discount_amount -= discount_update
discount_applicator.apply(line_item, discount_update)
end
end
end
CAMPAIGNS = [
TieredCartDiscountBySpendCampaign.new(SPENDING_THRESHOLDS),
]
CAMPAIGNS.each do |campaign|
campaign.run(Input.cart)
end
Output.cart = Input.cart
以消費金額計算的分級折扣
使用此程式碼提供百分比折扣,該折扣依據其購物車中商品的總價值而增加。
舉例來說,在顧客消費 $30 美元 (含) 以上時,提供 10% 折扣;消費 $50 美元 (含) 以上時,提供 15% 折扣;或在其消費 $100 美元 (含) 以上時,提供 20% 折扣。
# ================================ Customizable Settings ================================
# ================================================================
# Tiered Discounts by Spend Threshold
#
# If the cart total is greater than (or equal to) an entered
# threshold, the associated discount is applied to each item.
#
# - 'threshold' is the spend amount needed to qualify
# - 'discount_type' is the type of discount to provide. Can be
# either:
# - ':percent'
# - ':dollar'
# - 'discount_amount' is the percentage/dollar discount to
# apply (per item)
# - 'discount_message' is the message to show when a discount
# is applied
# ================================================================
SPENDING_THRESHOLDS = [
{
threshold: 30,
discount_type: :percent,
discount_amount: 10,
discount_message: 'Spend $30 and get 10% off!',
},
{
threshold: 50,
discount_type: :percent,
discount_amount: 15,
discount_message: 'Spend $50 and get 15% off!',
},
{
threshold: 100,
discount_type: :percent,
discount_amount: 20,
discount_message: 'Spend $100 and get 20% off!',
},
]
# ================================ Script Code (do not edit) ================================
# ================================================================
# DiscountApplicator
#
# Applies the entered discount to the supplied line item.
# ================================================================
class DiscountApplicator
def initialize(discount_type, discount_amount, discount_message)
@discount_type = discount_type
@discount_message = discount_message
@discount_amount = if discount_type == :percent
1 - (discount_amount * 0.01)
else
Money.new(cents: 100) * discount_amount
end
end
def apply(line_item)
new_line_price = if @discount_type == :percent
line_item.line_price * @discount_amount
else
[line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
end
line_item.change_line_price(new_line_price, message: @discount_message)
end
end
# ================================================================
# TieredDiscountBySpendCampaign
#
# If the cart total is greater than (or equal to) an entered
# threshold, the associated discount is applied to each item.
# ================================================================
class TieredDiscountBySpendCampaign
def initialize(tiers)
@tiers = tiers.sort_by { |tier| tier[:threshold] }.reverse
end
def run(cart)
applicable_tier = @tiers.find { |tier| cart.subtotal_price >= (Money.new(cents: 100) * tier[:threshold]) }
return if applicable_tier.nil?
discount_applicator = DiscountApplicator.new(
applicable_tier[:discount_type],
applicable_tier[:discount_amount],
applicable_tier[:discount_message]
)
cart.line_items.each do |line_item|
next if line_item.variant.product.gift_card?
discount_applicator.apply(line_item)
end
end
end
CAMPAIGNS = [
TieredDiscountBySpendCampaign.new(SPENDING_THRESHOLDS),
]
CAMPAIGNS.each do |campaign|
campaign.run(Input.cart)
end
Output.cart = Input.cart
依數量計算的分級商品折扣
使用此程式碼為特定產品提供大量折扣,該百分比折扣依據購物車中的產品總數而增加。
舉例來說,在顧客購買 2 頂帽子 (含) 以上時,各提供 10% 折扣;或在其購買 5 頂帽子 (含) 以上時,提供 15% 折扣。
# ================================ Customizable Settings ================================
# ================================================================
# Tiered Product Discount by Quantity
#
# If the total quantity of matching items is greater than (or
# equal to) an entered threshold, the associated discount is
# applied to each matching item.
#
# - 'product_selector_match_type' determines whether we look for
# products that do or don't match the entered selectors. Can
# be:
# - ':include' to check if the product does match
# - ':exclude' to make sure the product doesn't match
# - 'product_selector_type' determines how eligible products
# will be identified. Can be either:
# - ':tag' to find products by tag
# - ':type' to find products by type
# - ':vendor' to find products by vendor
# - ':product_id' to find products by ID
# - ':variant_id' to find products by variant ID
# - ':subscription' to find subscription products
# - ':all' for all products
# - 'product_selectors' is a list of identifiers (from above) for
# qualifying products. Product/Variant ID lists should only
# contain numbers (ie. no quotes). If ':all' is used, this
# can also be 'nil'.
# - 'tiers' is a list of tiers where:
# - 'quantity' is the minimum quantity you need to buy to
# qualify
# - 'discount_type' is the type of discount to provide. Can be
# either:
# - ':percent'
# - ':dollar'
# - 'discount_amount' is the percentage/dollar discount to
# apply (per item)
# - 'discount_message' is the message to show when a discount
# is applied
# ================================================================
PRODUCT_DISCOUNT_TIERS = [
{
product_selector_match_type: :include,
product_selector_type: :tag,
product_selectors: ["your_tag"],
tiers: [
{
quantity: 2,
discount_type: :percent,
discount_amount: 10,
discount_message: '10% off for 2+',
},
{
quantity: 5,
discount_type: :percent,
discount_amount: 15,
discount_message: '15% off for 5+',
},
],
},
]
# ================================ Script Code (do not edit) ================================
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
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 subscription(line_item)
!line_item.selling_plan_id.nil?
end
def all(line_item)
true
end
end
# ================================================================
# DiscountApplicator
#
# Applies the entered discount to the supplied line item.
# ================================================================
class DiscountApplicator
def initialize(discount_type, discount_amount, discount_message)
@discount_type = discount_type
@discount_message = discount_message
@discount_amount = if discount_type == :percent
1 - (discount_amount * 0.01)
else
Money.new(cents: 100) * discount_amount
end
end
def apply(line_item)
new_line_price = if @discount_type == :percent
line_item.line_price * @discount_amount
else
[line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
end
line_item.change_line_price(new_line_price, message: @discount_message)
end
end
# ================================================================
# TieredProductDiscountByQuantityCampaign
#
# If the total quantity of matching items is greater than (or
# equal to) an entered threshold, the associated discount is
# applied to each matching item.
# ================================================================
class TieredProductDiscountByQuantityCampaign
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],
)
applicable_items = cart.line_items.select { |line_item| product_selector.match?(line_item) }
next if applicable_items.nil?
total_applicable_quantity = applicable_items.map(&:quantity).reduce(0, :+)
tiers = campaign[:tiers].sort_by { |tier| tier[:quantity] }.reverse
applicable_tier = tiers.find { |tier| tier[:quantity] <= total_applicable_quantity }
next if applicable_tier.nil?
discount_applicator = DiscountApplicator.new(
applicable_tier[:discount_type],
applicable_tier[:discount_amount],
applicable_tier[:discount_message]
)
applicable_items.each do |line_item|
discount_applicator.apply(line_item)
end
end
end
end
CAMPAIGNS = [
TieredProductDiscountByQuantityCampaign.new(PRODUCT_DISCOUNT_TIERS),
]
CAMPAIGNS.each do |campaign|
campaign.run(Input.cart)
end
Output.cart = Input.cart
依產品消費金額計算的分級產品折扣
使用此程式碼以依據此購物車中特定商品項目的總價值,所提供的折扣百分比。
舉例來說,在顧客消費 $30 美元 (含) 以上時,提供 10% 折扣;消費 $50 美元 (含) 以上時,提供 15% 折扣;並在其消費 $100 美元 (含) 以上時,提供 20% 折扣,但僅針對符合特定標籤的品項提供折扣。
# ================================ Customizable Settings ================================
# ================================================================
# Tiered Product Discount by Product Spend Threhsold
#
# If the total amount spent on matching items is greather than (or
# equal to) an entered threshold, the associated discount is
# applied to each matching item.
#
# - 'product_selector_match_type' determines whether we look for
# products that do or don't match the entered selectors. Can
# be:
# - ':include' to check if the product does match
# - ':exclude' to make sure the product doesn't match
# - 'product_selector_type' determines how eligible products
# will be identified. Can be either:
# - ':tag' to find products by tag
# - ':type' to find products by type
# - ':vendor' to find products by vendor
# - ':product_id' to find products by ID
# - ':variant_id' to find products by variant ID
# - ':subscription' to find subscription products
# - ':all' for all products
# - 'product_selectors' is a list of identifiers (from above)
# for qualifying products. Product/Variant ID lists should
# only contain numbers (ie. no quotes). If ':all' is used,
# this can also be 'nil'.
# - 'tiers' is a list of tiers where:
# - 'threshold' is the minimum dollar amount needed to
# qualify
# - 'discount_type' is the type of discount to provide. Can be
# either:
# - ':percent'
# - ':dollar'
# - 'discount_amount' is the percentage/dollar discount to
# apply (per item)
# - 'discount_message' is the message to show when a discount
# is applied
# ================================================================
PRODUCT_DISCOUNT_TIERS = [
{
product_selector_match_type: :exclude,
product_selector_type: :tag,
product_selectors: ["your_tag", "another_tag"],
tiers: [
{
threshold: 100,
discount_type: :percent,
discount_amount: 10,
discount_message: 'Spend $100 or more, and get 10% off!',
},
],
},
]
# ================================ Script Code (do not edit) ================================
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
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 subscription(line_item)
!line_item.selling_plan_id.nil?
end
def all(line_item)
true
end
end
# ================================================================
# DiscountApplicator
#
# Applies the entered discount to the supplied line item.
# ================================================================
class DiscountApplicator
def initialize(discount_type, discount_amount, discount_message)
@discount_type = discount_type
@discount_message = discount_message
@discount_amount = if discount_type == :percent
1 - (discount_amount * 0.01)
else
Money.new(cents: 100) * discount_amount
end
end
def apply(line_item)
new_line_price = if @discount_type == :percent
line_item.line_price * @discount_amount
else
[line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
end
line_item.change_line_price(new_line_price, message: @discount_message)
end
end
# ================================================================
# TieredProductDiscountByProductSpendCampaign
#
# If the total amount spent on matching items is greather than (or
# equal to) an entered threshold, the associated discount is
# applied to each matching item.
# ================================================================
class TieredProductDiscountByProductSpendCampaign
def initialize(campaigns)
@campaigns = campaigns
end
def run(cart)
@campaigns.each do |campaign|
if campaign[:product_selector_type] == :all
total_applicable_cost = cart.subtotal_price
applicable_items = cart.line_items
else
product_selector = ProductSelector.new(
campaign[:product_selector_match_type],
campaign[:product_selector_type],
campaign[:product_selectors],
)
applicable_items = cart.line_items.select { |line_item| product_selector.match?(line_item) }
next if applicable_items.nil?
total_applicable_cost = applicable_items.map(&:line_price).reduce(Money.zero, :+)
end
tiers = campaign[:tiers].sort_by { |tier| tier[:threshold] }.reverse
applicable_tier = tiers.find { |tier| total_applicable_cost >= (Money.new(cents: 100) * tier[:threshold]) }
next if applicable_tier.nil?
discount_applicator = DiscountApplicator.new(
applicable_tier[:discount_type],
applicable_tier[:discount_amount],
applicable_tier[:discount_message]
)
applicable_items.each do |line_item|
discount_applicator.apply(line_item)
end
end
end
end
CAMPAIGNS = [
TieredProductDiscountByProductSpendCampaign.new(PRODUCT_DISCOUNT_TIERS),
]
CAMPAIGNS.each do |campaign|
campaign.run(Input.cart)
end
Output.cart = Input.cart
產品折扣
您可以使用這個指令碼針對特定品項提供折扣。
舉例而言,針對已標示 discounted
的品項,向顧客提供 10% 折扣。
# ================================ Customizable Settings ================================
# ================================================================
# Discount by Product
#
# Any matching item will be discounted by the entered amount.
#
# - 'product_selector_match_type' determines whether we look for
# products that do or don't match the entered selectors. Can
# be:
# - ':include' to check if the product does match
# - ':exclude' to make sure the product doesn't match
# - 'product_selector_type' determines how eligible products
# will be identified. Can be either:
# - ':tag' to find products by tag
# - ':type' to find products by type
# - ':vendor' to find products by vendor
# - ':product_id' to find products by ID
# - ':variant_id' to find products by variant ID
# - ':subscription' to find subscription products
# - ':all' for all products
# - 'product_selectors' is a list of identifiers (from above)
# for qualifying products. Product/Variant ID lists should
# only contain numbers (ie. no quotes). If ':all' is used,
# this can also be 'nil'.
# - 'discount_type' is the type of discount to provide. Can be
# either:
# - ':percent'
# - ':dollar'
# - 'discount_amount' is the percentage/dollar discount to
# apply (per item)
# - 'discount_message' is the message to show when a discount
# is applied
# ================================================================
PRODUCT_DISCOUNTS = [
{
product_selector_match_type: :include,
product_selector_type: :tag,
product_selectors: ["your_tag"],
discount_type: :percent,
discount_amount: 10,
discount_message: '10% off tagged products!'
}
]
# ================================ Script Code (do not edit) ================================
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
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 subscription(line_item)
!line_item.selling_plan_id.nil?
end
def all(line_item)
true
end
end
# ================================================================
# DiscountApplicator
#
# Applies the entered discount to the supplied line item.
# ================================================================
class DiscountApplicator
def initialize(discount_type, discount_amount, discount_message)
@discount_type = discount_type
@discount_message = discount_message
@discount_amount = if discount_type == :percent
1 - (discount_amount * 0.01)
else
Money.new(cents: 100) * discount_amount
end
end
def apply(line_item)
new_line_price = if @discount_type == :percent
line_item.line_price * @discount_amount
else
[line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
end
line_item.change_line_price(new_line_price, message: @discount_message)
end
end
# ================================================================
# ProductDiscountCampaign
#
# Any matching item will be discounted by the entered amount.
# ================================================================
class ProductDiscountCampaign
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],
)
discount_applicator = DiscountApplicator.new(
campaign[:discount_type],
campaign[:discount_amount],
campaign[:discount_message]
)
cart.line_items.each do |line_item|
next unless product_selector.match?(line_item)
discount_applicator.apply(line_item)
end
end
end
end
CAMPAIGNS = [
ProductDiscountCampaign.new(PRODUCT_DISCOUNTS),
]
CAMPAIGNS.each do |campaign|
campaign.run(Input.cart)
end
Output.cart = Input.cart
依折扣代碼提供產品折扣
使用此指令碼,讓系統只在顧客使用特定折扣代碼時提供特定品項的折扣。
舉例而言,若顧客使用 DISCOUNT_10
折扣代碼,則針對已標示 discounted
的品項,向顧客提供 10% 折扣。
# ================================ Customizable Settings ================================
# ================================================================
# Product Discount by Discount Code
#
# If any matching discount codes are used, any matching items
# will be discounted by the entered amount.
#
# - 'discount_code_match_type' determines whether the below
# strings should be an exact or partial match. Can be:
# - ':exact' for an exact match
# - ':partial' for a partial match
# - 'discount_codes' is a list of strings to identify discount
# codes
# - 'product_selector_match_type' determines whether we look for
# products that do or don't match the entered selectors. Can
# be:
# - ':include' to check if the product does match
# - ':exclude' to make sure the product doesn't match
# - 'product_selector_type' determines how eligible products
# will be identified. Can be either:
# - ':tag' to find products by tag
# - ':type' to find products by type
# - ':vendor' to find products by vendor
# - ':product_id' to find products by ID
# - ':variant_id' to find products by variant ID
# - ':subscription' to find subscription products
# - ':all' for all products
# - 'product_selectors' is a list of identifiers (from above)
# for qualifying products. Product/Variant ID lists should
# only contain numbers (ie. no quotes). If ':all' is used,
# this can also be 'nil'.
# - 'discount_type' is the type of discount to provide. Can be
# either:
# - ':percent'
# - ':dollar'
# - 'discount_amount' is the percentage/dollar discount to
# apply (per item)
# - 'discount_message' is the message to show when a discount
# is applied
# ================================================================
PRODUCT_DISCOUNTS_BY_DISCOUNT_CODE = [
{
discount_code_match_type: :exact,
discount_codes: ["TESTCODE1", "TESTCODE2"],
product_selector_match_type: :include,
product_selector_type: :tag,
product_selectors: ["your_tag"],
discount_type: :percent,
discount_amount: 10,
discount_message: '10% off tagged products!'
}
]
# ================================ Script Code (do not edit) ================================
# ================================================================
# DiscountCodeSelector
#
# Finds whether the supplied discount code matches any of the
# entered codes.
# ================================================================
class DiscountCodeSelector
def initialize(match_type, discount_codes)
@comparator = match_type == :exact ? '==' : 'include?'
@discount_codes = discount_codes.map { |discount_code| discount_code.upcase.strip }
end
def match?(discount_code)
@discount_codes.any? { |code| discount_code.code.upcase.send(@comparator, code) }
end
end
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
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 subscription(line_item)
!line_item.selling_plan_id.nil?
end
def all(line_item)
true
end
end
# ================================================================
# DiscountApplicator
#
# Applies the entered discount to the supplied line item.
# ================================================================
class DiscountApplicator
def initialize(discount_type, discount_amount, discount_message)
@discount_type = discount_type
@discount_message = discount_message
@discount_amount = if discount_type == :percent
1 - (discount_amount * 0.01)
else
Money.new(cents: 100) * discount_amount
end
end
def apply(line_item)
new_line_price = if @discount_type == :percent
line_item.line_price * @discount_amount
else
[line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
end
line_item.change_line_price(new_line_price, message: @discount_message)
end
end
# ================================================================
# ProductDiscountByCodeCampaign
#
# If any matching discount codes are used, any matching items
# will be discounted by the entered amount.
# ================================================================
class ProductDiscountByCodeCampaign
def initialize(campaigns)
@campaigns = campaigns
end
def run(cart)
return if cart.discount_code.nil?
@campaigns.each do |campaign|
discount_code_selector = DiscountCodeSelector.new(
campaign[:discount_code_match_type],
campaign[:discount_codes]
)
next unless discount_code_selector.match?(cart.discount_code)
product_selector = ProductSelector.new(
campaign[:product_selector_match_type],
campaign[:product_selector_type],
campaign[:product_selectors],
)
discount_applicator = DiscountApplicator.new(
campaign[:discount_type],
campaign[:discount_amount],
campaign[:discount_message]
)
cart.line_items.each do |line_item|
next unless product_selector.match?(line_item)
discount_applicator.apply(line_item)
end
end
end
end
CAMPAIGNS = [
ProductDiscountByCodeCampaign.new(PRODUCT_DISCOUNTS_BY_DISCOUNT_CODE),
]
CAMPAIGNS.each do |campaign|
campaign.run(Input.cart)
end
Output.cart = Input.cart
購買特定金額的產品,便可獲得第二筆金額折扣
使用此指令碼,如果購買超過指定數量的特定商品便可提供折扣。
舉例而言,在顧客購買 3 件已標示 discount
的品項時,第四件標示 discount
的品項可獲得 50% 折扣。
# ================================ Customizable Settings ================================
# ================================================================
# Buy X, Get Y For Z Discount
#
# Buy a certain number of matching items, get a certain number
# of the same matching items with the entered discount applied. For
# example:
#
# "Buy 2 products tagged with 'tag', get another product
# tagged with 'tag' for 10% off"
#
# - 'product_selector_match_type' determines whether we look for
# products that do or don't match the entered selectors. Can
# be:
# - ':include' to check if the product does match
# - ':exclude' to make sure the product doesn't match
# - 'product_selector_type' determines how eligible products
# will be identified. Can be either:
# - ':tag' to find products by tag
# - ':type' to find products by type
# - ':vendor' to find products by vendor
# - ':product_id' to find products by ID
# - ':variant_id' to find products by variant ID
# - ':subscription' to find subscription products
# - ':all' for all products
# - 'product_selectors' is a list of identifiers (from above) for
# qualifying products. Product/Variant ID lists should only
# contain numbers (ie. no quotes). If ':all' is used, this
# can also be 'nil'.
# - 'quantity_to_buy' is the number of products needed to
# qualify
# - 'quantity_to_discount' is the number of products to discount
# - 'discount_type' is the type of discount to provide. Can be
# either:
# - ':percent'
# - ':dollar'
# - 'discount_amount' is the percentage/dollar discount to
# apply (per item)
# - 'discount_message' is the message to show when a discount
# is applied
#
# Something to note for the case of running multiple offers is
# that there shouldn't be any overlap between product selection
# as this can lead to extra discounting. For example, you should
# NOT offer "Buy 1 Product X, get 1 50% off", as well as "Buy 2
# Product X, get 1 free"
# ================================================================
BUY_X_GET_Y_FOR_Z = [
{
product_selector_match_type: :include,
product_selector_type: :all,
product_selectors: nil,
quantity_to_buy: 1,
quantity_to_discount: 1,
discount_type: :percent,
discount_amount: 50,
discount_message: 'Buy one item, get the second 50% off!',
},
]
# ================================ Script Code (do not edit) ================================
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
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 subscription(line_item)
!line_item.selling_plan_id.nil?
end
def all(line_item)
true
end
end
# ================================================================
# DiscountApplicator
#
# Applies the entered discount to the supplied line item.
# ================================================================
class DiscountApplicator
def initialize(discount_type, discount_amount, discount_message)
@discount_type = discount_type
@discount_message = discount_message
@discount_amount = if discount_type == :percent
1 - (discount_amount * 0.01)
else
Money.new(cents: 100) * discount_amount
end
end
def apply(line_item)
new_line_price = if @discount_type == :percent
line_item.line_price * @discount_amount
else
[line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
end
line_item.change_line_price(new_line_price, message: @discount_message)
end
end
# ================================================================
# BuyXGetYForZCampaign
#
# Buy a certain number of matching items, get a certain number
# of the same matching items with the entered discount applied.
# ================================================================
class BuyXGetYForZCampaign
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_items = eligible_items.sort_by { |line_item| -line_item.variant.price }
quantity_to_buy = campaign[:quantity_to_buy]
quantity_to_discount = campaign[:quantity_to_discount]
bundle_size = quantity_to_buy + quantity_to_discount
number_of_bundles = (eligible_items.map(&:quantity).reduce(0, :+) / bundle_size).floor
number_of_discountable_items = number_of_bundles * quantity_to_discount
next unless number_of_discountable_items > 0
discount_applicator = DiscountApplicator.new(
campaign[:discount_type],
campaign[:discount_amount],
campaign[:discount_message]
)
self.loop_items(
discount_applicator, cart, eligible_items, number_of_discountable_items, quantity_to_buy, quantity_to_discount
)
end
end
def loop_items(discount_applicator, cart, line_items, num_to_discount, quantity_to_buy, quantity_to_discount)
surplus = 0
bundle_size = quantity_to_buy + quantity_to_discount
line_items.each do |line_item|
line_quantity = line_item.quantity + surplus
if line_quantity > quantity_to_buy
bundles_per_line = (line_quantity / bundle_size).floor
take_quantity = bundles_per_line * quantity_to_discount
surplus += (line_quantity - (bundle_size * bundles_per_line))
if line_item.quantity > take_quantity
discount_item = line_item.split(take: take_quantity)
discount_applicator.apply(discount_item)
position = cart.line_items.find_index(line_item)
cart.line_items.insert(position + 1, discount_item)
num_to_discount -= take_quantity
else
discount_applicator.apply(line_item)
num_to_discount -= line_item.quantity
end
else
surplus += line_quantity
end
break if num_to_discount <= 0
end
end
end
CAMPAIGNS = [
BuyXGetYForZCampaign.new(BUY_X_GET_Y_FOR_Z),
]
CAMPAIGNS.each do |campaign|
campaign.run(Input.cart)
end
Output.cart = Input.cart
購買套裝,並以折扣價獲得額外產品
使用此折扣碼,在顧客購買一些其他商品時,對特定商品提供折扣。
舉例而言,若顧客同時購買一頂帽子、一件 T 恤和一副太陽眼鏡,則可免費獲得一雙襪子。
# ================================ Customizable Settings ================================
# ================================================================
# Buy Products VWX, get Product Y for Z Discount
#
# Buy a specific bundle of items, get another item at a discount.
# For example:
#
# "Buy a t-shirt, a hat, and sunglasses, get a free pair of socks"
#
# - 'bundle_items' is a list of the items that comprise the
# bundle, where:
# - 'product_id' is the ID of the product
# - 'quantity_needed' is the quantity necessary to complete
# the bundle
# - 'quantity_to_buy' is the number of bundles needed to qualify
# for a discount product
# - 'discount_product_selector_match_type' determines whether we
# look for products that do or don't match the entered
# selectors. Can be:
# - ':include' to check if the product does match
# - ':exclude' to make sure the product doesn't match
# - 'discount_product_selector_type' determines how qualifying
# products will be identified. Can be either:
# - ':tag' to find products by tag
# - ':type' to find products by type
# - ':vendor' to find products by vendor
# - ':product_id' to find products by ID
# - ':variant_id' to find products by variant ID
# - ':subscription' to find subscription products
# - ':all' for all products
# - 'discount_product_selectors' is a list of identifiers (from
# above) for qualifying products. Product/Variant ID lists
# should only contain numbers (ie. no quotes). If ':all' is
# used, this can also be 'nil'.
# - 'quantity_to_discount' is the number of items to discount
# per offer
# - 'discount_type' is the type of discount to provide. Can be
# either:
# - ':percent'
# - ':dollar'
# - 'discount_amount' is the percentage/dollar discount to
# apply (per item)
# - 'discount_message' is the message to show when a discount
# is applied
# ================================================================
BUNDLE_DISCOUNTS = [
{
bundle_items: [
{
product_id: 1234567890987,
quantity_needed: 1
},
{
product_id: 1234567890986,
quantity_needed: 1
},
],
quantity_to_buy: 1,
discount_product_selector_match_type: :include,
discount_product_selector_type: :product_id,
discount_product_selectors: [1234567890123],
quantity_to_discount: 1,
discount_type: :percent,
discount_amount: 10,
discount_message: "Buy Product VWX, get Product Y for 10% off",
},
]
# ================================ Script Code (do not edit) ================================
# ================================================================
# BundleSelector
#
# Finds any items that are part of the entered bundle and saves
# them.
# ================================================================
class BundleSelector
def initialize(bundle_items)
@bundle_items = bundle_items.reduce({}) do |acc, bundle_item|
acc[bundle_item[:product_id]] = {
cart_items: [],
quantity_needed: bundle_item[:quantity_needed],
total_quantity: 0,
}
acc
end
end
def build(cart)
cart.line_items.each do |line_item|
next unless @bundle_items[line_item.variant.product.id]
@bundle_items[line_item.variant.product.id][:cart_items].push(line_item)
@bundle_items[line_item.variant.product.id][:total_quantity] += line_item.quantity
end
@bundle_items
end
end
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
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 subscription(line_item)
!line_item.selling_plan_id.nil?
end
def all(line_item)
true
end
end
# ================================================================
# DiscountApplicator
#
# Applies the entered discount to the supplied line item.
# ================================================================
class DiscountApplicator
def initialize(discount_type, discount_amount, discount_message)
@discount_type = discount_type
@discount_message = discount_message
@discount_amount = if discount_type == :percent
1 - (discount_amount * 0.01)
else
Money.new(cents: 100) * discount_amount
end
end
def apply(line_item)
new_line_price = if @discount_type == :percent
line_item.line_price * @discount_amount
else
[line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
end
line_item.change_line_price(new_line_price, message: @discount_message)
end
end
# ================================================================
# DiscountLoop
#
# Loops through the supplied line items and discounts the supplied
# number of items by the supplied discount.
# ================================================================
class DiscountLoop
def initialize(discount_applicator)
@discount_applicator = discount_applicator
end
def loop_items(cart, line_items, num_to_discount)
line_items.each_with_index 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_applicator.apply(split_line_item)
position = cart.line_items.find_index(line_item)
cart.line_items.insert(position + 1, split_line_item)
break
else
@discount_applicator.apply(line_item)
num_to_discount -= line_item.quantity
end
end
end
end
# ================================================================
# BundleDiscountCampaign
#
# If the entered bundle is present, the entered discount is
# applied to the entered product.
# ================================================================
class BundleDiscountCampaign
def initialize(campaigns)
@campaigns = campaigns
end
def run(cart)
@campaigns.each do |campaign|
bundle_selector = BundleSelector.new(campaign[:bundle_items])
bundle_items = bundle_selector.build(cart)
next if bundle_items.any? do |product_id, product_info|
product_info[:total_quantity] < product_info[:quantity_needed]
end
num_bundles = bundle_items.map do |product_id, product_info|
(product_info[:total_quantity] / product_info[:quantity_needed])
end
num_bundles = num_bundles.min.floor
product_selector = ProductSelector.new(
campaign[:discount_product_selector_match_type],
campaign[:discount_product_selector_type],
campaign[:discount_product_selectors],
)
discount_items = cart.line_items.select { |line_item| product_selector.match?(line_item) }
next if discount_items.nil?
discount_applicator = DiscountApplicator.new(
campaign[:discount_type],
campaign[:discount_amount],
campaign[:discount_message]
)
discount_loop = DiscountLoop.new(discount_applicator)
discount_loop.loop_items(cart, discount_items, (campaign[:quantity_to_discount] * num_bundles))
end
end
end
CAMPAIGNS = [
BundleDiscountCampaign.new(BUNDLE_DISCOUNTS),
]
CAMPAIGNS.each do |campaign|
campaign.run(Input.cart)
end
Output.cart = Input.cart
套裝折扣
在顧客將特定產品組合加入購物車時,使用此折扣碼提供折扣。
舉例而言,針對同時購買一件 T 恤、一頂帽子和一副太陽眼鏡的顧客,提供 20% 的折扣。
# ================================ Customizable Settings ================================
# ================================================================
# Buy Products WXY, get Z Discount
#
# Buy a specific bundle of products, get that bundle at a
# discount. For example:
#
# "Buy a t-shirt, a hat, and sunglasses, get 20% off each"
#
# - 'bundle_items' is a list of the items that comprise the
# bundle, where:
# - 'product_id' is the ID of the product
# - 'quantity_needed' is the quantity necessary to complete
# the bundle
# - 'discount_type' is the type of discount to provide. Can be
# either:
# - ':percent'
# - ':dollar'
# - 'discount_amount' is the percentage/dollar discount to
# apply (per item)
# - 'discount_message' is the message to show when a discount
# is applied
# ================================================================
BUNDLE_DISCOUNTS = [
{
bundle_items: [
{
product_id: 1234567890987,
quantity_needed: 1
},
{
product_id: 1234567890986,
quantity_needed: 1
},
],
discount_type: :percent,
discount_amount: 10,
discount_message: "Buy Product X and Product Y, get 10% off!",
},
]
# ================================ Script Code (do not edit) ================================
# ================================================================
# BundleSelector
#
# Finds any items that are part of the entered bundle and saves
# them.
# ================================================================
class BundleSelector
def initialize(bundle_items)
@bundle_items = bundle_items.reduce({}) do |acc, bundle_item|
acc[bundle_item[:product_id]] = {
cart_items: [],
quantity_needed: bundle_item[:quantity_needed],
total_quantity: 0,
}
acc
end
end
def build(cart)
cart.line_items.each do |line_item|
next if line_item.line_price_changed?
next unless @bundle_items[line_item.variant.product.id]
@bundle_items[line_item.variant.product.id][:cart_items].push(line_item)
@bundle_items[line_item.variant.product.id][:total_quantity] += line_item.quantity
end
@bundle_items
end
end
# ================================================================
# DiscountApplicator
#
# Applies the entered discount to the supplied line item.
# ================================================================
class DiscountApplicator
def initialize(discount_type, discount_amount, discount_message)
@discount_type = discount_type
@discount_message = discount_message
@discount_amount = if discount_type == :percent
1 - (discount_amount * 0.01)
else
Money.new(cents: 100) * discount_amount
end
end
def apply(line_item)
new_line_price = if @discount_type == :percent
line_item.line_price * @discount_amount
else
[line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
end
line_item.change_line_price(new_line_price, message: @discount_message)
end
end
# ================================================================
# DiscountLoop
#
# Loops through the supplied line items and discounts the supplied
# number of items by the supplied discount.
# ================================================================
class DiscountLoop
def initialize(discount_applicator)
@discount_applicator = discount_applicator
end
def loop_items(cart, line_items, num_to_discount)
line_items.each_with_index 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_applicator.apply(split_line_item)
position = cart.line_items.find_index(line_item)
cart.line_items.insert(position + 1, split_line_item)
break
else
@discount_applicator.apply(line_item)
num_to_discount -= line_item.quantity
end
end
end
end
# ================================================================
# BundleDiscountCampaign
#
# If the entered bundle is present, the entered discount is
# applied to each item in the bundle.
# ================================================================
class BundleDiscountCampaign
def initialize(campaigns)
@campaigns = campaigns
end
def run(cart)
@campaigns.each do |campaign|
bundle_selector = BundleSelector.new(campaign[:bundle_items])
bundle_items = bundle_selector.build(cart)
next if bundle_items.any? do |product_id, product_info|
product_info[:total_quantity] < product_info[:quantity_needed]
end
num_bundles = bundle_items.map do |product_id, product_info|
(product_info[:total_quantity] / product_info[:quantity_needed])
end
num_bundles = num_bundles.min.floor
discount_applicator = DiscountApplicator.new(
campaign[:discount_type],
campaign[:discount_amount],
campaign[:discount_message]
)
discount_loop = DiscountLoop.new(discount_applicator)
bundle_items.each do |product_id, product_info|
discount_loop.loop_items(
cart,
product_info[:cart_items],
(product_info[:quantity_needed] * num_bundles),
)
end
end
end
end
CAMPAIGNS = [
BundleDiscountCampaign.new(BUNDLE_DISCOUNTS),
]
CAMPAIGNS.each do |campaign|
campaign.run(Input.cart)
end
Output.cart = Input.cart
買一送一折扣
使用此指令碼,在顧客購買一定數量的不同產品時,對某項商品提供百分比折扣。
舉例而言,針對同時購買兩件 T 恤的顧客,提供帽子 10% 折扣。
# ================================ Customizable Settings ================================
# ================================================================
# Buy V of Product W, Get X of Product Y for Z Discount
#
# Buy a certain number of matching items, get a certain number of
# a different set of matching items with the entered discount
# applied. For example:
#
# "Buy 2 t-shirts, get 1 hat for 10% off"
#
# - 'buy_product_selector_match_type' determines whether we look
# for products that do or don't match the entered selectors.
# Can be:
# - ':include' to check if the product does match
# - ':exclude' to make sure the product doesn't match
# - 'buy_product_selector_type' determines how eligible products
# will be identified. Can be:
# - ':tag' to find products by tag
# - ':type' to find products by type
# - ':vendor' to find products by vendor
# - ':product_id' to find products by ID
# - ':variant_id' to find products by variant ID
# - ':subscription' to find subscription products
# - ':all' for all products
# - 'buy_product_selectors' is a list of identifiers (from above)
# for qualifying products. Product/Variant ID lists should only
# contain numbers (ie. no quotes). If ':all' is used, this
# can also be 'nil'.
# - 'quantity_to_buy' is the number of products needed to
# qualify
# - 'get_selector_match_type' is the same idea as the "Buy"
# version above
# - 'get_product_selector_type' is the same idea as the "Buy"
# version above
# - 'get_product_selectors' is the same idea as the "Buy"
# version above
# - 'quantity_to_discount' is the number of products to discount
# - 'allow_incomplete_bundle' determines whether a portion of
# the items to discount can be discounted, or all items
# need to be present. Can be:
# - 'true'
# - 'false'
# - 'discount_type' is the type of discount to provide. Can be
# either:
# - ':percent'
# - ':dollar'
# - 'discount_amount' is the percentage/dollar discount to
# apply (per item)
# - 'discount_message' is the message to show when a discount
# is applied
# ================================================================
BUYVOFW_GETXOFY_FORZ = [
{
buy_product_selector_match_type: :include,
buy_product_selector_type: :tag,
buy_product_selectors: ["your_tag", "another_tag"],
quantity_to_buy: 1,
get_product_selector_match_type: :include,
get_product_selector_type: :tag,
get_product_selectors: ["your_other_tag", "a_different_tag"],
quantity_to_discount: 1,
allow_incomplete_bundle: false,
discount_type: :percent,
discount_amount: 100,
discount_message: 'Buy a Product X, get a Product Y free!',
},
{
buy_product_selector_match_type: :include,
buy_product_selector_type: :product_id,
buy_product_selectors: [1234567890987, 1234567890986],
quantity_to_buy: 1,
get_product_selector_match_type: :include,
get_product_selector_type: :product_id,
get_product_selectors: [1234567890985, 1234567890984],
quantity_to_discount: 1,
allow_incomplete_bundle: false,
discount_type: :dollar,
discount_amount: 10,
discount_message: 'Buy a Product X, get $10 off a Product Y!',
},
]
# ================================ Script Code (do not edit) ================================
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
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 subscription(line_item)
!line_item.selling_plan_id.nil?
end
def all(line_item)
true
end
end
# ================================================================
# DiscountApplicator
#
# Applies the entered discount to the supplied line item.
# ================================================================
class DiscountApplicator
def initialize(discount_type, discount_amount, discount_message)
@discount_type = discount_type
@discount_message = discount_message
@discount_amount = if discount_type == :percent
1 - (discount_amount * 0.01)
else
Money.new(cents: 100) * discount_amount
end
end
def apply(line_item)
new_line_price = if @discount_type == :percent
line_item.line_price * @discount_amount
else
[line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
end
line_item.change_line_price(new_line_price, message: @discount_message)
end
end
# ================================================================
# DiscountLoop
#
# Loops through the supplied line items and discounts the supplied
# number of items by the supplied discount.
# ================================================================
class DiscountLoop
def initialize(discount_applicator)
@discount_applicator = discount_applicator
end
def loop_items(cart, line_items, num_to_discount)
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_applicator.apply(split_line_item)
position = cart.line_items.find_index(line_item)
cart.line_items.insert(position + 1, split_line_item)
break
else
@discount_applicator.apply(line_item)
num_to_discount -= line_item.quantity
end
end
end
end
# ================================================================
# BuyVofWGetXofYForZCampaign
#
# Buy a certain number of matching items, get a certain number of
# a different set of matching items with the entered discount
# applied.
# ================================================================
class BuyVofWGetXofYForZCampaign
def initialize(campaigns)
@campaigns = campaigns
end
def run(cart)
@campaigns.each do |campaign|
buy_product_selector = ProductSelector.new(
campaign[:buy_product_selector_match_type],
campaign[:buy_product_selector_type],
campaign[:buy_product_selectors],
)
get_product_selector = ProductSelector.new(
campaign[:get_product_selector_match_type],
campaign[:get_product_selector_type],
campaign[:get_product_selectors],
)
buy_items = []
get_items = []
cart.line_items.each do |line_item|
buy_items.push(line_item) if buy_product_selector.match?(line_item)
get_items.push(line_item) if get_product_selector.match?(line_item)
end
next if buy_items.empty? || get_items.empty?
get_items = get_items.sort_by { |line_item| line_item.variant.price }
quantity_to_buy = campaign[:quantity_to_buy]
quantity_to_discount = campaign[:quantity_to_discount]
buy_offers = (buy_items.map(&:quantity).reduce(0, :+) / quantity_to_buy).floor
if campaign[:allow_incomplete_bundle]
number_of_bundles = buy_offers
else
get_offers = (get_items.map(&:quantity).reduce(0, :+) / quantity_to_discount).floor
number_of_bundles = [buy_offers, get_offers].min
end
number_of_discountable_items = number_of_bundles * quantity_to_discount
next unless number_of_discountable_items > 0
discount_applicator = DiscountApplicator.new(
campaign[:discount_type],
campaign[:discount_amount],
campaign[:discount_message]
)
discount_loop = DiscountLoop.new(discount_applicator)
discount_loop.loop_items(cart, get_items, number_of_discountable_items)
end
end
end
CAMPAIGNS = [
BuyVofWGetXofYForZCampaign.new(BUYVOFW_GETXOFY_FORZ),
]
CAMPAIGNS.each do |campaign|
campaign.run(Input.cart)
end
Output.cart = Input.cart
以指定金額購買特定數量的產品
使用此指令碼以指定價格提供多種產品。
舉例而言,提供顧客 2 件 T 恤 $20 美元的優惠。
# ================================ Customizable Settings ================================
# ================================================================
# Buy X of Product Y for $Z
#
# Buy a certain number of matching items for a specific price.
# For example:
#
# "Buy 2 t-shirts for $20"
#
# - 'product_selector_match_type' determines whether we look for
# products that do or don't match the entered selectors. Can
# be:
# - ':include' to check if the product does match
# - ':exclude' to make sure the product doesn't match
# - 'product_selector_type' determines how eligible products
# will be identified. Can be either:
# - ':tag' to find products by tag
# - ':type' to find products by type
# - ':vendor' to find products by vendor
# - ':product_id' to find products by ID
# - ':variant_id' to find products by variant ID
# - ':subscription' to find subscription products
# - ':all' for all products
# - 'product_selectors' is a list of identifiers (from above)
# for qualifying products. Product/Variant ID lists should
# only contain numbers (ie. no quotes). If ':all' is used,
# this can also be 'nil'.
# - 'quantity_to_buy' is the number of products needed to
# qualify
# - 'final_price` is the amount to charge for all products that
# are part of the offer
# - 'discount_message' is the message to show when a discount
# is applied
# ================================================================
BUY_X_GET_Y_FOR_Z = [
{
product_selector_match_type: :include,
product_selector_type: :tag,
product_selectors: ["your_tag"],
quantity_to_buy: 2,
final_price: 100,
discount_message: 'Buy 2 for $20',
},
]
# ================================ Script Code (do not edit) ================================
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
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 subscription(line_item)
!line_item.selling_plan_id.nil?
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
購買即贈免費禮物
使用此指令碼,在購物車總額超過一定金額時,為特定產品提供折扣。
舉例而言,針對消費超過 $75 美元的顧客,提供免費贈品。
# ================================ Customizable Settings ================================
# ================================================================
# Spend $X, get Product Y for Z Discount
#
# If the cart total is greater than (or equal to) the entered
# threshold (less the discounted amount), the entered number of
# matching items is discounted by the entered amount.
#
# - 'product_selector_match_type' determines whether we look for
# products that do or don't match the entered selectors. Can
# be:
# - ':include' to check if the product does match
# - ':exclude' to make sure the product doesn't match
# - 'product_selector_type' determines how eligible products
# will be identified. Can be either:
# - ':tag' to find products by tag
# - ':type' to find products by type
# - ':vendor' to find products by vendor
# - ':product_id' to find products by ID
# - ':variant_id' to find products by variant ID
# - ':subscription' to find subscription products
# - ':all' for all products
# - 'product_selectors' is a list of identifiers (from above)
# for qualifying products. Product/Variant ID lists should
# only contain numbers (ie. no quotes). If ':all' is used,
# this can also be 'nil'.
# - 'threshold' is the dollar amount needed to spend to qualify
# - 'quantity_to_discount' is the number of items to discount
# if qualified
# - 'discount_type' is the type of discount to provide. Can be
# either:
# - ':percent'
# - ':dollar'
# - 'discount_amount' is the percentage/dollar discount to
# apply (per item)
# - 'discount_message' is the message to show when a discount
# is applied
# ================================================================
SPENDX_GETY_FORZ = [
{
product_selector_match_type: :include,
product_selector_type: :product_id,
product_selectors: [1234567890123],
threshold: 75,
quantity_to_discount: 1,
discount_type: :percent,
discount_amount: 100,
discount_message: 'Spend $75 or more and get a free Product X!',
},
]
# ================================ Script Code (do not edit) ================================
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
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 subscription(line_item)
!line_item.selling_plan_id.nil?
end
def all(line_item)
true
end
end
# ================================================================
# DiscountApplicator
#
# Applies the entered discount to the supplied line item.
# ================================================================
class DiscountApplicator
def initialize(discount_type, discount_amount, discount_message)
@discount_type = discount_type
@discount_message = discount_message
@discount_amount = if discount_type == :percent
1 - (discount_amount * 0.01)
else
Money.new(cents: 100) * discount_amount
end
end
def apply(line_item)
new_line_price = if @discount_type == :percent
line_item.line_price * @discount_amount
else
[line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
end
line_item.change_line_price(new_line_price, message: @discount_message)
end
end
# ================================================================
# DiscountLoop
#
# Loops through the supplied line items and discounts the supplied
# number of items by the supplied discount.
# ================================================================
class DiscountLoop
def initialize(discount_applicator)
@discount_applicator = discount_applicator
end
def loop_items(cart, line_items, num_to_discount)
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_applicator.apply(split_line_item)
position = cart.line_items.find_index(line_item)
cart.line_items.insert(position + 1, split_line_item)
break
else
@discount_applicator.apply(line_item)
num_to_discount -= line_item.quantity
end
end
end
end
# ================================================================
# SpendXGetYForZCampaign
#
# If the cart total is greater than (or equal to) the entered
# threshold (less the discounted amount), the entered number of
# matching items is discounted by the entered amount.
# ================================================================
class SpendXGetYForZCampaign
def initialize(campaigns)
@campaigns = campaigns
end
def run(cart)
@campaigns.each do |campaign|
threshold = Money.new(cents: 100) * campaign[:threshold]
next if cart.subtotal_price < threshold
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_items = eligible_items.sort_by { |line_item| line_item.variant.price }
num_to_discount = campaign[:quantity_to_discount]
cart_total = cart.subtotal_price
eligible_items.each do |line_item|
break if num_to_discount <= 0
if line_item.quantity > num_to_discount
cart_total -= line_item.variant.price * num_to_discount
break
else
cart_total -= line_item.line_price
num_to_discount -= line_item.quantity
end
end
next if cart_total < threshold
discount_applicator = discount_applicator = DiscountApplicator.new(
campaign[:discount_type],
campaign[:discount_amount],
campaign[:discount_message]
)
discount_loop = DiscountLoop.new(discount_applicator)
discount_loop.loop_items(cart, eligible_items, campaign[:quantity_to_discount])
end
end
end
CAMPAIGNS = [
SpendXGetYForZCampaign.new(SPENDX_GETY_FORZ),
]
CAMPAIGNS.each do |campaign|
campaign.run(Input.cart)
end
Output.cart = Input.cart
依顧客標籤提供產品折扣
使用此指令碼,針對有特定標籤的顧客,提供特定產品的折扣。
舉例而言,針對有 VIP
標籤的顧客,提供 20% 折扣。
# ================================ Customizable Settings ================================
# ================================================================
# Product Discounts by Customer Tag
#
# If we have a matching customer (by tag), the entered discount
# will be applied to any matching items.
#
# - 'customer_tag_match_type' determines whether we look for the customer
# to be tagged with any of the entered tags or not. Can be:
# - ':include' to check if the customer is tagged
# - ':exclude' to make sure the customer isn't tagged
# - 'customer_tags' is a list of tags to identify qualified
# customers
# - 'product_selector_match_type' determines whether we look for
# products that do or don't match the entered selectors. Can
# be:
# - ':include' to check if the product does match
# - ':exclude' to make sure the product doesn't match
# - 'product_selector_type' determines how eligible products
# will be identified. Can be either:
# - ':tag' to find products by tag
# - ':type' to find products by type
# - ':vendor' to find products by vendor
# - ':product_id' to find products by ID
# - ':variant_id' to find products by variant ID
# - ':subscription' to find subscription products
# - ':all' for all products
# - 'product_selectors' is a list of identifiers (from above)
# for qualifying products. Product/Variant ID lists should
# only contain numbers (ie. no quotes). If ':all' is used,
# this can also be 'nil'.
# - 'discount_type' is the type of discount to provide. Can be
# either:
# - ':percent'
# - ':dollar'
# - 'discount_amount' is the percentage/dollar discount to
# apply (per item)
# - 'discount_message' is the message to show when a discount
# is applied
# ================================================================
DISCOUNTS_FOR_CUSTOMER_TAG = [
{
customer_tag_match_type: :include,
customer_tags: ["VIP"],
product_selector_match_type: :include,
product_selector_type: :all,
product_selectors: nil,
discount_type: :percent,
discount_amount: 20,
discount_message: "Discount for VIP customers!",
},
]
# ================================ Script Code (do not edit) ================================
# ================================================================
# CustomerTagSelector
#
# Finds whether the supplied customer has any of the entered tags.
# ================================================================
class CustomerTagSelector
def initialize(match_type, tags)
@comparator = match_type == :include ? 'any?' : 'none?'
@tags = tags.map { |tag| tag.downcase.strip }
end
def match?(customer)
customer_tags = customer.tags.map { |tag| tag.downcase.strip }
(@tags & customer_tags).send(@comparator)
end
end
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
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 subscription(line_item)
!line_item.selling_plan_id.nil?
end
def all(line_item)
true
end
end
# ================================================================
# DiscountApplicator
#
# Applies the entered discount to the supplied line item.
# ================================================================
class DiscountApplicator
def initialize(discount_type, discount_amount, discount_message)
@discount_type = discount_type
@discount_message = discount_message
@discount_amount = if discount_type == :percent
1 - (discount_amount * 0.01)
else
Money.new(cents: 100) * discount_amount
end
end
def apply(line_item)
new_line_price = if @discount_type == :percent
line_item.line_price * @discount_amount
else
[line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
end
line_item.change_line_price(new_line_price, message: @discount_message)
end
end
# ================================================================
# DiscountForCustomerTagCampaign
#
# If we have a matching customer (by tag), the entered discount
# is applied to any matching items.
# ================================================================
class DiscountForCustomerTagCampaign
def initialize(campaigns)
@campaigns = campaigns
end
def run(cart)
return unless cart.customer&.tags
@campaigns.each do |campaign|
customer_tag_selector = CustomerTagSelector.new(campaign[:customer_tag_match_type], campaign[:customer_tags])
next unless customer_tag_selector.match?(cart.customer)
product_selector = ProductSelector.new(
campaign[:product_selector_match_type],
campaign[:product_selector_type],
campaign[:product_selectors]
)
discount_applicator = DiscountApplicator.new(
campaign[:discount_type],
campaign[:discount_amount],
campaign[:discount_message]
)
cart.line_items.each do |line_item|
next unless product_selector.match?(line_item)
discount_applicator.apply(line_item)
end
end
end
end
CAMPAIGNS = [
DiscountForCustomerTagCampaign.new(DISCOUNTS_FOR_CUSTOMER_TAG),
]
CAMPAIGNS.each do |campaign|
campaign.run(Input.cart)
end
Output.cart = Input.cart
依顧客行銷而定的產品折扣
您可以使用這個指令碼,針對接受行銷信件的顧客提供特定產品的折扣。
舉例而言,針對任何願意接受行銷資訊的顧客,提供適用於所有產品的 10% 折扣。
# ================================ Customizable Settings ================================
# ================================================================
# Product Discount by Customer Marketing
#
# If the customer accepts marketing, any matching items are
# discounted by the entered amount.
#
# - 'product_selector_match_type' determines whether we look for
# products that do or don't match the entered selectors. Can
# be:
# - ':include' to check if the product does match
# - ':exclude' to make sure the product doesn't match
# - 'product_selector_type' determines how eligible products
# will be identified. Can be either:
# - ':tag' to find products by tag
# - ':type' to find products by type
# - ':vendor' to find products by vendor
# - ':product_id' to find products by ID
# - ':variant_id' to find products by variant ID
# - ':subscription' to find subscription products
# - ':all' for all products
# - 'product_selectors' is a list of identifiers (from above)
# for qualifying products. Product/Variant ID lists should
# only contain numbers (ie. no quotes). If ':all' is used,
# this can also be 'nil'.
# - 'discount_type' is the type of discount to provide. Can be
# either:
# - ':percent'
# - ':dollar'
# - 'discount_amount' is the percentage/dollar discount to
# apply (per item)
# - 'discount_message' is the message to show when a discount
# is applied
# ================================================================
PRODUCT_DISCOUNTS_BY_CUSTOMER_MARKETING = [
{
product_selector_match_type: :include,
product_selector_type: :all,
product_selectors: nil,
discount_type: :percent,
discount_amount: 10,
discount_message: '10% off for subscribed customers!'
}
]
# ================================ Script Code (do not edit) ===============================
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
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 subscription(line_item)
!line_item.selling_plan_id.nil?
end
def all(line_item)
true
end
end
# ================================================================
# DiscountApplicator
#
# Applies the entered discount to the supplied line item.
# ================================================================
class DiscountApplicator
def initialize(discount_type, discount_amount, discount_message)
@discount_type = discount_type
@discount_message = discount_message
@discount_amount = if discount_type == :percent
1 - (discount_amount * 0.01)
else
Money.new(cents: 100) * discount_amount
end
end
def apply(line_item)
new_line_price = if @discount_type == :percent
line_item.line_price * @discount_amount
else
[line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
end
line_item.change_line_price(new_line_price, message: @discount_message)
end
end
# ================================================================
# ProductDiscountByCustomerMarketingCampaign
#
# If the customer accepts marketing, any matching items are
# discounted by the entered amount.
# ================================================================
class ProductDiscountByCustomerMarketingCampaign
def initialize(campaigns)
@campaigns = campaigns
end
def run(cart)
return if cart.customer.nil?
@campaigns.each do |campaign|
next unless cart.customer.accepts_marketing?
product_selector = ProductSelector.new(
campaign[:product_selector_match_type],
campaign[:product_selector_type],
campaign[:product_selectors]
)
discount_applicator = DiscountApplicator.new(
campaign[:discount_type],
campaign[:discount_amount],
campaign[:discount_message]
)
cart.line_items.each do |line_item|
next unless product_selector.match?(line_item)
discount_applicator.apply(line_item)
end
end
end
end
CAMPAIGNS = [
ProductDiscountByCustomerMarketingCampaign.new(PRODUCT_DISCOUNTS_BY_CUSTOMER_MARKETING),
]
CAMPAIGNS.each do |campaign|
campaign.run(Input.cart)
end
Output.cart = Input.cart
依顧客訂單數量而定的產品折扣
您可以使用這個指令碼,針對提交特定訂單數量的顧客提供特定產品的折扣。
舉例而言,針對沒有訂單的任何顧客,提供 10% 折扣。
# ================================ Customizable Settings ================================
# ================================================================
# Product Discount by Order Count
#
# If the customer has made a matching number of orders, any
# matching items are discounted by the entered amount.
#
# - 'order_count_match_type' determines how we compare the
# customer's order count to the entered limit. Can be:
# - ':greater_than' to ensure that the customer's order count
# is greater than the entered limit
# - ':greater_than_equal' to ensure that the customer's order
# count is greater than, or equal to, the entered limit
# - ':less_than' to ensure that the customer's order count is
# less than the entered limit
# - ':less_than_equal' to ensure that the customer's order
# count is less than, or equal to, the entered limit
# - 'product_selector_match_type' determines whether we look for
# products that do or don't match the entered selectors. Can
# be:
# - ':include' to check if the product does match
# - ':exclude' to make sure the product doesn't match
# - 'product_selector_type' determines how eligible products
# will be identified. Can be either:
# - ':tag' to find products by tag
# - ':type' to find products by type
# - ':vendor' to find products by vendor
# - ':product_id' to find products by ID
# - ':variant_id' to find products by variant ID
# - ':subscription' to find subscription products
# - ':all' for all products
# - 'product_selectors' is a list of identifiers (from above)
# for qualifying products. Product/Variant ID lists should
# only contain numbers (ie. no quotes). If ':all' is used,
# this can also be 'nil'.
# - 'discount_type' is the type of discount to provide. Can be
# either:
# - ':percent'
# - ':dollar'
# - 'discount_amount' is the percentage/dollar discount to
# apply (per item)
# - 'discount_message' is the message to show when a discount
# is applied
# ================================================================
PRODUCT_DISCOUNTS_BY_ORDER_COUNT = [
{
order_count_match_type: :less_than,
order_count_limit: 1,
product_selector_match_type: :include,
product_selector_type: :all,
product_selectors: nil,
discount_type: :percent,
discount_amount: 10,
discount_message: '10% off for first time customers!'
}
]
# ================================ Script Code (do not edit) ===============================
# ================================================================
# OrderCountSelector
#
# Finds whether the customer has made a certain number of orders
# ================================================================
class OrderCountSelector
def initialize(match_type, limit)
@match_type = match_type
@limit = limit
end
def match?(customer)
if self.respond_to?(@match_type)
self.send(@match_type, customer, @limit)
else
raise RuntimeError.new('Invalid order count match type')
end
end
def greater_than(customer, limit)
customer.orders_count > limit
end
def greater_than_equal(customer, limit)
customer.orders_count >= limit
end
def less_than(customer, limit)
customer.orders_count < limit
end
def less_than_equal(customer, limit)
customer.orders_count <= limit
end
end
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
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 subscription(line_item)
!line_item.selling_plan_id.nil?
end
def all(line_item)
true
end
end
# ================================================================
# DiscountApplicator
#
# Applies the entered discount to the supplied line item.
# ================================================================
class DiscountApplicator
def initialize(discount_type, discount_amount, discount_message)
@discount_type = discount_type
@discount_message = discount_message
@discount_amount = if discount_type == :percent
1 - (discount_amount * 0.01)
else
Money.new(cents: 100) * discount_amount
end
end
def apply(line_item)
new_line_price = if @discount_type == :percent
line_item.line_price * @discount_amount
else
[line_item.line_price - (@discount_amount * line_item.quantity), Money.zero].max
end
line_item.change_line_price(new_line_price, message: @discount_message)
end
end
# ================================================================
# ProductDiscountByOrderCountCampaign
#
# If the customer has made a matching number of orders, any
# matching items are discounted by the entered amount.
# ================================================================
class ProductDiscountByOrderCountCampaign
def initialize(campaigns)
@campaigns = campaigns
end
def run(cart)
return if cart.customer.nil?
@campaigns.each do |campaign|
order_count_selector = OrderCountSelector.new(
campaign[:order_count_match_type],
campaign[:order_count_limit]
)
next unless order_count_selector.match?(cart.customer)
product_selector = ProductSelector.new(
campaign[:product_selector_match_type],
campaign[:product_selector_type],
campaign[:product_selectors]
)
discount_applicator = DiscountApplicator.new(
campaign[:discount_type],
campaign[:discount_amount],
campaign[:discount_message]
)
cart.line_items.each do |line_item|
next unless product_selector.match?(line_item)
discount_applicator.apply(line_item)
end
end
end
end
CAMPAIGNS = [
ProductDiscountByOrderCountCampaign.new(PRODUCT_DISCOUNTS_BY_ORDER_COUNT),
]
CAMPAIGNS.each do |campaign|
campaign.run(Input.cart)
end
Output.cart = Input.cart
停用折扣碼
使用此指令碼在結帳流程中停用折扣碼。
舉例而言,避免顧客在您商店提供特價時使用折扣代碼。
# ================================ Customizable Settings ================================
# ================================================================
# Disable Discount Code Use
#
# Any discount codes will be rejected with the entered message.
#
# - 'REJECTION_MESSAGE' is the message to show when a discount
# code is rejected
# ================================================================
REJECTION_MESSAGE = 'Discount codes cannot be used during this sale'
# ================================ Script Code (do not edit) ================================
# ================================================================
# DisableDiscountCodesCampaign
#
# Any discount codes will be rejected with the entered message.
# ================================================================
class DisableDiscountCodesCampaign
def initialize(rejection_message)
@rejection_message = rejection_message
end
def run(cart)
return if cart.discount_code.nil?
cart.discount_code.reject(message: @rejection_message)
end
end
CAMPAIGNS = [
DisableDiscountCodesCampaign.new(REJECTION_MESSAGE),
]
CAMPAIGNS.each do |campaign|
campaign.run(Input.cart)
end
Output.cart = Input.cart
針對特定產品停用折扣代碼
使用此指令碼,在結帳流程中購物車內有特定產品時,停用折扣代碼。
舉例而言,在購物車中有標示 discounted
的品項時,避免顧客使用折扣代碼。
# ================================ Customizable Settings ================================
# ================================================================
# Disable Discount Code(s) For Products
#
# If any matching discount codes are used, and any matching items
# are in the cart, the discount code is rejected with the entered
# message.
#
# - 'discount_code_match_type' determines whether the below
# strings should be an exact or partial match. Can be:
# - ':exact' for an exact match
# - ':partial' for a partial match
# - 'discount_codes' is a list of strings to identify discount
# codes
# - 'product_selector_match_type' determines whether we look for
# products that do or don't match the entered selectors. Can
# be:
# - ':include' to check if the product does match
# - ':exclude' to make sure the product doesn't match
# - 'product_selector_type' determines how eligible products
# will be identified. Can be either:
# - ':tag' to find products by tag
# - ':type' to find products by type
# - ':vendor' to find products by vendor
# - ':product_id' to find products by ID
# - ':variant_id' to find products by variant ID
# - ':subscription' to find subscription products
# - ':all' for all products
# - 'product_selectors' is a list of identifiers (from above)
# for qualifying products. Product/Variant ID lists should
# only contain numbers (ie. no quotes). If ':all' is used,
# this can also be 'nil'.
# - 'rejection_message' is the message to show when a discount
# code is rejected
# ================================================================
REJECT_DISCOUNT_CODE_FOR_PRODUCTS = [
{
discount_code_match_type: :exact,
discount_codes: ["TESTCODE1", "TESTCODE2"],
product_selector_match_type: :include,
product_selector_type: :tag,
product_selectors: ["discounted"],
rejection_message: "Discount codes can't be used with 'discounted' products"
}
]
# ================================ Script Code (do not edit) ================================
# ================================================================
# DiscountCodeSelector
#
# Finds whether the supplied discount code matches any of the
# entered codes.
# ================================================================
class DiscountCodeSelector
def initialize(match_type, discount_codes)
@comparator = match_type == :exact ? '==' : 'include?'
@discount_codes = discount_codes.map { |discount_code| discount_code.upcase.strip }
end
def match?(discount_code)
@discount_codes.any? { |code| discount_code.code.upcase.send(@comparator, code) }
end
end
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
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 subscription(line_item)
!line_item.selling_plan_id.nil?
end
def all(line_item)
true
end
end
# ================================================================
# DisableDiscountCodesForProductsCampaign
#
# If any matching discount codes are used, and any matching items
# are in the cart, the discount code is rejected with the entered
# message.
# ================================================================
class DisableDiscountCodesForProductsCampaign
def initialize(campaigns)
@campaigns = campaigns
end
def run(cart)
return if cart.discount_code.nil?
@campaigns.each do |campaign|
discount_code_selector = DiscountCodeSelector.new(
campaign[:discount_code_match_type],
campaign[:discount_codes]
)
next unless discount_code_selector.match?(cart.discount_code)
product_selector = ProductSelector.new(
campaign[:product_selector_match_type],
campaign[:product_selector_type],
campaign[:product_selectors],
)
next unless cart.line_items.any? { |line_item| product_selector.match?(line_item) }
cart.discount_code.reject(message: campaign[:rejection_message])
end
end
end
CAMPAIGNS = [
DisableDiscountCodesForProductsCampaign.new(REJECT_DISCOUNT_CODE_FOR_PRODUCTS),
]
CAMPAIGNS.each do |campaign|
campaign.run(Input.cart)
end
Output.cart = Input.cart
限制產品數量
您可以使用這個指令碼對特定產品套用數量限制。
舉例而言,避免顧客在單筆訂單中購買超過 1 件「X 產品」。
# ================================ Customizable Settings ================================
# ================================================================
# Product Quantity Limits
#
# If the quantity of any matching items is greater than the
# entered threshold, the excess items are removed from the cart.
# It should be noted that there will be no notice to the customer
# when this happens.
#
# - 'enable' determines whether the campaign will run. Can be:
# - 'true' to run
# - 'false' to not run
# - 'product_selector_match_type' determines whether we look for
# products that do or don't match the entered selectors. Can
# be:
# - ':include' to check if the product does match
# - ':exclude' to make sure the product doesn't match
# - 'product_selector_type' determines how eligible products
# will be identified. Can be either:
# - ':tag' to find products by tag
# - ':type' to find products by type
# - ':vendor' to find products by vendor
# - ':product_id' to find products by ID
# - ':variant_id' to find products by variant ID
# - ':subscription' to find subscription products
# - ':all' for all products
# - 'product_selectors' is a list of identifiers (from above)
# for qualifying products. Product/Variant ID lists should
# only contain numbers (ie. no quotes). If ':all' is used,
# this can also be 'nil'.
# - 'variant_level_limit' determines whether the below limit
# is applied on a variant, or a total quantity, level. For
# example, can I have X number of individual matching items,
# or can I only have X number total of matching items?
# Can be:
# - 'true' to limit at a variant level
# - 'false' to limit total quantity
# - 'quantity_allowed' is the number of products allowed
# ================================================================
QUANTITY_LIMITS = {
enable: true,
campaigns: [
{
product_selector_match_type: :include,
product_selector_type: :tag,
product_selectors: ["limited"],
variant_level_limit: true,
quantity_allowed: 2,
},
]
}
# ================================ Script Code (do not edit) ================================
# ================================================================
# ProductSelector
#
# Finds matching products by the entered criteria.
# ================================================================
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 subscription(line_item)
!line_item.selling_plan_id.nil?
end
def all(line_item)
true
end
end
# ================================================================
# ProductQuantityLimitCampaign
#
# If the quantity of any matching items is greater than the
# entered threshold, the excess items are removed from the cart.
# ================================================================
class ProductQuantityLimitCampaign
def initialize(enable, campaigns)
@enable = enable
@campaigns = campaigns
end
def run(cart)
return unless @enable
@campaigns.each do |campaign|
product_selector = ProductSelector.new(
campaign[:product_selector_match_type],
campaign[:product_selector_type],
campaign[:product_selectors]
)
if campaign[:variant_level_limit]
applicable_items = {}
cart.line_items.each do |line_item|
next unless product_selector.match?(line_item)
id = line_item.variant.id
if applicable_items[id].nil?
applicable_items[id] = {
items: [],
total_quantity: 0
}
end
applicable_items[id][:items].push(line_item)
applicable_items[id][:total_quantity] += line_item.quantity
end
next if applicable_items.nil?
applicable_items.each do |id, info|
next unless info[:total_quantity] > campaign[:quantity_allowed]
num_to_remove = info[:total_quantity] - campaign[:quantity_allowed]
self.loop_items(cart, info[:items], num_to_remove)
end
else
applicable_items = cart.line_items.select { |line_item| product_selector.match?(line_item) }
next if applicable_items.nil?
total_quantity = applicable_items.map(&:quantity).reduce(0, :+)
next unless total_quantity > campaign[:quantity_allowed]
num_to_remove = total_quantity - campaign[:quantity_allowed]
self.loop_items(cart, applicable_items, num_to_remove)
end
end
end
def loop_items(cart, line_items, num_to_remove)
line_items.each do |line_item|
if line_item.quantity > num_to_remove
split_line_item = line_item.split(take: num_to_remove)
break
else
index = cart.line_items.find_index(line_item)
cart.line_items.delete_at(index)
num_to_remove -= line_item.quantity
end
break if num_to_remove <= 0
end
end
end
CAMPAIGNS = [
ProductQuantityLimitCampaign.new(
QUANTITY_LIMITS[:enable],
QUANTITY_LIMITS[:campaigns],
),
]
CAMPAIGNS.each do |campaign|
campaign.run(Input.cart)
end
Output.cart = Input.cart