Skip to content
Scripts examples

Migrating a script that took an amount off each item

Written four different ways in the wild, and two of them differ only in whether the number in the file already meant cents. Reading a generator’s "5" as five cents is a hundredfold error, so all four spellings are shown side by side.

Rebuilt
What the Script did

It subtracted a fixed sum from every matching unit in the cart.

Merchants wrote this 4 different ways, and they do not all mean the same thing. Each spelling is below with its own cart and its own numbers.

Run it against your own store

Paste it into Scripts rescue and watch what it rebuilds

  1. Copy the Ruby with the button beside any Script on this page.
  2. In your Shopify admin, open Stackable, then Tools, then Scripts rescue.
  3. Paste it there, read what the tool says it will build, and compare that with the settings on this page.

Scripts rescue is on the Pro plan. Nothing is published until you tell it what your old Script charged and the two figures match to the cent.

Amount off per unit (D1)

Rebuilt

Danger pair D1. This file and its partner look almost identical and behave differently, so read both before you decide which one you have.

The original Ruby

DISCOUNT = Money.new(cents: 999)

Input.cart.line_items.each do |line_item|
  next unless line_item.variant.product.product_type == "Clearance"
  new_price = line_item.line_price - (DISCOUNT * line_item.quantity)
  line_item.change_line_price(new_price, message: "$9.99 off each")
end

Output.cart = Input.cart
What it becomes

Recognised as Amount off at a quantity and rebuilt as a volume offer. These are the real configuration keys the offer is saved with, so you can check your own rebuild against them field by field.

Every setting, spelled out
SettingValueConfig key
Offer typevolumetype
Countingper_productcounting
Recurrenceoncerecurrence
Tier 1min qty 1 -> $9.99 off eachtiers[0]
Target scopeproduct_typestarget.scope
Target includesClearancetarget.includeIds
MethodautomaticdiscountMethod
Allocationstandardstacking.allocation
Combines with other discountsnostacking
You will be asked to confirm
  • Your Script limited this to a tag, vendor, or product type. We carried that filter over, so confirm it selects the products you expect.
The cart that proves it

Built on our public demo store, Meridian Goods, so every price here can be reproduced rather than taken on trust. The discount below is what the same engine that runs at checkout computes for this exact cart.

  • No Meridian Goods product carries the type Clearance. Set it on Aria Wireless Earbuds in Shopify before you build the cart, and on nothing else.
The cart that triggers this offer, and what it costs
ItemQtyUnit price
Aria Wireless Earbuds1$79.00
Subtotal$79.00
Discount-$9.99
Total$69.01

Signed out, or signed in as a customer with no tags.

The figure the tool checks you against

Before publishing, the tool asks what your old Script did to a cart you already have. Set it to 1 of Aria Wireless Earbuds at $79.00 and the answer is fixed.

The discount is $9.99. The total the shopper paid is $69.01.

The comparison is exact, down to the smallest unit of your currency. One cent out and publishing stays shut, which is the whole point of the step.

The cart that must produce nothing
  • swap the cart for a single Braided USB-C Cable
The cart that must not trigger this offer
ItemQtyUnit price
Braided USB-C Cable1$14.00
Subtotal$14.00
Total$14.00

Signed out, or signed in as a customer with no tags.

Amount off per unit (D7)

Rebuilt

Danger pair D7. This file and its partner look almost identical and behave differently, so read both before you decide which one you have.

The original Ruby

# GENERATED BY THE SHOPIFY SCRIPT CREATOR APP

class FixedItemDiscount
  def initialize(amount, message)
    @amount = Money.new(cents: 100) * amount
    @message = message
  end

  def apply(line_item)
    per_item_discount = @amount
    discount_to_apply = [(per_item_discount * line_item.quantity), line_item.line_price].min
    line_item.change_line_price([line_item.line_price - discount_to_apply, Money.zero].max, message: @message)
  end
end

CAMPAIGNS = [
  ConditionalDiscount.new(:all, nil, nil, nil, FixedItemDiscount.new(5, "$5 off each"), 0),
].freeze

CAMPAIGNS.each do |campaign|
  campaign.run(Input.cart)
end

Output.cart = Input.cart
What it becomes

Recognised as Amount off at a quantity and rebuilt as a volume offer. These are the real configuration keys the offer is saved with, so you can check your own rebuild against them field by field.

Every setting, spelled out
SettingValueConfig key
Offer typevolumetype
Countingper_productcounting
Recurrenceoncerecurrence
Tier 1min qty 1 -> $5.00 off eachtiers[0]
Target scopealltarget.scope
MethodautomaticdiscountMethod
Allocationstandardstacking.allocation
Combines with other discountsnostacking
You will be asked to confirm
  • Confirm which products this applies to. We could not read product targeting from the script.
The cart that proves it

Built on our public demo store, Meridian Goods, so every price here can be reproduced rather than taken on trust. The discount below is what the same engine that runs at checkout computes for this exact cart.

The cart that triggers this offer, and what it costs
ItemQtyUnit price
Braided USB-C Cable1$14.00
Subtotal$14.00
Discount-$5.00
Total$9.00

Signed out, or signed in as a customer with no tags.

The figure the tool checks you against

Before publishing, the tool asks what your old Script did to a cart you already have. Set it to 1 of Braided USB-C Cable at $14.00 and the answer is fixed.

The discount is $5.00. The total the shopper paid is $9.00.

The comparison is exact, down to the smallest unit of your currency. One cent out and publishing stays shut, which is the whole point of the step.

The cart that must produce nothing

This rule carries no condition at all, so it fires on any cart holding anything and there is no quiet cart to build. The negative check is an empty cart, which the app runs for you. That is correct behaviour, not a failure.

Amount off per unit dollars (D11)

Rebuilt

Danger pair D11. This file and its partner look almost identical and behave differently, so read both before you decide which one you have.

The original Ruby

# GENERATED BY THE SHOPIFY SCRIPT CREATOR APP

class FixedItemDiscount
  def initialize(amount, message)
    @amount = Money.new(cents: 100) * amount
    @message = message
  end

  def apply(line_item)
    discount_to_apply = [(@amount * line_item.quantity), line_item.line_price].min
    line_item.change_line_price([line_item.line_price - discount_to_apply, Money.zero].max, message: @message)
  end
end

CAMPAIGNS = [
  ConditionalDiscount.new(:all, nil, nil, nil, FixedItemDiscount.new(5, "$5 off each"), 0),
].freeze

CAMPAIGNS.each do |campaign|
  campaign.run(Input.cart)
end

Output.cart = Input.cart
What it becomes

Recognised as Amount off at a quantity and rebuilt as a volume offer. These are the real configuration keys the offer is saved with, so you can check your own rebuild against them field by field.

Every setting, spelled out
SettingValueConfig key
Offer typevolumetype
Countingper_productcounting
Recurrenceoncerecurrence
Tier 1min qty 1 -> $5.00 off eachtiers[0]
Target scopealltarget.scope
MethodautomaticdiscountMethod
Allocationstandardstacking.allocation
Combines with other discountsnostacking
You will be asked to confirm
  • Confirm which products this applies to. We could not read product targeting from the script.
The cart that proves it

Built on our public demo store, Meridian Goods, so every price here can be reproduced rather than taken on trust. The discount below is what the same engine that runs at checkout computes for this exact cart.

The cart that triggers this offer, and what it costs
ItemQtyUnit price
Braided USB-C Cable1$14.00
Subtotal$14.00
Discount-$5.00
Total$9.00

Signed out, or signed in as a customer with no tags.

The figure the tool checks you against

Before publishing, the tool asks what your old Script did to a cart you already have. Set it to 1 of Braided USB-C Cable at $14.00 and the answer is fixed.

The discount is $5.00. The total the shopper paid is $9.00.

The comparison is exact, down to the smallest unit of your currency. One cent out and publishing stays shut, which is the whole point of the step.

The cart that must produce nothing

This rule carries no condition at all, so it fires on any cart holding anything and there is no quiet cart to build. The negative check is an empty cart, which the app runs for you. That is correct behaviour, not a failure.

Amount off per unit cents (D11)

Rebuilt

Danger pair D11. This file and its partner look almost identical and behave differently, so read both before you decide which one you have.

The original Ruby

DISCOUNT = Money.new(cents: 500)

Input.cart.line_items.each do |line_item|
  discount_to_apply = [(DISCOUNT * line_item.quantity), line_item.line_price].min
  line_item.change_line_price([line_item.line_price - discount_to_apply, Money.zero].max, message: "$5 off each")
end

Output.cart = Input.cart
What it becomes

Recognised as Amount off at a quantity and rebuilt as a volume offer. These are the real configuration keys the offer is saved with, so you can check your own rebuild against them field by field.

Every setting, spelled out
SettingValueConfig key
Offer typevolumetype
Countingper_productcounting
Recurrenceoncerecurrence
Tier 1min qty 1 -> $5.00 off eachtiers[0]
Target scopealltarget.scope
MethodautomaticdiscountMethod
Allocationstandardstacking.allocation
Combines with other discountsnostacking
You will be asked to confirm
  • Confirm which products this applies to. We could not read product targeting from the script.
The cart that proves it

Built on our public demo store, Meridian Goods, so every price here can be reproduced rather than taken on trust. The discount below is what the same engine that runs at checkout computes for this exact cart.

The cart that triggers this offer, and what it costs
ItemQtyUnit price
Braided USB-C Cable1$14.00
Subtotal$14.00
Discount-$5.00
Total$9.00

Signed out, or signed in as a customer with no tags.

The figure the tool checks you against

Before publishing, the tool asks what your old Script did to a cart you already have. Set it to 1 of Braided USB-C Cable at $14.00 and the answer is fixed.

The discount is $5.00. The total the shopper paid is $9.00.

The comparison is exact, down to the smallest unit of your currency. One cent out and publishing stays shut, which is the whole point of the step.

The cart that must produce nothing

This rule carries no condition at all, so it fires on any cart holding anything and there is no quiet cart to build. The negative check is an empty cart, which the app runs for you. That is correct behaviour, not a failure.

Two files that look the same

Each pair below is one Script shape written two ways. They differ by a line or two and they do different things, so read both before deciding which one you have.

Danger pair D1

Amount off per unit (D1)

Rebuilt

Rebuilt as a volume offer.

Discount on its own cart: $9.99

Lines that differ
DISCOUNT = Money.new(cents: 999)
  new_price = line_item.line_price - (DISCOUNT * line_item.quantity)
  line_item.change_line_price(new_price, message: "$9.99 off each")
Read Amount off each item

Fixed price per unit (D1)

Rebuilt

Rebuilt as a volume offer.

Discount on its own cart: $69.01

Lines that differ
FIXED_PRICE = Money.new(cents: 999)
  line_item.change_line_price(FIXED_PRICE * line_item.quantity, message: "Clearance $9.99 each")
Read Fixed price per item

Danger pair D7

Amount off per unit (D7)

Rebuilt

Rebuilt as a volume offer.

Discount on its own cart: $5.00

Lines that differ
class FixedItemDiscount
  def initialize(amount, message)
    per_item_discount = @amount
    discount_to_apply = [(per_item_discount * line_item.quantity), line_item.line_price].min
    line_item.change_line_price([line_item.line_price - discount_to_apply, Money.zero].max, message: @message)
  ConditionalDiscount.new(:all, nil, nil, nil, FixedItemDiscount.new(5, "$5 off each"), 0),
Read Amount off each item

Fixed total discount cart (D7)

Rebuilt

Rebuilt as a spend_goal offer.

Discount on its own cart: $5.00

Lines that differ
class FixedTotalDiscount
  def initialize(amount, message, behaviour = :split)
    @discount_applied = Money.zero
    @behaviour = behaviour
    return unless @discount_applied < @amount
    discount_to_apply = [(@amount - @discount_applied), line_item.line_price].min

And 3 more differing lines, on its own page.

Read Amount off the order

Danger pair D11

Amount off per unit dollars (D11)

Rebuilt

Rebuilt as a volume offer.

Discount on its own cart: $5.00

Lines that differ
# GENERATED BY THE SHOPIFY SCRIPT CREATOR APP
class FixedItemDiscount
  def initialize(amount, message)
    @amount = Money.new(cents: 100) * amount
    @message = message
  end

And 10 more differing lines, on its own page.

Read Amount off each item

Amount off per unit cents (D11)

Rebuilt

Rebuilt as a volume offer.

Discount on its own cart: $5.00

Lines that differ
DISCOUNT = Money.new(cents: 500)
Input.cart.line_items.each do |line_item|
  discount_to_apply = [(DISCOUNT * line_item.quantity), line_item.line_price].min
  line_item.change_line_price([line_item.line_price - discount_to_apply, Money.zero].max, message: "$5 off each")
Read Amount off each item

Common questions

Will Stackable rebuild this script?
Yes. Every spelling on this page is rebuilt today as a volume offer, and the cart above is the one that proves it.
What cart proves it works?
1 x Aria Wireless Earbuds at $79.00. The engine takes $9.99 off, and checkout charges $69.01.
What will I have to check before publishing?
Your Script limited this to a tag, vendor, or product type. We carried that filter over, so confirm it selects the products you expect. Confirm which products this applies to. We could not read product targeting from the script.

Related Script shapes

Fixed price per item

Everything in the clearance line repriced to one round number per unit.

Amount off the order

One lump sum off the whole cart, spread across the lines.

Sitewide percentage off

One percentage off the whole catalogue, with no condition attached.

Paste your Script and see it rebuilt

The rebuild is checked against the money your old Script charged before you can publish it.

We use essential cookies to run this site, and, only with your permission, analytics cookies to understand traffic. Read our Cookie Policy.