@blamejs/blamejs-shop 0.3.11 → 0.3.13
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 +4 -0
- package/lib/admin.js +157 -117
- package/lib/asset-manifest.json +1 -1
- package/lib/currency-rounding.js +2 -14
- package/lib/index.js +1 -0
- package/lib/text-guard.js +227 -0
- package/lib/vendor/MANIFEST.json +2 -2
- package/lib/vendor/blamejs/CHANGELOG.md +2 -0
- package/lib/vendor/blamejs/README.md +3 -2
- package/lib/vendor/blamejs/SECURITY.md +3 -0
- package/lib/vendor/blamejs/api-snapshot.json +14 -2
- package/lib/vendor/blamejs/lib/agent-event-bus.js +4 -4
- package/lib/vendor/blamejs/lib/agent-idempotency.js +6 -6
- package/lib/vendor/blamejs/lib/agent-orchestrator.js +9 -9
- package/lib/vendor/blamejs/lib/agent-posture-chain.js +10 -10
- package/lib/vendor/blamejs/lib/agent-saga.js +6 -7
- package/lib/vendor/blamejs/lib/agent-snapshot.js +8 -8
- package/lib/vendor/blamejs/lib/agent-stream.js +3 -3
- package/lib/vendor/blamejs/lib/agent-tenant.js +4 -4
- package/lib/vendor/blamejs/lib/agent-trace.js +5 -5
- package/lib/vendor/blamejs/lib/ai-disclosure.js +3 -3
- package/lib/vendor/blamejs/lib/app.js +2 -2
- package/lib/vendor/blamejs/lib/archive-read.js +1 -1
- package/lib/vendor/blamejs/lib/archive-tar-read.js +1 -1
- package/lib/vendor/blamejs/lib/archive-wrap.js +5 -5
- package/lib/vendor/blamejs/lib/audit-tools.js +65 -5
- package/lib/vendor/blamejs/lib/audit.js +2 -2
- package/lib/vendor/blamejs/lib/auth/ciba.js +1 -1
- package/lib/vendor/blamejs/lib/auth/dpop.js +1 -1
- package/lib/vendor/blamejs/lib/auth/fal.js +1 -1
- package/lib/vendor/blamejs/lib/auth/fido-mds3.js +2 -3
- package/lib/vendor/blamejs/lib/auth/jwt-external.js +2 -2
- package/lib/vendor/blamejs/lib/auth/oauth.js +9 -9
- package/lib/vendor/blamejs/lib/auth/oid4vci.js +7 -7
- package/lib/vendor/blamejs/lib/auth/oid4vp.js +1 -1
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +5 -5
- package/lib/vendor/blamejs/lib/auth/passkey.js +6 -6
- package/lib/vendor/blamejs/lib/auth/saml.js +1 -1
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +3 -6
- package/lib/vendor/blamejs/lib/backup/index.js +18 -18
- package/lib/vendor/blamejs/lib/cache.js +4 -4
- package/lib/vendor/blamejs/lib/calendar.js +5 -5
- package/lib/vendor/blamejs/lib/circuit-breaker.js +1 -1
- package/lib/vendor/blamejs/lib/cms-codec.js +2 -2
- package/lib/vendor/blamejs/lib/compliance.js +14 -14
- package/lib/vendor/blamejs/lib/crypto-field.js +58 -21
- package/lib/vendor/blamejs/lib/crypto.js +5 -6
- package/lib/vendor/blamejs/lib/db-query.js +131 -9
- package/lib/vendor/blamejs/lib/db.js +106 -22
- package/lib/vendor/blamejs/lib/external-db.js +64 -16
- package/lib/vendor/blamejs/lib/framework-schema.js +4 -4
- package/lib/vendor/blamejs/lib/guard-list-id.js +2 -2
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +1 -2
- package/lib/vendor/blamejs/lib/incident-report.js +150 -0
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +1 -1
- package/lib/vendor/blamejs/lib/mail-deploy.js +3 -3
- package/lib/vendor/blamejs/lib/mail-server-managesieve.js +2 -2
- package/lib/vendor/blamejs/lib/mail-server-pop3.js +2 -2
- package/lib/vendor/blamejs/lib/mail-store.js +1 -1
- package/lib/vendor/blamejs/lib/metrics.js +8 -8
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/dpop.js +5 -5
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +21 -22
- package/lib/vendor/blamejs/lib/middleware/protected-resource-metadata.js +2 -2
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +2 -2
- package/lib/vendor/blamejs/lib/network-dns.js +1 -2
- package/lib/vendor/blamejs/lib/network-tls.js +0 -1
- package/lib/vendor/blamejs/lib/outbox.js +1 -1
- package/lib/vendor/blamejs/lib/pqc-agent.js +1 -1
- package/lib/vendor/blamejs/lib/retention.js +1 -1
- package/lib/vendor/blamejs/lib/retry.js +1 -1
- package/lib/vendor/blamejs/lib/safe-archive.js +2 -2
- package/lib/vendor/blamejs/lib/safe-ical.js +2 -2
- package/lib/vendor/blamejs/lib/safe-mime.js +1 -1
- package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +1 -1
- package/lib/vendor/blamejs/lib/self-update.js +2 -2
- package/lib/vendor/blamejs/lib/static.js +1 -1
- package/lib/vendor/blamejs/lib/subject.js +2 -2
- package/lib/vendor/blamejs/lib/vault/index.js +64 -1
- package/lib/vendor/blamejs/lib/vault/rotate.js +19 -0
- package/lib/vendor/blamejs/lib/vendor-data.js +1 -1
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.14.7.json +77 -0
- package/lib/vendor/blamejs/scripts/release.js +28 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-tools-dual-control.test.js +115 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +163 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +102 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-column-gate.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-key-aad.test.js +109 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +53 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/incident-report.test.js +65 -0
- package/lib/webhook-subscriptions.js +11 -24
- package/lib/webhooks.js +12 -33
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,10 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.3.x
|
|
10
10
|
|
|
11
|
+
- v0.3.13 (2026-05-30) — **Updated runtime hardens the storage and audit layers; admin error pages no longer show raw database text.** The vendored blamejs runtime is updated to v0.14.7, which tightens the data and audit layers the shop is built on: database queries are checked against each table's declared columns (a reference to an undeclared column fails closed), the database encryption key is bound to its deployment so a copied key can't be unsealed elsewhere, raw SQL fragments refuse embedded string literals so values bind through placeholders, audit-chain purges can require two authorizers, and breach-notification deadlines raise a running clock. Separately, when an admin form action fails the console now shows a short generic message with the correct status instead of rendering a raw database or parser string into its error banner. No operator action is required. **Changed:** *Vendored runtime updated to v0.14.7 (storage + audit hardening)* — The underlying runtime now checks every database query against the table's declared columns and fails closed on an undeclared reference, binds the database encryption key to its data directory and key path so a relocated key won't unseal, refuses embedded string literals in raw SQL fragments, supports two-authorizer dual control on audit-chain purges, can compute sealed-column lookup hashes as a keyed MAC, and ships a running clock for breach-notification deadlines. The shop inherits these protections; no configuration change is needed. **Fixed:** *Console error banners show a safe message, not internal text* — Every admin HTML form handler now routes a thrown error through one shared classifier: a duplicate key becomes a 409 "That value is already in use.", a missing referenced record a generic not-found, malformed input a generic "Invalid input.", and an unexpected failure a generic message whose detail is recorded server-side rather than shown. Operator-facing validation messages are unchanged. This closes the path where a create form could render a raw database constraint string into its error banner, matching the API behavior the bearer path already had.
|
|
12
|
+
|
|
13
|
+
- v0.3.12 (2026-05-30) — **Currency and outbound-host validation consolidated behind one shared check.** Input validation that was duplicated across the codebase is now a single shared module. Currency codes are validated against the ISO 4217 catalog consistently wherever the admin binds money — the same check the gift-card flow gained last release — and the outbound webhook host guard (refusing loopback, private, link-local, reserved, and cloud-metadata destinations, including trailing-dot forms) is one shared function used by both the delivery and registration paths. The shared module composes the framework's Unicode codepoint catalog so free-text fields can reject control, bidi-override, and zero-width characters when needed. Behavior is equal-or-stricter than before; no operator action is required. **Changed:** *Currency validation is consistently ISO 4217* — Admin paths that bind a currency now validate it against the ISO 4217 catalog in one place, so a code that isn't a real currency is rejected consistently rather than only in some flows. · *Outbound-host SSRF guard is a single shared check* — The webhook delivery and registration paths now share one host guard — loopback, private (RFC 1918), link-local, reserved, and cloud-metadata addresses (by IP literal or known name, including trailing-dot forms) are refused identically on both, so the two can't drift apart.
|
|
14
|
+
|
|
11
15
|
- v0.3.11 (2026-05-30) — **Webhook endpoints reject internal addresses; admin errors stay clean and typed.** Outbound webhook endpoints can no longer be pointed at loopback, private, link-local, or cloud-metadata addresses, closing a server-side request forgery path from operator-supplied URLs. Several admin endpoints that previously returned a 500 on bad input now return the correct 4xx — a duplicate slug or SKU is a 409 Conflict, a missing referenced record and malformed JSON are a 400 — and none of them echo internal database or parser text back in the response. Gift cards now reject a currency that isn't a real ISO 4217 code, and the returns refund action reports a malformed id the same way its sibling actions do. No configuration change is required; upgrading picks these up automatically. **Fixed:** *Admin errors return the right status and don't leak internals* — Creating a product, variant, or inventory row with a duplicate key now returns 409 Conflict instead of 500; attaching media to a missing product, and pasting malformed JSON into the shipping-zone editor, now return 400. None of these responses include raw database or JSON-parser text — the detail is a generic, operator-facing message and the underlying error is recorded server-side. The same applies to the receipt and packing-slip routes. · *Gift cards validate the currency* — Issuing a gift card with a currency that isn't a real ISO 4217 code is now rejected with a 400 instead of creating a card in a non-existent currency. · *Consistent error status for refunds* — The returns refund action reports a malformed return id with the same 400 its approve, receive, and reject siblings use; a well-formed id that doesn't exist is still a 404. **Security:** *Webhook endpoints can't target internal addresses* — A webhook endpoint URL that points at a loopback, private (RFC 1918), link-local, reserved, or cloud-metadata address — by IP literal or by a known name such as localhost, metadata.google.internal, or any *.internal host — is now refused when the subscription is created, on both the delivery and outbound-registration paths. A trailing-dot form of those hosts (for example localhost. or 169.254.169.254.) is normalized before the check so it can't slip past. Public https endpoints are unaffected.
|
|
12
16
|
|
|
13
17
|
- v0.3.10 (2026-05-30) — **Order discounts are split across lines for accurate partial refunds.** When an order carries a cart-level discount, the admin console now records how that discount split across the order's lines — proportional to each line's value — so a later partial refund of one line knows that line's discounted share rather than the whole-order amount. A new read-only Discount splits screen shows the per-line breakdown for an order. This is back-office bookkeeping recorded after the order is placed; it has no effect on what the shopper is charged, and an order with no discount records nothing. **Added:** *Per-line discount allocation + admin view* — An order that carried an automatic cart discount now gets a recorded breakdown of how that discount split across its lines — proportional to each line's subtotal, summing exactly to the discount. A read-only Discount splits admin screen shows the breakdown for an order. The recording runs after the order is placed and never affects the charged amount; an order with no discount records nothing.
|
package/lib/admin.js
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
var pricing = require("./pricing");
|
|
34
34
|
var collectionsModule = require("./collections");
|
|
35
35
|
var quantityDiscountsModule = require("./quantity-discounts");
|
|
36
|
+
var textGuard = require("./text-guard");
|
|
36
37
|
var { AsyncLocalStorage } = require("node:async_hooks"); // allow:non-shop-require — Node-core per-request context (no npm dep); the framework itself composes it in db-role-context / log. No b.* request-context primitive exists to wrap it.
|
|
37
38
|
|
|
38
39
|
var b = require("./vendor/blamejs");
|
|
@@ -385,6 +386,59 @@ function _redirect(res, location) {
|
|
|
385
386
|
if (res.end) res.end(); else res.send("");
|
|
386
387
|
}
|
|
387
388
|
|
|
389
|
+
// Single classifier for a thrown admin error → an operator-safe outcome,
|
|
390
|
+
// shared by BOTH the bearer JSON path (_wrap) and every cookie/HTML form
|
|
391
|
+
// path so the two can never diverge on what a given error class is allowed
|
|
392
|
+
// to reveal. Returns `{ status, code, message }`:
|
|
393
|
+
//
|
|
394
|
+
// - TypeError → 400 bad-request, `e.message` verbatim. These are
|
|
395
|
+
// our own validation throws (`currency "ZZZ" is not
|
|
396
|
+
// a known ISO 4217 code`, `regions_json must be valid
|
|
397
|
+
// JSON`) — intended, operator-safe text the form must
|
|
398
|
+
// surface so the operator can correct the input.
|
|
399
|
+
// - SyntaxError → 400 bad-request, generic "Invalid input." A parser
|
|
400
|
+
// SyntaxError that reached here echoes the parse
|
|
401
|
+
// position ("...JSON at position 1"); never surface it.
|
|
402
|
+
// - UNIQUE / FOREIGN KEY constraint → 409 conflict, generic in-use /
|
|
403
|
+
// referenced-record text — never the table/column/SQL.
|
|
404
|
+
// - CHECK / NOT NULL constraint → 400 bad-request, generic
|
|
405
|
+
// missing-or-invalid text.
|
|
406
|
+
// - anything else → 500 internal-error, generic "Something went wrong
|
|
407
|
+
// — please try again." The raw message is recorded
|
|
408
|
+
// server-side via the framework audit (drop-silent,
|
|
409
|
+
// outcome:"failure") so an operator can correlate;
|
|
410
|
+
// it never reaches the client.
|
|
411
|
+
//
|
|
412
|
+
// `auditAction` (optional) names the audit action for the unknown-error
|
|
413
|
+
// record; defaults to "request" so a bare call still files under a sensible
|
|
414
|
+
// namespace.
|
|
415
|
+
function _safeNotice(e, auditAction) {
|
|
416
|
+
if (e instanceof TypeError) {
|
|
417
|
+
return { status: 400, code: "bad-request", message: (e && e.message) || "Invalid input." };
|
|
418
|
+
}
|
|
419
|
+
if (e instanceof SyntaxError) {
|
|
420
|
+
return { status: 400, code: "bad-request", message: "Invalid input." };
|
|
421
|
+
}
|
|
422
|
+
var msg = (e && e.message) || "";
|
|
423
|
+
if (/UNIQUE constraint failed/i.test(msg) || /FOREIGN KEY constraint failed/i.test(msg)) {
|
|
424
|
+
return /FOREIGN KEY/i.test(msg)
|
|
425
|
+
? { status: 409, code: "conflict", message: "A referenced record does not exist." }
|
|
426
|
+
: { status: 409, code: "conflict", message: "That value is already in use." };
|
|
427
|
+
}
|
|
428
|
+
if (/(?:CHECK|NOT NULL) constraint failed/i.test(msg)) {
|
|
429
|
+
return { status: 400, code: "bad-request", message: "A required value is missing or invalid." };
|
|
430
|
+
}
|
|
431
|
+
// Genuine unknown error — record it server-side via the framework audit
|
|
432
|
+
// (drop-silent) so operators can correlate, then hand back a generic
|
|
433
|
+
// message + a 500. The raw message never reaches the client.
|
|
434
|
+
b.audit.safeEmit({
|
|
435
|
+
action: AUDIT_NAMESPACE + "." + (auditAction || "request") + ".error",
|
|
436
|
+
outcome: "failure",
|
|
437
|
+
metadata: { message: msg || String(e) },
|
|
438
|
+
});
|
|
439
|
+
return { status: 500, code: "internal-error", message: "Something went wrong — please try again." };
|
|
440
|
+
}
|
|
441
|
+
|
|
388
442
|
function _wrap(handler, opts) {
|
|
389
443
|
// Every admin handler routes through this wrapper: bearer-token
|
|
390
444
|
// gate, error-to-problem-details translation, audit write on the
|
|
@@ -410,35 +464,13 @@ function _wrap(handler, opts) {
|
|
|
410
464
|
}
|
|
411
465
|
return result;
|
|
412
466
|
} catch (e) {
|
|
413
|
-
|
|
414
|
-
//
|
|
415
|
-
//
|
|
416
|
-
//
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
// Storage-engine constraint violations reach here as plain Errors
|
|
421
|
-
// whose message names the table/column/SQL ("UNIQUE constraint
|
|
422
|
-
// failed: products.slug"). Map the class to a clean 4xx with a
|
|
423
|
-
// GENERIC operator-facing detail — never echo the table/column/SQL.
|
|
424
|
-
var msg = (e && e.message) || "";
|
|
425
|
-
if (/UNIQUE constraint failed/i.test(msg) || /FOREIGN KEY constraint failed/i.test(msg)) {
|
|
426
|
-
return _problem(res, 409, "conflict",
|
|
427
|
-
/FOREIGN KEY/i.test(msg) ? "A referenced record does not exist." : "That value is already in use.");
|
|
428
|
-
}
|
|
429
|
-
if (/(?:CHECK|NOT NULL) constraint failed/i.test(msg)) {
|
|
430
|
-
return _problem(res, 400, "bad-request", "A required value is missing or invalid.");
|
|
431
|
-
}
|
|
432
|
-
// Genuine unknown error — record it server-side via the framework
|
|
433
|
-
// audit (drop-silent) so operators can correlate, then return a
|
|
434
|
-
// generic 500 with NO detail. The raw message never reaches the
|
|
435
|
-
// client.
|
|
436
|
-
b.audit.safeEmit({
|
|
437
|
-
action: AUDIT_NAMESPACE + "." + (opts.audit || "request") + ".error",
|
|
438
|
-
outcome: "failure",
|
|
439
|
-
metadata: { message: msg || String(e) },
|
|
440
|
-
});
|
|
441
|
-
return _problem(res, 500, "internal-error");
|
|
467
|
+
// Single classification, shared with the cookie/HTML paths via
|
|
468
|
+
// _safeNotice. A 5xx carries NO error-derived detail (the unknown
|
|
469
|
+
// case is recorded server-side inside _safeNotice); 4xx surface the
|
|
470
|
+
// generic / validation message.
|
|
471
|
+
var n = _safeNotice(e, opts.audit);
|
|
472
|
+
if (n.status >= 500) return _problem(res, n.status, n.code);
|
|
473
|
+
return _problem(res, n.status, n.code, n.message);
|
|
442
474
|
}
|
|
443
475
|
};
|
|
444
476
|
}
|
|
@@ -549,14 +581,12 @@ function mount(router, deps) {
|
|
|
549
581
|
try {
|
|
550
582
|
made = await catalog.products.create(req.body || {});
|
|
551
583
|
} catch (e) {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
}
|
|
559
|
-
throw e;
|
|
584
|
+
var n = _safeNotice(e, "product.create");
|
|
585
|
+
var page = await catalog.products.list({ limit: 100 });
|
|
586
|
+
return _sendHtml(res, n.status, renderAdminProducts({
|
|
587
|
+
shop_name: deps.shop_name, nav_available: navAvailable, products: page.rows || [],
|
|
588
|
+
notice: n.message,
|
|
589
|
+
}));
|
|
560
590
|
}
|
|
561
591
|
b.audit.safeEmit({ action: AUDIT_NAMESPACE + ".product.create", outcome: "success", metadata: { id: made.id } });
|
|
562
592
|
_redirect(res, "/admin/products/" + encodeURIComponent(made.id) + "?created=1");
|
|
@@ -968,14 +998,12 @@ function mount(router, deps) {
|
|
|
968
998
|
if (!body.sku) throw new TypeError("sku required");
|
|
969
999
|
await catalog.inventory.create(body.sku, { stock_on_hand: parseInt(body.stock_on_hand, 10) || 0 });
|
|
970
1000
|
} catch (e) {
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
}
|
|
978
|
-
throw e;
|
|
1001
|
+
var n = _safeNotice(e, "inventory.create");
|
|
1002
|
+
var page = await catalog.inventory.list({ limit: 500 });
|
|
1003
|
+
return _sendHtml(res, n.status, renderAdminInventory({
|
|
1004
|
+
shop_name: deps.shop_name, nav_available: navAvailable, inventory: page.rows || [],
|
|
1005
|
+
notice: n.message,
|
|
1006
|
+
}));
|
|
979
1007
|
}
|
|
980
1008
|
b.audit.safeEmit({ action: AUDIT_NAMESPACE + ".inventory.create", outcome: "success", metadata: { sku: body.sku } });
|
|
981
1009
|
_redirect(res, "/admin/inventory?created=1");
|
|
@@ -1086,7 +1114,12 @@ function mount(router, deps) {
|
|
|
1086
1114
|
alt_text: typeof body.alt_text === "string" ? body.alt_text : undefined,
|
|
1087
1115
|
});
|
|
1088
1116
|
} catch (e) {
|
|
1089
|
-
|
|
1117
|
+
// Bad input (TypeError), a missing product (FOREIGN KEY constraint),
|
|
1118
|
+
// or any other failure → a notice via PRG. _safeNotice records an
|
|
1119
|
+
// unknown error server-side; the redirect itself carries no raw
|
|
1120
|
+
// message, so the storage-engine string can't ride the query into
|
|
1121
|
+
// the detail banner.
|
|
1122
|
+
_safeNotice(e, "media.attach");
|
|
1090
1123
|
return _redirect(res, "/admin/products/" + enc + "?err=1");
|
|
1091
1124
|
}
|
|
1092
1125
|
b.audit.safeEmit({ action: AUDIT_NAMESPACE + ".media.attach", outcome: "success", metadata: { id: id } });
|
|
@@ -1878,17 +1911,15 @@ function mount(router, deps) {
|
|
|
1878
1911
|
var win = { to: Date.now(), from: Date.now() - b.constants.TIME.days(30) };
|
|
1879
1912
|
try { status = _labelStatusParam(url); win = _labelWindow(url); }
|
|
1880
1913
|
catch (e) {
|
|
1881
|
-
|
|
1882
|
-
notice = e.message.replace(/^admin:\s*/, "");
|
|
1914
|
+
notice = _safeNotice(e, "shipping_label.list").message.replace(/^admin:\s*/, "");
|
|
1883
1915
|
}
|
|
1884
1916
|
var payload;
|
|
1885
1917
|
try { payload = await _labelListPayload(status, win); }
|
|
1886
1918
|
catch (e2) {
|
|
1887
|
-
if (!(e2 instanceof TypeError)) throw e2;
|
|
1888
1919
|
// A bad range that slipped past the param parse (primitive
|
|
1889
1920
|
// re-validates from < to) → fall back to the default window.
|
|
1890
1921
|
win = { to: Date.now(), from: Date.now() - b.constants.TIME.days(30) };
|
|
1891
|
-
notice = e2.message.replace(/^shipping-labels:\s*/, "");
|
|
1922
|
+
notice = _safeNotice(e2, "shipping_label.list").message.replace(/^shipping-labels:\s*/, "");
|
|
1892
1923
|
payload = await _labelListPayload(status, win);
|
|
1893
1924
|
}
|
|
1894
1925
|
_sendHtml(res, 200, renderAdminShippingLabels({
|
|
@@ -1909,8 +1940,7 @@ function mount(router, deps) {
|
|
|
1909
1940
|
var rows = [], notice = null;
|
|
1910
1941
|
try { rows = await shippingLabels.pendingLabels({ limit: MAX_LABEL_LIST_LIMIT }); }
|
|
1911
1942
|
catch (e) {
|
|
1912
|
-
|
|
1913
|
-
notice = e.message.replace(/^shipping-labels:\s*/, "");
|
|
1943
|
+
notice = _safeNotice(e, "shipping_label.pending").message.replace(/^shipping-labels:\s*/, "");
|
|
1914
1944
|
}
|
|
1915
1945
|
_sendHtml(res, 200, renderAdminShippingLabelsPending({
|
|
1916
1946
|
shop_name: deps.shop_name, nav_available: navAvailable,
|
|
@@ -1936,16 +1966,14 @@ function mount(router, deps) {
|
|
|
1936
1966
|
var win = { to: Date.now(), from: Date.now() - b.constants.TIME.days(30) };
|
|
1937
1967
|
try { win = _labelWindow(url); carrier = _labelCarrierParam(url); }
|
|
1938
1968
|
catch (e) {
|
|
1939
|
-
|
|
1940
|
-
notice = e.message.replace(/^admin:\s*/, "");
|
|
1969
|
+
notice = _safeNotice(e, "shipping_label.costs").message.replace(/^admin:\s*/, "");
|
|
1941
1970
|
}
|
|
1942
1971
|
var report;
|
|
1943
1972
|
try { report = await _labelCostsReport(win, carrier); }
|
|
1944
1973
|
catch (e2) {
|
|
1945
|
-
if (!(e2 instanceof TypeError)) throw e2;
|
|
1946
1974
|
win = { to: Date.now(), from: Date.now() - b.constants.TIME.days(30) };
|
|
1947
1975
|
carrier = null;
|
|
1948
|
-
notice = e2.message.replace(/^shipping-labels:\s*/, "");
|
|
1976
|
+
notice = _safeNotice(e2, "shipping_label.costs").message.replace(/^shipping-labels:\s*/, "");
|
|
1949
1977
|
report = await _labelCostsReport(win, carrier);
|
|
1950
1978
|
}
|
|
1951
1979
|
_sendHtml(res, 200, renderAdminShippingLabelCosts({
|
|
@@ -3219,11 +3247,11 @@ function mount(router, deps) {
|
|
|
3219
3247
|
try {
|
|
3220
3248
|
ep = await webhooks.endpoints.create({ url: (typeof body.url === "string" ? body.url.trim() : body.url), events: events });
|
|
3221
3249
|
} catch (e) {
|
|
3222
|
-
|
|
3250
|
+
var n = _safeNotice(e, "webhook.create");
|
|
3223
3251
|
var rows = await webhooks.endpoints.list();
|
|
3224
|
-
return _sendHtml(res,
|
|
3252
|
+
return _sendHtml(res, n.status, renderAdminWebhooks({
|
|
3225
3253
|
shop_name: deps.shop_name, nav_available: navAvailable, endpoints: rows,
|
|
3226
|
-
known_events: KNOWN_WH_EVENTS, notice:
|
|
3254
|
+
known_events: KNOWN_WH_EVENTS, notice: n.message.replace(/^webhooks:\s*/, ""),
|
|
3227
3255
|
}));
|
|
3228
3256
|
}
|
|
3229
3257
|
b.audit.safeEmit({ action: AUDIT_NAMESPACE + ".webhook.create", outcome: "success", metadata: { id: ep.id } });
|
|
@@ -3458,10 +3486,6 @@ function mount(router, deps) {
|
|
|
3458
3486
|
return n;
|
|
3459
3487
|
}
|
|
3460
3488
|
|
|
3461
|
-
function _cleanCreateMessage(e) {
|
|
3462
|
-
return (e && e.message || "Couldn't create that collection.").replace(/^collections[.:]\s*/, "");
|
|
3463
|
-
}
|
|
3464
|
-
|
|
3465
3489
|
// Map the ?active= query to a collections.list filter: 1/true →
|
|
3466
3490
|
// active-only, 0/false → archived-only, absent → all.
|
|
3467
3491
|
function _collectionsFilter(activeS) {
|
|
@@ -3544,11 +3568,11 @@ function mount(router, deps) {
|
|
|
3544
3568
|
});
|
|
3545
3569
|
}
|
|
3546
3570
|
} catch (e) {
|
|
3547
|
-
|
|
3571
|
+
var n = _safeNotice(e, "collection.create");
|
|
3548
3572
|
var rows = await _listForBrowser({});
|
|
3549
|
-
return _sendHtml(res,
|
|
3573
|
+
return _sendHtml(res, n.status, renderAdminCollections({
|
|
3550
3574
|
shop_name: deps.shop_name, nav_available: navAvailable, collections: rows,
|
|
3551
|
-
notice:
|
|
3575
|
+
notice: n.message.replace(/^collections[.:]\s*/, ""), form_type: type,
|
|
3552
3576
|
}));
|
|
3553
3577
|
}
|
|
3554
3578
|
b.audit.safeEmit({ action: AUDIT_NAMESPACE + ".collection.create", outcome: "success" });
|
|
@@ -3862,11 +3886,11 @@ function mount(router, deps) {
|
|
|
3862
3886
|
try {
|
|
3863
3887
|
await announcements.defineAnnouncement(_announcementFromForm(req.body || {}));
|
|
3864
3888
|
} catch (e) {
|
|
3865
|
-
|
|
3889
|
+
var n = _safeNotice(e, "announcement.define");
|
|
3866
3890
|
var rows = await announcements.listAnnouncements({});
|
|
3867
|
-
return _sendHtml(res,
|
|
3891
|
+
return _sendHtml(res, n.status, renderAdminAnnouncements({
|
|
3868
3892
|
shop_name: deps.shop_name, nav_available: navAvailable, announcements: rows,
|
|
3869
|
-
notice:
|
|
3893
|
+
notice: n.message.replace(/^announcementBar[.:]\s*/, ""),
|
|
3870
3894
|
}));
|
|
3871
3895
|
}
|
|
3872
3896
|
b.audit.safeEmit({ action: AUDIT_NAMESPACE + ".announcement.define", outcome: "success" });
|
|
@@ -3941,11 +3965,11 @@ function mount(router, deps) {
|
|
|
3941
3965
|
questions: _standardSurveyQuestions(body.kind),
|
|
3942
3966
|
});
|
|
3943
3967
|
} catch (e) {
|
|
3944
|
-
|
|
3968
|
+
var n = _safeNotice(e, "survey.define");
|
|
3945
3969
|
var rows = await surveys.listSurveys({});
|
|
3946
|
-
return _sendHtml(res,
|
|
3970
|
+
return _sendHtml(res, n.status, renderAdminSurveys({
|
|
3947
3971
|
shop_name: deps.shop_name, nav_available: navAvailable, surveys: rows,
|
|
3948
|
-
notice:
|
|
3972
|
+
notice: n.message.replace(/^customerSurveys[.:]\s*/, ""),
|
|
3949
3973
|
}));
|
|
3950
3974
|
}
|
|
3951
3975
|
b.audit.safeEmit({ action: AUDIT_NAMESPACE + ".survey.define", outcome: "success" });
|
|
@@ -4054,11 +4078,11 @@ function mount(router, deps) {
|
|
|
4054
4078
|
try {
|
|
4055
4079
|
await hours.defineSchedule(_scheduleFromForm(req.body || {}));
|
|
4056
4080
|
} catch (e) {
|
|
4057
|
-
|
|
4081
|
+
var n = _safeNotice(e, "hours.define");
|
|
4058
4082
|
var rows = await hours.listSchedules();
|
|
4059
|
-
return _sendHtml(res,
|
|
4083
|
+
return _sendHtml(res, n.status, renderAdminHours({
|
|
4060
4084
|
shop_name: deps.shop_name, nav_available: navAvailable, schedules: rows,
|
|
4061
|
-
notice:
|
|
4085
|
+
notice: n.message.replace(/^businessHours[.:]\s*/, ""),
|
|
4062
4086
|
}));
|
|
4063
4087
|
}
|
|
4064
4088
|
b.audit.safeEmit({ action: AUDIT_NAMESPACE + ".hours.define", outcome: "success" });
|
|
@@ -4239,11 +4263,10 @@ function mount(router, deps) {
|
|
|
4239
4263
|
var win, notice = null;
|
|
4240
4264
|
try { win = _reportWindow(url); }
|
|
4241
4265
|
catch (e) {
|
|
4242
|
-
if (!(e instanceof TypeError)) throw e;
|
|
4243
4266
|
// Bad range → re-render with the default window + a correction
|
|
4244
4267
|
// notice (config/entry tier: the operator fixes the typo).
|
|
4245
4268
|
win = { to: Date.now(), from: Date.now() - b.constants.TIME.days(30) };
|
|
4246
|
-
notice = e.message.replace(/^admin:\s*/, "");
|
|
4269
|
+
notice = _safeNotice(e, "report.view").message.replace(/^admin:\s*/, "");
|
|
4247
4270
|
}
|
|
4248
4271
|
// CSV download from the browser surface too (a link, not a fetch).
|
|
4249
4272
|
if (url && url.searchParams.get("format") === "csv") {
|
|
@@ -4264,9 +4287,8 @@ function mount(router, deps) {
|
|
|
4264
4287
|
var report;
|
|
4265
4288
|
try { report = await _buildReport(win); }
|
|
4266
4289
|
catch (e3) {
|
|
4267
|
-
if (!(e3 instanceof TypeError)) throw e3;
|
|
4268
4290
|
win = { to: Date.now(), from: Date.now() - b.constants.TIME.days(30) };
|
|
4269
|
-
notice = e3.message.replace(/^salesReports:\s*/, "");
|
|
4291
|
+
notice = _safeNotice(e3, "report.view").message.replace(/^salesReports:\s*/, "");
|
|
4270
4292
|
report = await _buildReport(win);
|
|
4271
4293
|
}
|
|
4272
4294
|
_sendHtml(res, 200, renderAdminReports({
|
|
@@ -4466,11 +4488,11 @@ function mount(router, deps) {
|
|
|
4466
4488
|
try {
|
|
4467
4489
|
await subscriptions.plans.create(input);
|
|
4468
4490
|
} catch (e) {
|
|
4469
|
-
|
|
4491
|
+
var n = _safeNotice(e, "subscription_plan.create");
|
|
4470
4492
|
var rows = await subscriptions.plans.list({});
|
|
4471
|
-
return _sendHtml(res,
|
|
4493
|
+
return _sendHtml(res, n.status, renderAdminSubscriptionPlans({
|
|
4472
4494
|
shop_name: deps.shop_name, nav_available: navAvailable, plans: rows,
|
|
4473
|
-
notice:
|
|
4495
|
+
notice: n.message.replace(/^subscriptions[.:]\s*/, ""),
|
|
4474
4496
|
}));
|
|
4475
4497
|
}
|
|
4476
4498
|
b.audit.safeEmit({ action: AUDIT_NAMESPACE + ".subscription_plan.create", outcome: "success" });
|
|
@@ -4628,11 +4650,11 @@ function mount(router, deps) {
|
|
|
4628
4650
|
try {
|
|
4629
4651
|
issued = await _issueGiftCard(req.body || {});
|
|
4630
4652
|
} catch (e) {
|
|
4631
|
-
|
|
4653
|
+
var n = _safeNotice(e, "gift_card.issue");
|
|
4632
4654
|
var rows = await giftcards.list({});
|
|
4633
|
-
return _sendHtml(res,
|
|
4655
|
+
return _sendHtml(res, n.status, renderAdminGiftCards({
|
|
4634
4656
|
shop_name: deps.shop_name, nav_available: navAvailable, cards: rows,
|
|
4635
|
-
notice:
|
|
4657
|
+
notice: n.message.replace(/^giftcards?[.:]\s*/i, ""),
|
|
4636
4658
|
}));
|
|
4637
4659
|
}
|
|
4638
4660
|
b.audit.safeEmit({ action: AUDIT_NAMESPACE + ".gift_card.issue", outcome: "success", metadata: { id: issued.id } });
|
|
@@ -4769,10 +4791,7 @@ function mount(router, deps) {
|
|
|
4769
4791
|
// framework's ISO 4217 catalog (the same b.money.CURRENCIES surface the
|
|
4770
4792
|
// currency-rounding + display primitives compose) and refuse unknown
|
|
4771
4793
|
// codes with a clean 400.
|
|
4772
|
-
|
|
4773
|
-
!Object.prototype.hasOwnProperty.call(b.money.CURRENCIES, input.currency)) {
|
|
4774
|
-
throw new TypeError("giftcards: currency " + JSON.stringify(input.currency) + " is not a known ISO 4217 code");
|
|
4775
|
-
}
|
|
4794
|
+
textGuard.currencyCode(input.currency, "giftcards: currency");
|
|
4776
4795
|
if (body.amount_minor != null && body.amount_minor !== "") {
|
|
4777
4796
|
input.amount_minor = _strictMinorInt(body.amount_minor, "giftcards", "amount_minor (minor units)");
|
|
4778
4797
|
}
|
|
@@ -4823,12 +4842,18 @@ function mount(router, deps) {
|
|
|
4823
4842
|
try {
|
|
4824
4843
|
await taxRates.defineRate(_taxRateInput(body));
|
|
4825
4844
|
} catch (e) {
|
|
4826
|
-
|
|
4845
|
+
// The primitive's overlap code carries an operator-safe message;
|
|
4846
|
+
// everything else routes through the shared classifier so a raw
|
|
4847
|
+
// constraint / parser / unknown error can't reach the banner.
|
|
4848
|
+
var overlap = e && e.code === "TAX_RATE_OVERLAP";
|
|
4849
|
+
var n = overlap ? { status: 400, message: (e.message || "").replace(/^taxRates[.:]\s*/, "") }
|
|
4850
|
+
: _safeNotice(e, "tax_rate.create");
|
|
4851
|
+
var noticeMsg = overlap ? n.message : n.message.replace(/^taxRates[.:]\s*/, "");
|
|
4827
4852
|
var rows = await _taxRatesForBrowser(jurisdiction);
|
|
4828
|
-
return _sendHtml(res,
|
|
4853
|
+
return _sendHtml(res, n.status, renderAdminTaxRates({
|
|
4829
4854
|
shop_name: deps.shop_name, nav_available: navAvailable, rates: rows,
|
|
4830
4855
|
jurisdiction: jurisdiction, sources: taxRates.SOURCES,
|
|
4831
|
-
notice:
|
|
4856
|
+
notice: noticeMsg,
|
|
4832
4857
|
}));
|
|
4833
4858
|
}
|
|
4834
4859
|
b.audit.safeEmit({ action: AUDIT_NAMESPACE + ".tax_rate.create", outcome: "success" });
|
|
@@ -5016,14 +5041,21 @@ function mount(router, deps) {
|
|
|
5016
5041
|
var filing;
|
|
5017
5042
|
try { filing = await salesTaxFilings.defineFilingPeriod(_filingPeriodInput(req.body || {})); }
|
|
5018
5043
|
catch (e) {
|
|
5019
|
-
|
|
5044
|
+
// The primitive's own duplicate-period code carries an operator-safe
|
|
5045
|
+
// message (a named UNIQUE index, no raw SQL); everything else routes
|
|
5046
|
+
// through the shared classifier so a raw constraint / parser / unknown
|
|
5047
|
+
// error can't reach the banner.
|
|
5048
|
+
var dup = e && e.code === "SALES_TAX_FILING_DUPLICATE";
|
|
5049
|
+
var n = dup ? { status: 400, message: (e.message || "").replace(/^salesTaxFilings[.:]\s*/, "") }
|
|
5050
|
+
: _safeNotice(e, "tax_filing.create");
|
|
5051
|
+
var noticeMsg = dup ? n.message : n.message.replace(/^salesTaxFilings[.:]\s*/, "");
|
|
5020
5052
|
var rows = await _filingsForBrowser({});
|
|
5021
5053
|
var upcoming = await _upcomingForBrowser();
|
|
5022
|
-
return _sendHtml(res,
|
|
5054
|
+
return _sendHtml(res, n.status, renderAdminTaxFilings({
|
|
5023
5055
|
shop_name: deps.shop_name, nav_available: navAvailable,
|
|
5024
5056
|
filings: rows, upcoming: upcoming,
|
|
5025
5057
|
kinds: salesTaxFilings.KINDS, statuses: salesTaxFilings.STATUSES,
|
|
5026
|
-
notice:
|
|
5058
|
+
notice: noticeMsg,
|
|
5027
5059
|
}));
|
|
5028
5060
|
}
|
|
5029
5061
|
b.audit.safeEmit({ action: AUDIT_NAMESPACE + ".tax_filing.create", outcome: "success", metadata: { id: filing.id } });
|
|
@@ -5066,7 +5098,7 @@ function mount(router, deps) {
|
|
|
5066
5098
|
notice = "Enter both a from and a to date (epoch-ms) for the report window.";
|
|
5067
5099
|
} else {
|
|
5068
5100
|
try { report = await salesTaxFilings.auditReportForJurisdiction({ jurisdiction: jurisdiction, from: from, to: to }); }
|
|
5069
|
-
catch (e) {
|
|
5101
|
+
catch (e) { notice = _safeNotice(e, "tax_filing.report").message.replace(/^salesTaxFilings[.:]\s*/, ""); }
|
|
5070
5102
|
}
|
|
5071
5103
|
_sendHtml(res, 200, renderAdminTaxFilingReport({
|
|
5072
5104
|
shop_name: deps.shop_name, nav_available: navAvailable,
|
|
@@ -5134,10 +5166,13 @@ function mount(router, deps) {
|
|
|
5134
5166
|
try {
|
|
5135
5167
|
await run(req.params.id, req.body || {});
|
|
5136
5168
|
} catch (e) {
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5169
|
+
// The primitive's not-found / bad-transition codes carry
|
|
5170
|
+
// operator-safe messages; everything else routes through the
|
|
5171
|
+
// shared classifier so a raw constraint / parser / unknown error
|
|
5172
|
+
// can't ride the err_msg query param into the detail banner.
|
|
5173
|
+
var safe = e && (e.code === "SALES_TAX_FILING_NOT_FOUND" || e.code === "SALES_TAX_FILING_BAD_TRANSITION");
|
|
5174
|
+
var msg = (safe ? (e.message || "") : _safeNotice(e, "tax_filing." + audit).message)
|
|
5175
|
+
.replace(/^salesTaxFilings[.:]\s*/, "");
|
|
5141
5176
|
return _redirect(res, "/admin/tax-filings/" + enc + "?err=1&err_msg=" + encodeURIComponent(msg));
|
|
5142
5177
|
}
|
|
5143
5178
|
b.audit.safeEmit({ action: AUDIT_NAMESPACE + ".tax_filing." + audit, outcome: "success", metadata: { id: req.params.id } });
|
|
@@ -5234,11 +5269,17 @@ function mount(router, deps) {
|
|
|
5234
5269
|
try {
|
|
5235
5270
|
await shippingZones.defineZone(_shippingZoneInput(req.body || {}));
|
|
5236
5271
|
} catch (e) {
|
|
5237
|
-
|
|
5272
|
+
// The primitive's already-exists code carries an operator-safe
|
|
5273
|
+
// message; everything else routes through the shared classifier so
|
|
5274
|
+
// a raw constraint / parser / unknown error can't reach the banner.
|
|
5275
|
+
var dup = e && e.code === "SHIPPING_ZONE_EXISTS";
|
|
5276
|
+
var n = dup ? { status: 400, message: (e.message || "").replace(/^shippingZones[.:]\s*/, "") }
|
|
5277
|
+
: _safeNotice(e, "shipping_zone.create");
|
|
5278
|
+
var noticeMsg = dup ? n.message : n.message.replace(/^shippingZones[.:]\s*/, "");
|
|
5238
5279
|
var rows = await shippingZones.listZones({});
|
|
5239
|
-
return _sendHtml(res,
|
|
5280
|
+
return _sendHtml(res, n.status, renderAdminShipping({
|
|
5240
5281
|
shop_name: deps.shop_name, nav_available: navAvailable, zones: rows,
|
|
5241
|
-
notice:
|
|
5282
|
+
notice: noticeMsg,
|
|
5242
5283
|
}));
|
|
5243
5284
|
}
|
|
5244
5285
|
b.audit.safeEmit({ action: AUDIT_NAMESPACE + ".shipping_zone.create", outcome: "success" });
|
|
@@ -5432,9 +5473,9 @@ function mount(router, deps) {
|
|
|
5432
5473
|
try {
|
|
5433
5474
|
await autoDiscount.defineRule(_discountInput(req.body || {}));
|
|
5434
5475
|
} catch (e) {
|
|
5435
|
-
|
|
5436
|
-
return _sendHtml(res,
|
|
5437
|
-
notice:
|
|
5476
|
+
var n = _safeNotice(e, "auto_discount.create");
|
|
5477
|
+
return _sendHtml(res, n.status, await _renderDiscounts({
|
|
5478
|
+
notice: n.message.replace(/^autoDiscount[.:]\s*/, ""),
|
|
5438
5479
|
}));
|
|
5439
5480
|
}
|
|
5440
5481
|
b.audit.safeEmit({ action: AUDIT_NAMESPACE + ".auto_discount.create", outcome: "success" });
|
|
@@ -5525,9 +5566,9 @@ function mount(router, deps) {
|
|
|
5525
5566
|
try {
|
|
5526
5567
|
await couponStacking.definePolicy(_policyInput(req.body || {}));
|
|
5527
5568
|
} catch (e) {
|
|
5528
|
-
|
|
5529
|
-
return _sendHtml(res,
|
|
5530
|
-
notice:
|
|
5569
|
+
var n = _safeNotice(e, "coupon_policy.create");
|
|
5570
|
+
return _sendHtml(res, n.status, await _renderDiscounts({
|
|
5571
|
+
notice: n.message.replace(/^couponStacking[.:]\s*/, ""),
|
|
5531
5572
|
}));
|
|
5532
5573
|
}
|
|
5533
5574
|
b.audit.safeEmit({ action: AUDIT_NAMESPACE + ".coupon_policy.create", outcome: "success" });
|
|
@@ -5641,10 +5682,6 @@ function mount(router, deps) {
|
|
|
5641
5682
|
return input;
|
|
5642
5683
|
}
|
|
5643
5684
|
|
|
5644
|
-
function _qdCleanMessage(e) {
|
|
5645
|
-
return (e && e.message || "Couldn't save that tier set.").replace(/^quantityDiscounts[.:]\s*/, "");
|
|
5646
|
-
}
|
|
5647
|
-
|
|
5648
5685
|
async function _renderQdList(flags) {
|
|
5649
5686
|
flags = flags || {};
|
|
5650
5687
|
var rows = await quantityDiscounts.list(_qdFilter(flags.archived_filter));
|
|
@@ -5684,8 +5721,10 @@ function mount(router, deps) {
|
|
|
5684
5721
|
try {
|
|
5685
5722
|
await quantityDiscounts.defineTier(_qdDefineInput(req.body || {}));
|
|
5686
5723
|
} catch (e) {
|
|
5687
|
-
|
|
5688
|
-
return _sendHtml(res,
|
|
5724
|
+
var n = _safeNotice(e, "quantity_discount.create");
|
|
5725
|
+
return _sendHtml(res, n.status, await _renderQdList({
|
|
5726
|
+
notice: n.message.replace(/^quantityDiscounts[.:]\s*/, ""),
|
|
5727
|
+
}));
|
|
5689
5728
|
}
|
|
5690
5729
|
b.audit.safeEmit({ action: AUDIT_NAMESPACE + ".quantity_discount.create", outcome: "success" });
|
|
5691
5730
|
_redirect(res, "/admin/quantity-discounts?created=1");
|
|
@@ -6029,9 +6068,10 @@ function mount(router, deps) {
|
|
|
6029
6068
|
if (values.support_url) await config.put("shop.support_url", values.support_url);
|
|
6030
6069
|
await config.put("setup.completed", true);
|
|
6031
6070
|
} catch (e) {
|
|
6032
|
-
|
|
6071
|
+
var n = _safeNotice(e, "setup.save");
|
|
6072
|
+
return _sendHtml(res, n.status, renderAdminSetup({
|
|
6033
6073
|
shop_name: deps.shop_name, values: values, nav_available: navAvailable,
|
|
6034
|
-
notice:
|
|
6074
|
+
notice: n.message,
|
|
6035
6075
|
}));
|
|
6036
6076
|
}
|
|
6037
6077
|
b.audit.safeEmit({ action: AUDIT_NAMESPACE + ".setup.save", outcome: "success", metadata: {} });
|
package/lib/asset-manifest.json
CHANGED
package/lib/currency-rounding.js
CHANGED
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
*/
|
|
82
82
|
|
|
83
83
|
var b = require("./vendor/blamejs");
|
|
84
|
+
var textGuard = require("./text-guard");
|
|
84
85
|
|
|
85
86
|
// ---- constants ----------------------------------------------------------
|
|
86
87
|
|
|
@@ -97,20 +98,7 @@ var MAX_LIST_LIMIT = 500;
|
|
|
97
98
|
// typo at config-time rather than at the first checkout that touches
|
|
98
99
|
// the bad code.
|
|
99
100
|
function _currency(code) {
|
|
100
|
-
|
|
101
|
-
throw new TypeError(
|
|
102
|
-
"currencyRounding: currency must be a 3-letter uppercase ISO 4217 code, got " +
|
|
103
|
-
JSON.stringify(code)
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
var catalog = b.money.CURRENCIES;
|
|
107
|
-
if (!Object.prototype.hasOwnProperty.call(catalog, code)) {
|
|
108
|
-
throw new TypeError(
|
|
109
|
-
"currencyRounding: currency " + JSON.stringify(code) +
|
|
110
|
-
" is not in the ISO 4217 catalog"
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
return code;
|
|
101
|
+
return textGuard.currencyCode(code, "currencyRounding: currency");
|
|
114
102
|
}
|
|
115
103
|
|
|
116
104
|
function _incrementMinor(n) {
|
package/lib/index.js
CHANGED