@tokenoftrust/storefront-runner 2.4.61 → 2.4.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.
@@ -204,7 +204,7 @@ function readSourcedEvidence(
204
204
  const asOfValue = verification[`${key}AsOf`];
205
205
  return {
206
206
  state,
207
- source: typeof sourceValue === "string" && sourceValue.length > 0 ? sourceValue : "checkout-order",
207
+ source: typeof sourceValue === "string" && sourceValue.length > 0 ? sourceValue : "absent",
208
208
  asOf: typeof asOfValue === "string" ? asOfValue : null,
209
209
  };
210
210
  }
@@ -266,13 +266,14 @@ export function releaseOf(order: ScrappyOrderRecord): OrderRelease {
266
266
  return { state: ready ? "ready" : "held", ready, blocks };
267
267
  }
268
268
 
269
- function checkoutRecord(order: CheckoutOrderSummary): ScrappyOrderRecord {
269
+ function checkoutRecord(order: CheckoutOrderSummary, scope: CommerceScope): ScrappyOrderRecord {
270
+ if (scope.tenantId.length === 0) throw new Error("checkout scope has no tenant");
270
271
  const payment: PaymentState = order.payment;
271
272
  return {
272
273
  id: order.id,
273
274
  placedAt: order.placedAt,
274
- tenantId: "",
275
- stage: "preview",
275
+ tenantId: scope.tenantId,
276
+ stage: scope.stage,
276
277
  currency: order.currency,
277
278
  payment: { state: payment, source: "checkout-order", asOf: order.placedAt },
278
279
  tax: readSourcedEvidence(order.verification, "tax"),
@@ -294,24 +295,16 @@ function checkoutRecord(order: CheckoutOrderSummary): ScrappyOrderRecord {
294
295
  }
295
296
 
296
297
  /** Checkout DTO assessment. `verification: null` reports UNKNOWN and held. */
297
- export function assessCheckoutOrder(order: CheckoutOrderSummary): CheckoutReadiness {
298
- const record = checkoutRecord(order);
299
- const release = order.verification == null
300
- ? heldRelease(releaseOf(record))
301
- : releaseOf(record);
298
+ export function assessCheckoutOrder(order: CheckoutOrderSummary, scope: CommerceScope): CheckoutReadiness {
299
+ const record = checkoutRecord(order, scope);
302
300
  return {
303
301
  columns: readinessColumns(record),
304
302
  evidence: evidenceOf(record),
305
- release,
303
+ release: releaseOf(record),
306
304
  riskLevel: deriveRisk(order.verification, order.blocker),
307
305
  };
308
306
  }
309
307
 
310
- function heldRelease(release: OrderRelease): OrderRelease {
311
- if (release.state === "held") return release;
312
- return { state: "held", ready: false, blocks: [...release.blocks, "evidence"] };
313
- }
314
-
315
308
  export function mapSummaryToRow(s: CheckoutOrderSummary): OrderRow {
316
309
  const verificationMissing = s.verification == null;
317
310
  const shipState: OrderShipState = verificationMissing ? "hold_review_required" : s.fulfillment;
@@ -346,8 +339,8 @@ function columnCheck(
346
339
  return { key: column.key, value: column.state, ...copy };
347
340
  }
348
341
 
349
- function deriveReadinessChecks(d: CheckoutOrderDetail): OrderReadinessCheck[] {
350
- const report = assessCheckoutOrder(d);
342
+ function deriveReadinessChecks(d: CheckoutOrderDetail, scope: CommerceScope): OrderReadinessCheck[] {
343
+ const report = assessCheckoutOrder(d, scope);
351
344
  const column = (key: OrderReadinessColumn["key"]) => report.columns.find((item) => item.key === key)!;
352
345
  const payment = column("payment");
353
346
  const tax = column("tax");
@@ -444,8 +437,8 @@ function deriveEvidenceTimeline(d: CheckoutOrderDetail): OrderEvidenceEvent[] {
444
437
  return events;
445
438
  }
446
439
 
447
- export function mapDetailToDetail(d: CheckoutOrderDetail, row: OrderRow): OrderDetail {
448
- const report = assessCheckoutOrder(d);
440
+ export function mapDetailToDetail(d: CheckoutOrderDetail, row: OrderRow, scope: CommerceScope): OrderDetail {
441
+ const report = assessCheckoutOrder(d, scope);
449
442
  return {
450
443
  ...row,
451
444
  lineItems: d.lineItems.map((li) => ({
@@ -457,7 +450,7 @@ export function mapDetailToDetail(d: CheckoutOrderDetail, row: OrderRow): OrderD
457
450
  shipping: d.totals.shipping,
458
451
  estimatedTax: d.totals.estimatedTax,
459
452
  taxesAndFees: d.totals.taxesAndFees,
460
- readinessChecks: deriveReadinessChecks(d),
453
+ readinessChecks: deriveReadinessChecks(d, scope),
461
454
  evidenceTimeline: deriveEvidenceTimeline(d),
462
455
  columns: report.columns,
463
456
  release: report.release,
@@ -481,13 +474,14 @@ export function mapOrdersToViewModel(
481
474
  targetTenant: string,
482
475
  summaries: CheckoutOrderSummary[],
483
476
  details: Record<string, CheckoutOrderDetail>,
477
+ scope: CommerceScope,
484
478
  ): OrdersViewModel {
485
479
  const rows = summaries.map(mapSummaryToRow);
486
480
  const detailsById: Record<string, OrderDetail> = {};
487
481
  for (const row of rows) {
488
482
  const d = details[row.id];
489
483
  detailsById[row.id] = d
490
- ? mapDetailToDetail(d, row)
484
+ ? mapDetailToDetail(d, row, scope)
491
485
  : {
492
486
  ...row,
493
487
  lineItems: [],
@@ -520,11 +514,25 @@ export function mapOrdersToViewModel(
520
514
  }
521
515
 
522
516
  export function emptyOrdersViewModel(targetTenant: string): OrdersViewModel {
523
- return mapOrdersToViewModel(targetTenant, [], {});
517
+ const rows: OrderRow[] = [];
518
+ return {
519
+ targetTenant,
520
+ queues: buildQueues(rows),
521
+ rows,
522
+ detailsById: {},
523
+ today: {
524
+ grossSales: 0,
525
+ paidOrders: 0,
526
+ averageOrder: 0,
527
+ refunds: 0,
528
+ oldestOpenAt: null,
529
+ },
530
+ };
524
531
  }
525
532
 
526
533
  export interface GetOrdersViewModelOptions {
527
534
  targetTenant: string;
535
+ scope: CommerceScope;
528
536
  client: CheckoutOrderClient;
529
537
  filters?: Parameters<CheckoutOrderClient["listOrders"]>[0];
530
538
  }
@@ -569,7 +577,7 @@ export async function getOrdersViewModelResult(opts: GetOrdersViewModelOptions):
569
577
  );
570
578
  return {
571
579
  status: orders.length ? "loaded" : "empty",
572
- vm: mapOrdersToViewModel(opts.targetTenant, orders, details),
580
+ vm: mapOrdersToViewModel(opts.targetTenant, orders, details, opts.scope),
573
581
  page,
574
582
  };
575
583
  } catch {
@@ -11,13 +11,13 @@ import { readEnv } from "@/lib/env";
11
11
  import { requireAdminApiEntry } from "@/lib/auth/adminApiGuard";
12
12
  import {
13
13
  commerceActorForAdmin,
14
- commerceStageForCustomization,
15
14
  createCheckoutOrderClient,
16
15
  orderApiBaseUrl,
17
16
  OrderApiError,
18
17
  projectCheckoutOrder,
19
18
  } from "@/lib/admin/orderClient";
20
19
  import { assessCheckoutOrder } from "@/lib/admin/services/orders";
20
+ import { commerceStageForEnv } from "@/lib/fulfillment/fulfillmentService";
21
21
 
22
22
  export const prerender = false;
23
23
 
@@ -46,25 +46,27 @@ export async function GET(context: APIContext): Promise<Response> {
46
46
  const bearer = await readEnv("FOXY_ORDER_API_KEY");
47
47
  if (!bearer) return jsonError("not_provisioned", 503);
48
48
 
49
+ const stage = commerceStageForEnv(customizationEnv);
50
+ if (!stage) return jsonError("stage-unknown", 409);
51
+ const tenantId = tenant.appDomain.trim();
52
+ if (!tenantId) return jsonError("not_provisioned", 503);
53
+
49
54
  const client = createCheckoutOrderClient({
50
55
  baseUrl: orderApiBaseUrl(customizationEnv),
51
- appDomain: tenant.appDomain,
56
+ appDomain: tenantId,
52
57
  bearer,
53
58
  });
54
59
 
55
60
  try {
56
- const stage = commerceStageForCustomization(customizationEnv);
61
+ const scope = { tenantId, stage };
57
62
  const actor = commerceActorForAdmin({
58
- tenantId: tenant.appDomain,
63
+ tenantId,
59
64
  stage,
60
65
  owner: access.entry.isOwnerViewer,
61
66
  admitted: access.entry.admitted,
62
67
  });
63
- const order = projectCheckoutOrder(await client.getOrder(orderId), actor, {
64
- tenantId: tenant.appDomain,
65
- stage,
66
- });
67
- const readiness = assessCheckoutOrder(order);
68
+ const order = projectCheckoutOrder(await client.getOrder(orderId), actor, scope);
69
+ const readiness = assessCheckoutOrder(order, scope);
68
70
  return new Response(JSON.stringify({ order, readiness }), { headers: JSON_HEADERS });
69
71
  } catch (err) {
70
72
  if (err instanceof OrderApiError) return jsonError(err.code, err.status);
@@ -17,6 +17,7 @@ import {
17
17
  orderApiBaseUrl,
18
18
  OrderApiError,
19
19
  } from "@/lib/admin/orderClient";
20
+ import { commerceStageForEnv } from "@/lib/fulfillment/fulfillmentService";
20
21
  import { assessCheckoutOrder, parseOrderFilters } from "@/lib/admin/services/orders";
21
22
 
22
23
  export const prerender = false;
@@ -49,21 +50,27 @@ export async function GET(context: APIContext): Promise<Response> {
49
50
  return jsonError("not_provisioned", 503);
50
51
  }
51
52
 
53
+ const stage = commerceStageForEnv(customizationEnv);
54
+ if (!stage) return jsonError("stage-unknown", 409);
55
+ const tenantId = tenant.appDomain.trim();
56
+ if (!tenantId) return jsonError("not_provisioned", 503);
57
+
52
58
  const url = new URL(context.request.url);
53
59
  // Tenant comes from the host, never from a query parameter.
54
60
  const filters = parseOrderFilters(url.searchParams);
55
61
 
56
62
  const client = createCheckoutOrderClient({
57
63
  baseUrl: orderApiBaseUrl(customizationEnv),
58
- appDomain: tenant.appDomain,
64
+ appDomain: tenantId,
59
65
  bearer,
60
66
  });
61
67
 
62
68
  try {
63
69
  const data = await client.listOrders(filters);
70
+ const scope = { tenantId, stage };
64
71
  const orders = data.orders.map((order) => ({
65
72
  ...order,
66
- readiness: assessCheckoutOrder(order),
73
+ readiness: assessCheckoutOrder(order, scope),
67
74
  }));
68
75
  return new Response(JSON.stringify({ ...data, orders }), { headers: JSON_HEADERS });
69
76
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/storefront-runner",
3
- "version": "2.4.61",
3
+ "version": "2.4.63",
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",
@@ -424,6 +424,18 @@ async function hmacHex(secret: string, message: string): Promise<string> {
424
424
  .join("");
425
425
  }
426
426
 
427
+ function constantTimeHexEqual(expected: string, presented: string): boolean {
428
+ const enc = new TextEncoder();
429
+ const left = enc.encode(expected);
430
+ const right = enc.encode(presented);
431
+ const length = Math.max(left.length, right.length);
432
+ let acc = left.length ^ right.length;
433
+ for (let i = 0; i < length; i++) {
434
+ acc |= (left[i] ?? 0) ^ (right[i] ?? 0);
435
+ }
436
+ return acc === 0;
437
+ }
438
+
427
439
  /**
428
440
  * Sign one field for product `code`. Editable fields hash the sentinel
429
441
  * `--OPEN--` and gain the `||open` suffix so checkout accepts a user-entered value.
@@ -596,7 +608,7 @@ export async function enforceCartSubmission(input: {
596
608
  if (!field.hash) return false;
597
609
  const valueToHash = field.open ? "--OPEN--" : field.value;
598
610
  const expected = await hmacHex(secret, codeField.value + field.baseName + valueToHash);
599
- return expected === field.hash;
611
+ return constantTimeHexEqual(expected, field.hash);
600
612
  };
601
613
 
602
614
  if (signed) {