@tokenoftrust/storefront-runner 2.4.63 → 2.4.65

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.
@@ -1,7 +1,6 @@
1
1
  export { getOrdersViewModel } from "./orders";
2
2
  export { getProductsViewModel } from "./products";
3
3
  export { getCustomersViewModel } from "./customers";
4
- export { getFulfillmentViewModel } from "./fulfillment";
5
4
  export { getReportingViewModel } from "./reporting";
6
5
  export { getSettingsViewModel } from "./settings";
7
6
  export { getCheckoutSettingsViewModel } from "./checkoutSettings";
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Daily batch desk. Queues, evidence, and commands are a pure projection of the
3
- * commerce contract. The test adapter is not a carrier and does not buy postage.
4
- * Print payload and tracking links are owner-only. Public events and the desk
5
- * log never carry a destination.
3
+ * commerce contract. Print payload and tracking links are owner-only. Public
4
+ * events and the desk log never carry a destination.
6
5
  */
7
6
  import {
8
7
  actorMayCommand,
@@ -224,25 +223,6 @@ export interface WorkspaceCommand {
224
223
  quantityInvalid: boolean;
225
224
  lineId: boolean;
226
225
  exception: string | null;
227
- accountConfigured: boolean | null;
228
- }
229
-
230
- export interface WorkspaceHttpRequest {
231
- method: string;
232
- url: string;
233
- body: string | null;
234
- }
235
-
236
- export interface WorkspaceHttpResponse {
237
- status: number;
238
- contentType: string;
239
- body: string;
240
- }
241
-
242
- export interface TestFulfillmentAdapter {
243
- readonly livePurchase: false;
244
- state(): WorkspaceState;
245
- handle(request: WorkspaceHttpRequest): WorkspaceHttpResponse;
246
226
  }
247
227
 
248
228
  export function defaultWorkspaceQuery(): WorkspaceQuery {
@@ -842,17 +822,6 @@ export function applyWorkspaceCommand(input: WorkspaceState, command: WorkspaceC
842
822
  ids.map((orderId) => blankResult(command.command, orderId, "refused", "partial-shipment")),
843
823
  );
844
824
  }
845
- if (command.command === "set-carrier-account") {
846
- const carrierReason = explicitCarrierReason(command.carrier);
847
- if (carrierReason) return finishCommand(state, command.actorName, [blankResult(command.command, "", "refused", carrierReason)]);
848
- if (command.accountConfigured === null) {
849
- return finishCommand(state, command.actorName, [blankResult(command.command, "", "refused", "carrier-account")]);
850
- }
851
- state.carrierAccountConfigured = command.accountConfigured;
852
- return finishCommand(state, command.actorName, [
853
- blankResult(command.command, "", "admitted", command.accountConfigured ? "configured" : "missing"),
854
- ]);
855
- }
856
825
  if (command.orderIds.length === 0) {
857
826
  return finishCommand(state, command.actorName, [blankResult(command.command, "", "refused", "empty-batch")]);
858
827
  }
@@ -883,7 +852,6 @@ const COMMANDS = new Set([
883
852
  "pick-batch",
884
853
  "packing-slip",
885
854
  "record-exception",
886
- "set-carrier-account",
887
855
  "refund",
888
856
  "return",
889
857
  ]);
@@ -922,8 +890,6 @@ export function parseWorkspaceCommand(params: URLSearchParams): { ok: true; comm
922
890
  else quantity = Number(raw);
923
891
  }
924
892
  }
925
- const configuredRaw = params.get("configured");
926
- const accountConfigured = configuredRaw === "1" ? true : configuredRaw === "0" ? false : null;
927
893
  const carrierRaw = params.get("carrier");
928
894
  const purchaseRaw = params.get("purchaseKey");
929
895
  return {
@@ -938,66 +904,6 @@ export function parseWorkspaceCommand(params: URLSearchParams): { ok: true; comm
938
904
  quantityInvalid,
939
905
  lineId: params.has("lineId"),
940
906
  exception: params.get("exception"),
941
- accountConfigured,
942
- },
943
- };
944
- }
945
-
946
- export function handleWorkspaceRequest(
947
- state: WorkspaceState,
948
- request: WorkspaceHttpRequest,
949
- ): { state: WorkspaceState; response: WorkspaceHttpResponse } {
950
- let url: URL;
951
- try {
952
- url = new URL(request.url, "http://127.0.0.1");
953
- } catch {
954
- return { state, response: { status: 400, contentType: "text/plain; charset=utf-8", body: "bad url" } };
955
- }
956
- if (url.pathname !== SCRAPPY_FULFILLMENT_PATH) {
957
- return { state, response: { status: 404, contentType: "text/plain; charset=utf-8", body: "not found" } };
958
- }
959
- const method = request.method.toUpperCase();
960
- if (method === "GET") {
961
- const view = projectWorkspace(state, parseWorkspaceQuery(url.searchParams));
962
- return {
963
- state,
964
- response: { status: 200, contentType: "text/html; charset=utf-8", body: renderFulfillmentDocument(view) },
965
- };
966
- }
967
- if (method === "POST") {
968
- const params = new URLSearchParams(request.body ?? "");
969
- const parsed = parseWorkspaceCommand(params);
970
- if (!parsed.ok) {
971
- return {
972
- state,
973
- response: {
974
- status: 400,
975
- contentType: "text/html; charset=utf-8",
976
- body: `<!doctype html><p data-command-error="${esc(parsed.error)}">Unknown command.</p>`,
977
- },
978
- };
979
- }
980
- const next = applyWorkspaceCommand(state, parsed.command);
981
- const query = parseWorkspaceQuery(params);
982
- query.cursor = null;
983
- const view = projectWorkspace(next, query);
984
- return {
985
- state: next,
986
- response: { status: 200, contentType: "text/html; charset=utf-8", body: renderFulfillmentDocument(view) },
987
- };
988
- }
989
- return { state, response: { status: 405, contentType: "text/plain; charset=utf-8", body: "method" } };
990
- }
991
-
992
- export function createTestFulfillmentAdapter(tenantId = "synthetic-tenant", stage: CommerceStage = "preview"): TestFulfillmentAdapter {
993
- let state = initialWorkspaceState(tenantId, stage);
994
- return {
995
- livePurchase: false,
996
- state: () => state,
997
- handle(request) {
998
- const outcome = handleWorkspaceRequest(state, request);
999
- state = outcome.state;
1000
- return outcome.response;
1001
907
  },
1002
908
  };
1003
909
  }
@@ -1041,10 +947,6 @@ function hiddenFields(view: FulfillmentViewModel, command: string, extras: Array
1041
947
  .join("");
1042
948
  }
1043
949
 
1044
- function resultItem(result: WorkspaceResult): string {
1045
- return `<li data-result-order="${esc(result.orderId)}" data-result-command="${esc(result.command)}" data-result-status="${result.status}" data-result-reason="${esc(result.reason)}" data-cost-minor="${result.costMinor ?? ""}" data-purchase-id="${esc(result.purchaseId ?? "")}" data-live-purchase="false" data-release-reservation="${result.releaseReservation ? "true" : "false"}" data-restock="false" data-blind-repurchase="false">${esc(result.command)} ${esc(result.orderId)} ${result.status} ${esc(result.reason)}</li>`;
1046
- }
1047
-
1048
950
  function orderActions(view: FulfillmentViewModel, order: WorkspaceOrderView, action: string): string {
1049
951
  const purchaseExtras: Array<[string, string]> = [["orderId", order.id]];
1050
952
  if (order.purchaseKey !== null) purchaseExtras.push(["purchaseKey", order.purchaseKey]);
@@ -1075,10 +977,6 @@ export function renderFulfillmentWorkspace(view: FulfillmentViewModel): string {
1075
977
  return `<a data-queue-link="${queue}" href="${esc(href(view, { queue, cursor: null }))}">${esc(label)}${count}</a>`;
1076
978
  })
1077
979
  .join("");
1078
- const actorLinks = ACTOR_SPECS.map(
1079
- (spec) =>
1080
- `<a data-actor-link="${spec.name}" href="${esc(href(view, { actorName: spec.name, cursor: null }))}">${esc(spec.name)}</a>`,
1081
- ).join("");
1082
980
  const action = esc(href(view, { cursor: null }));
1083
981
  const rows = view.orders
1084
982
  .map((order) => {
@@ -1134,12 +1032,11 @@ export function renderFulfillmentWorkspace(view: FulfillmentViewModel): string {
1134
1032
  ? `<a data-page-next href="${esc(href(view, { cursor: view.nextCursor }))}">Next page</a>`
1135
1033
  : "";
1136
1034
  const account = view.carrierAccountConfigured ? "configured" : "missing";
1137
- return `<div data-scrappy-batch data-target-tenant="${esc(view.targetTenant)}" data-test-adapter="1" data-live-purchase="false" data-carrier-account="${account}" data-prohibited-carrier="USPS" data-adult-signature-required="true" data-partial-shipment="unavailable" data-returns="unavailable" data-blind-repurchase="false" data-actor="${esc(view.actorName)}" data-actor-role="${esc(view.actorRole)}" data-storewide-ready="${view.storewide.ready}" data-storewide-held="${view.storewide.held}" data-storewide-in-progress="${view.storewide.in_progress}" data-storewide-shipped="${view.storewide.shipped}" data-filter-count="${view.filterCount}" data-page-count="${view.pageCount}" data-queue="${esc(view.queue)}">
1035
+ return `<div data-scrappy-batch data-target-tenant="${esc(view.targetTenant)}" data-live-purchase="false" data-carrier-account="${account}" data-prohibited-carrier="USPS" data-adult-signature-required="true" data-partial-shipment="unavailable" data-returns="unavailable" data-blind-repurchase="false" data-actor="${esc(view.actorName)}" data-actor-role="${esc(view.actorRole)}" data-storewide-ready="${view.storewide.ready}" data-storewide-held="${view.storewide.held}" data-storewide-in-progress="${view.storewide.in_progress}" data-storewide-shipped="${view.storewide.shipped}" data-filter-count="${view.filterCount}" data-page-count="${view.pageCount}" data-queue="${esc(view.queue)}">
1138
1036
  <h2 id="fulfillment-title">Daily batch</h2>
1139
- <p>This desk reads the test adapter. Not a live label purchase. Carrier account is configuration. USPS is prohibited. Adult signature is required. Shipment is the whole order. Partial shipment is not available. Returns are not automated.</p>
1037
+ <p>Not a live label purchase. Carrier account is configuration. USPS is prohibited. Adult signature is required. Shipment is the whole order. Partial shipment is not available. Returns are not automated.</p>
1140
1038
  <p data-carrier-account-text>${view.carrierAccountConfigured ? "Carrier account: configured." : "Carrier account: not configured. Do not choose a carrier."}</p>
1141
1039
  <p><a data-refresh href="${esc(href(view))}">Refresh</a></p>
1142
- <nav data-actor-switch>${actorLinks}</nav>
1143
1040
  <nav data-queue-switch>${queueLinks}</nav>
1144
1041
  <form method="get" action="${SCRAPPY_FULFILLMENT_PATH}">
1145
1042
  <input type="hidden" name="actor" value="${esc(view.actorName)}">
@@ -1164,12 +1061,5 @@ export function renderFulfillmentWorkspace(view: FulfillmentViewModel): string {
1164
1061
  ${next}
1165
1062
  <section data-pick-list><h3>Pick list</h3><ul>${pickItems}</ul></section>
1166
1063
  ${details}
1167
- <ol data-last-results>${view.lastResults.map(resultItem).join("")}</ol>
1168
- <section data-public-events>${view.events.map((event) => `<pre>${esc(JSON.stringify(event))}</pre>`).join("")}</section>
1169
- <pre data-adapter-log>${esc(view.log.join("\n"))}</pre>
1170
1064
  </div>`;
1171
1065
  }
1172
-
1173
- export function renderFulfillmentDocument(view: FulfillmentViewModel): string {
1174
- return `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Daily batch</title></head><body><section id="fulfillment" data-admin-panel data-agent-scope="fulfillment">${renderFulfillmentWorkspace(view)}</section></body></html>`;
1175
- }
@@ -87,10 +87,30 @@ export async function readCartSecret(
87
87
  * but the secret is missing, so NO cart may be built. The caller must render
88
88
  * no buy form (never a tamperable / broken one).
89
89
  */
90
+ /** Shopper copy when checkout cannot be signed. No operator setup detail. */
91
+ export const CHECKOUT_UNAVAILABLE_SHOPPER_MESSAGE = "Checkout is unavailable.";
92
+
93
+ /** Product-page alert. The cart signing env var name stays in the server log. */
94
+ export function shopperCheckoutUnavailableHtml(): string {
95
+ return (
96
+ `<p class="mb-3 text-sm text-[var(--color-text)]" role="alert" data-cart-operator-error>` +
97
+ `${CHECKOUT_UNAVAILABLE_SHOPPER_MESSAGE}</p>`
98
+ );
99
+ }
100
+
101
+ /**
102
+ * Message safe to put in an HTTP body. `signing-unavailable` carries operator
103
+ * setup text from the cart runtime; shoppers get the neutral message instead.
104
+ */
105
+ export function shopperCartDenialMessage(code: string, message: string): string {
106
+ if (code === "signing-unavailable") return CHECKOUT_UNAVAILABLE_SHOPPER_MESSAGE;
107
+ return message;
108
+ }
109
+
90
110
  export interface CartSigningResolution {
91
111
  enabled: boolean;
92
112
  secret?: string;
93
- /** Set when signed mode has no secret. Actionable for the operator. No secret value. */
113
+ /** Operator setup detail. Do not write this into shopper HTML or an HTTP body. */
94
114
  operatorError?: string;
95
115
  }
96
116
 
@@ -26,7 +26,7 @@ import {
26
26
  type CartDenialCode,
27
27
  } from "@tot/public-runtime";
28
28
  import { resolveCheckoutHandoffCorrelation } from "@/lib/commerce/checkoutHandoffCorrelation";
29
- import { resolveCartSigning } from "@/lib/checkoutCommerce";
29
+ import { resolveCartSigning, shopperCartDenialMessage } from "@/lib/checkoutCommerce";
30
30
 
31
31
  export const prerender = false;
32
32
 
@@ -88,7 +88,11 @@ async function cartFormPost(context: APIContext): Promise<Response> {
88
88
  }
89
89
  const signing = await resolveCartSigning(commerce);
90
90
  if (!signing.enabled) {
91
- return cartDenial("signing-unavailable", signing.operatorError ?? "Signed checkout is unavailable.", 503);
91
+ return cartDenial(
92
+ "signing-unavailable",
93
+ shopperCartDenialMessage("signing-unavailable", signing.operatorError ?? ""),
94
+ 503,
95
+ );
92
96
  }
93
97
  const form = await context.request.formData();
94
98
  const entries: Array<[string, string]> = [];
@@ -103,7 +107,7 @@ async function cartFormPost(context: APIContext): Promise<Response> {
103
107
  });
104
108
  if (!result.ok) {
105
109
  const status = result.code === "signing-unavailable" ? 503 : 422;
106
- return cartDenial(result.code, result.message, status);
110
+ return cartDenial(result.code, shopperCartDenialMessage(result.code, result.message), status);
107
111
  }
108
112
  return new Response(null, {
109
113
  status: 303,
@@ -37,7 +37,7 @@ import type {
37
37
  MembershipStackingPolicy,
38
38
  PurchaseOptionPricingContext,
39
39
  } from "@tot/public-runtime";
40
- import { resolveCartSigning } from "@/lib/checkoutCommerce";
40
+ import { resolveCartSigning, shopperCheckoutUnavailableHtml } from "@/lib/checkoutCommerce";
41
41
  import { isRegulatedTenant } from "@/lib/compliance/enforcement";
42
42
  import { resolveMemberContext } from "@/lib/membership/eligibility";
43
43
  import { truncate } from "@/lib/format";
@@ -223,14 +223,14 @@ let checkoutCart:
223
223
  byVariantId: Record<string, Record<string, VariantCartPayload>>;
224
224
  }
225
225
  | undefined;
226
- let cartOperatorError: string | undefined;
226
+ let checkoutUnavailable = false;
227
227
  if (commerce) {
228
228
  // Fail closed for production signed checkout: a signed tenant with no
229
229
  // cart-validation secret builds NO cart (never a tamperable form). See
230
230
  // resolveCartSigning — it also logs the misconfiguration loudly.
231
231
  const signing = await resolveCartSigning(commerce);
232
232
  if (!signing.enabled) {
233
- cartOperatorError = signing.operatorError;
233
+ checkoutUnavailable = true;
234
234
  } else {
235
235
  try {
236
236
  const purchaseOptions = visiblePurchaseOptions(
@@ -265,10 +265,9 @@ if (commerce) {
265
265
  byVariantId: Object.fromEntries(entries),
266
266
  };
267
267
  } catch (err) {
268
- cartOperatorError =
269
- err instanceof Error && err.name === "CartSigningUnavailableError"
270
- ? err.message
271
- : undefined;
268
+ if (err instanceof Error && err.name === "CartSigningUnavailableError") {
269
+ checkoutUnavailable = true;
270
+ }
272
271
  console.warn(
273
272
  `[storefront] Invalid checkout purchase options for ${product.handle}; cart disabled for this product.`,
274
273
  );
@@ -386,11 +385,7 @@ const jsonLd = [productJsonLd, breadcrumbLd(crumbs, canonicalBase)];
386
385
  The form action is the worker cart route, so a native submit reaches
387
386
  the server check. A clear browser preflight is not payment authority. */}
388
387
  <div class="mt-7" data-payment-authority="none">
389
- {cartOperatorError && (
390
- <p class="mb-3 text-sm text-[var(--color-text)]" role="alert" data-cart-operator-error>
391
- {cartOperatorError}
392
- </p>
393
- )}
388
+ {checkoutUnavailable && <Fragment set:html={shopperCheckoutUnavailableHtml()} />}
394
389
  <VariantSelector
395
390
  client:load
396
391
  variants={publicSelectorVariants(product.variants)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/storefront-runner",
3
- "version": "2.4.63",
3
+ "version": "2.4.65",
4
4
  "license": "SEE LICENSE IN LICENSE",
5
5
  "description": "World-shareable storefront runner: multi-tenant renderer on Astro/Cloudflare. No control plane.",
6
6
  "packageManager": "pnpm@11.9.0",
@@ -1,19 +0,0 @@
1
- import type { AdminTenantEnvelope } from "../../envelope";
2
- import { memoizeByEnvelope } from "../../memoizeByEnvelope";
3
- import {
4
- defaultWorkspaceQuery,
5
- initialWorkspaceState,
6
- projectWorkspace,
7
- type FulfillmentViewModel,
8
- } from "../../viewmodels/fulfillment";
9
-
10
- /** Initial test-adapter snapshot. Not a seeded queue and not a live order feed. */
11
- export function readTestAdapterSnapshot(targetTenant: string): FulfillmentViewModel {
12
- return projectWorkspace(initialWorkspaceState(targetTenant), defaultWorkspaceQuery());
13
- }
14
-
15
- function computeFulfillmentViewModel(envelope: AdminTenantEnvelope): FulfillmentViewModel {
16
- return readTestAdapterSnapshot(envelope.targetTenant);
17
- }
18
-
19
- export const getFulfillmentViewModel = memoizeByEnvelope(computeFulfillmentViewModel);