@final-commerce/command-frame 0.8.0 → 0.8.1-online.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.
Files changed (47) hide show
  1. package/dist/CommonTypes.d.ts +14 -0
  2. package/dist/actions/attach-checkout-contact/action.d.ts +6 -0
  3. package/dist/actions/attach-checkout-contact/action.js +8 -0
  4. package/dist/actions/attach-checkout-contact/mock.d.ts +15 -0
  5. package/dist/actions/attach-checkout-contact/mock.js +22 -0
  6. package/dist/actions/attach-checkout-contact/types.d.ts +35 -0
  7. package/dist/actions/attach-checkout-contact/types.js +16 -0
  8. package/dist/actions/get-outlets/action.js +2 -2
  9. package/dist/actions/get-outlets/mock.d.ts +12 -0
  10. package/dist/actions/get-outlets/mock.js +28 -4
  11. package/dist/actions/get-outlets/types.d.ts +19 -1
  12. package/dist/actions/resume-checkout/action.d.ts +6 -0
  13. package/dist/actions/resume-checkout/action.js +8 -0
  14. package/dist/actions/resume-checkout/mock.d.ts +16 -0
  15. package/dist/actions/resume-checkout/mock.js +51 -0
  16. package/dist/actions/resume-checkout/types.d.ts +59 -0
  17. package/dist/actions/resume-checkout/types.js +13 -0
  18. package/dist/actions/set-outlet/action.d.ts +6 -0
  19. package/dist/actions/set-outlet/action.js +8 -0
  20. package/dist/actions/set-outlet/mock.d.ts +13 -0
  21. package/dist/actions/set-outlet/mock.js +24 -0
  22. package/dist/actions/set-outlet/types.d.ts +31 -0
  23. package/dist/actions/set-outlet/types.js +21 -0
  24. package/dist/actions/start-checkout/action.d.ts +6 -0
  25. package/dist/actions/start-checkout/action.js +8 -0
  26. package/dist/actions/start-checkout/mock.d.ts +16 -0
  27. package/dist/actions/start-checkout/mock.js +80 -0
  28. package/dist/actions/start-checkout/types.d.ts +90 -0
  29. package/dist/actions/start-checkout/types.js +10 -0
  30. package/dist/index.d.ts +11 -1
  31. package/dist/index.js +9 -0
  32. package/dist/projects/render/mocks.js +10 -0
  33. package/dist/projects/render/types.d.ts +16 -1
  34. package/dist/pubsub/topics/checkout/checkout-started/types.d.ts +26 -0
  35. package/dist/pubsub/topics/checkout/checkout-started/types.js +1 -0
  36. package/dist/pubsub/topics/checkout/index.d.ts +7 -0
  37. package/dist/pubsub/topics/checkout/index.js +28 -0
  38. package/dist/pubsub/topics/checkout/payment-completed/types.d.ts +31 -0
  39. package/dist/pubsub/topics/checkout/payment-completed/types.js +1 -0
  40. package/dist/pubsub/topics/checkout/payment-failed/types.d.ts +20 -0
  41. package/dist/pubsub/topics/checkout/payment-failed/types.js +1 -0
  42. package/dist/pubsub/topics/checkout/types.d.ts +12 -0
  43. package/dist/pubsub/topics/checkout/types.js +8 -0
  44. package/dist/pubsub/topics/index.d.ts +18 -17
  45. package/dist/pubsub/topics/index.js +18 -17
  46. package/dist/pubsub/topics/types.d.ts +8 -0
  47. package/package.json +2 -2
@@ -181,9 +181,23 @@ export interface CFOutletInfo {
181
181
  state?: string;
182
182
  postCode?: string;
183
183
  };
184
+ address2?: string;
184
185
  city?: string;
185
186
  state?: string;
186
187
  country?: string;
188
+ postCode?: string;
189
+ phone?: string;
190
+ /** Short display alias, when the merchant set one (e.g. `DTWN`). */
191
+ alias?: string;
192
+ /**
193
+ * Whether this location can take an ONLINE payment. Storefront only — it is
194
+ * absent on a register, where it has no meaning.
195
+ *
196
+ * `false` means the outlet has no completed payment connection, so starting a
197
+ * checkout against it WILL fail. A pickup picker must not offer it as a
198
+ * selectable location, or must show it as unavailable.
199
+ */
200
+ connected?: boolean;
187
201
  }
188
202
  export interface CFContextManage {
189
203
  user: unknown;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Attach Checkout Contact action
3
+ * Calls the attachCheckoutContact action on the parent window
4
+ */
5
+ import type { AttachCheckoutContact } from './types';
6
+ export declare const attachCheckoutContact: AttachCheckoutContact;
@@ -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 {};
@@ -1,4 +1,4 @@
1
1
  import { commandFrameClient } from "../../client";
2
- export const getOutlets = async () => {
3
- return await commandFrameClient.call("getOutlets");
2
+ export const getOutlets = async (params) => {
3
+ return await commandFrameClient.call("getOutlets", params ?? {});
4
4
  };
@@ -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
- export const mockGetOutlets = async () => {
3
- console.log("[Mock] getOutlets called");
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
- export type GetOutlets = () => Promise<GetOutletsResponse>;
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>;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Resume Checkout action
3
+ * Calls the resumeCheckout action on the parent window
4
+ */
5
+ import type { ResumeCheckout } from './types';
6
+ export declare const resumeCheckout: ResumeCheckout;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Resume Checkout action
3
+ * Calls the resumeCheckout action on the parent window
4
+ */
5
+ import { commandFrameClient } from '../../client';
6
+ export const resumeCheckout = async (params) => {
7
+ return await commandFrameClient.call('resumeCheckout', params ?? {});
8
+ };
@@ -0,0 +1,16 @@
1
+ import { ResumeCheckout } from './types';
2
+ /**
3
+ * Standalone mock of the checkout return leg.
4
+ *
5
+ * It reads the SAME query parameters the real provider sends a shopper back
6
+ * with, so a builder can exercise the redirect path in preview by appending
7
+ * them by hand:
8
+ *
9
+ * ?sessionId=CS_MOCK&redirectResult=MOCK
10
+ *
11
+ * Without them it answers `{ resumed: false }` and does nothing, which is what
12
+ * the real command does on an ordinary page load. That is the branch a
13
+ * checkout screen hits on every normal visit, so it is the one worth getting
14
+ * right in a mock.
15
+ */
16
+ export declare const mockResumeCheckout: ResumeCheckout;
@@ -0,0 +1,51 @@
1
+ import { mockPublishEvent } from '../../demo/database';
2
+ /**
3
+ * Standalone mock of the checkout return leg.
4
+ *
5
+ * It reads the SAME query parameters the real provider sends a shopper back
6
+ * with, so a builder can exercise the redirect path in preview by appending
7
+ * them by hand:
8
+ *
9
+ * ?sessionId=CS_MOCK&redirectResult=MOCK
10
+ *
11
+ * Without them it answers `{ resumed: false }` and does nothing, which is what
12
+ * the real command does on an ordinary page load. That is the branch a
13
+ * checkout screen hits on every normal visit, so it is the one worth getting
14
+ * right in a mock.
15
+ */
16
+ export const mockResumeCheckout = async (params) => {
17
+ console.log('[Mock] resumeCheckout called', params);
18
+ const href = params?.url ?? (typeof window === 'undefined' ? '' : window.location.href);
19
+ let redirectResult = null;
20
+ try {
21
+ redirectResult = new URL(href).searchParams.get('redirectResult');
22
+ }
23
+ catch {
24
+ redirectResult = null;
25
+ }
26
+ if (!redirectResult) {
27
+ return {
28
+ success: true,
29
+ timestamp: new Date().toISOString(),
30
+ checkout: { resumed: false },
31
+ };
32
+ }
33
+ const suffix = String(Date.now()).slice(-6);
34
+ const checkout = {
35
+ resumed: true,
36
+ orderId: `mock-order-${suffix}`,
37
+ receiptId: `ON-001-${suffix}`,
38
+ // `Authorised` is what the real provider reports for a completed redirect.
39
+ // It is NOT settlement — see the checkout topic docs.
40
+ resultCode: 'Authorised',
41
+ sessionResult: `mock-session-result-${suffix}`,
42
+ orderPassword: `mock-pw-${suffix}`,
43
+ };
44
+ mockPublishEvent('checkout', 'payment-completed', {
45
+ orderId: checkout.orderId,
46
+ receiptId: checkout.receiptId,
47
+ resultCode: checkout.resultCode,
48
+ sessionResult: checkout.sessionResult,
49
+ });
50
+ return { success: true, timestamp: new Date().toISOString(), checkout };
51
+ };
@@ -0,0 +1,59 @@
1
+ export interface ResumeCheckoutParams {
2
+ /**
3
+ * Where to read the provider's return data from. Defaults to the current
4
+ * page URL, which is where the shopper has just landed.
5
+ *
6
+ * Only override this if you moved the query string somewhere else before
7
+ * calling — for example if your router strips it on mount.
8
+ */
9
+ url?: string;
10
+ }
11
+ /**
12
+ * What the return leg produced. Every field past `resumed` is absent when
13
+ * `resumed` is `false`.
14
+ */
15
+ export interface ResumeCheckoutResult {
16
+ /**
17
+ * `false` means THIS WAS AN ORDINARY PAGE LOAD — no provider return data was
18
+ * in the URL, so there was nothing to finish and nothing happened. It is not
19
+ * an error, and it is the answer on every normal visit to the page. Call this
20
+ * unconditionally when your checkout or confirmation screen mounts and branch
21
+ * on this field.
22
+ */
23
+ resumed: boolean;
24
+ orderId?: string;
25
+ receiptId?: string;
26
+ /**
27
+ * The provider's result code for the completed attempt, e.g. `Authorised` or
28
+ * `Refused`. As everywhere else in this topic, `Authorised` is an
29
+ * authorisation and NOT a settlement.
30
+ */
31
+ resultCode?: string;
32
+ /** Opaque proof of the payment — see `PaymentCompletedPayload.sessionResult`. */
33
+ sessionResult?: string;
34
+ /**
35
+ * The one-time order password from the original checkout, recovered from
36
+ * browser storage. The page that held it in memory is gone, so this is the
37
+ * only way back to the order's status after a redirect.
38
+ */
39
+ orderPassword?: string;
40
+ }
41
+ export interface ResumeCheckoutResponse {
42
+ success: boolean;
43
+ timestamp: string;
44
+ checkout: ResumeCheckoutResult;
45
+ }
46
+ /**
47
+ * Finish a checkout the shopper was redirected away from.
48
+ *
49
+ * SAFE TO CALL ON EVERY PAGE LOAD, and that is how it is meant to be used: on
50
+ * an ordinary visit it finds no return data and answers `{ resumed: false }`
51
+ * without touching the network. Calling it twice for one return is also safe —
52
+ * the return data is consumed the first time.
53
+ *
54
+ * The outcome is ALSO published on the `checkout` topic (`payment-completed` or
55
+ * `payment-failed`), exactly as it would have been had the shopper never left,
56
+ * so a page that already subscribes needs no second code path. Subscribe first,
57
+ * then call this, or the event fires before you are listening.
58
+ */
59
+ export type ResumeCheckout = (params?: ResumeCheckoutParams) => Promise<ResumeCheckoutResponse>;
@@ -0,0 +1,13 @@
1
+ // Resume Checkout Types
2
+ //
3
+ // The RETURN LEG of an online checkout. Some payment methods — and 3-D Secure
4
+ // on a card — take the shopper away to their bank or wallet and send them back
5
+ // to `returnUrl` on a FRESH PAGE LOAD. The payment is not finished at that
6
+ // point: the provider hands the browser a one-time result in the URL, and it
7
+ // has to be handed back to the provider to complete the charge.
8
+ //
9
+ // `startCheckout` cannot do this. It ran in the page that is now gone.
10
+ //
11
+ // Served ONLY by the storefront runtime (a published website), like
12
+ // `startCheckout`. On a register it does not exist.
13
+ export {};
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Set Outlet action
3
+ * Calls the setOutlet action on the parent window
4
+ */
5
+ import type { SetOutlet } from './types';
6
+ export declare const setOutlet: SetOutlet;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Set Outlet action
3
+ * Calls the setOutlet action on the parent window
4
+ */
5
+ import { commandFrameClient } from '../../client';
6
+ export const setOutlet = async (params) => {
7
+ return await commandFrameClient.call('setOutlet', params);
8
+ };
@@ -0,0 +1,13 @@
1
+ import type { SetOutlet } from './types';
2
+ /**
3
+ * Standalone mock of switching location.
4
+ *
5
+ * It mirrors the two REFUSALS the real command makes — a missing id, and an
6
+ * outlet that cannot take a payment — because those are the mistakes a builder
7
+ * actually hits. A mock that accepted anything would let a location picker look
8
+ * finished and then strand a shopper at a dead payment step on the real site.
9
+ *
10
+ * The unconnected id comes from the `getOutlets` mock rather than being repeated
11
+ * here, so the two cannot drift into telling different stories.
12
+ */
13
+ export declare const mockSetOutlet: SetOutlet;
@@ -0,0 +1,24 @@
1
+ import { MOCK_UNCONNECTED_OUTLET_ID } from '../get-outlets/mock';
2
+ /**
3
+ * Standalone mock of switching location.
4
+ *
5
+ * It mirrors the two REFUSALS the real command makes — a missing id, and an
6
+ * outlet that cannot take a payment — because those are the mistakes a builder
7
+ * actually hits. A mock that accepted anything would let a location picker look
8
+ * finished and then strand a shopper at a dead payment step on the real site.
9
+ *
10
+ * The unconnected id comes from the `getOutlets` mock rather than being repeated
11
+ * here, so the two cannot drift into telling different stories.
12
+ */
13
+ export const mockSetOutlet = async (params) => {
14
+ console.log('[Mock] setOutlet called', params);
15
+ if (!params)
16
+ throw new Error('Params required');
17
+ if (!params.outletId || !params.outletId.trim()) {
18
+ throw new Error('setOutlet: outletId is required');
19
+ }
20
+ if (params.outletId === MOCK_UNCONNECTED_OUTLET_ID) {
21
+ throw new Error(`setOutlet: outlet ${params.outletId} cannot take an online payment — offer only outlets with connected: true`);
22
+ }
23
+ return { outletId: params.outletId, rehydrated: true, timestamp: new Date().toISOString() };
24
+ };
@@ -0,0 +1,31 @@
1
+ export interface SetOutletParams {
2
+ /** An `id` from `getOutlets`. */
3
+ outletId: string;
4
+ }
5
+ export interface SetOutletResponse {
6
+ /** The location now in effect. */
7
+ outletId: string;
8
+ /**
9
+ * `true` when the catalog and tax tables were re-pulled for this location.
10
+ *
11
+ * `false` means the switch took effect but the refresh could not reach the
12
+ * server — the shop is running on what it already had cached. Totals may be
13
+ * the previous location's until the next refresh pass lands, so a flow can
14
+ * tell the shopper to try again rather than quietly showing stale tax.
15
+ */
16
+ rehydrated: boolean;
17
+ timestamp: string;
18
+ }
19
+ /**
20
+ * Switch the shop to another of the company's locations.
21
+ *
22
+ * REFUSES AN OUTLET THAT CANNOT TAKE A PAYMENT, and one that does not belong to
23
+ * this company, rather than letting either fail later at the payment step. Check
24
+ * `connected` from `getOutlets` and do not offer those, so this refusal is a
25
+ * backstop rather than something a shopper ever sees.
26
+ *
27
+ * The cart survives the switch. Its lines are kept and re-priced; a line that is
28
+ * not purchasable at the new location is not silently dropped here, it is
29
+ * reported when the checkout is next priced.
30
+ */
31
+ export type SetOutlet = (params: SetOutletParams) => Promise<SetOutletResponse>;
@@ -0,0 +1,21 @@
1
+ // Set Outlet Types
2
+ //
3
+ // Choose which of the company's locations this shop is ordering from.
4
+ //
5
+ // WHAT IT ACTUALLY CHANGES. An outlet decides three things, and a shop that lets
6
+ // a shopper pick one has to move all three together:
7
+ //
8
+ // tax rates follow the outlet's own address, so the total moves
9
+ // availability stock is counted per location, and a product can be hidden at one
10
+ // fulfilment the order records where the goods come from, so the right
11
+ // location's stock is the one that is drawn down
12
+ //
13
+ // So this is not a display preference. It re-pulls the catalog for the new
14
+ // location, re-hydrates the tax tables, and re-prices the cart. The totals a
15
+ // flow reads afterwards are the new location's.
16
+ //
17
+ // SAFE MID-CHECKOUT. Checkout starts when a shopper ARRIVES, so by the time
18
+ // anyone changes their mind an order usually already exists. That order is
19
+ // re-priced and moved to the new outlet in place, keeping its id and its receipt
20
+ // id, rather than being abandoned and replaced.
21
+ export {};
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Start Checkout action
3
+ * Calls the startCheckout action on the parent window
4
+ */
5
+ import type { StartCheckout } from './types';
6
+ export declare const startCheckout: StartCheckout;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Start Checkout action
3
+ * Calls the startCheckout action on the parent window
4
+ */
5
+ import { commandFrameClient } from '../../client';
6
+ export const startCheckout = async (params) => {
7
+ return await commandFrameClient.call('startCheckout', params);
8
+ };
@@ -0,0 +1,16 @@
1
+ import { StartCheckout } from './types';
2
+ /**
3
+ * Standalone mock of the storefront checkout.
4
+ *
5
+ * It mirrors the REFUSALS of the real command (empty cart,
6
+ * container not on the page) in the same order, because those are what a
7
+ * builder actually hits first — a mock that accepted anything would let a
8
+ * checkout screen look finished and fail on the published site.
9
+ *
10
+ * The mounted box is deliberately labelled as a mock. The real command mounts
11
+ * the provider's hosted card fields (iframes owned by the payment provider);
12
+ * nothing here takes a card number, and the placeholder must never look like
13
+ * it does. Its button publishes `payment-completed` so a flow's subscriber —
14
+ * the only way the outcome ever arrives — can be exercised in preview.
15
+ */
16
+ export declare const mockStartCheckout: StartCheckout;
@@ -0,0 +1,80 @@
1
+ import { MOCK_CART, MOCK_COMPANY, mockPublishEvent } from '../../demo/database';
2
+ /**
3
+ * Standalone mock of the storefront checkout.
4
+ *
5
+ * It mirrors the REFUSALS of the real command (empty cart,
6
+ * container not on the page) in the same order, because those are what a
7
+ * builder actually hits first — a mock that accepted anything would let a
8
+ * checkout screen look finished and fail on the published site.
9
+ *
10
+ * The mounted box is deliberately labelled as a mock. The real command mounts
11
+ * the provider's hosted card fields (iframes owned by the payment provider);
12
+ * nothing here takes a card number, and the placeholder must never look like
13
+ * it does. Its button publishes `payment-completed` so a flow's subscriber —
14
+ * the only way the outcome ever arrives — can be exercised in preview.
15
+ */
16
+ export const mockStartCheckout = async (params) => {
17
+ console.log('[Mock] startCheckout called', params);
18
+ if (!params)
19
+ throw new Error('Params required');
20
+ // No email check: contact is optional now, because checkout starts on arrival.
21
+ // Refusing here would make "start on arrival" fail in PREVIEW while working on
22
+ // the published site, which is the worst way for this to be wrong.
23
+ if (!params.container || typeof params.container !== 'string') {
24
+ throw new Error('startCheckout: container must be a CSS selector string (e.g. "#card-fields") — a DOM element cannot cross the command boundary');
25
+ }
26
+ const lineCount = (MOCK_CART.products?.length ?? 0) + (MOCK_CART.customSales?.length ?? 0);
27
+ if (!lineCount) {
28
+ throw new Error('startCheckout: cannot check out an empty cart');
29
+ }
30
+ // Resolved BEFORE the order is "created", exactly as the real command does:
31
+ // finding out the selector is wrong after an order exists would leave an
32
+ // unpaid order behind for a typo that cost nothing to catch first.
33
+ const container = typeof document === 'undefined' ? null : document.querySelector(params.container);
34
+ if (!container) {
35
+ throw new Error(`startCheckout: checkout container "${params.container}" was not found on the page`);
36
+ }
37
+ const serverTotal = Math.round(MOCK_CART.amountToBeCharged || MOCK_CART.total);
38
+ const currency = MOCK_COMPANY.settings?.currency ?? 'USD';
39
+ const suffix = String(Date.now()).slice(-6);
40
+ const order = {
41
+ orderId: `mock-order-${suffix}`,
42
+ receiptId: `ON-001-${suffix}`,
43
+ serverTotal,
44
+ currency,
45
+ paymentStatus: 'ready',
46
+ orderPassword: `mock-pw-${suffix}`,
47
+ };
48
+ mockPublishEvent('checkout', 'checkout-started', { ...order });
49
+ container.replaceChildren();
50
+ const box = document.createElement('div');
51
+ box.setAttribute('data-mock-checkout', 'true');
52
+ box.style.cssText =
53
+ 'border:1px dashed #9aa;border-radius:8px;padding:16px;font:14px system-ui,sans-serif;text-align:center;color:#334';
54
+ const label = document.createElement('div');
55
+ label.textContent = 'Mock payment fields — the published site shows the real hosted card fields here.';
56
+ label.style.cssText = 'margin-bottom:12px;opacity:.75';
57
+ const button = document.createElement('button');
58
+ button.type = 'button';
59
+ button.textContent = `Simulate paying ${(serverTotal / 100).toFixed(2)} ${currency}`;
60
+ button.style.cssText =
61
+ 'padding:10px 16px;border:0;border-radius:6px;background:#111;color:#fff;font-size:14px;cursor:pointer';
62
+ button.addEventListener('click', () => {
63
+ button.disabled = true;
64
+ // `Authorised` is what the real provider reports for a successful card
65
+ // entry. It is NOT settlement — see the checkout topic docs.
66
+ mockPublishEvent('checkout', 'payment-completed', {
67
+ orderId: order.orderId,
68
+ receiptId: order.receiptId,
69
+ resultCode: 'Authorised',
70
+ });
71
+ });
72
+ box.appendChild(label);
73
+ box.appendChild(button);
74
+ container.appendChild(box);
75
+ return {
76
+ success: true,
77
+ timestamp: new Date().toISOString(),
78
+ order,
79
+ };
80
+ };
@@ -0,0 +1,90 @@
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 StartCheckoutContact {
6
+ email: string;
7
+ name?: string;
8
+ phone?: string;
9
+ }
10
+ export interface StartCheckoutParams {
11
+ /**
12
+ * OPTIONAL, because checkout starts when the shopper ARRIVES rather than when
13
+ * they submit a form. The card fields cannot render without a payment session,
14
+ * a session needs a server-priced amount, and an amount needs an order — so the
15
+ * order has to exist before they have typed anything.
16
+ *
17
+ * Supply it afterwards with `attachCheckoutContact`, which re-sends the same
18
+ * idempotency key and so reaches the SAME order.
19
+ */
20
+ contact?: StartCheckoutContact;
21
+ /**
22
+ * CSS SELECTOR for the element the payment fields mount into, e.g.
23
+ * `'#card-fields'`. The element must already be in the DOM when this is
24
+ * called.
25
+ *
26
+ * A SELECTOR, NOT AN ELEMENT — and this is not a style preference. Command
27
+ * params cross a `postMessage` boundary, which serializes them with the
28
+ * structured clone algorithm; a DOM node is not cloneable, so passing
29
+ * `ref.current` throws `DataCloneError: HTMLDivElement object could not be
30
+ * cloned` before the command is ever sent. The string is resolved on the
31
+ * runtime's side against the same document the page rendered.
32
+ */
33
+ container: string;
34
+ /**
35
+ * Where the provider returns the shopper after a redirect payment method
36
+ * (bank apps, wallets). Defaults to the current URL.
37
+ *
38
+ * Must be HTTPS and on the SAME ORIGIN the storefront token was minted for;
39
+ * the server rejects anything else, since this URL is handed to a payment
40
+ * provider and an open redirect on a checkout is a phishing primitive.
41
+ */
42
+ returnUrl?: string;
43
+ /**
44
+ * Overrides the cart-derived idempotency key. Leave unset: the default is
45
+ * derived from the cart, which is what makes a shopper's double-tap on "Pay"
46
+ * return the SAME order instead of creating a second, abandoned one.
47
+ */
48
+ idempotencyKey?: string;
49
+ /** Defaults to the outlet the storefront booted against. */
50
+ outletId?: string;
51
+ /** Forwarded verbatim to the provider's Drop-in (`showPayButton`, `locale`, field styling, …). */
52
+ dropinConfiguration?: Record<string, unknown>;
53
+ }
54
+ /**
55
+ * The created order. Deliberately says nothing about money having moved — see
56
+ * `paymentStatus` and the `checkout` topic.
57
+ */
58
+ export interface StartCheckoutOrder {
59
+ orderId: string;
60
+ receiptId: string;
61
+ /** The SERVER-computed total in integer minor units — the only total that matters. */
62
+ serverTotal: number;
63
+ currency: string;
64
+ /**
65
+ * `ready` — the payment fields are mounted and the shopper can pay.
66
+ * `unavailable` — THE ORDER EXISTS but no payment could be started, so the
67
+ * shopper has NOT been charged. Never report this as a failed order.
68
+ */
69
+ paymentStatus: 'ready' | 'unavailable';
70
+ /**
71
+ * One-time password for reading this order back, returned only on the first
72
+ * create. It is the ONLY route to this order's status — the guest token
73
+ * grants order creation and deliberately not order reads — so a caller that
74
+ * drops it cannot check whether the payment settled.
75
+ */
76
+ orderPassword?: string;
77
+ }
78
+ export interface StartCheckoutResponse {
79
+ success: boolean;
80
+ timestamp: string;
81
+ order: StartCheckoutOrder;
82
+ }
83
+ /**
84
+ * Creates the server-priced order and mounts the provider's hosted card fields.
85
+ *
86
+ * RESOLVES WHEN THE SHOPPER *CAN* PAY, NOT WHEN THEY HAVE. The shopper has not
87
+ * typed a card yet when this returns. The outcome arrives on the `checkout`
88
+ * topic instead.
89
+ */
90
+ export type StartCheckout = (params: StartCheckoutParams) => Promise<StartCheckoutResponse>;
@@ -0,0 +1,10 @@
1
+ // Start Checkout Types
2
+ //
3
+ // The ONLINE counterpart of the POS tenders: a shopper on a published website
4
+ // pays for the current cart with their own card. Mirrors kaching's storefront
5
+ // `startCheckout` input and result. All money is integer MINOR currency units.
6
+ //
7
+ // This command is served ONLY by the storefront runtime (a published website).
8
+ // On a register it does not exist, exactly as the till tenders (`cashPayment`,
9
+ // `terminalPayment`, `tapToPayPayment`, `chargeMoto`) do not exist online.
10
+ export {};
package/dist/index.d.ts CHANGED
@@ -52,6 +52,10 @@ export declare const command: {
52
52
  readonly integrationPayment: import(".").IntegrationPayment;
53
53
  readonly createPaymentLink: import(".").CreatePaymentLink;
54
54
  readonly chargeMoto: import(".").ChargeMoto;
55
+ readonly startCheckout: import(".").StartCheckout;
56
+ readonly attachCheckoutContact: import(".").AttachCheckoutContact;
57
+ readonly setOutlet: import(".").SetOutlet;
58
+ readonly resumeCheckout: import(".").ResumeCheckout;
55
59
  readonly addCustomerNote: import(".").AddCustomerNote;
56
60
  readonly removeCustomerNote: import(".").RemoveCustomerNote;
57
61
  readonly removeCustomerFromCart: import(".").RemoveCustomerFromCart;
@@ -147,7 +151,7 @@ export type { AddProduct, AddProductParams, AddProductResponse } from './actions
147
151
  export type { EditProduct, EditProductParams, EditProductResponse } from './actions/edit-product/types';
148
152
  export type { EditProductVariants, EditProductVariantsParams, EditProductVariantsResponse, } from './actions/edit-product-variants/types';
149
153
  export type { DeleteProduct, DeleteProductParams, DeleteProductResponse } from './actions/delete-product/types';
150
- export type { GetOutlets, GetOutletsResponse } from './actions/get-outlets/types';
154
+ export type { GetOutlets, GetOutletsParams, GetOutletsResponse } from './actions/get-outlets/types';
151
155
  export type { GetStations, GetStationsParams, GetStationsResponse } from './actions/get-stations/types';
152
156
  export type { GetSmartGridLayout, GetSmartGridLayoutParams, GetSmartGridLayoutResponse, } from './actions/get-smart-grid-layout/types';
153
157
  export type { SaveSmartGridLayout, SaveSmartGridLayoutParams, SaveSmartGridLayoutResponse, } from './actions/save-smart-grid-layout/types';
@@ -196,6 +200,10 @@ export type { RedeemPayment, RedeemPaymentParams, RedeemPaymentResponse } from '
196
200
  export type { IntegrationPayment, IntegrationPaymentParams, IntegrationPaymentResponse, IntegrationEmvData, } from './actions/integration-payment/types';
197
201
  export type { CreatePaymentLink, CreatePaymentLinkParams, CreatePaymentLinkResponse, } from './actions/create-payment-link/types';
198
202
  export type { ChargeMoto, ChargeMotoParams, ChargeMotoResponse, ChargeMotoCardFields, } from './actions/charge-moto/types';
203
+ export type { StartCheckout, StartCheckoutParams, StartCheckoutResponse, StartCheckoutContact, StartCheckoutOrder, } from './actions/start-checkout/types';
204
+ export type { AttachCheckoutContact, AttachCheckoutContactParams, AttachCheckoutContactResponse, } from './actions/attach-checkout-contact/types';
205
+ export type { SetOutlet, SetOutletParams, SetOutletResponse } from './actions/set-outlet/types';
206
+ export type { ResumeCheckout, ResumeCheckoutParams, ResumeCheckoutResponse, ResumeCheckoutResult, } from './actions/resume-checkout/types';
199
207
  export type { AddCustomerNote, AddCustomerNoteParams, AddCustomerNoteResponse, } from './actions/add-customer-note/types';
200
208
  export type { RemoveCustomerNote, RemoveCustomerNoteParams, RemoveCustomerNoteResponse, } from './actions/remove-customer-note/types';
201
209
  export type { RemoveCustomerFromCart, RemoveCustomerFromCartResponse } from './actions/remove-customer-from-cart/types';
@@ -252,6 +260,7 @@ export { refundsTopic } from './pubsub/topics/refunds';
252
260
  export { productsTopic } from './pubsub/topics/products';
253
261
  export { cartTopic } from './pubsub/topics/cart';
254
262
  export { paymentsTopic } from './pubsub/topics/payments';
263
+ export { checkoutTopic } from './pubsub/topics/checkout';
255
264
  export { splitPaymentsTopic } from './pubsub/topics/split-payments';
256
265
  export { customTablesTopic } from './pubsub/topics/custom-tables';
257
266
  export { printTopic } from './pubsub/topics/print';
@@ -279,6 +288,7 @@ export type { CategoryCreatedPayload, CategoryCreatedEvent, CategoryUpdatedPaylo
279
288
  export type { AttributeCreatedPayload, AttributeCreatedEvent, AttributeUpdatedPayload, AttributeUpdatedEvent, AttributesEventType, AttributesEventPayload, } from './pubsub/topics/attributes/types';
280
289
  export type { CartCreatedPayload, CartCustomerAssignedPayload, ProductAddedPayload, ProductDeletedPayload, CartDiscountAddedPayload, CartDiscountRemovedPayload, CartFeeAddedPayload, CartFeeRemovedPayload, ProductDiscountAddedPayload, ProductDiscountRemovedPayload, ProductFeeAddedPayload, ProductFeeRemovedPayload, ProductNoteAddedPayload, ProductNoteRemovedPayload, CartCreatedEvent, CartCustomerAssignedEvent, ProductAddedEvent, ProductDeletedEvent, CartDiscountAddedEvent, CartDiscountRemovedEvent, CartFeeAddedEvent, CartFeeRemovedEvent, ProductDiscountAddedEvent, ProductDiscountRemovedEvent, ProductFeeAddedEvent, ProductFeeRemovedEvent, ProductNoteAddedEvent, ProductNoteRemovedEvent, CartEventType, CartEventPayload, } from './pubsub/topics/cart/types';
281
290
  export type { PaymentDonePayload, PaymentErrPayload, PaymentDoneEvent, PaymentErrEvent, PaymentsEventType, PaymentsEventPayload, } from './pubsub/topics/payments/types';
291
+ export type { CheckoutStartedPayload, PaymentCompletedPayload, PaymentFailedPayload, CheckoutStartedEvent, PaymentCompletedEvent, PaymentFailedEvent, CheckoutEventType, CheckoutEventPayload, } from './pubsub/topics/checkout/types';
282
292
  export type { SplitPaymentUpdatedPayload, SplitPaymentUpdatedEvent, SplitPaymentsEventType, SplitPaymentsEventPayload, } from './pubsub/topics/split-payments/types';
283
293
  export type { RowCreatedPayload, RowUpdatedPayload, RowDeletedPayload, RowCreatedEvent, RowUpdatedEvent, RowDeletedEvent, CustomTablesEventType, CustomTablesEventPayload, } from './pubsub/topics/custom-tables/types';
284
294
  export type { PrintStartedPayload, PrintCompletedPayload, PrintErrorPayload, PrintStartedEvent, PrintCompletedEvent, PrintErrorEvent, PrintEventType, PrintEventPayload, } from './pubsub/topics/print/types';
package/dist/index.js CHANGED
@@ -52,6 +52,10 @@ import { redeemPayment } from './actions/redeem-payment/action';
52
52
  import { integrationPayment } from './actions/integration-payment/action';
53
53
  import { createPaymentLink } from './actions/create-payment-link/action';
54
54
  import { chargeMoto } from './actions/charge-moto/action';
55
+ import { startCheckout } from './actions/start-checkout/action';
56
+ import { attachCheckoutContact } from './actions/attach-checkout-contact/action';
57
+ import { setOutlet } from './actions/set-outlet/action';
58
+ import { resumeCheckout } from './actions/resume-checkout/action';
55
59
  // Customer Actions
56
60
  import { addCustomerNote } from './actions/add-customer-note/action';
57
61
  import { removeCustomerNote } from './actions/remove-customer-note/action';
@@ -198,6 +202,10 @@ export const command = {
198
202
  integrationPayment,
199
203
  createPaymentLink,
200
204
  chargeMoto,
205
+ startCheckout,
206
+ attachCheckoutContact,
207
+ setOutlet,
208
+ resumeCheckout,
201
209
  // Customer Actions
202
210
  addCustomerNote,
203
211
  removeCustomerNote,
@@ -308,6 +316,7 @@ export { refundsTopic } from './pubsub/topics/refunds';
308
316
  export { productsTopic } from './pubsub/topics/products';
309
317
  export { cartTopic } from './pubsub/topics/cart';
310
318
  export { paymentsTopic } from './pubsub/topics/payments';
319
+ export { checkoutTopic } from './pubsub/topics/checkout';
311
320
  export { splitPaymentsTopic } from './pubsub/topics/split-payments';
312
321
  export { customTablesTopic } from './pubsub/topics/custom-tables';
313
322
  export { printTopic } from './pubsub/topics/print';
@@ -33,6 +33,11 @@ import { mockCashPayment } from '../../actions/cash-payment/mock';
33
33
  import { mockGetCashRoundingAmount } from '../../actions/get-cash-rounding-amount/mock';
34
34
  import { mockCreatePaymentLink } from '../../actions/create-payment-link/mock';
35
35
  import { mockChargeMoto } from '../../actions/charge-moto/mock';
36
+ import { mockStartCheckout } from '../../actions/start-checkout/mock';
37
+ import { mockAttachCheckoutContact } from '../../actions/attach-checkout-contact/mock';
38
+ import { mockGetOutlets } from '../../actions/get-outlets/mock';
39
+ import { mockSetOutlet } from '../../actions/set-outlet/mock';
40
+ import { mockResumeCheckout } from '../../actions/resume-checkout/mock';
36
41
  import { mockGetTimeClockStatus } from '../../actions/get-time-clock-status/mock';
37
42
  import { mockClearCart } from '../../actions/clear-cart/mock';
38
43
  import { mockDeleteParkedOrder } from '../../actions/delete-parked-order/mock';
@@ -139,6 +144,11 @@ export const RENDER_MOCKS = {
139
144
  getCashRoundingAmount: mockGetCashRoundingAmount,
140
145
  createPaymentLink: mockCreatePaymentLink,
141
146
  chargeMoto: mockChargeMoto,
147
+ startCheckout: mockStartCheckout,
148
+ attachCheckoutContact: mockAttachCheckoutContact,
149
+ getOutlets: mockGetOutlets,
150
+ setOutlet: mockSetOutlet,
151
+ resumeCheckout: mockResumeCheckout,
142
152
  clearCart: mockClearCart,
143
153
  deleteParkedOrder: mockDeleteParkedOrder,
144
154
  voidOrder: mockVoidOrder,
@@ -1,4 +1,4 @@
1
- import type { ExampleFunction, GetProducts, AddCustomSale, EditCustomSale, GetCustomers, AssignCustomer, AddCustomer, EditCustomer, GetCategories, GetOrders, GetRefunds, GetTaxTables, AddProductDiscount, AddProductToCart, RemoveProductFromCart, UpdateCartItemQuantity, AddCartDiscount, GetContext, GetFinalContext, AddProductNote, AddProductFee, GetProductModifierSelections, SetProductModifierSelections, SetActiveProductFee, SetActiveProductDiscount, GetActiveProduct, SetActiveProduct, AdjustInventory, AddOrderNote, AddCartFee, ClearCart, ParkOrder, ResumeParkedOrder, DeleteParkedOrder, VoidOrder, InitiateRefund, CashPayment, GetCashRoundingAmount, TapToPayPayment, TerminalPayment, ExtensionPayment, RedeemPayment, AddNonRevenueItem, AddCustomerNote, RemoveCustomerNote, RemoveCustomerFromCart, GoToStationHome, OpenCashDrawer, ShowNotification, ShowConfirmation, AuthenticateUser, PartialPayment, SwitchUser, SetRefundStockAction, SelectAllRefundItems, ResetRefundDetails, CalculateRefundTotal, GetRemainingRefundableQuantities, ProcessPartialRefund, RedeemRefund, GetRefundPlan, CheckPermission, GetCurrentCart, Print, SetActiveOrder, GetCustomTables, GetCustomTableData, UpsertCustomTableData, DeleteCustomTableData, GetCustomExtensions, GetCurrentCompanyCustomExtensions, GetCustomExtensionCustomTables, GetCustomTableFields, GetSecretsKeys, GetSecretVal, SetSecretVal, GetUsers, GetRoles, RemoveCartDiscount, GetActiveOrder, GetActiveCustomer, SetActiveCustomer, GetActiveOutlet, GetActiveStation, GetActiveSession, GetActiveUser, SetActiveUser, SetActiveRefund, RemoveProductDiscount, RemoveProductFee, RemoveProductNote, RemoveCartFee, RemoveOrderNote, RemoveCustomSale, RemoveNonRevenueItem, CanTransition, GetAvailableTransitions, ApplyTransition, IntegrationPayment, GetSmartGridLayout, SaveSmartGridLayout, SendEmail, SendSms, GetBookingResources, GetBookingAvailability, GetBookings, HoldBooking, AddBookingToCart, RemoveBookingFromCart, CancelBooking, CreatePaymentLink, ChargeMoto, GetTimeClockStatus } from '../../index';
1
+ import type { ExampleFunction, GetProducts, AddCustomSale, EditCustomSale, GetCustomers, AssignCustomer, AddCustomer, EditCustomer, GetCategories, GetOrders, GetRefunds, GetTaxTables, AddProductDiscount, AddProductToCart, RemoveProductFromCart, UpdateCartItemQuantity, AddCartDiscount, GetContext, GetFinalContext, AddProductNote, AddProductFee, GetProductModifierSelections, SetProductModifierSelections, SetActiveProductFee, SetActiveProductDiscount, GetActiveProduct, SetActiveProduct, AdjustInventory, AddOrderNote, AddCartFee, ClearCart, ParkOrder, ResumeParkedOrder, DeleteParkedOrder, VoidOrder, InitiateRefund, CashPayment, GetCashRoundingAmount, TapToPayPayment, TerminalPayment, ExtensionPayment, RedeemPayment, AddNonRevenueItem, AddCustomerNote, RemoveCustomerNote, RemoveCustomerFromCart, GoToStationHome, OpenCashDrawer, ShowNotification, ShowConfirmation, AuthenticateUser, PartialPayment, SwitchUser, SetRefundStockAction, SelectAllRefundItems, ResetRefundDetails, CalculateRefundTotal, GetRemainingRefundableQuantities, ProcessPartialRefund, RedeemRefund, GetRefundPlan, CheckPermission, GetCurrentCart, Print, SetActiveOrder, GetCustomTables, GetCustomTableData, UpsertCustomTableData, DeleteCustomTableData, GetCustomExtensions, GetCurrentCompanyCustomExtensions, GetCustomExtensionCustomTables, GetCustomTableFields, GetSecretsKeys, GetSecretVal, SetSecretVal, GetUsers, GetRoles, RemoveCartDiscount, GetActiveOrder, GetActiveCustomer, SetActiveCustomer, GetActiveOutlet, GetActiveStation, GetActiveSession, GetActiveUser, SetActiveUser, SetActiveRefund, RemoveProductDiscount, RemoveProductFee, RemoveProductNote, RemoveCartFee, RemoveOrderNote, RemoveCustomSale, RemoveNonRevenueItem, CanTransition, GetAvailableTransitions, ApplyTransition, IntegrationPayment, GetSmartGridLayout, SaveSmartGridLayout, SendEmail, SendSms, GetBookingResources, GetBookingAvailability, GetBookings, HoldBooking, AddBookingToCart, RemoveBookingFromCart, CancelBooking, CreatePaymentLink, ChargeMoto, StartCheckout, AttachCheckoutContact, GetOutlets, SetOutlet, ResumeCheckout, GetTimeClockStatus } from '../../index';
2
2
  export interface RenderProviderActions {
3
3
  exampleFunction: ExampleFunction;
4
4
  getProducts: GetProducts;
@@ -52,6 +52,21 @@ export interface RenderProviderActions {
52
52
  integrationPayment: IntegrationPayment;
53
53
  createPaymentLink: CreatePaymentLink;
54
54
  chargeMoto: ChargeMoto;
55
+ /**
56
+ * ONLINE checkout — served only by the storefront runtime (a published
57
+ * website). The till tenders above are refused there, and this is refused on
58
+ * a register, because the two declare different things to the card networks.
59
+ */
60
+ startCheckout: StartCheckout;
61
+ attachCheckoutContact: AttachCheckoutContact;
62
+ getOutlets: GetOutlets;
63
+ setOutlet: SetOutlet;
64
+ /**
65
+ * The RETURN LEG of an online checkout, after a redirect payment method or
66
+ * 3-D Secure sent the shopper away and back. Storefront-only, like
67
+ * `startCheckout`, and safe to call on every page load.
68
+ */
69
+ resumeCheckout: ResumeCheckout;
55
70
  addNonRevenueItem: AddNonRevenueItem;
56
71
  addCustomerNote: AddCustomerNote;
57
72
  removeCustomerNote: RemoveCustomerNote;
@@ -0,0 +1,26 @@
1
+ import type { TopicEvent } from "../../../types";
2
+ /**
3
+ * Payload for checkout-started event
4
+ *
5
+ * The order EXISTS and is unpaid. `serverTotal` is the server's own price for
6
+ * the cart, in integer minor units — the browser never sends a price, so this
7
+ * is the first and only authoritative total.
8
+ */
9
+ export interface CheckoutStartedPayload {
10
+ orderId: string;
11
+ receiptId: string;
12
+ serverTotal: number;
13
+ currency: string;
14
+ /**
15
+ * `ready` — the payment fields are mounted and the shopper can pay.
16
+ * `unavailable` — the order exists but no payment could be started, so the
17
+ * shopper has NOT been charged.
18
+ */
19
+ paymentStatus: "ready" | "unavailable";
20
+ /** One-time password for reading this order back. Returned only on the first create. */
21
+ orderPassword?: string;
22
+ }
23
+ /**
24
+ * Typed event for checkout-started
25
+ */
26
+ export type CheckoutStartedEvent = TopicEvent<CheckoutStartedPayload>;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Checkout Topic Definition
3
+ * Defines the checkout topic and its available event types
4
+ */
5
+ import type { TopicDefinition } from "../../types";
6
+ export declare const checkoutTopic: TopicDefinition;
7
+ export * from "./types";
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Checkout Topic Definition
3
+ * Defines the checkout topic and its available event types
4
+ */
5
+ export const checkoutTopic = {
6
+ id: "checkout",
7
+ name: "Checkout",
8
+ description: "Topic for online (website) checkout events",
9
+ eventTypes: [
10
+ {
11
+ id: "checkout-started",
12
+ name: "Checkout Started",
13
+ description: "Published when the order has been created and the payment fields are mounted"
14
+ },
15
+ {
16
+ id: "payment-completed",
17
+ name: "Payment Completed",
18
+ description: "Published when the shopper's payment succeeds on their side (authorisation, not settlement)"
19
+ },
20
+ {
21
+ id: "payment-failed",
22
+ name: "Payment Failed",
23
+ description: "Published when a payment attempt is refused or errors; the shopper can retry"
24
+ }
25
+ ]
26
+ };
27
+ // Re-export types
28
+ export * from "./types";
@@ -0,0 +1,31 @@
1
+ import type { TopicEvent } from "../../../types";
2
+ /**
3
+ * Payload for payment-completed event
4
+ *
5
+ * THIS IS NOT SETTLEMENT. It reports that the SHOPPER'S side finished —
6
+ * `resultCode` is the provider's own outcome, typically `Authorised`. The money
7
+ * is confirmed by the provider's capture webhook reaching the backend, out of
8
+ * band and after this page is done, so this event cannot and does not claim it.
9
+ */
10
+ export interface PaymentCompletedPayload {
11
+ orderId: string;
12
+ receiptId: string;
13
+ /** The provider's result code, e.g. `Authorised`. */
14
+ resultCode: string;
15
+ /**
16
+ * OPAQUE PROOF OF THIS PAYMENT, handed to the browser by the provider and to
17
+ * nobody else. Pass it to the order-status read and the server verifies it
18
+ * with the provider before believing a word of it.
19
+ *
20
+ * It is the only evidence of an outcome that exists outside the provider's
21
+ * webhook, so it is what recovers an order whose confirmation never arrived.
22
+ * Treat it as a credential: do not log it, do not put it in a URL you share.
23
+ *
24
+ * Absent when the provider reported no payment (see `resultCode`).
25
+ */
26
+ sessionResult?: string;
27
+ }
28
+ /**
29
+ * Typed event for payment-completed
30
+ */
31
+ export type PaymentCompletedEvent = TopicEvent<PaymentCompletedPayload>;
@@ -0,0 +1,20 @@
1
+ import type { TopicEvent } from "../../../types";
2
+ /**
3
+ * Payload for payment-failed event
4
+ *
5
+ * ONE DECLINED ATTEMPT, NOT A DEAD CHECKOUT. The order and the mounted payment
6
+ * fields both survive, so the shopper can try another card without the page
7
+ * starting a new checkout.
8
+ */
9
+ export interface PaymentFailedPayload {
10
+ orderId: string;
11
+ receiptId: string;
12
+ /** The provider's result code, e.g. `Refused`, or an error name. */
13
+ resultCode: string;
14
+ /** Present when the failure carried a message worth showing. */
15
+ message?: string;
16
+ }
17
+ /**
18
+ * Typed event for payment-failed
19
+ */
20
+ export type PaymentFailedEvent = TopicEvent<PaymentFailedPayload>;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Checkout Topic Types
3
+ * Aggregated types for all online-checkout events
4
+ */
5
+ export * from "./checkout-started/types";
6
+ export * from "./payment-completed/types";
7
+ export * from "./payment-failed/types";
8
+ import type { CheckoutStartedPayload } from "./checkout-started/types";
9
+ import type { PaymentCompletedPayload } from "./payment-completed/types";
10
+ import type { PaymentFailedPayload } from "./payment-failed/types";
11
+ export type CheckoutEventPayload = CheckoutStartedPayload | PaymentCompletedPayload | PaymentFailedPayload;
12
+ export type CheckoutEventType = "checkout-started" | "payment-completed" | "payment-failed";
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Checkout Topic Types
3
+ * Aggregated types for all online-checkout events
4
+ */
5
+ // Re-export all event types
6
+ export * from "./checkout-started/types";
7
+ export * from "./payment-completed/types";
8
+ export * from "./payment-failed/types";
@@ -2,20 +2,21 @@
2
2
  * Pub/Sub Topics
3
3
  * Export all available topics and their types
4
4
  */
5
- export * from './customers';
6
- export * from './orders';
7
- export * from './bookings';
8
- export * from './refunds';
9
- export * from './products';
10
- export * from './cart';
11
- export * from './payments';
12
- export * from './custom-tables';
13
- export * from './print';
14
- export * from './outlet';
15
- export * from './station';
16
- export * from './session';
17
- export * from './users';
18
- export * from './variants';
19
- export * from './transactions';
20
- export * from './categories';
21
- export * from './attributes';
5
+ export * from "./customers";
6
+ export * from "./orders";
7
+ export * from "./bookings";
8
+ export * from "./refunds";
9
+ export * from "./products";
10
+ export * from "./cart";
11
+ export * from "./payments";
12
+ export * from "./checkout";
13
+ export * from "./custom-tables";
14
+ export * from "./print";
15
+ export * from "./outlet";
16
+ export * from "./station";
17
+ export * from "./session";
18
+ export * from "./users";
19
+ export * from "./variants";
20
+ export * from "./transactions";
21
+ export * from "./categories";
22
+ export * from "./attributes";
@@ -2,20 +2,21 @@
2
2
  * Pub/Sub Topics
3
3
  * Export all available topics and their types
4
4
  */
5
- export * from './customers';
6
- export * from './orders';
7
- export * from './bookings';
8
- export * from './refunds';
9
- export * from './products';
10
- export * from './cart';
11
- export * from './payments';
12
- export * from './custom-tables';
13
- export * from './print';
14
- export * from './outlet';
15
- export * from './station';
16
- export * from './session';
17
- export * from './users';
18
- export * from './variants';
19
- export * from './transactions';
20
- export * from './categories';
21
- export * from './attributes';
5
+ export * from "./customers";
6
+ export * from "./orders";
7
+ export * from "./bookings";
8
+ export * from "./refunds";
9
+ export * from "./products";
10
+ export * from "./cart";
11
+ export * from "./payments";
12
+ export * from "./checkout";
13
+ export * from "./custom-tables";
14
+ export * from "./print";
15
+ export * from "./outlet";
16
+ export * from "./station";
17
+ export * from "./session";
18
+ export * from "./users";
19
+ export * from "./variants";
20
+ export * from "./transactions";
21
+ export * from "./categories";
22
+ export * from "./attributes";
@@ -4,6 +4,7 @@ import { RefundsEventType, RefundsEventPayload } from "./refunds/types";
4
4
  import { ProductsEventType, ProductsEventPayload } from "./products/types";
5
5
  import { CartEventType, CartEventPayload } from "./cart/types";
6
6
  import { PaymentsEventType, PaymentsEventPayload } from "./payments/types";
7
+ import { CheckoutEventType, CheckoutEventPayload } from "./checkout/types";
7
8
  import { SplitPaymentsEventType, SplitPaymentsEventPayload } from "./split-payments/types";
8
9
  import { CustomTablesEventPayload, CustomTablesEventType } from "./custom-tables/types";
9
10
  import { PrintEventType, PrintEventPayload } from "./print/types";
@@ -23,6 +24,13 @@ export interface TopicEventPayloadMap {
23
24
  products: Record<ProductsEventType, ProductsEventPayload>;
24
25
  cart: Record<CartEventType, CartEventPayload>;
25
26
  payments: Record<PaymentsEventType, PaymentsEventPayload>;
27
+ /**
28
+ * ONLINE checkout only (a published website), where the POS `payments`
29
+ * topic never fires: `payments` reports a till tender settling in front of
30
+ * a cashier, while a web shopper's outcome arrives asynchronously and can
31
+ * only ever be an authorisation until the provider's webhook lands.
32
+ */
33
+ checkout: Record<CheckoutEventType, CheckoutEventPayload>;
26
34
  "split-payments": Record<SplitPaymentsEventType, SplitPaymentsEventPayload>;
27
35
  customTables: Record<CustomTablesEventType, CustomTablesEventPayload>;
28
36
  print: Record<PrintEventType, PrintEventPayload>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@final-commerce/command-frame",
3
- "version": "0.8.0",
3
+ "version": "0.8.1-online.1",
4
4
  "description": "Commands Frame library",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -57,7 +57,7 @@
57
57
  "ignoreBranchesMissingTickets": true
58
58
  },
59
59
  "dependencies": {
60
- "@final-commerce/common": "2.4.0"
60
+ "@final-commerce/common": "2.4.1-online.1"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@commitlint/cli": "^19.0.0",