@final-commerce/command-frame 0.14.1-online.3 → 0.15.0-staging.2
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 +10 -0
- package/dist/CommonTypes.d.ts +2 -1
- package/dist/actions/add-booking-to-cart/mock.js +1 -1
- package/dist/actions/add-booking-to-cart/types.d.ts +33 -4
- package/dist/actions/attach-checkout-contact/types.d.ts +2 -14
- package/dist/actions/delete-custom-table-data/mock.js +2 -0
- package/dist/actions/get-active-user/mock.js +3 -1
- package/dist/actions/get-booking-rental-ends/action.d.ts +6 -0
- package/dist/actions/get-booking-rental-ends/action.js +8 -0
- package/dist/actions/get-booking-rental-ends/mock.d.ts +2 -0
- package/dist/actions/get-booking-rental-ends/mock.js +6 -0
- package/dist/actions/get-booking-rental-ends/types.d.ts +31 -0
- package/dist/actions/get-booking-rental-ends/types.js +1 -0
- package/dist/actions/get-cash-change/action.d.ts +2 -0
- package/dist/actions/get-cash-change/action.js +8 -0
- package/dist/actions/get-cash-change/mock.d.ts +2 -0
- package/dist/actions/get-cash-change/mock.js +6 -0
- package/dist/actions/get-cash-change/types.d.ts +23 -0
- package/dist/actions/get-cash-change/types.js +1 -0
- package/dist/actions/get-checkout-status/types.d.ts +0 -9
- package/dist/actions/get-custom-table-data/mock.d.ts +5 -0
- package/dist/actions/get-custom-table-data/mock.js +16 -27
- package/dist/actions/get-orders-summary/action.d.ts +2 -0
- package/dist/actions/get-orders-summary/action.js +5 -0
- package/dist/actions/get-orders-summary/mock.d.ts +2 -0
- package/dist/actions/get-orders-summary/mock.js +5 -0
- package/dist/actions/get-orders-summary/types.d.ts +24 -0
- package/dist/actions/get-orders-summary/types.js +1 -0
- package/dist/actions/hold-booking/mock.d.ts +1 -0
- package/dist/actions/hold-booking/mock.js +27 -13
- package/dist/actions/hold-booking/types.d.ts +35 -6
- package/dist/actions/partial-payment/types.d.ts +5 -0
- package/dist/actions/process-partial-refund/mock.js +5 -0
- package/dist/actions/tap-to-pay-payment/mock.js +2 -1
- package/dist/actions/terminal-payment/mock.js +2 -1
- package/dist/actions/upsert-custom-table-data/mock.d.ts +6 -0
- package/dist/actions/upsert-custom-table-data/mock.js +25 -9
- package/dist/demo/database.d.ts +16 -0
- package/dist/demo/database.js +18 -2
- package/dist/demo/mocks/custom-tables.d.ts +4 -1
- package/dist/demo/mocks/custom-tables.js +1 -0
- package/dist/index.d.ts +6 -16
- package/dist/index.js +6 -17
- package/dist/projects/render/mocks.js +6 -16
- package/dist/projects/render/types.d.ts +4 -10
- package/package.json +2 -2
- package/dist/actions/get-address-details/action.d.ts +0 -6
- package/dist/actions/get-address-details/action.js +0 -8
- package/dist/actions/get-address-details/mock.d.ts +0 -2
- package/dist/actions/get-address-details/mock.js +0 -7
- package/dist/actions/get-address-details/types.d.ts +0 -22
- package/dist/actions/get-address-details/types.js +0 -3
- package/dist/actions/get-shopper-account/action.d.ts +0 -6
- package/dist/actions/get-shopper-account/action.js +0 -8
- package/dist/actions/get-shopper-account/mock.d.ts +0 -3
- package/dist/actions/get-shopper-account/mock.js +0 -12
- package/dist/actions/get-shopper-account/types.d.ts +0 -46
- package/dist/actions/get-shopper-account/types.js +0 -6
- package/dist/actions/get-shopper-order/action.d.ts +0 -6
- package/dist/actions/get-shopper-order/action.js +0 -8
- package/dist/actions/get-shopper-order/mock.d.ts +0 -2
- package/dist/actions/get-shopper-order/mock.js +0 -24
- package/dist/actions/get-shopper-order/types.d.ts +0 -32
- package/dist/actions/get-shopper-order/types.js +0 -6
- package/dist/actions/get-shopper-orders/action.d.ts +0 -6
- package/dist/actions/get-shopper-orders/action.js +0 -8
- package/dist/actions/get-shopper-orders/mock.d.ts +0 -2
- package/dist/actions/get-shopper-orders/mock.js +0 -18
- package/dist/actions/get-shopper-orders/types.d.ts +0 -29
- package/dist/actions/get-shopper-orders/types.js +0 -6
- package/dist/actions/request-shopper-code/action.d.ts +0 -6
- package/dist/actions/request-shopper-code/action.js +0 -8
- package/dist/actions/request-shopper-code/mock.d.ts +0 -2
- package/dist/actions/request-shopper-code/mock.js +0 -4
- package/dist/actions/request-shopper-code/types.d.ts +0 -17
- package/dist/actions/request-shopper-code/types.js +0 -6
- package/dist/actions/search-addresses/action.d.ts +0 -6
- package/dist/actions/search-addresses/action.js +0 -8
- package/dist/actions/search-addresses/mock.d.ts +0 -3
- package/dist/actions/search-addresses/mock.js +0 -24
- package/dist/actions/search-addresses/types.d.ts +0 -31
- package/dist/actions/search-addresses/types.js +0 -5
- package/dist/actions/sign-in-shopper/action.d.ts +0 -6
- package/dist/actions/sign-in-shopper/action.js +0 -8
- package/dist/actions/sign-in-shopper/mock.d.ts +0 -2
- package/dist/actions/sign-in-shopper/mock.js +0 -7
- package/dist/actions/sign-in-shopper/types.d.ts +0 -19
- package/dist/actions/sign-in-shopper/types.js +0 -6
- package/dist/actions/sign-out-shopper/action.d.ts +0 -6
- package/dist/actions/sign-out-shopper/action.js +0 -8
- package/dist/actions/sign-out-shopper/mock.d.ts +0 -2
- package/dist/actions/sign-out-shopper/mock.js +0 -4
- package/dist/actions/sign-out-shopper/types.d.ts +0 -7
- package/dist/actions/sign-out-shopper/types.js +0 -6
package/README.md
CHANGED
|
@@ -147,6 +147,16 @@ Each client comes with built-in mock data for local development.
|
|
|
147
147
|
const client = new RenderClient({ mockMode: true, debug: true });
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
+
**Declines — mock only.** A test that needs to see how a flow treats a refusal sets `declines` in the dataset; a real till never reads it:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
setMockDatabase({ declines: { payments: ['terminal', 'tapToPay'], refunds: true, customTableWrites: true } });
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
- `payments`: those card commands reject with `CLOUD_PAYMENT_FAILED: Card declined` (cloud terminal) or `NATIVE_PAYMENT_FAILED: Card declined`.
|
|
157
|
+
- `refunds: true`: `processPartialRefund` rejects with `Cloud refund failed`.
|
|
158
|
+
- `customTableWrites: true`: `upsertCustomTableData` and `deleteCustomTableData` reject with `Table "<name>" not found`.
|
|
159
|
+
|
|
150
160
|
### Debugging
|
|
151
161
|
|
|
152
162
|
Enable debug logging to see all message passing activity in the console:
|
package/dist/CommonTypes.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from './common-types';
|
|
2
|
-
import type { Tax, Tip, Address, MetadataItem, OrderAssignedUser, OrderInCart, OrderCustomStatus, 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, ResolvedModifier, ResolvedModifierChoice, ModifierSelectionType, ModifierSelection, ModifierChoiceSelection, CartLineModifier, ProdModifierBreakdown, OrderLineItemModifier, CustomerNote, Attribute, AttributeOption, Category, Transaction, ActiveSplitPayment, BookingAvailability, BookingSlot, BookingSlotResource, BookingResource, Booking, CartReservation, OrderReservation, CartAddress, CartShipping, Composite, CompositePart, CompositeItem, CompositeChoice, CompositeItemUnavailable, CompositePick, CartLineComponent, OrderLineItemComponent } from '@final-commerce/common/pos-types';
|
|
2
|
+
import type { Tax, Tip, Address, MetadataItem, OrderAssignedUser, OrderInCart, OrderCustomStatus, 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, ResolvedModifier, ResolvedModifierChoice, ModifierSelectionType, ModifierSelection, ModifierChoiceSelection, CartLineModifier, ProdModifierBreakdown, OrderLineItemModifier, CustomerNote, Attribute, AttributeOption, Category, Transaction, ActiveSplitPayment, BookingAvailability, BookingSlot, BookingSlotResource, BookingResource, Booking, BookingRentalEnd, CartReservation, OrderReservation, CartAddress, CartShipping, Composite, CompositePart, CompositeItem, CompositeChoice, CompositeItemUnavailable, CompositePick, CartLineComponent, OrderLineItemComponent } 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>;
|
|
@@ -59,6 +59,7 @@ export type CFBookingSlot = BookingSlot;
|
|
|
59
59
|
export type CFBookingSlotResource = BookingSlotResource;
|
|
60
60
|
export type CFBookingResource = BookingResource;
|
|
61
61
|
export type CFBooking = Booking;
|
|
62
|
+
export type CFBookingRentalEnd = BookingRentalEnd;
|
|
62
63
|
/** A service sitting in the cart, and the same sale once it is on an order. A flow that draws a
|
|
63
64
|
* cart or a receipt needs both by name — reaching into the package's dist for them is not an
|
|
64
65
|
* interface. */
|
|
@@ -37,7 +37,7 @@ export const mockAddBookingToCart = async (params) => {
|
|
|
37
37
|
bookingId: booking.id,
|
|
38
38
|
productId: params.productId,
|
|
39
39
|
variantId: variant?._id ?? null,
|
|
40
|
-
resourceId:
|
|
40
|
+
resourceId: booking.resourceId,
|
|
41
41
|
name: product.name,
|
|
42
42
|
resourceName: booking.resourceName,
|
|
43
43
|
price,
|
|
@@ -2,16 +2,45 @@ import { CFBooking } from '../../CommonTypes';
|
|
|
2
2
|
export interface AddBookingToCartParams {
|
|
3
3
|
/** The bookable product (`productType: 'booking'`). */
|
|
4
4
|
productId: string;
|
|
5
|
-
/**
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Which resource serves it: a member of staff, a room, a scooter. Omit for "any free one": the host picks the first
|
|
7
|
+
* resource, in availability order, with `qty` places free for the whole window (or every session of a course).
|
|
8
|
+
* A storefront still needs it.
|
|
9
|
+
*/
|
|
10
|
+
resourceId?: string;
|
|
7
11
|
/** Must be the START of a slot getBookingAvailability offered, ISO 8601. */
|
|
8
12
|
startAt: string;
|
|
9
|
-
/** End of the window — the slot's own `endAt`, or the last slot's for a stay. */
|
|
10
|
-
endAt
|
|
13
|
+
/** End of the window — the slot's own `endAt`, or the last slot's for a stay. Omit it only when you pass `periods`. */
|
|
14
|
+
endAt?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Instead of `endAt`, for date-range rules: how many periods from `startAt` — hours, nights, days, or whole weeks /
|
|
17
|
+
* months. The host reads the end off its own grid and refuses what the rules refuse (`minStayLength` /
|
|
18
|
+
* `maxStayLength` from `getBookingAvailability`, each with its own unit — "2 nights", "1 week" — and whole periods).
|
|
19
|
+
* Pass exactly one of `endAt` and `periods`. A storefront still needs `endAt`.
|
|
20
|
+
*/
|
|
21
|
+
periods?: number;
|
|
11
22
|
/** The variant being sold. Defaults to the product's only variant. */
|
|
12
23
|
variantId?: string;
|
|
13
24
|
/** Whom the booking is for. Omit for a walk-in. */
|
|
14
25
|
customerId?: string;
|
|
26
|
+
/**
|
|
27
|
+
* How many places of the resource this booking takes (seats in a class, spots in a session). A whole
|
|
28
|
+
* number, default 1. Each place is charged: the reservation's `quantity` counts them and its `total` is
|
|
29
|
+
* `price × quantity`. Refused above the resource's capacity and above the rule's `maxQtyPerBooking`.
|
|
30
|
+
* A window of several nights or days (a date range on a till) is charged per period too: `quantity = qty × periods`.
|
|
31
|
+
*/
|
|
32
|
+
qty?: number;
|
|
33
|
+
/**
|
|
34
|
+
* How many people the booking is for (a room for 3, a table for 4). A whole number. Recorded on the booking; it takes
|
|
35
|
+
* no extra places and does not change the price. Refused above the rule's `maxPartySize`. Omit when not asked.
|
|
36
|
+
*/
|
|
37
|
+
partySize?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Book the whole course (session rules sold as a course): one hold per session, all or nothing. Pass the course's own
|
|
40
|
+
* `startAt` / `endAt` from `getBookingAvailability` (`availability.course`); other values are refused. Refused on rules
|
|
41
|
+
* that do not sell a course; a course rule refuses a booking without it.
|
|
42
|
+
*/
|
|
43
|
+
course?: boolean;
|
|
15
44
|
}
|
|
16
45
|
export interface AddBookingToCartResponse {
|
|
17
46
|
/** False when the command was refused — a taken window, a rule, a booking that is not yours. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Guest contact for the order. There is no shopper account, so this is the only
|
|
3
|
+
* way to reach them — `email` is where the receipt goes and is required.
|
|
4
4
|
*/
|
|
5
5
|
export interface AttachCheckoutContactParams {
|
|
6
6
|
email: string;
|
|
@@ -8,18 +8,6 @@ export interface AttachCheckoutContactParams {
|
|
|
8
8
|
phone?: string;
|
|
9
9
|
/** Defaults to the outlet the storefront booted against. */
|
|
10
10
|
outletId?: string;
|
|
11
|
-
/**
|
|
12
|
-
* The checkout's account box ("Track this order and check out faster next time", from
|
|
13
|
-
* `getShopperAccount`): links this email to a shopper account at this store. Omitted = not ticked.
|
|
14
|
-
*/
|
|
15
|
-
accountOptIn?: boolean;
|
|
16
|
-
/** The news box ("Email me news and offers"): this store's consent, true or false. Omitted = not asked. */
|
|
17
|
-
marketingOptIn?: boolean;
|
|
18
|
-
/**
|
|
19
|
-
* The "Save this address" box (signed-in shoppers): once the order is paid, its shipping address is
|
|
20
|
-
* saved to the shopper's account. Omitted = not ticked.
|
|
21
|
-
*/
|
|
22
|
-
saveAddress?: boolean;
|
|
23
11
|
}
|
|
24
12
|
export interface AttachCheckoutContactResponse {
|
|
25
13
|
/**
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { assertMockCustomTableWriteNotDeclined } from "../../demo/database";
|
|
1
2
|
export const mockDeleteCustomTableData = async (params) => {
|
|
2
3
|
console.log("[Mock] deleteCustomTableData called", params);
|
|
4
|
+
assertMockCustomTableWriteNotDeclined(params?.tableName ?? "");
|
|
3
5
|
return {
|
|
4
6
|
success: true,
|
|
5
7
|
timestamp: new Date().toISOString()
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { MOCK_USER_MARIO } from "../../demo/database";
|
|
1
|
+
import { MOCK_USER_MARIO, mockPublishEvent } from "../../demo/database";
|
|
2
2
|
export const mockGetActiveUser = async () => {
|
|
3
3
|
console.log("[Mock] getActiveUser called");
|
|
4
|
+
// Same as the host (kaching get-active-user handler): reading the user publishes `users` / `get-active-user`.
|
|
5
|
+
mockPublishEvent("users", "get-active-user", { user: MOCK_USER_MARIO });
|
|
4
6
|
return {
|
|
5
7
|
success: true,
|
|
6
8
|
user: MOCK_USER_MARIO,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get booking rental ends action
|
|
3
|
+
* Calls the getBookingRentalEnds action on the parent window
|
|
4
|
+
*/
|
|
5
|
+
import { commandFrameClient } from '../../client';
|
|
6
|
+
export const getBookingRentalEnds = async (params) => {
|
|
7
|
+
return await commandFrameClient.call('getBookingRentalEnds', params);
|
|
8
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { MOCK_ONLY_SINGLE_SLOTS } from '../hold-booking/mock';
|
|
2
|
+
// The mock shop has no date-range rules, so no stay can start in it — the same refusal `holdBooking` gives a stay.
|
|
3
|
+
export const mockGetBookingRentalEnds = async (params) => {
|
|
4
|
+
console.log('[Mock] getBookingRentalEnds called', params);
|
|
5
|
+
return { success: false, reason: MOCK_ONLY_SINGLE_SLOTS, timestamp: new Date().toISOString() };
|
|
6
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { CFBookingRentalEnd } from '../../CommonTypes';
|
|
2
|
+
export interface GetBookingRentalEndsParams {
|
|
3
|
+
/** The bookable product. Date-range rules only (hours, nights, days, weeks, months). */
|
|
4
|
+
productId: string;
|
|
5
|
+
/** The chosen check-in: the `startAt` of a slot `getBookingAvailability` offered, ISO 8601. */
|
|
6
|
+
startAt: string;
|
|
7
|
+
/**
|
|
8
|
+
* Last shop day an end may fall on, `YYYY-MM-DD` (the format of `getBookingAvailability`'s `fromDay`). Ends past it are
|
|
9
|
+
* not computed. Leave one page more than you show — a check-in on the last day, a whole week or month — and never a day
|
|
10
|
+
* before the chosen check-out; ask again with a later day when the cashier pages or steps past it. The shortest allowed
|
|
11
|
+
* stay comes even when it ends past it — alone — so a `minStay` longer than what you show still books.
|
|
12
|
+
*/
|
|
13
|
+
untilDay: string;
|
|
14
|
+
/** The resource that must serve the whole stay. Omit for "any free one", picked per end exactly as the hold picks it. */
|
|
15
|
+
resourceId?: string;
|
|
16
|
+
/** Places the booking takes, as you will pass to `addBookingToCart`. Default 1. */
|
|
17
|
+
qty?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface GetBookingRentalEndsResponse {
|
|
20
|
+
/** False when no stay can start here — a taken first night, a `minStay` the free nights do not reach, a non-range rule. */
|
|
21
|
+
success: boolean;
|
|
22
|
+
/** Why, in the host's own words. Absent on success. */
|
|
23
|
+
reason?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Every end the stay may have now, ascending, never empty on success. Enable only these as check-out; highlight the
|
|
26
|
+
* chosen one's `coveredSlots`; send its `endAt` to `addBookingToCart`.
|
|
27
|
+
*/
|
|
28
|
+
ends?: CFBookingRentalEnd[];
|
|
29
|
+
timestamp: string;
|
|
30
|
+
}
|
|
31
|
+
export type GetBookingRentalEnds = (params: GetBookingRentalEndsParams) => Promise<GetBookingRentalEndsResponse>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get cash change action — what a cash tender would give back, computed by the till exactly as `cashPayment` computes
|
|
3
|
+
* it (after cash rounding). Read-only: nothing is paid.
|
|
4
|
+
*/
|
|
5
|
+
import { commandFrameClient } from '../../client';
|
|
6
|
+
export const getCashChange = async (params) => {
|
|
7
|
+
return await commandFrameClient.call('getCashChange', params);
|
|
8
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// The mock does no money: change is the till's arithmetic (cash rounding included), so a screen sees the host's
|
|
2
|
+
// refusal instead of a number the mock made up.
|
|
3
|
+
export const mockGetCashChange = async (params) => {
|
|
4
|
+
console.log('[Mock] getCashChange called', params);
|
|
5
|
+
throw new Error('Cash change is computed by the till');
|
|
6
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface GetCashChangeParams {
|
|
2
|
+
/** Cash handed over, integer MINOR units (e.g. from common `parseAmountToMinorUnits`). */
|
|
3
|
+
tenderedAmount: number;
|
|
4
|
+
/** What it is tendered against, MINOR units. Default: the cart's balance due — the same default as `getCashRoundingAmount`. */
|
|
5
|
+
amount?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface GetCashChangeResponse {
|
|
8
|
+
success: boolean;
|
|
9
|
+
/** The amount tendered against, before cash rounding (MINOR units). */
|
|
10
|
+
amountDue: number;
|
|
11
|
+
/** After the company's cash-rounding setting — what `cashPayment` charges for cash (MINOR units). */
|
|
12
|
+
roundedAmountDue: number;
|
|
13
|
+
/** `roundedAmountDue − amountDue`, signed (MINOR units). 0 without a cash-rounding setting. */
|
|
14
|
+
cashRounding: number;
|
|
15
|
+
/** Echo of the tender (MINOR units). */
|
|
16
|
+
tenderedAmount: number;
|
|
17
|
+
/** ≥ 0: the change `cashPayment({ tenderedAmount })` returns for this tender. 0 while the tender is short. */
|
|
18
|
+
change: number;
|
|
19
|
+
/** ≥ 0: how much the tender is short of `roundedAmountDue`. 0 when it covers it. */
|
|
20
|
+
remaining: number;
|
|
21
|
+
timestamp: string;
|
|
22
|
+
}
|
|
23
|
+
export type GetCashChange = (params: GetCashChangeParams) => Promise<GetCashChangeResponse>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -21,15 +21,6 @@ export interface GetCheckoutStatusResponse {
|
|
|
21
21
|
*/
|
|
22
22
|
cancelReason?: CheckoutCancelReason;
|
|
23
23
|
receiptId: string | null;
|
|
24
|
-
/**
|
|
25
|
-
* The shopper account line for the confirmation page: "Added to your orders" when signed in, else
|
|
26
|
-
* (any guest who gave an email) a prompt to get a code; for `sign-in`, call requestShopperCode() with
|
|
27
|
-
* no params and it goes to that email. Ready to display.
|
|
28
|
-
*/
|
|
29
|
-
account?: {
|
|
30
|
-
label: string;
|
|
31
|
-
action: 'none' | 'sign-in';
|
|
32
|
-
};
|
|
33
24
|
timestamp: string;
|
|
34
25
|
}
|
|
35
26
|
/** Never throws for an unknown order: answers `status: null`. */
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
import { GetCustomTableData } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* The host's read (kaching get-custom-table-data/handler.ts, customTables.ts:99-113): the table's rows, offset 0 and
|
|
4
|
+
* limit 100 by default, so a row upsertCustomTableData wrote is read back. The mock filters by plain field equality only
|
|
5
|
+
* and refuses query operators rather than guess them.
|
|
6
|
+
*/
|
|
2
7
|
export declare const mockGetCustomTableData: GetCustomTableData;
|
|
@@ -1,34 +1,23 @@
|
|
|
1
|
+
import { MOCK_CUSTOM_TABLE_DATA } from "../../demo/mocks/custom-tables";
|
|
2
|
+
/**
|
|
3
|
+
* The host's read (kaching get-custom-table-data/handler.ts, customTables.ts:99-113): the table's rows, offset 0 and
|
|
4
|
+
* limit 100 by default, so a row upsertCustomTableData wrote is read back. The mock filters by plain field equality only
|
|
5
|
+
* and refuses query operators rather than guess them.
|
|
6
|
+
*/
|
|
1
7
|
export const mockGetCustomTableData = async (params) => {
|
|
2
8
|
console.log("[Mock] getCustomTableData called", params);
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
{
|
|
13
|
-
_id: "user_data_2",
|
|
14
|
-
fullName: "Jane Smith",
|
|
15
|
-
email: "jane.smith@example.com",
|
|
16
|
-
isActive: true,
|
|
17
|
-
createdAt: "2024-01-11T11:00:00.000Z",
|
|
18
|
-
updatedAt: "2024-01-11T11:00:00.000Z"
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
_id: "user_data_3",
|
|
22
|
-
fullName: "Bob Johnson",
|
|
23
|
-
email: "bob.johnson@example.com",
|
|
24
|
-
isActive: false,
|
|
25
|
-
createdAt: "2024-01-12T09:00:00.000Z",
|
|
26
|
-
updatedAt: "2024-01-15T14:30:00.000Z"
|
|
27
|
-
}
|
|
28
|
-
];
|
|
9
|
+
if (!params?.tableName)
|
|
10
|
+
throw new Error("tableName is required");
|
|
11
|
+
const query = params.query ?? {};
|
|
12
|
+
const operator = Object.entries(query).find(([key, value]) => key.startsWith("$") || (typeof value === "object" && value !== null));
|
|
13
|
+
if (operator) {
|
|
14
|
+
throw new Error(`The mock filters custom-table rows by plain field equality only; "${operator[0]}" is not modelled`);
|
|
15
|
+
}
|
|
16
|
+
const rows = (MOCK_CUSTOM_TABLE_DATA[params.tableName] ?? []).filter(row => Object.entries(query).every(([key, value]) => row[key] === value));
|
|
17
|
+
const offset = params.offset || 0;
|
|
29
18
|
return {
|
|
30
19
|
success: true,
|
|
31
|
-
data:
|
|
20
|
+
data: rows.slice(offset, offset + (params.limit || 100)),
|
|
32
21
|
timestamp: new Date().toISOString()
|
|
33
22
|
};
|
|
34
23
|
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Get orders summary action — counts and the money total of the orders `getOrders` would match, from the till. */
|
|
2
|
+
import { commandFrameClient } from '../../client';
|
|
3
|
+
export const getOrdersSummary = async (params) => {
|
|
4
|
+
return await commandFrameClient.call('getOrdersSummary', params);
|
|
5
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// The mock does no money: totals and shares are the till's, so a screen sees the host's refusal, not mock maths.
|
|
2
|
+
export const mockGetOrdersSummary = async (params) => {
|
|
3
|
+
console.log('[Mock] getOrdersSummary called', params);
|
|
4
|
+
throw new Error('The orders summary is computed by the till');
|
|
5
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { GetOrdersParams } from '../get-orders/types';
|
|
2
|
+
/** The same filters as `getOrders`; paging and sorting do not apply to a summary. */
|
|
3
|
+
export type GetOrdersSummaryParams = Omit<GetOrdersParams, 'limit' | 'offset' | 'sortBy' | 'sortDirection'>;
|
|
4
|
+
export interface GetOrdersSummaryResponse {
|
|
5
|
+
success: boolean;
|
|
6
|
+
/** Matched orders, leaving out those still in a cart, parked or deleted from park (legacy `status`). */
|
|
7
|
+
orderCount: number;
|
|
8
|
+
/** Σ `summary.total` of those orders, integer MINOR units. */
|
|
9
|
+
ordersTotal: number;
|
|
10
|
+
currency: string;
|
|
11
|
+
minorUnits: number;
|
|
12
|
+
/** Legacy `status === 'completed'`; `percentOfOrders` rounded to a whole percent by the till. */
|
|
13
|
+
completed: {
|
|
14
|
+
count: number;
|
|
15
|
+
percentOfOrders: number;
|
|
16
|
+
};
|
|
17
|
+
/** Legacy `status` 'refunded' or 'partial-refund'. */
|
|
18
|
+
refunded: {
|
|
19
|
+
count: number;
|
|
20
|
+
percentOfOrders: number;
|
|
21
|
+
};
|
|
22
|
+
timestamp: string;
|
|
23
|
+
}
|
|
24
|
+
export type GetOrdersSummary = (params?: GetOrdersSummaryParams) => Promise<GetOrdersSummaryResponse>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,35 +1,49 @@
|
|
|
1
|
-
import { MOCK_BOOKINGS, MOCK_BOOKING_RESOURCES, mockLiveBookings } from '../../demo/database';
|
|
2
|
-
import { ReservationStatus } from '@final-commerce/common';
|
|
1
|
+
import { MOCK_BOOKINGS, MOCK_BOOKING_RESOURCES, mockBookingAvailability, mockLiveBookings } from '../../demo/database';
|
|
2
|
+
import { ReservationStatus, pickFreeResource } from '@final-commerce/common';
|
|
3
3
|
const BUFFER_MINUTES = 5;
|
|
4
4
|
const HOLD_MINUTES = 10;
|
|
5
|
+
const TAKEN = 'That window has just been taken — pick another slot.';
|
|
6
|
+
export const MOCK_ONLY_SINGLE_SLOTS = "[Mock] the mock shop sells single 30-minute slots — pass the slot's endAt";
|
|
5
7
|
let held = 0;
|
|
8
|
+
const refused = (reason) => ({
|
|
9
|
+
success: false,
|
|
10
|
+
reason,
|
|
11
|
+
timestamp: new Date().toISOString(),
|
|
12
|
+
});
|
|
13
|
+
const offeredSlot = (productId, startAt) => mockBookingAvailability(productId, new Date(startAt), new Date(Date.parse(startAt) + 60000)).slots.find((slot) => slot.startAt === new Date(startAt).toISOString());
|
|
14
|
+
// The mock refuses a taken window like the host does. A mock that always said yes would
|
|
15
|
+
// let a booking screen ship without ever handling the one error it will actually meet.
|
|
16
|
+
const windowIsTaken = (resourceId, startAt, bufferEndAt) => mockLiveBookings().some((entry) => entry.resourceId === resourceId && new Date(entry.startAt) < bufferEndAt && new Date(entry.bufferEndAt) > startAt);
|
|
6
17
|
export const mockHoldBooking = async (params) => {
|
|
7
18
|
console.log('[Mock] holdBooking called', params);
|
|
19
|
+
if (params.course || (params.periods !== undefined && params.periods !== 1))
|
|
20
|
+
return refused(MOCK_ONLY_SINGLE_SLOTS);
|
|
21
|
+
const slot = offeredSlot(params.productId, params.startAt);
|
|
22
|
+
const resourceId = params.resourceId ?? (slot && pickFreeResource(slot, params.qty)?.resourceId);
|
|
23
|
+
const end = params.endAt ?? slot?.endAt;
|
|
24
|
+
if (!resourceId || !end)
|
|
25
|
+
return refused(TAKEN);
|
|
8
26
|
const startAt = new Date(params.startAt);
|
|
9
|
-
const endAt = new Date(
|
|
27
|
+
const endAt = new Date(end);
|
|
10
28
|
const bufferEndAt = new Date(endAt.getTime() + BUFFER_MINUTES * 60000);
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const clash = mockLiveBookings().some((entry) => entry.resourceId === params.resourceId &&
|
|
14
|
-
new Date(entry.startAt) < bufferEndAt &&
|
|
15
|
-
new Date(entry.bufferEndAt) > startAt);
|
|
16
|
-
if (clash) {
|
|
17
|
-
return { success: false, reason: 'That window has just been taken — pick another slot.', timestamp: new Date().toISOString() };
|
|
18
|
-
}
|
|
29
|
+
if (windowIsTaken(resourceId, startAt, bufferEndAt))
|
|
30
|
+
return refused(TAKEN);
|
|
19
31
|
const booking = {
|
|
20
32
|
// A counter, not a clock: two holds in the same millisecond used to share an id, and cancel
|
|
21
33
|
// or remove then acted on the wrong row. Scripted tests hit that routinely.
|
|
22
34
|
id: `bk_mock_${(held += 1)}`,
|
|
23
35
|
productId: params.productId,
|
|
24
|
-
resourceId
|
|
36
|
+
resourceId,
|
|
25
37
|
variantId: params.variantId,
|
|
26
38
|
startAt: startAt.toISOString(),
|
|
27
39
|
endAt: endAt.toISOString(),
|
|
28
40
|
bufferEndAt: bufferEndAt.toISOString(),
|
|
29
41
|
customerId: params.customerId,
|
|
42
|
+
qty: params.qty,
|
|
43
|
+
partySize: params.partySize,
|
|
30
44
|
status: ReservationStatus.HELD,
|
|
31
45
|
expiresAt: new Date(Date.now() + HOLD_MINUTES * 60000).toISOString(),
|
|
32
|
-
resourceName: MOCK_BOOKING_RESOURCES.find(({ id }) => id ===
|
|
46
|
+
resourceName: MOCK_BOOKING_RESOURCES.find(({ id }) => id === resourceId)?.name,
|
|
33
47
|
};
|
|
34
48
|
// Held in the same list availability reads, so the slot really does disappear.
|
|
35
49
|
MOCK_BOOKINGS.push(booking);
|
|
@@ -1,20 +1,46 @@
|
|
|
1
1
|
import { CFBooking } from '../../CommonTypes';
|
|
2
2
|
export interface HoldBookingParams {
|
|
3
3
|
productId: string;
|
|
4
|
-
/**
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Which resource serves it: a member of staff, a room, a scooter. Omit for "any free one": the host picks the first
|
|
6
|
+
* resource, in availability order, with `qty` places free for the whole window (or every session of a course).
|
|
7
|
+
* A storefront still needs it.
|
|
8
|
+
*/
|
|
9
|
+
resourceId?: string;
|
|
6
10
|
/** Must be the START of a slot the host offered, ISO 8601. */
|
|
7
11
|
startAt: string;
|
|
8
12
|
/**
|
|
9
|
-
* End of the window — the `endAt` of the slot you picked, or of the last slot in a stay.
|
|
10
|
-
*
|
|
11
|
-
* has no local copy of the rules to derive it from.
|
|
13
|
+
* End of the window — the `endAt` of the slot you picked, or of the last slot in a stay. A window that cannot be
|
|
14
|
+
* lined up with the grid is refused. Omit it only when you pass `periods`.
|
|
12
15
|
*/
|
|
13
|
-
endAt
|
|
16
|
+
endAt?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Instead of `endAt`, for date-range rules: how many periods from `startAt` — hours, nights, days, or whole weeks /
|
|
19
|
+
* months. The host reads the end off its own grid and refuses what the rules refuse (`minStayLength` /
|
|
20
|
+
* `maxStayLength` from `getBookingAvailability`, each with its own unit — "2 nights", "1 week" — and whole periods).
|
|
21
|
+
* Pass exactly one of `endAt` and `periods`. A storefront still needs `endAt`.
|
|
22
|
+
*/
|
|
23
|
+
periods?: number;
|
|
14
24
|
/** The variant being sold, when the flow already knows which one. */
|
|
15
25
|
variantId?: string;
|
|
16
26
|
/** Whom the booking is for. Omit for a walk-in. */
|
|
17
27
|
customerId?: string;
|
|
28
|
+
/**
|
|
29
|
+
* How many places of the resource this booking takes (seats in a class, spots in a session). A whole
|
|
30
|
+
* number, default 1. Refused above the resource's capacity and above the rule's `maxQtyPerBooking`.
|
|
31
|
+
*/
|
|
32
|
+
qty?: number;
|
|
33
|
+
/**
|
|
34
|
+
* How many people the booking is for (a room for 3, a table for 4). A whole number. Recorded on the booking; it takes
|
|
35
|
+
* no extra places and does not change the price. Refused above the rule's `maxPartySize`. Omit when not asked.
|
|
36
|
+
*/
|
|
37
|
+
partySize?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Book the whole course (session rules sold as a course): one hold per session, all or nothing. Pass the course's own
|
|
40
|
+
* `startAt` / `endAt` from `getBookingAvailability` (`availability.course`); other values are refused. Refused on rules
|
|
41
|
+
* that do not sell a course; a course rule refuses a booking without it.
|
|
42
|
+
*/
|
|
43
|
+
course?: boolean;
|
|
18
44
|
}
|
|
19
45
|
export interface HoldBookingResponse {
|
|
20
46
|
/** False when the command was refused — a taken window, a rule, a booking that is not yours. */
|
|
@@ -23,6 +49,9 @@ export interface HoldBookingResponse {
|
|
|
23
49
|
reason?: string;
|
|
24
50
|
/** The claim. `expiresAt` says how long it survives unpaid; `id` is what you confirm or cancel. */
|
|
25
51
|
booking?: CFBooking;
|
|
52
|
+
/** A course: the id every session's hold shares, and how many sessions it holds. `booking` is the first session. */
|
|
53
|
+
groupId?: string;
|
|
54
|
+
sessions?: number;
|
|
26
55
|
timestamp: string;
|
|
27
56
|
}
|
|
28
57
|
export type HoldBooking = (params: HoldBookingParams) => Promise<HoldBookingResponse>;
|
|
@@ -6,6 +6,11 @@ export interface PartialPaymentParams {
|
|
|
6
6
|
isPercent?: boolean;
|
|
7
7
|
/** If true, opens the split payment UI. */
|
|
8
8
|
openUI?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* true: the host refuses an amount above the balance due (the same balance `getCashRoundingAmount()` defaults to) or a
|
|
11
|
+
* percent above 100, with its own reason. Absent / false: no ceiling, exactly as before.
|
|
12
|
+
*/
|
|
13
|
+
refuseAboveBalance?: boolean;
|
|
9
14
|
}
|
|
10
15
|
export interface PartialPaymentResponse {
|
|
11
16
|
success: boolean;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MOCK_DECLINES } from '../../demo/database';
|
|
1
2
|
export const mockProcessPartialRefund = async (params) => {
|
|
2
3
|
// The mock has no split-payment modal or order engine, so `openUI` (default
|
|
3
4
|
// true on the real command), `legs` (the headless per-tender allocation), a
|
|
@@ -10,6 +11,10 @@ export const mockProcessPartialRefund = async (params) => {
|
|
|
10
11
|
if (params?.legs && params?.giftCard) {
|
|
11
12
|
throw new Error('refund.giftCardAndLegs: pass either `legs` (you allocate) or `giftCard` (the engine allocates), not both');
|
|
12
13
|
}
|
|
14
|
+
if (MOCK_DECLINES.refunds) {
|
|
15
|
+
console.warn('[Mock] processPartialRefund failed — the mock dataset sets declines.refunds');
|
|
16
|
+
throw new Error('Cloud refund failed');
|
|
17
|
+
}
|
|
13
18
|
console.log('[Mock] processPartialRefund called', {
|
|
14
19
|
...params,
|
|
15
20
|
openUI: params?.openUI ?? true,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { applyMockPayment, MOCK_CART } from '../../demo/database';
|
|
1
|
+
import { applyMockPayment, assertMockPaymentNotDeclined, MOCK_CART } from '../../demo/database';
|
|
2
2
|
export const mockTapToPayPayment = async (params) => {
|
|
3
3
|
console.log('[Mock] tapToPayPayment called', params);
|
|
4
|
+
assertMockPaymentNotDeclined('tapToPay', false);
|
|
4
5
|
// Simulate Tap to Pay interaction
|
|
5
6
|
window.alert('Demo: Processing Tap to Pay...\n(Please tap card or device on screen)');
|
|
6
7
|
const due = params?.amount ?? MOCK_CART.amountToBeCharged ?? MOCK_CART.total;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { applyMockPayment, MOCK_CART } from '../../demo/database';
|
|
1
|
+
import { applyMockPayment, assertMockPaymentNotDeclined, MOCK_CART } from '../../demo/database';
|
|
2
2
|
export const mockTerminalPayment = async (params) => {
|
|
3
3
|
console.log('[Mock] terminalPayment called', params);
|
|
4
4
|
const connectionType = params?.paymentType || 'Cloud';
|
|
5
|
+
assertMockPaymentNotDeclined('terminal', connectionType === 'Cloud');
|
|
5
6
|
// Simulate terminal interaction
|
|
6
7
|
window.alert(`Demo: Processing ${connectionType} Terminal Payment...\n(Please tap, insert, or swipe card on terminal)`);
|
|
7
8
|
const due = params?.amount ?? MOCK_CART.amountToBeCharged ?? MOCK_CART.total;
|
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
import { UpsertCustomTableData } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* The host's upsert (kaching upsert-custom-table-data/handler.ts, customTables.ts:120-271): an `_id` of an existing row
|
|
4
|
+
* updates it, anything else inserts (a given `_id` kept as-is); `createdAt`/`updatedAt` are the host's; the row is
|
|
5
|
+
* stored, so getCustomTableData reads it back. Not modelled: the table's field schema — the host drops keys that are
|
|
6
|
+
* not fields of the table and refuses an unknown table; the mock stores every key.
|
|
7
|
+
*/
|
|
2
8
|
export declare const mockUpsertCustomTableData: UpsertCustomTableData;
|