@blamejs/blamejs-shop 0.3.62 → 0.3.63
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/admin.js +12 -0
- package/lib/asset-manifest.json +1 -1
- package/lib/checkout.js +19 -2
- package/lib/webhooks.js +17 -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.3.x
|
|
10
10
|
|
|
11
|
+
- v0.3.63 (2026-06-04) — **Digital-only carts can check out, and the outbound TLS posture is documented end to end.** A cart containing only digital goods could never complete checkout: the shipping resolver correctly filters physical services out for a cart that ships nothing, but checkout then demanded the configured default physical service resolve anyway and refused the order. Digital-only carts now select the operator's digital-shipping service when one is configured, and otherwise complete with a synthesized zero-cost no-shipping selection; a physical cart with a misconfigured service id still fails loudly, since that is a real configuration error. Separately, every outbound TLS connection has been audited against the framework's post-quantum-first key-exchange posture: of all fixed external hosts, only the Stripe API edge refuses the post-quantum groups (already handled by the payment module's explicit agent) — the CAPTCHA providers, PayPal, and Cloudflare all negotiate hybrids cleanly. Operator-supplied destinations — webhook receiver URLs and admin media source URLs — deliberately keep the post-quantum-first default, with the failure mode documented where operators will look for it: a receiver that cannot negotiate a hybrid group sees deliveries fail with a TLS handshake failure, retry on the backoff schedule, and land in the dead-letter queue; a media source URL that cannot fails as the existing clean fetch error. A regression test pins that these dials never silently downgrade. **Changed:** *Outbound TLS posture audited and documented* — Every outbound connection was audited against the framework's post-quantum-first TLS key-exchange list. Fixed external hosts: the Stripe API edge is the only one that refuses the post-quantum groups, and the payment module's explicitly scoped agent already covers it (PayPal currently negotiates hybrids; the agent remains as defense). Operator-supplied destinations keep the post-quantum-first default deliberately: an outbound-webhook receiver that cannot negotiate a hybrid group sees deliveries fail with a TLS handshake failure (alert 40), retry on the backoff schedule, and reach the dead-letter queue after the fifth attempt — front such a receiver with a post-quantum-capable proxy or upgrade its TLS stack; an admin media source URL that cannot negotiate fails as the existing clean fetch error, and the file can be uploaded directly instead. Module documentation now states this where the integrations are configured, and a regression test pins that these dials never silently downgrade. **Fixed:** *Digital-only carts complete checkout* — Shipping rate resolution offers only digital-shipping services to a cart in which no line requires shipping — so the configured default physical service id could never resolve, and checkout refused every all-digital order. Checkout now falls back for all-digital carts: the operator's digital-shipping service is selected when configured, and a zero-cost "No shipping required" selection is synthesized otherwise, so the order completes and records zero shipping. Physical carts are unchanged: an unresolvable service id still fails at the boundary, because that is a real configuration typo.
|
|
12
|
+
|
|
11
13
|
- v0.3.62 (2026-06-04) — **Live card payments work, and back-in-stock unsubscribe is token-gated.** Checkout failed at the charge step on Stripe-configured deploys: the framework's outbound HTTP client offers only post-quantum hybrid TLS key-exchange groups, and the Stripe and PayPal API edges do not negotiate any of them yet, so every processor call died with a TLS handshake failure (alert 40) and checkout rendered a server error. The payment module now dials the processors through an explicitly constructed agent — TLS 1.3 minimum, the platform's default key-exchange groups — which is the framework's prescribed pattern for a peer that cannot meet the post-quantum list: the downgrade is visible in the code, scoped to the processor connections only, and every other outbound connection keeps the post-quantum-first default. Verified against the live Stripe API. The downgrade reverts when the processor edges add hybrid key-exchange support. Separately, the checkout error page no longer echoes upstream failure detail (such as TLS library internals) to the customer — that detail goes to the audit log, and the page states plainly that nothing was charged. This release also token-gates back-in-stock unsubscribe: cancelling a back-in-stock alert previously took only the subscriber's email address and the product SKU — guessable values, so a third party could silently cancel someone else's restock notification. Unsubscribe is now authorized by an opaque per-subscription token, carried in the confirmation and notification emails as a one-click link, exactly like the newsletter's unsubscribe. Tokens are stored only as keyed hashes, compared in constant time, and unknown tokens get the same response as valid ones, so the endpoint is not a subscription-existence oracle. A schema migration adds the token column; it applies automatically on deploy. **Changed:** *Back-in-stock unsubscribe requires the emailed token* — POST /stock-alert/unsubscribe no longer accepts an email + SKU pair; it requires the per-subscription token from the unsubscribe link in the confirmation or back-in-stock email. The token is minted at subscribe time for the confirmation mail and rotated per notification send, stored only as a keyed hash, and verified with a constant-time compare; bad or unknown tokens return the same uniform response as valid ones. The one-click link works without cookies or a signed-in session, so mail clients can fire it directly; each email carries the token that is current for its send. Operators upgrading: migration 0207 adds the token column and applies with the normal deploy. **Fixed:** *Stripe and PayPal connections complete their TLS handshake* — The outbound HTTP client's default TLS posture offers exclusively post-quantum hybrid key-exchange groups. Processor API edges currently answer that ClientHello with handshake_failure (TLS alert 40), so the first real charge attempt on a Stripe-configured deploy failed checkout with a server error. The payment module now passes its own connection agent on every processor call — TLS 1.3 minimum is kept, key exchange falls back to the platform defaults — while the client's SSRF pinning, retries, and response caps stay in force. The agent is scoped to the payment-processor dials; all other outbound traffic keeps the post-quantum-first posture. A regression test pins the agent's presence and configuration on the charge path. · *Checkout server errors no longer echo upstream detail to the customer* — When the charge step failed, the checkout error page interpolated the underlying error string — in this case raw TLS library internals — into the customer-facing message. Server-side failure detail now goes to the audit log; the customer sees a plain recoverable message that confirms nothing was charged. Field-validation messages, which are written for customers, are unaffected.
|
|
12
14
|
|
|
13
15
|
- v0.3.60 (2026-06-04) — **Exact integer math for percentage discounts and insurance premiums, and an honest Add-a-card state under partial Stripe configuration.** Every place that computes a percentage of a money amount — automatic percent-off discounts, bundle discounts and their per-line allocation, partial-refund policy amounts, and shipping-insurance premiums — now uses exact integer arithmetic instead of a floating-point intermediate. On very large amounts the float product loses precision past 2^53 and can drift the rounded result by a minor unit; charged totals were never affected (the charge path was already integer end-to-end), but displayed and clamped values now match exact math everywhere, with each site's rounding direction preserved. Separately, the account Payment-methods screen no longer links into a 503 when the Stripe publishable key is missing: the Add-a-card affordance now renders as a disabled control with an explanatory note until the configuration is complete, matching how the unconfigured checkout behaves. **Fixed:** *Percentage-of-amount math is exact integer arithmetic* — Automatic percent-off discounts, bundle discounts, the cart's per-line bundle allocation, partial-refund policy amounts, and shipping-insurance premiums all computed `amount × basis_points / 10000` through a floating-point multiply. Amounts and rates are both integers, so the computation is now exact integer arithmetic with each site's rounding direction unchanged — half-away-from-zero for discounts, floor for allocations and refunds, ceiling for insurance premiums (the insurer is never short). The drift was only reachable on very large amounts (products beyond 2^53) and never touched a charged total, which was already integer end-to-end; tests now pin exact results on amounts past the float-precision boundary. · *Add a card renders honestly when Stripe configuration is incomplete* — Saving a card needs the Stripe publishable key in addition to the server-side API keys. With the server keys set but the publishable key missing, the Payment-methods screen rendered a live "Add a card" link that dead-ended on a 503. The link now gates on the same condition the add route enforces: when the publishable key is absent it renders as a disabled control with a note explaining the missing configuration, while listing, choosing a default, and removing saved cards keep working. The route's own 503 remains as defense in depth for a directly typed URL.
|
package/lib/admin.js
CHANGED
|
@@ -1172,6 +1172,18 @@ function mount(router, deps) {
|
|
|
1172
1172
|
// records the media row. Endpoint is omitted entirely when no
|
|
1173
1173
|
// r2_bridge is wired (operator hasn't set D1_BRIDGE_URL +
|
|
1174
1174
|
// D1_BRIDGE_SECRET).
|
|
1175
|
+
//
|
|
1176
|
+
// TLS posture — `source_url` is operator-pasted and arbitrary, so the
|
|
1177
|
+
// fetch keeps b.httpClient's PQC-first default (ML-KEM hybrid groups,
|
|
1178
|
+
// TLS 1.3 minimum). A `source_url` host that doesn't negotiate an
|
|
1179
|
+
// ML-KEM hybrid group answers with a handshake_failure (TLS alert 40);
|
|
1180
|
+
// that surfaces below as the same clean 502 `source-fetch-failed`
|
|
1181
|
+
// (`e.message` carries the alert) the operator already retries by
|
|
1182
|
+
// re-submitting. We do NOT pin a classical-downgrade agent here the
|
|
1183
|
+
// way the PSP adapters do for their two fixed endpoints — an arbitrary
|
|
1184
|
+
// operator-supplied URL is not a fixed processor edge, so holding the
|
|
1185
|
+
// PQC default is the deliberate posture. Operators sourcing from a
|
|
1186
|
+
// host that can't meet the PQC list upload the file directly instead.
|
|
1175
1187
|
if (r2) {
|
|
1176
1188
|
// Fetch → store → attach, shared by the JSON upload route and the
|
|
1177
1189
|
// browser POST alias. Throws TypeError on bad input (mapped to 400);
|
package/lib/asset-manifest.json
CHANGED
package/lib/checkout.js
CHANGED
|
@@ -631,8 +631,25 @@ function create(deps) {
|
|
|
631
631
|
}
|
|
632
632
|
}
|
|
633
633
|
if (!selected) {
|
|
634
|
-
|
|
635
|
-
|
|
634
|
+
// All-digital cart: shipping.rates() deliberately filters the
|
|
635
|
+
// physical services out, so the caller's default (a physical
|
|
636
|
+
// service id like "std") can never resolve — that is the cart's
|
|
637
|
+
// shape, not a config error. Prefer the operator's digital_only
|
|
638
|
+
// offering when one is configured; otherwise synthesize the
|
|
639
|
+
// zero-cost no-shipping selection so a digital-only order
|
|
640
|
+
// completes. A physical cart with an unresolvable id still
|
|
641
|
+
// throws — that IS a config typo and must surface.
|
|
642
|
+
var anyShippable = enrichedLines.some(function (l) {
|
|
643
|
+
return l.requires_shipping === undefined ? true : !!l.requires_shipping;
|
|
644
|
+
});
|
|
645
|
+
if (!anyShippable) {
|
|
646
|
+
selected = ratesRow.services.length
|
|
647
|
+
? ratesRow.services[0]
|
|
648
|
+
: { id: "digital_none", label: "No shipping required", amount_minor: 0, digital_only: true };
|
|
649
|
+
} else {
|
|
650
|
+
throw new TypeError("checkout: selected_shipping_id " + JSON.stringify(input.selected_shipping_id) +
|
|
651
|
+
" not available for ship_to");
|
|
652
|
+
}
|
|
636
653
|
}
|
|
637
654
|
}
|
|
638
655
|
|
package/lib/webhooks.js
CHANGED
|
@@ -34,6 +34,23 @@
|
|
|
34
34
|
* `b.uuid.v7()` per delivery — re-deliveries (manual retry) carry a
|
|
35
35
|
* new id so the consumer's nonce store doesn't reject the replay as
|
|
36
36
|
* a duplicate.
|
|
37
|
+
*
|
|
38
|
+
* TLS posture — outbound deliveries dial through `b.httpClient`,
|
|
39
|
+
* which offers ONLY the framework's PQC-hybrid key-exchange groups
|
|
40
|
+
* (ML-KEM hybrid, `constants.TLS_GROUP_PREFERENCE`) with a TLS 1.3
|
|
41
|
+
* minimum. A receiver whose TLS stack does not yet negotiate an
|
|
42
|
+
* ML-KEM hybrid group answers the ClientHello with a
|
|
43
|
+
* handshake_failure (TLS alert 40); the framework surfaces that as a
|
|
44
|
+
* transport error, so the delivery row records `last_error`, runs the
|
|
45
|
+
* backoff retry schedule, and — if the endpoint never negotiates a
|
|
46
|
+
* supported group — lands in `webhook_dlq` after the fifth attempt.
|
|
47
|
+
* This is a deliberate posture choice, NOT a silent classical
|
|
48
|
+
* downgrade: the PSP adapters (lib/payment.js) pin a downgraded agent
|
|
49
|
+
* for their two FIXED endpoints, but a webhook receiver URL is
|
|
50
|
+
* operator-arbitrary, so the framework's PQC-first default is held.
|
|
51
|
+
* An operator whose receiver can't meet the PQC list terminates TLS
|
|
52
|
+
* for it on a fronting proxy (Cloudflare, a load balancer) that does,
|
|
53
|
+
* or upgrades the receiver's TLS stack.
|
|
37
54
|
*/
|
|
38
55
|
|
|
39
56
|
var b = require("./vendor/blamejs");
|
package/package.json
CHANGED