@final-commerce/command-frame 0.3.0-beta.3 → 0.3.0-beta.6
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/README.md +50 -28
- package/dist/CommonTypes.d.ts +2 -1
- package/dist/actions/add-cart-discount/mock.js +6 -3
- package/dist/actions/add-cart-fee/mock.js +6 -6
- package/dist/actions/add-custom-sale/mock.js +7 -2
- package/dist/actions/add-order-note/mock.js +3 -1
- package/dist/actions/apply-transition/action.d.ts +2 -0
- package/dist/actions/apply-transition/action.js +4 -0
- package/dist/actions/apply-transition/mock.d.ts +6 -0
- package/dist/actions/apply-transition/mock.js +33 -0
- package/dist/actions/apply-transition/types.d.ts +17 -0
- package/dist/actions/can-transition/action.d.ts +2 -0
- package/dist/actions/can-transition/action.js +4 -0
- package/dist/actions/can-transition/mock.d.ts +6 -0
- package/dist/actions/can-transition/mock.js +28 -0
- package/dist/actions/can-transition/types.d.ts +11 -0
- package/dist/actions/cash-payment/mock.js +12 -18
- package/dist/actions/cash-payment/types.d.ts +5 -0
- package/dist/actions/delete-parked-order/mock.js +3 -1
- package/dist/actions/delete-parked-order/types.d.ts +3 -0
- package/dist/actions/extension-payment/types.d.ts +8 -3
- package/dist/actions/get-available-transitions/action.d.ts +2 -0
- package/dist/actions/get-available-transitions/action.js +4 -0
- package/dist/actions/get-available-transitions/mock.d.ts +6 -0
- package/dist/actions/get-available-transitions/mock.js +26 -0
- package/dist/actions/get-available-transitions/types.d.ts +9 -0
- package/dist/actions/get-context/mock.js +1 -1
- package/dist/actions/get-orders/mock.js +3 -3
- package/dist/actions/get-products/mock.js +6 -1
- package/dist/actions/get-products/types.d.ts +2 -0
- package/dist/actions/get-smart-grid-layout/mock.d.ts +1 -1
- package/dist/actions/get-smart-grid-layout/mock.js +3 -1
- package/dist/actions/get-smart-grid-layout/types.d.ts +1 -3
- package/dist/actions/initiate-refund/types.d.ts +3 -0
- package/dist/actions/integration-payment/types.d.ts +48 -8
- package/dist/actions/open-extension-overlay/mock.js +1 -1
- package/dist/actions/open-extension-overlay/types.d.ts +1 -1
- package/dist/actions/park-order/mock.js +4 -1
- package/dist/actions/park-order/types.d.ts +3 -0
- package/dist/actions/partial-payment/mock.js +21 -3
- package/dist/actions/partial-payment/types.d.ts +5 -0
- package/dist/actions/process-partial-refund/types.d.ts +3 -0
- package/dist/actions/redeem-payment/types.d.ts +9 -5
- package/dist/actions/remove-custom-sale/mock.d.ts +2 -0
- package/dist/actions/remove-custom-sale/mock.js +29 -0
- package/dist/actions/remove-customer-from-cart/mock.js +3 -1
- package/dist/actions/remove-order-note/mock.d.ts +2 -0
- package/dist/actions/remove-order-note/mock.js +12 -0
- package/dist/actions/remove-product-discount/mock.d.ts +2 -0
- package/dist/actions/remove-product-discount/mock.js +17 -0
- package/dist/actions/remove-product-fee/mock.d.ts +2 -0
- package/dist/actions/remove-product-fee/mock.js +17 -0
- package/dist/actions/remove-product-from-cart/mock.js +5 -5
- package/dist/actions/resolve-extension-overlay/mock.js +1 -1
- package/dist/actions/resume-parked-order/mock.js +4 -1
- package/dist/actions/resume-parked-order/types.d.ts +3 -0
- package/dist/actions/save-smart-grid-layout/mock.d.ts +1 -1
- package/dist/actions/save-smart-grid-layout/mock.js +3 -0
- package/dist/actions/save-smart-grid-layout/types.d.ts +1 -3
- package/dist/actions/tap-to-pay-payment/mock.js +5 -3
- package/dist/actions/tap-to-pay-payment/types.d.ts +5 -0
- package/dist/actions/terminal-payment/mock.js +4 -5
- package/dist/actions/terminal-payment/types.d.ts +5 -0
- package/dist/actions/update-cart-item-quantity/mock.js +9 -9
- package/dist/actions/vendara-payment/types.d.ts +5 -0
- package/dist/common-types/index.d.ts +1 -0
- package/dist/common-types/index.js +1 -0
- package/dist/common-types/order-state.d.ts +32 -0
- package/dist/common-types/order-state.js +6 -0
- package/dist/common-types/state-fragment.d.ts +77 -0
- package/dist/common-types/state-fragment.js +10 -0
- package/dist/demo/database.d.ts +12 -1
- package/dist/demo/database.js +173 -3
- package/dist/fragments/index.d.ts +1 -0
- package/dist/fragments/index.js +1 -0
- package/dist/fragments/preorder-no-deposit.d.ts +26 -0
- package/dist/fragments/preorder-no-deposit.js +166 -0
- package/dist/index.d.ts +21 -20
- package/dist/index.js +23 -21
- package/dist/interceptors/index.d.ts +21 -0
- package/dist/interceptors/index.js +53 -0
- package/dist/interceptors/types.d.ts +8 -16
- package/dist/projects/render/mocks.js +19 -17
- package/dist/projects/render/types.d.ts +7 -6
- package/dist/pubsub/topics/orders/index.js +10 -0
- package/dist/pubsub/topics/orders/state-transition-blocked/types.d.ts +16 -0
- package/dist/pubsub/topics/orders/state-transition-blocked/types.js +1 -0
- package/dist/pubsub/topics/orders/state-transition-completed/types.d.ts +15 -0
- package/dist/pubsub/topics/orders/state-transition-completed/types.js +1 -0
- package/dist/pubsub/topics/orders/types.d.ts +6 -2
- package/dist/pubsub/topics/orders/types.js +2 -0
- package/dist/pubsub/topics/split-payments/index.d.ts +12 -0
- package/dist/pubsub/topics/split-payments/index.js +22 -0
- package/dist/pubsub/topics/split-payments/split-payment-updated/types.d.ts +16 -0
- package/dist/pubsub/topics/split-payments/split-payment-updated/types.js +1 -0
- package/dist/pubsub/topics/split-payments/types.d.ts +10 -0
- package/dist/pubsub/topics/split-payments/types.js +5 -0
- package/dist/pubsub/topics/types.d.ts +2 -0
- package/package.json +3 -3
- package/dist/actions/extension-refund/constants.d.ts +0 -2
- package/dist/actions/extension-refund/constants.js +0 -2
- package/dist/actions/extension-refund/extension-refund-listener.d.ts +0 -11
- package/dist/actions/extension-refund/extension-refund-listener.js +0 -60
- package/dist/actions/extension-refund/types.d.ts +0 -32
- package/dist/actions/extension-refund/types.js +0 -5
- package/dist/actions/set-active-outlet/action.d.ts +0 -2
- package/dist/actions/set-active-outlet/action.js +0 -4
- package/dist/actions/set-active-outlet/mock.d.ts +0 -2
- package/dist/actions/set-active-outlet/mock.js +0 -16
- package/dist/actions/set-active-outlet/types.d.ts +0 -10
- package/dist/actions/set-active-session/action.d.ts +0 -2
- package/dist/actions/set-active-session/action.js +0 -4
- package/dist/actions/set-active-session/mock.d.ts +0 -2
- package/dist/actions/set-active-session/mock.js +0 -15
- package/dist/actions/set-active-session/types.d.ts +0 -10
- package/dist/actions/set-active-station/action.d.ts +0 -2
- package/dist/actions/set-active-station/action.js +0 -4
- package/dist/actions/set-active-station/mock.d.ts +0 -2
- package/dist/actions/set-active-station/mock.js +0 -16
- package/dist/actions/set-active-station/types.d.ts +0 -10
- /package/dist/actions/{set-active-outlet → apply-transition}/types.js +0 -0
- /package/dist/actions/{set-active-session → can-transition}/types.js +0 -0
- /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
|
|
14
|
-
|
|
15
|
-
| **Commands**
|
|
16
|
-
| **Pub/Sub**
|
|
17
|
-
| **Hooks**
|
|
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
|
|
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
|
|
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
|
|
86
|
+
import { topics } from "@final-commerce/command-frame";
|
|
86
87
|
|
|
87
|
-
const subscriptionId = topics.subscribe(
|
|
88
|
-
console.log(
|
|
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(
|
|
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
|
|
105
|
-
|
|
106
|
-
hooks.register(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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(
|
|
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)
|
package/dist/CommonTypes.d.ts
CHANGED
|
@@ -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
|
|
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 -
|
|
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:
|
|
13
|
+
amount: storedAmount,
|
|
10
14
|
isPercent: params.isPercent || false,
|
|
11
15
|
applyTaxes: params.applyTaxes || false,
|
|
12
16
|
taxTableId: params.taxTableId
|
|
13
17
|
});
|
|
14
|
-
|
|
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
|
-
|
|
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,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,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 {
|
|
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
|
-
|
|
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(`
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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 =
|
|
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
|
-
/**
|
|
12
|
-
|
|
13
|
-
/**
|
|
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,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:
|
|
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(
|
|
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
|
|
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:
|
|
6
|
+
layout: MOCK_SMART_GRID_LAYOUTS[params.gridId] ?? null,
|
|
5
7
|
timestamp: new Date().toISOString()
|
|
6
8
|
});
|
|
7
9
|
};
|