@final-commerce/command-frame 0.12.0 → 0.13.1-preprod.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/dist/CommonTypes.d.ts +18 -1
- package/dist/actions/assign-order-user/mock.js +9 -9
- package/dist/actions/assign-order-user/types.d.ts +14 -3
- package/dist/actions/attach-checkout-contact/action.d.ts +6 -0
- package/dist/actions/attach-checkout-contact/action.js +8 -0
- package/dist/actions/attach-checkout-contact/mock.d.ts +15 -0
- package/dist/actions/attach-checkout-contact/mock.js +22 -0
- package/dist/actions/attach-checkout-contact/types.d.ts +35 -0
- package/dist/actions/attach-checkout-contact/types.js +16 -0
- package/dist/actions/get-address-options/action.d.ts +6 -0
- package/dist/actions/get-address-options/action.js +8 -0
- package/dist/actions/get-address-options/mock.d.ts +3 -0
- package/dist/actions/get-address-options/mock.js +15 -0
- package/dist/actions/get-address-options/types.d.ts +25 -0
- package/dist/actions/get-address-options/types.js +8 -0
- package/dist/actions/get-checkout-status/action.d.ts +6 -0
- package/dist/actions/get-checkout-status/action.js +8 -0
- package/dist/actions/get-checkout-status/mock.d.ts +3 -0
- package/dist/actions/get-checkout-status/mock.js +11 -0
- package/dist/actions/get-checkout-status/types.d.ts +27 -0
- package/dist/actions/get-checkout-status/types.js +12 -0
- package/dist/actions/get-orders/mock.js +9 -6
- package/dist/actions/get-orders/types.d.ts +8 -2
- package/dist/actions/get-outlets/action.js +2 -2
- package/dist/actions/get-outlets/mock.d.ts +12 -0
- package/dist/actions/get-outlets/mock.js +28 -4
- package/dist/actions/get-outlets/types.d.ts +19 -1
- package/dist/actions/get-product-modifier-selections/mock.js +7 -0
- package/dist/actions/get-product-modifier-selections/types.d.ts +4 -0
- package/dist/actions/get-products/types.d.ts +8 -8
- package/dist/actions/get-refund-plan/mockRows.js +1 -1
- package/dist/actions/get-refund-plan/types.d.ts +9 -6
- package/dist/actions/get-remaining-refundable-quantities/mock.js +1 -0
- package/dist/actions/get-remaining-refundable-quantities/types.d.ts +5 -0
- package/dist/actions/get-shipping-options/action.d.ts +6 -0
- package/dist/actions/get-shipping-options/action.js +8 -0
- package/dist/actions/get-shipping-options/mock.d.ts +3 -0
- package/dist/actions/get-shipping-options/mock.js +15 -0
- package/dist/actions/get-shipping-options/types.d.ts +43 -0
- package/dist/actions/get-shipping-options/types.js +1 -0
- package/dist/actions/process-partial-refund/types.d.ts +1 -1
- package/dist/actions/resume-checkout/action.d.ts +6 -0
- package/dist/actions/resume-checkout/action.js +8 -0
- package/dist/actions/resume-checkout/mock.d.ts +16 -0
- package/dist/actions/resume-checkout/mock.js +51 -0
- package/dist/actions/resume-checkout/types.d.ts +59 -0
- package/dist/actions/resume-checkout/types.js +13 -0
- package/dist/actions/select-shipping-option/action.d.ts +6 -0
- package/dist/actions/select-shipping-option/action.js +8 -0
- package/dist/actions/select-shipping-option/mock.d.ts +3 -0
- package/dist/actions/select-shipping-option/mock.js +31 -0
- package/dist/actions/select-shipping-option/types.d.ts +15 -0
- package/dist/actions/select-shipping-option/types.js +1 -0
- package/dist/actions/set-order-address/action.d.ts +6 -0
- package/dist/actions/set-order-address/action.js +8 -0
- package/dist/actions/set-order-address/mock.d.ts +7 -0
- package/dist/actions/set-order-address/mock.js +29 -0
- package/dist/actions/set-order-address/types.d.ts +19 -0
- package/dist/actions/set-order-address/types.js +1 -0
- package/dist/actions/set-outlet/action.d.ts +6 -0
- package/dist/actions/set-outlet/action.js +8 -0
- package/dist/actions/set-outlet/mock.d.ts +13 -0
- package/dist/actions/set-outlet/mock.js +24 -0
- package/dist/actions/set-outlet/types.d.ts +31 -0
- package/dist/actions/set-outlet/types.js +21 -0
- package/dist/actions/start-checkout/action.d.ts +6 -0
- package/dist/actions/start-checkout/action.js +8 -0
- package/dist/actions/start-checkout/mock.d.ts +16 -0
- package/dist/actions/start-checkout/mock.js +80 -0
- package/dist/actions/start-checkout/types.d.ts +90 -0
- package/dist/actions/start-checkout/types.js +10 -0
- package/dist/demo/shipping.d.ts +8 -0
- package/dist/demo/shipping.js +69 -0
- package/dist/index.d.ts +21 -1
- package/dist/index.js +19 -0
- package/dist/projects/render/mocks.js +20 -0
- package/dist/projects/render/types.d.ts +23 -1
- package/dist/pubsub/topics/checkout/checkout-started/types.d.ts +26 -0
- package/dist/pubsub/topics/checkout/checkout-started/types.js +1 -0
- package/dist/pubsub/topics/checkout/index.d.ts +7 -0
- package/dist/pubsub/topics/checkout/index.js +28 -0
- package/dist/pubsub/topics/checkout/payment-completed/types.d.ts +31 -0
- package/dist/pubsub/topics/checkout/payment-completed/types.js +1 -0
- package/dist/pubsub/topics/checkout/payment-failed/types.d.ts +23 -0
- package/dist/pubsub/topics/checkout/payment-failed/types.js +1 -0
- package/dist/pubsub/topics/checkout/types.d.ts +12 -0
- package/dist/pubsub/topics/checkout/types.js +8 -0
- package/dist/pubsub/topics/index.d.ts +18 -17
- package/dist/pubsub/topics/index.js +18 -17
- package/dist/pubsub/topics/types.d.ts +8 -0
- package/package.json +2 -2
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, 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, 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>;
|
|
@@ -108,6 +108,8 @@ export type CFActiveOrder = ActiveOrder;
|
|
|
108
108
|
export type CFActiveCustomSales = ActiveCustomSales;
|
|
109
109
|
export type CFNonRevenueItem = NonRevenueItem;
|
|
110
110
|
export type CFActiveCart = ActiveCart;
|
|
111
|
+
export type CFCartAddress = CartAddress;
|
|
112
|
+
export type CFCartShipping = CartShipping;
|
|
111
113
|
export type CFActivePark = ActivePark;
|
|
112
114
|
export type CFActiveCompany = ActiveCompany;
|
|
113
115
|
/** Tile kinds a SmartGrid cell can represent. Mirrors common's `BaseTile['type']`. */
|
|
@@ -150,6 +152,7 @@ export interface CFActiveRefundDetails {
|
|
|
150
152
|
buttonsDisabled?: Record<string, boolean>;
|
|
151
153
|
isRefund?: boolean;
|
|
152
154
|
customSalesQuantities?: Record<string, number>;
|
|
155
|
+
/** Keyed by cart-fee id, plus the reserved key `'shipping'` for the order's shipping. 0/1 each. */
|
|
153
156
|
cartFeesRefunds?: Record<string, number>;
|
|
154
157
|
tipsRefunds?: Record<string, number>;
|
|
155
158
|
refundProcessingStatus?: CFRefundProcessingStatus | null;
|
|
@@ -217,9 +220,23 @@ export interface CFOutletInfo {
|
|
|
217
220
|
state?: string;
|
|
218
221
|
postCode?: string;
|
|
219
222
|
};
|
|
223
|
+
address2?: string;
|
|
220
224
|
city?: string;
|
|
221
225
|
state?: string;
|
|
222
226
|
country?: string;
|
|
227
|
+
postCode?: string;
|
|
228
|
+
phone?: string;
|
|
229
|
+
/** Short display alias, when the merchant set one (e.g. `DTWN`). */
|
|
230
|
+
alias?: string;
|
|
231
|
+
/**
|
|
232
|
+
* Whether this location can take an ONLINE payment. Storefront only — it is
|
|
233
|
+
* absent on a register, where it has no meaning.
|
|
234
|
+
*
|
|
235
|
+
* `false` means the outlet has no completed payment connection, so starting a
|
|
236
|
+
* checkout against it WILL fail. A pickup picker must not offer it as a
|
|
237
|
+
* selectable location, or must show it as unavailable.
|
|
238
|
+
*/
|
|
239
|
+
connected?: boolean;
|
|
223
240
|
}
|
|
224
241
|
export interface CFContextManage {
|
|
225
242
|
user: unknown;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { applyAssignment } from '@final-commerce/common';
|
|
1
2
|
import { MOCK_ORDERS, MOCK_PARKED_ORDERS, MOCK_USERS, mockPublishEvent } from '../../demo/database';
|
|
2
3
|
export const mockAssignOrderUser = async (params) => {
|
|
3
4
|
console.log('[Mock] assignOrderUser called', params);
|
|
@@ -11,20 +12,19 @@ export const mockAssignOrderUser = async (params) => {
|
|
|
11
12
|
if (!order) {
|
|
12
13
|
throw new Error(`Order with ID ${params.orderId} not found`);
|
|
13
14
|
}
|
|
14
|
-
if (params.userId
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
else if (order.assignedUser?.userId !== params.userId) {
|
|
18
|
-
if (!MOCK_USERS.some((u) => u.id === params.userId)) {
|
|
19
|
-
throw new Error(`User with ID ${params.userId} not found`);
|
|
20
|
-
}
|
|
21
|
-
order.assignedUser = { userId: params.userId, assignedAt: new Date().toISOString() };
|
|
15
|
+
if (params.userId !== null && !MOCK_USERS.some((u) => u.id === params.userId)) {
|
|
16
|
+
throw new Error(`User with ID ${params.userId} not found`);
|
|
22
17
|
}
|
|
18
|
+
order.assignedUser = applyAssignment(order, {
|
|
19
|
+
userId: params.userId,
|
|
20
|
+
label: params.label,
|
|
21
|
+
at: new Date().toISOString(),
|
|
22
|
+
});
|
|
23
23
|
mockPublishEvent('orders', 'order-updated', { order });
|
|
24
24
|
return {
|
|
25
25
|
success: true,
|
|
26
26
|
orderId: order._id,
|
|
27
|
-
assignedUser: order.assignedUser
|
|
27
|
+
assignedUser: order.assignedUser,
|
|
28
28
|
timestamp: new Date().toISOString(),
|
|
29
29
|
};
|
|
30
30
|
};
|
|
@@ -2,14 +2,25 @@ import type { CFOrderAssignedUser } from '../../CommonTypes';
|
|
|
2
2
|
export interface AssignOrderUserParams {
|
|
3
3
|
/** Order to assign. Omit to target the live cart's order — it must already exist (parked, paid or transitioned). */
|
|
4
4
|
orderId?: string;
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* User to assign (e.g. the driver), or null to unassign. Assigning replaces the current
|
|
7
|
+
* assignee with the same `label`; the replaced one stays in the order's record, ended.
|
|
8
|
+
*/
|
|
6
9
|
userId: string | null;
|
|
10
|
+
/**
|
|
11
|
+
* Why the user is assigned, free text — e.g. 'delivery'. Omit for an unlabeled assignment.
|
|
12
|
+
* When unassigning, ends only this label's assignee; omit it to end every current assignment.
|
|
13
|
+
*/
|
|
14
|
+
label?: string | null;
|
|
7
15
|
}
|
|
8
16
|
export interface AssignOrderUserResponse {
|
|
9
17
|
success: boolean;
|
|
10
18
|
orderId: string;
|
|
11
|
-
/**
|
|
12
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Everyone who has had the order after the call, oldest first. Entries without
|
|
21
|
+
* `unassignedAt` are the current assignees.
|
|
22
|
+
*/
|
|
23
|
+
assignedUser: CFOrderAssignedUser[];
|
|
13
24
|
timestamp: string;
|
|
14
25
|
}
|
|
15
26
|
export type AssignOrderUser = (params: AssignOrderUserParams) => Promise<AssignOrderUserResponse>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attach Checkout Contact action
|
|
3
|
+
* Calls the attachCheckoutContact action on the parent window
|
|
4
|
+
*/
|
|
5
|
+
import { commandFrameClient } from '../../client';
|
|
6
|
+
export const attachCheckoutContact = async (params) => {
|
|
7
|
+
return await commandFrameClient.call('attachCheckoutContact', params);
|
|
8
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AttachCheckoutContact } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Standalone mock of attaching contact to an online order.
|
|
4
|
+
*
|
|
5
|
+
* It mirrors the one REFUSAL the real command makes — a missing email — because
|
|
6
|
+
* that is the mistake a builder actually hits, and a mock that accepted anything
|
|
7
|
+
* would let a checkout screen look finished and lose receipts on the published
|
|
8
|
+
* site.
|
|
9
|
+
*
|
|
10
|
+
* It does NOT mirror "no order to attach to". The real command answers
|
|
11
|
+
* `attached: false` there rather than throwing, precisely so a flow can call it
|
|
12
|
+
* optimistically and still let the shopper pay; the mock always has a checkout
|
|
13
|
+
* to attach to, so it reports `true`.
|
|
14
|
+
*/
|
|
15
|
+
export declare const mockAttachCheckoutContact: AttachCheckoutContact;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standalone mock of attaching contact to an online order.
|
|
3
|
+
*
|
|
4
|
+
* It mirrors the one REFUSAL the real command makes — a missing email — because
|
|
5
|
+
* that is the mistake a builder actually hits, and a mock that accepted anything
|
|
6
|
+
* would let a checkout screen look finished and lose receipts on the published
|
|
7
|
+
* site.
|
|
8
|
+
*
|
|
9
|
+
* It does NOT mirror "no order to attach to". The real command answers
|
|
10
|
+
* `attached: false` there rather than throwing, precisely so a flow can call it
|
|
11
|
+
* optimistically and still let the shopper pay; the mock always has a checkout
|
|
12
|
+
* to attach to, so it reports `true`.
|
|
13
|
+
*/
|
|
14
|
+
export const mockAttachCheckoutContact = async (params) => {
|
|
15
|
+
console.log('[Mock] attachCheckoutContact called', params);
|
|
16
|
+
if (!params)
|
|
17
|
+
throw new Error('Params required');
|
|
18
|
+
if (!params.email || !params.email.trim()) {
|
|
19
|
+
throw new Error('attachCheckoutContact: email is required — it is where the receipt goes');
|
|
20
|
+
}
|
|
21
|
+
return { attached: true, timestamp: new Date().toISOString() };
|
|
22
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
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
|
+
*/
|
|
5
|
+
export interface AttachCheckoutContactParams {
|
|
6
|
+
email: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
phone?: string;
|
|
9
|
+
/** Defaults to the outlet the storefront booted against. */
|
|
10
|
+
outletId?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface AttachCheckoutContactResponse {
|
|
13
|
+
/**
|
|
14
|
+
* `false` means there was nothing to attach to — no checkout had been started
|
|
15
|
+
* for this cart. It is NOT a failure to record the contact on an order that
|
|
16
|
+
* exists, and it never means the shopper was charged.
|
|
17
|
+
*/
|
|
18
|
+
attached: boolean;
|
|
19
|
+
timestamp: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Put the shopper's contact on the order `startCheckout` already created.
|
|
23
|
+
*
|
|
24
|
+
* Call it BEFORE paying, whenever the email is known — on blur, on submit, or
|
|
25
|
+
* as the shopper leaves the contact step. It reuses the order's idempotency
|
|
26
|
+
* key, so it updates that order rather than creating a second one.
|
|
27
|
+
*
|
|
28
|
+
* SAFE TO CALL TWICE. The server only ever ADDS contact to an order that has
|
|
29
|
+
* none, so a later call cannot redirect a receipt that was already addressed.
|
|
30
|
+
*
|
|
31
|
+
* NEVER THROWS FOR A MISSING ORDER. Failing to record an email must not stop a
|
|
32
|
+
* payment the shopper is trying to make, so the caller gets `attached: false`
|
|
33
|
+
* and can carry on to pay.
|
|
34
|
+
*/
|
|
35
|
+
export type AttachCheckoutContact = (params: AttachCheckoutContactParams) => Promise<AttachCheckoutContactResponse>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Attach Checkout Contact Types
|
|
2
|
+
//
|
|
3
|
+
// The second half of the online checkout. `startCheckout` creates the order and
|
|
4
|
+
// mounts the payment fields the moment a shopper ARRIVES; this puts their email
|
|
5
|
+
// on that same order once they have typed it.
|
|
6
|
+
//
|
|
7
|
+
// WHY THESE ARE TWO COMMANDS. The card fields cannot render without a payment
|
|
8
|
+
// session, a session needs a server-priced amount, and an amount needs an order.
|
|
9
|
+
// So the order has to exist before the shopper has typed anything. Demanding an
|
|
10
|
+
// email first is what forced the old "fill the form, then see the card fields"
|
|
11
|
+
// checkout — and it also meant every cart edit minted a NEW order, because the
|
|
12
|
+
// idempotency key moved with the cart.
|
|
13
|
+
//
|
|
14
|
+
// Served ONLY by the storefront runtime (a published website), like
|
|
15
|
+
// `startCheckout` itself. On a register it does not exist.
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GetAddressOptions action
|
|
3
|
+
* Calls the getAddressOptions action on the parent window
|
|
4
|
+
*/
|
|
5
|
+
import { commandFrameClient } from '../../client';
|
|
6
|
+
export const getAddressOptions = async (params) => {
|
|
7
|
+
return await commandFrameClient.call('getAddressOptions', params);
|
|
8
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CANADA_PROVINCES, listCountries } from '@final-commerce/common';
|
|
2
|
+
/** Standalone mock: the same country and province lists the runtime serves. The demo store is in Canada. */
|
|
3
|
+
export const mockGetAddressOptions = async (params) => {
|
|
4
|
+
console.log('[Mock] getAddressOptions called', params);
|
|
5
|
+
const french = params?.locale?.toLowerCase().startsWith('fr') ?? false;
|
|
6
|
+
const provinces = CANADA_PROVINCES.map((p) => ({ code: p.code, name: french ? p.nameFr : p.name }));
|
|
7
|
+
return {
|
|
8
|
+
countries: listCountries(params?.locale ?? 'en').map((c) => ({
|
|
9
|
+
...c,
|
|
10
|
+
provinces: c.code === 'CA' ? provinces : [],
|
|
11
|
+
})),
|
|
12
|
+
defaultCountry: 'CA',
|
|
13
|
+
timestamp: new Date().toISOString(),
|
|
14
|
+
};
|
|
15
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface AddressProvinceOption {
|
|
2
|
+
/** Province code, e.g. `QC`. This is what goes in the address's `state`. */
|
|
3
|
+
code: string;
|
|
4
|
+
/** In the requested locale where we have it (English or French). */
|
|
5
|
+
name: string;
|
|
6
|
+
}
|
|
7
|
+
export interface AddressCountryOption {
|
|
8
|
+
/** ISO 3166-1 alpha-2, e.g. `CA`. This is what goes in the address's `country`. */
|
|
9
|
+
code: string;
|
|
10
|
+
name: string;
|
|
11
|
+
/** Empty when the country has no province list; the form then shows a free text field. */
|
|
12
|
+
provinces: AddressProvinceOption[];
|
|
13
|
+
}
|
|
14
|
+
export interface GetAddressOptionsParams {
|
|
15
|
+
/** BCP 47 locale for the names, e.g. `fr-CA`. Defaults to `en`. */
|
|
16
|
+
locale?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface GetAddressOptionsResponse {
|
|
19
|
+
/** Sorted by name in the requested locale. */
|
|
20
|
+
countries: AddressCountryOption[];
|
|
21
|
+
/** The store's own country, to preselect. Null when it isn't known. */
|
|
22
|
+
defaultCountry: string | null;
|
|
23
|
+
timestamp: string;
|
|
24
|
+
}
|
|
25
|
+
export type GetAddressOptions = (params?: GetAddressOptionsParams) => Promise<GetAddressOptionsResponse>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Get Address Options Types
|
|
2
|
+
//
|
|
3
|
+
// What an address form offers: countries, and the provinces of the countries that
|
|
4
|
+
// have them (Canada today). A flow renders these as dropdowns and keeps no list of
|
|
5
|
+
// its own, so what the shopper picks is always a code the shipping rules match.
|
|
6
|
+
//
|
|
7
|
+
// Served by the till and by the storefront runtime.
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get Checkout Status action
|
|
3
|
+
* Calls the getCheckoutStatus action on the parent window
|
|
4
|
+
*/
|
|
5
|
+
import { commandFrameClient } from '../../client';
|
|
6
|
+
export const getCheckoutStatus = async (params) => {
|
|
7
|
+
return await commandFrameClient.call('getCheckoutStatus', params ?? {});
|
|
8
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Standalone mock: every order the preview knows about is already paid. */
|
|
2
|
+
export const mockGetCheckoutStatus = async (params) => {
|
|
3
|
+
console.log('[Mock] getCheckoutStatus called', params);
|
|
4
|
+
return {
|
|
5
|
+
success: true,
|
|
6
|
+
status: 'paid',
|
|
7
|
+
paid: true,
|
|
8
|
+
receiptId: params?.receiptId ?? 'ON-001-000001',
|
|
9
|
+
timestamp: new Date().toISOString(),
|
|
10
|
+
};
|
|
11
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** The shopper-facing vocabulary: never the platform's payment / fulfilment states. */
|
|
2
|
+
export type CheckoutOrderStatus = 'awaiting_payment' | 'confirming' | 'payment_failed' | 'paid' | 'fulfilled' | 'partially_refunded' | 'refunded' | 'cancelled';
|
|
3
|
+
/** Why the server cancelled an order the shopper paid for. */
|
|
4
|
+
export type CheckoutCancelReason = 'sold_out';
|
|
5
|
+
export interface GetCheckoutStatusParams {
|
|
6
|
+
/**
|
|
7
|
+
* The receipt `startCheckout` returned, to name which order. Omitted: the checkout in flight,
|
|
8
|
+
* else the last one this tab finished.
|
|
9
|
+
*/
|
|
10
|
+
receiptId?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface GetCheckoutStatusResponse {
|
|
13
|
+
success: boolean;
|
|
14
|
+
/** `null` when this tab placed no such order, or the server could not be asked. */
|
|
15
|
+
status: CheckoutOrderStatus | null;
|
|
16
|
+
/** True once the money is in: `paid`, `fulfilled`, or refunded since. */
|
|
17
|
+
paid: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Why a `cancelled` order was cancelled, when the server cancelled it. `sold_out`: the stock ran out
|
|
20
|
+
* before the payment was captured, so the payment was released and nothing was charged.
|
|
21
|
+
*/
|
|
22
|
+
cancelReason?: CheckoutCancelReason;
|
|
23
|
+
receiptId: string | null;
|
|
24
|
+
timestamp: string;
|
|
25
|
+
}
|
|
26
|
+
/** Never throws for an unknown order: answers `status: null`. */
|
|
27
|
+
export type GetCheckoutStatus = (params?: GetCheckoutStatusParams) => Promise<GetCheckoutStatusResponse>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Get Checkout Status Types
|
|
2
|
+
//
|
|
3
|
+
// Where an online order stands NOW, as the server holds it. A confirmation page uses it to move
|
|
4
|
+
// from "we're confirming your payment" to "paid" once the payment provider's notification has
|
|
5
|
+
// landed, which the browser alone can never know: it only ever sees that the provider ACCEPTED
|
|
6
|
+
// a payment.
|
|
7
|
+
//
|
|
8
|
+
// The runtime asks about a checkout THIS TAB placed (it keeps the checkout's key), so a page can
|
|
9
|
+
// only ever ask about its own order. No password: the server no longer issues one.
|
|
10
|
+
//
|
|
11
|
+
// Served ONLY by the storefront runtime (a published website). On a register it does not exist.
|
|
12
|
+
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { currentAssignees } from '@final-commerce/common';
|
|
1
2
|
import { MOCK_ORDERS, MOCK_PARKED_ORDERS, MOCK_USERS, MOCK_STATIONS, MOCK_OUTLETS, safeSerialize, } from '../../demo/database';
|
|
2
3
|
export const mockGetOrders = async (params) => {
|
|
3
4
|
console.log('[Mock] getOrders called', params);
|
|
@@ -5,7 +6,7 @@ export const mockGetOrders = async (params) => {
|
|
|
5
6
|
let orders = safeSerialize([...MOCK_ORDERS, ...MOCK_PARKED_ORDERS]);
|
|
6
7
|
// Filter simulation
|
|
7
8
|
if (params) {
|
|
8
|
-
const { customerId, status, sessionId, paymentState, fulfillmentState, outletId, assignedUserId, inCart, orderType, customStatusId, customStatusKey, scheduled, scheduledFrom, scheduledTo, searchValue, limit, offset, sortBy, sortDirection, } = params;
|
|
9
|
+
const { customerId, status, sessionId, paymentState, fulfillmentState, outletId, assignedUserId, assignedUserLabel, inCart, orderType, customStatusId, customStatusKey, scheduled, scheduledFrom, scheduledTo, searchValue, limit, offset, sortBy, sortDirection, } = params;
|
|
9
10
|
// 1. Filtering
|
|
10
11
|
if (customerId) {
|
|
11
12
|
orders = orders.filter((o) => o.customer && '_id' in o.customer && o.customer._id === customerId);
|
|
@@ -27,11 +28,13 @@ export const mockGetOrders = async (params) => {
|
|
|
27
28
|
if (outletId) {
|
|
28
29
|
orders = orders.filter((o) => o.posData?.outlet === outletId);
|
|
29
30
|
}
|
|
30
|
-
if (assignedUserId) {
|
|
31
|
-
orders = orders.filter((o) =>
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
if (assignedUserId || assignedUserId === null || assignedUserLabel !== undefined) {
|
|
32
|
+
orders = orders.filter((o) => {
|
|
33
|
+
const holders = currentAssignees(o, assignedUserLabel);
|
|
34
|
+
if (assignedUserId === null)
|
|
35
|
+
return holders.length === 0;
|
|
36
|
+
return assignedUserId ? holders.some((a) => a.userId === assignedUserId) : holders.length > 0;
|
|
37
|
+
});
|
|
35
38
|
}
|
|
36
39
|
if (orderType !== undefined) {
|
|
37
40
|
const types = Array.isArray(orderType) ? orderType : [orderType];
|
|
@@ -11,10 +11,16 @@ export interface GetOrdersParams {
|
|
|
11
11
|
/** Only orders placed at this outlet (`posData.outlet`). Without it, orders from every outlet in the company are returned. */
|
|
12
12
|
outletId?: string;
|
|
13
13
|
/**
|
|
14
|
-
* Only orders assigned to
|
|
15
|
-
* `null`: only orders nobody is assigned to
|
|
14
|
+
* Only orders this user is assigned to now (a current `assignedUser` entry), e.g. a driver's deliveries.
|
|
15
|
+
* `null`: only orders nobody is assigned to now (e.g. deliveries awaiting a driver).
|
|
16
16
|
*/
|
|
17
17
|
assignedUserId?: string | null;
|
|
18
|
+
/**
|
|
19
|
+
* Narrows the assignee test to assignments with this label, e.g. 'delivery': with `assignedUserId`, that
|
|
20
|
+
* user must hold the order under this label; with `assignedUserId: null`, nobody holds it under this label;
|
|
21
|
+
* alone, someone holds it under this label. `null` means unlabeled assignments.
|
|
22
|
+
*/
|
|
23
|
+
assignedUserLabel?: string | null;
|
|
18
24
|
/**
|
|
19
25
|
* true: only orders loaded in a cart; false: only orders that aren't. Reads `order.inCart` when the order
|
|
20
26
|
* carries it, otherwise the legacy reading (fulfillment 'draft' / status 'in-cart').
|
|
@@ -1,2 +1,14 @@
|
|
|
1
1
|
import { GetOutlets } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* A second location that CANNOT take an online payment.
|
|
4
|
+
*
|
|
5
|
+
* The shared demo data has exactly one outlet, so without this a pickup picker
|
|
6
|
+
* built against mocks would never render the case that matters: an outlet a
|
|
7
|
+
* shopper must not be allowed to check out against. `setOutlet` refuses this id,
|
|
8
|
+
* so the two mocks tell the same story.
|
|
9
|
+
*
|
|
10
|
+
* Mock-only, and deliberately NOT added to `MOCK_OUTLETS` — that array is shared
|
|
11
|
+
* with every other outlet mock and `MOCK_OUTLET`.
|
|
12
|
+
*/
|
|
13
|
+
export declare const MOCK_UNCONNECTED_OUTLET_ID = "outlet-mock-unconnected";
|
|
2
14
|
export declare const mockGetOutlets: GetOutlets;
|
|
@@ -1,16 +1,40 @@
|
|
|
1
1
|
import { MOCK_OUTLETS, safeSerialize } from "../../demo/database";
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* A second location that CANNOT take an online payment.
|
|
4
|
+
*
|
|
5
|
+
* The shared demo data has exactly one outlet, so without this a pickup picker
|
|
6
|
+
* built against mocks would never render the case that matters: an outlet a
|
|
7
|
+
* shopper must not be allowed to check out against. `setOutlet` refuses this id,
|
|
8
|
+
* so the two mocks tell the same story.
|
|
9
|
+
*
|
|
10
|
+
* Mock-only, and deliberately NOT added to `MOCK_OUTLETS` — that array is shared
|
|
11
|
+
* with every other outlet mock and `MOCK_OUTLET`.
|
|
12
|
+
*/
|
|
13
|
+
export const MOCK_UNCONNECTED_OUTLET_ID = "outlet-mock-unconnected";
|
|
14
|
+
const UNCONNECTED_OUTLET = {
|
|
15
|
+
_id: MOCK_UNCONNECTED_OUTLET_ID,
|
|
16
|
+
name: "Airport (not connected)",
|
|
17
|
+
address: "2 Terminal Road",
|
|
18
|
+
city: "San Francisco",
|
|
19
|
+
state: "CA",
|
|
20
|
+
country: "US",
|
|
21
|
+
postCode: "94128",
|
|
22
|
+
connected: false
|
|
23
|
+
};
|
|
24
|
+
export const mockGetOutlets = async (params) => {
|
|
25
|
+
console.log("[Mock] getOutlets called", params);
|
|
4
26
|
const outlets = safeSerialize(MOCK_OUTLETS).map((o) => ({
|
|
5
27
|
_id: o._id || o.id,
|
|
6
28
|
name: o.name || "",
|
|
7
29
|
address: o.address,
|
|
8
30
|
city: o.city,
|
|
9
31
|
state: o.state,
|
|
10
|
-
country: o.country
|
|
32
|
+
country: o.country,
|
|
33
|
+
connected: true
|
|
11
34
|
}));
|
|
35
|
+
const all = [...outlets, UNCONNECTED_OUTLET];
|
|
12
36
|
return {
|
|
13
|
-
outlets,
|
|
37
|
+
outlets: params?.connectedOnly ? all.filter((o) => o.connected) : all,
|
|
14
38
|
timestamp: new Date().toISOString()
|
|
15
39
|
};
|
|
16
40
|
};
|
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import { CFOutletInfo } from "../../CommonTypes";
|
|
2
|
+
export interface GetOutletsParams {
|
|
3
|
+
/**
|
|
4
|
+
* Return only locations that can take an online payment. Storefront only.
|
|
5
|
+
* Default `false`, so a flow sees every outlet and decides how to present
|
|
6
|
+
* the ones it cannot sell from.
|
|
7
|
+
*/
|
|
8
|
+
connectedOnly?: boolean;
|
|
9
|
+
}
|
|
2
10
|
export interface GetOutletsResponse {
|
|
3
11
|
outlets: CFOutletInfo[];
|
|
4
12
|
timestamp: string;
|
|
5
13
|
}
|
|
6
|
-
|
|
14
|
+
/**
|
|
15
|
+
* List the company's outlets.
|
|
16
|
+
*
|
|
17
|
+
* ON A STOREFRONT this is the pickup picker's source. Each outlet carries
|
|
18
|
+
* `connected`, and an outlet with `connected: false` cannot take a payment, so
|
|
19
|
+
* `setOutlet` refuses it — filter those out rather than relying on the refusal.
|
|
20
|
+
*
|
|
21
|
+
* There are no coordinates on an outlet, so "closest branch" is not something
|
|
22
|
+
* this can answer; sort on the address fields or let the shopper choose.
|
|
23
|
+
*/
|
|
24
|
+
export type GetOutlets = (params?: GetOutletsParams) => Promise<GetOutletsResponse>;
|
|
@@ -13,6 +13,8 @@ export const mockGetProductModifierSelections = async (params) => {
|
|
|
13
13
|
selections: [],
|
|
14
14
|
rows: [],
|
|
15
15
|
modifiersTotal: 0,
|
|
16
|
+
lineTotal: 0,
|
|
17
|
+
unitTotal: 0,
|
|
16
18
|
timestamp: new Date().toISOString(),
|
|
17
19
|
};
|
|
18
20
|
}
|
|
@@ -25,18 +27,23 @@ export const mockGetProductModifierSelections = async (params) => {
|
|
|
25
27
|
selections: [],
|
|
26
28
|
rows: [],
|
|
27
29
|
modifiersTotal: 0,
|
|
30
|
+
lineTotal: 0,
|
|
31
|
+
unitTotal: 0,
|
|
28
32
|
timestamp: new Date().toISOString(),
|
|
29
33
|
};
|
|
30
34
|
}
|
|
31
35
|
// The priced rows already sit on the line; extending them by the line quantity is
|
|
32
36
|
// the host's job, not the flow's.
|
|
33
37
|
const { rows, modifiersTotal } = buildModifierRows(line.modifiers, line.quantity);
|
|
38
|
+
const { modifiersTotal: unitModifiers } = buildModifierRows(line.modifiers, 1);
|
|
34
39
|
return {
|
|
35
40
|
success: true,
|
|
36
41
|
internalId: line.internalId,
|
|
37
42
|
selections: line.modifierSelections ?? [],
|
|
38
43
|
rows,
|
|
39
44
|
modifiersTotal,
|
|
45
|
+
lineTotal: line.price * line.quantity + modifiersTotal,
|
|
46
|
+
unitTotal: line.price + unitModifiers,
|
|
40
47
|
timestamp: new Date().toISOString(),
|
|
41
48
|
};
|
|
42
49
|
};
|
|
@@ -18,6 +18,10 @@ export interface GetProductModifierSelectionsResponse {
|
|
|
18
18
|
rows: CFProdModifierBreakdown[];
|
|
19
19
|
/** Sum of `rows[].amount` for THIS line, in minor units. */
|
|
20
20
|
modifiersTotal: number;
|
|
21
|
+
/** The whole line as the cart prices it (item, its discounts, fees and modifiers), in minor units. */
|
|
22
|
+
lineTotal: number;
|
|
23
|
+
/** The same for ONE unit, for an "each" price. */
|
|
24
|
+
unitTotal: number;
|
|
21
25
|
timestamp: string;
|
|
22
26
|
}
|
|
23
27
|
export type GetProductModifierSelections = (params?: GetProductModifierSelectionsParams) => Promise<GetProductModifierSelectionsResponse>;
|
|
@@ -44,20 +44,20 @@ export interface GetProductsParams {
|
|
|
44
44
|
externalId?: string;
|
|
45
45
|
/**
|
|
46
46
|
* Product-level flag filter. `true` = only products flagged `notSoldInPos`; `false` = only
|
|
47
|
-
* products NOT flagged (a missing field counts as not flagged).
|
|
48
|
-
* default
|
|
47
|
+
* products NOT flagged (a missing field counts as not flagged). On a till, naming it switches off
|
|
48
|
+
* the default exclusion, so `true` works without `includeUnlisted`.
|
|
49
49
|
*/
|
|
50
50
|
notSoldInPos?: boolean;
|
|
51
|
-
/** Product-level flag filter: `true` = flagged `notSoldOnline`; `false` = sold online (missing counts). */
|
|
51
|
+
/** Product-level flag filter: `true` = flagged `notSoldOnline`; `false` = sold online (missing counts). On a storefront, naming it switches off the default exclusion. */
|
|
52
52
|
notSoldOnline?: boolean;
|
|
53
53
|
[key: string]: any;
|
|
54
54
|
};
|
|
55
55
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
56
|
+
* The sales-channel flag follows the host channel: `notSoldInPos` on a till, `notSoldOnline` on a
|
|
57
|
+
* storefront. Products with that flag are left out unless this is `true` (default `false`) or the
|
|
58
|
+
* query names that flag. They still cannot be sold: `addProductToCart` / `addBookingToCart` refuse a
|
|
59
|
+
* product or a variant with it. Without it, flagged variants are also stripped from `variants[]`
|
|
60
|
+
* and a product whose every variant is flagged is left out — whatever the query says.
|
|
61
61
|
*/
|
|
62
62
|
includeUnlisted?: boolean;
|
|
63
63
|
/** Defaults to 0. */
|
|
@@ -96,7 +96,7 @@ export function mockRefundBreakdown(rows, items) {
|
|
|
96
96
|
return { type: row.type, itemKey: row.itemKey, quantity, amounts: scale(row.amounts, quantity / row.quantity) };
|
|
97
97
|
})
|
|
98
98
|
.filter((r) => r.quantity > 0);
|
|
99
|
-
const totals = { items: 0, discounts: 0, fees: 0, tax: 0, tip: 0, total: 0 };
|
|
99
|
+
const totals = { items: 0, discounts: 0, fees: 0, shipping: 0, tax: 0, tip: 0, total: 0 };
|
|
100
100
|
for (const r of selected) {
|
|
101
101
|
const a = r.amounts;
|
|
102
102
|
if (r.type === 'fee')
|