@lookiero/checkout 10.1.0 → 11.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 (74) hide show
  1. package/cypress/integration/checkout.spec.ts +0 -3
  2. package/dist/fake-dependencies/@lookiero/payments-front/index.d.ts +8 -7
  3. package/dist/fake-dependencies/@lookiero/payments-front/index.js +11 -3
  4. package/dist/index.d.ts +3 -3
  5. package/dist/src/ExpoRoot.js +17 -12
  6. package/dist/src/infrastructure/domain/checkoutBooking/react/useBlockCheckoutBooking.d.ts +1 -1
  7. package/dist/src/infrastructure/domain/checkoutBooking/react/useBlockCheckoutBooking.js +2 -0
  8. package/dist/src/infrastructure/projection/checkout/checkout.mock.d.ts +1 -0
  9. package/dist/src/infrastructure/projection/checkout/checkout.mock.js +3 -3
  10. package/dist/src/infrastructure/projection/pricing/react/useViewPricingByCheckoutId.d.ts +1 -1
  11. package/dist/src/infrastructure/projection/pricing/react/useViewPricingByCheckoutId.js +2 -1
  12. package/dist/src/infrastructure/tracking/tracking.d.ts +2 -2
  13. package/dist/src/infrastructure/tracking/useTrackCheckout.d.ts +10 -17
  14. package/dist/src/infrastructure/tracking/useTrackCheckout.js +27 -12
  15. package/dist/src/infrastructure/ui/Root.d.ts +6 -6
  16. package/dist/src/infrastructure/ui/Root.js +2 -3
  17. package/dist/src/infrastructure/ui/hooks/useCheckoutFlow.d.ts +26 -0
  18. package/dist/src/infrastructure/ui/hooks/useCheckoutFlow.js +127 -0
  19. package/dist/src/infrastructure/ui/hooks/usePaymentInstrumentEvents.d.ts +3 -2
  20. package/dist/src/infrastructure/ui/hooks/usePaymentInstrumentEvents.js +17 -26
  21. package/dist/src/infrastructure/ui/i18n/i18n.d.ts +1 -0
  22. package/dist/src/infrastructure/ui/i18n/i18n.js +1 -0
  23. package/dist/src/infrastructure/ui/routing/CheckoutMiddleware.js +1 -12
  24. package/dist/src/infrastructure/ui/routing/Routing.d.ts +5 -5
  25. package/dist/src/infrastructure/ui/routing/Routing.js +2 -10
  26. package/dist/src/infrastructure/ui/routing/routes.d.ts +0 -1
  27. package/dist/src/infrastructure/ui/routing/routes.js +0 -1
  28. package/dist/src/infrastructure/ui/views/App.js +5 -6
  29. package/dist/src/infrastructure/ui/views/checkout/Checkout.d.ts +7 -2
  30. package/dist/src/infrastructure/ui/views/checkout/Checkout.js +20 -9
  31. package/dist/src/infrastructure/ui/views/checkout/Checkout.style.d.ts +3 -0
  32. package/dist/src/infrastructure/ui/views/checkout/Checkout.style.js +3 -0
  33. package/dist/src/infrastructure/ui/views/checkout/components/paymentInstrument/PaymentInstrument.js +7 -7
  34. package/dist/src/projection/customer/customer.d.ts +2 -0
  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 +36 -8
  40. package/index.ts +10 -3
  41. package/package.json +3 -3
  42. package/src/ExpoRoot.tsx +43 -36
  43. package/src/infrastructure/domain/checkoutBooking/react/useBlockCheckoutBooking.ts +4 -1
  44. package/src/infrastructure/projection/checkout/checkout.mock.ts +8 -3
  45. package/src/infrastructure/projection/pricing/react/useViewPricingByCheckoutId.ts +3 -2
  46. package/src/infrastructure/tracking/tracking.ts +2 -2
  47. package/src/infrastructure/tracking/useTrackCheckout.test.tsx +51 -24
  48. package/src/infrastructure/tracking/useTrackCheckout.ts +66 -56
  49. package/src/infrastructure/ui/Root.tsx +9 -9
  50. package/src/infrastructure/ui/components/templates/header/itemHeader/ItemHeader.tsx +1 -0
  51. package/src/infrastructure/ui/hooks/useCheckoutFlow.test.tsx +302 -0
  52. package/src/infrastructure/ui/hooks/useCheckoutFlow.tsx +203 -0
  53. package/src/infrastructure/ui/hooks/usePaymentInstrumentEvents.ts +18 -60
  54. package/src/infrastructure/ui/i18n/i18n.ts +1 -0
  55. package/src/infrastructure/ui/routing/CheckoutMiddleware.test.tsx +0 -11
  56. package/src/infrastructure/ui/routing/CheckoutMiddleware.tsx +1 -15
  57. package/src/infrastructure/ui/routing/Routing.tsx +14 -25
  58. package/src/infrastructure/ui/routing/routes.ts +0 -1
  59. package/src/infrastructure/ui/views/App.tsx +5 -13
  60. package/src/infrastructure/ui/views/checkout/Checkout.style.ts +3 -0
  61. package/src/infrastructure/ui/views/checkout/Checkout.test.tsx +51 -43
  62. package/src/infrastructure/ui/views/checkout/Checkout.tsx +51 -13
  63. package/src/infrastructure/ui/views/checkout/components/paymentInstrument/PaymentInstrument.tsx +8 -8
  64. package/src/projection/customer/customer.ts +2 -0
  65. package/src/projection/order/order.ts +1 -1
  66. package/src/projection/subscription/subscription.ts +1 -1
  67. package/dist/src/infrastructure/ui/hooks/useSubmitCheckout.d.ts +0 -27
  68. package/dist/src/infrastructure/ui/hooks/useSubmitCheckout.js +0 -97
  69. package/dist/src/infrastructure/ui/views/checkout/components/checkoutPaymentModal/CheckoutPaymentModal.d.ts +0 -12
  70. package/dist/src/infrastructure/ui/views/checkout/components/checkoutPaymentModal/CheckoutPaymentModal.js +0 -88
  71. package/src/infrastructure/ui/hooks/useSubmitCheckout.test.ts +0 -297
  72. package/src/infrastructure/ui/hooks/useSubmitCheckout.ts +0 -169
  73. package/src/infrastructure/ui/views/checkout/components/checkoutPaymentModal/CheckoutPaymentModal.test.tsx +0 -134
  74. package/src/infrastructure/ui/views/checkout/components/checkoutPaymentModal/CheckoutPaymentModal.tsx +0 -124
@@ -57,11 +57,8 @@ const randomIndex = (count: number) => Math.floor(Math.random() * count);
57
57
  const navigateToHome = () => cy.visit("/");
58
58
  const confirmCheckout = () => {
59
59
  cy.shouldIncludePathInUrl(`${BASEPATH}/${Routes.CHECKOUT}`);
60
-
61
60
  cy.getByTestId("confirm-checkout-button").click();
62
61
 
63
- cy.shouldIncludePathInUrl(`${BASEPATH}/${Routes.CHECKOUT}/${Routes.CHECKOUT_PAYMENT}`);
64
-
65
62
  interceptViewFirstAvailableCheckoutByCustomerId(
66
63
  checkout({
67
64
  status: CheckoutStatus.SUBMITTED,
@@ -1,21 +1,22 @@
1
1
  import { FC, ForwardRefExoticComponent, PropsWithChildren, RefAttributes } from "react";
2
+ import { PaymentPayload } from "@lookiero/payments-front";
2
3
  declare const setPaymentsBridge: () => undefined;
3
4
  declare const PaymentsQueryProvider: FC<PropsWithChildren>;
4
5
  declare const PaymentInstrumentSelect: FC;
5
- interface StartLegacyBoxCheckoutCallbackArgs {
6
- readonly status: string;
7
- readonly final: boolean;
8
- }
9
6
  interface StartLegacyBoxCheckoutFunction {
10
- (paymentFlowPayload: unknown, callback: (params: StartLegacyBoxCheckoutCallbackArgs) => Promise<void>): void;
7
+ (paymentFlowPayload: unknown): void;
11
8
  }
12
9
  interface PaymentFlowRef {
13
10
  readonly startLegacyBoxCheckout: StartLegacyBoxCheckoutFunction;
14
11
  }
15
- declare const paymentFlowRef: PaymentFlowRef;
16
12
  declare const PaymentFlow: ForwardRefExoticComponent<RefAttributes<PaymentFlowRef>>;
13
+ interface UsePaymentStatusManagerResult {
14
+ isLoading: boolean;
15
+ consumePayload: (callback: (payload: PaymentPayload) => void) => void;
16
+ }
17
+ declare const usePaymentStatusManager: (section: Section) => UsePaymentStatusManagerResult;
17
18
  declare enum Section {
18
19
  BOX_CHECKOUT = "box-checkout"
19
20
  }
20
21
  export type { PaymentFlowRef };
21
- export { PaymentsQueryProvider, PaymentInstrumentSelect, PaymentFlow, Section, setPaymentsBridge, paymentFlowRef };
22
+ export { PaymentsQueryProvider, PaymentInstrumentSelect, PaymentFlow, Section, setPaymentsBridge, usePaymentStatusManager, };
@@ -1,11 +1,12 @@
1
1
  /* eslint-disable @typescript-eslint/naming-convention */
2
2
  import { forwardRef, useImperativeHandle, } from "react";
3
+ let startLegacyBoxCheckoutListener;
3
4
  const setPaymentsBridge = () => void 0;
4
5
  const PaymentsQueryProvider = ({ children }) => children;
5
6
  const PaymentInstrumentSelect = () => null;
6
7
  const paymentFlowRef = {
7
- startLegacyBoxCheckout: async (_paymentFlowPayload, callback) => {
8
- await callback({ status: "EXECUTED", final: true });
8
+ startLegacyBoxCheckout: async () => {
9
+ startLegacyBoxCheckoutListener?.({ success: true });
9
10
  },
10
11
  };
11
12
  const PaymentFlow = forwardRef((_props, ref) => {
@@ -13,8 +14,15 @@ const PaymentFlow = forwardRef((_props, ref) => {
13
14
  return null;
14
15
  });
15
16
  PaymentFlow.displayName = "PaymentFlow";
17
+ const paymentStatusManagerResult = {
18
+ isLoading: false,
19
+ consumePayload: (callback) => {
20
+ startLegacyBoxCheckoutListener = () => callback({ success: true });
21
+ },
22
+ };
23
+ const usePaymentStatusManager = () => paymentStatusManagerResult;
16
24
  var Section;
17
25
  (function (Section) {
18
26
  Section["BOX_CHECKOUT"] = "box-checkout";
19
27
  })(Section || (Section = {}));
20
- export { PaymentsQueryProvider, PaymentInstrumentSelect, PaymentFlow, Section, setPaymentsBridge, paymentFlowRef };
28
+ export { PaymentsQueryProvider, PaymentInstrumentSelect, PaymentFlow, Section, setPaymentsBridge, usePaymentStatusManager, };
package/dist/index.d.ts CHANGED
@@ -10,8 +10,8 @@ import { KameleoonEnvironment } from "./src/infrastructure/ab-testing/kameleoonE
10
10
  import { RootProps } from "./src/infrastructure/ui/Root";
11
11
  import { CheckoutProjection } from "./src/projection/checkout/checkout";
12
12
  import { Customer } from "./src/projection/customer/customer";
13
- import { Order } from "./src/projection/order/order";
14
- import { Subscription } from "./src/projection/subscription/subscription";
13
+ import { OrderProjection } from "./src/projection/order/order";
14
+ import { SubscriptionProjection } from "./src/projection/subscription/subscription";
15
15
  interface FirstAvailableCheckoutByCustomerIdFunctionArgs {
16
16
  readonly customerId: string | undefined;
17
17
  }
@@ -34,4 +34,4 @@ interface BootstrapFunction {
34
34
  }
35
35
  declare const bootstrap: BootstrapFunction;
36
36
  export { bootstrap, translationEndpoint, translationExternalEndpoint, Country, Segment, CheckoutStatus, Tradename };
37
- export type { SentryEnvironment, KameleoonEnvironment, Customer, Subscription, Order, Locale };
37
+ export type { SentryEnvironment, KameleoonEnvironment, Customer, SubscriptionProjection as Subscription, OrderProjection as Order, Locale, };
@@ -1,3 +1,4 @@
1
+ import { PortalProvider } from "@gorhom/portal";
1
2
  import { useFonts } from "expo-font";
2
3
  import "expo/build/Expo.fx";
3
4
  import React, { useCallback, useState } from "react";
@@ -18,7 +19,7 @@ import { root } from "./infrastructure/ui/Root";
18
19
  import { DOMAIN } from "./infrastructure/ui/i18n/i18n";
19
20
  import { Router } from "./infrastructure/ui/routing/router/Router";
20
21
  import { VERSION } from "./version";
21
- const locale = Locale.en_GB;
22
+ const locale = Locale.es_ES;
22
23
  const subscription = "b";
23
24
  const order = {
24
25
  isFirstOrder: false,
@@ -26,9 +27,11 @@ const order = {
26
27
  coupon: "MYLOOKIERO",
27
28
  };
28
29
  const customer = {
29
- customerId: "a4355713-469b-4684-bf90-3215702dfb1c",
30
- country: Country.ES,
30
+ customerId: "9cfb056e-6008-44b1-9075-320479bf92ac",
31
+ country: Country.NL,
31
32
  segment: Segment.WOMEN,
33
+ email: "email@example.com",
34
+ name: "Adèle Léonce Émilie",
32
35
  };
33
36
  const sentryConfig = {
34
37
  publicKey: "66cadf9444db4ea5945670f12ec08ae7",
@@ -41,7 +44,7 @@ const apiUrl = Platform.OS !== "web"
41
44
  : __DEV__
42
45
  ? "/local-to-dev"
43
46
  : "/checkout/api";
44
- const authToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjU2ODkxMDAsImV4cCI6MTc0NjI3ODQwMiwiZGlzcGxheU5hbWUiOiJUZXN0aW5nIiwiY291bnRyeV9jb2RlIjoiRVMiLCJhY2Nlc3NWaWEiOiJlbWFpbCIsInN1YnNjcmlwdGlvblN0YXJ0aW5nRGF0ZSI6IjIwMjUtMDQtMDMiLCJpbXBlcnNvbmF0ZWQiOmZhbHNlLCJ1dWlkIjoiYTQzNTU3MTMtNDY5Yi00Njg0LWJmOTAtMzIxNTcwMmRmYjFjIiwiaWF0IjoxNzQzNjg2NDAyfQ.pmqFo-4s0USFpzCnijRt78bWuBdu2Q7f4L9UtOkfAwo";
47
+ const authToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjU2NDk0NjYsImV4cCI6MTc0MjQ3MDI5OSwiZGlzcGxheU5hbWUiOiJNaWtlbCIsImNvdW50cnlfY29kZSI6IkVTIiwiYWNjZXNzVmlhIjoiZW1haWwiLCJzdWJzY3JpcHRpb25TdGFydGluZ0RhdGUiOiIyMDI0LTExLTExIiwiaW1wZXJzb25hdGVkIjpmYWxzZSwidXVpZCI6ImQzYzIzNTRiLTk4MTEtNDZkNC1iMmJhLTVmYmEwMTJlZDk0ZCIsImlhdCI6MTc0MDA1MTA5OX0.AkuUZTsn9mgplQwatg0dPKyv1Hsc6r267UMahxMH19g";
45
48
  const getAuthToken = () => Promise.resolve(authToken);
46
49
  const externalTranslationsUrl = Platform.OS !== "web"
47
50
  ? "https://backend-for-user.dev.envs.lookiero.tech/api/v2/translations"
@@ -76,6 +79,7 @@ setPaymentsBridge({
76
79
  useFeatureFlags: () => ({}),
77
80
  locale: () => Promise.resolve("es-ES"),
78
81
  scrollView: ScrollView,
82
+ hostUrl: "",
79
83
  });
80
84
  const kameleoonConfig = {
81
85
  siteCode: "aplm4v3ckn",
@@ -106,13 +110,14 @@ const ExpoRoot = () => {
106
110
  });
107
111
  const [isAccessible, setIsAccessible] = useState();
108
112
  const onNotAccessible = useCallback(() => setIsAccessible(false), []);
109
- return fontsLoaded ? (React.createElement(PaymentsQueryProvider, null,
110
- React.createElement(EventProvider, null,
111
- React.createElement(Aurora, null,
112
- isAccessible === false && React.createElement(Text, { heading: true }, "Checkout is not accessible!"),
113
- React.createElement(Router, null,
114
- React.createElement(Routes, null,
115
- React.createElement(Route, { path: "/checkout/*", element: React.createElement(Root, { basePath: "/checkout", customer: customer, layout: DummyLayout, locale: locale, order: order, subscription: subscription, tradename: Tradename.LOOKIERO, useRedirect: useRedirect, onNotAccessible: onNotAccessible }) }),
116
- React.createElement(Route, { element: React.createElement(Navigate, { to: "/checkout", replace: true }), path: "*" }))))))) : null;
113
+ return fontsLoaded ? (React.createElement(PortalProvider, null,
114
+ React.createElement(PaymentsQueryProvider, null,
115
+ React.createElement(EventProvider, null,
116
+ React.createElement(Aurora, null,
117
+ isAccessible === false && React.createElement(Text, { heading: true }, "Checkout is not accessible!"),
118
+ React.createElement(Router, null,
119
+ React.createElement(Routes, null,
120
+ React.createElement(Route, { path: "/checkout/*", element: React.createElement(Root, { basePath: "/checkout", customer: customer, layout: DummyLayout, locale: locale, order: order, subscription: subscription, tradename: Tradename.LOOKIERO, useRedirect: useRedirect, onCheckoutFlowSuccess: () => console.log("Checkout flow success!"), onNotAccessible: onNotAccessible }) }),
121
+ React.createElement(Route, { element: React.createElement(Navigate, { to: "/checkout", replace: true }), path: "*" })))))))) : null;
117
122
  };
118
123
  export { ExpoRoot };
@@ -5,7 +5,7 @@ interface BlockCheckoutBookingFunction {
5
5
  }
6
6
  type UseBlockCheckoutBooking = [blockCheckoutBooking: BlockCheckoutBookingFunction, status: CommandStatus];
7
7
  interface UseBlockCheckoutBookingFunctionArgs {
8
- readonly checkoutBookingId: string;
8
+ readonly checkoutBookingId: string | undefined;
9
9
  readonly logger: Logger;
10
10
  }
11
11
  interface UseBlockCheckoutBookingFunction {
@@ -1,4 +1,5 @@
1
1
  import { useCallback } from "react";
2
+ import invariant from "tiny-invariant";
2
3
  import { useCommand } from "@lookiero/messaging-react";
3
4
  import { NotificationLevel, useCreateToastNotification } from "@lookiero/sty-psp-notifications";
4
5
  import { blockCheckoutBooking as blockCheckoutBookingCommand } from "../../../../domain/checkoutBooking/command/blockCheckoutBooking";
@@ -8,6 +9,7 @@ const useBlockCheckoutBooking = ({ checkoutBookingId, logger }) => {
8
9
  const [commandBus, status] = useCommand({ contextId: MESSAGING_CONTEXT_ID });
9
10
  const [createNotification] = useCreateToastNotification({ contextId: MESSAGING_CONTEXT_ID, logger });
10
11
  const blockCheckoutBooking = useCallback(async () => {
12
+ invariant(checkoutBookingId, "checkoutBookingId is required");
11
13
  try {
12
14
  await commandBus(blockCheckoutBookingCommand({
13
15
  aggregateId: checkoutBookingId,
@@ -3,6 +3,7 @@ import { CheckoutProjection } from "../../../projection/checkout/checkout";
3
3
  import { CheckoutItemFunctionArgs } from "../checkoutItem/checkoutItem.mock";
4
4
  import { CheckoutDto } from "./checkout";
5
5
  interface CheckoutDtoFunctionArgs {
6
+ readonly id?: string;
6
7
  readonly status?: CheckoutStatus;
7
8
  readonly items: CheckoutItemFunctionArgs[];
8
9
  }
@@ -8,8 +8,8 @@ const itemIds = [
8
8
  "8af02b73-0b0a-46a0-a3a8-d9710815c739",
9
9
  "8890e00b-d4ed-4220-ae13-52cd88ae8ed5",
10
10
  ];
11
- const checkoutDto = ({ status = CheckoutStatus.STARTED, items }) => ({
12
- id: "9c450400-0cd7-44a4-b0e3-e0002a9bf8df",
11
+ const checkoutDto = ({ id = "9c450400-0cd7-44a4-b0e3-e0002a9bf8df", status = CheckoutStatus.STARTED, items, }) => ({
12
+ id,
13
13
  status,
14
14
  customerId: "0df61ca7-7e4d-462b-a422-a57de0d116b4",
15
15
  boxId: "9c406e57-100a-4aa6-83c5-016e7c2970e7",
@@ -17,5 +17,5 @@ const checkoutDto = ({ status = CheckoutStatus.STARTED, items }) => ({
17
17
  expiresOn: "2022-09-20",
18
18
  items: items.map(({ id, status, feedbacks, replacedFor }, index) => checkoutItem({ id: id || itemIds[index], status, feedbacks, replacedFor })),
19
19
  });
20
- const checkout = ({ status, items }) => toCheckoutProjection(checkoutDto({ status, items }));
20
+ const checkout = ({ id, status, items }) => toCheckoutProjection(checkoutDto({ id, status, items }));
21
21
  export { checkout, checkoutDto };
@@ -4,7 +4,7 @@ interface QueryOptions {
4
4
  readonly refetchOnMount?: boolean | "always";
5
5
  }
6
6
  interface UseViewPricingByCheckoutIdFunctionArgs {
7
- readonly checkoutId: string;
7
+ readonly checkoutId: string | undefined;
8
8
  readonly queryOptions?: QueryOptions;
9
9
  }
10
10
  interface UseViewPricingByCheckoutIdFunction {
@@ -13,7 +13,7 @@ const DEFAULT_QUERY_OPTIONS = {
13
13
  refetchOnMount: "always",
14
14
  };
15
15
  const useViewPricingByCheckoutId = ({ checkoutId, queryOptions = DEFAULT_QUERY_OPTIONS, }) => useQuery({
16
- query: viewPricingByCheckoutId({ checkoutId }),
16
+ query: viewPricingByCheckoutId({ checkoutId: checkoutId }),
17
17
  contextId: MESSAGING_CONTEXT_ID,
18
18
  invalidation: shouldInvalidate,
19
19
  options: {
@@ -21,6 +21,7 @@ const useViewPricingByCheckoutId = ({ checkoutId, queryOptions = DEFAULT_QUERY_O
21
21
  staleTime: Infinity,
22
22
  retry: false,
23
23
  refetchOnWindowFocus: false,
24
+ enabled: Boolean(checkoutId),
24
25
  },
25
26
  });
26
27
  export { useViewPricingByCheckoutId };
@@ -2,7 +2,7 @@ import { BaseTrackingEvent, TrackingEventCategory } from "@lookiero/sty-psp-trac
2
2
  import { CheckoutItemStatus } from "../../domain/checkoutItem/model/checkoutItem";
3
3
  import { Currency } from "../../domain/checkoutItem/model/currency";
4
4
  import { MediaPerspective } from "../../projection/checkoutItem/checkoutItem";
5
- import { Subscription } from "../../projection/subscription/subscription";
5
+ import { SubscriptionProjection } from "../../projection/subscription/subscription";
6
6
  declare const PROJECT = "checkout";
7
7
  declare enum TrackingPage {
8
8
  ITEM = "item",
@@ -96,7 +96,7 @@ interface CheckoutTrackingEvent extends CheckoutBaseTrackingEvent<TrackingEventN
96
96
  readonly actionField: {
97
97
  readonly items: number;
98
98
  readonly currencyCode: Currency;
99
- readonly subscription: Subscription;
99
+ readonly subscription: SubscriptionProjection;
100
100
  readonly coupon: string | null;
101
101
  readonly orderId: number;
102
102
  readonly value: number;
@@ -1,26 +1,19 @@
1
1
  import { Country } from "@lookiero/sty-psp-locale";
2
2
  import { Segment } from "@lookiero/sty-psp-segment";
3
- import { Currency } from "../../domain/checkoutItem/model/currency";
4
- import { Subscription } from "../../projection/subscription/subscription";
5
- import { TrackingPage } from "./tracking";
6
- interface TrackCheckoutFunctionArgs {
7
- readonly userId: string;
8
- readonly totalReplacedFor: number;
9
- readonly isFirstOrder: boolean;
10
- readonly totalCheckoutItemsKept: number;
11
- readonly currencyCode: Currency;
12
- readonly subscription: Subscription;
13
- readonly coupon: string | null;
14
- readonly orderNumber: number;
15
- readonly pendingToPay: number;
16
- }
3
+ import { CheckoutProjection } from "../../projection/checkout/checkout";
4
+ import { OrderProjection } from "../../projection/order/order";
5
+ import { PricingProjection } from "../../projection/pricing/pricing";
6
+ import { SubscriptionProjection } from "../../projection/subscription/subscription";
17
7
  interface TrackCheckoutFunction {
18
- (args: TrackCheckoutFunctionArgs): void;
8
+ (): void;
19
9
  }
20
10
  interface UseTrackCheckoutFunctionArgs {
21
- readonly page: TrackingPage;
11
+ readonly order: OrderProjection;
12
+ readonly checkout: CheckoutProjection | undefined;
13
+ readonly pricing: PricingProjection | undefined;
14
+ readonly subscription: SubscriptionProjection;
15
+ readonly userId: string;
22
16
  readonly country: Country;
23
- readonly checkoutId: string | undefined;
24
17
  readonly segment: Segment;
25
18
  }
26
19
  interface UseTrackCheckoutFunction {
@@ -1,38 +1,53 @@
1
1
  import { useCallback } from "react";
2
+ import invariant from "tiny-invariant";
2
3
  import { useEmitUserEvent } from "@lookiero/sty-psp-tracking";
3
- import { PROJECT } from "./tracking";
4
+ import { CheckoutItemStatus } from "../../domain/checkoutItem/model/checkoutItem";
5
+ import { PROJECT, TrackingPage } from "./tracking";
4
6
  import { TrackingEventName, TrackingEventCategory } from "./tracking";
5
- const useTrackCheckout = ({ page, country, checkoutId, segment }) => {
7
+ const useTrackCheckout = ({ order, checkout, pricing, subscription, segment, country, userId, }) => {
6
8
  const emitUserEvent = useEmitUserEvent();
7
- const trackCheckout = useCallback(({ coupon, currencyCode, isFirstOrder, orderNumber, pendingToPay, subscription, totalCheckoutItemsKept, totalReplacedFor, userId, }) => {
8
- if (!checkoutId) {
9
- return;
10
- }
9
+ const { coupon, isFirstOrder, orderNumber } = order;
10
+ const trackCheckout = useCallback(() => {
11
+ invariant(checkout, "Checkout must be defined to track checkout");
12
+ const checkoutItemsKept = checkout.items.filter((checkoutItem) => checkoutItem.status === CheckoutItemStatus.KEPT || checkoutItem.status === CheckoutItemStatus.REPLACED);
13
+ const totalReplacedFor = checkoutItemsKept?.filter(({ replacedFor }) => Boolean(replacedFor)).length || 0;
11
14
  const checkoutTrackingEvent = {
12
15
  event: TrackingEventName.CHECKOUT,
13
16
  eventCategory: TrackingEventCategory.ECOMMERCE,
14
- section: `${PROJECT}_${page}`,
17
+ section: `${PROJECT}_${TrackingPage.CHECKOUT}`,
15
18
  store: country,
16
19
  segment,
17
- checkoutId,
20
+ checkoutId: checkout.id,
18
21
  userId,
19
22
  sizeChanges: totalReplacedFor,
20
23
  isFirstOrder,
21
24
  ecommerce: {
22
25
  checkout: {
23
26
  actionField: {
24
- items: totalCheckoutItemsKept,
25
- currencyCode,
27
+ items: checkoutItemsKept.length || 0,
28
+ currencyCode: pricing?.pendingToPay.currency,
26
29
  subscription,
27
30
  coupon,
28
31
  orderId: orderNumber,
29
- value: pendingToPay,
32
+ value: pricing?.pendingToPay.amount / 100,
30
33
  },
31
34
  },
32
35
  },
33
36
  };
34
37
  emitUserEvent(checkoutTrackingEvent);
35
- }, [checkoutId, country, emitUserEvent, page, segment]);
38
+ }, [
39
+ checkout,
40
+ country,
41
+ coupon,
42
+ emitUserEvent,
43
+ isFirstOrder,
44
+ orderNumber,
45
+ pricing?.pendingToPay.amount,
46
+ pricing?.pendingToPay.currency,
47
+ segment,
48
+ subscription,
49
+ userId,
50
+ ]);
36
51
  return trackCheckout;
37
52
  };
38
53
  export { useTrackCheckout };
@@ -8,8 +8,8 @@ import { SentryEnvironment, SentryLoggerFunctionArgs } from "@lookiero/sty-psp-l
8
8
  import { Layout } from "@lookiero/sty-psp-ui";
9
9
  import { Tradename } from "@lookiero/sty-sp-tradename";
10
10
  import { Customer } from "../../projection/customer/customer";
11
- import { Order } from "../../projection/order/order";
12
- import { Subscription } from "../../projection/subscription/subscription";
11
+ import { OrderProjection } from "../../projection/order/order";
12
+ import { SubscriptionProjection } from "../../projection/subscription/subscription";
13
13
  import { KameleoonEnvironment } from "../ab-testing/kameleoonEnvironment";
14
14
  interface RootFunctionArgs {
15
15
  readonly Messaging: MessagingRoot;
@@ -25,14 +25,14 @@ interface RootFunction {
25
25
  }
26
26
  interface RootProps {
27
27
  readonly basePath: string;
28
- readonly locale?: Locale;
28
+ readonly locale: Locale;
29
29
  readonly customer: Customer;
30
- readonly order: Order | undefined;
31
- readonly subscription: Subscription | undefined;
30
+ readonly order: OrderProjection;
31
+ readonly subscription: SubscriptionProjection;
32
32
  readonly layout: Layout;
33
33
  readonly tradename: Tradename;
34
34
  readonly onNotAccessible: () => void;
35
- readonly onCheckoutSubmitted?: () => void;
35
+ readonly onCheckoutFlowSuccess: () => void;
36
36
  readonly useRedirect: () => Record<string, string>;
37
37
  readonly useRoutes?: typeof reactRouterUseRoutes;
38
38
  }
@@ -2,7 +2,6 @@
2
2
  import React, { useCallback } from "react";
3
3
  import { Platform } from "react-native";
4
4
  import { useRoutes as reactRouterUseRoutes } from "react-router-native";
5
- import { Locale } from "@lookiero/sty-psp-locale";
6
5
  import { sentryLogger, sentryLoggerHOC } from "@lookiero/sty-psp-logging";
7
6
  import { QueryBusProvider } from "./hooks/useQueryBus";
8
7
  import { Routing } from "./routing/Routing";
@@ -10,11 +9,11 @@ const root = ({ Messaging, I18n, queryBus, getAuthToken, development, sentry, ka
10
9
  const logger = sentryLogger(sentry);
11
10
  const kameleoon = kameleoonConfig();
12
11
  // eslint-disable-next-line react/display-name, react/prop-types
13
- const Root = ({ basePath, locale = Locale.en_GB, customer, order, subscription, layout, tradename, onNotAccessible, onCheckoutSubmitted, useRedirect, useRoutes = reactRouterUseRoutes, }) => {
12
+ const Root = ({ basePath, locale, customer, order, subscription, layout, tradename, onNotAccessible, onCheckoutFlowSuccess, useRedirect, useRoutes = reactRouterUseRoutes, }) => {
14
13
  const handleOnI18nError = useCallback((error) => logger.captureException(error), []);
15
14
  return (React.createElement(Messaging, { includeReactQueryDevTools: Platform.OS === "web" },
16
15
  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, tradename: tradename, useRedirect: useRedirect, useRoutes: useRoutes, onCheckoutSubmitted: onCheckoutSubmitted, onI18nError: development ? undefined : handleOnI18nError, onNotAccessible: onNotAccessible }))));
16
+ React.createElement(Routing, { I18n: I18n, basePath: basePath, customer: customer, getAuthToken: getAuthToken, kameleoon: kameleoon, layout: layout, locale: locale, order: order, subscription: subscription, tradename: tradename, useRedirect: useRedirect, useRoutes: useRoutes, onCheckoutFlowSuccess: onCheckoutFlowSuccess, onI18nError: development ? undefined : handleOnI18nError, onNotAccessible: onNotAccessible }))));
18
17
  };
19
18
  const hoc = sentryLoggerHOC({ logger });
20
19
  /**
@@ -0,0 +1,26 @@
1
+ import { ReactNode } from "react";
2
+ import { CheckoutProjection } from "../../../projection/checkout/checkout";
3
+ import { OrderProjection } from "../../../projection/order/order";
4
+ import { SubscriptionProjection } from "../../../projection/subscription/subscription";
5
+ type CheckoutFlowStatus = "idle" | "loading" | "success" | "error";
6
+ interface CheckoutFlowFunction {
7
+ (): Promise<void>;
8
+ }
9
+ type CheckoutFlowReturn = [
10
+ checkoutFlow: CheckoutFlowFunction,
11
+ checkoutFlowStatus: CheckoutFlowStatus,
12
+ paymentFlow: ReactNode
13
+ ];
14
+ interface UseCheckoutFlowArgs {
15
+ readonly checkout: CheckoutProjection | undefined;
16
+ readonly order: OrderProjection;
17
+ readonly subscription: SubscriptionProjection;
18
+ readonly getAuthToken: () => Promise<string>;
19
+ readonly onSuccess: () => void;
20
+ }
21
+ interface UseCheckoutFlowFunction {
22
+ (args: UseCheckoutFlowArgs): CheckoutFlowReturn;
23
+ }
24
+ declare const useCheckoutFlow: UseCheckoutFlowFunction;
25
+ export type { CheckoutFlowStatus };
26
+ export { useCheckoutFlow };
@@ -0,0 +1,127 @@
1
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { CommandStatus } from "@lookiero/messaging-react";
3
+ import { PaymentFlow, Section } from "@lookiero/payments-front";
4
+ import { useLogger } from "@lookiero/sty-psp-logging";
5
+ import { NotificationLevel, useCreateToastNotification } from "@lookiero/sty-psp-notifications";
6
+ import { viewCheckoutBookingById, } from "../../../projection/checkoutBooking/viewCheckoutBookingById";
7
+ import { MESSAGING_CONTEXT_ID } from "../../delivery/baseBootstrap";
8
+ import { useSubmitCheckout } from "../../domain/checkout/react/useSubmitCheckout";
9
+ import { useBlockCheckoutBooking } from "../../domain/checkoutBooking/react/useBlockCheckoutBooking";
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 { useTrackCheckout } from "../../tracking/useTrackCheckout";
14
+ import { I18nMessages } from "../i18n/i18n";
15
+ import { Routes } from "../routing/routes";
16
+ import { usePaymentInstrumentEvents } from "./usePaymentInstrumentEvents";
17
+ import { useQueryBus } from "./useQueryBus";
18
+ import { useStaticInfo } from "./useStaticInfo";
19
+ const useCheckoutFlow = ({ checkout: checkoutProjection, order: orderProjection, subscription: subscriptionProjection, getAuthToken, onSuccess, }) => {
20
+ const logger = useLogger();
21
+ const queryBus = useQueryBus();
22
+ const { customer: { customerId, country, segment, name, email }, basePath, } = useStaticInfo();
23
+ const paymentFlowRef = useRef(null);
24
+ const [paymentFlowPayload] = useViewPaymentFlowPayloadByCheckoutId({
25
+ checkoutId: checkoutProjection?.id,
26
+ });
27
+ const [sizeChangeEnabled] = useViewIsSizeChangeEnabledByCheckoutId({ checkoutId: checkoutProjection?.id });
28
+ const [pricing] = useViewPricingByCheckoutId({
29
+ checkoutId: checkoutProjection?.id,
30
+ queryOptions: { refetchOnMount: true },
31
+ });
32
+ const [submitCheckout, submitCheckoutStatus] = useSubmitCheckout({
33
+ checkoutId: checkoutProjection?.id,
34
+ logger,
35
+ });
36
+ const [blockCheckoutBooking, blockCheckoutBookingStatus] = useBlockCheckoutBooking({
37
+ checkoutBookingId: checkoutProjection?.checkoutBookingId,
38
+ logger,
39
+ });
40
+ const [createNotification] = useCreateToastNotification({ contextId: MESSAGING_CONTEXT_ID, logger });
41
+ const [checkoutBookingExpired, setCheckoutBookingExpired] = useState(false);
42
+ const [startLegacyBoxCheckoutStatus, setStartLegacyBoxCheckoutStatus] = useState("idle");
43
+ const [authToken, setAuthToken] = useState();
44
+ useEffect(() => {
45
+ const loadAuthToken = async () => setAuthToken(await getAuthToken());
46
+ loadAuthToken();
47
+ }, [getAuthToken]);
48
+ const trackCheckout = useTrackCheckout({
49
+ checkout: checkoutProjection,
50
+ order: orderProjection,
51
+ pricing,
52
+ subscription: subscriptionProjection,
53
+ userId: customerId,
54
+ country,
55
+ segment,
56
+ });
57
+ const checkoutFlow = useCallback(async () => {
58
+ try {
59
+ sizeChangeEnabled && (await blockCheckoutBooking());
60
+ }
61
+ catch (error) {
62
+ return;
63
+ }
64
+ if (checkoutProjection?.checkoutBookingId) {
65
+ const checkoutBooking = await queryBus(viewCheckoutBookingById({ checkoutBookingId: checkoutProjection?.checkoutBookingId }));
66
+ if (checkoutBooking?.isExpired) {
67
+ setCheckoutBookingExpired(true);
68
+ return;
69
+ }
70
+ }
71
+ setStartLegacyBoxCheckoutStatus("loading");
72
+ paymentFlowRef.current?.startLegacyBoxCheckout({
73
+ ...paymentFlowPayload,
74
+ userInformation: { email, name },
75
+ returnUrl: `${basePath}/${Routes.CHECKOUT}`,
76
+ });
77
+ }, [
78
+ checkoutProjection?.checkoutBookingId,
79
+ paymentFlowPayload,
80
+ email,
81
+ name,
82
+ basePath,
83
+ sizeChangeEnabled,
84
+ blockCheckoutBooking,
85
+ queryBus,
86
+ ]);
87
+ const onPaymentSuccess = useCallback(async () => {
88
+ setStartLegacyBoxCheckoutStatus("success");
89
+ await submitCheckout();
90
+ createNotification({
91
+ bodyI18nKey: I18nMessages.CHECKOUT_TOAST_PAYMENT_SUCCESS,
92
+ level: NotificationLevel.SUCCESS,
93
+ });
94
+ trackCheckout();
95
+ onSuccess();
96
+ }, [createNotification, onSuccess, submitCheckout, trackCheckout]);
97
+ const onPaymentError = useCallback((payload) => {
98
+ setStartLegacyBoxCheckoutStatus("error");
99
+ createNotification({
100
+ bodyI18nKey: payload.metadata?.toaster?.id || I18nMessages.CHECKOUT_TOAST_PAYMENT_ERROR,
101
+ level: NotificationLevel.ERROR,
102
+ });
103
+ }, [createNotification]);
104
+ usePaymentInstrumentEvents({ onSuccess: onPaymentSuccess, onError: onPaymentError });
105
+ const checkoutFlowStatus = useMemo(() => {
106
+ if (blockCheckoutBookingStatus === CommandStatus.LOADING ||
107
+ startLegacyBoxCheckoutStatus === "loading" ||
108
+ submitCheckoutStatus === CommandStatus.LOADING) {
109
+ return "loading";
110
+ }
111
+ if (blockCheckoutBookingStatus === CommandStatus.SUCCESS &&
112
+ startLegacyBoxCheckoutStatus === "success" &&
113
+ submitCheckoutStatus === CommandStatus.SUCCESS) {
114
+ return "success";
115
+ }
116
+ if (blockCheckoutBookingStatus === CommandStatus.ERROR ||
117
+ startLegacyBoxCheckoutStatus === "error" ||
118
+ submitCheckoutStatus === CommandStatus.ERROR ||
119
+ checkoutBookingExpired) {
120
+ return "error";
121
+ }
122
+ return "idle";
123
+ }, [blockCheckoutBookingStatus, startLegacyBoxCheckoutStatus, submitCheckoutStatus, checkoutBookingExpired]);
124
+ const paymentFlow = useMemo(() => (authToken ? React.createElement(PaymentFlow, { ref: paymentFlowRef, section: Section.BOX_CHECKOUT, token: authToken }) : null), [authToken]);
125
+ return useMemo(() => [checkoutFlow, checkoutFlowStatus, paymentFlow], [checkoutFlow, paymentFlow, checkoutFlowStatus]);
126
+ };
127
+ export { useCheckoutFlow };
@@ -1,6 +1,7 @@
1
- import { Logger } from "@lookiero/sty-psp-logging";
1
+ import { PaymentPayload } from "@lookiero/payments-front";
2
2
  interface UsePaymentInstrumentEventsFunctionArgs {
3
- readonly logger: Logger;
3
+ readonly onSuccess: (payload: PaymentPayload) => void;
4
+ readonly onError: (payload: PaymentPayload) => void;
4
5
  }
5
6
  interface UsePaymentInstrumentEventsFunction {
6
7
  (args: UsePaymentInstrumentEventsFunctionArgs): void;
@@ -1,29 +1,20 @@
1
- import { useCallback, useEffect } from "react";
2
- import { useEvent } from "@lookiero/event";
3
- import { NotificationLevel, useCreateToastNotification } from "@lookiero/sty-psp-notifications";
4
- import { MESSAGING_CONTEXT_ID } from "../../delivery/baseBootstrap";
5
- import { I18nMessages } from "../i18n/i18n";
6
- const PAYMENT_ERROR = "ERROR";
7
- const PAYMENT_SUCCESS = "PAYMENT_INSTRUMENT_UPDATED";
8
- const usePaymentInstrumentEvents = ({ logger }) => {
9
- const { subscribe, unsubscribe } = useEvent();
10
- const [createNotification] = useCreateToastNotification({ contextId: MESSAGING_CONTEXT_ID, logger });
11
- const onSuccess = useCallback(({ message }) => createNotification({ bodyI18nKey: message.id, level: NotificationLevel.SUCCESS }), [createNotification]);
12
- const onError = useCallback(({ error }) => {
13
- if (error.toaster) {
14
- createNotification({
15
- bodyI18nKey: error.toaster.id || I18nMessages.CHECKOUT_TOAST_PAYMENT_ERROR,
16
- level: NotificationLevel.ERROR,
17
- });
18
- }
19
- }, [createNotification]);
1
+ import { useEffect } from "react";
2
+ import { Section, usePaymentStatusManager } from "@lookiero/payments-front";
3
+ const usePaymentInstrumentEvents = ({ onSuccess, onError }) => {
4
+ const refreshStatus = usePaymentStatusManager(Section.BOX_CHECKOUT);
20
5
  useEffect(() => {
21
- subscribe({ event: PAYMENT_ERROR }, onError);
22
- subscribe({ event: PAYMENT_SUCCESS }, onSuccess);
23
- return () => {
24
- unsubscribe({ event: PAYMENT_ERROR }, onError);
25
- unsubscribe({ event: PAYMENT_SUCCESS }, onSuccess);
26
- };
27
- }, [subscribe, unsubscribe, createNotification, onError, onSuccess]);
6
+ const { isLoading, consumePayload } = refreshStatus;
7
+ if (isLoading) {
8
+ return;
9
+ }
10
+ consumePayload((payload) => {
11
+ if (payload.success) {
12
+ onSuccess(payload);
13
+ }
14
+ else {
15
+ onError(payload);
16
+ }
17
+ });
18
+ }, [onError, onSuccess, refreshStatus]);
28
19
  };
29
20
  export { usePaymentInstrumentEvents };