@blamejs/blamejs-shop 0.4.90 → 0.4.91
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/gift-card-ledger.js +8 -2
- package/lib/order.js +1 -0
- 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.91 (2026-06-24) — **A gift-card refund is now linked to the order it reverses, so an order's gift-card settlement history is complete.** When a refund returns money to a gift card (a cancelled or refunded order that had been paid with the card), the ledger credit now records the order it reverses in the structured order field, not only as a free-text reference. Previously a query for a given order's gift-card movements returned the debits that spent the card but not the refund credits that returned money to it, so the order's gift-card settlement history read as one-sided. The refund credit now carries the order link, so the order's gift-card history shows both the spend and the matching refund. No configuration changes, and existing gift-card behavior — balances, overdraft refusal, and the per-card tamper-evident hash chain — is unchanged. **Fixed:** *Refund-to-gift-card credits carry the order they reverse* — giftCardLedger.credit now accepts an optional order_id, and the order-cancellation/refund path that returns a redeemed amount to a gift card passes the order it reverses. The ledger's transactionsForOrder(order_id) lookup keys on that structured field, so it now returns the refund credits flowing back to a card alongside the debits that drained it for the order — previously the credits were keyed only by a free-text reference and were absent from the lookup. Each refund credit still participates in the per-card hash chain (the order link is covered by the row hash), and credits with no associated order (promotional or manual top-ups) are unaffected.
|
|
12
|
+
|
|
11
13
|
- v0.4.90 (2026-06-24) — **Credit-account releases and aging now attribute payments to orders correctly, so cancelling a paid order can't erase another order's debt.** Two related accounting fixes for B2B credit accounts, no configuration or schema changes. On a credit account, payments are revolving — they pay down the oldest outstanding charge first across all of a customer's orders. Releasing the hold on a cancelled or returned order previously reduced the balance by that order's full charge without accounting for payments already applied to it, so on an account with more than one order, cancelling an order that a payment had effectively settled drove the balance below what was genuinely owed and silently erased another order's debt from the outstanding total and the aging report. A release now reduces the balance only by the order's still-unpaid amount (its charges minus the payments attributed to it, oldest-first), so a cancelled-but-paid order is a no-op and every other order's genuine debt is preserved. Separately, the aging report was pooling order-scoped releases together with order-agnostic payments into a single oldest-first settlement, so releasing a recent order could settle the oldest charge and report a genuinely past-due balance as current; the report now nets each order's releases against that order's own charges before applying payments oldest-first, so the past-due buckets reflect reality. The outstanding total is unchanged in every case — only the misattribution is corrected. Upgrade to pick it up. **Changed:** *Vendored blamejs framework refreshed to 0.15.20* — The vendored framework moves from 0.15.19 to 0.15.20, with no API or configuration change. It refreshes the bundled Mozilla public-suffix list (used for cookie-scope, same-site, and DMARC/BIMI domain classification), updates the bundled WebAuthn server library to 13.3.2 (improved passkey attestation certificate-path validation), and corrects the vendored-dependency SBOM version metadata so CVE scanners match advisories against the versions actually shipped. **Fixed:** *Releasing a credit-account order caps at its unpaid amount, not its full charge* — creditLimits.releaseHold now reduces the outstanding balance only by the target order's unpaid exposure — its charges and holds, minus its own prior releases, minus the revolving payments attributed to it under an oldest-order-first allocation — capped at the live balance. Previously it reduced the balance by the order's full net charge and ignored payments entirely (payments carry no order reference), so on a multi-order account, cancelling an order that a payment had already settled released credit that was never owed and erased another order's genuine debt from the outstanding total. A cancelled order whose charge has effectively been paid now releases nothing (the request is refused as having no outstanding amount); a genuinely unpaid order still releases its full exposure. The check is a single atomic statement, so it stays correct under concurrent releases and payments. · *Aging report nets order releases against their own order before applying payments* — creditLimits.agingReport no longer pools order-scoped releases with order-agnostic payments into one oldest-first drain. It first nets each order's releases against that order's own charges (a release reverses its own order's charge, never another's), then applies the revolving payment pool oldest-charge-first across what remains. Previously, releasing a recent order could settle the oldest charge in the pooled drain, masking a genuinely past-due balance as current and inverting the aging buckets that drive dunning and collections. The total outstanding is identical to before — only the bucket distribution is corrected — and it uses the same payment-attribution model as the release cap, so a release and the report can never disagree on an order's unpaid amount.
|
|
12
14
|
|
|
13
15
|
- v0.4.89 (2026-06-24) — **Store-credit wallet balances are now tamper-evident, and concurrent gift-card ledger writes retry reliably in production.** The store-credit ledger gains the same per-customer cryptographic hash chain the gift-card ledger already carries, so a balance can no longer be silently altered by a direct database edit. Each wallet row now links to the prior one with a SHA3-512 hash over its contents, and a new verifyChain check recomputes the chain and reports the first row that doesn't link — a denormalized balance snapshot was previously a value, not a proof, so a row edit that inflated a balance or a deletion that dropped a debit passed a balance read undetected. The same change makes the balance computation safe under concurrency through a chain-parent fence and a re-read-and-retry on contention. Separately, a latent reliability bug in the gift-card ledger's concurrent-write retry is fixed: it detected a write collision by matching the database's error text, but the production data-layer redacts that text to a generic error, so a genuine collision between two writes against the same gift card could surface as a failed request instead of retrying — both ledgers now detect a collision by re-reading the chain tip, which is robust regardless of how the error is reported. No configuration changes; the store-credit schema additions apply automatically on upgrade. **Added:** *Store-credit ledger is tamper-evident with a per-customer hash chain* — Every store-credit wallet row now carries prev_hash + row_hash columns forming a per-customer SHA3-512 chain: row_hash = SHA3-512(prev_hash || canonical-json(row fields)), anchored at a genesis sentinel for a wallet's first row. A direct database edit that inflates a balance snapshot, or a deletion that drops a debit, breaks the chain at that row. A new storeCredit.verifyChain(customer_id) walks the wallet and returns the first divergence (or confirms the chain is intact); a wallet whose hash columns have all been cleared reads as unanchored rather than trusting the unverifiable rows. Because deleting a chain's most-recent rows leaves the remaining prefix internally consistent — a truncation an append-only chain can't detect on its own — verifyChain optionally accepts a trusted anchor (a row count and head hash captured from an earlier snapshot) and reports a possible truncation when the chain has fallen below it. The balance and occurred-at values are now computed in the application and protected under concurrency by a chain-parent uniqueness fence — a write derived from a stale view of the wallet collides and is retried against the current tip, so the denormalized balance stays exact under concurrent credits, debits, expirations, and the scheduled expiry sweep. **Fixed:** *Concurrent gift-card and store-credit ledger writes retry reliably in production* — The ledger retries a write that loses a race against a concurrent write to the same entity. It previously recognized that collision by matching the database engine's constraint-violation text — but the production data-layer redacts that text to a generic error, so in production a genuine collision was not recognized and the write surfaced as a failed request instead of retrying (the path only worked under the in-memory test engine, where the full error text survives). Both the gift-card ledger and the new store-credit chain now detect a collision by re-reading the chain tip after a failed write: if the tip has advanced past the row the write tried to extend, a competing write claimed the slot and the write is retried; otherwise the original error is surfaced. The retry is now robust regardless of how the underlying error is reported.
|
package/lib/asset-manifest.json
CHANGED
package/lib/gift-card-ledger.js
CHANGED
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
* degrade gracefully rather than refusing.
|
|
52
52
|
*
|
|
53
53
|
* Surface:
|
|
54
|
-
* - credit({ gift_card_id, amount_minor, source, source_ref, occurred_at? })
|
|
54
|
+
* - credit({ gift_card_id, amount_minor, source, source_ref, order_id?, occurred_at? })
|
|
55
55
|
* - debit({ gift_card_id, amount_minor, order_id, occurred_at? })
|
|
56
56
|
* - expire({ gift_card_id, amount_minor, reason, occurred_at? })
|
|
57
57
|
* - balance(gift_card_id)
|
|
@@ -341,6 +341,11 @@ function create(opts) {
|
|
|
341
341
|
var amount = _amountMinor(input.amount_minor, "amount_minor");
|
|
342
342
|
var source = _source(input.source);
|
|
343
343
|
var sourceRef = _sourceRef(input.source_ref, "source_ref");
|
|
344
|
+
// Optional structured order link — a refund_to_giftcard credit names
|
|
345
|
+
// the order it reverses, so transactionsForOrder(orderId) returns the
|
|
346
|
+
// money flowing BACK to the card alongside the debits that drained it.
|
|
347
|
+
// Other credit sources (promotional / manual top-ups) carry no order.
|
|
348
|
+
var orderId = input.order_id == null ? null : _uuid(input.order_id, "order_id");
|
|
344
349
|
var requested = _epochMs(input.occurred_at, "occurred_at");
|
|
345
350
|
if (requested == null) requested = _now();
|
|
346
351
|
|
|
@@ -349,7 +354,7 @@ function create(opts) {
|
|
|
349
354
|
amount: amount,
|
|
350
355
|
source: source,
|
|
351
356
|
sourceRef: sourceRef,
|
|
352
|
-
orderId:
|
|
357
|
+
orderId: orderId,
|
|
353
358
|
after: latest.balance + amount,
|
|
354
359
|
ts: _resolveOccurredAt(requested, latest.occurred_at),
|
|
355
360
|
guarded: false,
|
|
@@ -363,6 +368,7 @@ function create(opts) {
|
|
|
363
368
|
amount_minor: amount,
|
|
364
369
|
source: source,
|
|
365
370
|
source_ref: sourceRef,
|
|
371
|
+
order_id: orderId,
|
|
366
372
|
balance_after_minor: w.balance_after_minor,
|
|
367
373
|
occurred_at: w.occurred_at,
|
|
368
374
|
};
|
package/lib/order.js
CHANGED
|
@@ -394,6 +394,7 @@ function create(opts) {
|
|
|
394
394
|
amount_minor: rev.amount_minor,
|
|
395
395
|
source: "refund_to_giftcard",
|
|
396
396
|
source_ref: orderId,
|
|
397
|
+
order_id: orderId,
|
|
397
398
|
});
|
|
398
399
|
} catch (_ledgerErr) { /* drop-silent — the card-row balance restore above is authoritative; the ledger credit is the audit trail */ }
|
|
399
400
|
}
|
package/package.json
CHANGED