@final-commerce/command-frame 0.4.2-preprod.1 → 0.5.0-preprod.10
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 +4 -55
- package/dist/CommonTypes.d.ts +1 -1
- package/dist/actions/add-product-fee/types.d.ts +1 -2
- package/dist/actions/adjust-inventory/mock.js +25 -21
- package/dist/actions/adjust-inventory/types.d.ts +1 -1
- package/dist/actions/authenticate-user/mock.js +4 -0
- package/dist/actions/authenticate-user/types.d.ts +13 -0
- package/dist/actions/calculate-refund-total/action.js +2 -2
- package/dist/actions/can-transition/types.d.ts +1 -1
- package/dist/actions/cash-payment/mock.js +6 -2
- package/dist/actions/cash-payment/types.d.ts +11 -7
- package/dist/actions/delete-parked-order/types.d.ts +0 -3
- package/dist/actions/extension-payment/mock.js +5 -1
- package/dist/actions/extension-payment/types.d.ts +10 -5
- package/dist/actions/get-custom-table-data/types.d.ts +2 -4
- package/dist/actions/get-final-context/mock.js +1 -1
- package/dist/actions/get-refund-plan/mock.d.ts +5 -0
- package/dist/actions/get-refund-plan/mock.js +24 -0
- package/dist/actions/get-refund-plan/types.d.ts +92 -0
- package/dist/actions/get-refund-plan/types.js +6 -0
- package/dist/actions/get-refunds/types.d.ts +1 -1
- package/dist/actions/get-remaining-refundable-quantities/action.js +2 -2
- package/dist/actions/get-remaining-refundable-quantities/mock.js +2 -0
- package/dist/actions/get-remaining-refundable-quantities/types.d.ts +13 -0
- package/dist/actions/initiate-refund/types.d.ts +6 -3
- package/dist/actions/integration-payment/types.d.ts +5 -5
- package/dist/actions/park-order/types.d.ts +0 -3
- package/dist/actions/partial-payment/types.d.ts +0 -5
- package/dist/actions/print/mock.js +2 -0
- package/dist/actions/print/types.d.ts +7 -0
- package/dist/actions/process-partial-refund/mock.d.ts +1 -1
- package/dist/actions/process-partial-refund/mock.js +14 -5
- package/dist/actions/process-partial-refund/types.d.ts +65 -8
- package/dist/actions/redeem-payment/types.d.ts +2 -2
- package/dist/actions/remove-product-fee/types.d.ts +10 -1
- package/dist/actions/resume-parked-order/types.d.ts +0 -3
- package/dist/actions/select-all-refund-items/action.js +2 -2
- package/dist/actions/set-active-refund/types.d.ts +1 -1
- package/dist/actions/set-refund-stock-action/types.d.ts +1 -1
- package/dist/actions/tap-to-pay-payment/mock.js +5 -1
- package/dist/actions/tap-to-pay-payment/types.d.ts +15 -7
- package/dist/actions/terminal-payment/mock.js +5 -1
- package/dist/actions/terminal-payment/types.d.ts +11 -6
- package/dist/actions/upsert-custom-table-data/types.d.ts +11 -1
- package/dist/actions/void-order/types.d.ts +4 -6
- package/dist/index.d.ts +2 -10
- package/dist/index.js +0 -6
- package/dist/projects/render/mocks.js +0 -6
- package/dist/projects/render/types.d.ts +1 -6
- package/package.json +1 -1
- package/dist/actions/open-extension-overlay/action.d.ts +0 -2
- package/dist/actions/open-extension-overlay/action.js +0 -4
- package/dist/actions/open-extension-overlay/mock.d.ts +0 -2
- package/dist/actions/open-extension-overlay/mock.js +0 -1
- package/dist/actions/open-extension-overlay/types.d.ts +0 -9
- package/dist/actions/open-extension-overlay/types.js +0 -1
- package/dist/actions/resolve-extension-overlay/action.d.ts +0 -2
- package/dist/actions/resolve-extension-overlay/action.js +0 -4
- package/dist/actions/resolve-extension-overlay/mock.d.ts +0 -2
- package/dist/actions/resolve-extension-overlay/mock.js +0 -1
- package/dist/actions/resolve-extension-overlay/types.d.ts +0 -9
- package/dist/actions/resolve-extension-overlay/types.js +0 -1
- package/dist/actions/vendara-payment/action.d.ts +0 -6
- package/dist/actions/vendara-payment/action.js +0 -8
- package/dist/actions/vendara-payment/mock.d.ts +0 -2
- package/dist/actions/vendara-payment/mock.js +0 -11
- package/dist/actions/vendara-payment/types.d.ts +0 -26
- package/dist/actions/vendara-payment/types.js +0 -1
- package/dist/hooks/index.d.ts +0 -17
- package/dist/hooks/index.js +0 -39
- package/dist/hooks/types.d.ts +0 -15
- package/dist/hooks/types.js +0 -1
- package/dist/interceptors/index.d.ts +0 -21
- package/dist/interceptors/index.js +0 -53
- package/dist/interceptors/types.d.ts +0 -26
- package/dist/interceptors/types.js +0 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ A TypeScript library for type-safe communication between iframes and their paren
|
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
Command Frame provides a structured way to build integrations that run inside Final Commerce applications (
|
|
7
|
+
Command Frame provides a structured way to build integrations that run inside Final Commerce applications (the kaching POS runtime or the Manage Dashboard). It handles the underlying `postMessage` communication while enforcing strict type safety for both the host application (Provider) and the embedded app (Client).
|
|
8
8
|
|
|
9
9
|
`RenderClient` and `ManageClient` extend `CommandFrameClient`: dynamic methods such as `getProducts()` map to `postMessage` actions named after the method (camelCase), with typed params and responses per project.
|
|
10
10
|
|
|
@@ -14,8 +14,6 @@ The library provides three main capabilities:
|
|
|
14
14
|
| ------------------- | ------------------------------------------------------------------------- | ----------------------------------------- |
|
|
15
15
|
| **Commands** | Call host functions from the iframe (e.g. get products, open cash drawer) | Request/response per call |
|
|
16
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 |
|
|
19
17
|
| **Refund commands** | Refund payments to gift cards or redeem tenders via `redeemRefund`, or mixed-destination legs on `processPartialRefund`; query engine capacity with `getRefundPlan`; pre-gate UI with `checkPermission` (`issue_refunds` is enforced runtime-side) | Request/response per call |
|
|
20
18
|
|
|
21
19
|
Domain models (orders, cart, customers, products, and related types) are documented in **[Types reference](./src/types/README.md)**.
|
|
@@ -65,7 +63,7 @@ true` → `amount` is `0–100`), and quantities.
|
|
|
65
63
|
|
|
66
64
|
### Render (POS System)
|
|
67
65
|
|
|
68
|
-
For building applications that run inside the
|
|
66
|
+
For building applications that run inside the Final Commerce POS (the kaching runtime).
|
|
69
67
|
|
|
70
68
|
- **[Render Documentation](./src/projects/render/README.md)**
|
|
71
69
|
- **Features:** Order management, Product catalog, Customer management, Payments, Hardware integration (Cash drawer, Printer), Custom tables, Secrets storage.
|
|
@@ -93,10 +91,10 @@ const context = await client.getContext();
|
|
|
93
91
|
|
|
94
92
|
## Pub/Sub
|
|
95
93
|
|
|
96
|
-
The pub/sub system allows iframe extensions to subscribe to topics and receive real-time events published by the host (
|
|
94
|
+
The pub/sub system allows iframe extensions to subscribe to topics and receive real-time events published by the POS host (kaching). Subscriptions are **page-scoped** -- they fire only while the iframe is mounted on the current page.
|
|
97
95
|
|
|
98
96
|
- **[Pub/Sub Documentation](./src/pubsub/README.md)**
|
|
99
|
-
- **Topics:** Cart (16), Customers (8), Orders (
|
|
97
|
+
- **Topics:** Cart (16), Customers (8), Orders (7), Payments (2), Products (4), Refunds (4), Print (3), Custom Tables (3), Outlet (2), Station (2), Session (2), Users (4), Variants (2), Transactions (2), Categories (2), Attributes (2), Split Payments (1).
|
|
100
98
|
|
|
101
99
|
```typescript
|
|
102
100
|
import { topics } from '@final-commerce/command-frame';
|
|
@@ -109,55 +107,6 @@ const subscriptionId = topics.subscribe('cart', (event) => {
|
|
|
109
107
|
topics.unsubscribe('cart', subscriptionId);
|
|
110
108
|
```
|
|
111
109
|
|
|
112
|
-
## Hooks
|
|
113
|
-
|
|
114
|
-
Hooks are **session-scoped** event callbacks that run in the host (Render) context and persist across all page navigations -- even when the extension iframe is no longer on the current page. Use hooks for business logic that must run on every event (e.g. logging to custom tables, triggering webhooks).
|
|
115
|
-
|
|
116
|
-
- **[Hooks Documentation](./src/hooks/README.md)**
|
|
117
|
-
- The callback is serialized and sent to the host; it must be **self-contained** (no closures, no imports).
|
|
118
|
-
- A stable `hookId` is required for deduplication (safe on iframe reload).
|
|
119
|
-
|
|
120
|
-
```typescript
|
|
121
|
-
import { hooks } from '@final-commerce/command-frame';
|
|
122
|
-
|
|
123
|
-
hooks.register(
|
|
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
|
-
);
|
|
133
|
-
|
|
134
|
-
// Unregister when no longer needed
|
|
135
|
-
hooks.unregister('my-extension:cart-log');
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
## Interceptors
|
|
139
|
-
|
|
140
|
-
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**.
|
|
141
|
-
|
|
142
|
-
- **[Interceptors Documentation](./src/interceptors/README.md)**
|
|
143
|
-
- The callback is serialized and reconstructed on the host; it must be **self-contained** (no closures, no imports).
|
|
144
|
-
- A stable `interceptorId` is required for deduplication (safe on iframe reload).
|
|
145
|
-
|
|
146
|
-
```typescript
|
|
147
|
-
import { interceptors } from '@final-commerce/command-frame';
|
|
148
|
-
|
|
149
|
-
interceptors.register(
|
|
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
|
-
);
|
|
159
|
-
```
|
|
160
|
-
|
|
161
110
|
## Refunding redeem / extension payments
|
|
162
111
|
|
|
163
112
|
When staff refund an order that was paid with `paymentType: "redeem"` (via `redeemPayment` or `extensionPayment`), use the **`redeemRefund`** command to refund the amount onto a gift card or redeem tender.
|
package/dist/CommonTypes.d.ts
CHANGED
|
@@ -96,7 +96,7 @@ export interface CFActiveRefundDetails {
|
|
|
96
96
|
refundProcessingStatus?: CFRefundProcessingStatus | null;
|
|
97
97
|
sessionRefundedTotal?: number;
|
|
98
98
|
}
|
|
99
|
-
export type CFProjectName = "
|
|
99
|
+
export type CFProjectName = "kaching" | "Manage";
|
|
100
100
|
export interface CFContextRender {
|
|
101
101
|
userId: string | null;
|
|
102
102
|
companyId: string | null;
|
|
@@ -5,9 +5,8 @@ export interface AddProductFeeParams {
|
|
|
5
5
|
isPercent?: boolean;
|
|
6
6
|
/** Defaults to "Fee". */
|
|
7
7
|
label?: string;
|
|
8
|
-
/** Defaults to `false`. */
|
|
8
|
+
/** Defaults to `false`. When true, the fee inherits the target cart line's own tax table (a caller-supplied tax table is not honored). */
|
|
9
9
|
applyTaxes?: boolean;
|
|
10
|
-
taxTableId?: string;
|
|
11
10
|
/** The unique identifier for a specific line item in the cart. */
|
|
12
11
|
internalId?: string;
|
|
13
12
|
}
|
|
@@ -2,29 +2,33 @@ import { MOCK_PRODUCTS } from "../../demo/database";
|
|
|
2
2
|
export const mockAdjustInventory = async (params) => {
|
|
3
3
|
console.log("[Mock] adjustInventory called", params);
|
|
4
4
|
let newStock = 0;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (params.stockType === 'add') {
|
|
16
|
-
newStock = currentStock + changeAmount;
|
|
17
|
-
}
|
|
18
|
-
else if (params.stockType === 'subtract') {
|
|
19
|
-
newStock = currentStock - changeAmount;
|
|
20
|
-
}
|
|
21
|
-
else {
|
|
22
|
-
newStock = changeAmount;
|
|
23
|
-
}
|
|
24
|
-
// Update mock DB
|
|
25
|
-
variant.inventory[0].stock = newStock;
|
|
5
|
+
// Real handler keys off variantId (falling back to the active product's
|
|
6
|
+
// variant); mirror that here by finding the owning product for the variant.
|
|
7
|
+
if (params && params.variantId) {
|
|
8
|
+
const variantId = params.variantId;
|
|
9
|
+
let variant;
|
|
10
|
+
for (const product of MOCK_PRODUCTS) {
|
|
11
|
+
const match = product.variants.find(v => v._id === variantId);
|
|
12
|
+
if (match) {
|
|
13
|
+
variant = match;
|
|
14
|
+
break;
|
|
26
15
|
}
|
|
27
16
|
}
|
|
17
|
+
if (variant && variant.inventory && variant.inventory.length > 0) {
|
|
18
|
+
const currentStock = variant.inventory[0].stock || 0;
|
|
19
|
+
const changeAmount = Number(params.amount);
|
|
20
|
+
if (params.stockType === 'add') {
|
|
21
|
+
newStock = currentStock + changeAmount;
|
|
22
|
+
}
|
|
23
|
+
else if (params.stockType === 'subtract') {
|
|
24
|
+
newStock = currentStock - changeAmount;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
newStock = changeAmount;
|
|
28
|
+
}
|
|
29
|
+
// Update mock DB
|
|
30
|
+
variant.inventory[0].stock = newStock;
|
|
31
|
+
}
|
|
28
32
|
}
|
|
29
33
|
return {
|
|
30
34
|
success: true,
|
|
@@ -3,8 +3,8 @@ export interface AdjustInventoryParams {
|
|
|
3
3
|
amount: string;
|
|
4
4
|
/** 'add' (increase), 'subtract' (decrease), or 'set' (recount). */
|
|
5
5
|
stockType: 'add' | 'subtract' | 'set';
|
|
6
|
+
/** Variant to adjust. Omit to use the active product's selected variant. */
|
|
6
7
|
variantId?: string;
|
|
7
|
-
productId?: string;
|
|
8
8
|
}
|
|
9
9
|
export interface AdjustInventoryResponse {
|
|
10
10
|
success: boolean;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export const mockAuthenticateUser = async (params) => {
|
|
2
2
|
console.log("[Mock] authenticateUser called", params);
|
|
3
|
+
// Headless path requires both userId and pin together (mirrors the handler guard).
|
|
4
|
+
if ((params?.userId || params?.pin) && !(params?.userId && params?.pin)) {
|
|
5
|
+
throw new Error("userId and pin must be provided together");
|
|
6
|
+
}
|
|
3
7
|
return {
|
|
4
8
|
success: true,
|
|
5
9
|
roleIds: params?.roleIds || [],
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
export interface AuthenticateUserParams {
|
|
2
|
+
/** Role IDs authorized for the gated action. The authenticating user must hold one of these. */
|
|
2
3
|
roleIds: string[];
|
|
4
|
+
/**
|
|
5
|
+
* Headless validation: the id of the user whose PIN to verify. Provide together
|
|
6
|
+
* with `pin` to validate the credentials in-process and return success/failure
|
|
7
|
+
* WITHOUT showing kaching's PIN modal (the flow owns its own PIN UI). When
|
|
8
|
+
* omitted, the modal flow is used. Must be paired with `pin`.
|
|
9
|
+
*/
|
|
10
|
+
userId?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Headless validation: the PIN to check against `userId`'s stored pincode.
|
|
13
|
+
* Must be paired with `userId`. The user must also hold one of `roleIds`.
|
|
14
|
+
*/
|
|
15
|
+
pin?: string;
|
|
3
16
|
}
|
|
4
17
|
export interface AuthenticateUserResponse {
|
|
5
18
|
success: boolean;
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
* Calls the calculateRefundTotal action on the parent window
|
|
4
4
|
*/
|
|
5
5
|
import { commandFrameClient } from "../../client";
|
|
6
|
-
export const calculateRefundTotal = async () => {
|
|
7
|
-
return await commandFrameClient.call("calculateRefundTotal");
|
|
6
|
+
export const calculateRefundTotal = async (params) => {
|
|
7
|
+
return await commandFrameClient.call("calculateRefundTotal", params);
|
|
8
8
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CFStatePair, CFTransitionResult } from "../../common-types/order-state";
|
|
2
2
|
export interface CanTransitionParams {
|
|
3
|
-
/** Order to evaluate.
|
|
3
|
+
/** Order to evaluate. Defaults to the active order; if there is no active order (or none matches), evaluates as a brand-new order (from = null). */
|
|
4
4
|
orderId?: string;
|
|
5
5
|
/** Target state pair to transition to. */
|
|
6
6
|
to: CFStatePair;
|
|
@@ -11,7 +11,9 @@ export const mockCashPayment = async (params) => {
|
|
|
11
11
|
cashRounding: 0,
|
|
12
12
|
paymentType: "cash",
|
|
13
13
|
order: null,
|
|
14
|
-
timestamp: new Date().toISOString()
|
|
14
|
+
timestamp: new Date().toISOString(),
|
|
15
|
+
saleFinalized: false,
|
|
16
|
+
remainingBalance: 0
|
|
15
17
|
};
|
|
16
18
|
};
|
|
17
19
|
// Contract: `amount` is required, integer MINOR currency units — same
|
|
@@ -55,6 +57,8 @@ export const mockCashPayment = async (params) => {
|
|
|
55
57
|
cashRounding: 0,
|
|
56
58
|
paymentType: "cash",
|
|
57
59
|
order,
|
|
58
|
-
timestamp: new Date().toISOString()
|
|
60
|
+
timestamp: new Date().toISOString(),
|
|
61
|
+
saleFinalized: true,
|
|
62
|
+
remainingBalance: 0
|
|
59
63
|
};
|
|
60
64
|
};
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { CFOrder } from "../../CommonTypes";
|
|
2
|
-
import type { CFTransitionResult } from "../../common-types/order-state";
|
|
3
2
|
export interface CashPaymentParams {
|
|
4
3
|
/**
|
|
5
4
|
* The amount to pay with this tender, in integer MINOR currency units
|
|
6
5
|
* (e.g. 1575 = $15.75 — see `getContext().minorUnits` for the currency's
|
|
7
|
-
* exponent). Required
|
|
8
|
-
*
|
|
6
|
+
* exponent). Required whenever the balance due is greater than $0; may be
|
|
7
|
+
* omitted only on a cart that already nets to a $0 balance due (e.g. fully
|
|
8
|
+
* discounted), where it defaults to 0. Semantics against the cart's
|
|
9
|
+
* balance due:
|
|
10
|
+
* - missing → error, unless the balance due is $0 (→ 0)
|
|
9
11
|
* - less than balance → partial payment (the POS enters a fixed
|
|
10
12
|
* split-payment leg for this amount)
|
|
11
13
|
* - equal to balance → full payment
|
|
12
14
|
* - more than balance → error (overpayment is `tenderedAmount`'s job)
|
|
13
15
|
*/
|
|
14
|
-
amount
|
|
16
|
+
amount?: number;
|
|
15
17
|
/**
|
|
16
18
|
* Cash physically handed over by the customer, in integer MINOR currency
|
|
17
19
|
* units. When provided, the POS computes the change itself (after applying
|
|
@@ -29,7 +31,7 @@ export interface CashPaymentParams {
|
|
|
29
31
|
* POS still opens its legacy change-calculator modal.
|
|
30
32
|
*/
|
|
31
33
|
openChangeCalculator?: boolean;
|
|
32
|
-
/** Override the fulfillment state after full payment.
|
|
34
|
+
/** Override the fulfillment state after full payment. kaching resolves the cascade. */
|
|
33
35
|
checkoutFulfillmentTarget?: string;
|
|
34
36
|
}
|
|
35
37
|
export interface CashPaymentResponse {
|
|
@@ -56,7 +58,9 @@ export interface CashPaymentResponse {
|
|
|
56
58
|
paymentType: string;
|
|
57
59
|
order: CFOrder | null;
|
|
58
60
|
timestamp: string;
|
|
59
|
-
/**
|
|
60
|
-
|
|
61
|
+
/** True when this tender settled the cart's remaining balance (the sale completed). */
|
|
62
|
+
saleFinalized: boolean;
|
|
63
|
+
/** Balance still due after this tender, in integer MINOR currency units (0 once the sale is finalized). */
|
|
64
|
+
remainingBalance: number;
|
|
61
65
|
}
|
|
62
66
|
export type CashPayment = (params: CashPaymentParams) => Promise<CashPaymentResponse>;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { CFTransitionResult } from "../../common-types/order-state";
|
|
2
1
|
export interface DeleteParkedOrderParams {
|
|
3
2
|
orderId: string;
|
|
4
3
|
}
|
|
@@ -6,7 +5,5 @@ export interface DeleteParkedOrderResponse {
|
|
|
6
5
|
success: boolean;
|
|
7
6
|
orderId: string;
|
|
8
7
|
timestamp: string;
|
|
9
|
-
/** Present when the state machine blocked or forced the transition. */
|
|
10
|
-
transitionResult?: CFTransitionResult;
|
|
11
8
|
}
|
|
12
9
|
export type DeleteParkedOrder = (params?: DeleteParkedOrderParams) => Promise<DeleteParkedOrderResponse>;
|
|
@@ -6,6 +6,10 @@ export const mockExtensionPayment = async (params) => {
|
|
|
6
6
|
amount: params?.amount ?? null,
|
|
7
7
|
paymentType,
|
|
8
8
|
order: MOCK_ORDERS[0],
|
|
9
|
-
timestamp: new Date().toISOString()
|
|
9
|
+
timestamp: new Date().toISOString(),
|
|
10
|
+
change: 0,
|
|
11
|
+
cashRounding: 0,
|
|
12
|
+
saleFinalized: true,
|
|
13
|
+
remainingBalance: 0
|
|
10
14
|
};
|
|
11
15
|
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { CFOrder } from "../../CommonTypes";
|
|
2
|
-
import type { CFTransitionResult } from "../../common-types/order-state";
|
|
3
2
|
/** Params for extension-initiated payments; host routes by `paymentType`. */
|
|
4
3
|
export interface ExtensionPaymentParams {
|
|
5
4
|
paymentType: string;
|
|
@@ -15,8 +14,8 @@ export interface ExtensionPaymentParams {
|
|
|
15
14
|
referenceId?: string;
|
|
16
15
|
extensionId?: string;
|
|
17
16
|
metadata?: Record<string, unknown>;
|
|
18
|
-
/** Override the fulfillment
|
|
19
|
-
|
|
17
|
+
/** Override the fulfillment state the order lands in on full payment (validated against the fulfillment state machine; invalid values throw). Omitted: preserve advanced fulfillment, auto-fulfill from draft/pending/on_hold. */
|
|
18
|
+
checkoutFulfillmentTarget?: string;
|
|
20
19
|
/** EMV data when the underlying payment carries one (typed as `IntegrationEmvData` by the integration wrapper). */
|
|
21
20
|
emvData?: unknown;
|
|
22
21
|
/** Processor fee in integer MINOR currency units; recorded on the order's paymentMethod.processorFee. */
|
|
@@ -28,7 +27,13 @@ export interface ExtensionPaymentResponse {
|
|
|
28
27
|
paymentType: string;
|
|
29
28
|
order: CFOrder | null;
|
|
30
29
|
timestamp: string;
|
|
31
|
-
/**
|
|
32
|
-
|
|
30
|
+
/** Change due back to the customer in integer MINOR currency units (0 for non-cash tenders). */
|
|
31
|
+
change: number;
|
|
32
|
+
/** Signed cash-rounding delta applied to the charge, in integer MINOR currency units (positive = rounded up); 0 when the company has no cash-rounding setting. */
|
|
33
|
+
cashRounding: number;
|
|
34
|
+
/** True when this tender settled the cart's remaining balance (the sale completed). */
|
|
35
|
+
saleFinalized: boolean;
|
|
36
|
+
/** Balance still due after this tender, in integer MINOR currency units (0 once the sale is finalized). */
|
|
37
|
+
remainingBalance: number;
|
|
33
38
|
}
|
|
34
39
|
export type ExtensionPayment = (params?: ExtensionPaymentParams) => Promise<ExtensionPaymentResponse>;
|
|
@@ -4,10 +4,8 @@ export interface GetCustomTableDataResponse<T = any> {
|
|
|
4
4
|
timestamp: string;
|
|
5
5
|
}
|
|
6
6
|
export interface GetCustomTableDataParams {
|
|
7
|
-
/** Table name (kebab-case). Required if
|
|
8
|
-
tableName
|
|
9
|
-
/** Table ID. Required if tableName is not provided. */
|
|
10
|
-
tableId?: string;
|
|
7
|
+
/** Table name (kebab-case). Required — the handler throws if it is absent. */
|
|
8
|
+
tableName: string;
|
|
11
9
|
/** Optional query filter */
|
|
12
10
|
query?: any;
|
|
13
11
|
/** Pagination offset */
|
|
@@ -10,5 +10,10 @@ import { GetRefundPlan } from './types';
|
|
|
10
10
|
* `maxRefundable` as the full captured amount. Against real kaching those
|
|
11
11
|
* numbers come from `order.refund[]` and the capture's `emv` JSON. Use this
|
|
12
12
|
* only to shape UI in local/standalone mode — never to assert real capacity.
|
|
13
|
+
*
|
|
14
|
+
* `allocation` is likewise shape-only: the demo has no refund SELECTION state
|
|
15
|
+
* and no company cash-rounding setting, so it always describes a FULL refund
|
|
16
|
+
* with `rounding: 0` and no cash `payout`. Against real kaching the allocation
|
|
17
|
+
* tracks the live selection and carries the drawer snap.
|
|
13
18
|
*/
|
|
14
19
|
export declare const mockGetRefundPlan: GetRefundPlan;
|
|
@@ -10,6 +10,11 @@ import { MOCK_ORDERS } from '../../demo/database';
|
|
|
10
10
|
* `maxRefundable` as the full captured amount. Against real kaching those
|
|
11
11
|
* numbers come from `order.refund[]` and the capture's `emv` JSON. Use this
|
|
12
12
|
* only to shape UI in local/standalone mode — never to assert real capacity.
|
|
13
|
+
*
|
|
14
|
+
* `allocation` is likewise shape-only: the demo has no refund SELECTION state
|
|
15
|
+
* and no company cash-rounding setting, so it always describes a FULL refund
|
|
16
|
+
* with `rounding: 0` and no cash `payout`. Against real kaching the allocation
|
|
17
|
+
* tracks the live selection and carries the drawer snap.
|
|
13
18
|
*/
|
|
14
19
|
export const mockGetRefundPlan = async (params) => {
|
|
15
20
|
console.log('[Mock] getRefundPlan called', params);
|
|
@@ -45,10 +50,29 @@ export const mockGetRefundPlan = async (params) => {
|
|
|
45
50
|
});
|
|
46
51
|
const totalCaptured = sources.reduce((sum, s) => sum + s.capturedAmount, 0);
|
|
47
52
|
const nonRefundableLiability = order.summary?.nonRevenueTotal ?? 0;
|
|
53
|
+
// Full-refund legs: every source returns its whole capture (the demo's
|
|
54
|
+
// `maxRefundable`), which is exactly the shape a full selection produces.
|
|
55
|
+
const legs = sources
|
|
56
|
+
.filter((s) => s.maxRefundable > 0)
|
|
57
|
+
.map((s) => ({
|
|
58
|
+
transactionId: s.transactionId,
|
|
59
|
+
amount: s.maxRefundable,
|
|
60
|
+
paymentType: s.paymentType,
|
|
61
|
+
requiresDestination: s.paymentType === 'redeem',
|
|
62
|
+
requiresGiftCardDestination: s.paymentType === 'redeem',
|
|
63
|
+
}));
|
|
64
|
+
const budget = legs.reduce((sum, l) => sum + l.amount, 0);
|
|
48
65
|
return {
|
|
49
66
|
success: true,
|
|
50
67
|
orderId: order._id,
|
|
51
68
|
sources,
|
|
69
|
+
allocation: {
|
|
70
|
+
budget,
|
|
71
|
+
// No cash rounding in the demo, so the goods value and the budget agree.
|
|
72
|
+
itemTotal: budget,
|
|
73
|
+
rounding: 0,
|
|
74
|
+
legs,
|
|
75
|
+
},
|
|
52
76
|
// Demo: no prior refunds, so remaining = captured minus the non-revenue load.
|
|
53
77
|
remainingRefundable: Math.max(0, totalCaptured - nonRefundableLiability),
|
|
54
78
|
nonRefundableLiability,
|
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
export interface GetRefundPlanParams {
|
|
2
2
|
/** Order to inspect; defaults to the active order. */
|
|
3
3
|
orderId?: string;
|
|
4
|
+
/**
|
|
5
|
+
* The selection to allocate — the SAME array you will pass to
|
|
6
|
+
* `processPartialRefund({ items })`, so the plan you render and the refund
|
|
7
|
+
* you submit are computed from one input.
|
|
8
|
+
*
|
|
9
|
+
* A flow that owns its own refund UI holds the selection in its own state and
|
|
10
|
+
* never stages it on the POS, so without this there is nothing for the engine
|
|
11
|
+
* to allocate. Pass it here on every selection change to get the matching
|
|
12
|
+
* {@link RefundPlanAllocation} back. **Purely a read** — unlike
|
|
13
|
+
* `processPartialRefund`, this never stages the selection or touches POS
|
|
14
|
+
* state, so it is safe to call as the cashier ticks rows.
|
|
15
|
+
*
|
|
16
|
+
* Omit it to fall back to the selection already staged on the POS (what
|
|
17
|
+
* `selectAllRefundItems` sets) — the in-POS modal's path. Omitted with
|
|
18
|
+
* nothing staged, no `allocation` comes back.
|
|
19
|
+
*/
|
|
20
|
+
items?: {
|
|
21
|
+
/** `internalId` / `variantId` for a product, `customSaleId`, cart-fee id, or tip `transactionId`. */
|
|
22
|
+
itemKey: string;
|
|
23
|
+
quantity: number;
|
|
24
|
+
/** Optional hint; inferred from the order when omitted. */
|
|
25
|
+
type?: 'product' | 'customSale' | 'fee' | 'tip';
|
|
26
|
+
}[];
|
|
4
27
|
}
|
|
5
28
|
export interface RefundPlanSource {
|
|
6
29
|
transactionId: string;
|
|
@@ -17,10 +40,79 @@ export interface RefundPlanSource {
|
|
|
17
40
|
/** For redeem sources: the card number from the payment entry's emv, when present. */
|
|
18
41
|
cardNumber?: string;
|
|
19
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* One ready-to-submit refund leg. Pass these straight to
|
|
45
|
+
* `processPartialRefund({ openUI: false, legs })` — the amounts are the
|
|
46
|
+
* engine's own allocation and already satisfy its Σ-contract.
|
|
47
|
+
*/
|
|
48
|
+
export interface RefundPlanLeg {
|
|
49
|
+
/** `transactionId` of the source payment this leg draws from — join key to `sources`. */
|
|
50
|
+
transactionId: string;
|
|
51
|
+
/** Amount to return to this source (minor units). Submit VERBATIM; do not re-derive. */
|
|
52
|
+
amount: number;
|
|
53
|
+
/** `cash` / `card` / `redeem` / etc., copied from the source. */
|
|
54
|
+
paymentType: string;
|
|
55
|
+
/**
|
|
56
|
+
* True when the leg must carry a destination tender (a `redeem` source
|
|
57
|
+
* cannot be refunded to itself — the money needs somewhere to land, credited
|
|
58
|
+
* by the flow FIRST). The destination is usually a gift card but redeem is
|
|
59
|
+
* the general rail: loyalty and store-credit extensions ride it too.
|
|
60
|
+
*/
|
|
61
|
+
requiresDestination: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* @deprecated Same value as {@link RefundPlanLeg.requiresDestination} — the
|
|
64
|
+
* old name baked one extension (gift card) into a general redeem concept.
|
|
65
|
+
* Kept populated for existing callers; prefer `requiresDestination`.
|
|
66
|
+
*/
|
|
67
|
+
requiresGiftCardDestination: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Cash legs only: what the drawer actually pays after the company's
|
|
70
|
+
* cash-rounding snap, and the signed delta from `amount`. Display it
|
|
71
|
+
* ("drawer pays 6.50 (+0.01 rounding)") — never apply the snap yourself,
|
|
72
|
+
* and never stage `payout.amount` as the leg (`amount` is the leg).
|
|
73
|
+
*/
|
|
74
|
+
payout?: {
|
|
75
|
+
amount: number;
|
|
76
|
+
rounding: number;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The engine's own allocation of the CURRENT refund selection across the
|
|
81
|
+
* order's captures — what a flow renders and submits instead of computing a
|
|
82
|
+
* split of its own.
|
|
83
|
+
*
|
|
84
|
+
* Present when the call carries a selection: either `params.items` (a flow
|
|
85
|
+
* holding its own selection — the usual case) or a selection already staged on
|
|
86
|
+
* the POS for the active order (`selectAllRefundItems`). Omitted for a bare
|
|
87
|
+
* capacity read with neither.
|
|
88
|
+
*/
|
|
89
|
+
export interface RefundPlanAllocation {
|
|
90
|
+
/**
|
|
91
|
+
* What Σ `legs.amount` MUST equal — `min(itemTotal, Σ maxRefundable)`, which
|
|
92
|
+
* on a FULL selection is the captured total, not the goods value. Staging the
|
|
93
|
+
* goods value instead is rejected with `refund.legSumMismatch`.
|
|
94
|
+
*/
|
|
95
|
+
budget: number;
|
|
96
|
+
/** Goods value of the selection (minor units). DISPLAY ONLY — never allocate against it. */
|
|
97
|
+
itemTotal: number;
|
|
98
|
+
/**
|
|
99
|
+
* `budget − itemTotal` — the sale's cash rounding, returned to the tender that
|
|
100
|
+
* took it. Non-zero only on a cash-rounded capture; the engine stamps it as
|
|
101
|
+
* refund residue at commit.
|
|
102
|
+
*/
|
|
103
|
+
rounding: number;
|
|
104
|
+
/** One leg per source that receives money. Submit as `legs`, unchanged. */
|
|
105
|
+
legs: RefundPlanLeg[];
|
|
106
|
+
}
|
|
20
107
|
export interface GetRefundPlanResponse {
|
|
21
108
|
success: boolean;
|
|
22
109
|
orderId: string;
|
|
23
110
|
sources: RefundPlanSource[];
|
|
111
|
+
/**
|
|
112
|
+
* Ready-to-submit allocation of the current selection. Present only when a
|
|
113
|
+
* refund selection exists on the active order. See {@link RefundPlanAllocation}.
|
|
114
|
+
*/
|
|
115
|
+
allocation?: RefundPlanAllocation;
|
|
24
116
|
/** Order-level remaining refundable (minor units) — non-revenue liability already excluded. */
|
|
25
117
|
remainingRefundable: number;
|
|
26
118
|
/** Non-refundable liability (gift-card loads etc., minor units). */
|
|
@@ -4,4 +4,10 @@
|
|
|
4
4
|
// order-level math so flows can PRESENT accurate refund options without
|
|
5
5
|
// re-deriving the numbers client-side (the mutating commands —
|
|
6
6
|
// `processPartialRefund` / `redeemRefund` — re-validate at submit time).
|
|
7
|
+
//
|
|
8
|
+
// `allocation` closes the last gap: capacities alone still left a flow to work
|
|
9
|
+
// out WHICH tender gets WHAT, and a flow that split the goods value across the
|
|
10
|
+
// tenders shaved the sale's cash rounding off gift-card legs and was rejected
|
|
11
|
+
// at submit. The engine now returns the legs it would accept — render them,
|
|
12
|
+
// submit them unchanged, compute nothing.
|
|
7
13
|
export {};
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
* Calls the getRemainingRefundableQuantities action on the parent window
|
|
4
4
|
*/
|
|
5
5
|
import { commandFrameClient } from "../../client";
|
|
6
|
-
export const getRemainingRefundableQuantities = async () => {
|
|
7
|
-
return await commandFrameClient.call("getRemainingRefundableQuantities");
|
|
6
|
+
export const getRemainingRefundableQuantities = async (params) => {
|
|
7
|
+
return await commandFrameClient.call("getRemainingRefundableQuantities", params);
|
|
8
8
|
};
|
|
@@ -5,6 +5,19 @@ export interface GetRemainingRefundableQuantitiesResponse {
|
|
|
5
5
|
success: boolean;
|
|
6
6
|
lineItems: Record<string, number>;
|
|
7
7
|
customSales: Record<string, number>;
|
|
8
|
+
/**
|
|
9
|
+
* Remaining refundable cart fees, keyed by `order.cartFees[].id` —
|
|
10
|
+
* the same key `processPartialRefund` takes for `type: 'fee'` items.
|
|
11
|
+
* 0/1 semantics: `1` = still refundable, `0` = already refunded.
|
|
12
|
+
*/
|
|
13
|
+
cartFees: Record<string, number>;
|
|
14
|
+
/**
|
|
15
|
+
* Remaining refundable tips, keyed by the paying method's
|
|
16
|
+
* `transactionId` — the same key `processPartialRefund` takes for
|
|
17
|
+
* `type: 'tip'` items. Only payment methods that carry a tip appear.
|
|
18
|
+
* 0/1 semantics: `1` = still refundable, `0` = already refunded.
|
|
19
|
+
*/
|
|
20
|
+
tips: Record<string, number>;
|
|
8
21
|
timestamp: string;
|
|
9
22
|
}
|
|
10
23
|
export type GetRemainingRefundableQuantities = (params?: GetRemainingRefundableQuantitiesParams) => Promise<GetRemainingRefundableQuantitiesResponse>;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { CFTransitionResult } from "../../common-types/order-state";
|
|
2
1
|
export interface InitiateRefundParams {
|
|
3
2
|
/** The ID of the order to refund. If not provided, uses the currently active order. */
|
|
4
3
|
orderId?: string;
|
|
@@ -7,7 +6,11 @@ export interface InitiateRefundResponse {
|
|
|
7
6
|
success: boolean;
|
|
8
7
|
orderId: string;
|
|
9
8
|
timestamp: string;
|
|
10
|
-
/** Present when the state machine blocked or forced the transition. */
|
|
11
|
-
transitionResult?: CFTransitionResult;
|
|
12
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* @deprecated The host-side refund popup is disabled — no UI opens. Stages the
|
|
12
|
+
* active order (and arms barcode refund-scan routing when no `orderId` is
|
|
13
|
+
* given). Build refund UI in the flow: `getRefundPlan`,
|
|
14
|
+
* `getRemainingRefundableQuantities`, `processPartialRefund`, `redeemRefund`.
|
|
15
|
+
*/
|
|
13
16
|
export type InitiateRefund = (params?: InitiateRefundParams) => Promise<InitiateRefundResponse>;
|