@final-commerce/command-frame 0.3.0-beta.3 → 0.3.0-beta.5

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 (123) hide show
  1. package/README.md +50 -28
  2. package/dist/CommonTypes.d.ts +2 -1
  3. package/dist/actions/add-cart-discount/mock.js +6 -3
  4. package/dist/actions/add-cart-fee/mock.js +6 -6
  5. package/dist/actions/add-custom-sale/mock.js +7 -2
  6. package/dist/actions/add-order-note/mock.js +3 -1
  7. package/dist/actions/apply-transition/action.d.ts +2 -0
  8. package/dist/actions/apply-transition/action.js +4 -0
  9. package/dist/actions/apply-transition/mock.d.ts +6 -0
  10. package/dist/actions/apply-transition/mock.js +33 -0
  11. package/dist/actions/apply-transition/types.d.ts +17 -0
  12. package/dist/actions/can-transition/action.d.ts +2 -0
  13. package/dist/actions/can-transition/action.js +4 -0
  14. package/dist/actions/can-transition/mock.d.ts +6 -0
  15. package/dist/actions/can-transition/mock.js +28 -0
  16. package/dist/actions/can-transition/types.d.ts +11 -0
  17. package/dist/actions/cash-payment/mock.js +12 -18
  18. package/dist/actions/cash-payment/types.d.ts +5 -0
  19. package/dist/actions/delete-parked-order/mock.js +3 -1
  20. package/dist/actions/delete-parked-order/types.d.ts +3 -0
  21. package/dist/actions/extension-payment/types.d.ts +8 -3
  22. package/dist/actions/get-available-transitions/action.d.ts +2 -0
  23. package/dist/actions/get-available-transitions/action.js +4 -0
  24. package/dist/actions/get-available-transitions/mock.d.ts +6 -0
  25. package/dist/actions/get-available-transitions/mock.js +26 -0
  26. package/dist/actions/get-available-transitions/types.d.ts +9 -0
  27. package/dist/actions/get-context/mock.js +1 -1
  28. package/dist/actions/get-orders/mock.js +3 -3
  29. package/dist/actions/get-products/mock.js +6 -1
  30. package/dist/actions/get-products/types.d.ts +2 -0
  31. package/dist/actions/get-smart-grid-layout/mock.d.ts +1 -1
  32. package/dist/actions/get-smart-grid-layout/mock.js +3 -1
  33. package/dist/actions/get-smart-grid-layout/types.d.ts +1 -3
  34. package/dist/actions/initiate-refund/types.d.ts +3 -0
  35. package/dist/actions/integration-payment/types.d.ts +48 -8
  36. package/dist/actions/open-extension-overlay/mock.js +1 -1
  37. package/dist/actions/open-extension-overlay/types.d.ts +1 -1
  38. package/dist/actions/park-order/mock.js +4 -1
  39. package/dist/actions/park-order/types.d.ts +3 -0
  40. package/dist/actions/partial-payment/mock.js +21 -3
  41. package/dist/actions/partial-payment/types.d.ts +5 -0
  42. package/dist/actions/process-partial-refund/types.d.ts +3 -0
  43. package/dist/actions/redeem-payment/types.d.ts +9 -5
  44. package/dist/actions/remove-custom-sale/mock.d.ts +2 -0
  45. package/dist/actions/remove-custom-sale/mock.js +29 -0
  46. package/dist/actions/remove-customer-from-cart/mock.js +3 -1
  47. package/dist/actions/remove-order-note/mock.d.ts +2 -0
  48. package/dist/actions/remove-order-note/mock.js +12 -0
  49. package/dist/actions/remove-product-discount/mock.d.ts +2 -0
  50. package/dist/actions/remove-product-discount/mock.js +17 -0
  51. package/dist/actions/remove-product-fee/mock.d.ts +2 -0
  52. package/dist/actions/remove-product-fee/mock.js +17 -0
  53. package/dist/actions/remove-product-from-cart/mock.js +5 -5
  54. package/dist/actions/resolve-extension-overlay/mock.js +1 -1
  55. package/dist/actions/resume-parked-order/mock.js +4 -1
  56. package/dist/actions/resume-parked-order/types.d.ts +3 -0
  57. package/dist/actions/save-smart-grid-layout/mock.d.ts +1 -1
  58. package/dist/actions/save-smart-grid-layout/mock.js +3 -0
  59. package/dist/actions/save-smart-grid-layout/types.d.ts +1 -3
  60. package/dist/actions/tap-to-pay-payment/mock.js +5 -3
  61. package/dist/actions/tap-to-pay-payment/types.d.ts +5 -0
  62. package/dist/actions/terminal-payment/mock.js +4 -5
  63. package/dist/actions/terminal-payment/types.d.ts +5 -0
  64. package/dist/actions/update-cart-item-quantity/mock.js +9 -9
  65. package/dist/actions/vendara-payment/types.d.ts +5 -0
  66. package/dist/common-types/index.d.ts +1 -0
  67. package/dist/common-types/index.js +1 -0
  68. package/dist/common-types/order-state.d.ts +32 -0
  69. package/dist/common-types/order-state.js +6 -0
  70. package/dist/common-types/state-fragment.d.ts +77 -0
  71. package/dist/common-types/state-fragment.js +10 -0
  72. package/dist/demo/database.d.ts +12 -1
  73. package/dist/demo/database.js +173 -3
  74. package/dist/fragments/index.d.ts +1 -0
  75. package/dist/fragments/index.js +1 -0
  76. package/dist/fragments/preorder-no-deposit.d.ts +26 -0
  77. package/dist/fragments/preorder-no-deposit.js +166 -0
  78. package/dist/index.d.ts +21 -20
  79. package/dist/index.js +23 -21
  80. package/dist/interceptors/index.d.ts +21 -0
  81. package/dist/interceptors/index.js +53 -0
  82. package/dist/interceptors/types.d.ts +8 -16
  83. package/dist/projects/render/mocks.js +19 -17
  84. package/dist/projects/render/types.d.ts +7 -6
  85. package/dist/pubsub/topics/orders/index.js +10 -0
  86. package/dist/pubsub/topics/orders/state-transition-blocked/types.d.ts +16 -0
  87. package/dist/pubsub/topics/orders/state-transition-blocked/types.js +1 -0
  88. package/dist/pubsub/topics/orders/state-transition-completed/types.d.ts +15 -0
  89. package/dist/pubsub/topics/orders/state-transition-completed/types.js +1 -0
  90. package/dist/pubsub/topics/orders/types.d.ts +6 -2
  91. package/dist/pubsub/topics/orders/types.js +2 -0
  92. package/dist/pubsub/topics/split-payments/index.d.ts +12 -0
  93. package/dist/pubsub/topics/split-payments/index.js +22 -0
  94. package/dist/pubsub/topics/split-payments/split-payment-updated/types.d.ts +16 -0
  95. package/dist/pubsub/topics/split-payments/split-payment-updated/types.js +1 -0
  96. package/dist/pubsub/topics/split-payments/types.d.ts +10 -0
  97. package/dist/pubsub/topics/split-payments/types.js +5 -0
  98. package/dist/pubsub/topics/types.d.ts +2 -0
  99. package/package.json +3 -3
  100. package/dist/actions/extension-refund/constants.d.ts +0 -2
  101. package/dist/actions/extension-refund/constants.js +0 -2
  102. package/dist/actions/extension-refund/extension-refund-listener.d.ts +0 -11
  103. package/dist/actions/extension-refund/extension-refund-listener.js +0 -60
  104. package/dist/actions/extension-refund/types.d.ts +0 -32
  105. package/dist/actions/extension-refund/types.js +0 -5
  106. package/dist/actions/set-active-outlet/action.d.ts +0 -2
  107. package/dist/actions/set-active-outlet/action.js +0 -4
  108. package/dist/actions/set-active-outlet/mock.d.ts +0 -2
  109. package/dist/actions/set-active-outlet/mock.js +0 -16
  110. package/dist/actions/set-active-outlet/types.d.ts +0 -10
  111. package/dist/actions/set-active-session/action.d.ts +0 -2
  112. package/dist/actions/set-active-session/action.js +0 -4
  113. package/dist/actions/set-active-session/mock.d.ts +0 -2
  114. package/dist/actions/set-active-session/mock.js +0 -15
  115. package/dist/actions/set-active-session/types.d.ts +0 -10
  116. package/dist/actions/set-active-station/action.d.ts +0 -2
  117. package/dist/actions/set-active-station/action.js +0 -4
  118. package/dist/actions/set-active-station/mock.d.ts +0 -2
  119. package/dist/actions/set-active-station/mock.js +0 -16
  120. package/dist/actions/set-active-station/types.d.ts +0 -10
  121. /package/dist/actions/{set-active-outlet → apply-transition}/types.js +0 -0
  122. /package/dist/actions/{set-active-session → can-transition}/types.js +0 -0
  123. /package/dist/actions/{set-active-station → get-available-transitions}/types.js +0 -0
package/README.md CHANGED
@@ -10,11 +10,12 @@ Command Frame provides a structured way to build integrations that run inside Fi
10
10
 
11
11
  The library provides three main capabilities:
12
12
 
13
- | Capability | Purpose | Scope |
14
- |-----------|---------|-------|
15
- | **Commands** | Call host functions from the iframe (e.g. get products, open cash drawer) | Request/response per call |
16
- | **Pub/Sub** | Subscribe to real-time events from the host (e.g. cart changes, payments) | Page-scoped (while iframe is mounted) |
17
- | **Hooks** | Register business-logic callbacks that persist across all pages | Session-scoped (survives page navigation) |
13
+ | Capability | Purpose | Scope |
14
+ | ------------------------- | ----------------------------------------------------------------------------------------------- | ---------------------------------------------- |
15
+ | **Commands** | Call host functions from the iframe (e.g. get products, open cash drawer) | Request/response per call |
16
+ | **Pub/Sub** | Subscribe to real-time events from the host (e.g. cart changes, payments) | Page-scoped (while iframe is mounted) |
17
+ | **Hooks** | Register business-logic callbacks that persist across all pages | Session-scoped (survives page navigation) |
18
+ | **Interceptors** | Gate POS flows (approve / modify / block) at named points | Blocking; host waits for your response |
18
19
  | **Host → iframe refunds** | Render asks the extension to reverse redeem / gift-card payments before completing a POS refund | Parent `postMessage` + `requestId` (see below) |
19
20
 
20
21
  Domain models (orders, cart, customers, products, and related types) are documented in **[Types reference](./src/types/README.md)**.
@@ -54,7 +55,7 @@ For building applications that run inside the Render Point of Sale interface.
54
55
  - **Features:** Order management, Product catalog, Customer management, Payments, Hardware integration (Cash drawer, Printer), Custom tables, Secrets storage.
55
56
 
56
57
  ```typescript
57
- import { RenderClient } from '@final-commerce/command-frame';
58
+ import { RenderClient } from "@final-commerce/command-frame";
58
59
 
59
60
  const client = new RenderClient();
60
61
  const products = await client.getProducts();
@@ -68,7 +69,7 @@ For building applications that run inside the Final Commerce Management Dashboar
68
69
  - **Features:** Context, catalog, entities, custom tables, secrets, and optional host-specific commands (navigation, media, tax, branding, notifications) when the dashboard implements them.
69
70
 
70
71
  ```typescript
71
- import { ManageClient } from '@final-commerce/command-frame';
72
+ import { ManageClient } from "@final-commerce/command-frame";
72
73
 
73
74
  const client = new ManageClient();
74
75
  const context = await client.getContext();
@@ -82,14 +83,14 @@ The pub/sub system allows iframe extensions to subscribe to topics and receive r
82
83
  - **Topics:** Cart (16), Customers (8), Orders (4), Payments (2), Products (4), Refunds (4), Print (3), Custom Tables (3), Outlet (2), Station (2), Session (2), Users (2).
83
84
 
84
85
  ```typescript
85
- import { topics } from '@final-commerce/command-frame';
86
+ import { topics } from "@final-commerce/command-frame";
86
87
 
87
- const subscriptionId = topics.subscribe('cart', (event) => {
88
- console.log('Cart event:', event.type, event.data);
88
+ const subscriptionId = topics.subscribe("cart", event => {
89
+ console.log("Cart event:", event.type, event.data);
89
90
  });
90
91
 
91
92
  // Unsubscribe when done
92
- topics.unsubscribe('cart', subscriptionId);
93
+ topics.unsubscribe("cart", subscriptionId);
93
94
  ```
94
95
 
95
96
  ## Hooks
@@ -101,17 +102,44 @@ Hooks are **session-scoped** event callbacks that run in the host (Render) conte
101
102
  - A stable `hookId` is required for deduplication (safe on iframe reload).
102
103
 
103
104
  ```typescript
104
- import { hooks } from '@final-commerce/command-frame';
105
-
106
- hooks.register('cart', async (event, hostCommands) => {
107
- await hostCommands.upsertCustomTableData({
108
- tableName: 'cart-events-log',
109
- data: { eventType: event.type, payload: event.data, timestamp: event.timestamp },
110
- });
111
- }, { hookId: 'my-extension:cart-log' });
105
+ import { hooks } from "@final-commerce/command-frame";
106
+
107
+ hooks.register(
108
+ "cart",
109
+ async (event, hostCommands) => {
110
+ await hostCommands.upsertCustomTableData({
111
+ tableName: "cart-events-log",
112
+ data: { eventType: event.type, payload: event.data, timestamp: event.timestamp }
113
+ });
114
+ },
115
+ { hookId: "my-extension:cart-log" }
116
+ );
112
117
 
113
118
  // Unregister when no longer needed
114
- hooks.unregister('my-extension:cart-log');
119
+ hooks.unregister("my-extension:cart-log");
120
+ ```
121
+
122
+ ## Interceptors
123
+
124
+ Interceptors let an extension **gate a POS flow** (approve / modify / block) at a named point — the host waits for your interceptor and acts on what it returns. Unlike hooks, interceptors are **blocking**.
125
+
126
+ - **[Interceptors Documentation](./src/interceptors/README.md)**
127
+ - The callback is serialized and reconstructed on the host; it must be **self-contained** (no closures, no imports).
128
+ - A stable `interceptorId` is required for deduplication (safe on iframe reload).
129
+
130
+ ```typescript
131
+ import { interceptors } from '@final-commerce/command-frame';
132
+
133
+ interceptors.register(
134
+ 'refund_start',
135
+ async (payload, cmds) => {
136
+ if (payload.paymentTypes.includes('redeem')) {
137
+ return cmds.openExtensionOverlay({ point: 'refund_start', payload });
138
+ }
139
+ return true; // nothing for us to do
140
+ },
141
+ { interceptorId: 'my-extension:refund-guard' }
142
+ );
115
143
  ```
116
144
 
117
145
  ## Host-initiated extension refunds (redeem / gift card)
@@ -127,18 +155,12 @@ hooks.unregister('my-extension:cart-log');
127
155
  Exported APIs: `installExtensionRefundListener`, `EXTENSION_REFUND_REQUEST_ACTION`, types **`ExtensionRefundParams`** / **`ExtensionRefundResponse`**.
128
156
 
129
157
  ```typescript
130
- import {
131
- installExtensionRefundListener,
132
- type ExtensionRefundParams,
133
- type ExtensionRefundResponse
134
- } from '@final-commerce/command-frame';
158
+ import { installExtensionRefundListener, type ExtensionRefundParams, type ExtensionRefundResponse } from "@final-commerce/command-frame";
135
159
 
136
160
  const unsubscribe = installExtensionRefundListener(async (params: ExtensionRefundParams): Promise<ExtensionRefundResponse> => {
137
161
  // params.paymentType === "redeem", params.amount in major currency units, params.saleId, params.processor, etc.
138
162
  const ok = await myGiftCardProvider.refund(params);
139
- return ok
140
- ? { success: true, extensionTransactionId: ok.providerRefundId }
141
- : { success: false, error: 'Refund declined' };
163
+ return ok ? { success: true, extensionTransactionId: ok.providerRefundId } : { success: false, error: "Refund declined" };
142
164
  });
143
165
 
144
166
  // on teardown (optional)
@@ -1,5 +1,5 @@
1
1
  export * from "./common-types";
2
- import type { Tax, Tip, Address, MetadataItem, PosDataItem, CartFeeTaxEntry, CartFeeItem, CartDiscountItem, OrderNote, NonRevenueItem, DiscountDetail, FeeDetail, DiscountLineItem, FeeLineItem, LineItem, TipPayment, RefundedTipPayment, PaymentMethod, Discount, CustomFee, Summary, CustomSale, RefundedCustomSale, RefundItem, RefundedLineItem, ActiveStation, ActiveSession, ActiveOutlet, ActiveUser, ActiveUserRole, ActiveOrder, ActiveCart, ActivePark, ActiveCompany, ActiveCustomSales, ActiveProduct, ActiveCustomer, FullProduct, ProductVariant, Inventory, CustomerNote, Attribute, AttributeOption, Category, Transaction } from "@final-commerce/common/pos-types";
2
+ import type { Tax, Tip, Address, MetadataItem, PosDataItem, CartFeeTaxEntry, CartFeeItem, CartDiscountItem, OrderNote, NonRevenueItem, DiscountDetail, FeeDetail, DiscountLineItem, FeeLineItem, LineItem, TipPayment, RefundedTipPayment, PaymentMethod, Discount, CustomFee, Summary, CustomSale, RefundedCustomSale, RefundItem, RefundedLineItem, ActiveStation, ActiveSession, ActiveOutlet, ActiveUser, ActiveUserRole, ActiveOrder, ActiveCart, ActivePark, ActiveCompany, ActiveCustomSales, ActiveProduct, ActiveCustomer, FullProduct, ProductVariant, Inventory, CustomerNote, Attribute, AttributeOption, Category, Transaction, ActiveSplitPayment } from "@final-commerce/common/pos-types";
3
3
  export { CurrencyCode, ProductType as CFProductType, UserTypes as CFUserTypes } from "@final-commerce/common";
4
4
  /** Open record used as a base for active entities. */
5
5
  export type CFActiveEntity = Record<string, unknown>;
@@ -28,6 +28,7 @@ export type CFCartFeeItem = CartFeeItem;
28
28
  export type CFTipPayment = TipPayment;
29
29
  export type CFRefundedTipPayment = RefundedTipPayment;
30
30
  export type CFPaymentMethod = PaymentMethod;
31
+ export type CFSplitPayment = ActiveSplitPayment;
31
32
  export type CFPosDataItem = PosDataItem;
32
33
  export type CFDiscountDetail = DiscountDetail;
33
34
  export type CFFeeDetail = FeeDetail;
@@ -2,17 +2,20 @@ import { MOCK_CART, mockPublishEvent } from "../../demo/database";
2
2
  export const mockAddCartDiscount = async (params) => {
3
3
  console.log("[Mock] addCartDiscount called", params);
4
4
  if (params) {
5
+ // Mirror render: input is raw (50 = 50%, 5 = $5). Store percent as a
6
+ // fraction (0.5) and fixed as minor units (500), like the real handler.
7
+ const minorFactor = 10 ** (MOCK_CART.minorUnits ?? 2);
8
+ const value = params.isPercent ? params.amount / 100 : Math.round(params.amount * minorFactor);
5
9
  MOCK_CART.discount = {
6
- value: params.amount,
10
+ value,
7
11
  isPercent: params.isPercent,
8
12
  label: params.label
9
13
  };
10
- // Simple mock calc (not real logic)
11
14
  if (params.isPercent) {
12
15
  MOCK_CART.total = MOCK_CART.subtotal * (1 - params.amount / 100);
13
16
  }
14
17
  else {
15
- MOCK_CART.total = MOCK_CART.subtotal - params.amount;
18
+ MOCK_CART.total = MOCK_CART.subtotal - value;
16
19
  }
17
20
  MOCK_CART.amountToBeCharged = MOCK_CART.total;
18
21
  MOCK_CART.remainingBalance = MOCK_CART.total;
@@ -4,18 +4,18 @@ export const mockAddCartFee = async (params) => {
4
4
  if (params) {
5
5
  if (!MOCK_CART.customFee)
6
6
  MOCK_CART.customFee = [];
7
+ // Mirror render: input is raw (50 = 50%, 5 = $5). Store percent as a
8
+ // fraction (0.5) and fixed as minor units (500), like the real handler.
9
+ const minorFactor = 10 ** (MOCK_CART.minorUnits ?? 2);
10
+ const storedAmount = params.isPercent ? params.amount / 100 : Math.round(params.amount * minorFactor);
7
11
  MOCK_CART.customFee.push({
8
12
  label: params.label || "Fee",
9
- amount: params.amount,
13
+ amount: storedAmount,
10
14
  isPercent: params.isPercent || false,
11
15
  applyTaxes: params.applyTaxes || false,
12
16
  taxTableId: params.taxTableId
13
17
  });
14
- // Simple calc
15
- let feeAmount = params.amount;
16
- if (params.isPercent) {
17
- feeAmount = MOCK_CART.subtotal * (params.amount / 100);
18
- }
18
+ const feeAmount = params.isPercent ? MOCK_CART.subtotal * (params.amount / 100) : storedAmount;
19
19
  MOCK_CART.total += feeAmount;
20
20
  MOCK_CART.amountToBeCharged = MOCK_CART.total;
21
21
  MOCK_CART.remainingBalance = MOCK_CART.total;
@@ -1,11 +1,14 @@
1
- import { MOCK_CART } from "../../demo/database";
1
+ import { MOCK_CART, mockPublishEvent } from "../../demo/database";
2
2
  export const mockAddCustomSale = async (params) => {
3
3
  console.log("[Mock] addCustomSale called", params);
4
4
  if (!params)
5
5
  throw new Error("Params required");
6
6
  // Simple mock ID generation
7
7
  const mockId = 'sale_' + Math.random().toString(36).substr(2, 9);
8
- const price = Number(params.price);
8
+ // Mirror render: the flow sends raw dollars ($4); render does toMinorUnits.
9
+ // MOCK_CART tracks minor units, so convert here too.
10
+ const minorFactor = 10 ** (MOCK_CART.minorUnits ?? 2);
11
+ const price = Math.round(Number(params.price) * minorFactor);
9
12
  const quantity = 1; // Default to 1 for custom sale usually
10
13
  const customSale = {
11
14
  id: mockId,
@@ -24,6 +27,8 @@ export const mockAddCustomSale = async (params) => {
24
27
  MOCK_CART.total += price * quantity;
25
28
  MOCK_CART.amountToBeCharged = MOCK_CART.total;
26
29
  MOCK_CART.remainingBalance = MOCK_CART.total;
30
+ // Publish custom-sale-added event so cart subscribers refresh
31
+ mockPublishEvent('cart', 'custom-sale-added', { customSale });
27
32
  return {
28
33
  success: true,
29
34
  customSaleId: mockId,
@@ -1,4 +1,4 @@
1
- import { MOCK_CART } from "../../demo/database";
1
+ import { MOCK_CART, mockPublishEvent } from "../../demo/database";
2
2
  export const mockAddOrderNote = async (params) => {
3
3
  console.log("[Mock] addOrderNote called", params);
4
4
  // In Render, AddOrderNote usually adds a note to the active cart if it's not checked out yet.
@@ -9,6 +9,8 @@ export const mockAddOrderNote = async (params) => {
9
9
  else {
10
10
  MOCK_CART.orderNotes = params.note;
11
11
  }
12
+ // Publish order-note-added event so cart subscribers refresh.
13
+ mockPublishEvent("cart", "order-note-added", { note: params.note });
12
14
  }
13
15
  return {
14
16
  success: true,
@@ -0,0 +1,2 @@
1
+ import type { ApplyTransition } from "./types";
2
+ export declare const applyTransition: ApplyTransition;
@@ -0,0 +1,4 @@
1
+ import { commandFrameClient } from "../../client";
2
+ export const applyTransition = async (params) => {
3
+ return await commandFrameClient.call("applyTransition", params);
4
+ };
@@ -0,0 +1,6 @@
1
+ import type { ApplyTransitionParams, ApplyTransitionResponse } from "./types";
2
+ /**
3
+ * Mock implementation: applies all transitions except a few known-invalid ones
4
+ * so the demo app can show both successful and blocked responses.
5
+ */
6
+ export declare const applyTransitionMock: (params: ApplyTransitionParams) => Promise<ApplyTransitionResponse>;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Mock implementation: applies all transitions except a few known-invalid ones
3
+ * so the demo app can show both successful and blocked responses.
4
+ */
5
+ export const applyTransitionMock = (params) => {
6
+ const { to } = params;
7
+ if (to.payment === "refunded" && to.fulfillment === "draft") {
8
+ return Promise.resolve({
9
+ result: {
10
+ allowed: false,
11
+ blockedBy: "financial_invariant",
12
+ guard: "no-refund-in-draft",
13
+ reason: "Cannot refund an order that is still in draft"
14
+ }
15
+ });
16
+ }
17
+ if (to.payment === "paid" && to.fulfillment === "cancelled") {
18
+ return Promise.resolve({
19
+ result: {
20
+ allowed: false,
21
+ blockedBy: "cross_axis_rule",
22
+ guard: "no-pay-cancelled",
23
+ reason: "Cannot mark a cancelled order as paid"
24
+ }
25
+ });
26
+ }
27
+ return Promise.resolve({
28
+ result: { allowed: true },
29
+ from: { payment: "unpaid", fulfillment: "draft" },
30
+ to,
31
+ displayState: `${to.payment} / ${to.fulfillment}`
32
+ });
33
+ };
@@ -0,0 +1,17 @@
1
+ import type { CFStatePair, CFTransitionResult } from "../../common-types/order-state";
2
+ export interface ApplyTransitionParams {
3
+ /** Order to transition. */
4
+ orderId: string;
5
+ /** Target state pair. */
6
+ to: CFStatePair;
7
+ }
8
+ export interface ApplyTransitionResponse {
9
+ result: CFTransitionResult;
10
+ /** Previous state — present only when the transition was applied. */
11
+ from?: CFStatePair;
12
+ /** New state — present only when the transition was applied. */
13
+ to?: CFStatePair;
14
+ /** Computed display label — present only when the transition was applied. */
15
+ displayState?: string;
16
+ }
17
+ export type ApplyTransition = (params: ApplyTransitionParams) => Promise<ApplyTransitionResponse>;
@@ -0,0 +1,2 @@
1
+ import type { CanTransition } from "./types";
2
+ export declare const canTransition: CanTransition;
@@ -0,0 +1,4 @@
1
+ import { commandFrameClient } from "../../client";
2
+ export const canTransition = async (params) => {
3
+ return await commandFrameClient.call("canTransition", params);
4
+ };
@@ -0,0 +1,6 @@
1
+ import type { CanTransitionParams, CanTransitionResponse } from "./types";
2
+ /**
3
+ * Mock implementation: allows all transitions except a few known-invalid ones
4
+ * so the demo app can show both allowed and blocked responses.
5
+ */
6
+ export declare const canTransitionMock: (params: CanTransitionParams) => Promise<CanTransitionResponse>;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Mock implementation: allows all transitions except a few known-invalid ones
3
+ * so the demo app can show both allowed and blocked responses.
4
+ */
5
+ export const canTransitionMock = async (params) => {
6
+ const to = params.to;
7
+ if (to.payment === "refunded" && to.fulfillment === "draft") {
8
+ return {
9
+ result: {
10
+ allowed: false,
11
+ blockedBy: "financial_invariant",
12
+ guard: "no-refund-in-draft",
13
+ reason: "Cannot refund an order that is still in draft",
14
+ },
15
+ };
16
+ }
17
+ if (to.payment === "paid" && to.fulfillment === "cancelled") {
18
+ return {
19
+ result: {
20
+ allowed: false,
21
+ blockedBy: "cross_axis_rule",
22
+ guard: "no-pay-cancelled",
23
+ reason: "Cannot mark a cancelled order as paid",
24
+ },
25
+ };
26
+ }
27
+ return { result: { allowed: true } };
28
+ };
@@ -0,0 +1,11 @@
1
+ import type { CFStatePair, CFTransitionResult } from "../../common-types/order-state";
2
+ export interface CanTransitionParams {
3
+ /** Order to evaluate. If omitted, evaluates against a new order (from = null). */
4
+ orderId?: string;
5
+ /** Target state pair to transition to. */
6
+ to: CFStatePair;
7
+ }
8
+ export interface CanTransitionResponse {
9
+ result: CFTransitionResult;
10
+ }
11
+ export type CanTransition = (params: CanTransitionParams) => Promise<CanTransitionResponse>;
@@ -1,14 +1,14 @@
1
- import { createOrderFromCart, MOCK_CART, mockPublishEvent } from "../../demo/database";
1
+ import { applyMockPayment, MOCK_CART } from "../../demo/database";
2
2
  export const mockCashPayment = async (params) => {
3
3
  console.log("[Mock] cashPayment called", params);
4
- // Default to true to match action behavior
4
+ // Default to true to match action behavior.
5
5
  const openChangeCalculator = params?.openChangeCalculator ?? true;
6
- let amount = params?.amount || MOCK_CART.total;
6
+ // Amount due for THIS tender (the queued amount-to-be-charged), in minor units.
7
+ const due = params?.amount ?? MOCK_CART.amountToBeCharged ?? MOCK_CART.total;
7
8
  if (openChangeCalculator) {
8
9
  try {
9
- const input = window.prompt(`Total Due: $${MOCK_CART.total.toFixed(2)}\nEnter amount tendered:`, amount.toString());
10
+ const input = window.prompt(`Amount due: $${(due / 100).toFixed(2)}\nEnter amount tendered:`, (due / 100).toFixed(2));
10
11
  if (input === null) {
11
- // User cancelled
12
12
  return {
13
13
  success: false,
14
14
  amount: 0,
@@ -18,28 +18,22 @@ export const mockCashPayment = async (params) => {
18
18
  timestamp: new Date().toISOString()
19
19
  };
20
20
  }
21
- const tendered = parseFloat(input);
21
+ const tendered = parseFloat(input); // dollars
22
22
  if (!isNaN(tendered)) {
23
- amount = tendered;
24
- const change = tendered - MOCK_CART.total;
25
- if (change >= 0) {
26
- window.alert(`Change Due: $${change.toFixed(2)}`);
27
- }
28
- else {
29
- window.alert(`Warning: Tendered amount is less than total. Short by: $${Math.abs(change).toFixed(2)}`);
30
- }
23
+ const change = tendered - due / 100;
24
+ window.alert(change >= 0
25
+ ? `Change Due: $${change.toFixed(2)}`
26
+ : `Warning: Tendered is short by: $${Math.abs(change).toFixed(2)}`);
31
27
  }
32
28
  }
33
29
  catch (e) {
34
30
  console.warn("Could not open prompt/alert (possibly in non-interactive environment)", e);
35
31
  }
36
32
  }
37
- const order = createOrderFromCart("cash", amount, "cash");
38
- // Publish payment-done event
39
- mockPublishEvent('payments', 'payment-done', { order });
33
+ const order = applyMockPayment(due, "cash", "cash");
40
34
  return {
41
35
  success: true,
42
- amount,
36
+ amount: due,
43
37
  openChangeCalculator,
44
38
  paymentType: "cash",
45
39
  order,
@@ -1,9 +1,12 @@
1
1
  import { CFOrder } from "../../CommonTypes";
2
+ import type { CFTransitionResult } from "../../common-types/order-state";
2
3
  export interface CashPaymentParams {
3
4
  /** If not provided, uses the cart total. */
4
5
  amount?: number;
5
6
  /** Defaults to false. */
6
7
  openChangeCalculator?: boolean;
8
+ /** Override the fulfillment state after full payment. Render resolves the cascade. */
9
+ checkoutFulfillmentTarget?: string;
7
10
  }
8
11
  export interface CashPaymentResponse {
9
12
  success: boolean;
@@ -12,5 +15,7 @@ export interface CashPaymentResponse {
12
15
  paymentType: string;
13
16
  order: CFOrder | null;
14
17
  timestamp: string;
18
+ /** Present when the state machine blocked or forced the transition. */
19
+ transitionResult?: CFTransitionResult;
15
20
  }
16
21
  export type CashPayment = (params?: CashPaymentParams) => Promise<CashPaymentResponse>;
@@ -1,10 +1,12 @@
1
- import { MOCK_PARKED_ORDERS } from "../../demo/database";
1
+ import { MOCK_PARKED_ORDERS, mockPublishEvent } from "../../demo/database";
2
2
  export const mockDeleteParkedOrder = async (params) => {
3
3
  console.log("[Mock] deleteParkedOrder called", params);
4
4
  if (params?.orderId) {
5
5
  const index = MOCK_PARKED_ORDERS.findIndex(o => o._id === params.orderId);
6
6
  if (index !== -1) {
7
7
  MOCK_PARKED_ORDERS.splice(index, 1);
8
+ // Refresh order lists (the parked-order modal reads getOrders).
9
+ mockPublishEvent("orders", "parked-order-deleted", { orderId: params.orderId });
8
10
  }
9
11
  }
10
12
  return {
@@ -1,3 +1,4 @@
1
+ import type { CFTransitionResult } from "../../common-types/order-state";
1
2
  export interface DeleteParkedOrderParams {
2
3
  orderId: string;
3
4
  }
@@ -5,5 +6,7 @@ export interface DeleteParkedOrderResponse {
5
6
  success: boolean;
6
7
  orderId: string;
7
8
  timestamp: string;
9
+ /** Present when the state machine blocked or forced the transition. */
10
+ transitionResult?: CFTransitionResult;
8
11
  }
9
12
  export type DeleteParkedOrder = (params?: DeleteParkedOrderParams) => Promise<DeleteParkedOrderResponse>;
@@ -1,4 +1,5 @@
1
1
  import { CFOrder } from "../../CommonTypes";
2
+ import type { CFTransitionResult } from "../../common-types/order-state";
2
3
  /** Params for extension-initiated payments; host routes by `paymentType`. */
3
4
  export interface ExtensionPaymentParams {
4
5
  paymentType: string;
@@ -8,9 +9,11 @@ export interface ExtensionPaymentParams {
8
9
  referenceId?: string;
9
10
  extensionId?: string;
10
11
  metadata?: Record<string, unknown>;
11
- /** EMV tag string from an integration terminal; forwarded to the order PaymentMethod's `emv`. */
12
- emvData?: string;
13
- /** Processor fee for an integration payment; forwarded to the order PaymentMethod's `processorFee`. */
12
+ /** Override the fulfillment state after full payment. Render resolves the cascade. */
13
+ checkoutFulfillmentTarget?: string;
14
+ /** EMV data when the underlying payment carries one (typed as `IntegrationEmvData` by the integration wrapper). */
15
+ emvData?: unknown;
16
+ /** Processor fee in minor units; recorded on the order's paymentMethod.processorFee. */
14
17
  processorFee?: number;
15
18
  }
16
19
  export interface ExtensionPaymentResponse {
@@ -19,5 +22,7 @@ export interface ExtensionPaymentResponse {
19
22
  paymentType: string;
20
23
  order: CFOrder | null;
21
24
  timestamp: string;
25
+ /** Present when the state machine blocked or forced the transition. */
26
+ transitionResult?: CFTransitionResult;
22
27
  }
23
28
  export type ExtensionPayment = (params?: ExtensionPaymentParams) => Promise<ExtensionPaymentResponse>;
@@ -0,0 +1,2 @@
1
+ import type { GetAvailableTransitions } from "./types";
2
+ export declare const getAvailableTransitions: GetAvailableTransitions;
@@ -0,0 +1,4 @@
1
+ import { commandFrameClient } from "../../client";
2
+ export const getAvailableTransitions = async (params) => {
3
+ return await commandFrameClient.call("getAvailableTransitions", params);
4
+ };
@@ -0,0 +1,6 @@
1
+ import type { GetAvailableTransitionsParams, GetAvailableTransitionsResponse } from "./types";
2
+ /**
3
+ * Mock implementation: returns a fixed set of plausible transitions
4
+ * so the demo app has data to render.
5
+ */
6
+ export declare const getAvailableTransitionsMock: (_params: GetAvailableTransitionsParams) => Promise<GetAvailableTransitionsResponse>;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Mock implementation: returns a fixed set of plausible transitions
3
+ * so the demo app has data to render.
4
+ */
5
+ export const getAvailableTransitionsMock = async (_params) => ({
6
+ transitions: [
7
+ {
8
+ to: { payment: "refunded", fulfillment: "returned" },
9
+ displayLabel: "Full Refund",
10
+ conditions: [{ met: true, description: "Order is paid" }],
11
+ },
12
+ {
13
+ to: { payment: "partially_refunded", fulfillment: "partially_returned" },
14
+ displayLabel: "Partial Refund",
15
+ conditions: [{ met: true, description: "Order is paid" }],
16
+ },
17
+ {
18
+ to: { payment: "voided", fulfillment: "cancelled" },
19
+ displayLabel: "Void Order",
20
+ conditions: [
21
+ { met: true, description: "Order exists" },
22
+ { met: false, description: "No payments captured (mock: skipped)" },
23
+ ],
24
+ },
25
+ ],
26
+ });
@@ -0,0 +1,9 @@
1
+ import type { CFAvailableTransition } from "../../common-types/order-state";
2
+ export interface GetAvailableTransitionsParams {
3
+ /** Order to evaluate. Required — we need the current state. */
4
+ orderId: string;
5
+ }
6
+ export interface GetAvailableTransitionsResponse {
7
+ transitions: CFAvailableTransition[];
8
+ }
9
+ export type GetAvailableTransitions = (params: GetAvailableTransitionsParams) => Promise<GetAvailableTransitionsResponse>;
@@ -24,7 +24,7 @@ export const mockGetContext = () => {
24
24
  thousandSeparator: ",",
25
25
  decimalSeparator: ".",
26
26
  user: null,
27
- company: null,
27
+ company: { _id: MOCK_COMPANY.id, name: MOCK_COMPANY.name, logo: MOCK_COMPANY.logo },
28
28
  station: null,
29
29
  outlet: null,
30
30
  timestamp: new Date().toISOString()
@@ -1,8 +1,8 @@
1
- import { MOCK_ORDERS, MOCK_USERS, MOCK_STATIONS, MOCK_OUTLETS, safeSerialize } from "../../demo/database";
1
+ import { MOCK_ORDERS, MOCK_PARKED_ORDERS, MOCK_USERS, MOCK_STATIONS, MOCK_OUTLETS, safeSerialize } from "../../demo/database";
2
2
  export const mockGetOrders = async (params) => {
3
3
  console.log("[Mock] getOrders called", params);
4
- // Start with a safe copy of mock orders
5
- let orders = safeSerialize(MOCK_ORDERS);
4
+ // Start with a safe copy of mock orders, including parked (status: "parked").
5
+ let orders = safeSerialize([...MOCK_ORDERS, ...MOCK_PARKED_ORDERS]);
6
6
  // Filter simulation
7
7
  if (params) {
8
8
  const { customerId, status, sessionId, searchValue, limit, offset, sortBy, sortDirection } = params;
@@ -24,8 +24,13 @@ export const mockGetProducts = async (params) => {
24
24
  products = products.filter(p => (p.categories || []).some(c => c.name.includes(containsVal)));
25
25
  }
26
26
  }
27
+ const total = products.length;
28
+ const offset = params?.offset ?? 0;
29
+ const limit = params?.limit ?? 100;
30
+ const paged = products.slice(offset, offset + limit);
27
31
  return {
28
- products: safeSerialize(products),
32
+ products: safeSerialize(paged),
33
+ total,
29
34
  timestamp: new Date().toISOString()
30
35
  };
31
36
  };
@@ -29,6 +29,8 @@ export interface GetProductsParams {
29
29
  }
30
30
  export interface GetProductsResponse {
31
31
  products: CFProduct[];
32
+ /** Total number of products matching the query, ignoring offset/limit. Optional — hosts that cannot cheaply compute the total may omit it. */
33
+ total?: number;
32
34
  timestamp: string;
33
35
  }
34
36
  export type GetProducts = (params?: GetProductsParams) => Promise<GetProductsResponse>;
@@ -1,2 +1,2 @@
1
- import type { GetSmartGridLayout } from "./types";
1
+ import { GetSmartGridLayout } from "./types";
2
2
  export declare const mockGetSmartGridLayout: GetSmartGridLayout;
@@ -1,7 +1,9 @@
1
+ import { MOCK_SMART_GRID_LAYOUTS } from "../../demo/database";
1
2
  export const mockGetSmartGridLayout = (params) => {
3
+ console.log("[Mock] getSmartGridLayout called", params);
2
4
  return Promise.resolve({
3
5
  success: true,
4
- layout: { gridId: params.gridId, cells: [] },
6
+ layout: MOCK_SMART_GRID_LAYOUTS[params.gridId] ?? null,
5
7
  timestamp: new Date().toISOString()
6
8
  });
7
9
  };