@tokenoftrust/storefront-runner 2.4.91 → 2.5.1

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 (34) 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 +121 -0
  9. package/apps/storefront/src/lib/fulfillment/admin/commerceDeskRouting.ts +45 -0
  10. package/apps/storefront/src/lib/fulfillment/admin/viewmodels/fulfillment.ts +1 -1
  11. package/apps/storefront/src/lib/fulfillment/commerceProjection.ts +146 -0
  12. package/apps/storefront/src/lib/fulfillment/commerceReadback.ts +60 -0
  13. package/apps/storefront/src/lib/fulfillment/commerceRecovery.ts +65 -0
  14. package/apps/storefront/src/lib/fulfillment/commerceRuntime.ts +13 -0
  15. package/apps/storefront/src/lib/fulfillment/fulfillmentService.ts +50 -4
  16. package/apps/storefront/src/lib/fulfillment/readiness.ts +1 -0
  17. package/apps/storefront/src/lib/fulfillment/store.ts +15 -0
  18. package/apps/storefront/src/lib/integrations/webhooks/deliveryEngine.ts +6 -3
  19. package/apps/storefront/src/lib/integrations/webhooks/deliveryStore.ts +6 -5
  20. package/apps/storefront/src/lib/inventory/allocations.ts +58 -9
  21. package/apps/storefront/src/lib/inventory/mirrorStock.ts +121 -0
  22. package/apps/storefront/src/lib/orders/admin/services/orders.ts +8 -8
  23. package/apps/storefront/src/lib/orders/data/orderMapper.ts +22 -1
  24. package/apps/storefront/src/lib/orders/data/ordersStore.ts +12 -4
  25. package/apps/storefront/src/pages/api/admin/commerce-readiness.ts +56 -26
  26. package/apps/storefront/src/pages/api/commerce/checkout-handoff.ts +34 -2
  27. package/apps/storefront/src/pages/api/commerce/final-cart-authorization.ts +158 -306
  28. package/apps/storefront/src/pages/products/[handle].astro +13 -8
  29. package/apps/storefront/test/pipeline/harness.ts +6 -0
  30. package/apps/storefront/test/pipeline/pipeline-host.ts +29 -0
  31. package/package.json +1 -1
  32. package/packages/public-runtime/src/checkout-orders.ts +45 -7
  33. package/packages/public-runtime/src/checkout.ts +37 -9
  34. package/packages/public-runtime/src/tenant.ts +3 -0
@@ -1,333 +1,185 @@
1
- /**
2
- * POST /api/commerce/final-cart-authorization — time-limited final-cart admission.
3
- * Caller-owned asymmetric tokens only. Catalog availability, a browser confirmation,
4
- * and missing verification evidence do not authorize payment.
5
- */
1
+ /** Provider-authenticated final-cart admission. Evidence and applicability are server-owned. */
6
2
  import type { APIContext } from "astro";
7
- import { createLocalJWKSet, decodeJwt, decodeProtectedHeader, jwtVerify, type JSONWebKeySet, type JWTPayload } from "jose";
8
- import type { TenantConfig } from "@tot/public-runtime";
3
+ import { createLocalJWKSet, decodeProtectedHeader, jwtVerify, type JSONWebKeySet } from "jose";
9
4
  import {
10
- type CallerToken,
11
- type CommerceEvidence,
12
- type CommerceStage,
13
- type EvidenceState,
14
- type FinalCartBinding,
15
- type FulfillmentState,
16
- type LabelState,
17
- type PaymentState,
18
- type StockState,
5
+ commerceTenantId, totalsMatch, canonicalJson, deriveProductProfile,
6
+ type CallerToken, type CatalogProduct, type CommerceEvidence, type CommerceEvidenceProvenance,
7
+ type CommerceProviderCartSnapshot, type FinalCartBinding, type TenantConfig,
19
8
  } from "@tot/public-runtime";
20
- import { fromD1, listAllActiveProducts } from "@/lib/catalog/d1/catalog.js";
21
- import { readD1, readEnv } from "@/lib/publication/shared/env";
9
+ import { fromD1 } from "@/lib/catalog/d1/catalog.js";
10
+ import { readD1, readEnv, readKv } from "@/lib/publication/shared/env";
22
11
  import { evaluateFinalCart, type SeamView } from "@/lib/checkout/commerce/finalCartAuthorization.js";
23
- import { d1CommerceRunner, mirrorOnHand, reserveAllocation } from "@/lib/inventory/allocations.js";
12
+ import { d1CommerceRunner, reserveAllocation } from "@/lib/inventory/allocations.js";
13
+ import { readMirrorStock } from "@/lib/inventory/mirrorStock.js";
14
+ import { admissionMatches, readCommerceAdmission, admissionSnapshotStatement } from "@/lib/checkout/commerce/commerceAdmission.js";
15
+ import { isRegulatedTenant } from "@/lib/compliance/enforcement.js";
24
16
  import { POST as compliancePreflight } from "../compliance/preflight.js";
25
-
17
+ import { resolveAdminTargetTenant } from "@/config/adminTargetTenant";
18
+ import { withDeclaredTenantConfig } from "@/config/declaredTenantConfig";
19
+ import { createContentProvider } from "@/lib/content/storyblok/provider";
20
+ import { createToTClient } from "@/lib/catalog/tot/ToTClient";
26
21
  export const prerender = false;
27
-
28
- const EVIDENCE_STATES = new Set(["fresh", "UNKNOWN", "stale", "unreadable", "absent"]);
29
- const STAGES = new Set(["preview", "publication"]);
30
-
22
+ const HEX = /^[a-f0-9]{64}$/;
31
23
  function json(body: unknown, status = 200): Response {
32
- return new Response(JSON.stringify(body), {
33
- status,
34
- headers: { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" },
35
- });
36
- }
37
-
38
- function deny(finding: string, status: number, owner?: string): Response {
39
- return json(owner ? { ok: false, finding, owner } : { ok: false, finding }, status);
40
- }
41
-
42
- function statusFor(finding: string): number {
43
- if (finding === "store-unconfigured" || finding === "caller-unconfigured") return 503;
44
- if (
45
- finding === "browser-not-authority" ||
46
- finding === "tenant-unbound" ||
47
- finding === "shared-secret" ||
48
- finding === "signature" ||
49
- finding === "kid" ||
50
- finding === "issuer" ||
51
- finding === "audience" ||
52
- finding === "expired" ||
53
- finding === "lifetime" ||
54
- finding === "jti" ||
55
- finding === "tenant" ||
56
- finding === "stage"
57
- ) {
58
- return 401;
59
- }
60
- return 409;
24
+ return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" } });
61
25
  }
62
-
63
- function claimString(payload: JWTPayload, key: string): string {
64
- const value = payload[key];
65
- return typeof value === "string" ? value : "";
66
- }
67
-
68
- function parseList(value: string | undefined): string[] | null {
69
- if (!value || value.trim() === "") return null;
70
- const trimmed = value.trim();
71
- if (trimmed.startsWith("[")) {
72
- try {
73
- const parsed = JSON.parse(trimmed) as unknown;
74
- if (!Array.isArray(parsed) || parsed.some((item) => typeof item !== "string" || item.trim() === "")) return null;
75
- return parsed as string[];
76
- } catch {
77
- return null;
78
- }
79
- }
80
- const parts = trimmed.split(",").map((part) => part.trim()).filter(Boolean);
81
- return parts.length > 0 ? parts : null;
26
+ function deny(finding: string, status = 409, owner?: string): Response { return json({ ok: false, finding, ...(owner ? { owner } : {}) }, status); }
27
+ function list(value: string | undefined): string[] | null {
28
+ if (!value) return null;
29
+ try {
30
+ const values: unknown = value.trim().startsWith("[") ? JSON.parse(value) : value.split(",").map(v => v.trim());
31
+ return Array.isArray(values) && values.length > 0 && values.every(v => typeof v === "string" && v.length > 0) ? values : null;
32
+ } catch { return null; }
82
33
  }
83
-
84
34
  function publicJwks(value: string | undefined): JSONWebKeySet | null {
85
- if (!value) return null;
86
35
  try {
87
- const parsed = JSON.parse(value) as unknown;
88
- if (!parsed || typeof parsed !== "object" || !Array.isArray((parsed as { keys?: unknown }).keys)) return null;
89
- const keys = (parsed as { keys: unknown[] }).keys;
90
- if (keys.length < 1) return null;
91
- for (const key of keys) {
92
- if (!key || typeof key !== "object") return null;
93
- const record = key as Record<string, unknown>;
94
- if (typeof record.d === "string" || record.kty === "oct" || typeof record.k === "string") return null;
95
- }
36
+ const parsed: unknown = JSON.parse(value ?? "");
37
+ if (!parsed || typeof parsed !== "object" || !("keys" in parsed) || !Array.isArray(parsed.keys) || parsed.keys.length < 1) return null;
38
+ if (parsed.keys.some(key => !key || typeof key !== "object" || "d" in key || "k" in key || key.kty !== "OKP" || key.crv !== "Ed25519")) return null;
96
39
  return parsed as JSONWebKeySet;
97
- } catch {
98
- return null;
99
- }
40
+ } catch { return null; }
100
41
  }
101
-
102
- function readCart(value: unknown): FinalCartBinding | null {
103
- if (!value || typeof value !== "object") return null;
104
- const record = value as Record<string, unknown>;
105
- if (typeof record.tenantId !== "string" || typeof record.currency !== "string" || typeof record.destinationDigest !== "string") {
106
- return null;
107
- }
108
- if (typeof record.stage !== "string" || !STAGES.has(record.stage)) return null;
109
- if (typeof record.verificationExpiresAt !== "string" || typeof record.taxExpiresAt !== "string") return null;
110
- if (!Array.isArray(record.lines) || record.lines.length > 50) return null;
111
- const lines = [];
112
- for (const line of record.lines) {
113
- if (!line || typeof line !== "object") return null;
114
- const row = line as Record<string, unknown>;
115
- if (typeof row.sku !== "string" || typeof row.quantity !== "number" || typeof row.unitMinor !== "number") return null;
116
- lines.push({ sku: row.sku, quantity: row.quantity, unitMinor: row.unitMinor });
117
- }
118
- return {
119
- tenantId: record.tenantId,
120
- stage: record.stage as CommerceStage,
121
- currency: record.currency,
122
- lines,
123
- destinationDigest: record.destinationDigest,
124
- verificationExpiresAt: record.verificationExpiresAt,
125
- taxExpiresAt: record.taxExpiresAt,
126
- };
127
- }
128
-
129
- function readEvidence(value: unknown): CommerceEvidence | null {
130
- if (!value || typeof value !== "object") return null;
131
- const record = value as Record<string, unknown>;
132
- const evidence = {} as CommerceEvidence;
133
- for (const field of ["verification", "tax", "risk", "stock"] as const) {
134
- const state = record[field];
135
- if (typeof state !== "string" || !EVIDENCE_STATES.has(state)) return null;
136
- evidence[field] = state as EvidenceState;
137
- }
138
- return evidence;
42
+ async function digest(value: string): Promise<string> {
43
+ return [...new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value)))].map(b => b.toString(16).padStart(2, "0")).join("");
139
44
  }
140
-
141
- function readState<T extends string>(value: unknown, allowed: readonly T[]): T | undefined {
142
- return typeof value === "string" && (allowed as readonly string[]).includes(value) ? (value as T) : undefined;
45
+ function readSnapshot(value: unknown): CommerceProviderCartSnapshot | null {
46
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
47
+ const v = value as Record<string, unknown>;
48
+ const allowed = ["provider", "providerCartId", "providerCartDigest", "tenantId", "stage", "currency", "lines", "totals", "destinationDigest", "destination"];
49
+ if (Object.keys(v).some(k => !allowed.includes(k) && k !== "expectedAuthorizationId") || allowed.some(k => !(k in v))) return null;
50
+ if ("expectedAuthorizationId" in v && (typeof v.expectedAuthorizationId !== "string" || !/^[a-z0-9][a-z0-9-]{0,80}$/.test(v.expectedAuthorizationId))) return null;
51
+ if (v.provider !== "foxycart" || typeof v.providerCartId !== "string" || !/^[a-zA-Z0-9._:-]{1,128}$/.test(v.providerCartId) ||
52
+ typeof v.providerCartDigest !== "string" || !HEX.test(v.providerCartDigest) || typeof v.destinationDigest !== "string" || !HEX.test(v.destinationDigest) ||
53
+ typeof v.tenantId !== "string" || !["preview", "publication"].includes(String(v.stage)) || typeof v.currency !== "string" || !/^[A-Z]{3}$/.test(v.currency) ||
54
+ !Array.isArray(v.lines) || v.lines.length < 1 || v.lines.length > 50) return null;
55
+ if (v.lines.some(line => !line || typeof line !== "object" || typeof line.sku !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,80}$/.test(line.sku) ||
56
+ !Number.isSafeInteger(line.quantity) || line.quantity < 1 || line.quantity > 99 || !Number.isSafeInteger(line.unitMinor) || line.unitMinor < 0 ||
57
+ Object.keys(line).some(k => !["sku", "quantity", "unitMinor"].includes(k)))) return null;
58
+ if (!v.totals || typeof v.totals !== "object" || Object.keys(v.totals).length !== 4 ||
59
+ !["subtotalMinor", "shippingMinor", "taxMinor", "totalMinor"].every(k => Number.isSafeInteger((v.totals as Record<string, unknown>)[k]) && Number((v.totals as Record<string, unknown>)[k]) >= 0)) return null;
60
+ const d = v.destination as Record<string, unknown> | undefined;
61
+ if (!d || Object.keys(d).length !== 2 || typeof d.country !== "string" || !/^[A-Z]{2}$/.test(d.country) || typeof d.region !== "string" || !/^[A-Z0-9-]{1,8}$/.test(d.region)) return null;
62
+ const snapshot = v as unknown as CommerceProviderCartSnapshot;
63
+ if (new Set(snapshot.lines.map(line => line.sku)).size !== snapshot.lines.length) return null;
64
+ if (!totalsMatch(snapshot.totals) || snapshot.lines.reduce((sum, line) => sum + line.quantity * line.unitMinor, 0) !== snapshot.totals.subtotalMinor) return null;
65
+ return snapshot;
143
66
  }
144
-
145
- function destinationOf(value: unknown): { region: string; country: string } | null {
146
- if (!value || typeof value !== "object") return null;
147
- const record = value as Record<string, unknown>;
148
- const region = typeof record.region === "string" ? record.region.trim() : "";
149
- const country = typeof record.country === "string" ? record.country.trim() : "";
150
- if (!/^[A-Za-z]{2}$/.test(region)) return null;
151
- if (country !== "" && !/^[A-Za-z]{2}$/.test(country)) return null;
152
- return { region: region.toUpperCase(), country: country === "" ? "US" : country.toUpperCase() };
153
- }
154
-
155
- async function catalogAvailableFor(skus: readonly string[]): Promise<boolean> {
156
- if (skus.length === 0) return false;
157
- const database = await readD1("CATALOG_DB");
158
- if (!database) return false;
159
- const wanted = new Set(skus);
160
- try {
161
- const products = await listAllActiveProducts(fromD1(database));
162
- for (const product of products) {
163
- for (const variant of product.variants) {
164
- if (variant.sku === undefined) continue;
165
- if (wanted.has(variant.sku) && variant.inventory.available === true) return true;
166
- }
167
- }
168
- } catch {
169
- return false;
170
- }
171
- return false;
172
- }
173
-
174
- async function verificationSeam(
175
- context: APIContext,
176
- cart: FinalCartBinding,
177
- destination: { region: string; country: string } | null,
178
- ): Promise<SeamView> {
179
- const tot = (context.locals as { tot?: { getProductByHandle: (handle: string) => Promise<unknown> } }).tot ?? {
180
- getProductByHandle: async () => null,
181
- };
67
+ async function seam(context: APIContext, snapshot: CommerceProviderCartSnapshot, products: readonly CatalogProduct[]): Promise<SeamView> {
182
68
  const response = await compliancePreflight({
183
- request: new Request("https://synthetic-tenant.invalid/api/compliance/preflight", {
184
- method: "POST",
185
- headers: { "content-type": "application/json" },
186
- body: JSON.stringify({
187
- lines: cart.lines.map((line) => ({ handle: line.sku, sku: line.sku, quantity: line.quantity })),
188
- ...(destination ? { destination: { region: destination.region, country: destination.country } } : {}),
189
- }),
190
- }),
191
- locals: { tenant: context.locals.tenant, tot },
69
+ request: new Request("https://checkout.invalid/api/compliance/preflight", {
70
+ method: "POST", headers: { "content-type": "application/json" },
71
+ body: JSON.stringify({ lines: snapshot.lines.map(line => ({ handle: products.find(p => p.variants.some(v => v.sku === line.sku))!.handle, sku: line.sku, quantity: line.quantity })), destination: snapshot.destination }),
72
+ }), locals: { tenant: context.locals.tenant, tot: context.locals.tot },
192
73
  } as APIContext);
193
- let body: unknown;
194
74
  try {
195
- body = await response.json();
196
- } catch {
197
- return { status: "absent" };
198
- }
199
- const decision = body && typeof body === "object" ? (body as { decision?: { status?: unknown; failedClosed?: unknown; blocks?: unknown } }).decision : undefined;
200
- if (!decision || decision.status === "clear") return { status: decision ? "clear" : "absent" };
201
- const blocks = Array.isArray(decision.blocks) ? decision.blocks : [];
202
- const codes = blocks
203
- .map((block) => (block && typeof block === "object" ? (block as { code?: unknown }).code : undefined))
204
- .filter((code): code is string => typeof code === "string");
205
- return { status: "blocked", failedClosed: decision.failedClosed === true, codes };
75
+ const body = await response.json() as { decision?: { status?: string; failedClosed?: boolean; blocks?: { code?: string }[] } };
76
+ if (!body.decision) return { status: "absent" };
77
+ if (body.decision.status === "clear") return { status: "clear" };
78
+ return { status: "blocked", failedClosed: body.decision.failedClosed === true, codes: body.decision.blocks?.map(b => b.code ?? "") };
79
+ } catch { return { status: "absent" }; }
206
80
  }
207
-
208
81
  export async function POST(context: APIContext): Promise<Response> {
209
- const tenant = context.locals.tenant as TenantConfig | undefined;
210
- const appDomain = tenant?.appDomain ?? "";
211
- if (!tenant || appDomain === "") return deny("tenant-unbound", 401);
212
-
213
- let body: Record<string, unknown>;
214
- try {
215
- const parsed = await context.request.json();
216
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return deny("malformed-request", 409);
217
- body = parsed as Record<string, unknown>;
218
- } catch {
219
- return deny("malformed-request", 409);
220
- }
221
-
222
- if (body.scheme === "shared-secret") return deny("shared-secret", statusFor("shared-secret"));
223
-
224
- const quoted = readCart(body.quoted);
225
- const presented = readCart(body.presented);
226
- if (!quoted || !presented) return deny("malformed-request", 409);
227
-
228
- const jwks = publicJwks(await readEnv("SCRAPPY_CALLER_JWKS"));
229
- const issuers = parseList(await readEnv("SCRAPPY_CALLER_ISSUERS"));
230
- const audience = await readEnv("SCRAPPY_CALLER_AUDIENCE");
231
- const activeKids = parseList(await readEnv("SCRAPPY_CALLER_ACTIVE_KIDS"));
82
+ if (Number(context.request.headers.get("content-length") ?? 0) > 65536) return deny("malformed-request");
83
+ const raw = await context.request.text();
84
+ if (raw.length > 65536) return deny("malformed-request");
85
+ let snapshot: CommerceProviderCartSnapshot | null;
86
+ try { snapshot = readSnapshot(JSON.parse(raw)); } catch { snapshot = null; }
87
+ if (!snapshot) return deny("malformed-request");
88
+ const jwks = publicJwks(await readEnv("COMMERCE_CALLER_JWKS"));
89
+ const issuers = list(await readEnv("COMMERCE_CALLER_ISSUERS"));
90
+ const audience = await readEnv("COMMERCE_CALLER_AUDIENCE");
91
+ const activeKids = list(await readEnv("COMMERCE_CALLER_ACTIVE_KIDS"));
232
92
  if (!jwks || !issuers || !audience || !activeKids) return deny("caller-unconfigured", 503);
233
-
234
- const header = context.request.headers.get("authorization") ?? "";
235
- const bearer = header.startsWith("Bearer ") ? header.slice("Bearer ".length).trim() : "";
236
- let token: CallerToken | null = null;
237
- if (bearer !== "") {
238
- try {
239
- const decoded = decodeJwt(bearer);
240
- const protectedHeader = decodeProtectedHeader(bearer);
241
- let signatureValid = false;
242
- try {
243
- await jwtVerify(bearer, createLocalJWKSet(jwks), { algorithms: ["EdDSA"] });
244
- signatureValid = true;
245
- } catch (error) {
246
- const code = error && typeof error === "object" && "code" in error ? String((error as { code: unknown }).code) : "";
247
- signatureValid = code === "ERR_JWT_EXPIRED" || code === "ERR_JWT_CLAIM_VALIDATION_FAILED";
248
- }
249
- const exp = typeof decoded.exp === "number" ? new Date(decoded.exp * 1000).toISOString() : "";
250
- const audienceClaim = typeof decoded.aud === "string" ? decoded.aud : Array.isArray(decoded.aud) ? decoded.aud[0] ?? "" : "";
251
- const stageClaim = claimString(decoded, "stage");
252
- if (stageClaim !== "preview" && stageClaim !== "publication") return deny("stage", statusFor("stage"));
253
- token = {
254
- scheme: "asymmetric-jwks",
255
- signatureValid,
256
- kid: typeof protectedHeader.kid === "string" ? protectedHeader.kid : "",
257
- issuer: typeof decoded.iss === "string" ? decoded.iss : "",
258
- audience: audienceClaim,
259
- expiresAt: exp,
260
- jti: claimString(decoded, "jti"),
261
- tenantId: claimString(decoded, "tenantId"),
262
- stage: stageClaim,
263
- };
264
- } catch {
265
- token = null;
266
- }
93
+ const bearer = context.request.headers.get("authorization")?.replace(/^Bearer /, "") ?? "";
94
+ let token: CallerToken;
95
+ try {
96
+ const header = decodeProtectedHeader(bearer);
97
+ if (!header.kid || !activeKids.includes(header.kid)) return deny("kid", 401);
98
+ const { payload } = await jwtVerify(bearer, createLocalJWKSet(jwks), { algorithms: ["EdDSA"], issuer: issuers, audience });
99
+ if (payload.requestDigest !== await digest(raw)) return deny("request-digest", 401);
100
+ if (payload.tenantId !== snapshot.tenantId || payload.stage !== snapshot.stage) return deny("tenant", 401);
101
+ if (typeof payload.exp !== "number") return deny("expired", 401);
102
+ token = { scheme: "asymmetric-jwks", signatureValid: true, kid: header.kid, issuer: payload.iss ?? "", audience,
103
+ expiresAt: new Date(payload.exp * 1000).toISOString(), jti: payload.jti ?? "", tenantId: snapshot.tenantId, stage: snapshot.stage };
104
+ } catch { return deny("signature", 401); }
105
+ const allowedTenants = list(await readEnv("COMMERCE_TENANT_ALLOWLIST"));
106
+ if (!allowedTenants || !allowedTenants.includes(snapshot.tenantId)) return deny("tenant", 401);
107
+ const registered = await resolveAdminTargetTenant(snapshot.tenantId);
108
+ if (!registered || commerceTenantId(registered) !== snapshot.tenantId) return deny("tenant-unregistered", 401);
109
+ const kv = await readKv("TENANT_CACHE");
110
+ const kvGet = async (key: string): Promise<string | null> => kv ? kv.get(key) : null;
111
+ const contentEnv = snapshot.stage === "publication" ? "live" : "test";
112
+ const tenant = await withDeclaredTenantConfig(registered, kvGet, { env: contentEnv });
113
+ const content = createContentProvider(tenant, { get: kvGet, env: contentEnv });
114
+ const tot = await createToTClient(tenant, await readD1("CATALOG_DB"), content);
115
+ const authorityContext = { ...context, locals: { ...context.locals, tenant, tot, content } } as APIContext;
116
+ const { tenantId, stage, currency, totals, destinationDigest } = snapshot;
117
+ const lines = [...snapshot.lines].sort((a, b) => a.sku.localeCompare(b.sku) || a.unitMinor - b.unitMinor || a.quantity - b.quantity);
118
+ if (snapshot.providerCartDigest !== await digest(canonicalJson({ tenantId, stage, currency, lines, totals, destinationDigest }))) return deny("provider-cart-digest");
119
+ let products: CatalogProduct[];
120
+ try { products = await tot.getAllActiveProducts(); } catch { return deny("catalog-unavailable", 503); }
121
+ for (const line of snapshot.lines) {
122
+ const matches = products.flatMap(product => product.status === "active" && (!product.visibility || product.visibility === "public") && product.tenant_id === tenant.tot_data_scope
123
+ ? product.variants.filter(v => v.sku === line.sku).map(variant => ({ product, variant })) : []);
124
+ const digits = new Intl.NumberFormat("en", { style: "currency", currency: snapshot.currency }).resolvedOptions().maximumFractionDigits ?? 2;
125
+ const scaled = (matches[0]?.variant.price ?? -1) * 10 ** digits;
126
+ if (matches.length !== 1 || !matches[0]!.variant.inventory.available || matches[0]!.variant.currency !== snapshot.currency || Math.abs(scaled - Math.round(scaled)) > 0.000001 || Math.round(scaled) !== line.unitMinor) return deny("catalog-cart-mismatch");
267
127
  }
268
-
269
- const provider = tenant.inventory?.provider ?? "mirror";
270
- const authorities: unknown[] = [];
128
+ const database = await readD1("STOREFRONT_APPS_DB");
129
+ if (!database) return deny("store-unconfigured", 503);
130
+ if (tenant.inventory?.provider && tenant.inventory.provider !== "mirror") return deny("authority-unavailable");
271
131
  const now = new Date().toISOString();
272
- if (provider !== "mirror") {
273
- authorities.push({ provider, sku: presented.lines[0]?.sku });
274
- } else {
275
- const skus = [...new Set(presented.lines.map((line) => line.sku))];
276
- for (const sku of skus) {
277
- const onHand = mirrorOnHand(sku, "unspecified");
278
- authorities.push(
279
- onHand == null
280
- ? null
281
- : { provider: "mirror", readable: true, locationId: "unspecified", sku, onHand, asOf: now },
282
- );
132
+ const queryable = fromD1(database);
133
+ const prior = await queryable.first<{ authorization_id: string; state: string }>(
134
+ `SELECT s.authorization_id,a.state FROM commerce_admission_snapshots s JOIN scrappy_final_cart_authorizations a
135
+ ON a.tenant_id=s.tenant_id AND a.stage=s.stage AND a.authorization_id=s.authorization_id
136
+ JOIN commerce_provider_carts p ON p.tenant_id=s.tenant_id AND p.stage=s.stage AND p.authorization_id=s.authorization_id
137
+ WHERE p.tenant_id=? AND p.stage=? AND p.provider='foxycart' AND p.provider_cart_id=?`,
138
+ snapshot.tenantId, snapshot.stage, snapshot.providerCartId,
139
+ );
140
+ if (snapshot.expectedAuthorizationId && snapshot.expectedAuthorizationId !== prior?.authorization_id) return deny("provider-cart-conflict");
141
+ if (prior && prior.state !== "active") return deny("admission-consumed");
142
+ const existing = prior ? await readCommerceAdmission(queryable, snapshot.tenantId, snapshot.stage, prior.authorization_id) : null;
143
+ if (prior && (!existing || existing.expiresAt <= now)) return deny("admission-expired");
144
+ if (existing && !admissionMatches(existing, snapshot)) return deny("provider-cart-changed");
145
+ const policy = tenant.commerce?.authorityPolicy;
146
+ const testPolicy = policy?.purpose === "synthetic-nonbillable" && typeof policy.revision === "string" && /^[A-Za-z0-9._:-]{1,128}$/.test(policy.revision) &&
147
+ Number.isFinite(Date.parse(policy.expiresAt)) && policy.expiresAt > now && await readEnv("COMMERCE_DEMO_CHECKOUT_TIER") === "test" &&
148
+ tenant.commerce?.checkout?.storeSubDomain === "tot-preview" && tenant.commerce.checkout.mode === "signed" &&
149
+ ["verification", "tax", "risk"].every(field => ["required", "not-required"].includes(policy.requirements?.[field as "verification" | "tax" | "risk"]));
150
+ const regulated = isRegulatedTenant(tenant) || !!tenant.compliance?.pactAct || !!tenant.compliance?.exciseTax ||
151
+ snapshot.lines.some(line => products.some(p => p.variants.some(v => v.sku === line.sku && ["ends", "e-liquid", "other-nicotine"].includes(deriveProductProfile(p, v).category))));
152
+ if (testPolicy && regulated && (policy.requirements.verification === "not-required" || policy.requirements.tax === "not-required")) return deny("authority-policy-regulated");
153
+ const authorities = [];
154
+ for (const sku of new Set(snapshot.lines.map(line => line.sku))) {
155
+ authorities.push(await readMirrorStock(queryable, { tenantId: snapshot.tenantId, stage: snapshot.stage, sku, locationId: "unspecified", now, maxAgeMs: 15 * 60_000 }));
156
+ }
157
+ const stockExpiry = Math.min(...authorities.flatMap(authority => authority ? [Date.parse(authority.asOf) + 15 * 60_000] : []));
158
+ const evidence: CommerceEvidence = { verification: "absent", tax: "absent", risk: "absent", stock: "fresh" };
159
+ const expiresAt = existing?.expiresAt ?? new Date(Math.min(Date.now() + 5 * 60_000, stockExpiry, testPolicy ? Date.parse(policy.expiresAt) : Infinity)).toISOString();
160
+ const provenance = {} as CommerceEvidenceProvenance;
161
+ for (const field of ["verification", "tax", "risk"] as const) {
162
+ if (testPolicy && policy.requirements?.[field] === "not-required") {
163
+ evidence[field] = "not-required";
164
+ provenance[field] = { requirement: "not-required", state: "not-required", source: "declared-synthetic-policy", policyRevision: policy.revision, expiresAt };
283
165
  }
284
166
  }
285
- const catalogAvailable = await catalogAvailableFor(presented.lines.map((line) => line.sku));
286
-
287
- const decision = await evaluateFinalCart({
288
- now,
289
- routedTenantId: appDomain,
290
- quoted,
291
- presented,
292
- evidence: readEvidence(body.evidence),
293
- seam: await verificationSeam(context, presented, destinationOf(body.destination)),
294
- catalogAvailable,
295
- browserConfirmed: body.browserConfirmed === true,
296
- token,
297
- trustedIssuers: issuers,
298
- expectedAudience: audience,
299
- activeKids,
300
- authorities,
301
- payment: readState<PaymentState>(body.payment, ["UNKNOWN", "pending", "authorized", "captured", "capture_unknown", "voided", "refund_requested", "refunded", "refund_unknown"]),
302
- stock: readState<StockState>(body.stock, ["UNKNOWN", "unallocated", "reserved", "confirmed", "released", "expired"]),
303
- fulfillment: readState<FulfillmentState>(body.fulfillment, ["UNKNOWN", "held", "ready", "handed_off", "cancelled"]),
304
- label: readState<LabelState>(body.label, ["UNKNOWN", "none", "intent", "purchased", "printed", "void_requested", "voided"]),
305
- });
306
- if (!decision.ok) return deny(decision.finding, statusFor(decision.finding), decision.owner);
307
-
308
- const database = await readD1("STOREFRONT_APPS_DB");
309
- if (!database || typeof database.prepare !== "function") return deny("store-unconfigured", 503);
310
- const reserved = await reserveAllocation(d1CommerceRunner(database), {
311
- tenantId: decision.tenantId,
312
- stage: decision.stage,
313
- orderId: `a${crypto.randomUUID()}`,
314
- cartDigest: decision.digest,
315
- expiresAt: decision.expiresAt,
316
- now,
317
- currency: presented.currency,
318
- amountMinor: decision.amountMinor,
319
- issuer: decision.issuer,
320
- jti: decision.jti,
321
- holds: decision.holds,
322
- });
323
- if (!reserved.ok) return deny(reserved.finding, statusFor(reserved.finding));
324
- return json({
325
- ok: true,
326
- authorizationId: reserved.authorizationId,
327
- cartDigest: decision.digest,
328
- expiresAt: reserved.expiresAt,
329
- repeated: reserved.repeated,
330
- stage: decision.stage,
167
+ provenance.stock = { requirement: "required", state: "satisfied", source: "tenant-inventory-movements", policyRevision: "mirror-v1", expiresAt };
168
+ const presented: FinalCartBinding = { tenantId: snapshot.tenantId, stage: snapshot.stage, currency: snapshot.currency, lines: snapshot.lines,
169
+ destinationDigest: snapshot.destinationDigest, verificationExpiresAt: expiresAt, taxExpiresAt: expiresAt };
170
+ const decision = await evaluateFinalCart({ now, routedTenantId: commerceTenantId(tenant), quoted: presented, presented, evidence,
171
+ seam: await seam(authorityContext, snapshot, products), catalogAvailable: true, browserConfirmed: false, token,
172
+ trustedIssuers: issuers, expectedAudience: audience, activeKids, authorities });
173
+ if (!decision.ok) return deny(decision.finding, 409, decision.owner);
174
+ const authorizationId = existing?.authorizationId ?? `a${crypto.randomUUID()}`;
175
+ // Retries of the same verified provider cart share one reservation, including concurrent callbacks.
176
+ const cartDigest = existing?.cartDigest ?? await digest(`commerce-provider-cart-v1\n${snapshot.providerCartId}\n${snapshot.providerCartDigest}`);
177
+ const reserved = await reserveAllocation(d1CommerceRunner(database), { tenantId: decision.tenantId, stage: decision.stage, orderId: authorizationId,
178
+ cartDigest, expiresAt: decision.expiresAt, now, currency: snapshot.currency, amountMinor: snapshot.totals.totalMinor,
179
+ issuer: decision.issuer, jti: decision.jti, holds: decision.holds,
180
+ providerCart: { provider: snapshot.provider, id: snapshot.providerCartId, digest: snapshot.providerCartDigest, expectedAuthorizationId: snapshot.expectedAuthorizationId },
181
+ admissionStatement: admissionSnapshotStatement({ ...snapshot, authorizationId, cartDigest, evidence, provenance, expiresAt: decision.expiresAt, now }),
331
182
  });
183
+ if (!reserved.ok) return deny(reserved.finding);
184
+ return json({ ok: true, authorizationId: reserved.authorizationId, cartDigest, expiresAt: reserved.expiresAt, repeated: reserved.repeated, stage: snapshot.stage });
332
185
  }
333
-
@@ -22,6 +22,9 @@ import ReviewList from "@/components/catalog/storefront/commerce/ReviewList.astr
22
22
  import { isOnSale, isNew, isAvailable, fromPrice } from "@/lib/catalog/tot/query";
23
23
  import {
24
24
  buildMemberSignedCartPayload,
25
+ commerceTenantId,
26
+ SAMPLE_CART_HANDOFF_URL,
27
+ sampleVariantCartPayload,
25
28
  workerCartApiPath,
26
29
  normalizePurchaseOptions,
27
30
  visiblePurchaseOptions,
@@ -39,6 +42,7 @@ import type {
39
42
  PurchaseOptionPricingContext,
40
43
  } from "@tot/public-runtime";
41
44
  import { resolveCartSigning, shopperCheckoutUnavailableHtml } from "@/lib/checkout/shared/checkoutCommerce";
45
+ import { isLocalSampleCheckout } from "@/config/declaredTenantConfig";
42
46
  import { isRegulatedTenant } from "@/lib/compliance/enforcement";
43
47
  import { resolveMemberContext } from "@/lib/membership/eligibility";
44
48
  import { truncate } from "@/lib/content/shared/format";
@@ -233,11 +237,12 @@ let checkoutCart:
233
237
  }
234
238
  | undefined;
235
239
  let checkoutUnavailable = false;
236
- if (commerce) {
240
+ const sampleHandoff = isLocalSampleCheckout(tenant.tenant_id);
241
+ if (commerce || sampleHandoff) {
237
242
  // Fail closed for production signed checkout: a signed tenant with no
238
243
  // cart-validation secret builds NO cart (never a tamperable form). See
239
244
  // resolveCartSigning — it also logs the misconfiguration loudly.
240
- const signing = await resolveCartSigning(commerce);
245
+ const signing = sampleHandoff ? { enabled: true, secret: undefined } : await resolveCartSigning(commerce!);
241
246
  if (!signing.enabled) {
242
247
  checkoutUnavailable = true;
243
248
  } else {
@@ -253,13 +258,13 @@ if (commerce) {
253
258
  purchaseOptions.map(async (purchaseOption) => {
254
259
  return [
255
260
  purchaseOption.id,
256
- await buildMemberSignedCartPayload(product, v, {
257
- mode: commerce.mode,
261
+ sampleHandoff ? sampleVariantCartPayload(product.handle, v.id) : await buildMemberSignedCartPayload(product, v, {
262
+ mode: commerce!.mode,
258
263
  secret: signing.secret,
259
264
  purchaseOption,
260
265
  pricing: pricingCtx,
261
266
  autopilotEnabled,
262
- tenantId: tenant.tenant_id,
267
+ tenantId: commerceTenantId(tenant),
263
268
  }),
264
269
  ] as const;
265
270
  }),
@@ -269,7 +274,7 @@ if (commerce) {
269
274
  ),
270
275
  );
271
276
  checkoutCart = {
272
- action: cartApiPath,
277
+ action: sampleHandoff ? SAMPLE_CART_HANDOFF_URL : cartApiPath,
273
278
  purchaseOptions,
274
279
  byVariantId: Object.fromEntries(entries),
275
280
  };
@@ -415,13 +420,13 @@ const structuredData = {
415
420
  productTitle={product.title}
416
421
  checkoutCart={checkoutCart}
417
422
  pricing={pricing}
418
- handoffUrl={cartApiPath}
423
+ handoffUrl={sampleHandoff ? undefined : cartApiPath}
419
424
  />
420
425
  {/* Regulated-tenant checkout ENFORCEMENT gate. Intercepts the
421
426
  managed-cart handoff to clear age + ship-to compliance server-side
422
427
  before the external checkout. Only mounts when a store is wired AND
423
428
  the tenant is regulated. */}
424
- {checkoutCart && isRegulatedTenant(tenant) && compliance?.minAge && (
429
+ {checkoutCart && !sampleHandoff && isRegulatedTenant(tenant) && compliance?.minAge && (
425
430
  <CheckoutComplianceGate
426
431
  client:load
427
432
  preflightUrl={withBase(apiBasePath, "/api/compliance/preflight")}
@@ -22,6 +22,12 @@ interface HarnessEnv {
22
22
 
23
23
  type TenantPipelineStub = {
24
24
  [K in
25
+ | "holdCommerce"
26
+ | "resumeCommerce"
27
+ | "commerceRaceReceive"
28
+ | "commerceRaceWakeups"
29
+ | "scheduleCommerce"
30
+ | "commerceRuns"
25
31
  | "submit"
26
32
  | "deliverExternal"
27
33
  | "job"
@@ -80,6 +80,35 @@ function harnessSeedServices(
80
80
  const matrices = new WeakMap<DurableObjectState, { faults: MatrixFaults; registry: JobKindRegistry }>();
81
81
 
82
82
  export class TenantPipeline extends DeployedTenantPipeline {
83
+ private commercePaused = false;
84
+ private commerceResume: (() => void) | null = null;
85
+ private commerceOutcome: string = "complete";
86
+ async holdCommerce(outcome = "complete"): Promise<void> { this.commercePaused = true; this.commerceOutcome = outcome; }
87
+ async resumeCommerce(): Promise<void> {
88
+ this.commercePaused = false; this.commerceOutcome = "complete";
89
+ this.commerceResume?.(); this.commerceResume = null;
90
+ }
91
+ async commerceRaceReceive(tenantId: string): Promise<void> {
92
+ return this.ctx.blockConcurrencyWhile(async () => { await this.scheduleCommerce(tenantId, Date.now() + 2000); });
93
+ }
94
+ async commerceRaceWakeups(): Promise<number> {
95
+ return [...this.ctx.storage.sql.exec<{count:number}>(`SELECT COUNT(*) count FROM commerce_wakeup`)][0]?.count ?? 0;
96
+ }
97
+
98
+ protected override async runCommerce(tenantId:string,_now:string):Promise<number|null> {
99
+ this.ctx.storage.sql.exec(`CREATE TABLE IF NOT EXISTS commerce_probe(tenant_id TEXT PRIMARY KEY,runs INTEGER NOT NULL)`);
100
+ this.ctx.storage.sql.exec(`INSERT INTO commerce_probe VALUES(?,1) ON CONFLICT(tenant_id) DO UPDATE SET runs=runs+1`,tenantId);
101
+ const outcome = this.commerceOutcome;
102
+ if (this.commercePaused) await new Promise<void>(resolve => { this.commerceResume = resolve; });
103
+ if (outcome === "error") throw new Error("delivery-unavailable");
104
+ if (outcome === "retry") return Date.now() + 60_000;
105
+ return null;
106
+ }
107
+ async commerceRuns():Promise<number> {
108
+ this.ctx.storage.sql.exec(`CREATE TABLE IF NOT EXISTS commerce_probe(tenant_id TEXT PRIMARY KEY,runs INTEGER NOT NULL)`);
109
+ return [...this.ctx.storage.sql.exec<{runs:number}>(`SELECT SUM(runs) runs FROM commerce_probe`)][0]?.runs ?? 0;
110
+ }
111
+
83
112
  protected override jobKinds(ctx: DurableObjectState): JobKindRegistry {
84
113
  const faults = new MatrixFaults(ctx, () => publishWorldOf(ctx).kv);
85
114
  const services = previewServicesWithEffects(fakePreviewServices(ctx, faults.landed), faults.landed);