@tokenoftrust/storefront-runner 2.4.91 → 2.5.0

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.
Files changed (33) hide show
  1. package/apps/storefront/migrations-apps/0017_commerce_durable_projection.sql +28 -0
  2. package/apps/storefront/migrations-apps/0018_commerce_provider_cart_identity.sql +13 -0
  3. package/apps/storefront/src/config/declaredTenantConfig.ts +5 -0
  4. package/apps/storefront/src/config/devTenantSeed.ts +3 -2
  5. package/apps/storefront/src/lib/checkout/commerce/commerceAdmission.ts +108 -0
  6. package/apps/storefront/src/lib/checkout/commerce/finalCartAuthorization.ts +1 -1
  7. package/apps/storefront/src/lib/checkout/shared/checkoutCommerce.ts +6 -4
  8. package/apps/storefront/src/lib/fulfillment/admin/commerceDesk.ts +111 -0
  9. package/apps/storefront/src/lib/fulfillment/admin/viewmodels/fulfillment.ts +1 -1
  10. package/apps/storefront/src/lib/fulfillment/commerceProjection.ts +146 -0
  11. package/apps/storefront/src/lib/fulfillment/commerceReadback.ts +60 -0
  12. package/apps/storefront/src/lib/fulfillment/commerceRecovery.ts +65 -0
  13. package/apps/storefront/src/lib/fulfillment/commerceRuntime.ts +13 -0
  14. package/apps/storefront/src/lib/fulfillment/fulfillmentService.ts +50 -4
  15. package/apps/storefront/src/lib/fulfillment/readiness.ts +1 -0
  16. package/apps/storefront/src/lib/fulfillment/store.ts +15 -0
  17. package/apps/storefront/src/lib/integrations/webhooks/deliveryEngine.ts +6 -3
  18. package/apps/storefront/src/lib/integrations/webhooks/deliveryStore.ts +6 -5
  19. package/apps/storefront/src/lib/inventory/allocations.ts +58 -9
  20. package/apps/storefront/src/lib/inventory/mirrorStock.ts +121 -0
  21. package/apps/storefront/src/lib/orders/admin/services/orders.ts +8 -8
  22. package/apps/storefront/src/lib/orders/data/orderMapper.ts +22 -1
  23. package/apps/storefront/src/lib/orders/data/ordersStore.ts +12 -4
  24. package/apps/storefront/src/pages/api/admin/commerce-readiness.ts +56 -26
  25. package/apps/storefront/src/pages/api/commerce/checkout-handoff.ts +34 -2
  26. package/apps/storefront/src/pages/api/commerce/final-cart-authorization.ts +158 -306
  27. package/apps/storefront/src/pages/products/[handle].astro +13 -8
  28. package/apps/storefront/test/pipeline/harness.ts +6 -0
  29. package/apps/storefront/test/pipeline/pipeline-host.ts +29 -0
  30. package/package.json +1 -1
  31. package/packages/public-runtime/src/checkout-orders.ts +45 -7
  32. package/packages/public-runtime/src/checkout.ts +37 -9
  33. package/packages/public-runtime/src/tenant.ts +3 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/storefront-runner",
3
- "version": "2.4.91",
3
+ "version": "2.5.0",
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",
@@ -223,7 +223,7 @@ export const LABEL_STATES = [
223
223
  ] as const;
224
224
  export type LabelState = (typeof LABEL_STATES)[number];
225
225
 
226
- export const EVIDENCE_STATES = ["fresh", "UNKNOWN", "stale", "unreadable", "absent"] as const;
226
+ export const EVIDENCE_STATES = ["fresh", "not-required", "UNKNOWN", "stale", "unreadable", "absent"] as const;
227
227
  export type EvidenceState = (typeof EVIDENCE_STATES)[number];
228
228
 
229
229
  export const PROVIDER_OUTCOMES = ["intent", "accepted", "unknown", "voided", "refunded"] as const;
@@ -331,6 +331,48 @@ export interface FinalCartBinding {
331
331
  taxExpiresAt: string;
332
332
  }
333
333
 
334
+ /** Provider-authenticated cart facts; authority evidence is resolved by the receiving server. */
335
+ export interface CommerceProviderCartSnapshot {
336
+ provider: "foxycart";
337
+ providerCartId: string;
338
+ providerCartDigest: string;
339
+ /** Current PRIVATE provider-cart reference, verified by the provider integration. */
340
+ expectedAuthorizationId?: string;
341
+ tenantId: string;
342
+ stage: CommerceStage;
343
+ currency: string;
344
+ lines: readonly FinalCartLine[];
345
+ totals: MinorTotals;
346
+ destinationDigest: string;
347
+ destination: { country: string; region: string };
348
+ }
349
+
350
+ /** Safe source references explaining each server-owned authority decision. */
351
+ export interface CommerceAuthoritySource {
352
+ requirement: "required" | "not-required";
353
+ state: "satisfied" | "not-required";
354
+ source: string;
355
+ policyRevision: string;
356
+ expiresAt: string;
357
+ }
358
+ export type CommerceEvidenceProvenance = Record<keyof CommerceEvidence, CommerceAuthoritySource>;
359
+
360
+ export interface SyntheticCommerceAuthorityPolicy {
361
+ purpose: "synthetic-nonbillable";
362
+ revision: string;
363
+ expiresAt: string;
364
+ requirements: Record<"verification" | "tax" | "risk", "required" | "not-required">;
365
+ }
366
+
367
+ export interface CommerceAdmissionSnapshot extends CommerceProviderCartSnapshot {
368
+ authorizationId: string;
369
+ cartDigest: string;
370
+ evidence: CommerceEvidence;
371
+ provenance: CommerceEvidenceProvenance;
372
+ expiresAt: string;
373
+ now: string;
374
+ }
375
+
334
376
  export interface AsymmetricCallerToken {
335
377
  scheme: "asymmetric-jwks";
336
378
  signatureValid: boolean;
@@ -358,12 +400,8 @@ export type CallerTokenRefusal =
358
400
  | "stage";
359
401
 
360
402
  export function evidencePermitsRelease(evidence: CommerceEvidence): boolean {
361
- return (
362
- evidence.verification === "fresh" &&
363
- evidence.tax === "fresh" &&
364
- evidence.risk === "fresh" &&
365
- evidence.stock === "fresh"
366
- );
403
+ return evidence.stock === "fresh" && (["verification", "tax", "risk"] as const)
404
+ .every(field => evidence[field] === "fresh" || evidence[field] === "not-required");
367
405
  }
368
406
 
369
407
  export function paymentIsCaptured(payment: PaymentState): boolean {
@@ -25,13 +25,32 @@ import type {
25
25
  SubscriptionPurchaseOption,
26
26
  } from "./product.js";
27
27
  import { DEFAULT_ONE_TIME_PURCHASE_OPTION } from "./product.js";
28
+ import type { TenantConfig } from "./tenant.js";
29
+
30
+ /** Stable commerce scope shared by signed carts, provider callers and operator authorization. */
31
+ export function commerceTenantId(tenant: Pick<TenantConfig, "appDomain">): string {
32
+ return tenant.appDomain;
33
+ }
34
+
35
+ /** Public API destination on the declared nonproduction wildcard Worker route. */
36
+ export const SAMPLE_CART_HANDOFF_URL =
37
+ "https://synthetic-vendor-b.tokenoftrust.store/api/commerce/checkout-handoff";
38
+
39
+ /** Identifier-only local sample form. Product facts are resolved by the hosted catalog. */
40
+ export function sampleVariantCartPayload(handle: string, variantId: string): VariantCartPayload {
41
+ return {
42
+ hidden: [
43
+ { name: "sample_product", value: handle },
44
+ { name: "sample_variant", value: variantId },
45
+ ],
46
+ quantityName: "quantity",
47
+ };
48
+ }
28
49
 
29
50
  /**
30
51
  * Per-tenant managed-checkout wiring. Public config only — the signing SECRET is never
31
- * stored here (it's an env-injected Worker binding, resolved per request). In
32
- * `unsigned-demo` mode there is no secret at all: links go to a demo store that
33
- * has cart validation turned OFF, so a developer can run the full cart locally
34
- * (and on our shared preview worker) with zero secrets.
52
+ * stored here (it's an env-injected Worker binding, resolved per request).
53
+ * `unsigned-demo` sends plain fields and reads no secret.
35
54
  */
36
55
  export interface ManagedCheckoutConfig {
37
56
  /** Checkout store subdomain, e.g. "tot-demo". */
@@ -42,8 +61,8 @@ export interface ManagedCheckoutConfig {
42
61
  */
43
62
  storeDomain?: string;
44
63
  /**
45
- * "signed" (preview/live: cart validation ON, HMAC every field) or
46
- * "unsigned-demo" (local/preview demo: validation OFF, no secret needed).
64
+ * "signed" (cart validation ON, HMAC every field; requires FOXY_CART_SECRET) or
65
+ * "unsigned-demo" (plain fields, reads no secret).
47
66
  */
48
67
  mode: "signed" | "unsigned-demo";
49
68
  /**
@@ -59,9 +78,15 @@ export interface ManagedCheckoutConfig {
59
78
  cartDrawerCss?: string;
60
79
  }
61
80
 
81
+ /** Runtime tenant records must name a supported mode before they can build or admit a cart. */
82
+ export function isManagedCheckoutMode(value: unknown): value is ManagedCheckoutConfig["mode"] {
83
+ return value === "signed" || value === "unsigned-demo";
84
+ }
85
+
62
86
  /**
63
- * The ONE shared ToT-owned demo checkout store (test mode, cart validation
64
- * OFF, no secret). The local dev loop (`synthesizeDevTenant`) wires every
87
+ * The ONE shared ToT-owned demo checkout store (`tot-preview`). Carts are
88
+ * signed: HMAC cart validation is required, and FOXY_CART_SECRET must be set
89
+ * before they are served. The local dev loop (`synthesizeDevTenant`) wires every
65
90
  * local commerce checkout to this exact config; the hosted QA/test tier's
66
91
  * demo-checkout fallback (`resolveTenantCommerce`) reuses this same constant
67
92
  * rather than a second copy of the literal, so "the same as `tot dev`" stays
@@ -69,7 +94,7 @@ export interface ManagedCheckoutConfig {
69
94
  */
70
95
  export const SHARED_DEMO_CHECKOUT: ManagedCheckoutConfig = {
71
96
  storeSubDomain: "tot-preview",
72
- mode: "unsigned-demo",
97
+ mode: "signed",
73
98
  };
74
99
 
75
100
  /** Resolved, request-scoped commerce facts stashed on `Astro.locals`. */
@@ -244,6 +269,7 @@ export function quantityAllowed(value: string): boolean {
244
269
  export function resolveManagedCheckout(
245
270
  config: ManagedCheckoutConfig,
246
271
  ): ResolvedManagedCheckout {
272
+ if (!isManagedCheckoutMode(config.mode)) throw new CartSigningUnavailableError();
247
273
  const storeDomain =
248
274
  config.storeDomain ??
249
275
  `${config.storeSubDomain}${DEFAULT_CHECKOUT_HOST_SUFFIX}`;
@@ -463,6 +489,7 @@ export async function buildVariantCartPayload(
463
489
  variant: CatalogVariant,
464
490
  opts: BuildVariantCartPayloadOptions,
465
491
  ): Promise<VariantCartPayload> {
492
+ if (!isManagedCheckoutMode(opts.mode)) throw new CartSigningUnavailableError();
466
493
  // Hide-at-launch guard: refuse to SIGN a subscription option while
467
494
  // Autopilot is disabled. `visiblePurchaseOptions` removes subscription options
468
495
  // for such tenants, so one reaching the signer means the visibility filter was
@@ -586,6 +613,7 @@ export async function enforceCartSubmission(input: {
586
613
  secret?: string;
587
614
  trustedTenantId: string;
588
615
  }): Promise<CartEnforcement> {
616
+ if (!isManagedCheckoutMode(input.mode)) return denial("signing-unavailable");
589
617
  if (input.mode === "signed" && !input.secret) return denial("signing-unavailable");
590
618
  if (!input.trustedTenantId) return denial("tenant");
591
619
 
@@ -12,6 +12,7 @@ import type { IntegrationDeclarationInput } from "./integration/resolve.js";
12
12
  import type { EuUkVisitorPolicy } from "./integration/plan.js";
13
13
  import type { PublicationSpaceManifestV1 } from "./publication-space.js";
14
14
  import type { HtmlCachePolicy } from "./html-cache-policy.js";
15
+ import type { SyntheticCommerceAuthorityPolicy } from "./checkout-orders.js";
15
16
 
16
17
  /**
17
18
  * The shared semantic COLOR contract — the widget library's public API.
@@ -609,6 +610,8 @@ export interface TenantConfig {
609
610
  */
610
611
  commerce?: {
611
612
  checkout?: ManagedCheckoutConfig;
613
+ /** Server-owned applicability for nonbillable synthetic fixtures; never waives regulated rules. */
614
+ authorityPolicy?: SyntheticCommerceAuthorityPolicy;
612
615
  /**
613
616
  * Autopilot (subscription) program visibility. Absent → subscription
614
617
  * purchase options render wherever a product authors them (framework/dev