@final-commerce/command-frame 0.4.0-preprod.1 → 0.4.0-staging.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.
- package/README.md +30 -38
- package/dist/actions/add-product-to-cart/types.d.ts +13 -1
- package/dist/actions/apply-transition/mock.d.ts +1 -1
- package/dist/actions/apply-transition/mock.js +18 -8
- package/dist/actions/apply-transition/types.d.ts +4 -6
- package/dist/actions/extension-payment/types.d.ts +2 -2
- package/dist/actions/get-available-transitions/mock.d.ts +3 -5
- package/dist/actions/get-available-transitions/mock.js +12 -24
- package/dist/actions/integration-payment/types.d.ts +0 -2
- package/dist/actions/partial-payment/types.d.ts +2 -2
- package/dist/actions/redeem-payment/types.d.ts +0 -2
- package/dist/actions/terminal-payment/types.d.ts +3 -3
- package/dist/actions/update-cart-item-quantity/types.d.ts +8 -1
- package/dist/demo/database.d.ts +2 -3
- package/dist/demo/database.js +250 -310
- package/dist/index.d.ts +1 -3
- package/dist/index.js +0 -2
- package/dist/projects/render/mocks.js +0 -4
- package/dist/projects/render/types.d.ts +1 -3
- package/dist/pubsub/topics/orders/index.d.ts +2 -2
- package/dist/pubsub/topics/orders/index.js +24 -29
- package/dist/pubsub/topics/orders/types.d.ts +14 -16
- package/dist/pubsub/topics/orders/types.js +6 -7
- package/package.json +2 -2
- package/dist/actions/void-order/action.d.ts +0 -6
- package/dist/actions/void-order/action.js +0 -8
- package/dist/actions/void-order/mock.d.ts +0 -2
- package/dist/actions/void-order/mock.js +0 -23
- package/dist/actions/void-order/types.d.ts +0 -27
- package/dist/actions/void-order/types.js +0 -1
- package/dist/pubsub/topics/orders/order-voided/types.d.ts +0 -14
- package/dist/pubsub/topics/orders/order-voided/types.js +0 -1
package/README.md
CHANGED
|
@@ -71,7 +71,7 @@ For building applications that run inside the Render Point of Sale interface.
|
|
|
71
71
|
- **Features:** Order management, Product catalog, Customer management, Payments, Hardware integration (Cash drawer, Printer), Custom tables, Secrets storage.
|
|
72
72
|
|
|
73
73
|
```typescript
|
|
74
|
-
import { RenderClient } from
|
|
74
|
+
import { RenderClient } from "@final-commerce/command-frame";
|
|
75
75
|
|
|
76
76
|
const client = new RenderClient();
|
|
77
77
|
const products = await client.getProducts();
|
|
@@ -85,7 +85,7 @@ For building applications that run inside the Final Commerce Management Dashboar
|
|
|
85
85
|
- **Features:** Context, catalog, entities, custom tables, secrets, and optional host-specific commands (navigation, media, tax, branding, notifications) when the dashboard implements them.
|
|
86
86
|
|
|
87
87
|
```typescript
|
|
88
|
-
import { ManageClient } from
|
|
88
|
+
import { ManageClient } from "@final-commerce/command-frame";
|
|
89
89
|
|
|
90
90
|
const client = new ManageClient();
|
|
91
91
|
const context = await client.getContext();
|
|
@@ -96,17 +96,17 @@ const context = await client.getContext();
|
|
|
96
96
|
The pub/sub system allows iframe extensions to subscribe to topics and receive real-time events published by the host (Render). Subscriptions are **page-scoped** -- they fire only while the iframe is mounted on the current page.
|
|
97
97
|
|
|
98
98
|
- **[Pub/Sub Documentation](./src/pubsub/README.md)**
|
|
99
|
-
- **Topics:** Cart (16), Customers (8), Orders (
|
|
99
|
+
- **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).
|
|
100
100
|
|
|
101
101
|
```typescript
|
|
102
|
-
import { topics } from
|
|
102
|
+
import { topics } from "@final-commerce/command-frame";
|
|
103
103
|
|
|
104
|
-
const subscriptionId = topics.subscribe(
|
|
105
|
-
|
|
104
|
+
const subscriptionId = topics.subscribe("cart", event => {
|
|
105
|
+
console.log("Cart event:", event.type, event.data);
|
|
106
106
|
});
|
|
107
107
|
|
|
108
108
|
// Unsubscribe when done
|
|
109
|
-
topics.unsubscribe(
|
|
109
|
+
topics.unsubscribe("cart", subscriptionId);
|
|
110
110
|
```
|
|
111
111
|
|
|
112
112
|
## Hooks
|
|
@@ -118,21 +118,21 @@ Hooks are **session-scoped** event callbacks that run in the host (Render) conte
|
|
|
118
118
|
- A stable `hookId` is required for deduplication (safe on iframe reload).
|
|
119
119
|
|
|
120
120
|
```typescript
|
|
121
|
-
import { hooks } from
|
|
121
|
+
import { hooks } from "@final-commerce/command-frame";
|
|
122
122
|
|
|
123
123
|
hooks.register(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
124
|
+
"cart",
|
|
125
|
+
async (event, hostCommands) => {
|
|
126
|
+
await hostCommands.upsertCustomTableData({
|
|
127
|
+
tableName: "cart-events-log",
|
|
128
|
+
data: { eventType: event.type, payload: event.data, timestamp: event.timestamp }
|
|
129
|
+
});
|
|
130
|
+
},
|
|
131
|
+
{ hookId: "my-extension:cart-log" }
|
|
132
132
|
);
|
|
133
133
|
|
|
134
134
|
// Unregister when no longer needed
|
|
135
|
-
hooks.unregister(
|
|
135
|
+
hooks.unregister("my-extension:cart-log");
|
|
136
136
|
```
|
|
137
137
|
|
|
138
138
|
## Interceptors
|
|
@@ -147,14 +147,14 @@ Interceptors let an extension **gate a POS flow** (approve / modify / block) at
|
|
|
147
147
|
import { interceptors } from '@final-commerce/command-frame';
|
|
148
148
|
|
|
149
149
|
interceptors.register(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
150
|
+
'refund_start',
|
|
151
|
+
async (payload, cmds) => {
|
|
152
|
+
if (payload.paymentTypes.includes('redeem')) {
|
|
153
|
+
return cmds.openExtensionOverlay({ point: 'refund_start', payload });
|
|
154
|
+
}
|
|
155
|
+
return true; // nothing for us to do
|
|
156
|
+
},
|
|
157
|
+
{ interceptorId: 'my-extension:refund-guard' }
|
|
158
158
|
);
|
|
159
159
|
```
|
|
160
160
|
|
|
@@ -171,21 +171,13 @@ interceptors.register(
|
|
|
171
171
|
Exported APIs: `installExtensionRefundListener`, `EXTENSION_REFUND_REQUEST_ACTION`, types **`ExtensionRefundParams`** / **`ExtensionRefundResponse`**.
|
|
172
172
|
|
|
173
173
|
```typescript
|
|
174
|
-
import {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
type ExtensionRefundResponse,
|
|
178
|
-
} from '@final-commerce/command-frame';
|
|
179
|
-
|
|
180
|
-
const unsubscribe = installExtensionRefundListener(
|
|
181
|
-
async (params: ExtensionRefundParams): Promise<ExtensionRefundResponse> => {
|
|
174
|
+
import { installExtensionRefundListener, type ExtensionRefundParams, type ExtensionRefundResponse } from "@final-commerce/command-frame";
|
|
175
|
+
|
|
176
|
+
const unsubscribe = installExtensionRefundListener(async (params: ExtensionRefundParams): Promise<ExtensionRefundResponse> => {
|
|
182
177
|
// params.paymentType === "redeem", params.amount in major currency units, params.saleId, params.processor, etc.
|
|
183
178
|
const ok = await myGiftCardProvider.refund(params);
|
|
184
|
-
return ok
|
|
185
|
-
|
|
186
|
-
: { success: false, error: 'Refund declined' };
|
|
187
|
-
},
|
|
188
|
-
);
|
|
179
|
+
return ok ? { success: true, extensionTransactionId: ok.providerRefundId } : { success: false, error: "Refund declined" };
|
|
180
|
+
});
|
|
189
181
|
|
|
190
182
|
// on teardown (optional)
|
|
191
183
|
// unsubscribe();
|
|
@@ -3,7 +3,19 @@ import type { AddProductFeeParams } from "../add-product-fee/types";
|
|
|
3
3
|
export interface AddProductToCartParams {
|
|
4
4
|
/** ID of the variant to add. */
|
|
5
5
|
variantId: string;
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Defaults to 1. **May be fractional** — a variant sold by weight, volume or length is priced
|
|
8
|
+
* per its own unit and keyed in that unit, so `0.456` kg is a quantity, not a typo.
|
|
9
|
+
*
|
|
10
|
+
* How many decimals are allowed is the variant's own business: `variant.unit.precision` —
|
|
11
|
+
* `3` for a litre, `0` for anything sold by the piece. The engine refuses a quantity finer
|
|
12
|
+
* than that and says which unit it was measured against; do not round, floor or clamp before
|
|
13
|
+
* sending, and never key a quantity in base units to work around it (a per-100g price with a
|
|
14
|
+
* gram count is explicitly not supported).
|
|
15
|
+
*
|
|
16
|
+
* A quantity field should take its step and its decimal count from `variant.unit.precision`,
|
|
17
|
+
* not from a constant.
|
|
18
|
+
*/
|
|
7
19
|
quantity?: number;
|
|
8
20
|
/** Array of discounts to apply immediately. */
|
|
9
21
|
discounts?: AddProductDiscountParams[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ApplyTransitionParams, ApplyTransitionResponse } from "./types";
|
|
2
2
|
/**
|
|
3
|
-
* Mock implementation: applies all transitions except a known-invalid
|
|
3
|
+
* Mock implementation: applies all transitions except a few known-invalid ones
|
|
4
4
|
* so the demo app can show both successful and blocked responses.
|
|
5
5
|
*/
|
|
6
6
|
export declare const applyTransitionMock: (params: ApplyTransitionParams) => Promise<ApplyTransitionResponse>;
|
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Mock implementation: applies all transitions except a known-invalid
|
|
2
|
+
* Mock implementation: applies all transitions except a few known-invalid ones
|
|
3
3
|
* so the demo app can show both successful and blocked responses.
|
|
4
4
|
*/
|
|
5
5
|
export const applyTransitionMock = (params) => {
|
|
6
|
-
const {
|
|
7
|
-
if (
|
|
6
|
+
const { to } = params;
|
|
7
|
+
if (to.payment === "refunded" && to.fulfillment === "draft") {
|
|
8
8
|
return Promise.resolve({
|
|
9
9
|
result: {
|
|
10
10
|
allowed: false,
|
|
11
|
-
blockedBy: "
|
|
12
|
-
guard: "no-
|
|
13
|
-
reason: "Cannot
|
|
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"
|
|
14
24
|
}
|
|
15
25
|
});
|
|
16
26
|
}
|
|
17
27
|
return Promise.resolve({
|
|
18
28
|
result: { allowed: true },
|
|
19
29
|
from: { payment: "unpaid", fulfillment: "draft" },
|
|
20
|
-
to
|
|
21
|
-
displayState:
|
|
30
|
+
to,
|
|
31
|
+
displayState: `${to.payment} / ${to.fulfillment}`
|
|
22
32
|
});
|
|
23
33
|
};
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import type { CFStatePair, CFTransitionResult } from "../../common-types/order-state";
|
|
2
2
|
export interface ApplyTransitionParams {
|
|
3
|
-
/** Order to transition.
|
|
4
|
-
orderId
|
|
5
|
-
/** Target
|
|
6
|
-
|
|
7
|
-
/** Clear the terminal after transitioning the ACTIVE order (park parity). Default true. */
|
|
8
|
-
clearTerminal?: boolean;
|
|
3
|
+
/** Order to transition. */
|
|
4
|
+
orderId: string;
|
|
5
|
+
/** Target state pair. */
|
|
6
|
+
to: CFStatePair;
|
|
9
7
|
}
|
|
10
8
|
export interface ApplyTransitionResponse {
|
|
11
9
|
result: CFTransitionResult;
|
|
@@ -15,8 +15,8 @@ export interface ExtensionPaymentParams {
|
|
|
15
15
|
referenceId?: string;
|
|
16
16
|
extensionId?: string;
|
|
17
17
|
metadata?: Record<string, unknown>;
|
|
18
|
-
/** Override the fulfillment
|
|
19
|
-
|
|
18
|
+
/** Override the fulfillment state after full payment. Render resolves the cascade. */
|
|
19
|
+
checkoutFulfillmentTarget?: string;
|
|
20
20
|
/** EMV data when the underlying payment carries one (typed as `IntegrationEmvData` by the integration wrapper). */
|
|
21
21
|
emvData?: unknown;
|
|
22
22
|
/** Processor fee in integer MINOR currency units; recorded on the order's paymentMethod.processorFee. */
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { GetAvailableTransitionsParams, GetAvailableTransitionsResponse } from "./types";
|
|
2
2
|
/**
|
|
3
3
|
* Mock implementation: returns a fixed set of plausible transitions
|
|
4
|
-
* so the demo app has data to render.
|
|
5
|
-
* default (empty cross-axis rules) config: fulfillment can advance without
|
|
6
|
-
* payment (FI-6383), plus the classic park/void moves.
|
|
4
|
+
* so the demo app has data to render.
|
|
7
5
|
*/
|
|
8
|
-
export declare const getAvailableTransitionsMock:
|
|
6
|
+
export declare const getAvailableTransitionsMock: (_params: GetAvailableTransitionsParams) => Promise<GetAvailableTransitionsResponse>;
|
|
@@ -1,38 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Mock implementation: returns a fixed set of plausible transitions
|
|
3
|
-
* so the demo app has data to render.
|
|
4
|
-
* default (empty cross-axis rules) config: fulfillment can advance without
|
|
5
|
-
* payment (FI-6383), plus the classic park/void moves.
|
|
3
|
+
* so the demo app has data to render.
|
|
6
4
|
*/
|
|
7
|
-
export const getAvailableTransitionsMock = () =>
|
|
5
|
+
export const getAvailableTransitionsMock = async (_params) => ({
|
|
8
6
|
transitions: [
|
|
9
7
|
{
|
|
10
|
-
to: { payment: "
|
|
11
|
-
displayLabel: "
|
|
12
|
-
conditions: [{ met: true, description: "Order
|
|
8
|
+
to: { payment: "refunded", fulfillment: "returned" },
|
|
9
|
+
displayLabel: "Full Refund",
|
|
10
|
+
conditions: [{ met: true, description: "Order is paid" }],
|
|
13
11
|
},
|
|
14
12
|
{
|
|
15
|
-
to: { payment: "
|
|
16
|
-
displayLabel: "
|
|
17
|
-
conditions: [{ met: true, description: "Order
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
to: { payment: "unpaid", fulfillment: "on_hold" },
|
|
21
|
-
displayLabel: "Park Order",
|
|
22
|
-
conditions: [{ met: true, description: "Order is open" }]
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
to: { payment: "paid", fulfillment: "fulfilled" },
|
|
26
|
-
displayLabel: "Complete Payment",
|
|
27
|
-
conditions: [{ met: true, description: "Balance due > 0" }]
|
|
13
|
+
to: { payment: "partially_refunded", fulfillment: "partially_returned" },
|
|
14
|
+
displayLabel: "Partial Refund",
|
|
15
|
+
conditions: [{ met: true, description: "Order is paid" }],
|
|
28
16
|
},
|
|
29
17
|
{
|
|
30
18
|
to: { payment: "voided", fulfillment: "cancelled" },
|
|
31
19
|
displayLabel: "Void Order",
|
|
32
20
|
conditions: [
|
|
33
21
|
{ met: true, description: "Order exists" },
|
|
34
|
-
{ met: false, description: "No payments captured (mock: skipped)" }
|
|
35
|
-
]
|
|
36
|
-
}
|
|
37
|
-
]
|
|
22
|
+
{ met: false, description: "No payments captured (mock: skipped)" },
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
],
|
|
38
26
|
});
|
|
@@ -51,8 +51,6 @@ export interface IntegrationPaymentParams {
|
|
|
51
51
|
metadata?: Record<string, unknown>;
|
|
52
52
|
/** Provider fee in integer MINOR currency units — stored on paymentMethod.processorFee. */
|
|
53
53
|
processorFee?: number;
|
|
54
|
-
/** Override the fulfillment landing on full payment. Omitted: preserve advanced fulfillment, auto-fulfill from draft/pending/on_hold. */
|
|
55
|
-
targetFulfillmentState?: string;
|
|
56
54
|
}
|
|
57
55
|
export type IntegrationPaymentResponse = ExtensionPaymentResponse;
|
|
58
56
|
export type IntegrationPayment = (params: IntegrationPaymentParams) => Promise<IntegrationPaymentResponse>;
|
|
@@ -7,8 +7,8 @@ export interface PartialPaymentParams {
|
|
|
7
7
|
isPercent?: boolean;
|
|
8
8
|
/** If true, opens the split payment UI. */
|
|
9
9
|
openUI?: boolean;
|
|
10
|
-
/** Override the fulfillment
|
|
11
|
-
|
|
10
|
+
/** Override the fulfillment state after full payment. Render resolves the cascade. */
|
|
11
|
+
checkoutFulfillmentTarget?: string;
|
|
12
12
|
}
|
|
13
13
|
export interface PartialPaymentResponse {
|
|
14
14
|
success: boolean;
|
|
@@ -12,8 +12,6 @@ export interface RedeemPaymentParams {
|
|
|
12
12
|
processor?: string;
|
|
13
13
|
referenceId?: string;
|
|
14
14
|
metadata?: Record<string, unknown>;
|
|
15
|
-
/** Override the fulfillment landing on full payment. Omitted: preserve advanced fulfillment, auto-fulfill from draft/pending/on_hold. */
|
|
16
|
-
targetFulfillmentState?: string;
|
|
17
15
|
}
|
|
18
16
|
export type RedeemPaymentResponse = ExtensionPaymentResponse;
|
|
19
17
|
export type RedeemPayment = (params: RedeemPaymentParams) => Promise<RedeemPaymentResponse>;
|
|
@@ -12,9 +12,9 @@ export interface TerminalPaymentParams {
|
|
|
12
12
|
*/
|
|
13
13
|
amount: number;
|
|
14
14
|
/** "Bluetooth" or "Cloud". Defaults to "Cloud". */
|
|
15
|
-
paymentType?:
|
|
16
|
-
/** Override the fulfillment
|
|
17
|
-
|
|
15
|
+
paymentType?: 'Bluetooth' | 'Cloud';
|
|
16
|
+
/** Override the fulfillment state after full payment. Render resolves the cascade. */
|
|
17
|
+
checkoutFulfillmentTarget?: string;
|
|
18
18
|
}
|
|
19
19
|
export interface TerminalPaymentResponse {
|
|
20
20
|
success: boolean;
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
export interface UpdateCartItemQuantityParams {
|
|
2
2
|
/** The unique identifier for the specific cart item to update. */
|
|
3
3
|
internalId: string;
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* The new quantity. If set to 0, the item will be removed from the cart.
|
|
6
|
+
*
|
|
7
|
+
* **May be fractional** for a variant sold by measure; the number of decimals allowed comes
|
|
8
|
+
* from `variant.unit.precision`. The engine refuses anything finer and names the unit in the
|
|
9
|
+
* error. Do not round or clamp before sending — a silently altered quantity is charged and
|
|
10
|
+
* deducted differently than the one the cashier typed.
|
|
11
|
+
*/
|
|
5
12
|
quantity: number;
|
|
6
13
|
}
|
|
7
14
|
export interface UpdateCartItemQuantityResponse {
|
package/dist/demo/database.d.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Mock Database for Standalone/Demo Mode
|
|
3
3
|
* Stores mock data that mimics the Render environment
|
|
4
4
|
*/
|
|
5
|
-
import { CFActiveCompany, CFActiveUser, CFActiveStation, CFActiveOutlet, CFActiveOrder, CFCustomer, CFProduct, CFActiveCart, CFCategory, CFActiveProduct, CFSession, CFActiveRefundDetails, CFSmartGridLayout } from
|
|
6
|
-
export * from
|
|
5
|
+
import { CFActiveCompany, CFActiveUser, CFActiveStation, CFActiveOutlet, CFActiveOrder, CFCustomer, CFProduct, CFActiveCart, CFCategory, CFActiveProduct, CFSession, CFActiveRefundDetails, CFSmartGridLayout } from "../CommonTypes";
|
|
6
|
+
export * from "./mocks";
|
|
7
7
|
/** Replace mock catalog / context data in place (same array references mock handlers use). */
|
|
8
8
|
export interface MockDatabaseConfig {
|
|
9
9
|
company?: Partial<CFActiveCompany>;
|
|
@@ -51,7 +51,6 @@ export declare const MOCK_PRODUCT_BLACK_GARLIC: import("@final-commerce/common/p
|
|
|
51
51
|
export declare const MOCK_ORDER_1: CFActiveOrder;
|
|
52
52
|
export declare const MOCK_ORDER_2: CFActiveOrder;
|
|
53
53
|
export declare const MOCK_ORDER_3: CFActiveOrder;
|
|
54
|
-
export declare const MOCK_ORDER_4: CFActiveOrder;
|
|
55
54
|
export declare const MOCK_PARKED_ORDER_1: CFActiveOrder;
|
|
56
55
|
export declare const MOCK_PARKED_ORDER_2: CFActiveOrder;
|
|
57
56
|
export declare const MOCK_USERS: import("@final-commerce/common/pos-types").ActiveUser[];
|