@final-commerce/command-frame 0.10.0 → 0.11.0-preprod.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 (78) hide show
  1. package/dist/CommonTypes.d.ts +4 -1
  2. package/dist/actions/assign-order-user/action.d.ts +6 -0
  3. package/dist/actions/assign-order-user/action.js +8 -0
  4. package/dist/actions/assign-order-user/mock.d.ts +2 -0
  5. package/dist/actions/assign-order-user/mock.js +30 -0
  6. package/dist/actions/assign-order-user/types.d.ts +15 -0
  7. package/dist/actions/assign-order-user/types.js +1 -0
  8. package/dist/actions/get-current-cart/mock.js +3 -0
  9. package/dist/actions/get-current-cart/types.d.ts +6 -0
  10. package/dist/actions/get-moto-config/action.d.ts +6 -0
  11. package/dist/actions/get-moto-config/action.js +8 -0
  12. package/dist/actions/get-moto-config/mock.d.ts +7 -0
  13. package/dist/actions/get-moto-config/mock.js +9 -0
  14. package/dist/actions/get-moto-config/types.d.ts +34 -0
  15. package/dist/actions/get-moto-config/types.js +5 -0
  16. package/dist/actions/get-order-statuses/action.d.ts +6 -0
  17. package/dist/actions/get-order-statuses/action.js +8 -0
  18. package/dist/actions/get-order-statuses/mock.d.ts +2 -0
  19. package/dist/actions/get-order-statuses/mock.js +5 -0
  20. package/dist/actions/get-order-statuses/types.d.ts +8 -0
  21. package/dist/actions/get-order-statuses/types.js +1 -0
  22. package/dist/actions/get-orders/action.d.ts +1 -1
  23. package/dist/actions/get-orders/action.js +2 -2
  24. package/dist/actions/get-orders/mock.d.ts +1 -1
  25. package/dist/actions/get-orders/mock.js +74 -19
  26. package/dist/actions/get-orders/types.d.ts +33 -2
  27. package/dist/actions/record-external-payment/action.d.ts +6 -0
  28. package/dist/actions/record-external-payment/action.js +8 -0
  29. package/dist/actions/record-external-payment/mock.d.ts +2 -0
  30. package/dist/actions/record-external-payment/mock.js +36 -0
  31. package/dist/actions/record-external-payment/types.d.ts +37 -0
  32. package/dist/actions/record-external-payment/types.js +1 -0
  33. package/dist/actions/release-from-cart/action.d.ts +6 -0
  34. package/dist/actions/release-from-cart/action.js +8 -0
  35. package/dist/actions/release-from-cart/mock.d.ts +2 -0
  36. package/dist/actions/release-from-cart/mock.js +26 -0
  37. package/dist/actions/release-from-cart/types.d.ts +8 -0
  38. package/dist/actions/release-from-cart/types.js +1 -0
  39. package/dist/actions/resume-order/action.d.ts +6 -0
  40. package/dist/actions/resume-order/action.js +8 -0
  41. package/dist/actions/resume-order/mock.d.ts +2 -0
  42. package/dist/actions/resume-order/mock.js +55 -0
  43. package/dist/actions/resume-order/types.d.ts +17 -0
  44. package/dist/actions/resume-order/types.js +1 -0
  45. package/dist/actions/resume-parked-order/mock.d.ts +5 -0
  46. package/dist/actions/resume-parked-order/mock.js +25 -24
  47. package/dist/actions/set-order-metadata/action.d.ts +6 -0
  48. package/dist/actions/set-order-metadata/action.js +8 -0
  49. package/dist/actions/set-order-metadata/mock.d.ts +5 -0
  50. package/dist/actions/set-order-metadata/mock.js +38 -0
  51. package/dist/actions/set-order-metadata/types.d.ts +20 -0
  52. package/dist/actions/set-order-metadata/types.js +1 -0
  53. package/dist/actions/set-order-status/action.d.ts +6 -0
  54. package/dist/actions/set-order-status/action.js +8 -0
  55. package/dist/actions/set-order-status/mock.d.ts +3 -0
  56. package/dist/actions/set-order-status/mock.js +44 -0
  57. package/dist/actions/set-order-status/types.d.ts +25 -0
  58. package/dist/actions/set-order-status/types.js +1 -0
  59. package/dist/actions/set-order-type/action.d.ts +6 -0
  60. package/dist/actions/set-order-type/action.js +8 -0
  61. package/dist/actions/set-order-type/mock.d.ts +3 -0
  62. package/dist/actions/set-order-type/mock.js +27 -0
  63. package/dist/actions/set-order-type/types.d.ts +17 -0
  64. package/dist/actions/set-order-type/types.js +1 -0
  65. package/dist/actions/set-scheduled-for/action.d.ts +6 -0
  66. package/dist/actions/set-scheduled-for/action.js +8 -0
  67. package/dist/actions/set-scheduled-for/mock.d.ts +4 -0
  68. package/dist/actions/set-scheduled-for/mock.js +45 -0
  69. package/dist/actions/set-scheduled-for/types.d.ts +22 -0
  70. package/dist/actions/set-scheduled-for/types.js +1 -0
  71. package/dist/common-types/order-state.d.ts +20 -1
  72. package/dist/demo/database.d.ts +4 -0
  73. package/dist/demo/database.js +29 -0
  74. package/dist/index.d.ts +20 -0
  75. package/dist/index.js +20 -0
  76. package/dist/projects/render/mocks.js +20 -0
  77. package/dist/projects/render/types.d.ts +11 -1
  78. package/package.json +2 -2
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Resume order action
3
+ * Calls the resumeOrder action on the parent window
4
+ */
5
+ import { commandFrameClient } from '../../client';
6
+ export const resumeOrder = async (params) => {
7
+ return await commandFrameClient.call('resumeOrder', params);
8
+ };
@@ -0,0 +1,2 @@
1
+ import type { ResumeOrder } from './types';
2
+ export declare const mockResumeOrder: ResumeOrder;
@@ -0,0 +1,55 @@
1
+ import { MOCK_ORDERS, MOCK_PARKED_ORDERS, mockPublishEvent } from '../../demo/database';
2
+ import { restoreMockCartFromOrder } from '../resume-parked-order/mock';
3
+ // Mirrors the host's rule (see README): anything not already in a cart and not finished.
4
+ const BLOCKED_PAYMENT = new Set(['refunded', 'partially_refunded', 'voided', 'payment_pending']);
5
+ const FINISHED_FULFILLMENT = new Set(['cancelled', 'returned', 'partially_returned']);
6
+ export const mockResumeOrder = async (params) => {
7
+ console.log('[Mock] resumeOrder called', params);
8
+ const orderId = params?.orderId;
9
+ if (!orderId) {
10
+ throw new Error('Order ID is required');
11
+ }
12
+ const order = [...MOCK_PARKED_ORDERS, ...MOCK_ORDERS].find((o) => o._id === orderId);
13
+ if (!order) {
14
+ throw new Error(`Order with ID ${orderId} not found`);
15
+ }
16
+ const from = { payment: order.paymentState ?? 'unpaid', fulfillment: order.fulfillmentState ?? 'draft' };
17
+ const inCart = order.inCart != null ? order.inCart.active : from.fulfillment === 'draft';
18
+ if (inCart) {
19
+ throw new Error(`Order ${orderId} is already in a cart`);
20
+ }
21
+ if (BLOCKED_PAYMENT.has(from.payment) ||
22
+ FINISHED_FULFILLMENT.has(from.fulfillment) ||
23
+ (from.payment === 'paid' && from.fulfillment === 'fulfilled')) {
24
+ throw new Error(`Order ${orderId} cannot be resumed from ${from.payment} × ${from.fulfillment}`);
25
+ }
26
+ // Keeps the state; only parked (on_hold) / draft move.
27
+ const leavesParked = from.fulfillment === 'on_hold' || from.fulfillment === 'draft';
28
+ const to = {
29
+ payment: from.payment,
30
+ fulfillment: !leavesParked ? from.fulfillment : from.payment === 'unpaid' ? 'draft' : 'in_progress',
31
+ };
32
+ restoreMockCartFromOrder(order);
33
+ const parkedIndex = MOCK_PARKED_ORDERS.findIndex((o) => o._id === orderId);
34
+ if (parkedIndex !== -1)
35
+ MOCK_PARKED_ORDERS.splice(parkedIndex, 1);
36
+ mockPublishEvent('cart', 'cart-created', { orderId });
37
+ mockPublishEvent('order-state', 'state-transition-completed', {
38
+ orderId,
39
+ from,
40
+ to,
41
+ timestamp: new Date().toISOString(),
42
+ });
43
+ return {
44
+ success: true,
45
+ order: {
46
+ ...order,
47
+ paymentState: to.payment,
48
+ fulfillmentState: to.fulfillment,
49
+ inCart: { active: true, stationId: null, since: new Date().toISOString() },
50
+ },
51
+ from,
52
+ to,
53
+ timestamp: new Date().toISOString(),
54
+ };
55
+ };
@@ -0,0 +1,17 @@
1
+ import { CFOrder } from '../../CommonTypes';
2
+ import type { CFStatePair } from '../../common-types/order-state';
3
+ export interface ResumeOrderParams {
4
+ /** The order to load back into the cart. Must be open — see the README for which states resume. */
5
+ orderId: string;
6
+ }
7
+ export interface ResumeOrderResponse {
8
+ success: boolean;
9
+ /** The resumed order, re-read after the transition. */
10
+ order: CFOrder;
11
+ /** State before resuming. */
12
+ from: CFStatePair;
13
+ /** State after resuming — `draft` when nothing was paid, otherwise the order stays out of the cart states. */
14
+ to: CFStatePair;
15
+ timestamp: string;
16
+ }
17
+ export type ResumeOrder = (params: ResumeOrderParams) => Promise<ResumeOrderResponse>;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,2 +1,7 @@
1
1
  import { ResumeParkedOrder } from './types';
2
+ import type { CFActiveOrder } from '../../CommonTypes';
3
+ /**
4
+ * Best-effort Order -> MOCK_CART mapping shared by the resume mocks.
5
+ */
6
+ export declare const restoreMockCartFromOrder: (orderToResume: CFActiveOrder) => void;
2
7
  export declare const mockResumeParkedOrder: ResumeParkedOrder;
@@ -1,4 +1,28 @@
1
1
  import { MOCK_PARKED_ORDERS, MOCK_CART, MOCK_PRODUCTS, resetMockCart, mockPublishEvent } from '../../demo/database';
2
+ /**
3
+ * Best-effort Order -> MOCK_CART mapping shared by the resume mocks.
4
+ */
5
+ export const restoreMockCartFromOrder = (orderToResume) => {
6
+ resetMockCart();
7
+ MOCK_CART.customer = orderToResume.customer; // Cast if needed
8
+ MOCK_CART.products = orderToResume.lineItems.map((li) => ({
9
+ id: li.productId,
10
+ name: li.name,
11
+ quantity: li.quantity,
12
+ price: li.price,
13
+ internalId: li.internalId || li.productId,
14
+ variantId: li.variantId,
15
+ sku: li.sku,
16
+ images: [li.image],
17
+ stock: 100,
18
+ taxTableId: '',
19
+ attributes: li.attributes,
20
+ }));
21
+ MOCK_CART.subtotal = orderToResume.summary.subtotalAfterFees;
22
+ MOCK_CART.total = orderToResume.summary.total;
23
+ MOCK_CART.amountToBeCharged = MOCK_CART.total;
24
+ MOCK_CART.remainingBalance = MOCK_CART.total;
25
+ };
2
26
  export const mockResumeParkedOrder = async (params) => {
3
27
  console.log('[Mock] resumeParkedOrder called', params);
4
28
  const orderId = params?.orderId;
@@ -25,30 +49,7 @@ export const mockResumeParkedOrder = async (params) => {
25
49
  .map((li) => li.name);
26
50
  if (notSoldNames.length)
27
51
  throw new Error(`${notSoldNames.join(', ')} is not sold in POS`);
28
- // Restore to MOCK_CART
29
- // Logic to convert Order back to Cart (simplified)
30
- resetMockCart();
31
- // Copy properties back
32
- MOCK_CART.customer = orderToResume.customer; // Cast if needed
33
- // MOCK_CART.products = orderToResume.lineItems... (Mapping needed)
34
- // For Mock demo, we'll do a best effort mapping
35
- MOCK_CART.products = orderToResume.lineItems.map((li) => ({
36
- id: li.productId,
37
- name: li.name,
38
- quantity: li.quantity,
39
- price: li.price,
40
- internalId: li.internalId || li.productId,
41
- variantId: li.variantId,
42
- sku: li.sku,
43
- images: [li.image],
44
- stock: 100,
45
- taxTableId: '',
46
- attributes: li.attributes,
47
- }));
48
- MOCK_CART.subtotal = orderToResume.summary.subtotalAfterFees;
49
- MOCK_CART.total = orderToResume.summary.total;
50
- MOCK_CART.amountToBeCharged = MOCK_CART.total;
51
- MOCK_CART.remainingBalance = MOCK_CART.total;
52
+ restoreMockCartFromOrder(orderToResume);
52
53
  // Remove from parked
53
54
  MOCK_PARKED_ORDERS.splice(index, 1);
54
55
  // Refresh the restored cart and the order lists.
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Set order metadata action
3
+ * Calls the setOrderMetadata action on the parent window
4
+ */
5
+ import type { SetOrderMetadata } from './types';
6
+ export declare const setOrderMetadata: SetOrderMetadata;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Set order metadata action
3
+ * Calls the setOrderMetadata action on the parent window
4
+ */
5
+ import { commandFrameClient } from '../../client';
6
+ export const setOrderMetadata = async (params) => {
7
+ return await commandFrameClient.call('setOrderMetadata', params);
8
+ };
@@ -0,0 +1,5 @@
1
+ import type { CFMetadataItem } from '../../CommonTypes';
2
+ import type { SetOrderMetadata } from './types';
3
+ /** Set/remove keys; untouched items (and extra fields such as `externalId`) are kept as-is. */
4
+ export declare const mergeMetadata: <T extends CFMetadataItem>(current: T[] | undefined, changes: Record<string, string | null>) => T[];
5
+ export declare const mockSetOrderMetadata: SetOrderMetadata;
@@ -0,0 +1,38 @@
1
+ import { MOCK_ORDERS, MOCK_PARKED_ORDERS, mockPublishEvent } from '../../demo/database';
2
+ /** Metadata for the demo's live cart, which has no order until it is paid or parked. */
3
+ let mockCartMetadata = [];
4
+ /** Set/remove keys; untouched items (and extra fields such as `externalId`) are kept as-is. */
5
+ export const mergeMetadata = (current, changes) => {
6
+ const byKey = new Map((current ?? []).map((m) => [m.key, m]));
7
+ for (const [key, value] of Object.entries(changes)) {
8
+ if (value === null)
9
+ byKey.delete(key);
10
+ else
11
+ byKey.set(key, { ...byKey.get(key), key, value });
12
+ }
13
+ return [...byKey.values()];
14
+ };
15
+ export const mockSetOrderMetadata = async (params) => {
16
+ console.log('[Mock] setOrderMetadata called', params);
17
+ if (!params?.metadata || Object.keys(params.metadata).length === 0) {
18
+ throw new Error('setOrderMetadata: metadata must set or remove at least one key');
19
+ }
20
+ for (const [key, value] of Object.entries(params.metadata)) {
21
+ if (!key.trim())
22
+ throw new Error('setOrderMetadata: metadata keys must be non-empty');
23
+ if (value !== null && typeof value !== 'string') {
24
+ throw new Error(`setOrderMetadata: value for "${key}" must be a string or null`);
25
+ }
26
+ }
27
+ if (!params.orderId) {
28
+ mockCartMetadata = mergeMetadata(mockCartMetadata, params.metadata);
29
+ return { success: true, orderId: null, metadata: mockCartMetadata, timestamp: new Date().toISOString() };
30
+ }
31
+ const order = [...MOCK_ORDERS, ...MOCK_PARKED_ORDERS].find((o) => o._id === params.orderId);
32
+ if (!order) {
33
+ throw new Error(`Order with ID ${params.orderId} not found`);
34
+ }
35
+ order.metadata = mergeMetadata(order.metadata, params.metadata);
36
+ mockPublishEvent('orders', 'order-updated', { order });
37
+ return { success: true, orderId: order._id, metadata: order.metadata, timestamp: new Date().toISOString() };
38
+ };
@@ -0,0 +1,20 @@
1
+ import type { CFMetadataItem } from '../../CommonTypes';
2
+ export interface SetOrderMetadataParams {
3
+ /**
4
+ * Order to update. Omit to target the live cart's order — including a cart
5
+ * that hasn't become an order yet (the metadata is carried onto the order
6
+ * when it is created).
7
+ */
8
+ orderId?: string;
9
+ /** Keys to set (string) or remove (null). Keys not listed are left as they are. */
10
+ metadata: Record<string, string | null>;
11
+ }
12
+ export interface SetOrderMetadataResponse {
13
+ success: boolean;
14
+ /** The order that was updated, or null when the live cart has no order yet. */
15
+ orderId: string | null;
16
+ /** The full metadata after the update. */
17
+ metadata: CFMetadataItem[];
18
+ timestamp: string;
19
+ }
20
+ export type SetOrderMetadata = (params: SetOrderMetadataParams) => Promise<SetOrderMetadataResponse>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Set order status action
3
+ * Calls the setOrderStatus action on the parent window
4
+ */
5
+ import type { SetOrderStatus } from './types';
6
+ export declare const setOrderStatus: SetOrderStatus;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Set order status action
3
+ * Calls the setOrderStatus action on the parent window
4
+ */
5
+ import { commandFrameClient } from '../../client';
6
+ export const setOrderStatus = async (params) => {
7
+ return await commandFrameClient.call('setOrderStatus', params);
8
+ };
@@ -0,0 +1,3 @@
1
+ import type { SetOrderStatus } from './types';
2
+ /** Best-effort mock: no state-machine guards beyond the status's payment requirement. */
3
+ export declare const mockSetOrderStatus: SetOrderStatus;
@@ -0,0 +1,44 @@
1
+ import { MOCK_ORDERS, MOCK_ORDER_STATUSES, MOCK_PARKED_ORDERS, mockPublishEvent } from '../../demo/database';
2
+ /** Best-effort mock: no state-machine guards beyond the status's payment requirement. */
3
+ export const mockSetOrderStatus = async (params) => {
4
+ console.log('[Mock] setOrderStatus called', params);
5
+ if (!params?.orderId) {
6
+ throw new Error('setOrderStatus: the demo has no live-cart order — pass an orderId');
7
+ }
8
+ const order = [...MOCK_ORDERS, ...MOCK_PARKED_ORDERS].find((o) => o._id === params.orderId);
9
+ if (!order) {
10
+ throw new Error(`Order with ID ${params.orderId} not found`);
11
+ }
12
+ const from = { payment: order.paymentState ?? 'unpaid', fulfillment: order.fulfillmentState ?? 'draft' };
13
+ const requested = params.key !== undefined ? params.key : params.statusId;
14
+ if (requested === undefined) {
15
+ throw new Error('setOrderStatus: pass a status key, or null to clear the status');
16
+ }
17
+ if (requested === null) {
18
+ order.customStatus = null;
19
+ }
20
+ else {
21
+ const def = MOCK_ORDER_STATUSES.find((s) => s.key === requested) ??
22
+ (params.key === undefined ? MOCK_ORDER_STATUSES.find((s) => s.id === requested) : undefined);
23
+ if (!def) {
24
+ throw new Error(`setOrderStatus: unknown status "${requested}"`);
25
+ }
26
+ if (def.requiresPaymentState && !def.requiresPaymentState.includes(from.payment)) {
27
+ throw new Error(`setOrderStatus: "${def.label}" requires payment state ${def.requiresPaymentState.join(' or ')} (order is ${from.payment})`);
28
+ }
29
+ if (def.fulfillmentState)
30
+ order.fulfillmentState = def.fulfillmentState;
31
+ order.customStatus = { id: def.id, key: def.key, label: def.label, setAt: new Date().toISOString(), setBy: null };
32
+ }
33
+ mockPublishEvent('orders', 'order-updated', { order });
34
+ const to = { payment: order.paymentState ?? 'unpaid', fulfillment: order.fulfillmentState ?? 'draft' };
35
+ return {
36
+ success: true,
37
+ orderId: order._id,
38
+ customStatus: order.customStatus ?? null,
39
+ from,
40
+ to,
41
+ order,
42
+ timestamp: new Date().toISOString(),
43
+ };
44
+ };
@@ -0,0 +1,25 @@
1
+ import type { CFOrder, CFOrderCustomStatus } from '../../CommonTypes';
2
+ import type { CFStatePair } from '../../common-types/order-state';
3
+ export interface SetOrderStatusParams {
4
+ /** Order to set the status on. Omit for the live cart's order. */
5
+ orderId?: string;
6
+ /** Key of one of the flow's statuses (see getOrderStatuses), or null to clear the status. */
7
+ key?: string | null;
8
+ /**
9
+ * @deprecated Use `key`. Kept for flows written before keys: matched against a status's
10
+ * key, then its id. Ignored when `key` is given.
11
+ */
12
+ statusId?: string | null;
13
+ }
14
+ export interface SetOrderStatusResponse {
15
+ success: boolean;
16
+ orderId: string;
17
+ /** The order's status after the call; null when cleared. */
18
+ customStatus: CFOrderCustomStatus | null;
19
+ /** State before and after — they differ only when the status is bound to a fulfillment state the order wasn't in. */
20
+ from: CFStatePair | null;
21
+ to: CFStatePair | null;
22
+ order: CFOrder;
23
+ timestamp: string;
24
+ }
25
+ export type SetOrderStatus = (params: SetOrderStatusParams) => Promise<SetOrderStatusResponse>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Set order type action
3
+ * Calls the setOrderType action on the parent window
4
+ */
5
+ import type { SetOrderType } from './types';
6
+ export declare const setOrderType: SetOrderType;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Set order type action
3
+ * Calls the setOrderType action on the parent window
4
+ */
5
+ import { commandFrameClient } from '../../client';
6
+ export const setOrderType = async (params) => {
7
+ return await commandFrameClient.call('setOrderType', params);
8
+ };
@@ -0,0 +1,3 @@
1
+ import type { SetOrderType } from './types';
2
+ export declare const normalizeOrderType: (value: unknown) => string | null;
3
+ export declare const mockSetOrderType: SetOrderType;
@@ -0,0 +1,27 @@
1
+ import { MOCK_ORDERS, MOCK_PARKED_ORDERS, mockPublishEvent } from '../../demo/database';
2
+ /** Type for the demo's live cart, which has no order until it is paid or parked. */
3
+ let mockCartOrderType = null;
4
+ export const normalizeOrderType = (value) => {
5
+ if (value === null)
6
+ return null;
7
+ if (typeof value !== 'string' || !value.trim()) {
8
+ throw new Error('setOrderType: orderType must be a non-empty string or null');
9
+ }
10
+ return value.trim();
11
+ };
12
+ export const mockSetOrderType = async (params) => {
13
+ console.log('[Mock] setOrderType called', params);
14
+ const orderType = normalizeOrderType(params?.orderType);
15
+ const timestamp = new Date().toISOString();
16
+ if (!params.orderId) {
17
+ mockCartOrderType = orderType;
18
+ return { success: true, orderId: null, orderType: mockCartOrderType, timestamp };
19
+ }
20
+ const order = [...MOCK_ORDERS, ...MOCK_PARKED_ORDERS].find((o) => o._id === params.orderId);
21
+ if (!order) {
22
+ throw new Error(`Order with ID ${params.orderId} not found`);
23
+ }
24
+ order.orderType = orderType;
25
+ mockPublishEvent('orders', 'order-updated', { order });
26
+ return { success: true, orderId: order._id, orderType, timestamp };
27
+ };
@@ -0,0 +1,17 @@
1
+ export interface SetOrderTypeParams {
2
+ /**
3
+ * Order to label. Omit to target the live cart — including a cart that isn't an
4
+ * order yet; the type is carried onto the order when it's created.
5
+ */
6
+ orderId?: string;
7
+ /** Any non-empty label (e.g. 'takeout', 'pickup', 'delivery'), or null to clear it. */
8
+ orderType: string | null;
9
+ }
10
+ export interface SetOrderTypeResponse {
11
+ success: boolean;
12
+ /** The order that was updated, or null when the live cart has no order yet. */
13
+ orderId: string | null;
14
+ orderType: string | null;
15
+ timestamp: string;
16
+ }
17
+ export type SetOrderType = (params: SetOrderTypeParams) => Promise<SetOrderTypeResponse>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Set scheduled-for action
3
+ * Calls the setScheduledFor action on the parent window
4
+ */
5
+ import type { SetScheduledFor } from './types';
6
+ export declare const setScheduledFor: SetScheduledFor;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Set scheduled-for action
3
+ * Calls the setScheduledFor action on the parent window
4
+ */
5
+ import { commandFrameClient } from '../../client';
6
+ export const setScheduledFor = async (params) => {
7
+ return await commandFrameClient.call('setScheduledFor', params);
8
+ };
@@ -0,0 +1,4 @@
1
+ import type { SetScheduledFor } from './types';
2
+ /** ISO 8601 in, UTC ISO 8601 out; null stays null (as soon as possible). */
3
+ export declare const normalizeScheduledFor: (value: unknown) => string | null;
4
+ export declare const mockSetScheduledFor: SetScheduledFor;
@@ -0,0 +1,45 @@
1
+ import { MOCK_ORDERS, MOCK_PARKED_ORDERS, mockPublishEvent } from '../../demo/database';
2
+ /** Time for the demo's live cart, which has no order until it is paid or parked. */
3
+ let mockCartScheduledFor = null;
4
+ /**
5
+ * An ISO 8601 date-time WITH a zone — 'Z' or an offset. A zoneless time ('2026-10-13T19:00') is
6
+ * refused: it would mean a different moment on every device. Date.parse alone is too lenient
7
+ * (it accepts strings like "tomorrow at 7"), so the shape is checked first.
8
+ */
9
+ const ISO_DATE_TIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d{1,9})?)?(Z|[+-]\d{2}:?\d{2})$/;
10
+ /** The YYYY-MM-DD part is a real day — Date.parse quietly rolls "Feb 30" over to March 2. */
11
+ const isCalendarDate = (text) => {
12
+ const [y, m, d] = text.slice(0, 10).split('-').map(Number);
13
+ const day = new Date(Date.UTC(y, m - 1, d));
14
+ return day.getUTCFullYear() === y && day.getUTCMonth() === m - 1 && day.getUTCDate() === d;
15
+ };
16
+ /** ISO 8601 in, UTC ISO 8601 out; null stays null (as soon as possible). */
17
+ export const normalizeScheduledFor = (value) => {
18
+ if (value === null)
19
+ return null;
20
+ if (typeof value !== 'string' || !value.trim()) {
21
+ throw new Error('setScheduledFor: scheduledFor must be an ISO 8601 date-time or null');
22
+ }
23
+ const text = value.trim();
24
+ const ms = ISO_DATE_TIME.test(text) && isCalendarDate(text) ? Date.parse(text) : NaN;
25
+ if (Number.isNaN(ms)) {
26
+ throw new Error(`setScheduledFor: "${value}" is not an ISO 8601 date-time with Z or an offset`);
27
+ }
28
+ return new Date(ms).toISOString();
29
+ };
30
+ export const mockSetScheduledFor = async (params) => {
31
+ console.log('[Mock] setScheduledFor called', params);
32
+ const scheduledFor = normalizeScheduledFor(params?.scheduledFor);
33
+ const timestamp = new Date().toISOString();
34
+ if (!params.orderId) {
35
+ mockCartScheduledFor = scheduledFor;
36
+ return { success: true, orderId: null, scheduledFor: mockCartScheduledFor, timestamp };
37
+ }
38
+ const order = [...MOCK_ORDERS, ...MOCK_PARKED_ORDERS].find((o) => o._id === params.orderId);
39
+ if (!order) {
40
+ throw new Error(`Order with ID ${params.orderId} not found`);
41
+ }
42
+ order.scheduledFor = scheduledFor;
43
+ mockPublishEvent('orders', 'order-updated', { order });
44
+ return { success: true, orderId: order._id, scheduledFor, timestamp };
45
+ };
@@ -0,0 +1,22 @@
1
+ export interface SetScheduledForParams {
2
+ /**
3
+ * Order to schedule. Omit to target the live cart — including a cart that isn't an
4
+ * order yet; the time is carried onto the order when it's created.
5
+ */
6
+ orderId?: string;
7
+ /**
8
+ * When the customer wants the order, as an ISO 8601 date-time with Z or an offset (e.g.
9
+ * '2026-10-13T23:00:00.000Z' or '2026-10-13T19:00:00-04:00'), or null for as soon as possible.
10
+ * Stored in UTC.
11
+ */
12
+ scheduledFor: string | null;
13
+ }
14
+ export interface SetScheduledForResponse {
15
+ success: boolean;
16
+ /** The order that was updated, or null when the live cart has no order yet. */
17
+ orderId: string | null;
18
+ /** The time after the call, normalised to UTC ISO 8601 — or null (as soon as possible). */
19
+ scheduledFor: string | null;
20
+ timestamp: string;
21
+ }
22
+ export type SetScheduledFor = (params: SetScheduledForParams) => Promise<SetScheduledForResponse>;
@@ -0,0 +1 @@
1
+ export {};
@@ -7,7 +7,7 @@ export interface CFStatePair {
7
7
  payment: string;
8
8
  fulfillment: string;
9
9
  }
10
- export type CFBlockedBy = "financial_invariant" | "cross_axis_rule" | "path" | "condition";
10
+ export type CFBlockedBy = 'financial_invariant' | 'cross_axis_rule' | 'path' | 'condition';
11
11
  export interface CFTransitionResult {
12
12
  allowed: boolean;
13
13
  blockedBy?: CFBlockedBy;
@@ -30,3 +30,22 @@ export interface CFAvailableTransition {
30
30
  displayLabel: string;
31
31
  conditions: CFConditionStatus[];
32
32
  }
33
+ /**
34
+ * An order status the running flow defines (authored in the flow builder, shipped with
35
+ * each deployed version). `fulfillmentState`: setting the status moves the order there
36
+ * (normal state-machine rules). `requiresPaymentState`: setting it fails unless the
37
+ * order's payment state is one of these — a status never moves money. Neither set = a
38
+ * label-only status.
39
+ */
40
+ export interface CFOrderStatusDefinition {
41
+ /** Durable id; survives a renamed key or label. */
42
+ id: string;
43
+ /** What flow code passes to setOrderStatus and getOrders({ customStatusKey }), e.g. 'in-kitchen'. */
44
+ key: string;
45
+ /** What staff see. */
46
+ label: string;
47
+ color?: string;
48
+ icon?: string;
49
+ fulfillmentState?: string;
50
+ requiresPaymentState?: string[];
51
+ }
@@ -2,6 +2,7 @@
2
2
  * Mock Database for Standalone/Demo Mode
3
3
  * Stores mock data that mimics the Render environment
4
4
  */
5
+ import type { CFOrderStatusDefinition } from '../common-types/order-state';
5
6
  import { CFActiveCompany, CFActiveUser, CFActiveStation, CFActiveOutlet, CFActiveOrder, CFCustomer, CFProduct, CFComposite, CFCartLineComponent, CFActiveCart, CFCategory, CFActiveProduct, CFSession, CFActiveRefundDetails, CFSmartGridLayout, CFBooking, CFBookingAvailability, CFBookingResource, CFBookingSlot, CFCartLineModifier, CFProdModifierBreakdown, CFModifierSelection, CFResolvedModifier } from '../CommonTypes';
6
7
  export * from './mocks';
7
8
  /** Replace mock catalog / context data in place (same array references mock handlers use). */
@@ -19,6 +20,7 @@ export interface MockDatabaseConfig {
19
20
  bookingResources?: CFBookingResource[];
20
21
  /** Windows already taken when the dataset loads, so a calendar does not open empty. */
21
22
  bookings?: CFBooking[];
23
+ orderStatuses?: CFOrderStatusDefinition[];
22
24
  /** Products hidden at an outlet (the host's `catalog-visibility`, product-level rows). Empty = sold everywhere. */
23
25
  catalogVisibility?: MockCatalogVisibility[];
24
26
  }
@@ -113,6 +115,8 @@ export declare let MOCK_ACTIVE_PRODUCT: CFActiveProduct;
113
115
  export declare const setMockActiveProduct: (activeProduct: CFActiveProduct) => void;
114
116
  export declare const MOCK_SMART_GRID_LAYOUTS: Record<string, CFSmartGridLayout>;
115
117
  export declare const resetMockCart: () => void;
118
+ /** The demo flow's order statuses (getOrderStatuses / setOrderStatus). */
119
+ export declare const MOCK_ORDER_STATUSES: CFOrderStatusDefinition[];
116
120
  /**
117
121
  * Replace in-memory mock data used by default mock handlers. Arrays are mutated in place
118
122
  * so existing imports from this module keep working.
@@ -936,11 +936,40 @@ function resolveMockOrderCurrency() {
936
936
  }
937
937
  return CurrencyCode.USD;
938
938
  }
939
+ /** The demo flow's order statuses (getOrderStatuses / setOrderStatus). */
940
+ export const MOCK_ORDER_STATUSES = [
941
+ { id: 'status_1', key: 'in-kitchen', label: 'In kitchen', color: '#f59e0b', fulfillmentState: 'pending' },
942
+ {
943
+ id: 'status_2',
944
+ key: 'ready-for-pickup',
945
+ label: 'Ready for pickup',
946
+ color: '#10b981',
947
+ fulfillmentState: 'in_progress',
948
+ },
949
+ {
950
+ id: 'status_3',
951
+ key: 'out-for-delivery',
952
+ label: 'Out for delivery',
953
+ color: '#3b82f6',
954
+ fulfillmentState: 'partially_fulfilled',
955
+ },
956
+ {
957
+ id: 'status_4',
958
+ key: 'delivered',
959
+ label: 'Delivered',
960
+ fulfillmentState: 'fulfilled',
961
+ requiresPaymentState: ['paid'],
962
+ },
963
+ { id: 'status_5', key: 'driver-called', label: 'Driver called' },
964
+ ];
939
965
  /**
940
966
  * Replace in-memory mock data used by default mock handlers. Arrays are mutated in place
941
967
  * so existing imports from this module keep working.
942
968
  */
943
969
  export function setMockDatabase(config) {
970
+ if (config.orderStatuses !== undefined) {
971
+ MOCK_ORDER_STATUSES.splice(0, MOCK_ORDER_STATUSES.length, ...config.orderStatuses);
972
+ }
944
973
  if (config.company !== undefined) {
945
974
  Object.assign(MOCK_COMPANY, config.company);
946
975
  }