@blamejs/blamejs-shop 0.4.119 → 0.4.120
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 +2 -0
- package/lib/asset-manifest.json +1 -1
- package/lib/discount-allocation.js +33 -59
- package/package.json +1 -1
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.120 (2026-06-26) — **Splitting a discount across order lines no longer rounds a line's share negative.** When an order-level discount is allocated across the order's lines (proportionally, equally, or by quantity), each line's share was rounded independently with half-even rounding. Independently rounded shares can sum to MORE than the discount, which drove the leftover remainder negative and produced a negative discount on one line — and the checkout recorder, which rejects a negative share, then dropped the whole allocation breakdown, leaving nothing for a later per-line partial refund to draw on. Reversing an allocation for a refund had the same flaw and could persist a negative per-line refund (a charge-back on that line). The split now composes the framework's largest-remainder allocator: it floors each share and hands out the non-negative leftover one minor unit at a time, so every line's share is non-negative, the shares sum to the discount (or the refund) exactly, and no breakdown is dropped. The only visible change is which line absorbs a leftover cent — now the line with the largest fractional remainder rather than the highest subtotal. **Fixed:** *Per-line discount and refund shares are floored and distributed, never rounded negative* — The per-line discount split rounded each share half-even and independently, so the rounded shares could sum above the discount, send the remainder negative, and assign a negative discount to one line; the checkout path then dropped the breakdown (its recorder refuses a negative share), so a subsequent per-line refund had no recorded allocation to subtract. The reverse path likewise could persist a negative per-line refund. Both now compose the framework's largest-remainder allocator, which floors each share and distributes the non-negative leftover one minor unit at a time to the largest remainders — every share is non-negative, the shares sum exactly to the total, and the allocation is recorded. The leftover cent now lands on the largest-remainder line instead of the highest-subtotal line.
|
|
12
|
+
|
|
11
13
|
- v0.4.119 (2026-06-26) — **Vendored framework refreshed to 0.15.32.** Updates the vendored blamejs framework to 0.15.32, taking in the 0.15.31 and 0.15.32 upstream releases. Both are internal test-suite tooling only: they extend the framework's guard suite with a re-verification gate that retires renamed suppression-marker tokens (so a re-verified suppression class can't inherit a stale approval) and update the matching in-source marker comments. No runtime code, public API, or wire format changed upstream, so there is no runtime or behavior change for this storefront and no operator action is required. **Changed:** *Vendored framework refreshed to 0.15.32* — Refreshes the vendored blamejs framework to 0.15.32 (taking in 0.15.31 and 0.15.32), upstream releases scoped to test-suite tooling — a re-verification gate for renamed suppression markers and the matching marker-comment updates. The shipped framework runtime, public API, and wire format are unchanged, so no operator action is required.
|
|
12
14
|
|
|
13
15
|
- v0.4.118 (2026-06-26) — **Changing an affiliate's commission kind now revalidates the commission value against the new kind's cap.** An affiliate's commission value is validated against its kind's ceiling when it is set: a percentage commission is capped at 100% (10000 basis points), while a flat per-order amount may be far larger. Changing only the commission kind — without resupplying the value — skipped that revalidation. Switching a flat per-order amount to a percentage therefore left the large flat value in place as a percentage rate: a $500-per-order flat amount became a 5,000,000% rate that computes a runaway commission on the next recorded order and feeds it straight into the pending-payout total. Updating the commission kind now revalidates the effective commission value (the resupplied one, or the stored one) against the new kind, so a change that would push the value over the new kind's cap is rejected with the same error as setting it directly, instead of being silently persisted. **Fixed:** *An affiliate commission-kind change revalidates the stored value against the new kind's cap* — Updating an affiliate re-checked the commission value against the (possibly new) kind's cap only when the value itself was part of the same update. A kind-only change left the stored value untouched and unvalidated, so switching a flat per-order amount to a percentage kept a value that is legal as a flat amount (up to the large flat ceiling) but thousands of times over the 100%-basis-point percentage cap — producing a grossly inflated commission on the next order and an inflated pending payout. The update path now revalidates the effective commission value against the effective kind whenever either the kind or the value changes, rejecting an over-cap combination rather than persisting it.
|
package/lib/asset-manifest.json
CHANGED
|
@@ -181,18 +181,18 @@ function _validateLines(lines) {
|
|
|
181
181
|
|
|
182
182
|
// ---- allocation math (pure) --------------------------------------------
|
|
183
183
|
|
|
184
|
-
// Compute the per-line share via b.money's
|
|
185
|
-
//
|
|
186
|
-
// is
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
// `allocated_minor` values sum exactly to `T`.
|
|
184
|
+
// Compute the per-line share of `total` via b.money's largest-remainder
|
|
185
|
+
// allocator. Each share is `floor(total * wi / W)`, and the non-negative
|
|
186
|
+
// leftover is handed out one minor unit at a time to the lines with the
|
|
187
|
+
// largest fractional remainders, so the `allocated_minor` values sum to
|
|
188
|
+
// `total` EXACTLY and none is ever negative.
|
|
190
189
|
//
|
|
191
|
-
// "By quantity" weights use quantity; "equal" weights every line by
|
|
192
|
-
//
|
|
193
|
-
// "proportional" weight by subtotal.
|
|
194
|
-
//
|
|
195
|
-
//
|
|
190
|
+
// "By quantity" weights use quantity; "equal" weights every line by `1`
|
|
191
|
+
// (so each gets `total / N` floored, leftover spread); "by_subtotal" /
|
|
192
|
+
// "proportional" weight by subtotal. Composing the framework allocator
|
|
193
|
+
// instead of independently rounding each share is what keeps the split
|
|
194
|
+
// conservation-safe — a per-line half-even round can sum above `total`
|
|
195
|
+
// and force a negative share, which this avoids by construction.
|
|
196
196
|
function _shares(total, lines, kind) {
|
|
197
197
|
var weights = new Array(lines.length);
|
|
198
198
|
var sumWeight = 0n;
|
|
@@ -220,32 +220,18 @@ function _shares(total, lines, kind) {
|
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
|
|
224
|
-
|
|
223
|
+
// Compose b.money's largest-remainder allocator: it floors each share and
|
|
224
|
+
// distributes the (always non-negative) leftover one minor unit at a time
|
|
225
|
+
// to the largest fractional remainders, so the shares sum to `total`
|
|
226
|
+
// EXACTLY and none is ever negative. The prior hand-rolled half-even
|
|
227
|
+
// multiply rounded each share independently and could push their sum ABOVE
|
|
228
|
+
// `total` — driving the remainder negative and a line's share below zero.
|
|
229
|
+
var allocated = b.money
|
|
230
|
+
.fromMinorUnits(BigInt(total), ROUNDING_CURRENCY)
|
|
231
|
+
.allocate(weights);
|
|
225
232
|
var shares = new Array(lines.length);
|
|
226
|
-
var allocated = 0n;
|
|
227
233
|
for (var j = 0; j < lines.length; j += 1) {
|
|
228
|
-
|
|
229
|
-
.multiply([BigInt(weights[j]), sumWeight])
|
|
230
|
-
.toMinorUnits();
|
|
231
|
-
shares[j] = share;
|
|
232
|
-
allocated += share;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Place the rounding remainder (positive or negative) on the line
|
|
236
|
-
// with the highest subtotal_minor. Ties broken by original index so
|
|
237
|
-
// the placement is deterministic across runtimes.
|
|
238
|
-
var remainder = BigInt(total) - allocated;
|
|
239
|
-
if (remainder !== 0n) {
|
|
240
|
-
var bestIdx = 0;
|
|
241
|
-
var bestSubtotal = lines[0].subtotal_minor;
|
|
242
|
-
for (var m = 1; m < lines.length; m += 1) {
|
|
243
|
-
if (lines[m].subtotal_minor > bestSubtotal) {
|
|
244
|
-
bestSubtotal = lines[m].subtotal_minor;
|
|
245
|
-
bestIdx = m;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
shares[bestIdx] = shares[bestIdx] + remainder;
|
|
234
|
+
shares[j] = allocated[j].toMinorUnits();
|
|
249
235
|
}
|
|
250
236
|
return shares;
|
|
251
237
|
}
|
|
@@ -409,10 +395,9 @@ function create(opts) {
|
|
|
409
395
|
|
|
410
396
|
// Reverse-walk an allocation: given a refund amount, distribute it
|
|
411
397
|
// across the original breakdown proportional to each line's
|
|
412
|
-
// `allocated_minor
|
|
413
|
-
//
|
|
414
|
-
//
|
|
415
|
-
// reverse direction).
|
|
398
|
+
// `allocated_minor`, via the same b.money largest-remainder allocator
|
|
399
|
+
// as the forward split — so the per-line refund shares sum to `refund`
|
|
400
|
+
// exactly and none is negative.
|
|
416
401
|
function _reverseShares(refund, breakdown) {
|
|
417
402
|
var weights = new Array(breakdown.length);
|
|
418
403
|
var sumWeight = 0n;
|
|
@@ -430,28 +415,17 @@ function create(opts) {
|
|
|
430
415
|
sumWeight += 1n;
|
|
431
416
|
}
|
|
432
417
|
}
|
|
433
|
-
|
|
434
|
-
|
|
418
|
+
// Same largest-remainder allocator as the forward split: floors each
|
|
419
|
+
// share and hands out the non-negative leftover unit-by-unit, so the
|
|
420
|
+
// per-line refund shares sum to `refund` exactly and none is negative
|
|
421
|
+
// (the prior half-even multiply could overshoot and persist a negative
|
|
422
|
+
// per-line refund — effectively a charge-back on that line).
|
|
423
|
+
var allocated = b.money
|
|
424
|
+
.fromMinorUnits(BigInt(refund), ROUNDING_CURRENCY)
|
|
425
|
+
.allocate(weights);
|
|
435
426
|
var shares = new Array(breakdown.length);
|
|
436
|
-
var allocated = 0n;
|
|
437
427
|
for (var j = 0; j < breakdown.length; j += 1) {
|
|
438
|
-
|
|
439
|
-
.multiply([BigInt(weights[j]), sumWeight])
|
|
440
|
-
.toMinorUnits();
|
|
441
|
-
shares[j] = share;
|
|
442
|
-
allocated += share;
|
|
443
|
-
}
|
|
444
|
-
var remainder = BigInt(refund) - allocated;
|
|
445
|
-
if (remainder !== 0n) {
|
|
446
|
-
var bestIdx = 0;
|
|
447
|
-
var bestAlloc = breakdown[0].allocated_minor;
|
|
448
|
-
for (var m = 1; m < breakdown.length; m += 1) {
|
|
449
|
-
if (breakdown[m].allocated_minor > bestAlloc) {
|
|
450
|
-
bestAlloc = breakdown[m].allocated_minor;
|
|
451
|
-
bestIdx = m;
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
shares[bestIdx] = shares[bestIdx] + remainder;
|
|
428
|
+
shares[j] = allocated[j].toMinorUnits();
|
|
455
429
|
}
|
|
456
430
|
return shares;
|
|
457
431
|
}
|
package/package.json
CHANGED