@lookiero/checkout 10.0.0-beta.3 → 10.0.0

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 (84) hide show
  1. package/dist/fake-dependencies/@lookiero/payments-front/index.d.ts +6 -8
  2. package/dist/fake-dependencies/@lookiero/payments-front/index.js +4 -7
  3. package/dist/index.d.ts +4 -3
  4. package/dist/src/Expo.js +2 -0
  5. package/dist/src/ExpoRoot.js +13 -17
  6. package/dist/src/infrastructure/domain/checkoutBooking/react/useBlockCheckoutBooking.d.ts +1 -1
  7. package/dist/src/infrastructure/domain/checkoutBooking/react/useBlockCheckoutBooking.js +0 -2
  8. package/dist/src/infrastructure/projection/pricing/react/useViewPricingByCheckoutId.d.ts +1 -1
  9. package/dist/src/infrastructure/projection/pricing/react/useViewPricingByCheckoutId.js +1 -2
  10. package/dist/src/infrastructure/tracking/tracking.d.ts +2 -2
  11. package/dist/src/infrastructure/tracking/useTrackCheckout.d.ts +17 -10
  12. package/dist/src/infrastructure/tracking/useTrackCheckout.js +12 -27
  13. package/dist/src/infrastructure/ui/Root.d.ts +9 -7
  14. package/dist/src/infrastructure/ui/Root.js +3 -2
  15. package/dist/src/infrastructure/ui/hooks/usePaymentInstrumentEvents.d.ts +2 -3
  16. package/dist/src/infrastructure/ui/hooks/usePaymentInstrumentEvents.js +26 -17
  17. package/dist/src/infrastructure/ui/hooks/useStaticInfo.d.ts +3 -0
  18. package/dist/src/infrastructure/ui/hooks/useStaticInfo.js +7 -2
  19. package/dist/src/infrastructure/ui/i18n/i18n.d.ts +0 -1
  20. package/dist/src/infrastructure/ui/i18n/i18n.js +0 -1
  21. package/dist/src/infrastructure/ui/routing/CheckoutMiddleware.js +12 -1
  22. package/dist/src/infrastructure/ui/routing/Routing.d.ts +7 -5
  23. package/dist/src/infrastructure/ui/routing/Routing.js +11 -3
  24. package/dist/src/infrastructure/ui/routing/routes.d.ts +1 -0
  25. package/dist/src/infrastructure/ui/routing/routes.js +1 -0
  26. package/dist/src/infrastructure/ui/views/App.js +6 -5
  27. package/dist/src/infrastructure/ui/views/checkout/Checkout.d.ts +2 -7
  28. package/dist/src/infrastructure/ui/views/checkout/Checkout.js +9 -20
  29. package/dist/src/infrastructure/ui/views/checkout/Checkout.style.d.ts +0 -3
  30. package/dist/src/infrastructure/ui/views/checkout/Checkout.style.js +0 -3
  31. package/dist/src/infrastructure/ui/views/checkout/components/checkoutPaymentModal/CheckoutPaymentModal.js +1 -3
  32. package/dist/src/infrastructure/ui/views/checkout/components/paymentInstrument/PaymentInstrument.js +7 -7
  33. package/dist/src/infrastructure/ui/views/summary/Summary.js +1 -2
  34. package/dist/src/projection/customer/customer.d.ts +0 -2
  35. package/dist/src/projection/order/order.d.ts +1 -1
  36. package/dist/src/projection/subscription/subscription.d.ts +1 -1
  37. package/dist/src/version.d.ts +1 -1
  38. package/dist/src/version.js +1 -1
  39. package/fake-dependencies/@lookiero/payments-front/index.tsx +9 -32
  40. package/index.ts +4 -10
  41. package/package.json +6 -4
  42. package/src/Expo.tsx +3 -0
  43. package/src/ExpoRoot.tsx +37 -42
  44. package/src/infrastructure/domain/checkoutBooking/react/useBlockCheckoutBooking.ts +1 -4
  45. package/src/infrastructure/projection/pricing/react/useViewPricingByCheckoutId.ts +2 -3
  46. package/src/infrastructure/tracking/tracking.ts +2 -2
  47. package/src/infrastructure/tracking/useTrackCheckout.ts +56 -66
  48. package/src/infrastructure/ui/Root.tsx +14 -10
  49. package/src/infrastructure/ui/components/templates/header/itemHeader/ItemHeader.test.tsx +6 -0
  50. package/src/infrastructure/ui/components/templates/header/itemHeader/ItemHeader.tsx +0 -1
  51. package/src/infrastructure/ui/hooks/usePaymentInstrumentEvents.ts +60 -18
  52. package/src/infrastructure/ui/hooks/useStaticInfo.test.tsx +6 -1
  53. package/src/infrastructure/ui/hooks/useStaticInfo.tsx +13 -2
  54. package/src/infrastructure/ui/hooks/useSubmitCheckout.test.ts +297 -0
  55. package/src/infrastructure/ui/hooks/useSubmitCheckout.ts +169 -0
  56. package/src/infrastructure/ui/i18n/i18n.ts +0 -1
  57. package/src/infrastructure/ui/routing/CheckoutMiddleware.test.tsx +9 -1
  58. package/src/infrastructure/ui/routing/CheckoutMiddleware.tsx +15 -1
  59. package/src/infrastructure/ui/routing/Routing.tsx +29 -15
  60. package/src/infrastructure/ui/routing/routes.ts +1 -0
  61. package/src/infrastructure/ui/views/App.tsx +13 -5
  62. package/src/infrastructure/ui/views/checkout/Checkout.style.ts +0 -3
  63. package/src/infrastructure/ui/views/checkout/Checkout.test.tsx +43 -51
  64. package/src/infrastructure/ui/views/checkout/Checkout.tsx +13 -51
  65. package/src/infrastructure/ui/views/checkout/components/checkoutPaymentModal/CheckoutPaymentModal.test.tsx +134 -0
  66. package/src/infrastructure/ui/views/checkout/components/checkoutPaymentModal/CheckoutPaymentModal.tsx +124 -0
  67. package/src/infrastructure/ui/views/checkout/components/paymentInstrument/PaymentInstrument.tsx +8 -8
  68. package/src/infrastructure/ui/views/return/components/returnQuestionsForm/ReturnQuestionsForm.test.tsx +6 -0
  69. package/src/infrastructure/ui/views/summary/Summary.tsx +1 -1
  70. package/src/infrastructure/ui/views/summaryTabs/SummaryTabs.test.tsx +4 -1
  71. package/src/projection/customer/customer.ts +0 -2
  72. package/src/projection/order/order.ts +1 -1
  73. package/src/projection/subscription/subscription.ts +1 -1
  74. package/dist/public/public/assets/adaptive-icon.png +0 -0
  75. package/dist/public/public/assets/favicon.png +0 -0
  76. package/dist/public/public/assets/icon.png +0 -0
  77. package/dist/public/public/assets/splash.png +0 -0
  78. package/dist/public/public/images/not-found.png +0 -0
  79. package/dist/src/infrastructure/ui/hooks/useCheckoutFlow.d.ts +0 -26
  80. package/dist/src/infrastructure/ui/hooks/useCheckoutFlow.js +0 -127
  81. package/dist/src/infrastructure/ui/routing/useBasePath.d.ts +0 -8
  82. package/dist/src/infrastructure/ui/routing/useBasePath.js +0 -9
  83. package/src/infrastructure/ui/hooks/useCheckoutFlow.test.tsx +0 -302
  84. package/src/infrastructure/ui/hooks/useCheckoutFlow.tsx +0 -203
@@ -30,9 +30,6 @@ const style = StyleSheet.create({
30
30
  paymentSelector: {
31
31
  marginTop: space6,
32
32
  },
33
- paymentSelectorNL: {
34
- marginBottom: space6,
35
- },
36
33
  princingWrapper: {
37
34
  padding: space6,
38
35
  },
@@ -1,13 +1,10 @@
1
1
  import { fireEvent } from "@testing-library/react-native";
2
- import React, { ReactNode } from "react";
3
- import { View } from "react-native";
2
+ import React from "react";
4
3
  import { QueryStatus } from "@lookiero/messaging-react";
5
4
  import { Country } from "@lookiero/sty-psp-locale";
6
5
  import { Segment } from "@lookiero/sty-psp-segment";
7
6
  import { DummyLayout } from "@lookiero/sty-psp-ui";
8
7
  import { CheckoutItemStatus } from "../../../../domain/checkoutItem/model/checkoutItem";
9
- import { OrderProjection } from "../../../../projection/order/order";
10
- import { SubscriptionProjection } from "../../../../projection/subscription/subscription";
11
8
  import { checkout } from "../../../projection/checkout/checkout.mock";
12
9
  import { useViewFirstAvailableCheckoutByCustomerId } from "../../../projection/checkout/react/useViewFirstAvailableCheckoutByCustomerId";
13
10
  import { pricing } from "../../../projection/pricing/pricing.mock";
@@ -17,9 +14,7 @@ import { Routes } from "../../routing/routes";
17
14
  import { render } from "../../test/render";
18
15
  import { Checkout } from "./Checkout";
19
16
 
20
- const getAuthToken = () => Promise.resolve("token");
21
17
  const customerId = "a8fff6d7-708c-41a7-b42a-58c5706d33df";
22
- const basePath = "/checkout";
23
18
  const country = Country.ES;
24
19
  const segment = Segment.WOMEN;
25
20
  const mockCheckout = checkout({
@@ -31,29 +26,49 @@ const mockCheckout = checkout({
31
26
  { status: CheckoutItemStatus.REPLACED },
32
27
  ],
33
28
  });
34
- const order: OrderProjection = {
35
- orderNumber: 12345,
36
- isFirstOrder: false,
37
- coupon: null,
38
- };
39
- const subscription: SubscriptionProjection = "o";
40
29
  const mockUseRedirect = jest.fn(() => ({ returnUrl: "https://web2.dev.aws.lookiero.es/user/" }));
41
30
 
42
- const mockOnSuccess = jest.fn();
43
- const mockCheckoutFlow = jest.fn();
44
- const paymentFlowTestId = "payment-flow";
45
- const mockPaymentFlowComponent: ReactNode = <View testID={paymentFlowTestId}>PaymentFlow</View>;
46
- jest.mock("../../hooks/useCheckoutFlow", () => ({
47
- useCheckoutFlow: () => [mockCheckoutFlow, QueryStatus.SUCCESS, mockPaymentFlowComponent],
48
- }));
49
-
50
31
  jest.mock("../../hooks/useStaticInfo", () => ({
51
- useStaticInfo: () => ({ customer: { customerId, country, segment }, basePath }),
32
+ useStaticInfo: () => ({
33
+ customer: { customerId, country, segment },
34
+ basePath: "",
35
+ }),
52
36
  }));
53
- // eslint-disable-next-line @typescript-eslint/naming-convention
54
- jest.mock("./components/paymentInstrument/PaymentInstrument", () => ({ PaymentInstrument: () => null }));
37
+
55
38
  jest.mock("../../../projection/checkout/react/useViewFirstAvailableCheckoutByCustomerId");
56
39
  jest.mock("../../../projection/pricing/react/useViewPricingByCheckoutId");
40
+ jest.mock("../../hooks/usePaymentInstrumentEvents");
41
+
42
+ const mockStartLegacyBoxCheckout = jest.fn();
43
+ jest.mock("@lookiero/payments-front", () => {
44
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
45
+ const { useImperativeHandle, forwardRef } = require("react");
46
+
47
+ return {
48
+ CheckoutStatus: {
49
+ REJECTED: "REJECTED",
50
+ ERROR: "ERROR",
51
+ FULFILLED: "FULFILLED",
52
+ },
53
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
54
+ // @ts-ignore
55
+ // eslint-disable-next-line @typescript-eslint/naming-convention, react/display-name
56
+ PaymentFlow: forwardRef((params, ref) => {
57
+ useImperativeHandle(ref, () => ({
58
+ startLegacyBoxCheckout: mockStartLegacyBoxCheckout,
59
+ }));
60
+
61
+ return null;
62
+ }),
63
+ PaymentMethod: {
64
+ ["GOOGLE_PAY"]: "google_pay",
65
+ },
66
+ Section: {
67
+ ["BOX_CHECKOUT"]: "box-checkout",
68
+ },
69
+ PaymentInstrumentSelect: jest.fn(() => <></>),
70
+ };
71
+ });
57
72
 
58
73
  const mockTrackPressContinue = jest.fn();
59
74
  jest.mock("../../../tracking/useTrackPressContinue", () => ({
@@ -72,25 +87,13 @@ jest.mock("react-router-native", () => ({
72
87
  useNavigate: () => mockUseNavigate,
73
88
  }));
74
89
 
75
- beforeEach(() => {
76
- mockCheckoutFlow.mockClear();
77
- mockTrackPressContinue.mockClear();
78
- mockTrackPressBack.mockClear();
79
- });
80
-
81
90
  describe("Checkout view", () => {
82
91
  it("renders correctly", async () => {
83
92
  (useViewFirstAvailableCheckoutByCustomerId as jest.Mock).mockReturnValue([mockCheckout, QueryStatus.SUCCESS]);
84
93
  (useViewPricingByCheckoutId as jest.Mock).mockReturnValue([pricing(), QueryStatus.SUCCESS]);
94
+
85
95
  const { findByText, getAllByTestId, getByText, getByTestId } = render(
86
- <Checkout
87
- getAuthToken={getAuthToken}
88
- layout={DummyLayout}
89
- order={order}
90
- subscription={subscription}
91
- useRedirect={mockUseRedirect}
92
- onCheckoutFlowSuccess={mockOnSuccess}
93
- />,
96
+ <Checkout layout={DummyLayout} useRedirect={mockUseRedirect} />,
94
97
  );
95
98
 
96
99
  expect(await findByText(I18nMessages.CHECKOUT_TITLE)).toBeTruthy();
@@ -111,17 +114,15 @@ describe("Checkout view", () => {
111
114
  expect(getByText(I18nMessages.SUMMARY_FEE)).toBeTruthy();
112
115
  expect(getByText("-€10.00")).toBeTruthy();
113
116
 
114
- expect(getByTestId(paymentFlowTestId)).toBeTruthy();
115
-
116
117
  expect(getByText(I18nMessages.CHECKOUT_PAY_BUTTON)).toBeTruthy();
117
118
  fireEvent.press(getByText(I18nMessages.CHECKOUT_PAY_BUTTON));
118
119
  expect(mockTrackPressContinue).toHaveBeenCalled();
119
- expect(mockCheckoutFlow).toHaveBeenCalled();
120
+ expect(mockUseNavigate).toHaveBeenCalledWith(`/${Routes.CHECKOUT}/${Routes.CHECKOUT_PAYMENT}`, { replace: true });
120
121
 
121
122
  expect(getByTestId("checkout-header")).toBeTruthy();
122
123
  fireEvent.press(getByTestId("arrow-left-button-icon"));
123
124
  expect(mockTrackPressBack).toHaveBeenCalled();
124
- expect(mockUseNavigate).toHaveBeenCalledWith(`${basePath}/${Routes.SUMMARY}`);
125
+ expect(mockUseNavigate).toHaveBeenCalledWith(`/${Routes.SUMMARY}`);
125
126
  });
126
127
 
127
128
  it("does not render a delivery banner", async () => {
@@ -137,16 +138,7 @@ describe("Checkout view", () => {
137
138
  (useViewFirstAvailableCheckoutByCustomerId as jest.Mock).mockReturnValue([mockCheckout, QueryStatus.SUCCESS]);
138
139
  (useViewPricingByCheckoutId as jest.Mock).mockReturnValue([pricing(), QueryStatus.SUCCESS]);
139
140
 
140
- const { findByText, queryByText } = render(
141
- <Checkout
142
- getAuthToken={getAuthToken}
143
- layout={DummyLayout}
144
- order={order}
145
- subscription={subscription}
146
- useRedirect={mockUseRedirect}
147
- onCheckoutFlowSuccess={mockOnSuccess}
148
- />,
149
- );
141
+ const { findByText, queryByText } = render(<Checkout layout={DummyLayout} useRedirect={mockUseRedirect} />);
150
142
 
151
143
  expect(await findByText(I18nMessages.CHECKOUT_TITLE)).toBeTruthy();
152
144
 
@@ -1,14 +1,11 @@
1
- import React, { FC, useCallback, useMemo, useState } from "react";
1
+ import React, { FC, ReactNode, useCallback, useMemo, useState } from "react";
2
2
  import { LayoutRectangle, Platform, ScrollView, View } from "react-native";
3
3
  import { useNavigate } from "react-router-native";
4
4
  import { Box, Button, Layout as AuroraLayout, Spinner, Text, useDevice } from "@lookiero/aurora";
5
5
  import { useI18nMessage } from "@lookiero/i18n-react";
6
6
  import { QueryStatus } from "@lookiero/messaging-react";
7
- import { Country } from "@lookiero/sty-psp-locale";
8
7
  import { Layout as UiLayout, Sticky } from "@lookiero/sty-psp-ui";
9
8
  import { CheckoutItemStatus } from "../../../../domain/checkoutItem/model/checkoutItem";
10
- import { OrderProjection } from "../../../../projection/order/order";
11
- import { SubscriptionProjection } from "../../../../projection/subscription/subscription";
12
9
  import { useViewFirstAvailableCheckoutByCustomerId } from "../../../projection/checkout/react/useViewFirstAvailableCheckoutByCustomerId";
13
10
  import { useViewPricingByCheckoutId } from "../../../projection/pricing/react/useViewPricingByCheckoutId";
14
11
  import { TrackingPage } from "../../../tracking/tracking";
@@ -17,7 +14,6 @@ import { useTrackPressBack } from "../../../tracking/useTrackPressBack";
17
14
  import { useTrackPressContinue } from "../../../tracking/useTrackPressContinue";
18
15
  import { Body } from "../../components/layouts/body/Body";
19
16
  import { CheckoutHeader } from "../../components/templates/header/checkoutHeader/CheckoutHeader";
20
- import { useCheckoutFlow } from "../../hooks/useCheckoutFlow";
21
17
  import { useStaticInfo } from "../../hooks/useStaticInfo";
22
18
  import { DOMAIN, I18nMessages } from "../../i18n/i18n";
23
19
  import { Routes } from "../../routing/routes";
@@ -28,22 +24,12 @@ import { DeliveryBanner } from "./components/deliveryBanner/DeliveryBanner";
28
24
  import { PaymentInstrument } from "./components/paymentInstrument/PaymentInstrument";
29
25
 
30
26
  interface CheckoutProps {
27
+ readonly children?: ReactNode;
31
28
  readonly layout: UiLayout;
32
- readonly order: OrderProjection;
33
- readonly subscription: SubscriptionProjection;
34
- readonly getAuthToken: () => Promise<string>;
35
- readonly onCheckoutFlowSuccess: () => void;
36
29
  readonly useRedirect: () => Record<string, string>;
37
30
  }
38
31
 
39
- const Checkout: FC<CheckoutProps> = ({
40
- layout: Layout,
41
- order,
42
- subscription,
43
- getAuthToken,
44
- useRedirect,
45
- onCheckoutFlowSuccess,
46
- }) => {
32
+ const Checkout: FC<CheckoutProps> = ({ children, layout: Layout, useRedirect }) => {
47
33
  const {
48
34
  customer: { customerId, country, segment },
49
35
  basePath,
@@ -57,14 +43,6 @@ const Checkout: FC<CheckoutProps> = ({
57
43
  const [checkout, checkoutStatus] = useViewFirstAvailableCheckoutByCustomerId({ customerId });
58
44
  const [pricing, pricingStatus] = useViewPricingByCheckoutId({ checkoutId: checkout?.id as string });
59
45
 
60
- const [checkoutFlow, checkoutFlowStatus, paymentFlowComponent] = useCheckoutFlow({
61
- checkout,
62
- order,
63
- subscription,
64
- getAuthToken,
65
- onSuccess: onCheckoutFlowSuccess,
66
- });
67
-
68
46
  useTrackPageView({
69
47
  page: TrackingPage.CHECKOUT,
70
48
  country,
@@ -73,16 +51,17 @@ const Checkout: FC<CheckoutProps> = ({
73
51
  });
74
52
 
75
53
  const navigate = useNavigate();
54
+
76
55
  const trackPressContinue = useTrackPressContinue({
77
56
  page: TrackingPage.CHECKOUT,
78
57
  country,
79
58
  segment,
80
59
  checkoutId: checkout?.id,
81
60
  });
82
- const handleOnSubmit = useCallback(async () => {
61
+ const handleOnSubmit = useCallback(() => {
83
62
  trackPressContinue();
84
- await checkoutFlow();
85
- }, [checkoutFlow, trackPressContinue]);
63
+ navigate(`${basePath}/${Routes.CHECKOUT}/${Routes.CHECKOUT_PAYMENT}`, { replace: true });
64
+ }, [basePath, navigate, trackPressContinue]);
86
65
 
87
66
  const checkoutItemsKept = useMemo(
88
67
  () =>
@@ -136,12 +115,6 @@ const Checkout: FC<CheckoutProps> = ({
136
115
  >
137
116
  <Box size={{ L: "2/3" }} style={screen.L && style.desktopListSpacing}>
138
117
  <View style={[style.contentWrapper, screen.L && style.desktopContentWrapper]}>
139
- {country === Country.NL && (
140
- <View style={style.paymentSelectorNL}>
141
- <PaymentInstrument useRedirect={useRedirect} />
142
- </View>
143
- )}
144
-
145
118
  <Text level={3} style={style.title} heading>
146
119
  {titleText}
147
120
  </Text>
@@ -165,11 +138,9 @@ const Checkout: FC<CheckoutProps> = ({
165
138
  </View>
166
139
  ))}
167
140
 
168
- {country !== Country.NL && (
169
- <View style={style.paymentSelector}>
170
- <PaymentInstrument useRedirect={useRedirect} />
171
- </View>
172
- )}
141
+ <View style={style.paymentSelector}>
142
+ <PaymentInstrument useRedirect={useRedirect} />
143
+ </View>
173
144
  </View>
174
145
  </Box>
175
146
 
@@ -179,11 +150,7 @@ const Checkout: FC<CheckoutProps> = ({
179
150
  <Pricing pricing={pricing} totalCheckoutItemsKept={checkoutItemsKept?.length || 0} />
180
151
 
181
152
  {screen.L ? (
182
- <Button
183
- busy={checkoutFlowStatus === "loading"}
184
- testID="confirm-checkout-button"
185
- onPress={handleOnSubmit}
186
- >
153
+ <Button testID="confirm-checkout-button" onPress={handleOnSubmit}>
187
154
  {submitButtonText}
188
155
  </Button>
189
156
  ) : null}
@@ -196,19 +163,14 @@ const Checkout: FC<CheckoutProps> = ({
196
163
  {pricing && !screen.L ? (
197
164
  <Sticky style={style.sticky} onLayout={Platform.OS !== "web" ? handleOnPricingLayout : undefined}>
198
165
  <Body>
199
- <Button
200
- busy={checkoutFlowStatus === "loading"}
201
- testID="confirm-checkout-button"
202
- small
203
- onPress={handleOnSubmit}
204
- >
166
+ <Button testID="confirm-checkout-button" small onPress={handleOnSubmit}>
205
167
  {submitButtonText}
206
168
  </Button>
207
169
  </Body>
208
170
  </Sticky>
209
171
  ) : null}
210
172
 
211
- {paymentFlowComponent}
173
+ {children}
212
174
  </Layout>
213
175
  );
214
176
  };
@@ -0,0 +1,134 @@
1
+ import { waitFor } from "@testing-library/react-native";
2
+ import React from "react";
3
+ import { QueryStatus } from "@lookiero/messaging-react";
4
+ import { Country } from "@lookiero/sty-psp-locale";
5
+ import { Segment } from "@lookiero/sty-psp-segment";
6
+ import { CheckoutItemStatus } from "../../../../../../domain/checkoutItem/model/checkoutItem";
7
+ import { checkout } from "../../../../../projection/checkout/checkout.mock";
8
+ import { useViewFirstAvailableCheckoutByCustomerId } from "../../../../../projection/checkout/react/useViewFirstAvailableCheckoutByCustomerId";
9
+ import { useViewIsSizeChangeEnabledByCheckoutId } from "../../../../../projection/checkout/react/useViewIsSizeChangeEnabledByCheckoutId";
10
+ import { paymentFlowPayload as mockPaymentFlowPayload } from "../../../../../projection/payment/paymentFlowPayload.mock";
11
+ import { useViewPaymentFlowPayloadByCheckoutId } from "../../../../../projection/payment/react/useViewPaymentFlowPayloadByCheckoutId";
12
+ import { pricing } from "../../../../../projection/pricing/pricing.mock";
13
+ import { useViewPricingByCheckoutId } from "../../../../../projection/pricing/react/useViewPricingByCheckoutId";
14
+ import { useSubmitCheckout } from "../../../../hooks/useSubmitCheckout";
15
+ import { Routes } from "../../../../routing/routes";
16
+ import { render } from "../../../../test/render";
17
+ import { CheckoutPaymentModal } from "./CheckoutPaymentModal";
18
+
19
+ const customerId = "a8fff6d7-708c-41a7-b42a-58c5706d33df";
20
+ const country = Country.ES;
21
+ const segment = Segment.WOMEN;
22
+ const orderNumber = 3691625;
23
+ const isFirstOrder = false;
24
+ const getAuthToken = () => Promise.resolve("token");
25
+ const mockCheckout = checkout({
26
+ items: [
27
+ { status: CheckoutItemStatus.RETURNED },
28
+ { status: CheckoutItemStatus.KEPT },
29
+ { status: CheckoutItemStatus.KEPT },
30
+ { status: CheckoutItemStatus.KEPT },
31
+ { status: CheckoutItemStatus.REPLACED },
32
+ ],
33
+ });
34
+ const mockPricing = pricing();
35
+
36
+ jest.mock("../../../../hooks/useStaticInfo", () => ({
37
+ useStaticInfo: () => ({
38
+ customer: { customerId, country, segment },
39
+ basePath: "",
40
+ }),
41
+ }));
42
+
43
+ jest.mock("../../../../../projection/checkout/react/useViewFirstAvailableCheckoutByCustomerId");
44
+ jest.mock("../../../../../projection/payment/react/useViewPaymentFlowPayloadByCheckoutId");
45
+ jest.mock("../../../../../projection/checkout/react/useViewIsSizeChangeEnabledByCheckoutId");
46
+ jest.mock("../../../../../projection/pricing/react/useViewPricingByCheckoutId");
47
+ jest.mock("../../../../hooks/useSubmitCheckout");
48
+ jest.mock("../../../../../tracking/useTrackCheckout");
49
+
50
+ const mockStartLegacyBoxCheckout = jest.fn();
51
+ jest.mock("@lookiero/payments-front", () => {
52
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
53
+ const { useImperativeHandle, forwardRef } = require("react");
54
+
55
+ return {
56
+ CheckoutStatus: {
57
+ REJECTED: "REJECTED",
58
+ ERROR: "ERROR",
59
+ FULFILLED: "FULFILLED",
60
+ },
61
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
62
+ // @ts-ignore
63
+ // eslint-disable-next-line @typescript-eslint/naming-convention, react/display-name
64
+ PaymentFlow: forwardRef((params, ref) => {
65
+ useImperativeHandle(ref, () => ({
66
+ startLegacyBoxCheckout: mockStartLegacyBoxCheckout,
67
+ }));
68
+
69
+ return null;
70
+ }),
71
+ PaymentMethod: {
72
+ ["GOOGLE_PAY"]: "google_pay",
73
+ },
74
+ Section: {
75
+ ["BOX_CHECKOUT"]: "box-checkout",
76
+ },
77
+ PaymentInstrumentSelect: jest.fn(() => <></>),
78
+ };
79
+ });
80
+
81
+ const mockUseNavigate = jest.fn();
82
+ jest.mock("react-router-native", () => ({
83
+ ...jest.requireActual("react-router-native"),
84
+ useNavigate: () => mockUseNavigate,
85
+ }));
86
+
87
+ describe("CheckoutPaymentModal component", () => {
88
+ it("renders correctly", async () => {
89
+ const mockSubmitCheckout = jest.fn();
90
+ (useViewFirstAvailableCheckoutByCustomerId as jest.Mock).mockReturnValue([mockCheckout, QueryStatus.SUCCESS]);
91
+ (useViewPaymentFlowPayloadByCheckoutId as jest.Mock).mockReturnValue([mockPaymentFlowPayload, QueryStatus.SUCCESS]);
92
+ (useViewIsSizeChangeEnabledByCheckoutId as jest.Mock).mockReturnValue([true, QueryStatus.SUCCESS]);
93
+ (useViewPricingByCheckoutId as jest.Mock).mockReturnValue([mockPricing, QueryStatus.SUCCESS]);
94
+ (useSubmitCheckout as jest.Mock).mockReturnValue([mockSubmitCheckout, "success"]);
95
+
96
+ await waitFor(() => {
97
+ render(
98
+ <CheckoutPaymentModal
99
+ coupon={null}
100
+ getAuthToken={getAuthToken}
101
+ isFirstOrder={isFirstOrder}
102
+ orderNumber={orderNumber}
103
+ subscription="b"
104
+ />,
105
+ );
106
+ expect(mockSubmitCheckout).toHaveBeenCalled();
107
+ });
108
+ });
109
+
110
+ it("navigates back to /checkout when checkout-flow fails", async () => {
111
+ (useViewFirstAvailableCheckoutByCustomerId as jest.Mock).mockReturnValue([mockCheckout, QueryStatus.SUCCESS]);
112
+ (useViewPaymentFlowPayloadByCheckoutId as jest.Mock).mockReturnValue([mockPaymentFlowPayload, QueryStatus.SUCCESS]);
113
+ (useViewIsSizeChangeEnabledByCheckoutId as jest.Mock).mockReturnValue([true, QueryStatus.SUCCESS]);
114
+ (useViewPricingByCheckoutId as jest.Mock).mockReturnValue([mockPricing, QueryStatus.SUCCESS]);
115
+ (useSubmitCheckout as jest.Mock).mockImplementation(({ onError }) => {
116
+ const submitCheckout = () => onError();
117
+
118
+ return [submitCheckout, "error"];
119
+ });
120
+
121
+ await waitFor(() => {
122
+ render(
123
+ <CheckoutPaymentModal
124
+ coupon={null}
125
+ getAuthToken={getAuthToken}
126
+ isFirstOrder={isFirstOrder}
127
+ orderNumber={orderNumber}
128
+ subscription="b"
129
+ />,
130
+ );
131
+ expect(mockUseNavigate).toHaveBeenCalledWith(`/${Routes.CHECKOUT}`, { replace: true });
132
+ });
133
+ });
134
+ });
@@ -0,0 +1,124 @@
1
+ import React, { FC, useCallback, useEffect, useRef, useState } from "react";
2
+ import { useNavigate } from "react-router-native";
3
+ import { QueryStatus } from "@lookiero/messaging-react";
4
+ import { PaymentFlow, PaymentFlowRef } from "@lookiero/payments-front";
5
+ import { useLogger } from "@lookiero/sty-psp-logging";
6
+ import { CheckoutItemStatus } from "../../../../../../domain/checkoutItem/model/checkoutItem";
7
+ import { Currency } from "../../../../../../domain/checkoutItem/model/currency";
8
+ import { Subscription } from "../../../../../../projection/subscription/subscription";
9
+ import { useViewFirstAvailableCheckoutByCustomerId } from "../../../../../projection/checkout/react/useViewFirstAvailableCheckoutByCustomerId";
10
+ import { useViewIsSizeChangeEnabledByCheckoutId } from "../../../../../projection/checkout/react/useViewIsSizeChangeEnabledByCheckoutId";
11
+ import { useViewPaymentFlowPayloadByCheckoutId } from "../../../../../projection/payment/react/useViewPaymentFlowPayloadByCheckoutId";
12
+ import { useViewPricingByCheckoutId } from "../../../../../projection/pricing/react/useViewPricingByCheckoutId";
13
+ import { TrackingPage } from "../../../../../tracking/tracking";
14
+ import { useTrackCheckout } from "../../../../../tracking/useTrackCheckout";
15
+ import { useStaticInfo } from "../../../../hooks/useStaticInfo";
16
+ import { useSubmitCheckout } from "../../../../hooks/useSubmitCheckout";
17
+ import { Routes } from "../../../../routing/routes";
18
+
19
+ interface CheckoutPaymentModalProps {
20
+ readonly coupon: string | null;
21
+ readonly orderNumber: number;
22
+ readonly isFirstOrder: boolean;
23
+ readonly subscription: Subscription;
24
+ readonly getAuthToken: () => Promise<string>;
25
+ readonly onCheckoutSubmitted?: () => void;
26
+ }
27
+
28
+ const CheckoutPaymentModal: FC<CheckoutPaymentModalProps> = ({
29
+ coupon,
30
+ isFirstOrder,
31
+ subscription,
32
+ orderNumber,
33
+ getAuthToken,
34
+ onCheckoutSubmitted,
35
+ }) => {
36
+ const paymentFlowRef = useRef<PaymentFlowRef>(null);
37
+ const logger = useLogger();
38
+ const {
39
+ customer: { customerId, country, segment },
40
+ basePath,
41
+ } = useStaticInfo();
42
+
43
+ const [checkout, checkoutStatus] = useViewFirstAvailableCheckoutByCustomerId({ customerId });
44
+ const [paymentFlowPayload] = useViewPaymentFlowPayloadByCheckoutId({
45
+ checkoutId: checkout?.id as string,
46
+ });
47
+ const [sizeChangeEnabled] = useViewIsSizeChangeEnabledByCheckoutId({ checkoutId: checkout?.id as string });
48
+ const [pricing] = useViewPricingByCheckoutId({
49
+ checkoutId: checkout?.id as string,
50
+ queryOptions: { refetchOnMount: true },
51
+ });
52
+
53
+ const [authToken, setAuthToken] = useState<string>();
54
+ useEffect(() => {
55
+ const loadAuthToken = async () => setAuthToken(await getAuthToken());
56
+ loadAuthToken();
57
+ }, [getAuthToken]);
58
+
59
+ const navigate = useNavigate();
60
+ const trackCheckout = useTrackCheckout({
61
+ page: TrackingPage.CHECKOUT,
62
+ checkoutId: checkout?.id as string,
63
+ country,
64
+ segment,
65
+ });
66
+
67
+ const handleOnSubmitCheckoutError = useCallback(
68
+ () => navigate(`${basePath}/${Routes.CHECKOUT}`, { replace: true }),
69
+ [basePath, navigate],
70
+ );
71
+ const handleOnSubmitCheckoutSuccess = useCallback(() => {
72
+ const checkoutItemsKept = checkout?.items.filter(
73
+ (checkoutItem) =>
74
+ checkoutItem.status === CheckoutItemStatus.KEPT || checkoutItem.status === CheckoutItemStatus.REPLACED,
75
+ );
76
+ const totalReplacedFor = checkoutItemsKept?.filter(({ replacedFor }) => Boolean(replacedFor)).length || 0;
77
+
78
+ trackCheckout({
79
+ totalCheckoutItemsKept: checkoutItemsKept?.length || 0,
80
+ pendingToPay: (pricing?.pendingToPay.amount as number) / 100,
81
+ coupon,
82
+ currencyCode: pricing?.pendingToPay.currency as Currency,
83
+ isFirstOrder,
84
+ orderNumber,
85
+ totalReplacedFor,
86
+ subscription,
87
+ userId: customerId,
88
+ });
89
+ onCheckoutSubmitted?.();
90
+ }, [
91
+ checkout?.items,
92
+ coupon,
93
+ customerId,
94
+ isFirstOrder,
95
+ onCheckoutSubmitted,
96
+ orderNumber,
97
+ pricing?.pendingToPay.amount,
98
+ pricing?.pendingToPay.currency,
99
+ subscription,
100
+ trackCheckout,
101
+ ]);
102
+ const [submitCheckout] = useSubmitCheckout({
103
+ checkoutId: checkout?.id as string,
104
+ checkoutBookingId: checkout?.checkoutBookingId as string,
105
+ paymentFlowRef,
106
+ onError: handleOnSubmitCheckoutError,
107
+ onSuccess: handleOnSubmitCheckoutSuccess,
108
+ logger,
109
+ });
110
+ useEffect(() => {
111
+ if (paymentFlowPayload && sizeChangeEnabled !== undefined && pricing !== undefined) {
112
+ submitCheckout({ paymentFlowPayload, sizeChangeEnabled });
113
+ }
114
+ }, [paymentFlowPayload, pricing, sizeChangeEnabled, submitCheckout]);
115
+
116
+ const dependenciesLoadedStatuses = [QueryStatus.ERROR, QueryStatus.SUCCESS];
117
+ const dependenciesLoaded = (dependenciesLoadedStatuses.includes(checkoutStatus) || checkout) && authToken;
118
+
119
+ if (!dependenciesLoaded) return null;
120
+
121
+ return <PaymentFlow ref={paymentFlowRef} token={authToken} />;
122
+ };
123
+
124
+ export { CheckoutPaymentModal };
@@ -1,24 +1,24 @@
1
- import React, { FC } from "react";
1
+ import React, { FC, useRef } from "react";
2
2
  import { PaymentInstrumentSelect, Section } from "@lookiero/payments-front";
3
- import { useStaticInfo } from "../../../../hooks/useStaticInfo";
3
+ import { useLogger } from "@lookiero/sty-psp-logging";
4
+ import { usePaymentInstrumentEvents } from "../../../../hooks/usePaymentInstrumentEvents";
4
5
 
5
6
  interface PaymentInstrumentProps {
6
7
  readonly useRedirect: () => Record<string, string>;
7
8
  }
8
9
  const PaymentInstrument: FC<PaymentInstrumentProps> = ({ useRedirect }) => {
10
+ const paymentInstrumentSelectRef = useRef(null);
9
11
  const { returnUrl } = useRedirect();
10
- const { customer } = useStaticInfo();
12
+ const logger = useLogger();
13
+
14
+ usePaymentInstrumentEvents({ logger });
11
15
 
12
16
  return (
13
17
  <PaymentInstrumentSelect
18
+ ref={paymentInstrumentSelectRef}
14
19
  beforeRedirect={returnUrl ? () => Promise.resolve(returnUrl) : undefined}
15
20
  hasError={false}
16
21
  section={Section.BOX_CHECKOUT}
17
- userInformation={{
18
- email: customer.email,
19
- name: customer.name,
20
- }}
21
- showSingleUsePaymentMethods
22
22
  />
23
23
  );
24
24
  };
@@ -52,6 +52,12 @@ jest.mock("../../../../../tracking/useTrackReturnItem", () => ({
52
52
  useTrackReturnItem: () => mockReturnItem,
53
53
  }));
54
54
 
55
+ jest.mock("../../../../hooks/useStaticInfo", () => ({
56
+ useStaticInfo: () => ({
57
+ basePath: "",
58
+ }),
59
+ }));
60
+
55
61
  describe("Return", () => {
56
62
  test("renders correctly", async () => {
57
63
  (useListReturnQuestionsByCheckoutItemId as jest.Mock).mockReturnValue([mockReturnQuestions, QueryStatus.SUCCESS]);
@@ -29,6 +29,7 @@ interface SummaryProps {
29
29
  const Summary: FC<SummaryProps> = ({ layout: Layout, children }) => {
30
30
  const {
31
31
  customer: { customerId, country, segment },
32
+ basePath,
32
33
  } = useStaticInfo();
33
34
  const [pricingCollapsed, setPricingCollapsed] = useState(true);
34
35
  const [pricingHeight, setPricingHeight] = useState(0);
@@ -42,7 +43,6 @@ const Summary: FC<SummaryProps> = ({ layout: Layout, children }) => {
42
43
  const [fiveItemsDiscount = 0, fiveItemsDiscountStatus] = useViewFiveItemsDiscountByCustomerId({ customerId });
43
44
 
44
45
  const navigate = useNavigate();
45
- const { basePath } = useStaticInfo();
46
46
 
47
47
  const match = useMatch(`${basePath}/${Routes.SUMMARY}/${Routes.SUMMARY_TABS}`);
48
48
  const tab = match?.params.tab;
@@ -21,7 +21,10 @@ const checkoutMock = checkout({
21
21
  });
22
22
 
23
23
  jest.mock("../../hooks/useStaticInfo", () => ({
24
- useStaticInfo: () => ({ customer: { customerId, country, segment } }),
24
+ useStaticInfo: () => ({
25
+ customer: { customerId, country, segment },
26
+ basePath: "",
27
+ }),
25
28
  }));
26
29
 
27
30
  const mockUseNavigate = jest.fn();
@@ -5,8 +5,6 @@ interface Customer {
5
5
  readonly customerId: string;
6
6
  readonly country: Country;
7
7
  readonly segment: Segment;
8
- readonly name: string;
9
- readonly email: string;
10
8
  }
11
9
 
12
10
  export type { Customer };
@@ -4,4 +4,4 @@ interface Order {
4
4
  readonly coupon: string | null;
5
5
  }
6
6
 
7
- export type { Order as OrderProjection };
7
+ export type { Order };
@@ -1,3 +1,3 @@
1
1
  type Subscription = "o" | "m" | "b" | "q";
2
2
 
3
- export type { Subscription as SubscriptionProjection };
3
+ export type { Subscription };