@lookiero/checkout 10.0.1 → 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.
- package/cypress/integration/checkout.spec.ts +0 -3
- package/dist/fake-dependencies/@lookiero/payments-front/index.d.ts +8 -7
- package/dist/fake-dependencies/@lookiero/payments-front/index.js +11 -3
- package/dist/index.d.ts +3 -3
- package/dist/src/ExpoRoot.js +17 -12
- package/dist/src/infrastructure/domain/checkoutBooking/react/useBlockCheckoutBooking.d.ts +1 -1
- package/dist/src/infrastructure/domain/checkoutBooking/react/useBlockCheckoutBooking.js +2 -0
- package/dist/src/infrastructure/projection/checkout/checkout.mock.d.ts +1 -0
- package/dist/src/infrastructure/projection/checkout/checkout.mock.js +3 -3
- package/dist/src/infrastructure/projection/pricing/react/useViewPricingByCheckoutId.d.ts +1 -1
- package/dist/src/infrastructure/projection/pricing/react/useViewPricingByCheckoutId.js +2 -1
- package/dist/src/infrastructure/tracking/tracking.d.ts +2 -2
- package/dist/src/infrastructure/tracking/useTrackCheckout.d.ts +10 -17
- package/dist/src/infrastructure/tracking/useTrackCheckout.js +27 -12
- package/dist/src/infrastructure/ui/Root.d.ts +6 -6
- package/dist/src/infrastructure/ui/Root.js +2 -3
- package/dist/src/infrastructure/ui/hooks/useCheckoutFlow.d.ts +26 -0
- package/dist/src/infrastructure/ui/hooks/useCheckoutFlow.js +127 -0
- package/dist/src/infrastructure/ui/hooks/usePaymentInstrumentEvents.d.ts +3 -2
- package/dist/src/infrastructure/ui/hooks/usePaymentInstrumentEvents.js +17 -26
- package/dist/src/infrastructure/ui/i18n/i18n.d.ts +1 -0
- package/dist/src/infrastructure/ui/i18n/i18n.js +1 -0
- package/dist/src/infrastructure/ui/routing/CheckoutMiddleware.js +1 -12
- package/dist/src/infrastructure/ui/routing/Routing.d.ts +5 -5
- package/dist/src/infrastructure/ui/routing/Routing.js +2 -10
- package/dist/src/infrastructure/ui/routing/routes.d.ts +0 -1
- package/dist/src/infrastructure/ui/routing/routes.js +0 -1
- package/dist/src/infrastructure/ui/views/App.js +5 -6
- package/dist/src/infrastructure/ui/views/checkout/Checkout.d.ts +7 -2
- package/dist/src/infrastructure/ui/views/checkout/Checkout.js +20 -9
- package/dist/src/infrastructure/ui/views/checkout/Checkout.style.d.ts +3 -0
- package/dist/src/infrastructure/ui/views/checkout/Checkout.style.js +3 -0
- package/dist/src/infrastructure/ui/views/checkout/components/paymentInstrument/PaymentInstrument.js +7 -7
- package/dist/src/projection/customer/customer.d.ts +2 -0
- package/dist/src/projection/order/order.d.ts +1 -1
- package/dist/src/projection/subscription/subscription.d.ts +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/fake-dependencies/@lookiero/payments-front/index.tsx +36 -8
- package/index.ts +10 -3
- package/package.json +4 -4
- package/src/ExpoRoot.tsx +43 -36
- package/src/infrastructure/domain/checkoutBooking/react/useBlockCheckoutBooking.ts +4 -1
- package/src/infrastructure/projection/checkout/checkout.mock.ts +8 -3
- package/src/infrastructure/projection/pricing/react/useViewPricingByCheckoutId.ts +3 -2
- package/src/infrastructure/tracking/tracking.ts +2 -2
- package/src/infrastructure/tracking/useTrackCheckout.test.tsx +51 -24
- package/src/infrastructure/tracking/useTrackCheckout.ts +66 -56
- package/src/infrastructure/ui/Root.tsx +9 -9
- package/src/infrastructure/ui/components/templates/header/itemHeader/ItemHeader.tsx +1 -0
- package/src/infrastructure/ui/hooks/useCheckoutFlow.test.tsx +302 -0
- package/src/infrastructure/ui/hooks/useCheckoutFlow.tsx +203 -0
- package/src/infrastructure/ui/hooks/usePaymentInstrumentEvents.ts +18 -60
- package/src/infrastructure/ui/i18n/i18n.ts +1 -0
- package/src/infrastructure/ui/routing/CheckoutMiddleware.test.tsx +0 -11
- package/src/infrastructure/ui/routing/CheckoutMiddleware.tsx +1 -15
- package/src/infrastructure/ui/routing/Routing.tsx +14 -25
- package/src/infrastructure/ui/routing/routes.ts +0 -1
- package/src/infrastructure/ui/views/App.tsx +5 -13
- package/src/infrastructure/ui/views/checkout/Checkout.style.ts +3 -0
- package/src/infrastructure/ui/views/checkout/Checkout.test.tsx +51 -43
- package/src/infrastructure/ui/views/checkout/Checkout.tsx +51 -13
- package/src/infrastructure/ui/views/checkout/components/paymentInstrument/PaymentInstrument.tsx +8 -8
- package/src/projection/customer/customer.ts +2 -0
- package/src/projection/order/order.ts +1 -1
- package/src/projection/subscription/subscription.ts +1 -1
- package/dist/src/infrastructure/ui/hooks/useSubmitCheckout.d.ts +0 -27
- package/dist/src/infrastructure/ui/hooks/useSubmitCheckout.js +0 -97
- package/dist/src/infrastructure/ui/views/checkout/components/checkoutPaymentModal/CheckoutPaymentModal.d.ts +0 -12
- package/dist/src/infrastructure/ui/views/checkout/components/checkoutPaymentModal/CheckoutPaymentModal.js +0 -88
- package/src/infrastructure/ui/hooks/useSubmitCheckout.test.ts +0 -297
- package/src/infrastructure/ui/hooks/useSubmitCheckout.ts +0 -169
- package/src/infrastructure/ui/views/checkout/components/checkoutPaymentModal/CheckoutPaymentModal.test.tsx +0 -134
- package/src/infrastructure/ui/views/checkout/components/checkoutPaymentModal/CheckoutPaymentModal.tsx +0 -124
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import React, { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { CommandStatus } from "@lookiero/messaging-react";
|
|
3
|
+
import { PaymentFlow, PaymentFlowRef, PaymentPayload, Section } from "@lookiero/payments-front";
|
|
4
|
+
import { LegacyBoxCheckoutStrategyPayload } from "@lookiero/payments-front/build/components/PaymentFlow/internals/strategies/LegacyBoxCheckoutStrategy";
|
|
5
|
+
import { useLogger } from "@lookiero/sty-psp-logging";
|
|
6
|
+
import { NotificationLevel, useCreateToastNotification } from "@lookiero/sty-psp-notifications";
|
|
7
|
+
import { CheckoutProjection } from "../../../projection/checkout/checkout";
|
|
8
|
+
import {
|
|
9
|
+
ViewCheckoutBookingById,
|
|
10
|
+
viewCheckoutBookingById,
|
|
11
|
+
ViewCheckoutBookingByIdResult,
|
|
12
|
+
} from "../../../projection/checkoutBooking/viewCheckoutBookingById";
|
|
13
|
+
import { OrderProjection } from "../../../projection/order/order";
|
|
14
|
+
import { SubscriptionProjection } from "../../../projection/subscription/subscription";
|
|
15
|
+
import { MESSAGING_CONTEXT_ID } from "../../delivery/baseBootstrap";
|
|
16
|
+
import { useSubmitCheckout } from "../../domain/checkout/react/useSubmitCheckout";
|
|
17
|
+
import { useBlockCheckoutBooking } from "../../domain/checkoutBooking/react/useBlockCheckoutBooking";
|
|
18
|
+
import { useViewIsSizeChangeEnabledByCheckoutId } from "../../projection/checkout/react/useViewIsSizeChangeEnabledByCheckoutId";
|
|
19
|
+
import { useViewPaymentFlowPayloadByCheckoutId } from "../../projection/payment/react/useViewPaymentFlowPayloadByCheckoutId";
|
|
20
|
+
import { useViewPricingByCheckoutId } from "../../projection/pricing/react/useViewPricingByCheckoutId";
|
|
21
|
+
import { useTrackCheckout } from "../../tracking/useTrackCheckout";
|
|
22
|
+
import { I18nMessages } from "../i18n/i18n";
|
|
23
|
+
import { Routes } from "../routing/routes";
|
|
24
|
+
import { usePaymentInstrumentEvents } from "./usePaymentInstrumentEvents";
|
|
25
|
+
import { useQueryBus } from "./useQueryBus";
|
|
26
|
+
import { useStaticInfo } from "./useStaticInfo";
|
|
27
|
+
|
|
28
|
+
type CheckoutFlowStatus = "idle" | "loading" | "success" | "error";
|
|
29
|
+
|
|
30
|
+
interface CheckoutFlowFunction {
|
|
31
|
+
(): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type CheckoutFlowReturn = [
|
|
35
|
+
checkoutFlow: CheckoutFlowFunction,
|
|
36
|
+
checkoutFlowStatus: CheckoutFlowStatus,
|
|
37
|
+
paymentFlow: ReactNode,
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
interface UseCheckoutFlowArgs {
|
|
41
|
+
readonly checkout: CheckoutProjection | undefined;
|
|
42
|
+
readonly order: OrderProjection;
|
|
43
|
+
readonly subscription: SubscriptionProjection;
|
|
44
|
+
readonly getAuthToken: () => Promise<string>;
|
|
45
|
+
readonly onSuccess: () => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface UseCheckoutFlowFunction {
|
|
49
|
+
(args: UseCheckoutFlowArgs): CheckoutFlowReturn;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const useCheckoutFlow: UseCheckoutFlowFunction = ({
|
|
53
|
+
checkout: checkoutProjection,
|
|
54
|
+
order: orderProjection,
|
|
55
|
+
subscription: subscriptionProjection,
|
|
56
|
+
getAuthToken,
|
|
57
|
+
onSuccess,
|
|
58
|
+
}) => {
|
|
59
|
+
const logger = useLogger();
|
|
60
|
+
const queryBus = useQueryBus();
|
|
61
|
+
const {
|
|
62
|
+
customer: { customerId, country, segment, name, email },
|
|
63
|
+
basePath,
|
|
64
|
+
} = useStaticInfo();
|
|
65
|
+
const paymentFlowRef = useRef<PaymentFlowRef>(null);
|
|
66
|
+
const [paymentFlowPayload] = useViewPaymentFlowPayloadByCheckoutId({
|
|
67
|
+
checkoutId: checkoutProjection?.id as string,
|
|
68
|
+
});
|
|
69
|
+
const [sizeChangeEnabled] = useViewIsSizeChangeEnabledByCheckoutId({ checkoutId: checkoutProjection?.id as string });
|
|
70
|
+
const [pricing] = useViewPricingByCheckoutId({
|
|
71
|
+
checkoutId: checkoutProjection?.id,
|
|
72
|
+
queryOptions: { refetchOnMount: true },
|
|
73
|
+
});
|
|
74
|
+
const [submitCheckout, submitCheckoutStatus] = useSubmitCheckout({
|
|
75
|
+
checkoutId: checkoutProjection?.id,
|
|
76
|
+
logger,
|
|
77
|
+
});
|
|
78
|
+
const [blockCheckoutBooking, blockCheckoutBookingStatus] = useBlockCheckoutBooking({
|
|
79
|
+
checkoutBookingId: checkoutProjection?.checkoutBookingId,
|
|
80
|
+
logger,
|
|
81
|
+
});
|
|
82
|
+
const [createNotification] = useCreateToastNotification({ contextId: MESSAGING_CONTEXT_ID, logger });
|
|
83
|
+
const [checkoutBookingExpired, setCheckoutBookingExpired] = useState(false);
|
|
84
|
+
const [startLegacyBoxCheckoutStatus, setStartLegacyBoxCheckoutStatus] = useState<CheckoutFlowStatus>("idle");
|
|
85
|
+
const [authToken, setAuthToken] = useState<string>();
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
const loadAuthToken = async () => setAuthToken(await getAuthToken());
|
|
88
|
+
loadAuthToken();
|
|
89
|
+
}, [getAuthToken]);
|
|
90
|
+
const trackCheckout = useTrackCheckout({
|
|
91
|
+
checkout: checkoutProjection,
|
|
92
|
+
order: orderProjection,
|
|
93
|
+
pricing,
|
|
94
|
+
subscription: subscriptionProjection,
|
|
95
|
+
userId: customerId,
|
|
96
|
+
country,
|
|
97
|
+
segment,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const checkoutFlow: CheckoutFlowFunction = useCallback(async () => {
|
|
101
|
+
try {
|
|
102
|
+
sizeChangeEnabled && (await blockCheckoutBooking());
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (checkoutProjection?.checkoutBookingId) {
|
|
108
|
+
const checkoutBooking = await queryBus<ViewCheckoutBookingById, ViewCheckoutBookingByIdResult>(
|
|
109
|
+
viewCheckoutBookingById({ checkoutBookingId: checkoutProjection?.checkoutBookingId }),
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
if (checkoutBooking?.isExpired) {
|
|
113
|
+
setCheckoutBookingExpired(true);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
setStartLegacyBoxCheckoutStatus("loading");
|
|
119
|
+
|
|
120
|
+
paymentFlowRef.current?.startLegacyBoxCheckout({
|
|
121
|
+
...paymentFlowPayload,
|
|
122
|
+
userInformation: { email, name },
|
|
123
|
+
returnUrl: `${basePath}/${Routes.CHECKOUT}`,
|
|
124
|
+
} as unknown as LegacyBoxCheckoutStrategyPayload);
|
|
125
|
+
}, [
|
|
126
|
+
checkoutProjection?.checkoutBookingId,
|
|
127
|
+
paymentFlowPayload,
|
|
128
|
+
email,
|
|
129
|
+
name,
|
|
130
|
+
basePath,
|
|
131
|
+
sizeChangeEnabled,
|
|
132
|
+
blockCheckoutBooking,
|
|
133
|
+
queryBus,
|
|
134
|
+
]);
|
|
135
|
+
|
|
136
|
+
const onPaymentSuccess = useCallback(async () => {
|
|
137
|
+
setStartLegacyBoxCheckoutStatus("success");
|
|
138
|
+
|
|
139
|
+
await submitCheckout();
|
|
140
|
+
|
|
141
|
+
createNotification({
|
|
142
|
+
bodyI18nKey: I18nMessages.CHECKOUT_TOAST_PAYMENT_SUCCESS,
|
|
143
|
+
level: NotificationLevel.SUCCESS,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
trackCheckout();
|
|
147
|
+
onSuccess();
|
|
148
|
+
}, [createNotification, onSuccess, submitCheckout, trackCheckout]);
|
|
149
|
+
const onPaymentError = useCallback(
|
|
150
|
+
(payload: PaymentPayload) => {
|
|
151
|
+
setStartLegacyBoxCheckoutStatus("error");
|
|
152
|
+
|
|
153
|
+
createNotification({
|
|
154
|
+
bodyI18nKey: payload.metadata?.toaster?.id || I18nMessages.CHECKOUT_TOAST_PAYMENT_ERROR,
|
|
155
|
+
level: NotificationLevel.ERROR,
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
[createNotification],
|
|
159
|
+
);
|
|
160
|
+
usePaymentInstrumentEvents({ onSuccess: onPaymentSuccess, onError: onPaymentError });
|
|
161
|
+
|
|
162
|
+
const checkoutFlowStatus: CheckoutFlowStatus = useMemo(() => {
|
|
163
|
+
if (
|
|
164
|
+
blockCheckoutBookingStatus === CommandStatus.LOADING ||
|
|
165
|
+
startLegacyBoxCheckoutStatus === "loading" ||
|
|
166
|
+
submitCheckoutStatus === CommandStatus.LOADING
|
|
167
|
+
) {
|
|
168
|
+
return "loading";
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (
|
|
172
|
+
blockCheckoutBookingStatus === CommandStatus.SUCCESS &&
|
|
173
|
+
startLegacyBoxCheckoutStatus === "success" &&
|
|
174
|
+
submitCheckoutStatus === CommandStatus.SUCCESS
|
|
175
|
+
) {
|
|
176
|
+
return "success";
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (
|
|
180
|
+
blockCheckoutBookingStatus === CommandStatus.ERROR ||
|
|
181
|
+
startLegacyBoxCheckoutStatus === "error" ||
|
|
182
|
+
submitCheckoutStatus === CommandStatus.ERROR ||
|
|
183
|
+
checkoutBookingExpired
|
|
184
|
+
) {
|
|
185
|
+
return "error";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return "idle";
|
|
189
|
+
}, [blockCheckoutBookingStatus, startLegacyBoxCheckoutStatus, submitCheckoutStatus, checkoutBookingExpired]);
|
|
190
|
+
|
|
191
|
+
const paymentFlow = useMemo(
|
|
192
|
+
() => (authToken ? <PaymentFlow ref={paymentFlowRef} section={Section.BOX_CHECKOUT} token={authToken} /> : null),
|
|
193
|
+
[authToken],
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
return useMemo(
|
|
197
|
+
() => [checkoutFlow, checkoutFlowStatus, paymentFlow],
|
|
198
|
+
[checkoutFlow, paymentFlow, checkoutFlowStatus],
|
|
199
|
+
);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export type { CheckoutFlowStatus };
|
|
203
|
+
export { useCheckoutFlow };
|
|
@@ -1,75 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { Logger } from "@lookiero/sty-psp-logging";
|
|
4
|
-
import { NotificationLevel, useCreateToastNotification } from "@lookiero/sty-psp-notifications";
|
|
5
|
-
import { MESSAGING_CONTEXT_ID } from "../../delivery/baseBootstrap";
|
|
6
|
-
import { I18nMessages } from "../i18n/i18n";
|
|
7
|
-
|
|
8
|
-
const PAYMENT_ERROR = "ERROR";
|
|
9
|
-
const PAYMENT_SUCCESS = "PAYMENT_INSTRUMENT_UPDATED";
|
|
10
|
-
|
|
11
|
-
interface Message {
|
|
12
|
-
readonly id: string;
|
|
13
|
-
}
|
|
14
|
-
interface OnSuccessFunctionArgs {
|
|
15
|
-
readonly message: Message;
|
|
16
|
-
}
|
|
17
|
-
interface OnSuccessFunction {
|
|
18
|
-
(args: OnSuccessFunctionArgs): void;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface Toaster {
|
|
22
|
-
readonly id: string;
|
|
23
|
-
}
|
|
24
|
-
interface Error {
|
|
25
|
-
readonly toaster?: Toaster;
|
|
26
|
-
}
|
|
27
|
-
interface OnErrorFunctionArgs {
|
|
28
|
-
readonly error: Error;
|
|
29
|
-
}
|
|
30
|
-
interface OnErrorFunction {
|
|
31
|
-
(args: OnErrorFunctionArgs): void;
|
|
32
|
-
}
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { PaymentPayload, Section, usePaymentStatusManager } from "@lookiero/payments-front";
|
|
33
3
|
|
|
34
4
|
interface UsePaymentInstrumentEventsFunctionArgs {
|
|
35
|
-
readonly
|
|
5
|
+
readonly onSuccess: (payload: PaymentPayload) => void;
|
|
6
|
+
readonly onError: (payload: PaymentPayload) => void;
|
|
36
7
|
}
|
|
37
8
|
|
|
38
9
|
interface UsePaymentInstrumentEventsFunction {
|
|
39
10
|
(args: UsePaymentInstrumentEventsFunctionArgs): void;
|
|
40
11
|
}
|
|
41
12
|
|
|
42
|
-
const usePaymentInstrumentEvents: UsePaymentInstrumentEventsFunction = ({
|
|
43
|
-
const
|
|
13
|
+
const usePaymentInstrumentEvents: UsePaymentInstrumentEventsFunction = ({ onSuccess, onError }) => {
|
|
14
|
+
const refreshStatus = usePaymentStatusManager(Section.BOX_CHECKOUT);
|
|
44
15
|
|
|
45
|
-
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
const { isLoading, consumePayload } = refreshStatus;
|
|
46
18
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
);
|
|
19
|
+
if (isLoading) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
51
22
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
level: NotificationLevel.ERROR,
|
|
58
|
-
});
|
|
23
|
+
consumePayload((payload) => {
|
|
24
|
+
if (payload.success) {
|
|
25
|
+
onSuccess(payload);
|
|
26
|
+
} else {
|
|
27
|
+
onError(payload);
|
|
59
28
|
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
useEffect(() => {
|
|
65
|
-
subscribe({ event: PAYMENT_ERROR }, onError);
|
|
66
|
-
subscribe({ event: PAYMENT_SUCCESS }, onSuccess);
|
|
67
|
-
|
|
68
|
-
return () => {
|
|
69
|
-
unsubscribe({ event: PAYMENT_ERROR }, onError);
|
|
70
|
-
unsubscribe({ event: PAYMENT_SUCCESS }, onSuccess);
|
|
71
|
-
};
|
|
72
|
-
}, [subscribe, unsubscribe, createNotification, onError, onSuccess]);
|
|
29
|
+
});
|
|
30
|
+
}, [onError, onSuccess, refreshStatus]);
|
|
73
31
|
};
|
|
74
32
|
|
|
75
33
|
export { usePaymentInstrumentEvents };
|
|
@@ -50,6 +50,7 @@ enum I18nMessages {
|
|
|
50
50
|
CHECKOUT_TITLE = "checkout.title",
|
|
51
51
|
CHECKOUT_PAY_BUTTON = "checkout.pay_button",
|
|
52
52
|
CHECKOUT_TOAST_PAYMENT_ERROR = "checkout.toast_payment_error",
|
|
53
|
+
CHECKOUT_TOAST_PAYMENT_SUCCESS = "checkout.toast_payment_success",
|
|
53
54
|
CHECKOUT_SUCCESS_MODAL_TITLE = "checkout.success_modal_title",
|
|
54
55
|
CHECKOUT_SUCCESS_MODAL_DESCRIPTION = "checkout.success_modal_description",
|
|
55
56
|
CHECKOUT_SUCCESS_MODAL_BUTTON = "checkout.success_modal_button",
|
|
@@ -158,17 +158,6 @@ describe("CheckoutMiddleware component", () => {
|
|
|
158
158
|
expect(onNotAccessible).toHaveBeenCalled();
|
|
159
159
|
});
|
|
160
160
|
|
|
161
|
-
it("payment route should not be directly accessible", async () => {
|
|
162
|
-
const checkoutMock = checkout({ items: [{ status: CheckoutItemStatus.INITIAL }] });
|
|
163
|
-
(useViewFirstAvailableCheckoutByCustomerId as jest.Mock).mockReturnValue([checkoutMock, QueryStatus.SUCCESS]);
|
|
164
|
-
(useStartCheckout as jest.Mock).mockReturnValue([mockStartCheckout, CommandStatus.SUCCESS]);
|
|
165
|
-
|
|
166
|
-
const onNotAccessible = jest.fn();
|
|
167
|
-
renderCheckoutMiddleware({ path: `${Routes.CHECKOUT}/${Routes.CHECKOUT_PAYMENT}`, onNotAccessible });
|
|
168
|
-
|
|
169
|
-
expect(onNotAccessible).toHaveBeenCalled();
|
|
170
|
-
});
|
|
171
|
-
|
|
172
161
|
it("should redirect to 'feedback' if checkout's status is SUBMITTED", async () => {
|
|
173
162
|
const checkoutMock = checkout({
|
|
174
163
|
status: CheckoutStatus.SUBMITTED,
|
|
@@ -48,12 +48,6 @@ const CheckoutMiddleware: FC<CheckoutMiddlewareProps> = ({
|
|
|
48
48
|
const feedbackRouteMatch = useMatch(`${basePath}/${Routes.FEEDBACK}`);
|
|
49
49
|
const feedbackRouteMatchRef = useRef(feedbackRouteMatch);
|
|
50
50
|
feedbackRouteMatchRef.current = feedbackRouteMatch;
|
|
51
|
-
const checkoutPaymentRouteMatch = useMatch(`${basePath}/${Routes.CHECKOUT}/${Routes.CHECKOUT_PAYMENT}`);
|
|
52
|
-
const checkoutPaymentRouteMatchRef = useRef(checkoutPaymentRouteMatch);
|
|
53
|
-
checkoutPaymentRouteMatchRef.current = checkoutPaymentRouteMatch;
|
|
54
|
-
|
|
55
|
-
const checkoutShown = useRef(false);
|
|
56
|
-
checkoutShown.current = checkoutShown.current || (Boolean(checkoutRouteMatch) && !Boolean(checkoutPaymentRouteMatch));
|
|
57
51
|
|
|
58
52
|
const [checkout] = useViewFirstAvailableCheckoutByCustomerId({ customerId });
|
|
59
53
|
const checkoutItemsRef = useRef<CheckoutItemProjection[]>();
|
|
@@ -99,8 +93,7 @@ const CheckoutMiddleware: FC<CheckoutMiddlewareProps> = ({
|
|
|
99
93
|
summaryRouteMatchRef.current ||
|
|
100
94
|
summaryTabsRouteMatchRef.current ||
|
|
101
95
|
itemDetailRouteMatchRef.current ||
|
|
102
|
-
checkoutRouteMatchRef.current
|
|
103
|
-
checkoutPaymentRouteMatchRef.current
|
|
96
|
+
checkoutRouteMatchRef.current
|
|
104
97
|
)
|
|
105
98
|
) {
|
|
106
99
|
navigateRef.current(`${basePath}/${Routes.SUMMARY}`, { replace: true });
|
|
@@ -127,13 +120,6 @@ const CheckoutMiddleware: FC<CheckoutMiddlewareProps> = ({
|
|
|
127
120
|
return null;
|
|
128
121
|
}
|
|
129
122
|
|
|
130
|
-
/* Prevent direct payment access */
|
|
131
|
-
if (checkoutPaymentRouteMatch && !checkoutShown.current) {
|
|
132
|
-
onNotAccessible();
|
|
133
|
-
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
123
|
return children;
|
|
138
124
|
};
|
|
139
125
|
|
|
@@ -7,13 +7,12 @@ import { Locale } from "@lookiero/sty-psp-locale";
|
|
|
7
7
|
import { Layout } from "@lookiero/sty-psp-ui";
|
|
8
8
|
import { Tradename } from "@lookiero/sty-sp-tradename";
|
|
9
9
|
import { Customer } from "../../../projection/customer/customer";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { OrderProjection } from "../../../projection/order/order";
|
|
11
|
+
import { SubscriptionProjection } from "../../../projection/subscription/subscription";
|
|
12
12
|
import { KameleoonEnvironment } from "../../ab-testing/kameleoonEnvironment";
|
|
13
13
|
import { StaticInfoProvider } from "../hooks/useStaticInfo";
|
|
14
14
|
import { App } from "../views/App";
|
|
15
15
|
import { Checkout } from "../views/checkout/Checkout";
|
|
16
|
-
import { CheckoutPaymentModal } from "../views/checkout/components/checkoutPaymentModal/CheckoutPaymentModal";
|
|
17
16
|
import { Feedback } from "../views/feedback/Feedback";
|
|
18
17
|
import { Item } from "../views/item/Item";
|
|
19
18
|
import { Return } from "../views/return/Return";
|
|
@@ -25,8 +24,8 @@ import { Routes } from "./routes";
|
|
|
25
24
|
interface RoutingProps {
|
|
26
25
|
readonly basePath?: string;
|
|
27
26
|
readonly customer: Customer;
|
|
28
|
-
readonly order:
|
|
29
|
-
readonly subscription:
|
|
27
|
+
readonly order: OrderProjection;
|
|
28
|
+
readonly subscription: SubscriptionProjection;
|
|
30
29
|
readonly locale: Locale;
|
|
31
30
|
readonly I18n: I18n;
|
|
32
31
|
readonly kameleoon: KameleoonEnvironment;
|
|
@@ -34,7 +33,7 @@ interface RoutingProps {
|
|
|
34
33
|
readonly tradename: Tradename;
|
|
35
34
|
readonly getAuthToken: () => Promise<string>;
|
|
36
35
|
readonly onNotAccessible: () => void;
|
|
37
|
-
readonly
|
|
36
|
+
readonly onCheckoutFlowSuccess: () => void;
|
|
38
37
|
readonly onI18nError?: (err: Error) => void;
|
|
39
38
|
readonly useRedirect: () => Record<string, string>;
|
|
40
39
|
readonly useRoutes: typeof reactRouterUseRoutes;
|
|
@@ -53,7 +52,7 @@ const Routing: FC<RoutingProps> = ({
|
|
|
53
52
|
getAuthToken,
|
|
54
53
|
onI18nError,
|
|
55
54
|
onNotAccessible,
|
|
56
|
-
|
|
55
|
+
onCheckoutFlowSuccess,
|
|
57
56
|
useRedirect,
|
|
58
57
|
useRoutes = reactRouterUseRoutes,
|
|
59
58
|
}) => {
|
|
@@ -114,26 +113,16 @@ const Routing: FC<RoutingProps> = ({
|
|
|
114
113
|
path: Routes.CHECKOUT,
|
|
115
114
|
element: (
|
|
116
115
|
<Suspense fallback={<Spinner />}>
|
|
117
|
-
<Checkout
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
<Checkout
|
|
117
|
+
getAuthToken={getAuthToken}
|
|
118
|
+
layout={layout}
|
|
119
|
+
order={order}
|
|
120
|
+
subscription={subscription}
|
|
121
|
+
useRedirect={useRedirect}
|
|
122
|
+
onCheckoutFlowSuccess={onCheckoutFlowSuccess}
|
|
123
|
+
/>
|
|
120
124
|
</Suspense>
|
|
121
125
|
),
|
|
122
|
-
children: [
|
|
123
|
-
{
|
|
124
|
-
path: Routes.CHECKOUT_PAYMENT,
|
|
125
|
-
element: (
|
|
126
|
-
<CheckoutPaymentModal
|
|
127
|
-
coupon={order?.coupon || null}
|
|
128
|
-
getAuthToken={getAuthToken}
|
|
129
|
-
isFirstOrder={order?.isFirstOrder as boolean}
|
|
130
|
-
orderNumber={order?.orderNumber as number}
|
|
131
|
-
subscription={subscription as Subscription}
|
|
132
|
-
onCheckoutSubmitted={onCheckoutSubmitted}
|
|
133
|
-
/>
|
|
134
|
-
),
|
|
135
|
-
},
|
|
136
|
-
],
|
|
137
126
|
},
|
|
138
127
|
{
|
|
139
128
|
path: Routes.FEEDBACK,
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PortalHost } from "@gorhom/portal";
|
|
2
2
|
import React, { FC } from "react";
|
|
3
3
|
import { StatusBar } from "react-native";
|
|
4
4
|
import { SafeAreaProvider } from "react-native-safe-area-context";
|
|
5
|
-
import { PortalProvider as AuroraPortalProvider } from "@lookiero/aurora";
|
|
6
5
|
import { Notifications } from "@lookiero/sty-psp-notifications";
|
|
7
6
|
import { theme } from "@lookiero/sty-psp-ui";
|
|
8
7
|
import { MESSAGING_CONTEXT_ID } from "../../delivery/baseBootstrap";
|
|
@@ -16,17 +15,10 @@ interface AppProps {
|
|
|
16
15
|
|
|
17
16
|
const App: FC<AppProps> = ({ children }) => (
|
|
18
17
|
<SafeAreaProvider>
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
We are using the Aurora's PortalProvider at this level for notifications to work properly.
|
|
24
|
-
|
|
25
|
-
PaymentInstrumentSelect uses Aurora's Portal, and if we rely on UAF's Portal (injected by <Aurora>)
|
|
26
|
-
notifications would be displayed in a layer below Portal's one (not visible).
|
|
27
|
-
*/}
|
|
28
|
-
<AuroraPortalProvider>{children}</AuroraPortalProvider>
|
|
29
|
-
</PortalProvider>
|
|
18
|
+
<StatusBar backgroundColor={colorBgBase} barStyle="dark-content" translucent />
|
|
19
|
+
<Notifications contextId={MESSAGING_CONTEXT_ID} domain={DOMAIN} portalHostName="Checkout" />
|
|
20
|
+
<PortalHost name="Checkout" />
|
|
21
|
+
{children}
|
|
30
22
|
</SafeAreaProvider>
|
|
31
23
|
);
|
|
32
24
|
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { fireEvent } from "@testing-library/react-native";
|
|
2
|
-
import React from "react";
|
|
2
|
+
import React, { ReactNode } from "react";
|
|
3
|
+
import { View } from "react-native";
|
|
3
4
|
import { QueryStatus } from "@lookiero/messaging-react";
|
|
4
5
|
import { Country } from "@lookiero/sty-psp-locale";
|
|
5
6
|
import { Segment } from "@lookiero/sty-psp-segment";
|
|
6
7
|
import { DummyLayout } from "@lookiero/sty-psp-ui";
|
|
7
8
|
import { CheckoutItemStatus } from "../../../../domain/checkoutItem/model/checkoutItem";
|
|
9
|
+
import { OrderProjection } from "../../../../projection/order/order";
|
|
10
|
+
import { SubscriptionProjection } from "../../../../projection/subscription/subscription";
|
|
8
11
|
import { checkout } from "../../../projection/checkout/checkout.mock";
|
|
9
12
|
import { useViewFirstAvailableCheckoutByCustomerId } from "../../../projection/checkout/react/useViewFirstAvailableCheckoutByCustomerId";
|
|
10
13
|
import { pricing } from "../../../projection/pricing/pricing.mock";
|
|
@@ -14,7 +17,9 @@ import { Routes } from "../../routing/routes";
|
|
|
14
17
|
import { render } from "../../test/render";
|
|
15
18
|
import { Checkout } from "./Checkout";
|
|
16
19
|
|
|
20
|
+
const getAuthToken = () => Promise.resolve("token");
|
|
17
21
|
const customerId = "a8fff6d7-708c-41a7-b42a-58c5706d33df";
|
|
22
|
+
const basePath = "/checkout";
|
|
18
23
|
const country = Country.ES;
|
|
19
24
|
const segment = Segment.WOMEN;
|
|
20
25
|
const mockCheckout = checkout({
|
|
@@ -26,49 +31,29 @@ const mockCheckout = checkout({
|
|
|
26
31
|
{ status: CheckoutItemStatus.REPLACED },
|
|
27
32
|
],
|
|
28
33
|
});
|
|
34
|
+
const order: OrderProjection = {
|
|
35
|
+
orderNumber: 12345,
|
|
36
|
+
isFirstOrder: false,
|
|
37
|
+
coupon: null,
|
|
38
|
+
};
|
|
39
|
+
const subscription: SubscriptionProjection = "o";
|
|
29
40
|
const mockUseRedirect = jest.fn(() => ({ returnUrl: "https://web2.dev.aws.lookiero.es/user/" }));
|
|
30
41
|
|
|
31
|
-
jest.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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],
|
|
36
48
|
}));
|
|
37
49
|
|
|
50
|
+
jest.mock("../../hooks/useStaticInfo", () => ({
|
|
51
|
+
useStaticInfo: () => ({ customer: { customerId, country, segment }, basePath }),
|
|
52
|
+
}));
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
54
|
+
jest.mock("./components/paymentInstrument/PaymentInstrument", () => ({ PaymentInstrument: () => null }));
|
|
38
55
|
jest.mock("../../../projection/checkout/react/useViewFirstAvailableCheckoutByCustomerId");
|
|
39
56
|
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
|
-
});
|
|
72
57
|
|
|
73
58
|
const mockTrackPressContinue = jest.fn();
|
|
74
59
|
jest.mock("../../../tracking/useTrackPressContinue", () => ({
|
|
@@ -87,13 +72,25 @@ jest.mock("react-router-native", () => ({
|
|
|
87
72
|
useNavigate: () => mockUseNavigate,
|
|
88
73
|
}));
|
|
89
74
|
|
|
75
|
+
beforeEach(() => {
|
|
76
|
+
mockCheckoutFlow.mockClear();
|
|
77
|
+
mockTrackPressContinue.mockClear();
|
|
78
|
+
mockTrackPressBack.mockClear();
|
|
79
|
+
});
|
|
80
|
+
|
|
90
81
|
describe("Checkout view", () => {
|
|
91
82
|
it("renders correctly", async () => {
|
|
92
83
|
(useViewFirstAvailableCheckoutByCustomerId as jest.Mock).mockReturnValue([mockCheckout, QueryStatus.SUCCESS]);
|
|
93
84
|
(useViewPricingByCheckoutId as jest.Mock).mockReturnValue([pricing(), QueryStatus.SUCCESS]);
|
|
94
|
-
|
|
95
85
|
const { findByText, getAllByTestId, getByText, getByTestId } = render(
|
|
96
|
-
<Checkout
|
|
86
|
+
<Checkout
|
|
87
|
+
getAuthToken={getAuthToken}
|
|
88
|
+
layout={DummyLayout}
|
|
89
|
+
order={order}
|
|
90
|
+
subscription={subscription}
|
|
91
|
+
useRedirect={mockUseRedirect}
|
|
92
|
+
onCheckoutFlowSuccess={mockOnSuccess}
|
|
93
|
+
/>,
|
|
97
94
|
);
|
|
98
95
|
|
|
99
96
|
expect(await findByText(I18nMessages.CHECKOUT_TITLE)).toBeTruthy();
|
|
@@ -114,15 +111,17 @@ describe("Checkout view", () => {
|
|
|
114
111
|
expect(getByText(I18nMessages.SUMMARY_FEE)).toBeTruthy();
|
|
115
112
|
expect(getByText("-€10.00")).toBeTruthy();
|
|
116
113
|
|
|
114
|
+
expect(getByTestId(paymentFlowTestId)).toBeTruthy();
|
|
115
|
+
|
|
117
116
|
expect(getByText(I18nMessages.CHECKOUT_PAY_BUTTON)).toBeTruthy();
|
|
118
117
|
fireEvent.press(getByText(I18nMessages.CHECKOUT_PAY_BUTTON));
|
|
119
118
|
expect(mockTrackPressContinue).toHaveBeenCalled();
|
|
120
|
-
expect(
|
|
119
|
+
expect(mockCheckoutFlow).toHaveBeenCalled();
|
|
121
120
|
|
|
122
121
|
expect(getByTestId("checkout-header")).toBeTruthy();
|
|
123
122
|
fireEvent.press(getByTestId("arrow-left-button-icon"));
|
|
124
123
|
expect(mockTrackPressBack).toHaveBeenCalled();
|
|
125
|
-
expect(mockUseNavigate).toHaveBeenCalledWith(
|
|
124
|
+
expect(mockUseNavigate).toHaveBeenCalledWith(`${basePath}/${Routes.SUMMARY}`);
|
|
126
125
|
});
|
|
127
126
|
|
|
128
127
|
it("does not render a delivery banner", async () => {
|
|
@@ -138,7 +137,16 @@ describe("Checkout view", () => {
|
|
|
138
137
|
(useViewFirstAvailableCheckoutByCustomerId as jest.Mock).mockReturnValue([mockCheckout, QueryStatus.SUCCESS]);
|
|
139
138
|
(useViewPricingByCheckoutId as jest.Mock).mockReturnValue([pricing(), QueryStatus.SUCCESS]);
|
|
140
139
|
|
|
141
|
-
const { findByText, queryByText } = render(
|
|
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
|
+
);
|
|
142
150
|
|
|
143
151
|
expect(await findByText(I18nMessages.CHECKOUT_TITLE)).toBeTruthy();
|
|
144
152
|
|