@tokenoftrust/storefront-runner 6.1.0 → 6.1.2
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/apps/storefront/migrations-apps/0024_order_contact_erasure_state.sql +22 -0
- package/apps/storefront/src/lib/fulfillment/fulfillmentService.ts +5 -1
- package/apps/storefront/src/lib/orders/admin/orderClient.ts +83 -13
- package/apps/storefront/src/lib/orders/admin/services/orders.ts +7 -2
- package/apps/storefront/src/lib/orders/vault/orderContactVault.ts +15 -3
- package/apps/storefront/src/pages/api/admin/orders/[id].ts +3 -1
- package/apps/storefront/src/pages/api/admin/orders.ts +7 -5
- package/package.json +1 -1
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
-- Erasure group bits: identity=1, email=2, phone=4, ship=8, bill=16.
|
|
2
|
+
ALTER TABLE `order_contact_markers`
|
|
3
|
+
ADD COLUMN `erased_groups` integer NOT NULL DEFAULT 0 CHECK (`erased_groups` BETWEEN 0 AND 31);
|
|
4
|
+
--> statement-breakpoint
|
|
5
|
+
UPDATE `order_contact_markers` AS m SET `erased_groups` = COALESCE((
|
|
6
|
+
SELECT MAX(CASE WHEN instr(',' || a.groups || ',', ',identity,') > 0 THEN 1 ELSE 0 END)
|
|
7
|
+
+ MAX(CASE WHEN instr(',' || a.groups || ',', ',email,') > 0 THEN 2 ELSE 0 END)
|
|
8
|
+
+ MAX(CASE WHEN instr(',' || a.groups || ',', ',phone,') > 0 THEN 4 ELSE 0 END)
|
|
9
|
+
+ MAX(CASE WHEN instr(',' || a.groups || ',', ',ship,') > 0 THEN 8 ELSE 0 END)
|
|
10
|
+
+ MAX(CASE WHEN instr(',' || a.groups || ',', ',bill,') > 0 THEN 16 ELSE 0 END)
|
|
11
|
+
FROM `contact_access_audit` AS a
|
|
12
|
+
WHERE a.tenant_id = m.tenant_id AND a.stage = m.stage AND a.record_id = m.order_id
|
|
13
|
+
AND a.record_kind = 'order-contact' AND a.action = 'erase' AND a.outcome = 'admitted'
|
|
14
|
+
), 0);
|
|
15
|
+
--> statement-breakpoint
|
|
16
|
+
CREATE TRIGGER `order_contact_markers_erasure_monotonic`
|
|
17
|
+
BEFORE UPDATE ON `order_contact_markers`
|
|
18
|
+
WHEN NEW.tenant_id != OLD.tenant_id OR NEW.stage != OLD.stage OR NEW.order_id != OLD.order_id
|
|
19
|
+
OR (NEW.erased_groups | OLD.erased_groups) != NEW.erased_groups
|
|
20
|
+
BEGIN
|
|
21
|
+
SELECT RAISE(ABORT, 'erasure-monotonic');
|
|
22
|
+
END;
|
|
@@ -801,7 +801,11 @@ export class FulfillmentService {
|
|
|
801
801
|
if (state.shipment.labelPurchaseState === mapped.shipment && state.labelOutcome?.adultSignature === input.adultSignature) {
|
|
802
802
|
return { type: "done", view: state.view };
|
|
803
803
|
}
|
|
804
|
-
if(input.outcome==="purchased"
|
|
804
|
+
if (input.outcome === "purchased") {
|
|
805
|
+
if (state.sourceHolds.length) return { type: "refuse", code: state.sourceHolds[0]!, holds: state.sourceHolds };
|
|
806
|
+
const contactHolds = releaseHolds(this.readiness(state)).filter((hold) => hold.startsWith("ship-to-"));
|
|
807
|
+
if (contactHolds.length) return { type: "refuse", code: contactHolds[0]!, holds: contactHolds };
|
|
808
|
+
}
|
|
805
809
|
const version = state.order.eventVersion + 1;
|
|
806
810
|
return {
|
|
807
811
|
type: "write",
|
|
@@ -14,16 +14,21 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import type {
|
|
16
16
|
CheckoutOrderDetail,
|
|
17
|
-
|
|
17
|
+
CheckoutOrderSummary,
|
|
18
18
|
CheckoutOrderListFilters,
|
|
19
19
|
CheckoutOrderListResponse,
|
|
20
20
|
CommerceActor,
|
|
21
21
|
CommerceRole,
|
|
22
22
|
CommerceScope,
|
|
23
23
|
CommerceStage,
|
|
24
|
+
CheckoutOrderCustomer,
|
|
25
|
+
OrderContactAvailability,
|
|
26
|
+
OrderContactState,
|
|
24
27
|
} from "@tot/public-runtime";
|
|
25
|
-
import { readOwnerAddress } from "@tot/public-runtime";
|
|
28
|
+
import { actorMayCommand, readOwnerAddress } from "@tot/public-runtime";
|
|
26
29
|
import type { CustomizationEnv } from "@tot/public-runtime";
|
|
30
|
+
import { contactReaderAuthority } from "@/lib/access/auth/contactReaderAuthority";
|
|
31
|
+
import type { SessionRecord } from "@/lib/access/auth/session";
|
|
27
32
|
|
|
28
33
|
/** Fetch shape we depend on — the platform `fetch`, injectable for tests. */
|
|
29
34
|
export type FetchLike = (
|
|
@@ -107,26 +112,83 @@ export function commerceStageForCustomization(env: CustomizationEnv): CommerceSt
|
|
|
107
112
|
return env === "live" ? "publication" : "preview";
|
|
108
113
|
}
|
|
109
114
|
|
|
110
|
-
/**
|
|
115
|
+
/** Private ownership comes from tenant membership, independently of staff operational capability. */
|
|
111
116
|
export function commerceActorForAdmin(input: {
|
|
112
117
|
tenantId: string;
|
|
113
118
|
stage: CommerceStage;
|
|
114
|
-
|
|
119
|
+
record: SessionRecord | null | undefined;
|
|
120
|
+
nowSeconds: number;
|
|
115
121
|
admitted: boolean;
|
|
116
122
|
}): CommerceActor {
|
|
117
|
-
const
|
|
123
|
+
const privateOwner = contactReaderAuthority(input.record, input.tenantId, input.nowSeconds).kind === "owner";
|
|
124
|
+
const role: CommerceRole = !input.admitted ? "denied" : privateOwner ? "owner" : "caller";
|
|
118
125
|
return { role, tenantId: input.tenantId, stage: input.stage };
|
|
119
126
|
}
|
|
120
127
|
|
|
121
128
|
/** Drop the address unless this actor may read it for this tenant and stage. */
|
|
122
|
-
export function projectCheckoutOrder<T extends { shippingAddress?: unknown }>(
|
|
129
|
+
export function projectCheckoutOrder<T extends { shippingAddress?: unknown; customer?: CheckoutOrderCustomer; contactAvailability?: OrderContactAvailability }>(
|
|
123
130
|
order: T,
|
|
124
131
|
actor: CommerceActor,
|
|
125
132
|
resource: CommerceScope,
|
|
126
133
|
): T {
|
|
134
|
+
const unknown: OrderContactAvailability = { identity: "unreadable", email: "unreadable", shipTo: "unreadable" };
|
|
135
|
+
const availability = order.contactAvailability ?? {
|
|
136
|
+
...unknown,
|
|
137
|
+
shipTo: Object.hasOwn(order, "shippingAddress") ? addressAvailability(order.shippingAddress) : "unreadable",
|
|
138
|
+
};
|
|
127
139
|
const read = readOwnerAddress(actor, resource, () => order.shippingAddress ?? null);
|
|
128
|
-
if (read.ok) return order;
|
|
129
|
-
|
|
140
|
+
if (read.ok) return { ...order, contactAvailability: availability };
|
|
141
|
+
const restricted = (state: OrderContactState): OrderContactState => state === "present" ? "restricted" : state;
|
|
142
|
+
const projected: Record<string, unknown> = { ...order };
|
|
143
|
+
for (const key of ["customerName", "customerEmail", "email", "phone", "name", "line1", "line2", "postalCode"]) delete projected[key];
|
|
144
|
+
return {
|
|
145
|
+
...projected,
|
|
146
|
+
shippingAddress: null,
|
|
147
|
+
...(order.customer ? { customer: { id: null, name: "Checkout customer" } } : {}),
|
|
148
|
+
contactAvailability: actorMayCommand(actor, resource) ? {
|
|
149
|
+
identity: restricted(availability.identity), email: restricted(availability.email), shipTo: restricted(availability.shipTo),
|
|
150
|
+
} : unknown,
|
|
151
|
+
} as T;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function addressAvailability(value: unknown): OrderContactState {
|
|
155
|
+
if (value === null) return "absent";
|
|
156
|
+
if (!isRecord(value)) return "unreadable";
|
|
157
|
+
return ["line1", "city", "country"].every((key) => typeof value[key] === "string" && value[key].trim() !== "") ? "present" : "unreadable";
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
161
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const SUMMARY_FIELDS = ["id", "placedAt", "currency", "customer", "contactAvailability", "itemsSummary", "itemCount", "total", "payment", "fulfillment", "blocker", "verification"];
|
|
165
|
+
const DETAIL_FIELDS = [...SUMMARY_FIELDS, "lineItems", "totals", "shippingAddress", "verificationDetail"];
|
|
166
|
+
|
|
167
|
+
/** Refuse an unsupported upstream body before any raw contact aliases reach an admin response. */
|
|
168
|
+
function frozenOrder(value: unknown, detail: true): value is CheckoutOrderDetail;
|
|
169
|
+
function frozenOrder(value: unknown, detail: false): value is CheckoutOrderSummary;
|
|
170
|
+
function frozenOrder(value: unknown, detail: boolean): boolean {
|
|
171
|
+
if (!isRecord(value) || Object.keys(value).some((key) => !(detail ? DETAIL_FIELDS : SUMMARY_FIELDS).includes(key))) return false;
|
|
172
|
+
if (!["id", "placedAt", "currency", "itemsSummary"].every((key) => typeof value[key] === "string")) return false;
|
|
173
|
+
if (!isRecord(value.customer) || typeof value.customer.name !== "string" ||
|
|
174
|
+
!(value.customer.id === null || typeof value.customer.id === "string") ||
|
|
175
|
+
Object.keys(value.customer).some((key) => key !== "id" && key !== "name")) return false;
|
|
176
|
+
if (typeof value.total !== "number" || !Number.isFinite(value.total) || !Number.isInteger(value.itemCount)) return false;
|
|
177
|
+
if (!["authorized", "captured", "refunded"].includes(String(value.payment)) ||
|
|
178
|
+
!["unfulfilled", "ready_to_ship", "hold_review_required", "completed"].includes(String(value.fulfillment))) return false;
|
|
179
|
+
if (value.blocker !== null && typeof value.blocker !== "string") return false;
|
|
180
|
+
if (value.verification !== null && !isRecord(value.verification)) return false;
|
|
181
|
+
const availability = value.contactAvailability;
|
|
182
|
+
if (availability !== undefined && (!isRecord(availability) ||
|
|
183
|
+
!["identity", "email", "shipTo"].every((key) => typeof availability[key] === "string" &&
|
|
184
|
+
["present", "restricted", "absent", "expired", "erased", "unreadable"].includes(availability[key])) ||
|
|
185
|
+
Object.keys(availability).some((key) => !["identity", "email", "shipTo"].includes(key)))) return false;
|
|
186
|
+
return !detail || (Array.isArray(value.lineItems) && isRecord(value.totals) && isRecord(value.verificationDetail) &&
|
|
187
|
+
(value.shippingAddress === null || isRecord(value.shippingAddress)));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function unsupportedBody(): OrderApiError {
|
|
191
|
+
return new OrderApiError("bad_gateway", 502);
|
|
130
192
|
}
|
|
131
193
|
|
|
132
194
|
/** Build the list querystring: appDomain (required) + any provided filters. */
|
|
@@ -151,12 +213,12 @@ export function createCheckoutOrderClient(
|
|
|
151
213
|
const base = config.baseUrl.replace(/\/+$/, "");
|
|
152
214
|
const headers = { authorization: `Bearer ${config.bearer}` };
|
|
153
215
|
|
|
154
|
-
async function getJson
|
|
216
|
+
async function getJson(url: string): Promise<unknown> {
|
|
155
217
|
const res = await doFetch(url, { method: "GET", headers });
|
|
156
218
|
const body = await res.text();
|
|
157
219
|
if (!res.ok) throw toError(res.status, body);
|
|
158
220
|
try {
|
|
159
|
-
return JSON.parse(body) as
|
|
221
|
+
return JSON.parse(body) as unknown;
|
|
160
222
|
} catch {
|
|
161
223
|
throw new OrderApiError("unexpected", res.status, "malformed JSON");
|
|
162
224
|
}
|
|
@@ -165,14 +227,22 @@ export function createCheckoutOrderClient(
|
|
|
165
227
|
return {
|
|
166
228
|
async listOrders(filters) {
|
|
167
229
|
const qs = listQuery(config.appDomain, filters);
|
|
168
|
-
|
|
230
|
+
const body = await getJson(`${base}/commerce/orders?${qs}`);
|
|
231
|
+
if (!isRecord(body) || !Array.isArray(body.orders) || !body.orders.every((order): order is CheckoutOrderSummary => frozenOrder(order, false)) ||
|
|
232
|
+
!isRecord(body.page) || Object.keys(body).some((key) => key !== "orders" && key !== "page")) throw unsupportedBody();
|
|
233
|
+
const page = body.page;
|
|
234
|
+
if (typeof page.limit !== "number" || !(page.cursor === null || typeof page.cursor === "string") ||
|
|
235
|
+
!(page.nextCursor === null || typeof page.nextCursor === "string") || !(page.total === null || typeof page.total === "number") ||
|
|
236
|
+
Object.keys(page).some((key) => !["limit", "cursor", "nextCursor", "total"].includes(key))) throw unsupportedBody();
|
|
237
|
+
return { orders: body.orders, page: { limit: page.limit, cursor: page.cursor, nextCursor: page.nextCursor, total: page.total } };
|
|
169
238
|
},
|
|
170
239
|
async getOrder(orderId) {
|
|
171
240
|
const qs = new URLSearchParams({ appDomain: config.appDomain }).toString();
|
|
172
|
-
const
|
|
241
|
+
const body = await getJson(
|
|
173
242
|
`${base}/commerce/orders/${encodeURIComponent(orderId)}?${qs}`,
|
|
174
243
|
);
|
|
175
|
-
|
|
244
|
+
if (!isRecord(body) || !frozenOrder(body.order, true) || Object.keys(body).some((key) => key !== "order")) throw unsupportedBody();
|
|
245
|
+
return body.order;
|
|
176
246
|
},
|
|
177
247
|
};
|
|
178
248
|
}
|
|
@@ -28,6 +28,7 @@ import type {
|
|
|
28
28
|
TenantConfig,
|
|
29
29
|
OrderContactState,
|
|
30
30
|
} from "@tot/public-runtime";
|
|
31
|
+
import { projectCheckoutOrder } from "../orderClient";
|
|
31
32
|
import {
|
|
32
33
|
actorMayCommand,
|
|
33
34
|
decideCommerceCommand,
|
|
@@ -644,6 +645,7 @@ export function emptyOrdersViewModel(targetTenant: string, timeZone: string = DE
|
|
|
644
645
|
}
|
|
645
646
|
|
|
646
647
|
export interface GetOrdersViewModelOptions {
|
|
648
|
+
actor?: CommerceActor;
|
|
647
649
|
targetTenant: string;
|
|
648
650
|
scope: CommerceScope;
|
|
649
651
|
client: CheckoutOrderClient;
|
|
@@ -719,12 +721,15 @@ async function readOrdersToday(client: CheckoutOrderClient, now: Date, timeZone:
|
|
|
719
721
|
|
|
720
722
|
export async function getOrdersViewModelResult(opts: GetOrdersViewModelOptions): Promise<OrdersLoadResult> {
|
|
721
723
|
try {
|
|
722
|
-
const
|
|
724
|
+
const listed = await opts.client.listOrders(opts.filters);
|
|
725
|
+
const actor = opts.actor ?? { ...opts.scope, role: "caller" as const };
|
|
726
|
+
const orders = listed.orders.map((order) => projectCheckoutOrder(order, actor, opts.scope));
|
|
727
|
+
const page = listed.page;
|
|
723
728
|
const details: Record<string, CheckoutOrderDetail> = {};
|
|
724
729
|
await Promise.all(
|
|
725
730
|
orders.map(async (o) => {
|
|
726
731
|
try {
|
|
727
|
-
details[o.id] = await opts.client.getOrder(o.id);
|
|
732
|
+
details[o.id] = projectCheckoutOrder(await opts.client.getOrder(o.id), actor, opts.scope);
|
|
728
733
|
} catch {
|
|
729
734
|
/* detail unavailable — row still renders from its summary */
|
|
730
735
|
}
|
|
@@ -71,6 +71,8 @@ const LAYOUT: Readonly<Record<Group, GroupLayout>> = {
|
|
|
71
71
|
bill: { sealed: ["bill_name_ct", "bill_company_ct", "bill_line1_ct", "bill_line2_ct"], bidx: null, plain: ["bill_city", "bill_postal_code"] },
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
+
const ERASURE_BITS: Readonly<Record<Group, number>> = { identity: 1, email: 2, phone: 4, ship: 8, bill: 16 };
|
|
75
|
+
|
|
74
76
|
function groupColumns(group: Group): string[] {
|
|
75
77
|
const layout = LAYOUT[group];
|
|
76
78
|
return [...layout.sealed, ...(layout.bidx ? [layout.bidx] : []), ...layout.plain];
|
|
@@ -624,7 +626,9 @@ function completeGroupSql(group: typeof AVAILABILITY_GROUPS[number]): string {
|
|
|
624
626
|
|
|
625
627
|
const AVAILABILITY_SELECT = `SELECT o.order_id, o.placed_at AS order_placed_at,
|
|
626
628
|
o.fulfillment_state AS order_fulfillment_state, (${correlatedTerminalSql("o")}) AS order_terminal_at,
|
|
627
|
-
v.order_id IS NOT NULL AS vault_present,
|
|
629
|
+
v.order_id IS NOT NULL AS vault_present,
|
|
630
|
+
COALESCE((SELECT m.erased_groups FROM order_contact_markers m
|
|
631
|
+
WHERE m.tenant_id = o.tenant_id AND m.stage = o.stage AND m.order_id = o.order_id), 0) AS erased_groups,
|
|
628
632
|
EXISTS (SELECT 1 FROM order_contact_markers m
|
|
629
633
|
WHERE m.tenant_id = o.tenant_id AND m.stage = o.stage AND m.order_id = o.order_id) AS marked,
|
|
630
634
|
EXISTS (SELECT 1 FROM contact_vault_data_keys k
|
|
@@ -648,13 +652,17 @@ function uniformAvailability(state: OrderContactState): OrderContactAvailability
|
|
|
648
652
|
function contactAvailability(row: VaultRow, input: {
|
|
649
653
|
tenantId: string; stage: VaultStage; now: Date; policy: RetentionPolicy;
|
|
650
654
|
}): OrderContactAvailability {
|
|
651
|
-
|
|
655
|
+
const erased = (group: Group): boolean => (Number(row.erased_groups) & ERASURE_BITS[group]) !== 0;
|
|
656
|
+
if (Number(row.vault_present) !== 1) {
|
|
657
|
+
const missing = (group: Group): OrderContactState => erased(group) ? "erased" : Number(row.marked) === 1 ? "expired" : "absent";
|
|
658
|
+
return { identity: missing("identity"), email: missing("email"), shipTo: missing("ship") };
|
|
659
|
+
}
|
|
652
660
|
const times = orderTimes(row.order_placed_at, row.order_fulfillment_state, row.order_terminal_at);
|
|
653
661
|
if (times === "order-time-unreadable" || !Number.isFinite(input.now.getTime())) return uniformAvailability("unreadable");
|
|
654
662
|
const states = groupStates(row, input.tenantId, input.stage, times, input.now, input.policy);
|
|
655
663
|
const state = (group: typeof AVAILABILITY_GROUPS[number]): OrderContactState => {
|
|
656
664
|
const current = states[group];
|
|
657
|
-
if (!current.sealed &&
|
|
665
|
+
if (!current.sealed && erased(group)) return "erased";
|
|
658
666
|
if (!current.expiresAt) return "unreadable";
|
|
659
667
|
if (!isGroupReadable(input.now, current.expiresAt, text(row, `${group}_ceiling`)) ||
|
|
660
668
|
(input.stage === "preview" && input.now.getTime() >= Date.parse(previewDeleteAt(times.placedAt, input.policy)))) return "expired";
|
|
@@ -949,6 +957,10 @@ export async function eraseOrderContact(
|
|
|
949
957
|
});
|
|
950
958
|
const statements: QueryStatement[] = [
|
|
951
959
|
batchAuditStatement(audit),
|
|
960
|
+
{
|
|
961
|
+
sql: `UPDATE order_contact_markers SET erased_groups = erased_groups | ? WHERE ${scope}`,
|
|
962
|
+
params: [targets.reduce((mask, group) => mask | ERASURE_BITS[group], 0), ...scopeParams],
|
|
963
|
+
},
|
|
952
964
|
{ sql: `UPDATE order_contact_vault SET ${clearSql(targets)}, erased_at = ? WHERE ${scope}`, params: [now.toISOString(), ...scopeParams] },
|
|
953
965
|
...normalizeStatements(scope, scopeParams),
|
|
954
966
|
];
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { APIContext } from "astro";
|
|
9
9
|
import { requireAdminApiEntry } from "@/lib/access/auth/adminApiGuard";
|
|
10
|
+
import { readViewerSession } from "@/lib/access/auth/route";
|
|
10
11
|
import { commerceActorForAdmin, OrderApiError, projectCheckoutOrder } from "@/lib/orders/admin/orderClient";
|
|
11
12
|
import { resolveOrderSource, workerOrderSourceDeps } from "@/lib/orders/admin/orderSource";
|
|
12
13
|
import { admitSharedStoreTenant } from "@/lib/checkout/commerce/sharedStoreAdmission";
|
|
@@ -47,7 +48,8 @@ export async function GET(context: APIContext): Promise<Response> {
|
|
|
47
48
|
const actor = commerceActorForAdmin({
|
|
48
49
|
tenantId: scope.tenantId,
|
|
49
50
|
stage: scope.stage,
|
|
50
|
-
|
|
51
|
+
record: await readViewerSession(context),
|
|
52
|
+
nowSeconds: Math.floor(Date.now() / 1000),
|
|
51
53
|
admitted: access.entry.admitted,
|
|
52
54
|
});
|
|
53
55
|
const order = projectCheckoutOrder(await source.client.getOrder(orderId), actor, scope);
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import type { APIContext } from "astro";
|
|
13
13
|
import { requireAdminApiEntry } from "@/lib/access/auth/adminApiGuard";
|
|
14
|
-
import {
|
|
14
|
+
import { readViewerSession } from "@/lib/access/auth/route";
|
|
15
|
+
import { commerceActorForAdmin, OrderApiError, projectCheckoutOrder } from "@/lib/orders/admin/orderClient";
|
|
15
16
|
import { resolveOrderSource, workerOrderSourceDeps } from "@/lib/orders/admin/orderSource";
|
|
16
17
|
import { admitSharedStoreTenant } from "@/lib/checkout/commerce/sharedStoreAdmission";
|
|
17
18
|
import { assessCheckoutOrder, parseOrderFilters } from "@/lib/orders/admin/services/orders";
|
|
@@ -51,10 +52,11 @@ export async function GET(context: APIContext): Promise<Response> {
|
|
|
51
52
|
|
|
52
53
|
try {
|
|
53
54
|
const data = await source.client.listOrders(filters);
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
const actor = commerceActorForAdmin({ ...source.scope, record: await readViewerSession(context), nowSeconds: Math.floor(Date.now() / 1000), admitted: access.entry.admitted });
|
|
56
|
+
const orders = data.orders.map((raw) => {
|
|
57
|
+
const order = projectCheckoutOrder(raw, actor, source.scope);
|
|
58
|
+
return { ...order, readiness: assessCheckoutOrder(order, source.scope) };
|
|
59
|
+
});
|
|
58
60
|
return new Response(JSON.stringify({ ...data, orders }), { headers: JSON_HEADERS });
|
|
59
61
|
} catch (err) {
|
|
60
62
|
if (err instanceof OrderApiError) return jsonError(err.code, err.status);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "6.1.
|
|
3
|
+
"version": "6.1.2",
|
|
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",
|