local-storefront 0.3.0-rc.3 → 0.3.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,29 @@
3
3
  What changed in each version of `lsf`, for someone who uses it. The section of a version is
4
4
  what its [GitHub release](https://github.com/0xtlt/local-storefront/releases) says.
5
5
 
6
+ ## 0.3.0-rc.4 - 2026-11-12
7
+
8
+ Fourth release candidate of 0.3.0. It fixes the code discounts of 0.3.0-rc.3: their uses, their
9
+ minimums and their one use per customer now follow what Shopify does.
10
+
11
+ ### Fixed
12
+
13
+ #### The checkout of an app
14
+
15
+ - **A code discount's `usageLimit` and `appliesOncePerCustomer` hold when checkouts complete at
16
+ once.** The code is checked again when the order is recorded; when the buyer can no longer use
17
+ it, the completion answers `422` with the checkout without it and "`<code>` discount code isn't
18
+ available to you right now".
19
+ - **A free shipping code that takes nothing off is not on the order.** Over its
20
+ `maximumShippingPrice` for the selected rate, on a cart with nothing to ship, or beaten by a
21
+ Function's shipping discount, it is no longer in `discount_codes` of the order and its
22
+ webhooks, and it is not counted as a use.
23
+ - **Minimums count the discounts before them**, as Shopify's help center describes: a free
24
+ shipping minimum the cart after the product and order discounts, an amount off minimum the
25
+ items it applies to after the product discounts.
26
+ - **Renaming or deleting a redeemed code no longer resets `appliesOncePerCustomer`.** Uses are
27
+ matched by discount, not by its codes of the moment.
28
+
6
29
  ## 0.3.0-rc.3 - 2026-11-12
7
30
 
8
31
  Third release candidate of 0.3.0. The local checkout applies the code discounts a backend makes
package/docs/admin.md CHANGED
@@ -1631,13 +1631,21 @@ What the backend changes through the Admin API is what the storefront and the
1631
1631
  checkout takes the code while the discount is `ACTIVE` by its dates, its `usageLimit` is not
1632
1632
  reached, the buyer is one of its `context` (all buyers, the customers it lists, the members of
1633
1633
  the segments it lists: a guest is the customer whose email they gave), has not used it when it
1634
- `appliesOncePerCustomer`, and the cart meets its `minimumRequirement` (the subtotal or the
1635
- quantity of the items it applies to, before discounts); free shipping makes free the rates up
1636
- to its `maximumShippingPrice`, to the countries of its `destination`. Otherwise the page says
1637
- "Enter a valid discount code", and the state's `discountCodes` says why (`reason`). The order
1638
- carries the code (`discountCodes`, a `DiscountCodeApplication`, the allocations), so do the
1639
- `discount_codes` and `discount_applications` of `orders/create` and `orders/paid`, and the
1640
- discount's `asyncUsageCount` (and its code's) goes up by one at once. One code of these types
1634
+ `appliesOncePerCustomer`, and the cart meets its `minimumRequirement` (the quantity of the
1635
+ items it applies to; a subtotal at the prices the discounts before it left, as on Shopify:
1636
+ the items it applies to after the product discounts for an amount off, the cart after the
1637
+ product and order discounts for free shipping); free shipping makes free the rates up to its
1638
+ `maximumShippingPrice`, to the countries of its `destination`. Otherwise the page says "Enter
1639
+ a valid discount code", and the state's `discountCodes` says why (`reason`). The order carries
1640
+ the code it took something off with (`discountCodes`, a `DiscountCodeApplication`, the
1641
+ allocations), so do the `discount_codes` and `discount_applications` of `orders/create` and
1642
+ `orders/paid` (`type` `shipping` for free shipping), and the discount's `asyncUsageCount` (and
1643
+ its code's) goes up by one at once. A free shipping code that took nothing off (a rate over
1644
+ its maximum, nothing to ship) is neither on the order nor used. The usage limit and the one
1645
+ use per customer are checked again as the order is recorded, so that two checkouts priced at
1646
+ once do not both take the last use: the second completion is refused. A use is the
1647
+ discount's: renaming its code or deleting it (`discountCodeRedeemCodeBulkDelete`) does not
1648
+ give a buyer who used it another one. One code of these types
1641
1649
  applies per checkout (lsf does not read `combinesWith`), and it stacks with the discounts of
1642
1650
  the Functions; of the shipping discounts, the best one applies. Not read: a `context` of
1643
1651
  markets (every buyer), `appliesOnOneTimePurchase` and `appliesOnSubscription`. Buy X get Y
package/docs/checkout.md CHANGED
@@ -1124,15 +1124,30 @@ amount off the order is an order discount, one off listed products, variants or
1124
1124
  off each item) a product discount, free shipping a shipping discount, and each takes its place
1125
1125
  among the discounts of the Functions by the rules above. The code is checked when the
1126
1126
  checkout is computed: the discount's dates and usage limit, its buyers, one use per customer,
1127
- its minimum (on the items it applies to, before discounts), and for free shipping the country
1128
- of the address (a code is taken before there is one). A refused code shows "Enter a valid
1129
- discount code", and its entry of `discountCodes` in the state has a `reason`
1130
- (`"its minimum purchase is 50.00 USD, and the items it applies to come to 32.00 USD"`). One code
1131
- of these types applies per checkout, the first one entered that qualifies: a second is refused,
1132
- as on Shopify when a discount combines with no other (lsf does not read `combinesWith`). Its
1127
+ its minimum, and for free shipping the country of the address (a code is taken before there is
1128
+ one). A minimum subtotal counts the prices the discounts before it left, as Shopify's do
1129
+ ([amount off](https://help.shopify.com/en/manual/discounts/discount-types/percentage-fixed-amount),
1130
+ [free shipping](https://help.shopify.com/en/manual/discounts/discount-types/free-shipping),
1131
+ "Understanding minimum purchase requirements"): for an amount off, the items it applies to
1132
+ after the product discounts of the Functions; for free shipping, the whole cart after the
1133
+ product and order discounts. A refused code shows "Enter a valid discount code", and its entry
1134
+ of `discountCodes` in the state has a `reason` (`"its minimum purchase is 50.00 USD, and the
1135
+ items it applies to come to 32.00 USD after the product discounts"`). One code of these types
1136
+ applies per checkout, the first one entered that qualifies: a second is refused, as on Shopify
1137
+ when a discount combines with no other (lsf does not read `combinesWith`). Its
1133
1138
  `DiscountApplication` is `ACROSS` (`EACH` off each item), `ALL` for the order and `ENTITLED`
1134
1139
  for listed items; free shipping is 100% off the shipping line, `EACH`.
1135
1140
 
1141
+ A free shipping code that takes nothing off (the selected rate costs more than its
1142
+ `maximumShippingPrice`, nothing in the cart ships, or a shipping discount of a Function beat it)
1143
+ stays applicable on the page, as entered, but it is not the order's: the order does not carry it
1144
+ and it is not counted as a use. The codes are checked again as the order is recorded: when
1145
+ another order took the last use of the discount (`usageLimit`) or the buyer's one
1146
+ (`appliesOncePerCustomer`) after the checkout was priced, the order is not placed. The
1147
+ completion answers 422 with the checkout as it now is (the code refused, with its `reason`, the
1148
+ price without it) and the error "`<code>` discount code isn't available to you right now" on
1149
+ `$.cart.discountCodes`; the buyer pays again for what they now see.
1150
+
1136
1151
  An amount is shared between the lines in proportion to what they cost, to the cent, and the
1137
1152
  allocations of the lines (`discountAllocations`, in the page and for the Functions that run
1138
1153
  later) add up to the discount.
@@ -1645,8 +1660,9 @@ store before a release:
1645
1660
  - **Discount combinations and conditions**: lsf combines every product and order discount
1646
1661
  ([how discounts stack](#how-discounts-stack)).
1647
1662
  - **Codes of Shopify's own discount types**: one applies per checkout whatever its
1648
- `combinesWith`, its minimum is checked on the items it applies to before any discount, and a
1649
- refused code says lsf's reason, where Shopify's checkout has messages of its own.
1663
+ `combinesWith`, a minimum subtotal counts the prices after every discount before it (Shopify
1664
+ counts only those that combine with it), and a refused code says lsf's reason, where
1665
+ Shopify's checkout has messages of its own.
1650
1666
  - **Currencies and price lists**: a buyer pays in the currency of the country of the shipping
1651
1667
  address (else of the one selected on the storefront), at its market's `rate` or fixed `prices`
1652
1668
  ([localization](data-format.md#localization)); the Functions read that rate as
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "local-storefront",
3
- "version": "0.3.0-rc.3",
3
+ "version": "0.3.0-rc.4",
4
4
  "description": "Run Shopify themes and apps locally, without Shopify: the storefront, a local checkout for Functions and checkout UI extensions, and a local GraphQL Admin API with webhooks, from JSON store data. Made for tests in CI.",
5
5
  "keywords": [
6
6
  "shopify",
@@ -38,11 +38,11 @@
38
38
  "node": ">=18"
39
39
  },
40
40
  "optionalDependencies": {
41
- "local-storefront-darwin-arm64": "0.3.0-rc.3",
42
- "local-storefront-darwin-x64": "0.3.0-rc.3",
43
- "local-storefront-linux-arm64": "0.3.0-rc.3",
44
- "local-storefront-linux-x64": "0.3.0-rc.3",
45
- "local-storefront-win32-arm64": "0.3.0-rc.3",
46
- "local-storefront-win32-x64": "0.3.0-rc.3"
41
+ "local-storefront-darwin-arm64": "0.3.0-rc.4",
42
+ "local-storefront-darwin-x64": "0.3.0-rc.4",
43
+ "local-storefront-linux-arm64": "0.3.0-rc.4",
44
+ "local-storefront-linux-x64": "0.3.0-rc.4",
45
+ "local-storefront-win32-arm64": "0.3.0-rc.4",
46
+ "local-storefront-win32-x64": "0.3.0-rc.4"
47
47
  }
48
48
  }