@blamejs/blamejs-shop 0.4.100 → 0.4.101

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
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.4.x
10
10
 
11
+ - v0.4.101 (2026-06-25) — **Bulk cart operations now use the same per-line quantity ceiling as a normal add-to-cart, so a large line is no longer un-bulk-addable or silently reduced.** The cart enforces a per-line quantity ceiling on every add-to-cart. The bulk-operations surface (bulk add and reorder) carried its own copy of that ceiling that had drifted ten times lower than the cart's. The mismatch meant a line legitimately built up to the cart's ceiling through normal add-to-cart could not be added to in bulk (the whole bulk batch was rejected), and a reorder of such a line silently clamped its quantity down instead of preserving it. Bulk operations now source the ceiling directly from the cart primitive, so the two always agree: the same quantity that add-to-cart accepts is accepted in bulk, and a reorder no longer quietly reduces a line. No configuration changes. **Fixed:** *Bulk add and reorder share the cart's per-line quantity ceiling* — Bulk cart operations validated and clamped line quantities against their own ceiling, which was ten times lower than the one the cart applies on a normal add-to-cart. As a result a line whose quantity was legitimately built up to the cart's ceiling could not be bulk-added to — the entire batch was refused — and a reorder of that line silently clamped its quantity down rather than keeping it. The bulk surface now reads the ceiling from the cart primitive itself, so bulk add accepts exactly what add-to-cart accepts and a reorder preserves a line's quantity instead of reducing it.
12
+
11
13
  - v0.4.100 (2026-06-25) — **A coupon-stacking policy is now enforced at checkout, so two code-gated discounts can no longer combine past a policy that forbids it.** The coupon-stacking policy surface decides which discount codes may combine on one order — whether codes stack at all, a per-order code cap, and codes that are exclusive of everything else. That policy was applied in the admin surface but never on the buy path: checkout summed every code-gated rule a shopper had unlocked, so an operator who authored a policy forbidding combination still had it ignored on the charged price, and a customer could stack two codes the policy said could not combine. Checkout now consults the stacking policy before the discount engine sums anything: when an active policy governs the cart, the presented codes are narrowed to the policy-approved subset (honoring the non-combinable, over-cap, and exclusive-code rules) and only that subset unlocks discounts. Governance is opt-in — with no active policy the presented codes pass through exactly as before, so carts that never used stacking policies are unaffected. Operators who offer multiple combinable codes and want them to keep stacking should define a policy with allow_combine.with_other_codes enabled. **Fixed:** *Code-gated discounts now honor the coupon-stacking policy at checkout* — Checkout resolved automatic discounts by summing every rule a shopper's presented codes unlocked, with no consultation of the coupon-stacking policy — so a policy that forbade combining codes, capped the number of codes per order, or marked a code exclusive was enforced only in the admin surface, never on the price the customer was actually charged. Checkout now evaluates the stacking policy first: when an active policy governs the cart, the presented codes are reduced to the policy-approved subset before any discount is computed, so non-combinable, over-cap, and exclusive-code rules hold on the charged total. When no stacking policy is active the codes are left untouched, so existing carts that don't use stacking policies see no change; to keep multiple codes combining, define a policy with allow_combine.with_other_codes enabled.
12
14
 
13
15
  - v0.4.99 (2026-06-25) — **A subscription whose dunning retries are exhausted is now actually cancelled, instead of being marked cancelled while it keeps billing.** Dunning walks a failed-payment enrollment through its retry schedule and, when the attempt cap is reached, is documented to auto-cancel the subscription. Only an explicit cancel_subscription step in the schedule actually composed the subscription-side cancel, though: when the cap fired on the final retry/reminder step instead — the common case for a schedule that simply runs out of attempts — the enrollment was flipped to 'cancelled' but the subscription cancel was never propagated, so the recurring charge kept firing against a customer the policy had given up on. The attempt-cap close now composes the same subscription cancel an explicit cancel step does, and records it as its own audit event with its real outcome (a failed cancel is visible, not masked). A separate audit-integrity fix removes a hardcoded 'cancelled the subscription' event that the schedule-exhaustion branch wrote unconditionally, including when no cancel was attempted. No configuration changes; operators running dunning policies that rely on the attempt cap (rather than an explicit cancel step) should upgrade. **Fixed:** *Reaching the dunning attempt cap now cancels the subscription, not just the enrollment row* — When a dunning enrollment reached cancel_after_attempts on a retry or reminder step, the enrollment was closed as 'cancelled' but the subscription-side cancellation was composed only for an explicit cancel_subscription step — so a policy that relied on the attempt cap to give up left the underlying subscription active and continuing to bill. The attempt-cap close now composes the subscription cancel that the documented 'tried everything' exit always promised, resolving the invoice's subscription and signaling its cancellation exactly as an explicit cancel step does. The cancel is recorded as its own entry in the dunning audit log with the actual outcome, so a cancel that fails downstream is surfaced for follow-up rather than reported as success. · *The dunning audit log no longer records a cancellation that did not happen* — The schedule-exhaustion path wrote a fixed 'cancel_subscription / ok' audit entry regardless of how the enrollment actually closed — so an enrollment that ended without a cancellation still showed a successful cancel in the history an operator replays on the customer's timeline. The terminal audit entry now reflects what actually happened: a real cancel with its real outcome when the subscription is cancelled, and a plain schedule-exhausted record when it is not.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.4.100",
2
+ "version": "0.4.101",
3
3
  "assets": {
4
4
  "css/admin.css": {
5
5
  "integrity": "sha384-imfe0otYErcB8rr2h6KLSGTtStirysptpXETSPY4zLv3bZoIT75Lo1dOvkOav+xL",
@@ -66,7 +66,13 @@ var SLUG_RE = /^[a-z0-9](?:[a-z0-9-]{0,198}[a-z0-9])?$/;
66
66
  var SKU_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
67
67
  var CURRENCY_RE = /^[A-Z]{3}$/;
68
68
  var MAX_BULK = 500;
69
- var MAX_QTY = 9999;
69
+ // The per-line quantity ceiling is owned by the cart primitive — source it
70
+ // from there so bulk operations and cart.addLine never diverge on the max a
71
+ // single line may hold. A hand-copied value had drifted 10x lower, which made
72
+ // a line legitimately built past it via cart.addLine un-bulk-addable (addLines
73
+ // threw) and silently clamped on reorder. (cart.js does not require this
74
+ // module, so the top-level require is acyclic.)
75
+ var MAX_QTY = require("./cart").MAX_QTY;
70
76
  var SPLIT_BY = Object.freeze(["vendor", "category", "drop_ship_supplier"]);
71
77
 
72
78
  // ---- validators ---------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/blamejs-shop",
3
- "version": "0.4.100",
3
+ "version": "0.4.101",
4
4
  "description": "Open-source framework built on blamejs. Vendored stack, zero npm runtime deps, PQC-first crypto, security-on by default.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {