@lookiero/checkout 9.8.2 → 9.8.4

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/index.js CHANGED
@@ -14,7 +14,7 @@ const bootstrap = ({ apiUrl, getAuthToken, translations, sentry, kameleoon }) =>
14
14
  });
15
15
  const firstAvailableCheckoutByCustomerId = ({ customerId }) => queryBus(viewFirstAvailableCheckoutByCustomerId({ customerId: customerId }));
16
16
  return {
17
- root: root({ Messaging, I18n, getAuthToken, sentry, kameleoon }),
17
+ root: root({ Messaging, I18n, queryBus, getAuthToken, sentry, kameleoon }),
18
18
  firstAvailableCheckoutByCustomerId,
19
19
  };
20
20
  };
@@ -24,7 +24,7 @@ const order = {
24
24
  coupon: "MYLOOKIERO",
25
25
  };
26
26
  const customer = {
27
- customerId: "74cb4f76-3f14-4983-81a6-2ee2a9a275d4",
27
+ customerId: "d3c2354b-9811-46d4-b2ba-5fba012ed94d",
28
28
  country: Country.ES,
29
29
  segment: Segment.WOMEN,
30
30
  };
@@ -79,7 +79,7 @@ const kameleoonConfig = {
79
79
  siteCode: "aplm4v3ckn",
80
80
  experiments: {},
81
81
  };
82
- const { Component: Messaging } = process.env.EXPO_PUBLIC_APP_VARIANT === "test"
82
+ const { Component: Messaging, queryBus } = process.env.EXPO_PUBLIC_APP_VARIANT === "test"
83
83
  ? checkoutMockBootstrap()
84
84
  : checkoutBootstrap({ apiUrl: () => apiUrl, getAuthToken });
85
85
  const I18n = i18n({
@@ -89,6 +89,7 @@ const I18n = i18n({
89
89
  const Root = root({
90
90
  Messaging,
91
91
  I18n,
92
+ queryBus,
92
93
  getAuthToken,
93
94
  development: false,
94
95
  sentry: () => (process.env.EXPO_PUBLIC_APP_VARIANT === "test" ? {} : sentryConfig),
@@ -1,6 +1,7 @@
1
1
  import { ComponentType } from "react";
2
2
  import { useRoutes as reactRouterUseRoutes } from "react-router-native";
3
3
  import { I18n } from "@lookiero/i18n-react";
4
+ import { QueryBus } from "@lookiero/messaging";
4
5
  import { MessagingRoot } from "@lookiero/messaging-react/bootstrap";
5
6
  import { Locale } from "@lookiero/sty-psp-locale";
6
7
  import { SentryEnvironment, SentryLoggerFunctionArgs } from "@lookiero/sty-psp-logging";
@@ -12,6 +13,7 @@ import { KameleoonEnvironment } from "../ab-testing/kameleoonEnvironment";
12
13
  interface RootFunctionArgs {
13
14
  readonly Messaging: MessagingRoot;
14
15
  readonly I18n: I18n;
16
+ readonly queryBus: QueryBus;
15
17
  readonly development?: boolean;
16
18
  readonly sentry: () => SentryEnvironment;
17
19
  readonly getAuthToken: () => Promise<string>;
@@ -4,15 +4,17 @@ import { Platform } from "react-native";
4
4
  import { useRoutes as reactRouterUseRoutes } from "react-router-native";
5
5
  import { Locale } from "@lookiero/sty-psp-locale";
6
6
  import { sentryLogger, sentryLoggerHOC } from "@lookiero/sty-psp-logging";
7
+ import { QueryBusProvider } from "./hooks/useQueryBus";
7
8
  import { Routing } from "./routing/Routing";
8
- const root = ({ Messaging, I18n, getAuthToken, development, sentry, kameleoon: kameleoonConfig }) => {
9
+ const root = ({ Messaging, I18n, queryBus, getAuthToken, development, sentry, kameleoon: kameleoonConfig, }) => {
9
10
  const logger = sentryLogger(sentry);
10
11
  const kameleoon = kameleoonConfig();
11
12
  // eslint-disable-next-line react/display-name, react/prop-types
12
13
  const Root = ({ basePath, locale = Locale.en_GB, customer, order, subscription, layout, onNotAccessible, onCheckoutSubmitted, useRedirect, useRoutes = reactRouterUseRoutes, }) => {
13
14
  const handleOnI18nError = useCallback((error) => logger.captureException(error), []);
14
15
  return (React.createElement(Messaging, { includeReactQueryDevTools: Platform.OS === "web" },
15
- React.createElement(Routing, { I18n: I18n, basePath: basePath, customer: customer, getAuthToken: getAuthToken, kameleoon: kameleoon, layout: layout, locale: locale, order: order, subscription: subscription, useRedirect: useRedirect, useRoutes: useRoutes, onCheckoutSubmitted: onCheckoutSubmitted, onI18nError: development ? undefined : handleOnI18nError, onNotAccessible: onNotAccessible })));
16
+ React.createElement(QueryBusProvider, { queryBus: queryBus },
17
+ React.createElement(Routing, { I18n: I18n, basePath: basePath, customer: customer, getAuthToken: getAuthToken, kameleoon: kameleoon, layout: layout, locale: locale, order: order, subscription: subscription, useRedirect: useRedirect, useRoutes: useRoutes, onCheckoutSubmitted: onCheckoutSubmitted, onI18nError: development ? undefined : handleOnI18nError, onNotAccessible: onNotAccessible }))));
16
18
  };
17
19
  const hoc = sentryLoggerHOC({ logger });
18
20
  /**
@@ -0,0 +1,9 @@
1
+ import { FC, ReactNode } from "react";
2
+ import { QueryBus } from "@lookiero/messaging";
3
+ interface QueryBusProviderProps {
4
+ readonly children: ReactNode;
5
+ readonly queryBus: QueryBus;
6
+ }
7
+ declare const QueryBusProvider: FC<QueryBusProviderProps>;
8
+ declare const useQueryBus: () => QueryBus;
9
+ export { useQueryBus, QueryBusProvider };
@@ -0,0 +1,10 @@
1
+ import React, { createContext, useContext } from "react";
2
+ import invariant from "tiny-invariant";
3
+ const QueryBusContext = createContext(null);
4
+ const QueryBusProvider = ({ children, queryBus }) => (React.createElement(QueryBusContext.Provider, { value: queryBus }, children));
5
+ const useQueryBus = () => {
6
+ const queryBus = useContext(QueryBusContext);
7
+ invariant(queryBus, "Your are trying to use the useQueryBus hook without wrapping your app with the <QueryBusProvider>.");
8
+ return queryBus;
9
+ };
10
+ export { useQueryBus, QueryBusProvider };
@@ -1,10 +1,12 @@
1
1
  import { useCallback, useMemo, useState } from "react";
2
2
  import { CommandStatus } from "@lookiero/messaging-react";
3
3
  import { NotificationLevel, useCreateToastNotification } from "@lookiero/sty-psp-notifications";
4
+ import { viewCheckoutBookingById, } from "../../../projection/checkoutBooking/viewCheckoutBookingById";
4
5
  import { MESSAGING_CONTEXT_ID } from "../../delivery/baseBootstrap";
5
6
  import { useSubmitCheckout as useSubmitCheckoutCommand } from "../../domain/checkout/react/useSubmitCheckout";
6
7
  import { useBlockCheckoutBooking } from "../../domain/checkoutBooking/react/useBlockCheckoutBooking";
7
8
  import { I18nMessages } from "../i18n/i18n";
9
+ import { useQueryBus } from "./useQueryBus";
8
10
  var ChargeStatus;
9
11
  (function (ChargeStatus) {
10
12
  ChargeStatus["EXECUTED"] = "EXECUTED";
@@ -17,9 +19,11 @@ var ChargeStatus;
17
19
  ChargeStatus["UNKNOWN"] = "UNKNOWN";
18
20
  })(ChargeStatus || (ChargeStatus = {}));
19
21
  const useSubmitCheckout = ({ checkoutId, checkoutBookingId, paymentFlowRef, onError, onSuccess, logger, }) => {
22
+ const queryBus = useQueryBus();
20
23
  const [submitCheckoutCommand, submitCheckoutCommandStatus] = useSubmitCheckoutCommand({ checkoutId, logger });
21
24
  const [blockCheckoutBooking, blockCheckoutBookingStatus] = useBlockCheckoutBooking({ checkoutBookingId, logger });
22
25
  const [createNotification] = useCreateToastNotification({ contextId: MESSAGING_CONTEXT_ID, logger });
26
+ const [checkoutBookingExpired, setCheckoutBookingExpired] = useState(false);
23
27
  const [startLegacyBoxCheckoutStatus, setStartLegacyBoxCheckoutStatus] = useState("idle");
24
28
  const submitCheckout = useCallback(async ({ paymentFlowPayload, sizeChangeEnabled }) => {
25
29
  try {
@@ -28,6 +32,11 @@ const useSubmitCheckout = ({ checkoutId, checkoutBookingId, paymentFlowRef, onEr
28
32
  catch (error) {
29
33
  return;
30
34
  }
35
+ const checkoutBooking = await queryBus(viewCheckoutBookingById({ checkoutBookingId }));
36
+ if (checkoutBooking?.isExpired) {
37
+ setCheckoutBookingExpired(true);
38
+ return;
39
+ }
31
40
  paymentFlowRef.current?.startLegacyBoxCheckout(
32
41
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
33
42
  // @ts-ignore
@@ -48,7 +57,15 @@ const useSubmitCheckout = ({ checkoutId, checkoutBookingId, paymentFlowRef, onEr
48
57
  }
49
58
  }
50
59
  });
51
- }, [paymentFlowRef, blockCheckoutBooking, submitCheckoutCommand, onSuccess, createNotification]);
60
+ }, [
61
+ queryBus,
62
+ checkoutBookingId,
63
+ paymentFlowRef,
64
+ blockCheckoutBooking,
65
+ submitCheckoutCommand,
66
+ onSuccess,
67
+ createNotification,
68
+ ]);
52
69
  const status = useMemo(() => {
53
70
  if (blockCheckoutBookingStatus === CommandStatus.LOADING ||
54
71
  startLegacyBoxCheckoutStatus === "loading" ||
@@ -62,12 +79,19 @@ const useSubmitCheckout = ({ checkoutId, checkoutBookingId, paymentFlowRef, onEr
62
79
  }
63
80
  if (blockCheckoutBookingStatus === CommandStatus.ERROR ||
64
81
  startLegacyBoxCheckoutStatus === "error" ||
65
- submitCheckoutCommandStatus === CommandStatus.ERROR) {
82
+ submitCheckoutCommandStatus === CommandStatus.ERROR ||
83
+ checkoutBookingExpired) {
66
84
  onError();
67
85
  return "error";
68
86
  }
69
87
  return "idle";
70
- }, [blockCheckoutBookingStatus, submitCheckoutCommandStatus, onError, startLegacyBoxCheckoutStatus]);
88
+ }, [
89
+ blockCheckoutBookingStatus,
90
+ startLegacyBoxCheckoutStatus,
91
+ submitCheckoutCommandStatus,
92
+ checkoutBookingExpired,
93
+ onError,
94
+ ]);
71
95
  return [submitCheckout, status];
72
96
  };
73
97
  export { useSubmitCheckout };
@@ -1 +1 @@
1
- export declare const VERSION = "9.8.2";
1
+ export declare const VERSION = "9.8.4";
@@ -1 +1 @@
1
- export const VERSION = "9.8.2";
1
+ export const VERSION = "9.8.4";
package/index.ts CHANGED
@@ -51,7 +51,7 @@ const bootstrap: BootstrapFunction = ({ apiUrl, getAuthToken, translations, sent
51
51
  queryBus(viewFirstAvailableCheckoutByCustomerId({ customerId: customerId as string }));
52
52
 
53
53
  return {
54
- root: root({ Messaging, I18n, getAuthToken, sentry, kameleoon }),
54
+ root: root({ Messaging, I18n, queryBus, getAuthToken, sentry, kameleoon }),
55
55
  firstAvailableCheckoutByCustomerId,
56
56
  };
57
57
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lookiero/checkout",
3
- "version": "9.8.2",
3
+ "version": "9.8.4",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "sideEffects": "false",
package/src/ExpoRoot.tsx CHANGED
@@ -32,7 +32,7 @@ const order: Order = {
32
32
  };
33
33
 
34
34
  const customer: Customer = {
35
- customerId: "74cb4f76-3f14-4983-81a6-2ee2a9a275d4",
35
+ customerId: "d3c2354b-9811-46d4-b2ba-5fba012ed94d",
36
36
  country: Country.ES,
37
37
  segment: Segment.WOMEN,
38
38
  };
@@ -99,7 +99,7 @@ const kameleoonConfig: KameleoonEnvironment = {
99
99
  experiments: {},
100
100
  };
101
101
 
102
- const { Component: Messaging } =
102
+ const { Component: Messaging, queryBus } =
103
103
  process.env.EXPO_PUBLIC_APP_VARIANT === "test"
104
104
  ? checkoutMockBootstrap()
105
105
  : checkoutBootstrap({ apiUrl: () => apiUrl, getAuthToken });
@@ -110,6 +110,7 @@ const I18n = i18n({
110
110
  const Root = root({
111
111
  Messaging,
112
112
  I18n,
113
+ queryBus,
113
114
  getAuthToken,
114
115
  development: false,
115
116
  sentry: () => (process.env.EXPO_PUBLIC_APP_VARIANT === "test" ? ({} as SentryEnvironment) : sentryConfig),
@@ -3,6 +3,7 @@ import React, { ComponentType, useCallback } from "react";
3
3
  import { Platform } from "react-native";
4
4
  import { useRoutes as reactRouterUseRoutes } from "react-router-native";
5
5
  import { I18n } from "@lookiero/i18n-react";
6
+ import { QueryBus } from "@lookiero/messaging";
6
7
  import { MessagingRoot } from "@lookiero/messaging-react/bootstrap";
7
8
  import { Locale } from "@lookiero/sty-psp-locale";
8
9
  import { SentryEnvironment, SentryLoggerFunctionArgs, sentryLogger, sentryLoggerHOC } from "@lookiero/sty-psp-logging";
@@ -11,11 +12,13 @@ import { Customer } from "../../projection/customer/customer";
11
12
  import { Order } from "../../projection/order/order";
12
13
  import { Subscription } from "../../projection/subscription/subscription";
13
14
  import { KameleoonEnvironment } from "../ab-testing/kameleoonEnvironment";
15
+ import { QueryBusProvider } from "./hooks/useQueryBus";
14
16
  import { Routing } from "./routing/Routing";
15
17
 
16
18
  interface RootFunctionArgs {
17
19
  readonly Messaging: MessagingRoot;
18
20
  readonly I18n: I18n;
21
+ readonly queryBus: QueryBus;
19
22
  readonly development?: boolean;
20
23
  readonly sentry: () => SentryEnvironment;
21
24
  readonly getAuthToken: () => Promise<string>;
@@ -39,7 +42,15 @@ interface RootProps {
39
42
  readonly useRoutes?: typeof reactRouterUseRoutes;
40
43
  }
41
44
 
42
- const root: RootFunction = ({ Messaging, I18n, getAuthToken, development, sentry, kameleoon: kameleoonConfig }) => {
45
+ const root: RootFunction = ({
46
+ Messaging,
47
+ I18n,
48
+ queryBus,
49
+ getAuthToken,
50
+ development,
51
+ sentry,
52
+ kameleoon: kameleoonConfig,
53
+ }) => {
43
54
  const logger = sentryLogger(sentry);
44
55
  const kameleoon = kameleoonConfig();
45
56
 
@@ -60,22 +71,24 @@ const root: RootFunction = ({ Messaging, I18n, getAuthToken, development, sentry
60
71
 
61
72
  return (
62
73
  <Messaging includeReactQueryDevTools={Platform.OS === "web"}>
63
- <Routing
64
- I18n={I18n}
65
- basePath={basePath}
66
- customer={customer}
67
- getAuthToken={getAuthToken}
68
- kameleoon={kameleoon}
69
- layout={layout}
70
- locale={locale}
71
- order={order}
72
- subscription={subscription}
73
- useRedirect={useRedirect}
74
- useRoutes={useRoutes}
75
- onCheckoutSubmitted={onCheckoutSubmitted}
76
- onI18nError={development ? undefined : handleOnI18nError}
77
- onNotAccessible={onNotAccessible}
78
- />
74
+ <QueryBusProvider queryBus={queryBus}>
75
+ <Routing
76
+ I18n={I18n}
77
+ basePath={basePath}
78
+ customer={customer}
79
+ getAuthToken={getAuthToken}
80
+ kameleoon={kameleoon}
81
+ layout={layout}
82
+ locale={locale}
83
+ order={order}
84
+ subscription={subscription}
85
+ useRedirect={useRedirect}
86
+ useRoutes={useRoutes}
87
+ onCheckoutSubmitted={onCheckoutSubmitted}
88
+ onI18nError={development ? undefined : handleOnI18nError}
89
+ onNotAccessible={onNotAccessible}
90
+ />
91
+ </QueryBusProvider>
79
92
  </Messaging>
80
93
  );
81
94
  };
@@ -0,0 +1,23 @@
1
+ import { renderHook, waitFor } from "@testing-library/react-native";
2
+ import { mockFn } from "jest-mock-extended";
3
+ import React, { FC } from "react";
4
+ import { QueryBus } from "@lookiero/messaging";
5
+ import { QueryBusProvider, useQueryBus as sut } from "./useQueryBus";
6
+
7
+ const mockQueryBus = mockFn<QueryBus>();
8
+
9
+ interface WrapperProps {
10
+ readonly children: JSX.Element;
11
+ }
12
+
13
+ const Wrapper: FC<WrapperProps> = ({ children }) => (
14
+ <QueryBusProvider queryBus={mockQueryBus}>{children}</QueryBusProvider>
15
+ );
16
+
17
+ describe("useQueryBus hook", () => {
18
+ it("returns the QueryBusProvider provided queryBus", async () => {
19
+ const { result } = renderHook(() => sut(), { wrapper: Wrapper });
20
+
21
+ await waitFor(() => expect(result.current).toEqual(mockQueryBus));
22
+ });
23
+ });
@@ -0,0 +1,27 @@
1
+ import React, { createContext, FC, ReactNode, useContext } from "react";
2
+ import invariant from "tiny-invariant";
3
+ import { QueryBus } from "@lookiero/messaging";
4
+
5
+ const QueryBusContext = createContext<QueryBus>(null as unknown as QueryBus);
6
+
7
+ interface QueryBusProviderProps {
8
+ readonly children: ReactNode;
9
+ readonly queryBus: QueryBus;
10
+ }
11
+
12
+ const QueryBusProvider: FC<QueryBusProviderProps> = ({ children, queryBus }) => (
13
+ <QueryBusContext.Provider value={queryBus}>{children}</QueryBusContext.Provider>
14
+ );
15
+
16
+ const useQueryBus = () => {
17
+ const queryBus = useContext(QueryBusContext);
18
+
19
+ invariant(
20
+ queryBus,
21
+ "Your are trying to use the useQueryBus hook without wrapping your app with the <QueryBusProvider>.",
22
+ );
23
+
24
+ return queryBus;
25
+ };
26
+
27
+ export { useQueryBus, QueryBusProvider };
@@ -6,9 +6,11 @@ import { PaymentFlowRef } from "@lookiero/payments-front";
6
6
  import { ChargeStatus } from "@lookiero/payments-front/build/infrastructure/CheckoutAPI";
7
7
  import { Logger } from "@lookiero/sty-psp-logging";
8
8
  import { NotificationLevel, useCreateToastNotification } from "@lookiero/sty-psp-notifications";
9
+ import { CheckoutBookingProjection } from "../../../projection/checkoutBooking/checkoutBooking";
9
10
  import { useSubmitCheckout } from "../../domain/checkout/react/useSubmitCheckout";
10
11
  import { useBlockCheckoutBooking } from "../../domain/checkoutBooking/react/useBlockCheckoutBooking";
11
12
  import { paymentFlowPayload as mockPaymentFlowPayload } from "../../projection/payment/paymentFlowPayload.mock";
13
+ import { useQueryBus } from "./useQueryBus";
12
14
  import { useSubmitCheckout as sut } from "./useSubmitCheckout";
13
15
 
14
16
  const checkoutId = "9c450400-0cd7-44a4-b0e3-e0002a9bf8df";
@@ -18,6 +20,7 @@ const errorChargeStatuses = Object.values(ChargeStatus).filter((status) => statu
18
20
  const logger = mock<Logger>();
19
21
 
20
22
  jest.mock("@lookiero/sty-psp-notifications");
23
+ jest.mock("./useQueryBus");
21
24
  jest.mock("../../domain/checkout/react/useSubmitCheckout");
22
25
  jest.mock("../../domain/checkoutBooking/react/useBlockCheckoutBooking");
23
26
 
@@ -38,6 +41,7 @@ describe("useSubmitCheckout custom hook", () => {
38
41
  },
39
42
  };
40
43
 
44
+ (useQueryBus as jest.Mock).mockReturnValue(() => ({ isExpired: false }) as CheckoutBookingProjection);
41
45
  (useBlockCheckoutBooking as jest.Mock).mockReturnValue([mockBlockCheckoutBooking, CommandStatus.SUCCESS]);
42
46
  (useCreateToastNotification as jest.Mock).mockReturnValue([mockCreateToastNotification, CommandStatus.SUCCESS]);
43
47
  (useSubmitCheckout as jest.Mock).mockReturnValue([mockSubmitCheckout, CommandStatus.SUCCESS]);
@@ -86,6 +90,7 @@ describe("useSubmitCheckout custom hook", () => {
86
90
  },
87
91
  };
88
92
 
93
+ (useQueryBus as jest.Mock).mockReturnValue(() => ({ isExpired: false }) as CheckoutBookingProjection);
89
94
  (useBlockCheckoutBooking as jest.Mock).mockReturnValue([mockBlockCheckoutBooking, CommandStatus.SUCCESS]);
90
95
  (useCreateToastNotification as jest.Mock).mockReturnValue([mockCreateToastNotification, CommandStatus.SUCCESS]);
91
96
  (useSubmitCheckout as jest.Mock).mockReturnValue([mockSubmitCheckout, CommandStatus.SUCCESS]);
@@ -126,6 +131,7 @@ describe("useSubmitCheckout custom hook", () => {
126
131
  },
127
132
  };
128
133
 
134
+ (useQueryBus as jest.Mock).mockReturnValue(() => ({ isExpired: false }) as CheckoutBookingProjection);
129
135
  (useBlockCheckoutBooking as jest.Mock).mockReturnValue([mockBlockCheckoutBooking, CommandStatus.ERROR]);
130
136
  (useCreateToastNotification as jest.Mock).mockReturnValue([mockCreateToastNotification, CommandStatus.IDLE]);
131
137
  (useSubmitCheckout as jest.Mock).mockReturnValue([mockSubmitCheckout, CommandStatus.IDLE]);
@@ -166,6 +172,7 @@ describe("useSubmitCheckout custom hook", () => {
166
172
  },
167
173
  };
168
174
 
175
+ (useQueryBus as jest.Mock).mockReturnValue(() => ({ isExpired: false }) as CheckoutBookingProjection);
169
176
  (useBlockCheckoutBooking as jest.Mock).mockReturnValue([mockBlockCheckoutBooking, CommandStatus.SUCCESS]);
170
177
  (useCreateToastNotification as jest.Mock).mockReturnValue([mockCreateToastNotification, CommandStatus.SUCCESS]);
171
178
  (useSubmitCheckout as jest.Mock).mockReturnValue([mockSubmitCheckout, CommandStatus.IDLE]);
@@ -212,6 +219,7 @@ describe("useSubmitCheckout custom hook", () => {
212
219
  },
213
220
  };
214
221
 
222
+ (useQueryBus as jest.Mock).mockReturnValue(() => ({ isExpired: false }) as CheckoutBookingProjection);
215
223
  (useBlockCheckoutBooking as jest.Mock).mockReturnValue([mockBlockCheckoutBooking, CommandStatus.SUCCESS]);
216
224
  (useSubmitCheckout as jest.Mock).mockReturnValue([mockSubmitCheckout, CommandStatus.ERROR]);
217
225
  (useCreateToastNotification as jest.Mock).mockReturnValue([mockCreateToastNotification, CommandStatus.SUCCESS]);
@@ -236,4 +244,54 @@ describe("useSubmitCheckout custom hook", () => {
236
244
  expect(mockOnError).toHaveBeenCalled();
237
245
  });
238
246
  });
247
+
248
+ test("returns submitCheckout error as the status right after calling 'submitCheckout' if CheckoutBooking isExpired", async () => {
249
+ const mockBlockCheckoutBooking = jest.fn();
250
+ const mockSubmitCheckout = jest.fn();
251
+ const mockCreateToastNotification = jest.fn();
252
+ const mockOnError = jest.fn();
253
+ const mockOnSuccess = jest.fn();
254
+ const mockPaymentFlowRef: RefObject<PaymentFlowRef> = {
255
+ current: {
256
+ // eslint-disable-next-line @typescript-eslint/naming-convention
257
+ startLegacyBoxCheckout: jest.fn().mockImplementation((_payload, callback) => {
258
+ callback({ status: ChargeStatus.EXECUTED, final: true });
259
+ }),
260
+ startCheckout: jest.fn(),
261
+ },
262
+ };
263
+
264
+ (useQueryBus as jest.Mock).mockReturnValue(() => ({ isExpired: true }) as CheckoutBookingProjection);
265
+ (useBlockCheckoutBooking as jest.Mock).mockReturnValue([mockBlockCheckoutBooking, CommandStatus.SUCCESS]);
266
+ (useCreateToastNotification as jest.Mock).mockReturnValue([mockCreateToastNotification, CommandStatus.SUCCESS]);
267
+ (useSubmitCheckout as jest.Mock).mockReturnValue([mockSubmitCheckout, CommandStatus.SUCCESS]);
268
+
269
+ const { result } = renderHook(() =>
270
+ sut({
271
+ checkoutId,
272
+ checkoutBookingId,
273
+ paymentFlowRef: mockPaymentFlowRef,
274
+ onError: mockOnError,
275
+ onSuccess: mockOnSuccess,
276
+ logger,
277
+ }),
278
+ );
279
+
280
+ await act(async () => {
281
+ const [submitCheckout] = result.current;
282
+ await submitCheckout({ paymentFlowPayload: mockPaymentFlowPayload, sizeChangeEnabled: true });
283
+ });
284
+
285
+ await waitFor(() => {
286
+ const [, status] = result.current;
287
+
288
+ expect(mockBlockCheckoutBooking).toHaveBeenCalled();
289
+ expect(mockCreateToastNotification).not.toHaveBeenCalled();
290
+ expect(mockSubmitCheckout).not.toHaveBeenCalled();
291
+
292
+ expect(status).toBe("error");
293
+ expect(mockOnSuccess).not.toHaveBeenCalled();
294
+ expect(mockOnError).toHaveBeenCalled();
295
+ });
296
+ });
239
297
  });
@@ -3,11 +3,17 @@ import { CommandStatus } from "@lookiero/messaging-react";
3
3
  import { PaymentFlowRef } from "@lookiero/payments-front";
4
4
  import { Logger } from "@lookiero/sty-psp-logging";
5
5
  import { NotificationLevel, useCreateToastNotification } from "@lookiero/sty-psp-notifications";
6
+ import {
7
+ ViewCheckoutBookingById,
8
+ viewCheckoutBookingById,
9
+ ViewCheckoutBookingByIdResult,
10
+ } from "../../../projection/checkoutBooking/viewCheckoutBookingById";
6
11
  import { PaymentFlowPayloadProjection } from "../../../projection/payment/paymentFlowPayload";
7
12
  import { MESSAGING_CONTEXT_ID } from "../../delivery/baseBootstrap";
8
13
  import { useSubmitCheckout as useSubmitCheckoutCommand } from "../../domain/checkout/react/useSubmitCheckout";
9
14
  import { useBlockCheckoutBooking } from "../../domain/checkoutBooking/react/useBlockCheckoutBooking";
10
15
  import { I18nMessages } from "../i18n/i18n";
16
+ import { useQueryBus } from "./useQueryBus";
11
17
 
12
18
  enum ChargeStatus {
13
19
  EXECUTED = "EXECUTED",
@@ -63,9 +69,11 @@ const useSubmitCheckout: UseSubmitCheckoutFunction = ({
63
69
  onSuccess,
64
70
  logger,
65
71
  }) => {
72
+ const queryBus = useQueryBus();
66
73
  const [submitCheckoutCommand, submitCheckoutCommandStatus] = useSubmitCheckoutCommand({ checkoutId, logger });
67
74
  const [blockCheckoutBooking, blockCheckoutBookingStatus] = useBlockCheckoutBooking({ checkoutBookingId, logger });
68
75
  const [createNotification] = useCreateToastNotification({ contextId: MESSAGING_CONTEXT_ID, logger });
76
+ const [checkoutBookingExpired, setCheckoutBookingExpired] = useState(false);
69
77
 
70
78
  const [startLegacyBoxCheckoutStatus, setStartLegacyBoxCheckoutStatus] = useState<Status>("idle");
71
79
 
@@ -77,6 +85,15 @@ const useSubmitCheckout: UseSubmitCheckoutFunction = ({
77
85
  return;
78
86
  }
79
87
 
88
+ const checkoutBooking = await queryBus<ViewCheckoutBookingById, ViewCheckoutBookingByIdResult>(
89
+ viewCheckoutBookingById({ checkoutBookingId }),
90
+ );
91
+
92
+ if (checkoutBooking?.isExpired) {
93
+ setCheckoutBookingExpired(true);
94
+ return;
95
+ }
96
+
80
97
  paymentFlowRef.current?.startLegacyBoxCheckout(
81
98
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
82
99
  // @ts-ignore
@@ -100,7 +117,15 @@ const useSubmitCheckout: UseSubmitCheckoutFunction = ({
100
117
  },
101
118
  );
102
119
  },
103
- [paymentFlowRef, blockCheckoutBooking, submitCheckoutCommand, onSuccess, createNotification],
120
+ [
121
+ queryBus,
122
+ checkoutBookingId,
123
+ paymentFlowRef,
124
+ blockCheckoutBooking,
125
+ submitCheckoutCommand,
126
+ onSuccess,
127
+ createNotification,
128
+ ],
104
129
  );
105
130
 
106
131
  const status: Status = useMemo(() => {
@@ -121,14 +146,21 @@ const useSubmitCheckout: UseSubmitCheckoutFunction = ({
121
146
  if (
122
147
  blockCheckoutBookingStatus === CommandStatus.ERROR ||
123
148
  startLegacyBoxCheckoutStatus === "error" ||
124
- submitCheckoutCommandStatus === CommandStatus.ERROR
149
+ submitCheckoutCommandStatus === CommandStatus.ERROR ||
150
+ checkoutBookingExpired
125
151
  ) {
126
152
  onError();
127
153
  return "error";
128
154
  }
129
155
 
130
156
  return "idle";
131
- }, [blockCheckoutBookingStatus, submitCheckoutCommandStatus, onError, startLegacyBoxCheckoutStatus]);
157
+ }, [
158
+ blockCheckoutBookingStatus,
159
+ startLegacyBoxCheckoutStatus,
160
+ submitCheckoutCommandStatus,
161
+ checkoutBookingExpired,
162
+ onError,
163
+ ]);
132
164
 
133
165
  return [submitCheckout, status];
134
166
  };