diff --git a/ts/__mocks__/paymentPayloads.ts b/ts/__mocks__/paymentPayloads.ts index d1122ca6950..842081c0e72 100644 --- a/ts/__mocks__/paymentPayloads.ts +++ b/ts/__mocks__/paymentPayloads.ts @@ -3,10 +3,10 @@ import { IWithinRangeStringTag } from "@pagopa/ts-commons/lib/strings"; -import { paymentVerifica } from "../store/actions/wallet/payment"; import { ImportoEuroCents } from "../../definitions/backend/ImportoEuroCents"; import { myRptId } from "../utils/testFaker"; import { Amount, Transaction } from "../types/pagopa"; +import { paymentVerifica } from "../store/actions/legacyWallet"; export const messageId = "abcde-12345"; diff --git a/ts/boot/__tests__/__snapshots__/persistedStore.test.ts.snap b/ts/boot/__tests__/__snapshots__/persistedStore.test.ts.snap index 5208d691e46..5cde17f323b 100644 --- a/ts/boot/__tests__/__snapshots__/persistedStore.test.ts.snap +++ b/ts/boot/__tests__/__snapshots__/persistedStore.test.ts.snap @@ -90,17 +90,6 @@ exports[`Check the addition for new fields to the persisted store. If one of thi } `; -exports[`Check the addition for new fields to the persisted store. If one of this test fails, check that exists the migration before updating the snapshot! Freeze 'payments' state 1`] = ` -{ - "creditCardInsertion": [], - "current": { - "kind": "UNSTARTED", - }, - "history": [], - "lastDeleted": null, -} -`; - exports[`Check the addition for new fields to the persisted store. If one of this test fails, check that exists the migration before updating the snapshot! Freeze 'persistedPreferences' state 1`] = ` { "continueWithRootOrJailbreak": false, @@ -124,9 +113,3 @@ exports[`Check the addition for new fields to the persisted store. If one of thi "kind": "PotNone", } `; - -exports[`Check the addition for new fields to the persisted store. If one of this test fails, check that exists the migration before updating the snapshot! Freeze 'wallet.wallets.walletById' state 1`] = ` -{ - "kind": "PotNone", -} -`; diff --git a/ts/boot/__tests__/persistedStore.test.ts b/ts/boot/__tests__/persistedStore.test.ts index 0aa4833763c..6588da2ee12 100644 --- a/ts/boot/__tests__/persistedStore.test.ts +++ b/ts/boot/__tests__/persistedStore.test.ts @@ -35,9 +35,6 @@ describe("Check the addition for new fields to the persisted store. If one of th it("Freeze 'installation' state", () => { expect(globalState.installation).toMatchSnapshot(); }); - it("Freeze 'payments' state", () => { - expect(globalState.payments).toMatchSnapshot(); - }); it("Freeze 'content' state", () => { expect(globalState.content).toMatchSnapshot(); }); @@ -54,9 +51,6 @@ describe("Check the addition for new fields to the persisted store. If one of th it("Freeze 'identification' state", () => { expect(globalState.identification).toMatchSnapshot(); }); - it("Freeze 'wallet.wallets.walletById' state", () => { - expect(globalState.wallet.wallets.walletById).toMatchSnapshot(); - }); it("Freeze 'installation.appVersionHistory' state", () => { expect(globalState.installation.appVersionHistory).toMatchSnapshot(); diff --git a/ts/boot/configureStoreAndPersistor.ts b/ts/boot/configureStoreAndPersistor.ts index 6f36402e35f..d8366084293 100644 --- a/ts/boot/configureStoreAndPersistor.ts +++ b/ts/boot/configureStoreAndPersistor.ts @@ -47,7 +47,6 @@ import { } from "../features/pushNotifications/store/reducers"; import { getInitialState as getInstallationInitialState } from "../features/pushNotifications/store/reducers/installation"; import { GlobalState, PersistedGlobalState } from "../store/reducers/types"; -import { walletsPersistConfig } from "../store/reducers/wallet"; import { DateISO8601Transform } from "../store/transforms/dateISO8601Tranform"; import { PotTransform } from "../store/transforms/potTransform"; import { isDevEnv } from "../utils/environment"; @@ -491,7 +490,6 @@ const persistedReducer: Reducer = persistReducer< createRootReducer([ rootPersistConfig, authenticationPersistConfig, - walletsPersistConfig, entitiesPersistConfig ]) ); diff --git a/ts/components/__tests__/ContextualInfo.test.tsx b/ts/components/__tests__/ContextualInfo.test.tsx deleted file mode 100644 index b19efe4f57c..00000000000 --- a/ts/components/__tests__/ContextualInfo.test.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { fireEvent, RenderAPI } from "@testing-library/react-native"; -import React from "react"; -import { Text } from "react-native"; -import { createStore } from "redux"; -import { IOIcons } from "@pagopa/io-app-design-system"; -import ROUTES from "../../navigation/routes"; - -import { applicationChangeState } from "../../store/actions/application"; -import { appReducer } from "../../store/reducers"; -import { GlobalState } from "../../store/reducers/types"; -import { renderScreenWithNavigationStoreContext } from "../../utils/testWrapper"; -import ContextualInfo from "../ContextualInfo"; - -jest.useFakeTimers(); - -/** - * Finds the first button with iconName as a child. - * Behaves as getAll* - * - * TODO: this helper is a stub for further development around an a11y-oriented - * library that can be shared across projects. - */ -export function buttonByIconName(iconName: IOIcons, renderAPI: RenderAPI) { - return renderAPI.getAllByRole("button").find( - button => - !!button.children.find(child => { - if (typeof child !== "string") { - return child.props.name === iconName; - } - return false; - }) - ); -} - -const options = { - title: "a title", - body: jest.fn(), - onClose: jest.fn() -}; - -describe("ContextualInfo component", () => { - it("should render a close button", () => { - const component = renderComponent(options); - expect( - component.queryByTestId("contextualInfo_closeButton") - ).not.toBeNull(); - }); - - describe("when the close button is pressed", () => { - it("should call `onClose`", () => { - const onClose = jest.fn(); - const component = renderComponent({ ...options, onClose }); - const closeButton = component.queryByTestId( - "contextualInfo_closeButton" - )!; - fireEvent(closeButton, "onPress"); - expect(onClose).toHaveBeenCalledTimes(1); - }); - }); - - it("should render the body", () => { - const body = jest.fn(() => {"a very small body"}); - const component = renderComponent({ ...options, body }); - expect(body).toHaveBeenCalledTimes(1); - expect(component.getByText("a very small body")).toBeDefined(); - }); - - it("should render the title", () => { - const component = renderComponent(options); - expect(component.getByText(options.title)).toBeDefined(); - }); -}); - -function renderComponent(props: React.ComponentProps) { - const globalState = appReducer(undefined, applicationChangeState("active")); - return renderScreenWithNavigationStoreContext( - () => , - ROUTES.WALLET_CHECKOUT_3DS_SCREEN, - {}, - createStore(appReducer, globalState as any) - ); -} diff --git a/ts/components/__tests__/PspComponent.test.tsx b/ts/components/__tests__/PspComponent.test.tsx deleted file mode 100644 index 7dead7e8fb2..00000000000 --- a/ts/components/__tests__/PspComponent.test.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { fireEvent, render } from "@testing-library/react-native"; -import * as O from "fp-ts/lib/Option"; -import React from "react"; -import { PspData } from "../../../definitions/pagopa/PspData"; -import * as hooks from "../../features/wallet/onboarding/bancomat/hooks/useImageResize"; -import { getPspIconUrlFromAbi } from "../../utils/paymentMethod"; -import { PspComponent } from "../wallet/payment/PspComponent"; - -const psp: PspData = { - id: 1, - codiceAbi: "0001", - defaultPsp: true, - fee: 100, - idPsp: "1", - onboard: true, - privacyUrl: "https://io.italia.it", - ragioneSociale: "PayTipper" -}; - -describe("Test PspComponent", () => { - it("should call onPress if psp button is pressed", () => { - const onPress = jest.fn(); - const component = renderComponent(onPress); - - fireEvent.press(component.getByTestId(`psp-${psp.idPsp}`)); - expect(onPress).toHaveBeenCalled(); - }); - it("should show the logoPSP if there is one and useImageResize return some value", () => { - jest.spyOn(hooks, "useImageResize").mockReturnValue(O.some([15, 15])); - const onPress = jest.fn(); - const component = renderComponent(onPress); - const logoPSP = component.queryByTestId("logoPSP"); - - expect(logoPSP).not.toBeNull(); - expect(logoPSP).toHaveProp("source", { - uri: getPspIconUrlFromAbi(psp.codiceAbi) - }); - }); - it("should show the businessName if there isn't logoPSP", () => { - jest.spyOn(hooks, "useImageResize").mockReturnValue(O.none); - const onPress = jest.fn(); - const component = renderComponent(onPress); - const businessName = component.queryByTestId("businessName"); - - expect(businessName).not.toBeNull(); - }); -}); - -const renderComponent = (onPress: () => void) => - render(); diff --git a/ts/components/wallet/FavoriteMethodSwitch.tsx b/ts/components/wallet/FavoriteMethodSwitch.tsx deleted file mode 100644 index b807ef22d2e..00000000000 --- a/ts/components/wallet/FavoriteMethodSwitch.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as React from "react"; -import { View, ActivityIndicator } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { NativeSwitch } from "@pagopa/io-app-design-system"; -import I18n from "../../i18n"; -import { setFavouriteWalletRequest } from "../../store/actions/wallet/wallets"; -import { GlobalState } from "../../store/reducers/types"; -import { - getFavoriteWalletId, - updatingFavouriteWalletSelector -} from "../../store/reducers/wallet/wallets"; -import { PaymentMethod } from "../../types/pagopa"; -import { isAndroid } from "../../utils/platform"; -import { handleSetFavourite } from "../../utils/wallet"; -import { IOStyleVariables } from "../core/variables/IOStyleVariables"; -import { PreferencesListItem } from "../PreferencesListItem"; - -type OwnProps = { - onValueChange?: (value: boolean) => void; - paymentMethod: PaymentMethod; -}; - -type Props = ReturnType & - ReturnType & - OwnProps; - -/** - * This component represents the payment "favorite" status and allows the user to change it with a switch - * @param props - * @constructor - */ -const FavoritePaymentMethodSwitch = (props: Props) => { - // check if we are setting this specific wallet - const isTheSameWallet: boolean = pot.getOrElseWithUpdating( - pot.map( - props.updatingFavouriteWallet, - _ => _ === props.paymentMethod.idWallet - ), - false - ); - const isLoading = - (pot.isUpdating(props.updatingFavouriteWallet) || - pot.isLoading(props.updatingFavouriteWallet)) && - isTheSameWallet; - const isFavorite = pot.map( - props.favoriteWalletId, - _ => _ === props.paymentMethod.idWallet - ); - - const rightElement = isLoading ? ( - - - - ) : ( - - handleSetFavourite(v, () => - props.setFavoriteWallet(props.paymentMethod.idWallet) - ) - } - value={pot.getOrElse(isFavorite, false)} - /> - ); - return ( - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - setFavoriteWallet: (walletId?: number) => - dispatch(setFavouriteWalletRequest(walletId)) -}); - -const mapStateToProps = (state: GlobalState) => ({ - updatingFavouriteWallet: updatingFavouriteWalletSelector(state), - favoriteWalletId: getFavoriteWalletId(state) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(FavoritePaymentMethodSwitch); diff --git a/ts/components/wallet/InternationalCircuitIconsBar.tsx b/ts/components/wallet/InternationalCircuitIconsBar.tsx deleted file mode 100644 index f1be66f901c..00000000000 --- a/ts/components/wallet/InternationalCircuitIconsBar.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { HSpacer } from "@pagopa/io-app-design-system"; -import * as React from "react"; -import { Image, StyleSheet, View } from "react-native"; -import maestro from "../../../img/wallet/cards-icons/maestro.png"; -import mastercard from "../../../img/wallet/cards-icons/mastercard.png"; -import vpay from "../../../img/wallet/cards-icons/vPay.png"; -import visaElectron from "../../../img/wallet/cards-icons/visa-electron.png"; -import visa from "../../../img/wallet/cards-icons/visa.png"; - -const styles = StyleSheet.create({ - brandLogo: { - width: 40, - height: 25, - resizeMode: "contain" - }, - row: { - flexDirection: "row" - } -}); - -const InternationalCircuitIconsBar: React.FunctionComponent = () => ( - - - - - - - - - - - -); - -export default InternationalCircuitIconsBar; diff --git a/ts/components/wallet/OutcomeCodeMessageComponent.tsx b/ts/components/wallet/OutcomeCodeMessageComponent.tsx deleted file mode 100644 index 891c9677bfd..00000000000 --- a/ts/components/wallet/OutcomeCodeMessageComponent.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { FooterWithButtons } from "@pagopa/io-app-design-system"; -import React from "react"; -import { ImageSourcePropType, SafeAreaView, View } from "react-native"; -import I18n from "../../i18n"; -import { OutcomeCode } from "../../types/outcomeCode"; -import { emptyContextualHelp } from "../../utils/emptyContextualHelp"; -import { getFullLocale } from "../../utils/locale"; -import { IOStyles } from "../core/variables/IOStyles"; -import { InfoScreenComponent } from "../infoScreen/InfoScreenComponent"; -import { renderInfoRasterImage } from "../infoScreen/imageRendering"; -import BaseScreenComponent from "../screens/BaseScreenComponent"; - -type OwnProp = { - hideContextualHelp?: true; - outcomeCode: OutcomeCode; - successComponent: React.FC; - onClose: () => void; - successFooter?: React.FC; - onLearnMore?: () => void; -}; -type Props = OwnProp; - -const blockingFooterWithButton = ( - onClose: () => void, - onLearnMore: (() => void) | undefined -) => - onLearnMore ? ( - - ) : ( - - ); - -/** - * This component renders the message associated to the outcome code of a payment. - * This component renders an image, a title and a description or a success component. - * - * Since there are 3 possible case for the outcomeCode.status and the footer buttons are - * related to every case the choice of the footer is based on the outcomeCode.status props. - * - * @param props - */ -const OutcomeCodeMessageComponent: React.FC = (props: Props) => { - const locale = getFullLocale(); - const title = props.outcomeCode.title - ? props.outcomeCode.title[locale] - : undefined; - const description = props.outcomeCode.description - ? props.outcomeCode.description[locale] - : undefined; - return ( - } - contextualHelp={ - props.hideContextualHelp === true ? undefined : emptyContextualHelp - } - > - {props.outcomeCode.status === "success" ? ( - - - {props.successFooter && } - - ) : ( - <> - - {/* Since the description can be undefined only the title is used for the conditional rendering condition */} - {title && ( - - )} - - {props.outcomeCode.status === "errorBlocking" && - blockingFooterWithButton(props.onClose, props.onLearnMore)} - - )} - - ); -}; - -export default OutcomeCodeMessageComponent; diff --git a/ts/components/wallet/PayWebViewModal.tsx b/ts/components/wallet/PayWebViewModal.tsx deleted file mode 100644 index 749eb349c1e..00000000000 --- a/ts/components/wallet/PayWebViewModal.tsx +++ /dev/null @@ -1,222 +0,0 @@ -import * as O from "fp-ts/lib/Option"; -import _ from "lodash"; -import React from "react"; -import { Modal, StyleSheet, View } from "react-native"; -import WebView from "react-native-webview"; -import { WebViewNavigation } from "react-native-webview/lib/WebViewTypes"; -import URLParse from "url-parse"; -import { v4 as uuid } from "uuid"; - -import { InfoBox } from "../../components/box/InfoBox"; -import { Label } from "../../components/core/typography/Label"; -import { useHardwareBackButton } from "../../hooks/useHardwareBackButton"; -import I18n from "../../i18n"; -import { WithTestID } from "../../types/WithTestID"; -import { emptyContextualHelp } from "../../utils/emptyContextualHelp"; -import { isTestEnv } from "../../utils/environment"; -import BaseScreenComponent from "../screens/BaseScreenComponent"; -import { RefreshIndicator } from "../ui/RefreshIndicator"; - -type OwnProps = { - // the uri to send the form data thought POST - postUri: string; - // data to include into the form to submit - formData: Record; - /** - * the path name that means the end of the process - * ex: the process ends when this url will be load https://yourdomain.com/finish/path/name - * finishPathName should be "/finish/path/name" - */ - finishPathName: string; - /** - * the name of the query param used to include the outcome code - * ex: https://yourdomain.com/finish/path/name?myoutcome=123 - * outcomeQueryparamName should be "myoutcome" - */ - outcomeQueryparamName: string; - // outcomeCode could be the outcome code detected during navigation - // navigations urls is the list of urls browsed during navigation - onFinish: ( - outcomeCode: O.Option, - navigationUrls: ReadonlyArray - ) => void; - onGoBack: () => void; - modalHeaderTitle?: string; - // if undefined -> true as default - showInfoHeader?: boolean; - // if undefined -> true as default - isVisible?: boolean; -}; - -type Props = WithTestID; - -const styles = StyleSheet.create({ - descriptionContainer: { paddingHorizontal: 20, paddingVertical: 14 }, - refreshIndicatorContainer: { - position: "absolute", - left: 0, - right: 0, - top: 0, - bottom: 0, - justifyContent: "center", - alignItems: "center", - zIndex: 1000 - } -}); - -// the ID used as form ID and to identify it to ensure the autosubmit -const formId = `io-form-id-${uuid()}`; -// the javascript submit command -const injectedJSPostForm: string = ``; - -const kvToString = (kv: [string, unknown]) => - ``; - -// create an html form giving a form data and an uri -const crateAutoPostForm = ( - form: Record, - uri: string -): string => - ``; - -/** - * return a 2-tuple - * 0 element: boolean. true if the given {@param urlParse} contains the given {@param finishPathName} path name - * 1 element: string | undefined. a string contains the value of {@param outcomeQueryparamName} in query string of {@param url} - * @param urlParse - * @param finishPathName - * @param outcomeQueryparamName - */ -const getFinishAndOutcome = ( - urlParse: URLParse, - finishPathName: string, - outcomeQueryparamName: string -): [isFinish: boolean, outComeCode: string | undefined] => { - // find the object entry name in case insensitive - const maybeEntry = _.toPairs(urlParse.query).find( - kv => kv[0].toLowerCase() === outcomeQueryparamName.toLowerCase() - ); - const outcome = maybeEntry ? maybeEntry[1] : undefined; - // found exit path - if (urlParse.pathname.toLowerCase().includes(finishPathName.toLowerCase())) { - return [true, outcome]; - } - return [false, outcome]; -}; - -// a loading component rendered during the webview loading times -const renderLoading = () => ( - - - -); - -/** - * A modal including a webview component. - * It must be used to handle a payment. - * A payment could happen - * - on credit card on-boarding (2cents fake payment as pre-authorization) - * - on a regular payment - * https://pagopa.atlassian.net/wiki/spaces/IOAPP/pages/404457605/Pagamento - * - * this is how this component works: - * - at the start up, it creates a temporary html hidden form with all pre-set data (props.formFata) - * - it appends, at the end of that html, a JS script to auto-submit the created form to the given url (props.postUri) - * - it sets that html as starting page - * - on each page load request it checks the url - * - if it is the exit url - * - if it contains the outcome code - * - when the exit url is found, it doesn't load it and call the handler props.onFinish passing the found (maybe not) outcome value - */ -export const PayWebViewModal = (props: Props) => { - const [outcomeCode, setOutcomeCode] = React.useState( - undefined - ); - const { showInfoHeader = true } = props; - const navigationUrlsRef = React.useRef>([]); - useHardwareBackButton(() => { - props.onGoBack(); - return true; - }); - - const handleOnShouldStartLoadWithRequest = (navState: WebViewNavigation) => { - if (navState.url) { - const urlParse = new URLParse(navState.url, true); - // it happens when navState.url is "about:blank" - // i.e when we load local html - if (urlParse.origin !== "null") { - // eslint-disable-next-line functional/immutable-data - navigationUrlsRef.current.push(urlParse.origin); - } - - const [isFinish, maybeOutcome] = getFinishAndOutcome( - urlParse, - props.finishPathName, - props.outcomeQueryparamName - ); - const outcome = maybeOutcome ?? outcomeCode; - if (outcome !== outcomeCode) { - setOutcomeCode(outcome); - } - // found exit path - if (isFinish) { - props.onFinish(O.fromNullable(outcome), navigationUrlsRef.current); - return false; - } - } - return true; - }; - - return ( - - - {showInfoHeader && ( - - - - - - )} - - - - - ); -}; - -// keep encapsulation strong -export const testableGetFinishAndOutcome = isTestEnv - ? getFinishAndOutcome - : undefined; diff --git a/ts/components/wallet/PaymentBannerComponent.tsx b/ts/components/wallet/PaymentBannerComponent.tsx deleted file mode 100644 index 2d28a92e942..00000000000 --- a/ts/components/wallet/PaymentBannerComponent.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { View, StyleSheet } from "react-native"; -import { IOColors } from "@pagopa/io-app-design-system"; -import { ImportoEuroCents } from "../../../definitions/backend/ImportoEuroCents"; -import I18n from "../../i18n"; -import { formatNumberCentsToAmount } from "../../utils/stringBuilder"; -import { Body } from "../core/typography/Body"; -import { Label } from "../core/typography/Label"; -import { IOStyles } from "../core/variables/IOStyles"; - -type Props = Readonly<{ - paymentReason: string; - currentAmount: ImportoEuroCents; - fee?: ImportoEuroCents; -}>; - -const styles = StyleSheet.create({ - container: { - paddingTop: 12, - paddingBottom: 16, - backgroundColor: IOColors.bluegrey - } -}); - -/** - * This component displays a summary on the transaction. - * Used for the screens from the identification of the transaction to the end of the procedure. - * Fee is shown only when a method screen is selected - */ -const PaymentBannerComponent = (props: Props) => { - const totalAmount = pipe( - props.fee, - O.fromNullable, - O.fold( - () => props.currentAmount, - fee => (props.currentAmount as number) + (fee as number) - ) - ); - return ( - - - - - - - - - {I18n.t("wallet.ConfirmPayment.fee")} - - {formatNumberCentsToAmount(props.fee ?? 0, true)} - - - - - - - - ); -}; - -export default PaymentBannerComponent; diff --git a/ts/components/wallet/PaymentHistoryItem.tsx b/ts/components/wallet/PaymentHistoryItem.tsx deleted file mode 100644 index 698a1715430..00000000000 --- a/ts/components/wallet/PaymentHistoryItem.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import * as React from "react"; -import { View, StyleSheet } from "react-native"; -import { Icon, HSpacer, VSpacer } from "@pagopa/io-app-design-system"; -import I18n from "../../i18n"; -import customVariables from "../../theme/variables"; -import { Body } from "../core/typography/Body"; -import { IOStyles } from "../core/variables/IOStyles"; -import { BadgeComponent } from "../screens/BadgeComponent"; -import TouchableDefaultOpacity from "../TouchableDefaultOpacity"; - -type Props = Readonly<{ - text11: string; - text2: string; - text3: string; - color: string; - onPressItem: () => void; -}>; - -const styles = StyleSheet.create({ - verticalPad: { - paddingVertical: customVariables.spacerHeight - }, - spaced: { - justifyContent: "flex-start", - flexDirection: "row", - alignItems: "center" - }, - icon: { - width: 64, - alignItems: "flex-end", - justifyContent: "center" - }, - text3Line: { - flex: 1, - flexDirection: "row" - }, - text3Container: { - minHeight: 24 - } -}); - -export default class PaymentHistoryItem extends React.PureComponent< - React.PropsWithChildren -> { - public render() { - return ( - - - - - {this.props.text11} - - - - {this.props.text2} - - - - - - {`${I18n.t("payment.IUV")} ${this.props.text3}`} - - - - - - - {this.props.children} - - ); - } -} diff --git a/ts/components/wallet/PaymentMethodsList.tsx b/ts/components/wallet/PaymentMethodsList.tsx deleted file mode 100644 index 5e30ae1e75b..00000000000 --- a/ts/components/wallet/PaymentMethodsList.tsx +++ /dev/null @@ -1,232 +0,0 @@ -/** - * This component will display the payment methods that can be registered - * on the app - */ -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { FC } from "react"; -import { - View, - Alert, - FlatList, - ListRenderItemInfo, - StyleSheet, - Pressable -} from "react-native"; -import { SvgProps } from "react-native-svg"; -import { connect } from "react-redux"; -import { Icon, HSpacer, VSpacer, Divider } from "@pagopa/io-app-design-system"; -import { BackendStatus } from "../../../definitions/content/BackendStatus"; -import { LevelEnum } from "../../../definitions/content/SectionStatus"; -import I18n from "../../i18n"; -import { - backendStatusSelector, - SectionStatusKey -} from "../../store/reducers/backendStatus"; -import { GlobalState } from "../../store/reducers/types"; -import { getFullLocale } from "../../utils/locale"; -import { IOBadge, IOBadgeOutlineColors } from "../core/IOBadge"; -import { H3 } from "../core/typography/H3"; -import { H5 } from "../core/typography/H5"; -import { IOStyles } from "../core/variables/IOStyles"; -import { withLightModalContext } from "../helpers/withLightModalContext"; -import { LightModalContextInterface } from "../ui/LightModal"; - -type OwnProps = Readonly<{ - paymentMethods: ReadonlyArray; -}>; - -type Props = OwnProps & - LightModalContextInterface & - ReturnType; - -export type IPaymentMethod = Readonly<{ - name: string; - description: string; - icon: FC; - maxFee?: string; - status: "implemented" | "incoming" | "notImplemented"; - section?: SectionStatusKey; - onPress?: () => void; -}>; - -const styles = StyleSheet.create({ - container: { - paddingRight: 10, - paddingLeft: 0 - }, - flexColumn: { - flexDirection: "column", - justifyContent: "space-between", - flex: 1 - }, - descriptionPadding: { paddingRight: 24 } -}); - -export const showPaymentMethodIncomingAlert = () => - Alert.alert( - I18n.t("wallet.incoming.title"), - I18n.t("wallet.incoming.message"), - undefined, - { cancelable: true } - ); - -/** - * return a status badge displaying the section.badge label - * the badge background color is according with the status (normal | warning | critical) - * if it is critical status, it returns also an alert function, undefined otherwise - * the alert display a title (section.badge) and a message (section.message) with default dismiss button - * @param paymentMethod - * @param backendStatus - */ -const getBadgeStatus = ( - paymentMethod: IPaymentMethod, - backendStatus: O.Option -): null | { badge: React.ReactNode; alert?: () => void } => { - const itemSection = paymentMethod.section; - - const badgeColorMap: Record = { - [LevelEnum.normal]: "blue", - [LevelEnum.warning]: "orange", - [LevelEnum.critical]: "red" - }; - - // no section - if (itemSection === undefined) { - return null; - } - return pipe( - backendStatus, - O.chainNullableK(bs => bs.sections), - O.fold( - () => null, - sections => { - const section = sections[itemSection]; - // no badge if section is not visible or badge is not defined - if ( - section === undefined || - section.is_visible === false || - section.badge === undefined - ) { - return null; - } - const locale = getFullLocale(); - const badgeLabel = section.badge[locale]; - return { - badge: ( - - ), - alert: - section.level === LevelEnum.critical - ? () => Alert.alert(badgeLabel, section.message[locale]) - : undefined - }; - } - ) - ); -}; - -const renderListItem = ( - itemInfo: ListRenderItemInfo, - backendStatus: O.Option -) => { - switch (itemInfo.item.status) { - case "implemented": { - const badgeStatus = getBadgeStatus(itemInfo.item, backendStatus); - return ( - - - - {itemInfo.item.icon({ width: 20, height: 20 })} - - - - - {badgeStatus?.badge} -

- {itemInfo.item.name} -

-
-
-
- {itemInfo.item.description} -
-
- -
- -
- ); - } - case "incoming": - return ( - - - - - - -

- {itemInfo.item.name} -

-
-
- {itemInfo.item.description} -
-
-
- -
- ); - case "notImplemented": - return null; - } -}; - -const PaymentMethodsList: React.FunctionComponent = (props: Props) => ( - - - - item.name} - ItemSeparatorComponent={() => } - ListFooterComponent={} - renderItem={i => renderListItem(i, props.sectionStatus)} - /> - -); -const mapStateToProps = (state: GlobalState) => ({ - sectionStatus: backendStatusSelector(state) -}); -export default connect(mapStateToProps)( - withLightModalContext(PaymentMethodsList) -); diff --git a/ts/components/wallet/PaymentSummaryComponent.tsx b/ts/components/wallet/PaymentSummaryComponent.tsx deleted file mode 100644 index 9062b327a33..00000000000 --- a/ts/components/wallet/PaymentSummaryComponent.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { HSpacer, VSpacer } from "@pagopa/io-app-design-system"; -import * as React from "react"; -import { Image, ImageSourcePropType, StyleSheet, View } from "react-native"; -import I18n from "../../i18n"; -import { isStringNullyOrEmpty } from "../../utils/strings"; -import ItemSeparatorComponent from "../ItemSeparatorComponent"; -import { Body } from "../core/typography/Body"; -import { H3 } from "../core/typography/H3"; -import { IOStyles } from "../core/variables/IOStyles"; -import { BadgeComponent } from "../screens/BadgeComponent"; - -type Props = Readonly<{ - title: string; - recipient?: string; - description?: string; - codiceAvviso?: string; - error?: string; - dateTime?: string; - dark?: boolean; - image?: ImageSourcePropType; - paymentStatus?: paymentStatusType; -}>; - -export type paymentStatusType = { - color: string; - description: string; -}; - -const styles = StyleSheet.create({ - column: { - flexDirection: "column", - flex: 1, - paddingRight: 4 - }, - paymentOutcome: { - flexDirection: "row", - alignItems: "center" - } -}); - -/** - * This component displays the transaction details - */ -export const PaymentSummaryComponent = (props: Props) => { - const renderItem = (label: string, value?: string) => { - if (isStringNullyOrEmpty(value)) { - return null; - } - return ( - - - {label} - - - {value} - - - - ); - }; - - const paymentStatus = props.paymentStatus && ( - - - - - {props.paymentStatus.description} - - - - ); - - return ( - -

- {props.title} -

- - {/** screen title */} - - - - - {paymentStatus} - - - - {renderItem(I18n.t("wallet.recipient"), props.recipient)} - - {renderItem( - I18n.t("wallet.firstTransactionSummary.object"), - props.description - )} - - {renderItem(I18n.t("payment.noticeCode"), props.codiceAvviso)} - - - {props.image !== undefined && ( - - )} - -
- ); -}; diff --git a/ts/components/wallet/PaymentsHistoryList.tsx b/ts/components/wallet/PaymentsHistoryList.tsx deleted file mode 100644 index 0a078548324..00000000000 --- a/ts/components/wallet/PaymentsHistoryList.tsx +++ /dev/null @@ -1,154 +0,0 @@ -/** - * This component displays a list of payments - */ -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { - View, - FlatList, - ListRenderItemInfo, - StyleSheet, - ScrollView -} from "react-native"; - -import { pipe } from "fp-ts/lib/function"; -import { ContentWrapper, IOColors } from "@pagopa/io-app-design-system"; -import I18n from "../../i18n"; -import { - PaymentHistory, - PaymentsHistoryState -} from "../../store/reducers/payments/history"; -import { isPaymentDoneSuccessfully } from "../../store/reducers/payments/utils"; -import customVariables from "../../theme/variables"; -import { formatDateAsLocal } from "../../utils/dates"; -import { getIuv } from "../../utils/payment"; -import ItemSeparatorComponent from "../ItemSeparatorComponent"; -import { EdgeBorderComponent } from "../screens/EdgeBorderComponent"; - -import { Body } from "../core/typography/Body"; -import PaymentHistoryItem from "./PaymentHistoryItem"; - -type Props = Readonly<{ - title: string; - payments: PaymentsHistoryState; - navigateToPaymentHistoryDetail: (payment: PaymentHistory) => void; - ListEmptyComponent?: React.JSX.Element; -}>; - -const styles = StyleSheet.create({ - whiteContent: { - backgroundColor: IOColors.white, - flex: 1 - }, - subHeaderContent: { - flexDirection: "row", - alignItems: "baseline", - justifyContent: "space-between" - } -}); - -const notAvailable = I18n.t("global.remoteStates.notAvailable"); -export const getPaymentHistoryInfo = ( - paymentHistory: PaymentHistory, - paymentCheckout: O.Option -) => - pipe( - paymentCheckout, - O.fold( - () => ({ - text11: I18n.t("payment.details.state.incomplete"), - text3: getIuv(paymentHistory.data), - color: IOColors["warning-500"] - }), - success => { - if (success) { - return { - text11: I18n.t("payment.details.state.successful"), - text3: pipe( - paymentHistory.verifiedData, - O.fromNullable, - O.fold( - () => notAvailable, - vd => - pipe( - vd.causaleVersamento, - O.fromNullable, - O.fold( - () => notAvailable, - cv => cv - ) - ) - ) - ), - color: customVariables.colorHighlight - }; - } - - return { - text11: I18n.t("payment.details.state.failed"), - text3: getIuv(paymentHistory.data), - color: customVariables.brandDanger - }; - } - ) - ); -/** - * Payments List component - */ - -export default class PaymentHistoryList extends React.Component { - private renderHistoryPaymentItem = ( - info: ListRenderItemInfo - ) => { - const paymentCheckout = isPaymentDoneSuccessfully(info.item); - const paymentInfo = getPaymentHistoryInfo(info.item, paymentCheckout); - - const datetime: string = `${formatDateAsLocal( - new Date(info.item.started_at), - true, - true - )} - ${new Date(info.item.started_at).toLocaleTimeString()}`; - return ( - { - this.props.navigateToPaymentHistoryDetail(info.item); - }} - /> - ); - }; - - public render(): React.ReactNode { - const { ListEmptyComponent, payments } = this.props; - - return payments.length === 0 && ListEmptyComponent ? ( - ListEmptyComponent - ) : ( - - - - - {I18n.t("payment.details.list.title")} - - - - ( - - )} - ListFooterComponent={ - payments.length > 0 ? : null - } - keyExtractor={(_, index) => index.toString()} - /> - - - ); - } -} diff --git a/ts/components/wallet/SelectionBox.tsx b/ts/components/wallet/SelectionBox.tsx deleted file mode 100644 index 17258bf5d59..00000000000 --- a/ts/components/wallet/SelectionBox.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React from "react"; -import { View, StyleSheet } from "react-native"; -import { IOColors } from "@pagopa/io-app-design-system"; -import { H4 } from "../core/typography/H4"; -import { LabelSmall } from "../core/typography/LabelSmall"; -import TouchableDefaultOpacity from "../TouchableDefaultOpacity"; - -type Props = { - logo?: React.ReactNode; - mainText: string; - subText: string; - ctaText?: string; - onPress?: () => void; - accessibilityLabel?: string; -}; - -const styles = StyleSheet.create({ - selectionBox: { - borderWidth: 1, - borderColor: IOColors.bluegreyLight, - borderRadius: 8, - padding: 16, - flexDirection: "row", - alignItems: "center" - }, - - selectionBoxIcon: { - flexGrow: 0, - flexShrink: 0, - flexBasis: "auto", - paddingRight: 16 - }, - - selectionBoxContent: { - flexGrow: 1, - flexShrink: 1, - flexBasis: "100%" - }, - - selectionBoxTrail: { - flexGrow: 0, - flexShrink: 0, - flexBasis: "auto", - paddingLeft: 16 - } -}); - -/** - * A bordered and rounded box made up by three columns: - * - The first one containing an icon - * - The second one with a title and a subtitle - * - The third one containing a CTA - */ -export const SelectionBox = (props: Props) => ( - - - {props.logo && {props.logo}} - - -

{props.mainText}

- - {props.subText} - -
- - {props.ctaText && ( - -

- {props.ctaText} -

-
- )} -
-
-); diff --git a/ts/components/wallet/SlidedContentComponent.tsx b/ts/components/wallet/SlidedContentComponent.tsx deleted file mode 100644 index b84e3d19aa3..00000000000 --- a/ts/components/wallet/SlidedContentComponent.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import * as React from "react"; -import { ScrollView, StyleSheet, View } from "react-native"; -import { IOColors, IOStyles } from "@pagopa/io-app-design-system"; -import { SafeAreaView } from "react-native-safe-area-context"; - -type Props = Readonly<{ - dark?: boolean; - hasFlatBottom?: boolean; -}>; - -const styles = StyleSheet.create({ - container: { - backgroundColor: IOColors.bluegrey - }, - content: { - borderRadius: 8, - marginHorizontal: 8, - marginBottom: 16, - padding: 16 - }, - contentBottomFlat: { - borderTopLeftRadius: 8, - borderTopRightRadius: 8, - marginHorizontal: 8, - padding: 16 - }, - dark: { - backgroundColor: IOColors.bluegrey - }, - white: { - backgroundColor: IOColors.white - }, - flexGrow: { - flexGrow: 1 - } -}); - -/** - * A component to render the Screen Content as a slide on top of the Container background - * Props: - * - dark: the backgound is a dark gray - * - hasFlatBottom: the bottom is anchored to the container bottom - */ -export const SlidedContentComponent = ( - props: React.PropsWithChildren -) => ( - - - - {props.children} - - - -); diff --git a/ts/components/wallet/TransactionsList.tsx b/ts/components/wallet/TransactionsList.tsx deleted file mode 100644 index 94ee4dec836..00000000000 --- a/ts/components/wallet/TransactionsList.tsx +++ /dev/null @@ -1,181 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as React from "react"; -import { FlatList, ListRenderItemInfo, StyleSheet, View } from "react-native"; -import { ScrollView } from "react-native-gesture-handler"; -import { - ButtonOutline, - IOColors, - IOVisualCostants, - ListItemTransaction, - VSpacer -} from "@pagopa/io-app-design-system"; -import { formatNumberCurrencyCents } from "../../features/idpay/common/utils/strings"; -import I18n from "../../i18n"; -import variables from "../../theme/variables"; -import { Transaction } from "../../types/pagopa"; -import { format } from "../../utils/dates"; -import ItemSeparatorComponent from "../ItemSeparatorComponent"; -import { Body } from "../core/typography/Body"; -import { H3 } from "../core/typography/H3"; -import { EdgeBorderComponent } from "../screens/EdgeBorderComponent"; -import BoxedRefreshIndicator from "../ui/BoxedRefreshIndicator"; -import { getAccessibleAmountText } from "../../utils/accessibility"; - -type Props = Readonly<{ - title: string; - transactions: pot.Pot, Error>; - areMoreTransactionsAvailable: boolean; - onLoadMoreTransactions: () => void; - navigateToTransactionDetails: (transaction: Transaction) => void; - helpMessage?: React.ReactNode; - ListEmptyComponent?: React.ReactElement; -}>; - -export const TransactionsList = (props: Props) => { - const [isLoadingMore, setIsLoadingMore] = React.useState(false); - - const { - ListEmptyComponent, - areMoreTransactionsAvailable, - onLoadMoreTransactions, - helpMessage - } = props; - - React.useEffect(() => { - if ( - isLoadingMore && - (pot.isSome(props.transactions) || pot.isError(props.transactions)) - ) { - setIsLoadingMore(false); - } - }, [props.transactions, isLoadingMore]); - - const transactions: ReadonlyArray = pot.getOrElse( - props.transactions, - [] - ); - - // ------------------ loading guard ------------------ - if (!isLoadingMore && pot.isLoading(props.transactions)) { - return ( - {I18n.t("wallet.transactionsLoadMessage")}} - /> - ); - } - - // ------------------ components + utils ------------------ - const shouldShowFooterComponent = ( - ListEmptyComponent?: React.ReactElement - ): ListEmptyComponent is React.ReactElement => - transactions.length === 0 && - !areMoreTransactionsAvailable && - ListEmptyComponent !== undefined; - - const footerListComponent = ( - transactions: ReadonlyArray - ): React.ComponentProps["ListFooterComponent"] => { - if (!areMoreTransactionsAvailable) { - return transactions.length > 0 ? : null; - } - - return ( - <> - { - setIsLoadingMore(true); - onLoadMoreTransactions(); - }} - disabled={isLoadingMore} - /> - - - ); - }; - - const renderTransaction = (info: ListRenderItemInfo) => { - const item = info.item; - const recipient = item.merchant; - - const amountText = formatNumberCurrencyCents(item.amount.amount); - const datetime: string = format(item.created, "DD MMM YYYY, HH:mm"); - - const accessibleDatetime: string = format( - item.created, - "DD MMMM YYYY, HH:mm" - ); - const accessibleAmountText = getAccessibleAmountText(amountText); - const accessibilityLabel = `${recipient}; ${accessibleDatetime}; ${accessibleAmountText}`; - - return ( - props.navigateToTransactionDetails(item)} - transactionStatus="success" - transactionAmount={amountText} - accessible={true} - accessibilityLabel={accessibilityLabel} - /> - ); - }; - - // ------------------ render ------------------ - /** - * 1 - if more transaction are available to load, show the load more button - * 2 - if all transactions are loaded, show end list component - */ - return shouldShowFooterComponent(ListEmptyComponent) ? ( - ListEmptyComponent - ) : ( - - - -

- {I18n.t("wallet.latestTransactions")} -

-
-
- {helpMessage} - } - keyExtractor={item => item.id.toString()} - ListFooterComponent={footerListComponent(transactions)} - /> -
- ); -}; - -const styles = StyleSheet.create({ - scrollView: { - paddingTop: variables.contentPadding, - backgroundColor: IOColors.white, - flex: 1 - }, - subHeaderContent: { - flexDirection: "row", - alignItems: "baseline", - justifyContent: "space-between", - paddingHorizontal: variables.contentPadding - } -}); diff --git a/ts/components/wallet/WalletHomeHeader.tsx b/ts/components/wallet/WalletHomeHeader.tsx deleted file mode 100644 index d5d71b8ca8d..00000000000 --- a/ts/components/wallet/WalletHomeHeader.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { - Divider, - HSpacer, - Icon, - ListItemNav, - VSpacer -} from "@pagopa/io-app-design-system"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { FlatList, View } from "react-native"; -import { navigateToAvailableBonusListScreen } from "../../features/bonus/common/navigation/actions"; -import I18n from "../../i18n"; -import NavigationService from "../../navigation/NavigationService"; -import { navigateToWalletAddPaymentMethod } from "../../store/actions/navigation"; -import { - IOBottomSheetModal, - useIOBottomSheetAutoresizableModal -} from "../../utils/hooks/bottomSheet"; -import TouchableDefaultOpacity from "../TouchableDefaultOpacity"; -import { H1 } from "../core/typography/H1"; -import { H4 } from "../core/typography/H4"; - -type NavigationListItem = { - title: string; - subtitle: string; - testId?: string; - onPress: () => void; -}; - -export const useWalletHomeHeaderBottomSheet = (): IOBottomSheetModal => { - const navigationListItems: ReadonlyArray = [ - { - title: I18n.t("wallet.paymentMethod"), - testId: "wallet.paymentMethod", - subtitle: I18n.t("wallet.paymentMethodDesc"), - onPress: () => - navigateToWalletAddPaymentMethod({ - inPayment: O.none, - keyFrom: NavigationService.getCurrentRouteKey() - }) - }, - { - title: I18n.t("wallet.methods.bonus.name"), - subtitle: I18n.t("wallet.methods.bonus.description"), - testId: "bonusNameTestId", - onPress: navigateToAvailableBonusListScreen - } - ]; - - const { present, bottomSheet, dismiss } = useIOBottomSheetAutoresizableModal({ - title: I18n.t("global.buttons.add"), - component: ( - item.title} - renderItem={({ item }) => ( - { - dismiss(); - item.onPress(); - }} - value={item.title} - accessibilityLabel={item.title} - description={item.subtitle} - testID={item.testId} - /> - )} - ItemSeparatorComponent={() => } - ListFooterComponent={() => } - /> - ) - }); - return { present, bottomSheet, dismiss }; -}; - -const WalletHomeHeader = () => { - const { present, bottomSheet } = useWalletHomeHeaderBottomSheet(); - - return ( - -

- {I18n.t("wallet.wallet")} -

- - - -

- {I18n.t("wallet.newPaymentMethod.add").toUpperCase()} -

-
- {bottomSheet} -
- ); -}; - -export default WalletHomeHeader; diff --git a/ts/components/wallet/WalletLayout.tsx b/ts/components/wallet/WalletLayout.tsx deleted file mode 100644 index 64bc93c0762..00000000000 --- a/ts/components/wallet/WalletLayout.tsx +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Layout for the wallet section of the app. - * This is comprised by a customizable header part - * (with optionally a card displayed on the bottom - * of this header), and a customized content on - * the bottom part of the screen. Both are - * wrapped in a ScrollView, and optionally a - * footer with a button for starting a new payment - */ - -import * as React from "react"; -import { - Animated, - Dimensions, - ScrollView, - StyleProp, - View, - ViewStyle -} from "react-native"; -import { IOColors } from "@pagopa/io-app-design-system"; -import I18n from "../../i18n"; -import { FAQsCategoriesType } from "../../utils/faq"; -import { - ContextualHelpProps, - ContextualHelpPropsMarkdown -} from "../screens/BaseScreenComponent"; -import DarkLayout from "../screens/DarkLayout"; - -type Props = Readonly<{ - accessibilityLabel?: string; - title: string; - allowGoBack: boolean; - topContentHeight?: number; - topContent?: React.ReactNode; - hideHeader?: boolean; - hideBaseHeader?: boolean; - footerContent?: React.ReactNode; - contentStyle?: StyleProp; - refreshControl?: Animated.ComponentProps["refreshControl"]; - contextualHelp?: ContextualHelpProps; - contextualHelpMarkdown?: ContextualHelpPropsMarkdown; - faqCategories?: ReadonlyArray; - appLogo?: boolean; - gradientHeader?: boolean; - headerPaddingMin?: boolean; - footerFullWidth?: React.ReactNode; - referenceToContentScreen?: React.RefObject; -}>; - -export default class WalletLayout extends React.Component< - React.PropsWithChildren -> { - public render(): React.ReactNode { - const { - title, - accessibilityLabel, - allowGoBack, - hideHeader, - hideBaseHeader = false, - footerContent, - contentStyle, - appLogo, - footerFullWidth - } = this.props; - - /* The dimensions of the screen that will be used - to hide the white background when inertial - scrolling is turned on. */ - const { height: screenHeight, width: screenWidth } = - Dimensions.get("screen"); - - return ( - - {/* Add a fake View with a dark background to hide - the white block when the inertial scroll is enabled - (that means the user is using negative scroll values) */} - - {/* End of the hacky solution */} - - {this.props.children} - - ); - } -} diff --git a/ts/components/wallet/__test__/InternationalCircuitIconsBar.test.tsx b/ts/components/wallet/__test__/InternationalCircuitIconsBar.test.tsx deleted file mode 100644 index 64faf17d35d..00000000000 --- a/ts/components/wallet/__test__/InternationalCircuitIconsBar.test.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { render } from "@testing-library/react-native"; -import * as React from "react"; -import InternationalCircuitIconsBar from "../InternationalCircuitIconsBar"; - -describe("InternationalCircuitIconBar component", () => { - ["maestro", "mastercard", "visa", "visaElectron", "vPay"].forEach(circuit => - it(`should show the ${circuit} icon`, () => { - const component = render(); - - expect(component.queryByTestId(circuit)).not.toBeNull(); - }) - ); -}); diff --git a/ts/components/wallet/__test__/OutcomeCodeMessageComponent.test.tsx b/ts/components/wallet/__test__/OutcomeCodeMessageComponent.test.tsx deleted file mode 100644 index d7ad960ff8d..00000000000 --- a/ts/components/wallet/__test__/OutcomeCodeMessageComponent.test.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import * as React from "react"; -import { View } from "react-native"; -import configureMockStore from "redux-mock-store"; -import { setLocale } from "../../../i18n"; -import ROUTES from "../../../navigation/routes"; -import { applicationChangeState } from "../../../store/actions/application"; -import { appReducer } from "../../../store/reducers"; -import { GlobalState } from "../../../store/reducers/types"; -import { OutcomeCode } from "../../../types/outcomeCode"; -import { renderScreenWithNavigationStoreContext } from "../../../utils/testWrapper"; -import OutcomeCodeMessageComponent from "../OutcomeCodeMessageComponent"; - -const ASuccessComponent = () => ; -const ASuccessFooter = () => ; -const onClose = jest.fn(); - -describe("OutcomeCodeMessageComponent", () => { - jest.useFakeTimers(); - it("should be not null", () => { - const outcomeCode = { status: "success" } as OutcomeCode; - const component = renderComponent(outcomeCode, ASuccessComponent, onClose); - - expect(component).not.toBeNull(); - }); - it("should render ASuccessComponent if outcomeCode status is equal to success and prop successComponent has been passed", () => { - const outcomeCode = { status: "success" } as OutcomeCode; - const component = renderComponent(outcomeCode, ASuccessComponent, onClose); - - const successComponent = component.queryByTestId("a-success-component"); - expect(component).not.toBeNull(); - expect(successComponent).not.toBeNull(); - }); - it("should render ASuccessFooter if outcomeCode status is equal to success and prop successFooter has been passed", () => { - const outcomeCode = { status: "success" } as OutcomeCode; - const component = renderComponent( - outcomeCode, - ASuccessComponent, - onClose, - ASuccessFooter - ); - - const successFooter = component.queryByTestId("a-success-footer"); - expect(component).not.toBeNull(); - expect(successFooter).not.toBeNull(); - }); - it("should render InfoScreenComponent if outcomeCode status is not equal to success, outcomeCode title and description are defined and showing the right text", () => { - const outcomeCode = { - status: "errorTryAgain", - title: { - "en-EN": "title en", - "it-IT": "title it" - }, - description: { - "en-EN": "description en", - "it-IT": "description it" - } - } as OutcomeCode; - setLocale("it"); - const component = renderComponent(outcomeCode, ASuccessComponent, onClose); - - expect(component).not.toBeNull(); - const titleComponent = component.queryByText("title it"); - expect(titleComponent).toBeTruthy(); - const descriptionComponent = component.queryByText("description it"); - expect(descriptionComponent).toBeTruthy(); - expect(component.queryByTestId("InfoScreenComponent")).not.toBeNull(); - }); - it("should render FooterWithButtons if outcomeCode status is equal to errorBlocking", () => { - const outcomeCode = { - status: "errorBlocking" - } as OutcomeCode; - const component = renderComponent(outcomeCode, ASuccessComponent, onClose); - - expect(component).not.toBeNull(); - expect(component.queryByTestId("FooterWithButtons")).not.toBeNull(); - }); -}); - -const renderComponent = ( - outcomeCode: OutcomeCode, - successComponent: React.FC, - onClose: () => void, - successFooter?: React.FC -) => { - const globalState = appReducer(undefined, applicationChangeState("active")); - - const mockStore = configureMockStore(); - const store: ReturnType = mockStore({ - ...globalState - } as GlobalState); - - return renderScreenWithNavigationStoreContext( - () => ( - - ), - ROUTES.ADD_CREDIT_CARD_OUTCOMECODE_MESSAGE, - {}, - store - ); -}; diff --git a/ts/components/wallet/__test__/PayWebViewModal.test.tsx b/ts/components/wallet/__test__/PayWebViewModal.test.tsx deleted file mode 100644 index 8811f2b4769..00000000000 --- a/ts/components/wallet/__test__/PayWebViewModal.test.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import React from "react"; -import URLParse from "url-parse"; -import { createStore } from "redux"; -import { - PayWebViewModal, - testableGetFinishAndOutcome -} from "../PayWebViewModal"; -import { applicationChangeState } from "../../../store/actions/application"; -import { GlobalState } from "../../../store/reducers/types"; -import { renderScreenWithNavigationStoreContext } from "../../../utils/testWrapper"; -import { appReducer } from "../../../store/reducers"; -import ROUTES from "../../../navigation/routes"; -import I18n from "../../../i18n"; - -const loadingCases: ReadonlyArray< - [ - url: string, - finishPathName: string, - outcomeQueryparamName: string, - expectedResult: [isFinish: boolean, outcomeCode: string | undefined] - ] -> = [ - ["http://mydomain.com/path", "/finish/path", "empty", [false, undefined]], - [ - "http://mydomain.com/finish/path", - "/finish/path", - "empty", - [true, undefined] - ], - [ - "http://mydomain.com/finish/path?empt=1234", - "/finish/path2", - "empty", - [false, undefined] - ], - [ - "http://mydomain.com/finish/path?mycode=12345", - "/finish/path", - "empty", - [true, undefined] - ], - [ - "http://mydomain.com/finish/path?mycode=12345", - "/finish/path", - "mycode", - [true, "12345"] - ], - [ - "http://mydomain.com/fiNIsh/Path?MyCode=12345", - "/finish/path", - "mycode", - [true, "12345"] - ] -]; - -describe("getFinishAndOutcome", () => { - test.each(loadingCases)( - "given %p as url, %p as finishPathName, %p as outcomeQueryparamName, returns %p", - (url, finishPathName, outcomeQueryparamName, expectedResult) => { - const result = testableGetFinishAndOutcome!( - new URLParse(url, true), - finishPathName, - outcomeQueryparamName - ); - expect(result).toEqual(expectedResult); - } - ); -}); - -describe("PayWebViewModal component", () => { - jest.useFakeTimers(); - - it("should render the screen's header", () => { - const component = renderComponent(); - expect( - component.getByText(I18n.t("wallet.challenge3ds.header")) - ).toBeDefined(); - }); - - it("should render the info message", () => { - const component = renderComponent(); - expect( - component.getByText(I18n.t("wallet.challenge3ds.description")) - ).toBeDefined(); - }); - - it("should render the info icon", () => { - const component = renderComponent(); - expect( - component - .getByTestId("PayWebViewModal-description") - .find(node => node.props.iconName === "info") - ).toBeDefined(); - }); -}); - -function renderComponent() { - const globalState = appReducer(undefined, applicationChangeState("active")); - return renderScreenWithNavigationStoreContext( - () => ( - undefined} - onGoBack={() => undefined} - modalHeaderTitle={I18n.t("wallet.challenge3ds.header")} - /> - ), - ROUTES.WALLET_CHECKOUT_3DS_SCREEN, - {}, - createStore(appReducer, globalState as any) - ); -} diff --git a/ts/components/wallet/__test__/PaymentBannerComponent.test.tsx b/ts/components/wallet/__test__/PaymentBannerComponent.test.tsx deleted file mode 100644 index 74e9bf083af..00000000000 --- a/ts/components/wallet/__test__/PaymentBannerComponent.test.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import * as React from "react"; -import { render } from "@testing-library/react-native"; -import PaymentBannerComponent from "../PaymentBannerComponent"; -import { ImportoEuroCents } from "../../../../definitions/backend/ImportoEuroCents"; -import { formatNumberCentsToAmount } from "../../../utils/stringBuilder"; - -describe("PaymentBannerComponent", () => { - describe("given a transaction fee", () => { - const currentAmount = 1500; - const fee = 1000; - const transactionWithFee = { - paymentReason: "test", - currentAmount: currentAmount as ImportoEuroCents, - fee: fee as ImportoEuroCents - }; - - it("renders the fee localized with the currency", () => { - const fixture = formatNumberCentsToAmount(fee, true); - const component = render( - - ); - expect( - component.queryByTestId("PaymentBannerComponentFee") - ).toHaveTextContent(fixture); - }); - - it("renders the sum of current amount and fee as total", () => { - const fixture = formatNumberCentsToAmount(currentAmount + fee, true); - const component = render( - - ); - expect( - component.queryByTestId("PaymentBannerComponentTotal") - ).toHaveTextContent(fixture); - }); - }); - - describe("given no transaction fee", () => { - const currentAmount = 1500; - const fee = 0; - const transactionWithoutFee = { - paymentReason: "test", - currentAmount: currentAmount as ImportoEuroCents, - fee: fee as ImportoEuroCents - }; - - it("renders 0 localized with the currency as fee", () => { - const fixture = formatNumberCentsToAmount(0, true); - const component = render( - - ); - expect( - component.queryByTestId("PaymentBannerComponentFee") - ).toHaveTextContent(fixture); - }); - - it("renders the current amount as total", () => { - const fixture = formatNumberCentsToAmount(currentAmount, true); - const component = render( - - ); - expect( - component.queryByTestId("PaymentBannerComponentTotal") - ).toHaveTextContent(fixture); - }); - }); -}); diff --git a/ts/components/wallet/__test__/PaymentsHistoryList.test.ts b/ts/components/wallet/__test__/PaymentsHistoryList.test.ts deleted file mode 100644 index afdda07e136..00000000000 --- a/ts/components/wallet/__test__/PaymentsHistoryList.test.ts +++ /dev/null @@ -1,108 +0,0 @@ -import * as O from "fp-ts/lib/Option"; -import { RptId } from "@pagopa/io-pagopa-commons/lib/pagopa"; -import { IPatternStringTag } from "@pagopa/ts-commons/lib/strings"; - -import { PaymentHistory } from "../../../store/reducers/payments/history"; -import { isPaymentDoneSuccessfully } from "../../../store/reducers/payments/utils"; -import { paymentVerificaResponseWithMessage as verifiedData } from "../../../__mocks__/paymentPayloads"; - -const data: RptId = { - organizationFiscalCode: "01199250158" as string & - IPatternStringTag<"^[0-9]{11}$">, - paymentNoticeNumber: { - applicationCode: "12" as string & IPatternStringTag<"[0-9]{2}">, - auxDigit: "0", - checkDigit: "19" as string & IPatternStringTag<"[0-9]{2}">, - iuv13: "3456789999999" as string & IPatternStringTag<"[0-9]{13}"> - } -}; - -// a successful payment -const paymentHistorySuccess: PaymentHistory = { - started_at: "2020-04-16T13:59:19.031Z", - data, - paymentId: "ca7d9be4-7da1-442d-92c6-d403d7361f65", - transaction: { - id: 7090047996, - created: new Date("2020-02-07T08:43:38.000Z"), - updated: new Date("2020-02-07T08:43:38.000Z"), - amount: { - currency: "EUR", - amount: 1, - decimalDigits: 2 - }, - grandTotal: { - currency: "EUR", - amount: 51, - decimalDigits: 2 - }, - description: "/RFB/719094842555711/0.01/TXT/Avviso di prova app IO", - merchant: "Comune di Milano", - idStatus: 3, - statusMessage: "Confermato", - error: false, - success: true, - fee: { - currency: "EUR", - amount: 50, - decimalDigits: 2 - }, - token: "NzA5MDA0ODAwOQ==", - idWallet: 38404, - idPsp: 401164, - idPayment: 71160, - nodoIdPayment: "375daf96-d8e6-42fb-b095-4e3c270923ad", - spcNodeStatus: 0, - accountingStatus: 1, - authorizationCode: "00", - orderNumber: 7090048009, - rrn: "200380002021", - numAut: "431061" - }, - verifiedData, - startOrigin: "message" -}; - -// an incomplete payment -const paymentHistoryIncomplete: PaymentHistory = { - started_at: "2020-04-16T13:59:19.031Z", - data, - paymentId: "ca7d9be4-7da1-442d-92c6-d403d7361f65", - verifiedData, - startOrigin: "message" -}; - -// a failed payment -const paymentHistoryFailed: PaymentHistory = { - data: { - paymentNoticeNumber: { - auxDigit: "3", - checkDigit: "37" as string & IPatternStringTag<"[0-9]{2}">, - iuv13: "0000000004976" as string & IPatternStringTag<"[0-9]{13}">, - segregationCode: "02" as string & IPatternStringTag<"[0-9]{2}"> - }, - organizationFiscalCode: "00122230196" as string & - IPatternStringTag<"^[0-9]{11}$"> - }, - started_at: "2020-04-05T15:51:16.237Z", - failure: "PPT_DOMINIO_SCONOSCIUTO", - startOrigin: "message" -}; - -describe("test the checkPaymentOutcome function", () => { - it("the first test must show the payment status as Success, because paymentState and transactionState exist", () => { - expect(isPaymentDoneSuccessfully(paymentHistorySuccess)).toEqual( - O.some(true) - ); - }); - - it("the second test must show the payment status as Failed, because paymentState and transactionState do not exist", () => { - expect(isPaymentDoneSuccessfully(paymentHistoryFailed)).toEqual( - O.some(false) - ); - }); - - it("the last test must show the payment status as Incomplete, because only paymentState is set and transactionState is undefined or set to false", () => { - expect(isPaymentDoneSuccessfully(paymentHistoryIncomplete)).toEqual(O.none); - }); -}); diff --git a/ts/components/wallet/card/CardComponent.tsx b/ts/components/wallet/card/CardComponent.tsx deleted file mode 100644 index a8383c68db5..00000000000 --- a/ts/components/wallet/card/CardComponent.tsx +++ /dev/null @@ -1,266 +0,0 @@ -/** - * Component rendering a credit card, - * with different appearences based on - * the props passed - */ -import { IOColors, Icon, VSpacer } from "@pagopa/io-app-design-system"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as E from "fp-ts/lib/Either"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import * as React from "react"; -import { Image, Platform, StyleSheet, View } from "react-native"; -import { BlurredPan } from "../../../features/wallet/component/card/BlurredPan"; -import I18n from "../../../i18n"; -import { CreditCard, CreditCardType, Wallet } from "../../../types/pagopa"; -import { CreditCardDetector, SupportedBrand } from "../../../utils/creditCard"; -import { isPaymentMethodExpired } from "../../../utils/paymentMethod"; -import { buildExpirationDate } from "../../../utils/stringBuilder"; -import { FOUR_UNICODE_CIRCLES } from "../../../utils/wallet"; -import TouchableDefaultOpacity from "../../TouchableDefaultOpacity"; -import { Body } from "../../core/typography/Body"; -import { H5 } from "../../core/typography/H5"; -import Logo, { cardIcons } from "./Logo"; - -interface BaseProps { - wallet: Wallet; -} - -interface FullCommonProps extends BaseProps { - isFavorite?: pot.Pot; - onSetFavorite?: (willBeFavorite: boolean) => void; - extraSpace?: boolean; - hideFavoriteIcon?: boolean; - onDelete?: () => void; -} - -interface FullProps extends FullCommonProps { - type: "Full"; - mainAction?: (item: Wallet) => void; -} - -interface HeaderProps extends FullCommonProps { - type: "Header"; -} - -interface PreviewProps extends BaseProps { - type: "Preview"; -} - -interface PickingProps extends BaseProps { - type: "Picking"; - mainAction: (wallet: Wallet) => void; -} - -type Props = FullProps | HeaderProps | PreviewProps | PickingProps; - -const styles = StyleSheet.create({ - cardHeader: { - marginTop: -20, - marginLeft: 12, - marginRight: 12, - paddingBottom: 10, - borderBottomLeftRadius: 8, - borderBottomRightRadius: 8 - }, - cardShadow: { - shadowColor: IOColors.black, - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.1, - shadowRadius: 1.5, - elevation: Platform.OS === "android" ? 5 : 25, - zIndex: Platform.OS === "android" ? 5 : 25 - }, - card: { - // iOS and Android card shadow - backgroundColor: IOColors.greyUltraLight, - borderRadius: 8, - marginLeft: 0, - marginRight: 0 - }, - cardInner: { - paddingBottom: 16, - paddingLeft: 16, - paddingRight: 16, - paddingTop: 18 - }, - row: { - flexDirection: "row" - }, - spaced: { - justifyContent: "space-between" - }, - columns: { - flexDirection: "row", - justifyContent: "space-between" - }, - cardLogo: { - height: 30, - width: 48 - }, - flatBottom: { - borderBottomLeftRadius: 0, - borderBottomRightRadius: 0 - }, - paddedTop: { - paddingTop: 10 - } -}); - -/** - * Credit card component - * @deprecated Use {@link BaseCardComponent} and related custom implementation (eg: {@link CreditCardComponent}) - */ -export default class CardComponent extends React.Component { - private handleFavoritePress = () => { - if ( - (this.props.type === "Full" || this.props.type === "Header") && - this.props.onSetFavorite !== undefined && - this.props.isFavorite !== undefined && - !pot.isLoading(this.props.isFavorite) && - !pot.isUpdating(this.props.isFavorite) - ) { - this.props.onSetFavorite(!pot.getOrElse(this.props.isFavorite, false)); - } - }; - - private renderTopRightCorner() { - if (this.props.type === "Preview" || this.props.type === "Picking") { - const { wallet } = this.props; - return ( - - - - ); - } - - if (this.props.type === "Header") { - const { hideFavoriteIcon, isFavorite } = this.props; - - return ( - - {!hideFavoriteIcon && isFavorite !== undefined && ( - - - - )} - - ); - } - - return null; - } - - private renderBody(creditCard: CreditCard) { - const { type } = this.props; - - if (type === "Preview") { - return null; - } - - // Extract the brand name from the credit card pan - const detectedBrand: SupportedBrand = CreditCardDetector.validate( - O.some(creditCard.pan) - ); - /** - * Extract the brand logo from the brand name - * since the keys of the @link{cardIcons} are the @link{CreditCardType} - * we must manage the case with the different name but the same logo - */ - const creditCardType = CreditCardType.decode( - detectedBrand.name.toUpperCase() - ); - const logo = - cardIcons[ - pipe( - creditCardType, - E.getOrElseW(() => "UNKNOWN" as const) - ) - ]; - - const BASE_ICON_W = 48; - const BASE_ICON_H = 30; - - const getBodyIcon = () => { - switch (type) { - case "Picking": - return null; - case "Full": - case "Header": - return ( - - - - ); - } - }; - const expirationDate = buildExpirationDate(creditCard); - const isCardExpired = this.props.wallet.paymentMethod - ? pipe( - isPaymentMethodExpired(this.props.wallet.paymentMethod), - E.getOrElse(() => false) - ) - : false; - return ( - - -
- {`${I18n.t("cardComponent.validUntil")} ${expirationDate}`} -
- - {creditCard.holder.toUpperCase()} -
- {getBodyIcon()} -
- ); - } - - public render(): React.ReactNode { - const { wallet } = this.props; - - const hasFlatBottom = - this.props.type === "Preview" || this.props.type === "Header"; - - const isHeader = this.props.type === "Header"; - - return wallet.creditCard === undefined ? null : ( - - - - - - {`${FOUR_UNICODE_CIRCLES} ${wallet.creditCard.pan.slice(-4)}`} - - - {this.renderTopRightCorner()} - - {hasFlatBottom && } - {isHeader && } - {this.renderBody(wallet.creditCard)} - - - ); - } -} diff --git a/ts/components/wallet/card/Logo.tsx b/ts/components/wallet/card/Logo.tsx deleted file mode 100644 index a9db17dad56..00000000000 --- a/ts/components/wallet/card/Logo.tsx +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Component representing the appropriate - * credit card logo based on its pan - */ -import * as React from "react"; -import { Image, ImageStyle, StyleProp, StyleSheet } from "react-native"; -import { CreditCardType } from "../../../types/pagopa"; -import { getResourceNameFromUrl } from "../../../utils/url"; - -export const cardIcons: { [key in CreditCardType]: any } = { - MASTERCARD: require("../../../../img/wallet/cards-icons/mastercard.png"), - VISA: require("../../../../img/wallet/cards-icons/visa.png"), - AMEX: require("../../../../img/wallet/cards-icons/amex.png"), - DINERS: require("../../../../img/wallet/cards-icons/diners.png"), - MAESTRO: require("../../../../img/wallet/cards-icons/maestro.png"), - VISAELECTRON: require("../../../../img/wallet/cards-icons/visa-electron.png"), - POSTEPAY: require("../../../../img/wallet/cards-icons/postepay.png"), - UNIONPAY: require("../../../../img/wallet/cards-icons/unionpay.png"), - DISCOVER: require("../../../../img/wallet/cards-icons/discover.png"), - JCB: require("../../../../img/wallet/cards-icons/jcb.png"), - JCB15: require("../../../../img/wallet/cards-icons/jcb.png"), - UNKNOWN: require("../../../../img/wallet/cards-icons/unknown.png") -}; - -const cardMapIcon: { [key in string]: any } = { - carta_mc: require("../../../../img/wallet/cards-icons/mastercard.png"), - carta_visa: require("../../../../img/wallet/cards-icons/visa.png"), - carta_amex: require("../../../../img/wallet/cards-icons/amex.png"), - carta_diners: require("../../../../img/wallet/cards-icons/diners.png"), - carta_visaelectron: require("../../../../img/wallet/cards-icons/visa-electron.png"), - carta_poste: require("../../../../img/wallet/cards-icons/postepay.png"), - carta_maestro: require("../../../../img/wallet/cards-icons/maestro.png"), - carta_vpay: require("../../../../img/wallet/cards-icons/vPay.png") -}; - -import { CardInfo } from "../../../../definitions/pagopa/walletv2/CardInfo"; -import defaultCardIcon from "../../../../img/wallet/cards-icons/unknown.png"; -/** - * pagoPA's "brandLogo" field contains an url to an image - * From the given url it will check if there is a matching and an icon will be returned - * If there is NO matching a default card icon will be returned - * Consider to evaluate the field "brand" instead of "brandLogo" - * because it should contain only the name of the credit card type - * @param cardInfo - */ -export const getCardIconFromBrandLogo = (cardInfo: CardInfo) => { - if (!cardInfo.brandLogo) { - return defaultCardIcon; - } - const imageName = getResourceNameFromUrl(cardInfo.brandLogo); - return imageName && cardMapIcon[imageName] - ? cardMapIcon[imageName] - : defaultCardIcon; -}; - -const styles = StyleSheet.create({ - issuerLogo: { - width: "100%", - height: "100%", - resizeMode: "contain" - } -}); - -type Props = Readonly<{ - item?: CardInfo; - imageStyle?: StyleProp; - pspLogo?: string; -}>; - -const Logo = (props: Props) => { - const getSource = () => { - if (props.pspLogo && props.pspLogo.trim().length > 0) { - return { uri: props.pspLogo }; - } - - if (props.item) { - return getCardIconFromBrandLogo(props.item); - } - return defaultCardIcon; - }; - - return ( - - ); -}; - -export default Logo; diff --git a/ts/components/wallet/card/SectionCardComponent.tsx b/ts/components/wallet/card/SectionCardComponent.tsx deleted file mode 100644 index f6d14e2d926..00000000000 --- a/ts/components/wallet/card/SectionCardComponent.tsx +++ /dev/null @@ -1,196 +0,0 @@ -import * as React from "react"; -import { - View, - ActivityIndicator, - Platform, - StyleSheet, - ViewStyle -} from "react-native"; -import { - IOColors, - Icon, - hexToRgba, - HSpacer -} from "@pagopa/io-app-design-system"; -import I18n from "../../../i18n"; -import TouchableDefaultOpacity from "../../TouchableDefaultOpacity"; -import { IOBadge } from "../../core/IOBadge"; -import { Label } from "../../core/typography/Label"; -import { IOStyles } from "../../core/variables/IOStyles"; - -export type SectionCardStatus = "add" | "refresh" | "loading" | "show"; -type Props = { - onPress: () => void; - label: string; - status?: SectionCardStatus; - isError?: boolean; - isNew?: boolean; - cardStyle?: ViewStyle; - accessibilityLabel?: string; - accessibilityHint?: string; - testId?: string; -}; - -const opaqueBorderColor = hexToRgba(IOColors.black, 0.1); - -const styles = StyleSheet.create({ - cardInner: { - paddingBottom: 13, - paddingLeft: 16, - paddingRight: 16, - paddingTop: 13 - }, - card: { - shadowOffset: { - width: 0, - height: 3 - }, - shadowOpacity: 0.29, - shadowRadius: 4.65, - borderRadius: 8, - zIndex: -7, - elevation: -7, - height: 88, - marginLeft: 0, - marginRight: 0 - }, - cardBlueGrey: { - backgroundColor: IOColors.bluegrey - }, - flatBottom: { - borderBottomLeftRadius: 0, - borderBottomRightRadius: 0 - }, - rotateCard: { - shadowColor: IOColors.black, - marginBottom: -38, - flex: 1, - shadowRadius: 10, - shadowOpacity: 0.15, - transform: [{ perspective: 1200 }, { rotateX: "-20deg" }, { scaleX: 0.99 }] - }, - button: { - flexDirection: "row", - alignItems: "center", - justifyContent: "flex-end" - }, - shadowBox: { - marginBottom: -15, - borderRadius: 8, - borderTopWidth: 8, - borderTopColor: opaqueBorderColor, - height: 15 - } -}); - -const SectionCardComponent: React.FunctionComponent = (props: Props) => { - const { label, onPress, isNew, isError, cardStyle, testId } = props; - const rightLabel = () => { - switch (props.status) { - case undefined: - case "add": - return ( - <> - - - - - ); - case "loading": - return ( - - ); - case "refresh": - return ( - - - - - - ); - case "show": - return ( - - - - - - ); - } - }; - - return ( - <> - {Platform.OS === "android" && ( - - )} - - - - - - - {isNew && ( - - - - - - )} - - {!isError && {rightLabel()}} - - - - - - ); -}; - -export default SectionCardComponent; diff --git a/ts/components/wallet/creditCardOnboardingAttempts/CreditCardAttemptsList.tsx b/ts/components/wallet/creditCardOnboardingAttempts/CreditCardAttemptsList.tsx deleted file mode 100644 index f391096337b..00000000000 --- a/ts/components/wallet/creditCardOnboardingAttempts/CreditCardAttemptsList.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import { - ContentWrapper, - H3, - IOColors, - Icon, - VSpacer -} from "@pagopa/io-app-design-system"; -import * as React from "react"; -import { - FlatList, - ListRenderItemInfo, - ScrollView, - StyleSheet, - View -} from "react-native"; -import I18n from "../../../i18n"; -import { - CreditCardInsertion, - CreditCardInsertionState -} from "../../../store/reducers/wallet/creditCard"; -import customVariables from "../../../theme/variables"; -import { formatDateAsLocal } from "../../../utils/dates"; -import { FOUR_UNICODE_CIRCLES } from "../../../utils/wallet"; -import ItemSeparatorComponent from "../../ItemSeparatorComponent"; -import TouchableDefaultOpacity from "../../TouchableDefaultOpacity"; -import { Label } from "../../core/typography/Label"; -import { IOStyles } from "../../core/variables/IOStyles"; -import { BadgeComponent } from "../../screens/BadgeComponent"; -import { EdgeBorderComponent } from "../../screens/EdgeBorderComponent"; - -type Props = Readonly<{ - title: string; - creditCardAttempts: CreditCardInsertionState; - onAttemptPress: (attempt: CreditCardInsertion) => void; - ListEmptyComponent: React.ComponentProps< - typeof FlatList - >["ListEmptyComponent"]; -}>; - -const styles = StyleSheet.create({ - whiteContent: { - backgroundColor: IOColors.white, - flex: 1 - }, - subHeaderContent: { - flexDirection: "row", - alignItems: "baseline", - justifyContent: "space-between" - } -}); - -const itemStyles = StyleSheet.create({ - verticalPad: { - paddingVertical: customVariables.spacerHeight - }, - spaced: { - justifyContent: "flex-start", - flexDirection: "row", - alignItems: "center" - }, - text11: { - paddingLeft: 8 - }, - icon: { - width: 64, - alignItems: "flex-end", - alignContent: "center", - alignSelf: "center", - justifyContent: "center" - } -}); - -const ICON_WIDTH = 24; -const labelColor = "bluegrey"; -export const getPanDescription = (attempt: CreditCardInsertion) => - `${FOUR_UNICODE_CIRCLES} ${attempt.blurredPan}\n ${I18n.t( - "cardComponent.validUntil" - ).toLowerCase()} ${attempt.expireMonth}/${attempt.expireYear}`; - -const getAttemptData = (attempt: CreditCardInsertion) => { - const conditionalData = attempt.onboardingComplete - ? { - color: "green", - header: I18n.t("wallet.creditCard.onboardingAttempts.success") - } - : { - color: "red", - header: I18n.t("wallet.creditCard.onboardingAttempts.failure") - }; - const startDate = new Date(attempt.startDate); - const when = `${formatDateAsLocal( - startDate, - true, - true - )} - ${startDate.toLocaleTimeString()}`; - return { - panAndExpiringDate: getPanDescription(attempt), - when, - ...conditionalData - }; -}; - -/** - * This component shows a list with the last credit card onboarding attempts - */ -export const CreditCardAttemptsList: React.FC = (props: Props) => { - const { ListEmptyComponent, creditCardAttempts } = props; - const renderCreditCardAttempt = ( - info: ListRenderItemInfo - ) => { - const attemptData = getAttemptData(info.item); - return ( - - props.onAttemptPress(info.item)} - style={itemStyles.verticalPad} - > - - - - - - - - - - - - - - - - - ); - }; - - return ( - - - - -

{props.title}

-
-
- - ( - - )} - ListFooterComponent={ - creditCardAttempts.length > 0 ? : null - } - keyExtractor={c => c.hashedPan} - /> -
-
- ); -}; diff --git a/ts/components/wallet/payment/PickAvailablePaymentMethodListItem.tsx b/ts/components/wallet/payment/PickAvailablePaymentMethodListItem.tsx deleted file mode 100644 index 8eccd6c94cf..00000000000 --- a/ts/components/wallet/payment/PickAvailablePaymentMethodListItem.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as React from "react"; -import { ImageSourcePropType } from "react-native"; -import { connect } from "react-redux"; -import { Icon } from "@pagopa/io-app-design-system"; -import pagoBancomatLogo from "../../../../img/wallet/cards-icons/pagobancomat.png"; -import bancomatPayLogo from "../../../../img/wallet/payment-methods/bpay.png"; -import paypalLogo from "../../../../img/wallet/payment-methods/paypal.png"; -import I18n from "../../../i18n"; -import { profileNameSurnameSelector } from "../../../store/reducers/profile"; -import { GlobalState } from "../../../store/reducers/types"; -import { getFavoriteWalletId } from "../../../store/reducers/wallet/wallets"; -import { PaymentMethod } from "../../../types/pagopa"; -import { getPickPaymentMethodDescription } from "../../../utils/payment"; -import { getCardIconFromBrandLogo } from "../card/Logo"; -import PickPaymentMethodBaseListItem from "./PickPaymentMethodBaseListItem"; - -type Props = { - isFirst: boolean; - paymentMethod: PaymentMethod; - rightElement?: JSX.Element; - onPress?: () => void; -} & ReturnType; - -type PaymentMethodInformation = { - logo: ImageSourcePropType; - title: string; - description: string; -}; - -const extractInfoFromPaymentMethod = ( - paymentMethod: PaymentMethod, - nameSurname: string -): PaymentMethodInformation => { - switch (paymentMethod.kind) { - case "CreditCard": - return { - logo: getCardIconFromBrandLogo(paymentMethod.info), - title: paymentMethod.caption, - description: getPickPaymentMethodDescription(paymentMethod, nameSurname) - }; - case "Bancomat": - return { - logo: pagoBancomatLogo, - title: paymentMethod.caption, - description: getPickPaymentMethodDescription(paymentMethod, nameSurname) - }; - case "BPay": - return { - logo: bancomatPayLogo, - title: paymentMethod.caption, - description: paymentMethod.info.numberObfuscated ?? "" - }; - case "PayPal": - return { - logo: paypalLogo, - title: I18n.t("wallet.methods.paypal.name"), - description: paymentMethod.caption - }; - } -}; - -const PickAvailablePaymentMethodListItem: React.FC = (props: Props) => { - const { logo, title, description } = extractInfoFromPaymentMethod( - props.paymentMethod, - props.nameSurname ?? "" - ); - return ( - - ) - } - onPress={props.onPress} - /> - ); -}; - -const mapStateToProps = (state: GlobalState) => ({ - favoriteWalletId: getFavoriteWalletId(state), - nameSurname: profileNameSurnameSelector(state) -}); -export default connect(mapStateToProps)(PickAvailablePaymentMethodListItem); diff --git a/ts/components/wallet/payment/PickNotAvailablePaymentMethodListItem.tsx b/ts/components/wallet/payment/PickNotAvailablePaymentMethodListItem.tsx deleted file mode 100644 index 0ab65acb1aa..00000000000 --- a/ts/components/wallet/payment/PickNotAvailablePaymentMethodListItem.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as React from "react"; -import { ImageSourcePropType } from "react-native"; -import { connect } from "react-redux"; -import { Icon, VSpacer } from "@pagopa/io-app-design-system"; -import pagoBancomatLogo from "../../../../img/wallet/cards-icons/pagobancomat.png"; -import bancomatPayLogo from "../../../../img/wallet/payment-methods/bancomatpay-logo.png"; -import I18n from "../../../i18n"; -import { profileNameSurnameSelector } from "../../../store/reducers/profile"; -import { GlobalState } from "../../../store/reducers/types"; -import { getFavoriteWalletId } from "../../../store/reducers/wallet/wallets"; -import { PaymentMethod } from "../../../types/pagopa"; -import { useIOBottomSheetAutoresizableModal } from "../../../utils/hooks/bottomSheet"; -import { getPickPaymentMethodDescription } from "../../../utils/payment"; -import { getPaypalAccountEmail } from "../../../utils/paypal"; -import { H4 } from "../../core/typography/H4"; -import { getCardIconFromBrandLogo } from "../card/Logo"; -import PickPaymentMethodBaseListItem from "./PickPaymentMethodBaseListItem"; - -type Props = { - isFirst: boolean; - paymentMethod: PaymentMethod; -} & ReturnType; - -const unacceptedBottomSheetTitle = () => - I18n.t( - "wallet.payWith.pickPaymentMethod.notAvailable.unaccepted.bottomSheetTitle" - ); -const unacceptedBottomSheetBody = () => ( - <> - -

- {I18n.t( - "wallet.payWith.pickPaymentMethod.notAvailable.unaccepted.bottomSheetDescription" - )} -

- - -); - -const paymentDisabledBottomSheetTitle = () => - I18n.t( - "wallet.payWith.pickPaymentMethod.notAvailable.payment_disabled.bottomSheetTitle" - ); -const paymentDisabledBottomSheetBody = () => ( - <> - -

- {I18n.t( - "wallet.payWith.pickPaymentMethod.notAvailable.payment_disabled.bottomSheetDescription" - )} -

- - -); - -type PaymentMethodInformation = { - logo: ImageSourcePropType; - title: string; - description: string; - bottomSheetTitle: string; - bottomSheetBody: JSX.Element; -}; - -const extractInfoFromPaymentMethod = ( - paymentMethod: PaymentMethod, - nameSurname: string -): PaymentMethodInformation => { - switch (paymentMethod.kind) { - case "CreditCard": - // The only paymentMethod of kind "CreditCard" that will be render in this component - // will be the co-badge cards. - return { - logo: getCardIconFromBrandLogo(paymentMethod.info), - title: paymentMethod.caption, - description: getPickPaymentMethodDescription( - paymentMethod, - nameSurname - ), - bottomSheetTitle: paymentDisabledBottomSheetTitle(), - bottomSheetBody: paymentDisabledBottomSheetBody() - }; - case "Bancomat": - return { - logo: pagoBancomatLogo, - title: paymentMethod.caption, - description: getPickPaymentMethodDescription( - paymentMethod, - nameSurname - ), - bottomSheetTitle: unacceptedBottomSheetTitle(), - bottomSheetBody: unacceptedBottomSheetBody() - }; - case "BPay": - return { - logo: bancomatPayLogo, - title: paymentMethod.caption, - description: paymentMethod.info.numberObfuscated ?? "", - bottomSheetTitle: paymentDisabledBottomSheetTitle(), - bottomSheetBody: paymentDisabledBottomSheetBody() - }; - case "PayPal": - return { - logo: paymentMethod.icon, - title: paymentMethod.kind, - description: getPaypalAccountEmail(paymentMethod.info), - bottomSheetTitle: paymentDisabledBottomSheetTitle(), - bottomSheetBody: paymentDisabledBottomSheetBody() - }; - } -}; - -const PickNotAvailablePaymentMethodListItem: React.FC = ( - props: Props -) => { - const { logo, title, description, bottomSheetTitle, bottomSheetBody } = - extractInfoFromPaymentMethod(props.paymentMethod, props.nameSurname ?? ""); - - const { present, bottomSheet } = useIOBottomSheetAutoresizableModal( - { - component: bottomSheetBody, - title: bottomSheetTitle - }, - 32 - ); - - return ( - <> - {bottomSheet} - } - onPress={present} - /> - - ); -}; - -const mapStateToProps = (state: GlobalState) => ({ - favoriteWalletId: getFavoriteWalletId(state), - nameSurname: profileNameSurnameSelector(state) -}); -export default connect(mapStateToProps)(PickNotAvailablePaymentMethodListItem); diff --git a/ts/components/wallet/payment/PickPaymentMethodBaseListItem.tsx b/ts/components/wallet/payment/PickPaymentMethodBaseListItem.tsx deleted file mode 100644 index da3d3f9d6c4..00000000000 --- a/ts/components/wallet/payment/PickPaymentMethodBaseListItem.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { Icon, VSpacer } from "@pagopa/io-app-design-system"; -import * as React from "react"; -import { - Image, - ImageSourcePropType, - Pressable, - StyleSheet, - View -} from "react-native"; -import { WithTestID } from "../../../types/WithTestID"; -import { H4 } from "../../core/typography/H4"; -import { H5 } from "../../core/typography/H5"; -import { IOStyles } from "../../core/variables/IOStyles"; - -type Props = WithTestID<{ - isFirst: boolean; - isFavourite: boolean; - logo: ImageSourcePropType; - title: string; - description: string; - rightElement: JSX.Element; - onPress?: () => void; -}>; - -const styles = StyleSheet.create({ - cardLogo: { - height: 26, - width: 41, - resizeMode: "contain" - }, - paymentMethodInfo: { - paddingLeft: 15, - paddingRight: 15, - flex: 1 - }, - contentContainer: { - flexDirection: "row", - justifyContent: "space-between", - flex: 1 - }, - row: { - flexDirection: "row", - alignItems: "center" - } -}); -const PickPaymentMethodBaseListItem: React.FC = ({ - isFavourite, - logo, - title, - description, - rightElement, - onPress, - testID -}) => ( - - - - - - - -

- {title} -

-
- {description} -
-
-
- - {isFavourite && } - {rightElement} - -
- -
-); - -export default PickPaymentMethodBaseListItem; diff --git a/ts/components/wallet/payment/PspComponent.tsx b/ts/components/wallet/payment/PspComponent.tsx deleted file mode 100644 index 55396a17c80..00000000000 --- a/ts/components/wallet/payment/PspComponent.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { Icon } from "@pagopa/io-app-design-system"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import React, { FC } from "react"; -import { Image, ImageStyle, StyleProp, StyleSheet, View } from "react-native"; -import { PspData } from "../../../../definitions/pagopa/PspData"; -import { useImageResize } from "../../../features/wallet/onboarding/bancomat/hooks/useImageResize"; -import customVariables from "../../../theme/variables"; -import { getPspIconUrlFromAbi } from "../../../utils/paymentMethod"; -import { formatNumberCentsToAmount } from "../../../utils/stringBuilder"; -import TouchableDefaultOpacity from "../../TouchableDefaultOpacity"; -import { Body } from "../../core/typography/Body"; -import { H4 } from "../../core/typography/H4"; - -const ICON_SIZE = 24; -const IMAGE_WIDTH = 100; -const IMAGE_HEIGHT = 50; - -const styles = StyleSheet.create({ - itemContainer: { - paddingVertical: 16, - paddingHorizontal: customVariables.contentPadding, - flexDirection: "column" - }, - line1: { - flexDirection: "row", - alignItems: "center", - justifyContent: "space-between" - }, - feeContainer: { - flexDirection: "row", - alignItems: "center" - } -}); - -type Props = { - psp: PspData; - onPress: () => void; -}; - -export const PspComponent: FC = ({ psp, onPress }) => { - const pspLogoUrl = getPspIconUrlFromAbi(psp.codiceAbi); - const imgDimensions = useImageResize(IMAGE_WIDTH, IMAGE_HEIGHT, pspLogoUrl); - const cost = formatNumberCentsToAmount(psp.fee); - - const imageStyle: StyleProp | undefined = pipe( - imgDimensions, - O.fold( - () => undefined, - imgDim => ({ - width: imgDim[0], - height: imgDim[1], - resizeMode: "contain" - }) - ) - ); - - return ( - - - {imageStyle ? ( - - ) : ( - {psp.ragioneSociale} - )} - -

{cost}

- -
-
-
- ); -}; diff --git a/ts/features/barcode/screens/BarcodeScanScreen.tsx b/ts/features/barcode/screens/BarcodeScanScreen.tsx index b0c083f61f5..bc92900a972 100644 --- a/ts/features/barcode/screens/BarcodeScanScreen.tsx +++ b/ts/features/barcode/screens/BarcodeScanScreen.tsx @@ -17,8 +17,7 @@ import { AppParamsList, IOStackNavigationProp } from "../../../navigation/params/AppParamsList"; -import { paymentInitializeState } from "../../../store/actions/wallet/payment"; -import { useIODispatch, useIOSelector } from "../../../store/hooks"; +import { useIOSelector } from "../../../store/hooks"; import { barcodesScannerConfigSelector, isIdPayEnabledSelector @@ -48,7 +47,6 @@ import { FCI_ROUTES } from "../../fci/navigation/routes"; const BarcodeScanScreen = () => { const navigation = useNavigation>(); - const dispatch = useIODispatch(); const openDeepLink = useOpenDeepLink(); const isIdPayEnabled = useIOSelector(isIdPayEnabledSelector); @@ -132,8 +130,6 @@ const BarcodeScanScreen = () => { switch (barcode.type) { case "PAGOPA": - dispatch(paymentInitializeState()); - const isDataMatrix = barcode.format === "DATA_MATRIX"; if (isDataMatrix) { diff --git a/ts/features/bonus/common/navigation/actions.tsx b/ts/features/bonus/common/navigation/actions.tsx deleted file mode 100644 index f5d7d99eeda..00000000000 --- a/ts/features/bonus/common/navigation/actions.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { CommonActions } from "@react-navigation/native"; -import NavigationService from "../../../../navigation/NavigationService"; -import ROUTES from "../../../../navigation/routes"; -import { BONUS_ROUTES } from "./navigator"; - -export const navigateToAvailableBonusListScreen = () => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: BONUS_ROUTES.MAIN, - params: { - screen: BONUS_ROUTES.BONUS_AVAILABLE_LIST - } - }) - ); diff --git a/ts/features/design-system/core/DSLegacyAdvice.tsx b/ts/features/design-system/core/DSLegacyAdvice.tsx index b71cf33e08c..ce31f85278b 100644 --- a/ts/features/design-system/core/DSLegacyAdvice.tsx +++ b/ts/features/design-system/core/DSLegacyAdvice.tsx @@ -2,11 +2,8 @@ import { Icon, VSpacer } from "@pagopa/io-app-design-system"; import * as React from "react"; import { StyleSheet, View } from "react-native"; import { InfoBox } from "../../../components/box/InfoBox"; -import PaymentBannerComponent from "../../../components/wallet/PaymentBannerComponent"; -import { DSFullWidthComponent } from "../components/DSFullWidthComponent"; /* Types */ -import { ImportoEuroCents } from "../../../../definitions/backend/ImportoEuroCents"; import AdviceComponent from "../../../components/AdviceComponent"; import { Body } from "../../../components/core/typography/Body"; import { H5 } from "../../../components/core/typography/H5"; @@ -61,14 +58,6 @@ export const DSLegacyAdvice = () => ( } /> - - - - diff --git a/ts/features/design-system/core/DSListItems.tsx b/ts/features/design-system/core/DSListItems.tsx index 711562e8b06..6ba719c888a 100644 --- a/ts/features/design-system/core/DSListItems.tsx +++ b/ts/features/design-system/core/DSListItems.tsx @@ -24,7 +24,6 @@ import { DSComponentViewerBox } from "../components/DSComponentViewerBox"; import { ProductCategoryEnum } from "../../../../definitions/cgn/merchants/ProductCategory"; import { CgnMerchantDiscountItem } from "../../bonus/cgn/components/merchants/CgnMerchantsDiscountItem"; import { getBadgeTextByTransactionStatus } from "../../payments/common/utils"; -import { BankPreviewItem } from "../../wallet/onboarding/bancomat/components/BankPreviewItem"; import { DesignSystemScreen } from "../components/DesignSystemScreen"; const onButtonPress = () => { @@ -78,17 +77,6 @@ export const DSListItems = () => {

Specific

- - - { const navigation = useIONavigation(); @@ -36,9 +36,8 @@ export const ConfigurationSuccessScreen = () => { const handleNavigateToInitiativePress = () => machine.send({ type: "next" }); const handleAddPaymentMethodButtonPress = () => - navigation.replace(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.WALLET_ADD_PAYMENT_METHOD, - params: { inPayment: O.none } + navigation.replace(PaymentsOnboardingRoutes.PAYMENT_ONBOARDING_NAVIGATOR, { + screen: PaymentsOnboardingRoutes.PAYMENT_ONBOARDING_SELECT_METHOD }); const renderButtons = () => { diff --git a/ts/features/idpay/configuration/screens/InstrumentsEnrollmentScreen.tsx b/ts/features/idpay/configuration/screens/InstrumentsEnrollmentScreen.tsx index 0a010a11096..43caf9d6a0a 100644 --- a/ts/features/idpay/configuration/screens/InstrumentsEnrollmentScreen.tsx +++ b/ts/features/idpay/configuration/screens/InstrumentsEnrollmentScreen.tsx @@ -15,7 +15,6 @@ import { H1 } from "../../../../components/core/typography/H1"; import BaseScreenComponent from "../../../../components/screens/BaseScreenComponent"; import I18n from "../../../../i18n"; import { useIONavigation } from "../../../../navigation/params/AppParamsList"; -import ROUTES from "../../../../navigation/routes"; import { Wallet } from "../../../../types/pagopa"; import { emptyContextualHelp } from "../../../../utils/emptyContextualHelp"; import { useIOBottomSheetAutoresizableModal } from "../../../../utils/hooks/bottomSheet"; @@ -33,6 +32,7 @@ import { IdPayConfigurationParamsList } from "../navigation/params"; import { ConfigurationMode } from "../types"; import { InitiativeFailureType } from "../types/failure"; import { isLoadingSelector } from "../../common/machine/selectors"; +import { PaymentsOnboardingRoutes } from "../../../payments/onboarding/navigation/routes"; export type IdPayInstrumentsEnrollmentScreenParams = { initiativeId?: string; @@ -100,9 +100,8 @@ export const InstrumentsEnrollmentScreen = () => { const handleContinueButton = () => machine.send({ type: "next" }); const handleAddPaymentMethodButton = () => - navigation.replace(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.WALLET_ADD_PAYMENT_METHOD, - params: { inPayment: O.none } + navigation.replace(PaymentsOnboardingRoutes.PAYMENT_ONBOARDING_NAVIGATOR, { + screen: PaymentsOnboardingRoutes.PAYMENT_ONBOARDING_SELECT_METHOD }); const handleEnrollConfirm = () => { diff --git a/ts/features/messages/components/MessageDetail/MessageDetailsPayment.tsx b/ts/features/messages/components/MessageDetail/MessageDetailsPayment.tsx index 382532939e2..5cdef78c88d 100644 --- a/ts/features/messages/components/MessageDetail/MessageDetailsPayment.tsx +++ b/ts/features/messages/components/MessageDetail/MessageDetailsPayment.tsx @@ -40,7 +40,6 @@ export const MessageDetailsPayment = ({ messageId={messageId} noSpaceOnTop noticeNumber={paymentData.noticeNumber} - paymentAmount={paymentData.amount} rptId={rptId} serviceId={serviceId} /> diff --git a/ts/features/messages/components/MessageDetail/MessageDetailsPaymentButton.tsx b/ts/features/messages/components/MessageDetail/MessageDetailsPaymentButton.tsx index d074b74543c..cf95005f2b3 100644 --- a/ts/features/messages/components/MessageDetail/MessageDetailsPaymentButton.tsx +++ b/ts/features/messages/components/MessageDetail/MessageDetailsPaymentButton.tsx @@ -1,22 +1,16 @@ import React from "react"; import { ButtonSolid, useIOToast } from "@pagopa/io-app-design-system"; -import { PaymentData, UIMessageId } from "../../types"; -import { - useIODispatch, - useIOSelector, - useIOStore -} from "../../../../store/hooks"; +import { PaymentData } from "../../types"; +import { useIODispatch, useIOStore } from "../../../../store/hooks"; import I18n from "../../../../i18n"; import { getRptIdStringFromPaymentData, initializeAndNavigateToWalletForPayment } from "../../utils"; -import { isNewPaymentSectionEnabledSelector } from "../../../../store/reducers/backendStatus"; import { ServiceId } from "../../../../../definitions/backend/ServiceId"; import { computeAndTrackPaymentStart } from "./detailsUtils"; type MessageDetailsPaymentButtonProps = { - messageId: UIMessageId; paymentData: PaymentData; canNavigateToPayment: boolean; isLoading: boolean; @@ -24,7 +18,6 @@ type MessageDetailsPaymentButtonProps = { }; export const MessageDetailsPaymentButton = ({ - messageId, paymentData, canNavigateToPayment, isLoading, @@ -33,21 +26,14 @@ export const MessageDetailsPaymentButton = ({ const dispatch = useIODispatch(); const store = useIOStore(); const toast = useIOToast(); - // Checks if the new wallet section is enabled - const isNewWalletSectionEnabled = useIOSelector( - isNewPaymentSectionEnabledSelector - ); return ( initializeAndNavigateToWalletForPayment( - isNewWalletSectionEnabled, - messageId, getRptIdStringFromPaymentData(paymentData), false, - paymentData.amount, canNavigateToPayment, dispatch, () => computeAndTrackPaymentStart(serviceId, store.getState()), diff --git a/ts/features/messages/components/MessageDetail/MessageDetailsStickyFooter.tsx b/ts/features/messages/components/MessageDetail/MessageDetailsStickyFooter.tsx index 99933e1feb8..a3568e1924a 100644 --- a/ts/features/messages/components/MessageDetail/MessageDetailsStickyFooter.tsx +++ b/ts/features/messages/components/MessageDetail/MessageDetailsStickyFooter.tsx @@ -158,7 +158,6 @@ const computeFooterData = ( }; const renderPaymentWithDoubleCTA = ( - messageId: UIMessageId, serviceId: ServiceId, paymentData: PaymentData, canNavigateToPayment: boolean, @@ -171,7 +170,6 @@ const renderPaymentWithDoubleCTA = ( ) => ( <> ); const renderPaymentWithCTA = ( - messageId: UIMessageId, serviceId: ServiceId, paymentData: PaymentData, canNavigateToPayment: boolean, @@ -206,7 +203,6 @@ const renderPaymentWithCTA = ( ) => ( <> ); const renderPayment = ( - messageId: UIMessageId, serviceId: ServiceId, paymentData: PaymentData, canNavigateToPayment: boolean, isLoadingPayment: boolean ) => ( renderPaymentWithDoubleCTA( - messageId, serviceId, paymentWithDoubleCTA.paymentData, canNavigateToPayment, @@ -345,7 +338,6 @@ export const MessageDetailsStickyFooter = ({ ), paymentWithCTA => renderPaymentWithCTA( - messageId, serviceId, paymentWithCTA.paymentData, canNavigateToPayment, @@ -364,7 +356,6 @@ export const MessageDetailsStickyFooter = ({ ), payment => renderPayment( - messageId, serviceId, payment.paymentData, canNavigateToPayment, diff --git a/ts/features/messages/components/MessageDetail/MessagePaymentItem.tsx b/ts/features/messages/components/MessageDetail/MessagePaymentItem.tsx index ccdce45e004..ffe9960046f 100644 --- a/ts/features/messages/components/MessageDetail/MessagePaymentItem.tsx +++ b/ts/features/messages/components/MessageDetail/MessagePaymentItem.tsx @@ -41,7 +41,6 @@ import { import { initializeAndNavigateToWalletForPayment } from "../../utils"; import { getBadgeTextByPaymentNoticeStatus } from "../../utils/strings"; import { formatPaymentNoticeNumber } from "../../../payments/common/utils"; -import { isNewPaymentSectionEnabledSelector } from "../../../../store/reducers/backendStatus"; import { ServiceId } from "../../../../../definitions/backend/ServiceId"; import { trackPNPaymentStart } from "../../../pn/analytics"; import { computeAndTrackPaymentStart } from "./detailsUtils"; @@ -169,7 +168,6 @@ export const MessagePaymentItem = ({ messageId, noSpaceOnTop = false, noticeNumber, - paymentAmount = undefined, rptId, serviceId, willNavigateToPayment = undefined @@ -191,17 +189,10 @@ export const MessagePaymentItem = ({ canNavigateToPaymentFromMessageSelector(state) ); - // Checks if the new wallet section is enabled - const isNewWalletSectionEnabled = useIOSelector( - isNewPaymentSectionEnabledSelector - ); const startPaymentCallback = useCallback(() => { initializeAndNavigateToWalletForPayment( - isNewWalletSectionEnabled, - messageId, rptId, isError(paymentStatusForUI), - paymentAmount, canNavigateToPayment, dispatch, () => { @@ -217,10 +208,7 @@ export const MessagePaymentItem = ({ }, [ canNavigateToPayment, dispatch, - isNewWalletSectionEnabled, isPNPayment, - messageId, - paymentAmount, paymentStatusForUI, rptId, serviceId, diff --git a/ts/features/messages/components/MessageDetail/__tests__/MessageDetailsPaymentButton.test.tsx b/ts/features/messages/components/MessageDetail/__tests__/MessageDetailsPaymentButton.test.tsx index eb7bc180dda..f09fc024ceb 100644 --- a/ts/features/messages/components/MessageDetail/__tests__/MessageDetailsPaymentButton.test.tsx +++ b/ts/features/messages/components/MessageDetail/__tests__/MessageDetailsPaymentButton.test.tsx @@ -5,7 +5,7 @@ import { preferencesDesignSystemSetEnabled } from "../../../../../store/actions/ import { appReducer } from "../../../../../store/reducers"; import { MessageDetailsPaymentButton } from "../MessageDetailsPaymentButton"; import { renderScreenWithNavigationStoreContext } from "../../../../../utils/testWrapper"; -import { PaymentData, UIMessageId } from "../../../types"; +import { PaymentData } from "../../../types"; import { ServiceId } from "../../../../../../definitions/backend/ServiceId"; describe("MessageDetailsPaymentButton", () => { @@ -30,7 +30,6 @@ const renderScreen = (isLoading: boolean) => { return renderScreenWithNavigationStoreContext( () => ( 5; diff --git a/ts/features/messages/saga/index.ts b/ts/features/messages/saga/index.ts index 9bf22547ca7..a45b9b591c2 100644 --- a/ts/features/messages/saga/index.ts +++ b/ts/features/messages/saga/index.ts @@ -43,8 +43,8 @@ import { } from "./handleUpsertMessageStatusAttributes"; import { handleMessagePrecondition } from "./handleMessagePrecondition"; import { handleThirdPartyMessage } from "./handleThirdPartyMessage"; -import { handlePaymentUpdateRequests } from "./handlePaymentUpdateRequests"; import { handlePaymentStatusForAnalyticsTracking } from "./handlePaymentStatusForAnalyticsTracking"; +import { handlePaymentUpdateRequests } from "./handlePaymentUpdateRequests"; /** * Handle messages requests diff --git a/ts/features/messages/utils/__tests__/index.test.ts b/ts/features/messages/utils/__tests__/index.test.ts index 5dc50d66249..9a5ba2ae015 100644 --- a/ts/features/messages/utils/__tests__/index.test.ts +++ b/ts/features/messages/utils/__tests__/index.test.ts @@ -6,12 +6,10 @@ import { getRptIdStringFromPaymentData, initializeAndNavigateToWalletForPayment } from ".."; -import { PaymentData, UIMessageId } from "../../types"; +import { PaymentData } from "../../types"; import NavigationService from "../../../../navigation/NavigationService"; import ROUTES from "../../../../navigation/routes"; import { addUserSelectedPaymentRptId } from "../../store/actions"; -import { paymentInitializeState } from "../../../../store/actions/wallet/payment"; -import { PaymentAmount } from "../../../../../definitions/backend/PaymentAmount"; describe("getRptIdStringFromPaymentData", () => { it("should properly format the RptID", () => { @@ -40,11 +38,8 @@ describe("intializeAndNavigateToWalletForPayment", () => { const prenavigationCallback = jest.fn(); const analyticsCallback = jest.fn(); initializeAndNavigateToWalletForPayment( - false, - "01HRA60BRYF6BCHF17SMXG8PP2" as UIMessageId, paymentId, false, - undefined, true, {} as Dispatch, analyticsCallback, @@ -63,11 +58,8 @@ describe("intializeAndNavigateToWalletForPayment", () => { const prenavigationCallback = jest.fn(); const analyticsCallback = jest.fn(); initializeAndNavigateToWalletForPayment( - false, - "01HRA60BRYF6BCHF17SMXG8PP2" as UIMessageId, paymentId, false, - undefined, false, {} as Dispatch, analyticsCallback, @@ -85,14 +77,12 @@ describe("intializeAndNavigateToWalletForPayment", () => { }); }); it("should navigate to Payment Transaction Summary with default 0-amount", () => { - const navigateSpy = jest.spyOn(NavigationService, "navigate"); const organizationFiscalCode = "11111111111"; const auxDigit = "0"; const applicationCode = "22"; const iuv13 = "3333333333333"; const checkDigit = "44"; - const messageId = "01HRA60BRYF6BCHF17SMXG8PP2" as UIMessageId; const paymentId = `${organizationFiscalCode}${auxDigit}${applicationCode}${iuv13}${checkDigit}`; const dispatch = jest.fn(); @@ -101,11 +91,8 @@ describe("intializeAndNavigateToWalletForPayment", () => { const analyticsCallback = jest.fn(); initializeAndNavigateToWalletForPayment( - false, - messageId, paymentId, false, - undefined, true, dispatch, analyticsCallback, @@ -119,34 +106,14 @@ describe("intializeAndNavigateToWalletForPayment", () => { expect(dispatch.mock.calls[0][0]).toStrictEqual( addUserSelectedPaymentRptId(paymentId) ); - expect(dispatch.mock.calls[1][0]).toStrictEqual(paymentInitializeState()); - expect(navigateSpy).toHaveBeenCalledWith(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.PAYMENT_TRANSACTION_SUMMARY, - params: { - rptId: { - organizationFiscalCode, - paymentNoticeNumber: { - applicationCode, - auxDigit, - checkDigit, - iuv13 - } - }, - paymentStartOrigin: "message", - initialAmount: "0000", - messageId - } - }); }); it("should navigate to Payment Transaction Summary with default 0-amount and no prenavigation callback", () => { - const navigateSpy = jest.spyOn(NavigationService, "navigate"); const organizationFiscalCode = "11111111111"; const auxDigit = "0"; const applicationCode = "22"; const iuv13 = "3333333333333"; const checkDigit = "44"; - const messageId = "01HRA60BRYF6BCHF17SMXG8PP2" as UIMessageId; const paymentId = `${organizationFiscalCode}${auxDigit}${applicationCode}${iuv13}${checkDigit}`; const dispatch = jest.fn(); @@ -154,11 +121,8 @@ describe("intializeAndNavigateToWalletForPayment", () => { const analyticsCallback = jest.fn(); initializeAndNavigateToWalletForPayment( - false, - messageId, paymentId, false, - undefined, true, dispatch, analyticsCallback, @@ -170,36 +134,15 @@ describe("intializeAndNavigateToWalletForPayment", () => { expect(dispatch.mock.calls[0][0]).toStrictEqual( addUserSelectedPaymentRptId(paymentId) ); - expect(dispatch.mock.calls[1][0]).toStrictEqual(paymentInitializeState()); - expect(navigateSpy).toHaveBeenCalledWith(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.PAYMENT_TRANSACTION_SUMMARY, - params: { - rptId: { - organizationFiscalCode, - paymentNoticeNumber: { - applicationCode, - auxDigit, - checkDigit, - iuv13 - } - }, - paymentStartOrigin: "message", - initialAmount: "0000", - messageId - } - }); }); it("should navigate to Payment Transaction Summary with given amount and track PN event", () => { - const navigateSpy = jest.spyOn(NavigationService, "navigate"); const organizationFiscalCode = "11111111111"; const auxDigit = "0"; const applicationCode = "22"; const iuv13 = "3333333333333"; const checkDigit = "44"; - const messageId = "01HRA60BRYF6BCHF17SMXG8PP2" as UIMessageId; const paymentId = `${organizationFiscalCode}${auxDigit}${applicationCode}${iuv13}${checkDigit}`; - const paymentAmount = 199 as PaymentAmount; const dispatch = jest.fn(); const decodeErrorCallback = jest.fn(); @@ -207,11 +150,8 @@ describe("intializeAndNavigateToWalletForPayment", () => { const analyticsCallback = jest.fn(); initializeAndNavigateToWalletForPayment( - false, - messageId, paymentId, false, - paymentAmount, true, dispatch, analyticsCallback, @@ -225,91 +165,15 @@ describe("intializeAndNavigateToWalletForPayment", () => { expect(dispatch.mock.calls[0][0]).toStrictEqual( addUserSelectedPaymentRptId(paymentId) ); - expect(dispatch.mock.calls[1][0]).toStrictEqual(paymentInitializeState()); - expect(navigateSpy).toHaveBeenCalledWith(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.PAYMENT_TRANSACTION_SUMMARY, - params: { - rptId: { - organizationFiscalCode, - paymentNoticeNumber: { - applicationCode, - auxDigit, - checkDigit, - iuv13 - } - }, - paymentStartOrigin: "message", - initialAmount: `${paymentAmount}`, - messageId - } - }); - }); - it("should navigate to Payment Transaction Summary with given amount", () => { - const navigateSpy = jest.spyOn(NavigationService, "navigate"); - const organizationFiscalCode = "11111111111"; - const auxDigit = "0"; - const applicationCode = "22"; - const iuv13 = "3333333333333"; - const checkDigit = "44"; - - const messageId = "01HRA60BRYF6BCHF17SMXG8PP2" as UIMessageId; - const paymentId = `${organizationFiscalCode}${auxDigit}${applicationCode}${iuv13}${checkDigit}`; - const paymentAmount = 199 as PaymentAmount; - - const dispatch = jest.fn(); - const decodeErrorCallback = jest.fn(); - const prenavigationCallback = jest.fn(); - const analyticsCallback = jest.fn(); - - initializeAndNavigateToWalletForPayment( - false, - messageId, - paymentId, - false, - paymentAmount, - true, - dispatch, - analyticsCallback, - decodeErrorCallback, - prenavigationCallback - ); - expect(decodeErrorCallback).not.toHaveBeenCalled(); - expect(prenavigationCallback).toHaveBeenCalledTimes(1); - expect(analyticsCallback).toHaveBeenCalledTimes(1); - expect(dispatch.mock.calls).toHaveLength(2); - expect(dispatch.mock.calls[0][0]).toStrictEqual( - addUserSelectedPaymentRptId(paymentId) - ); - expect(dispatch.mock.calls[1][0]).toStrictEqual(paymentInitializeState()); - expect(navigateSpy).toHaveBeenCalledWith(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.PAYMENT_TRANSACTION_SUMMARY, - params: { - rptId: { - organizationFiscalCode, - paymentNoticeNumber: { - applicationCode, - auxDigit, - checkDigit, - iuv13 - } - }, - paymentStartOrigin: "message", - initialAmount: `${paymentAmount}`, - messageId - } - }); }); it("should navigate to Payment Transaction Summary with given amount but not dispatch an `addUserSelectedPaymentRptId`", () => { - const navigateSpy = jest.spyOn(NavigationService, "navigate"); const organizationFiscalCode = "11111111111"; const auxDigit = "0"; const applicationCode = "22"; const iuv13 = "3333333333333"; const checkDigit = "44"; - const messageId = "01HRA60BRYF6BCHF17SMXG8PP2" as UIMessageId; const paymentId = `${organizationFiscalCode}${auxDigit}${applicationCode}${iuv13}${checkDigit}`; - const paymentAmount = 199 as PaymentAmount; const dispatch = jest.fn(); const decodeErrorCallback = jest.fn(); @@ -317,11 +181,8 @@ describe("intializeAndNavigateToWalletForPayment", () => { const analyticsCallback = jest.fn(); initializeAndNavigateToWalletForPayment( - false, - messageId, paymentId, true, - paymentAmount, true, dispatch, analyticsCallback, @@ -332,80 +193,62 @@ describe("intializeAndNavigateToWalletForPayment", () => { expect(prenavigationCallback).toHaveBeenCalledTimes(1); expect(analyticsCallback).toHaveBeenCalledTimes(1); expect(dispatch.mock.calls).toHaveLength(1); - expect(dispatch.mock.calls[0][0]).toStrictEqual(paymentInitializeState()); - expect(navigateSpy).toHaveBeenCalledWith(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.PAYMENT_TRANSACTION_SUMMARY, - params: { - rptId: { - organizationFiscalCode, - paymentNoticeNumber: { - applicationCode, - auxDigit, - checkDigit, - iuv13 - } - }, - paymentStartOrigin: "message", - initialAmount: `${paymentAmount}`, - messageId - } - }); }); -}); -describe("duplicateSetAndAdd", () => { - it("should duplicate input set and add new item", () => { - const inputSet = new Set(); - const newItem = "newItem"; - const outputSet = duplicateSetAndAdd(inputSet, newItem); - expect(inputSet).not.toBe(outputSet); - expect(inputSet.size).toBe(outputSet.size - 1); - expect(inputSet.has(newItem)).toBe(false); - expect(outputSet.has(newItem)).toBe(true); - }); - it("should duplicate input set but not add an existing item", () => { - const inputSet = new Set(); - const existingItem = "existingItem"; - inputSet.add(existingItem); - const duplicatedItem = "existingItem"; - const outputSet = duplicateSetAndAdd(inputSet, duplicatedItem); - expect(inputSet).not.toBe(outputSet); - expect(inputSet.size).toBe(outputSet.size); - expect(inputSet.has(existingItem)).toBe(true); - expect(outputSet.has(existingItem)).toBe(true); - expect(inputSet.has(duplicatedItem)).toBe(true); - expect(outputSet.has(duplicatedItem)).toBe(true); + describe("duplicateSetAndAdd", () => { + it("should duplicate input set and add new item", () => { + const inputSet = new Set(); + const newItem = "newItem"; + const outputSet = duplicateSetAndAdd(inputSet, newItem); + expect(inputSet).not.toBe(outputSet); + expect(inputSet.size).toBe(outputSet.size - 1); + expect(inputSet.has(newItem)).toBe(false); + expect(outputSet.has(newItem)).toBe(true); + }); + it("should duplicate input set but not add an existing item", () => { + const inputSet = new Set(); + const existingItem = "existingItem"; + inputSet.add(existingItem); + const duplicatedItem = "existingItem"; + const outputSet = duplicateSetAndAdd(inputSet, duplicatedItem); + expect(inputSet).not.toBe(outputSet); + expect(inputSet.size).toBe(outputSet.size); + expect(inputSet.has(existingItem)).toBe(true); + expect(outputSet.has(existingItem)).toBe(true); + expect(inputSet.has(duplicatedItem)).toBe(true); + expect(outputSet.has(duplicatedItem)).toBe(true); + }); }); -}); -describe("duplicateSetAndRemove", () => { - it("should duplicate input set and remove existing item", () => { - const inputSet = new Set(); - const existingItem = "newItem"; - inputSet.add(existingItem); - const outputSet = duplicateSetAndRemove(inputSet, existingItem); - expect(inputSet).not.toBe(outputSet); - expect(inputSet.size).toBe(outputSet.size + 1); - expect(inputSet.has(existingItem)).toBe(true); - expect(outputSet.has(existingItem)).toBe(false); - }); - it("should duplicate input set and do nothing it the item does not exist", () => { - const inputSet = new Set(); - const existingItem = "existingItem"; - inputSet.add(existingItem); - const unmatchingItem = "unmathingItem"; - const outputSet = duplicateSetAndRemove(inputSet, unmatchingItem); - expect(inputSet).not.toBe(outputSet); - expect(inputSet.size).toBe(outputSet.size); - expect(inputSet.has(existingItem)).toBe(true); - expect(outputSet.has(existingItem)).toBe(true); - expect(inputSet.has(unmatchingItem)).toBe(false); - expect(outputSet.has(unmatchingItem)).toBe(false); + describe("duplicateSetAndRemove", () => { + it("should duplicate input set and remove existing item", () => { + const inputSet = new Set(); + const existingItem = "newItem"; + inputSet.add(existingItem); + const outputSet = duplicateSetAndRemove(inputSet, existingItem); + expect(inputSet).not.toBe(outputSet); + expect(inputSet.size).toBe(outputSet.size + 1); + expect(inputSet.has(existingItem)).toBe(true); + expect(outputSet.has(existingItem)).toBe(false); + }); + it("should duplicate input set and do nothing it the item does not exist", () => { + const inputSet = new Set(); + const existingItem = "existingItem"; + inputSet.add(existingItem); + const unmatchingItem = "unmathingItem"; + const outputSet = duplicateSetAndRemove(inputSet, unmatchingItem); + expect(inputSet).not.toBe(outputSet); + expect(inputSet.size).toBe(outputSet.size); + expect(inputSet.has(existingItem)).toBe(true); + expect(outputSet.has(existingItem)).toBe(true); + expect(inputSet.has(unmatchingItem)).toBe(false); + expect(outputSet.has(unmatchingItem)).toBe(false); + }); }); -}); -describe("emptyMessageArray", () => { - it("should return an empty array", () => { - expect(emptyMessageArray).toStrictEqual([]); + describe("emptyMessageArray", () => { + it("should return an empty array", () => { + expect(emptyMessageArray).toStrictEqual([]); + }); }); }); diff --git a/ts/features/messages/utils/index.ts b/ts/features/messages/utils/index.ts index 106b057ae50..53b91bb9b67 100644 --- a/ts/features/messages/utils/index.ts +++ b/ts/features/messages/utils/index.ts @@ -1,18 +1,11 @@ import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; import * as E from "fp-ts/lib/Either"; -import { - AmountInEuroCents, - RptIdFromString -} from "@pagopa/io-pagopa-commons/lib/pagopa"; +import { RptIdFromString } from "@pagopa/io-pagopa-commons/lib/pagopa"; import { Dispatch } from "redux"; import NavigationService from "../../../navigation/NavigationService"; import ROUTES from "../../../navigation/routes"; -import { paymentInitializeState } from "../../../store/actions/wallet/payment"; -import { PaymentData, UIMessage, UIMessageId } from "../types"; +import { PaymentData, UIMessage } from "../types"; import { NetworkError, getNetworkError } from "../../../utils/errors"; -import { PaymentAmount } from "../../../../definitions/backend/PaymentAmount"; -import { getAmountFromPaymentAmount } from "../../../utils/payment"; import { addUserSelectedPaymentRptId } from "../store/actions"; import { Action } from "../../../store/actions/types"; import { startPaymentFlowWithRptIdWorkaround } from "../../payments/checkout/tempWorkaround/pagoPaPaymentWorkaround"; @@ -32,11 +25,8 @@ export const getRptIdStringFromPaymentData = ( ): string => `${paymentData.payee.fiscalCode}${paymentData.noticeNumber}`; export const initializeAndNavigateToWalletForPayment = ( - isNewWalletSectionEnabled: boolean, - messageId: UIMessageId, paymentId: string, isPaidOrHasAnError: boolean, - paymentAmount: PaymentAmount | undefined, canNavigateToPayment: boolean, dispatch: Dispatch, analyticsCallback: (() => void) | undefined, @@ -69,34 +59,12 @@ export const initializeAndNavigateToWalletForPayment = ( dispatch(addUserSelectedPaymentRptId(paymentId)); } - if (isNewWalletSectionEnabled) { - startPaymentFlowWithRptIdWorkaround( - eitherRptId.right, - dispatch, - NavigationService.navigate, - { startOrigin: "message" } - ); - } else { - dispatch(paymentInitializeState()); - - const initialAmount = pipe( - paymentAmount, - O.fromNullable, - O.map(getAmountFromPaymentAmount), - O.flatten, - O.getOrElse(() => "0000" as AmountInEuroCents) - ); - - NavigationService.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.PAYMENT_TRANSACTION_SUMMARY, - params: { - rptId: eitherRptId.right, - paymentStartOrigin: "message", - initialAmount, - messageId - } - }); - } + startPaymentFlowWithRptIdWorkaround( + eitherRptId.right, + dispatch, + NavigationService.navigate, + { startOrigin: "message" } + ); }; export const duplicateSetAndAdd = (inputSet: Set, item: T) => { diff --git a/ts/features/newWallet/screens/WalletHomeScreen.tsx b/ts/features/newWallet/screens/WalletHomeScreen.tsx index f00a58430bc..d0a37967967 100644 --- a/ts/features/newWallet/screens/WalletHomeScreen.tsx +++ b/ts/features/newWallet/screens/WalletHomeScreen.tsx @@ -24,6 +24,11 @@ import { } from "../../itwallet/analytics"; import { useOnFirstRender } from "../../../utils/hooks/useOnFirstRender"; +export type WalletHomeNavigationParams = Readonly<{ + newMethodAdded: boolean; + keyFrom?: string; +}>; + type Props = IOStackNavigationRouteProps; const WalletHomeScreen = ({ route }: Props) => { diff --git a/ts/features/payments/barcode/screens/PaymentsBarcodeChoiceScreen.tsx b/ts/features/payments/barcode/screens/PaymentsBarcodeChoiceScreen.tsx index c463be1a795..cf95ab0fbaa 100644 --- a/ts/features/payments/barcode/screens/PaymentsBarcodeChoiceScreen.tsx +++ b/ts/features/payments/barcode/screens/PaymentsBarcodeChoiceScreen.tsx @@ -5,12 +5,7 @@ import { VSpacer } from "@pagopa/io-app-design-system"; import { PaymentNoticeNumberFromString } from "@pagopa/io-pagopa-commons/lib/pagopa"; -import { - RouteProp, - useFocusEffect, - useNavigation, - useRoute -} from "@react-navigation/native"; +import { RouteProp, useFocusEffect, useRoute } from "@react-navigation/native"; import * as A from "fp-ts/lib/Array"; import { contramap } from "fp-ts/lib/Ord"; import { pipe } from "fp-ts/lib/function"; @@ -19,16 +14,6 @@ import React from "react"; import { ScrollView } from "react-native-gesture-handler"; import BaseScreenComponent from "../../../../components/screens/BaseScreenComponent"; import I18n from "../../../../i18n"; -import { - AppParamsList, - IOStackNavigationProp -} from "../../../../navigation/params/AppParamsList"; -import ROUTES from "../../../../navigation/routes"; -import { - PaymentStartOrigin, - paymentInitializeState -} from "../../../../store/actions/wallet/payment"; -import { useIODispatch } from "../../../../store/hooks"; import * as analytics from "../../../barcode/analytics"; import { PagoPaBarcode } from "../../../barcode/types/IOBarcode"; import { usePagoPaPayment } from "../../checkout/hooks/usePagoPaPayment"; @@ -45,11 +30,7 @@ const sortByAmount = pipe( ); const PaymentsBarcodeChoiceScreen = () => { - const dispatch = useIODispatch(); - const navigation = useNavigation>(); - - const { startPaymentFlowWithRptId, isNewWalletSectionEnabled } = - usePagoPaPayment(); + const { startPaymentFlowWithRptId } = usePagoPaPayment(); useFocusEffect( React.useCallback(() => { @@ -63,28 +44,16 @@ const PaymentsBarcodeChoiceScreen = () => { const { barcodes } = route.params; const handleBarcodeSelected = (barcode: PagoPaBarcode) => { - const paymentStartOrigin: PaymentStartOrigin = + const paymentStartOrigin = barcode.format === "DATA_MATRIX" ? "poste_datamatrix_scan" : "qrcode_scan"; analytics.trackBarcodeMultipleCodesSelection(); - if (isNewWalletSectionEnabled) { - startPaymentFlowWithRptId(barcode.rptId, { - onSuccess: "showTransaction", - startOrigin: paymentStartOrigin - }); - } else { - dispatch(paymentInitializeState()); - navigation.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.PAYMENT_TRANSACTION_SUMMARY, - params: { - initialAmount: barcode.amount, - rptId: barcode.rptId, - paymentStartOrigin - } - }); - } + startPaymentFlowWithRptId(barcode.rptId, { + onSuccess: "showTransaction", + startOrigin: paymentStartOrigin + }); }; const renderBarcodeItem = (barcode: PagoPaBarcode) => { diff --git a/ts/features/payments/barcode/screens/PaymentsBarcodeScanScreen.tsx b/ts/features/payments/barcode/screens/PaymentsBarcodeScanScreen.tsx index 2ee00fe4038..c6c8f62af3f 100644 --- a/ts/features/payments/barcode/screens/PaymentsBarcodeScanScreen.tsx +++ b/ts/features/payments/barcode/screens/PaymentsBarcodeScanScreen.tsx @@ -13,11 +13,8 @@ import { AppParamsList, IOStackNavigationProp } from "../../../../navigation/params/AppParamsList"; -import ROUTES from "../../../../navigation/routes"; -import { paymentInitializeState } from "../../../../store/actions/wallet/payment"; -import { useIODispatch, useIOSelector } from "../../../../store/hooks"; +import { useIOSelector } from "../../../../store/hooks"; import { barcodesScannerConfigSelector } from "../../../../store/reducers/backendStatus"; -import { isDesignSystemEnabledSelector } from "../../../../store/reducers/persistedPreferences"; import { BarcodeFailure, BarcodeScanBaseScreenComponent, @@ -43,14 +40,11 @@ const contextualHelpMarkdown: ContextualHelpPropsMarkdown = { const PaymentsBarcodeScanScreen = () => { const navigation = useNavigation>(); - const dispatch = useIODispatch(); const { dataMatrixPosteEnabled } = useIOSelector( barcodesScannerConfigSelector ); - const isDesignSystemEnabled = useIOSelector(isDesignSystemEnabledSelector); - const { startPaymentFlowWithRptId, isNewWalletSectionEnabled } = - usePagoPaPayment(); + const { startPaymentFlowWithRptId } = usePagoPaPayment(); const barcodeFormats: Array = IO_BARCODE_ALL_FORMATS.filter( format => (format === "DATA_MATRIX" ? dataMatrixPosteEnabled : true) @@ -93,41 +87,13 @@ const PaymentsBarcodeScanScreen = () => { const barcode = pagoPaBarcodes[0]; if (barcode.type === "PAGOPA") { - if (isNewWalletSectionEnabled) { - startPaymentFlowWithRptId(barcode.rptId, { - onSuccess: "showTransaction", - startOrigin: - barcode.format === "DATA_MATRIX" - ? "poste_datamatrix_scan" - : "qrcode_scan" - }); - } else { - dispatch(paymentInitializeState()); - switch (barcode.format) { - case "QR_CODE": - navigation.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.PAYMENT_TRANSACTION_SUMMARY, - params: { - initialAmount: barcode.amount, - rptId: barcode.rptId, - paymentStartOrigin: "qrcode_scan" - } - }); - break; - case "DATA_MATRIX": - void mixpanelTrack("WALLET_SCAN_POSTE_DATAMATRIX_SUCCESS"); - navigation.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.PAYMENT_TRANSACTION_SUMMARY, - params: { - initialAmount: barcode.amount, - rptId: barcode.rptId, - paymentStartOrigin: "poste_datamatrix_scan" - } - }); - - break; - } - } + startPaymentFlowWithRptId(barcode.rptId, { + onSuccess: "showTransaction", + startOrigin: + barcode.format === "DATA_MATRIX" + ? "poste_datamatrix_scan" + : "qrcode_scan" + }); } }; @@ -145,16 +111,9 @@ const PaymentsBarcodeScanScreen = () => { const handleManualInputPressed = () => { analytics.trackBarcodeManualEntryPath("avviso"); - if (isDesignSystemEnabled || isNewWalletSectionEnabled) { - navigation.navigate(PaymentsCheckoutRoutes.PAYMENT_CHECKOUT_NAVIGATOR, { - screen: PaymentsCheckoutRoutes.PAYMENT_CHECKOUT_INPUT_NOTICE_NUMBER - }); - } else { - navigation.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.PAYMENT_MANUAL_DATA_INSERTION, - params: {} - }); - } + navigation.navigate(PaymentsCheckoutRoutes.PAYMENT_CHECKOUT_NAVIGATOR, { + screen: PaymentsCheckoutRoutes.PAYMENT_CHECKOUT_INPUT_NOTICE_NUMBER + }); }; const { diff --git a/ts/features/payments/checkout/screens/WalletPaymentInputFiscalCodeScreen.tsx b/ts/features/payments/checkout/screens/WalletPaymentInputFiscalCodeScreen.tsx index 8e0a1ed405f..0d05bf0a4c0 100644 --- a/ts/features/payments/checkout/screens/WalletPaymentInputFiscalCodeScreen.tsx +++ b/ts/features/payments/checkout/screens/WalletPaymentInputFiscalCodeScreen.tsx @@ -8,7 +8,6 @@ import { VSpacer } from "@pagopa/io-app-design-system"; import { - AmountInEuroCents, PaymentNoticeNumberFromString, RptId } from "@pagopa/io-pagopa-commons/lib/pagopa"; @@ -31,9 +30,6 @@ import { AppParamsList, IOStackNavigationProp } from "../../../../navigation/params/AppParamsList"; -import ROUTES from "../../../../navigation/routes"; -import { paymentInitializeState } from "../../../../store/actions/wallet/payment"; -import { useIODispatch } from "../../../../store/hooks"; import themeVariables from "../../../../theme/variables"; import { setAccessibilityFocus } from "../../../../utils/accessibility"; import { emptyContextualHelp } from "../../../../utils/emptyContextualHelp"; @@ -62,11 +58,9 @@ type InputState = { const WalletPaymentInputFiscalCodeScreen = () => { const { params } = useRoute(); - const dispatch = useIODispatch(); const navigation = useNavigation>(); - const { startPaymentFlowWithRptId, isNewWalletSectionEnabled } = - usePagoPaPayment(); + const { startPaymentFlowWithRptId } = usePagoPaPayment(); const [inputState, setInputState] = React.useState({ fiscalCodeText: "", @@ -90,23 +84,10 @@ const WalletPaymentInputFiscalCodeScreen = () => { navigation.popToTop(); navigation.pop(); // Navigate to the payment details screen (payment verification) - if (isNewWalletSectionEnabled) { - startPaymentFlowWithRptId(rptId, { - onSuccess: "showTransaction", - startOrigin: "manual_insertion" - }); - } else { - dispatch(paymentInitializeState()); - navigation.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.PAYMENT_TRANSACTION_SUMMARY, - params: { - // Set the initial amount to a fixed value (1) because it is not used - initialAmount: "1" as AmountInEuroCents, - rptId, - paymentStartOrigin: "manual_insertion" - } - }); - } + startPaymentFlowWithRptId(rptId, { + onSuccess: "showTransaction", + startOrigin: "manual_insertion" + }); }) ); }; diff --git a/ts/features/payments/common/store/actions/index.ts b/ts/features/payments/common/store/actions/index.ts index 6041c3a030d..8ea736e9b50 100644 --- a/ts/features/payments/common/store/actions/index.ts +++ b/ts/features/payments/common/store/actions/index.ts @@ -15,6 +15,7 @@ import { PaymentsTransactionBizEventsActions } from "../../../bizEventsTransacti import { NetworkError } from "../../../../../utils/errors"; import { SessionTokenResponse } from "../../../../../../definitions/pagopa/platform/SessionTokenResponse"; import { Action } from "../../../../../store/actions/types"; +import { LegacyTransactionsActions } from "../../../transaction/store/actions/legacyTransactionsActions"; import { PaymentsBackoffRetry } from "../../types/PaymentsBackoffRetry"; export const paymentsGetPagoPaPlatformSessionTokenAction = createAsyncAction( @@ -57,4 +58,5 @@ export type PaymentsActions = | PaymentsHistoryActions | PaymentsHomeActions | PaymentsWalletActions + | LegacyTransactionsActions | PaymentsTransactionBizEventsActions; diff --git a/ts/features/payments/common/store/reducers/index.ts b/ts/features/payments/common/store/reducers/index.ts index 6cc053deec2..2636a8158d6 100644 --- a/ts/features/payments/common/store/reducers/index.ts +++ b/ts/features/payments/common/store/reducers/index.ts @@ -12,8 +12,8 @@ import historyReducer, { import onboardingReducer, { PaymentsOnboardingState } from "../../../onboarding/store/reducers"; -import transactionReducer, { - PaymentsTransactionState +import legacyTransactionReducer, { + PaymentsLegacyTransactionState } from "../../../transaction/store/reducers"; import homeReducer, { PaymentsHomeState } from "../../../home/store/reducers"; import paymentsWalletReducer, { @@ -33,7 +33,7 @@ export type PaymentsState = { onboarding: PaymentsOnboardingState; details: PaymentsMethodDetailsState & PersistPartial; checkout: PaymentsCheckoutState; - transaction: PaymentsTransactionState; + legacyTransaction: PaymentsLegacyTransactionState; history: PaymentsHistoryState & PersistPartial; home: PaymentsHomeState & PersistPartial; wallet: PaymentsWalletState; @@ -46,7 +46,7 @@ const paymentsReducer = combineReducers({ onboarding: onboardingReducer, details: detailsReducer, checkout: paymentReducer, - transaction: transactionReducer, + legacyTransaction: legacyTransactionReducer, history: historyReducer, home: homeReducer, wallet: paymentsWalletReducer, diff --git a/ts/features/payments/details/components/WalletDetailsPaymentMethodSettings.tsx b/ts/features/payments/details/components/WalletDetailsPaymentMethodSettings.tsx index 587e335baee..0406600d583 100644 --- a/ts/features/payments/details/components/WalletDetailsPaymentMethodSettings.tsx +++ b/ts/features/payments/details/components/WalletDetailsPaymentMethodSettings.tsx @@ -2,7 +2,6 @@ import { H6, IOSpacingScale } from "@pagopa/io-app-design-system"; import * as React from "react"; import { WalletInfo } from "../../../../../definitions/pagopa/walletv3/WalletInfo"; import ItemSeparatorComponent from "../../../../components/ItemSeparatorComponent"; -import FavoritePaymentMethodSwitch from "../../../../components/wallet/FavoriteMethodSwitch"; import I18n from "../../../../i18n"; import WalletDetailsPagoPaPaymentCapability from "./WalletDetailsPagoPaPaymentCapability"; @@ -12,8 +11,6 @@ const componentVerticalSpacing: IOSpacingScale = 12; /** * This component allows the user to choose and change the common settings for a payment methods - * The {@link FavoritePaymentMethodSwitch} should be rendered only if the payment method has the capability pagoPA and - * the payment are active (paymentMethod.pagoPA === true) * @param props * @constructor */ diff --git a/ts/features/payments/transaction/saga/handleGetTransactionDetails.ts b/ts/features/payments/transaction/saga/handleGetTransactionDetails.ts index 8ded1471ddf..8245b6f055e 100644 --- a/ts/features/payments/transaction/saga/handleGetTransactionDetails.ts +++ b/ts/features/payments/transaction/saga/handleGetTransactionDetails.ts @@ -2,8 +2,8 @@ import * as pot from "@pagopa/ts-commons/lib/pot"; import { put, select } from "typed-redux-saga/macro"; import { ActionType } from "typesafe-actions"; import { getPaymentsTransactionDetailsAction } from "../store/actions"; -import { getTransactions } from "../../../../store/reducers/wallet/transactions"; import { getGenericError } from "../../../../utils/errors"; +import { getTransactions } from "../store/selectors"; /** * Handle the remote call to get the transaction details @@ -18,8 +18,8 @@ export function* handleGetTransactionDetails( // TODO: Add the whole logic here to call the BIZ Event API as soon as it will be available and replace the following code const transactions = yield* select(getTransactions); const transactionDetails = pot.toUndefined( - pot.map(transactions, transactions => - transactions.find(trx => trx.id === action.payload.transactionId) + pot.map(transactions, legacyTansactions => + legacyTansactions.find(trx => trx.id === action.payload.transactionId) ) ); if (transactionDetails) { diff --git a/ts/features/payments/transaction/screens/PaymentsTransactionDetailsScreen.tsx b/ts/features/payments/transaction/screens/PaymentsTransactionDetailsScreen.tsx index 42ed81e31f8..41496dcca59 100644 --- a/ts/features/payments/transaction/screens/PaymentsTransactionDetailsScreen.tsx +++ b/ts/features/payments/transaction/screens/PaymentsTransactionDetailsScreen.tsx @@ -5,7 +5,6 @@ import * as React from "react"; import { Dimensions, StyleSheet, View } from "react-native"; import FocusAwareStatusBar from "../../../../components/ui/FocusAwareStatusBar"; import I18n from "../../../../i18n"; -import { fetchPsp } from "../../../../store/actions/wallet/transactions"; import { useIODispatch, useIOSelector } from "../../../../store/hooks"; import { Psp } from "../../../../types/pagopa"; import { emptyContextualHelp } from "../../../../utils/emptyContextualHelp"; @@ -16,6 +15,7 @@ import { PaymentsTransactionParamsList } from "../navigation/params"; import { getPaymentsTransactionDetailsAction } from "../store/actions"; import { walletTransactionDetailsPotSelector } from "../store/selectors"; import { IOScrollViewWithLargeHeader } from "../../../../components/ui/IOScrollViewWithLargeHeader"; +import { fetchPsp } from "../store/actions/legacyTransactionsActions"; export type PaymentsTransactionDetailsScreenParams = { transactionId: number; diff --git a/ts/features/payments/transaction/screens/PaymentsTransactionListScreen.tsx b/ts/features/payments/transaction/screens/PaymentsTransactionListScreen.tsx index bf602f2570d..5c806b6a647 100644 --- a/ts/features/payments/transaction/screens/PaymentsTransactionListScreen.tsx +++ b/ts/features/payments/transaction/screens/PaymentsTransactionListScreen.tsx @@ -22,17 +22,18 @@ import { useHeaderSecondLevel } from "../../../../hooks/useHeaderSecondLevel"; import { useOnFirstRender } from "../../../../utils/hooks/useOnFirstRender"; import I18n from "../../../../i18n"; import { PaymentsTransactionRoutes } from "../navigation/routes"; + import { + isPaymentsLegacyTransactionsEmptySelector, areMoreTransactionsAvailable, getTransactionsLoadedLength, latestTransactionsSelector -} from "../../../../store/reducers/wallet/transactions"; -import { isPaymentsLegacyTransactionsEmptySelector } from "../store/selectors"; -import { fetchTransactionsRequestWithExpBackoff } from "../../../../store/actions/wallet/transactions"; +} from "../store/selectors"; import { Transaction } from "../../../../types/pagopa"; import { PaymentsLegacyListItemTransaction } from "../components/PaymentsLegacyListItemTransaction"; import { usePaymentsLegacyAttachmentBottomSheet } from "../components/PaymentsLegacyAttachmentBottomSheet"; import { PaymentsLegacyTransactionsEmptyContent } from "../components/PaymentsLegacyTransactionsEmptyContent"; +import { fetchTransactionsRequestWithExpBackoff } from "../store/actions/legacyTransactionsActions"; import * as analytics from "../analytics"; export type PaymentsTransactionListScreenProps = RouteProp< diff --git a/ts/store/actions/wallet/transactions.ts b/ts/features/payments/transaction/store/actions/legacyTransactionsActions.ts similarity index 96% rename from ts/store/actions/wallet/transactions.ts rename to ts/features/payments/transaction/store/actions/legacyTransactionsActions.ts index 01200414f1b..29f867c9d7a 100644 --- a/ts/store/actions/wallet/transactions.ts +++ b/ts/features/payments/transaction/store/actions/legacyTransactionsActions.ts @@ -4,7 +4,7 @@ import { createAsyncAction, createStandardAction } from "typesafe-actions"; -import { Psp, Transaction } from "../../../types/pagopa"; +import { Psp, Transaction } from "../../../../../types/pagopa"; // // fetch all transactions @@ -81,7 +81,7 @@ export const fetchPsp = createAsyncAction( "FETCH_PSP_FAILURE" )(); -export type TransactionsActions = +export type LegacyTransactionsActions = | ActionType | ActionType | ActionType diff --git a/ts/features/payments/transaction/store/reducers/index.ts b/ts/features/payments/transaction/store/reducers/index.ts index df3b422f959..2148bedfaad 100644 --- a/ts/features/payments/transaction/store/reducers/index.ts +++ b/ts/features/payments/transaction/store/reducers/index.ts @@ -1,23 +1,36 @@ import * as pot from "@pagopa/ts-commons/lib/pot"; +import { pipe } from "fp-ts/lib/function"; +import * as O from "fp-ts/lib/Option"; import { getType } from "typesafe-actions"; import { Action } from "../../../../../store/actions/types"; import { NetworkError } from "../../../../../utils/errors"; import { Transaction } from "../../../../../types/pagopa"; import { getPaymentsTransactionDetailsAction } from "../actions"; +import { IndexedById, toIndexed } from "../../../../../store/helpers/indexer"; +import { + fetchTransactionsFailure, + fetchTransactionsRequest, + fetchTransactionsRequestWithExpBackoff, + fetchTransactionsSuccess +} from "../actions/legacyTransactionsActions"; -export type PaymentsTransactionState = { +export type PaymentsLegacyTransactionState = { details: pot.Pot; + transactions: pot.Pot, Error>; + total: pot.Pot; }; -const INITIAL_STATE: PaymentsTransactionState = { - details: pot.noneLoading +const INITIAL_STATE: PaymentsLegacyTransactionState = { + details: pot.noneLoading, + transactions: pot.none, + total: pot.none }; const reducer = ( - state: PaymentsTransactionState = INITIAL_STATE, + state: PaymentsLegacyTransactionState = INITIAL_STATE, action: Action -): PaymentsTransactionState => { +): PaymentsLegacyTransactionState => { switch (action.type) { // GET TRANSACTION DETAILS case getType(getPaymentsTransactionDetailsAction.request): @@ -40,6 +53,45 @@ const reducer = ( ...state, details: pot.none }; + + // FETCH LEGACY TRANSACTIONS + case getType(fetchTransactionsRequestWithExpBackoff): + case getType(fetchTransactionsRequest): + return { + ...state, + transactions: pot.toLoading(state.transactions), + total: pot.toLoading(state.total) + }; + + case getType(fetchTransactionsSuccess): + const prevTransactions = pot.getOrElse>( + state.transactions, + {} + ); + const total = { + ...prevTransactions, + ...toIndexed(action.payload.data, _ => _.id) + }; + return { + ...state, + transactions: pot.some(total as IndexedById), + total: pot.some( + pipe( + action.payload.total, + O.fold( + () => 0, + s => s + ) + ) + ) + }; + + case getType(fetchTransactionsFailure): + return { + ...state, + transactions: pot.toError(state.transactions, action.payload), + total: pot.toError(state.total, action.payload) + }; } return state; }; diff --git a/ts/features/payments/transaction/store/saga/fetchPspRequestHandler.ts b/ts/features/payments/transaction/store/saga/fetchPspRequestHandler.ts new file mode 100644 index 00000000000..e81d049aaba --- /dev/null +++ b/ts/features/payments/transaction/store/saga/fetchPspRequestHandler.ts @@ -0,0 +1,65 @@ +import * as E from "fp-ts/lib/Either"; +import { call, put } from "typed-redux-saga/macro"; +import { ActionType } from "typesafe-actions"; +import { PaymentManagerClient } from "../../../../../api/pagopa"; +import { PaymentManagerToken } from "../../../../../types/pagopa"; +import { SessionManager } from "../../../../../utils/SessionManager"; +import { fetchPsp } from "../actions/legacyTransactionsActions"; +import { + ReduxSagaEffect, + SagaCallReturnType +} from "../../../../../types/utils"; +import { readablePrivacyReport } from "../../../../../utils/reporters"; +import { + convertUnknownToError, + getNetworkError, + isTimeoutError +} from "../../../../../utils/errors"; +import { checkCurrentSession } from "../../../../../store/actions/authentication"; + +/** + * Handles fetchPspRequest + */ +export function* fetchPspRequestHandler( + pagoPaClient: PaymentManagerClient, + pmSessionManager: SessionManager, + action: ActionType<(typeof fetchPsp)["request"]> +): Generator { + const request = pmSessionManager.withRefresh( + pagoPaClient.getPsp(action.payload.idPsp) + ); + try { + const response: SagaCallReturnType = yield* call(request); + + if (E.isRight(response)) { + if (response.right.status === 200) { + const psp = response.right.value.data; + const successAction = fetchPsp.success({ + idPsp: action.payload.idPsp, + psp + }); + yield* put(successAction); + if (action.payload.onSuccess) { + action.payload.onSuccess(successAction); + } + } else { + throw Error(`response status ${response.right.status}`); + } + } else { + throw Error(readablePrivacyReport(response.left)); + } + } catch (e) { + const failureAction = fetchPsp.failure({ + idPsp: action.payload.idPsp, + error: convertUnknownToError(e) + }); + if (isTimeoutError(getNetworkError(e))) { + // check if also the IO session is expired + yield* put(checkCurrentSession.request()); + } + yield* put(failureAction); + if (action.payload.onFailure) { + action.payload.onFailure(failureAction); + } + } +} diff --git a/ts/features/payments/transaction/store/saga/fetchTransactionRequestHandler.ts b/ts/features/payments/transaction/store/saga/fetchTransactionRequestHandler.ts new file mode 100644 index 00000000000..509aab48c40 --- /dev/null +++ b/ts/features/payments/transaction/store/saga/fetchTransactionRequestHandler.ts @@ -0,0 +1,53 @@ +import * as E from "fp-ts/lib/Either"; +import { call, put } from "typed-redux-saga/macro"; +import { ActionType } from "typesafe-actions"; +import { PaymentManagerClient } from "../../../../../api/pagopa"; +import { PaymentManagerToken } from "../../../../../types/pagopa"; +import { SessionManager } from "../../../../../utils/SessionManager"; +import { + fetchTransactionFailure, + fetchTransactionRequest, + fetchTransactionSuccess +} from "../actions/legacyTransactionsActions"; +import { + ReduxSagaEffect, + SagaCallReturnType +} from "../../../../../types/utils"; +import { readablePrivacyReport } from "../../../../../utils/reporters"; +import { + convertUnknownToError, + getNetworkError, + isTimeoutError +} from "../../../../../utils/errors"; +import { checkCurrentSession } from "../../../../../store/actions/authentication"; + +/** + * Handles fetchTransactionRequest + */ +export function* fetchTransactionRequestHandler( + pagoPaClient: PaymentManagerClient, + pmSessionManager: SessionManager, + action: ActionType +): Generator { + const request = pmSessionManager.withRefresh( + pagoPaClient.getTransaction(action.payload) + ); + try { + const response: SagaCallReturnType = yield* call(request); + if (E.isRight(response)) { + if (response.right.status === 200) { + yield* put(fetchTransactionSuccess(response.right.value.data)); + } else { + throw Error(`response status ${response.right.status}`); + } + } else { + throw Error(readablePrivacyReport(response.left)); + } + } catch (e) { + if (isTimeoutError(getNetworkError(e))) { + // check if also the IO session is expired + yield* put(checkCurrentSession.request()); + } + yield* put(fetchTransactionFailure(convertUnknownToError(e))); + } +} diff --git a/ts/features/payments/transaction/store/saga/fetchTransactionsRequestHandler.ts b/ts/features/payments/transaction/store/saga/fetchTransactionsRequestHandler.ts new file mode 100644 index 00000000000..6c3fa98445b --- /dev/null +++ b/ts/features/payments/transaction/store/saga/fetchTransactionsRequestHandler.ts @@ -0,0 +1,59 @@ +import * as E from "fp-ts/lib/Either"; +import * as O from "fp-ts/lib/Option"; +import { call, put } from "typed-redux-saga/macro"; +import { ActionType } from "typesafe-actions"; +import { PaymentManagerClient } from "../../../../../api/pagopa"; +import { PaymentManagerToken } from "../../../../../types/pagopa"; +import { SessionManager } from "../../../../../utils/SessionManager"; +import { + fetchTransactionsFailure, + fetchTransactionsRequest, + fetchTransactionsSuccess +} from "../actions/legacyTransactionsActions"; +import { + ReduxSagaEffect, + SagaCallReturnType +} from "../../../../../types/utils"; +import { readablePrivacyReport } from "../../../../../utils/reporters"; +import { + convertUnknownToError, + getNetworkError, + isTimeoutError +} from "../../../../../utils/errors"; +import { checkCurrentSession } from "../../../../../store/actions/authentication"; + +/** + * Handles fetchTransactionsRequest + */ +export function* fetchTransactionsRequestHandler( + pagoPaClient: PaymentManagerClient, + pmSessionManager: SessionManager, + action: ActionType +): Generator { + const request = pmSessionManager.withRefresh( + pagoPaClient.getTransactions(action.payload.start) + ); + try { + const response: SagaCallReturnType = yield* call(request); + if (E.isRight(response)) { + if (response.right.status === 200) { + yield* put( + fetchTransactionsSuccess({ + data: response.right.value.data, + total: O.fromNullable(response.right.value.total) + }) + ); + } else { + throw Error(`response status ${response.right.status}`); + } + } else { + throw Error(readablePrivacyReport(response.left)); + } + } catch (e) { + if (isTimeoutError(getNetworkError(e))) { + // check if also the IO session is expired + yield* put(checkCurrentSession.request()); + } + yield* put(fetchTransactionsFailure(convertUnknownToError(e))); + } +} diff --git a/ts/features/payments/transaction/store/saga/index.ts b/ts/features/payments/transaction/store/saga/index.ts new file mode 100644 index 00000000000..746c0c40d57 --- /dev/null +++ b/ts/features/payments/transaction/store/saga/index.ts @@ -0,0 +1,99 @@ +/** + * A saga that manages the Wallet. + */ +import * as E from "fp-ts/lib/Either"; +import * as O from "fp-ts/lib/Option"; +import { call, put, select, takeLatest } from "typed-redux-saga/macro"; +import { ActionType, getType } from "typesafe-actions"; +import { ReduxSagaEffect } from "../../../../../types/utils"; +import { PaymentManagerClient } from "../../../../../api/pagopa"; +import { defaultRetryingFetch } from "../../../../../utils/fetch"; +import { fetchPaymentManagerLongTimeout } from "../../../../../config"; +import { SessionManager } from "../../../../../utils/SessionManager"; +import { isProfileEmailValidatedSelector } from "../../../../../store/reducers/profile"; +import { + fetchPsp, + fetchTransactionRequest, + fetchTransactionsFailure, + fetchTransactionsRequest, + fetchTransactionsRequestWithExpBackoff +} from "../actions/legacyTransactionsActions"; +import { waitBackoffError } from "../../../../../utils/backoffError"; +import { fetchTransactionsRequestHandler } from "./fetchTransactionsRequestHandler"; +import { fetchTransactionRequestHandler } from "./fetchTransactionRequestHandler"; +import { fetchPspRequestHandler } from "./fetchPspRequestHandler"; + +export function* watchLegacyTransactionSaga( + walletToken: string, + paymentManagerUrlPrefix: string +): Generator { + // Client for the PagoPA PaymentManager + const paymentManagerClient: PaymentManagerClient = PaymentManagerClient( + paymentManagerUrlPrefix, + walletToken, + // despite both fetch have same configuration, keeping both ensures possible modding + defaultRetryingFetch(fetchPaymentManagerLongTimeout, 0), + defaultRetryingFetch(fetchPaymentManagerLongTimeout, 0) + ); + + // Helper function that requests a new session token from the PaymentManager. + // When calling the PM APIs, we must use separate session, generated from the + // walletToken. + const getPaymentManagerSession = async () => { + try { + const response = await paymentManagerClient.getSession(walletToken); + if (E.isRight(response) && response.right.status === 200) { + return O.some(response.right.value.data.sessionToken); + } + return O.none; + } catch { + return O.none; + } + }; + + // The session manager for the PagoPA PaymentManager (PM) will manage the + // refreshing of the PM session when calling its APIs, keeping a shared token + // and serializing the refresh requests. + const pmSessionManager = new SessionManager(getPaymentManagerSession); + // check if the current profile (this saga starts only when the user is logged in) + // has an email address validated + const isEmailValidated = yield* select(isProfileEmailValidatedSelector); + yield* call(pmSessionManager.setSessionEnabled, isEmailValidated); + // + // Sagas + // + + // API requests + // + + yield* takeLatest( + getType(fetchTransactionsRequest), + fetchTransactionsRequestHandler, + paymentManagerClient, + pmSessionManager + ); + + yield* takeLatest( + getType(fetchTransactionsRequestWithExpBackoff), + function* ( + action: ActionType + ) { + yield* call(waitBackoffError, fetchTransactionsFailure); + yield* put(fetchTransactionsRequest(action.payload)); + } + ); + + yield* takeLatest( + getType(fetchTransactionRequest), + fetchTransactionRequestHandler, + paymentManagerClient, + pmSessionManager + ); + + yield* takeLatest( + getType(fetchPsp.request), + fetchPspRequestHandler, + paymentManagerClient, + pmSessionManager + ); +} diff --git a/ts/features/payments/transaction/store/selectors/index.ts b/ts/features/payments/transaction/store/selectors/index.ts index c28b4572c6c..fc1cae9ea7a 100644 --- a/ts/features/payments/transaction/store/selectors/index.ts +++ b/ts/features/payments/transaction/store/selectors/index.ts @@ -1,15 +1,98 @@ -import _ from "lodash"; +import _, { values } from "lodash"; import * as pot from "@pagopa/ts-commons/lib/pot"; import { createSelector } from "reselect"; +import { IndexedById } from "../../../../../store/helpers/indexer"; +import { isSuccessTransaction, Transaction } from "../../../../../types/pagopa"; + import { GlobalState } from "../../../../../store/reducers/types"; -import { latestTransactionsSelector } from "../../../../../store/reducers/wallet/transactions"; + +/** + * The transactions selector will truncate the list at this length + */ +const MAX_TRANSACTIONS_IN_LIST = 500; + +export type TransactionsState = Readonly<{ + transactions: pot.Pot, Error>; + total: pot.Pot; +}>; const walletTransactionSelector = (state: GlobalState) => - state.features.payments.transaction; + state.features.payments.legacyTransaction; export const walletTransactionDetailsPotSelector = (state: GlobalState) => walletTransactionSelector(state).details; +const potTransactions = (state: GlobalState) => + state.features.payments.legacyTransaction.transactions; + +// selectors +export const getTransactions = createSelector( + potTransactions, + transactionsPot => + pot.map( + transactionsPot, + txs => + values(txs).filter(value => + isSuccessTransaction(value) + ) as ReadonlyArray + ) +); + +export const latestTransactionsSelector = createSelector( + getTransactions, + transactionsPot => + pot.map( + transactionsPot, + transactions => + [...transactions] + .sort((a, b) => + // FIXME: code here is checking for NaN assuming creation dates may + // be undefined, but since we override the pagoPA Wallet + // type to force creation dates to always be defined and we + // use that new type for parsing responses, we ignore + // wallets with undefined creation dates... so the check + // is unnecessary. + + isNaN(a.created as any) || isNaN(b.created as any) + ? -1 // define behavior for undefined creation dates (pagoPA allows these to be undefined) + : b.created.toISOString().localeCompare(a.created.toISOString()) + ) + .filter(t => t.statusMessage !== "rifiutato") + .slice(0, MAX_TRANSACTIONS_IN_LIST) // WIP no magic numbers + ) +); + +// return true if there are more transactions to load +export const areMoreTransactionsAvailable = (state: GlobalState): boolean => + pot.getOrElse( + pot.map( + state.features.payments.legacyTransaction.transactions, + transactions => + pot.getOrElse( + pot.map( + state.features.payments.legacyTransaction.total, + t => + Object.keys(transactions).length < + Math.min(t, MAX_TRANSACTIONS_IN_LIST) + ), + false + ) + ), + false + ); + +// return the number of transactions loaded +// note transactions loaded should be different (in cardinality) from ones displayed since we operate +// a filter over them (see latestTransactionsSelector) +export const getTransactionsLoadedLength = (state: GlobalState) => + pot.getOrElse( + pot.map( + state.features.payments.legacyTransaction.transactions, + txs => Object.keys(txs).length + ), + 0 + ); + export const isPaymentsLegacyTransactionsEmptySelector = createSelector( latestTransactionsSelector, transactionsPot => diff --git a/ts/features/pn/components/MessageFooter.tsx b/ts/features/pn/components/MessageFooter.tsx index 6fce6821b11..977db5f378c 100644 --- a/ts/features/pn/components/MessageFooter.tsx +++ b/ts/features/pn/components/MessageFooter.tsx @@ -17,7 +17,6 @@ import { trackPNPaymentStart, trackPNShowAllPayments } from "../analytics"; import { initializeAndNavigateToWalletForPayment } from "../../messages/utils"; import { paymentsButtonStateSelector } from "../store/reducers/payments"; import { shouldUseBottomSheetForPayments } from "../utils"; -import { isNewPaymentSectionEnabledSelector } from "../../../store/reducers/backendStatus"; import { ServiceId } from "../../../../definitions/backend/ServiceId"; type MessageFooterProps = { @@ -50,10 +49,6 @@ export const MessageFooter = ({ const canNavigateToPayment = useIOSelector(state => canNavigateToPaymentFromMessageSelector(state) ); - // Checks if the new wallet section is enabled - const isNewWalletSectionEnabled = useIOSelector( - isNewPaymentSectionEnabledSelector - ); const onFooterPressCallback = useCallback(() => { if (shouldUseBottomSheetForPayments(false, payments)) { trackPNShowAllPayments(); @@ -62,11 +57,8 @@ export const MessageFooter = ({ const firstPayment = payments[0]; const paymentId = getRptIdStringFromPayment(firstPayment); initializeAndNavigateToWalletForPayment( - isNewWalletSectionEnabled, - messageId, paymentId, false, - undefined, canNavigateToPayment, dispatch, () => trackPNPaymentStart(), @@ -76,8 +68,6 @@ export const MessageFooter = ({ }, [ canNavigateToPayment, dispatch, - isNewWalletSectionEnabled, - messageId, payments, presentPaymentsBottomSheetRef, toast diff --git a/ts/features/pushNotifications/sagas/__tests__/common.test.ts b/ts/features/pushNotifications/sagas/__tests__/common.test.ts index 5add4301e09..87e0c5d30a0 100644 --- a/ts/features/pushNotifications/sagas/__tests__/common.test.ts +++ b/ts/features/pushNotifications/sagas/__tests__/common.test.ts @@ -10,7 +10,6 @@ import { trackMessageNotificationTapIfNeeded, updateNotificationPermissionsIfNeeded } from "../common"; -import { isPaymentOngoingSelector } from "../../../../store/reducers/wallet/payment"; import { navigateToMessageRouterAction } from "../../utils/navigation"; import { UIMessageId } from "../../../messages/types"; import { clearNotificationPendingMessage } from "../../store/actions/pendingMessage"; @@ -42,8 +41,6 @@ describe("handlePendingMessageStateIfAllowed", () => { .next(mockedPendingMessageState) .call(trackMessageNotificationTapIfNeeded, mockedPendingMessageState) .next() - .select(isPaymentOngoingSelector) - .next(false) .put(clearNotificationPendingMessage()) .next() .select(isArchivingDisabledSelector) @@ -68,8 +65,6 @@ describe("handlePendingMessageStateIfAllowed", () => { .next(mockedPendingMessageState) .call(trackMessageNotificationTapIfNeeded, mockedPendingMessageState) .next() - .select(isPaymentOngoingSelector) - .next(false) .put(clearNotificationPendingMessage()) .next() .call(navigateToMainNavigatorAction) @@ -96,8 +91,6 @@ describe("handlePendingMessageStateIfAllowed", () => { .next(mockedPendingMessageState) .call(trackMessageNotificationTapIfNeeded, mockedPendingMessageState) .next() - .select(isPaymentOngoingSelector) - .next(false) .put(clearNotificationPendingMessage()) .next() .select(isArchivingDisabledSelector) @@ -112,36 +105,12 @@ describe("handlePendingMessageStateIfAllowed", () => { .isDone(); }); - it("does nothing if there is a payment going on", () => { - testSaga(handlePendingMessageStateIfAllowed, false) - .next() - .select(pendingMessageStateSelector) - .next(mockedPendingMessageState) - .next() - .select(isPaymentOngoingSelector) - .next(true) - .isDone(); - }); - it("does nothing if there are not pending messages", () => { testSaga(handlePendingMessageStateIfAllowed, false) .next() .select(pendingMessageStateSelector) .next(null) .next() - .select(isPaymentOngoingSelector) - .next(false) - .isDone(); - }); - - it("does nothing if there are not pending messages and there is a payment going on", () => { - testSaga(handlePendingMessageStateIfAllowed, false) - .next() - .select(pendingMessageStateSelector) - .next(null) - .next() - .select(isPaymentOngoingSelector) - .next(true) .isDone(); }); }); diff --git a/ts/features/pushNotifications/sagas/common.ts b/ts/features/pushNotifications/sagas/common.ts index 1dcb961d910..403bdd3cab8 100644 --- a/ts/features/pushNotifications/sagas/common.ts +++ b/ts/features/pushNotifications/sagas/common.ts @@ -9,7 +9,6 @@ import { PendingMessageState, pendingMessageStateSelector } from "../store/reducers/pendingMessage"; -import { isPaymentOngoingSelector } from "../../../store/reducers/wallet/payment"; import { clearNotificationPendingMessage } from "../store/actions/pendingMessage"; import { navigateToMainNavigatorAction } from "../../../store/actions/navigation"; import { isArchivingDisabledSelector } from "../../messages/store/reducers/archiving"; @@ -69,10 +68,7 @@ export function* handlePendingMessageStateIfAllowed( // the application was killed and the push notification is tapped) yield* call(trackMessageNotificationTapIfNeeded, pendingMessageState); - // Check if there is a payment ongoing - const isPaymentOngoing = yield* select(isPaymentOngoingSelector); - - if (!isPaymentOngoing && pendingMessageState) { + if (pendingMessageState) { // We have a pending notification message to handle const messageId = pendingMessageState.id; diff --git a/ts/features/wallet/bancomat/component/BancomatWalletPreview.tsx b/ts/features/wallet/bancomat/component/BancomatWalletPreview.tsx deleted file mode 100644 index b7e0426799a..00000000000 --- a/ts/features/wallet/bancomat/component/BancomatWalletPreview.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import * as React from "react"; -import { Image, ImageStyle, StyleProp } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import pagoBancomatImage from "../../../../../img/wallet/cards-icons/pagobancomat.png"; -import { Body } from "../../../../components/core/typography/Body"; -import { IOStyles } from "../../../../components/core/variables/IOStyles"; -import I18n from "../../../../i18n"; -import { navigateToBancomatDetailScreen } from "../../../../store/actions/navigation"; -import { GlobalState } from "../../../../store/reducers/types"; -import { BancomatPaymentMethod } from "../../../../types/pagopa"; -import { CardLogoPreview } from "../../component/card/CardLogoPreview"; -import { useImageResize } from "../../onboarding/bancomat/hooks/useImageResize"; - -type OwnProps = { bancomat: BancomatPaymentMethod }; - -type Props = ReturnType & - ReturnType & - OwnProps; - -const BASE_IMG_W = 160; -const BASE_IMG_H = 20; - -/** - * Render the image (if available) or the bank name (if available) - * or the generic bancomat string (final fallback). - * @param props - * @param size - */ -const renderLeft = (props: Props, size: O.Option<[number, number]>) => - pipe( - size, - O.fold( - () => ( - - {props.bancomat.abiInfo?.name ?? - I18n.t("wallet.methods.bancomat.name")} - - ), - imgDim => { - const imageUrl = props.bancomat.abiInfo?.logoUrl; - const imageStyle: StyleProp = { - width: imgDim[0], - height: imgDim[1], - resizeMode: "contain" - }; - return imageUrl ? ( - - ) : null; - } - ) - ); - -const getAccessibilityRepresentation = (bancomat: BancomatPaymentMethod) => { - const cardRepresentation = I18n.t("wallet.accessibility.folded.bancomat", { - bankName: bancomat.caption - }); - const cta = I18n.t("wallet.accessibility.folded.cta"); - return `${cardRepresentation}, ${cta}`; -}; - -/** - * A card preview for a bancomat card - * @param props - * @constructor - */ -const BancomatWalletPreview: React.FunctionComponent = props => { - const imgDimensions = useImageResize( - BASE_IMG_W, - BASE_IMG_H, - props.bancomat.abiInfo?.logoUrl - ); - - return ( - props.navigateToBancomatDetails(props.bancomat)} - /> - ); -}; - -const mapDispatchToProps = (_: Dispatch) => ({ - navigateToBancomatDetails: (bancomat: BancomatPaymentMethod) => - navigateToBancomatDetailScreen(bancomat) -}); - -const mapStateToProps = (_: GlobalState) => ({}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(BancomatWalletPreview); diff --git a/ts/features/wallet/bancomat/screen/BancomatDetailScreen.tsx b/ts/features/wallet/bancomat/screen/BancomatDetailScreen.tsx deleted file mode 100644 index 9a1e3730fe0..00000000000 --- a/ts/features/wallet/bancomat/screen/BancomatDetailScreen.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { Banner } from "@pagopa/io-app-design-system"; -import { openAuthenticationSession } from "@pagopa/io-react-native-login-utils"; -import { sequenceS } from "fp-ts/lib/Apply"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import * as React from "react"; -import { Route, useRoute } from "@react-navigation/native"; -import WorkunitGenericFailure from "../../../../components/error/WorkunitGenericFailure"; -import I18n from "../../../../i18n"; -import { useIOSelector } from "../../../../store/hooks"; -import { profileNameSurnameSelector } from "../../../../store/reducers/profile"; -import { paymentMethodByIdSelector } from "../../../../store/reducers/wallet/wallets"; -import { BancomatPaymentMethod, isBancomat } from "../../../../types/pagopa"; -import BasePaymentMethodScreen from "../../common/BasePaymentMethodScreen"; -import { acceptedPaymentMethodsFaqUrl } from "../../../../urls"; -import { PaymentCardBig } from "../../../payments/common/components/PaymentCardBig"; - -export type BancomatDetailScreenNavigationParams = Readonly<{ - // TODO: we should use only the id and retrieve it from the store, otherwise we lose all the updates - bancomat: BancomatPaymentMethod; -}>; - -/** - * Detail screen for a bancomat - * @constructor - */ -const BancomatDetailScreen = () => { - const { idWallet } = - useRoute< - Route<"WALLET_BANCOMAT_DETAIL", BancomatDetailScreenNavigationParams> - >().params.bancomat; - const bancomat = useIOSelector(state => - paymentMethodByIdSelector(state, idWallet) - ); - const bannerViewRef = React.useRef(null); - const nameSurname = useIOSelector(profileNameSurnameSelector); - // should never happen - if (!isBancomat(bancomat)) { - return ; - } - const cardData = pipe( - bancomat, - // as with card, they technically should never be undefined, - // but in the remote case they were we'd rather show a skeleton - ({ info, abiInfo }) => - sequenceS(O.Monad)({ - abiCode: O.some(abiInfo?.abi), - holderName: O.fromNullable(nameSurname), - expireMonth: O.fromNullable(info.expireMonth), - expireYear: O.fromNullable(info.expireYear), - bankName: O.some(abiInfo?.name) - }), - O.map(cardData => ({ - ...cardData, - expirationDate: new Date( - Number(cardData.expireYear), - // month is 0 based, BE res is not - Number(cardData.expireMonth) - 1 - ) - })) - ); - - const cardComponent = pipe( - cardData, - O.fold( - () => , - cardData => - ) - ); - return ( - - openAuthenticationSession(acceptedPaymentMethodsFaqUrl, "") - } - /> - } - /> - ); -}; - -export default BancomatDetailScreen; diff --git a/ts/features/wallet/bancomat/screen/BancomatInformation.tsx b/ts/features/wallet/bancomat/screen/BancomatInformation.tsx deleted file mode 100644 index 1d124368191..00000000000 --- a/ts/features/wallet/bancomat/screen/BancomatInformation.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import * as React from "react"; -import { View, StyleSheet } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { ButtonOutline, Icon, VSpacer } from "@pagopa/io-app-design-system"; -import { H3 } from "../../../../components/core/typography/H3"; -import InternationalCircuitIconsBar from "../../../../components/wallet/InternationalCircuitIconsBar"; -import I18n from "../../../../i18n"; -import { GlobalState } from "../../../../store/reducers/types"; -import bancomatInformationBottomSheet from "../utils/bancomatInformationBottomSheet"; -import TouchableDefaultOpacity from "../../../../components/TouchableDefaultOpacity"; - -type OwnProps = { - onAddPaymentMethod: () => void; -}; - -type Props = ReturnType & - ReturnType & - OwnProps; - -const styles = StyleSheet.create({ - titleContainer: { - flex: 1, - flexDirection: "row", - justifyContent: "space-between" - } -}); - -/** - * Display the logo bar and a cta to start the onboarding of a new - * payment method. - * @constructor - */ - -const BancomatInformation: React.FunctionComponent = props => { - const { present, bottomSheet } = bancomatInformationBottomSheet( - props.onAddPaymentMethod - ); - return ( - - -

{I18n.t("wallet.bancomat.details.debit.title")}

- - - -
- - - - { - props.onAddPaymentMethod?.(); - }} - testID={"addPaymentMethodButton"} - label={I18n.t("wallet.bancomat.details.debit.addCta")} - /> - {bottomSheet} -
- ); -}; - -const mapDispatchToProps = (_: Dispatch) => ({}); - -const mapStateToProps = (_: GlobalState) => ({}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(BancomatInformation); diff --git a/ts/features/wallet/bancomat/screen/__tests__/BancomatInformation.test.tsx b/ts/features/wallet/bancomat/screen/__tests__/BancomatInformation.test.tsx deleted file mode 100644 index 6e99c5980ff..00000000000 --- a/ts/features/wallet/bancomat/screen/__tests__/BancomatInformation.test.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { fireEvent, render } from "@testing-library/react-native"; -import * as React from "react"; -import { Provider } from "react-redux"; -import configureMockStore from "redux-mock-store"; -import BancomatInformation from "../BancomatInformation"; - -const mockPresentFn = jest.fn(); -jest.mock("../../utils/bancomatInformationBottomSheet", () => ({ - __esModule: true, - default: () => ({ present: mockPresentFn }) -})); - -describe("BancomatInformation component", () => { - it("should show the InternationalCircuitIconsBar", () => { - const onAddPaymentMethod = jest.fn(); - const component = getComponent(onAddPaymentMethod); - const internationalCircuitIconsBar = component.queryByTestId( - "internationalCircuitIconsBar" - ); - - expect(internationalCircuitIconsBar).not.toBeNull(); - }); - - it("should call the present function when click on notice icon", () => { - const onAddPaymentMethod = jest.fn(); - const component = getComponent(onAddPaymentMethod); - const ioNoticeIcon = component.queryByTestId("noticeIcon"); - - expect(ioNoticeIcon).not.toBeNull(); - - if (ioNoticeIcon !== null) { - fireEvent.press(ioNoticeIcon); - expect(mockPresentFn).toHaveBeenCalledTimes(1); - } - }); - it("should call the onAddPaymentMethod function when click on addPaymentMethod button", () => { - const onAddPaymentMethod = jest.fn(); - const component = getComponent(onAddPaymentMethod); - const addPaymentMethodButton = component.queryByTestId( - "addPaymentMethodButton" - ); - - expect(addPaymentMethodButton).not.toBeNull(); - - if (addPaymentMethodButton !== null) { - fireEvent.press(addPaymentMethodButton); - expect(onAddPaymentMethod).toHaveBeenCalledTimes(1); - } - }); -}); - -const getComponent = (onAddPaymentMethod: () => void) => { - const mockStore = configureMockStore(); - - const store: ReturnType = mockStore(); - return render( - - - - ); -}; diff --git a/ts/features/wallet/bancomat/utils/bancomatInformationBottomSheet.tsx b/ts/features/wallet/bancomat/utils/bancomatInformationBottomSheet.tsx deleted file mode 100644 index 21ea4017007..00000000000 --- a/ts/features/wallet/bancomat/utils/bancomatInformationBottomSheet.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import * as React from "react"; -import { View } from "react-native"; -import { ButtonOutline, VSpacer } from "@pagopa/io-app-design-system"; -import I18n from "../../../../i18n"; -import InternationalCircuitIconsBar from "../../../../components/wallet/InternationalCircuitIconsBar"; -import { H4 } from "../../../../components/core/typography/H4"; -import { useLegacyIOBottomSheetModal } from "../../../../utils/hooks/bottomSheet"; - -/** - * A bottomsheet that display generic information on bancomat and a cta to start the onboarding of a new - * payment method. - * This will be also visualized inside a bottomsheet after an addition of a new bancomat - */ -export default (onAdd?: () => void) => { - const { present, bottomSheet, dismiss } = useLegacyIOBottomSheetModal( - - - -

- {I18n.t("wallet.bancomat.details.debit.body")} -

- - { - onAdd?.(); - dismiss(); - }} - label={I18n.t("wallet.bancomat.details.debit.addCta")} - /> -
, - I18n.t("wallet.bancomat.details.debit.title"), - 385 - ); - return { - present, - bottomSheet, - dismiss - }; -}; diff --git a/ts/features/wallet/bancomatpay/component/BPayCard.tsx b/ts/features/wallet/bancomatpay/component/BPayCard.tsx deleted file mode 100644 index 0d1598a97db..00000000000 --- a/ts/features/wallet/bancomatpay/component/BPayCard.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { HSpacer, Icon } from "@pagopa/io-app-design-system"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import * as React from "react"; -import { Image, StyleSheet, View } from "react-native"; -import { connect } from "react-redux"; -import bancomatLogoMin from "../../../../../img/wallet/payment-methods/bancomatpay-logo.png"; -import { H4 } from "../../../../components/core/typography/H4"; -import { IOStyles } from "../../../../components/core/variables/IOStyles"; -import I18n from "../../../../i18n"; -import { profileNameSurnameSelector } from "../../../../store/reducers/profile"; -import { GlobalState } from "../../../../store/reducers/types"; -import BaseCardComponent from "../../component/card/BaseCardComponent"; - -type Props = { - phone?: string; -} & ReturnType; - -const styles = StyleSheet.create({ - bpayLogo: { - width: 80, - height: 40, - resizeMode: "contain" - } -}); - -/** - * Generate the accessibility label for the card. - */ -const getAccessibilityRepresentation = (holder?: string, phone?: string) => { - const cardRepresentation = I18n.t("wallet.accessibility.folded.bancomatPay"); - - const computedHolder = - holder !== undefined - ? `, ${I18n.t("wallet.accessibility.cardHolder")} ${holder}` - : ""; - - const computedPhone = phone !== undefined ? `, ${phone}` : ""; - - return `${cardRepresentation}${computedHolder}${computedPhone}`; -}; - -/** - * Add a row; on the left the phone number, on the right the favourite star icon - * @param phone - */ -const topLeft = (phone: string) => ( - - {phone && ( - - - -

- {phone} -

-
- )} -
-); - -const BPayCard: React.FunctionComponent = (props: Props) => ( - - {pipe( - props.nameSurname, - O.fromNullable, - O.fold( - () => undefined, - nameSurname => ( -

- {nameSurname.toLocaleUpperCase()} -

- ) - ) - )} -
- } - bottomRightCorner={ - - - - } - /> -); - -const mapStateToProps = (state: GlobalState) => ({ - nameSurname: profileNameSurnameSelector(state) -}); - -export default connect(mapStateToProps)(BPayCard); diff --git a/ts/features/wallet/bancomatpay/component/BPayWalletPreview.tsx b/ts/features/wallet/bancomatpay/component/BPayWalletPreview.tsx deleted file mode 100644 index de068cf4187..00000000000 --- a/ts/features/wallet/bancomatpay/component/BPayWalletPreview.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import bPayImage from "../../../../../img/wallet/cards-icons/bPay.png"; -import { Body } from "../../../../components/core/typography/Body"; -import { IOStyles } from "../../../../components/core/variables/IOStyles"; -import I18n from "../../../../i18n"; -import { navigateToBPayDetailScreen } from "../../../../store/actions/navigation"; -import { GlobalState } from "../../../../store/reducers/types"; -import { BPayPaymentMethod } from "../../../../types/pagopa"; -import { CardLogoPreview } from "../../component/card/CardLogoPreview"; - -type OwnProps = { - bPay: BPayPaymentMethod; -}; - -type Props = ReturnType & - ReturnType & - OwnProps; - -/** - * Render the phone number. - * @param props - */ -const renderLeft = (props: Props) => ( - - {props.bPay?.info?.numberObfuscated} - -); - -const getAccessibilityRepresentation = () => { - const cardRepresentation = I18n.t("wallet.accessibility.folded.bancomatPay"); - const cta = I18n.t("wallet.accessibility.folded.cta"); - return `${cardRepresentation}, ${cta}`; -}; - -/** - * A card preview for a bancomat card - * @param props - * @constructor - */ -const BPayWalletPreview: React.FunctionComponent = props => ( - props.navigateToBPayDetails(props.bPay)} - /> -); - -const mapDispatchToProps = (_: Dispatch) => ({ - navigateToBPayDetails: (bPay: BPayPaymentMethod) => - navigateToBPayDetailScreen(bPay) -}); - -const mapStateToProps = (_: GlobalState) => ({}); - -export default connect(mapStateToProps, mapDispatchToProps)(BPayWalletPreview); diff --git a/ts/features/wallet/bancomatpay/component/__test__/BPayCard.test.tsx b/ts/features/wallet/bancomatpay/component/__test__/BPayCard.test.tsx deleted file mode 100644 index 71b95b17217..00000000000 --- a/ts/features/wallet/bancomatpay/component/__test__/BPayCard.test.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { render } from "@testing-library/react-native"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { Provider } from "react-redux"; -import { Store } from "redux"; -import configureMockStore from "redux-mock-store"; -import { InitializedProfile } from "../../../../../../definitions/backend/InitializedProfile"; -import mockedProfile from "../../../../../__mocks__/initializedProfile"; -import * as hooks from "../../../onboarding/bancomat/hooks/useImageResize"; -import BPayCard from "../BPayCard"; - -const aPhone = "+39 34*******0000"; -describe("BPayWalletPreview component", () => { - const mockStore = configureMockStore(); - // eslint-disable-next-line functional/no-let - let store: ReturnType; - - beforeEach(() => { - store = mockStore(mockProfileNameSurnameState(mockedProfile)); - }); - - it("should show the phone in is defined", () => { - jest.spyOn(hooks, "useImageResize").mockReturnValue(O.none); - const component = getComponent(store, aPhone); - const phone = component.queryByTestId("phone"); - - expect(phone).not.toBeNull(); - expect(phone).toHaveTextContent(aPhone); - }); - it("should show the uppercase name surname in is defined", () => { - jest.spyOn(hooks, "useImageResize").mockReturnValue(O.none); - const component = getComponent(store, aPhone); - const nameSurname = component.queryByTestId("nameSurname"); - - expect(nameSurname).not.toBeNull(); - expect(nameSurname).toHaveTextContent( - `${mockedProfile.name.toUpperCase()} ${mockedProfile.family_name.toLocaleUpperCase()}` - ); - }); -}); - -const mockProfileNameSurnameState = (profile: InitializedProfile) => ({ - profile: pot.some(profile) -}); - -const getComponent = (store: Store, phone: string) => - render( - - - - ); diff --git a/ts/features/wallet/bancomatpay/component/__test__/BPayWalletPreview.test.tsx b/ts/features/wallet/bancomatpay/component/__test__/BPayWalletPreview.test.tsx deleted file mode 100644 index 17efe9831d8..00000000000 --- a/ts/features/wallet/bancomatpay/component/__test__/BPayWalletPreview.test.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { CommonActions } from "@react-navigation/native"; -import { fireEvent } from "@testing-library/react-native"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { Store } from "redux"; -import configureMockStore from "redux-mock-store"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { ToolEnum } from "../../../../../../definitions/content/AssistanceToolConfig"; -import { BackendStatus } from "../../../../../../definitions/content/BackendStatus"; -import { Config } from "../../../../../../definitions/content/Config"; -import NavigationService from "../../../../../navigation/NavigationService"; -import ROUTES from "../../../../../navigation/routes"; -import { BPayPaymentMethod } from "../../../../../types/pagopa"; -import { renderScreenWithNavigationStoreContext } from "../../../../../utils/testWrapper"; -import * as hooks from "../../../onboarding/bancomat/hooks/useImageResize"; -import BPayWalletPreview from "../BPayWalletPreview"; -import { SubscriptionStateEnum } from "../../../../../../definitions/trial_system/SubscriptionState"; -import { TrialSystemState } from "../../../../trialSystem/store/reducers"; -import { itwTrialId } from "../../../../../config"; -import { ItWalletState } from "../../../../itwallet/common/store/reducers"; -import { ItwLifecycleState } from "../../../../itwallet/lifecycle/store/reducers"; -import { PersistedFeaturesState } from "../../../../common/store/reducers"; - -describe("BPayWalletPreview component", () => { - const mockStore = configureMockStore(); - // eslint-disable-next-line functional/no-let - let store: ReturnType; - - const aBPay: BPayPaymentMethod = { - walletType: "BPay", - createDate: "2021-07-08", - enableableFunctions: ["FA", "pagoPA"], - favourite: false, - idWallet: 25572, - info: { - numberObfuscated: "*******0000", - paymentInstruments: [], - uidHash: - "d48a59cdfbe3da7e4fe25e28cbb47d5747720ecc6fc392c87f1636fe95db22f90004" - }, - onboardingChannel: "IO", - pagoPA: true, - updateDate: "2020-11-20", - kind: "BPay", - caption: "BANCOMAT Pay", - icon: 37 - } as BPayPaymentMethod; - - beforeEach(() => { - store = mockStore({ - trialSystem: { - [itwTrialId]: pot.some(SubscriptionStateEnum.UNSUBSCRIBED) - } as TrialSystemState, - backendStatus: { - status: O.some({ - config: { - assistanceTool: { tool: ToolEnum.none }, - cgn: { enabled: true }, - newPaymentSection: { - enabled: false, - min_app_version: { - android: "0.0.0.0", - ios: "0.0.0.0" - } - }, - fims: { enabled: true }, - itw: { - enabled: true, - min_app_version: { - android: "0.0.0.0", - ios: "0.0.0.0" - } - } - } as Config - } as BackendStatus) - }, - features: { - itWallet: { - lifecycle: ItwLifecycleState.ITW_LIFECYCLE_INSTALLED - } as ItWalletState - } as PersistedFeaturesState - }); - }); - - it("should call navigateToBPayDetails when press on it", () => { - jest.spyOn(hooks, "useImageResize").mockReturnValue(O.none); - const spy = jest.spyOn(NavigationService, "dispatchNavigationAction"); - const component = getComponent(aBPay, store); - const cardComponent = component.queryByTestId("cardPreview"); - if (cardComponent) { - fireEvent.press(cardComponent); - expect(spy).toHaveBeenCalledWith( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.WALLET_BPAY_DETAIL, - params: { bPay: aBPay } - }) - ); - } - }); -}); - -const getComponent = (bPay: BPayPaymentMethod, store: Store) => - renderScreenWithNavigationStoreContext( - () => , - "WALLET_HOME", - {}, - store - ); diff --git a/ts/features/wallet/bancomatpay/screen/BPayDetailScreen.tsx b/ts/features/wallet/bancomatpay/screen/BPayDetailScreen.tsx deleted file mode 100644 index 3aba20d6c08..00000000000 --- a/ts/features/wallet/bancomatpay/screen/BPayDetailScreen.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { Route, useRoute } from "@react-navigation/native"; -import { sequenceS } from "fp-ts/lib/Apply"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import * as React from "react"; -import WorkunitGenericFailure from "../../../../components/error/WorkunitGenericFailure"; -import { useIOSelector } from "../../../../store/hooks"; -import { paymentMethodByIdSelector } from "../../../../store/reducers/wallet/wallets"; -import { BPayPaymentMethod, isBPay } from "../../../../types/pagopa"; -import BasePaymentMethodScreen from "../../common/BasePaymentMethodScreen"; -import PaymentMethodFeatures from "../../component/features/PaymentMethodFeatures"; -import { profileNameSurnameSelector } from "../../../../store/reducers/profile"; -import { - PaymentCardBig, - PaymentCardBigProps -} from "../../../payments/common/components/PaymentCardBig"; - -export type BPayDetailScreenNavigationParams = Readonly<{ - // TODO: we should use only the id and retrieve it from the store, otherwise we lose all the updates - bPay: BPayPaymentMethod; -}>; - -/** - * Detail screen for a Bancomat Pay - * @constructor - */ -export const BPayDetailScreen: React.FunctionComponent = () => { - const route = - useRoute>(); - const bPayId = route.params.bPay.idWallet; - const bPay = useIOSelector(s => paymentMethodByIdSelector(s, bPayId)); - const nameSurname = useIOSelector(profileNameSurnameSelector); - // it should never happen - if (!isBPay(bPay)) { - return ; - } - const cardProps = pipe( - bPay, - // as with card, they technically should never be undefined, - // but in the remote case they were we'd rather show a skeleton - ({ info }) => - sequenceS(O.Monad)({ - cardType: O.some("BANCOMATPAY"), - phoneNumber: O.fromNullable(info.numberObfuscated), - holderName: O.fromNullable(nameSurname) - }) as O.Option, - O.getOrElse((): PaymentCardBigProps => ({ isLoading: true })) - ); - return ( - } - content={} - /> - ); -}; diff --git a/ts/features/wallet/cobadge/component/BaseCoBadgeCard.tsx b/ts/features/wallet/cobadge/component/BaseCoBadgeCard.tsx deleted file mode 100644 index fbe3e41a378..00000000000 --- a/ts/features/wallet/cobadge/component/BaseCoBadgeCard.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import { VSpacer } from "@pagopa/io-app-design-system"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import * as React from "react"; -import { - Image, - ImageSourcePropType, - ImageStyle, - StyleProp, - StyleSheet, - View -} from "react-native"; -import { Abi } from "../../../../../definitions/pagopa/walletv2/Abi"; -import abiLogoFallback from "../../../../../img/wallet/cards-icons/abiLogoFallback.png"; -import { IOBadge } from "../../../../components/core/IOBadge"; -import { H5 } from "../../../../components/core/typography/H5"; -import I18n from "../../../../i18n"; -import { localeDateFormat } from "../../../../utils/locale"; -import BaseCardComponent from "../../component/card/BaseCardComponent"; -import { BlurredPan } from "../../component/card/BlurredPan"; -import { useImageResize } from "../../onboarding/bancomat/hooks/useImageResize"; - -type Props = { - caption?: string; - expiringDate?: Date; - isExpired?: boolean; - abi: Abi; - brand?: string; - brandLogo: ImageSourcePropType; - blocked?: boolean; - accessibilityLabel?: string; -}; - -const styles = StyleSheet.create({ - abiLogoFallback: { - width: 40, - height: 40, - resizeMode: "contain" - }, - brandLogo: { - width: 48, - height: 31, - resizeMode: "contain" - } -}); - -const BASE_IMG_W = 160; -const BASE_IMG_H = 40; - -/** - * Generate the accessibility label for the card. - */ -const getAccessibilityRepresentation = ( - brand: string, - bankName: string, - expiringDate?: Date -) => { - const cardRepresentation = I18n.t("wallet.accessibility.folded.coBadge", { - brand, - bankName - }); - - const computedValidity = - expiringDate !== undefined - ? `, ${I18n.t("cardComponent.validUntil")} ${localeDateFormat( - expiringDate, - I18n.t("global.dateFormats.numericMonthYear") - )}` - : ""; - - return `${cardRepresentation}${computedValidity}`; -}; - -const BaseCoBadgeCard: React.FunctionComponent = (props: Props) => { - const imgDimensions = useImageResize( - BASE_IMG_W, - BASE_IMG_H, - props.abi?.logoUrl - ); - - const imageStyle: StyleProp | undefined = pipe( - imgDimensions, - O.fold( - () => undefined, - imgDim => ({ - width: imgDim[0], - height: imgDim[1], - resizeMode: "contain" - }) - ) - ); - return ( - - - {props.abi.logoUrl && imageStyle ? ( - - ) : ( - - )} - {props.blocked && ( - - )} - - {props.expiringDate && ( - <> - -
- {I18n.t("wallet.cobadge.details.card.validUntil", { - expiryDate: localeDateFormat( - props.expiringDate, - I18n.t("global.dateFormats.numericMonthYear") - ) - })} -
- - )} - - } - bottomLeftCorner={ - <> - {props.caption && ( - <> - - {props.caption} - - - - )} - - } - bottomRightCorner={ - - - - } - /> - ); -}; - -export default BaseCoBadgeCard; diff --git a/ts/features/wallet/cobadge/component/CobadgeWalletPreview.tsx b/ts/features/wallet/cobadge/component/CobadgeWalletPreview.tsx deleted file mode 100644 index ab765803f3d..00000000000 --- a/ts/features/wallet/cobadge/component/CobadgeWalletPreview.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import * as React from "react"; -import { Image, ImageStyle, StyleProp } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { Body } from "../../../../components/core/typography/Body"; -import { IOStyles } from "../../../../components/core/variables/IOStyles"; -import { getCardIconFromBrandLogo } from "../../../../components/wallet/card/Logo"; -import I18n from "../../../../i18n"; -import { navigateToCobadgeDetailScreen } from "../../../../store/actions/navigation"; -import { GlobalState } from "../../../../store/reducers/types"; -import { CreditCardPaymentMethod } from "../../../../types/pagopa"; -import { CardLogoPreview } from "../../component/card/CardLogoPreview"; -import { useImageResize } from "../../onboarding/bancomat/hooks/useImageResize"; - -type OwnProps = { - cobadge: CreditCardPaymentMethod; -}; - -type Props = ReturnType & - ReturnType & - OwnProps; - -const BASE_IMG_W = 160; -const BASE_IMG_H = 20; -/** - * Render the image (if available) or the bank name (if available) - * or the generic CreditCardPaymentMethod caption (final fallback). - * @param props - * @param size - */ -const renderLeft = (props: Props, size: O.Option<[number, number]>) => - pipe( - size, - O.fold( - () => ( - - {props.cobadge.caption} - - ), - imgDim => { - const imageUrl = props.cobadge.abiInfo?.logoUrl; - - const imageStyle: StyleProp = { - width: imgDim[0], - height: imgDim[1], - resizeMode: "contain" - }; - return imageUrl ? ( - - ) : null; - } - ) - ); - -const getAccessibilityRepresentation = (cobadge: CreditCardPaymentMethod) => { - const cardRepresentation = I18n.t("wallet.accessibility.folded.coBadge", { - brand: cobadge.info.brand, - bankName: - cobadge.abiInfo?.name ?? - I18n.t("wallet.accessibility.folded.bankNotAvailable") - }); - const cta = I18n.t("wallet.accessibility.folded.cta"); - return `${cardRepresentation}, ${cta}`; -}; - -/** - * A card preview for a cobadge card - * @param props - * @constructor - */ -const CobadgeWalletPreview: React.FunctionComponent = props => { - const imgDimensions = useImageResize( - BASE_IMG_W, - BASE_IMG_H, - props.cobadge.abiInfo?.logoUrl - ); - - const brandLogo = getCardIconFromBrandLogo(props.cobadge.info); - return ( - props.navigateToCobadgeDetails(props.cobadge)} - /> - ); -}; - -const mapDispatchToProps = (_: Dispatch) => ({ - navigateToCobadgeDetails: (cobadge: CreditCardPaymentMethod) => - navigateToCobadgeDetailScreen(cobadge) -}); - -const mapStateToProps = (_: GlobalState) => ({}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(CobadgeWalletPreview); diff --git a/ts/features/wallet/cobadge/component/PreviewCoBadgeCard.tsx b/ts/features/wallet/cobadge/component/PreviewCoBadgeCard.tsx deleted file mode 100644 index 9699beb54d4..00000000000 --- a/ts/features/wallet/cobadge/component/PreviewCoBadgeCard.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import * as React from "react"; -import { Abi } from "../../../../../definitions/pagopa/walletv2/Abi"; -import { PaymentInstrument } from "../../../../../definitions/pagopa/walletv2/PaymentInstrument"; -import { getCardIconFromPaymentNetwork } from "../../../../utils/card"; -import { - getTitleFromPaymentInstrument, - isCoBadgeBlocked -} from "../../../../utils/paymentMethod"; -import BaseCoBadgeCard from "./BaseCoBadgeCard"; - -type Props = { coBadge: PaymentInstrument; abi: Abi }; - -/** - * Display a preview of a cobadge that the user could add to the wallet - * @constructor - */ -const PreviewCoBadgeCard: React.FunctionComponent = props => { - const brandLogo = getCardIconFromPaymentNetwork(props.coBadge.paymentNetwork); - return ( - - ); -}; -export default PreviewCoBadgeCard; diff --git a/ts/features/wallet/cobadge/component/__tests__/BaseCobadgeCard.test.tsx b/ts/features/wallet/cobadge/component/__tests__/BaseCobadgeCard.test.tsx deleted file mode 100644 index 635afa1b87f..00000000000 --- a/ts/features/wallet/cobadge/component/__tests__/BaseCobadgeCard.test.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { render } from "@testing-library/react-native"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { ImageSourcePropType } from "react-native"; -import { Provider } from "react-redux"; -import { Store } from "redux"; -import configureMockStore from "redux-mock-store"; -import { Abi } from "../../../../../../definitions/pagopa/walletv2/Abi"; -import defaultCardIcon from "../../../../../../img/wallet/cards-icons/unknown.png"; -import * as hooks from "../../../onboarding/bancomat/hooks/useImageResize"; -import CobadgeCard from "../BaseCoBadgeCard"; - -jest.mock("../../../onboarding/bancomat/hooks/useImageResize"); - -const aCaption = "****1234"; -const anAbiLogo = "http://127.0.0.1:3000/static_contents/logos/abi/03069.png"; -const aBrandLogo = defaultCardIcon; -const anexpiringDateNotExpired = new Date("01/05/2023"); -describe("CoBadgeWalletPreview component", () => { - const mockStore = configureMockStore(); - // eslint-disable-next-line functional/no-let - let store: ReturnType; - - beforeEach(() => { - store = mockStore(); - }); - it("should show the abiLogoFallback if there isn't the abiLogo", () => { - jest.spyOn(hooks, "useImageResize").mockReturnValue(O.none); - const anAbiWithoutAbilogo = {} as Abi; - const component = getComponent(store, aBrandLogo, anAbiWithoutAbilogo); - const abiLogoFallbackComponent = component.queryByTestId("abiLogoFallback"); - - expect(abiLogoFallbackComponent).not.toBeNull(); - expect(abiLogoFallbackComponent).toHaveProp("source", { - testUri: "../../../img/wallet/cards-icons/abiLogoFallback.png" - }); - }); - - it("should show the abiLogo if there is an abiLogo and useImageResize return some value", () => { - jest.spyOn(hooks, "useImageResize").mockReturnValue(O.some([15, 15])); - const anAbiWithAbiLogo = { logoUrl: anAbiLogo } as Abi; - const component = getComponent(store, aBrandLogo, anAbiWithAbiLogo); - const abiLogo = component.queryByTestId("abiLogo"); - - expect(abiLogo).not.toBeNull(); - expect(abiLogo).toHaveProp("source", { uri: anAbiLogo }); - }); - - it("should show the expiration date if expiringDate is defined", () => { - jest.spyOn(hooks, "useImageResize").mockReturnValue(O.none); - const component = getComponent( - store, - aBrandLogo, - anAbiLogo, - aCaption, - anexpiringDateNotExpired - ); - const expirationDate = component.queryByTestId("expirationDate"); - - expect(expirationDate).not.toBeNull(); - expect(expirationDate).toHaveTextContent(`Valid until 01/2023`); - }); - - it("should show the caption if is defined", () => { - jest.spyOn(hooks, "useImageResize").mockReturnValue(O.none); - const component = getComponent(store, aBrandLogo, anAbiLogo, aCaption); - const caption = component.queryByTestId("caption"); - - expect(caption).not.toBeNull(); - expect(caption).toHaveTextContent(aCaption); - }); - - it("should show the blocked badge if is blocked prop is true", () => { - jest.spyOn(hooks, "useImageResize").mockReturnValue(O.none); - const component = getComponent( - store, - aBrandLogo, - anAbiLogo, - aCaption, - anexpiringDateNotExpired, - true - ); - const blockedBadge = component.queryByTestId("blockedBadge"); - - expect(blockedBadge).not.toBeNull(); - }); -}); - -const getComponent = ( - store: Store, - brandLogo: ImageSourcePropType, - abi: Abi, - caption?: string, - expiringDate?: Date, - blocked?: boolean -) => - render( - - - - ); diff --git a/ts/features/wallet/cobadge/component/__tests__/CobadgeWalletPreview.test.tsx b/ts/features/wallet/cobadge/component/__tests__/CobadgeWalletPreview.test.tsx deleted file mode 100644 index 8c93fee07d4..00000000000 --- a/ts/features/wallet/cobadge/component/__tests__/CobadgeWalletPreview.test.tsx +++ /dev/null @@ -1,208 +0,0 @@ -import { NavigationAction } from "@react-navigation/native"; -import { fireEvent } from "@testing-library/react-native"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { Store } from "redux"; -import configureMockStore from "redux-mock-store"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { ToolEnum } from "../../../../../../definitions/content/AssistanceToolConfig"; -import { BackendStatus } from "../../../../../../definitions/content/BackendStatus"; -import { Config } from "../../../../../../definitions/content/Config"; -import NavigationService from "../../../../../navigation/NavigationService"; -import ROUTES from "../../../../../navigation/routes"; -import { CreditCardPaymentMethod } from "../../../../../types/pagopa"; -import { renderScreenWithNavigationStoreContext } from "../../../../../utils/testWrapper"; -import * as hooks from "../../../onboarding/bancomat/hooks/useImageResize"; -import CobadgeWalletPreview from "../CobadgeWalletPreview"; -import { ItWalletState } from "../../../../itwallet/common/store/reducers"; -import { PersistedFeaturesState } from "../../../../common/store/reducers"; -import { ItwLifecycleState } from "../../../../itwallet/lifecycle/store/reducers"; -import { itwTrialId } from "../../../../../config"; -import { SubscriptionStateEnum } from "../../../../../../definitions/trial_system/SubscriptionState"; -import { TrialSystemState } from "../../../../trialSystem/store/reducers"; - -jest.mock("../../../onboarding/bancomat/hooks/useImageResize"); -describe("CobadgeWalletPreview component", () => { - const mockStore = configureMockStore(); - // eslint-disable-next-line functional/no-let - let store: ReturnType; - - const aCobadgeCard: CreditCardPaymentMethod = { - walletType: "Card", - createDate: "2021-07-08", - enableableFunctions: ["FA", "pagoPA"], - favourite: false, - idWallet: 25572, - info: { - blurredNumber: "0001", - brand: "Maestro", - brandLogo: - "https://wisp2.pagopa.gov.it/wallet/assets/img/creditcard/carta_maestro.png", - expireMonth: "11", - expireYear: "2021", - hashPan: - "d48a59cdfbe3da7e4fe25e28cbb47d5747720ecc6fc392c87f1636fe95db22f90004", - holder: "Maria Rossi", - htokenList: ["token1", "token2"], - issuerAbiCode: "08161", - type: "PP" - }, - onboardingChannel: "IO", - pagoPA: false, - updateDate: "2020-11-20", - kind: "CreditCard", - caption: "●●●●0001", - icon: 37 - } as CreditCardPaymentMethod; - - beforeEach(() => { - store = mockStore({ - trialSystem: { - [itwTrialId]: pot.some(SubscriptionStateEnum.UNSUBSCRIBED) - } as TrialSystemState, - backendStatus: { - status: O.some({ - config: { - assistanceTool: { tool: ToolEnum.none }, - cgn: { enabled: true }, - newPaymentSection: { - enabled: false, - min_app_version: { - android: "0.0.0.0", - ios: "0.0.0.0" - } - }, - fims: { enabled: true }, - itw: { - enabled: true, - min_app_version: { - android: "0.0.0.0", - ios: "0.0.0.0" - } - } - } as Config - } as BackendStatus) - }, - features: { - itWallet: { - lifecycle: ItwLifecycleState.ITW_LIFECYCLE_INSTALLED - } as ItWalletState - } as PersistedFeaturesState - }); - }); - it("should show the caption if useImageResize return none", () => { - const myspy = jest.spyOn(hooks, "useImageResize").mockReturnValue(O.none); - const component = getComponent(aCobadgeCard, store); - const bankLogo = component.queryByTestId("bankLogoFallback"); - - expect(bankLogo).not.toBeNull(); - expect(bankLogo).toHaveTextContent(aCobadgeCard.caption); - expect(myspy).toHaveBeenCalledTimes(1); - }); - - it("should show nothing if useImageResize return a size but there isn't the logoUrl", () => { - jest.spyOn(hooks, "useImageResize").mockReturnValue(O.some([15, 15])); - const component = getComponent(aCobadgeCard, store); - const bankLogo = component.queryByTestId("bankLogo"); - const bankLogoFallback = component.queryByTestId("bankLogoFallback"); - - expect(bankLogo).toBeNull(); - expect(bankLogoFallback).toBeNull(); - }); - - it("should show the logo image if there is the abiInfo logoUrl", () => { - jest.spyOn(hooks, "useImageResize").mockReturnValue(O.some([15, 15])); - - const infobankName = "a different bank name"; - const abiInfoBankName = "INTESA SANPAOLO - S.P.A."; - const logoUrl = "http://127.0.0.1:3000/static_contents/logos/abi/03069.png"; - const component = getComponent( - { - ...aCobadgeCard, - info: { ...aCobadgeCard.info, bankName: infobankName }, - abiInfo: { - abi: "03069", - name: abiInfoBankName, - logoUrl - } - }, - store - ); - const bankLogo = component.queryByTestId("bankLogo"); - - expect(bankLogo).not.toBeNull(); - expect(bankLogo).toHaveProp("source", { uri: logoUrl }); - }); - - it("should show a visa card icon if in the info there is the corresponding name", () => { - jest.spyOn(hooks, "useImageResize").mockReturnValue(O.some([15, 15])); - const component = getComponent( - { - ...aCobadgeCard, - info: { - brandLogo: "carta_visaelectron" - } - }, - store - ); - - const defaultBrandLogoUrl = - "../../../img/wallet/cards-icons/visa-electron.png"; - const defaultBrandLogo = component.queryByTestId("cardImage"); - - expect(defaultBrandLogo).toHaveProp("source", { - testUri: defaultBrandLogoUrl - }); - }); - it("should show a default card icon if in the info there isn't the brandlogo", () => { - jest.spyOn(hooks, "useImageResize").mockReturnValue(O.some([15, 15])); - const component = getComponent( - { - ...aCobadgeCard, - info: { ...aCobadgeCard.info, brandLogo: undefined } - }, - store - ); - - const defaultBrandLogoUrl = "../../../img/wallet/cards-icons/unknown.png"; - const defaultBrandLogo = component.queryByTestId("cardImage"); - - expect(defaultBrandLogo).toHaveProp("source", { - testUri: defaultBrandLogoUrl - }); - }); - - it("should call navigateToCobadgeDetails when press on it", () => { - jest.spyOn(hooks, "useImageResize").mockReturnValue(O.none); - const spy = jest.spyOn(NavigationService, "dispatchNavigationAction"); - const component = getComponent(aCobadgeCard, store); - const cardComponent = component.queryByTestId("cardPreview"); - const expectedPayload: NavigationAction = { - type: "NAVIGATE", - payload: { - name: ROUTES.WALLET_NAVIGATOR, - params: { - screen: ROUTES.WALLET_COBADGE_DETAIL, - params: { - cobadge: aCobadgeCard - } - } - } - }; - if (cardComponent) { - fireEvent.press(cardComponent); - expect(spy).toHaveBeenCalledWith(expectedPayload); - } - }); -}); - -const getComponent = ( - cobadge: CreditCardPaymentMethod, - store: Store -) => - renderScreenWithNavigationStoreContext( - () => , - "WALLET_HOME", - {}, - store - ); diff --git a/ts/features/wallet/cobadge/screen/CobadgeDetailScreen.tsx b/ts/features/wallet/cobadge/screen/CobadgeDetailScreen.tsx deleted file mode 100644 index 4da614806bc..00000000000 --- a/ts/features/wallet/cobadge/screen/CobadgeDetailScreen.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { Banner, IOLogoPaymentExtType } from "@pagopa/io-app-design-system"; -import { openAuthenticationSession } from "@pagopa/io-react-native-login-utils"; -import { sequenceS } from "fp-ts/lib/Apply"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import * as React from "react"; -import { Route, useRoute } from "@react-navigation/native"; -import WorkunitGenericFailure from "../../../../components/error/WorkunitGenericFailure"; -import I18n from "../../../../i18n"; -import { useIOSelector } from "../../../../store/hooks"; -import { creditCardByIdSelector } from "../../../../store/reducers/wallet/wallets"; -import { CreditCardPaymentMethod } from "../../../../types/pagopa"; -import { acceptedPaymentMethodsFaqUrl } from "../../../../urls"; -import { isCobadge } from "../../../../utils/paymentMethodCapabilities"; -import BasePaymentMethodScreen from "../../common/BasePaymentMethodScreen"; -import { PaymentCardBig } from "../../../payments/common/components/PaymentCardBig"; - -export type CobadgeDetailScreenNavigationParams = Readonly<{ - // TODO: we should use only the id and retrieve it from the store, otherwise we lose all the updates - cobadge: CreditCardPaymentMethod; -}>; - -/** - * Detail screen for a cobadge card - * @constructor - */ -const CobadgeDetailScreen = () => { - const { cobadge } = - useRoute< - Route<"WALLET_COBADGE_DETAIL", CobadgeDetailScreenNavigationParams> - >().params; - const card = useIOSelector(state => - creditCardByIdSelector(state, cobadge.idWallet) - ); - const bannerViewRef = React.useRef(null); - if (card === undefined || !isCobadge(card)) { - return ; - } - const paymentCardData = pipe( - cobadge, - ({ info, abiInfo }) => - sequenceS(O.Monad)({ - // all or nothing, if one of these is missing we don't show the card - blurredNumber: O.fromNullable(info.blurredNumber), - expireMonth: O.fromNullable(info.expireMonth), - expireYear: O.fromNullable(info.expireYear), - holderName: O.fromNullable(info.holder), - cardIcon: O.fromNullable(info.brand) as O.Option, - abiCode: O.some(abiInfo?.abi), - bankName: O.some(abiInfo?.name) - // store gives it as string, - // is later checked by component and null case is handled - }), - O.map(cardData => ({ - ...cardData, - expirationDate: new Date( - Number(cardData.expireYear), - // month is 0 based, BE response is not - Number(cardData.expireMonth) - 1 - ) - })) - ); - - const cardComponent = pipe( - paymentCardData, - O.fold( - () => , - data => - ) - ); - return ( - - openAuthenticationSession(acceptedPaymentMethodsFaqUrl, "") - } - /> - } - /> - ); -}; - -export default CobadgeDetailScreen; diff --git a/ts/features/wallet/common/BasePaymentMethodScreen.tsx b/ts/features/wallet/common/BasePaymentMethodScreen.tsx deleted file mode 100644 index eb5909857e8..00000000000 --- a/ts/features/wallet/common/BasePaymentMethodScreen.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { useNavigation } from "@react-navigation/native"; -import * as React from "react"; -import { - Alert, - GestureResponderEvent, - Platform, - StyleSheet, - View -} from "react-native"; -import { ScrollView } from "react-native-gesture-handler"; -import { - IOColors, - VSpacer, - IOSpacingScale, - ListItemAction, - useIOToast -} from "@pagopa/io-app-design-system"; -import LoadingSpinnerOverlay from "../../../components/LoadingSpinnerOverlay"; -import { IOStyles } from "../../../components/core/variables/IOStyles"; -import BaseScreenComponent from "../../../components/screens/BaseScreenComponent"; -import FocusAwareStatusBar from "../../../components/ui/FocusAwareStatusBar"; -import I18n from "../../../i18n"; -import { deleteWalletRequest } from "../../../store/actions/wallet/wallets"; -import { useIODispatch, useIOSelector } from "../../../store/hooks"; -import { getWalletsById } from "../../../store/reducers/wallet/wallets"; -import { PaymentMethod } from "../../../types/pagopa"; -import { emptyContextualHelp } from "../../../utils/emptyContextualHelp"; -import { isDesignSystemEnabledSelector } from "../../../store/reducers/persistedPreferences"; - -type Props = { - paymentMethod: PaymentMethod; - card: React.ReactNode; - content: React.ReactNode; - headerTitle?: string; -}; - -// ----------------------------- component ----------------------------------- - -/** - * Base layout for payment methods screen & legacy delete handling - */ -const BasePaymentMethodScreen = (props: Props) => { - const { card, content, paymentMethod } = props; - const hasErrorDelete = pot.isError(useIOSelector(getWalletsById)); - const [isLoadingDelete, setIsLoadingDelete] = React.useState(false); - const dispatch = useIODispatch(); - const isDSenabled = useIOSelector(isDesignSystemEnabledSelector); - const blueHeaderColor = isDSenabled ? IOColors["blueIO-600"] : IOColors.blue; - - const navigation = useNavigation(); - const toast = useIOToast(); - - const deleteWallet = (walletId: number) => - dispatch( - deleteWalletRequest({ - walletId, - onSuccess: _ => { - toast.success(I18n.t("wallet.delete.successful")); - navigation.goBack(); - }, - onFailure: _ => { - toast.error(I18n.t("wallet.delete.failed")); - } - }) - ); - - React.useEffect(() => { - if (hasErrorDelete) { - setIsLoadingDelete(false); - } - }, [hasErrorDelete]); - - const onDeleteMethod = () => { - // Create a native Alert to confirm or cancel the delete action - Alert.alert( - I18n.t("wallet.newRemove.title"), - I18n.t("wallet.newRemove.body"), - [ - { - text: - Platform.OS === "ios" - ? I18n.t(`wallet.delete.ios.confirm`) - : I18n.t(`wallet.delete.android.confirm`), - style: "destructive", - onPress: () => { - deleteWallet(paymentMethod.idWallet); - } - }, - { - text: I18n.t("global.buttons.cancel"), - style: "default" - } - ], - { cancelable: false } - ); - }; - - if (isLoadingDelete) { - return ( - - ); - } - - return ( - - - - - {card} - - - - {content} - - - - - - - ); -}; - -// ----------------------------- utils & export ----------------------------------- - -const DeleteButton = ({ - onPress -}: { - onPress: (event: GestureResponderEvent) => void; -}) => ( - -); - -// ----------------------------- styles ----------------------------------- - -const cardContainerHorizontalSpacing: IOSpacingScale = 16; - -const styles = StyleSheet.create({ - cardContainer: { - paddingHorizontal: cardContainerHorizontalSpacing, - alignItems: "center", - marginBottom: "-15%", - aspectRatio: 1.7, - width: "100%" - }, - blueHeader: { - paddingTop: "75%", - marginTop: "-75%", - marginBottom: "15%" - } -}); - -export default BasePaymentMethodScreen; diff --git a/ts/features/wallet/component/NewMethodAddedNotifier.tsx b/ts/features/wallet/component/NewMethodAddedNotifier.tsx deleted file mode 100644 index 0165b1c23c5..00000000000 --- a/ts/features/wallet/component/NewMethodAddedNotifier.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { useState } from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { GlobalState } from "../../../store/reducers/types"; -import { useActionOnFocus } from "../../../utils/hooks/useOnFocus"; -import bancomatInformationBottomSheet from "../bancomat/utils/bancomatInformationBottomSheet"; -import { onboardingBancomatAddedPansSelector } from "../onboarding/bancomat/store/reducers/addedPans"; -import { navigateToOnboardingCoBadgeChooseTypeStartScreen } from "../onboarding/cobadge/navigation/action"; - -type Props = ReturnType & - ReturnType; - -/** - * Handle the notification for a new payment method added - * TODO: add other methods, handle only bancomat atm - * @constructor - */ -const NewPaymentMethodAddedNotifier = (props: Props) => { - // Save the latest visualized bottom sheet, in order to avoid to show it again if focus changed - const [lastNotifiedBancomatHash, setLastNotifiedBancomatHash] = - useState(""); - - const { present, bottomSheet } = bancomatInformationBottomSheet( - props.startCoBadgeOnboarding - ); - - useActionOnFocus(() => { - const lastAddedHash = props.addedBancomat.reduce( - (acc, val) => acc + val.idWallet.toString(), - "" - ); - // a new set of bancomat has been added and no bottomsheet has been dispayed - if (lastAddedHash !== "" && lastNotifiedBancomatHash !== lastAddedHash) { - setLastNotifiedBancomatHash(lastAddedHash); - void present(); - } - }); - - return bottomSheet; -}; - -const mapDispatchToProps = (_: Dispatch) => ({ - startCoBadgeOnboarding: () => - navigateToOnboardingCoBadgeChooseTypeStartScreen({}) -}); - -const mapStateToProps = (state: GlobalState) => ({ - addedBancomat: onboardingBancomatAddedPansSelector(state) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(NewPaymentMethodAddedNotifier); diff --git a/ts/features/wallet/component/__test__/CardLayoutPreview.test.tsx b/ts/features/wallet/component/__test__/CardLayoutPreview.test.tsx deleted file mode 100644 index 476b2d7f9fa..00000000000 --- a/ts/features/wallet/component/__test__/CardLayoutPreview.test.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { render } from "@testing-library/react-native"; -import { View } from "react-native"; -import * as React from "react"; -import { CardLayoutPreview } from "../card/CardLayoutPreview"; - -describe("CardLayoutPreview", () => { - it("should show the left and the right received components", () => { - const left = () => ; - const right = () => ; - const component = render( - - ); - - expect(component.queryByTestId("leftComponent")).not.toBeNull(); - expect(component.queryByTestId("right")).not.toBeNull(); - }); -}); diff --git a/ts/features/wallet/component/__test__/CardLogoPreview.test.tsx b/ts/features/wallet/component/__test__/CardLogoPreview.test.tsx deleted file mode 100644 index 622965890fc..00000000000 --- a/ts/features/wallet/component/__test__/CardLogoPreview.test.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { fireEvent, render } from "@testing-library/react-native"; -import { View } from "react-native"; -import * as React from "react"; -import { CardLogoPreview } from "../card/CardLogoPreview"; -import anImage from "../../../../../img/wallet/cards-icons/bPay.png"; - -describe("CardLogoPreview", () => { - it("should show the left component, the image and when press should run the onPress", () => { - const left = () => ; - const onPress = jest.fn(); - const component = render( - - ); - const cardImage = component.queryByTestId("cardImage"); - const cardPreview = component.queryByTestId("cardPreview"); - - expect(component.queryByTestId("leftComponent")).not.toBeNull(); - expect(cardImage).not.toBeNull(); - expect(cardImage).toHaveProp("source", anImage); - - if (cardPreview !== null) { - fireEvent.press(cardPreview); - expect(onPress).toHaveBeenCalledTimes(1); - } - }); -}); diff --git a/ts/features/wallet/component/__test__/FeaturedCardCarousel.test.tsx b/ts/features/wallet/component/__test__/FeaturedCardCarousel.test.tsx deleted file mode 100644 index 3d08349ee14..00000000000 --- a/ts/features/wallet/component/__test__/FeaturedCardCarousel.test.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import * as React from "react"; -import configureMockStore, { MockStoreEnhanced } from "redux-mock-store"; -import { BonusVisibilityEnum } from "../../../../../definitions/content/BonusVisibility"; -import { appReducer } from "../../../../store/reducers"; -import { GlobalState } from "../../../../store/reducers/types"; -import { renderScreenWithNavigationStoreContext } from "../../../../utils/testWrapper"; -import { availableBonuses } from "../../../bonus/__mock__/availableBonuses"; -import * as cgnDetailSelectors from "../../../bonus/cgn/store/reducers/details"; -import { loadAvailableBonuses } from "../../../bonus/common/store/actions/availableBonusesTypes"; -import * as bonus from "../../../bonus/common/utils"; -import { ID_CGN_TYPE } from "../../../bonus/common/utils"; -import { MESSAGES_ROUTES } from "../../../messages/navigation/routes"; -import FeaturedCardCarousel from "../card/FeaturedCardCarousel"; - -jest.mock("react-native-share", () => jest.fn()); -describe("FeaturedCardCarousel", () => { - it("CGN should be not displayed (FF off, CGN not enrolled)", () => { - jest - .spyOn(bonus, "mapBonusIdFeatureFlag") - .mockImplementation( - () => new Map([[ID_CGN_TYPE, false]]) - ); - jest - .spyOn(cgnDetailSelectors, "isCgnEnrolledSelector") - .mockImplementation(() => false); - - const mockStore = configureMockStore(); - const cgnBonus = availableBonuses[2]; - const updatedAvailableBonuses = availableBonuses.filter( - b => b.id_type !== ID_CGN_TYPE - ); - const withBonusAvailable = appReducer( - undefined, - loadAvailableBonuses.success([ - ...updatedAvailableBonuses, - { ...cgnBonus, visibility: BonusVisibilityEnum.visible } - ]) - ); - - const component = getComponent(mockStore(withBonusAvailable)); - expect(component).toBeDefined(); - const featuredCardCarousel = component.queryByTestId( - "FeaturedCardCarousel" - ); - expect(featuredCardCarousel).toBeNull(); - const cgnItem = component.queryByTestId("FeaturedCardCGNTestID"); - expect(cgnItem).toBeNull(); - }); - - it("CGN should be not displayed (FF on, CGN enrolled)", () => { - jest - .spyOn(bonus, "mapBonusIdFeatureFlag") - .mockImplementation( - () => new Map([[ID_CGN_TYPE, true]]) - ); - jest - .spyOn(cgnDetailSelectors, "isCgnEnrolledSelector") - .mockImplementation(() => true); - - const mockStore = configureMockStore(); - const cgnBonus = availableBonuses[2]; - const updatedAvailableBonuses = availableBonuses.filter( - b => b.id_type !== ID_CGN_TYPE - ); - const withBonusAvailable = appReducer( - undefined, - loadAvailableBonuses.success([ - ...updatedAvailableBonuses, - { ...cgnBonus, visibility: BonusVisibilityEnum.visible } - ]) - ); - - const component = getComponent(mockStore(withBonusAvailable)); - expect(component).toBeDefined(); - const featuredCardCarousel = component.queryByTestId( - "FeaturedCardCarousel" - ); - expect(featuredCardCarousel).toBeNull(); - const cgnItem = component.queryByTestId("FeaturedCardCGNTestID"); - expect(cgnItem).toBeNull(); - }); - - it("CGN should be not displayed (FF on, CGN undefined)", () => { - jest - .spyOn(bonus, "mapBonusIdFeatureFlag") - .mockImplementation( - () => new Map([[ID_CGN_TYPE, true]]) - ); - jest - .spyOn(cgnDetailSelectors, "isCgnEnrolledSelector") - .mockImplementation(() => undefined); - - const mockStore = configureMockStore(); - const cgnBonus = availableBonuses[2]; - const updatedAvailableBonuses = availableBonuses.filter( - b => b.id_type !== ID_CGN_TYPE - ); - const withBonusAvailable = appReducer( - undefined, - loadAvailableBonuses.success([ - ...updatedAvailableBonuses, - { ...cgnBonus, visibility: BonusVisibilityEnum.visible } - ]) - ); - - const component = getComponent(mockStore(withBonusAvailable)); - expect(component).toBeDefined(); - const featuredCardCarousel = component.queryByTestId( - "FeaturedCardCarousel" - ); - expect(featuredCardCarousel).toBeNull(); - const cgnItem = component.queryByTestId("FeaturedCardCGNTestID"); - expect(cgnItem).toBeNull(); - }); - - it("CGN should be not displayed (FF on, CGN not enrolled, bonus visibility hidden)", () => { - jest - .spyOn(bonus, "mapBonusIdFeatureFlag") - .mockImplementation( - () => new Map([[ID_CGN_TYPE, true]]) - ); - jest - .spyOn(cgnDetailSelectors, "isCgnEnrolledSelector") - .mockImplementation(() => false); - - const mockStore = configureMockStore(); - const cgnBonus = availableBonuses[2]; - const updatedAvailableBonuses = availableBonuses.filter( - b => b.id_type !== ID_CGN_TYPE - ); - const withBonusAvailable = appReducer( - undefined, - loadAvailableBonuses.success([ - ...updatedAvailableBonuses, - { ...cgnBonus, visibility: BonusVisibilityEnum.hidden } - ]) - ); - - const component = getComponent(mockStore(withBonusAvailable)); - expect(component).toBeDefined(); - const featuredCardCarousel = component.queryByTestId( - "FeaturedCardCarousel" - ); - expect(featuredCardCarousel).toBeNull(); - const cgnItem = component.queryByTestId("FeaturedCardCGNTestID"); - expect(cgnItem).toBeNull(); - }); -}); - -const getComponent = (mockStore: MockStoreEnhanced) => - renderScreenWithNavigationStoreContext( - () => , - MESSAGES_ROUTES.MESSAGE_DETAIL, - {}, - mockStore - ); diff --git a/ts/features/wallet/component/__test__/PagoPaPaymentCapability.test.tsx b/ts/features/wallet/component/__test__/PagoPaPaymentCapability.test.tsx deleted file mode 100644 index f7f55983d65..00000000000 --- a/ts/features/wallet/component/__test__/PagoPaPaymentCapability.test.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import * as React from "react"; - -import { createStore } from "redux"; -import { EnableableFunctionsEnum } from "../../../../../definitions/pagopa/EnableableFunctions"; -import ROUTES from "../../../../navigation/routes"; -import { applicationChangeState } from "../../../../store/actions/application"; -import { fetchWalletsSuccess } from "../../../../store/actions/wallet/wallets"; -import { appReducer } from "../../../../store/reducers"; -import { GlobalState } from "../../../../store/reducers/types"; -import { walletsV2_1 } from "../../../../store/reducers/wallet/__mocks__/wallets"; -import { - BancomatPaymentMethod, - CreditCardPaymentMethod, - CreditCardType, - PatchedWalletV2ListResponse, - PaymentMethod -} from "../../../../types/pagopa"; -import { renderScreenWithNavigationStoreContext } from "../../../../utils/testWrapper"; -import { convertWalletV2toWalletV1 } from "../../../../utils/walletv2"; -import PagoPaPaymentCapability from "../features/PagoPaPaymentCapability"; - -jest.mock("../../../../config", () => ({ pmActivatePaymentEnabled: true })); - -describe("PagoPaPaymentCapability", () => { - jest.useFakeTimers(); - it("should render a toggle with kind=CreditCard and without issuerAbiCode and enableableFunction contains pagoPA", () => { - const aNonMaestroCreditCard = { - info: { - brand: "VISA" as CreditCardType - } - } as CreditCardPaymentMethod; - const aPaymentMethod = { - ...aNonMaestroCreditCard, - kind: "CreditCard", - pagoPA: true, - idWallet: 23216, - enableableFunctions: [EnableableFunctionsEnum.pagoPA] - } as PaymentMethod; - - const paymentMethods = walletsV2_1 as PatchedWalletV2ListResponse; - const updatedMethods = paymentMethods.data!.map(w => - convertWalletV2toWalletV1({ ...w, pagoPA: false }) - ); - - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - store.dispatch(fetchWalletsSuccess(updatedMethods)); - - const testComponent = renderScreenWithNavigationStoreContext( - () => , - ROUTES.WALLET_HOME, - {}, - store - ); - - expect(testComponent.getByTestId("PaymentStatusSwitch")).toBeTruthy(); - }); - - it("should render a badge with the text Incompatible if passed a payment method of kind Bancomat", () => { - const aBancomat = {} as BancomatPaymentMethod; - const aPaymentMethod = { - ...aBancomat, - kind: "Bancomat", - enableableFunctions: [] - } as PaymentMethod; - - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - - const component = renderScreenWithNavigationStoreContext( - () => , - ROUTES.WALLET_HOME, - {}, - store - ); - - expect(component.getByText("Incompatible")).toBeTruthy(); - }); - - it("should render a badge with the text Incompatible if passed a co-badge, payment method of kind CreditCard with issuerAbiCode and doesn't have enableableFunction pagoPA", () => { - const aNonMaestroCreditCard = { - info: { - brand: "VISA" as CreditCardType, - issuerAbiCode: "123" - } - } as CreditCardPaymentMethod; - const aPaymentMethod = { - ...aNonMaestroCreditCard, - kind: "CreditCard", - pagoPA: false, - enableableFunctions: [] - } as PaymentMethod; - - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - - const component = renderScreenWithNavigationStoreContext( - () => , - ROUTES.WALLET_HOME, - {}, - store - ); - expect(component.getByText("Incompatible")).not.toBeNull(); - }); -}); diff --git a/ts/features/wallet/component/card/BaseCardComponent.tsx b/ts/features/wallet/component/card/BaseCardComponent.tsx deleted file mode 100644 index e4ff7250f45..00000000000 --- a/ts/features/wallet/component/card/BaseCardComponent.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import * as React from "react"; -import { View, Platform, StyleSheet } from "react-native"; -import { widthPercentageToDP } from "react-native-responsive-screen"; -import { IOColors, hexToRgba } from "@pagopa/io-app-design-system"; -import customVariables from "../../../../theme/variables"; -import { TestID } from "../../../../types/WithTestID"; - -type Props = { - topLeftCorner: React.ReactNode; - bottomLeftCorner: React.ReactNode; - bottomRightCorner: React.ReactNode; - accessibilityLabel?: string; -} & TestID; - -const opaqueBorderColor = hexToRgba(IOColors.black, 0.1); - -const styles = StyleSheet.create({ - cardBox: { - height: 192, - width: widthPercentageToDP("90%"), - maxWidth: 343, - paddingHorizontal: customVariables.contentPadding, - paddingTop: 20, - paddingBottom: 22, - flexDirection: "column", - justifyContent: "space-between", - backgroundColor: IOColors.greyUltraLight, - borderRadius: 8, - shadowColor: IOColors.black, - shadowOffset: { - width: 0, - height: 3 - }, - shadowOpacity: 0.18, - shadowRadius: 4.65, - zIndex: 7, - elevation: 7 - }, - shadowBox: { - marginBottom: -13, - borderRadius: 8, - borderTopWidth: 10, - borderTopColor: opaqueBorderColor, - height: 15, - width: widthPercentageToDP("90%"), - maxWidth: 343 - }, - bottomRow: { - flexDirection: "row", - justifyContent: "space-between", - alignItems: "flex-end" - } -}); - -/** - * The base component that represents a full bancomat card - * @param props - * @constructor - */ -const BaseCardComponent: React.FunctionComponent = (props: Props) => ( - <> - {Platform.OS === "android" && } - - {props.topLeftCorner} - - {props.bottomLeftCorner} - {props.bottomRightCorner} - - - -); - -export default BaseCardComponent; diff --git a/ts/features/wallet/component/card/BlurredPan.tsx b/ts/features/wallet/component/card/BlurredPan.tsx deleted file mode 100644 index 921ba7b6898..00000000000 --- a/ts/features/wallet/component/card/BlurredPan.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import * as React from "react"; -import { Monospace } from "../../../../components/core/typography/Monospace"; - -/** - * Represent a blurred pan using a consistent style within different components - * @param props - * @constructor - */ - -type OwnProps = Omit< - React.ComponentProps, - "color" | "weight" ->; - -export const BlurredPan: React.FunctionComponent = props => ( - - {props.children} - -); diff --git a/ts/features/wallet/component/card/BrandImage.tsx b/ts/features/wallet/component/card/BrandImage.tsx deleted file mode 100644 index 4ba7d5a4700..00000000000 --- a/ts/features/wallet/component/card/BrandImage.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import * as React from "react"; -import { Image, ImageSourcePropType, StyleSheet } from "react-native"; - -type Props = { - image: ImageSourcePropType; - scale?: number; -}; - -const styles = StyleSheet.create({ - // TODO: Remove this unused style, use const variables instead - // eslint-disable-next-line react-native/no-unused-styles - cardLogo: { - height: 30, - width: 48 - } -}); - -/** - * Represent in a standard way the brand for a payment method (eg: Visa logo, Mastercard logo, etc.) - * @param props - * @constructor - */ -export const BrandImage = (props: Props): React.ReactElement => ( - -); diff --git a/ts/features/wallet/component/card/CardLayoutPreview.tsx b/ts/features/wallet/component/card/CardLayoutPreview.tsx deleted file mode 100644 index 480f13d4297..00000000000 --- a/ts/features/wallet/component/card/CardLayoutPreview.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import * as React from "react"; -import { View, AccessibilityProps, Platform, StyleSheet } from "react-native"; -import { IOColors, hexToRgba, VSpacer } from "@pagopa/io-app-design-system"; -import TouchableDefaultOpacity from "../../../../components/TouchableDefaultOpacity"; - -type Props = { - left: React.ReactNode; - right: React.ReactNode; - onPress?: () => void; -} & AccessibilityProps; - -const opaqueBorderColor = hexToRgba(IOColors.black, 0.1); - -const styles = StyleSheet.create({ - card: { - // iOS and Android card shadow - shadowColor: IOColors.black, - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.1, - shadowRadius: 1.5, - elevation: -7, - zIndex: -7, - backgroundColor: IOColors.greyUltraLight, - borderRadius: 8, - marginLeft: 0, - marginRight: 0 - }, - cardInner: { - paddingBottom: 16, - paddingLeft: 16, - paddingRight: 16, - paddingTop: 18 - }, - row: { - flexDirection: "row" - }, - spaced: { - justifyContent: "space-between" - }, - - rotatedCard: { - shadowColor: IOColors.black, - marginBottom: -30, - flex: 1, - shadowRadius: 10, - shadowOpacity: 0.15, - transform: [{ perspective: 1200 }, { rotateX: "-20deg" }, { scaleX: 0.99 }], - height: 95 - }, - shadowBox: { - marginBottom: -15, - borderRadius: 8, - borderTopWidth: 8, - borderTopColor: opaqueBorderColor, - height: 15 - } -}); - -/** - * A preview card layout that generalizes a card preview for a payment method layout. - * Can be used to render a generic wallet preview card. - * @param props - * @constructor - */ -export const CardLayoutPreview: React.FunctionComponent = props => ( - <> - {/* In order to render the shadow on android */} - {Platform.OS === "android" && } - - - - - {props.left} - {props.right} - - - - - - -); diff --git a/ts/features/wallet/component/card/CardLogoPreview.tsx b/ts/features/wallet/component/card/CardLogoPreview.tsx deleted file mode 100644 index 53c00b75ed5..00000000000 --- a/ts/features/wallet/component/card/CardLogoPreview.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import * as React from "react"; -import { AccessibilityProps, ImageSourcePropType } from "react-native"; -import { BrandImage } from "./BrandImage"; -import { CardLayoutPreview } from "./CardLayoutPreview"; - -type Props = { - left: React.ReactNode; - image: ImageSourcePropType; - onPress?: () => void; -} & AccessibilityProps; - -/** - * A preview card that shows as right section an image of fixed dimensions. - * @param props - * @constructor - */ -export const CardLogoPreview: React.FunctionComponent = props => ( - } /> -); diff --git a/ts/features/wallet/component/card/FeaturedCard.tsx b/ts/features/wallet/component/card/FeaturedCard.tsx deleted file mode 100644 index 6afaf684ceb..00000000000 --- a/ts/features/wallet/component/card/FeaturedCard.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { IOColors, VSpacer } from "@pagopa/io-app-design-system"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import * as React from "react"; -import { Image, ImageSourcePropType, StyleSheet, View } from "react-native"; -import { widthPercentageToDP } from "react-native-responsive-screen"; -import TouchableDefaultOpacity, { - TouchableDefaultOpacityProps -} from "../../../../components/TouchableDefaultOpacity"; -import { IOBadge } from "../../../../components/core/IOBadge"; -import { H3 } from "../../../../components/core/typography/H3"; -import { IOStyles } from "../../../../components/core/variables/IOStyles"; -import I18n from "../../../../i18n"; -import customVariables from "../../../../theme/variables"; - -type Props = { - title: string; - onPress?: () => void; - image?: ImageSourcePropType; - isNew: boolean; - testID?: TouchableDefaultOpacityProps["testID"]; -}; - -const styles = StyleSheet.create({ - container: { - paddingHorizontal: customVariables.contentPadding / 2, - paddingVertical: 14, - marginBottom: 10, - borderRadius: 8, - width: widthPercentageToDP("42.13%"), - backgroundColor: IOColors.white, - shadowColor: customVariables.cardShadow, - shadowOffset: { - width: 0, - height: 5 - }, - shadowOpacity: 0.1, - shadowRadius: 10, - elevation: 2, - marginRight: widthPercentageToDP("2.93%") - }, - image: { - width: 40, - height: 40, - resizeMode: "contain" - } -}); - -const FeaturedCard: React.FunctionComponent = (props: Props) => ( - - - {pipe( - props.image, - O.fromNullable, - O.fold( - () => ( - - ), - i => ( - - ) - ) - )} - {props.isNew && ( - - - - )} - - -

- {props.title} -

-
-); - -export default FeaturedCard; diff --git a/ts/features/wallet/component/card/FeaturedCardCarousel.tsx b/ts/features/wallet/component/card/FeaturedCardCarousel.tsx deleted file mode 100644 index af02cdbe5d4..00000000000 --- a/ts/features/wallet/component/card/FeaturedCardCarousel.tsx +++ /dev/null @@ -1,198 +0,0 @@ -import { IOColors, IOToast } from "@pagopa/io-app-design-system"; -import { useNavigation } from "@react-navigation/native"; -import * as AR from "fp-ts/lib/Array"; -import * as O from "fp-ts/lib/Option"; -import { constUndefined, pipe } from "fp-ts/lib/function"; -import * as React from "react"; -import { useEffect } from "react"; -import { ScrollView, StyleSheet, View } from "react-native"; -import { connect } from "react-redux"; -import { BonusAvailable } from "../../../../../definitions/content/BonusAvailable"; -import cgnLogo from "../../../../../img/bonus/cgn/cgn_logo.png"; -import { H3 } from "../../../../components/core/typography/H3"; -import { IOStyles } from "../../../../components/core/variables/IOStyles"; -import I18n from "../../../../i18n"; -import { - AppParamsList, - IOStackNavigationProp -} from "../../../../navigation/params/AppParamsList"; -import { loadServiceDetail } from "../../../services/details/store/actions/details"; -import { Dispatch } from "../../../../store/actions/types"; -import { useIODispatch, useIOSelector } from "../../../../store/hooks"; -import { - isCGNEnabledSelector, - isCdcEnabledSelector -} from "../../../../store/reducers/backendStatus"; -import { GlobalState } from "../../../../store/reducers/types"; -import { cgnActivationStart } from "../../../bonus/cgn/store/actions/activation"; -import { isCgnEnrolledSelector } from "../../../bonus/cgn/store/reducers/details"; -import { - availableBonusTypesSelectorFromId, - serviceFromAvailableBonusSelector, - supportedAvailableBonusSelector -} from "../../../bonus/common/store/selectors"; -import { ID_CDC_TYPE, ID_CGN_TYPE } from "../../../bonus/common/utils"; -import { getRemoteLocale } from "../../../messages/utils/messages"; -import { SERVICES_ROUTES } from "../../../services/common/navigation/routes"; -import FeaturedCard from "./FeaturedCard"; - -type Props = ReturnType & - ReturnType; - -type BonusUtils = { - logo?: typeof cgnLogo; - handler: (bonus: BonusAvailable) => void; -}; - -const styles = StyleSheet.create({ - container: { - backgroundColor: IOColors.white, - paddingTop: 14 - }, - scrollViewPadding: { - paddingVertical: 15 - } -}); - -/** - * this component shows an horizontal scrollview of items - * an item represents a bonus that the app can handle (relative feature flag enabled and handler set) and its - * visibility is 'visible' or 'experimental' - */ - -const FeaturedCardCarousel: React.FunctionComponent = (props: Props) => { - const dispatch = useIODispatch(); - const navigation = useNavigation>(); - const isCgnEnabled = useIOSelector(isCGNEnabledSelector); - const isCdcEnabled = useIOSelector(isCdcEnabledSelector); - const cdcService = useIOSelector( - serviceFromAvailableBonusSelector(ID_CDC_TYPE) - ); - const cdcBonus = useIOSelector( - availableBonusTypesSelectorFromId(ID_CDC_TYPE) - ); - - const bonusMap: Map = new Map([]); - - // If the cdc service is not loaded try to load it - useEffect(() => { - const cdcServiceId = cdcBonus?.service_id ?? undefined; - if (isCdcEnabled && O.isNone(cdcService) && cdcServiceId) { - dispatch(loadServiceDetail.request(cdcServiceId)); - } - }, [cdcBonus, isCdcEnabled, cdcService, dispatch]); - - if (isCgnEnabled) { - bonusMap.set(ID_CGN_TYPE, { - logo: cgnLogo, - handler: _ => props.startCgnActivation() - }); - } - - if (isCdcEnabled) { - bonusMap.set(ID_CDC_TYPE, { - handler: _ => { - pipe( - cdcService, - O.fold( - () => { - // TODO: add mixpanel tracking and alert: https://pagopa.atlassian.net/browse/AP-14 - IOToast.info(I18n.t("bonus.cdc.serviceEntryPoint.notAvailable")); - }, - s => () => - navigation.navigate(SERVICES_ROUTES.SERVICES_NAVIGATOR, { - screen: SERVICES_ROUTES.SERVICE_DETAIL, - params: { serviceId: s.service_id } - }) - ) - ); - } - }); - } - - // are there any bonus to activate? - const anyBonusNotActive = - (props.cgnActiveBonus === false && isCgnEnabled) || isCdcEnabled; - - return props.availableBonusesList.length > 0 && anyBonusNotActive ? ( - - -

- {I18n.t("wallet.featured")} -

-
- - {AR.reverse([...props.availableBonusesList]).map((b, i) => { - const handler = pipe( - bonusMap.get(b.id_type), - O.fromNullable, - O.fold( - () => constUndefined, - bu => bu.handler - ) - ); - const logo = pipe( - bonusMap.get(b.id_type), - O.fromNullable, - O.fold( - () => undefined, - bu => bu.logo - ) - ); - const currentLocale = getRemoteLocale(); - - switch (b.id_type) { - case ID_CGN_TYPE: - return ( - props.cgnActiveBonus === false && - isCgnEnabled && ( - handler(b)} - /> - ) - ); - case ID_CDC_TYPE: - return ( - isCdcEnabled && ( - handler(b)} - /> - ) - ); - default: - return null; - } - })} - -
- ) : null; -}; - -const mapStateToProps = (state: GlobalState) => ({ - cgnActiveBonus: isCgnEnrolledSelector(state), - availableBonusesList: supportedAvailableBonusSelector(state) -}); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - startCgnActivation: () => dispatch(cgnActivationStart()) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(FeaturedCardCarousel); diff --git a/ts/features/wallet/component/card/WalletV2PreviewCards.tsx b/ts/features/wallet/component/card/WalletV2PreviewCards.tsx deleted file mode 100644 index 0b7240fc194..00000000000 --- a/ts/features/wallet/component/card/WalletV2PreviewCards.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { - bancomatPayConfigSelector, - isPaypalEnabledSelector -} from "../../../../store/reducers/backendStatus"; -import { GlobalState } from "../../../../store/reducers/types"; -import { paymentMethodListVisibleInWalletSelector } from "../../../../store/reducers/wallet/wallets"; -import { PaymentMethod } from "../../../../types/pagopa"; -import { isCobadge } from "../../../../utils/paymentMethodCapabilities"; -import BancomatWalletPreview from "../../bancomat/component/BancomatWalletPreview"; -import BPayWalletPreview from "../../bancomatpay/component/BPayWalletPreview"; -import CobadgeWalletPreview from "../../cobadge/component/CobadgeWalletPreview"; -import CreditCardWalletPreview from "../../creditCard/component/CreditCardWalletPreview"; -import PayPalWalletPreview from "../../paypal/PayPalWalletPreview"; - -type Props = ReturnType & - ReturnType; - -const paymentMethodPreview = ( - pm: PaymentMethod, - props: Props -): React.ReactElement | null => { - switch (pm.kind) { - case "PayPal": - if (!props.isPaypalEnabled) { - return null; - } - return ; - case "Bancomat": - return ; - case "CreditCard": - // We should distinguish between a plain credit card and a cobadge credit card. - // Unfortunately, the cobadge card doesn't have a own type but is a CreditCard that has an issuerAbiCode - return isCobadge(pm) ? ( - - ) : ( - - ); - case "BPay": - if (!props.bancomatPayConfig.display) { - return null; - } - return ; - } -}; - -/** - * The new wallet preview that renders all the new v2 methods as folded card preview - * @constructor - */ -const WalletV2PreviewCards: React.FunctionComponent = props => ( - <> - {pot.toUndefined( - pot.mapNullable(props.paymentMethods, pm => ( - <>{pm.map(p => paymentMethodPreview(p, props))} - )) - )} - -); - -const mapDispatchToProps = (_: Dispatch) => ({}); - -const mapStateToProps = (state: GlobalState) => ({ - paymentMethods: paymentMethodListVisibleInWalletSelector(state), - isPaypalEnabled: isPaypalEnabledSelector(state), - bancomatPayConfig: bancomatPayConfigSelector(state) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(WalletV2PreviewCards); diff --git a/ts/features/wallet/component/features/PagoPaPaymentCapability.tsx b/ts/features/wallet/component/features/PagoPaPaymentCapability.tsx deleted file mode 100644 index 2283412aa0d..00000000000 --- a/ts/features/wallet/component/features/PagoPaPaymentCapability.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import * as React from "react"; -import { View, Switch } from "react-native"; -import { ButtonLink } from "@pagopa/io-app-design-system"; -import { PreferencesListItem } from "../../../../components/PreferencesListItem"; -import TouchableDefaultOpacity from "../../../../components/TouchableDefaultOpacity"; -import { IOBadge } from "../../../../components/core/IOBadge"; -import LegacyMarkdown from "../../../../components/ui/Markdown/LegacyMarkdown"; -import I18n from "../../../../i18n"; -import { PaymentMethod } from "../../../../types/pagopa"; -import { PaymentSupportStatus } from "../../../../types/paymentMethodCapabilities"; -import { acceptedPaymentMethodsFaqUrl } from "../../../../urls"; -import { useIOBottomSheetAutoresizableModal } from "../../../../utils/hooks/bottomSheet"; -import { isPaymentSupported } from "../../../../utils/paymentMethodCapabilities"; -import { openWebUrl } from "../../../../utils/url"; -import PaymentStatusSwitch from "./PaymentStatusSwitch"; - -type Props = { paymentMethod: PaymentMethod }; - -const getLocales = () => ({ - available: I18n.t("wallet.methods.card.pagoPaCapability.active"), - arriving: I18n.t("wallet.methods.card.pagoPaCapability.arriving"), - incompatible: I18n.t("wallet.methods.card.pagoPaCapability.incompatible") -}); - -const availabilityBadge = ( - badgeType: PaymentSupportStatus, - paymentMethod: PaymentMethod -) => { - const { arriving, incompatible } = getLocales(); - switch (badgeType) { - case "available": - return ; - case "arriving": - return ; - case "notAvailable": - return ; - case "onboardableNotImplemented": - return ; - } -}; - -/** - * Represent the capability to pay in PagoPa of a payment method. - * - * We have 4 possible different cases: - * - The card can pay on IO -> has capability pagoPa - * - The card will be able to pay in the future on IO -> BPay - * - The card is not able to pay on IO, (no pagoPa capability) and type === PRV or Bancomat - * - The card can onboard another card that can pay on IO -> co-badge credit card (no pagoPa capability) and type !== PRV - * @param props - */ -const PagoPaPaymentCapability: React.FC = props => { - const onOpenLearnMoreAboutInAppPayments = () => - openWebUrl(acceptedPaymentMethodsFaqUrl); - const paymentSupported = isPaymentSupported(props.paymentMethod); - - const { present, bottomSheet } = useIOBottomSheetAutoresizableModal( - { - component: ( - - - {I18n.t("wallet.methods.card.pagoPaCapability.bottomSheetBody")} - - - - ), - title: I18n.t("wallet.methods.card.pagoPaCapability.bottomSheetTitle") - }, - 48 - ); - - return ( - <> - {bottomSheet} - - - {availabilityBadge(paymentSupported, props.paymentMethod)} -
- } - /> - - - ); -}; - -export default PagoPaPaymentCapability; diff --git a/ts/features/wallet/component/features/PaymentMethodFeatures.tsx b/ts/features/wallet/component/features/PaymentMethodFeatures.tsx deleted file mode 100644 index c8942581e7f..00000000000 --- a/ts/features/wallet/component/features/PaymentMethodFeatures.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { Alert } from "@pagopa/io-app-design-system"; -import * as E from "fp-ts/lib/Either"; -import { pipe } from "fp-ts/lib/function"; -import * as React from "react"; - -import I18n from "../../../../i18n"; -import { isIdPayEnabledSelector } from "../../../../store/reducers/backendStatus"; -import { PaymentMethod } from "../../../../types/pagopa"; -import { isPaymentMethodExpired } from "../../../../utils/paymentMethod"; - -import { useIOSelector } from "../../../../store/hooks"; -import PaymentMethodInitiatives from "./PaymentMethodInitiatives"; -import PaymentMethodSettings from "./PaymentMethodSettings"; - -type Props = { paymentMethod: PaymentMethod }; - -/** - * Display the features available for a payment method: - * - vertical initiatives (eg: cashback, fa) - * - global settings (payment capability, favourite, etc.) - */ -const PaymentMethodFeatures = ({ paymentMethod }: Props) => { - const isMethodExpired = pipe( - paymentMethod, - isPaymentMethodExpired, - E.getOrElse(() => false) - ); - const isIdpayEnabled = useIOSelector(isIdPayEnabledSelector); - - if (isMethodExpired) { - return ( - - ); - } - return ( - <> - {isIdpayEnabled ? ( - - ) : null} - - - ); -}; - -export default PaymentMethodFeatures; diff --git a/ts/features/wallet/component/features/PaymentMethodInitiatives.tsx b/ts/features/wallet/component/features/PaymentMethodInitiatives.tsx deleted file mode 100644 index 5329c6b5fbf..00000000000 --- a/ts/features/wallet/component/features/PaymentMethodInitiatives.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import { Body, H6, IOStyles, VSpacer } from "@pagopa/io-app-design-system"; -import { useFocusEffect, useNavigation } from "@react-navigation/native"; -import * as React from "react"; -import { View } from "react-native"; -import I18n from "../../../../i18n"; -import { IOStackNavigationProp } from "../../../../navigation/params/AppParamsList"; -import { WalletParamsList } from "../../../../navigation/params/WalletParamsList"; -import ROUTES from "../../../../navigation/routes"; -import { useIODispatch, useIOSelector } from "../../../../store/hooks"; -import { PaymentMethod } from "../../../../types/pagopa"; -import { IdPayInstrumentInitiativesList } from "../../../idpay/wallet/components/IdPayInstrumentInitiativesList"; -import { - idPayInitiativesFromInstrumentGet, - idPayInitiativesFromInstrumentRefreshStart, - idPayInitiativesFromInstrumentRefreshStop -} from "../../../idpay/wallet/store/actions"; -import { idPayEnabledInitiativesFromInstrumentSelector } from "../../../idpay/wallet/store/reducers"; - -type Props = { - paymentMethod: PaymentMethod; -} & Pick, "style">; - -/** - * This component enlists the different initiatives active on the payment methods - * @param props - * @constructor - */ -const PaymentMethodInitiatives = (props: Props): React.ReactElement | null => { - const navigation = useNavigation>(); - const idWalletString = String(props.paymentMethod.idWallet); - - const dispatch = useIODispatch(); - - const startInitiativeRefreshPolling = React.useCallback(() => { - dispatch( - idPayInitiativesFromInstrumentGet.request({ - idWallet: idWalletString - }) - ); - dispatch( - idPayInitiativesFromInstrumentRefreshStart({ - idWallet: idWalletString - }) - ); - return () => { - dispatch(idPayInitiativesFromInstrumentRefreshStop()); - }; - }, [idWalletString, dispatch]); - - useFocusEffect(startInitiativeRefreshPolling); - - const initiativesList = useIOSelector( - idPayEnabledInitiativesFromInstrumentSelector - ); - - const navigateToPairableInitiativesList = () => - navigation.navigate(ROUTES.WALLET_IDPAY_INITIATIVE_LIST, { - idWallet: idWalletString - }); - - return initiativesList.length > 0 ? ( - - -
{I18n.t("wallet.capability.title")}
- - {I18n.t("idpay.wallet.preview.showAll")} - -
- - -
- ) : null; -}; - -export default PaymentMethodInitiatives; diff --git a/ts/features/wallet/component/features/PaymentMethodSettings.tsx b/ts/features/wallet/component/features/PaymentMethodSettings.tsx deleted file mode 100644 index 54290091ea6..00000000000 --- a/ts/features/wallet/component/features/PaymentMethodSettings.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import * as React from "react"; -import { H6, IOSpacingScale } from "@pagopa/io-app-design-system"; -import ItemSeparatorComponent from "../../../../components/ItemSeparatorComponent"; -import FavoritePaymentMethodSwitch from "../../../../components/wallet/FavoriteMethodSwitch"; -import I18n from "../../../../i18n"; -import { PaymentMethod } from "../../../../types/pagopa"; -import { isEnabledToPay } from "../../../../utils/paymentMethodCapabilities"; -import PagoPaPaymentCapability from "./PagoPaPaymentCapability"; - -type Props = { paymentMethod: PaymentMethod }; - -const componentVerticalSpacing: IOSpacingScale = 12; - -/** - * This component allows the user to choose and change the common settings for a payment methods - * The {@link FavoritePaymentMethodSwitch} should be rendered only if the payment method has the capability pagoPA and - * the payment are active (paymentMethod.pagoPA === true) - * @param props - * @constructor - */ -const PaymentMethodSettings = (props: Props): React.ReactElement => ( - <> -
- {I18n.t("global.buttons.settings")} -
- - - {isEnabledToPay(props.paymentMethod) && ( - - )} - -); - -export default PaymentMethodSettings; diff --git a/ts/features/wallet/component/features/PaymentStatusSwitch.tsx b/ts/features/wallet/component/features/PaymentStatusSwitch.tsx deleted file mode 100644 index b923c64ee86..00000000000 --- a/ts/features/wallet/component/features/PaymentStatusSwitch.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import { View } from "react-native"; -import * as React from "react"; -import { useEffect, useRef } from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { Icon, IOToast } from "@pagopa/io-app-design-system"; -import { RemoteSwitch } from "../../../../components/core/selection/RemoteSwitch"; -import { IOStyleVariables } from "../../../../components/core/variables/IOStyleVariables"; -import I18n from "../../../../i18n"; -import { mixpanelTrack } from "../../../../mixpanel"; -import { - fetchWalletsRequestWithExpBackoff, - updatePaymentStatus, - UpdatePaymentStatusPayload -} from "../../../../store/actions/wallet/wallets"; -import { GlobalState } from "../../../../store/reducers/types"; -import { getPaymentStatusById } from "../../../../store/reducers/wallet/wallets"; -import { PaymentMethod } from "../../../../types/pagopa"; - -type OwnProps = { - paymentMethod: PaymentMethod; -}; - -type Props = ReturnType & - ReturnType & - OwnProps; - -/** - * TypeGuard to extract the good payment status some(pot) - * @param p - */ -const toOptionPot = ( - p: pot.Pot -): O.Option> => - pot.fold( - p, - () => O.some(pot.none) as O.Option>, - () => O.some(pot.noneLoading), - newVal => - newVal !== undefined ? O.some(pot.noneUpdating(newVal)) : O.none, - error => O.some(pot.noneError(error)), - value => (value !== undefined ? O.some(pot.some(value)) : O.none), - value => (value !== undefined ? O.some(pot.someLoading(value)) : O.none), - (oldValue, newValue) => - oldValue !== undefined && newValue !== undefined - ? O.some(pot.someUpdating(oldValue, newValue)) - : O.none, - (value, error) => - value !== undefined ? O.some(pot.someError(value, error)) : O.none - ); - -/** - * This should never happens, track the error and display a close icon - * @constructor - */ -const Fallback = () => { - void mixpanelTrack("PAYMENT_STATUS_SWITCH_ID_NOT_IN_WALLET_LIST"); - return ( - - - - ); -}; -/** - * A switch that represent the current Payment status (enabled to payments) for a payment method. - * The user can change the setting using this Switch. - * @param props - * @constructor - */ -const PaymentStatusSwitch = (props: Props): React.ReactElement | null => { - // Should never be none, this will happens only if the idWallet is not in the walletList - const maybePaymentMethod = props.paymentStatus(props.paymentMethod.idWallet); - const paymentMethodExists = toOptionPot(maybePaymentMethod); - - const isError = pot.isError(maybePaymentMethod); - const isFirstRender = useRef(true); - - useEffect(() => { - if (!isFirstRender.current) { - if (isError) { - IOToast.error( - I18n.t("wallet.methods.card.pagoPaCapability.operationError") - ); - } - } else { - // eslint-disable-next-line functional/immutable-data - isFirstRender.current = false; - } - }, [isError, maybePaymentMethod]); - - return pipe( - paymentMethodExists, - O.fold( - () => , - val => ( - { - props.updatePaymentStatus({ - paymentEnabled: newVal, - idWallet: props.paymentMethod.idWallet - }); - }} - /> - ) - ) - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - loadWallets: () => dispatch(fetchWalletsRequestWithExpBackoff()), - updatePaymentStatus: (payload: UpdatePaymentStatusPayload) => - dispatch(updatePaymentStatus.request(payload)) -}); -const mapStateToProps = (state: GlobalState) => ({ - paymentStatus: (id: number) => getPaymentStatusById(state, id) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(PaymentStatusSwitch); diff --git a/ts/features/wallet/creditCard/component/CreditCardWalletPreview.tsx b/ts/features/wallet/creditCard/component/CreditCardWalletPreview.tsx deleted file mode 100644 index 5da7176ed7b..00000000000 --- a/ts/features/wallet/creditCard/component/CreditCardWalletPreview.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { IOStyles } from "../../../../components/core/variables/IOStyles"; -import { getCardIconFromBrandLogo } from "../../../../components/wallet/card/Logo"; -import { navigateToCreditCardDetailScreen } from "../../../../store/actions/navigation"; -import { GlobalState } from "../../../../store/reducers/types"; -import { CreditCardPaymentMethod } from "../../../../types/pagopa"; -import { BlurredPan } from "../../component/card/BlurredPan"; -import { CardLogoPreview } from "../../component/card/CardLogoPreview"; -import I18n from "../../../../i18n"; - -type OwnProps = { - creditCard: CreditCardPaymentMethod; -}; - -type Props = ReturnType & - ReturnType & - OwnProps; - -const getAccessibilityRepresentation = ( - creditCard: CreditCardPaymentMethod -) => { - const cardRepresentation = I18n.t("wallet.accessibility.folded.creditCard", { - brand: creditCard.info.brand, - blurredNumber: creditCard.info.blurredNumber - }); - const cta = I18n.t("wallet.accessibility.folded.cta"); - return `${cardRepresentation}, ${cta}`; -}; - -/** - * Folded preview representation for the Credit Card payment method. - * @param props - * @constructor - */ -const CreditCardWalletPreview = (props: Props): React.ReactElement => ( - - {props.creditCard.caption} - - } - image={getCardIconFromBrandLogo(props.creditCard.info)} - onPress={() => props.navigateToCreditCardDetail(props.creditCard)} - /> -); - -const mapDispatchToProps = (_: Dispatch) => ({ - navigateToCreditCardDetail: (creditCard: CreditCardPaymentMethod) => - navigateToCreditCardDetailScreen({ creditCard }) -}); -const mapStateToProps = (_: GlobalState) => ({}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(CreditCardWalletPreview); diff --git a/ts/features/wallet/creditCard/screen/CreditCardDetailScreen.tsx b/ts/features/wallet/creditCard/screen/CreditCardDetailScreen.tsx deleted file mode 100644 index 6aeea54b2c8..00000000000 --- a/ts/features/wallet/creditCard/screen/CreditCardDetailScreen.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import * as React from "react"; - -import { sequenceS } from "fp-ts/lib/Apply"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import { IOLogoPaymentExtType } from "@pagopa/io-app-design-system"; -import { Route, useRoute } from "@react-navigation/native"; -import LoadingSpinnerOverlay from "../../../../components/LoadingSpinnerOverlay"; -import WorkunitGenericFailure from "../../../../components/error/WorkunitGenericFailure"; -import { useIOSelector } from "../../../../store/hooks"; -import { creditCardByIdSelector } from "../../../../store/reducers/wallet/wallets"; -import { CreditCardPaymentMethod } from "../../../../types/pagopa"; -import { idPayAreInitiativesFromInstrumentLoadingSelector } from "../../../idpay/wallet/store/reducers"; -import BasePaymentMethodScreen from "../../common/BasePaymentMethodScreen"; -import PaymentMethodFeatures from "../../component/features/PaymentMethodFeatures"; -import { capitalize } from "../../../../utils/strings"; -import { PaymentCardBig } from "../../../payments/common/components/PaymentCardBig"; - -export type CreditCardDetailScreenNavigationParams = Readonly<{ - // Since we don't have a typed ID for the payment methods, we keep the creditCard as param even if it is then read by the store - creditCard: CreditCardPaymentMethod; -}>; - -/** - * Detail screen for a credit card - */ -const CreditCardDetailScreen = () => { - const [walletExisted, setWalletExisted] = React.useState(false); - const { creditCard: paramCreditCard } = - useRoute< - Route<"WALLET_CREDIT_CARD_DETAIL", CreditCardDetailScreenNavigationParams> - >().params; - // We need to read the card from the store to receive the updates - // TODO: to avoid this we need a store refactoring for the wallet section (all the component should receive the id and not the wallet, in order to update when needed) - const storeCreditCard = useIOSelector(state => - creditCardByIdSelector(state, paramCreditCard.idWallet) - ); - const areIdpayInitiativesLoading = useIOSelector( - idPayAreInitiativesFromInstrumentLoadingSelector - ); - - // This will set the flag `walletExisted` to true - // if, during this component lifecycle, a card actually - // existed in the state and has been removed. It's used to - // prevent the show of the `WorkunitGenericFailure`. - React.useEffect(() => { - if (storeCreditCard) { - setWalletExisted(true); - } - }, [storeCreditCard, setWalletExisted]); - - if (storeCreditCard !== undefined) { - const paymentCardData = pipe( - storeCreditCard, - ({ info }) => - sequenceS(O.Monad)({ - // all or nothing, if one of these is missing we don't show the card - blurredNumber: O.fromNullable(info.blurredNumber), - expireMonth: O.fromNullable(info.expireMonth), - expireYear: O.fromNullable(info.expireYear), - holder: O.fromNullable(info.holder), - brand: O.fromNullable(info.brand as IOLogoPaymentExtType | undefined) - // store gives it as string, - // is later checked by component and null case is handled - }), - O.map(cardData => ({ - ...cardData, - expDate: new Date( - Number(cardData.expireYear), - // month is 0 based, while BE response isn't - Number(cardData.expireMonth) - 1 - ) - })) - ); - - const cardComponent = pipe( - paymentCardData, - O.fold( - () => , - ({ expDate, holder, blurredNumber, brand }) => ( - - ) - ) - ); - const capitalizedCardCircuit = capitalize( - storeCreditCard.info.brand?.toLowerCase() ?? "" - ); - return ( - - } - headerTitle={`${capitalizedCardCircuit} ••${storeCreditCard.info.blurredNumber}`} - /> - - ); - } else if (!walletExisted) { - return ; - } - return null; -}; - -export default CreditCardDetailScreen; diff --git a/ts/features/wallet/creditCard/screen/__tests__/CreditCardDetailScreen.test.tsx b/ts/features/wallet/creditCard/screen/__tests__/CreditCardDetailScreen.test.tsx deleted file mode 100644 index f7898d87a47..00000000000 --- a/ts/features/wallet/creditCard/screen/__tests__/CreditCardDetailScreen.test.tsx +++ /dev/null @@ -1,177 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -import { waitFor } from "@testing-library/react-native"; -import { Store, createStore } from "redux"; -import { StatusEnum } from "../../../../../../definitions/idpay/InitiativeDTO"; -import { EnableableFunctionsEnum } from "../../../../../../definitions/pagopa/EnableableFunctions"; -import { WalletTypeEnum } from "../../../../../../definitions/pagopa/WalletV2"; -import { TypeEnum } from "../../../../../../definitions/pagopa/walletv2/CardInfo"; -import ROUTES from "../../../../../navigation/routes"; -import { applicationChangeState } from "../../../../../store/actions/application"; -import { fetchWalletsSuccess } from "../../../../../store/actions/wallet/wallets"; -import { appReducer } from "../../../../../store/reducers"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { walletsV2_1 } from "../../../../../store/reducers/wallet/__mocks__/wallets"; -import { - CreditCardPaymentMethod, - PatchedWalletV2ListResponse -} from "../../../../../types/pagopa"; -import { renderScreenWithNavigationStoreContext } from "../../../../../utils/testWrapper"; -import { convertWalletV2toWalletV1 } from "../../../../../utils/walletv2"; -import CreditCardDetailScreen from "../CreditCardDetailScreen"; - -const creditCard: CreditCardPaymentMethod = { - walletType: WalletTypeEnum.Card, - createDate: "2021-07-08", - enableableFunctions: [EnableableFunctionsEnum.pagoPA], - favourite: false, - idWallet: 23216, - info: { - blurredNumber: "0001", - brand: "Maestro", - brandLogo: - "https://wisp2.pagopa.gov.it/wallet/assets/img/creditcard/carta_maestro.png", - expireMonth: "11", - expireYear: "2021", - hashPan: - "d48a59cdfbe3da7e4fe25e28cbb47d5747720ecc6fc392c87f1636fe95db22f90004", - holder: "Maria Rossi", - htokenList: ["token1", "token2"], - type: TypeEnum.CRD - }, - onboardingChannel: "IO", - pagoPA: true, - updateDate: "2020-11-20", - kind: "CreditCard", - caption: "●●●●0001", - icon: 37 -}; - -const mockInitiative = { - initiativeId: "idpay", - initiativeName: "idpay", - status: StatusEnum.REFUNDABLE -}; -const initiativesFromInstrumentMock = jest - .spyOn( - require("../../../../idpay/wallet/store/reducers"), - "idPayEnabledInitiativesFromInstrumentSelector" - ) - .mockImplementation(() => [mockInitiative]); -const isIDPayEnabledMock = jest.spyOn( - require("../../../../../store/reducers/backendStatus"), - "isIdPayEnabledSelector" -); - -describe("Test CreditCardDetailScreen", () => { - jest.useFakeTimers(); - it("When navigate to the screen and no wallet are in the store, should render WorkunitGenericFailure", () => { - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - const screen = renderDetailScreen(store, creditCard); - expect(screen.getByTestId("WorkunitGenericFailure")).not.toBeNull(); - }); - it("does not render idpay initiatives when isIdpayEnabled is false, even if there are some ", async () => { - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - const paymentMethods = walletsV2_1 as PatchedWalletV2ListResponse; - const updatedMethods = paymentMethods.data!.map(w => - convertWalletV2toWalletV1({ ...w, pagoPA: false }) - ); - isIDPayEnabledMock.mockReturnValue(false); - - store.dispatch(fetchWalletsSuccess(updatedMethods)); - const screen = renderDetailScreen(store, creditCard); - await waitFor(() => { - expect(screen.queryByTestId("idPayInitiativesList")).toBeNull(); - }); - }); - it("does not render idpay initiatives when isIdpayEnabled is true, but there are no initiatives", async () => { - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - const paymentMethods = walletsV2_1 as PatchedWalletV2ListResponse; - const updatedMethods = paymentMethods.data!.map(w => - convertWalletV2toWalletV1({ ...w, pagoPA: false }) - ); - initiativesFromInstrumentMock.mockImplementation(() => []); - isIDPayEnabledMock.mockReturnValue(true); - - store.dispatch(fetchWalletsSuccess(updatedMethods)); - const screen = renderDetailScreen(store, creditCard); - await waitFor(() => { - expect(screen.queryByTestId("idPayInitiativesList")).toBeNull(); - }); - }); - it("renders idpay initiatives when isIdpayEnabled is true and there are some ", async () => { - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - const paymentMethods = walletsV2_1 as PatchedWalletV2ListResponse; - const updatedMethods = paymentMethods.data!.map(w => - convertWalletV2toWalletV1({ ...w, pagoPA: false }) - ); - initiativesFromInstrumentMock.mockImplementation(() => [mockInitiative]); - isIDPayEnabledMock.mockReturnValue(true); - - store.dispatch(fetchWalletsSuccess(updatedMethods)); - const screen = renderDetailScreen(store, creditCard); - await waitFor(() => { - expect(screen.queryByTestId("idPayInitiativesList")).not.toBeNull(); - }); - }); - it( - "When pagoPA=false the CreditCardDetailScreen should contains" + - " the CreditCardComponent and PaymentStatusSwitch", - () => { - const globalState = appReducer( - undefined, - applicationChangeState("active") - ); - const store = createStore(appReducer, globalState as any); - - const paymentMethods = walletsV2_1 as PatchedWalletV2ListResponse; - const updatedMethods = paymentMethods.data!.map(w => - convertWalletV2toWalletV1({ ...w, pagoPA: false }) - ); - store.dispatch(fetchWalletsSuccess(updatedMethods)); - const screen = renderDetailScreen(store, creditCard); - - expect(screen.queryByTestId("CreditCardComponent")).not.toBeNull(); - expect(screen.queryByTestId("FavoritePaymentMethodSwitch")).toBeNull(); - expect(screen.queryByTestId("PaymentStatusSwitch")).not.toBeNull(); - } - ); - it( - "When pagoPA=true the CreditCardDetailScreen should contains" + - " CreditCardComponent, FavoritePaymentMethodSwitch and PaymentStatusSwitch", - () => { - const globalState = appReducer( - undefined, - applicationChangeState("active") - ); - const store = createStore(appReducer, globalState as any); - - const paymentMethods = walletsV2_1 as PatchedWalletV2ListResponse; - const updatedMethods = paymentMethods.data!.map(w => - convertWalletV2toWalletV1(w) - ); - store.dispatch(fetchWalletsSuccess(updatedMethods)); - const screen = renderDetailScreen(store, creditCard); - - expect(screen.queryByTestId("CreditCardComponent")).not.toBeNull(); - expect( - screen.queryByTestId("FavoritePaymentMethodSwitch") - ).not.toBeNull(); - expect(screen.queryByTestId("PaymentStatusSwitch")).not.toBeNull(); - } - ); -}); - -const renderDetailScreen = ( - store: Store, - creditCard: CreditCardPaymentMethod -) => - renderScreenWithNavigationStoreContext( - CreditCardDetailScreen, - ROUTES.WALLET_CREDIT_CARD_DETAIL, - { creditCard }, - store - ); diff --git a/ts/features/wallet/onboarding/__e2e__/creditCardOnboarding.e2e.ts b/ts/features/wallet/onboarding/__e2e__/creditCardOnboarding.e2e.ts deleted file mode 100644 index 60f248c7cdf..00000000000 --- a/ts/features/wallet/onboarding/__e2e__/creditCardOnboarding.e2e.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { e2eWaitRenderTimeout } from "../../../../__e2e__/config"; -import { closeKeyboard, ensureLoggedIn } from "../../../../__e2e__/utils"; -import I18n from "../../../../i18n"; - -describe("Credit Card onboarding", () => { - beforeEach(async () => { - await device.launchApp({ newInstance: true }); - await ensureLoggedIn(); - }); - - it("when the user inserts all the required valid data, it should add successfully the credit card to the wallet", async () => { - await waitFor(element(by.text(I18n.t("global.navigator.wallet")))) - .toBeVisible() - .withTimeout(e2eWaitRenderTimeout); - - // Footer, Wallet icon - await element(by.text(I18n.t("global.navigator.wallet"))).tap(); - - await waitFor(element(by.id("walletAddNewPaymentMethodTestId"))) - .toBeVisible() - .withTimeout(e2eWaitRenderTimeout); - - // Button "+ Add" - await element(by.id("walletAddNewPaymentMethodTestId")).tap(); - - await waitFor(element(by.id("wallet.paymentMethod"))) - .toBeVisible() - .withTimeout(e2eWaitRenderTimeout); - - // Add payment method listItem in bottomSheet - await element(by.id("wallet.paymentMethod")).tap(); - - await waitFor(element(by.text(I18n.t("wallet.methods.card.name")))) - .toBeVisible() - .withTimeout(e2eWaitRenderTimeout); - // Add Credit Card List Item - await element(by.text(I18n.t("wallet.methods.card.name"))).tap(); - - await waitFor(element(by.id("cardHolderInput"))) - .toBeVisible() - .withTimeout(e2eWaitRenderTimeout); - - // Fill the credit card data - await element(by.id("cardHolderInput")).typeText("Gian Maria Mario"); - - await element(by.id("panInputMask")).typeText("4444333322221111"); - - await element(by.id("expirationDateInputMask")).typeText("1299"); - - await element(by.id("securityCodeInputMask")).typeText("123"); - - // Close the keyboard - await closeKeyboard(); - await element(by.text(I18n.t("global.buttons.continue"))).tap(); - - await waitFor(element(by.id("saveOrContinueButton"))) - .toBeVisible() - .withTimeout(e2eWaitRenderTimeout); - await element(by.id("saveOrContinueButton")).tap(); - - // Wait for 3ds webview - await waitFor(element(by.text(I18n.t("wallet.challenge3ds.description")))) - .toBeVisible() - .withTimeout(e2eWaitRenderTimeout); - - // Wait for success screen - await waitFor( - element( - by.text(I18n.t("wallet.outcomeMessage.addCreditCard.success.title")) - ) - ) - .toBeVisible() - .withTimeout(e2eWaitRenderTimeout); - - // Wait for return to wallet - await waitFor(element(by.id("wallet-home-header-title"))) - .toBeVisible() - .withTimeout(e2eWaitRenderTimeout); - }); -}); diff --git a/ts/features/wallet/onboarding/bancomat/components/BankPreviewItem.tsx b/ts/features/wallet/onboarding/bancomat/components/BankPreviewItem.tsx deleted file mode 100644 index 0c9d6165ace..00000000000 --- a/ts/features/wallet/onboarding/bancomat/components/BankPreviewItem.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { - Icon, - PressableListItemBase, - VSpacer -} from "@pagopa/io-app-design-system"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import * as React from "react"; -import { Image, ImageStyle, StyleProp, StyleSheet, View } from "react-native"; -import { Abi } from "../../../../../../definitions/pagopa/walletv2/Abi"; -import { LabelSmall } from "../../../../../components/core/typography/LabelSmall"; -import I18n from "../../../../../i18n"; -import { useImageResize } from "../hooks/useImageResize"; - -type Props = { - // TODO: change bank in info and use a generic type - bank: Abi; - onPress: (abi: string) => void; -}; - -const BASE_IMG_H = 40; -const BASE_IMG_W = 160; - -const styles = StyleSheet.create({ - listItem: { - flexDirection: "column", - alignItems: "flex-start" - }, - boundaryImage: { - height: BASE_IMG_H, - width: BASE_IMG_W, - justifyContent: "center", - alignItems: "flex-start" - } -}); - -// TODO: rename the component, in order to have a generic list item that accepts an image with a text -export const BankPreviewItem: React.FunctionComponent = ( - props: Props -) => { - const imageDimensions = useImageResize( - BASE_IMG_W, - BASE_IMG_H, - props.bank.logoUrl - ); - - const imageStyle: StyleProp | undefined = pipe( - imageDimensions, - O.fold( - () => undefined, - imgDim => ({ - width: imgDim[0], - height: imgDim[1], - resizeMode: "contain" - }) - ) - ); - - const onItemPress = () => props.bank.abi && props.onPress(props.bank.abi); - const bankName = props.bank.name || I18n.t("wallet.searchAbi.noName"); - const bankLogo = ( - - {props.bank.logoUrl && imageDimensions && ( - - )} - - ); - - return ( - - - {bankLogo} - - - {bankName} - - - - - ); -}; diff --git a/ts/features/wallet/onboarding/bancomat/hooks/useImageResize.ts b/ts/features/wallet/onboarding/bancomat/hooks/useImageResize.ts deleted file mode 100644 index a8f1d4d42d3..00000000000 --- a/ts/features/wallet/onboarding/bancomat/hooks/useImageResize.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { useEffect, useState } from "react"; -import { Image } from "react-native"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; - -/** - * To keep the image bounded in the predefined maximum dimensions (40x160) we use the resizeMode "contain" - * and always calculate the resize width keeping fixed the height to 40, in this way all images will have an height of 40 - * and a variable width until the limit of 160. - * Calculating the new image height based on its width may cause an over boundary dimension in some case. - * @param width - * @param height - * @param maxW - * @param maxH - */ -const handleImageDimensionSuccess = ( - width: number, - height: number, - maxW: number, - maxH: number -): [number, number] | undefined => { - if (width > 0 && height > 0) { - const ratio = Math.min(maxW / width, maxH / height); - return [width * ratio, height * ratio]; - } - return undefined; -}; -type FutureSize = O.Option<[number, number]>; -export const useImageResize = ( - maxWidth: number, - maxHeight: number, - imageUrl?: string -): FutureSize => { - const [size, setSize] = useState(O.none); - - useEffect(() => { - pipe( - imageUrl, - O.fromNullable, - O.map(url => - Image.getSize(url, (w, h) => - setSize( - O.fromNullable( - handleImageDimensionSuccess(w, h, maxWidth, maxHeight) - ) - ) - ) - ) - ); - }, [imageUrl, maxHeight, maxWidth]); - return size; -}; diff --git a/ts/features/wallet/onboarding/bancomat/saga/networking/index.ts b/ts/features/wallet/onboarding/bancomat/saga/networking/index.ts deleted file mode 100644 index 04244142052..00000000000 --- a/ts/features/wallet/onboarding/bancomat/saga/networking/index.ts +++ /dev/null @@ -1,165 +0,0 @@ -import * as E from "fp-ts/lib/Either"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import * as RA from "fp-ts/lib/ReadonlyArray"; -import { call, put } from "typed-redux-saga/macro"; -import { ActionType } from "typesafe-actions"; -import { ContentClient } from "../../../../../../api/content"; -import { PaymentManagerClient } from "../../../../../../api/pagopa"; -import { - isRawBancomat, - PaymentManagerToken -} from "../../../../../../types/pagopa"; -import { SagaCallReturnType } from "../../../../../../types/utils"; -import { - convertUnknownToError, - getGenericError -} from "../../../../../../utils/errors"; -import { getPaymentMethodHash } from "../../../../../../utils/paymentMethod"; -import { readablePrivacyReport } from "../../../../../../utils/reporters"; -import { SessionManager } from "../../../../../../utils/SessionManager"; -import { convertWalletV2toWalletV1 } from "../../../../../../utils/walletv2"; -import { - addBancomatToWallet, - loadAbi, - searchUserPans -} from "../../store/actions"; - -// load all bancomat abi -export function* handleLoadAbi( - getAbi: ReturnType["getAbiList"] -) { - try { - const getAbiWithRefreshResult: SagaCallReturnType = - yield* call(getAbi); - if (E.isRight(getAbiWithRefreshResult)) { - if (getAbiWithRefreshResult.right.status === 200) { - yield* put(loadAbi.success(getAbiWithRefreshResult.right.value)); - } else { - throw new Error( - `response status ${getAbiWithRefreshResult.right.status}` - ); - } - } else { - throw new Error(readablePrivacyReport(getAbiWithRefreshResult.left)); - } - } catch (e) { - yield* put(loadAbi.failure(convertUnknownToError(e))); - } -} - -// get user's pans -export function* handleLoadPans( - getPans: ReturnType["getPans"], - sessionManager: SessionManager, - action: ActionType -) { - try { - const getPansWithRefresh = sessionManager.withRefresh( - getPans(action.payload) - ); - - const getPansWithRefreshResult: SagaCallReturnType< - typeof getPansWithRefresh - > = yield* call(getPansWithRefresh); - if (E.isRight(getPansWithRefreshResult)) { - if (getPansWithRefreshResult.right.status === 200) { - const response = getPansWithRefreshResult.right.value.data; - return yield* put( - searchUserPans.success( - pipe( - O.fromNullable({ - cards: response?.data ?? RA.empty, - messages: response?.messages ?? RA.empty - }), - O.getOrElseW(() => ({ cards: [], messages: [] })) - ) - ) - ); - } else { - return yield* put( - searchUserPans.failure( - getGenericError( - new Error( - `response status ${getPansWithRefreshResult.right.status}` - ) - ) - ) - ); - } - } else { - return yield* put( - searchUserPans.failure( - getGenericError( - new Error(readablePrivacyReport(getPansWithRefreshResult.left)) - ) - ) - ); - } - } catch (e) { - if (e === "max-retries") { - return yield* put(searchUserPans.failure({ kind: "timeout" })); - } - if (typeof e === "string") { - return yield* put(searchUserPans.failure(getGenericError(new Error(e)))); - } - return yield* put( - searchUserPans.failure(getGenericError(convertUnknownToError(e))) - ); - } -} - -// add pan to wallet -export function* handleAddPan( - addPans: ReturnType["addPans"], - sessionManager: SessionManager, - action: ActionType -) { - try { - const addPansWithRefresh = sessionManager.withRefresh( - // add a card as an array of one element - addPans({ data: { data: [action.payload] } }) - ); - const addPansWithRefreshResult: SagaCallReturnType< - typeof addPansWithRefresh - > = yield* call(addPansWithRefresh); - if (E.isRight(addPansWithRefreshResult)) { - if (addPansWithRefreshResult.right.status === 200) { - const wallets = (addPansWithRefreshResult.right.value.data ?? []).map( - convertWalletV2toWalletV1 - ); - // search for the added bancomat. - const maybeWallet = pipe( - O.fromNullable( - wallets.find( - w => - w.paymentMethod && - getPaymentMethodHash(w.paymentMethod) === action.payload.hpan - ) - ) - ); - if ( - O.isSome(maybeWallet) && - isRawBancomat(maybeWallet.value.paymentMethod) - ) { - yield* put( - // success - addBancomatToWallet.success(maybeWallet.value.paymentMethod) - ); - } else { - throw new Error( - `cannot find added bancomat in wallets list response` - ); - } - } else { - throw new Error( - `response status ${addPansWithRefreshResult.right.status}` - ); - } - } else { - throw new Error(readablePrivacyReport(addPansWithRefreshResult.left)); - } - } catch (e) { - yield* put(addBancomatToWallet.failure(convertUnknownToError(e))); - } -} diff --git a/ts/features/wallet/onboarding/bancomat/store/actions/index.ts b/ts/features/wallet/onboarding/bancomat/store/actions/index.ts deleted file mode 100644 index 6507620d0a5..00000000000 --- a/ts/features/wallet/onboarding/bancomat/store/actions/index.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { - ActionType, - createAsyncAction, - createStandardAction -} from "typesafe-actions"; -import { AbiListResponse } from "../../../../../../../definitions/pagopa/walletv2/AbiListResponse"; -import { Card } from "../../../../../../../definitions/pagopa/walletv2/Card"; -import { RawBancomatPaymentMethod } from "../../../../../../types/pagopa"; -import { Message } from "../../../../../../../definitions/pagopa/walletv2/Message"; -import { NetworkError } from "../../../../../../utils/errors"; - -/** - * Request the list of all abi - */ -export const loadAbi = createAsyncAction( - "WALLET_LOAD_ABI_REQUEST", - "WALLET_LOAD_ABI_SUCCESS", - "WALLET_LOAD_ABI_FAILURE" -)(); - -// pans response contains pans (list of card) and messages (info services data provider) -export type PansResponse = { - cards: ReadonlyArray; - messages: ReadonlyArray; -}; -/** - * search for user's bancomat pans - */ -export const searchUserPans = createAsyncAction( - "WALLET_ONBOARDING_BANCOMAT_LOAD_PANS_REQUEST", - "WALLET_ONBOARDING_BANCOMAT_LOAD_PANS_SUCCESS", - "WALLET_ONBOARDING_BANCOMAT_LOAD_PANS_FAILURE" -)(); - -/** - * The user select the current bancomat to add to the wallet - */ -export const addBancomatToWallet = createAsyncAction( - "WALLET_ONBOARDING_BANCOMAT_ADD_REQUEST", - "WALLET_ONBOARDING_BANCOMAT_ADD_SUCCESS", - "WALLET_ONBOARDING_BANCOMAT_ADD_FAILURE" -)(); - -/** - * The user choose to start the workflow to add a new bancomat to the wallet - */ -export const walletAddBancomatStart = createStandardAction( - "WALLET_ONBOARDING_BANCOMAT_START" -)(); - -/** - * The user complete the workflow to add a new bancomat to the wallet - * (at least one bancomat has been added) - */ -export const walletAddBancomatCompleted = createStandardAction( - "WALLET_ONBOARDING_BANCOMAT_COMPLETED" -)(); - -/** - * The user choose to cancel the addition of a new bancomat to the wallet (no bancomat has been added) - */ -export const walletAddBancomatCancel = createStandardAction( - "WALLET_ONBOARDING_BANCOMAT_CANCEL" -)(); - -/** - * The workflow fails - */ -export const walletAddBancomatFailure = createStandardAction( - "WALLET_ONBOARDING_BANCOMAT_FAILURE" -)(); - -/** - * The user choose `back` from the first screen - */ -export const walletAddBancomatBack = createStandardAction( - "WALLET_ONBOARDING_BANCOMAT_BACK" -)(); - -export type AbiActions = - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType; diff --git a/ts/features/wallet/onboarding/bancomat/store/reducers/abiSelected.ts b/ts/features/wallet/onboarding/bancomat/store/reducers/abiSelected.ts deleted file mode 100644 index 9d6d375e6fa..00000000000 --- a/ts/features/wallet/onboarding/bancomat/store/reducers/abiSelected.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { getType } from "typesafe-actions"; -import { Action } from "../../../../../../store/actions/types"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { searchUserPans, walletAddBancomatStart } from "../actions"; - -export type AbiSelected = string | null; - -const abiSelectedReducer = ( - state: AbiSelected = null, - action: Action -): AbiSelected => { - switch (action.type) { - case getType(searchUserPans.request): - return action.payload ?? null; - // reset at the start - case getType(walletAddBancomatStart): - return null; - } - return state; -}; - -/** - * Return the abi chosen from the user to search for their bancomat - * @param state - */ -export const onboardingBancomatAbiSelectedSelector = ( - state: GlobalState -): string | undefined => - state.wallet.onboarding.bancomat.abiSelected === null - ? undefined - : state.wallet.onboarding.bancomat.abiSelected; - -export default abiSelectedReducer; diff --git a/ts/features/wallet/onboarding/bancomat/store/reducers/addedPans.ts b/ts/features/wallet/onboarding/bancomat/store/reducers/addedPans.ts deleted file mode 100644 index 773894ceb78..00000000000 --- a/ts/features/wallet/onboarding/bancomat/store/reducers/addedPans.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { createSelector } from "reselect"; -import { getType } from "typesafe-actions"; -import { Action } from "../../../../../../store/actions/types"; -import { - BancomatPaymentMethod, - RawBancomatPaymentMethod -} from "../../../../../../types/pagopa"; -import { enhanceBancomat } from "../../../../../../utils/paymentMethod"; -import { getValueOrElse } from "../../../../../../common/model/RemoteValue"; -import { abiSelector } from "../../../store/abi"; -import { addBancomatToWallet, walletAddBancomatStart } from "../actions"; - -const addedPansReducer = ( - state: ReadonlyArray = [], - action: Action -): ReadonlyArray => { - switch (action.type) { - // Register a new Bancomat added in the current onboarding session - case getType(addBancomatToWallet.success): - return [...state, action.payload]; - // Reset the state when starting a new onboarding bancomat workflow - case getType(walletAddBancomatStart): - return []; - } - return state; -}; - -export const onboardingBancomatAddedPansSelector = createSelector( - [state => state.wallet.onboarding.bancomat.addedPans, abiSelector], - (addedPans, remoteAbi): ReadonlyArray => - addedPans.map(p => enhanceBancomat(p, getValueOrElse(remoteAbi, {}))) -); - -export default addedPansReducer; diff --git a/ts/features/wallet/onboarding/bancomat/store/reducers/addingPans.ts b/ts/features/wallet/onboarding/bancomat/store/reducers/addingPans.ts deleted file mode 100644 index 31bba91157f..00000000000 --- a/ts/features/wallet/onboarding/bancomat/store/reducers/addingPans.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { getType } from "typesafe-actions"; -import { Card } from "../../../../../../../definitions/pagopa/walletv2/Card"; -import { Action } from "../../../../../../store/actions/types"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { RawBancomatPaymentMethod } from "../../../../../../types/pagopa"; -import { - remoteError, - remoteLoading, - remoteReady, - remoteUndefined, - RemoteValue -} from "../../../../../../common/model/RemoteValue"; -import { addBancomatToWallet, walletAddBancomatStart } from "../actions"; - -export type AddingPansState = { - addingResult: RemoteValue; - selectedPan?: Card; -}; - -const initialState: AddingPansState = { - addingResult: remoteUndefined -}; - -const addingPansReducer = ( - state: AddingPansState = initialState, - action: Action -): AddingPansState => { - switch (action.type) { - case getType(addBancomatToWallet.request): - return { - selectedPan: action.payload, - addingResult: remoteLoading - }; - case getType(addBancomatToWallet.success): - return { - ...state, - addingResult: remoteReady(action.payload) - }; - case getType(addBancomatToWallet.failure): - return { - ...state, - addingResult: remoteError(action.payload) - }; - case getType(walletAddBancomatStart): - return initialState; - } - return state; -}; - -export const onboardingBancomatChosenPanSelector = ( - state: GlobalState -): Card | undefined => state.wallet.onboarding.bancomat.addingPans.selectedPan; - -export const onboardingBancomatAddingResultSelector = ( - state: GlobalState -): RemoteValue => - state.wallet.onboarding.bancomat.addingPans.addingResult; - -export default addingPansReducer; diff --git a/ts/features/wallet/onboarding/bancomat/store/reducers/index.ts b/ts/features/wallet/onboarding/bancomat/store/reducers/index.ts deleted file mode 100644 index d15cdb0193a..00000000000 --- a/ts/features/wallet/onboarding/bancomat/store/reducers/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Action, combineReducers } from "redux"; -import { RawBancomatPaymentMethod } from "../../../../../../types/pagopa"; -import abiSelectedReducer, { AbiSelected } from "./abiSelected"; -import addedPansReducer from "./addedPans"; -import addingPansReducer, { AddingPansState } from "./addingPans"; -import pansReducer, { Pans } from "./pans"; - -export type OnboardingBancomatState = { - foundPans: Pans; - addingPans: AddingPansState; - addedPans: ReadonlyArray; - abiSelected: AbiSelected; -}; - -const onboardingBancomatReducer = combineReducers< - OnboardingBancomatState, - Action ->({ - // the bancomat pans found for the user during the onboarding phase of a new bancomat - foundPans: pansReducer, - // the bancomat pan that user is adding to his wallet - addingPans: addingPansReducer, - // the bancomat pan that user add to his wallet (during the last bancomat onboarding workflow) - addedPans: addedPansReducer, - // the bank (abi) chosen by the user during the onboarding phase. Can be null (the user skip the bank selection) - abiSelected: abiSelectedReducer -}); - -export default onboardingBancomatReducer; diff --git a/ts/features/wallet/onboarding/bancomat/store/reducers/pans.ts b/ts/features/wallet/onboarding/bancomat/store/reducers/pans.ts deleted file mode 100644 index 1ae18e247c5..00000000000 --- a/ts/features/wallet/onboarding/bancomat/store/reducers/pans.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { getType } from "typesafe-actions"; -import { Action } from "../../../../../../store/actions/types"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { - isError, - remoteError, - remoteLoading, - remoteReady, - remoteUndefined, - RemoteValue -} from "../../../../../../common/model/RemoteValue"; -import { PansResponse, searchUserPans } from "../actions"; -import { NetworkError } from "../../../../../../utils/errors"; - -export type Pans = RemoteValue; - -const pansReducer = (state: Pans = remoteUndefined, action: Action): Pans => { - switch (action.type) { - case getType(searchUserPans.request): - return remoteLoading; - case getType(searchUserPans.success): - return remoteReady(action.payload); - case getType(searchUserPans.failure): - return remoteError(action.payload); - } - return state; -}; - -export const onboardingBancomatFoundPansSelector = (state: GlobalState): Pans => - state.wallet.onboarding.bancomat.foundPans; - -export const onboardingBancomatPansIsError = (state: GlobalState): boolean => - isError(state.wallet.onboarding.bancomat.foundPans); - -export default pansReducer; diff --git a/ts/features/wallet/onboarding/bancomat/utils/abi.ts b/ts/features/wallet/onboarding/bancomat/utils/abi.ts deleted file mode 100644 index 8543500d551..00000000000 --- a/ts/features/wallet/onboarding/bancomat/utils/abi.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Abi } from "../../../../../../definitions/pagopa/walletv2/Abi"; - -/** - * This function aims to order the list of Abis returned by the PM in alphabetical order based on his name - * @param abis the abi list returned from the PM - */ -export const sortAbiByName = (abis: ReadonlyArray) => - [...abis].sort((abi1: Abi, abi2: Abi) => { - const abi1Name = abi1.name ?? ""; - const abi2Name = abi2.name ?? ""; - return abi1Name - .toLocaleLowerCase() - .localeCompare(abi2Name.toLocaleLowerCase()); - }); diff --git a/ts/features/wallet/onboarding/bancomatPay/analytics/index.ts b/ts/features/wallet/onboarding/bancomatPay/analytics/index.ts deleted file mode 100644 index 67f2349b089..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/analytics/index.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { getType } from "typesafe-actions"; -import { mixpanel } from "../../../../../mixpanel"; -import { Action } from "../../../../../store/actions/types"; -import { - getNetworkErrorMessage, - isTimeoutError -} from "../../../../../utils/errors"; -import { - addBPayToWalletAction, - searchUserBPay, - walletAddBPayBack, - walletAddBPayCancel, - walletAddBPayCompleted, - walletAddBPayFailure, - walletAddBPayStart -} from "../store/actions"; - -export const trackBPayAction = - (mp: NonNullable) => - (action: Action): void => { - switch (action.type) { - case getType(walletAddBPayStart): - case getType(walletAddBPayCompleted): - case getType(walletAddBPayCancel): - case getType(walletAddBPayBack): - case getType(addBPayToWalletAction.request): - case getType(addBPayToWalletAction.success): - return mp.track(action.type); - case getType(searchUserBPay.request): - return mp.track(action.type, { abi: action.payload ?? "all" }); - case getType(searchUserBPay.success): - return mp.track(action.type, { - count: action.payload.length, - serviceStateList: action.payload.map(bPay => - bPay.serviceState?.toString() - ) - }); - - case getType(addBPayToWalletAction.failure): - return mp.track(action.type, { - reason: getNetworkErrorMessage(action.payload) - }); - - case getType(searchUserBPay.failure): - return mp.track(action.type, { - reason: isTimeoutError(action.payload) - ? action.payload.kind - : action.payload.value.message - }); - case getType(walletAddBPayFailure): - return mp.track(action.type, { - reason: action.payload - }); - } - }; diff --git a/ts/features/wallet/onboarding/bancomatPay/api/placeholder b/ts/features/wallet/onboarding/bancomatPay/api/placeholder deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/ts/features/wallet/onboarding/bancomatPay/components/placeholder b/ts/features/wallet/onboarding/bancomatPay/components/placeholder deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/ts/features/wallet/onboarding/bancomatPay/navigation/action.ts b/ts/features/wallet/onboarding/bancomatPay/navigation/action.ts deleted file mode 100644 index 20a7c40ef06..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/navigation/action.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { CommonActions } from "@react-navigation/native"; -import NavigationService from "../../../../../navigation/NavigationService"; -import ROUTES from "../../../../../navigation/routes"; -import WALLET_ONBOARDING_BPAY_ROUTES from "./routes"; - -/** - * @deprecated Do not use this method when you have access to a navigation prop or useNavigation since it will behave differently, - * and many helper methods specific to screens won't be available. - */ -export const navigateToOnboardingBPaySearchStartScreen = () => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: WALLET_ONBOARDING_BPAY_ROUTES.MAIN, - params: { - screen: WALLET_ONBOARDING_BPAY_ROUTES.START - } - }) - ); - -/** - * @deprecated Do not use this method when you have access to a navigation prop or useNavigation since it will behave differently, - * and many helper methods specific to screens won't be available. - */ -export const navigateToOnboardingBPayChooseBank = () => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: WALLET_ONBOARDING_BPAY_ROUTES.MAIN, - params: { - screen: WALLET_ONBOARDING_BPAY_ROUTES.CHOOSE_BANK - } - }) - ); - -/** - * @deprecated Do not use this method when you have access to a navigation prop or useNavigation since it will behave differently, - * and many helper methods specific to screens won't be available. - */ -export const navigateToOnboardingBPaySearchAvailableUserAccount = () => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: WALLET_ONBOARDING_BPAY_ROUTES.MAIN, - params: { - screen: WALLET_ONBOARDING_BPAY_ROUTES.SEARCH_AVAILABLE_USER_ACCOUNT - } - }) - ); diff --git a/ts/features/wallet/onboarding/bancomatPay/navigation/navigator.tsx b/ts/features/wallet/onboarding/bancomatPay/navigation/navigator.tsx deleted file mode 100644 index 5992ae302db..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/navigation/navigator.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import * as React from "react"; -import { createStackNavigator } from "@react-navigation/stack"; -import BPaySearchBankScreen from "../screens/search/BPaySearchBankScreen"; -import BPaySearchStartScreen from "../screens/search/BPaySearchStartScreen"; -import SearchAvailableUserBPayScreen from "../screens/searchBPay/SearchAvailableUserBPayScreen"; -import { isGestureEnabled } from "../../../../../utils/navigation"; -import WALLET_ONBOARDING_BPAY_ROUTES from "./routes"; -import { PaymentMethodOnboardingBPayParamsList } from "./params"; - -const Stack = createStackNavigator(); - -const PaymentMethodOnboardingBPayNavigator = () => ( - - - - - -); - -export default PaymentMethodOnboardingBPayNavigator; diff --git a/ts/features/wallet/onboarding/bancomatPay/navigation/params.ts b/ts/features/wallet/onboarding/bancomatPay/navigation/params.ts deleted file mode 100644 index 7d34ce18ba6..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/navigation/params.ts +++ /dev/null @@ -1,8 +0,0 @@ -import WALLET_ONBOARDING_BPAY_ROUTES from "./routes"; - -export type PaymentMethodOnboardingBPayParamsList = { - [WALLET_ONBOARDING_BPAY_ROUTES.MAIN]: undefined; - [WALLET_ONBOARDING_BPAY_ROUTES.START]: undefined; - [WALLET_ONBOARDING_BPAY_ROUTES.CHOOSE_BANK]: undefined; - [WALLET_ONBOARDING_BPAY_ROUTES.SEARCH_AVAILABLE_USER_ACCOUNT]: undefined; -}; diff --git a/ts/features/wallet/onboarding/bancomatPay/navigation/routes.ts b/ts/features/wallet/onboarding/bancomatPay/navigation/routes.ts deleted file mode 100644 index 5ab61b1bbef..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/navigation/routes.ts +++ /dev/null @@ -1,10 +0,0 @@ -const WALLET_ONBOARDING_BPAY_ROUTES = { - MAIN: "WALLET_ONBOARDING_BPAY_MAIN", - - START: "WALLET_ONBOARDING_BPAY_START", - CHOOSE_BANK: "WALLET_ONBOARDING_BPAY_CHOOSE_BANK_SCREEN", - SEARCH_AVAILABLE_USER_ACCOUNT: - "WALLET_ONBOARDING_BPAY_SEARCH_AVAILABLE_USER_ACCOUNT" -} as const; - -export default WALLET_ONBOARDING_BPAY_ROUTES; diff --git a/ts/features/wallet/onboarding/bancomatPay/saga/networking/index.ts b/ts/features/wallet/onboarding/bancomatPay/saga/networking/index.ts deleted file mode 100644 index 0d7ea80b960..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/saga/networking/index.ts +++ /dev/null @@ -1,145 +0,0 @@ -import * as E from "fp-ts/lib/Either"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import { call, put } from "typed-redux-saga/macro"; -import { ActionType } from "typesafe-actions"; -import { RestBPayResponse } from "../../../../../../../definitions/pagopa/walletv2/RestBPayResponse"; -import { PaymentManagerClient } from "../../../../../../api/pagopa"; -import { - PatchedWalletV2ListResponse, - PaymentManagerToken -} from "../../../../../../types/pagopa"; -import { SagaCallReturnType } from "../../../../../../types/utils"; -import { - getGenericError, - getNetworkError -} from "../../../../../../utils/errors"; -import { readablePrivacyReport } from "../../../../../../utils/reporters"; -import { SessionManager } from "../../../../../../utils/SessionManager"; -import { fromPatchedWalletV2ToRawBPay } from "../../../../../../utils/walletv2"; -import { - addBPayToWalletAction as addBpayToWalletAction, - searchUserBPay -} from "../../store/actions"; - -/** - * Load all the user BPay accounts - */ -export function* handleSearchUserBPay( - searchBPay: ReturnType["searchBPay"], - sessionManager: SessionManager, - action: ActionType -) { - try { - const searchBPayWithRefresh = sessionManager.withRefresh( - searchBPay(action.payload) - ); - - const searchBPayWithRefreshResult: SagaCallReturnType< - typeof searchBPayWithRefresh - > = yield* call(searchBPayWithRefresh); - if (E.isRight(searchBPayWithRefreshResult)) { - const statusCode = searchBPayWithRefreshResult.right.status; - if (statusCode === 200) { - const payload: RestBPayResponse = - searchBPayWithRefreshResult.right.value; - const bPayList = payload.data ?? []; - return yield* put(searchUserBPay.success(bPayList)); - } else if (statusCode === 404) { - // the user doesn't own any bpay - return yield* put(searchUserBPay.success([])); - } else { - return yield* put( - searchUserBPay.failure( - getGenericError( - new Error( - `response status ${searchBPayWithRefreshResult.right.status}` - ) - ) - ) - ); - } - } else { - return yield* put( - searchUserBPay.failure( - getGenericError( - new Error(readablePrivacyReport(searchBPayWithRefreshResult.left)) - ) - ) - ); - } - } catch (e) { - return yield* put(searchUserBPay.failure(getNetworkError(e))); - } -} - -/** - * Add user BPay account to wallet - */ -export function* handleAddpayToWallet( - addBPayToWallet: ReturnType["addBPayToWallet"], - sessionManager: SessionManager, - action: ActionType -) { - try { - const addBPayToWalletWithRefresh = sessionManager.withRefresh( - addBPayToWallet({ data: [action.payload] }) - ); - - const addBPayToWalletWithRefreshResult: SagaCallReturnType< - typeof addBPayToWalletWithRefresh - > = yield* call(addBPayToWalletWithRefresh); - if (E.isRight(addBPayToWalletWithRefreshResult)) { - const statusCode = addBPayToWalletWithRefreshResult.right.status; - if (statusCode === 200) { - const payload: PatchedWalletV2ListResponse = - addBPayToWalletWithRefreshResult.right.value; - // search for the added bpay - const maybeAddedBPay = (payload.data ?? []) - .map(fromPatchedWalletV2ToRawBPay) - .find(w => - pipe( - w, - O.map(bp => bp.info.uidHash === action.payload.uidHash), - O.getOrElse(() => false) - ) - ); - if (maybeAddedBPay && O.isSome(maybeAddedBPay)) { - return yield* put( - addBpayToWalletAction.success(maybeAddedBPay.value) - ); - } else { - return yield* put( - addBpayToWalletAction.failure( - getGenericError( - new Error(`cannot find added bpay in wallets list response`) - ) - ) - ); - } - } else { - return yield* put( - addBpayToWalletAction.failure( - getGenericError( - new Error( - `response status ${addBPayToWalletWithRefreshResult.right.status}` - ) - ) - ) - ); - } - } else { - return yield* put( - addBpayToWalletAction.failure( - getGenericError( - new Error( - readablePrivacyReport(addBPayToWalletWithRefreshResult.left) - ) - ) - ) - ); - } - } catch (e) { - return yield* put(addBpayToWalletAction.failure(getNetworkError(e))); - } -} diff --git a/ts/features/wallet/onboarding/bancomatPay/saga/orchestration/addBPayToWallet.ts b/ts/features/wallet/onboarding/bancomatPay/saga/orchestration/addBPayToWallet.ts deleted file mode 100644 index 09d507bb984..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/saga/orchestration/addBPayToWallet.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { call, put } from "typed-redux-saga/macro"; -import NavigationService from "../../../../../../navigation/NavigationService"; -import ROUTES from "../../../../../../navigation/routes"; -import { - WorkUnitHandler, - executeWorkUnit, - withResetNavigationStack -} from "../../../../../../sagas/workUnit"; -import { navigateToWalletHome } from "../../../../../../store/actions/navigation"; -import { fetchWalletsRequest } from "../../../../../../store/actions/wallet/wallets"; - -import { navigateToOnboardingBPaySearchStartScreen } from "../../navigation/action"; -import WALLET_ONBOARDING_BPAY_ROUTES from "../../navigation/routes"; -import { - walletAddBPayBack, - walletAddBPayCancel, - walletAddBPayCompleted, - walletAddBPayFailure -} from "../../store/actions"; - -/** - * Define the workflow that allows the user to add BPay accounts to the wallet. - * The workflow ends when: - * - The user add at least one owned BPay to the wallet {@link walletAddBPayCompleted} - * - The user abort the insertion of a BPay {@link walletAddBPayCancel} - * - The user choose back from the first screen {@link walletAddBPayBack} - */ -function* bPayWorkUnit() { - return yield* call(executeWorkUnit, { - startScreenNavigation: navigateToOnboardingBPaySearchStartScreen, - startScreenName: WALLET_ONBOARDING_BPAY_ROUTES.START, - complete: walletAddBPayCompleted, - back: walletAddBPayBack, - cancel: walletAddBPayCancel, - failure: walletAddBPayFailure - }); -} - -/** - * add Bpay to wallet saga - */ -export function* addBPayToWalletSaga() { - const initialScreenName: ReturnType< - typeof NavigationService.getCurrentRouteName - > = yield* call(NavigationService.getCurrentRouteName); - const res = yield* call( - withResetNavigationStack, - bPayWorkUnit - ); - - if ( - res !== "back" && - initialScreenName === ROUTES.WALLET_ADD_PAYMENT_METHOD - ) { - // integration with the legacy "Add a payment" - // If the payment starts from "WALLET_ADD_DIGITAL_PAYMENT_METHOD", remove from stack - // This shouldn't happens if all the workflow will use the executeWorkUnit - - yield* call(navigateToWalletHome); - } - - if (res === "completed") { - // refresh wallets list - yield* put(fetchWalletsRequest()); - } -} diff --git a/ts/features/wallet/onboarding/bancomatPay/screens/add-account/AddBPayComponent.tsx b/ts/features/wallet/onboarding/bancomatPay/screens/add-account/AddBPayComponent.tsx deleted file mode 100644 index c0d554ba683..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/screens/add-account/AddBPayComponent.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { - FooterWithButtons, - HSpacer, - VSpacer -} from "@pagopa/io-app-design-system"; -import * as React from "react"; -import { SafeAreaView, StyleSheet, View } from "react-native"; -import { ScrollView } from "react-native-gesture-handler"; -import { connect } from "react-redux"; -import { InitializedProfile } from "../../../../../../../definitions/backend/InitializedProfile"; -import { BPay } from "../../../../../../../definitions/pagopa/BPay"; -import { H1 } from "../../../../../../components/core/typography/H1"; -import { H4 } from "../../../../../../components/core/typography/H4"; -import { IOStyles } from "../../../../../../components/core/variables/IOStyles"; -import BaseScreenComponent from "../../../../../../components/screens/BaseScreenComponent"; -import I18n from "../../../../../../i18n"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import BPayCard from "../../../../bancomatpay/component/BPayCard"; -import { abiListSelector } from "../../../store/abi"; - -type Props = { - account: BPay; - accountsNumber: number; - currentIndex: number; - handleContinue: () => void; - handleSkip: () => void; - profile?: InitializedProfile; -} & ReturnType & - Pick, "contextualHelp">; - -const styles = StyleSheet.create({ - container: { - alignItems: "center" - }, - title: { lineHeight: 33, alignSelf: "flex-start" }, - flexStart: { alignSelf: "flex-start" } -}); - -const AddBPayComponent: React.FunctionComponent = (props: Props) => ( - } - headerTitle={I18n.t("wallet.onboarding.bPay.headerTitle")} - contextualHelp={props.contextualHelp} - > - - - - -

- {I18n.t("wallet.onboarding.bPay.add.screenTitle")} -

- -

- {I18n.t("wallet.onboarding.bPay.add.label", { - current: props.currentIndex + 1, - length: props.accountsNumber - })} -

- - -
- -
-
- -
-); - -const mapStateToProps = (state: GlobalState) => ({ - abiList: abiListSelector(state) -}); - -export default connect(mapStateToProps)(AddBPayComponent); diff --git a/ts/features/wallet/onboarding/bancomatPay/screens/add-account/AddBPayScreen.tsx b/ts/features/wallet/onboarding/bancomatPay/screens/add-account/AddBPayScreen.tsx deleted file mode 100644 index 4afc7d0b5cf..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/screens/add-account/AddBPayScreen.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as AR from "fp-ts/lib/Array"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { BPay } from "../../../../../../../definitions/pagopa/BPay"; -import BaseScreenComponent from "../../../../../../components/screens/BaseScreenComponent"; -import { profileSelector } from "../../../../../../store/reducers/profile"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { - getValueOrElse, - isError, - isLoading, - isReady -} from "../../../../../../common/model/RemoteValue"; -import { - addBPayToWalletAction, - walletAddBPayCancel, - walletAddBPayCompleted -} from "../../store/actions"; -import { - onboardingBPayAddingResultSelector, - onboardingBPayChosenPanSelector -} from "../../store/reducers/addingBPay"; -import { onboardingBPayFoundAccountsSelector } from "../../store/reducers/foundBpay"; -import AddBPayComponent from "./AddBPayComponent"; -import LoadAddBPayComponent from "./LoadAddBPayComponent"; - -type Props = ReturnType & - ReturnType & - Pick, "contextualHelp">; - -type NextAction = { - index: number; - skip: boolean; -}; -/** - * This screen is displayed when BPay are found and ready to be added in wallet - * @constructor - */ -const AddBPayScreen = (props: Props): React.ReactElement | null => { - // next could be skip or not (a pan should be added) - const [currentAction, setNextAction] = React.useState({ - index: 0, - skip: false - }); - const { isAddingReady, bPayAccounts, onCompleted } = props; - - const currentIndex = currentAction.index; - - React.useEffect(() => { - // call onCompleted when the end of bpay pans has been reached - // and the adding phase has been completed (or it was skipped step) - if ( - currentIndex >= bPayAccounts.length && - (currentAction.skip || isAddingReady) - ) { - onCompleted(); - } - }, [currentAction, isAddingReady, bPayAccounts, onCompleted, currentIndex]); - - const nextPan = (skip: boolean) => { - const nextIndex = currentIndex + 1; - setNextAction({ index: nextIndex, skip }); - }; - - const handleOnContinue = () => { - if (currentIndex < props.bPayAccounts.length) { - props.addBPay(props.bPayAccounts[currentIndex]); - } - nextPan(false); - }; - - const currentPan = AR.lookup(currentIndex, [...props.bPayAccounts]); - - return props.loading || props.isAddingResultError ? ( - - pipe(props.selectedBPay, O.fromNullable, O.map(props.onRetry)) - } - /> - ) : O.isSome(currentPan) ? ( - nextPan(true)} - contextualHelp={props.contextualHelp} - /> - ) : null; // this should not happen -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - addBPay: (bPay: BPay) => dispatch(addBPayToWalletAction.request(bPay)), - onCompleted: () => dispatch(walletAddBPayCompleted()), - onCancel: () => dispatch(walletAddBPayCancel()), - onRetry: (bPay: BPay) => dispatch(addBPayToWalletAction.request(bPay)) -}); - -const mapStateToProps = (state: GlobalState) => { - const remoteBPay = onboardingBPayFoundAccountsSelector(state); - const addingResult = onboardingBPayAddingResultSelector(state); - const bPayAccounts = getValueOrElse(remoteBPay, []); - return { - isAddingReady: isReady(addingResult), - loading: isLoading(addingResult), - isAddingResultError: isError(addingResult), - remoteBPay, - selectedBPay: onboardingBPayChosenPanSelector(state), - bPayAccounts, - profile: pot.toUndefined(profileSelector(state)) - }; -}; - -export default connect(mapStateToProps, mapDispatchToProps)(AddBPayScreen); diff --git a/ts/features/wallet/onboarding/bancomatPay/screens/add-account/LoadAddBPayComponent.tsx b/ts/features/wallet/onboarding/bancomatPay/screens/add-account/LoadAddBPayComponent.tsx deleted file mode 100644 index 8fa52831913..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/screens/add-account/LoadAddBPayComponent.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import * as React from "react"; -import I18n from "../../../../../../i18n"; -import { useHardwareBackButton } from "../../../../../../hooks/useHardwareBackButton"; -import { LoadingErrorComponent } from "../../../../../../components/LoadingErrorComponent"; - -export type Props = { - isLoading: boolean; - onCancel: () => void; - onRetry: () => void; -}; - -/** - * This screen displays a loading or error message while adding a BPay account to the wallet - * In error case a retry button will be shown - * @constructor - */ -const LoadAddBPayComponent = (props: Props): React.ReactElement => { - useHardwareBackButton(() => { - if (!props.isLoading) { - props.onCancel(); - } - return true; - }); - return ( - - ); -}; - -export default LoadAddBPayComponent; diff --git a/ts/features/wallet/onboarding/bancomatPay/screens/search/BPaySearchBankScreen.tsx b/ts/features/wallet/onboarding/bancomatPay/screens/search/BPaySearchBankScreen.tsx deleted file mode 100644 index 926f960dbdb..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/screens/search/BPaySearchBankScreen.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import SearchBankScreen from "../../../common/searchBank/SearchBankScreen"; -import { searchUserBPay } from "../../store/actions"; -import { navigateToOnboardingBPaySearchAvailableUserAccount } from "../../navigation/action"; - -type Props = ReturnType & - ReturnType; - -/** - * This screen allows the user to choose a specific bank to search for their BPay Account. - * @constructor - */ -const BPaySearchBankScreen: React.FunctionComponent = (props: Props) => ( - -); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - searchAccounts: (abi?: string) => { - dispatch(searchUserBPay.request(abi)); - navigateToOnboardingBPaySearchAvailableUserAccount(); - } -}); - -const mapStateToProps = (_: GlobalState) => ({}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(BPaySearchBankScreen); diff --git a/ts/features/wallet/onboarding/bancomatPay/screens/search/BPaySearchStartScreen.tsx b/ts/features/wallet/onboarding/bancomatPay/screens/search/BPaySearchStartScreen.tsx deleted file mode 100644 index 03691e0b767..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/screens/search/BPaySearchStartScreen.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { isError, isLoading } from "../../../../../../common/model/RemoteValue"; -import { LightModalContext } from "../../../../../../components/ui/LightModal"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import TosBonusComponent from "../../../../../bonus/common/components/TosBonusComponent"; -import SearchStartScreen from "../../../common/searchBank/SearchStartScreen"; -import { abiSelector } from "../../../store/abi"; -import { - navigateToOnboardingBPayChooseBank, - navigateToOnboardingBPaySearchAvailableUserAccount -} from "../../navigation/action"; -import { - searchUserBPay, - walletAddBPayBack, - walletAddBPayCancel -} from "../../store/actions"; - -type Props = ReturnType & - ReturnType; - -const tos_url = "https://io.italia.it/app-content/privacy_bpay.html"; - -/** - * This screen allows the user to choose a specific bank to search for their Bancomat. - * @constructor - */ -const BPaySearchStartScreen: React.FunctionComponent = ( - props: Props -) => { - const { showModal, hideModal } = React.useContext(LightModalContext); - - const openTosModal = () => { - showModal(); - }; - - return ( - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - onCancel: () => dispatch(walletAddBPayCancel()), - onBack: () => dispatch(walletAddBPayBack()), - searchAccounts: (abi?: string) => { - dispatch(searchUserBPay.request(abi)); - navigateToOnboardingBPaySearchAvailableUserAccount(); - }, - navigateToSearchBankScreen: () => { - navigateToOnboardingBPayChooseBank(); - } -}); - -const mapStateToProps = (state: GlobalState) => ({ - isLoading: isLoading(abiSelector(state)), - isError: isError(abiSelector(state)) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(BPaySearchStartScreen); diff --git a/ts/features/wallet/onboarding/bancomatPay/screens/searchBPay/BPayKoNotFound.tsx b/ts/features/wallet/onboarding/bancomatPay/screens/searchBPay/BPayKoNotFound.tsx deleted file mode 100644 index 8dd53e73405..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/screens/searchBPay/BPayKoNotFound.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { FooterWithButtons } from "@pagopa/io-app-design-system"; -import * as React from "react"; -import { SafeAreaView } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import image from "../../../../../../../img/servicesStatus/error-detail-icon.png"; -import { IOStyles } from "../../../../../../components/core/variables/IOStyles"; -import { InfoScreenComponent } from "../../../../../../components/infoScreen/InfoScreenComponent"; -import { renderInfoRasterImage } from "../../../../../../components/infoScreen/imageRendering"; -import BaseScreenComponent from "../../../../../../components/screens/BaseScreenComponent"; -import I18n from "../../../../../../i18n"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { walletAddBPayCancel } from "../../store/actions"; - -export type Props = ReturnType & - ReturnType & - Pick, "contextualHelp">; - -const loadLocales = () => ({ - headerTitle: I18n.t("wallet.onboarding.bPay.headerTitle"), - title: I18n.t("wallet.onboarding.bPay.koNotFound.title"), - body: I18n.t("wallet.onboarding.bPay.koNotFound.body") -}); - -/** - * This screen informs the user that no BPay accounts in his name were found. - * @constructor - */ -const BPayKoNotFound = (props: Props): React.ReactElement => { - const { headerTitle, title, body } = loadLocales(); - - return ( - - - - - - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - cancel: () => dispatch(walletAddBPayCancel()) -}); - -const mapStateToProps = (_: GlobalState) => ({}); - -export default connect(mapStateToProps, mapDispatchToProps)(BPayKoNotFound); diff --git a/ts/features/wallet/onboarding/bancomatPay/screens/searchBPay/BPayKoTimeout.tsx b/ts/features/wallet/onboarding/bancomatPay/screens/searchBPay/BPayKoTimeout.tsx deleted file mode 100644 index 1cdda0d2438..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/screens/searchBPay/BPayKoTimeout.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import * as React from "react"; -import { SafeAreaView } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { FooterWithButtons } from "@pagopa/io-app-design-system"; -import image from "../../../../../../../img/servicesStatus/error-detail-icon.png"; -import { IOStyles } from "../../../../../../components/core/variables/IOStyles"; -import { renderInfoRasterImage } from "../../../../../../components/infoScreen/imageRendering"; -import { InfoScreenComponent } from "../../../../../../components/infoScreen/InfoScreenComponent"; -import BaseScreenComponent from "../../../../../../components/screens/BaseScreenComponent"; -import I18n from "../../../../../../i18n"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { searchUserBPay, walletAddBPayCancel } from "../../store/actions"; -import { onboardingBPayAbiSelectedSelector } from "../../store/reducers/abiSelected"; - -export type Props = ReturnType & - ReturnType & - Pick, "contextualHelp">; - -const loadLocales = () => ({ - headerTitle: I18n.t("wallet.onboarding.bPay.headerTitle"), - title: I18n.t("wallet.onboarding.bPay.koTimeout.title"), - body: I18n.t("wallet.onboarding.bPay.koTimeout.body"), - cancel: I18n.t("global.buttons.cancel"), - retry: I18n.t("global.buttons.retry") -}); - -/** - * This screen informs the user that the search operation could not be completed - * @constructor - */ -const BPayKoTimeout = (props: Props): React.ReactElement => { - const { headerTitle, title, body, cancel, retry } = loadLocales(); - return ( - - - - - props.retry(props.abiSelected) - } - }} - /> - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - cancel: () => dispatch(walletAddBPayCancel()), - retry: (abiSelected: string | undefined) => - dispatch(searchUserBPay.request(abiSelected)) -}); - -const mapStateToProps = (state: GlobalState) => ({ - abiSelected: onboardingBPayAbiSelectedSelector(state) -}); - -export default connect(mapStateToProps, mapDispatchToProps)(BPayKoTimeout); diff --git a/ts/features/wallet/onboarding/bancomatPay/screens/searchBPay/LoadBPaySearch.tsx b/ts/features/wallet/onboarding/bancomatPay/screens/searchBPay/LoadBPaySearch.tsx deleted file mode 100644 index 25d34e652e5..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/screens/searchBPay/LoadBPaySearch.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import I18n from "../../../../../../i18n"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { useHardwareBackButton } from "../../../../../../hooks/useHardwareBackButton"; -import { LoadingErrorComponent } from "../../../../../../components/LoadingErrorComponent"; -import { searchUserBPay, walletAddBPayCancel } from "../../store/actions"; -import { onboardingBPayAbiSelectedSelector } from "../../store/reducers/abiSelected"; -import { onboardingBpayFoundAccountsIsError } from "../../store/reducers/foundBpay"; - -export type Props = ReturnType & - ReturnType; - -/** - * This screen is displayed when searching for BPay accounts - * @constructor - */ -const LoadBPaySearch = (props: Props): React.ReactElement => { - useHardwareBackButton(() => { - if (!props.isLoading) { - props.cancel(); - } - return true; - }); - return ( - props.retry(props.abiSelected)} - /> - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - cancel: () => dispatch(walletAddBPayCancel()), - retry: (abiSelected: string | undefined) => - dispatch(searchUserBPay.request(abiSelected)) -}); - -const mapStateToProps = (state: GlobalState) => ({ - abiSelected: onboardingBPayAbiSelectedSelector(state), - isLoading: !onboardingBpayFoundAccountsIsError(state) -}); - -export default connect(mapStateToProps, mapDispatchToProps)(LoadBPaySearch); diff --git a/ts/features/wallet/onboarding/bancomatPay/screens/searchBPay/SearchAvailableUserBPayScreen.tsx b/ts/features/wallet/onboarding/bancomatPay/screens/searchBPay/SearchAvailableUserBPayScreen.tsx deleted file mode 100644 index 70c7a1d0b69..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/screens/searchBPay/SearchAvailableUserBPayScreen.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { emptyContextualHelp } from "../../../../../../utils/emptyContextualHelp"; -import { isTimeoutError } from "../../../../../../utils/errors"; -import { - isError, - isLoading, - isReady -} from "../../../../../../common/model/RemoteValue"; -import { onboardingBPayFoundAccountsSelector } from "../../store/reducers/foundBpay"; -import { useAvoidHardwareBackButton } from "../../../../../../utils/useAvoidHardwareBackButton"; -import AddBPayScreen from "../add-account/AddBPayScreen"; -import BPayKoNotFound from "./BPayKoNotFound"; -import BPayKoTimeout from "./BPayKoTimeout"; -import LoadBPaySearch from "./LoadBPaySearch"; - -export type Props = ReturnType & - ReturnType; - -/** - * This screen handle the errors and loading for the user BPay. - * @constructor - */ -const SearchAvailableUserBPayScreen = (props: Props): React.ReactElement => { - useAvoidHardwareBackButton(); - - const bPayAccounts = props.bPayAccounts; - const noBPayFound = isReady(bPayAccounts) && bPayAccounts.value.length === 0; - - if (noBPayFound) { - // The user doesn't have a BPay account - return ; - } - - if (isError(bPayAccounts) && isTimeoutError(bPayAccounts.error)) { - return ; - } - if (isLoading(bPayAccounts) || isError(bPayAccounts)) { - return ; - } - // success! The user can now optionally add found BPay accounts to the wallet - return ; -}; - -const mapDispatchToProps = (_: Dispatch) => ({}); - -const mapStateToProps = (state: GlobalState) => ({ - bPayAccounts: onboardingBPayFoundAccountsSelector(state) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(SearchAvailableUserBPayScreen); diff --git a/ts/features/wallet/onboarding/bancomatPay/store/actions/index.ts b/ts/features/wallet/onboarding/bancomatPay/store/actions/index.ts deleted file mode 100644 index f6566a097f6..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/store/actions/index.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { - ActionType, - createAsyncAction, - createStandardAction -} from "typesafe-actions"; -import { BPay } from "../../../../../../../definitions/pagopa/BPay"; -import { RawBPayPaymentMethod } from "../../../../../../types/pagopa"; -import { NetworkError } from "../../../../../../utils/errors"; - -/** - * Search for user's BPay accounts - */ -export const searchUserBPay = createAsyncAction( - "WALLET_ONBOARDING_BPAY_SEARCH_REQUEST", - "WALLET_ONBOARDING_BPAY_SEARCH_SUCCESS", - "WALLET_ONBOARDING_BPAY_SEARCH_FAILURE" -), NetworkError>(); - -/** - * The user add a specific BPay account to the wallet - */ -export const addBPayToWalletAction = createAsyncAction( - "WALLET_ONBOARDING_BPAY_ADD_REQUEST", - "WALLET_ONBOARDING_BPAY_ADD_SUCCESS", - "WALLET_ONBOARDING_BPAY_ADD_FAILURE" -)(); - -/** - * The user choose to start the workflow to add a new BPay to the wallet - */ -export const walletAddBPayStart = createStandardAction( - "WALLET_ONBOARDING_BPAY_START" -)(); - -/** - * The user complete the workflow to add a new BPay to the wallet - * (at least one BPay has been added) - */ -export const walletAddBPayCompleted = createStandardAction( - "WALLET_ONBOARDING_BPAY_COMPLETED" -)(); - -/** - * The user choose to cancel the addition of a new BPay to the wallet (no BPay has been added) - */ -export const walletAddBPayCancel = createStandardAction( - "WALLET_ONBOARDING_BPAY_CANCEL" -)(); - -/** - * The workflow fails - */ -export const walletAddBPayFailure = createStandardAction( - "WALLET_ONBOARDING_BPAY_FAILURE" -)(); - -/** - * The user choose `back` from the first screen - */ -export const walletAddBPayBack = createStandardAction( - "WALLET_ONBOARDING_BPAY_BACK" -)(); - -export type BPayActions = - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType; diff --git a/ts/features/wallet/onboarding/bancomatPay/store/reducers/__mock__/bpay.mock.ts b/ts/features/wallet/onboarding/bancomatPay/store/reducers/__mock__/bpay.mock.ts deleted file mode 100644 index dcc11309c03..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/store/reducers/__mock__/bpay.mock.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { BPay } from "../../../../../../../../definitions/pagopa/BPay"; - -export const bPayAttMock: BPay = { - bankName: "Bank Name", - instituteCode: "123", - numberObfuscated: "+3934*****123", - paymentInstruments: [], - serviceState: "ATT", - uidHash: "uidHash" -}; - -export const bPayDisMock: BPay = { - bankName: "Bank Name", - instituteCode: "123", - numberObfuscated: "+3934*****123", - paymentInstruments: [], - serviceState: "DIS", - uidHash: "uidHash" -}; diff --git a/ts/features/wallet/onboarding/bancomatPay/store/reducers/__test__/foundBpay.test.ts b/ts/features/wallet/onboarding/bancomatPay/store/reducers/__test__/foundBpay.test.ts deleted file mode 100644 index 9ab6d4e7817..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/store/reducers/__test__/foundBpay.test.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { applicationChangeState } from "../../../../../../../store/actions/application"; -import { appReducer } from "../../../../../../../store/reducers"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { NetworkError } from "../../../../../../../utils/errors"; -import { - remoteError, - remoteLoading, - remoteReady, - remoteUndefined -} from "../../../../../../../common/model/RemoteValue"; -import { searchUserBPay } from "../../actions"; -import { onboardingBPayFoundAccountsSelector } from "../foundBpay"; -import { bPayAttMock, bPayDisMock } from "../__mock__/bpay.mock"; - -jest.mock("@react-native-async-storage/async-storage", () => ({ - AsyncStorage: jest.fn() -})); - -jest.mock("react-native-share", () => ({ - open: jest.fn() -})); - -describe("test onboardingBPayFoundAccountsSelector", () => { - const globalState: GlobalState = appReducer( - undefined, - applicationChangeState("active") - ); - it("should return RemoteUndefined with the default state", () => { - expect(onboardingBPayFoundAccountsSelector(globalState)).toBe( - remoteUndefined - ); - }); - it("should return RemoteLoading after dispatch searchUserBPay.request", () => { - const loadingState: GlobalState = appReducer( - globalState, - searchUserBPay.request(undefined) - ); - - expect(onboardingBPayFoundAccountsSelector(loadingState)).toBe( - remoteLoading - ); - }); - it("should return RemoteError after dispatch searchUserBPay.failure", () => { - const error: NetworkError = { kind: "timeout" }; - - const loadingState: GlobalState = appReducer( - globalState, - searchUserBPay.failure(error) - ); - - expect(onboardingBPayFoundAccountsSelector(loadingState)).toStrictEqual( - remoteError(error) - ); - }); - it("should return remoteReady with a BPay serviceState===ATT", () => { - const loadingState: GlobalState = appReducer( - globalState, - searchUserBPay.success([bPayAttMock]) - ); - - expect(onboardingBPayFoundAccountsSelector(loadingState)).toStrictEqual( - remoteReady([bPayAttMock]) - ); - }); - it("should return remoteReady with an empty array if BPay serviceState===DIS", () => { - const loadingState: GlobalState = appReducer( - globalState, - searchUserBPay.success([bPayDisMock]) - ); - - expect(onboardingBPayFoundAccountsSelector(loadingState)).toStrictEqual( - remoteReady([]) - ); - }); - it("should return remoteReady with only BPay with serviceState!==DIS", () => { - const loadingState: GlobalState = appReducer( - globalState, - searchUserBPay.success([bPayDisMock, bPayAttMock]) - ); - - expect(onboardingBPayFoundAccountsSelector(loadingState)).toStrictEqual( - remoteReady([bPayAttMock]) - ); - }); - it("should return a BPay with a generic serviceState", () => { - const unknownServiceState = { ...bPayAttMock, serviceState: "UNKNOWN" }; - - const loadingState: GlobalState = appReducer( - globalState, - searchUserBPay.success([unknownServiceState]) - ); - - expect(onboardingBPayFoundAccountsSelector(loadingState)).toStrictEqual( - remoteReady([unknownServiceState]) - ); - }); - it("should return a BPay with an undefined serviceState", () => { - const undefinedServiceState = { ...bPayAttMock, serviceState: undefined }; - - const loadingState: GlobalState = appReducer( - globalState, - searchUserBPay.success([undefinedServiceState]) - ); - - expect(onboardingBPayFoundAccountsSelector(loadingState)).toStrictEqual( - remoteReady([undefinedServiceState]) - ); - }); -}); diff --git a/ts/features/wallet/onboarding/bancomatPay/store/reducers/abiSelected.ts b/ts/features/wallet/onboarding/bancomatPay/store/reducers/abiSelected.ts deleted file mode 100644 index 58d7eee707e..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/store/reducers/abiSelected.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { getType } from "typesafe-actions"; -import { Action } from "../../../../../../store/actions/types"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { searchUserBPay, walletAddBPayStart } from "../actions"; - -export type AbiSelected = string | null; - -const abiSelectedReducer = ( - state: AbiSelected = null, - action: Action -): AbiSelected => { - switch (action.type) { - case getType(searchUserBPay.request): - return action.payload ?? null; - // reset at the start - case getType(walletAddBPayStart): - return null; - } - return state; -}; - -/** - * Return the abi chosen from the user to search for their BPay - * @param state - */ -export const onboardingBPayAbiSelectedSelector = ( - state: GlobalState -): string | undefined => state.wallet.onboarding.bPay.abiSelected ?? undefined; - -export default abiSelectedReducer; diff --git a/ts/features/wallet/onboarding/bancomatPay/store/reducers/addedBPay.ts b/ts/features/wallet/onboarding/bancomatPay/store/reducers/addedBPay.ts deleted file mode 100644 index b47f75c9b09..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/store/reducers/addedBPay.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { createSelector } from "reselect"; -import { getType } from "typesafe-actions"; -import { Action } from "../../../../../../store/actions/types"; -import { - BPayPaymentMethod, - RawBPayPaymentMethod -} from "../../../../../../types/pagopa"; -import { enhanceBPay } from "../../../../../../utils/paymentMethod"; -import { getValueOrElse } from "../../../../../../common/model/RemoteValue"; -import { abiSelector } from "../../../store/abi"; -import { addBPayToWalletAction, walletAddBPayStart } from "../actions"; - -const addedBPayReducer = ( - state: ReadonlyArray = [], - action: Action -): ReadonlyArray => { - switch (action.type) { - // Register a new BPay account added in the current onboarding session - case getType(addBPayToWalletAction.success): - return [...state, action.payload]; - // Reset the state when starting a new BPay onboarding workflow - case getType(walletAddBPayStart): - return []; - } - return state; -}; - -export const onboardingBPayAddedAccountSelector = createSelector( - [state => state.wallet.onboarding.bPay.addedBPay, abiSelector], - (addedPans, remoteAbi): ReadonlyArray => - addedPans.map(p => enhanceBPay(p, getValueOrElse(remoteAbi, {}))) -); - -export default addedBPayReducer; diff --git a/ts/features/wallet/onboarding/bancomatPay/store/reducers/addingBPay.ts b/ts/features/wallet/onboarding/bancomatPay/store/reducers/addingBPay.ts deleted file mode 100644 index a55df64f42f..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/store/reducers/addingBPay.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { getType } from "typesafe-actions"; -import { BPay } from "../../../../../../../definitions/pagopa/BPay"; -import { Action } from "../../../../../../store/actions/types"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { RawBPayPaymentMethod } from "../../../../../../types/pagopa"; -import { - remoteError, - remoteLoading, - remoteReady, - remoteUndefined, - RemoteValue -} from "../../../../../../common/model/RemoteValue"; -import { addBPayToWalletAction, walletAddBPayStart } from "../actions"; -import { NetworkError } from "../../../../../../utils/errors"; - -export type AddingBPayState = { - addingResult: RemoteValue; - selectedBPay?: BPay; -}; - -const initialState: AddingBPayState = { - addingResult: remoteUndefined -}; - -const addingBPayReducer = ( - state: AddingBPayState = initialState, - action: Action -): AddingBPayState => { - switch (action.type) { - case getType(addBPayToWalletAction.request): - return { - selectedBPay: action.payload, - addingResult: remoteLoading - }; - case getType(addBPayToWalletAction.success): - return { - ...state, - addingResult: remoteReady(action.payload) - }; - case getType(addBPayToWalletAction.failure): - return { - ...state, - addingResult: remoteError(action.payload) - }; - case getType(walletAddBPayStart): - return initialState; - } - return state; -}; - -export const onboardingBPayChosenPanSelector = ( - state: GlobalState -): BPay | undefined => state.wallet.onboarding.bPay.addingBPay.selectedBPay; - -export const onboardingBPayAddingResultSelector = ( - state: GlobalState -): RemoteValue => - state.wallet.onboarding.bPay.addingBPay.addingResult; - -export default addingBPayReducer; diff --git a/ts/features/wallet/onboarding/bancomatPay/store/reducers/foundBpay.ts b/ts/features/wallet/onboarding/bancomatPay/store/reducers/foundBpay.ts deleted file mode 100644 index 0f2c0d83ebe..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/store/reducers/foundBpay.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { getType } from "typesafe-actions"; -import { BPay } from "../../../../../../../definitions/pagopa/BPay"; -import { Action } from "../../../../../../store/actions/types"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { NetworkError } from "../../../../../../utils/errors"; -import { - isError, - isReady, - remoteError, - remoteLoading, - remoteReady, - remoteUndefined, - RemoteValue -} from "../../../../../../common/model/RemoteValue"; -import { searchUserBPay } from "../actions"; - -export type RemoteBPay = RemoteValue, NetworkError>; - -const foundBpayReducer = ( - state: RemoteBPay = remoteUndefined, - action: Action -): RemoteBPay => { - switch (action.type) { - case getType(searchUserBPay.request): - return remoteLoading; - case getType(searchUserBPay.success): - return remoteReady(action.payload); - case getType(searchUserBPay.failure): - return remoteError(action.payload); - } - return state; -}; - -/** - * Return {@link RemoteBPay}, a list of BPay accounts to be viewed by the user. - * Remove from the list the disabled accounts - * @param state - */ -export const onboardingBPayFoundAccountsSelector = ( - state: GlobalState -): RemoteBPay => - isReady(state.wallet.onboarding.bPay.foundBPay) - ? remoteReady( - state.wallet.onboarding.bPay.foundBPay.value.filter( - // Remove from the found accounts the disabled (serviceState === "DIS") - bPay => bPay.serviceState !== "DIS" - ) - ) - : state.wallet.onboarding.bPay.foundBPay; - -/** - * The search BPay account APi have an error - * @param state - */ -export const onboardingBpayFoundAccountsIsError = ( - state: GlobalState -): boolean => isError(state.wallet.onboarding.bPay.foundBPay); - -export default foundBpayReducer; diff --git a/ts/features/wallet/onboarding/bancomatPay/store/reducers/index.ts b/ts/features/wallet/onboarding/bancomatPay/store/reducers/index.ts deleted file mode 100644 index c2584c26148..00000000000 --- a/ts/features/wallet/onboarding/bancomatPay/store/reducers/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Action, combineReducers } from "redux"; -import { RawBPayPaymentMethod } from "../../../../../../types/pagopa"; -import abiSelectedReducer, { AbiSelected } from "./abiSelected"; -import addedBPayReducer from "./addedBPay"; -import addingBPayReducer, { AddingBPayState } from "./addingBPay"; -import foundBpayReducer, { RemoteBPay } from "./foundBpay"; - -export type OnboardingBPayState = { - foundBPay: RemoteBPay; - addingBPay: AddingBPayState; - addedBPay: ReadonlyArray; - abiSelected: AbiSelected; -}; - -const onboardingBPayReducer = combineReducers({ - // the BPay account found for the user during the onboarding phase - foundBPay: foundBpayReducer, - // the BPay account that user is adding to his wallet - addingBPay: addingBPayReducer, - // the BPay account that user added to his wallet (during the last BPay onboarding workflow) - addedBPay: addedBPayReducer, - // the bank (abi) chosen by the user during the onboarding phase. Can be null (the user skip the bank selection) - abiSelected: abiSelectedReducer -}); - -export default onboardingBPayReducer; diff --git a/ts/features/wallet/onboarding/cobadge/analytics/index.ts b/ts/features/wallet/onboarding/cobadge/analytics/index.ts deleted file mode 100644 index 6e90f71606b..00000000000 --- a/ts/features/wallet/onboarding/cobadge/analytics/index.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { getType } from "typesafe-actions"; -import { CoBadgeServices } from "../../../../../../definitions/pagopa/cobadge/configuration/CoBadgeServices"; -import { CobadgeResponse } from "../../../../../../definitions/pagopa/walletv2/CobadgeResponse"; -import { ExecutionStatusEnum } from "../../../../../../definitions/pagopa/walletv2/SearchRequestMetadata"; -import { mixpanel } from "../../../../../mixpanel"; -import { Action } from "../../../../../store/actions/types"; -import { getNetworkErrorMessage } from "../../../../../utils/errors"; -import { sendAddCobadgeMessage } from "../../../../../store/actions/wallet/wallets"; -import { - addCoBadgeToWallet, - loadCoBadgeAbiConfiguration, - searchUserCoBadge, - walletAddCoBadgeBack, - walletAddCoBadgeCancel, - walletAddCoBadgeCompleted, - walletAddCoBadgeFailure, - walletAddCoBadgeStart -} from "../store/actions"; - -export const trackCoBadgeAction = - (mp: NonNullable) => - (action: Action): void => { - switch (action.type) { - case getType(walletAddCoBadgeStart): - return mp.track(action.type, { - abi: action.payload - }); - case getType(walletAddCoBadgeCompleted): - case getType(walletAddCoBadgeCancel): - case getType(walletAddCoBadgeBack): - case getType(addCoBadgeToWallet.request): - case getType(loadCoBadgeAbiConfiguration.request): - return mp.track(action.type); - case getType(searchUserCoBadge.request): - return mp.track(action.type, { abi: action.payload ?? "all" }); - case getType(loadCoBadgeAbiConfiguration.success): - return mp.track(action.type, trackCoBadgeServices(action.payload)); - case getType(searchUserCoBadge.success): - return mp.track(action.type, trackCobadgeResponse(action.payload)); - case getType(addCoBadgeToWallet.success): - return mp.track(action.type, { - abi: action.payload.info.issuerAbiCode - }); - case getType(addCoBadgeToWallet.failure): - case getType(loadCoBadgeAbiConfiguration.failure): - case getType(searchUserCoBadge.failure): - return mp.track(action.type, { - reason: getNetworkErrorMessage(action.payload) - }); - - case getType(walletAddCoBadgeFailure): - return mp.track(action.type, { - reason: action.payload - }); - case getType(sendAddCobadgeMessage): - return mp.track(action.type, { canAdd: action.payload }); - } - }; - -/** - * Transform a {@link CobadgeResponse} into a {@link MixpanelPayload} - * @param response - */ -export const trackCobadgeResponse = ( - response: CobadgeResponse -): Record | undefined => - response.payload?.searchRequestMetadata?.reduce>( - (acc, val) => { - if (val.serviceProviderName !== undefined) { - return { - ...acc, - [`${val.serviceProviderName}executionStatus`]: val.executionStatus, - [`${val.serviceProviderName}retrievedInstrumentsCount`]: - val.retrievedInstrumentsCount - }; - } - return acc; - }, - { - serviceProviders: response.payload?.searchRequestMetadata?.map(s => - s.serviceProviderName?.toString() - ), - anyServiceError: response.payload?.searchRequestMetadata.some( - m => m.executionStatus === ExecutionStatusEnum.KO - ), - anyServicePending: response.payload?.searchRequestMetadata.some( - m => m.executionStatus === ExecutionStatusEnum.PENDING - ), - count: response.payload?.paymentInstruments?.length, - status: response.status - } as Record - ); - -/** - * Transform a {@link CoBadgeServices} into a {@link MixpanelPayload} - * @param cobadgeSettings - */ -const trackCoBadgeServices = (cobadgeSettings: CoBadgeServices) => - Object.keys(cobadgeSettings).reduce>( - (acc, val) => ({ ...acc, [`${val}service`]: cobadgeSettings[val].status }), - { - serviceProviders: Object.keys(cobadgeSettings) - } - ); diff --git a/ts/features/wallet/onboarding/cobadge/navigation/action.ts b/ts/features/wallet/onboarding/cobadge/navigation/action.ts deleted file mode 100644 index 6ffb0063c1d..00000000000 --- a/ts/features/wallet/onboarding/cobadge/navigation/action.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { CommonActions } from "@react-navigation/native"; -import NavigationService from "../../../../../navigation/NavigationService"; -import ROUTES from "../../../../../navigation/routes"; -import { CoBadgeChooseTypeNavigationProps } from "../screens/CoBadgeChooseType"; -import WALLET_ONBOARDING_COBADGE_ROUTES from "./routes"; - -/** - * @deprecated Do not use this method when you have access to a navigation prop or useNavigation since it will behave differently, - * and many helper methods specific to screens won't be available. - */ -export const navigateToOnboardingCoBadgeChooseTypeStartScreen = ( - navigationParams: CoBadgeChooseTypeNavigationProps -) => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: WALLET_ONBOARDING_COBADGE_ROUTES.MAIN, - params: { - screen: WALLET_ONBOARDING_COBADGE_ROUTES.CHOOSE_TYPE, - params: navigationParams - } - }) - ); - -/** - * @deprecated Do not use this method when you have access to a navigation prop or useNavigation since it will behave differently, - * and many helper methods specific to screens won't be available. - */ -export const navigateToOnboardingCoBadgeSearchStartScreen = () => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: WALLET_ONBOARDING_COBADGE_ROUTES.MAIN, - params: { - screen: WALLET_ONBOARDING_COBADGE_ROUTES.START - } - }) - ); - -/** - * @deprecated Do not use this method when you have access to a navigation prop or useNavigation since it will behave differently, - * and many helper methods specific to screens won't be available. - */ -export const navigateToOnboardingCoBadgeSearchAvailable = () => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: WALLET_ONBOARDING_COBADGE_ROUTES.MAIN, - params: { - screen: WALLET_ONBOARDING_COBADGE_ROUTES.SEARCH_AVAILABLE - } - }) - ); diff --git a/ts/features/wallet/onboarding/cobadge/navigation/navigator.tsx b/ts/features/wallet/onboarding/cobadge/navigation/navigator.tsx deleted file mode 100644 index 7777e13c577..00000000000 --- a/ts/features/wallet/onboarding/cobadge/navigation/navigator.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import * as React from "react"; -import { createStackNavigator } from "@react-navigation/stack"; -import CoBadgeChooseType from "../screens/CoBadgeChooseType"; -import SearchAvailableCoBadgeScreen from "../screens/search/SearchAvailableCoBadgeScreen"; -import CoBadgeStartScreen from "../screens/start/CoBadgeStartScreen"; -import { isGestureEnabled } from "../../../../../utils/navigation"; -import WALLET_ONBOARDING_COBADGE_ROUTES from "./routes"; -import { PaymentMethodOnboardingCoBadgeParamsList } from "./params"; - -const Stack = createStackNavigator(); - -const PaymentMethodOnboardingCoBadgeNavigator = () => ( - - - - - -); - -export default PaymentMethodOnboardingCoBadgeNavigator; diff --git a/ts/features/wallet/onboarding/cobadge/navigation/params.ts b/ts/features/wallet/onboarding/cobadge/navigation/params.ts deleted file mode 100644 index 2d89409040f..00000000000 --- a/ts/features/wallet/onboarding/cobadge/navigation/params.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { CoBadgeChooseTypeNavigationProps } from "../screens/CoBadgeChooseType"; -import WALLET_ONBOARDING_COBADGE_ROUTES from "./routes"; - -export type PaymentMethodOnboardingCoBadgeParamsList = { - [WALLET_ONBOARDING_COBADGE_ROUTES.MAIN]: undefined; - [WALLET_ONBOARDING_COBADGE_ROUTES.CHOOSE_TYPE]: CoBadgeChooseTypeNavigationProps; - [WALLET_ONBOARDING_COBADGE_ROUTES.START]: undefined; - [WALLET_ONBOARDING_COBADGE_ROUTES.SEARCH_AVAILABLE]: undefined; -}; diff --git a/ts/features/wallet/onboarding/cobadge/navigation/routes.ts b/ts/features/wallet/onboarding/cobadge/navigation/routes.ts deleted file mode 100644 index ff162ba4325..00000000000 --- a/ts/features/wallet/onboarding/cobadge/navigation/routes.ts +++ /dev/null @@ -1,10 +0,0 @@ -const WALLET_ONBOARDING_COBADGE_ROUTES = { - MAIN: "WALLET_ONBOARDING_COBADGE_MAIN", - - CHOOSE_TYPE: "WALLET_ONBOARDING_COBADGE_CHOOSE_TYPE", - - START: "WALLET_ONBOARDING_COBADGE_START", - SEARCH_AVAILABLE: "WALLET_ONBOARDING_COBADGE_SEARCH_AVAILABLE" -} as const; - -export default WALLET_ONBOARDING_COBADGE_ROUTES; diff --git a/ts/features/wallet/onboarding/cobadge/saga/networking/__tests__/searchUserCobadge.test.ts b/ts/features/wallet/onboarding/cobadge/saga/networking/__tests__/searchUserCobadge.test.ts deleted file mode 100644 index ad84429204c..00000000000 --- a/ts/features/wallet/onboarding/cobadge/saga/networking/__tests__/searchUserCobadge.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { right } from "fp-ts/lib/Either"; -import { some } from "fp-ts/lib/Option"; -import { createStore } from "redux"; -import { expectSaga } from "redux-saga-test-plan"; -import { CobadgeResponse } from "../../../../../../../../definitions/pagopa/walletv2/CobadgeResponse"; -import { RestCobadgeResponse } from "../../../../../../../../definitions/pagopa/walletv2/RestCobadgeResponse"; -import { ExecutionStatusEnum } from "../../../../../../../../definitions/pagopa/walletv2/SearchRequestMetadata"; -import { applicationChangeState } from "../../../../../../../store/actions/application"; -import { appReducer } from "../../../../../../../store/reducers"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { PaymentManagerToken } from "../../../../../../../types/pagopa"; -import { SessionManager } from "../../../../../../../utils/SessionManager"; -import { searchUserCoBadge } from "../../../store/actions"; -import { handleSearchUserCoBadge } from "../index"; - -jest.mock("react-native-share", () => ({ - open: jest.fn() -})); - -jest.mock("react-native-background-timer", () => ({})); - -const baseCobadgeResponseOk: CobadgeResponse = { - status: "OK", - errors: [], - payload: { - searchRequestId: "searchRequestId", - searchRequestMetadata: [ - { - retrievedInstrumentsCount: 0, - executionStatus: ExecutionStatusEnum.OK, - serviceProviderName: "serviceProvider" - } - ], - paymentInstruments: [] - } -}; - -const baseCobadgeResponsePending: CobadgeResponse = { - status: "OK", - errors: [], - payload: { - searchRequestId: "searchRequestId", - searchRequestMetadata: [ - { - retrievedInstrumentsCount: 0, - executionStatus: ExecutionStatusEnum.PENDING, - serviceProviderName: "serviceProvider" - } - ], - paymentInstruments: [] - } -}; - -const mockRestCobadgeResponse = ( - res: CobadgeResponse -): RestCobadgeResponse => ({ - data: res -}); - -describe("searchUserCobadge", () => { - const searchCobadgePans = jest.fn(); - const getCobadgePans = jest.fn(); - const response200SuccessOk = right({ - status: 200, - value: mockRestCobadgeResponse(baseCobadgeResponseOk) - }); - getCobadgePans.mockReturnValue(() => response200SuccessOk); - searchCobadgePans.mockReturnValue(() => response200SuccessOk); - const aPMToken = "1234" as PaymentManagerToken; - const aPmSessionManager: SessionManager = - new SessionManager(jest.fn(() => Promise.resolve(some(aPMToken)))); - beforeEach(() => { - getCobadgePans.mockClear(); - searchCobadgePans.mockClear(); - }); - - it("With 200 RestCobadgeResponse OK, dispatch searchUserCoBadge.success", () => - expectSaga( - handleSearchUserCoBadge, - getCobadgePans, - searchCobadgePans, - aPmSessionManager, - searchUserCoBadge.request("abi") - ) - .withReducer(appReducer) - .put(searchUserCoBadge.success(baseCobadgeResponseOk)) - .run() - .then(rr => { - const globalState = rr.storeState as GlobalState; - // Without pending, no searchCoBadgeRequestId is expected - expect( - globalState.wallet.onboarding.coBadge.searchCoBadgeRequestId - ).toBeNull(); - // without searchCoBadgeRequestId in the initial state, only 1 call to getCobadgePans is expected - expect(getCobadgePans.mock.calls.length).toBe(1); - expect(searchCobadgePans.mock.calls.length).toBe(0); - })); - it("With 200 RestCobadgeResponse OK, starting from a pending state, dispatch searchUserCoBadge.success", () => { - // Simulate a previous call with pending state - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - store.dispatch(searchUserCoBadge.success(baseCobadgeResponsePending)); - expect( - store.getState().wallet.onboarding.coBadge.searchCoBadgeRequestId - ).toBe("searchRequestId"); - - // execute a new call - return expectSaga( - handleSearchUserCoBadge, - getCobadgePans, - searchCobadgePans, - aPmSessionManager, - searchUserCoBadge.request("abi") - ) - .withReducer(appReducer, store.getState()) - .put(searchUserCoBadge.success(baseCobadgeResponseOk)) - .run() - .then(rr => { - const globalState = rr.storeState as GlobalState; - // Without pending in the response, no searchCoBadgeRequestId is expected - expect( - globalState.wallet.onboarding.coBadge.searchCoBadgeRequestId - ).toBeNull(); - // with searchCoBadgeRequestId in the initial state, only 1 call to searchCobadgePans is expected - expect(getCobadgePans.mock.calls.length).toBe(0); - expect(searchCobadgePans.mock.calls.length).toBe(1); - }); - }); -}); diff --git a/ts/features/wallet/onboarding/cobadge/saga/networking/addCobadgeToWallet.ts b/ts/features/wallet/onboarding/cobadge/saga/networking/addCobadgeToWallet.ts deleted file mode 100644 index ad0da6b2e2c..00000000000 --- a/ts/features/wallet/onboarding/cobadge/saga/networking/addCobadgeToWallet.ts +++ /dev/null @@ -1,84 +0,0 @@ -import * as E from "fp-ts/lib/Either"; -import * as O from "fp-ts/lib/Option"; -import { readableReport } from "@pagopa/ts-commons/lib/reporters"; -import { PaymentInstrument } from "../../../../../../../definitions/pagopa/walletv2/PaymentInstrument"; -import { PaymentManagerClient } from "../../../../../../api/pagopa"; -import { - PaymentManagerToken, - RawPaymentMethod -} from "../../../../../../types/pagopa"; -import { - getGenericError, - getNetworkError, - NetworkError -} from "../../../../../../utils/errors"; -import { getPaymentMethodHash } from "../../../../../../utils/paymentMethod"; -import { SessionManager } from "../../../../../../utils/SessionManager"; -import { convertWalletV2toWalletV1 } from "../../../../../../utils/walletv2"; - -/** - * Handle the networking logic to add a co-badge card to the wallet - * @param addCobadgeToWallet http client to execute the request - * @param sessionManager pm session manager - * @param paymentInstrument the payload representing the cobadge card to add - */ -export const addCobadgeToWallet = async ( - addCobadgeToWallet: ReturnType< - typeof PaymentManagerClient - >["addCobadgeToWallet"], - sessionManager: SessionManager, - paymentInstrument: PaymentInstrument -): Promise> => { - try { - const addCobadgeToWalletWithRefresh = sessionManager.withRefresh( - addCobadgeToWallet({ - data: { payload: { paymentInstruments: [paymentInstrument] } } - }) - ); - const addCobadgeToWalletWithRefreshResult = - await addCobadgeToWalletWithRefresh(); - if (E.isRight(addCobadgeToWalletWithRefreshResult)) { - if (addCobadgeToWalletWithRefreshResult.right.status === 200) { - const wallets = ( - addCobadgeToWalletWithRefreshResult.right.value.data ?? [] - ).map(convertWalletV2toWalletV1); - // search for the added cobadge. - const maybeWallet = O.fromNullable( - wallets.find( - w => - w.paymentMethod && - getPaymentMethodHash(w.paymentMethod) === paymentInstrument.hpan - ) - ); - if ( - O.isSome(maybeWallet) && - maybeWallet.value.paymentMethod !== undefined - ) { - return E.right(maybeWallet.value.paymentMethod); - } else { - return E.left( - getGenericError( - new Error(`cannot find added cobadge in wallets list response`) - ) - ); - } - } else { - return E.left( - getGenericError( - new Error( - `response status ${addCobadgeToWalletWithRefreshResult.right.status}` - ) - ) - ); - } - } else { - return E.left( - getGenericError( - new Error(readableReport(addCobadgeToWalletWithRefreshResult.left)) - ) - ); - } - } catch (e) { - return E.left(getNetworkError(e)); - } -}; diff --git a/ts/features/wallet/onboarding/cobadge/saga/networking/index.ts b/ts/features/wallet/onboarding/cobadge/saga/networking/index.ts deleted file mode 100644 index 441bb5ded52..00000000000 --- a/ts/features/wallet/onboarding/cobadge/saga/networking/index.ts +++ /dev/null @@ -1,136 +0,0 @@ -import * as E from "fp-ts/lib/Either"; -import { pipe } from "fp-ts/lib/function"; -import { call, put, select } from "typed-redux-saga/macro"; -import { ActionType } from "typesafe-actions"; -import { ContentClient } from "../../../../../../api/content"; -import { PaymentManagerClient } from "../../../../../../api/pagopa"; -import { - isRawCreditCard, - PaymentManagerToken, - RawCreditCardPaymentMethod, - RawPaymentMethod -} from "../../../../../../types/pagopa"; -import { SagaCallReturnType } from "../../../../../../types/utils"; -import { - getGenericError, - getNetworkError, - NetworkError -} from "../../../../../../utils/errors"; -import { readablePrivacyReport } from "../../../../../../utils/reporters"; -import { SessionManager } from "../../../../../../utils/SessionManager"; -import { - addCoBadgeToWallet, - loadCoBadgeAbiConfiguration, - searchUserCoBadge -} from "../../store/actions"; -import { onboardingCoBadgeSearchRequestId } from "../../store/reducers/searchCoBadgeRequestId"; -import { addCobadgeToWallet } from "./addCobadgeToWallet"; -import { searchUserCobadge } from "./searchUserCobadge"; - -/** - * Load the user's cobadge cards. if a previous stored SearchRequestId is found then it will be used - * within the search searchCobadgePans API, otherwise getCobadgePans will be used - */ -export function* handleSearchUserCoBadge( - getCobadgePans: ReturnType["getCobadgePans"], - searchCobadgePans: ReturnType< - typeof PaymentManagerClient - >["searchCobadgePans"], - sessionManager: SessionManager, - searchAction: ActionType -) { - // try to retrieve the searchRequestId for co-badge search - const onboardingCoBadgeSearchRequest = yield* select( - onboardingCoBadgeSearchRequestId - ); - - // get the results - const result = yield* call( - searchUserCobadge, - { abiCode: searchAction.payload }, - getCobadgePans, - searchCobadgePans, - sessionManager, - onboardingCoBadgeSearchRequest - ); - - // dispatch the related action - if (E.isRight(result)) { - yield* put(searchUserCoBadge.success(result.right)); - } else { - yield* put(searchUserCoBadge.failure(result.left)); - } -} - -const toRawCreditCardPaymentMethod = ( - rpm: RawPaymentMethod -): E.Either => - isRawCreditCard(rpm) - ? E.right(rpm) - : E.left( - getGenericError( - new Error("Cannot decode the payload as RawCreditCardPaymentMethod") - ) - ); - -/** - * Add Cobadge to wallet - */ -export function* handleAddCoBadgeToWallet( - addCobadgeToWalletClient: ReturnType< - typeof PaymentManagerClient - >["addCobadgeToWallet"], - sessionManager: SessionManager, - action: ActionType -) { - // get the results - const result: SagaCallReturnType = yield* call( - addCobadgeToWallet, - addCobadgeToWalletClient, - sessionManager, - action.payload - ); - - const eitherRawCreditCard = pipe( - result, - E.chain(toRawCreditCardPaymentMethod) - ); - - // dispatch the related action - if (E.isRight(eitherRawCreditCard)) { - yield* put(addCoBadgeToWallet.success(eitherRawCreditCard.right)); - } else { - yield* put(addCoBadgeToWallet.failure(eitherRawCreditCard.left)); - } -} - -/** - * Load CoBadge configuration - */ -export function* handleLoadCoBadgeConfiguration( - getCobadgeServices: ReturnType["getCobadgeServices"], - _: ActionType -) { - try { - const getCobadgeServicesResult: SagaCallReturnType< - typeof getCobadgeServices - > = yield* call(getCobadgeServices); - if (E.isRight(getCobadgeServicesResult)) { - if (getCobadgeServicesResult.right.status === 200) { - yield* put( - loadCoBadgeAbiConfiguration.success( - getCobadgeServicesResult.right.value - ) - ); - } else { - throw new Error( - `response status ${getCobadgeServicesResult.right.status}` - ); - } - } else { - throw new Error(readablePrivacyReport(getCobadgeServicesResult.left)); - } - } catch (e) { - yield* put(loadCoBadgeAbiConfiguration.failure(getNetworkError(e))); - } -} diff --git a/ts/features/wallet/onboarding/cobadge/saga/networking/searchUserCobadge.tsx b/ts/features/wallet/onboarding/cobadge/saga/networking/searchUserCobadge.tsx deleted file mode 100644 index 995ef788711..00000000000 --- a/ts/features/wallet/onboarding/cobadge/saga/networking/searchUserCobadge.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import * as E from "fp-ts/lib/Either"; -import { CobadgeResponse } from "../../../../../../../definitions/pagopa/walletv2/CobadgeResponse"; -import { PaymentManagerClient } from "../../../../../../api/pagopa"; -import { mixpanelTrack } from "../../../../../../mixpanel"; -import { PaymentManagerToken } from "../../../../../../types/pagopa"; -import { - getGenericError, - getNetworkError, - NetworkError -} from "../../../../../../utils/errors"; -import { readablePrivacyReport } from "../../../../../../utils/reporters"; -import { SessionManager } from "../../../../../../utils/SessionManager"; -import { trackCobadgeResponse } from "../../analytics"; - -type CoBadgeRequestQuery = { - abiCode?: string; - panCode?: string; -}; - -const cobadgePrefix = "WALLET_ONBOARDING_COBADGE_SEARCH"; - -const withTokenSuffix = "WITH_TOKEN"; -const withoutTokenSuffix = "WITHOUT_TOKEN"; - -/** - * This method contains the generic networking logic used to request a user's cobadge - * @param cobadgeQuery the input data to search the cobadge - * @param getCobadgePans the http client for /cobadge/pans - * @param searchCobadgePans the http client for /cobadge/search - * @param sessionManager - * @param searchRequestId the searchRequestToken that should be used if defined - */ -export const searchUserCobadge = async ( - cobadgeQuery: CoBadgeRequestQuery, - getCobadgePans: ReturnType["getCobadgePans"], - searchCobadgePans: ReturnType< - typeof PaymentManagerClient - >["searchCobadgePans"], - sessionManager: SessionManager, - searchRequestId: string | undefined -): Promise> => { - const logPrefix = `${cobadgePrefix}_${ - searchRequestId ? withTokenSuffix : withoutTokenSuffix - }`; - - try { - void mixpanelTrack(`${logPrefix}_REQUEST`, { - abi: cobadgeQuery.abiCode ?? "all" - }); - - const getPansWithRefreshResult = await sessionManager.withRefresh( - searchRequestId - ? searchCobadgePans(searchRequestId) - : getCobadgePans(cobadgeQuery.abiCode, cobadgeQuery.panCode) - )(); - if (E.isRight(getPansWithRefreshResult)) { - if (getPansWithRefreshResult.right.status === 200) { - if (getPansWithRefreshResult.right.value.data) { - void mixpanelTrack( - `${logPrefix}_SUCCESS`, - trackCobadgeResponse(getPansWithRefreshResult.right.value.data) - ); - return E.right(getPansWithRefreshResult.right.value.data); - } else { - // it should not never happen - const error = getGenericError(new Error(`data is undefined`)); - void mixpanelTrack(`${logPrefix}_FAILURE`, { reason: error }); - return E.left(error); - } - } else { - const error = getGenericError( - new Error(`response status ${getPansWithRefreshResult.right.status}`) - ); - void mixpanelTrack(`${logPrefix}_FAILURE`, { reason: error }); - return E.left(error); - } - } else { - const error = getGenericError( - new Error(readablePrivacyReport(getPansWithRefreshResult.left)) - ); - void mixpanelTrack(`${logPrefix}_FAILURE`, { reason: error }); - return E.left(error); - } - } catch (e) { - const error = getNetworkError(e); - void mixpanelTrack(`${logPrefix}_FAILURE`, { reason: error }); - return E.left(error); - } -}; diff --git a/ts/features/wallet/onboarding/cobadge/saga/orchestration/addCoBadgeToWallet.ts b/ts/features/wallet/onboarding/cobadge/saga/orchestration/addCoBadgeToWallet.ts deleted file mode 100644 index dfb48e4e668..00000000000 --- a/ts/features/wallet/onboarding/cobadge/saga/orchestration/addCoBadgeToWallet.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { call, put } from "typed-redux-saga/macro"; -import NavigationService from "../../../../../../navigation/NavigationService"; -import ROUTES from "../../../../../../navigation/routes"; -import { - executeWorkUnit, - withFailureHandling, - withResetNavigationStack -} from "../../../../../../sagas/workUnit"; -import { navigateToWalletHome } from "../../../../../../store/actions/navigation"; -import { fetchWalletsRequest } from "../../../../../../store/actions/wallet/wallets"; -import { SagaCallReturnType } from "../../../../../../types/utils"; -import { navigateToOnboardingCoBadgeSearchStartScreen } from "../../navigation/action"; -import WALLET_ONBOARDING_COBADGE_ROUTES from "../../navigation/routes"; -import { - walletAddCoBadgeBack, - walletAddCoBadgeCancel, - walletAddCoBadgeCompleted, - walletAddCoBadgeFailure -} from "../../store/actions"; - -/** - * Define the workflow that allows the user to add a co-badge card to the wallet. - * The workflow ends when: - * - The user adds at least one owned co-badge card to the wallet {@link walletAddCoBadgeCompleted} - * - The user aborts the insertion of a co-badge {@link walletAddCoBadgeCancel} - * - The user chooses back from the first screen {@link walletAddCoBadgeBack} - */ -function* coBadgeWorkUnit() { - return yield* call(executeWorkUnit, { - startScreenNavigation: navigateToOnboardingCoBadgeSearchStartScreen, - startScreenName: WALLET_ONBOARDING_COBADGE_ROUTES.START, - complete: walletAddCoBadgeCompleted, - back: walletAddCoBadgeBack, - cancel: walletAddCoBadgeCancel, - failure: walletAddCoBadgeFailure - }); -} - -/** - * When the workUnit start from one of these route, we should return to wallet home - */ -const returnToWalletRoutes = new Set([ - ROUTES.WALLET_ADD_CARD, - WALLET_ONBOARDING_COBADGE_ROUTES.CHOOSE_TYPE -]); - -/** - * add co-badge to wallet saga - */ -export function* addCoBadgeToWalletSaga() { - const initialScreenName: ReturnType< - typeof NavigationService.getCurrentRouteName - > = yield* call(NavigationService.getCurrentRouteName); - const sagaExecution = () => - withFailureHandling(() => withResetNavigationStack(coBadgeWorkUnit)); - - const res: SagaCallReturnType = yield* call( - sagaExecution - ); - - const shouldNavigateWalletHome = - initialScreenName !== undefined && - returnToWalletRoutes.has(initialScreenName) && - res === "completed"; - - if (shouldNavigateWalletHome) { - // If the addition starts from "WALLET_ONBOARDING_COBADGE_CHOOSE_TYPE", remove from stack - // This shouldn't happens if all the workflow will use the executeWorkUnit - - yield* call(navigateToWalletHome); - } - - if (res === "completed") { - // refresh wallets list - yield* put(fetchWalletsRequest()); - } -} diff --git a/ts/features/wallet/onboarding/cobadge/screens/CoBadgeChooseType.tsx b/ts/features/wallet/onboarding/cobadge/screens/CoBadgeChooseType.tsx deleted file mode 100644 index 4d130cce0fe..00000000000 --- a/ts/features/wallet/onboarding/cobadge/screens/CoBadgeChooseType.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { - View, - FlatList, - ListRenderItemInfo, - SafeAreaView, - StyleSheet -} from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { - FooterWithButtons, - Icon, - PressableListItemBase, - VSpacer -} from "@pagopa/io-app-design-system"; -import { Route, useRoute } from "@react-navigation/native"; -import { H1 } from "../../../../../components/core/typography/H1"; -import { H3 } from "../../../../../components/core/typography/H3"; -import { H4 } from "../../../../../components/core/typography/H4"; -import { H5 } from "../../../../../components/core/typography/H5"; -import { IOStyles } from "../../../../../components/core/variables/IOStyles"; -import BaseScreenComponent from "../../../../../components/screens/BaseScreenComponent"; -import I18n from "../../../../../i18n"; -import { - navigateBack as legacyNavigateBack, - navigateToWalletAddCreditCard -} from "../../../../../store/actions/navigation"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { emptyContextualHelp } from "../../../../../utils/emptyContextualHelp"; -import { walletAddCoBadgeStart } from "../store/actions"; - -type Props = ReturnType & - ReturnType; - -export type CoBadgeChooseTypeNavigationProps = { - abi?: string; - // Added for backward compatibility, in order to return back after add credit card workflow. - // Number of screens that need to be removed from the stack - legacyAddCreditCardBack?: number; -}; - -const styles = StyleSheet.create({ - flexColumn: { - flexDirection: "column", - justifyContent: "space-between", - flex: 1 - }, - row: { - flexDirection: "row", - justifyContent: "space-between" - }, - descriptionPadding: { paddingRight: 24 } -}); -type CardOnlinePurchase = "enabled" | "disabled" | "unknown"; - -type IAddCardPath = Readonly<{ - path: CardOnlinePurchase; - title: string; - description: string; - onPress: () => void; -}>; - -const renderListItem = (cardPathItem: ListRenderItemInfo) => ( - - - - -

- {cardPathItem.item.title} -

-
- -
-
- {cardPathItem.item.description} -
-
-
-); -/** - * This screen allows the user to choose the exact type of card he intends to add - * @param props - * @constructor - */ -const CoBadgeChooseType = (props: Props): React.ReactElement => { - const route = - useRoute< - Route< - "WALLET_ONBOARDING_COBADGE_CHOOSE_TYPE", - CoBadgeChooseTypeNavigationProps - > - >(); - const abi = route.params.abi; - const legacyAddCreditCardBack = route.params.legacyAddCreditCardBack; - const addCardPath: ReadonlyArray = [ - { - path: "enabled", - title: I18n.t("wallet.onboarding.coBadge.chooseType.path.enabled.title"), - description: I18n.t( - "wallet.onboarding.coBadge.chooseType.path.enabled.description" - ), - onPress: () => props.addCreditCard(legacyAddCreditCardBack) - }, - { - path: "disabled", - title: I18n.t("wallet.onboarding.coBadge.chooseType.path.disabled.title"), - description: I18n.t( - "wallet.onboarding.coBadge.chooseType.path.disabled.description" - ), - onPress: () => props.addCoBadge(abi) - }, - { - path: "unknown", - title: I18n.t("wallet.onboarding.coBadge.chooseType.path.unknown.title"), - description: I18n.t( - "wallet.onboarding.coBadge.chooseType.path.unknown.description" - ), - onPress: () => props.addCoBadge(abi) - } - ]; - return ( - - - -

{I18n.t("wallet.onboarding.coBadge.chooseType.title")}

- -

- {I18n.t("wallet.onboarding.coBadge.chooseType.description")} -

- - item.path} - ListFooterComponent={} - renderItem={i => renderListItem(i)} - /> -
-
- -
- ); -}; - -const navigateBack = (n: number, dispatch: Dispatch) => { - if (n <= 0) { - return; - } - legacyNavigateBack(); - navigateBack(n - 1, dispatch); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - back: () => legacyNavigateBack(), - addCoBadge: (abi: string | undefined) => dispatch(walletAddCoBadgeStart(abi)), - addCreditCard: (popScreenNumber: number = 0) => { - navigateBack(popScreenNumber, dispatch); - navigateToWalletAddCreditCard({ - inPayment: O.none - }); - } -}); - -const mapStateToProps = (_: GlobalState) => ({}); - -export default connect(mapStateToProps, mapDispatchToProps)(CoBadgeChooseType); diff --git a/ts/features/wallet/onboarding/cobadge/screens/__tests__/CoBadgeChooseType.test.tsx b/ts/features/wallet/onboarding/cobadge/screens/__tests__/CoBadgeChooseType.test.tsx deleted file mode 100644 index 4981aafe9e2..00000000000 --- a/ts/features/wallet/onboarding/cobadge/screens/__tests__/CoBadgeChooseType.test.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { CommonActions } from "@react-navigation/native"; -import { fireEvent } from "@testing-library/react-native"; -import * as O from "fp-ts/lib/Option"; -import configureMockStore from "redux-mock-store"; -import NavigationService from "../../../../../../navigation/NavigationService"; -import ROUTES from "../../../../../../navigation/routes"; -import { applicationChangeState } from "../../../../../../store/actions/application"; -import * as CustomNavigationActions from "../../../../../../store/actions/navigation"; -import { appReducer } from "../../../../../../store/reducers"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { renderScreenWithNavigationStoreContext } from "../../../../../../utils/testWrapper"; -import WALLET_ONBOARDING_COBADGE_ROUTES from "../../navigation/routes"; -import CoBadgeChooseType from "../CoBadgeChooseType"; - -jest.mock("react-native-share", () => ({ - open: jest.fn() -})); - -describe("CoBadgeChooseType component", () => { - beforeEach(() => jest.useFakeTimers()); - it("should dispatch navigateToWalletAddCreditCard action if press the enabled item", () => { - const { component } = getComponent(undefined, 1); - const enabledItem = component.queryByTestId("enabledItem"); - const spyBack = jest.spyOn(CustomNavigationActions, "navigateBack"); - const spyService = jest.spyOn( - NavigationService, - "dispatchNavigationAction" - ); - - expect(component).not.toBeNull(); - expect(enabledItem).not.toBeNull(); - - if (enabledItem) { - fireEvent.press(enabledItem); - expect(spyBack).toHaveBeenCalledTimes(1); - expect(spyService.mock.calls).toEqual([ - [CommonActions.goBack()], - [ - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.WALLET_ADD_CARD, - params: { inPayment: O.none } - }) - ] - ]); - } - }); - it("should dispatch walletAddCoBadgeStart action if press disabled or unknown item", () => { - const anAbi = "1234"; - const { component, store } = getComponent(anAbi, 1); - const disabledItem = component.queryByTestId("disabledItem"); - const unknownItem = component.queryByTestId("unknownItem"); - - expect(disabledItem).not.toBeNull(); - expect(unknownItem).not.toBeNull(); - - const expectedPayload = { - type: WALLET_ONBOARDING_COBADGE_ROUTES.START, - payload: anAbi - }; - if (disabledItem) { - fireEvent.press(disabledItem); - expect(store.getActions()).toEqual([expectedPayload]); - } - if (unknownItem) { - fireEvent.press(unknownItem); - expect(store.getActions()).toEqual([expectedPayload, expectedPayload]); - } - }); -}); - -const getComponent = (abi?: string, legacyAddCreditCardBack?: number) => { - const globalState = appReducer(undefined, applicationChangeState("active")); - - const mockStore = configureMockStore(); - const store: ReturnType = mockStore({ - ...globalState - } as GlobalState); - - return { - component: renderScreenWithNavigationStoreContext( - CoBadgeChooseType, - ROUTES.WALLET_BPAY_DETAIL, - { abi, legacyAddCreditCardBack }, - store - ), - store - }; -}; diff --git a/ts/features/wallet/onboarding/cobadge/screens/add-account/AddCoBadgeScreen.tsx b/ts/features/wallet/onboarding/cobadge/screens/add-account/AddCoBadgeScreen.tsx deleted file mode 100644 index 517f1921e18..00000000000 --- a/ts/features/wallet/onboarding/cobadge/screens/add-account/AddCoBadgeScreen.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as AR from "fp-ts/lib/Array"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { PaymentInstrument } from "../../../../../../../definitions/pagopa/walletv2/PaymentInstrument"; -import BaseScreenComponent from "../../../../../../components/screens/BaseScreenComponent"; -import { profileSelector } from "../../../../../../store/reducers/profile"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { - getValueOrElse, - isError, - isLoading, - isReady -} from "../../../../../../common/model/RemoteValue"; -import { - addCoBadgeToWallet, - walletAddCoBadgeCancel, - walletAddCoBadgeCompleted -} from "../../store/actions"; -import { - onboardingCobadgeAddingResultSelector, - onboardingCobadgeChosenSelector -} from "../../store/reducers/addingCoBadge"; -import { onboardingCoBadgeFoundSelector } from "../../store/reducers/foundCoBadge"; -import AddCobadgeComponent from "./AddCobadgeComponent"; -import LoadAddCoBadgeComponent from "./LoadAddCoBadgeComponent"; - -type Props = ReturnType & - ReturnType & - Pick, "contextualHelp">; - -type NextAction = { - index: number; - skip: boolean; -}; -/** - * This screen is displayed when co-badge cards are found and ready to be added in wallet - * @constructor - */ -const AddCoBadgeScreen = (props: Props): React.ReactElement | null => { - // next could be skip or not (a pan should be added) - const [currentAction, setNextAction] = React.useState({ - index: 0, - skip: false - }); - - const { coBadgeList, isAddingReady, onCompleted } = props; - - const currentIndex = currentAction.index; - - React.useEffect(() => { - // call onCompleted when the end of co-badge list has been reached - // and the adding phase has been completed (or it was skipped step) - if ( - currentIndex >= coBadgeList.length && - (currentAction.skip || isAddingReady) - ) { - onCompleted(); - } - }, [currentAction, coBadgeList, isAddingReady, onCompleted, currentIndex]); - - const nextPan = (skip: boolean) => { - const nextIndex = currentIndex + 1; - setNextAction({ index: nextIndex, skip }); - }; - - const handleOnContinue = () => { - if (currentIndex < props.coBadgeList.length) { - props.addCoBadge(props.coBadgeList[currentIndex]); - } - nextPan(false); - }; - - const currentPan = AR.lookup(currentIndex, [...props.coBadgeList]); - - return props.loading || props.isAddingResultError ? ( - - pipe(props.selectedCoBadge, O.fromNullable, O.map(props.onRetry)) - } - /> - ) : O.isSome(currentPan) ? ( - nextPan(true)} - contextualHelp={props.contextualHelp} - /> - ) : null; // this should not happen -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - addCoBadge: (coBadge: PaymentInstrument) => - dispatch(addCoBadgeToWallet.request(coBadge)), - onCompleted: () => dispatch(walletAddCoBadgeCompleted()), - onCancel: () => dispatch(walletAddCoBadgeCancel()), - onRetry: (coBadge: PaymentInstrument) => - dispatch(addCoBadgeToWallet.request(coBadge)) -}); - -const mapStateToProps = (state: GlobalState) => { - const remoteCoBadge = onboardingCoBadgeFoundSelector(state); - const addingResult = onboardingCobadgeAddingResultSelector(state); - const coBadgeList: ReadonlyArray = pipe( - getValueOrElse(remoteCoBadge, undefined), - O.fromNullable, - O.chainNullableK(response => response.payload?.paymentInstruments), - O.getOrElseW(() => []) - ); - return { - isAddingReady: isReady(addingResult), - loading: isLoading(addingResult), - isAddingResultError: isError(addingResult), - remoteCoBadge, - selectedCoBadge: onboardingCobadgeChosenSelector(state), - coBadgeList, - profile: pot.toUndefined(profileSelector(state)) - }; -}; - -export default connect(mapStateToProps, mapDispatchToProps)(AddCoBadgeScreen); diff --git a/ts/features/wallet/onboarding/cobadge/screens/add-account/AddCobadgeComponent.tsx b/ts/features/wallet/onboarding/cobadge/screens/add-account/AddCobadgeComponent.tsx deleted file mode 100644 index 96fc29679c9..00000000000 --- a/ts/features/wallet/onboarding/cobadge/screens/add-account/AddCobadgeComponent.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import * as React from "react"; -import { View, SafeAreaView, StyleSheet } from "react-native"; -import { ScrollView } from "react-native-gesture-handler"; -import { connect } from "react-redux"; -import { - FooterWithButtons, - HSpacer, - VSpacer -} from "@pagopa/io-app-design-system"; -import { InitializedProfile } from "../../../../../../../definitions/backend/InitializedProfile"; -import { PaymentInstrument } from "../../../../../../../definitions/pagopa/walletv2/PaymentInstrument"; -import { InfoBox } from "../../../../../../components/box/InfoBox"; -import { Body } from "../../../../../../components/core/typography/Body"; -import { H1 } from "../../../../../../components/core/typography/H1"; -import { H4 } from "../../../../../../components/core/typography/H4"; -import { Label } from "../../../../../../components/core/typography/Label"; -import { IOStyles } from "../../../../../../components/core/variables/IOStyles"; -import BaseScreenComponent from "../../../../../../components/screens/BaseScreenComponent"; -import I18n from "../../../../../../i18n"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { abiListSelector } from "../../../store/abi"; -import { Abi } from "../../../../../../../definitions/pagopa/walletv2/Abi"; -import PreviewCoBadgeCard from "../../../../cobadge/component/PreviewCoBadgeCard"; -import { isCoBadgeBlocked } from "../../../../../../utils/paymentMethod"; - -type Props = { - pan: PaymentInstrument; - pansNumber: number; - currentIndex: number; - handleContinue: () => void; - handleSkip: () => void; - profile?: InitializedProfile; -} & ReturnType & - Pick, "contextualHelp">; - -const styles = StyleSheet.create({ - container: { - alignItems: "center" - }, - title: { lineHeight: 33, alignSelf: "flex-start" }, - flexStart: { alignSelf: "flex-start" } -}); - -const loadLocales = (props: Props) => ({ - headerTitle: I18n.t("wallet.onboarding.coBadge.headerTitle"), - screenTitle: I18n.t("wallet.onboarding.coBadge.add.screenTitle"), - label: I18n.t("wallet.onboarding.coBadge.add.label", { - current: props.currentIndex + 1, - length: props.pansNumber - }), - blockedCard: I18n.t("wallet.onboarding.coBadge.add.blocked"), - warning1: I18n.t("wallet.onboarding.coBadge.add.warning1"), - warning2: I18n.t("wallet.onboarding.coBadge.add.warning2") -}); - -const AddCobadgeComponent: React.FunctionComponent = (props: Props) => { - const [abiInfo, setAbiInfo] = React.useState({}); - const { pan, abiList } = props; - const { headerTitle, screenTitle, label, blockedCard, warning1, warning2 } = - loadLocales(props); - React.useEffect(() => { - const abi: Abi | undefined = abiList.find(elem => elem.abi === pan.abiCode); - setAbiInfo(abi ?? {}); - }, [pan, abiList]); - - return ( - } - headerTitle={headerTitle} - contextualHelp={props.contextualHelp} - > - - - - -

{screenTitle}

- -

- {label} -

- - - - {isCoBadgeBlocked(props.pan) ? ( - - {blockedCard} - - ) : ( - - - {warning1} - - - )} -
- -
-
- {isCoBadgeBlocked(props.pan) ? ( - - ) : ( - - )} -
- ); -}; - -const mapStateToProps = (state: GlobalState) => ({ - abiList: abiListSelector(state) -}); - -export default connect(mapStateToProps)(AddCobadgeComponent); diff --git a/ts/features/wallet/onboarding/cobadge/screens/add-account/LoadAddCoBadgeComponent.tsx b/ts/features/wallet/onboarding/cobadge/screens/add-account/LoadAddCoBadgeComponent.tsx deleted file mode 100644 index f792c0d2f6c..00000000000 --- a/ts/features/wallet/onboarding/cobadge/screens/add-account/LoadAddCoBadgeComponent.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import * as React from "react"; -import I18n from "../../../../../../i18n"; -import { useHardwareBackButton } from "../../../../../../hooks/useHardwareBackButton"; -import { LoadingErrorComponent } from "../../../../../../components/LoadingErrorComponent"; - -export type Props = { - isLoading: boolean; - onCancel: () => void; - onRetry: () => void; -}; - -/** - * This screen displays a loading or error message while adding a co-badge card to the wallet - * In error case a retry button will be shown - * @constructor - */ -const LoadAddCoBadgeComponent = (props: Props): React.ReactElement => { - useHardwareBackButton(() => { - if (!props.isLoading) { - props.onCancel(); - } - return true; - }); - return ( - - ); -}; - -export default LoadAddCoBadgeComponent; diff --git a/ts/features/wallet/onboarding/cobadge/screens/search/LoadCoBadgeSearch.tsx b/ts/features/wallet/onboarding/cobadge/screens/search/LoadCoBadgeSearch.tsx deleted file mode 100644 index 64fb0d1d962..00000000000 --- a/ts/features/wallet/onboarding/cobadge/screens/search/LoadCoBadgeSearch.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import I18n from "../../../../../../i18n"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { WithTestID } from "../../../../../../types/WithTestID"; -import { useHardwareBackButton } from "../../../../../../hooks/useHardwareBackButton"; -import { LoadingErrorComponent } from "../../../../../../components/LoadingErrorComponent"; -import { searchUserCoBadge, walletAddCoBadgeCancel } from "../../store/actions"; -import { onboardingCoBadgeAbiSelectedSelector } from "../../store/reducers/abiSelected"; -import { onboardingCoBadgeFoundIsError } from "../../store/reducers/foundCoBadge"; - -export type Props = WithTestID< - ReturnType & ReturnType ->; - -/** - * This screen is displayed when searching for co-badge - * @constructor - */ -const LoadCoBadgeSearch = (props: Props): React.ReactElement => { - useHardwareBackButton(() => { - if (!props.isLoading) { - props.cancel(); - } - return true; - }); - return ( - props.retry(props.abiSelected)} - /> - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - cancel: () => dispatch(walletAddCoBadgeCancel()), - retry: (abiSelected: string | undefined) => - dispatch(searchUserCoBadge.request(abiSelected)) -}); - -const mapStateToProps = (state: GlobalState) => ({ - abiSelected: onboardingCoBadgeAbiSelectedSelector(state), - isLoading: !onboardingCoBadgeFoundIsError(state) -}); - -export default connect(mapStateToProps, mapDispatchToProps)(LoadCoBadgeSearch); diff --git a/ts/features/wallet/onboarding/cobadge/screens/search/SearchAvailableCoBadgeScreen.tsx b/ts/features/wallet/onboarding/cobadge/screens/search/SearchAvailableCoBadgeScreen.tsx deleted file mode 100644 index 77e6c15fea7..00000000000 --- a/ts/features/wallet/onboarding/cobadge/screens/search/SearchAvailableCoBadgeScreen.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import * as E from "fp-ts/lib/Either"; -import { pipe } from "fp-ts/lib/function"; -import * as t from "io-ts"; -import * as React from "react"; -import { useEffect } from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { CobadgeResponse } from "../../../../../../../definitions/pagopa/walletv2/CobadgeResponse"; -import { PaymentInstrument } from "../../../../../../../definitions/pagopa/walletv2/PaymentInstrument"; -import { - ExecutionStatusEnum, - SearchRequestMetadata -} from "../../../../../../../definitions/pagopa/walletv2/SearchRequestMetadata"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { emptyContextualHelp } from "../../../../../../utils/emptyContextualHelp"; -import { isTimeoutError } from "../../../../../../utils/errors"; -import { useAvoidHardwareBackButton } from "../../../../../../utils/useAvoidHardwareBackButton"; -import { isError, isReady } from "../../../../../../common/model/RemoteValue"; -import { searchUserCoBadge } from "../../store/actions"; -import { onboardingCoBadgeAbiSelectedSelector } from "../../store/reducers/abiSelected"; -import { onboardingCoBadgeFoundSelector } from "../../store/reducers/foundCoBadge"; -import AddCoBadgeScreen from "../add-account/AddCoBadgeScreen"; -import CoBadgeKoNotFound from "./ko/CoBadgeKoNotFound"; -import CoBadgeKoServiceError from "./ko/CoBadgeKoServiceError"; -import CoBadgeKoSingleBankNotFound from "./ko/CoBadgeKoSingleBankNotFound"; -import CoBadgeKoTimeout from "./ko/CoBadgeKoTimeout"; -import LoadCoBadgeSearch from "./LoadCoBadgeSearch"; - -export type Props = ReturnType & - ReturnType; - -export const CoBadgePayloadR = t.interface({ - paymentInstruments: t.readonlyArray( - PaymentInstrument, - "array of PaymentInstrument" - ), - searchRequestMetadata: t.readonlyArray( - SearchRequestMetadata, - "array of SearchRequestMetadata" - ) -}); - -const CoBadgePayloadP = t.partial({ - searchRequestId: t.string -}); - -export const CoBadgePayload = t.intersection( - [CoBadgePayloadR, CoBadgePayloadP], - "CoBadgePayload" -); - -export type CoBadgePayload = t.TypeOf; - -const decodePayload = (cobadge: CobadgeResponse) => - CoBadgePayload.decode(cobadge.payload); - -const CobadgePayloadRight = (p: { - payload: CoBadgePayload; - props: Props; -}): React.ReactElement => { - const { props, payload } = p; - const anyPendingRequest = payload.searchRequestMetadata.some( - m => m.executionStatus === ExecutionStatusEnum.PENDING - ); - - const anyServiceError = payload.searchRequestMetadata.some( - m => m.executionStatus === ExecutionStatusEnum.KO - ); - - // not all the services replied with success - if (anyServiceError) { - return ; - } - - // with a pending request we show the timeout screen and the user will retry with the response token - if (anyPendingRequest) { - return ; - } - - const noCoBadgeFound = payload.paymentInstruments.length === 0; - if (noCoBadgeFound) { - // No results with a single Abi - return props.abiSelected !== undefined ? ( - - ) : ( - // No results with global research - - ); - } - // success! payload.paymentInstruments.length > 0, the user can now choose to add to the wallet - return ; -}; - -/** - * This screen handle the errors and loading for the co-badge. - * @constructor - */ -const SearchAvailableCoBadgeScreen = ( - props: Props -): React.ReactElement | null => { - const { search, abiSelected } = props; - useEffect(() => { - search(abiSelected); - }, [search, abiSelected]); - - useAvoidHardwareBackButton(); - - const coBadgeFound = props.coBadgeFound; - - if (isReady(coBadgeFound)) { - const payload = decodePayload(coBadgeFound.value); - return pipe( - payload, - E.fold( - _ => , - val => - ) - ); - } - - if (isError(coBadgeFound) && isTimeoutError(coBadgeFound.error)) { - return ; - } - return ; -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - search: (abi?: string) => dispatch(searchUserCoBadge.request(abi)) -}); - -const mapStateToProps = (state: GlobalState) => ({ - abiSelected: onboardingCoBadgeAbiSelectedSelector(state), - coBadgeFound: onboardingCoBadgeFoundSelector(state) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(SearchAvailableCoBadgeScreen); diff --git a/ts/features/wallet/onboarding/cobadge/screens/search/__test__/SearchAvailableCoBadgeScreen.test.tsx b/ts/features/wallet/onboarding/cobadge/screens/search/__test__/SearchAvailableCoBadgeScreen.test.tsx deleted file mode 100644 index 0793b1e2a53..00000000000 --- a/ts/features/wallet/onboarding/cobadge/screens/search/__test__/SearchAvailableCoBadgeScreen.test.tsx +++ /dev/null @@ -1,299 +0,0 @@ -import { fireEvent, RenderAPI } from "@testing-library/react-native"; -import * as React from "react"; - -import { Action, createStore, Store } from "redux"; -import { CobadgeResponse } from "../../../../../../../../definitions/pagopa/walletv2/CobadgeResponse"; -import { - PaymentInstrument, - PaymentNetworkEnum, - ProductTypeEnum, - ValidityStatusEnum -} from "../../../../../../../../definitions/pagopa/walletv2/PaymentInstrument"; -import { - ExecutionStatusEnum, - SearchRequestMetadata -} from "../../../../../../../../definitions/pagopa/walletv2/SearchRequestMetadata"; -import I18n from "../../../../../../../i18n"; -import { applicationChangeState } from "../../../../../../../store/actions/application"; -import { appReducer } from "../../../../../../../store/reducers"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { - getGenericError, - getTimeoutError -} from "../../../../../../../utils/errors"; -import { renderScreenWithNavigationStoreContext } from "../../../../../../../utils/testWrapper"; -import { FOUR_UNICODE_CIRCLES } from "../../../../../../../utils/wallet"; -import WALLET_ONBOARDING_COBADGE_ROUTES from "../../../navigation/routes"; -import { - searchUserCoBadge, - walletAddCoBadgeStart -} from "../../../store/actions"; -import SearchAvailableCoBadgeScreen from "../SearchAvailableCoBadgeScreen"; - -const abiTestId = "9876"; -const timeoutError = getTimeoutError(); -const genericError = getGenericError(new Error("Generic Error")); -const malformedData: CobadgeResponse = {}; -const noCardResponse: CobadgeResponse = { - payload: { paymentInstruments: [], searchRequestMetadata: [] } -}; - -const paymentInstrument: PaymentInstrument = { - validityStatus: ValidityStatusEnum.VALID, - paymentNetwork: PaymentNetworkEnum.MAESTRO, - expiringDate: new Date("2025-11-09"), - abiCode: abiTestId, - productType: ProductTypeEnum.CREDIT, - hpan: "hpan", - panCode: "panCode", - panPartialNumber: "panPartialNumber", - tokenMac: "tokenMac" -}; - -const oneCardResponse: CobadgeResponse = { - payload: { - paymentInstruments: [paymentInstrument], - searchRequestMetadata: [] - } -}; - -const searchRequestMetaPending: SearchRequestMetadata = { - executionStatus: ExecutionStatusEnum.PENDING, - retrievedInstrumentsCount: 0, - serviceProviderName: "ServiceNameHere" -}; - -const searchRequestMetaOK: SearchRequestMetadata = { - executionStatus: ExecutionStatusEnum.OK, - retrievedInstrumentsCount: 0, - serviceProviderName: "ServiceNameHere" -}; - -const searchRequestMetaKO: SearchRequestMetadata = { - executionStatus: ExecutionStatusEnum.KO, - retrievedInstrumentsCount: 0, - serviceProviderName: "ServiceNameHere" -}; - -const withSearchRequestMetadata = - (metadataList: ReadonlyArray) => - (response: CobadgeResponse) => ({ - ...response, - payload: { ...response.payload, searchRequestMetadata: [...metadataList] } - }); - -describe("Test behaviour of the SearchAvailableCoBadgeScreen", () => { - jest.useFakeTimers(); - it("With default state and searchUserCobadge.request should render LoadCoBadgeSearch", () => { - const { store, testComponent } = getSearchAvailableCoBadgeScreen(); - expect(isLoadingScreen(testComponent)).toBe(true); - store.dispatch(searchUserCoBadge.request(undefined)); - expect(isLoadingScreen(testComponent)).toBe(true); - store.dispatch(searchUserCoBadge.request(abiTestId)); - expect(isLoadingScreen(testComponent)).toBe(true); - }); - it("With timeout error should render CoBadgeKoTimeout and pressing on retry should request a reload", () => { - const { store, testComponent } = getSearchAvailableCoBadgeScreen(); - store.dispatch(searchUserCoBadge.request(undefined)); - store.dispatch(searchUserCoBadge.failure(timeoutError)); - expect(isTimeoutScreen(testComponent)).toBe(true); - - const retryButton = testComponent.queryByText( - I18n.t("global.buttons.retry") - ); - expect(retryButton).toBeTruthy(); - - if (retryButton !== null) { - fireEvent.press(retryButton); - expect(isLoadingScreen(testComponent)).toBe(true); - } - }); - it("With generic error should render LoadCoBadgeSearch with error state", () => { - const { store, testComponent } = getSearchAvailableCoBadgeScreen(); - store.dispatch(searchUserCoBadge.request(undefined)); - store.dispatch(searchUserCoBadge.failure(genericError)); - expect(isLoadingScreen(testComponent)).toBe(true); - expect( - testComponent.queryByTestId("LoadingErrorComponentError") - ).toBeTruthy(); - }); - it("With malformed data should render CoBadgeKoTimeout", () => { - const { store, testComponent } = getSearchAvailableCoBadgeScreen(); - store.dispatch(searchUserCoBadge.request(undefined)); - store.dispatch(searchUserCoBadge.success(malformedData)); - expect(isTimeoutScreen(testComponent)).toBe(true); - }); - it("With no card found, single abi, should render CoBadgeKoSingleBankNotFound", () => { - const { store, testComponent } = getSearchAvailableCoBadgeScreen(); - store.dispatch(searchUserCoBadge.request(abiTestId)); - store.dispatch(searchUserCoBadge.success(noCardResponse)); - expect(isCoBadgeKoSingleBankNotFoundScreen(testComponent)).toBe(true); - - const searchAllBankButton = testComponent.queryByText( - I18n.t("global.buttons.continue") - ); - expect(searchAllBankButton).toBeTruthy(); - - if (searchAllBankButton !== null) { - fireEvent.press(searchAllBankButton); - // After press the continue button, start the search for all abi - expect(isLoadingScreen(testComponent)).toBe(true); - // Simulate the response with no card - store.dispatch(searchUserCoBadge.success(noCardResponse)); - // The user will see the Ko screen not found for all abi search - expect(isCoBadgeKoNotFoundScreen(testComponent)).toBe(true); - } - }); - it("With no card found, all abi, should render CoBadgeKoNotFound", () => { - const { store, testComponent } = getSearchAvailableCoBadgeScreen(); - store.dispatch(searchUserCoBadge.request(undefined)); - store.dispatch(searchUserCoBadge.success(noCardResponse)); - expect(isCoBadgeKoNotFoundScreen(testComponent)).toBe(true); - }); - describe("Search request metadata pending behaviour", () => { - [undefined, abiTestId].forEach(abi => - it(`With at least one pending in search metadata request, abi=${abi}, should render CoBadgeKoServiceError`, () => { - const { store, testComponent } = getSearchAvailableCoBadgeScreen(); - store.dispatch(searchUserCoBadge.request(undefined)); - store.dispatch( - searchUserCoBadge.success( - withSearchRequestMetadata([searchRequestMetaPending])( - noCardResponse - ) - ) - ); - expect(isTimeoutScreen(testComponent)).toBe(true); - store.dispatch( - searchUserCoBadge.success( - withSearchRequestMetadata([ - searchRequestMetaPending, - searchRequestMetaPending - ])(noCardResponse) - ) - ); - expect(isTimeoutScreen(testComponent)).toBe(true); - store.dispatch( - searchUserCoBadge.success( - withSearchRequestMetadata([ - searchRequestMetaPending, - searchRequestMetaOK - ])(noCardResponse) - ) - ); - expect(isTimeoutScreen(testComponent)).toBe(true); - }) - ); - }); - - describe("Search request metadata error behaviour", () => { - [undefined, abiTestId].forEach(abi => - it(`With at least one error in search metadata request, abi=${abi}, should render CoBadgeKoServiceError`, () => { - const { store, testComponent } = getSearchAvailableCoBadgeScreen(); - store.dispatch(searchUserCoBadge.request(abi)); - store.dispatch( - searchUserCoBadge.success( - withSearchRequestMetadata([searchRequestMetaKO])(noCardResponse) - ) - ); - expect(isCoBadgeKoServiceErrorScreen(testComponent)).toBe(true); - store.dispatch( - searchUserCoBadge.success( - withSearchRequestMetadata([ - searchRequestMetaKO, - searchRequestMetaKO - ])(noCardResponse) - ) - ); - expect(isCoBadgeKoServiceErrorScreen(testComponent)).toBe(true); - store.dispatch( - searchUserCoBadge.success( - withSearchRequestMetadata([ - searchRequestMetaPending, - searchRequestMetaKO - ])(noCardResponse) - ) - ); - expect(isCoBadgeKoServiceErrorScreen(testComponent)).toBe(true); - store.dispatch( - searchUserCoBadge.success( - withSearchRequestMetadata([ - searchRequestMetaOK, - searchRequestMetaKO - ])(noCardResponse) - ) - ); - expect(isCoBadgeKoServiceErrorScreen(testComponent)).toBe(true); - }) - ); - }); - describe("Payment methods found", () => { - [undefined, abiTestId].forEach(abi => - it(`With at least one payment method found and all search metadata request ok, abi=${abi}, should render AddCoBadgeScreen`, () => { - const { store, testComponent } = getSearchAvailableCoBadgeScreen(); - store.dispatch(searchUserCoBadge.request(abi)); - store.dispatch( - searchUserCoBadge.success( - withSearchRequestMetadata([searchRequestMetaOK])(oneCardResponse) - ) - ); - expect(isAddCoBadgeScreen(testComponent)).toBe(true); - expect(paymentInstrument.panPartialNumber).toBeDefined(); - if (paymentInstrument.panPartialNumber) { - expect( - testComponent.queryByText( - `${FOUR_UNICODE_CIRCLES} ${paymentInstrument.panPartialNumber}` - ) - ).toBeTruthy(); - } - }) - ); - }); -}); - -const getSearchAvailableCoBadgeScreen = (abi?: string) => { - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - store.dispatch(walletAddCoBadgeStart(abi)); - const testComponent = renderSearchAvailableCoBadgeScreen(store); - return { store, testComponent }; -}; - -const renderSearchAvailableCoBadgeScreen = ( - store: Store -) => - renderScreenWithNavigationStoreContext( - () => , - WALLET_ONBOARDING_COBADGE_ROUTES.SEARCH_AVAILABLE, - {}, - store - ); - -const isLoadingScreen = (component: RenderAPI) => - component.queryByTestId("LoadCoBadgeSearch") !== null; - -const isTimeoutScreen = (component: RenderAPI) => - component.queryByTestId("CoBadgeKoTimeout") !== null && - component.queryByText( - I18n.t("wallet.onboarding.coBadge.search.koTimeout.body") - ) !== null; - -const isCoBadgeKoSingleBankNotFoundScreen = (component: RenderAPI) => - component.queryByTestId("CoBadgeKoSingleBankNotFound") !== null && - component.queryByText( - I18n.t("wallet.onboarding.coBadge.search.koSingleBankNotFound.body") - ) !== null; - -const isCoBadgeKoNotFoundScreen = (component: RenderAPI) => - component.queryByTestId("CoBadgeKoNotFound") !== null && - component.queryByText( - I18n.t("wallet.onboarding.coBadge.search.koNotFound.body") - ) !== null; - -const isCoBadgeKoServiceErrorScreen = (component: RenderAPI) => - component.queryByTestId("CoBadgeKoServiceError") !== null && - component.queryByText( - I18n.t("wallet.onboarding.coBadge.search.koServiceError.body") - ) !== null; - -const isAddCoBadgeScreen = (component: RenderAPI) => - component.queryByTestId("AddCobadgeComponent") !== null; diff --git a/ts/features/wallet/onboarding/cobadge/screens/search/ko/CoBadgeKoNotFound.tsx b/ts/features/wallet/onboarding/cobadge/screens/search/ko/CoBadgeKoNotFound.tsx deleted file mode 100644 index 15de1727c92..00000000000 --- a/ts/features/wallet/onboarding/cobadge/screens/search/ko/CoBadgeKoNotFound.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import * as React from "react"; -import { useContext } from "react"; -import { SafeAreaView, View } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import image from "../../../../../../../../img/servicesStatus/error-detail-icon.png"; -import { FooterStackButton } from "../../../../../../../components/buttons/FooterStackButtons"; -import { IOStyles } from "../../../../../../../components/core/variables/IOStyles"; -import { InfoScreenComponent } from "../../../../../../../components/infoScreen/InfoScreenComponent"; -import { renderInfoRasterImage } from "../../../../../../../components/infoScreen/imageRendering"; -import BaseScreenComponent from "../../../../../../../components/screens/BaseScreenComponent"; -import { LightModalContext } from "../../../../../../../components/ui/LightModal"; -import { useHardwareBackButton } from "../../../../../../../hooks/useHardwareBackButton"; -import I18n from "../../../../../../../i18n"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { emptyContextualHelp } from "../../../../../../../utils/emptyContextualHelp"; -import TosBonusComponent from "../../../../../../bonus/common/components/TosBonusComponent"; -import { walletAddCoBadgeCancel } from "../../../store/actions"; - -export type Props = ReturnType & - ReturnType & - Pick, "contextualHelp">; - -const loadLocales = () => ({ - headerTitle: I18n.t("wallet.onboarding.coBadge.headerTitle"), - title: I18n.t("wallet.onboarding.coBadge.search.koNotFound.title"), - body: I18n.t("wallet.onboarding.coBadge.search.koNotFound.body"), - close: I18n.t("global.buttons.close"), - findOutMore: I18n.t("global.buttons.findOutMore") -}); - -// TODO: unify with the link used in CoBadgeSingleBankScreen https://www.pivotaltracker.com/story/show/176780396 -const participatingBankUrl = - "https://io.italia.it/cashback/carta-non-abilitata-pagamenti-online"; - -/** - * This screen informs the user that no co-badge in his name were found. - * @constructor - */ -const CoBadgeKoNotFound = (props: Props): React.ReactElement => { - const { headerTitle, title, body, close, findOutMore } = loadLocales(); - - useHardwareBackButton(() => { - props.cancel(); - return true; - }); - const { showModal, hideModal } = useContext(LightModalContext); - const openCardsNotEnabledModal = () => { - showModal( - - ); - }; - return ( - } - headerTitle={headerTitle} - contextualHelp={emptyContextualHelp} - > - - - - - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - cancel: () => dispatch(walletAddCoBadgeCancel()) -}); - -const mapStateToProps = (_: GlobalState) => ({}); - -export default connect(mapStateToProps, mapDispatchToProps)(CoBadgeKoNotFound); diff --git a/ts/features/wallet/onboarding/cobadge/screens/search/ko/CoBadgeKoServiceError.tsx b/ts/features/wallet/onboarding/cobadge/screens/search/ko/CoBadgeKoServiceError.tsx deleted file mode 100644 index ab6f15669b6..00000000000 --- a/ts/features/wallet/onboarding/cobadge/screens/search/ko/CoBadgeKoServiceError.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { FooterWithButtons } from "@pagopa/io-app-design-system"; -import * as React from "react"; -import { SafeAreaView, View } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import image from "../../../../../../../../img/wallet/errors/payment-unavailable-icon.png"; -import { IOStyles } from "../../../../../../../components/core/variables/IOStyles"; -import { InfoScreenComponent } from "../../../../../../../components/infoScreen/InfoScreenComponent"; -import { renderInfoRasterImage } from "../../../../../../../components/infoScreen/imageRendering"; -import BaseScreenComponent from "../../../../../../../components/screens/BaseScreenComponent"; -import { useHardwareBackButton } from "../../../../../../../hooks/useHardwareBackButton"; -import I18n from "../../../../../../../i18n"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { emptyContextualHelp } from "../../../../../../../utils/emptyContextualHelp"; -import { walletAddCoBadgeCancel } from "../../../store/actions"; - -export type Props = ReturnType & - ReturnType & - Pick, "contextualHelp">; - -const loadLocales = () => ({ - headerTitle: I18n.t("wallet.onboarding.coBadge.headerTitle"), - title: I18n.t("wallet.onboarding.coBadge.search.koServiceError.title"), - body: I18n.t("wallet.onboarding.coBadge.search.koServiceError.body"), - close: I18n.t("global.buttons.close") -}); - -/** - * This screen informs the user that no co-badge in his name were found because not all - * the services reply with success. - * @constructor - */ -const CoBadgeKoServiceError: React.FunctionComponent = props => { - const { headerTitle, title, body, close } = loadLocales(); - - useHardwareBackButton(() => { - props.cancel(); - return true; - }); - return ( - } - headerTitle={headerTitle} - contextualHelp={emptyContextualHelp} - > - - - - - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - cancel: () => dispatch(walletAddCoBadgeCancel()) -}); - -const mapStateToProps = (_: GlobalState) => ({}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(CoBadgeKoServiceError); diff --git a/ts/features/wallet/onboarding/cobadge/screens/search/ko/CoBadgeKoSingleBankNotFound.tsx b/ts/features/wallet/onboarding/cobadge/screens/search/ko/CoBadgeKoSingleBankNotFound.tsx deleted file mode 100644 index 798cd9fb821..00000000000 --- a/ts/features/wallet/onboarding/cobadge/screens/search/ko/CoBadgeKoSingleBankNotFound.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { FooterWithButtons } from "@pagopa/io-app-design-system"; -import * as React from "react"; -import { SafeAreaView, View } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import image from "../../../../../../../../img/servicesStatus/error-detail-icon.png"; -import { IOStyles } from "../../../../../../../components/core/variables/IOStyles"; -import { InfoScreenComponent } from "../../../../../../../components/infoScreen/InfoScreenComponent"; -import { renderInfoRasterImage } from "../../../../../../../components/infoScreen/imageRendering"; -import BaseScreenComponent from "../../../../../../../components/screens/BaseScreenComponent"; - -import { useHardwareBackButton } from "../../../../../../../hooks/useHardwareBackButton"; -import I18n from "../../../../../../../i18n"; -import { navigateBack } from "../../../../../../../store/actions/navigation"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { emptyContextualHelp } from "../../../../../../../utils/emptyContextualHelp"; -import { - searchUserCoBadge, - walletAddCoBadgeCancel -} from "../../../store/actions"; - -export type Props = ReturnType & - ReturnType & - Pick, "contextualHelp">; - -const loadLocales = () => ({ - headerTitle: I18n.t("wallet.onboarding.coBadge.headerTitle"), - title: I18n.t("wallet.onboarding.coBadge.search.koSingleBankNotFound.title"), - body: I18n.t("wallet.onboarding.coBadge.search.koSingleBankNotFound.body"), - continueStr: I18n.t("global.buttons.continue") -}); - -/** - * This screen informs the user that no co-badge cards in his name were found. - * A specific bank (ABI) has been selected - * @constructor - */ -const CoBadgeKoSingleBankNotFound: React.FunctionComponent = props => { - const { headerTitle, title, body, continueStr } = loadLocales(); - - const onSearchAll = () => props.searchAll(); - - useHardwareBackButton(() => { - props.cancel(); - return true; - }); - - return ( - } - headerTitle={headerTitle} - contextualHelp={emptyContextualHelp} - > - - - - - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - cancel: () => dispatch(walletAddCoBadgeCancel()), - back: () => navigateBack(), - searchAll: () => dispatch(searchUserCoBadge.request(undefined)) -}); - -const mapStateToProps = (_: GlobalState) => ({}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(CoBadgeKoSingleBankNotFound); diff --git a/ts/features/wallet/onboarding/cobadge/screens/search/ko/CoBadgeKoTimeout.tsx b/ts/features/wallet/onboarding/cobadge/screens/search/ko/CoBadgeKoTimeout.tsx deleted file mode 100644 index f5f14913d76..00000000000 --- a/ts/features/wallet/onboarding/cobadge/screens/search/ko/CoBadgeKoTimeout.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import * as React from "react"; -import { View, SafeAreaView } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { FooterWithButtons } from "@pagopa/io-app-design-system"; -import image from "../../../../../../../../img/servicesStatus/error-detail-icon.png"; -import { IOStyles } from "../../../../../../../components/core/variables/IOStyles"; -import { renderInfoRasterImage } from "../../../../../../../components/infoScreen/imageRendering"; -import { InfoScreenComponent } from "../../../../../../../components/infoScreen/InfoScreenComponent"; -import BaseScreenComponent from "../../../../../../../components/screens/BaseScreenComponent"; -import I18n from "../../../../../../../i18n"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { emptyContextualHelp } from "../../../../../../../utils/emptyContextualHelp"; -import { useHardwareBackButton } from "../../../../../../../hooks/useHardwareBackButton"; -import { - searchUserCoBadge, - walletAddCoBadgeCancel -} from "../../../store/actions"; -import { onboardingCoBadgeAbiSelectedSelector } from "../../../store/reducers/abiSelected"; - -export type Props = ReturnType & - ReturnType & - Pick, "contextualHelp">; - -const loadLocales = () => ({ - headerTitle: I18n.t("wallet.onboarding.coBadge.headerTitle"), - title: I18n.t("wallet.onboarding.coBadge.search.koTimeout.title"), - body: I18n.t("wallet.onboarding.coBadge.search.koTimeout.body"), - cancel: I18n.t("global.buttons.cancel"), - retry: I18n.t("global.buttons.retry") -}); - -/** - * This screen informs the user that the search operation could not be completed - * @constructor - */ -const CoBadgeKoTimeout = (props: Props): React.ReactElement => { - const { headerTitle, title, body, cancel, retry } = loadLocales(); - - useHardwareBackButton(() => { - props.cancel(); - return true; - }); - return ( - } - headerTitle={headerTitle} - contextualHelp={emptyContextualHelp} - > - - - props.retry(props.abiSelected) - } - }} - /> - - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - cancel: () => dispatch(walletAddCoBadgeCancel()), - retry: (abiSelected: string | undefined) => - dispatch(searchUserCoBadge.request(abiSelected)) -}); - -const mapStateToProps = (state: GlobalState) => ({ - abiSelected: onboardingCoBadgeAbiSelectedSelector(state) -}); - -export default connect(mapStateToProps, mapDispatchToProps)(CoBadgeKoTimeout); diff --git a/ts/features/wallet/onboarding/cobadge/screens/start/CoBadgeChosenBankScreen.tsx b/ts/features/wallet/onboarding/cobadge/screens/start/CoBadgeChosenBankScreen.tsx deleted file mode 100644 index 1fa0c58d0dc..00000000000 --- a/ts/features/wallet/onboarding/cobadge/screens/start/CoBadgeChosenBankScreen.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import * as React from "react"; -import { useContext } from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { Abi } from "../../../../../../../definitions/pagopa/walletv2/Abi"; -import { LightModalContext } from "../../../../../../components/ui/LightModal"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { WithTestID } from "../../../../../../types/WithTestID"; -import TosBonusComponent from "../../../../../bonus/common/components/TosBonusComponent"; -import SearchStartScreen from "../../../common/searchBank/SearchStartScreen"; -import { abiListSelector, abiSelector } from "../../../store/abi"; -import { navigateToOnboardingCoBadgeSearchAvailable } from "../../navigation/action"; -import { - walletAddCoBadgeCancel, - walletAddCoBadgeFailure -} from "../../store/actions"; -import { fold, isReady } from "../../../../../../common/model/RemoteValue"; -import { loadAbi } from "../../../bancomat/store/actions"; -import I18n from "../../../../../../i18n"; -import { LoadingErrorComponent } from "../../../../../../components/LoadingErrorComponent"; -import { mixpanelTrack } from "../../../../../../mixpanel"; - -type OwnProps = { - abi?: string; -}; -type Props = WithTestID & - ReturnType & - ReturnType; - -const tos_url = - "https://io.italia.it/app-content/privacy_circuiti_internazionali.html"; - -const participatingBank_url = - "https://io.italia.it/cashback/carta-non-abilitata-pagamenti-online"; - -const abiListLoadingError = ( - isLoading: boolean, - onAbort: () => void, - onRetry: () => void -) => ( - -); - -/** - * The initial screen of the co-badge workflow (starting with a specific ABI, eg. from BANCOMAT screen) - * The user can see the selected bank and can start the search for all the co-badge for the specific bank. - * @constructor - * @param props - */ -const CoBadgeChosenBankScreen = (props: Props): React.ReactElement | null => { - const { showModal, hideModal } = useContext(LightModalContext); - const { abiListStatus, loadAbiList } = props; - - React.useEffect(() => { - fold( - abiListStatus, - () => loadAbiList(), - () => null, - _ => null, - _ => loadAbiList() - ); - }, [abiListStatus, loadAbiList]); - - const openTosModal = () => { - showModal(); - }; - - const openPartecipatingBankModal = () => { - showModal( - - ); - }; - - const abiInfo: Abi | undefined = props.abiList.find( - aI => aI.abi === props.abi - ); - - if ( - abiInfo === undefined && - props.abi !== undefined && - isReady(props.abiListStatus) - ) { - props.onGeneralError("Abi not found in abilist in CoBadgeChosenBankScreen"); - void mixpanelTrack("COBADGE_CHOSEN_BANK_SCREEN_ABI_NOT_FOUND", { - issuerAbiCode: props.abi - }); - return null; - } - - return fold( - props.abiListStatus, - () => abiListLoadingError(false, props.onCancel, props.loadAbiList), - () => abiListLoadingError(true, props.onCancel, props.loadAbiList), - _ => ( - - ), - _ => abiListLoadingError(false, props.onCancel, props.loadAbiList) - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - onCancel: () => dispatch(walletAddCoBadgeCancel()), - searchAccounts: () => navigateToOnboardingCoBadgeSearchAvailable(), - loadAbiList: () => dispatch(loadAbi.request()), - onGeneralError: (reason: string) => dispatch(walletAddCoBadgeFailure(reason)) -}); - -const mapStateToProps = (state: GlobalState) => ({ - abiListStatus: abiSelector(state), - abiList: abiListSelector(state) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(CoBadgeChosenBankScreen); diff --git a/ts/features/wallet/onboarding/cobadge/screens/start/CoBadgeStartScreen.tsx b/ts/features/wallet/onboarding/cobadge/screens/start/CoBadgeStartScreen.tsx deleted file mode 100644 index e642e44b45f..00000000000 --- a/ts/features/wallet/onboarding/cobadge/screens/start/CoBadgeStartScreen.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import _ from "lodash"; -import * as React from "react"; -import { useEffect, useRef } from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { StatusEnum } from "../../../../../../../definitions/pagopa/cobadge/configuration/CoBadgeService"; -import { mixpanelTrack } from "../../../../../../mixpanel"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { loadCoBadgeAbiConfiguration } from "../../store/actions"; -import { getCoBadgeAbiConfigurationSelector } from "../../store/reducers/abiConfiguration"; -import { onboardingCoBadgeAbiSelectedSelector } from "../../store/reducers/abiSelected"; -import CoBadgeChosenBankScreen from "./CoBadgeChosenBankScreen"; -import CoBadgeStartKoDisabled from "./ko/CoBadgeStartKoDisabled"; -import CoBadgeStartKoUnavailable from "./ko/CoBadgeStartKoUnavailable"; -import LoadAbiConfiguration from "./LoadAbiConfiguration"; - -type Props = ReturnType & - ReturnType; - -const eventTrackName = "WALLET_ONBOARDING_COBADGE_SCREEN_START_RESULT"; -type StartScreenState = "All" | "Enabled" | "Disabled" | "Unavailable"; - -const trackOutput = (screenState: StartScreenState, abi?: string) => - mixpanelTrack(eventTrackName, { screen: screenState, abi }); - -/** - * The entry screen that orchestrate the different screens - * @constructor - * @param props - */ -const CoBadgeStartComponent = (props: Props): React.ReactElement => { - const { loadAbiConfig, maybeAbiSelected } = props; - const firstLoading = useRef(true); - // If a single ABI is selected, we should check the abiConfiguration - useEffect(() => { - if (maybeAbiSelected !== undefined && firstLoading.current) { - loadAbiConfig(); - // eslint-disable-next-line functional/immutable-data - firstLoading.current = false; - } - }, [loadAbiConfig, maybeAbiSelected]); - - // All ABI selected - if (props.maybeAbiSelected === undefined) { - void trackOutput("All"); - return ; - } - - // The ABI configuration is loading - if (props.abiSelectedConfiguration.kind !== "PotSome") { - return ; - } - switch (props.abiSelectedConfiguration.value) { - case StatusEnum.enabled: - void trackOutput("Enabled", props.maybeAbiSelected); - // Single ABI (bank) screen that allow to start the search - return ( - - ); - case StatusEnum.disabled: - void trackOutput("Disabled", props.maybeAbiSelected); - // The chosen ABI is disabled (not yet available) - return ; - case StatusEnum.unavailable: - void trackOutput("Unavailable", props.maybeAbiSelected); - // THe chosen ABI is unavailable (technical problems) - return ; - } -}; - -const CoBadgeStartMemo = React.memo( - CoBadgeStartComponent, - (prev: Props, curr: Props) => - prev.maybeAbiSelected === curr.maybeAbiSelected && - prev.abiSelectedConfiguration.kind === curr.abiSelectedConfiguration.kind && - pot.isSome(prev.abiSelectedConfiguration) && - pot.isSome(curr.abiSelectedConfiguration) && - _.isEqual( - curr.abiSelectedConfiguration.value, - prev.abiSelectedConfiguration.value - ) -); - -const CoBadgeStartScreen = (props: Props) => ; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - loadAbiConfig: () => dispatch(loadCoBadgeAbiConfiguration.request()) -}); - -const mapStateToProps = (state: GlobalState) => { - const maybeAbiSelected = onboardingCoBadgeAbiSelectedSelector(state); - const abiSelectedConfiguration = pipe( - maybeAbiSelected, - O.fromNullable, - O.map(abiSelected => - getCoBadgeAbiConfigurationSelector(state, abiSelected) - ), - O.getOrElseW(() => pot.some(StatusEnum.disabled)) - ); - return { maybeAbiSelected, abiSelectedConfiguration }; -}; - -export default connect(mapStateToProps, mapDispatchToProps)(CoBadgeStartScreen); diff --git a/ts/features/wallet/onboarding/cobadge/screens/start/LoadAbiConfiguration.tsx b/ts/features/wallet/onboarding/cobadge/screens/start/LoadAbiConfiguration.tsx deleted file mode 100644 index 2290b825e58..00000000000 --- a/ts/features/wallet/onboarding/cobadge/screens/start/LoadAbiConfiguration.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import I18n from "../../../../../../i18n"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { WithTestID } from "../../../../../../types/WithTestID"; -import { useHardwareBackButton } from "../../../../../../hooks/useHardwareBackButton"; -import { LoadingErrorComponent } from "../../../../../../components/LoadingErrorComponent"; -import { - loadCoBadgeAbiConfiguration, - walletAddCoBadgeCancel -} from "../../store/actions"; -import { coBadgeAbiConfigurationSelector } from "../../store/reducers/abiConfiguration"; - -export type Props = WithTestID< - ReturnType & ReturnType ->; - -/** - * This screen is displayed when loading co-badge configuration - * @constructor - */ -const LoadAbiConfiguration = (props: Props): React.ReactElement => { - useHardwareBackButton(() => { - if (!props.isLoading) { - props.cancel(); - } - return true; - }); - return ( - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - cancel: () => dispatch(walletAddCoBadgeCancel()), - retry: () => dispatch(loadCoBadgeAbiConfiguration.request()) -}); - -const mapStateToProps = (state: GlobalState) => ({ - isLoading: pot.isLoading(coBadgeAbiConfigurationSelector(state)) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(LoadAbiConfiguration); diff --git a/ts/features/wallet/onboarding/cobadge/screens/start/__test__/CoBadgeStartScreen.test.tsx b/ts/features/wallet/onboarding/cobadge/screens/start/__test__/CoBadgeStartScreen.test.tsx deleted file mode 100644 index 1fc63dbad62..00000000000 --- a/ts/features/wallet/onboarding/cobadge/screens/start/__test__/CoBadgeStartScreen.test.tsx +++ /dev/null @@ -1,275 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { fireEvent, RenderAPI } from "@testing-library/react-native"; -import * as React from "react"; - -import { Action, createStore, Store } from "redux"; -import { StatusEnum } from "../../../../../../../../definitions/pagopa/cobadge/configuration/CoBadgeService"; -import { CoBadgeServices } from "../../../../../../../../definitions/pagopa/cobadge/configuration/CoBadgeServices"; -import I18n from "../../../../../../../i18n"; -import { applicationChangeState } from "../../../../../../../store/actions/application"; -import { appReducer } from "../../../../../../../store/reducers"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { getGenericError } from "../../../../../../../utils/errors"; -import { renderScreenWithNavigationStoreContext } from "../../../../../../../utils/testWrapper"; -import { loadAbi } from "../../../../bancomat/store/actions"; -import WALLET_ONBOARDING_COBADGE_ROUTES from "../../../navigation/routes"; -import { - loadCoBadgeAbiConfiguration, - walletAddCoBadgeStart -} from "../../../store/actions"; -import CoBadgeStartScreen from "../CoBadgeStartScreen"; - -jest.mock("react-native-share", () => jest.fn()); - -const configurationFailure = getGenericError(new Error("generic Error")); -const abiTestId = "9876"; -const abiTestId2 = "2222"; -const abiConfigurationWithoutBankMock: CoBadgeServices = { - ServiceName: { - status: StatusEnum.enabled, - issuers: [{ abi: "1", name: "bankName" }] - } -}; -const abiConfigurationDisabled: CoBadgeServices = { - ServiceName: { - status: StatusEnum.disabled, - issuers: [{ abi: abiTestId, name: "bankName" }] - } -}; -const abiConfigurationUnavailable: CoBadgeServices = { - ServiceName: { - status: StatusEnum.unavailable, - issuers: [{ abi: abiTestId, name: "bankName" }] - } -}; -const abiConfigurationEnabled: CoBadgeServices = { - ServiceName: { - status: StatusEnum.enabled, - issuers: [ - { abi: abiTestId, name: "bankName1" }, - { abi: abiTestId2, name: "bankName2" } - ] - } -}; - -describe("Test behaviour of the CoBadgeStartScreen", () => { - jest.useFakeTimers(); - it("With the default state, the screen should render LoadingErrorComponent if the abiList is different from remoteReady", () => { - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - - // check default initial state - expect(store.getState().wallet.onboarding.coBadge.abiConfiguration).toBe( - pot.none - ); - expect(store.getState().wallet.onboarding.coBadge.abiSelected).toBeNull(); - - const testComponent = renderCoBadgeScreen(store); - - expect(isAbiListLoadingError(testComponent)).toBe(true); - }); - it("With the configuration loading and a specific abi, the screen should render LoadAbiConfiguration (loading state)", () => { - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - store.dispatch(walletAddCoBadgeStart(abiTestId)); - store.dispatch(loadCoBadgeAbiConfiguration.request()); - - // check default initial state - expect(store.getState().wallet.onboarding.coBadge.abiConfiguration).toBe( - pot.noneLoading - ); - expect(store.getState().wallet.onboarding.coBadge.abiSelected).toBe( - abiTestId - ); - - const testComponent = renderCoBadgeScreen(store); - expect(isLoadingScreen(testComponent)).toBe(true); - - // check the loading state for the loading error component - expect( - testComponent.queryByTestId("LoadingErrorComponentLoading") - ).toBeTruthy(); - expect( - testComponent.queryByText( - I18n.t("wallet.onboarding.coBadge.start.loading") - ) - ).toBeTruthy(); - - store.dispatch( - loadCoBadgeAbiConfiguration.failure({ - kind: "generic", - value: new Error() - }) - ); - expect( - testComponent.queryByTestId("LoadingErrorComponentError") - ).toBeTruthy(); - }); - it("With the configuration error and a specific abi, the screen should render LoadAbiConfiguration (loading state)", () => { - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - store.dispatch(walletAddCoBadgeStart(abiTestId)); - store.dispatch(loadCoBadgeAbiConfiguration.failure(configurationFailure)); - - // check default initial state - expect( - store.getState().wallet.onboarding.coBadge.abiConfiguration - ).toStrictEqual(pot.noneError(configurationFailure)); - expect(store.getState().wallet.onboarding.coBadge.abiSelected).toBe( - abiTestId - ); - - const testComponent = renderCoBadgeScreen(store); - - // When the component is mounted with coBadgeConfiguration pot.Error, the component try to reload the state - expect( - store.getState().wallet.onboarding.coBadge.abiConfiguration - ).toStrictEqual(pot.noneLoading); - - expect(isLoadingScreen(testComponent)).toBe(true); - - // check the loading state for the loading error component - expect( - testComponent.queryByTestId("LoadingErrorComponentLoading") - ).toBeTruthy(); - }); - it("When receive an error during the loading, the screen should render LoadAbiConfiguration (error state)", () => { - const { store, testComponent } = getInitCoBadgeStartScreen(abiTestId); - - store.dispatch(loadCoBadgeAbiConfiguration.failure(configurationFailure)); - // After an error, the component switch to "error" mode - expect(isLoadingScreen(testComponent)).toBe(true); - // check the error state for the loading error component - expect( - testComponent.queryByTestId("LoadingErrorComponentError") - ).toBeTruthy(); - - const retryButton = testComponent.queryByText( - I18n.t("global.buttons.retry") - ); - expect(retryButton).toBeTruthy(); - if (retryButton !== null) { - // If the user press the retry button, the state change to LoadingErrorComponentLoading - fireEvent.press(retryButton); - expect( - testComponent.queryByTestId("LoadingErrorComponentLoading") - ).toBeTruthy(); - } - }); - it("When receive a configuration without the selected abi, the screen should render CoBadgeStartKoDisabled", () => { - const { store, testComponent } = getInitCoBadgeStartScreen(abiTestId); - - store.dispatch( - loadCoBadgeAbiConfiguration.success(abiConfigurationWithoutBankMock) - ); - // The user should see the disabled screen when the selected abi is not in the remote configuration - expect(isDisabledScreen(testComponent)).toBe(true); - }); - it("When receive a configuration without an abi disabled, the screen should render CoBadgeStartKoDisabled", () => { - const { store, testComponent } = getInitCoBadgeStartScreen(abiTestId); - - store.dispatch( - loadCoBadgeAbiConfiguration.success(abiConfigurationDisabled) - ); - // The user should see the disabled screen - expect(isDisabledScreen(testComponent)).toBe(true); - }); - it("When receive a configuration with an abi unavailable, the screen should render CoBadgeStartKoUnavailable", () => { - const { store, testComponent } = getInitCoBadgeStartScreen(abiTestId); - - store.dispatch( - loadCoBadgeAbiConfiguration.success(abiConfigurationUnavailable) - ); - // The user should see the unavailable screen - expect(isUnavailableScreen(testComponent)).toBe(true); - }); - it("When receive a configuration with an abi enabled, and the abiList is not remoteReady the screen should render AbiListLoadingError", () => { - const { store, testComponent } = getInitCoBadgeStartScreen(abiTestId); - store.dispatch( - loadCoBadgeAbiConfiguration.success(abiConfigurationEnabled) - ); - // if the selected abi is not in the abi list, the user will see a generic text: - expect(isAbiListLoadingError(testComponent)).toBe(true); - const bankName = "abiBankName"; - store.dispatch( - loadAbi.success({ data: [{ abi: abiTestId, name: bankName }] }) - ); - }); - it("When receive a configuration with an abi enabled, and the abiList is remoteReady the screen should render CoBadgeChosenBankScreen", () => { - const { store, testComponent } = getInitCoBadgeStartScreen(abiTestId); - const bankName = "abiBankName"; - store.dispatch( - loadAbi.success({ data: [{ abi: abiTestId, name: bankName }] }) - ); - store.dispatch( - loadCoBadgeAbiConfiguration.success(abiConfigurationEnabled) - ); - - // if the selected abi is in the abi list, - // the user will see the name of the bank instead of the generic "all bank" - // The name displayed to the user is taken from abiListSelector - // The user should see the single bank screen - expect(isCoBadgeChosenSingleBankScreen(testComponent)).toBe(true); - expect(testComponent.queryByText(bankName)).toBeTruthy(); - }); - it("When change the configuration, CoBadgeChosenBankScreen should update (check memoization)", () => { - const { store, testComponent } = getInitCoBadgeStartScreen(abiTestId); - const bankName = "abiBankName1"; - const bankName2 = "abiBankName2"; - store.dispatch( - loadAbi.success({ - data: [ - { abi: abiTestId, name: bankName }, - { abi: abiTestId2, name: bankName2 } - ] - }) - ); - - store.dispatch( - loadCoBadgeAbiConfiguration.success(abiConfigurationEnabled) - ); - // The user should see the single bank screen - expect(isCoBadgeChosenSingleBankScreen(testComponent)).toBe(true); - - expect(testComponent.queryByText(bankName)).toBeTruthy(); - - store.dispatch(walletAddCoBadgeStart(abiTestId2)); - - expect(testComponent.queryByText(bankName2)).toBeTruthy(); - }); -}); - -/** - * Initialize a CoBadgeStartScreen, that pass in loading state after the mount - * @param abi - */ -const getInitCoBadgeStartScreen = (abi: string) => { - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - store.dispatch(walletAddCoBadgeStart(abi)); - const testComponent = renderCoBadgeScreen(store); - return { store, testComponent }; -}; - -const renderCoBadgeScreen = (store: Store) => - renderScreenWithNavigationStoreContext( - () => , - WALLET_ONBOARDING_COBADGE_ROUTES.CHOOSE_TYPE, - {}, - store - ); - -const isLoadingScreen = (component: RenderAPI) => - component.queryByTestId("LoadAbiConfiguration") !== null; - -const isDisabledScreen = (component: RenderAPI) => - component.queryByTestId("CoBadgeStartKoDisabled") !== null; - -const isUnavailableScreen = (component: RenderAPI) => - component.queryByTestId("CoBadgeStartKoUnavailable") !== null; - -const isCoBadgeChosenSingleBankScreen = (component: RenderAPI) => - component.queryByTestId("CoBadgeChosenBankScreenSingleBank") !== null; - -const isAbiListLoadingError = (component: RenderAPI) => - component.queryByTestId("abiListLoadingError") !== null; diff --git a/ts/features/wallet/onboarding/cobadge/screens/start/ko/CoBadgeStartKoDisabled.tsx b/ts/features/wallet/onboarding/cobadge/screens/start/ko/CoBadgeStartKoDisabled.tsx deleted file mode 100644 index 9b776d356ec..00000000000 --- a/ts/features/wallet/onboarding/cobadge/screens/start/ko/CoBadgeStartKoDisabled.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import * as React from "react"; -import { useContext } from "react"; -import { SafeAreaView, View } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import image from "../../../../../../../../img/wallet/errors/payment-unavailable-icon.png"; -import { FooterStackButton } from "../../../../../../../components/buttons/FooterStackButtons"; -import { IOStyles } from "../../../../../../../components/core/variables/IOStyles"; -import { InfoScreenComponent } from "../../../../../../../components/infoScreen/InfoScreenComponent"; -import { renderInfoRasterImage } from "../../../../../../../components/infoScreen/imageRendering"; -import BaseScreenComponent from "../../../../../../../components/screens/BaseScreenComponent"; -import { LightModalContext } from "../../../../../../../components/ui/LightModal"; -import { useHardwareBackButton } from "../../../../../../../hooks/useHardwareBackButton"; -import I18n from "../../../../../../../i18n"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { emptyContextualHelp } from "../../../../../../../utils/emptyContextualHelp"; -import TosBonusComponent from "../../../../../../bonus/common/components/TosBonusComponent"; -import { walletAddCoBadgeCancel } from "../../../store/actions"; - -type Props = ReturnType & - ReturnType; - -const loadLocales = () => ({ - headerTitle: I18n.t("wallet.onboarding.coBadge.headerTitle"), - title: I18n.t("wallet.onboarding.coBadge.start.koDisabled.title"), - body: I18n.t("wallet.onboarding.coBadge.start.koDisabled.body"), - close: I18n.t("global.buttons.close"), - findOutMore: I18n.t("global.buttons.findOutMore") -}); - -// TODO: unify with the link used in CoBadgeSingleBankScreen https://www.pivotaltracker.com/story/show/176780396 -const participatingBankUrl = - "https://io.italia.it/cashback/carta-non-abilitata-pagamenti-online"; - -/** - * The co-badge workflow is not yet available for the selected bank - * @constructor - * @param props - */ -const CoBadgeStartKoDisabled = (props: Props): React.ReactElement => { - useHardwareBackButton(() => { - props.cancel(); - return true; - }); - const { headerTitle, title, body, close, findOutMore } = loadLocales(); - const { showModal, hideModal } = useContext(LightModalContext); - const openCardsNotEnabledModal = () => { - showModal( - - ); - }; - return ( - } - headerTitle={headerTitle} - contextualHelp={emptyContextualHelp} - > - - - - - - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - cancel: () => dispatch(walletAddCoBadgeCancel()) -}); - -const mapStateToProps = (_: GlobalState) => ({}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(CoBadgeStartKoDisabled); diff --git a/ts/features/wallet/onboarding/cobadge/screens/start/ko/CoBadgeStartKoUnavailable.tsx b/ts/features/wallet/onboarding/cobadge/screens/start/ko/CoBadgeStartKoUnavailable.tsx deleted file mode 100644 index 5e349174648..00000000000 --- a/ts/features/wallet/onboarding/cobadge/screens/start/ko/CoBadgeStartKoUnavailable.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { FooterWithButtons } from "@pagopa/io-app-design-system"; -import * as React from "react"; -import { SafeAreaView, View } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import image from "../../../../../../../../img/servicesStatus/error-detail-icon.png"; -import { IOStyles } from "../../../../../../../components/core/variables/IOStyles"; -import { InfoScreenComponent } from "../../../../../../../components/infoScreen/InfoScreenComponent"; -import { renderInfoRasterImage } from "../../../../../../../components/infoScreen/imageRendering"; -import BaseScreenComponent from "../../../../../../../components/screens/BaseScreenComponent"; -import { useHardwareBackButton } from "../../../../../../../hooks/useHardwareBackButton"; -import I18n from "../../../../../../../i18n"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { emptyContextualHelp } from "../../../../../../../utils/emptyContextualHelp"; -import { walletAddCoBadgeCancel } from "../../../store/actions"; - -type Props = ReturnType & - ReturnType; - -const loadLocales = () => ({ - headerTitle: I18n.t("wallet.onboarding.coBadge.headerTitle"), - title: I18n.t("wallet.onboarding.coBadge.start.koUnavailable.title"), - body: I18n.t("wallet.onboarding.coBadge.start.koUnavailable.body"), - close: I18n.t("global.buttons.close") -}); - -/** - * The co-badge service is remotely disabled for the selected bank - * @constructor - * @param props - */ -const CoBadgeStartKoUnavailable = (props: Props): React.ReactElement => { - useHardwareBackButton(() => { - props.cancel(); - return true; - }); - const { headerTitle, title, body, close } = loadLocales(); - return ( - } - headerTitle={headerTitle} - contextualHelp={emptyContextualHelp} - > - - - - - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - cancel: () => dispatch(walletAddCoBadgeCancel()) -}); - -const mapStateToProps = (_: GlobalState) => ({}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(CoBadgeStartKoUnavailable); diff --git a/ts/features/wallet/onboarding/cobadge/store/actions/index.ts b/ts/features/wallet/onboarding/cobadge/store/actions/index.ts deleted file mode 100644 index b9dca31f475..00000000000 --- a/ts/features/wallet/onboarding/cobadge/store/actions/index.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { - ActionType, - createAsyncAction, - createStandardAction -} from "typesafe-actions"; -import { CoBadgeServices } from "../../../../../../../definitions/pagopa/cobadge/configuration/CoBadgeServices"; -import { CobadgeResponse } from "../../../../../../../definitions/pagopa/walletv2/CobadgeResponse"; -import { PaymentInstrument } from "../../../../../../../definitions/pagopa/walletv2/PaymentInstrument"; -import { RawCreditCardPaymentMethod } from "../../../../../../types/pagopa"; -import { NetworkError } from "../../../../../../utils/errors"; - -/** - * Search for user's cobadge cards - */ -export const searchUserCoBadge = createAsyncAction( - "WALLET_ONBOARDING_COBADGE_SEARCH_REQUEST", - "WALLET_ONBOARDING_COBADGE_SEARCH_SUCCESS", - "WALLET_ONBOARDING_COBADGE_SEARCH_FAILURE" -)(); - -/** - * The user adds a specific cobadge card to the wallet - */ -export const addCoBadgeToWallet = createAsyncAction( - "WALLET_ONBOARDING_COBADGE_ADD_REQUEST", - "WALLET_ONBOARDING_COBADGE_ADD_SUCCESS", - "WALLET_ONBOARDING_COBADGE_ADD_FAILURE" -)(); - -/** - * Load the Abi configuration for the cobadge services (the list of abi supported and the operational state) - */ -export const loadCoBadgeAbiConfiguration = createAsyncAction( - "WALLET_ONBOARDING_COBADGE_LOAD_ABI_CONFIG_REQUEST", - "WALLET_ONBOARDING_COBADGE_LOAD_ABI_CONFIG_SUCCESS", - "WALLET_ONBOARDING_COBADGE_LOAD_ABI_CONFIG_FAILURE" -)(); - -/** - * The user chooses to start the workflow to add a new cobadge to the wallet - */ -export const walletAddCoBadgeStart = createStandardAction( - "WALLET_ONBOARDING_COBADGE_START" -)(); - -/** - * The user completes the workflow to add a new cobadge card to the wallet - * (at least one cobadge has been added) - */ -export const walletAddCoBadgeCompleted = createStandardAction( - "WALLET_ONBOARDING_COBADGE_COMPLETED" -)(); - -/** - * The user chooses to cancel the addition of a cobadge to the wallet (no cobadge has been added) - */ -export const walletAddCoBadgeCancel = createStandardAction( - "WALLET_ONBOARDING_COBADGE_CANCEL" -)(); - -/** - * The workflow fails - */ -export const walletAddCoBadgeFailure = createStandardAction( - "WALLET_ONBOARDING_COBADGE_FAILURE" -)(); - -/** - * The user chooses `back` from the first screen - */ -export const walletAddCoBadgeBack = createStandardAction( - "WALLET_ONBOARDING_COBADGE_BACK" -)(); - -export type CoBadgeActions = - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType; diff --git a/ts/features/wallet/onboarding/cobadge/store/reducers/abiConfiguration.ts b/ts/features/wallet/onboarding/cobadge/store/reducers/abiConfiguration.ts deleted file mode 100644 index 28db81fd307..00000000000 --- a/ts/features/wallet/onboarding/cobadge/store/reducers/abiConfiguration.ts +++ /dev/null @@ -1,68 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { getType } from "typesafe-actions"; -import { StatusEnum } from "../../../../../../../definitions/pagopa/cobadge/configuration/CoBadgeService"; -import { Action } from "../../../../../../store/actions/types"; -import { IndexedById } from "../../../../../../store/helpers/indexer"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { loadCoBadgeAbiConfiguration } from "../actions"; -import { NetworkError } from "../../../../../../utils/errors"; - -export type AbiConfigurationState = pot.Pot< - IndexedById, - NetworkError ->; - -const abiConfigurationReducer = ( - state: AbiConfigurationState = pot.none, - action: Action -): AbiConfigurationState => { - switch (action.type) { - case getType(loadCoBadgeAbiConfiguration.request): - return pot.toLoading(state); - case getType(loadCoBadgeAbiConfiguration.success): - // TODO: check for performance and compare with immutable-js if needed - // First layer: key: serviceid, values: abi list and activation state - const abiConfiguration = Object.keys(action.payload).reduce< - IndexedById - >((acc, val) => { - // foreach service, generate the mapping abi: state - const serviceState = action.payload[val].status; - const serviceAbiWithConfiguration = action.payload[val].issuers.reduce< - IndexedById - >( - (acc, val) => ({ - ...acc, - [val.abi]: serviceState - }), - {} - ); - // return a flat IndexedById - return { ...acc, ...serviceAbiWithConfiguration }; - }, {}); - return pot.some(abiConfiguration); - case getType(loadCoBadgeAbiConfiguration.failure): - return pot.toError(state, action.payload); - } - return state; -}; - -export const coBadgeAbiConfigurationSelector = ( - state: GlobalState -): pot.Pot, NetworkError> => - state.wallet.onboarding.coBadge.abiConfiguration; - -/** - * Return the co-badge configuration for a specific AbiId - * @param state - * @param abiId - */ -export const getCoBadgeAbiConfigurationSelector = ( - state: GlobalState, - abiId: string -): pot.Pot => - pot.map( - state.wallet.onboarding.coBadge.abiConfiguration, - abiConfigById => abiConfigById[abiId] ?? StatusEnum.disabled - ); - -export default abiConfigurationReducer; diff --git a/ts/features/wallet/onboarding/cobadge/store/reducers/abiSelected.ts b/ts/features/wallet/onboarding/cobadge/store/reducers/abiSelected.ts deleted file mode 100644 index 0228b38297a..00000000000 --- a/ts/features/wallet/onboarding/cobadge/store/reducers/abiSelected.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { getType } from "typesafe-actions"; -import { Action } from "../../../../../../store/actions/types"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { searchUserCoBadge, walletAddCoBadgeStart } from "../actions"; - -export type AbiSelected = string | null; - -const abiSelectedReducer = ( - state: AbiSelected = null, - action: Action -): AbiSelected => { - switch (action.type) { - case getType(searchUserCoBadge.request): - case getType(walletAddCoBadgeStart): - return action.payload ?? null; - } - return state; -}; - -/** - * Return the abi chosen from the user to search for their CoBadge - * @param state - */ -export const onboardingCoBadgeAbiSelectedSelector = ( - state: GlobalState -): string | undefined => - state.wallet.onboarding.coBadge.abiSelected ?? undefined; - -export default abiSelectedReducer; diff --git a/ts/features/wallet/onboarding/cobadge/store/reducers/addedCoBadge.ts b/ts/features/wallet/onboarding/cobadge/store/reducers/addedCoBadge.ts deleted file mode 100644 index 428fb9c7141..00000000000 --- a/ts/features/wallet/onboarding/cobadge/store/reducers/addedCoBadge.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { createSelector } from "reselect"; -import { getType } from "typesafe-actions"; -import { Action } from "../../../../../../store/actions/types"; -import { - CreditCardPaymentMethod, - RawCreditCardPaymentMethod -} from "../../../../../../types/pagopa"; -import { enhanceCreditCard } from "../../../../../../utils/paymentMethod"; -import { getValueOrElse } from "../../../../../../common/model/RemoteValue"; -import { abiSelector } from "../../../store/abi"; -import { addCoBadgeToWallet, walletAddCoBadgeStart } from "../actions"; - -const addedCoBadgeReducer = ( - state: ReadonlyArray = [], - action: Action -): ReadonlyArray => { - switch (action.type) { - // Register a new Cobadge added in the current onboarding session - case getType(addCoBadgeToWallet.success): - return [...state, action.payload]; - // Reset the state when starting a new Cobadge onboarding workflow - case getType(walletAddCoBadgeStart): - return []; - } - return state; -}; - -export const onboardingCoBadgeAddedSelector = createSelector( - [state => state.wallet.onboarding.coBadge.addedCoBadge, abiSelector], - (addedCoBadge, remoteAbi): ReadonlyArray => - addedCoBadge.map(p => enhanceCreditCard(p, getValueOrElse(remoteAbi, {}))) -); - -export default addedCoBadgeReducer; diff --git a/ts/features/wallet/onboarding/cobadge/store/reducers/addingCoBadge.ts b/ts/features/wallet/onboarding/cobadge/store/reducers/addingCoBadge.ts deleted file mode 100644 index 5b0c4755016..00000000000 --- a/ts/features/wallet/onboarding/cobadge/store/reducers/addingCoBadge.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { getType } from "typesafe-actions"; -import { PaymentInstrument } from "../../../../../../../definitions/pagopa/walletv2/PaymentInstrument"; -import { Action } from "../../../../../../store/actions/types"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { RawCreditCardPaymentMethod } from "../../../../../../types/pagopa"; -import { NetworkError } from "../../../../../../utils/errors"; -import { - remoteError, - remoteLoading, - remoteReady, - remoteUndefined, - RemoteValue -} from "../../../../../../common/model/RemoteValue"; -import { addCoBadgeToWallet, walletAddCoBadgeStart } from "../actions"; - -export type AddingCoBadgeState = { - addingResult: RemoteValue; - selectedCoBadge?: PaymentInstrument; -}; - -const initialState: AddingCoBadgeState = { - addingResult: remoteUndefined -}; - -const addingCoBadgeReducer = ( - state: AddingCoBadgeState = initialState, - action: Action -): AddingCoBadgeState => { - switch (action.type) { - case getType(addCoBadgeToWallet.request): - return { - selectedCoBadge: action.payload, - addingResult: remoteLoading - }; - case getType(addCoBadgeToWallet.success): - return { - ...state, - addingResult: remoteReady(action.payload) - }; - case getType(addCoBadgeToWallet.failure): - return { - ...state, - addingResult: remoteError(action.payload) - }; - case getType(walletAddCoBadgeStart): - return initialState; - } - return state; -}; - -export const onboardingCobadgeChosenSelector = ( - state: GlobalState -): PaymentInstrument | undefined => - state.wallet.onboarding.coBadge.addingCoBadge.selectedCoBadge; - -export const onboardingCobadgeAddingResultSelector = ( - state: GlobalState -): RemoteValue => - state.wallet.onboarding.coBadge.addingCoBadge.addingResult; - -export default addingCoBadgeReducer; diff --git a/ts/features/wallet/onboarding/cobadge/store/reducers/foundCoBadge.ts b/ts/features/wallet/onboarding/cobadge/store/reducers/foundCoBadge.ts deleted file mode 100644 index 8a39f221e16..00000000000 --- a/ts/features/wallet/onboarding/cobadge/store/reducers/foundCoBadge.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { getType } from "typesafe-actions"; -import { Action } from "../../../../../../store/actions/types"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { NetworkError } from "../../../../../../utils/errors"; -import { - isError, - remoteError, - remoteLoading, - remoteReady, - remoteUndefined, - RemoteValue -} from "../../../../../../common/model/RemoteValue"; -import { searchUserCoBadge } from "../actions"; -import { CobadgeResponse } from "../../../../../../../definitions/pagopa/walletv2/CobadgeResponse"; - -export type RemoteCoBadge = RemoteValue; - -const foundCoBadgeReducer = ( - state: RemoteCoBadge = remoteUndefined, - action: Action -): RemoteCoBadge => { - switch (action.type) { - case getType(searchUserCoBadge.request): - return remoteLoading; - case getType(searchUserCoBadge.success): - return remoteReady(action.payload); - case getType(searchUserCoBadge.failure): - return remoteError(action.payload); - } - return state; -}; - -/** - * Return {@link RemoteCoBadge}, a list of Cobadge cards to be viewed by the user. - * @param state - */ -export const onboardingCoBadgeFoundSelector = ( - state: GlobalState -): RemoteCoBadge => state.wallet.onboarding.coBadge.foundCoBadge; - -/** - * The search CoBadge API have an error - * @param state - */ -export const onboardingCoBadgeFoundIsError = (state: GlobalState): boolean => - isError(state.wallet.onboarding.coBadge.foundCoBadge); - -export default foundCoBadgeReducer; diff --git a/ts/features/wallet/onboarding/cobadge/store/reducers/index.ts b/ts/features/wallet/onboarding/cobadge/store/reducers/index.ts deleted file mode 100644 index fd90f633ab8..00000000000 --- a/ts/features/wallet/onboarding/cobadge/store/reducers/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Action, combineReducers } from "redux"; -import { RawCreditCardPaymentMethod } from "../../../../../../types/pagopa"; -import abiConfigurationReducer, { - AbiConfigurationState -} from "./abiConfiguration"; -import abiSelectedReducer, { AbiSelected } from "./abiSelected"; -import addedCoBadgeReducer from "./addedCoBadge"; -import addingCoBadgeReducer, { AddingCoBadgeState } from "./addingCoBadge"; -import foundCoBadgeReducer, { RemoteCoBadge } from "./foundCoBadge"; -import searchRequestIdReducer, { - SearchCoBadgeRequestIdState -} from "./searchCoBadgeRequestId"; - -export type OnboardingCoBadgeState = { - foundCoBadge: RemoteCoBadge; - addingCoBadge: AddingCoBadgeState; - addedCoBadge: ReadonlyArray; - abiSelected: AbiSelected; - abiConfiguration: AbiConfigurationState; - searchCoBadgeRequestId: SearchCoBadgeRequestIdState; -}; - -const onboardingCoBadgeReducer = combineReducers< - OnboardingCoBadgeState, - Action ->({ - // the CoBadge found for the user during the onboarding phase - foundCoBadge: foundCoBadgeReducer, - // the CoBadge that user is adding to his wallet - addingCoBadge: addingCoBadgeReducer, - // the CoBadge that user added to his wallet (during the last CoBadge onboarding workflow) - addedCoBadge: addedCoBadgeReducer, - // the bank (abi) chosen by the user during the onboarding phase. Can be null (the user skip the bank selection) - abiSelected: abiSelectedReducer, - // A list of abi for which it is allowed to use the co-badge search service - abiConfiguration: abiConfigurationReducer, - // the search id used to follow up the cobadge search - searchCoBadgeRequestId: searchRequestIdReducer -}); - -export default onboardingCoBadgeReducer; diff --git a/ts/features/wallet/onboarding/cobadge/store/reducers/searchCoBadgeRequestId.ts b/ts/features/wallet/onboarding/cobadge/store/reducers/searchCoBadgeRequestId.ts deleted file mode 100644 index 144118df554..00000000000 --- a/ts/features/wallet/onboarding/cobadge/store/reducers/searchCoBadgeRequestId.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { getType } from "typesafe-actions"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import { Action } from "../../../../../../store/actions/types"; -import { searchUserCoBadge, walletAddCoBadgeStart } from "../actions"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { CobadgeResponse } from "../../../../../../../definitions/pagopa/walletv2/CobadgeResponse"; -import { ExecutionStatusEnum } from "../../../../../../../definitions/pagopa/walletv2/SearchRequestMetadata"; - -export type SearchCoBadgeRequestIdState = string | null; - -/** - * Return true if there is at least one request pending - * @param cobadgeResponse - */ -const isCobadgeResponsePending = (cobadgeResponse: CobadgeResponse): boolean => - pipe( - cobadgeResponse.payload, - O.fromNullable, - O.chainNullableK(p => p.searchRequestMetadata), - O.map(sm => - sm.some(s => s.executionStatus === ExecutionStatusEnum.PENDING) - ), - O.getOrElse(() => false) - ); - -const reducer = ( - state: SearchCoBadgeRequestIdState = null, - action: Action -): SearchCoBadgeRequestIdState => { - switch (action.type) { - case getType(searchUserCoBadge.success): - // if response is pending then save the searchRequestId - return isCobadgeResponsePending(action.payload) - ? action.payload?.payload?.searchRequestId ?? null - : null; - // reset at the start - case getType(walletAddCoBadgeStart): - return null; - } - return state; -}; - -export const onboardingCoBadgeSearchRequestId = ( - state: GlobalState -): string | undefined => - state.wallet.onboarding.coBadge.searchCoBadgeRequestId ?? undefined; - -export default reducer; diff --git a/ts/features/wallet/onboarding/common/searchBank/SearchBankComponent.tsx b/ts/features/wallet/onboarding/common/searchBank/SearchBankComponent.tsx deleted file mode 100644 index 8837ddf3f30..00000000000 --- a/ts/features/wallet/onboarding/common/searchBank/SearchBankComponent.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import * as React from "react"; -import { - FlatList, - ListRenderItemInfo, - ActivityIndicator, - Keyboard -} from "react-native"; -import { debounce } from "lodash"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import I18n from "../../../../../i18n"; -import { Abi } from "../../../../../../definitions/pagopa/walletv2/Abi"; -import { BankPreviewItem } from "../../bancomat/components/BankPreviewItem"; -import { sortAbiByName } from "../../bancomat/utils/abi"; -import { LabelledItem } from "../../../../../components/LabelledItem"; - -type Props = { - bankList: ReadonlyArray; - isLoading: boolean; - onItemPress: (abi: string) => void; -}; - -export const SearchBankComponent: React.FunctionComponent = ( - props: Props -) => { - const [searchText, setSearchText] = React.useState(""); - const [filteredList, setFilteredList] = React.useState>( - [] - ); - const { isLoading } = props; - - const performSearch = (text: string, bankList: ReadonlyArray) => { - if (text.length === 0) { - setFilteredList([]); - return; - } - const resultList = bankList.filter( - bank => - (bank.abi && bank.abi.toLowerCase().indexOf(text.toLowerCase()) > -1) || - (bank.name && bank.name.toLowerCase().indexOf(text.toLowerCase()) > -1) - ); - setFilteredList(sortAbiByName(resultList)); - }; - const debounceRef = React.useRef(debounce(performSearch, 300)); - - React.useEffect(() => { - debounceRef.current(searchText, props.bankList); - }, [searchText, props.bankList]); - - const handleFilter = (text: string) => { - setSearchText(text); - }; - - const keyExtractor = (bank: Abi, index: number): string => - bank.abi ? bank.abi : `abi_item_${index}`; - - const renderListItem = (info: ListRenderItemInfo) => ( - { - props.onItemPress(abi); - setSearchText(""); - Keyboard.dismiss(); - }} - /> - ); - - return ( - <> - - - {isLoading ? ( - <> - - - - ) : ( - - )} - - ); -}; diff --git a/ts/features/wallet/onboarding/common/searchBank/SearchBankScreen.tsx b/ts/features/wallet/onboarding/common/searchBank/SearchBankScreen.tsx deleted file mode 100644 index c18d8f9612f..00000000000 --- a/ts/features/wallet/onboarding/common/searchBank/SearchBankScreen.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { FooterWithButtons } from "@pagopa/io-app-design-system"; -import { useFocusEffect } from "@react-navigation/native"; -import * as React from "react"; -import { useRef } from "react"; -import { SafeAreaView, View } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { - isError, - isLoading, - isUndefined -} from "../../../../../common/model/RemoteValue"; -import SectionStatusComponent from "../../../../../components/SectionStatus"; -import { IOStyles } from "../../../../../components/core/variables/IOStyles"; -import BaseScreenComponent from "../../../../../components/screens/BaseScreenComponent"; -import { fetchPagoPaTimeout } from "../../../../../config"; -import I18n from "../../../../../i18n"; -import { navigateBack } from "../../../../../store/actions/navigation"; -import { SectionStatusKey } from "../../../../../store/reducers/backendStatus"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { emptyContextualHelp } from "../../../../../utils/emptyContextualHelp"; -import { loadAbi } from "../../bancomat/store/actions"; -import { abiListSelector, abiSelector } from "../../store/abi"; -import { SearchBankComponent } from "./SearchBankComponent"; - -type MethodType = "bancomatPay" | "bancomat"; - -type Props = { - methodType: MethodType; - onItemPress: (abi?: string) => void; -} & ReturnType & - ReturnType; - -const renderFooterButtons = (onClose: () => void) => ( - -); - -const getSectionName = (methodType: MethodType): SectionStatusKey => { - switch (methodType) { - case "bancomat": - return "bancomat"; - case "bancomatPay": - return "bancomatpay"; - } -}; - -/** - * This is the component that defines the base screen where users can find and choose a specific bank - * to search for a user PagoBANCOMAT / BPay account. - * @constructor - */ -const SearchBankScreen: React.FunctionComponent = (props: Props) => { - const errorRetry = useRef(undefined); - const { bankRemoteValue, loadAbis } = props; - - React.useEffect(() => { - if (isUndefined(bankRemoteValue)) { - loadAbis(); - } else if (isError(bankRemoteValue)) { - // eslint-disable-next-line functional/immutable-data - errorRetry.current = setTimeout(loadAbis, fetchPagoPaTimeout); - } - }, [bankRemoteValue, loadAbis]); - - useFocusEffect(React.useCallback(() => clearTimeout(errorRetry.current), [])); - - return ( - - - - - - - - {renderFooterButtons(props.onBack)} - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - loadAbis: () => dispatch(loadAbi.request()), - onBack: () => navigateBack() -}); - -const mapStateToProps = (state: GlobalState) => ({ - isLoading: isLoading(abiSelector(state)), - isError: isError(abiSelector(state)), - bankList: abiListSelector(state), - bankRemoteValue: abiSelector(state) -}); - -export default connect(mapStateToProps, mapDispatchToProps)(SearchBankScreen); diff --git a/ts/features/wallet/onboarding/common/searchBank/SearchStartComponent.tsx b/ts/features/wallet/onboarding/common/searchBank/SearchStartComponent.tsx deleted file mode 100644 index c0d98f8eab6..00000000000 --- a/ts/features/wallet/onboarding/common/searchBank/SearchStartComponent.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import * as React from "react"; -import { VSpacer } from "@pagopa/io-app-design-system"; -import I18n from "../../../../../i18n"; -import { Body } from "../../../../../components/core/typography/Body"; -import { H1 } from "../../../../../components/core/typography/H1"; -import { H4 } from "../../../../../components/core/typography/H4"; -import { Link } from "../../../../../components/core/typography/Link"; -import InternationalCircuitIconsBar from "../../../../../components/wallet/InternationalCircuitIconsBar"; - -type Props = { - openTosModal: () => void; - onSearch?: () => void; - methodType: "bancomatPay" | "bancomat" | "cobadge"; - showCircuitLogo?: boolean; - bankName?: string; - openParticipatingBanksModal?: () => void; -}; - -const bancomatLocales = () => ({ - title: I18n.t("wallet.searchAbi.bancomat.title"), - text1: I18n.t("wallet.searchAbi.bancomat.description.text1"), - text2: I18n.t("wallet.searchAbi.bancomat.description.text2"), - text3: I18n.t("wallet.searchAbi.bancomat.description.text3"), - text4: I18n.t("wallet.searchAbi.bancomat.description.text4") -}); -const bancomatPayLocales = () => ({ - title: I18n.t("wallet.searchAbi.bpay.title"), - text1: I18n.t("wallet.searchAbi.bpay.description.text1"), - text2: I18n.t("wallet.searchAbi.bpay.description.text2"), - text3: I18n.t("wallet.searchAbi.bpay.description.text3"), - text4: I18n.t("wallet.searchAbi.bpay.description.text4") -}); -const cobadgeLocales = () => ({ - title: I18n.t("wallet.searchAbi.cobadge.title"), - text1: I18n.t("wallet.searchAbi.cobadge.description.text1"), - text2: "", - text3: I18n.t("wallet.searchAbi.cobadge.description.text3"), - text4: I18n.t("wallet.searchAbi.cobadge.description.text4") -}); -const loadLocales = (methodType: "bancomatPay" | "bancomat" | "cobadge") => { - switch (methodType) { - case "bancomat": - return bancomatLocales(); - case "bancomatPay": - return bancomatPayLocales(); - case "cobadge": - return cobadgeLocales(); - } -}; - -export const SearchStartComponent: React.FunctionComponent = ( - props: Props -) => { - const locales = loadLocales(props.methodType); - - return ( - <> -

{locales.title}

- {props.showCircuitLogo && ( - <> - - - - )} - - - -

- {locales.text1} -

- - {props.methodType === "cobadge" && props.bankName ? ( -

{props.bankName}

- ) : ( - - {locales.text2} - - )} - - - - -

- {locales.text3} -

- - {locales.text4} - - - - ); -}; diff --git a/ts/features/wallet/onboarding/common/searchBank/SearchStartScreen.tsx b/ts/features/wallet/onboarding/common/searchBank/SearchStartScreen.tsx deleted file mode 100644 index 4ae290d4269..00000000000 --- a/ts/features/wallet/onboarding/common/searchBank/SearchStartScreen.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { FooterWithButtons } from "@pagopa/io-app-design-system"; -import * as React from "react"; -import { SafeAreaView, View } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { isError, isLoading } from "../../../../../common/model/RemoteValue"; -import SectionStatusComponent from "../../../../../components/SectionStatus"; -import { IOStyles } from "../../../../../components/core/variables/IOStyles"; -import BaseScreenComponent from "../../../../../components/screens/BaseScreenComponent"; -import I18n from "../../../../../i18n"; -import { SectionStatusKey } from "../../../../../store/reducers/backendStatus"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { WithTestID } from "../../../../../types/WithTestID"; -import { emptyContextualHelp } from "../../../../../utils/emptyContextualHelp"; -import { loadAbi } from "../../bancomat/store/actions"; -import { abiSelector } from "../../store/abi"; -import { SearchStartComponent } from "./SearchStartComponent"; - -type MethodType = "bancomatPay" | "bancomat" | "cobadge"; - -type Props = WithTestID<{ - methodType: MethodType; - onSearch: (abi?: string) => void; - navigateToSearchBank?: () => void; - onCancel: () => void; - handleTosModal: () => void; - handleParticipatingBanksModal?: () => void; - bankName?: string; -}> & - ReturnType & - ReturnType; - -const handleMethodName = (methodType: MethodType) => { - switch (methodType) { - case "bancomatPay": - return I18n.t("wallet.methods.bancomatPay.name"); - case "bancomat": - return I18n.t("wallet.methods.pagobancomat.name"); - case "cobadge": - return I18n.t("wallet.methods.cobadge.name"); - } -}; - -const getSectionName = (methodType: MethodType): SectionStatusKey => { - switch (methodType) { - case "bancomat": - return "bancomat"; - case "bancomatPay": - return "bancomatpay"; - case "cobadge": - return "cobadge"; - } -}; - -/** - * This is the component that defines the base screen for the main screen to start the funnel - * to search for a user PagoBANCOMAT / BPay account. - * @constructor - */ -const SearchStartScreen: React.FunctionComponent = (props: Props) => { - const onContinueHandler = () => { - props.onSearch(); - }; - - return ( - - - - - - - - - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - loadAbis: () => dispatch(loadAbi.request()) -}); - -const mapStateToProps = (state: GlobalState) => ({ - isLoading: isLoading(abiSelector(state)), - isError: isError(abiSelector(state)) -}); - -export default connect(mapStateToProps, mapDispatchToProps)(SearchStartScreen); diff --git a/ts/features/wallet/onboarding/paypal/analytics/index.ts b/ts/features/wallet/onboarding/paypal/analytics/index.ts deleted file mode 100644 index 902693cb50e..00000000000 --- a/ts/features/wallet/onboarding/paypal/analytics/index.ts +++ /dev/null @@ -1,54 +0,0 @@ -import * as O from "fp-ts/lib/Option"; -import { getType } from "typesafe-actions"; -import { mixpanel } from "../../../../../mixpanel"; -import { Action } from "../../../../../store/actions/types"; -import { getNetworkErrorMessage } from "../../../../../utils/errors"; -import { - searchPaypalPsp, - walletAddPaypalBack, - walletAddPaypalCancel, - walletAddPaypalCompleted, - walletAddPaypalFailure, - walletAddPaypalOutcome, - walletAddPaypalPspSelected, - walletAddPaypalRefreshPMToken, - walletAddPaypalStart -} from "../store/actions"; - -const trackPaypalOnboarding = - (mp: NonNullable) => - (action: Action): void => { - switch (action.type) { - case getType(walletAddPaypalFailure): - case getType(walletAddPaypalCancel): - case getType(walletAddPaypalBack): - case getType(walletAddPaypalCompleted): - case getType(walletAddPaypalStart): - case getType(walletAddPaypalRefreshPMToken.request): - case getType(walletAddPaypalRefreshPMToken.success): - case getType(searchPaypalPsp.request): - return mp.track(action.type); - case getType(walletAddPaypalPspSelected): - return mp.track(action.type, { - psp: action.payload.name - }); - case getType(walletAddPaypalOutcome): - return mp.track(action.type, { - outcome: O.toUndefined(action.payload) - }); - case getType(searchPaypalPsp.success): - return mp.track(action.type, { - count: action.payload.length - }); - case getType(walletAddPaypalRefreshPMToken.failure): - return mp.track(action.type, { - reason: action.payload.message - }); - case getType(searchPaypalPsp.failure): - return mp.track(action.type, { - reason: getNetworkErrorMessage(action.payload) - }); - } - }; - -export default trackPaypalOnboarding; diff --git a/ts/features/wallet/onboarding/paypal/components/PspInfoBottomSheet.tsx b/ts/features/wallet/onboarding/paypal/components/PspInfoBottomSheet.tsx deleted file mode 100644 index 258d410b080..00000000000 --- a/ts/features/wallet/onboarding/paypal/components/PspInfoBottomSheet.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { TouchableWithoutFeedback } from "@gorhom/bottom-sheet"; -import { Icon, VSpacer } from "@pagopa/io-app-design-system"; -import { NonNegativeNumber } from "@pagopa/ts-commons/lib/numbers"; -import * as React from "react"; -import { ReactNode } from "react"; -import { StyleSheet, View } from "react-native"; -import { Body } from "../../../../../components/core/typography/Body"; -import { Label } from "../../../../../components/core/typography/Label"; -import { Link } from "../../../../../components/core/typography/Link"; -import { IOStyles } from "../../../../../components/core/variables/IOStyles"; -import I18n from "../../../../../i18n"; -import { formatNumberCentsToAmount } from "../../../../../utils/stringBuilder"; -import { openWebUrl } from "../../../../../utils/url"; - -const styles = StyleSheet.create({ - rowContainer: { - flex: 1, - flexDirection: "row", - marginTop: 12 - }, - rowIcon: { marginTop: 6, marginRight: 16 } -}); - -type Props = { - pspName: string; - pspFee: NonNegativeNumber; - pspPrivacyUrl?: string; -}; - -const iconSize = 24; -// items to be displayed inside the bottom sheet content -// icon and description -const getItem = (props: Props) => [ - { - icon: , - description: ( - - {I18n.t( - "wallet.onboarding.paypal.selectPsp.infoBottomSheet.row1Description" - )} - - ) - }, - { - icon: , - description: ( - - {I18n.t( - "wallet.onboarding.paypal.selectPsp.infoBottomSheet.row2Description1" - )} - - {I18n.t( - "wallet.onboarding.paypal.selectPsp.infoBottomSheet.row2Description2" - )} - - ) - }, - { - icon: , - description: ( - - props.pspPrivacyUrl && openWebUrl(props.pspPrivacyUrl)} - > - - {I18n.t( - "wallet.onboarding.paypal.selectPsp.infoBottomSheet.row3Description1", - { psp: props.pspName } - )} - - - - ) - } -]; - -/** - * row layout - * icon + textual body - * @param props - * @constructor - */ -const ItemLayout = (props: { icon: JSX.Element; description: ReactNode }) => ( - - {props.icon} - {props.description} - -); - -/** - * bottom sheet content used to show info about PayPal psp - * @param props - * @constructor - */ -export const PspInfoBottomSheetContent = (props: Props) => ( - - {getItem(props).map((item, idx) => ( - - ))} - - -); diff --git a/ts/features/wallet/onboarding/paypal/components/PspRadioItem.tsx b/ts/features/wallet/onboarding/paypal/components/PspRadioItem.tsx deleted file mode 100644 index 605492f45f1..00000000000 --- a/ts/features/wallet/onboarding/paypal/components/PspRadioItem.tsx +++ /dev/null @@ -1,131 +0,0 @@ -// component that represents the item in the radio list -import { - ButtonSolid, - ContentWrapper, - Icon, - VSpacer -} from "@pagopa/io-app-design-system"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import React from "react"; -import { Dimensions, Image, StyleSheet, View } from "react-native"; -import TouchableDefaultOpacity from "../../../../../components/TouchableDefaultOpacity"; -import { H4 } from "../../../../../components/core/typography/H4"; -import I18n from "../../../../../i18n"; -import { TestID } from "../../../../../types/WithTestID"; -import { useIOBottomSheetAutoresizableModal } from "../../../../../utils/hooks/bottomSheet"; -import { useImageResize } from "../../bancomat/hooks/useImageResize"; -import { IOPayPalPsp } from "../types"; -import { PspInfoBottomSheetContent } from "./PspInfoBottomSheet"; - -export const PSP_LOGO_MAX_WIDTH = Dimensions.get("window").width; -export const PSP_LOGO_MAX_HEIGHT = 32; -type RadioItemProps = { - psp: IOPayPalPsp; -} & TestID; - -const styles = StyleSheet.create({ - pspLogo: { - height: 32, - flex: 1, - flexDirection: "row", - justifyContent: "flex-start" - }, - radioItemBody: { - alignItems: "flex-start", - justifyContent: "space-between", - flexDirection: "row" - }, - radioItemRight: { - flex: 1, - flexDirection: "column", - alignItems: "flex-end" - } -}); - -/** - * item used in a radio list - * it represents a PayPal psp with logo and info icon - * @param props - * @constructor - */ -export const PspRadioItem = ( - props: RadioItemProps -): React.ReactElement | null => { - const { psp } = props; - const imgDimensions = useImageResize( - PSP_LOGO_MAX_WIDTH, - PSP_LOGO_MAX_HEIGHT, - psp.logoUrl - ); - - const { present, bottomSheet, dismiss } = useIOBottomSheetAutoresizableModal( - { - title: I18n.t( - "wallet.onboarding.paypal.selectPsp.infoBottomSheet.title", - { - pspName: psp.name - } - ), - component: ( - - ), - footer: ( - - dismiss()} - label={I18n.t( - "wallet.onboarding.paypal.selectPsp.infoBottomSheet.ctaTitle" - )} - accessibilityLabel={I18n.t( - "wallet.onboarding.paypal.selectPsp.infoBottomSheet.ctaTitle" - )} - fullWidth={true} - /> - - - ) - }, - 130 - ); - - return ( - - {/* show the psp name while its image is loading */} - {pipe( - imgDimensions, - O.fold( - () => ( -

- {psp.name} -

- ), - imgDim => ( - - ) - ) - )} - - - - - - {bottomSheet} -
- ); -}; diff --git a/ts/features/wallet/onboarding/paypal/navigation/navigator.tsx b/ts/features/wallet/onboarding/paypal/navigation/navigator.tsx deleted file mode 100644 index 8c4c07a231d..00000000000 --- a/ts/features/wallet/onboarding/paypal/navigation/navigator.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import * as React from "react"; -import { createStackNavigator } from "@react-navigation/stack"; -import PayPalOnboardingCheckoutCompletedScreen from "../screen/PayPalOnboardingCheckoutCompletedScreen"; -import PayPalOnboardingCheckoutScreen from "../screen/PayPalOnboardingCheckoutScreen"; -import PayPalPspSelectionScreen from "../screen/PayPalPspSelectionScreen"; -import PayPalStartOnboardingScreen from "../screen/PayPalStartOnboardingScreen"; -import { isGestureEnabled } from "../../../../../utils/navigation"; -import PAYPAL_ROUTES from "./routes"; -import { PaymentMethodOnboardingPayPalParamsList } from "./params"; - -const Stack = createStackNavigator(); - -export const PaymentMethodOnboardingPayPalOnboardingNavigator = () => ( - - - - - - -); diff --git a/ts/features/wallet/onboarding/paypal/navigation/params.ts b/ts/features/wallet/onboarding/paypal/navigation/params.ts deleted file mode 100644 index ef6abd3c3bd..00000000000 --- a/ts/features/wallet/onboarding/paypal/navigation/params.ts +++ /dev/null @@ -1,9 +0,0 @@ -import PAYPAL_ROUTES from "./routes"; - -export type PaymentMethodOnboardingPayPalParamsList = { - [PAYPAL_ROUTES.ONBOARDING.MAIN]: undefined; - [PAYPAL_ROUTES.ONBOARDING.START]: undefined; - [PAYPAL_ROUTES.ONBOARDING.SEARCH_PSP]: undefined; - [PAYPAL_ROUTES.ONBOARDING.CHECKOUT]: undefined; - [PAYPAL_ROUTES.ONBOARDING.CHECKOUT_COMPLETED]: undefined; -}; diff --git a/ts/features/wallet/onboarding/paypal/navigation/routes.ts b/ts/features/wallet/onboarding/paypal/navigation/routes.ts deleted file mode 100644 index d6c0950f6e9..00000000000 --- a/ts/features/wallet/onboarding/paypal/navigation/routes.ts +++ /dev/null @@ -1,11 +0,0 @@ -const PAYPAL_ROUTES = { - ONBOARDING: { - MAIN: "WALLET_ONBOARDING_PAYPAL_MAIN", - START: "WALLET_ONBOARDING_PAYPAL_START", - SEARCH_PSP: "WALLET_ONBOARDING_PAYPAL_SEARCH_PSP", - CHECKOUT: "WALLET_ONBOARDING_PAYPAL_CHECKOUT", - CHECKOUT_COMPLETED: "WALLET_ONBOARDING_PAYPAL_CHECKOUT_COMPLETED" - } -} as const; - -export default PAYPAL_ROUTES; diff --git a/ts/features/wallet/onboarding/paypal/saga/index.ts b/ts/features/wallet/onboarding/paypal/saga/index.ts deleted file mode 100644 index ac3cdebab8e..00000000000 --- a/ts/features/wallet/onboarding/paypal/saga/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { takeLatest } from "typed-redux-saga/macro"; -import { PaymentManagerClient } from "../../../../../api/pagopa"; -import { SessionManager } from "../../../../../utils/SessionManager"; -import { PaymentManagerToken } from "../../../../../types/pagopa"; -import { - searchPaypalPsp, - walletAddPaypalRefreshPMToken, - walletAddPaypalStart -} from "../store/actions"; -import { handlePaypalSearchPsp, refreshPMToken } from "./networking"; -import { addPaypalToWallet } from "./orchestration"; - -// watch for all actions regarding paypal -export function* watchPaypalOnboardingSaga( - pmClient: PaymentManagerClient, - sessionManager: SessionManager -) { - // search for paypal psp - yield* takeLatest( - searchPaypalPsp.request, - handlePaypalSearchPsp, - pmClient.searchPayPalPsp, - sessionManager - ); - - // start paypal onboarding - yield* takeLatest(walletAddPaypalStart, addPaypalToWallet); - - // refresh PM token before checkout - yield* takeLatest( - walletAddPaypalRefreshPMToken.request, - refreshPMToken, - sessionManager - ); -} diff --git a/ts/features/wallet/onboarding/paypal/saga/networking/index.ts b/ts/features/wallet/onboarding/paypal/saga/networking/index.ts deleted file mode 100644 index 20265170fc0..00000000000 --- a/ts/features/wallet/onboarding/paypal/saga/networking/index.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { call, put } from "typed-redux-saga/macro"; -import * as O from "fp-ts/lib/Option"; -import * as E from "fp-ts/lib/Either"; -import { PaymentManagerClient } from "../../../../../../api/pagopa"; -import { SessionManager } from "../../../../../../utils/SessionManager"; -import { PaymentManagerToken } from "../../../../../../types/pagopa"; -import { SagaCallReturnType } from "../../../../../../types/utils"; -import { - searchPaypalPsp, - walletAddPaypalRefreshPMToken -} from "../../store/actions"; -import { - getError, - getGenericError, - getNetworkError -} from "../../../../../../utils/errors"; -import { readablePrivacyReport } from "../../../../../../utils/reporters"; -import { convertPayPalPsp } from "../../store/transformers"; - -/** - * handle the request of searching for PayPal psp - * @param searchPsp - * @param sessionManager - */ -export function* handlePaypalSearchPsp( - searchPsp: PaymentManagerClient["searchPayPalPsp"], - sessionManager: SessionManager -) { - try { - const searchPayPalPspRequest: SagaCallReturnType = - yield* call(sessionManager.withRefresh(searchPsp)); - if (E.isRight(searchPayPalPspRequest)) { - if (searchPayPalPspRequest.right.status === 200) { - yield* put( - searchPaypalPsp.success( - searchPayPalPspRequest.right.value.data.map(convertPayPalPsp) - ) - ); - return; - } - // != 200 - yield* put( - searchPaypalPsp.failure( - getGenericError( - new Error(`response status ${searchPayPalPspRequest.right.status}`) - ) - ) - ); - } else { - yield* put( - searchPaypalPsp.failure( - getGenericError( - new Error(readablePrivacyReport(searchPayPalPspRequest.left)) - ) - ) - ); - } - } catch (e) { - yield* put(searchPaypalPsp.failure(getNetworkError(e))); - } -} - -// refresh PM token (it is needed in the webview) to ensure it won't expire during the checkout process -export function* refreshPMToken( - sessionManager: SessionManager -) { - try { - // If the request for the new token fails a new Error is raised - const pagoPaToken: O.Option = yield* call( - sessionManager.getNewToken - ); - if (O.isSome(pagoPaToken)) { - yield* put(walletAddPaypalRefreshPMToken.success(pagoPaToken.value)); - } else { - yield* put( - walletAddPaypalRefreshPMToken.failure( - new Error("cant load pm session token") - ) - ); - } - } catch (e) { - yield* put(walletAddPaypalRefreshPMToken.failure(getError(e))); - } -} diff --git a/ts/features/wallet/onboarding/paypal/saga/orchestration/index.ts b/ts/features/wallet/onboarding/paypal/saga/orchestration/index.ts deleted file mode 100644 index 3098a3ab9ad..00000000000 --- a/ts/features/wallet/onboarding/paypal/saga/orchestration/index.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { CommonActions, StackActions } from "@react-navigation/native"; -import { call } from "typed-redux-saga/macro"; -import { ActionType } from "typesafe-actions"; -import NavigationService from "../../../../../../navigation/NavigationService"; -import ROUTES from "../../../../../../navigation/routes"; -import { - executeWorkUnit, - withResetNavigationStack, - WorkUnitHandler -} from "../../../../../../sagas/workUnit"; -import { navigateToPayPalDetailScreen } from "../../../../../../store/actions/navigation"; -import PAYPAL_ROUTES from "../../navigation/routes"; -import { - walletAddPaypalBack, - walletAddPaypalCancel, - walletAddPaypalCompleted, - walletAddPaypalFailure, - walletAddPaypalStart -} from "../../store/actions"; - -// handle the flow of paypal onboarding -function* paypalWorkOnboaringUnit() { - return yield* call(executeWorkUnit, { - startScreenNavigation: () => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: PAYPAL_ROUTES.ONBOARDING.MAIN, - params: { - screen: PAYPAL_ROUTES.ONBOARDING.START - } - }) - ), - startScreenName: PAYPAL_ROUTES.ONBOARDING.START, - complete: walletAddPaypalCompleted, - back: walletAddPaypalBack, - cancel: walletAddPaypalCancel, - failure: walletAddPaypalFailure - }); -} - -export function* addPaypalToWallet( - action: ActionType -) { - const res = yield* call( - withResetNavigationStack, - paypalWorkOnboaringUnit - ); - - // onboarding gone successfully, go to the paypal screen detail - if (res === "completed") { - switch (action.payload) { - case "payment_method_details": - // reset the stack to land in the wallet section - yield* call( - NavigationService.dispatchNavigationAction, - StackActions.popToTop() - ); - yield* call( - NavigationService.dispatchNavigationAction, - navigateToPayPalDetailScreen() - ); - break; - // if the destination is to return back, remove 1 screen from the stack - case "back": - yield* call( - NavigationService.dispatchNavigationAction, - StackActions.pop(1) - ); - break; - } - } -} diff --git a/ts/features/wallet/onboarding/paypal/screen/PayPalOnboardingCheckoutCompletedScreen.tsx b/ts/features/wallet/onboarding/paypal/screen/PayPalOnboardingCheckoutCompletedScreen.tsx deleted file mode 100644 index 5b5bf93aba5..00000000000 --- a/ts/features/wallet/onboarding/paypal/screen/PayPalOnboardingCheckoutCompletedScreen.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as O from "fp-ts/lib/Option"; -import React, { useCallback, useEffect } from "react"; -import WorkunitGenericFailure from "../../../../../components/error/WorkunitGenericFailure"; -import OutcomeCodeMessageComponent from "../../../../../components/wallet/OutcomeCodeMessageComponent"; -import I18n from "../../../../../i18n"; -import { fetchWalletsRequestWithExpBackoff } from "../../../../../store/actions/wallet/wallets"; -import { useIODispatch, useIOSelector } from "../../../../../store/hooks"; -import { extractOutcomeCode } from "../../../../../store/reducers/wallet/outcomeCode"; -import { paypalSelector } from "../../../../../store/reducers/wallet/wallets"; -import { LoadingErrorComponent } from "../../../../../components/LoadingErrorComponent"; -import { walletAddPaypalFailure } from "../store/actions"; -import { paypalOnboardingOutcomeCodeSelector } from "../store/reducers/onOboardingCompleted"; -import PayPalOnboardingCompletedSuccessComponent from "./PayPalOnboardingCompletedSuccessComponent"; - -/** - * The paypal onboarding is completed - * This screen show a success or a failure based on the outcome code obtained in the previous step - * @constructor - */ -const PayPalOnboardingCheckoutCompletedScreen = () => - // TODO review the error code messages see https://pagopa.atlassian.net/browse/IA-484 - { - const dispatch = useIODispatch(); - const paypalOutcomeCode = useIOSelector( - paypalOnboardingOutcomeCodeSelector - ); - const paypalPaymentMethod = useIOSelector(paypalSelector); - const loadWallets = useCallback(() => { - dispatch(fetchWalletsRequestWithExpBackoff()); - }, [dispatch]); - // refresh wallet to load the added paypal method - useEffect(() => { - loadWallets(); - }, [loadWallets]); - - // this should not happen (we can say nothing about the error) - if (paypalOutcomeCode === undefined) { - return ; - } - const outcomeCode = extractOutcomeCode(O.some(paypalOutcomeCode)); - // it should never happen (the outcome code is not recognized as valid) - if (O.isNone(outcomeCode)) { - return ; - } - // show a loading or error component to handle the wallet reload - if ( - pot.isLoading(paypalPaymentMethod) || - pot.isError(paypalPaymentMethod) - ) { - return ( - - ); - } - return ( - } - onClose={() => dispatch(walletAddPaypalFailure())} - /> - ); - }; - -export default PayPalOnboardingCheckoutCompletedScreen; diff --git a/ts/features/wallet/onboarding/paypal/screen/PayPalOnboardingCheckoutScreen.tsx b/ts/features/wallet/onboarding/paypal/screen/PayPalOnboardingCheckoutScreen.tsx deleted file mode 100644 index ff20f2acdc1..00000000000 --- a/ts/features/wallet/onboarding/paypal/screen/PayPalOnboardingCheckoutScreen.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import * as O from "fp-ts/lib/Option"; -import React, { useEffect, useState } from "react"; -import { Alert } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import WorkunitGenericFailure from "../../../../../components/error/WorkunitGenericFailure"; -import BaseScreenComponent from "../../../../../components/screens/BaseScreenComponent"; -import { PayWebViewModal } from "../../../../../components/wallet/PayWebViewModal"; -import { - pagoPaApiUrlPrefix, - pagoPaApiUrlPrefixTest -} from "../../../../../config"; -import I18n from "../../../../../i18n"; -import { isPagoPATestEnabledSelector } from "../../../../../store/reducers/persistedPreferences"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { pmSessionTokenSelector } from "../../../../../store/reducers/wallet/payment"; -import { emptyContextualHelp } from "../../../../../utils/emptyContextualHelp"; -import { getLocalePrimaryWithFallback } from "../../../../../utils/locale"; -import { getLookUpIdPO } from "../../../../../utils/pmLookUpId"; -import { LoadingErrorComponent } from "../../../../../components/LoadingErrorComponent"; -import { fold } from "../../../../../common/model/RemoteValue"; -import { - walletAddPaypalBack, - walletAddPaypalOutcome, - walletAddPaypalRefreshPMToken -} from "../store/actions"; -import { paypalOnboardingSelectedPsp } from "../store/reducers/selectedPsp"; -import PAYPAL_ROUTES from "../navigation/routes"; -import { useIONavigation } from "../../../../../navigation/params/AppParamsList"; -import ROUTES from "../../../../../navigation/routes"; - -type Props = ReturnType & - ReturnType; - -const payUrlSuffix = "/v3/webview/paypal/onboarding/psp"; -const webViewExitPathName = "/v3/webview/logout/bye"; -const webViewOutcomeParamName = "outcome"; - -const LoadingOrError = (loadingProps: { - hasError: boolean; - onRetry: () => void; -}) => ( - -); - -const CheckoutContent = ( - props: Props & { - onCheckoutCompleted: (outcode: O.Option) => void; - onGoBack: () => void; - } -) => { - const [isOnboardingCompleted, setIsOnboardingComplete] = useState(false); - const urlPrefix = props.isPagoPATestEnabled - ? pagoPaApiUrlPrefixTest - : pagoPaApiUrlPrefix; - return fold( - props.pmToken, - () => , - () => , - sessionToken => { - // it should not never happen since this screen is just after the psp selection - if (props.pspSelected === null) { - return ; - } - // we have all we need to starts the checkout into the webview - const formData = { - idPsp: props.pspSelected.id, - language: getLocalePrimaryWithFallback(), - sessionToken, - ...getLookUpIdPO() - }; - return ( - { - setIsOnboardingComplete(true); - props.onCheckoutCompleted(outcomeCode); - }} - outcomeQueryparamName={webViewOutcomeParamName} - onGoBack={props.onGoBack} - modalHeaderTitle={I18n.t("wallet.onboarding.paypal.headerTitle")} - /> - ); - }, - _ => - ); -}; - -/** - * This screen includes a webview where the paypal checkout happens. This flow is external to IO, it happens in the Payment Manager - * As first step it asks for a fresh token from the Payment Manager, it will be included in the webview - * 1. request for a fresh PM token - * 2. when the PM token is obtained, starts the checkout challenge in the webview - * 3. handle the outcome code coming from the step 2 - * 4. navigate to the checkout completed screen - */ -const PayPalOnboardingCheckoutScreen = (props: Props) => { - const navigation = useIONavigation(); - const { refreshPMtoken } = props; - // refresh the PM at the startup - useEffect(() => { - refreshPMtoken(); - }, [refreshPMtoken]); - - const handleCheckoutCompleted = (outcomeCode: O.Option) => { - props.setOutcomeCode(outcomeCode); - navigation.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: PAYPAL_ROUTES.ONBOARDING.MAIN, - params: { - screen: PAYPAL_ROUTES.ONBOARDING.CHECKOUT_COMPLETED - } - }); - }; - - // notify the user that the current onboarding operation will be interrupted - const handleBack = () => { - Alert.alert(I18n.t("wallet.abortWebView.title"), "", [ - { - text: I18n.t("wallet.abortWebView.confirm"), - onPress: props.goBack, - style: "cancel" - }, - { - text: I18n.t("wallet.abortWebView.cancel") - } - ]); - }; - - return ( - - - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - goBack: () => dispatch(walletAddPaypalBack()), - setOutcomeCode: (oc: O.Option) => - dispatch(walletAddPaypalOutcome(oc)), - refreshPMtoken: () => dispatch(walletAddPaypalRefreshPMToken.request()) -}); -const mapStateToProps = (state: GlobalState) => ({ - pmToken: pmSessionTokenSelector(state), - isPagoPATestEnabled: isPagoPATestEnabledSelector(state), - pspSelected: paypalOnboardingSelectedPsp(state) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(PayPalOnboardingCheckoutScreen); diff --git a/ts/features/wallet/onboarding/paypal/screen/PayPalOnboardingCompletedSuccessComponent.tsx b/ts/features/wallet/onboarding/paypal/screen/PayPalOnboardingCompletedSuccessComponent.tsx deleted file mode 100644 index 5e21709129d..00000000000 --- a/ts/features/wallet/onboarding/paypal/screen/PayPalOnboardingCompletedSuccessComponent.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { FooterWithButtons } from "@pagopa/io-app-design-system"; -import React from "react"; -import successImage from "../../../../../../img/pictograms/payment-completed.png"; -import { InfoScreenComponent } from "../../../../../components/infoScreen/InfoScreenComponent"; -import { renderInfoRasterImage } from "../../../../../components/infoScreen/imageRendering"; -import I18n from "../../../../../i18n"; -import { useIODispatch, useIOSelector } from "../../../../../store/hooks"; -import { walletAddPaypalCompleted } from "../store/actions"; -import { paypalOnboardingCompletedSelector } from "../store/reducers/onOboardingCompleted"; - -const getLocales = (isPaymentOnGoing: boolean) => { - if (isPaymentOnGoing) { - return { - title: I18n.t( - "wallet.onboarding.paypal.onBoardingCompletedWhilePayment.title" - ), - body: I18n.t( - "wallet.onboarding.paypal.onBoardingCompletedWhilePayment.body" - ), - ctaTitle: I18n.t( - "wallet.onboarding.paypal.onBoardingCompletedWhilePayment.primaryButton" - ) - }; - } - return { - title: I18n.t("wallet.onboarding.paypal.onBoardingCompleted.title"), - body: I18n.t("wallet.onboarding.paypal.onBoardingCompleted.body"), - ctaTitle: I18n.t( - "wallet.onboarding.paypal.onBoardingCompleted.primaryButton" - ) - }; -}; - -/** - * this screen shows that the onboarding is completed successfully - * footer button navigates to the PayPal method details - * @deprecated Replace the screen content with `OperationResultScreen` instead - * @constructor - */ -const PayPalOnboardingCompletedSuccessComponent = () => { - const dispatch = useIODispatch(); - const onPaypalCompletion = useIOSelector(paypalOnboardingCompletedSelector); - // 'payment_method_details' means we want to check the added payment method detail - // in the payment flow we have to continue the payment - const locales = getLocales(onPaypalCompletion !== "payment_method_details"); - return ( - <> - - dispatch(walletAddPaypalCompleted()), - testID: "primaryButtonId" - } - }} - /> - - ); -}; - -export default PayPalOnboardingCompletedSuccessComponent; diff --git a/ts/features/wallet/onboarding/paypal/screen/PayPalPspSelectionScreen.tsx b/ts/features/wallet/onboarding/paypal/screen/PayPalPspSelectionScreen.tsx deleted file mode 100644 index dc5dc40bc78..00000000000 --- a/ts/features/wallet/onboarding/paypal/screen/PayPalPspSelectionScreen.tsx +++ /dev/null @@ -1,227 +0,0 @@ -import { FooterWithButtons, VSpacer } from "@pagopa/io-app-design-system"; -import React, { useEffect, useState } from "react"; -import { SafeAreaView, ScrollView, StyleSheet, View } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { - getValueOrElse, - isError, - isReady -} from "../../../../../common/model/RemoteValue"; -import { LoadingErrorComponent } from "../../../../../components/LoadingErrorComponent"; -import { - RadioButtonList, - RadioItem -} from "../../../../../components/core/selection/RadioButtonList"; -import { Body } from "../../../../../components/core/typography/Body"; -import { H1 } from "../../../../../components/core/typography/H1"; -import { H4 } from "../../../../../components/core/typography/H4"; -import { Link } from "../../../../../components/core/typography/Link"; -import { IOStyles } from "../../../../../components/core/variables/IOStyles"; -import BaseScreenComponent from "../../../../../components/screens/BaseScreenComponent"; -import I18n from "../../../../../i18n"; -import { useIONavigation } from "../../../../../navigation/params/AppParamsList"; -import ROUTES from "../../../../../navigation/routes"; -import { useIODispatch } from "../../../../../store/hooks"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { emptyContextualHelp } from "../../../../../utils/emptyContextualHelp"; -import { useIOBottomSheetAutoresizableModal } from "../../../../../utils/hooks/bottomSheet"; -import { PspRadioItem } from "../components/PspRadioItem"; -import PAYPAL_ROUTES from "../navigation/routes"; -import { - searchPaypalPsp as searchPaypalPspAction, - walletAddPaypalBack, - walletAddPaypalCancel, - walletAddPaypalPspSelected -} from "../store/actions"; -import { payPalPspSelector } from "../store/reducers/searchPsp"; -import { IOPayPalPsp } from "../types"; - -type Props = ReturnType & - ReturnType; - -const styles = StyleSheet.create({ - radioListHeaderRightColumn: { - flex: 1, - textAlign: "right" - } -}); - -// an header over the psp list with 2 columns -const RadioListHeader = (props: { - leftColumnTitle: string; - rightColumnTitle: string; -}) => { - const color = "bluegrey"; - const weight = "Regular"; - return ( - -

- {props.leftColumnTitle} -

-

- {props.rightColumnTitle} -

-
- ); -}; - -const getPspListRadioItems = ( - pspList: ReadonlyArray -): ReadonlyArray> => - pspList.map(psp => ({ - id: psp.id, - body: - })); - -const getLocales = () => ({ - title: I18n.t("wallet.onboarding.paypal.selectPsp.title"), - body: I18n.t("wallet.onboarding.paypal.selectPsp.body"), - link: I18n.t("wallet.onboarding.paypal.selectPsp.link"), - leftColumnTitle: I18n.t("wallet.onboarding.paypal.selectPsp.leftColumnTitle"), - rightColumnTitle: I18n.t( - "wallet.onboarding.paypal.selectPsp.rightColumnTitle" - ), - whatIsPspBody: I18n.t( - "wallet.onboarding.paypal.selectPsp.whatIsPspBottomSheet.body" - ), - whatIsPspTitle: I18n.t( - "wallet.onboarding.paypal.selectPsp.whatIsPspBottomSheet.title" - ) -}); - -/** - * This screen is where the user picks a PSP that will be used to handle PayPal transactions within PayPal - * Only 1 psp can be selected - */ -const PayPalPspSelectionScreen = (props: Props): React.ReactElement | null => { - const locales = getLocales(); - const { present: presentWhatIsPspBottomSheet, bottomSheet } = - useIOBottomSheetAutoresizableModal({ - title: locales.whatIsPspTitle, - component: ( - - {locales.whatIsPspBody} - - - ) - }); - const pspList = getValueOrElse(props.pspList, []); - const [selectedPsp, setSelectedPsp] = useState(); - const dispatch = useIODispatch(); - const navigation = useIONavigation(); - const searchPaypalPsp = () => { - dispatch(searchPaypalPspAction.request()); - }; - useEffect(searchPaypalPsp, [dispatch]); - useEffect(() => { - // auto select if the psp list has 1 element - setSelectedPsp(pspList.length === 1 ? pspList[0] : undefined); - }, [pspList]); - - return ( - - {isReady(props.pspList) ? ( - <> - - - -

{locales.title}

- - - {locales.body} - - {locales.link} - - - - - - key="paypal_psp_selection" - items={getPspListRadioItems(pspList)} - selectedItem={selectedPsp?.id} - onPress={(idPsp: string) => { - setSelectedPsp(pspList.find(p => p.id === idPsp)); - }} - /> - -
- - {bottomSheet} -
- { - if (selectedPsp) { - props.setPspSelected(selectedPsp); - navigation.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: PAYPAL_ROUTES.ONBOARDING.MAIN, - params: { - screen: PAYPAL_ROUTES.ONBOARDING.CHECKOUT - } - }); - } - }, - disabled: selectedPsp === undefined, - testID: "continueButtonId" - } - }} - /> - - ) : ( - - )} -
- ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - goBack: () => dispatch(walletAddPaypalBack()), - cancel: () => dispatch(walletAddPaypalCancel()), - setPspSelected: (psp: IOPayPalPsp) => - dispatch(walletAddPaypalPspSelected(psp)) -}); -const mapStateToProps = (state: GlobalState) => ({ - pspList: payPalPspSelector(state) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(PayPalPspSelectionScreen); diff --git a/ts/features/wallet/onboarding/paypal/screen/PayPalStartOnboardingScreen.tsx b/ts/features/wallet/onboarding/paypal/screen/PayPalStartOnboardingScreen.tsx deleted file mode 100644 index 75372e1225b..00000000000 --- a/ts/features/wallet/onboarding/paypal/screen/PayPalStartOnboardingScreen.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import { FooterWithButtons } from "@pagopa/io-app-design-system"; -import React from "react"; -import { Dimensions, SafeAreaView, View } from "react-native"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import Oval from "../../../../../../img/wallet/payment-methods/paypal/oval.svg"; -import PPLogo from "../../../../../../img/wallet/payment-methods/paypal/paypal_logo.svg"; -import SectionStatusComponent from "../../../../../components/SectionStatus"; -import { Body } from "../../../../../components/core/typography/Body"; -import { IOStyles } from "../../../../../components/core/variables/IOStyles"; -import { InfoScreenComponent } from "../../../../../components/infoScreen/InfoScreenComponent"; -import BaseScreenComponent from "../../../../../components/screens/BaseScreenComponent"; -import I18n from "../../../../../i18n"; -import { useIONavigation } from "../../../../../navigation/params/AppParamsList"; -import ROUTES from "../../../../../navigation/routes"; -import { GlobalState } from "../../../../../store/reducers/types"; -import { emptyContextualHelp } from "../../../../../utils/emptyContextualHelp"; -import PAYPAL_ROUTES from "../navigation/routes"; -import { walletAddPaypalBack, walletAddPaypalCancel } from "../store/actions"; - -type Props = ReturnType & - ReturnType; - -/** - * logo should fill the screen width for 206 at maximum - * otherwise it will take the 60% of the screen width - * this is to avoid a too big logo on narrow screens - */ -const ovalWith = Math.min(206, Dimensions.get("window").width * 0.6); -const logoWidth = ovalWith * 0.4; -// an oval background with PP logo on it, at the center -const PayPalLogo = () => ( - - - - -); - -/** - * This screen is the start point to onboard PayPal as payment method - * It shows the PP logo and some texts - * At the bottom 2 CTA to cancel or continue - */ -const PayPalStartOnboardingScreen = ( - props: Props -): React.ReactElement | null => { - const navigationContext = useIONavigation(); - - const navigateToSearchPsp = () => - navigationContext.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: PAYPAL_ROUTES.ONBOARDING.MAIN, - params: { - screen: PAYPAL_ROUTES.ONBOARDING.SEARCH_PSP - } - }); - - return ( - - - } - title={I18n.t("wallet.onboarding.paypal.start.title")} - body={ - - {I18n.t("wallet.onboarding.paypal.start.body")} - - } - /> - - - - - ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - goBack: () => dispatch(walletAddPaypalBack()), - cancel: () => dispatch(walletAddPaypalCancel()) -}); -const mapStateToProps = (_: GlobalState) => ({}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(PayPalStartOnboardingScreen); diff --git a/ts/features/wallet/onboarding/paypal/screen/__mocks__/psp.ts b/ts/features/wallet/onboarding/paypal/screen/__mocks__/psp.ts deleted file mode 100644 index 402236e1c69..00000000000 --- a/ts/features/wallet/onboarding/paypal/screen/__mocks__/psp.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { NonNegativeNumber } from "@pagopa/ts-commons/lib/numbers"; -import { IOPayPalPsp } from "../../types"; - -const mockPrivacyUrl = "https://io.italia.it/app-content/tos_privacy.html"; - -export const pspList: ReadonlyArray = [ - { - id: "1", - logoUrl: "https://paytipper.com/wp-content/uploads/2021/02/logo.png", - name: "PayTipper", - fee: 100 as NonNegativeNumber, - privacyUrl: mockPrivacyUrl - }, - { - id: "2", - logoUrl: "https://www.dropbox.com/s/smk5cyxx1qevn6a/mat_bank.png?dl=1", - name: "Mat Bank", - fee: 50 as NonNegativeNumber, - privacyUrl: mockPrivacyUrl - } -]; diff --git a/ts/features/wallet/onboarding/paypal/screen/__tests__/PayPalOnboardingCheckoutScreen.test.tsx b/ts/features/wallet/onboarding/paypal/screen/__tests__/PayPalOnboardingCheckoutScreen.test.tsx deleted file mode 100644 index e768c71f13e..00000000000 --- a/ts/features/wallet/onboarding/paypal/screen/__tests__/PayPalOnboardingCheckoutScreen.test.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import { createStore, Store } from "redux"; - -import { Alert } from "react-native"; -import { fireEvent } from "@testing-library/react-native"; -import { appReducer } from "../../../../../../store/reducers"; -import { setLocale } from "../../../../../../i18n"; -import { renderScreenWithNavigationStoreContext } from "../../../../../../utils/testWrapper"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import PayPalOnboardingCheckoutScreen from "../PayPalOnboardingCheckoutScreen"; -import { applicationChangeState } from "../../../../../../store/actions/application"; -import { - walletAddPaypalPspSelected, - walletAddPaypalRefreshPMToken -} from "../../store/actions"; -import PAYPAL_ROUTES from "../../navigation/routes"; -import { PaymentManagerToken } from "../../../../../../types/pagopa"; -import { pspList } from "../__mocks__/psp"; - -jest.spyOn(Alert, "alert"); - -describe("PayPalOnboardingCheckoutScreen", () => { - beforeAll(() => { - setLocale("it"); - }); - jest.useFakeTimers(); - - it(`screen should be defined`, () => { - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - const render = renderComponent(store); - expect(render.component).not.toBeNull(); - }); - - describe("when the PM token is not in the store", () => { - it(`it should show the loading`, () => { - const globalState = appReducer( - undefined, - applicationChangeState("active") - ); - const store = createStore(appReducer, globalState as any); - const render = renderComponent(store); - expect( - render.component.queryByTestId( - "PayPalOnboardingCheckoutScreenLoadingError" - ) - ).not.toBeNull(); - }); - }); - - describe("when the PM token is requested and the response is a failure", () => { - it(`it should show the error component with the retry button`, () => { - const globalState = appReducer( - undefined, - applicationChangeState("active") - ); - const store = createStore(appReducer, globalState as any); - const render = renderComponent(store); - expect( - render.component.queryByTestId( - "PayPalOnboardingCheckoutScreenLoadingError" - ) - ).not.toBeNull(); - store.dispatch(walletAddPaypalRefreshPMToken.failure(Error("an error"))); - expect( - render.component.queryByTestId("LoadingErrorComponentError") - ).not.toBeNull(); - }); - }); - - describe("when the PM token is requested and the response is a success", () => { - it(`it should show a generic error since the pspSelected is none`, () => { - const globalState = appReducer( - undefined, - walletAddPaypalRefreshPMToken.request() - ); - const store = createStore(appReducer, globalState as any); - const render = renderComponent(store); - expect( - render.component.queryByTestId( - "PayPalOnboardingCheckoutScreenLoadingError" - ) - ).not.toBeNull(); - store.dispatch( - walletAddPaypalRefreshPMToken.success("a token" as PaymentManagerToken) - ); - expect( - render.component.queryByTestId("WorkunitGenericFailure") - ).not.toBeNull(); - }); - }); - - describe("when the PM token is requested and the response is a success and the there is a psp selected", () => { - it(`it should show the checkout web view`, () => { - const globalState = appReducer( - undefined, - walletAddPaypalRefreshPMToken.request() - ); - const store = createStore(appReducer, globalState as any); - const render = renderComponent(store); - expect( - render.component.queryByTestId( - "PayPalOnboardingCheckoutScreenLoadingError" - ) - ).not.toBeNull(); - store.dispatch( - walletAddPaypalRefreshPMToken.success("a token" as PaymentManagerToken) - ); - store.dispatch(walletAddPaypalPspSelected(pspList[0])); - expect( - render.component.queryByTestId("PayWebViewModalTestID") - ).not.toBeNull(); - }); - - it(`the back button (soft&hard) should show an alert`, () => { - const globalState = appReducer( - undefined, - walletAddPaypalRefreshPMToken.request() - ); - const store = createStore(appReducer, globalState as any); - const render = renderComponent(store); - expect( - render.component.queryByTestId( - "PayPalOnboardingCheckoutScreenLoadingError" - ) - ).not.toBeNull(); - store.dispatch( - walletAddPaypalRefreshPMToken.success("a token" as PaymentManagerToken) - ); - store.dispatch(walletAddPaypalPspSelected(pspList[0])); - /** - * this component has 2 back buttons: - * 1- in the screen that includes the modal - * 2- in the modal - */ - const screenBackButton = - render.component.queryByTestId("host-back-button"); - expect(screenBackButton).not.toBeNull(); - const modalBackButton = render.component.queryByTestId("back-button"); - expect(modalBackButton).not.toBeNull(); - if (screenBackButton != null) { - fireEvent.press(screenBackButton); - expect(Alert.alert).toHaveBeenCalledTimes(1); - } - if (modalBackButton != null) { - fireEvent.press(modalBackButton); - expect(Alert.alert).toHaveBeenCalledTimes(2); - } - }); - }); -}); - -const renderComponent = (store: Store) => ({ - component: renderScreenWithNavigationStoreContext( - PayPalOnboardingCheckoutScreen, - PAYPAL_ROUTES.ONBOARDING.CHECKOUT, - {}, - store - ), - store -}); diff --git a/ts/features/wallet/onboarding/paypal/screen/__tests__/PayPalOnboardingCompletedSuccessComponent.test.tsx b/ts/features/wallet/onboarding/paypal/screen/__tests__/PayPalOnboardingCompletedSuccessComponent.test.tsx deleted file mode 100644 index a24df22decf..00000000000 --- a/ts/features/wallet/onboarding/paypal/screen/__tests__/PayPalOnboardingCompletedSuccessComponent.test.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { createStore } from "redux"; - -import { appReducer } from "../../../../../../store/reducers"; -import { applicationChangeState } from "../../../../../../store/actions/application"; -import { renderScreenWithNavigationStoreContext } from "../../../../../../utils/testWrapper"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import PayPalOnboardingCompletedSuccessComponent from "../PayPalOnboardingCompletedSuccessComponent"; -import I18n, { setLocale } from "../../../../../../i18n"; -import { walletAddPaypalStart } from "../../store/actions"; - -describe("PayPalOnboardingCompletedSuccessComponent", () => { - beforeAll(() => { - setLocale("it"); - }); - jest.useFakeTimers(); - - it(`should match the snapshot`, () => { - const { render } = renderComponent(); - expect(render.component.toJSON()).toMatchSnapshot(); - }); - - it(`screen should be defined`, () => { - const { render } = renderComponent(); - expect( - render.component.queryByTestId("InfoScreenComponent") - ).not.toBeNull(); - }); - - it(`buttons should be defined`, () => { - const { render } = renderComponent(); - expect(render.component.queryByTestId("primaryButtonId")).not.toBeNull(); - }); - - describe("when paypal is onboarded from the wallet section", () => { - it(`then the texts shown should match the dedicated messages`, () => { - const { render, store } = renderComponent(); - store.dispatch(walletAddPaypalStart("payment_method_details")); - expect( - render.component.queryByText( - I18n.t("wallet.onboarding.paypal.onBoardingCompleted.title") - ) - ).not.toBeNull(); - expect( - render.component.queryByText( - I18n.t("wallet.onboarding.paypal.onBoardingCompleted.body") - ) - ).not.toBeNull(); - expect( - render.component.queryByText( - I18n.t("wallet.onboarding.paypal.onBoardingCompleted.primaryButton") - ) - ).not.toBeNull(); - }); - }); - - describe("when paypal is onboarded during a payment", () => { - it(`then the texts shown should match the dedicated messages`, () => { - const { render, store } = renderComponent(); - store.dispatch(walletAddPaypalStart("back")); - expect( - render.component.queryByText( - I18n.t( - "wallet.onboarding.paypal.onBoardingCompletedWhilePayment.title" - ) - ) - ).not.toBeNull(); - expect( - render.component.queryByText( - I18n.t( - "wallet.onboarding.paypal.onBoardingCompletedWhilePayment.body" - ) - ) - ).not.toBeNull(); - expect( - render.component.queryByText( - I18n.t( - "wallet.onboarding.paypal.onBoardingCompletedWhilePayment.primaryButton" - ) - ) - ).not.toBeNull(); - }); - }); -}); - -const renderComponent = () => { - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - const render = { - component: renderScreenWithNavigationStoreContext( - PayPalOnboardingCompletedSuccessComponent, - "N/A", - {}, - store - ), - store - }; - return { render, store }; -}; diff --git a/ts/features/wallet/onboarding/paypal/screen/__tests__/PayPalPpsSelectionScreen.test.tsx b/ts/features/wallet/onboarding/paypal/screen/__tests__/PayPalPpsSelectionScreen.test.tsx deleted file mode 100644 index 48ea5ea8053..00000000000 --- a/ts/features/wallet/onboarding/paypal/screen/__tests__/PayPalPpsSelectionScreen.test.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { createStore } from "redux"; -import { fireEvent } from "@testing-library/react-native"; -import { appReducer } from "../../../../../../store/reducers"; -import { applicationChangeState } from "../../../../../../store/actions/application"; -import { renderScreenWithNavigationStoreContext } from "../../../../../../utils/testWrapper"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import PayPalPpsSelectionScreen from "../PayPalPspSelectionScreen"; -import I18n from "../../../../../../i18n"; -import { searchPaypalPsp } from "../../store/actions"; -import { getNetworkError } from "../../../../../../utils/errors"; -import { pspList } from "../__mocks__/psp"; - -const mockPresentBottomSheet = jest.fn(); - -jest.mock("../../../../../../utils/hooks/bottomSheet", () => { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const react = require("react-native"); - return { - __esModule: true, - BottomSheetScrollView: react.ScrollView, - TouchableWithoutFeedback: react.TouchableWithoutFeedback, - useIOBottomSheetAutoresizableModal: () => ({ - present: mockPresentBottomSheet - }) - }; -}); - -describe("PayPalPpsSelectionScreen", () => { - jest.useFakeTimers(); - it(`screen should be defined when the psp list is ready`, () => { - const render = renderComponent(); - render.store.dispatch(searchPaypalPsp.success(pspList)); - expect( - render.component.queryByTestId("PayPalPpsSelectionScreen") - ).not.toBeNull(); - }); - - it(`footer buttons should be defined when the psp list is ready`, () => { - const render = renderComponent(); - render.store.dispatch(searchPaypalPsp.success(pspList)); - expect( - render.component.queryByText(I18n.t("global.buttons.cancel")) - ).not.toBeNull(); - expect( - render.component.queryByText(I18n.t("global.buttons.continue")) - ).not.toBeNull(); - }); - it("psp items shown should match those one in the store", () => { - const render = renderComponent(); - render.store.dispatch(searchPaypalPsp.success(pspList)); - pspList.forEach(psp => { - expect( - render.component.queryByTestId(`pspItemTestID_${psp.id}`) - ).not.toBeNull(); - }); - }); - it("loading should be shown when the data is loading", () => { - const render = renderComponent(); - render.store.dispatch(searchPaypalPsp.request()); - expect( - render.component.queryByTestId(`PayPalPpsSelectionScreenLoadingError`) - ).not.toBeNull(); - }); - it("error and retry button should be shown when some error occurred while retrieving data", () => { - const render = renderComponent(); - render.store.dispatch( - searchPaypalPsp.failure(getNetworkError(new Error("test"))) - ); - expect( - render.component.queryByTestId(`LoadingErrorComponentError`) - ).not.toBeNull(); - }); - - it(`"what is a psp" link should be defined open a bottom sheet on onPress, when the psp list is ready`, () => { - const render = renderComponent(); - render.store.dispatch(searchPaypalPsp.success(pspList)); - const link = render.component.getByText( - I18n.t("wallet.onboarding.paypal.selectPsp.link") - ); - expect(link).not.toBeNull(); - if (link) { - fireEvent.press(link); - expect(mockPresentBottomSheet).toBeCalledTimes(1); - } - }); -}); - -const renderComponent = () => { - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - return { - component: renderScreenWithNavigationStoreContext( - PayPalPpsSelectionScreen, - "N/A", - {}, - store - ), - store - }; -}; diff --git a/ts/features/wallet/onboarding/paypal/screen/__tests__/PayPalPspUpdateScreen.test.tsx b/ts/features/wallet/onboarding/paypal/screen/__tests__/PayPalPspUpdateScreen.test.tsx deleted file mode 100644 index 76304895e6f..00000000000 --- a/ts/features/wallet/onboarding/paypal/screen/__tests__/PayPalPspUpdateScreen.test.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { createStore } from "redux"; -import { PspData } from "../../../../../../../definitions/pagopa/PspData"; -import I18n from "../../../../../../i18n"; -import { applicationChangeState } from "../../../../../../store/actions/application"; -import { pspForPaymentV2 } from "../../../../../../store/actions/wallet/payment"; -import { appReducer } from "../../../../../../store/reducers"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { getNetworkError } from "../../../../../../utils/errors"; -import { renderScreenWithNavigationStoreContext } from "../../../../../../utils/testWrapper"; -import PayPalPspUpdateScreen from "../../../../paypal/screen/PayPalPspUpdateScreen"; - -const pspList: ReadonlyArray = [ - { - id: 1, - codiceAbi: "0001", - defaultPsp: true, - fee: 100, - idPsp: "1", - onboard: true, - privacyUrl: "https://io.italia.it", - ragioneSociale: "PayTipper" - }, - { - id: 2, - codiceAbi: "0002", - defaultPsp: true, - fee: 120, - idPsp: "2", - onboard: true, - privacyUrl: "https://io.italia.it", - ragioneSociale: "PayTipper2" - } -]; - -describe("PayPalPspUpdateScreen", () => { - jest.useFakeTimers(); - describe("when the psp list is ready", () => { - it(`then content should be displayed`, () => { - const render = renderComponent(); - render.store.dispatch(pspForPaymentV2.success(pspList)); - expect( - render.component.queryByTestId("PayPalPspUpdateScreen") - ).not.toBeNull(); - }); - - it(`then the footer button should be defined`, () => { - const render = renderComponent(); - render.store.dispatch(pspForPaymentV2.success(pspList)); - expect( - render.component.queryByText(I18n.t("global.buttons.cancel")) - ).not.toBeNull(); - }); - - it("then psp items shown should match those one in the store", () => { - const render = renderComponent(); - render.store.dispatch(pspForPaymentV2.success(pspList)); - pspList.forEach(psp => { - expect( - render.component.queryByTestId(`pspItemTestID_${psp.idPsp}`) - ).not.toBeNull(); - }); - }); - }); - - describe("when the psp list is loading", () => { - it("then a loading should be shown", () => { - const render = renderComponent(); - render.store.dispatch( - pspForPaymentV2.request({ idPayment: "x", idWallet: 1 }) - ); - expect( - render.component.queryByTestId(`PayPalPpsUpdateScreenLoadingError`) - ).not.toBeNull(); - }); - }); - - describe("when the psp list is in error", () => { - it("then the error content and retry button should be shown", () => { - const render = renderComponent(); - render.store.dispatch( - pspForPaymentV2.failure(getNetworkError(new Error("test"))) - ); - expect( - render.component.queryByTestId(`LoadingErrorComponentError`) - ).not.toBeNull(); - }); - }); -}); - -const renderComponent = () => { - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - return { - component: renderScreenWithNavigationStoreContext( - PayPalPspUpdateScreen, - "N/A", - {}, - store - ), - store - }; -}; diff --git a/ts/features/wallet/onboarding/paypal/screen/__tests__/PayPalStartOnboardingScreen.test.tsx b/ts/features/wallet/onboarding/paypal/screen/__tests__/PayPalStartOnboardingScreen.test.tsx deleted file mode 100644 index 1239ae31832..00000000000 --- a/ts/features/wallet/onboarding/paypal/screen/__tests__/PayPalStartOnboardingScreen.test.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { createStore, Store } from "redux"; - -import PayPalStartOnboardingScreen from "../PayPalStartOnboardingScreen"; -import { renderScreenWithNavigationStoreContext } from "../../../../../../utils/testWrapper"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { appReducer } from "../../../../../../store/reducers"; -import { applicationChangeState } from "../../../../../../store/actions/application"; - -describe("PayPalStartOnboardingScreen", () => { - jest.useFakeTimers(); - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - it(`screen should be defined`, () => { - const render = renderComponent(store); - expect( - render.component.queryByTestId("PayPalStartOnboardingScreen") - ).not.toBeNull(); - }); - - it(`footer buttons should be defined`, () => { - const render = renderComponent(store); - expect(render.component.queryByTestId("cancelButtonId")).not.toBeNull(); - expect(render.component.queryByTestId("continueButtonId")).not.toBeNull(); - }); - - it(`PayPal logo should be defined`, () => { - const render = renderComponent(store); - expect(render.component.queryByTestId("payPalLogo")).not.toBeNull(); - }); -}); - -const renderComponent = (store: Store) => ({ - component: renderScreenWithNavigationStoreContext( - PayPalStartOnboardingScreen, - "N/A", - {}, - store - ), - store -}); diff --git a/ts/features/wallet/onboarding/paypal/screen/__tests__/PspInfoBottomSheetContent.test.tsx b/ts/features/wallet/onboarding/paypal/screen/__tests__/PspInfoBottomSheetContent.test.tsx deleted file mode 100644 index 8728497efb2..00000000000 --- a/ts/features/wallet/onboarding/paypal/screen/__tests__/PspInfoBottomSheetContent.test.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { render } from "@testing-library/react-native"; -import React from "react"; -import { NonNegativeNumber } from "@pagopa/ts-commons/lib/numbers"; -import { PspInfoBottomSheetContent } from "../../components/PspInfoBottomSheet"; - -const props = { - pspName: "pspNameTest", - pspFee: 123 as NonNegativeNumber, - pspPrivacyUrl: "privacyUrlTest", - pspTosUrl: "pspTosUrl" -}; - -describe("PspInfoBottomSheetContent", () => { - jest.useFakeTimers(); - it(`component should be defined`, () => { - const renderComponent = render(); - expect( - renderComponent.queryByTestId("PspInfoBottomSheetContentTestID") - ).not.toBeNull(); - }); -}); diff --git a/ts/features/wallet/onboarding/paypal/screen/__tests__/PspRadioItem.test.tsx b/ts/features/wallet/onboarding/paypal/screen/__tests__/PspRadioItem.test.tsx deleted file mode 100644 index 790e4ae8fd3..00000000000 --- a/ts/features/wallet/onboarding/paypal/screen/__tests__/PspRadioItem.test.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { NonNegativeNumber } from "@pagopa/ts-commons/lib/numbers"; -import { render } from "@testing-library/react-native"; -import React from "react"; -import { PspRadioItem } from "../../components/PspRadioItem"; -import { IOPayPalPsp } from "../../types"; - -jest.mock("react-native-safe-area-context", () => ({ - useSafeAreaInsets: jest - .fn() - .mockReturnValue({ top: 20, left: 0, right: 0, bottom: 0 }) -})); - -const mockPrivacyUrl = "https://io.italia.it/app-content/tos_privacy.html"; - -const payPalPsp: IOPayPalPsp = { - id: "1", - logoUrl: "https://paytipper.com/wp-content/uploads/2021/02/logo.png", - name: "PayTipper", - fee: 100 as NonNegativeNumber, - privacyUrl: mockPrivacyUrl -}; - -describe("PspRadioItem", () => { - jest.useFakeTimers(); - it(`match snapshot`, () => { - const component = render(); - expect(component).toMatchSnapshot(); - }); - - it(`component should be defined`, () => { - const component = render( - - ); - expect(component.queryByTestId("PspRadioItemTestID")).not.toBeNull(); - }); - - it(`should be present the info icon`, () => { - const component = render(); - expect(component.queryByTestId("infoIconTestID")).not.toBeNull(); - }); - - it(`should be present at least the name or the logo`, () => { - const component = render(); - const pspName = component.queryByTestId("pspNameTestID"); - const pspLogo = component.queryByTestId("pspNameLogoID"); - expect(pspName === null && pspLogo === null).toBeFalsy(); - }); -}); diff --git a/ts/features/wallet/onboarding/paypal/screen/__tests__/__snapshots__/PayPalOnboardingCompletedSuccessComponent.test.tsx.snap b/ts/features/wallet/onboarding/paypal/screen/__tests__/__snapshots__/PayPalOnboardingCompletedSuccessComponent.test.tsx.snap deleted file mode 100644 index 8b3abf3d4d7..00000000000 --- a/ts/features/wallet/onboarding/paypal/screen/__tests__/__snapshots__/PayPalOnboardingCompletedSuccessComponent.test.tsx.snap +++ /dev/null @@ -1,619 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PayPalOnboardingCompletedSuccessComponent should match the snapshot 1`] = ` - - - - - - - - - - - - - - - N/A - - - - - - - - - - - - - - - - - - - - - Fatto! - - - - Ora puoi continuare con il pagamento. - - - - - - - - - - - Continua - - - - - - - - - - - - - - - - - - - -`; diff --git a/ts/features/wallet/onboarding/paypal/screen/__tests__/__snapshots__/PspRadioItem.test.tsx.snap b/ts/features/wallet/onboarding/paypal/screen/__tests__/__snapshots__/PspRadioItem.test.tsx.snap deleted file mode 100644 index c405d63aec4..00000000000 --- a/ts/features/wallet/onboarding/paypal/screen/__tests__/__snapshots__/PspRadioItem.test.tsx.snap +++ /dev/null @@ -1,600 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PspRadioItem match snapshot 1`] = ` - - - PayTipper - - - - - - - - - - - - - - - - - - - - - - - - - - It's a Payment Service Provider (PSP) which can process your transactions via PayPal. - - - - - - - - - - - - - - When you pay a notice, this PSP can apply a maximum fee of - - € 1.00 - - . You'll see the right amount before confirming the payment. - - - - - - - - - - - - - - - PayTipper’s Terms and Conditions and Privacy Policy apply - - - - - - - - - - - -`; diff --git a/ts/features/wallet/onboarding/paypal/store/actions/index.ts b/ts/features/wallet/onboarding/paypal/store/actions/index.ts deleted file mode 100644 index d0f68915ee7..00000000000 --- a/ts/features/wallet/onboarding/paypal/store/actions/index.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { - ActionType, - createAsyncAction, - createStandardAction -} from "typesafe-actions"; -import { Option } from "fp-ts/lib/Option"; -import { NetworkError } from "../../../../../../utils/errors"; -import { IOPayPalPsp } from "../../types"; -import { PaymentManagerToken } from "../../../../../../types/pagopa"; - -/** - * Request the available psp for Paypal - */ -export const searchPaypalPsp = createAsyncAction( - "WALLET_ONBOARDING_PAYPAL_PSP_SEARCH_REQUEST", - "WALLET_ONBOARDING_PAYPAL_PSP_SEARCH_SUCCESS", - "WALLET_ONBOARDING_PAYPAL_PSP_SEARCH_FAILURE" -), NetworkError>(); - -// describes the next step should be done when the onboarding is completed -export type OnOnboardingCompleted = "payment_method_details" | "back"; -/** - * The user chooses to start the workflow to add a new paypal account to the wallet - */ -export const walletAddPaypalStart = createStandardAction( - "WALLET_ONBOARDING_PAYPAL_START" -)(); - -export const walletAddPaypalCompleted = createStandardAction( - "WALLET_ONBOARDING_PAYPAL_COMPLETED" -)(); - -export const walletAddPaypalBack = createStandardAction( - "WALLET_ONBOARDING_PAYPAL_BACK" -)(); - -export const walletAddPaypalCancel = createStandardAction( - "WALLET_ONBOARDING_PAYPAL_CANCEL" -)(); - -export const walletAddPaypalFailure = createStandardAction( - "WALLET_ONBOARDING_PAYPAL_FAILURE" -)(); - -// user selects the psp to handle payments within Paypal -export const walletAddPaypalPspSelected = createStandardAction( - "WALLET_ONBOARDING_PAYPAL_PSP_SELECTED" -)(); - -/** - * user is going to onboard paypal, a fresh PM token is required - */ -export const walletAddPaypalRefreshPMToken = createAsyncAction( - "WALLET_ONBOARDING_PAYPAL_REFRESH_PM_TOKEN_REQUEST", - "WALLET_ONBOARDING_PAYPAL_REFRESH_PM_TOKEN_SUCCESS", - "WALLET_ONBOARDING_PAYPAL_REFRESH_PM_TOKEN_FAILURE" -)(); - -export const walletAddPaypalOutcome = createStandardAction( - "WALLET_ONBOARDING_PAYPAL_OUTCOME_CODE" -)>(); - -export type PayPalOnboardingActions = - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType; diff --git a/ts/features/wallet/onboarding/paypal/store/reducers/__test__/onOboardingCompleted.test.ts b/ts/features/wallet/onboarding/paypal/store/reducers/__test__/onOboardingCompleted.test.ts deleted file mode 100644 index 57866f46e1b..00000000000 --- a/ts/features/wallet/onboarding/paypal/store/reducers/__test__/onOboardingCompleted.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -import * as O from "fp-ts/lib/Option"; -import { GlobalState } from "../../../../../../../store/reducers/types"; -import { appReducer } from "../../../../../../../store/reducers"; -import { reproduceSequence } from "../../../../../../../utils/tests"; -import { - paypalOnboardingCompletedSelector, - paypalOnboardingOutcomeCodeSelector -} from "../onOboardingCompleted"; -import { applicationChangeState } from "../../../../../../../store/actions/application"; -import { - walletAddPaypalCompleted, - walletAddPaypalOutcome, - walletAddPaypalStart -} from "../../actions"; - -describe("onboardingCompletedReducer", () => { - describe("when the store is empty", () => { - it("should return the default state", () => { - const state: GlobalState = reproduceSequence( - {} as GlobalState, - appReducer, - [applicationChangeState("active")] - ); - expect(paypalOnboardingCompletedSelector(state)).toBeUndefined(); - expect(paypalOnboardingOutcomeCodeSelector(state)).toBeUndefined(); - }); - }); - - describe("when the onboarding starts", () => { - describe("and it starts from the wallet", () => { - it("should return 'payment_method_details' and outcome should be undefined", () => { - const state: GlobalState = reproduceSequence( - {} as GlobalState, - appReducer, - [ - applicationChangeState("active"), - walletAddPaypalStart("payment_method_details") - ] - ); - expect(paypalOnboardingCompletedSelector(state)).toEqual( - "payment_method_details" - ); - expect(paypalOnboardingOutcomeCodeSelector(state)).toBeUndefined(); - }); - }); - - describe("and it starts during a payment", () => { - it("should return 'back' and outcome should be undefined", () => { - const state: GlobalState = reproduceSequence( - {} as GlobalState, - appReducer, - [applicationChangeState("active"), walletAddPaypalStart("back")] - ); - expect(paypalOnboardingCompletedSelector(state)).toEqual("back"); - expect(paypalOnboardingOutcomeCodeSelector(state)).toBeUndefined(); - }); - }); - }); - - describe("when walletAddPaypalOutcome (with outcome) is dispatched", () => { - it("should return the outcome", () => { - const state: GlobalState = reproduceSequence( - {} as GlobalState, - appReducer, - [walletAddPaypalOutcome(O.some("outcomeX"))] - ); - expect(paypalOnboardingOutcomeCodeSelector(state)).toEqual("outcomeX"); - }); - }); - - describe("when walletAddPaypalOutcome (with no outcome) is dispatched", () => { - it("should return the outcome", () => { - const state: GlobalState = reproduceSequence( - {} as GlobalState, - appReducer, - [walletAddPaypalOutcome(O.none)] - ); - expect(paypalOnboardingOutcomeCodeSelector(state)).toBeUndefined(); - }); - }); - - describe("when the walletAddPaypalStart and walletAddPaypalOutcome are dispatched", () => { - it("should return the expected state", () => { - const state: GlobalState = reproduceSequence( - {} as GlobalState, - appReducer, - [ - walletAddPaypalStart("payment_method_details"), - walletAddPaypalOutcome(O.some("outcomeX")) - ] - ); - expect(paypalOnboardingOutcomeCodeSelector(state)).toEqual("outcomeX"); - expect(paypalOnboardingCompletedSelector(state)).toEqual( - "payment_method_details" - ); - }); - }); - - describe("when the onboarding is completed", () => { - it("should reset the state", () => { - const state: GlobalState = reproduceSequence( - {} as GlobalState, - appReducer, - [ - walletAddPaypalStart("payment_method_details"), - walletAddPaypalOutcome(O.some("outcomeX")), - walletAddPaypalCompleted() - ] - ); - expect(paypalOnboardingOutcomeCodeSelector(state)).toBeUndefined(); - expect(paypalOnboardingCompletedSelector(state)).toBeUndefined(); - }); - }); -}); diff --git a/ts/features/wallet/onboarding/paypal/store/reducers/index.ts b/ts/features/wallet/onboarding/paypal/store/reducers/index.ts deleted file mode 100644 index 8355eb87258..00000000000 --- a/ts/features/wallet/onboarding/paypal/store/reducers/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { combineReducers } from "redux"; -import { Action } from "../../../../../../store/actions/types"; -import searchPspReducer, { RemotePayPalPsp } from "./searchPsp"; -import selectedPspReducer, { PayPalSelectedPspState } from "./selectedPsp"; -import onboardingCompletedReducer, { - PaypalOnOnboardingCompletedState -} from "./onOboardingCompleted"; - -export type OnboardPayPalState = { - psp: RemotePayPalPsp; - selectedPsp: PayPalSelectedPspState; - onboardingCompletion: PaypalOnOnboardingCompletedState; -}; - -export const onboardingPaypalReducer = combineReducers< - OnboardPayPalState, - Action ->({ - // the psp whose handle the payment for PayPal - psp: searchPspReducer, - // the psp selected to handle payments with PayPal - selectedPsp: selectedPspReducer, - // info about the onboarding completion - onboardingCompletion: onboardingCompletedReducer -}); diff --git a/ts/features/wallet/onboarding/paypal/store/reducers/onOboardingCompleted.ts b/ts/features/wallet/onboarding/paypal/store/reducers/onOboardingCompleted.ts deleted file mode 100644 index cbed70b5b0c..00000000000 --- a/ts/features/wallet/onboarding/paypal/store/reducers/onOboardingCompleted.ts +++ /dev/null @@ -1,48 +0,0 @@ -import * as O from "fp-ts/lib/Option"; -import { getType } from "typesafe-actions"; -import { Action } from "../../../../../../store/actions/types"; -import { GlobalState } from "../../../../../../store/reducers/types"; -import { - OnOnboardingCompleted, - walletAddPaypalCompleted, - walletAddPaypalFailure, - walletAddPaypalOutcome, - walletAddPaypalStart -} from "../actions"; - -export type PaypalOnOnboardingCompletedState = Partial<{ - onCompleted: OnOnboardingCompleted; - outcome: string; -}>; -const initialState: PaypalOnOnboardingCompletedState = { - onCompleted: undefined, - outcome: undefined -}; -const onboardingCompletedReducer = ( - state: PaypalOnOnboardingCompletedState = initialState, - action: Action -): PaypalOnOnboardingCompletedState => { - switch (action.type) { - case getType(walletAddPaypalStart): - return { onCompleted: action.payload, outcome: undefined }; - case getType(walletAddPaypalOutcome): - return { ...state, outcome: O.toUndefined(action.payload) }; - case getType(walletAddPaypalFailure): - case getType(walletAddPaypalCompleted): - return initialState; - default: - return state; - } -}; - -export const paypalOnboardingCompletedSelector = ( - state: GlobalState -): PaypalOnOnboardingCompletedState["onCompleted"] => - state.wallet.onboarding.paypal.onboardingCompletion.onCompleted; - -export const paypalOnboardingOutcomeCodeSelector = ( - state: GlobalState -): PaypalOnOnboardingCompletedState["outcome"] => - state.wallet.onboarding.paypal.onboardingCompletion.outcome; - -export default onboardingCompletedReducer; diff --git a/ts/features/wallet/onboarding/paypal/store/reducers/searchPsp.ts b/ts/features/wallet/onboarding/paypal/store/reducers/searchPsp.ts deleted file mode 100644 index d2b2464b6f1..00000000000 --- a/ts/features/wallet/onboarding/paypal/store/reducers/searchPsp.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { getType } from "typesafe-actions"; -import { - remoteError, - remoteLoading, - remoteReady, - remoteUndefined, - RemoteValue -} from "../../../../../../common/model/RemoteValue"; -import { NetworkError } from "../../../../../../utils/errors"; -import { IOPayPalPsp } from "../../types"; -import { Action } from "../../../../../../store/actions/types"; -import { searchPaypalPsp } from "../actions"; -import { GlobalState } from "../../../../../../store/reducers/types"; - -export type RemotePayPalPsp = RemoteValue< - ReadonlyArray, - NetworkError ->; - -const payPalPspReducer = ( - state: RemotePayPalPsp = remoteUndefined, - action: Action -): RemotePayPalPsp => { - switch (action.type) { - case getType(searchPaypalPsp.request): - return remoteLoading; - case getType(searchPaypalPsp.success): - return remoteReady(action.payload); - case getType(searchPaypalPsp.failure): - return remoteError(action.payload); - default: - return state; - } -}; - -export const payPalPspSelector = (state: GlobalState) => - state.wallet.onboarding.paypal.psp; - -export default payPalPspReducer; diff --git a/ts/features/wallet/onboarding/paypal/store/reducers/selectedPsp.ts b/ts/features/wallet/onboarding/paypal/store/reducers/selectedPsp.ts deleted file mode 100644 index efdfcea2eb3..00000000000 --- a/ts/features/wallet/onboarding/paypal/store/reducers/selectedPsp.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { getType } from "typesafe-actions"; -import { IOPayPalPsp } from "../../types"; -import { Action } from "../../../../../../store/actions/types"; -import { walletAddPaypalPspSelected, walletAddPaypalStart } from "../actions"; -import { GlobalState } from "../../../../../../store/reducers/types"; - -export type PayPalSelectedPspState = IOPayPalPsp | null; -const initialState = null; - -const selectedPspReducer = ( - state: PayPalSelectedPspState = initialState, - action: Action -): PayPalSelectedPspState => { - switch (action.type) { - // reset the state when paypal onboarding flow starts - case getType(walletAddPaypalStart): - return initialState; - case getType(walletAddPaypalPspSelected): - return action.payload; - default: - return state; - } -}; - -export const paypalOnboardingSelectedPsp = ( - state: GlobalState -): PayPalSelectedPspState => state.wallet.onboarding.paypal.selectedPsp; - -export default selectedPspReducer; diff --git a/ts/features/wallet/onboarding/paypal/store/transformers.ts b/ts/features/wallet/onboarding/paypal/store/transformers.ts deleted file mode 100644 index ec7e65e88db..00000000000 --- a/ts/features/wallet/onboarding/paypal/store/transformers.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { NonNegativeNumber } from "@pagopa/ts-commons/lib/numbers"; -import { PspData } from "../../../../../../definitions/pagopa/PspData"; -import { IOPayPalPsp } from "../types"; -import { getPspIconUrlFromAbi } from "../../../../../utils/paymentMethod"; -import { PayPalPsp } from "../../../../../../definitions/pagopa/PayPalPsp"; - -/** - * convert pspData (one of many PM representations of a psp) into IOPayPalPsp - * @param psp - */ -export const convertPspData = (psp: PspData): IOPayPalPsp => ({ - id: psp.idPsp, - logoUrl: getPspIconUrlFromAbi(psp.codiceAbi), - name: psp.ragioneSociale, - fee: psp.fee as NonNegativeNumber, - privacyUrl: psp.privacyUrl -}); - -// convert a paypal psp returned by the API into the app domain model -export const convertPayPalPsp = (psp: PayPalPsp): IOPayPalPsp => ({ - id: psp.idPsp, - logoUrl: getPspIconUrlFromAbi(psp.codiceAbi), - name: psp.ragioneSociale, - fee: psp.maxFee as NonNegativeNumber, - privacyUrl: psp.privacyUrl -}); diff --git a/ts/features/wallet/onboarding/paypal/types/index.ts b/ts/features/wallet/onboarding/paypal/types/index.ts deleted file mode 100644 index 5d8327be7b1..00000000000 --- a/ts/features/wallet/onboarding/paypal/types/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { NonNegativeNumber } from "@pagopa/ts-commons/lib/numbers"; - -export type IOPayPalPsp = { - id: string; - logoUrl: string; - name: string; - fee: NonNegativeNumber; - privacyUrl?: string; -}; diff --git a/ts/features/wallet/onboarding/store/abi.ts b/ts/features/wallet/onboarding/store/abi.ts deleted file mode 100644 index 83fcd141975..00000000000 --- a/ts/features/wallet/onboarding/store/abi.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { getType } from "typesafe-actions"; -import { createSelector } from "reselect"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import { IndexedById } from "../../../../store/helpers/indexer"; -import { Abi } from "../../../../../definitions/pagopa/walletv2/Abi"; -import { - isReady, - remoteError, - remoteLoading, - remoteReady, - remoteUndefined, - RemoteValue -} from "../../../../common/model/RemoteValue"; -import { Action } from "../../../../store/actions/types"; -import { loadAbi } from "../bancomat/store/actions"; -import { GlobalState } from "../../../../store/reducers/types"; -import { getBancomatAbiIconUrl } from "../../../../utils/paymentMethod"; - -// plain object where the key is the abi and the value is the abi object -export type AbiState = RemoteValue, Error>; - -const getIndexedAbi = (abis: ReadonlyArray): IndexedById => - abis.reduce( - (acc: IndexedById, curr: Abi) => - pipe( - curr.abi, - O.fromNullable, - O.fold( - () => acc, - abi => ({ - ...acc, - [abi]: curr - }) - ) - ), - {} - ); - -const abiReducer = ( - state: AbiState = remoteUndefined, - action: Action -): AbiState => { - switch (action.type) { - case getType(loadAbi.request): - return remoteLoading; - case getType(loadAbi.success): - // since all fields are optional we ensure to index only entries those have abi defined - const indexedAbi: IndexedById = pipe( - action.payload.data, - O.fromNullable, - O.fold(() => ({}), getIndexedAbi) - ); - return remoteReady(indexedAbi); - case getType(loadAbi.failure): - return remoteError(action.payload); - } - return state; -}; - -export default abiReducer; - -const getAbiEnhanced = (indexedAbis: IndexedById): IndexedById => { - const abis: ReadonlyArray = Object.keys(indexedAbis).map< - Abi | undefined - >(k => indexedAbis[k]); - return getIndexedAbi( - abis.map(a => ({ - ...a, - logoUrl: a?.abi && getBancomatAbiIconUrl(a.abi) - })) - ); -}; -/** - * AbiState, memoized - */ -export const abiSelector = createSelector( - [(state: GlobalState) => state.wallet.abi], - (abis: AbiState): AbiState => - isReady(abis) ? remoteReady(getAbiEnhanced(abis.value)) : abis -); - -// return the abi list as array -export const abiListSelector = createSelector< - GlobalState, - AbiState, - ReadonlyArray ->(abiSelector, abis => - isReady(abis) - ? // build an array excluding the entry undefined - Object.keys(abis.value).reduce( - (acc: ReadonlyArray, curr: string) => - pipe( - abis.value[curr], - O.fromNullable, - O.fold( - () => acc, - abi => [...acc, abi] - ) - ), - [] - ) - : [] -); diff --git a/ts/features/wallet/onboarding/store/index.ts b/ts/features/wallet/onboarding/store/index.ts deleted file mode 100644 index 4592b8696b7..00000000000 --- a/ts/features/wallet/onboarding/store/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Action, combineReducers } from "redux"; -import onboardingBancomatReducer, { - OnboardingBancomatState -} from "../bancomat/store/reducers"; -import onboardingBPayReducer, { - OnboardingBPayState -} from "../bancomatPay/store/reducers"; -import onboardingCoBadgeReducer, { - OnboardingCoBadgeState -} from "../cobadge/store/reducers"; -import { - onboardingPaypalReducer, - OnboardPayPalState -} from "../paypal/store/reducers"; - -export type PaymentMethodOnboardingState = { - // The information related to adding new Bancomat to the wallet - bancomat: OnboardingBancomatState; - bPay: OnboardingBPayState; - coBadge: OnboardingCoBadgeState; - paypal: OnboardPayPalState; -}; - -const onboardingReducer = combineReducers( - { - bancomat: onboardingBancomatReducer, - bPay: onboardingBPayReducer, - coBadge: onboardingCoBadgeReducer, - paypal: onboardingPaypalReducer - } -); - -export default onboardingReducer; diff --git a/ts/features/wallet/paypal/PayPalWalletPreview.tsx b/ts/features/wallet/paypal/PayPalWalletPreview.tsx deleted file mode 100644 index c081050afbd..00000000000 --- a/ts/features/wallet/paypal/PayPalWalletPreview.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { useNavigation } from "@react-navigation/native"; -import * as React from "react"; -import payPalCard from "../../../../img/wallet/cards-icons/paypal_card.png"; -import { Body } from "../../../components/core/typography/Body"; -import { IOStyles } from "../../../components/core/variables/IOStyles"; -import I18n from "../../../i18n"; -import { - AppParamsList, - IOStackNavigationProp -} from "../../../navigation/params/AppParamsList"; -import { PayPalPaymentMethod } from "../../../types/pagopa"; -import { getPaypalAccountEmail } from "../../../utils/paypal"; -import { CardLogoPreview } from "../component/card/CardLogoPreview"; - -type OwnProps = { - paypal: PayPalPaymentMethod; -}; - -type Props = OwnProps; - -const getAccessibilityRepresentation = () => { - const paypal = I18n.t("wallet.onboarding.paypal.name"); - const cta = I18n.t("wallet.accessibility.folded.cta"); - return `${paypal}, ${cta}`; -}; - -/** - * A card preview for PayPal - * @param props - * @constructor - */ -const PayPalWalletPreview: React.FunctionComponent = props => { - const navigation = useNavigation>(); - return ( - - {getPaypalAccountEmail(props.paypal.info)} - - } - image={payPalCard} - onPress={() => - navigation.navigate("WALLET_NAVIGATOR", { - screen: "WALLET_PAYPAL_DETAIL" - }) - } - /> - ); -}; - -export default PayPalWalletPreview; diff --git a/ts/features/wallet/paypal/screen/PayPalPspUpdateScreen.tsx b/ts/features/wallet/paypal/screen/PayPalPspUpdateScreen.tsx deleted file mode 100644 index 8cd1b487b1f..00000000000 --- a/ts/features/wallet/paypal/screen/PayPalPspUpdateScreen.tsx +++ /dev/null @@ -1,236 +0,0 @@ -import { - FooterWithButtons, - Icon, - PressableListItemBase, - VSpacer -} from "@pagopa/io-app-design-system"; -import { Route, useRoute } from "@react-navigation/native"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import React, { useEffect } from "react"; -import { - Image, - SafeAreaView, - ScrollView, - StyleSheet, - View -} from "react-native"; -import { isError, isReady } from "../../../../common/model/RemoteValue"; -import { LoadingErrorComponent } from "../../../../components/LoadingErrorComponent"; -import { Body } from "../../../../components/core/typography/Body"; -import { H1 } from "../../../../components/core/typography/H1"; -import { H4 } from "../../../../components/core/typography/H4"; -import { Label } from "../../../../components/core/typography/Label"; -import { IOStyles } from "../../../../components/core/variables/IOStyles"; -import BaseScreenComponent from "../../../../components/screens/BaseScreenComponent"; -import I18n from "../../../../i18n"; -import { useIONavigation } from "../../../../navigation/params/AppParamsList"; -import { - pspForPaymentV2, - pspSelectedForPaymentV2 -} from "../../../../store/actions/wallet/payment"; -import { useIODispatch, useIOSelector } from "../../../../store/hooks"; -import { pspV2ListSelector } from "../../../../store/reducers/wallet/payment"; -import { emptyContextualHelp } from "../../../../utils/emptyContextualHelp"; -import { formatNumberCentsToAmount } from "../../../../utils/stringBuilder"; -import { useImageResize } from "../../onboarding/bancomat/hooks/useImageResize"; -import { - PSP_LOGO_MAX_HEIGHT, - PSP_LOGO_MAX_WIDTH -} from "../../onboarding/paypal/components/PspRadioItem"; -import { convertPspData } from "../../onboarding/paypal/store/transformers"; -import { IOPayPalPsp } from "../../onboarding/paypal/types"; - -const styles = StyleSheet.create({ - radioListHeaderRightColumn: { - flex: 1, - textAlign: "right" - }, - pspLogo: { - height: 32, - flex: 1, - flexDirection: "row", - justifyContent: "flex-start" - } - // pspListItem: { - // flexDirection: "row", - // justifyContent: "space-between", - // paddingLeft: 0, - // paddingRight: 0, - // flex: 1 - // } -}); - -// an header over the psp list with 2 columns -const PspListHeader = (props: { - leftColumnTitle: string; - rightColumnTitle: string; -}) => { - const color = "bluegrey"; - const weight = "Regular"; - return ( - -

- {props.leftColumnTitle} -

-

- {props.rightColumnTitle} -

-
- ); -}; - -const getLocales = () => ({ - title: I18n.t("wallet.onboarding.paypal.updatePsp.title"), - body: I18n.t("wallet.onboarding.paypal.updatePsp.body"), - leftColumnTitle: I18n.t("wallet.onboarding.paypal.updatePsp.leftColumnTitle"), - rightColumnTitle: I18n.t( - "wallet.onboarding.paypal.updatePsp.rightColumnTitle" - ) -}); - -const PspItem = (props: { psp: IOPayPalPsp; onPress: () => void }) => { - const { psp } = props; - const imgDimensions = useImageResize( - PSP_LOGO_MAX_WIDTH, - PSP_LOGO_MAX_HEIGHT, - psp.logoUrl - ); - return ( - - - {pipe( - imgDimensions, - O.fold( - () => ( -

- {psp.name} -

- ), - imgDim => ( - - ) - ) - )} -
- - - - - - -
- ); -}; -export type PayPalPspUpdateScreenNavigationParams = { - idPayment: string; - idWallet: number; -}; - -/** - * This screen is where the user updates the PSP that will be used for the payment - * Only 1 psp can be selected - */ -const PayPalPspUpdateScreen: React.FunctionComponent = () => { - const { idPayment, idWallet } = - useRoute< - Route< - "WALLET_PAYPAL_UPDATE_PAYMENT_PSP", - PayPalPspUpdateScreenNavigationParams - > - >().params; - const locales = getLocales(); - const navigation = useIONavigation(); - const dispatch = useIODispatch(); - const pspList = useIOSelector(pspV2ListSelector); - const searchPaypalPsp = () => { - dispatch(pspForPaymentV2.request({ idPayment, idWallet })); - }; - useEffect(searchPaypalPsp, [dispatch, idPayment, idWallet]); - - const goBack = () => navigation.goBack(); - return ( - - {isReady(pspList) ? ( - <> - - - -

{locales.title}

- - - {locales.body} - - - - {pspList.value.map(psp => { - const paypalPsp = convertPspData(psp); - return ( - { - dispatch(pspSelectedForPaymentV2(psp)); - goBack(); - }} - /> - ); - })} - -
-
- - - ) : ( - - )} -
- ); -}; - -export default PayPalPspUpdateScreen; diff --git a/ts/features/wallet/paypal/screen/PaypalDetailScreen.tsx b/ts/features/wallet/paypal/screen/PaypalDetailScreen.tsx deleted file mode 100644 index 8389e2aaa99..00000000000 --- a/ts/features/wallet/paypal/screen/PaypalDetailScreen.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import * as React from "react"; -import WorkunitGenericFailure from "../../../../components/error/WorkunitGenericFailure"; -import { useIOSelector } from "../../../../store/hooks"; -import { paypalSelector } from "../../../../store/reducers/wallet/wallets"; -import { getPaypalAccountEmail } from "../../../../utils/paypal"; -import BasePaymentMethodScreen from "../../common/BasePaymentMethodScreen"; -import PaymentMethodFeatures from "../../component/features/PaymentMethodFeatures"; -import { - PaymentCardBig, - PaymentCardBigProps -} from "../../../payments/common/components/PaymentCardBig"; - -/** - * Detail screen for a PayPal payment method - * @constructor - */ -const PaypalDetailScreen = () => { - const [walletExisted, setWalletExisted] = React.useState(false); - const paypalSelectorVal = useIOSelector(paypalSelector); - const paypal = pot.toUndefined(paypalSelectorVal); - const propsData = pipe( - paypalSelectorVal, - pot.toOption, - O.fold( - (): PaymentCardBigProps => ({ - isLoading: true - }), - (paypal): PaymentCardBigProps => ({ - cardType: "PAYPAL", - holderEmail: getPaypalAccountEmail(paypal.info) - }) - ) - ); - // This will set the flag `walletExisted` to true - // if, during this component lifecycle, the PayPal wallet actually - // existed in the state and has been removed. It's used to - // prevent the show of the `WorkunitGenericFailure`. - React.useEffect(() => { - if (paypal) { - setWalletExisted(true); - } - }, [paypal, setWalletExisted]); - - return paypal ? ( - } - content={} - headerTitle="PayPal" - /> - ) : !walletExisted ? ( - - ) : null; -}; - -export default PaypalDetailScreen; diff --git a/ts/navigation/AppStackNavigator.tsx b/ts/navigation/AppStackNavigator.tsx index 11b67e829e4..518660c3275 100644 --- a/ts/navigation/AppStackNavigator.tsx +++ b/ts/navigation/AppStackNavigator.tsx @@ -123,14 +123,6 @@ const InnerNavigationContainer = (props: InnerNavigationContainerProps) => { [ROUTES.PROFILE_PRIVACY_MAIN]: "privacy-main" } }, - [ROUTES.WALLET_NAVIGATOR]: { - path: "wallet", - screens: { - [ROUTES.PAYMENTS_HISTORY_SCREEN]: "payments-history", - [ROUTES.CREDIT_CARD_ONBOARDING_ATTEMPTS_SCREEN]: - "card-onboarding-attempts" - } - }, [SERVICES_ROUTES.SERVICES_NAVIGATOR]: { path: "services", screens: { diff --git a/ts/navigation/AuthenticatedStackNavigator.tsx b/ts/navigation/AuthenticatedStackNavigator.tsx index b7828900aa8..5aba4fd2973 100644 --- a/ts/navigation/AuthenticatedStackNavigator.tsx +++ b/ts/navigation/AuthenticatedStackNavigator.tsx @@ -74,7 +74,6 @@ import { AppParamsList } from "./params/AppParamsList"; import ProfileStackNavigator from "./ProfileNavigator"; import ROUTES from "./routes"; import { MainTabNavigator } from "./TabNavigator"; -import WalletNavigator from "./WalletNavigator"; const Stack = createStackNavigator(); @@ -150,11 +149,6 @@ const AuthenticatedStackNavigator = () => { }) }} /> - { /> ( diff --git a/ts/navigation/WalletNavigator.tsx b/ts/navigation/WalletNavigator.tsx deleted file mode 100644 index d0e3c64e971..00000000000 --- a/ts/navigation/WalletNavigator.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import { createStackNavigator } from "@react-navigation/stack"; -import * as React from "react"; -import { - BONUS_ROUTES, - BonusNavigator -} from "../features/bonus/common/navigation/navigator"; -import { IdPayInstrumentInitiativesScreen } from "../features/idpay/wallet/screens/IdPayInstrumentInitiativesScreen"; -import BancomatDetailScreen from "../features/wallet/bancomat/screen/BancomatDetailScreen"; -import { BPayDetailScreen } from "../features/wallet/bancomatpay/screen/BPayDetailScreen"; -import CobadgeDetailScreen from "../features/wallet/cobadge/screen/CobadgeDetailScreen"; -import CreditCardDetailScreen from "../features/wallet/creditCard/screen/CreditCardDetailScreen"; -import PaymentMethodOnboardingBPayNavigator from "../features/wallet/onboarding/bancomatPay/navigation/navigator"; -import WALLET_ONBOARDING_BPAY_ROUTES from "../features/wallet/onboarding/bancomatPay/navigation/routes"; -import PaymentMethodOnboardingCoBadgeNavigator from "../features/wallet/onboarding/cobadge/navigation/navigator"; -import WALLET_ONBOARDING_COBADGE_ROUTES from "../features/wallet/onboarding/cobadge/navigation/routes"; -import { PaymentMethodOnboardingPayPalOnboardingNavigator } from "../features/wallet/onboarding/paypal/navigation/navigator"; -import PAYPAL_ROUTES from "../features/wallet/onboarding/paypal/navigation/routes"; -import PayPalPspUpdateScreen from "../features/wallet/paypal/screen/PayPalPspUpdateScreen"; -import PaypalDetailScreen from "../features/wallet/paypal/screen/PaypalDetailScreen"; -import AddCardScreen from "../screens/wallet/AddCardScreen"; -import AddCreditCardOutcomeCodeMessage from "../screens/wallet/AddCreditCardOutcomeCodeMessage"; -import AddPaymentMethodScreen from "../screens/wallet/AddPaymentMethodScreen"; -import ConfirmCardDetailsScreen from "../screens/wallet/ConfirmCardDetailsScreen"; -import PaymentHistoryDetailsScreen from "../screens/wallet/PaymentHistoryDetailsScreen"; -import PaymentsHistoryScreen from "../screens/wallet/PaymentsHistoryScreen"; -import TransactionDetailsScreen from "../screens/wallet/TransactionDetailsScreen"; -import CreditCardOnboardingAttemptDetailScreen from "../screens/wallet/creditCardOnboardingAttempts/CreditCardOnboardingAttemptDetailScreen"; -import CreditCardOnboardingAttemptsScreen from "../screens/wallet/creditCardOnboardingAttempts/CreditCardOnboardingAttemptsScreen"; -import ConfirmPaymentMethodScreen from "../screens/wallet/payment/ConfirmPaymentMethodScreen"; -import ManualDataInsertionScreen from "../screens/wallet/payment/ManualDataInsertionScreen"; -import PaymentOutcomeCodeMessage from "../screens/wallet/payment/PaymentOutcomeCodeMessage"; -import PickPaymentMethodScreen from "../screens/wallet/payment/PickPaymentMethodScreen"; -import PickPspScreen from "../screens/wallet/payment/PickPspScreen"; -import TransactionErrorScreen from "../screens/wallet/payment/TransactionErrorScreen"; -import TransactionSummaryScreen from "../screens/wallet/payment/TransactionSummaryScreen"; -import ROUTES from "./routes"; - -const Stack = createStackNavigator(); - -const bpay_cobadge_routes = () => ( - <> - - - -); - -const WalletNavigator = () => ( - - - - - - - - - - - - - - - - - - - - - - - - - {/* Paypal */} - - {/* Bonus */} - - - {/* BPD */} - {bpay_cobadge_routes()} - -); -export default WalletNavigator; diff --git a/ts/navigation/components/HeaderFirstLevelHandler.tsx b/ts/navigation/components/HeaderFirstLevelHandler.tsx index ded1a28db05..f6a539e4a67 100644 --- a/ts/navigation/components/HeaderFirstLevelHandler.tsx +++ b/ts/navigation/components/HeaderFirstLevelHandler.tsx @@ -6,7 +6,6 @@ import * as O from "fp-ts/lib/Option"; import { pipe } from "fp-ts/lib/function"; import React, { ComponentProps, useCallback, useMemo } from "react"; import { useServicesHomeBottomSheet } from "../../features/services/home/hooks/useServicesHomeBottomSheet"; -import { useWalletHomeHeaderBottomSheet } from "../../components/wallet/WalletHomeHeader"; import { MESSAGES_ROUTES } from "../../features/messages/navigation/routes"; import { SupportRequestParams, @@ -18,10 +17,7 @@ import { SERVICES_ROUTES } from "../../features/services/common/navigation/route import { MainTabParamsList } from "../params/MainTabParamsList"; import ROUTES from "../routes"; import { useIONavigation } from "../params/AppParamsList"; -import { - isNewPaymentSectionEnabledSelector, - isSettingsVisibleAndHideProfileSelector -} from "../../store/reducers/backendStatus"; +import { isSettingsVisibleAndHideProfileSelector } from "../../store/reducers/backendStatus"; import * as analytics from "../../features/services/common/analytics"; import { isArchivingInProcessingModeSelector, @@ -89,10 +85,6 @@ export const HeaderFirstLevelHandler = ({ currentRouteName }: Props) => { const navigation = useIONavigation(); const store = useIOStore(); - const isNewWalletSectionEnabled = useIOSelector( - isNewPaymentSectionEnabledSelector - ); - const isSettingsVisibleAndHideProfile = useIOSelector( isSettingsVisibleAndHideProfileSelector ); @@ -209,21 +201,6 @@ export const HeaderFirstLevelHandler = ({ currentRouteName }: Props) => { [startSupportRequest] ); - const { - bottomSheet: WalletHomeHeaderBottomSheet, - present: presentWalletHomeHeaderBottomsheet - } = useWalletHomeHeaderBottomSheet(); - - const walletAction: HeaderActionProps = useMemo( - () => ({ - icon: "add", - accessibilityLabel: I18n.t("wallet.accessibility.addElement"), - onPress: presentWalletHomeHeaderBottomsheet, - testID: "walletAddNewPaymentMethodTestId" - }), - [presentWalletHomeHeaderBottomsheet] - ); - const searchMessageAction: HeaderActionProps = useMemo( () => ({ icon: "search", @@ -266,36 +243,16 @@ export const HeaderFirstLevelHandler = ({ currentRouteName }: Props) => { firstAction: helpAction }; case ROUTES.WALLET_HOME: - if (isNewWalletSectionEnabled) { - return { - ...commonProp, - title: I18n.t("wallet.wallet"), - firstAction: helpAction, - testID: "wallet-home-header-title", - ...(isSettingsVisibleAndHideProfile - ? { - type: "twoActions", - secondAction: settingsAction - } - : { type: "singleAction" }) - }; - } return { - ...commonProp, title: I18n.t("wallet.wallet"), firstAction: helpAction, - backgroundColor: "dark", testID: "wallet-home-header-title", ...(isSettingsVisibleAndHideProfile ? { - type: "threeActions", - secondAction: settingsAction, - thirdAction: walletAction - } - : { type: "twoActions", - secondAction: walletAction - }) + secondAction: settingsAction + } + : { type: "singleAction" }) }; case ROUTES.PAYMENTS_HOME: return { @@ -334,10 +291,8 @@ export const HeaderFirstLevelHandler = ({ currentRouteName }: Props) => { helpAction, settingsActionInServicesSection, searchInstitutionAction, - isNewWalletSectionEnabled, isSettingsVisibleAndHideProfile, settingsAction, - walletAction, settingsActionInMessageSection, searchMessageAction ]); @@ -346,7 +301,6 @@ export const HeaderFirstLevelHandler = ({ currentRouteName }: Props) => { <> {ServicesHomeBottomSheet} - {WalletHomeHeaderBottomSheet} ); }; diff --git a/ts/navigation/params/AppParamsList.ts b/ts/navigation/params/AppParamsList.ts index 1a2d9609779..5a894ce60f5 100644 --- a/ts/navigation/params/AppParamsList.ts +++ b/ts/navigation/params/AppParamsList.ts @@ -64,7 +64,6 @@ import { CheckEmailParamsList } from "./CheckEmailParamsList"; import { MainTabParamsList } from "./MainTabParamsList"; import { OnboardingParamsList } from "./OnboardingParamsList"; import { ProfileParamsList } from "./ProfileParamsList"; -import { WalletParamsList } from "./WalletParamsList"; export type AppParamsList = { [ROUTES.INGRESS]: undefined; @@ -78,7 +77,6 @@ export type AppParamsList = { [MESSAGES_ROUTES.MESSAGES_NAVIGATOR]: NavigatorScreenParams; [MESSAGES_ROUTES.MESSAGES_SEARCH]: undefined; [NOTIFICATIONS_ROUTES.SYSTEM_NOTIFICATION_PERMISSIONS]: undefined; - [ROUTES.WALLET_NAVIGATOR]: NavigatorScreenParams; [SERVICES_ROUTES.SERVICES_NAVIGATOR]: NavigatorScreenParams; [SERVICES_ROUTES.SEARCH]: undefined; [ROUTES.PROFILE_NAVIGATOR]: NavigatorScreenParams; diff --git a/ts/navigation/params/MainTabParamsList.ts b/ts/navigation/params/MainTabParamsList.ts index 709197c6cdc..f78c1568f80 100644 --- a/ts/navigation/params/MainTabParamsList.ts +++ b/ts/navigation/params/MainTabParamsList.ts @@ -1,7 +1,7 @@ -import { WalletHomeNavigationParams } from "../../screens/wallet/WalletHomeScreen"; import ROUTES from "../routes"; import { MESSAGES_ROUTES } from "../../features/messages/navigation/routes"; import { SERVICES_ROUTES } from "../../features/services/common/navigation/routes"; +import { WalletHomeNavigationParams } from "../../features/newWallet/screens/WalletHomeScreen"; export type MainTabParamsList = { [MESSAGES_ROUTES.MESSAGES_HOME]: undefined; diff --git a/ts/navigation/params/WalletParamsList.ts b/ts/navigation/params/WalletParamsList.ts index 5c2d8884144..4187d336dff 100644 --- a/ts/navigation/params/WalletParamsList.ts +++ b/ts/navigation/params/WalletParamsList.ts @@ -1,64 +1,10 @@ -import { NavigatorScreenParams } from "@react-navigation/native"; import { IdPayInstrumentInitiativesScreenRouteParams } from "../../features/idpay/wallet/screens/IdPayInstrumentInitiativesScreen"; -import { BancomatDetailScreenNavigationParams } from "../../features/wallet/bancomat/screen/BancomatDetailScreen"; -import { BPayDetailScreenNavigationParams } from "../../features/wallet/bancomatpay/screen/BPayDetailScreen"; -import { CobadgeDetailScreenNavigationParams } from "../../features/wallet/cobadge/screen/CobadgeDetailScreen"; -import { CreditCardDetailScreenNavigationParams } from "../../features/wallet/creditCard/screen/CreditCardDetailScreen"; -import { PaymentMethodOnboardingBPayParamsList } from "../../features/wallet/onboarding/bancomatPay/navigation/params"; -import WALLET_ONBOARDING_BPAY_ROUTES from "../../features/wallet/onboarding/bancomatPay/navigation/routes"; -import { PaymentMethodOnboardingCoBadgeParamsList } from "../../features/wallet/onboarding/cobadge/navigation/params"; -import WALLET_ONBOARDING_COBADGE_ROUTES from "../../features/wallet/onboarding/cobadge/navigation/routes"; -import { PaymentMethodOnboardingPayPalParamsList } from "../../features/wallet/onboarding/paypal/navigation/params"; -import PAYPAL_ROUTES from "../../features/wallet/onboarding/paypal/navigation/routes"; -import { PayPalPspUpdateScreenNavigationParams } from "../../features/wallet/paypal/screen/PayPalPspUpdateScreen"; -import { AddCardScreenNavigationParams } from "../../screens/wallet/AddCardScreen"; -import { AddCreditCardOutcomeCodeMessageNavigationParams } from "../../screens/wallet/AddCreditCardOutcomeCodeMessage"; -import { AddPaymentMethodScreenNavigationParams } from "../../screens/wallet/AddPaymentMethodScreen"; -import { ConfirmCardDetailsScreenNavigationParams } from "../../screens/wallet/ConfirmCardDetailsScreen"; -import { PaymentHistoryDetailsScreenNavigationParams } from "../../screens/wallet/PaymentHistoryDetailsScreen"; -import { TransactionDetailsScreenNavigationParams } from "../../screens/wallet/TransactionDetailsScreen"; -import { CreditCardOnboardingAttemptDetailScreenNavigationParams } from "../../screens/wallet/creditCardOnboardingAttempts/CreditCardOnboardingAttemptDetailScreen"; -import { ConfirmPaymentMethodScreenNavigationParams } from "../../screens/wallet/payment/ConfirmPaymentMethodScreen"; -import { ManualDataInsertionScreenNavigationParams } from "../../screens/wallet/payment/ManualDataInsertionScreen"; -import { PaymentOutcomeCodeMessageNavigationParams } from "../../screens/wallet/payment/PaymentOutcomeCodeMessage"; -import { PickPaymentMethodScreenNavigationParams } from "../../screens/wallet/payment/PickPaymentMethodScreen"; -import { PickPspScreenNavigationParams } from "../../screens/wallet/payment/PickPspScreen"; -import { TransactionErrorScreenNavigationParams } from "../../screens/wallet/payment/TransactionErrorScreen"; -import { TransactionSummaryScreenNavigationParams } from "../../screens/wallet/payment/TransactionSummaryScreen"; import ROUTES from "../routes"; import { BONUS_ROUTES } from "../../features/bonus/common/navigation/navigator"; export type WalletParamsList = { [ROUTES.WALLET_HOME]: undefined; [ROUTES.WALLET_IDPAY_INITIATIVE_LIST]: IdPayInstrumentInitiativesScreenRouteParams; - [ROUTES.WALLET_ADD_PAYMENT_METHOD]: AddPaymentMethodScreenNavigationParams; - [ROUTES.WALLET_TRANSACTION_DETAILS]: TransactionDetailsScreenNavigationParams; - [ROUTES.WALLET_CREDIT_CARD_DETAIL]: CreditCardDetailScreenNavigationParams; - [ROUTES.WALLET_BANCOMAT_DETAIL]: BancomatDetailScreenNavigationParams; - [ROUTES.WALLET_PAYPAL_DETAIL]: undefined; - [ROUTES.WALLET_PAYPAL_UPDATE_PAYMENT_PSP]: PayPalPspUpdateScreenNavigationParams; - [ROUTES.WALLET_BPAY_DETAIL]: BPayDetailScreenNavigationParams; - [ROUTES.WALLET_COBADGE_DETAIL]: CobadgeDetailScreenNavigationParams; - [ROUTES.WALLET_ADD_CARD]: AddCardScreenNavigationParams; - [ROUTES.WALLET_CONFIRM_CARD_DETAILS]: ConfirmCardDetailsScreenNavigationParams; - [ROUTES.PAYMENT_SCAN_QR_CODE]: undefined; - [ROUTES.PAYMENT_MANUAL_DATA_INSERTION]: ManualDataInsertionScreenNavigationParams; - [ROUTES.PAYMENT_TRANSACTION_SUMMARY]: TransactionSummaryScreenNavigationParams; - [ROUTES.PAYMENT_TRANSACTION_ERROR]: TransactionErrorScreenNavigationParams; - [ROUTES.PAYMENT_CONFIRM_PAYMENT_METHOD]: ConfirmPaymentMethodScreenNavigationParams; - [ROUTES.PAYMENT_PICK_PSP]: PickPspScreenNavigationParams; - [ROUTES.PAYMENT_PICK_PAYMENT_METHOD]: PickPaymentMethodScreenNavigationParams; - [ROUTES.PAYMENTS_HISTORY_SCREEN]: undefined; - [ROUTES.PAYMENT_HISTORY_DETAIL_INFO]: PaymentHistoryDetailsScreenNavigationParams; - [ROUTES.CREDIT_CARD_ONBOARDING_ATTEMPTS_SCREEN]: undefined; - [ROUTES.CREDIT_CARD_ONBOARDING_ATTEMPT_DETAIL]: CreditCardOnboardingAttemptDetailScreenNavigationParams; - [ROUTES.ADD_CREDIT_CARD_OUTCOMECODE_MESSAGE]: AddCreditCardOutcomeCodeMessageNavigationParams; - [ROUTES.PAYMENT_OUTCOMECODE_MESSAGE]: PaymentOutcomeCodeMessageNavigationParams; [BONUS_ROUTES.MAIN]: undefined; - - [WALLET_ONBOARDING_BPAY_ROUTES.MAIN]: NavigatorScreenParams; - [WALLET_ONBOARDING_COBADGE_ROUTES.MAIN]: NavigatorScreenParams; - [PAYPAL_ROUTES.ONBOARDING - .MAIN]: NavigatorScreenParams; }; diff --git a/ts/navigation/routes.ts b/ts/navigation/routes.ts index 01191b3b220..a17aeac8e53 100644 --- a/ts/navigation/routes.ts +++ b/ts/navigation/routes.ts @@ -64,38 +64,13 @@ const ROUTES = { ONBOARDING_EMAIL_VERIFICATION_SCREEN: "ONBOARDING_EMAIL_VERIFICATION_SCREEN", // Wallet - WALLET_NAVIGATOR: "WALLET_NAVIGATOR", WALLET_HOME: "WALLET_HOME", - WALLET_TRANSACTION_DETAILS: "WALLET_TRANSACTION_DETAILS", - WALLET_CREDIT_CARD_DETAIL: "WALLET_CREDIT_CARD_DETAIL", + + // IDPay WALLET_IDPAY_INITIATIVE_LIST: "WALLET_IDPAY_INITIATIVE_LIST", - WALLET_BANCOMAT_DETAIL: "WALLET_BANCOMAT_DETAIL", - WALLET_PAYPAL_DETAIL: "WALLET_PAYPAL_DETAIL", - WALLET_PAYPAL_UPDATE_PAYMENT_PSP: "WALLET_PAYPAL_UPDATE_PAYMENT_PSP", - WALLET_BPAY_DETAIL: "WALLET_BPAY_DETAIL", - WALLET_COBADGE_DETAIL: "WALLET_COBADGE_DETAIL", - WALLET_ADD_PAYMENT_METHOD: "WALLET_ADD_PAYMENT_METHOD", - WALLET_ADD_CARD: "WALLET_ADD_CARD", - WALLET_CONFIRM_CARD_DETAILS: "WALLET_CONFIRM_CARD_DETAILS", - WALLET_CHECKOUT_3DS_SCREEN: "WALLET_CHECKOUT_3DS_SCREEN", - ADD_CREDIT_CARD_OUTCOMECODE_MESSAGE: "ADD_CREDIT_CARD_OUTCOMECODE_MESSAGE", - - // Payment + + // Payments PAYMENTS_HOME: "PAYMENTS_HOME", - PAYMENT_SCAN_QR_CODE: "PAYMENT_SCAN_QR_CODE", - PAYMENT_MANUAL_DATA_INSERTION: "PAYMENT_MANUAL_DATA_INSERTION", - PAYMENT_TRANSACTION_SUMMARY: "PAYMENT_TRANSACTION_SUMMARY", - PAYMENT_TRANSACTION_ERROR: "PAYMENT_TRANSACTION_ERROR", - PAYMENT_PICK_PAYMENT_METHOD: "PAYMENT_PICK_PAYMENT_METHOD", - PAYMENT_CONFIRM_PAYMENT_METHOD: "PAYMENT_CONFIRM_PAYMENT_METHOD", - PAYMENT_PICK_PSP: "PAYMENT_PICK_PSP", - PAYMENTS_HISTORY_SCREEN: "PAYMENTS_HISTORY_SCREEN", - PAYMENT_HISTORY_DETAIL_INFO: "PAYMENT_HISTORY_DETAIL_INFO", - CREDIT_CARD_ONBOARDING_ATTEMPTS_SCREEN: - "CREDIT_CARD_ONBOARDING_ATTEMPTS_SCREEN", - CREDIT_CARD_ONBOARDING_ATTEMPT_DETAIL: - "CREDIT_CARD_ONBOARDING_ATTEMPT_DETAIL", - PAYMENT_OUTCOMECODE_MESSAGE: "PAYMENT_OUTCOMECODE_MESSAGE", // Main MAIN: "MAIN", diff --git a/ts/sagas/__tests__/wallet.test.ts b/ts/sagas/__tests__/wallet.test.ts deleted file mode 100644 index 09b501bbb2f..00000000000 --- a/ts/sagas/__tests__/wallet.test.ts +++ /dev/null @@ -1,178 +0,0 @@ -import * as O from "fp-ts/lib/Option"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { testSaga } from "redux-saga-test-plan"; -import { ActionType, getType } from "typesafe-actions"; -import { TypeEnum } from "../../../definitions/pagopa/Wallet"; -import { addCreditCardOutcomeCode } from "../../store/actions/wallet/outcomeCode"; -import { - addWalletCreditCardFailure, - addWalletCreditCardSuccess, - addWalletCreditCardWithBackoffRetryRequest, - addWalletNewCreditCardSuccess, - fetchWalletsFailure, - fetchWalletsRequest, - fetchWalletsSuccess, - refreshPMTokenWhileAddCreditCard, - runStartOrResumeAddCreditCardSaga -} from "../../store/actions/wallet/wallets"; -import { - lastPaymentOutcomeCodeSelector, - OutcomeCodeState -} from "../../store/reducers/wallet/outcomeCode"; -import { getAllWallets } from "../../store/reducers/wallet/wallets"; -import { OutcomeCode } from "../../types/outcomeCode"; -import { NullableWallet, PaymentManagerToken } from "../../types/pagopa"; -import { - CreditCardCVC, - CreditCardExpirationMonth, - CreditCardExpirationYear, - CreditCardPan -} from "../../utils/input"; -import { SessionManager } from "../../utils/SessionManager"; -import { testableWalletsSaga } from "../wallet"; -import { runDeleteActivePaymentSaga } from "../../store/actions/wallet/payment"; - -jest.mock("react-native-background-timer", () => ({ - startTimer: jest.fn() -})); - -jest.mock("react-native-share", () => ({ - open: jest.fn() -})); - -jest.mock("../../api/backend"); - -const walletState = { - walletById: pot.none, - creditCardAddWallet: pot.none, - creditCardVerification: pot.none -}; - -const anOutcomeCode: OutcomeCodeState = { - outcomeCode: O.some({ status: "success" } as OutcomeCode) -}; - -const aCreditCard = { - pan: "1234567891234567890" as CreditCardPan, - holder: "Maria Rossi", - expireMonth: "1" as CreditCardExpirationMonth, - expireYear: "25" as CreditCardExpirationYear, - securityCode: "123" as CreditCardCVC -}; - -const anAction = { - payload: { - creditCard: aCreditCard, - setAsFavorite: true, - onSuccess: undefined - } -} as ActionType; - -const aCreditCardWallet: NullableWallet = { - idWallet: undefined, - type: TypeEnum.CREDIT_CARD, - favourite: anAction.payload.setAsFavorite, - creditCard: anAction.payload.creditCard, - psp: undefined -}; -describe("startOrResumeAddCreditCardSaga", () => { - it("should add a card if all the 4 steps run sucessfully", () => { - const aPMToken = "1234" as PaymentManagerToken; - const aPmSessionManager: SessionManager = - new SessionManager(jest.fn(() => Promise.resolve(O.some(aPMToken)))); - const aNewPMToken = "5678" as PaymentManagerToken; - jest - .spyOn(aPmSessionManager, "getNewToken") - .mockReturnValue(Promise.resolve(O.some(aNewPMToken))); - const anIdWallet = 123456; - - const walletStateCardAdded = { - ...walletState, - creditCardAddWallet: pot.some({ data: { idWallet: anIdWallet } }) - }; - - testSaga( - testableWalletsSaga!.startOrResumeAddCreditCardSaga, - aPmSessionManager, - anAction - ) - // Step 1 - .next() - .select(getAllWallets) - .next(walletState) - .put( - addWalletCreditCardWithBackoffRetryRequest({ - creditcard: aCreditCardWallet - }) - ) - .next() - .take([addWalletCreditCardSuccess, addWalletCreditCardFailure]) - .next(getType(addWalletCreditCardSuccess)) - // Step 2 - .select(getAllWallets) - .next(walletStateCardAdded) - .put(refreshPMTokenWhileAddCreditCard.request({ idWallet: anIdWallet })) - .next() - .call(aPmSessionManager.getNewToken) - .next(O.some(aNewPMToken)) - .put(refreshPMTokenWhileAddCreditCard.success(aNewPMToken)) - .next() - .take(addCreditCardOutcomeCode) - .next() - .select(lastPaymentOutcomeCodeSelector) - .next(anOutcomeCode) - .put(addWalletNewCreditCardSuccess()) - .next() - .put(fetchWalletsRequest()) - .next() - .take([fetchWalletsSuccess, fetchWalletsFailure]) - .next({ - type: getType(fetchWalletsSuccess), - payload: [{ idWallet: anIdWallet }] - }) - .delay(testableWalletsSaga!.successScreenDelay) - .next(); - }); -}); - -describe("deleteUnsuccessfulActivePaymentSaga", () => { - describe("when there is no outcomeCode", () => { - it("it should do nothing", () => { - testSaga(testableWalletsSaga!.deleteUnsuccessfulActivePaymentSaga) - .next() - .select(lastPaymentOutcomeCodeSelector) - .next({ outcomeCode: O.none }) - .isDone(); - }); - }); - - describe("when there is an outcomeCode and it is not a success", () => { - it("it should put runDeleteActivePaymentSaga", () => { - testSaga(testableWalletsSaga!.deleteUnsuccessfulActivePaymentSaga) - .next() - .select(lastPaymentOutcomeCodeSelector) - .next({ outcomeCode: O.some({ status: "errorBlocking" }) }) - .put(runDeleteActivePaymentSaga()) - .next() - .isDone(); - - testSaga(testableWalletsSaga!.deleteUnsuccessfulActivePaymentSaga) - .next() - .select(lastPaymentOutcomeCodeSelector) - .next({ outcomeCode: O.some({ status: "errorTryAgain" }) }) - .put(runDeleteActivePaymentSaga()) - .next() - .isDone(); - }); - }); - - describe("when there is an outcomeCode and it is a success", () => { - it("it should do nothing", () => { - testSaga(testableWalletsSaga!.deleteUnsuccessfulActivePaymentSaga) - .next() - .select(lastPaymentOutcomeCodeSelector) - .next({ outcomeCode: O.some({ status: "success" }) }) - .isDone(); - }); - }); -}); diff --git a/ts/sagas/identification.ts b/ts/sagas/identification.ts index 79069fcc45c..39a5b78929e 100644 --- a/ts/sagas/identification.ts +++ b/ts/sagas/identification.ts @@ -15,17 +15,12 @@ import { identificationStart, identificationSuccess } from "../store/actions/identification"; -import { - paymentDeletePayment, - runDeleteActivePaymentSaga -} from "../store/actions/wallet/payment"; import { IdentificationCancelData, IdentificationGenericData, IdentificationResult, IdentificationSuccessData } from "../store/reducers/identification"; -import { paymentsCurrentStateSelector } from "../store/reducers/payments/current"; import { PinString } from "../types/PinString"; import { ReduxSagaEffect, SagaCallReturnType } from "../types/utils"; import { deletePin, getPin } from "../utils/keychain"; @@ -55,18 +50,6 @@ function* waitIdentificationResult(): Generator< return IdentificationResult.cancel; case getType(identificationPinReset): { - // If a payment is occurring, delete the active payment from pagoPA - const paymentState: ReturnType = - yield* select(paymentsCurrentStateSelector); - if (paymentState.kind === "ACTIVATED") { - yield* put(runDeleteActivePaymentSaga()); - // we try to wait until the payment deactivation is completed. If the request to backend fails for any reason, we proceed anyway with session invalidation - yield* take([ - paymentDeletePayment.failure, - paymentDeletePayment.success - ]); - } - // Invalidate the session yield* put(sessionInvalid()); diff --git a/ts/sagas/index.ts b/ts/sagas/index.ts index a3e55e04f1a..5088716af60 100644 --- a/ts/sagas/index.ts +++ b/ts/sagas/index.ts @@ -13,10 +13,6 @@ import { loadSystemPreferencesSaga } from "./preferences"; import { startupSaga } from "./startup"; import { removePersistedStatesSaga } from "./removePersistedStates"; -import { - watchBackToEntrypointPaymentSaga, - watchPaymentInitializeSaga -} from "./wallet"; import { watchIdentification } from "./identification"; import { watchApplicationActivitySaga } from "./startup/watchApplicationActivitySaga"; @@ -30,8 +26,6 @@ export default function* root() { call(loadSystemPreferencesSaga), call(removePersistedStatesSaga), call(watchContentSaga), - call(watchPaymentInitializeSaga), - call(watchBackToEntrypointPaymentSaga), call(watchTokenRefreshSaga), call(watchPendingActionsSaga), zendeskEnabled ? call(watchZendeskSupportSaga) : undefined diff --git a/ts/sagas/legacyWallet/pagopaApis.ts b/ts/sagas/legacyWallet/pagopaApis.ts new file mode 100644 index 00000000000..a4adb4e4659 --- /dev/null +++ b/ts/sagas/legacyWallet/pagopaApis.ts @@ -0,0 +1,66 @@ +import { RptId, RptIdFromString } from "@pagopa/io-pagopa-commons/lib/pagopa"; +import * as E from "fp-ts/lib/Either"; +import { call, put, select } from "typed-redux-saga/macro"; +import { ActionType } from "typesafe-actions"; +import { Action } from "redux"; +import { BackendClient } from "../../api/backend"; +import { paymentVerifica } from "../../store/actions/legacyWallet"; +import { isPagoPATestEnabledSelector } from "../../store/reducers/persistedPreferences"; +import { SagaCallReturnType } from "../../types/utils"; +import { getWalletError } from "../../utils/errors"; +import { readablePrivacyReport } from "../../utils/reporters"; +import { PaymentRequestsGetResponse } from "../../../definitions/backend/PaymentRequestsGetResponse"; +import { Detail_v2Enum } from "../../../definitions/backend/PaymentProblemJson"; +import { withRefreshApiCall } from "../../features/fastLogin/saga/utils"; + +export function* commonPaymentVerificationProcedure( + getVerificaRpt: ReturnType["getVerificaRpt"], + rptId: RptId, + successActionProvider: (paymentData: PaymentRequestsGetResponse) => A, + failureActionProvider: (details: Detail_v2Enum) => A, + action?: ActionType +) { + try { + const isPagoPATestEnabled: ReturnType = + yield* select(isPagoPATestEnabledSelector); + + const request = getVerificaRpt({ + rptId: RptIdFromString.encode(rptId), + test: isPagoPATestEnabled + }); + const response = (yield* call( + withRefreshApiCall, + request, + action as any + )) as SagaCallReturnType; + if (E.isRight(response)) { + if (response.right.status === 200) { + // Verifica succeeded + const paymentData = response.right.value; + const successAction = successActionProvider(paymentData); + yield* put(successAction); + } else if ( + response.right.status === 500 || + response.right.status === 504 + ) { + // Verifica failed with a 500 or 504, that usually means there was an error + // interacting with pagoPA that we can interpret + const details = response.right.value.detail_v2; + const failureAction = failureActionProvider(details); + yield* put(failureAction); + } else if (response.right.status === 401) { + // This status code does not represent an error to show to the user + // The authentication will be handled by the Fast Login token refresh procedure + } else { + throw Error(`response status ${response.right.status}`); + } + } else { + throw Error(readablePrivacyReport(response.left)); + } + } catch (e) { + // Probably a timeout + const details = getWalletError(e); + const failureAction = failureActionProvider(details); + yield* put(failureAction); + } +} diff --git a/ts/sagas/payments.ts b/ts/sagas/payments.ts deleted file mode 100644 index fe7153275b0..00000000000 --- a/ts/sagas/payments.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { put, select, take } from "typed-redux-saga/macro"; -import { ActionType, isActionOf } from "typesafe-actions"; -import { paymentsLastDeletedSet } from "../store/actions/payments"; -import { - paymentDeletePayment, - paymentInitializeState -} from "../store/actions/wallet/payment"; -import { paymentsCurrentStateSelector } from "../store/reducers/payments/current"; - -/** - * Detect an uncompleted payment and send the delete to the PaymentManager. - */ -export function* paymentsDeleteUncompletedSaga() { - const paymentsCurrentState: ReturnType = - yield* select(paymentsCurrentStateSelector); - - if (paymentsCurrentState.kind === "ACTIVATED") { - const { rptId } = paymentsCurrentState.initializationData; - const { idPayment } = paymentsCurrentState.activationData; - - yield* put(paymentDeletePayment.request({ paymentId: idPayment })); - - const resultAction = yield* take< - ActionType< - | typeof paymentDeletePayment.success - | typeof paymentDeletePayment.failure - > - >([paymentDeletePayment.success, paymentDeletePayment.failure]); - - if (isActionOf(paymentDeletePayment.success, resultAction)) { - yield* put( - paymentsLastDeletedSet({ - at: Date.now(), - rptId, - idPayment - }) - ); - } - - // Reinitialize the state of the Payment - yield* put(paymentInitializeState()); - } -} diff --git a/ts/sagas/startup.ts b/ts/sagas/startup.ts index 3eb8a03004a..3168d8533f7 100644 --- a/ts/sagas/startup.ts +++ b/ts/sagas/startup.ts @@ -99,7 +99,6 @@ import { import { ReduxSagaEffect, SagaCallReturnType } from "../types/utils"; import { trackKeychainFailures } from "../utils/analytics"; import { isTestEnv } from "../utils/environment"; -import { walletPaymentHandlersInitialized } from "../store/actions/wallet/payment"; import { watchFimsSaga } from "../features/fims/common/saga"; import { deletePin, getPin } from "../utils/keychain"; import { watchEmailValidationSaga } from "../store/sagas/emailValidationPollingSaga"; @@ -116,6 +115,7 @@ import { cancellAllLocalNotifications } from "../features/pushNotifications/util import { handleApplicationStartupTransientError } from "../features/startup/sagas"; import { formatRequestedTokenString } from "../features/zendesk/utils"; import { isBlockingScreenSelector } from "../features/ingress/store/selectors"; +import { watchLegacyTransactionSaga } from "../features/payments/transaction/store/saga"; import { startAndReturnIdentificationResult } from "./identification"; import { previousInstallationDataDeleteSaga } from "./installation"; import { @@ -152,7 +152,6 @@ import { watchLogoutSaga } from "./startup/watchLogoutSaga"; import { watchSessionExpiredSaga } from "./startup/watchSessionExpiredSaga"; import { checkItWalletIdentitySaga } from "./startup/checkItWalletIdentitySaga"; import { watchUserDataProcessingSaga } from "./user/userDataProcessing"; -import { watchWalletSaga } from "./wallet"; import { watchProfileEmailValidationChangedSaga } from "./watchProfileEmailValidationChangedSaga"; export const WAIT_INITIALIZE_SAGA = 5000 as Millisecond; @@ -605,8 +604,7 @@ export function* initializeApplicationSaga( yield* select(isPagoPATestEnabledSelector); yield* fork( - watchWalletSaga, - sessionToken, + watchLegacyTransactionSaga, walletToken, isPagoPATestEnabled ? pagoPaApiUrlPrefixTest : pagoPaApiUrlPrefix ); @@ -689,7 +687,7 @@ export function* initializeApplicationSaga( yield* put( applicationInitialized({ - actionsToWaitFor: [walletPaymentHandlersInitialized] + actionsToWaitFor: [] }) ); } diff --git a/ts/sagas/wallet.ts b/ts/sagas/wallet.ts deleted file mode 100644 index 5c7299ed3a7..00000000000 --- a/ts/sagas/wallet.ts +++ /dev/null @@ -1,824 +0,0 @@ -/** - * A saga that manages the Wallet. - */ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { DeferredPromise } from "@pagopa/ts-commons/lib/promises"; -import { Millisecond } from "@pagopa/ts-commons/lib/units"; -import { CommonActions } from "@react-navigation/native"; -import * as E from "fp-ts/lib/Either"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import { - call, - delay, - fork, - put, - select, - take, - takeEvery, - takeLatest -} from "typed-redux-saga/macro"; -import { ActionType, getType, isActionOf } from "typesafe-actions"; -import { TypeEnum } from "../../definitions/pagopa/Wallet"; -import { BackendClient } from "../api/backend"; -import { ContentClient } from "../api/content"; -import { PaymentManagerClient } from "../api/pagopa"; -import { - apiUrlPrefix, - fetchPagoPaTimeout, - fetchPaymentManagerLongTimeout -} from "../config"; -import { - handleAddPan, - handleLoadAbi, - handleLoadPans -} from "../features/wallet/onboarding/bancomat/saga/networking"; -import { - addBancomatToWallet, - loadAbi, - searchUserPans -} from "../features/wallet/onboarding/bancomat/store/actions"; -import { - handleAddpayToWallet, - handleSearchUserBPay -} from "../features/wallet/onboarding/bancomatPay/saga/networking"; -import { addBPayToWalletSaga } from "../features/wallet/onboarding/bancomatPay/saga/orchestration/addBPayToWallet"; -import { - addBPayToWalletAction, - searchUserBPay, - walletAddBPayStart -} from "../features/wallet/onboarding/bancomatPay/store/actions"; -import { - handleAddCoBadgeToWallet, - handleLoadCoBadgeConfiguration, - handleSearchUserCoBadge -} from "../features/wallet/onboarding/cobadge/saga/networking"; -import { addCoBadgeToWalletSaga } from "../features/wallet/onboarding/cobadge/saga/orchestration/addCoBadgeToWallet"; -import { - addCoBadgeToWallet, - loadCoBadgeAbiConfiguration, - searchUserCoBadge, - walletAddCoBadgeStart -} from "../features/wallet/onboarding/cobadge/store/actions"; -import { watchPaypalOnboardingSaga } from "../features/wallet/onboarding/paypal/saga"; -import NavigationService from "../navigation/NavigationService"; -import { navigateToWalletHome } from "../store/actions/navigation"; -import { profileLoadSuccess, profileUpsert } from "../store/actions/profile"; -import { deleteAllPaymentMethodsByFunction } from "../store/actions/wallet/delete"; -import { - addCreditCardOutcomeCode, - paymentOutcomeCode -} from "../store/actions/wallet/outcomeCode"; -import { - abortRunningPayment, - backToEntrypointPayment, - paymentAttiva, - paymentCheck, - paymentCompletedSuccess, - paymentDeletePayment, - paymentExecuteStart, - paymentIdPolling, - paymentInitializeEntrypointRoute, - paymentInitializeState, - paymentUpdateWalletPsp, - paymentVerifica, - pspForPaymentV2, - pspForPaymentV2WithCallbacks, - runDeleteActivePaymentSaga, - walletPaymentHandlersInitialized -} from "../store/actions/wallet/payment"; -import { - fetchPsp, - fetchTransactionRequest, - fetchTransactionsFailure, - fetchTransactionsRequest, - fetchTransactionsRequestWithExpBackoff -} from "../store/actions/wallet/transactions"; -import { - addWalletCreditCardFailure, - addWalletCreditCardRequest, - addWalletCreditCardSuccess, - addWalletCreditCardWithBackoffRetryRequest, - addWalletNewCreditCardFailure, - addWalletNewCreditCardSuccess, - deleteWalletRequest, - fetchWalletsFailure, - fetchWalletsRequest, - fetchWalletsRequestWithExpBackoff, - fetchWalletsSuccess, - refreshPMTokenWhileAddCreditCard, - runSendAddCobadgeTrackSaga, - runStartOrResumeAddCreditCardSaga, - setFavouriteWalletRequest, - setWalletSessionEnabled, - updatePaymentStatus -} from "../store/actions/wallet/wallets"; - -import { isProfileEmailValidatedSelector } from "../store/reducers/profile"; -import { GlobalState } from "../store/reducers/types"; -import { lastPaymentOutcomeCodeSelector } from "../store/reducers/wallet/outcomeCode"; -import { paymentIdSelector } from "../store/reducers/wallet/payment"; -import { getAllWallets } from "../store/reducers/wallet/wallets"; -import { SessionToken } from "../types/SessionToken"; -import { NullableWallet, PaymentManagerToken } from "../types/pagopa"; -import { ReduxSagaEffect } from "../types/utils"; -import { SessionManager } from "../utils/SessionManager"; -import { waitBackoffError } from "../utils/backoffError"; -import { isTestEnv } from "../utils/environment"; -import { convertUnknownToError } from "../utils/errors"; -import { defaultRetryingFetch } from "../utils/fetch"; -import { newLookUpId, resetLookUpId } from "../utils/pmLookUpId"; -import { paymentsDeleteUncompletedSaga } from "./payments"; -import { sendAddCobadgeMessageSaga } from "./wallet/cobadgeReminder"; -import { - addWalletCreditCardRequestHandler, - deleteAllPaymentMethodsByFunctionRequestHandler, - deleteWalletRequestHandler, - fetchPspRequestHandler, - fetchTransactionRequestHandler, - fetchTransactionsRequestHandler, - getPspV2, - getPspV2WithCallbacks, - getWallets, - paymentAttivaRequestHandler, - paymentCheckRequestHandler, - paymentDeletePaymentRequestHandler, - paymentIdPollingRequestHandler, - paymentStartRequest, - paymentVerificaRequestHandler, - setFavouriteWalletRequestHandler, - updatePaymentStatusSaga, - updateWalletPspRequestHandler -} from "./wallet/pagopaApis"; - -const successScreenDelay = 2000 as Millisecond; - -/** - * This saga manages the flow for adding a new card. - * - * Adding a new card can happen either from the wallet home screen or during the - * payment process from the payment method selection screen. - * - * To board a new card, we must complete the following steps: - * - * 1) add the card to the user wallets - * 2) complete the 3DS checkout for a fake payment - * - * This saga updates a state for each step, thus it can be run multiple times - * to resume the flow from the last successful step (retry behavior). - * - * This saga gets run from ConfirmCardDetailsScreen that is also responsible - * for showing relevant error and loading states to the user based on the - * potential state of the flow substates (see GlobalState.wallet.wallets). - * - */ -// eslint-disable-next-line sonarjs/cognitive-complexity -function* startOrResumeAddCreditCardSaga( - pmSessionManager: SessionManager, - action: ActionType -) { - // prepare a new wallet (payment method) that describes the credit card we - // want to add - const creditCardWallet: NullableWallet = { - idWallet: undefined, - type: TypeEnum.CREDIT_CARD, - favourite: action.payload.setAsFavorite, - creditCard: action.payload.creditCard, - psp: undefined - }; - newLookUpId(); - while (true) { - // before each step we select the updated payment state to know what has - // been already done. - const state: ReturnType = yield* select( - getAllWallets - ); - // - // First step: add the credit card to the user wallets - // - // Note that the new wallet will not be visibile to the user until all the - // card onboarding steps have been completed. - // - if (pot.isNone(state.creditCardAddWallet)) { - yield* put( - addWalletCreditCardWithBackoffRetryRequest({ - creditcard: creditCardWallet - }) - ); - const responseAction = yield* take< - ActionType< - typeof addWalletCreditCardSuccess | typeof addWalletCreditCardFailure - > - >([addWalletCreditCardSuccess, addWalletCreditCardFailure]); - if (isActionOf(addWalletCreditCardFailure, responseAction)) { - // this step failed, exit the flow - if ( - responseAction.payload.kind === "ALREADY_EXISTS" && - action.payload.onFailure - ) { - // if the card already exists, run onFailure before exiting the flow - action.payload.onFailure(responseAction.payload.kind); - } - break; - } - // all is ok, continue to the next step - continue; - } - - const { idWallet } = state.creditCardAddWallet.value.data; - - function* dispatchAddNewCreditCardFailure() { - yield* put(addWalletNewCreditCardFailure()); - if (action.payload.onFailure) { - action.payload.onFailure(); - } - } - - function* waitAndNavigateToWalletHome() { - // Add a delay to allow the user to see the thank you page - yield* delay(successScreenDelay); - yield* call(navigateToWalletHome); - } - - /** - * Second step: process the 3ds checkout: - * 1. Request a new token to the PM - * 2. Check the new token response: - * - If the token is returned successfully request all the wallets (to check if the temporary one is present in the wallet list) - * - else dispatch a failure action and call the onFailure function - * 3. Wait until the addCreditCardOutcomeCode action is dispatched -> the user exit from the webview - * 4. Check if lastPaymentOutcomeCodeSelector is some or none: - * - The value will be always some since we are waiting the dispatch of the addCreditCardOutcomeCode action - * 5. Check if lastPaymentOutcomeCodeSelector.status is success: - * - If success show the thank you page for 2 seconds and starts the bpd onboarding - * - If not success dispatch a failure action and show the ko page (the show of the ko page is - * managed inside the PayWebViewModal component) - * */ - try { - // change the store to loading just before ask for a new token - yield* put(refreshPMTokenWhileAddCreditCard.request({ idWallet })); - // Request a new token to the PM. This prevent expired token during the webview navigation. - // If the request for the new token fails a new Error is caught, the step fails and we exit the flow. - const pagoPaToken: O.Option = yield* call( - pmSessionManager.getNewToken - ); - if (O.isSome(pagoPaToken)) { - // store the pm session token - yield* put(refreshPMTokenWhileAddCreditCard.success(pagoPaToken.value)); - // Wait until the outcome code from the webview is available - yield* take(addCreditCardOutcomeCode); - - const maybeOutcomeCode: ReturnType< - typeof lastPaymentOutcomeCodeSelector - > = yield* select(lastPaymentOutcomeCodeSelector); - // Since we wait the dispatch of the addCreditCardOutcomeCode action, - // the else case can't happen, because the action in every case set a some value in the store. - if (O.isSome(maybeOutcomeCode.outcomeCode)) { - const outcomeCode = maybeOutcomeCode.outcomeCode.value; - - // The credit card was added successfully - if (outcomeCode.status === "success") { - yield* put(addWalletNewCreditCardSuccess()); - - // Get all the wallets - yield* put(fetchWalletsRequest()); - const fetchWalletsResultAction = yield* take< - ActionType< - typeof fetchWalletsSuccess | typeof fetchWalletsFailure - > - >([fetchWalletsSuccess, fetchWalletsFailure]); - if (isActionOf(fetchWalletsSuccess, fetchWalletsResultAction)) { - const updatedWallets = fetchWalletsResultAction.payload; - const maybeAddedWallet = updatedWallets.find( - _ => _.idWallet === idWallet - ); - - if (maybeAddedWallet !== undefined) { - // Add a delay to allow the user to see the thank you page - yield* delay(successScreenDelay); - // signal the completion - if (action.payload.onSuccess) { - action.payload.onSuccess(maybeAddedWallet); - } - } else { - // cant find wallet in wallet list - yield* call(waitAndNavigateToWalletHome); - } - } else { - // cant load wallets but credit card is added successfully - yield* call(waitAndNavigateToWalletHome); - break; - } - } else { - // outcome is different from success - yield* call(dispatchAddNewCreditCardFailure); - } - } else { - // the outcome is none - yield* call(dispatchAddNewCreditCardFailure); - } - } else { - yield* put( - refreshPMTokenWhileAddCreditCard.failure( - new Error("cant load pm session token") - ) - ); - // Cannot refresh wallet token - yield* call(dispatchAddNewCreditCardFailure); - break; - } - } catch (e) { - if (action.payload.onFailure) { - action.payload.onFailure( - // This cast should be safe enough conceptually. - convertUnknownToError(e).message as "ALREADY_EXISTS" | undefined - ); - } - } - break; - } - resetLookUpId(); -} - -/** - * This saga attempts to delete the active payment, if there's one. - * - * This is a best effort operation as the result is actually ignored. - */ -function* deleteActivePaymentSaga() { - const potPaymentId: ReturnType = yield* select( - paymentIdSelector - ); - const maybePaymentId = pot.toOption(potPaymentId); - // stop polling - shouldAbortPaymentIdPollingRequest.e2(true); - if (O.isSome(maybePaymentId)) { - yield* put( - paymentDeletePayment.request({ paymentId: maybePaymentId.value }) - ); - } -} - -/** - * this saga checks the outcome codes coming from a payment or from the payment-check done during the credit card onboarding - * if the outcome exits and it is different from "success" it tries to delete the payment activation - */ -function* deleteUnsuccessfulActivePaymentSaga() { - // it can be related to a payment or a payment check done during the credit card onboarding - const lastPaymentOutCome = yield* select(lastPaymentOutcomeCodeSelector); - if ( - pipe( - lastPaymentOutCome.outcomeCode, - O.exists(({ status }) => status !== "success") - ) - ) { - /** - * run the procedure to delete the payment activation - * even if there is no one running (that check is done by the relative saga) - */ - yield* put(runDeleteActivePaymentSaga()); - } -} - -/** - * this saga delete a payment just before the user pays - * it should be invoked from the payment UX - */ -function* abortRunningPaymentSaga() { - // delete the active payment from pagoPA - yield* put(runDeleteActivePaymentSaga()); - // navigate to entrypoint of payment or wallet home - yield* put(backToEntrypointPayment()); -} - -// this is a shared DeferredPromise used to stop polling when user aborts a running payment - -// eslint-disable-next-line functional/no-let -let shouldAbortPaymentIdPollingRequest = DeferredPromise(); -/** - * Main wallet saga. - * - * This saga is responsible for handling actions the mostly correspond to API - * requests towards the pagoPA "nodo" and the pagoPA "PaymentManager" APIs. - * - * This saga gets forked from the startup saga each time the user authenticates - * and a new PagopaToken gets received from the backend. Infact, the - * pagoPaClient passed as paramenter to this saga, embeds the PagopaToken. - */ - -export function* watchWalletSaga( - sessionToken: SessionToken, - walletToken: string, - paymentManagerUrlPrefix: string -): Generator { - // Builds a backend client specifically for the pagopa-proxy endpoints that - // need a fetch instance that doesn't retry requests and have longer timeout - const pagopaNodoClient = BackendClient( - apiUrlPrefix, - sessionToken, - {}, - defaultRetryingFetch(fetchPagoPaTimeout, 0) - ); - - // Backend client for polling for paymentId - uses an instance of fetch that - // considers a 404 as a transient error and retries with a constant delay - const pollingPagopaNodoClient = BackendClient(apiUrlPrefix, sessionToken); - - // Client for the PagoPA PaymentManager - const paymentManagerClient: PaymentManagerClient = PaymentManagerClient( - paymentManagerUrlPrefix, - walletToken, - // despite both fetch have same configuration, keeping both ensures possible modding - defaultRetryingFetch(fetchPaymentManagerLongTimeout, 0), - defaultRetryingFetch(fetchPaymentManagerLongTimeout, 0) - ); - - // Helper function that requests a new session token from the PaymentManager. - // When calling the PM APIs, we must use separate session, generated from the - // walletToken. - const getPaymentManagerSession = async () => { - try { - const response = await paymentManagerClient.getSession(walletToken); - if (E.isRight(response) && response.right.status === 200) { - return O.some(response.right.value.data.sessionToken); - } - return O.none; - } catch { - return O.none; - } - }; - - // The session manager for the PagoPA PaymentManager (PM) will manage the - // refreshing of the PM session when calling its APIs, keeping a shared token - // and serializing the refresh requests. - const pmSessionManager = new SessionManager(getPaymentManagerSession); - // check if the current profile (this saga starts only when the user is logged in) - // has an email address validated - const isEmailValidated = yield* select(isProfileEmailValidatedSelector); - yield* call(pmSessionManager.setSessionEnabled, isEmailValidated); - // - // Sagas - // - - yield* takeLatest( - getType(runStartOrResumeAddCreditCardSaga), - startOrResumeAddCreditCardSaga, - pmSessionManager - ); - - yield* takeLatest( - getType(runDeleteActivePaymentSaga), - deleteActivePaymentSaga - ); - - yield* takeLatest( - [paymentOutcomeCode, addCreditCardOutcomeCode], - deleteUnsuccessfulActivePaymentSaga - ); - - // - yield* takeLatest(getType(abortRunningPayment), abortRunningPaymentSaga); - - // - // API requests - // - - yield* takeLatest( - getType(fetchTransactionsRequest), - fetchTransactionsRequestHandler, - paymentManagerClient, - pmSessionManager - ); - - yield* takeLatest( - getType(fetchTransactionsRequestWithExpBackoff), - function* ( - action: ActionType - ) { - yield* call(waitBackoffError, fetchTransactionsFailure); - yield* put(fetchTransactionsRequest(action.payload)); - } - ); - - yield* takeLatest( - getType(fetchTransactionRequest), - fetchTransactionRequestHandler, - paymentManagerClient, - pmSessionManager - ); - - yield* takeLatest(getType(fetchWalletsRequestWithExpBackoff), function* () { - yield* call(waitBackoffError, fetchWalletsFailure); - yield* put(fetchWalletsRequest()); - }); - - yield* takeLatest( - getType(fetchWalletsRequest), - getWallets, - paymentManagerClient, - pmSessionManager - ); - - yield* takeLatest( - getType(addWalletCreditCardRequest), - addWalletCreditCardRequestHandler, - paymentManagerClient, - pmSessionManager - ); - - yield* takeLatest( - getType(addWalletCreditCardWithBackoffRetryRequest), - function* ( - action: ActionType - ) { - yield* call(waitBackoffError, addWalletCreditCardFailure); - yield* put(addWalletCreditCardRequest(action.payload)); - } - ); - - yield* takeLatest( - getType(setFavouriteWalletRequest), - setFavouriteWalletRequestHandler, - paymentManagerClient, - pmSessionManager - ); - - yield* takeLatest( - getType(paymentUpdateWalletPsp.request), - updateWalletPspRequestHandler, - paymentManagerClient, - pmSessionManager - ); - - yield* takeLatest( - getType(deleteAllPaymentMethodsByFunction.request), - deleteAllPaymentMethodsByFunctionRequestHandler, - paymentManagerClient, - pmSessionManager - ); - - yield* takeLatest( - getType(deleteWalletRequest), - deleteWalletRequestHandler, - paymentManagerClient, - pmSessionManager - ); - - yield* takeLatest( - getType(paymentVerifica.request), - paymentVerificaRequestHandler, - pagopaNodoClient.getVerificaRpt - ); - - yield* takeLatest( - getType(paymentAttiva.request), - paymentAttivaRequestHandler, - pagopaNodoClient.postAttivaRpt - ); - - yield* takeLatest( - getType(paymentIdPolling.request), - function* (action: ActionType<(typeof paymentIdPolling)["request"]>) { - // getPaymentId is a tuple2 - // e1: deferredPromise, used to abort the constantPollingFetch - // e2: the fetch to execute - const getPaymentId = pollingPagopaNodoClient.getPaymentId(); - shouldAbortPaymentIdPollingRequest = getPaymentId.e1; - yield* call(paymentIdPollingRequestHandler, getPaymentId, action); - } - ); - - yield* takeLatest( - getType(paymentCheck.request), - paymentCheckRequestHandler, - paymentManagerClient, - pmSessionManager - ); - - yield* takeLatest( - getType(paymentExecuteStart.request), - paymentStartRequest, - pmSessionManager - ); - - yield* takeLatest( - getType(paymentDeletePayment.request), - paymentDeletePaymentRequestHandler, - paymentManagerClient, - pmSessionManager - ); - - yield* takeLatest( - getType(fetchPsp.request), - fetchPspRequestHandler, - paymentManagerClient, - pmSessionManager - ); - - yield* put(walletPaymentHandlersInitialized()); - - /** - * whenever the profile is loaded (from a load request or from un update) - * check if the email is validated. If it not the session manager has to be disabled - */ - yield* takeLatest( - [getType(profileUpsert.success), getType(profileLoadSuccess)], - checkProfile - ); - - yield* takeLatest( - getType(setWalletSessionEnabled), - setWalletSessionEnabledSaga, - pmSessionManager - ); - - yield* takeLatest( - getType(updatePaymentStatus.request), - updatePaymentStatusSaga, - paymentManagerClient, - pmSessionManager - ); - - yield* takeLatest( - getType(pspForPaymentV2.request), - getPspV2, - paymentManagerClient.getPspV2, - pmSessionManager - ); - - yield* takeLatest( - getType(pspForPaymentV2WithCallbacks), - getPspV2WithCallbacks - ); - - // here it used to check for BPD enabled - const contentClient = ContentClient(); - - // watch for load abi request - yield* takeLatest(loadAbi.request, handleLoadAbi, contentClient.getAbiList); - - // watch for load pans request - yield* takeLatest( - searchUserPans.request, - handleLoadPans, - paymentManagerClient.getPans, - pmSessionManager - ); - - // watch for add pan request - yield* takeLatest( - addBancomatToWallet.request, - handleAddPan, - paymentManagerClient.addPans, - pmSessionManager - ); - // watch for add BPay to Wallet workflow - yield* takeLatest(walletAddBPayStart, addBPayToWalletSaga); - - // watch for BancomatPay search request - yield* takeLatest( - searchUserBPay.request, - handleSearchUserBPay, - paymentManagerClient.searchBPay, - pmSessionManager - ); - // watch for add BancomatPay to the user's wallet - yield* takeLatest( - addBPayToWalletAction.request, - handleAddpayToWallet, - paymentManagerClient.addBPayToWallet, - pmSessionManager - ); - - // watch for CoBadge search request - yield* takeLatest( - searchUserCoBadge.request, - handleSearchUserCoBadge, - paymentManagerClient.getCobadgePans, - paymentManagerClient.searchCobadgePans, - pmSessionManager - ); - // watch for add CoBadge to the user's wallet - yield* takeLatest( - addCoBadgeToWallet.request, - handleAddCoBadgeToWallet, - paymentManagerClient.addCobadgeToWallet, - pmSessionManager - ); - // watch for CoBadge configuration request - yield* takeLatest( - loadCoBadgeAbiConfiguration.request, - handleLoadCoBadgeConfiguration, - contentClient.getCobadgeServices - ); - - // watch for add co-badge to Wallet workflow - yield* takeLatest(walletAddCoBadgeStart, addCoBadgeToWalletSaga); - - yield* fork( - watchPaypalOnboardingSaga, - paymentManagerClient, - pmSessionManager - ); - // end of BPDEnabled check - - // Check if a user has a bancomat and has not requested a cobadge yet and send - // the information to mixpanel - yield* takeLatest(runSendAddCobadgeTrackSaga, sendAddCobadgeMessageSaga); - yield* fork(paymentsDeleteUncompletedSaga); -} - -function* checkProfile( - action: - | ActionType - | ActionType -) { - const enabled = - action.type === getType(profileUpsert.success) - ? action.payload.newValue.is_email_validated === true - : action.payload.is_email_validated === true; - yield* put(setWalletSessionEnabled(enabled)); -} - -function* enableSessionManager( - enable: boolean, - sessionManager: SessionManager -) { - yield* call(sessionManager.setSessionEnabled, enable); -} - -/** - * enable the Session Manager to perform request with a fresh token - * otherwise the Session Manager doesn't refresh the token and it doesn't - * perform requests to payment manager - * @param sessionManager - * @param action - */ -function* setWalletSessionEnabledSaga( - sessionManager: SessionManager, - action: ActionType -): Iterator { - yield* call(enableSessionManager, action.payload, sessionManager); -} -/** - * This saga checks what is the route whence a new payment is started - */ -export function* watchPaymentInitializeSaga(): Iterator { - yield* takeEvery(getType(paymentInitializeState), function* () { - const currentRouteName = NavigationService.getCurrentRouteName(); - const currentRouteKey = NavigationService.getCurrentRouteKey(); - if (currentRouteName !== undefined && currentRouteKey !== undefined) { - yield* put( - paymentInitializeEntrypointRoute({ - name: currentRouteName, - key: currentRouteKey - }) - ); - } - }); - - /** - * create and destroy the PM lookUpID through the payment flow - * more details https://www.pivotaltracker.com/story/show/177132354 - */ - yield* takeEvery(getType(paymentInitializeState), function* () { - newLookUpId(); - yield* take< - ActionType< - typeof paymentCompletedSuccess | typeof runDeleteActivePaymentSaga - > - >([paymentCompletedSuccess, runDeleteActivePaymentSaga]); - resetLookUpId(); - }); -} - -/** - * This saga back to entrypoint payment if the payment was initiated from the message list or detail - * otherwise if the payment starts in scan qr code screen or in Manual data insertion screen - * it makes one or two supplementary step backs (the correspondant step to wallet home from these screens) - */ -export function* watchBackToEntrypointPaymentSaga(): Iterator { - yield* takeEvery(getType(backToEntrypointPayment), function* () { - const entrypointRoute: GlobalState["wallet"]["payment"]["entrypointRoute"] = - yield* select(_ => _.wallet.payment.entrypointRoute); - if (entrypointRoute !== undefined) { - yield* call( - NavigationService.dispatchNavigationAction, - CommonActions.navigate({ - name: entrypointRoute.name, - merge: true - }) - ); - yield* put(paymentInitializeState()); - } - }); -} - -// to keep solid code encapsulation -export const testableWalletsSaga = isTestEnv - ? { - startOrResumeAddCreditCardSaga, - successScreenDelay, - deleteUnsuccessfulActivePaymentSaga - } - : undefined; diff --git a/ts/sagas/wallet/__tests__/cobadgeRemainder.test.ts b/ts/sagas/wallet/__tests__/cobadgeRemainder.test.ts deleted file mode 100644 index 3113c433844..00000000000 --- a/ts/sagas/wallet/__tests__/cobadgeRemainder.test.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { testSaga } from "redux-saga-test-plan"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { getType } from "typesafe-actions"; -import { sendAddCobadgeMessageSaga } from "../cobadgeReminder"; -import { - bancomatListVisibleInWalletSelector, - cobadgeListVisibleInWalletSelector -} from "../../../store/reducers/wallet/wallets"; -import { sendAddCobadgeMessage } from "../../../store/actions/wallet/wallets"; -import { StatusEnum } from "../../../../definitions/pagopa/cobadge/configuration/CoBadgeService"; -import { - BancomatPaymentMethod, - CreditCardPaymentMethod -} from "../../../types/pagopa"; -import { TypeEnum } from "../../../../definitions/pagopa/walletv2/CardInfo"; -import { coBadgeAbiConfigurationSelector } from "../../../features/wallet/onboarding/cobadge/store/reducers/abiConfiguration"; -import { loadCoBadgeAbiConfiguration } from "../../../features/wallet/onboarding/cobadge/store/actions"; - -const anAbiCode = "123"; -const anotherAbiCode = "456"; -const aBancomat = { - walletType: "Bancomat", - kind: "Bancomat", - info: { - issuerAbiCode: anAbiCode - } -} as BancomatPaymentMethod; - -const aCoBadge = { - walletType: "Card", - kind: "CreditCard", - pagoPA: false, - info: { - issuerAbiCode: anotherAbiCode, - type: TypeEnum.CRD - } -} as CreditCardPaymentMethod; - -describe("sendAddCobadgeMessageSaga", () => { - it("should dispatch the sendAddCobadgeMessage action with payload false if there isn't at least one bancomat", () => { - testSaga(sendAddCobadgeMessageSaga) - .next() - .select(bancomatListVisibleInWalletSelector) - .next(pot.some([])) - .put(sendAddCobadgeMessage(false)); - }); - it("should dispatch the sendAddCobadgeMessage action with payload true if there is at least one bancomat, the abi is in the abiConfig and is enabled and there isn't a co-badge with the same abi", () => { - testSaga(sendAddCobadgeMessageSaga) - .next() - .select(bancomatListVisibleInWalletSelector) - .next(pot.some([aBancomat])) - .select(coBadgeAbiConfigurationSelector) - .next(pot.none) - .put(loadCoBadgeAbiConfiguration.request()) - .next() - .take([ - loadCoBadgeAbiConfiguration.success, - loadCoBadgeAbiConfiguration.failure - ]) - .next(getType(loadCoBadgeAbiConfiguration.success)) - .select(coBadgeAbiConfigurationSelector) - .next(pot.some({ "123": StatusEnum.enabled })) - .select(cobadgeListVisibleInWalletSelector) - .next(pot.some([aCoBadge])) - .put(sendAddCobadgeMessage(true)); - }); - it("should dispatch the sendAddCobadgeMessage action with payload false if there is at least one bancomat, the abi is in the abiConfig and is not enabled", () => { - testSaga(sendAddCobadgeMessageSaga) - .next() - .select(bancomatListVisibleInWalletSelector) - .next(pot.some([aBancomat])) - .select(coBadgeAbiConfigurationSelector) - .next(pot.some({ "123": StatusEnum.disabled })) - .select(coBadgeAbiConfigurationSelector) - .next(pot.some({ "123": StatusEnum.disabled })) - .select(cobadgeListVisibleInWalletSelector) - .next(pot.some([aCoBadge])) - .put(sendAddCobadgeMessage(false)); - }); - it("should dispatch the sendAddCobadgeMessage action with payload false if there is at least one bancomat, the abi is not in the abiConfig", () => { - testSaga(sendAddCobadgeMessageSaga) - .next() - .select(bancomatListVisibleInWalletSelector) - .next(pot.some([aBancomat])) - .select(coBadgeAbiConfigurationSelector) - .next(pot.some({ "789": StatusEnum.disabled })) - .select(coBadgeAbiConfigurationSelector) - .next(pot.some({ "789": StatusEnum.disabled })) - .select(cobadgeListVisibleInWalletSelector) - .next(pot.some([aCoBadge])) - .put(sendAddCobadgeMessage(false)); - }); - it("should dispatch the sendAddCobadgeMessage action with payload false if there is at least one bancomat and there is a co-badge with the same abi", () => { - testSaga(sendAddCobadgeMessageSaga) - .next() - .select(bancomatListVisibleInWalletSelector) - .next(pot.some([aBancomat])) - .select(coBadgeAbiConfigurationSelector) - .next(pot.some({ "123": StatusEnum.disabled })) - .select(coBadgeAbiConfigurationSelector) - .next(pot.some({ "123": StatusEnum.disabled })) - .select(cobadgeListVisibleInWalletSelector) - .next( - pot.some([ - { ...aCoBadge, info: { ...aCoBadge.info, issuerAbiCode: anAbiCode } } - ]) - ) - .put(sendAddCobadgeMessage(false)); - }); - it("should dispatch the sendAddCobadgeMessage action with payload false if there is at least one bancomat but without the issuerAbiCode", () => { - testSaga(sendAddCobadgeMessageSaga) - .next() - .select(bancomatListVisibleInWalletSelector) - .next(pot.some([{ ...aBancomat, info: {} }])) - .select(coBadgeAbiConfigurationSelector) - .next(pot.some({ "123": StatusEnum.disabled })) - .select(coBadgeAbiConfigurationSelector) - .next(pot.some({ "123": StatusEnum.disabled })) - .select(cobadgeListVisibleInWalletSelector) - .next(pot.some([aCoBadge])) - .put(sendAddCobadgeMessage(false)); - }); -}); diff --git a/ts/sagas/wallet/__tests__/pagopaApisPaymentStartRequest.test.ts b/ts/sagas/wallet/__tests__/pagopaApisPaymentStartRequest.test.ts deleted file mode 100644 index 2fcdf433e28..00000000000 --- a/ts/sagas/wallet/__tests__/pagopaApisPaymentStartRequest.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import * as O from "fp-ts/lib/Option"; -import { testSaga } from "redux-saga-test-plan"; -import { paymentExecuteStart } from "../../../store/actions/wallet/payment"; -import { PaymentManagerToken } from "../../../types/pagopa"; -import { SessionManager } from "../../../utils/SessionManager"; -import { paymentStartRequest } from "../pagopaApis"; - -jest.mock("@react-native-async-storage/async-storage", () => ({ - AsyncStorage: jest.fn() -})); - -jest.mock("react-native-share", () => ({ - open: jest.fn() -})); - -jest.mock("react-native-background-timer", () => ({ - startTimer: jest.fn() -})); - -describe("paymentStartRequest", () => { - it("can get a pm token", () => { - const mockToken = "1234" as PaymentManagerToken; - const aPmSessionManager: SessionManager = - new SessionManager(jest.fn()); - jest - .spyOn(aPmSessionManager, "getNewToken") - .mockReturnValue(Promise.resolve(O.some(mockToken))); - testSaga(paymentStartRequest, aPmSessionManager) - .next() - .call(aPmSessionManager.getNewToken) - .next(O.some(mockToken)) - .put(paymentExecuteStart.success(mockToken)) - .next(); - }); - - it("cannot get a pm token", () => { - const aPmSessionManager: SessionManager = - new SessionManager(jest.fn()); - jest - .spyOn(aPmSessionManager, "getNewToken") - .mockReturnValue(Promise.resolve(O.none)); - testSaga(paymentStartRequest, aPmSessionManager) - .next() - .call(aPmSessionManager.getNewToken) - .next(O.none) - .put( - paymentExecuteStart.failure( - new Error("cannot retrieve a valid PM session token") - ) - ) - .next(); - }); -}); diff --git a/ts/sagas/wallet/cobadgeReminder.ts b/ts/sagas/wallet/cobadgeReminder.ts deleted file mode 100644 index cd96841fd24..00000000000 --- a/ts/sagas/wallet/cobadgeReminder.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { put, select, take } from "typed-redux-saga/macro"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { ActionType, isActionOf } from "typesafe-actions"; -import { - bancomatListVisibleInWalletSelector, - cobadgeListVisibleInWalletSelector -} from "../../store/reducers/wallet/wallets"; -import { - BancomatPaymentMethod, - CreditCardPaymentMethod -} from "../../types/pagopa"; -import { coBadgeAbiConfigurationSelector } from "../../features/wallet/onboarding/cobadge/store/reducers/abiConfiguration"; -import { IndexedById } from "../../store/helpers/indexer"; -import { StatusEnum } from "../../../definitions/pagopa/cobadge/configuration/CoBadgeService"; -import { NetworkError } from "../../utils/errors"; -import { loadCoBadgeAbiConfiguration } from "../../features/wallet/onboarding/cobadge/store/actions"; -import { sendAddCobadgeMessage } from "../../store/actions/wallet/wallets"; - -/** - * This saga aims to send an event to Mixpanel to get information about whether the user has a bancomat card with which - * it is allowed to add a co-badge card and has not yet done or not. - * - * This saga is called only if the {@link bancomatListVisibleInWalletSelector} return some - */ -export function* sendAddCobadgeMessageSaga() { - // Check if there is at least one bancomat - const maybeBancomatListVisibleInWallet: pot.Pot< - ReadonlyArray, - Error - > = yield* select(bancomatListVisibleInWalletSelector); - - const bancomatListVisibleInWallet = pot.getOrElse( - maybeBancomatListVisibleInWallet, - [] - ); - - if (bancomatListVisibleInWallet.length === 0) { - yield* put(sendAddCobadgeMessage(false)); - return; - } - - // Check if the abiConfiguration is Some - // and if not request the abiConfiguration - if (!pot.isSome(yield* select(coBadgeAbiConfigurationSelector))) { - yield* put(loadCoBadgeAbiConfiguration.request()); - - // Wait for the request results - const loadCoBadgeAbiRes = yield* take< - ActionType< - | typeof loadCoBadgeAbiConfiguration.success - | typeof loadCoBadgeAbiConfiguration.failure - > - >([ - loadCoBadgeAbiConfiguration.success, - loadCoBadgeAbiConfiguration.failure - ]); - - // If the request result is failure return - if (isActionOf(loadCoBadgeAbiConfiguration.failure, loadCoBadgeAbiRes)) { - return; - } - } - const maybeCoBadgeAbiConfiguration: pot.Pot< - IndexedById, - NetworkError - > = yield* select(coBadgeAbiConfigurationSelector); - - if (pot.isSome(maybeCoBadgeAbiConfiguration)) { - const coBadgeAbiConfiguration = maybeCoBadgeAbiConfiguration.value; - - // Extract the cobadgeAbi if there is at least a cobadge card - const maybeCobadgeVisibleInWallet: pot.Pot< - ReadonlyArray, - Error - > = yield* select(cobadgeListVisibleInWalletSelector); - - const cobadgeVisibleInWallet = pot.getOrElse( - maybeCobadgeVisibleInWallet, - [] - ); - const cobadgeAbis = cobadgeVisibleInWallet.map( - cWithAbi => cWithAbi.info.issuerAbiCode - ); - // Extract a list of abi that satisfy the following conditions: - // - is a bancomat in the wallet of the user - // - the abi of the bancomat is in the abiConfiguration list - // - the abi of the bancomant has the status enabled in the abiConfiguration list - // - there isn't a cobadge card in the wallet of the user with the sami abi - const enabledAbis = bancomatListVisibleInWallet.filter( - b => - b.info.issuerAbiCode !== undefined && - coBadgeAbiConfiguration[b.info.issuerAbiCode] === StatusEnum.enabled && - !cobadgeAbis.some(abi => abi === b.info.issuerAbiCode) - ); - - yield* put(sendAddCobadgeMessage(enabledAbis.length > 0)); - } -} diff --git a/ts/sagas/wallet/pagopaApis.ts b/ts/sagas/wallet/pagopaApis.ts deleted file mode 100644 index f273f43833f..00000000000 --- a/ts/sagas/wallet/pagopaApis.ts +++ /dev/null @@ -1,934 +0,0 @@ -import { RptId, RptIdFromString } from "@pagopa/io-pagopa-commons/lib/pagopa"; -import * as E from "fp-ts/lib/Either"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import { call, put, select, take } from "typed-redux-saga/macro"; -import { ActionType, isActionOf } from "typesafe-actions"; -import { Action } from "redux"; -import { IOToast } from "@pagopa/io-app-design-system"; -import { BackendClient } from "../../api/backend"; -import { PaymentManagerClient } from "../../api/pagopa"; -import { mixpanelTrack } from "../../mixpanel"; -import { getFilteredPspsList } from "../../screens/wallet/payment/common"; -import { checkCurrentSession } from "../../store/actions/authentication"; -import { deleteAllPaymentMethodsByFunction } from "../../store/actions/wallet/delete"; -import { - paymentAttiva, - paymentCheck, - paymentDeletePayment, - paymentExecuteStart, - paymentIdPolling, - paymentUpdateWalletPsp, - paymentVerifica, - pspForPaymentV2, - pspForPaymentV2WithCallbacks -} from "../../store/actions/wallet/payment"; -import { - fetchPsp, - fetchTransactionFailure, - fetchTransactionRequest, - fetchTransactionsFailure, - fetchTransactionsRequest, - fetchTransactionsSuccess, - fetchTransactionSuccess -} from "../../store/actions/wallet/transactions"; -import { - addWalletCreditCardFailure, - addWalletCreditCardRequest, - addWalletCreditCardSuccess, - deleteWalletFailure, - deleteWalletRequest, - deleteWalletSuccess, - fetchWalletsFailure, - fetchWalletsSuccess, - setFavouriteWalletFailure, - setFavouriteWalletRequest, - setFavouriteWalletSuccess, - updatePaymentStatus -} from "../../store/actions/wallet/wallets"; -import { preferredPspsByOriginSelector } from "../../store/reducers/backendStatus"; -import { isPagoPATestEnabledSelector } from "../../store/reducers/persistedPreferences"; -import { paymentStartOriginSelector } from "../../store/reducers/wallet/payment"; -import { PaymentManagerToken, Wallet } from "../../types/pagopa"; -import { ReduxSagaEffect, SagaCallReturnType } from "../../types/utils"; -import { - convertUnknownToError, - getError, - getErrorFromNetworkError, - getGenericError, - getNetworkError, - getWalletError, - isTimeoutError -} from "../../utils/errors"; -import { readablePrivacyReport } from "../../utils/reporters"; -import { SessionManager } from "../../utils/SessionManager"; -import { convertWalletV2toWalletV1 } from "../../utils/walletv2"; -import I18n from "../../i18n"; -import { PaymentRequestsGetResponse } from "../../../definitions/backend/PaymentRequestsGetResponse"; -import { Detail_v2Enum } from "../../../definitions/backend/PaymentProblemJson"; -import { withRefreshApiCall } from "../../features/fastLogin/saga/utils"; - -// -// Payment Manager APIs -// - -/** - * Handles fetchWalletsRequest - */ - -export function* getWallets( - pagoPaClient: PaymentManagerClient, - pmSessionManager: SessionManager -): Generator>, any> { - return yield* call(getWalletsV2, pagoPaClient, pmSessionManager); -} - -// load wallet from api /v2/wallet -// it converts walletV2 into walletV1 -export function* getWalletsV2( - pagoPaClient: PaymentManagerClient, - pmSessionManager: SessionManager -): Generator>, any> { - try { - void mixpanelTrack("WALLETS_LOAD_REQUEST"); - const request = pmSessionManager.withRefresh(pagoPaClient.getWalletsV2); - const getResponse: SagaCallReturnType = yield* call( - request - ); - if (E.isRight(getResponse)) { - if (getResponse.right.status === 200) { - const wallets = (getResponse.right.value.data ?? []).map( - convertWalletV2toWalletV1 - ); - void mixpanelTrack("WALLETS_LOAD_SUCCESS", { - count: wallets.length - }); - yield* put(fetchWalletsSuccess(wallets)); - return E.right>(wallets); - } else { - throw Error(`response status ${getResponse.right.status}`); - } - } else { - throw Error(readablePrivacyReport(getResponse.left)); - } - } catch (e) { - const computedError = convertUnknownToError(e); - - // this is required to handle 401 response from PM - // On 401 response sessionManager retries for X attempts to get a valid session - // If it exceeds a fixed threshold of attempts a max retries error will be dispatched - - void mixpanelTrack("WALLETS_LOAD_FAILURE", { - reason: computedError.message - }); - - if (isTimeoutError(getNetworkError(e))) { - // check if also the IO session is expired - yield* put(checkCurrentSession.request()); - } - - yield* put(fetchWalletsFailure(computedError)); - return E.left>(computedError); - } -} - -/** - * Handles fetchTransactionsRequest - */ -export function* fetchTransactionsRequestHandler( - pagoPaClient: PaymentManagerClient, - pmSessionManager: SessionManager, - action: ActionType -): Generator { - const request = pmSessionManager.withRefresh( - pagoPaClient.getTransactions(action.payload.start) - ); - try { - const response: SagaCallReturnType = yield* call(request); - if (E.isRight(response)) { - if (response.right.status === 200) { - yield* put( - fetchTransactionsSuccess({ - data: response.right.value.data, - total: O.fromNullable(response.right.value.total) - }) - ); - } else { - throw Error(`response status ${response.right.status}`); - } - } else { - throw Error(readablePrivacyReport(response.left)); - } - } catch (e) { - if (isTimeoutError(getNetworkError(e))) { - // check if also the IO session is expired - yield* put(checkCurrentSession.request()); - } - yield* put(fetchTransactionsFailure(convertUnknownToError(e))); - } -} - -/** - * Handles fetchTransactionRequest - */ -export function* fetchTransactionRequestHandler( - pagoPaClient: PaymentManagerClient, - pmSessionManager: SessionManager, - action: ActionType -): Generator { - const request = pmSessionManager.withRefresh( - pagoPaClient.getTransaction(action.payload) - ); - try { - const response: SagaCallReturnType = yield* call(request); - if (E.isRight(response)) { - if (response.right.status === 200) { - yield* put(fetchTransactionSuccess(response.right.value.data)); - } else { - throw Error(`response status ${response.right.status}`); - } - } else { - throw Error(readablePrivacyReport(response.left)); - } - } catch (e) { - if (isTimeoutError(getNetworkError(e))) { - // check if also the IO session is expired - yield* put(checkCurrentSession.request()); - } - yield* put(fetchTransactionFailure(convertUnknownToError(e))); - } -} - -/** - * Handles fetchPspRequest - */ -export function* fetchPspRequestHandler( - pagoPaClient: PaymentManagerClient, - pmSessionManager: SessionManager, - action: ActionType<(typeof fetchPsp)["request"]> -): Generator { - const request = pmSessionManager.withRefresh( - pagoPaClient.getPsp(action.payload.idPsp) - ); - try { - const response: SagaCallReturnType = yield* call(request); - - if (E.isRight(response)) { - if (response.right.status === 200) { - const psp = response.right.value.data; - const successAction = fetchPsp.success({ - idPsp: action.payload.idPsp, - psp - }); - yield* put(successAction); - if (action.payload.onSuccess) { - action.payload.onSuccess(successAction); - } - } else { - throw Error(`response status ${response.right.status}`); - } - } else { - throw Error(readablePrivacyReport(response.left)); - } - } catch (e) { - const failureAction = fetchPsp.failure({ - idPsp: action.payload.idPsp, - error: convertUnknownToError(e) - }); - if (isTimeoutError(getNetworkError(e))) { - // check if also the IO session is expired - yield* put(checkCurrentSession.request()); - } - yield* put(failureAction); - if (action.payload.onFailure) { - action.payload.onFailure(failureAction); - } - } -} - -/** - * Handles the update of payment method status - * (enable or disable a payment method to pay with pagoPa) - */ -export function* updatePaymentStatusSaga( - pagoPaClient: PaymentManagerClient, - pmSessionManager: SessionManager, - action: ActionType -): Generator { - const updatePayment = pagoPaClient.updatePaymentStatus(action.payload); - const request = pmSessionManager.withRefresh(updatePayment); - try { - const response: SagaCallReturnType = yield* call(request); - if (E.isRight(response)) { - if (response.right.status === 200) { - if (response.right.value.data) { - yield* put( - updatePaymentStatus.success( - convertWalletV2toWalletV1(response.right.value.data) - ) - ); - IOToast.success( - I18n.t("wallet.methods.card.pagoPaCapability.operationCompleted") - ); - } else { - // this should not never happen (payload weak typed) - yield* put( - updatePaymentStatus.failure( - getNetworkError(Error("payload is empty")) - ) - ); - } - } else { - const errorStatus = Error(`response status ${response.right.status}`); - yield* put(updatePaymentStatus.failure(getNetworkError(errorStatus))); - } - } else { - const errorDescription = Error(readablePrivacyReport(response.left)); - yield* put( - updatePaymentStatus.failure(getNetworkError(errorDescription)) - ); - } - } catch (error) { - if (isTimeoutError(getNetworkError(error))) { - // check if also the IO session is expired - yield* put(checkCurrentSession.request()); - } - yield* put(updatePaymentStatus.failure(getNetworkError(error))); - } -} - -/** - * Handles setFavouriteWalletRequest - */ -export function* setFavouriteWalletRequestHandler( - pagoPaClient: PaymentManagerClient, - pmSessionManager: SessionManager, - action: ActionType -): Generator { - const favouriteWalletId = action.payload; - if (favouriteWalletId === undefined) { - // FIXME: currently there is no way to unset a favourite wallet - return; - } - const setFavouriteWallet = pagoPaClient.favouriteWallet(favouriteWalletId); - - const request = pmSessionManager.withRefresh(setFavouriteWallet); - try { - const response: SagaCallReturnType = yield* call(request); - if (E.isRight(response)) { - if (response.right.status === 200) { - yield* put(setFavouriteWalletSuccess(response.right.value.data)); - } else { - throw Error(`response status ${response.right.status}`); - } - } else { - throw Error(readablePrivacyReport(response.left)); - } - } catch (e) { - if (isTimeoutError(getNetworkError(e))) { - // check if also the IO session is expired - yield* put(checkCurrentSession.request()); - } - yield* put(setFavouriteWalletFailure(convertUnknownToError(e))); - } -} - -/** - * Updates a Wallet with a new favorite PSP - * - * TODO: consider avoiding the fetch, let the application logic decide - */ -// eslint-disable-next-line -export function* updateWalletPspRequestHandler( - pagoPaClient: PaymentManagerClient, - pmSessionManager: SessionManager, - action: ActionType<(typeof paymentUpdateWalletPsp)["request"]> -) { - // First update the selected wallet (walletId) with the - // new PSP (action.payload); then request a new list - // of wallets (which will contain the updated PSP) - const { wallet, psp, idPayment } = action.payload; - const { id: idPsp } = psp; - - const apiUpdateWalletPsp = pagoPaClient.updateWalletPsp(wallet.idWallet, { - data: { idPsp, idPagamentoFromEC: idPayment } - }); - const updateWalletPspWithRefresh = - pmSessionManager.withRefresh(apiUpdateWalletPsp); - - try { - const response: SagaCallReturnType = - yield* call(updateWalletPspWithRefresh); - if (E.isRight(response)) { - if (response.right.status === 200) { - const maybeWallets: SagaCallReturnType = yield* call( - getWallets, - pagoPaClient, - pmSessionManager - ); - if (E.isRight(maybeWallets)) { - // look for the updated wallet - const updatedWallet = maybeWallets.right.find( - _ => _.idWallet === wallet.idWallet - ); - if (updatedWallet !== undefined) { - // the wallet is still there, we can proceed - const successAction = paymentUpdateWalletPsp.success({ - wallets: maybeWallets.right, - // attention: updatedWallet is V1 - updatedWallet: response.right.value.data - }); - yield* put(successAction); - if (action.payload.onSuccess) { - // signal the callee if requested - action.payload.onSuccess(successAction); - } - } else { - // oops, the wallet is not there anymore! - throw Error(`response status ${response.right.status}`); - } - } else { - throw maybeWallets.left; - } - } else { - // oops, the wallet is not there anymore! - throw Error(`response status ${response.right.status}`); - } - } else { - throw Error(readablePrivacyReport(response.left)); - } - } catch (e) { - if (isTimeoutError(getNetworkError(e))) { - // check if also the IO session is expired - yield* put(checkCurrentSession.request()); - } - const failureAction = paymentUpdateWalletPsp.failure( - convertUnknownToError(e) - ); - yield* put(failureAction); - if (action.payload.onFailure) { - // signal the callee if requested - action.payload.onFailure(failureAction); - } - } -} - -/** - * delete all those payment methods that have the specified function enabled - * @param pagoPaClient - * @param pmSessionManager - * @param action - */ -export function* deleteAllPaymentMethodsByFunctionRequestHandler( - pagoPaClient: PaymentManagerClient, - pmSessionManager: SessionManager, - action: ActionType -): Generator { - const deleteAllByFunctionApi = pagoPaClient.deleteAllPaymentMethodsByFunction( - action.payload as string - ); - const deleteAllByFunctionApiWithRefresh = pmSessionManager.withRefresh( - deleteAllByFunctionApi - ); - - try { - const deleteResponse: SagaCallReturnType< - typeof deleteAllByFunctionApiWithRefresh - > = yield* call(deleteAllByFunctionApiWithRefresh); - if (E.isRight(deleteResponse) && deleteResponse.right.status === 200) { - const deletedMethodsCount = - deleteResponse.right.value.data?.deletedWallets ?? -1; - - const remainingWallets = ( - deleteResponse.right.value.data?.remainingWallets ?? [] - ).map(convertWalletV2toWalletV1); - - const successAction = deleteAllPaymentMethodsByFunction.success({ - wallets: remainingWallets, - deletedMethodsCount - }); - yield* put(successAction); - } else { - const error = Error( - pipe( - deleteResponse, - E.foldW( - readablePrivacyReport, - ({ status }) => `response status ${status}` - ) - ) - ); - yield* put( - deleteAllPaymentMethodsByFunction.failure({ - error - }) - ); - } - } catch (e) { - if (isTimeoutError(getNetworkError(e))) { - // check if also the IO session is expired - yield* put(checkCurrentSession.request()); - } - yield* put( - deleteAllPaymentMethodsByFunction.failure({ - error: getErrorFromNetworkError(getNetworkError(e)) - }) - ); - } -} - -/** - * Handles deleteWalletRequest - * - * TODO: consider avoiding the fetch, let the appliction logic decide - */ -// eslint-disable-next-line -export function* deleteWalletRequestHandler( - pagoPaClient: PaymentManagerClient, - pmSessionManager: SessionManager, - action: ActionType -): Generator { - const deleteWalletApi = pagoPaClient.deleteWallet(action.payload.walletId); - const deleteWalletWithRefresh = pmSessionManager.withRefresh(deleteWalletApi); - - try { - const deleteResponse: SagaCallReturnType = - yield* call(deleteWalletWithRefresh); - if (E.isRight(deleteResponse) && deleteResponse.right.status === 200) { - const maybeWallets: SagaCallReturnType = yield* call( - getWallets, - pagoPaClient, - pmSessionManager - ); - if (E.isRight(maybeWallets)) { - const successAction = deleteWalletSuccess(maybeWallets.right); - yield* put(successAction); - if (action.payload.onSuccess) { - action.payload.onSuccess(successAction); - } - } else { - throw maybeWallets.left; - } - } else { - throw Error( - pipe( - deleteResponse, - E.foldW( - readablePrivacyReport, - ({ status }) => `response status ${status}` - ) - ) - ); - } - } catch (e) { - if (isTimeoutError(getNetworkError(e))) { - // check if also the IO session is expired - yield* put(checkCurrentSession.request()); - } - const failureAction = deleteWalletFailure( - e instanceof Error ? e : new Error() - ); - yield* put(failureAction); - if (action.payload.onFailure) { - action.payload.onFailure(failureAction); - } - } -} - -/** - * Handles addWalletCreditCardRequest - */ -export function* addWalletCreditCardRequestHandler( - pagoPaClient: PaymentManagerClient, - pmSessionManager: SessionManager, - action: ActionType -) { - const boardCreditCard = pagoPaClient.addWalletCreditCard( - action.payload.creditcard - ); - const boardCreditCardWithRefresh = - pmSessionManager.withRefresh(boardCreditCard); - - try { - const response: SagaCallReturnType = - yield* call(boardCreditCardWithRefresh); - - if (E.isRight(response)) { - if (response.right.status === 200) { - yield* put(addWalletCreditCardSuccess(response.right.value)); - } else if ( - response.right.status === 422 && - response.right.value.message === "creditcard.already_exists" - ) { - yield* put(addWalletCreditCardFailure({ kind: "ALREADY_EXISTS" })); - } else { - throw Error(`response status ${response.right.status}`); - } - } else { - throw Error(readablePrivacyReport(response.left)); - } - } catch (e) { - if (isTimeoutError(getNetworkError(e))) { - // check if also the IO session is expired - yield* put(checkCurrentSession.request()); - } - yield* put( - addWalletCreditCardFailure({ - kind: "GENERIC_ERROR", - reason: getError(e).message - }) - ); - } -} - -/** - * Handles paymentCheckRequest - */ -export function* paymentCheckRequestHandler( - pagoPaClient: PaymentManagerClient, - pmSessionManager: SessionManager, - action: ActionType<(typeof paymentCheck)["request"]> -): Generator { - // FIXME: we should not use default pagopa client for checkpayment, need to - // a client that doesn't retry on failure!!! checkpayment is NOT - // idempotent, the 2nd time it will error! - const apiCheckPayment = () => pagoPaClient.checkPayment(action.payload); - const checkPaymentWithRefresh = pmSessionManager.withRefresh(apiCheckPayment); - try { - const response: SagaCallReturnType = - yield* call(checkPaymentWithRefresh); - if (E.isRight(response)) { - if ( - response.right.status === 200 || - (response.right.status as number) === 422 - ) { - // TODO: remove the cast of response.status to number as soon as the - // paymentmanager specs include the 422 status. - // https://www.pivotaltracker.com/story/show/161053093 - yield* put(paymentCheck.success(true)); - } else { - throw response.right; - } - } else { - throw Error(readablePrivacyReport(response.left)); - } - } catch (e) { - if (isTimeoutError(getNetworkError(e))) { - // check if also the IO session is expired - yield* put(checkCurrentSession.request()); - } - yield* put(paymentCheck.failure(e instanceof Error ? e : new Error())); - } -} - -/** - * handle the start of a payment - * we already know which is the payment (idPayment) and the used wallet to pay (idWallet) - * we need a fresh PM session token to start the challenge into the PayWebViewModal - * @param pmSessionManager - */ -export function* paymentStartRequest( - pmSessionManager: SessionManager -) { - const pmSessionToken: O.Option = yield* call( - pmSessionManager.getNewToken - ); - if (O.isSome(pmSessionToken)) { - yield* put(paymentExecuteStart.success(pmSessionToken.value)); - } else { - yield* put( - paymentExecuteStart.failure( - new Error("cannot retrieve a valid PM session token") - ) - ); - } -} - -/** - * Handles paymentDeletePaymentRequest - */ -export function* paymentDeletePaymentRequestHandler( - pagoPaClient: PaymentManagerClient, - pmSessionManager: SessionManager, - action: ActionType<(typeof paymentDeletePayment)["request"]> -): Generator { - const apiPostPayment = pagoPaClient.deletePayment(action.payload.paymentId); - const request = pmSessionManager.withRefresh(apiPostPayment); - try { - const response: SagaCallReturnType = yield* call(request); - - if (E.isRight(response)) { - if (response.right.status === 200) { - yield* put(paymentDeletePayment.success()); - } else { - throw Error(`response status ${response.right.status}`); - } - } else { - throw Error(readablePrivacyReport(response.left)); - } - } catch (e) { - if (isTimeoutError(getNetworkError(e))) { - // check if also the IO session is expired - yield* put(checkCurrentSession.request()); - } - yield* put( - paymentDeletePayment.failure(e instanceof Error ? e : new Error()) - ); - } -} - -// -// Nodo APIs -// - -/** - * Handles paymentVerificaRequest - */ -export function* paymentVerificaRequestHandler( - getVerificaRpt: ReturnType["getVerificaRpt"], - action: ActionType<(typeof paymentVerifica)["request"]> -) { - yield* call( - commonPaymentVerificationProcedure, - getVerificaRpt, - action.payload.rptId, - paymentData => paymentVerifica.success(paymentData), - details => paymentVerifica.failure(details), - action - ); -} - -export function* commonPaymentVerificationProcedure( - getVerificaRpt: ReturnType["getVerificaRpt"], - rptId: RptId, - successActionProvider: (paymentData: PaymentRequestsGetResponse) => A, - failureActionProvider: (details: Detail_v2Enum) => A, - action?: ActionType<(typeof paymentVerifica)["request"]> -) { - try { - const isPagoPATestEnabled: ReturnType = - yield* select(isPagoPATestEnabledSelector); - - const request = getVerificaRpt({ - rptId: RptIdFromString.encode(rptId), - test: isPagoPATestEnabled - }); - const response: SagaCallReturnType = (yield* call( - withRefreshApiCall, - request, - action - )) as unknown as SagaCallReturnType; - if (E.isRight(response)) { - if (response.right.status === 200) { - // Verifica succeeded - const paymentData = response.right.value; - const successAction = successActionProvider(paymentData); - yield* put(successAction); - } else if ( - response.right.status === 500 || - response.right.status === 504 - ) { - // Verifica failed with a 500 or 504, that usually means there was an error - // interacting with pagoPA that we can interpret - const details = response.right.value.detail_v2; - const failureAction = failureActionProvider(details); - yield* put(failureAction); - } else if (response.right.status === 401) { - // This status code does not represent an error to show to the user - // The authentication will be handled by the Fast Login token refresh procedure - } else { - throw Error(`response status ${response.right.status}`); - } - } else { - throw Error(readablePrivacyReport(response.left)); - } - } catch (e) { - // Probably a timeout - const details = getWalletError(e); - const failureAction = failureActionProvider(details); - yield* put(failureAction); - } -} - -/** - * Handles paymentAttivaRequest - */ -export function* paymentAttivaRequestHandler( - postAttivaRpt: ReturnType["postAttivaRpt"], - action: ActionType<(typeof paymentAttiva)["request"]> -) { - try { - const isPagoPATestEnabled: ReturnType = - yield* select(isPagoPATestEnabledSelector); - - const request = postAttivaRpt({ - body: { - rptId: RptIdFromString.encode(action.payload.rptId), - codiceContestoPagamento: - action.payload.verifica.codiceContestoPagamento, - importoSingoloVersamento: - action.payload.verifica.importoSingoloVersamento - }, - test: isPagoPATestEnabled - }); - - const response: SagaCallReturnType = (yield* call( - withRefreshApiCall, - request, - action - )) as unknown as SagaCallReturnType; - - if (E.isRight(response)) { - if (response.right.status === 200) { - // Attiva succeeded - yield* put(paymentAttiva.success(response.right.value)); - } else if ( - response.right.status === 500 || - response.right.status === 504 - ) { - // Attiva failed - throw Error(response.right.value.detail_v2); - } else if (response.right.status === 401) { - // This status code does not represent an error to show to the user - // The authentication will be handled by the Fast Login token refresh procedure - } else { - throw Error(`response status ${response.right.status}`); - } - } else { - throw Error(readablePrivacyReport(response.left)); - } - } catch (e) { - // Probably a timeout - yield* put(paymentAttiva.failure(getWalletError(e))); - } -} - -/** - * Handles paymentIdPollingRequest - * - * Polls the backend for the paymentId linked to the payment context code - */ -export function* paymentIdPollingRequestHandler( - getPaymentIdApi: ReturnType["getPaymentId"]>, - action: ActionType<(typeof paymentIdPolling)["request"]> -) { - // successfully request the payment activation - // now poll until a paymentId is made available - - try { - const isPagoPATestEnabled: ReturnType = - yield* select(isPagoPATestEnabledSelector); - - const getPaymentId = getPaymentIdApi.e2; - - const request = getPaymentId({ - codiceContestoPagamento: action.payload.codiceContestoPagamento, - test: isPagoPATestEnabled - }); - - const response: SagaCallReturnType = (yield* call( - withRefreshApiCall, - request, - action - )) as unknown as SagaCallReturnType; - - if (E.isRight(response)) { - // Attiva succeeded - if (response.right.status === 200) { - yield* put(paymentIdPolling.success(response.right.value.idPagamento)); - } else if (response.right.status === 400) { - // Attiva failed - throw Error("PAYMENT_ID_TIMEOUT"); - } else { - throw Error(`response status ${response.right.status}`); - } - } else { - throw Error(readablePrivacyReport(response.left)); - } - } catch (e) { - yield* put(paymentIdPolling.failure("PAYMENT_ID_TIMEOUT")); - } -} - -/** - * request the list of psp that can handle a payment from a given idWallet and idPayment - * @param getPspV2 - * @param pmSessionManager - * @param action - */ -export function* getPspV2( - getPspV2: ReturnType["getPspV2"], - pmSessionManager: SessionManager, - action: ActionType<(typeof pspForPaymentV2)["request"]> -) { - const getPspV2Request = getPspV2(action.payload); - const request = pmSessionManager.withRefresh(getPspV2Request); - try { - const response: SagaCallReturnType = yield* call(request); - if (E.isRight(response)) { - if (response.right.status === 200) { - const psps = response.right.value.data; - - const paymentStartOrigin = yield* select(paymentStartOriginSelector); - const preferredPspsByOrigin = yield* select( - preferredPspsByOriginSelector - ); - - const filteredPsps = getFilteredPspsList( - psps, - paymentStartOrigin, - preferredPspsByOrigin - ); - yield* put(pspForPaymentV2.success(filteredPsps)); - } else { - yield* put( - pspForPaymentV2.failure( - getGenericError(Error(`response status ${response.right.status}`)) - ) - ); - } - } else { - yield* put( - pspForPaymentV2.failure( - getGenericError(Error(readablePrivacyReport(response.left))) - ) - ); - } - } catch (e) { - if (isTimeoutError(getNetworkError(e))) { - // check if also the IO session is expired - yield* put(checkCurrentSession.request()); - } - yield* put(pspForPaymentV2.failure(getNetworkError(e))); - } -} - -/** - * @deprecated this function request and handle the psp list by using a callback approach - * it should not be used! - * Use instead {@link pspForPaymentV2} action and relative saga {@link getPspV2} to retrieve the psp list - */ -export function* getPspV2WithCallbacks( - action: ActionType -) { - yield* put(pspForPaymentV2.request(action.payload)); - const result = yield* take< - ActionType - >([pspForPaymentV2.success, pspForPaymentV2.failure]); - if (isActionOf(pspForPaymentV2.failure, result)) { - action.payload.onFailure(); - } else if (isActionOf(pspForPaymentV2.success, result)) { - const psps = result.payload; - - const paymentStartOrigin = yield* select(paymentStartOriginSelector); - const preferredPspsByOrigin = yield* select(preferredPspsByOriginSelector); - - const filteredPsps = getFilteredPspsList( - psps, - paymentStartOrigin, - preferredPspsByOrigin - ); - - action.payload.onSuccess(filteredPsps); - } -} diff --git a/ts/screens/profile/DeveloperModeSection.tsx b/ts/screens/profile/DeveloperModeSection.tsx index f70cdd96f1c..e77452c4674 100644 --- a/ts/screens/profile/DeveloperModeSection.tsx +++ b/ts/screens/profile/DeveloperModeSection.tsx @@ -26,7 +26,6 @@ import { isFastLoginEnabledSelector } from "../../features/fastLogin/store/selec import { lollipopPublicKeySelector } from "../../features/lollipop/store/reducers/lollipop"; import { toThumbprint } from "../../features/lollipop/utils/crypto"; import { notificationsInstallationSelector } from "../../features/pushNotifications/store/reducers/installation"; -import { walletAddCoBadgeStart } from "../../features/wallet/onboarding/cobadge/store/actions"; import { useIONavigation } from "../../navigation/params/AppParamsList"; import ROUTES from "../../navigation/routes"; import { sessionExpired } from "../../store/actions/authentication"; @@ -326,14 +325,8 @@ const DesignSystemSection = () => { }; const PlaygroundsSection = () => { - const dispatch = useIODispatch(); const navigation = useIONavigation(); const isIdPayTestEnabled = useIOSelector(isIdPayTestEnabledSelector); - const isPagoPATestEnabled = useIOSelector(isPagoPATestEnabledSelector); - - const onAddTestCard = () => { - dispatch(walletAddCoBadgeStart(undefined)); - }; const playgroundsNavListItems: ReadonlyArray = [ { @@ -393,11 +386,6 @@ const PlaygroundsSection = () => { navigation.navigate(ITW_ROUTES.MAIN, { screen: ITW_ROUTES.PLAYGROUNDS }) - }, - { - condition: isPagoPATestEnabled, - value: I18n.t("profile.main.addTestCard.title"), - onPress: onAddTestCard } ]; diff --git a/ts/screens/wallet/AddCardScreen.tsx b/ts/screens/wallet/AddCardScreen.tsx deleted file mode 100644 index 4bbc71b1908..00000000000 --- a/ts/screens/wallet/AddCardScreen.tsx +++ /dev/null @@ -1,472 +0,0 @@ -/** - * Screen for entering the credit card details - * (holder, pan, cvc, expiration date) - */ - -import { - ContentWrapper, - FooterWithButtons, - HSpacer, - IOColors, - IOToast, - VSpacer -} from "@pagopa/io-app-design-system"; -import { AmountInEuroCents, RptId } from "@pagopa/io-pagopa-commons/lib/pagopa"; -import { Route, useRoute } from "@react-navigation/native"; -import * as E from "fp-ts/lib/Either"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import React, { useState } from "react"; -import { - Keyboard, - SafeAreaView, - ScrollView, - StyleSheet, - View -} from "react-native"; -import { PaymentRequestsGetResponse } from "../../../definitions/backend/PaymentRequestsGetResponse"; -import { LabelledItem } from "../../components/LabelledItem"; -import SectionStatusComponent from "../../components/SectionStatus"; -import { Link } from "../../components/core/typography/Link"; -import BaseScreenComponent, { - ContextualHelpPropsMarkdown -} from "../../components/screens/BaseScreenComponent"; -import I18n from "../../i18n"; -import { useIONavigation } from "../../navigation/params/AppParamsList"; -import { navigateToWalletConfirmCardDetails } from "../../store/actions/navigation"; -import { CreditCard } from "../../types/pagopa"; -import { acceptedPaymentMethodsFaqUrl } from "../../urls"; -import { useScreenReaderEnabled } from "../../utils/accessibility"; -import { CreditCardDetector, SupportedBrand } from "../../utils/creditCard"; -import { isExpired } from "../../utils/dates"; -import { isTestEnv } from "../../utils/environment"; -import { useLuhnValidation } from "../../utils/hooks/useLuhnValidation"; -import { - CreditCardExpirationMonth, - CreditCardExpirationYear, - CreditCardState, - CreditCardStateKeys, - INITIAL_CARD_FORM_STATE, - MIN_PAN_DIGITS, - getCreditCardFromState, - isValidCardHolder, - isValidPan, - isValidSecurityCode -} from "../../utils/input"; -import { openWebUrl } from "../../utils/url"; - -export type AddCardScreenNavigationParams = Readonly<{ - inPayment: O.Option<{ - rptId: RptId; - initialAmount: AmountInEuroCents; - verifica: PaymentRequestsGetResponse; - idPayment: string; - }>; - keyFrom?: string; -}>; - -const styles = StyleSheet.create({ - creditCardForm: { - height: 24, - width: 24 - }, - whiteBg: { - backgroundColor: IOColors.white - }, - flex: { - flex: 1 - } -}); - -const contextualHelpMarkdown: ContextualHelpPropsMarkdown = { - title: "wallet.saveCard.contextualHelpTitle", - body: "wallet.saveCard.contextualHelpContent" -}; - -const openSupportedCardsPage = (): void => { - openWebUrl(acceptedPaymentMethodsFaqUrl, () => - IOToast.error(I18n.t("wallet.alert.supportedCardPageLinkError")) - ); -}; - -const usePrimaryButtonPropsFromState = ( - state: CreditCardState, - onNavigate: (card: CreditCard) => void, - isHolderValid: boolean, - isExpirationDateValid?: boolean -): React.ComponentProps["primary"] => { - const { isCardNumberValid, isCvvValid } = useLuhnValidation( - pipe( - state.pan, - O.getOrElse(() => "") - ), - pipe( - state.securityCode, - O.getOrElse(() => "") - ) - ); - - const card = getCreditCardFromState(state); - - return pipe( - card, - E.foldW( - e => ({ - type: "Solid", - buttonProps: { - disabled: true, - accessibilityLabel: e, - // eslint-disable-next-line @typescript-eslint/no-empty-function - onPress: () => {}, - label: I18n.t("global.buttons.continue") - } - }), - c => ({ - type: "Solid", - buttonProps: { - disabled: - !isCardNumberValid || - !isCvvValid || - !isHolderValid || - !isExpirationDateValid, - onPress: () => { - Keyboard.dismiss(); - onNavigate(c); - }, - accessibilityLabel: I18n.t("global.buttons.continue"), - label: I18n.t("global.buttons.continue") - } - }) - ) - ); -}; - -// return some(true) if the date is invalid or expired -// none if it can't be evaluated -const isCreditCardDateExpiredOrInvalid = ( - expireDate: O.Option -): O.Option => - pipe( - expireDate, - O.chain(date => { - // split the date in two parts: month, year - const splitted = date.split("/"); - if (splitted.length !== 2) { - return O.none; - } - - return O.some([splitted[0], splitted[1]]); - }), - O.chain(my => { - // if the input is not in the required format mm/yy - if ( - !CreditCardExpirationMonth.is(my[0]) || - !CreditCardExpirationYear.is(my[1]) - ) { - return O.some(true); - } - return O.fromEither(isExpired(my[0], my[1])); - }) - ); - -const maybeCreditCardValidOrExpired = ( - creditCard: CreditCardState -): O.Option => - pipe( - isCreditCardDateExpiredOrInvalid(creditCard.expirationDate), - O.map(v => !v) - ); - -const getAccessibilityLabels = (creditCard: CreditCardState) => ({ - cardHolder: - O.isNone(creditCard.holder) || isValidCardHolder(creditCard.holder) - ? I18n.t("wallet.dummyCard.accessibility.holder.base") - : I18n.t("wallet.dummyCard.accessibility.holder.error"), - pan: - O.isNone(creditCard.pan) || isValidPan(creditCard.pan) - ? I18n.t("wallet.dummyCard.accessibility.pan.base") - : I18n.t("wallet.dummyCard.accessibility.pan.error", { - minLength: MIN_PAN_DIGITS - }), - expirationDate: - O.isNone(maybeCreditCardValidOrExpired(creditCard)) || - O.toUndefined(maybeCreditCardValidOrExpired(creditCard)) - ? I18n.t("wallet.dummyCard.accessibility.expirationDate.base") - : I18n.t("wallet.dummyCard.accessibility.expirationDate.error"), - securityCode3D: - O.isNone(creditCard.securityCode) || - isValidSecurityCode(creditCard.securityCode) - ? I18n.t("wallet.dummyCard.accessibility.securityCode.3D.base") - : I18n.t("wallet.dummyCard.accessibility.securityCode.3D.error"), - securityCode4D: - O.isNone(creditCard.securityCode) || - isValidSecurityCode(creditCard.securityCode) - ? I18n.t("wallet.dummyCard.accessibility.securityCode.4D.base") - : I18n.t("wallet.dummyCard.accessibility.securityCode.4D.error") -}); - -const AddCardScreen: React.FC = () => { - const [creditCard, setCreditCard] = useState( - INITIAL_CARD_FORM_STATE - ); - - const navigation = useIONavigation(); - const { inPayment, keyFrom } = - useRoute>().params; - const navigateToConfirmCardDetailsScreen = (creditCard: CreditCard) => - navigateToWalletConfirmCardDetails({ - creditCard, - inPayment, - keyFrom - }); - - const isCardHolderValid = O.isNone(creditCard.holder) - ? undefined - : isValidCardHolder(creditCard.holder); - - const isCardExpirationDateValid = O.toUndefined( - maybeCreditCardValidOrExpired(creditCard) - ); - - const detectedBrand: SupportedBrand = CreditCardDetector.validate( - creditCard.pan - ); - - const { isCardNumberValid, isCvvValid } = useLuhnValidation( - pipe( - creditCard.pan, - O.getOrElse(() => "") - ), - pipe( - creditCard.securityCode, - O.getOrElse(() => "") - ) - ); - const isCardCvvValid = pipe( - creditCard.securityCode, - O.getOrElse(() => "") - ) - ? isCvvValid - : undefined; - - const isCreditCardValid = O.isNone(creditCard.pan) - ? undefined - : isCardNumberValid; - - const updateState = (key: CreditCardStateKeys, value: string) => { - setCreditCard({ - ...creditCard, - [key]: O.fromPredicate((value: string) => value.length > 0)(value) - }); - }; - - const isScreenReaderEnabled = !useScreenReaderEnabled(); - const placeholders = isScreenReaderEnabled - ? { - placeholderCard: I18n.t("wallet.dummyCard.values.pan"), - placeholderHolder: I18n.t("wallet.dummyCard.values.holder"), - placeholderDate: I18n.t("wallet.dummyCard.values.expirationDate"), - placeholderSecureCode: I18n.t( - detectedBrand.cvvLength === 4 - ? "wallet.dummyCard.values.securityCode4D" - : "wallet.dummyCard.values.securityCode" - ) - } - : {}; - - const accessibilityLabels = getAccessibilityLabels(creditCard); - - return ( - - - - - "") - ), - placeholder: placeholders.placeholderHolder, - autoCapitalize: "words", - keyboardType: "default", - returnKeyType: "done", - onChangeText: (value: string) => updateState("holder", value) - }} - overrideBorderColor={getColorFromInputValidatorState( - isCardHolderValid - )} - testID={"cardHolder"} - /> - - - - "") - ), - placeholder: placeholders.placeholderCard, - keyboardType: "numeric", - returnKeyType: "done", - maxLength: 23, - type: "custom", - options: { - mask: "9999 9999 9999 9999 999", - getRawValue: value1 => value1.replace(/ /g, "") - }, - includeRawValueInChangeText: true, - onChangeText: (_, value) => { - if (value !== undefined) { - updateState("pan", value); - } - } - }} - overrideBorderColor={getColorFromInputValidatorState( - isCreditCardValid - )} - accessibilityLabel={accessibilityLabels.pan} - testID={"pan"} - /> - - - - - - "") - ), - placeholder: placeholders.placeholderDate, - keyboardType: "numeric", - returnKeyType: "done", - type: "custom", - options: { mask: "99/99" }, - includeRawValueInChangeText: true, - onChangeText: value => updateState("expirationDate", value) - }} - overrideBorderColor={getColorFromInputValidatorState( - isCardExpirationDateValid - )} - testID={"expirationDate"} - /> - - - - "") - ), - placeholder: placeholders.placeholderSecureCode, - returnKeyType: "done", - maxLength: 4, - type: "custom", - options: { mask: "9999" }, - keyboardType: "numeric", - secureTextEntry: true, - includeRawValueInChangeText: true, - onChangeText: value => updateState("securityCode", value) - }} - overrideBorderColor={getColorFromInputValidatorState( - isCardCvvValid - )} - testID={"securityCode"} - /> - - - - - - - {I18n.t("wallet.openAcceptedCardsPageCTA")} - - - - - - - - ); -}; - -export default AddCardScreen; - -// keep encapsulation strong -export const testableAddCardScreen = isTestEnv - ? { isCreditCardDateExpiredOrInvalid } - : undefined; - -function getColorFromInputValidatorState( - isCreditCardValid: boolean | undefined -): string | undefined { - return isCreditCardValid === undefined - ? undefined - : isCreditCardValid - ? IOColors.green - : IOColors.red; -} diff --git a/ts/screens/wallet/AddCreditCardOutcomeCodeMessage.tsx b/ts/screens/wallet/AddCreditCardOutcomeCodeMessage.tsx deleted file mode 100644 index f82601bc414..00000000000 --- a/ts/screens/wallet/AddCreditCardOutcomeCodeMessage.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import React from "react"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import paymentCompleted from "../../../img/pictograms/payment-completed.png"; -import { renderInfoRasterImage } from "../../components/infoScreen/imageRendering"; -import { InfoScreenComponent } from "../../components/infoScreen/InfoScreenComponent"; -import OutcomeCodeMessageComponent from "../../components/wallet/OutcomeCodeMessageComponent"; -import I18n from "../../i18n"; -import { IOStackNavigationRouteProps } from "../../navigation/params/AppParamsList"; -import { WalletParamsList } from "../../navigation/params/WalletParamsList"; -import { navigateToWalletHome } from "../../store/actions/navigation"; -import { GlobalState } from "../../store/reducers/types"; -import { lastPaymentOutcomeCodeSelector } from "../../store/reducers/wallet/outcomeCode"; -import { Wallet } from "../../types/pagopa"; - -export type AddCreditCardOutcomeCodeMessageNavigationParams = Readonly<{ - selectedWallet: Wallet; -}>; - -type OwnProps = { - navigation: IOStackNavigationRouteProps< - WalletParamsList, - "ADD_CREDIT_CARD_OUTCOMECODE_MESSAGE" - >; -}; -type Props = OwnProps & - ReturnType & - ReturnType; - -const successComponent = () => ( - -); - -/** - * This is the wrapper component which takes care of showing the outcome message after that - * a user tries to add a card to his/her wallet. - * The component expects the action outcomeCodeRetrieved to be dispatched before being rendered, - * so the pot.none case is not taken into account. - * - */ -const AddCreditCardOutcomeCodeMessage: React.FC = (props: Props) => { - const outcomeCode = pipe(props.outcomeCode.outcomeCode, O.toUndefined); - - return outcomeCode ? ( - - ) : null; -}; - -const mapDispatchToProps = (_: Dispatch) => ({ - navigateToWalletHome: () => navigateToWalletHome() -}); - -const mapStateToProps = (state: GlobalState) => ({ - outcomeCode: lastPaymentOutcomeCodeSelector(state) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(AddCreditCardOutcomeCodeMessage); diff --git a/ts/screens/wallet/AddPaymentMethodScreen.tsx b/ts/screens/wallet/AddPaymentMethodScreen.tsx deleted file mode 100644 index 1c56df0bcb7..00000000000 --- a/ts/screens/wallet/AddPaymentMethodScreen.tsx +++ /dev/null @@ -1,253 +0,0 @@ -import { FooterWithButtons, VSpacer } from "@pagopa/io-app-design-system"; -import { AmountInEuroCents, RptId } from "@pagopa/io-pagopa-commons/lib/pagopa"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { Route, useRoute } from "@react-navigation/native"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { ScrollView, View } from "react-native"; -import { connect } from "react-redux"; -import { PaymentRequestsGetResponse } from "../../../definitions/backend/PaymentRequestsGetResponse"; -import BpayLogo from "../../../img/wallet/payment-methods/bancomat_pay.svg"; -import CreditCard from "../../../img/wallet/payment-methods/creditcard.svg"; -import PaypalLogo from "../../../img/wallet/payment-methods/paypal/paypal_logo.svg"; -import { H1 } from "../../components/core/typography/H1"; -import { IOStyles } from "../../components/core/variables/IOStyles"; -import BaseScreenComponent, { - ContextualHelpPropsMarkdown -} from "../../components/screens/BaseScreenComponent"; -import PaymentBannerComponent from "../../components/wallet/PaymentBannerComponent"; -import PaymentMethodsList, { - IPaymentMethod -} from "../../components/wallet/PaymentMethodsList"; -import { walletAddBancomatStart } from "../../features/wallet/onboarding/bancomat/store/actions"; -import { walletAddBPayStart } from "../../features/wallet/onboarding/bancomatPay/store/actions"; -import { - OnOnboardingCompleted, - walletAddPaypalStart -} from "../../features/wallet/onboarding/paypal/store/actions"; -import I18n from "../../i18n"; -import { - navigateBack, - navigateToWalletAddCreditCard -} from "../../store/actions/navigation"; -import { Dispatch } from "../../store/actions/types"; -import { - bancomatPayConfigSelector, - isPaypalEnabledSelector -} from "../../store/reducers/backendStatus"; -import { GlobalState } from "../../store/reducers/types"; -import { paypalSelector } from "../../store/reducers/wallet/wallets"; -import { AsyncAlert } from "../../utils/asyncAlert"; -import { isTestEnv } from "../../utils/environment"; - -export type AddPaymentMethodScreenNavigationParams = Readonly<{ - inPayment: O.Option<{ - rptId: RptId; - initialAmount: AmountInEuroCents; - verifica: PaymentRequestsGetResponse; - idPayment: string; - }>; - // if set, only those methods that can pay with pagoPA will be shown - showOnlyPayablePaymentMethods?: true; - keyFrom?: string; -}>; - -type Props = ReturnType & - ReturnType; - -const contextualHelpMarkdown: ContextualHelpPropsMarkdown = { - title: "wallet.newPaymentMethod.contextualHelpTitle", - body: "wallet.newPaymentMethod.contextualHelpContent" -}; - -const getPaymentMethods = ( - props: Props, - options: { - onlyPaymentMethodCanPay: boolean; - isPaymentOnGoing: boolean; - isPaypalEnabled: boolean; - canOnboardBPay: boolean; - }, - navigateToAddCreditCard: () => void -): ReadonlyArray => [ - { - name: I18n.t("wallet.methods.card.name"), - description: I18n.t("wallet.methods.card.description"), - icon: CreditCard, - onPress: navigateToAddCreditCard, - status: "implemented", - section: "credit_card" - }, - { - name: I18n.t("wallet.methods.paypal.name"), - description: I18n.t("wallet.methods.paypal.description"), - icon: PaypalLogo, - onPress: options.isPaypalEnabled - ? async () => { - const startPaypalOnboarding = () => - props.startPaypalOnboarding( - options.isPaymentOnGoing ? "back" : "payment_method_details" - ); - if (props.isPaypalAlreadyAdded) { - await AsyncAlert( - I18n.t("wallet.onboarding.paypal.paypalAlreadyAdded.alert.title"), - I18n.t( - "wallet.onboarding.paypal.paypalAlreadyAdded.alert.message" - ), - [ - { - text: I18n.t("global.buttons.continue"), - style: "default", - onPress: startPaypalOnboarding - }, - { - text: I18n.t("global.buttons.cancel"), - style: "cancel" - } - ] - ); - return; - } - startPaypalOnboarding(); - } - : undefined, - status: options.isPaypalEnabled ? "implemented" : "notImplemented", - section: "paypal" - }, - { - name: I18n.t("wallet.methods.bancomatPay.name"), - description: I18n.t("wallet.methods.bancomatPay.description"), - icon: BpayLogo, - status: options.canOnboardBPay ? "implemented" : "notImplemented", - onPress: props.startBPayOnboarding, - section: "digital_payments" - } -]; - -/** - * This is the screen presented to the user - * when they request adding a new payment method. - * From here, they can select their payment method - * of choice (although only credit cards will be allowed - * initially). - * - * This screen allows also to add a new payment method after a transaction is identified. - * - * The header banner provides a summary on the transaction to perform. - * - * Keep in mind that the rest of the "add credit card" process - * is handled @https://www.pivotaltracker.com/story/show/157838293 - */ -const AddPaymentMethodScreen: React.FunctionComponent = ( - props: Props -) => { - const { inPayment, showOnlyPayablePaymentMethods, keyFrom } = - useRoute< - Route<"WALLET_ADD_PAYMENT_METHOD", AddPaymentMethodScreenNavigationParams> - >().params; - - const navigateToAddCreditCard = () => - navigateToWalletAddCreditCard({ - inPayment, - keyFrom - }); - - const buttonLabel = O.isSome(inPayment) - ? I18n.t("global.buttons.back") - : I18n.t("global.buttons.cancel"); - - return ( - - - {O.isSome(inPayment) ? ( - <> - - - -

{I18n.t("wallet.payWith.title")}

- - {/* since we're paying show only those method can pay with pagoPA */} - -
- - ) : ( - - )} -
- -
- ); -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - navigateBack: () => navigateBack(), - startBPayOnboarding: () => dispatch(walletAddBPayStart()), - startPaypalOnboarding: (onOboardingCompleted: OnOnboardingCompleted) => - dispatch(walletAddPaypalStart(onOboardingCompleted)), - startAddBancomat: () => dispatch(walletAddBancomatStart()) -}); - -const mapStateToProps = (state: GlobalState) => { - const bpayConfig = bancomatPayConfigSelector(state); - return { - isPaypalAlreadyAdded: pot.isSome(paypalSelector(state)), - isPaypalEnabled: isPaypalEnabledSelector(state), - canOnboardBPay: bpayConfig.onboarding, - canPayWithBPay: bpayConfig.payment - }; -}; - -export default connect( - mapStateToProps, - mapDispatchToProps -)(AddPaymentMethodScreen); - -// to keep solid code encapsulation -export const testableFunctions = { - getPaymentMethods: isTestEnv ? getPaymentMethods : undefined -}; diff --git a/ts/screens/wallet/ConfirmCardDetailsScreen.tsx b/ts/screens/wallet/ConfirmCardDetailsScreen.tsx deleted file mode 100644 index 1d853631fef..00000000000 --- a/ts/screens/wallet/ConfirmCardDetailsScreen.tsx +++ /dev/null @@ -1,552 +0,0 @@ -/** - * This screen presents a summary on the credit card after the user - * inserted the data required to save a new card - */ -import { AmountInEuroCents, RptId } from "@pagopa/io-pagopa-commons/lib/pagopa"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { constNull, pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { - Alert, - SafeAreaView, - ScrollView, - StyleSheet, - View -} from "react-native"; -import { connect } from "react-redux"; - -import { - ContentWrapper, - FooterWithButtons, - HSpacer, - IOToast, - NativeSwitch, - VSpacer -} from "@pagopa/io-app-design-system"; -import { Route, useNavigation, useRoute } from "@react-navigation/native"; -import { PaymentRequestsGetResponse } from "../../../definitions/backend/PaymentRequestsGetResponse"; -import { TypeEnum } from "../../../definitions/pagopa/Wallet"; -import image from "../../../img/wallet/errors/payment-unavailable-icon.png"; -import { InfoBox } from "../../components/box/InfoBox"; -import { FooterStackButton } from "../../components/buttons/FooterStackButtons"; -import { H1 } from "../../components/core/typography/H1"; -import { H4 } from "../../components/core/typography/H4"; -import { H5 } from "../../components/core/typography/H5"; -import { IOStyles } from "../../components/core/variables/IOStyles"; -import { withLoadingSpinner } from "../../components/helpers/withLoadingSpinner"; -import { renderInfoRasterImage } from "../../components/infoScreen/imageRendering"; -import { InfoScreenComponent } from "../../components/infoScreen/InfoScreenComponent"; -import BaseScreenComponent, { - ContextualHelpPropsMarkdown -} from "../../components/screens/BaseScreenComponent"; -import CardComponent from "../../components/wallet/card/CardComponent"; -import { PayWebViewModal } from "../../components/wallet/PayWebViewModal"; -import { pagoPaApiUrlPrefix, pagoPaApiUrlPrefixTest } from "../../config"; - -import { - isError, - isReady, - isLoading as isRemoteLoading -} from "../../common/model/RemoteValue"; -import { LoadingErrorComponent } from "../../components/LoadingErrorComponent"; -import { LightModalContext } from "../../components/ui/LightModal"; -import I18n from "../../i18n"; -import { - IOStackNavigationProp, - IOStackNavigationRouteProps -} from "../../navigation/params/AppParamsList"; -import { WalletParamsList } from "../../navigation/params/WalletParamsList"; -import { - navigateToAddCreditCardOutcomeCode, - navigateToPaymentPickPaymentMethodScreen, - navigateToWalletHome -} from "../../store/actions/navigation"; -import { Dispatch } from "../../store/actions/types"; -import { addCreditCardOutcomeCode } from "../../store/actions/wallet/outcomeCode"; -import { - addCreditCardWebViewEnd, - AddCreditCardWebViewEndReason, - addWalletCreditCardInit, - creditCardPaymentNavigationUrls, - fetchWalletsRequestWithExpBackoff, - runStartOrResumeAddCreditCardSaga -} from "../../store/actions/wallet/wallets"; -import { isPagoPATestEnabledSelector } from "../../store/reducers/persistedPreferences"; -import { GlobalState } from "../../store/reducers/types"; -import { pmSessionTokenSelector } from "../../store/reducers/wallet/payment"; -import { getAllWallets } from "../../store/reducers/wallet/wallets"; -import { CreditCard, Wallet } from "../../types/pagopa"; -import { getLocalePrimaryWithFallback } from "../../utils/locale"; -import { getLookUpIdPO } from "../../utils/pmLookUpId"; -import { dispatchPickPspOrConfirm } from "./payment/common"; - -export type ConfirmCardDetailsScreenNavigationParams = Readonly<{ - creditCard: CreditCard; - inPayment: O.Option<{ - rptId: RptId; - initialAmount: AmountInEuroCents; - verifica: PaymentRequestsGetResponse; - idPayment: string; - }>; - keyFrom?: string; -}>; - -type ReduxMergedProps = Readonly<{ - onRetry?: () => void; -}>; - -type OwnProps = IOStackNavigationRouteProps< - WalletParamsList, - "WALLET_CONFIRM_CARD_DETAILS" ->; - -type Props = ReturnType & - ReturnType & - ReduxMergedProps & - OwnProps; - -type State = Readonly<{ - setAsFavourite: boolean; -}>; - -const styles = StyleSheet.create({ - preferredMethodContainer: { - flexDirection: "row", - justifyContent: "space-between" - } -}); - -const contextualHelpMarkdown: ContextualHelpPropsMarkdown = { - title: "wallet.saveCard.contextualHelpTitle", - body: "wallet.saveCard.contextualHelpContent" -}; - -class ConfirmCardDetailsScreen extends React.Component { - public componentDidMount() { - // reset the credit card boarding state on mount - this.props.addWalletCreditCardInit(); - } - - constructor(props: Props) { - super(props); - this.state = { - setAsFavourite: true - }; - } - - // It supports switch state changes - private onSetFavouriteValueChange = () => { - this.setState(prevState => ({ - setAsFavourite: !prevState.setAsFavourite - })); - }; - - private goBack = () => { - this.props.navigation.goBack(); - }; - - public render(): React.ReactNode { - const creditCard = this.props.route.params.creditCard; - const isInPayment = O.isSome(this.props.route.params.inPayment); - - // WebView parameters - const payUrlSuffix = "/v3/webview/transactions/cc/verify"; - const webViewExitPathName = "/v3/webview/logout/bye"; - const webViewOutcomeParamName = "outcome"; - - const urlPrefix = this.props.isPagoPATestEnabled - ? pagoPaApiUrlPrefixTest - : pagoPaApiUrlPrefix; - - // the user press back during the pay web view challenge - const handlePayWebviewGoBack = () => { - Alert.alert(I18n.t("wallet.abortWebView.title"), "", [ - { - text: I18n.t("wallet.abortWebView.confirm"), - onPress: () => { - this.props.dispatchEndAddCreditCardWebview("USER_ABORT"); - this.props.onCancel(); - }, - style: "cancel" - }, - { - text: I18n.t("wallet.abortWebView.cancel") - } - ]); - }; - - const payWebViewPayload = - isReady(this.props.pmSessionToken) && - O.isSome(this.props.creditCardTempWallet) && - creditCard.securityCode - ? { - formData: { - idWallet: this.props.creditCardTempWallet.value.idWallet, - securityCode: creditCard.securityCode, - sessionToken: this.props.pmSessionToken.value, - language: getLocalePrimaryWithFallback() - }, - crediCardTempWallet: this.props.creditCardTempWallet.value - } - : undefined; - - const wallet = { - creditCard, - type: TypeEnum.CREDIT_CARD, - idWallet: -1, // FIXME: no magic numbers - psp: undefined - }; - - // shown when wallets pot is in error state - const walletsInErrorContent = ( - - - { - // load wallets and navigate to wallet home - this.props.loadWallets(); - this.props.navigateToWalletHome(); - }, - label: I18n.t("wallet.refreshWallet"), - accessibilityLabel: I18n.t("wallet.refreshWallet") - }} - /> - - ); - - // shown when any steps of credit card onboarding are in error state - const creditCardErrorContent = ( - "")(this.props.error)} - onRetry={this.props.onRetry ?? constNull} - onAbort={this.goBack} - /> - ); - // this component is shown only in error case (wallet || credit card onboarding) - const errorContent = this.props.areWalletsInError - ? walletsInErrorContent - : creditCardErrorContent; - - const formData = pipe( - payWebViewPayload?.formData, - O.fromNullable, - O.map(payload => ({ - ...payload, - ...getLookUpIdPO() - })), - O.getOrElse(() => ({})) - ); - - const noErrorContent = ( - <> - - - -

{I18n.t("wallet.saveCard.title")}

-

{I18n.t("wallet.saveCard.subtitle")}

- - - - -
{I18n.t("wallet.saveCard.notice")}
-
- - - -

- {I18n.t("wallet.saveCard.infoTitle")} -

-
- {I18n.t("wallet.saveCard.info")} -
-
- - - - -
-
-
- - {/* - * When the first step is finished (creditCardAddWallet === O.some) show the webview - * for the payment component. - */} - {payWebViewPayload && ( - { - this.props.dispatchCreditCardPaymentNavigationUrls( - navigationUrls - ); - this.props.storeCreditCardOutcome(maybeCode); - this.props.goToAddCreditCardOutcomeCode( - payWebViewPayload.crediCardTempWallet - ); - this.props.dispatchEndAddCreditCardWebview("EXIT_PATH"); - }} - outcomeQueryparamName={webViewOutcomeParamName} - onGoBack={handlePayWebviewGoBack} - modalHeaderTitle={I18n.t("wallet.challenge3ds.header")} - /> - )} -
- - this.props.runStartOrResumeAddCreditCardSaga( - creditCard, - this.state.setAsFavourite - ), - label: isInPayment - ? I18n.t("wallet.saveCardInPayment.save") - : I18n.t("global.buttons.continue"), - accessibilityLabel: isInPayment - ? I18n.t("wallet.saveCardInPayment.save") - : I18n.t("global.buttons.continue"), - testID: "saveOrContinueButton" - } - }} - /> - - ); - const error = O.isSome(this.props.error) || this.props.areWalletsInError; - return ( - - {/* error could include credit card errors (add wallet (step-1)) - and load wallets error too - */} - {error ? errorContent : noErrorContent} - - ); - } -} - -const mapStateToProps = (state: GlobalState) => { - const { creditCardAddWallet, walletById } = state.wallet.wallets; - - const { pspsV2 } = state.wallet.payment; - const pmSessionToken = pmSessionTokenSelector(state); - const isLoading = - isRemoteLoading(pmSessionToken) || - pot.isLoading(creditCardAddWallet) || - pot.isLoading(walletById) || - isRemoteLoading(pspsV2.psps); - - // considering wallet error only when the first step is completed and not in error - const areWalletsInError = - pot.isError(walletById) && pot.isSome(creditCardAddWallet); - - const error = - (pot.isError(creditCardAddWallet) && - creditCardAddWallet.error.kind !== "ALREADY_EXISTS") || - isError(pspsV2.psps) - ? O.some(I18n.t("wallet.saveCard.temporaryError")) - : O.none; - - // Props needed to create the form for the payment web view - const allWallets = getAllWallets(state); - const creditCardTempWallet: O.Option = pipe( - pot.toOption(allWallets.creditCardAddWallet), - O.map(c => c.data) - ); - - return { - isLoading, - error, - areWalletsInError, - loadingOpacity: 0.98, - loadingCaption: I18n.t("wallet.saveCard.loadingAlert"), - creditCardTempWallet, - pmSessionToken, - isPagoPATestEnabled: isPagoPATestEnabledSelector(state) - }; -}; - -const mapDispatchToProps = (dispatch: Dispatch, props: OwnProps) => { - const navigateToNextScreen = (maybeWallet: O.Option) => { - const inPayment = props.route.params.inPayment; - if (O.isSome(inPayment)) { - const { rptId, initialAmount, verifica, idPayment } = inPayment.value; - dispatchPickPspOrConfirm(dispatch)( - rptId, - initialAmount, - verifica, - idPayment, - maybeWallet, - failureReason => { - // trying to use this card for the current payment has failed, show - // a toast and navigate to the wallet selection screen - if (failureReason === "FETCH_PSPS_FAILURE") { - // fetching the PSPs for the payment has failed - IOToast.warning(I18n.t("wallet.payWith.fetchPspFailure")); - } else if (failureReason === "NO_PSPS_AVAILABLE") { - // this card cannot be used for this payment - // TODO: perhaps we can temporarily hide the selected wallet from - // the list of available wallets - IOToast.error(I18n.t("wallet.payWith.noPspsAvailable")); - } - // navigate to the wallet selection screen - - navigateToPaymentPickPaymentMethodScreen({ - rptId, - initialAmount, - verifica, - idPayment - }); - } - ); - } else { - navigateToWalletHome({ - newMethodAdded: O.isSome(maybeWallet), - keyFrom: props.route.params.keyFrom - }); - } - }; - return { - navigateToWalletHome: () => navigateToWalletHome(), - loadWallets: () => dispatch(fetchWalletsRequestWithExpBackoff()), - addWalletCreditCardInit: () => dispatch(addWalletCreditCardInit()), - runStartOrResumeAddCreditCardSaga: ( - creditCard: CreditCard, - setAsFavorite: boolean - ) => - dispatch( - runStartOrResumeAddCreditCardSaga({ - creditCard, - setAsFavorite, - onSuccess: addedWallet => { - navigateToNextScreen(O.some(addedWallet)); - }, - onFailure: error => { - IOToast.error( - I18n.t( - error === "ALREADY_EXISTS" - ? "wallet.newPaymentMethod.failedCardAlreadyExists" - : "wallet.newPaymentMethod.failed" - ) - ); - navigateToNextScreen(O.none); - } - }) - ), - onCancel: () => props.navigation.goBack(), - storeCreditCardOutcome: (outcomeCode: O.Option) => - dispatch(addCreditCardOutcomeCode(outcomeCode)), - goToAddCreditCardOutcomeCode: (creditCard: Wallet) => - navigateToAddCreditCardOutcomeCode({ selectedWallet: creditCard }), - dispatchEndAddCreditCardWebview: ( - reason: AddCreditCardWebViewEndReason - ) => { - dispatch(addCreditCardWebViewEnd(reason)); - }, - dispatchCreditCardPaymentNavigationUrls: ( - navigationUrls: ReadonlyArray - ) => { - dispatch(creditCardPaymentNavigationUrls(navigationUrls)); - } - }; -}; - -const mergeProps = ( - stateProps: ReturnType, - dispatchProps: ReturnType, - ownProps: OwnProps -) => { - const maybeError = stateProps.error; - const isRetriableError = - O.isNone(maybeError) || maybeError.value !== "ALREADY_EXISTS"; - const onRetry = isRetriableError - ? () => { - dispatchProps.runStartOrResumeAddCreditCardSaga( - ownProps.route.params.creditCard, - // FIXME: Unfortunately we can't access the internal component state - // from here so we cannot know if the user wants to set this - // card as favourite, we pass true anyway since it's the - // default. - true - ); - } - : undefined; - return { - ...stateProps, - ...dispatchProps, - ...ownProps, - ...{ - onRetry - } - }; -}; - -const ConnectedConfirmCardDetailsScreen = connect( - mapStateToProps, - mapDispatchToProps, - mergeProps -)(withLoadingSpinner(ConfirmCardDetailsScreen)); - -const ConfirmCardDetailsScreenFC = () => { - const { ...modalContext } = React.useContext(LightModalContext); - const navigation = - useNavigation< - IOStackNavigationProp - >(); - const route = - useRoute< - Route< - "WALLET_CONFIRM_CARD_DETAILS", - ConfirmCardDetailsScreenNavigationParams - > - >(); - return ( - - ); -}; - -export default ConfirmCardDetailsScreenFC; diff --git a/ts/screens/wallet/PaymentHistoryDetailsScreen.tsx b/ts/screens/wallet/PaymentHistoryDetailsScreen.tsx deleted file mode 100644 index dcf6a8c9b24..00000000000 --- a/ts/screens/wallet/PaymentHistoryDetailsScreen.tsx +++ /dev/null @@ -1,447 +0,0 @@ -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { View } from "react-native"; -import { connect } from "react-redux"; -import { VSpacer, ButtonOutline } from "@pagopa/io-app-design-system"; -import { useNavigation, useRoute, Route } from "@react-navigation/native"; -import { EnteBeneficiario } from "../../../definitions/backend/EnteBeneficiario"; -import { PaymentRequestsGetResponse } from "../../../definitions/backend/PaymentRequestsGetResponse"; -import { ToolEnum } from "../../../definitions/content/AssistanceToolConfig"; -import { ZendeskCategory } from "../../../definitions/content/ZendeskCategory"; -import CopyButtonComponent from "../../components/CopyButtonComponent"; -import { Body } from "../../components/core/typography/Body"; -import { Label } from "../../components/core/typography/Label"; -import { IOStyles } from "../../components/core/variables/IOStyles"; -import ItemSeparatorComponent from "../../components/ItemSeparatorComponent"; -import BaseScreenComponent from "../../components/screens/BaseScreenComponent"; -import { getPaymentHistoryInfo } from "../../components/wallet/PaymentsHistoryList"; -import { - paymentStatusType, - PaymentSummaryComponent -} from "../../components/wallet/PaymentSummaryComponent"; -import { SlidedContentComponent } from "../../components/wallet/SlidedContentComponent"; -import { - zendeskSelectedCategory, - zendeskSupportStart -} from "../../features/zendesk/store/actions"; -import I18n from "../../i18n"; -import { - IOStackNavigationProp, - IOStackNavigationRouteProps -} from "../../navigation/params/AppParamsList"; -import { WalletParamsList } from "../../navigation/params/WalletParamsList"; -import { Dispatch } from "../../store/actions/types"; -import { canShowHelpSelector } from "../../store/reducers/assistanceTools"; -import { assistanceToolConfigSelector } from "../../store/reducers/backendStatus"; -import { PaymentHistory } from "../../store/reducers/payments/history"; -import { isPaymentDoneSuccessfully } from "../../store/reducers/payments/utils"; -import { GlobalState } from "../../store/reducers/types"; -import { outcomeCodesSelector } from "../../store/reducers/wallet/outcomeCode"; -import { Transaction } from "../../types/pagopa"; -import { formatDateAsLocal } from "../../utils/dates"; -import { maybeInnerProperty } from "../../utils/options"; -import { - getCodiceAvviso, - getErrorDescriptionV2, - getPaymentHistoryDetails, - getPaymentOutcomeCodeDescription, - getTransactionFee -} from "../../utils/payment"; -import { formatNumberCentsToAmount } from "../../utils/stringBuilder"; -import { isStringNullyOrEmpty } from "../../utils/strings"; -import { - addTicketCustomField, - appendLog, - assistanceToolRemoteConfig, - resetCustomFields, - zendeskCategoryId, - zendeskPaymentCategory, - zendeskPaymentFailure, - zendeskPaymentNav, - zendeskPaymentOrgFiscalCode, - zendeskPaymentStartOrigin -} from "../../utils/supportAssistance"; -import { H2 } from "../../components/core/typography/H2"; - -export type PaymentHistoryDetailsScreenNavigationParams = Readonly<{ - payment: PaymentHistory; -}>; - -type Props = IOStackNavigationRouteProps< - WalletParamsList, - "PAYMENT_HISTORY_DETAIL_INFO" -> & - ReturnType & - ReturnType; - -const notAvailable = I18n.t("global.remoteStates.notAvailable"); -const renderItem = (label: string, value?: string) => { - if (isStringNullyOrEmpty(value)) { - return null; - } - return ( - - {label} - - - - ); -}; - -/** - * Payment Details - */ -class PaymentHistoryDetailsScreen extends React.Component { - private zendeskAssistanceLogAndStart = () => { - resetCustomFields(); - // Set pagamenti_pagopa as category - addTicketCustomField(zendeskCategoryId, zendeskPaymentCategory.value); - - // Add organization fiscal code custom field - addTicketCustomField( - zendeskPaymentOrgFiscalCode, - this.props.route.params.payment.data.organizationFiscalCode - ); - if (this.props.route.params.payment.failure) { - // Add failure custom field - addTicketCustomField( - zendeskPaymentFailure, - this.props.route.params.payment.failure - ); - } - // Add start origin custom field - addTicketCustomField( - zendeskPaymentStartOrigin, - this.props.route.params.payment.startOrigin - ); - // Add rptId custom field - addTicketCustomField( - zendeskPaymentNav, - getCodiceAvviso(this.props.route.params.payment.data) - ); - // Append the payment history details in the log - appendLog(getPaymentHistoryDetails(this.props.route.params.payment)); - - this.props.zendeskSupportWorkunitStart(); - this.props.zendeskSelectedCategory(zendeskPaymentCategory); - }; - private choosenTool = assistanceToolRemoteConfig( - this.props.assistanceToolConfig - ); - - private handleAskAssistance = () => { - switch (this.choosenTool) { - case ToolEnum.zendesk: - this.zendeskAssistanceLogAndStart(); - break; - } - }; - - private getData = () => { - const payment = this.props.route.params.payment; - const codiceAvviso = getCodiceAvviso(payment.data); - const paymentCheckout = isPaymentDoneSuccessfully(payment); - const paymentInfo = getPaymentHistoryInfo(payment, paymentCheckout); - const paymentStatus: paymentStatusType = { - color: paymentInfo.color, - description: paymentInfo.text11 - }; - // the error could be on attiva or while the payment execution - // so the description is built first checking the attiva failure, alternatively - // it checks about the outcome if the payment went wrong - const errorDetail = pipe( - getErrorDescriptionV2(payment.failure), - O.fromNullable, - O.alt(() => - pipe( - payment.outcomeCode, - O.fromNullable, - O.chain(oc => - getPaymentOutcomeCodeDescription(oc, this.props.outcomeCodes) - ) - ) - ) - ); - - const paymentOutcome = isPaymentDoneSuccessfully(payment); - - const dateTime: string = `${formatDateAsLocal( - new Date(payment.started_at), - true, - true - )} - ${new Date(payment.started_at).toLocaleTimeString()}`; - - const reason = pipe( - maybeInnerProperty< - PaymentRequestsGetResponse, - "causaleVersamento", - string - >(payment.verifiedData, "causaleVersamento", m => m), - O.fold( - () => notAvailable, - cv => cv - ) - ); - - const recipient = pipe( - maybeInnerProperty( - payment.transaction, - "merchant", - m => m - ), - O.fold( - () => notAvailable, - c => c - ) - ); - - const amount = maybeInnerProperty( - payment.transaction, - "amount", - m => m.amount - ); - - const grandTotal = maybeInnerProperty( - payment.transaction, - "grandTotal", - m => m.amount - ); - const idTransaction = pipe( - maybeInnerProperty( - payment.transaction, - "id", - m => m - ), - O.fold( - () => notAvailable, - id => `${id}` - ) - ); - - const fee = getTransactionFee(payment.transaction); - - const enteBeneficiario = pipe( - maybeInnerProperty< - PaymentRequestsGetResponse, - "enteBeneficiario", - EnteBeneficiario | undefined - >(payment.verifiedData, "enteBeneficiario", m => m), - O.toUndefined - ); - - const outcomeCode = payment.outcomeCode ?? "-"; - return { - recipient, - reason, - enteBeneficiario, - codiceAvviso, - paymentOutcome, - paymentInfo, - paymentStatus, - dateTime, - outcomeCode, - amount, - fee, - grandTotal, - errorDetail, - idTransaction - }; - }; - - private standardRow = (label: string, value: string) => ( - - - {label} - - - - ); - - private renderSeparator = () => ( - - - - - - ); - - /** - * This fragment is rendered only if {@link canShowHelp} is true - */ - private renderHelper = () => ( - - - {I18n.t("payment.details.info.help")} - - - - - - ); - - public render(): React.ReactNode { - const data = this.getData(); - - return ( - this.props.navigation.goBack()} - showChat={false} - dark={true} - headerTitle={I18n.t("payment.details.info.title")} - > - - {O.isSome(data.paymentOutcome) && data.paymentOutcome.value ? ( - - ) : ( - - - {data.enteBeneficiario && - renderItem( - I18n.t("payment.details.info.enteCreditore"), - `${data.enteBeneficiario.denominazioneBeneficiario}\n${data.enteBeneficiario.identificativoUnivocoBeneficiario}` - )} - {O.isSome(data.errorDetail) && ( - - {I18n.t("payment.errorDetails")} - - {data.errorDetail.value} - - - )} - - )} - - {this.standardRow( - I18n.t("payment.details.info.outcomeCode"), - data.outcomeCode - )} - - {this.standardRow( - I18n.t("payment.details.info.dateAndTime"), - data.dateTime - )} - {this.renderSeparator()} - {O.isSome(data.paymentOutcome) && - data.paymentOutcome.value && - O.isSome(data.amount) && - O.isSome(data.grandTotal) && ( - - {/** amount */} - {this.standardRow( - I18n.t("wallet.firstTransactionSummary.amount"), - formatNumberCentsToAmount(data.amount.value, true) - )} - - {/** fee */} - {data.fee && - this.standardRow( - I18n.t("wallet.firstTransactionSummary.fee"), - data.fee - )} - - - - {/** total amount */} - - -

{I18n.t("wallet.firstTransactionSummary.total")}

-
-

- {formatNumberCentsToAmount(data.grandTotal.value, true)} -

-
- - {this.renderSeparator()} - - {/** Transaction id */} - - - {I18n.t("wallet.firstTransactionSummary.idTransaction")} - - - - {data.idTransaction} - - - - - -
- )} - {/* This check is redundant, since if the help can't be shown the user can't get there */} - {this.props.canShowHelp && this.renderHelper()} -
-
- ); - } -} - -const mapStateToProps = (state: GlobalState) => ({ - outcomeCodes: outcomeCodesSelector(state), - assistanceToolConfig: assistanceToolConfigSelector(state), - canShowHelp: canShowHelpSelector(state) -}); -const mapDispatchToProps = (dispatch: Dispatch) => ({ - // Start the assistance without FAQ ("n/a" is a placeholder) - zendeskSupportWorkunitStart: () => - dispatch( - zendeskSupportStart({ - startingRoute: "n/a", - assistanceForPayment: true, - assistanceForCard: false, - assistanceForFci: false - }) - ), - zendeskSelectedCategory: (category: ZendeskCategory) => - dispatch(zendeskSelectedCategory(category)) -}); - -const ConnectedPaymentHistoryDetailsScreen = connect( - mapStateToProps, - mapDispatchToProps -)(PaymentHistoryDetailsScreen); - -const PaymentHistoryDetailsScreenFC = () => { - const navigation = - useNavigation< - IOStackNavigationProp - >(); - const route = - useRoute< - Route< - "PAYMENT_HISTORY_DETAIL_INFO", - PaymentHistoryDetailsScreenNavigationParams - > - >(); - return ( - - ); -}; -export default PaymentHistoryDetailsScreenFC; diff --git a/ts/screens/wallet/PaymentsHistoryScreen.tsx b/ts/screens/wallet/PaymentsHistoryScreen.tsx deleted file mode 100644 index 21da18918ca..00000000000 --- a/ts/screens/wallet/PaymentsHistoryScreen.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { ContentWrapper, VSpacer } from "@pagopa/io-app-design-system"; -import * as AR from "fp-ts/lib/Array"; -import * as React from "react"; -import { Body } from "../../components/core/typography/Body"; -import { H2 } from "../../components/core/typography/H2"; -import { withValidatedPagoPaVersion } from "../../components/helpers/withValidatedPagoPaVersion"; -import BaseScreenComponent from "../../components/screens/BaseScreenComponent"; -import { EdgeBorderComponent } from "../../components/screens/EdgeBorderComponent"; -import PaymentHistoryList from "../../components/wallet/PaymentsHistoryList"; -import I18n from "../../i18n"; -import { useIONavigation } from "../../navigation/params/AppParamsList"; -import { navigateToPaymentHistoryDetail } from "../../store/actions/navigation"; -import { - PaymentHistory, - paymentsHistorySelector -} from "../../store/reducers/payments/history"; -import { useIOSelector } from "../../store/hooks"; - -const ListEmptyComponent = (): React.JSX.Element => ( - -

{I18n.t("payment.details.list.empty.title")}

- - {I18n.t("payment.details.list.empty.description")} - - -
-); - -/** - * A screen displaying the list of all the transactions apprached - * by the user (completed or cancelled for any reason). - */ -const PaymentsHistoryScreen = () => { - const historyPayments = useIOSelector(paymentsHistorySelector); - const navigation = useIONavigation(); - - return ( - navigation.goBack()} - headerTitle={I18n.t("payment.details.list.title")} - > - } - navigateToPaymentHistoryDetail={(payment: PaymentHistory) => - navigateToPaymentHistoryDetail({ - payment - }) - } - /> - - ); -}; - -export default withValidatedPagoPaVersion(PaymentsHistoryScreen); diff --git a/ts/screens/wallet/TransactionDetailsScreen.tsx b/ts/screens/wallet/TransactionDetailsScreen.tsx deleted file mode 100644 index 97bcfe643fd..00000000000 --- a/ts/screens/wallet/TransactionDetailsScreen.tsx +++ /dev/null @@ -1,397 +0,0 @@ -import { ButtonOutline, IOColors, VSpacer } from "@pagopa/io-app-design-system"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { Route, useNavigation, useRoute } from "@react-navigation/native"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import * as React from "react"; -import { - BackHandler, - Image, - NativeEventSubscription, - StyleSheet, - View -} from "react-native"; -import { connect } from "react-redux"; -import CopyButtonComponent from "../../components/CopyButtonComponent"; -import ItemSeparatorComponent from "../../components/ItemSeparatorComponent"; -import { Body } from "../../components/core/typography/Body"; -import { H2 } from "../../components/core/typography/H2"; -import { Link } from "../../components/core/typography/Link"; -import { IOStyles } from "../../components/core/variables/IOStyles"; -import { withLoadingSpinner } from "../../components/helpers/withLoadingSpinner"; -import BaseScreenComponent, { - ContextualHelpPropsMarkdown -} from "../../components/screens/BaseScreenComponent"; -import FocusAwareStatusBar from "../../components/ui/FocusAwareStatusBar"; -import { - LightModalContext, - LightModalContextInterface -} from "../../components/ui/LightModal"; -import { PaymentSummaryComponent } from "../../components/wallet/PaymentSummaryComponent"; -import { SlidedContentComponent } from "../../components/wallet/SlidedContentComponent"; -import I18n from "../../i18n"; -import { - IOStackNavigationProp, - IOStackNavigationRouteProps -} from "../../navigation/params/AppParamsList"; -import { WalletParamsList } from "../../navigation/params/WalletParamsList"; -import { Dispatch } from "../../store/actions/types"; -import { backToEntrypointPayment } from "../../store/actions/wallet/payment"; -import { fetchPsp } from "../../store/actions/wallet/transactions"; -import { GlobalState } from "../../store/reducers/types"; -import { pspStateByIdSelector } from "../../store/reducers/wallet/pspsById"; -import { getWalletsById } from "../../store/reducers/wallet/wallets"; -import { Transaction } from "../../types/pagopa"; -import { clipboardSetStringWithFeedback } from "../../utils/clipboard"; -import { formatDateAsLocal } from "../../utils/dates"; -import { - cleanTransactionDescription, - getTransactionFee, - getTransactionIUV -} from "../../utils/payment"; -import { formatNumberCentsToAmount } from "../../utils/stringBuilder"; - -export type TransactionDetailsScreenNavigationParams = Readonly<{ - isPaymentCompletedTransaction: boolean; - transaction: Transaction; -}>; - -type OwnProps = IOStackNavigationRouteProps< - WalletParamsList, - "WALLET_TRANSACTION_DETAILS" ->; - -type Props = ReturnType & - ReturnType; - -type TransactionDetailsScreenProps = LightModalContextInterface & - Props & - OwnProps; - -/** - * isTransactionStarted will be true when the user accepted to proceed with a transaction - * and he is going to display the detail of the transaction as receipt - */ - -const styles = StyleSheet.create({ - cardLogo: { - alignSelf: "flex-end", - height: 30, - width: 48 - } -}); - -const contextualHelpMarkdown: ContextualHelpPropsMarkdown = { - title: "wallet.transactionDetails", - body: "wallet.detailsTransaction.contextualHelpContent" -}; - -type State = { - showFullReason: boolean; -}; - -/** - * Transaction details screen, displaying - * a list of information available about a - * specific transaction. - */ -class TransactionDetailsScreen extends React.Component< - TransactionDetailsScreenProps, - State -> { - private subscription: NativeEventSubscription | undefined; - private navigationEventUnsubscribe!: () => void; - constructor(props: TransactionDetailsScreenProps) { - super(props); - this.state = { showFullReason: false }; - } - - public componentDidMount() { - // eslint-disable-next-line functional/immutable-data - this.navigationEventUnsubscribe = this.props.navigation.addListener( - "focus", - this.handleWillFocus - ); - // eslint-disable-next-line functional/immutable-data - this.subscription = BackHandler.addEventListener( - "hardwareBackPress", - this.handleBackPress - ); - } - - public componentWillUnmount() { - this.subscription?.remove(); - this.navigationEventUnsubscribe(); - } - - private handleBackPress = () => { - this.props.navigation.goBack(); - return true; - }; - - private handleWillFocus = () => { - const transaction = this.props.route.params.transaction; - // Fetch psp only if the store not contains this psp - if (transaction.idPsp !== undefined && this.props.psp === undefined) { - this.props.fetchPsp(transaction.idPsp); - } - }; - - private getData = () => { - const transaction = this.props.route.params.transaction; - const amount = formatNumberCentsToAmount(transaction.amount.amount, true); - - // fee - const fee = getTransactionFee(transaction); - - const totalAmount = formatNumberCentsToAmount( - transaction.grandTotal.amount, - true - ); - - const transactionWallet = this.props.wallets - ? this.props.wallets[transaction.idWallet] - : undefined; - - const transactionDateTime = formatDateAsLocal(transaction.created, true) - .concat(" - ") - .concat(transaction.created.toLocaleTimeString()); - - const paymentMethodIcon = pipe( - transactionWallet && - transactionWallet.creditCard && - transactionWallet.creditCard.brandLogo, - O.fromNullable, - O.map(logo => (logo.trim().length > 0 ? logo.trim() : undefined)), - O.toUndefined - ); - - const paymentMethodBrand = - transactionWallet && - transactionWallet.creditCard && - transactionWallet.creditCard.brand; - - const iuv = pipe(getTransactionIUV(transaction.description), O.toUndefined); - - const idTransaction = transaction.id; - return { - fullReason: transaction.description, - iuv, - idTransaction, - paymentMethodBrand, - paymentMethodIcon, - transactionDateTime, - amount, - totalAmount, - fee - }; - }; - - private handleOnFullReasonPress = () => - this.setState(ps => ({ showFullReason: !ps.showFullReason })); - - public render(): React.ReactNode { - const { psp } = this.props; - const transaction = this.props.route.params.transaction; - const data = this.getData(); - - const standardRow = (label: string, value: string) => ( - - - {label} - - - {value} - - - ); - - return ( - - - - - - {I18n.t("wallet.transactionFullReason")} - - {this.state.showFullReason && ( - - clipboardSetStringWithFeedback(data.fullReason) - } - > - {data.fullReason} - - )} - - {data.iuv && standardRow(I18n.t("payment.IUV"), data.iuv)} - {/** transaction date */} - - - {standardRow( - I18n.t("wallet.firstTransactionSummary.date"), - data.transactionDateTime - )} - - - - - - {standardRow( - I18n.t("wallet.firstTransactionSummary.amount"), - data.amount - )} - - {data.fee && ( - <> - - {standardRow( - I18n.t("wallet.firstTransactionSummary.fee"), - data.fee - )} - - )} - - - - {/** Total amount (amount + fee) */} - - -

- {I18n.t("wallet.firstTransactionSummary.total")} -

-
-

- {data.totalAmount} -

-
- - {(data.paymentMethodIcon || (psp && psp.logoPSP)) && ( - - - - - - )} - - {/** paymnet method icon */} - {/** to be implemented with the card logo when https://github.com/pagopa/io-app/pull/1622/ is merged */} - - {data.paymentMethodIcon ? ( - - {I18n.t("wallet.paymentMethod")} - - - ) : ( - data.paymentMethodBrand && ( - {data.paymentMethodBrand} - ) - )} - - {(data.paymentMethodIcon || data.paymentMethodBrand) && ( - - )} - - {/** Transaction id */} - - - {I18n.t("wallet.firstTransactionSummary.idTransaction")} - - - {data.idTransaction} - - - - - - -
-
- ); - } -} - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - navigateBackToEntrypointPayment: () => dispatch(backToEntrypointPayment()), - fetchPsp: (idPsp: number) => dispatch(fetchPsp.request({ idPsp })) -}); - -const mapStateToProps = (state: GlobalState, ownProps: OwnProps) => { - const transaction = ownProps.route.params.transaction; - const idPsp = String(transaction.idPsp); - - const potPsp = pipe( - pspStateByIdSelector(idPsp)(state), - O.fromNullable, - O.map(_ => _.psp), - O.getOrElseW(() => pot.none) - ); - const isLoading = pot.isLoading(potPsp); - - return { - wallets: pot.toUndefined(getWalletsById(state)), - isLoading, - psp: pot.toUndefined(potPsp) - }; -}; - -const ConnectedTransactionDetailsScreen = connect( - mapStateToProps, - mapDispatchToProps -)(withLoadingSpinner(TransactionDetailsScreen)); - -const TransactionDetailsScreenFC = () => { - const { ...modalContext } = React.useContext(LightModalContext); - const navigation = - useNavigation< - IOStackNavigationProp - >(); - const route = - useRoute< - Route< - "WALLET_TRANSACTION_DETAILS", - TransactionDetailsScreenNavigationParams - > - >(); - return ( - - ); -}; - -export default TransactionDetailsScreenFC; diff --git a/ts/screens/wallet/WalletHomeScreen.tsx b/ts/screens/wallet/WalletHomeScreen.tsx deleted file mode 100644 index 9e9f6f15765..00000000000 --- a/ts/screens/wallet/WalletHomeScreen.tsx +++ /dev/null @@ -1,544 +0,0 @@ -import { - ButtonOutline, - ButtonSolid, - IOColors, - IOToast, - VSpacer -} from "@pagopa/io-app-design-system"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as O from "fp-ts/lib/Option"; -import { constVoid } from "fp-ts/lib/function"; -import * as React from "react"; -import { - BackHandler, - Image, - NativeEventSubscription, - ScrollView, - StyleSheet, - View -} from "react-native"; -import { connect } from "react-redux"; -import { TypeEnum } from "../../../definitions/pagopa/Wallet"; -import SectionStatusComponent from "../../components/SectionStatus"; -import { Body } from "../../components/core/typography/Body"; -import { H3 } from "../../components/core/typography/H3"; -import { IOStyles } from "../../components/core/variables/IOStyles"; -import { withLightModalContext } from "../../components/helpers/withLightModalContext"; -import { - TabBarItemPressType, - withUseTabItemPressWhenScreenActive -} from "../../components/helpers/withUseTabItemPressWhenScreenActive"; -import { withValidatedPagoPaVersion } from "../../components/helpers/withValidatedPagoPaVersion"; -import { ContextualHelpPropsMarkdown } from "../../components/screens/BaseScreenComponent"; -import { EdgeBorderComponent } from "../../components/screens/EdgeBorderComponent"; -import { LightModalContextInterface } from "../../components/ui/LightModal"; -import { TransactionsList } from "../../components/wallet/TransactionsList"; -import WalletLayout from "../../components/wallet/WalletLayout"; -import SectionCardComponent, { - SectionCardStatus -} from "../../components/wallet/card/SectionCardComponent"; -import CgnCardInWalletContainer from "../../features/bonus/cgn/components/CgnCardInWalletComponent"; -import { cgnDetails } from "../../features/bonus/cgn/store/actions/details"; -import { - cgnDetailSelector, - isCgnInformationAvailableSelector -} from "../../features/bonus/cgn/store/reducers/details"; -import { loadAvailableBonuses } from "../../features/bonus/common/store/actions/availableBonusesTypes"; -import { supportedAvailableBonusSelector } from "../../features/bonus/common/store/selectors"; -import IDPayCardsInWalletContainer from "../../features/idpay/wallet/components/IDPayCardsInWalletContainer"; -import { idPayWalletGet } from "../../features/idpay/wallet/store/actions"; -import { idPayWalletInitiativeListSelector } from "../../features/idpay/wallet/store/reducers"; -import { PaymentsBarcodeRoutes } from "../../features/payments/barcode/navigation/routes"; -import { PaymentsTransactionRoutes } from "../../features/payments/transaction/navigation/routes"; -import NewPaymentMethodAddedNotifier from "../../features/wallet/component/NewMethodAddedNotifier"; -import FeaturedCardCarousel from "../../features/wallet/component/card/FeaturedCardCarousel"; -import WalletV2PreviewCards from "../../features/wallet/component/card/WalletV2PreviewCards"; -import I18n from "../../i18n"; -import { IOStackNavigationRouteProps } from "../../navigation/params/AppParamsList"; -import { MainTabParamsList } from "../../navigation/params/MainTabParamsList"; -import { - navigateBack, - navigateToTransactionDetailsScreen, - navigateToWalletAddPaymentMethod -} from "../../store/actions/navigation"; -import { Dispatch } from "../../store/actions/types"; -import { - fetchTransactionsLoadComplete, - fetchTransactionsRequestWithExpBackoff -} from "../../store/actions/wallet/transactions"; -import { - fetchWalletsRequestWithExpBackoff, - runSendAddCobadgeTrackSaga -} from "../../store/actions/wallet/wallets"; -import { - isCGNEnabledSelector, - isIdPayEnabledSelector -} from "../../store/reducers/backendStatus"; -import { paymentsHistorySelector } from "../../store/reducers/payments/history"; -import { isPagoPATestEnabledSelector } from "../../store/reducers/persistedPreferences"; -import { GlobalState } from "../../store/reducers/types"; -import { creditCardAttemptsSelector } from "../../store/reducers/wallet/creditCard"; -import { - areMoreTransactionsAvailable, - getTransactionsLoadedLength, - latestTransactionsSelector -} from "../../store/reducers/wallet/transactions"; -import { - bancomatListVisibleInWalletSelector, - cobadgeListVisibleInWalletSelector, - pagoPaCreditCardWalletV1Selector -} from "../../store/reducers/wallet/wallets"; -import customVariables from "../../theme/variables"; -import { Transaction, Wallet } from "../../types/pagopa"; - -export type WalletHomeNavigationParams = Readonly<{ - newMethodAdded: boolean; - keyFrom?: string; -}>; - -type State = { - hasFocus: boolean; -}; - -type Props = ReturnType & - ReturnType & - IOStackNavigationRouteProps & - LightModalContextInterface & - TabBarItemPressType; - -const styles = StyleSheet.create({ - emptyListWrapper: { - padding: customVariables.contentPadding, - alignItems: "center" - }, - emptyListContentTitle: { - paddingBottom: customVariables.contentPadding / 2 - }, - whiteBg: { - backgroundColor: IOColors.white - }, - noBottomPadding: { - padding: customVariables.contentPadding, - paddingBottom: 0 - } -}); - -const contextualHelpMarkdown: ContextualHelpPropsMarkdown = { - title: "wallet.contextualHelpTitle", - body: "wallet.contextualHelpContent" -}; - -/** - * Wallet home screen, with a list of recent transactions and payment methods, - * a "pay notice" button and payment methods info/button to add new ones - */ -class WalletHomeScreen extends React.PureComponent { - private subscription: NativeEventSubscription | undefined; - private focusUnsubscribe!: () => void; - private blurUnsubscribe!: () => void; - private scrollViewContentRef: React.RefObject = React.createRef(); - constructor(props: Props) { - super(props); - this.state = { - hasFocus: false - }; - } - - private handleBackPress = () => { - const keyFrom = this.props.route.params?.keyFrom; - const shouldPop = - this.props.route.params?.newMethodAdded && keyFrom !== undefined; - - if (shouldPop) { - this.props.navigateBack(); - return true; - } - return false; - }; - - private onFocus = () => { - this.props.loadAvailableBonuses(); - this.loadBonusIDPay(); - this.setState({ hasFocus: true }); - }; - - private onLostFocus = () => { - this.setState({ hasFocus: false }); - }; - - private loadBonusCgn = () => { - if (this.props.isCgnEnabled) { - this.props.loadCgnData(); - } - }; - - private loadBonusIDPay = () => { - if (this.props.isIdPayEnabled) { - this.props.loadIdPayWalletData(); - } - }; - - public componentDidMount() { - // eslint-disable-next-line functional/immutable-data - this.blurUnsubscribe = this.props.navigation.addListener( - "blur", - this.onLostFocus - ); - // eslint-disable-next-line functional/immutable-data - this.focusUnsubscribe = this.props.navigation.addListener( - "focus", - this.onFocus - ); - // WIP loadTransactions should not be called from here - // (transactions should be persisted & fetched periodically) - // https://www.pivotaltracker.com/story/show/168836972 - - this.props.loadWallets(); - - // load the bonus information on Wallet mount - // FIXME restore loadTransactions see https://www.pivotaltracker.com/story/show/176051000 - - // eslint-disable-next-line functional/immutable-data - this.subscription = BackHandler.addEventListener( - "hardwareBackPress", - this.handleBackPress - ); - - // Dispatch the action associated to the saga responsible to remind a user - // to add the co-badge card. - // This cover the case in which a user update the app and don't refresh the wallet. - this.props.runSendAddCobadgeMessageSaga(); - - this.props.setTabPressCallback(() => () => { - this.scrollViewContentRef.current?.scrollTo({ - x: 0, - y: 0, - animated: true - }); - }); - } - - public componentWillUnmount() { - this.subscription?.remove(); - this.focusUnsubscribe(); - this.blurUnsubscribe(); - } - - public componentDidUpdate(prevProps: Readonly) { - // check when all transactions are been loaded - // then dispatch an action to notify the loading is completed - if ( - prevProps.areMoreTransactionsAvailable && - !this.props.areMoreTransactionsAvailable && - pot.isSome(this.props.potTransactions) - ) { - this.props.dispatchAllTransactionLoaded(this.props.potTransactions.value); - } - if ( - this.state.hasFocus && - !pot.isError(prevProps.potWallets) && - pot.isError(this.props.potWallets) - ) { - IOToast.error(I18n.t("wallet.errors.loadingData")); - } - - // Dispatch the action associated to the saga responsible to remind a user - // to add the co-badge card only if a new bancomat or a co-badge card was added - const isBancomatListUpdated = - pot.isSome(this.props.bancomatListVisibleInWallet) && - (!pot.isSome(prevProps.bancomatListVisibleInWallet) || - this.props.bancomatListVisibleInWallet.value.length !== - prevProps.bancomatListVisibleInWallet.value.length); - - const isCobadgeListUpdated = - pot.isSome(this.props.coBadgeListVisibleInWallet) && - (!pot.isSome(prevProps.coBadgeListVisibleInWallet) || - this.props.coBadgeListVisibleInWallet.value.length !== - prevProps.coBadgeListVisibleInWallet.value.length); - - if (isBancomatListUpdated || isCobadgeListUpdated) { - this.props.runSendAddCobadgeMessageSaga(); - } - } - - private cardHeader(isError: boolean = false) { - const sectionCardStatus: SectionCardStatus = pot.fold( - this.props.potWallets, - () => "show", - () => "loading", - _ => "loading", - _ => "show", - _ => "refresh", - _ => "loading", - _ => "loading", - _ => "refresh" - ); - return ( - { - if (sectionCardStatus !== "loading") { - this.props.loadWallets(); - } - }} - isError={isError} - /> - ); - } - - private getCreditCards = () => - pot - .getOrElse(this.props.potWallets, []) - .filter(w => w.type === TypeEnum.CREDIT_CARD); - - private getBonusLoadingStatus = (): SectionCardStatus => { - const isCgnLoading = pot.isLoading(this.props.cgnDetails); - const isIdPayLoading = pot.isLoading(this.props.idPayDetails); - const areBonusesLoading = isCgnLoading || isIdPayLoading; - const areBonusesSome = - pot.isSome(this.props.cgnDetails) || pot.isSome(this.props.idPayDetails); - // if any bonus is loading or updating - if (areBonusesLoading) { - return "loading"; - } - // if at least one bonus is some - if (areBonusesSome) { - return "refresh"; - } - return "show"; - }; - - private cardPreview() { - const bonusLoadingStatus = this.getBonusLoadingStatus(); - const { isCgnEnabled, isIdPayEnabled } = this.props; - return ( - - - {this.cardHeader(false)} - {/* new payment methods rendering */} - - { - if (bonusLoadingStatus !== "loading") { - this.props.loadAvailableBonuses(); - if (isCgnEnabled) { - this.loadBonusCgn(); - } - if (isIdPayEnabled) { - this.loadBonusIDPay(); - } - } - }} - /> - {isCgnEnabled && } - {isIdPayEnabled && } - - ); - } - - private transactionError() { - return ( - -

- {I18n.t("wallet.latestTransactions")} -

- - - this.props.loadTransactions(this.props.transactionsLoadedLength) - } - /> - - -
- ); - } - - private listEmptyComponent() { - return ( - - - - {I18n.t("wallet.noTransactionsInWalletHome")} - - - - - - ); - } - - private handleLoadMoreTransactions = () => { - this.props.loadTransactions(this.props.transactionsLoadedLength); - }; - - private navigateToWalletTransactionDetailsScreen = ( - transaction: Transaction - ) => { - this.props.navigation.navigate( - PaymentsTransactionRoutes.PAYMENT_TRANSACTION_NAVIGATOR, - { - screen: PaymentsTransactionRoutes.PAYMENT_TRANSACTION_DETAILS, - params: { - transactionId: transaction.id - } - } - ); - }; - - private transactionList( - potTransactions: pot.Pot, Error> - ) { - return ( - - ); - } - - private navigateToPaymentScanQrCode = () => { - this.props.navigation.navigate( - PaymentsBarcodeRoutes.PAYMENT_BARCODE_NAVIGATOR, - { - screen: PaymentsBarcodeRoutes.PAYMENT_BARCODE_SCAN - } - ); - }; - - private footerButton(potWallets: pot.Pot, Error>) { - return ( - - ); - } - - public render(): React.ReactNode { - const { potWallets, potTransactions } = this.props; - - const headerContent = <>{this.cardPreview()}; - const transactionContent = - pot.isError(potTransactions) || - (pot.isNone(potTransactions) && - !pot.isLoading(potTransactions) && - !pot.isUpdating(potTransactions)) - ? this.transactionError() - : this.transactionList(potTransactions); - - const footerContent = pot.isSome(potWallets) - ? this.footerButton(potWallets) - : undefined; - - return ( - } - > - <> - {this.props.isCgnEnabled && } - {transactionContent} - - - - ); - } - - private getHeaderHeight() { - return ( - 250 + - (this.props.isCgnEnabled && this.props.isCgnInfoAvailable ? 88 : 0) + - this.getCreditCards().length * 56 - ); - } -} - -const mapStateToProps = (state: GlobalState) => ({ - availableBonusesList: supportedAvailableBonusSelector(state), - // TODO: This selector (pagoPaCreditCardWalletV1Selector) should return the credit cards - // available for display in the wallet, so the cards added with the APP or with the WISP. - // But it leverage on the assumption that the meaning of pagoPA === true is the same of onboardingChannel !== "EXT" - potWallets: pagoPaCreditCardWalletV1Selector(state), - anyHistoryPayments: paymentsHistorySelector(state).length > 0, - anyCreditCardAttempts: creditCardAttemptsSelector(state).length > 0, - potTransactions: latestTransactionsSelector(state), - transactionsLoadedLength: getTransactionsLoadedLength(state), - areMoreTransactionsAvailable: areMoreTransactionsAvailable(state), - isPagoPATestEnabled: isPagoPATestEnabledSelector(state), - cgnDetails: cgnDetailSelector(state), - idPayDetails: idPayWalletInitiativeListSelector(state), - isCgnInfoAvailable: isCgnInformationAvailableSelector(state), - isCgnEnabled: isCGNEnabledSelector(state), - bancomatListVisibleInWallet: bancomatListVisibleInWalletSelector(state), - coBadgeListVisibleInWallet: cobadgeListVisibleInWalletSelector(state), - isIdPayEnabled: isIdPayEnabledSelector(state) -}); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - loadCgnData: () => dispatch(cgnDetails.request()), - loadIdPayWalletData: () => dispatch(idPayWalletGet.request()), - navigateToWalletAddPaymentMethod: (keyFrom?: string) => - navigateToWalletAddPaymentMethod({ inPayment: O.none, keyFrom }), - navigateToTransactionDetailsScreen: (transaction: Transaction) => { - navigateToTransactionDetailsScreen({ - transaction, - isPaymentCompletedTransaction: false - }); - }, - loadAvailableBonuses: () => dispatch(loadAvailableBonuses.request()), - navigateBack: () => navigateBack(), - loadTransactions: (start: number) => - dispatch(fetchTransactionsRequestWithExpBackoff({ start })), - loadWallets: () => dispatch(fetchWalletsRequestWithExpBackoff()), - dispatchAllTransactionLoaded: (transactions: ReadonlyArray) => - dispatch(fetchTransactionsLoadComplete(transactions)), - runSendAddCobadgeMessageSaga: () => dispatch(runSendAddCobadgeTrackSaga()) -}); - -export default withValidatedPagoPaVersion( - connect( - mapStateToProps, - mapDispatchToProps - )( - withUseTabItemPressWhenScreenActive(withLightModalContext(WalletHomeScreen)) - ) -); diff --git a/ts/screens/wallet/__tests__/AddCardScreen.test.tsx b/ts/screens/wallet/__tests__/AddCardScreen.test.tsx deleted file mode 100644 index 0ccd35a5063..00000000000 --- a/ts/screens/wallet/__tests__/AddCardScreen.test.tsx +++ /dev/null @@ -1,288 +0,0 @@ -import { fireEvent } from "@testing-library/react-native"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { createStore } from "redux"; -import { IPaymentMethod } from "../../../components/wallet/PaymentMethodsList"; -import I18n from "../../../i18n"; -import ROUTES from "../../../navigation/routes"; -import { applicationChangeState } from "../../../store/actions/application"; -import { appReducer } from "../../../store/reducers"; -import { GlobalState } from "../../../store/reducers/types"; -import { isValidCardHolder } from "../../../utils/input"; -import { renderScreenWithNavigationStoreContext } from "../../../utils/testWrapper"; -import AddCardScreen, { AddCardScreenNavigationParams } from "../AddCardScreen"; -import { testableFunctions } from "../AddPaymentMethodScreen"; - -jest.mock("react-native-share", () => ({ - open: jest.fn() -})); - -const aValidCardHolder = "Mario Rossi"; -const aValidPan = "4916916025914971"; -const aValidAmexPan = "374623599297410"; -const aValidExpirationDate = "12/99"; -const aValidSecurityCode = "123"; -const aValidAmexSecurityCode = "1234"; - -const anInvalidCardHolder = "Màriò Ròssì"; -describe("AddCardScreen", () => { - beforeEach(() => jest.useFakeTimers()); - it("should show the continue button disabled if there aren't data", () => { - const component = getComponent(); - const continueButton = component.queryByText( - I18n.t("global.buttons.continue") - ); - expect(continueButton).not.toBeNull(); - expect(continueButton).toBeDisabled(); - }); - - it("should show the continue button active if all fields are correctly filled with non amex card", () => { - const component = getComponent(); - const cardHolderInput = component.queryByTestId("cardHolderInput"); - const panInputMask = component.queryByTestId("panInputMask"); - const expirationDateInput = component.queryByTestId( - "expirationDateInputMask" - ); - const securityCodeInput = component.queryByTestId("securityCodeInputMask"); - const continueButton = component.queryByText( - I18n.t("global.buttons.continue") - ); - - expect(cardHolderInput).not.toBeNull(); - expect(panInputMask).not.toBeNull(); - expect(expirationDateInput).not.toBeNull(); - expect(securityCodeInput).not.toBeNull(); - - if ( - cardHolderInput && - panInputMask && - expirationDateInput && - securityCodeInput - ) { - fireEvent.changeText(panInputMask, aValidPan); - fireEvent.changeText(expirationDateInput, aValidExpirationDate); - fireEvent.changeText(securityCodeInput, aValidSecurityCode); - fireEvent.changeText(cardHolderInput, aValidCardHolder); - } - - expect(continueButton).not.toBeDisabled(); - }); - - it("should show the continue button active if all fields are correctly filled with amex card", () => { - const component = getComponent(); - const cardHolderInput = component.queryByTestId("cardHolderInput"); - const panInputMask = component.queryByTestId("panInputMask"); - const expirationDateInput = component.queryByTestId( - "expirationDateInputMask" - ); - const securityCodeInput = component.queryByTestId("securityCodeInputMask"); - const continueButton = component.queryByText( - I18n.t("global.buttons.continue") - ); - - expect(cardHolderInput).not.toBeNull(); - expect(panInputMask).not.toBeNull(); - expect(expirationDateInput).not.toBeNull(); - expect(securityCodeInput).not.toBeNull(); - - if ( - cardHolderInput && - panInputMask && - expirationDateInput && - securityCodeInput - ) { - fireEvent.changeText(panInputMask, aValidAmexPan); - fireEvent.changeText(expirationDateInput, aValidExpirationDate); - fireEvent.changeText(securityCodeInput, aValidAmexSecurityCode); - fireEvent.changeText(cardHolderInput, aValidCardHolder); - } - - expect(continueButton).not.toBeDisabled(); - }); - - it("should show the continue button disabled if the cardHolder is invalid", () => { - const component = getComponent(); - const cardHolderInput = component.queryByTestId("cardHolderInput"); - const panInputMask = component.queryByTestId("panInputMask"); - const expirationDateInput = component.queryByTestId( - "expirationDateInputMask" - ); - const securityCodeInput = component.queryByTestId("securityCodeInputMask"); - const continueButton = component.queryByText( - I18n.t("global.buttons.continue") - ); - - if ( - cardHolderInput && - panInputMask && - expirationDateInput && - securityCodeInput - ) { - fireEvent.changeText(panInputMask, aValidPan); - fireEvent.changeText(expirationDateInput, aValidExpirationDate); - fireEvent.changeText(securityCodeInput, aValidSecurityCode); - fireEvent.changeText(cardHolderInput, anInvalidCardHolder); - } - - const errorMessage = component.queryByText( - I18n.t("wallet.dummyCard.labels.holder.description.error") - ); - - expect(isValidCardHolder(O.some(anInvalidCardHolder))).toBeFalsy(); - expect(errorMessage).not.toBeNull(); - expect(continueButton).toBeDisabled(); - }); -}); - -describe("getPaymentMethods", () => { - beforeEach(() => jest.useFakeTimers()); - const props = { - navigateBack: jest.fn(), - startBPayOnboarding: jest.fn(), - startPaypalOnboarding: jest.fn(), - startAddBancomat: jest.fn(), - isPaypalAlreadyAdded: true, - isPaypalEnabled: true, - canOnboardBPay: false, - canPayWithBPay: false - }; - // TODO: ⚠️ cast to any only to complete the merge, should be removed! - const methods = testableFunctions.getPaymentMethods!( - props as any, - { - onlyPaymentMethodCanPay: true, - isPaymentOnGoing: true, - isPaypalEnabled: true, - canOnboardBPay: true - }, - jest.fn() - ); - - const getMethodStatus = ( - methods: ReadonlyArray, - name: string - ): IPaymentMethod["status"] => methods.find(m => m.name === name)!.status; - - it("credit card should be always implemented", () => { - expect( - getMethodStatus(methods, I18n.t("wallet.methods.card.name")) - ).toEqual("implemented"); - }); - - it("paypal should be always implemented when the FF is ON", () => { - expect( - getMethodStatus(methods, I18n.t("wallet.methods.paypal.name")) - ).toEqual("implemented"); - }); - - it("paypal should be always notImplemented when the FF is OFF", () => { - // TODO: ⚠️ cast to any only to complete the merge, should be removed! - const methods = testableFunctions.getPaymentMethods!( - props as any, - { - onlyPaymentMethodCanPay: true, - isPaymentOnGoing: true, - isPaypalEnabled: false, - canOnboardBPay: true - }, - jest.fn() - ); - expect( - getMethodStatus(methods, I18n.t("wallet.methods.paypal.name")) - ).toEqual("notImplemented"); - }); - - it("bpay should be always notImplemented if Bpay onboarding FF is OFF", () => { - // TODO: ⚠️ cast to any only to complete the merge, should be removed! - const methods = testableFunctions.getPaymentMethods!( - props as any, - { - onlyPaymentMethodCanPay: true, - isPaymentOnGoing: true, - isPaypalEnabled: true, - canOnboardBPay: false - }, - jest.fn() - ); - expect( - getMethodStatus(methods, I18n.t("wallet.methods.bancomatPay.name")) - ).toEqual("notImplemented"); - }); - - it("bpay should be always implemented if Bpay onboarding FF is ON and onlyPaymentMethodCanPay flag is OFF", () => { - // TODO: ⚠️ cast to any only to complete the merge, should be removed! - const methods = testableFunctions.getPaymentMethods!( - props as any, - { - onlyPaymentMethodCanPay: false, - isPaymentOnGoing: true, - isPaypalEnabled: true, - canOnboardBPay: true - }, - jest.fn() - ); - expect( - getMethodStatus(methods, I18n.t("wallet.methods.bancomatPay.name")) - ).toEqual("implemented"); - }); - - it("bpay should be notImplemented while a payment if it can be onboarded but it cannot pay", () => { - const canPayWithBPay = false; - const canOnboardBPay = true; - // TODO: ⚠️ cast to any only to complete the merge, should be removed! - const methods = testableFunctions.getPaymentMethods!( - { ...props, canPayWithBPay } as any, - { - onlyPaymentMethodCanPay: true, - isPaymentOnGoing: true, - isPaypalEnabled: true, - canOnboardBPay: canPayWithBPay && canOnboardBPay - }, - jest.fn() - ); - expect( - getMethodStatus(methods, I18n.t("wallet.methods.bancomatPay.name")) - ).toEqual("notImplemented"); - }); - - it("bpay should be implemented outside a payment if it can be onboarded but it cannot pay", () => { - const canPayWithBPay = true; - const canOnboardBPay = true; - const methods = testableFunctions.getPaymentMethods!( - // TODO: ⚠️ cast to any only to complete the merge, should be removed! - { ...props, canPayWithBPay } as any, - { - onlyPaymentMethodCanPay: true, - isPaymentOnGoing: false, - isPaypalEnabled: true, - canOnboardBPay: canPayWithBPay && canOnboardBPay - }, - jest.fn() - ); - expect( - getMethodStatus(methods, I18n.t("wallet.methods.bancomatPay.name")) - ).toEqual("implemented"); - }); -}); - -const getComponent = () => { - type NavigationParams = AddCardScreenNavigationParams; - const params: NavigationParams = { - inPayment: O.none - } as NavigationParams; - - const ToBeTested: React.FunctionComponent< - React.ComponentProps - > = (props: React.ComponentProps) => ( - - ); - - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - return renderScreenWithNavigationStoreContext( - ToBeTested, - ROUTES.WALLET_ADD_CARD, - params, - store - ); -}; diff --git a/ts/screens/wallet/__tests__/ConfirmCardDetailScreen.test.tsx b/ts/screens/wallet/__tests__/ConfirmCardDetailScreen.test.tsx deleted file mode 100644 index 2dc4002a935..00000000000 --- a/ts/screens/wallet/__tests__/ConfirmCardDetailScreen.test.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { createStore } from "redux"; -import I18n from "../../../i18n"; -import ROUTES from "../../../navigation/routes"; -import { applicationChangeState } from "../../../store/actions/application"; -import { - addWalletCreditCardWithBackoffRetryRequest, - fetchWalletsRequest -} from "../../../store/actions/wallet/wallets"; -import { appReducer } from "../../../store/reducers"; -import { GlobalState } from "../../../store/reducers/types"; -import { CreditCard, NullableWallet } from "../../../types/pagopa"; -import { CreditCardPan } from "../../../utils/input"; -import { renderScreenWithNavigationStoreContext } from "../../../utils/testWrapper"; -import ConfirmCardDetailsScreen, { - ConfirmCardDetailsScreenNavigationParams -} from "../ConfirmCardDetailsScreen"; - -jest.mock("react-native-share", () => ({ - open: jest.fn() -})); - -describe("ConfirmCardDetailScreen", () => { - beforeEach(() => jest.useFakeTimers()); - - it("should show the loading modal if creditCardAddWallet is pot.loading", () => { - const { component, store } = getComponent(); - - store.dispatch( - addWalletCreditCardWithBackoffRetryRequest({ - creditcard: {} as NullableWallet - }) - ); - - const loadingModal = component.getAllByTestId("overlayComponent"); - const loadingText = component.getByText( - I18n.t("wallet.saveCard.loadingAlert") - ); - - expect(loadingModal).not.toBeNull(); - expect(loadingText).not.toBeEmpty(); - }); - - it("should show the loading modal if walletById is pot.loading", () => { - const { component, store } = getComponent(); - - store.dispatch(fetchWalletsRequest()); - - const loadingModal = component.getAllByTestId("overlayComponent"); - const loadingText = component.getByText( - I18n.t("wallet.saveCard.loadingAlert") - ); - - expect(loadingModal).not.toBeNull(); - expect(loadingText).not.toBeEmpty(); - }); -}); - -const getComponent = () => { - const params: ConfirmCardDetailsScreenNavigationParams = { - creditCard: { - pan: "123456789" as CreditCardPan, - holder: "tester" - } as CreditCard, - inPayment: O.none - } as ConfirmCardDetailsScreenNavigationParams; - const ToBeTested: React.FunctionComponent< - React.ComponentProps - > = () => ; - - const globalState = appReducer(undefined, applicationChangeState("active")); - const store = createStore(appReducer, globalState as any); - const component = renderScreenWithNavigationStoreContext( - ToBeTested, - ROUTES.WALLET_ADD_CARD, - params, - store - ); - - return { component, store }; -}; diff --git a/ts/screens/wallet/__tests__/WalletHomeScreen.test.tsx b/ts/screens/wallet/__tests__/WalletHomeScreen.test.tsx deleted file mode 100644 index 1883191dedb..00000000000 --- a/ts/screens/wallet/__tests__/WalletHomeScreen.test.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import DeviceInfo from "react-native-device-info"; - -import { createStore } from "redux"; -import { versionInfoLoadSuccess } from "../../../common/versionInfo/store/actions/versionInfo"; -import { mockIoVersionInfo } from "../../../common/versionInfo/store/reducers/__mock__/ioVersionInfo"; -import { minAppVersionAppVersionTestCases } from "../../../common/versionInfo/store/reducers/__mock__/testVersion"; -import I18n from "../../../i18n"; -import ROUTES from "../../../navigation/routes"; -import { applicationChangeState } from "../../../store/actions/application"; -import { appReducer } from "../../../store/reducers"; -import { GlobalState } from "../../../store/reducers/types"; -import { renderScreenWithNavigationStoreContext } from "../../../utils/testWrapper"; -import WalletHomeScreen from "../WalletHomeScreen"; - -describe("WalletHomeScreen", () => { - jest.useFakeTimers(); - minAppVersionAppVersionTestCases.forEach(t => { - it(`When min_app_version: ${t.e1}, appVersion: ${ - t.e2 - }, WalletHomeScreen should ${ - t.e3 ? "not " : "" - }render UpdateAppModal`, () => { - testWalletHomeScreen(t.e1, t.e2, t.e3); - }); - }); -}); - -const testWalletHomeScreen = ( - minVersion: string, - appVersion: string, - isSupported: boolean -) => { - const globalState = appReducer(undefined, applicationChangeState("active")); - jest.spyOn(DeviceInfo, "getVersion").mockReturnValue(appVersion); - jest.spyOn(DeviceInfo, "getReadableVersion").mockReturnValue(appVersion); - const store = createStore(appReducer, globalState as any); - store.dispatch( - versionInfoLoadSuccess({ - ...mockIoVersionInfo, - min_app_version_pagopa: { - ios: minVersion, - android: minVersion - } - }) - ); - expect(store.getState().versionInfo?.min_app_version_pagopa).toStrictEqual({ - ios: minVersion, - android: minVersion - }); - - const testComponent = renderScreenWithNavigationStoreContext( - WalletHomeScreen, - ROUTES.WALLET_HOME, - {}, - store - ); - expect(testComponent).not.toBeNull(); - - const searchUpdateText = expect( - testComponent.queryByText(I18n.t("wallet.alert.titlePagoPaUpdateApp")) - ); - if (isSupported) { - searchUpdateText.toBeNull(); - } else { - searchUpdateText.not.toBeNull(); - } -}; diff --git a/ts/screens/wallet/creditCardOnboardingAttempts/CreditCardOnboardingAttemptDetailScreen.tsx b/ts/screens/wallet/creditCardOnboardingAttempts/CreditCardOnboardingAttemptDetailScreen.tsx deleted file mode 100644 index e2c28d87516..00000000000 --- a/ts/screens/wallet/creditCardOnboardingAttempts/CreditCardOnboardingAttemptDetailScreen.tsx +++ /dev/null @@ -1,217 +0,0 @@ -import { ButtonOutline, VSpacer } from "@pagopa/io-app-design-system"; -import { Route, useRoute } from "@react-navigation/native"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { View, StyleSheet } from "react-native"; -import { ToolEnum } from "../../../../definitions/content/AssistanceToolConfig"; -import { Body } from "../../../components/core/typography/Body"; -import { H3 } from "../../../components/core/typography/H3"; -import { Label } from "../../../components/core/typography/Label"; -import { IOStyles } from "../../../components/core/variables/IOStyles"; -import { BadgeComponent } from "../../../components/screens/BadgeComponent"; -import BaseScreenComponent from "../../../components/screens/BaseScreenComponent"; -import { SlidedContentComponent } from "../../../components/wallet/SlidedContentComponent"; -import { getPanDescription } from "../../../components/wallet/creditCardOnboardingAttempts/CreditCardAttemptsList"; -import { - zendeskSelectedCategory, - zendeskSupportStart -} from "../../../features/zendesk/store/actions"; -import I18n from "../../../i18n"; -import { useIONavigation } from "../../../navigation/params/AppParamsList"; -import { useIODispatch, useIOSelector } from "../../../store/hooks"; -import { canShowHelpSelector } from "../../../store/reducers/assistanceTools"; -import { assistanceToolConfigSelector } from "../../../store/reducers/backendStatus"; -import { CreditCardInsertion } from "../../../store/reducers/wallet/creditCard"; -import { outcomeCodesSelector } from "../../../store/reducers/wallet/outcomeCode"; -import customVariables from "../../../theme/variables"; -import { formatDateAsLocal } from "../../../utils/dates"; -import { getPaymentOutcomeCodeDescription } from "../../../utils/payment"; -import { - addTicketCustomField, - appendLog, - assistanceToolRemoteConfig, - resetCustomFields, - zendeskCategoryId, - zendeskPaymentMethodCategory -} from "../../../utils/supportAssistance"; -import ItemSeparatorComponent from "../../../components/ItemSeparatorComponent"; - -export type CreditCardOnboardingAttemptDetailScreenNavigationParams = Readonly<{ - attempt: CreditCardInsertion; -}>; - -const styles = StyleSheet.create({ - row: { - flexDirection: "row", - justifyContent: "space-between", - alignItems: "center" - }, - padded: { paddingHorizontal: customVariables.contentPadding }, - - badge: { - justifyContent: "flex-start", - flexDirection: "row", - alignItems: "center" - }, - badgeLabel: { - paddingLeft: 8 - } -}); - -const renderRow = (label: string, value: string) => ( - - - {value} - -); -/** - * This screen shows credit card onboarding attempt details and allows the user - * to ask assistance about this attempts - */ -const CreditCardOnboardingAttemptDetailScreen = () => { - const dispatch = useIODispatch(); - const { attempt } = - useRoute< - Route< - "CREDIT_CARD_ONBOARDING_ATTEMPT_DETAIL", - CreditCardOnboardingAttemptDetailScreenNavigationParams - > - >().params; - const navigation = useIONavigation(); - const assistanceToolConfig = useIOSelector(assistanceToolConfigSelector); - const outcomeCodes = useIOSelector(outcomeCodesSelector); - const choosenTool = assistanceToolRemoteConfig(assistanceToolConfig); - const canShowHelp = useIOSelector(canShowHelpSelector); - - const zendeskAssistanceLogAndStart = () => { - resetCustomFields(); - // Set metodo_di_pagamento as category - addTicketCustomField(zendeskCategoryId, zendeskPaymentMethodCategory.value); - // Append the attempt in the log - appendLog(JSON.stringify(attempt)); - dispatch( - zendeskSupportStart({ - startingRoute: "n/a", - assistanceForPayment: false, - assistanceForCard: true, - assistanceForFci: false - }) - ); - dispatch(zendeskSelectedCategory(zendeskPaymentMethodCategory)); - }; - - const handleAskAssistance = () => { - switch (choosenTool) { - case ToolEnum.zendesk: - zendeskAssistanceLogAndStart(); - break; - } - }; - - const renderSeparator = () => ( - - - - - - ); - - const renderHelper = () => ( - - - - - - ); - - const startDate = new Date(attempt.startDate); - const when = `${formatDateAsLocal( - startDate, - true, - true - )} - ${startDate.toLocaleTimeString()}`; - const conditionalData = attempt.onboardingComplete - ? { - color: "green", - header: I18n.t("wallet.creditCard.onboardingAttempts.success") - } - : { - color: "red", - header: I18n.t("wallet.creditCard.onboardingAttempts.failure") - }; - const errorDescription = - !attempt.onboardingComplete && attempt.outcomeCode - ? getPaymentOutcomeCodeDescription(attempt.outcomeCode, outcomeCodes) - : undefined; - return ( - navigation.goBack()} - showChat={false} - dark={true} - headerTitle={I18n.t("wallet.creditCard.onboardingAttempts.title")} - > - - -

- {I18n.t("wallet.creditCard.onboardingAttempts.detailTitle")} -

- {renderSeparator()} - - - - - - { - - } - - {attempt.failureReason && ( - <> - - - - )} - {errorDescription && O.isSome(errorDescription) && ( - <> - - - - )} - {renderRow( - I18n.t("payment.details.info.outcomeCode"), - attempt.outcomeCode ?? "-" - )} - - {renderRow( - I18n.t("wallet.creditCard.onboardingAttempts.dateTime"), - when - )} - {renderSeparator()} - {/* This check is redundant, since if the help can't be shown the user can't get there */} - {canShowHelp && renderHelper()} -
-
- ); -}; - -export default CreditCardOnboardingAttemptDetailScreen; diff --git a/ts/screens/wallet/creditCardOnboardingAttempts/CreditCardOnboardingAttemptsScreen.tsx b/ts/screens/wallet/creditCardOnboardingAttempts/CreditCardOnboardingAttemptsScreen.tsx deleted file mode 100644 index ae7b194ea90..00000000000 --- a/ts/screens/wallet/creditCardOnboardingAttempts/CreditCardOnboardingAttemptsScreen.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { H4, VSpacer } from "@pagopa/io-app-design-system"; -import * as React from "react"; -import { Body } from "../../../components/core/typography/Body"; -import { withValidatedPagoPaVersion } from "../../../components/helpers/withValidatedPagoPaVersion"; -import BaseScreenComponent from "../../../components/screens/BaseScreenComponent"; -import { EdgeBorderComponent } from "../../../components/screens/EdgeBorderComponent"; -import { CreditCardAttemptsList } from "../../../components/wallet/creditCardOnboardingAttempts/CreditCardAttemptsList"; -import I18n from "../../../i18n"; -import { useIONavigation } from "../../../navigation/params/AppParamsList"; -import { navigateToCreditCardOnboardingAttempt } from "../../../store/actions/navigation"; -import { - CreditCardInsertion, - creditCardAttemptsSelector -} from "../../../store/reducers/wallet/creditCard"; -import { useIOSelector } from "../../../store/hooks"; - -const ListEmptyComponent = ( - <> - -

- {I18n.t("wallet.creditCard.onboardingAttempts.emptyTitle")} -

- - {I18n.t("wallet.creditCard.onboardingAttempts.emptyBody")} - - - -); - -/** - * This screen shows all attempts of onboarding payment instruments - */ -const CreditCardOnboardingAttemptsScreen = () => { - const creditCardOnboardingAttempts = useIOSelector( - creditCardAttemptsSelector - ); - const navigation = useIONavigation(); - - return ( - navigation.goBack()} - headerTitle={I18n.t("wallet.creditCard.onboardingAttempts.title")} - > - - navigateToCreditCardOnboardingAttempt({ - attempt - }) - } - /> - - ); -}; - -export default withValidatedPagoPaVersion(CreditCardOnboardingAttemptsScreen); diff --git a/ts/screens/wallet/payment/CodesPositionManualPaymentModal.tsx b/ts/screens/wallet/payment/CodesPositionManualPaymentModal.tsx deleted file mode 100644 index 47db9291c00..00000000000 --- a/ts/screens/wallet/payment/CodesPositionManualPaymentModal.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import * as React from "react"; -import { - BackHandler, - Dimensions, - Image, - NativeEventSubscription, - StyleSheet, - View -} from "react-native"; -import ImageZoom from "react-native-image-pan-zoom"; -import { - HeaderSecondLevel, - IOColors, - IOStyles -} from "@pagopa/io-app-design-system"; -import I18n from "../../../i18n"; - -type Props = { - onCancel: () => void; -}; - -const pngHeight = 2000; -const pngWidth = 2828; -const screenHeight = Dimensions.get("screen").height; -const screenWidth = Dimensions.get("screen").width; - -const imageHeight = (screenWidth * pngWidth) / pngHeight; -const imageWidth = (screenHeight * pngHeight) / pngWidth; -const styles = StyleSheet.create({ - imageStyle: { - width: imageWidth, - height: imageHeight, - resizeMode: "contain", - justifyContent: "center", - alignSelf: "center" - } -}); - -class CodesPositionManualPaymentModal extends React.PureComponent { - private subscription: NativeEventSubscription | undefined; - private handleBackPress = () => { - this.props.onCancel(); - return true; - }; - - public componentDidMount() { - // eslint-disable-next-line functional/immutable-data - this.subscription = BackHandler.addEventListener( - "hardwareBackPress", - this.handleBackPress - ); - } - - public componentWillUnmount() { - this.subscription?.remove(); - } - - public render() { - return ( - - - - - - - ); - } -} - -export default CodesPositionManualPaymentModal; diff --git a/ts/screens/wallet/payment/ConfirmPaymentMethodScreen.tsx b/ts/screens/wallet/payment/ConfirmPaymentMethodScreen.tsx deleted file mode 100644 index a260c127b6f..00000000000 --- a/ts/screens/wallet/payment/ConfirmPaymentMethodScreen.tsx +++ /dev/null @@ -1,670 +0,0 @@ -import { - FooterWithButtons, - HSpacer, - IOColors, - IOToast, - Icon, - VSpacer -} from "@pagopa/io-app-design-system"; -import { AmountInEuroCents, RptId } from "@pagopa/io-pagopa-commons/lib/pagopa"; -import { Route, useRoute } from "@react-navigation/native"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import * as React from "react"; -import { - Alert, - SafeAreaView, - ScrollView, - StyleSheet, - Text, - View -} from "react-native"; -import { connect } from "react-redux"; -import { ImportoEuroCents } from "../../../../definitions/backend/ImportoEuroCents"; -import { PaymentRequestsGetResponse } from "../../../../definitions/backend/PaymentRequestsGetResponse"; -import { PspData } from "../../../../definitions/pagopa/PspData"; -import CardIcon from "../../../../img/wallet/card.svg"; -import BancomatPayLogo from "../../../../img/wallet/payment-methods/bancomat_pay.svg"; -import PaypalLogo from "../../../../img/wallet/payment-methods/paypal/paypal_logo.svg"; -import TagIcon from "../../../../img/wallet/tag.svg"; -import { - getValueOrElse, - isError, - isLoading, - isReady -} from "../../../common/model/RemoteValue"; -import LoadingSpinnerOverlay from "../../../components/LoadingSpinnerOverlay"; -import { H1 } from "../../../components/core/typography/H1"; -import { H3 } from "../../../components/core/typography/H3"; -import { H4 } from "../../../components/core/typography/H4"; -import { LabelSmall } from "../../../components/core/typography/LabelSmall"; -import { IOStyles } from "../../../components/core/variables/IOStyles"; -import BaseScreenComponent, { - ContextualHelpPropsMarkdown -} from "../../../components/screens/BaseScreenComponent"; -import { - LightModalContext, - LightModalContextInterface -} from "../../../components/ui/LightModal"; -import { PayWebViewModal } from "../../../components/wallet/PayWebViewModal"; -import { SelectionBox } from "../../../components/wallet/SelectionBox"; -import { getCardIconFromBrandLogo } from "../../../components/wallet/card/Logo"; -import { pagoPaApiUrlPrefix, pagoPaApiUrlPrefixTest } from "../../../config"; -import { BrandImage } from "../../../features/wallet/component/card/BrandImage"; -import I18n from "../../../i18n"; -import { useIONavigation } from "../../../navigation/params/AppParamsList"; -import ROUTES from "../../../navigation/routes"; -import { - navigateToPaymentOutcomeCode, - navigateToPaymentPickPaymentMethodScreen, - navigateToPaymentPickPspScreen -} from "../../../store/actions/navigation"; -import { Dispatch } from "../../../store/actions/types"; -import { paymentOutcomeCode } from "../../../store/actions/wallet/outcomeCode"; -import { - PaymentMethodType, - PaymentWebViewEndReason, - abortRunningPayment, - paymentCompletedFailure, - paymentCompletedSuccess, - paymentExecuteStart, - paymentWebViewEnd -} from "../../../store/actions/wallet/payment"; -import { fetchTransactionsRequestWithExpBackoff } from "../../../store/actions/wallet/transactions"; -import { - bancomatPayConfigSelector, - isPaypalEnabledSelector -} from "../../../store/reducers/backendStatus"; -import { isPagoPATestEnabledSelector } from "../../../store/reducers/persistedPreferences"; -import { GlobalState } from "../../../store/reducers/types"; -import { outcomeCodesSelector } from "../../../store/reducers/wallet/outcomeCode"; -import { - PaymentStartWebViewPayload, - paymentStartPayloadSelector, - pmSessionTokenSelector, - pspSelectedV2ListSelector, - pspV2ListSelector -} from "../../../store/reducers/wallet/payment"; -import { paymentMethodByIdSelector } from "../../../store/reducers/wallet/wallets"; -import { OutcomeCodesKey } from "../../../types/outcomeCode"; -import { - PaymentMethod, - RawPaymentMethod, - Wallet, - isCreditCard, - isRawPayPal -} from "../../../types/pagopa"; -import { PayloadForAction } from "../../../types/utils"; -import { getTranslatedShortNumericMonthYear } from "../../../utils/dates"; -import { getLocalePrimaryWithFallback } from "../../../utils/locale"; -import { isPaymentOutcomeCodeSuccessfully } from "../../../utils/payment"; -import { getPaypalAccountEmail } from "../../../utils/paypal"; -import { getLookUpIdPO } from "../../../utils/pmLookUpId"; -import { formatNumberCentsToAmount } from "../../../utils/stringBuilder"; -import { openWebUrl } from "../../../utils/url"; - -// temporary feature flag since this feature is still WIP -// (missing task to complete https://pagopa.atlassian.net/browse/IA-684?filter=10121) -export const editPaypalPspEnabled = false; - -export type ConfirmPaymentMethodScreenNavigationParams = Readonly<{ - rptId: RptId; - initialAmount: AmountInEuroCents; - verifica: PaymentRequestsGetResponse; - idPayment: string; - wallet: Wallet; - psps: ReadonlyArray; -}>; - -type Props = ReturnType & - ReturnType; - -type ConfirmPaymentMethodScreenProps = LightModalContextInterface & Props; - -const styles = StyleSheet.create({ - totalContainer: { - flexDirection: "row", - justifyContent: "space-between", - borderBottomWidth: 1, - borderBottomColor: IOColors.greyLight - }, - - iconRow: { - flexDirection: "row", - alignItems: "center" - }, - - flex: { flex: 1 } -}); - -const contextualHelpMarkdown: ContextualHelpPropsMarkdown = { - title: "wallet.whyAFee.title", - body: "wallet.whyAFee.text" -}; - -const payUrlSuffix = "/v3/webview/transactions/pay"; -const webViewExitPathName = "/v3/webview/logout/bye"; -const webViewOutcomeParamName = "outcome"; - -type ComputedPaymentMethodInfo = { - logo: JSX.Element; - subject: string; - caption: string; - accessibilityLabel: string; -}; - -const getPaymentMethodInfo = ( - paymentMethod: PaymentMethod | undefined, - options: { isPaypalEnabled: boolean; isBPayPaymentEnabled: boolean } -): O.Option => { - switch (paymentMethod?.kind) { - case "CreditCard": - const holder = paymentMethod.info.holder ?? ""; - const expiration = - getTranslatedShortNumericMonthYear( - paymentMethod.info.expireYear, - paymentMethod.info.expireMonth - ) ?? ""; - - return O.some({ - logo: ( - - ), - subject: `${holder}${expiration ? " · " + expiration : ""}`, - expiration, - caption: paymentMethod.caption ?? "", - accessibilityLabel: `${I18n.t( - "wallet.accessibility.folded.creditCard", - { - brand: paymentMethod.info.brand, - blurredNumber: paymentMethod.info.blurredNumber - } - )}, ${holder}, ${expiration}` - }); - - case "PayPal": - const paypalEmail = getPaypalAccountEmail(paymentMethod.info); - return pipe( - O.some({ - logo: , - subject: paypalEmail, - caption: I18n.t("wallet.onboarding.paypal.name"), - accessibilityLabel: `${I18n.t( - "wallet.onboarding.paypal.name" - )}, ${paypalEmail}` - }), - O.filter(() => options.isPaypalEnabled) - ); - case "BPay": - return pipe( - O.some({ - logo: , - subject: paymentMethod?.caption, - caption: paymentMethod.info.numberObfuscated ?? "", - accessibilityLabel: `${I18n.t("wallet.methods.bancomatPay.name")}` - }), - O.filter(() => options.isBPayPaymentEnabled) - ); - - default: - return O.none; - } -}; - -/** - * return the type of the paying method - * atm only three methods can pay: credit card, paypal and bancomat pay - * @param paymentMethod - */ -const getPaymentMethodType = ( - paymentMethod: RawPaymentMethod | undefined -): PaymentMethodType => { - switch (paymentMethod?.kind) { - case "BPay": - case "CreditCard": - case "PayPal": - return paymentMethod.kind; - default: - return "Unknown"; - } -}; - -const ConfirmPaymentMethodScreen: React.FC = ( - props: ConfirmPaymentMethodScreenProps -) => { - const navigation = useIONavigation(); - const { rptId, initialAmount, verifica, idPayment, wallet, psps } = - useRoute< - Route< - "PAYMENT_CONFIRM_PAYMENT_METHOD", - ConfirmPaymentMethodScreenNavigationParams - > - >().params; - - React.useEffect(() => { - // show a toast if we got an error while retrieving pm session token - if (O.isSome(props.retrievingSessionTokenError)) { - IOToast.error(I18n.t("global.actions.retry")); - } - }, [props.retrievingSessionTokenError]); - - const pickPaymentMethod = () => - navigateToPaymentPickPaymentMethodScreen({ - rptId, - initialAmount, - verifica, - idPayment - }); - const pickPsp = () => - navigateToPaymentPickPspScreen({ - rptId, - initialAmount, - verifica, - idPayment, - psps, - wallet, - chooseToChange: true - }); - - const urlPrefix = props.isPagoPATestEnabled - ? pagoPaApiUrlPrefixTest - : pagoPaApiUrlPrefix; - - const paymentReason = verifica.causaleVersamento; - const maybePsp = O.fromNullable(wallet.psp); - const isPayingWithPaypal = isRawPayPal(wallet.paymentMethod); - - // each payment method has its own psp fee - const paymentMethodType = getPaymentMethodType(wallet.paymentMethod); - const fee: number | undefined = isPayingWithPaypal - ? props.paypalSelectedPsp?.fee - : pipe( - maybePsp, - O.fold( - () => undefined, - psp => psp.fixedCost.amount - ) - ); - - const totalAmount = - (verifica.importoSingoloVersamento as number) + (fee ?? 0); - - // emit an event to inform the pay web view finished - // dispatch the outcome code and navigate to payment outcome code screen - const handlePaymentOutcome = (maybeOutcomeCode: O.Option) => { - // the outcome is a payment done successfully - if ( - O.isSome(maybeOutcomeCode) && - isPaymentOutcomeCodeSuccessfully( - maybeOutcomeCode.value, - props.outcomeCodes - ) - ) { - // store the rptid of a payment done - props.dispatchPaymentCompleteSuccessfully(rptId); - // refresh transactions list - props.loadTransactions(); - } else { - props.dispatchPaymentFailure( - pipe(maybeOutcomeCode, O.filter(OutcomeCodesKey.is), O.toUndefined), - idPayment - ); - } - props.dispatchEndPaymentWebview("EXIT_PATH", paymentMethodType); - props.dispatchPaymentOutCome(maybeOutcomeCode, paymentMethodType); - props.navigateToOutComePaymentScreen((fee ?? 0) as ImportoEuroCents); - }; - - // the user press back during the pay web view challenge - const handlePayWebviewGoBack = () => { - Alert.alert(I18n.t("payment.abortWebView.title"), "", [ - { - text: I18n.t("payment.abortWebView.confirm"), - onPress: () => { - props.dispatchCancelPayment(); - props.dispatchEndPaymentWebview("USER_ABORT", paymentMethodType); - }, - style: "cancel" - }, - { - text: I18n.t("payment.abortWebView.cancel") - } - ]); - }; - - // navigate to the screen where the user can pick the desired psp - const handleOnEditPaypalPsp = () => { - navigation.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.WALLET_PAYPAL_UPDATE_PAYMENT_PSP, - params: { idWallet: wallet.idWallet, idPayment } - }); - }; - - // Handle the PSP change, this will trigger - // a different callback for a payment with PayPal. - const handleChangePsp = isPayingWithPaypal ? handleOnEditPaypalPsp : pickPsp; - - const formData = pipe( - props.payStartWebviewPayload, - O.map(payload => ({ - ...payload, - ...getLookUpIdPO() - })), - O.getOrElse(() => ({})) - ); - - const paymentMethod = props.getPaymentMethodById(wallet.idWallet); - const isPaymentMethodCreditCard = - paymentMethod !== undefined && isCreditCard(paymentMethod); - - const formattedSingleAmount = formatNumberCentsToAmount( - verifica.importoSingoloVersamento, - true - ); - const formattedTotal = formatNumberCentsToAmount(totalAmount, true); - const formattedFees = formatNumberCentsToAmount(fee ?? 0, true); - - // Retrieve all the informations needed by the - // user interface based on the payment method - // selected by the user. - const paymentMethodInfo = pipe( - getPaymentMethodInfo(paymentMethod, { - isPaypalEnabled: props.isPaypalEnabled, - isBPayPaymentEnabled: props.isBPayPaymentEnabled - }), - O.getOrElse(() => ({ - subject: "", - caption: "", - logo: , - accessibilityLabel: "" - })) - ); - - // It should be possible to change PSP only when the user - // is not paying using PayPal or the relative flag is - // enabled. - const canChangePsp = !isPayingWithPaypal || editPaypalPspEnabled; - - // The privacy url needed when paying - // using PayPal. - const privacyUrl = props.paypalSelectedPsp?.privacyUrl; - - // Retrieve the PSP name checking if the user is - // paying using PayPal or another method. The PSP - // could always be `undefined`. - const pspName = pipe( - isPayingWithPaypal - ? props.paypalSelectedPsp?.ragioneSociale - : wallet.psp?.businessName, - O.fromNullable, - O.map(name => `${I18n.t("wallet.ConfirmPayment.providedBy")} ${name}`), - O.getOrElse(() => I18n.t("payment.noPsp")) - ); - - return ( - - - - - - - - -

{I18n.t("wallet.ConfirmPayment.total")}

-

{formattedTotal}

-
- - - - - - -

- {I18n.t("wallet.ConfirmPayment.paymentInformations")} -

-
- - - - -

- {paymentReason} -

- - - {formattedSingleAmount} - -
- - - - - - -

- {I18n.t("wallet.ConfirmPayment.payWith")} -

-
- - - - - - - - - - -

- {I18n.t("wallet.ConfirmPayment.transactionCosts")} -

-
- - - - - - {isPayingWithPaypal && privacyUrl && ( - <> - - - openWebUrl(privacyUrl)} - accessibilityRole="link" - > - - {`${I18n.t( - "wallet.onboarding.paypal.paymentCheckout.privacyDisclaimer" - )} `} - - - - {I18n.t( - "wallet.onboarding.paypal.paymentCheckout.privacyTerms" - )} - - - - )} - - -
-
- - {O.isSome(props.payStartWebviewPayload) && ( - - )} -
- - props.dispatchPaymentStart({ - idWallet: wallet.idWallet, - idPayment, - language: getLocalePrimaryWithFallback() - }) - } - }} - /> -
-
- ); -}; -const mapStateToProps = (state: GlobalState) => { - const pmSessionToken = pmSessionTokenSelector(state); - const paymentStartPayload = paymentStartPayloadSelector(state); - // if there is no psp selected pick the default one from the list (if any) - const paypalSelectedPsp: PspData | undefined = - pspSelectedV2ListSelector(state) || - getValueOrElse(pspV2ListSelector(state), []).find(psp => psp.defaultPsp); - const payStartWebviewPayload: O.Option = - isReady(pmSessionToken) && paymentStartPayload - ? O.some({ ...paymentStartPayload, sessionToken: pmSessionToken.value }) - : O.none; - return { - paypalSelectedPsp, - getPaymentMethodById: (idWallet: number) => - paymentMethodByIdSelector(state, idWallet), - isPagoPATestEnabled: isPagoPATestEnabledSelector(state), - outcomeCodes: outcomeCodesSelector(state), - isPaypalEnabled: isPaypalEnabledSelector(state), - isBPayPaymentEnabled: bancomatPayConfigSelector(state).payment, - payStartWebviewPayload, - isLoading: isLoading(pmSessionToken), - retrievingSessionTokenError: isError(pmSessionToken) - ? O.some(pmSessionToken.error.message) - : O.none - }; -}; - -const mapDispatchToProps = (dispatch: Dispatch) => { - const dispatchCancelPayment = () => { - dispatch(abortRunningPayment()); - IOToast.success(I18n.t("wallet.ConfirmPayment.cancelPaymentSuccess")); - }; - return { - onCancel: () => { - Alert.alert( - I18n.t("wallet.ConfirmPayment.confirmCancelTitle"), - undefined, - [ - { - text: I18n.t("wallet.ConfirmPayment.confirmCancelPayment"), - style: "destructive", - onPress: () => { - dispatchCancelPayment(); - } - }, - { - text: I18n.t("wallet.ConfirmPayment.confirmContinuePayment"), - style: "cancel" - } - ] - ); - }, - dispatchPaymentStart: ( - payload: PayloadForAction<(typeof paymentExecuteStart)["request"]> - ) => dispatch(paymentExecuteStart.request(payload)), - dispatchEndPaymentWebview: ( - reason: PaymentWebViewEndReason, - paymentMethodType: PaymentMethodType - ) => { - dispatch(paymentWebViewEnd({ reason, paymentMethodType })); - }, - dispatchCancelPayment, - dispatchPaymentOutCome: ( - outComeCode: O.Option, - paymentMethodType: PaymentMethodType - ) => - dispatch(paymentOutcomeCode({ outcome: outComeCode, paymentMethodType })), - navigateToOutComePaymentScreen: (fee: ImportoEuroCents) => - navigateToPaymentOutcomeCode({ fee }), - loadTransactions: () => - dispatch(fetchTransactionsRequestWithExpBackoff({ start: 0 })), - - dispatchPaymentCompleteSuccessfully: (rptId: RptId) => - dispatch( - paymentCompletedSuccess({ - kind: "COMPLETED", - rptId, - transaction: undefined - }) - ), - dispatchPaymentFailure: ( - outcomeCode: OutcomeCodesKey | undefined, - paymentId: string - ) => dispatch(paymentCompletedFailure({ outcomeCode, paymentId })) - }; -}; - -const ConfirmPaymentMethodScreenWithContext = (props: Props) => { - const { ...modalContext } = React.useContext(LightModalContext); - return ; -}; - -export default connect( - mapStateToProps, - mapDispatchToProps -)(ConfirmPaymentMethodScreenWithContext); diff --git a/ts/screens/wallet/payment/ManualDataInsertionScreen.tsx b/ts/screens/wallet/payment/ManualDataInsertionScreen.tsx deleted file mode 100644 index 8c8d8169704..00000000000 --- a/ts/screens/wallet/payment/ManualDataInsertionScreen.tsx +++ /dev/null @@ -1,325 +0,0 @@ -import { - ContentWrapper, - FooterWithButtons, - IOColors, - VSpacer -} from "@pagopa/io-app-design-system"; -import { - AmountInEuroCents, - PaymentNoticeNumberFromString, - RptId -} from "@pagopa/io-pagopa-commons/lib/pagopa"; -import { - NonEmptyString, - OrganizationFiscalCode -} from "@pagopa/ts-commons/lib/strings"; -import * as E from "fp-ts/lib/Either"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import * as React from "react"; -import { Keyboard, SafeAreaView, ScrollView, StyleSheet } from "react-native"; -import { connect } from "react-redux"; -import { LabelledItem } from "../../../components/LabelledItem"; -import { Body } from "../../../components/core/typography/Body"; -import { H1 } from "../../../components/core/typography/H1"; -import { Link } from "../../../components/core/typography/Link"; -import { IOStyles } from "../../../components/core/variables/IOStyles"; -import BaseScreenComponent, { - ContextualHelpPropsMarkdown -} from "../../../components/screens/BaseScreenComponent"; -import { - LightModalContext, - LightModalContextInterface -} from "../../../components/ui/LightModal"; -import I18n from "../../../i18n"; -import { - navigateBack, - navigateToPaymentTransactionSummaryScreen, - navigateToWalletAddPaymentMethod, - navigateToWalletHome -} from "../../../store/actions/navigation"; -import { Dispatch } from "../../../store/actions/types"; -import { paymentInitializeState } from "../../../store/actions/wallet/payment"; -import { GlobalState } from "../../../store/reducers/types"; -import { withPaymentFeatureSelector } from "../../../store/reducers/wallet/wallets"; -import { alertNoPayablePaymentMethods } from "../../../utils/paymentMethod"; -import CodesPositionManualPaymentModal from "./CodesPositionManualPaymentModal"; - -export type ManualDataInsertionScreenNavigationParams = { - isInvalidAmount?: boolean; -}; - -type Props = ReturnType & - ReturnType; - -type ManualDataInsertionScreenProps = Props & LightModalContextInterface; - -type State = Readonly<{ - paymentNoticeNumber: O.Option< - ReturnType - >; - organizationFiscalCode: O.Option< - ReturnType - >; - noticeNumberInputValue: string; - orgFiscalCodeInputValue: string; -}>; - -const styles = StyleSheet.create({ - whiteBg: { - backgroundColor: IOColors.white - } -}); - -// helper to translate O.Option to true|false|void semantics -const unwrapOptionalEither = (o: O.Option>) => - pipe(o, O.map(E.isRight), O.toUndefined); - -const contextualHelpMarkdown: ContextualHelpPropsMarkdown = { - title: "wallet.insertManually.contextualHelpTitle", - body: "wallet.insertManually.contextualHelpContent" -}; - -/** - * This screen allows the user to manually insert the data which identify the transaction: - * - Numero Avviso, which includes: aux, digit, application code, codice IUV - * - Codice Fiscale Ente CReditore (corresponding to codiceIdentificativoEnte) - * - amount of the transaction - * TODO: - * - integrate contextual help to obtain details on the data to insert for manually identifying the transaction - * https://www.pivotaltracker.com/n/projects/2048617/stories/157874540 - */ -class ManualDataInsertionScreen extends React.Component< - ManualDataInsertionScreenProps, - State -> { - constructor(props: ManualDataInsertionScreenProps) { - super(props); - this.state = { - paymentNoticeNumber: O.none, - organizationFiscalCode: O.none, - noticeNumberInputValue: "", - orgFiscalCodeInputValue: "" - }; - } - - public componentDidMount() { - if (!this.props.hasMethodsCanPay) { - alertNoPayablePaymentMethods(this.props.navigateToWalletAddPaymentMethod); - } - } - - private isFormValid = () => - pipe( - this.state.paymentNoticeNumber, - O.map(E.isRight), - O.getOrElseW(() => false) - ) && - pipe( - this.state.organizationFiscalCode, - O.map(E.isRight), - O.getOrElseW(() => false) - ); - - /** - * This method collects the data from the form and, - * if it is syntactically correct, it dispatches a - * request to proceed with the summary of the transaction - */ - private proceedToSummary = () => { - // first make sure all the elements have been entered correctly - - pipe( - this.state.paymentNoticeNumber, - O.chain(O.fromEither), - O.chain(paymentNoticeNumber => - pipe( - this.state.organizationFiscalCode, - O.chain(O.fromEither), - O.chain(organizationFiscalCode => - pipe( - { - paymentNoticeNumber, - organizationFiscalCode - }, - RptId.decode, - O.fromEither, - O.map(rptId => { - // Set the initial amount to a fixed value (1) because it is not used, waiting to be removed from the API - const initialAmount = "1" as AmountInEuroCents; - this.props.navigateToTransactionSummary(rptId, initialAmount); - }) - ) - ) - ) - ) - ); - }; - - /** - * Converts the validator state into a color string. - * @param isFieldValid - the validator state. - * @returns green string if isFieldValid is true, red string if false, undefined if undefined. - */ - private getColorFromInputValidatorState(isFieldValid: boolean | undefined) { - return pipe( - isFieldValid, - O.fromNullable, - O.fold( - () => undefined, - isValid => (isValid ? IOColors.green : IOColors.red) - ) - ); - } - - public render(): React.ReactNode { - return ( - - - - -

{I18n.t("wallet.insertManually.title")}

- {I18n.t("wallet.insertManually.info")} - - {I18n.t("wallet.insertManually.link")} - - - | IUV 17>>| - onChangeText: value => { - this.setState({ - noticeNumberInputValue: value, - paymentNoticeNumber: pipe( - O.some(value), - O.filter(NonEmptyString.is), - O.map(_ => _.replace(/\s/g, "")), - O.map(_ => PaymentNoticeNumberFromString.decode(_)) - ) - }); - } - }} - /> - - { - this.setState({ - orgFiscalCodeInputValue: value, - organizationFiscalCode: pipe( - O.some(value), - O.filter(NonEmptyString.is), - O.map(_ => _.replace(/\s/g, "")), - O.map(_ => OrganizationFiscalCode.decode(_)) - ) - }); - } - }} - /> -
-
-
- -
- ); - } - private showModal = () => { - Keyboard.dismiss(); - this.props.showModal( - - ); - }; -} - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - goBack: () => { - navigateBack(); - }, - navigateToWalletHome: () => navigateToWalletHome(), - navigateToWalletAddPaymentMethod: () => - navigateToWalletAddPaymentMethod({ - inPayment: O.none, - showOnlyPayablePaymentMethods: true - }), - navigateToTransactionSummary: ( - rptId: RptId, - initialAmount: AmountInEuroCents - ) => { - Keyboard.dismiss(); - dispatch(paymentInitializeState()); - - navigateToPaymentTransactionSummaryScreen({ - rptId, - initialAmount, - paymentStartOrigin: "manual_insertion" - }); - } -}); - -const mapStateToProps = (state: GlobalState) => ({ - hasMethodsCanPay: withPaymentFeatureSelector(state).length > 0 -}); - -const ManualDataInsertionScreenFC = (props: Props) => { - const { ...modalContext } = React.useContext(LightModalContext); - return ; -}; - -export default connect( - mapStateToProps, - mapDispatchToProps -)(ManualDataInsertionScreenFC); diff --git a/ts/screens/wallet/payment/PaymentOutcomeCodeMessage.tsx b/ts/screens/wallet/payment/PaymentOutcomeCodeMessage.tsx deleted file mode 100644 index ccf082981b4..00000000000 --- a/ts/screens/wallet/payment/PaymentOutcomeCodeMessage.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import { FooterWithButtons, VSpacer } from "@pagopa/io-app-design-system"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { Route, useRoute } from "@react-navigation/native"; -import * as O from "fp-ts/lib/Option"; -import React from "react"; -import { View } from "react-native"; -import { widthPercentageToDP } from "react-native-responsive-screen"; -import { connect } from "react-redux"; -import { Dispatch } from "redux"; -import { ImportoEuroCents } from "../../../../definitions/backend/ImportoEuroCents"; -import paymentCompleted from "../../../../img/pictograms/payment-completed.png"; -import { Label } from "../../../components/core/typography/Label"; -import { InfoScreenComponent } from "../../../components/infoScreen/InfoScreenComponent"; -import { renderInfoRasterImage } from "../../../components/infoScreen/imageRendering"; -import OutcomeCodeMessageComponent from "../../../components/wallet/OutcomeCodeMessageComponent"; -import { WalletPaymentFeebackBanner } from "../../../features/payments/checkout/components/WalletPaymentFeedbackBanner"; -import { useHardwareBackButton } from "../../../hooks/useHardwareBackButton"; -import I18n from "../../../i18n"; -import { navigateToWalletHome } from "../../../store/actions/navigation"; -import { backToEntrypointPayment } from "../../../store/actions/wallet/payment"; -import { profileEmailSelector } from "../../../store/reducers/profile"; -import { GlobalState } from "../../../store/reducers/types"; -import { lastPaymentOutcomeCodeSelector } from "../../../store/reducers/wallet/outcomeCode"; -import { - entrypointRouteSelector, - paymentVerificaSelector -} from "../../../store/reducers/wallet/payment"; -import { formatNumberCentsToAmount } from "../../../utils/stringBuilder"; -import { openWebUrl } from "../../../utils/url"; - -export type PaymentOutcomeCodeMessageNavigationParams = Readonly<{ - fee: ImportoEuroCents; -}>; - -type Props = ReturnType & - ReturnType; - -const SuccessBody = ({ emailAddress }: { emailAddress: string }) => ( - - - - - - - -); - -const successComponent = (emailAddress: string, amount?: string) => ( - } - /> -); - -const successFooter = (onClose: () => void) => ( - -); - -/** - * This is the wrapper component which takes care of showing the outcome message after that - * a user makes a payment. - * The component expects the action outcomeCodeRetrieved to be dispatched before being rendered, - * so the pot.none case is not taken into account. - * - * If the outcome code is of type success the render a single buttons footer that allow the user to go to the wallet home. - */ -const PaymentOutcomeCodeMessage: React.FC = (props: Props) => { - const { fee } = - useRoute< - Route< - "PAYMENT_OUTCOMECODE_MESSAGE", - PaymentOutcomeCodeMessageNavigationParams - > - >().params; - const outcomeCode = O.toNullable(props.outcomeCode.outcomeCode); - const learnMoreLink = "https://io.italia.it/faq/#pagamenti"; - const onLearnMore = () => openWebUrl(learnMoreLink); - - useHardwareBackButton(() => { - props.navigateToWalletHome(props.shouldGoBackToEntrypointOnSuccess); - return true; - }); - - const renderSuccessComponent = () => { - if (pot.isSome(props.verifica)) { - const totalAmount = - (props.verifica.value.importoSingoloVersamento as number) + - (fee as number); - - return successComponent( - O.getOrElse(() => "")(props.profileEmail), - formatNumberCentsToAmount(totalAmount, true) - ); - } else { - return successComponent(O.getOrElse(() => "")(props.profileEmail)); - } - }; - - return outcomeCode ? ( - - props.navigateToWalletHome(props.shouldGoBackToEntrypointOnSuccess) - } - successComponent={renderSuccessComponent} - successFooter={() => - successFooter(() => - props.navigateToWalletHome(props.shouldGoBackToEntrypointOnSuccess) - ) - } - onLearnMore={onLearnMore} - /> - ) : null; -}; - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - navigateToWalletHome: (shouldGoBackToEntrypointOnSuccess: boolean) => - shouldGoBackToEntrypointOnSuccess - ? dispatch(backToEntrypointPayment()) - : navigateToWalletHome() -}); - -const mapStateToProps = (state: GlobalState) => ({ - shouldGoBackToEntrypointOnSuccess: - entrypointRouteSelector(state)?.name === "PN_ROUTES_MESSAGE_DETAILS", - outcomeCode: lastPaymentOutcomeCodeSelector(state), - profileEmail: profileEmailSelector(state), - verifica: paymentVerificaSelector(state) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(PaymentOutcomeCodeMessage); diff --git a/ts/screens/wallet/payment/PickPaymentMethodScreen.tsx b/ts/screens/wallet/payment/PickPaymentMethodScreen.tsx deleted file mode 100644 index 5be94ba608c..00000000000 --- a/ts/screens/wallet/payment/PickPaymentMethodScreen.tsx +++ /dev/null @@ -1,318 +0,0 @@ -/** - * This screen allows the user to select the payment method for a selected transaction - */ -import { - ContentWrapper, - Divider, - FooterWithButtons, - IOToast, - VSpacer -} from "@pagopa/io-app-design-system"; -import { AmountInEuroCents, RptId } from "@pagopa/io-pagopa-commons/lib/pagopa"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { Route, useNavigation, useRoute } from "@react-navigation/native"; -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { FlatList, SafeAreaView } from "react-native"; -import { ScrollView } from "react-native-gesture-handler"; -import { connect } from "react-redux"; -import { PaymentRequestsGetResponse } from "../../../../definitions/backend/PaymentRequestsGetResponse"; -import { withLoadingSpinner } from "../../../components/helpers/withLoadingSpinner"; -import BaseScreenComponent, { - ContextualHelpPropsMarkdown -} from "../../../components/screens/BaseScreenComponent"; - -import { - isLoading as isLoadingRemote, - isLoading as isRemoteLoading -} from "../../../common/model/RemoteValue"; -import { H1 } from "../../../components/core/typography/H1"; -import { H4 } from "../../../components/core/typography/H4"; -import { IOStyles } from "../../../components/core/variables/IOStyles"; -import PickAvailablePaymentMethodListItem from "../../../components/wallet/payment/PickAvailablePaymentMethodListItem"; -import PickNotAvailablePaymentMethodListItem from "../../../components/wallet/payment/PickNotAvailablePaymentMethodListItem"; -import PaymentStatusSwitch from "../../../features/wallet/component/features/PaymentStatusSwitch"; -import I18n from "../../../i18n"; -import { - IOStackNavigationProp, - IOStackNavigationRouteProps -} from "../../../navigation/params/AppParamsList"; -import { WalletParamsList } from "../../../navigation/params/WalletParamsList"; -import { - navigateBack, - navigateToWalletAddPaymentMethod -} from "../../../store/actions/navigation"; -import { Dispatch } from "../../../store/actions/types"; -import { - bancomatPayConfigSelector, - isPaypalEnabledSelector -} from "../../../store/reducers/backendStatus"; -import { profileNameSurnameSelector } from "../../../store/reducers/profile"; -import { GlobalState } from "../../../store/reducers/types"; -import { pspV2ListSelector } from "../../../store/reducers/wallet/payment"; -import { - bPayListVisibleInWalletSelector, - bancomatListVisibleInWalletSelector, - creditCardListVisibleInWalletSelector, - paypalListSelector -} from "../../../store/reducers/wallet/wallets"; -import { PaymentMethod, Wallet } from "../../../types/pagopa"; -import { - hasPaymentFeature, - isDisabledToPay, - isEnabledToPay -} from "../../../utils/paymentMethodCapabilities"; -import { convertWalletV2toWalletV1 } from "../../../utils/walletv2"; -import { dispatchPickPspOrConfirm } from "./common"; - -export type PickPaymentMethodScreenNavigationParams = Readonly<{ - rptId: RptId; - initialAmount: AmountInEuroCents; - verifica: PaymentRequestsGetResponse; - idPayment: string; -}>; - -type OwnProps = IOStackNavigationRouteProps< - WalletParamsList, - "PAYMENT_PICK_PAYMENT_METHOD" ->; - -type Props = ReturnType & - ReturnType & - OwnProps; - -const contextualHelpMarkdown: ContextualHelpPropsMarkdown = { - title: "wallet.payWith.contextualHelpTitle", - body: "wallet.payWith.contextualHelpContent" -}; - -const PickPaymentMethodScreen: React.FunctionComponent = ( - props: Props -) => { - const { methodsCanPay, methodsCantPay, methodsCanPayButDisabled } = props; - - return ( - - - - -

{I18n.t("wallet.payWith.pickPaymentMethod.title")}

- - {methodsCanPay.length > 0 ? ( - <> -

- {I18n.t("wallet.payWith.text")} -

- } - keyExtractor={item => item.idWallet.toString()} - ListFooterComponent={} - renderItem={i => ( - - props.navigateToConfirmOrPickPsp( - // Since only credit cards are now accepted method we manage only this case - // TODO: if payment methods different from credit card should be accepted manage every case - convertWalletV2toWalletV1(i.item) - ) - } - /> - )} - /> - - - ) : ( -

- {I18n.t("wallet.payWith.noWallets.text")} -

- )} - - {methodsCanPayButDisabled.length > 0 && ( - <> - -

- {I18n.t("wallet.payWith.pickPaymentMethod.disabled.title")} -

- - } - keyExtractor={item => `disabled_payment_${item.idWallet}`} - ListFooterComponent={} - renderItem={i => ( - - } - isFirst={i.index === 0} - paymentMethod={i.item} - onPress={undefined} - /> - )} - /> - - )} - - {methodsCantPay.length > 0 && ( - <> - -

- {I18n.t( - "wallet.payWith.pickPaymentMethod.notAvailable.title" - )} -

- - } - keyExtractor={item => item.idWallet.toString()} - ListFooterComponent={} - renderItem={i => ( - - )} - /> - - )} -
-
-
- -
- ); -}; - -const mapStateToProps = (state: GlobalState) => { - const potVisibleCreditCard = creditCardListVisibleInWalletSelector(state); - const potVisiblePaypal = isPaypalEnabledSelector(state) - ? paypalListSelector(state) - : pot.none; - const potVisibleBancomat = bancomatListVisibleInWalletSelector(state); - const potVisibleBPay = bancomatPayConfigSelector(state).payment - ? bPayListVisibleInWalletSelector(state) - : pot.none; - const psps = state.wallet.payment.pspsV2.psps; - const pspV2 = pspV2ListSelector(state); - const isLoading = - pot.isLoading(potVisibleCreditCard) || - isRemoteLoading(psps) || - isLoadingRemote(pspV2); - - const visibleWallets = [ - potVisibleCreditCard, - potVisiblePaypal, - potVisibleBancomat, - potVisibleBPay - ].reduce( - ( - acc: ReadonlyArray, - curr: pot.Pot, unknown> - ) => [...acc, ...pot.getOrElse(curr, [])], - [] as ReadonlyArray - ); - return { - methodsCanPay: visibleWallets.filter(isEnabledToPay), - methodsCanPayButDisabled: visibleWallets.filter(isDisabledToPay), - methodsCantPay: visibleWallets.filter(v => !hasPaymentFeature(v)), - isLoading, - nameSurname: profileNameSurnameSelector(state) - }; -}; - -const mapDispatchToProps = (dispatch: Dispatch, props: OwnProps) => ({ - goBack: () => navigateBack(), - navigateToConfirmOrPickPsp: (wallet: Wallet) => { - dispatchPickPspOrConfirm(dispatch)( - props.route.params.rptId, - props.route.params.initialAmount, - props.route.params.verifica, - props.route.params.idPayment, - O.some(wallet), - failureReason => { - // selecting the payment method has failed, show a toast and stay in - // this screen - - if (failureReason === "FETCH_PSPS_FAILURE") { - // fetching the PSPs for the payment has failed - IOToast.warning(I18n.t("wallet.payWith.fetchPspFailure")); - } else if (failureReason === "NO_PSPS_AVAILABLE") { - // this wallet cannot be used for this payment - // TODO: perhaps we can temporarily hide the selected wallet from - // the list of available wallets - IOToast.error(I18n.t("wallet.payWith.noPspsAvailable")); - } - } - ); - }, - navigateToAddPaymentMethod: () => - navigateToWalletAddPaymentMethod({ - inPayment: O.some({ - rptId: props.route.params.rptId, - initialAmount: props.route.params.initialAmount, - verifica: props.route.params.verifica, - idPayment: props.route.params.idPayment - }) - }) -}); - -const ConnectedPickPaymentMethodScreen = connect( - mapStateToProps, - mapDispatchToProps -)(withLoadingSpinner(PickPaymentMethodScreen)); - -const PickPaymentMethodScreenFC = () => { - const navigation = - useNavigation< - IOStackNavigationProp - >(); - const route = - useRoute< - Route< - "PAYMENT_PICK_PAYMENT_METHOD", - PickPaymentMethodScreenNavigationParams - > - >(); - return ( - - ); -}; -export default PickPaymentMethodScreenFC; diff --git a/ts/screens/wallet/payment/PickPspScreen.tsx b/ts/screens/wallet/payment/PickPspScreen.tsx deleted file mode 100644 index d7c1db3e78f..00000000000 --- a/ts/screens/wallet/payment/PickPspScreen.tsx +++ /dev/null @@ -1,237 +0,0 @@ -import { - FooterWithButtons, - IOToast, - VSpacer -} from "@pagopa/io-app-design-system"; -import { AmountInEuroCents, RptId } from "@pagopa/io-pagopa-commons/lib/pagopa"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { Route, useNavigation, useRoute } from "@react-navigation/native"; -import * as React from "react"; -import { FlatList, SafeAreaView, StyleSheet, View } from "react-native"; -import { connect } from "react-redux"; -import { PaymentRequestsGetResponse } from "../../../../definitions/backend/PaymentRequestsGetResponse"; -import { PspData } from "../../../../definitions/pagopa/PspData"; -import { - getValueOrElse, - isError, - isLoading -} from "../../../common/model/RemoteValue"; -import ItemSeparatorComponent from "../../../components/ItemSeparatorComponent"; -import { LoadingErrorComponent } from "../../../components/LoadingErrorComponent"; -import { H1 } from "../../../components/core/typography/H1"; -import { H4 } from "../../../components/core/typography/H4"; -import { H5 } from "../../../components/core/typography/H5"; -import { IOStyles } from "../../../components/core/variables/IOStyles"; -import BaseScreenComponent, { - ContextualHelpPropsMarkdown -} from "../../../components/screens/BaseScreenComponent"; -import { - LightModalContext, - LightModalContextInterface -} from "../../../components/ui/LightModal"; -import { PspComponent } from "../../../components/wallet/payment/PspComponent"; -import I18n from "../../../i18n"; -import { - IOStackNavigationProp, - IOStackNavigationRouteProps -} from "../../../navigation/params/AppParamsList"; -import { WalletParamsList } from "../../../navigation/params/WalletParamsList"; -import { navigateBack } from "../../../store/actions/navigation"; -import { Dispatch } from "../../../store/actions/types"; -import { pspForPaymentV2 } from "../../../store/actions/wallet/payment"; -import { GlobalState } from "../../../store/reducers/types"; -import { pspV2ListSelector } from "../../../store/reducers/wallet/payment"; -import customVariables from "../../../theme/variables"; -import { Wallet } from "../../../types/pagopa"; -import { orderPspByAmount } from "../../../utils/payment"; -import { dispatchUpdatePspForWalletAndConfirm } from "./common"; - -export type PickPspScreenNavigationParams = Readonly<{ - rptId: RptId; - initialAmount: AmountInEuroCents; - verifica: PaymentRequestsGetResponse; - idPayment: string; - psps: ReadonlyArray; - wallet: Wallet; - chooseToChange?: boolean; -}>; - -type OwnProps = IOStackNavigationRouteProps< - WalletParamsList, - "PAYMENT_PICK_PSP" ->; - -type Props = ReturnType & - ReturnType; - -type PickPspScreenProps = LightModalContextInterface & Props & OwnProps; - -const styles = StyleSheet.create({ - header: { - flexDirection: "row", - alignItems: "center", - justifyContent: "space-between" - }, - padded: { paddingHorizontal: customVariables.contentPadding } -}); - -const contextualHelpMarkdown: ContextualHelpPropsMarkdown = { - title: "wallet.pickPsp.contextualHelpTitle", - body: "wallet.pickPsp.contextualHelpContent" -}; - -/** - * Select a PSP to be used for a the current selected wallet - */ -class PickPspScreen extends React.Component { - public componentDidMount() { - // load all psp in order to offer to the user the complete psps list - const idWallet = this.props.route.params.wallet.idWallet; - const idPayment = this.props.route.params.idPayment; - this.props.loadAllPsp(idWallet, idPayment); - } - - private headerItem = ( - - -
- {I18n.t("wallet.pickPsp.provider")} -
-
{`${I18n.t( - "wallet.pickPsp.maxFee" - )} (€)`}
-
- - -
- ); - - public render(): React.ReactNode { - const availablePsps = orderPspByAmount(this.props.allPsps); - - return ( - - {this.props.isLoading || this.props.hasError ? ( - { - this.props.loadAllPsp( - this.props.route.params.wallet.idWallet, - this.props.route.params.idPayment - ); - }} - loadingCaption={I18n.t("wallet.pickPsp.loadingPsps")} - /> - ) : ( - <> - - - -

{I18n.t("wallet.pickPsp.title")}

- -

- {I18n.t("wallet.pickPsp.info")} -

-

- {I18n.t("wallet.pickPsp.info2")} -

{` ${I18n.t( - "wallet.pickPsp.info2Bold" - )}`}

- -
- - } - removeClippedSubviews={false} - data={availablePsps} - keyExtractor={item => item.idPsp} - renderItem={({ item }) => ( - this.props.pickPsp(item, this.props.allPsps)} - /> - )} - ListHeaderComponent={this.headerItem} - ListFooterComponent={() => } - /> -
- - - )} -
- ); - } -} - -const mapStateToProps = (state: GlobalState) => { - const psps = pspV2ListSelector(state); - return { - isLoading: - pot.isLoading(state.wallet.wallets.walletById) || isLoading(psps), - hasError: pot.isError(state.wallet.wallets.walletById) || isError(psps), - allPsps: getValueOrElse(psps, []) - }; -}; - -const mapDispatchToProps = (dispatch: Dispatch, props: OwnProps) => ({ - navigateBack: () => navigateBack(), - loadAllPsp: (idWallet: number, idPayment: string) => { - dispatch( - pspForPaymentV2.request({ - idWallet, - idPayment - }) - ); - }, - pickPsp: (psp: PspData, psps: ReadonlyArray) => - dispatchUpdatePspForWalletAndConfirm(dispatch)( - psp, - props.route.params.wallet, - props.route.params.rptId, - props.route.params.initialAmount, - props.route.params.verifica, - props.route.params.idPayment, - psps, - () => IOToast.error(I18n.t("wallet.pickPsp.onUpdateWalletPspFailure")) - ) -}); - -const ConnectedPickPspScreen = connect( - mapStateToProps, - mapDispatchToProps -)(PickPspScreen); - -const PickPspScreenFC = () => { - const { ...modalContext } = React.useContext(LightModalContext); - const navigation = - useNavigation< - IOStackNavigationProp - >(); - const route = - useRoute>(); - return ( - - ); -}; - -export default PickPspScreenFC; diff --git a/ts/screens/wallet/payment/TransactionErrorScreen.tsx b/ts/screens/wallet/payment/TransactionErrorScreen.tsx deleted file mode 100644 index dc8bd483e65..00000000000 --- a/ts/screens/wallet/payment/TransactionErrorScreen.tsx +++ /dev/null @@ -1,442 +0,0 @@ -/** - * The screen to display to the user the various types of errors that occurred during the transaction. - * Inside the cancel and retry buttons are conditionally returned. - */ -import { - ButtonOutline, - ButtonSolid, - IOPictograms, - VSpacer -} from "@pagopa/io-app-design-system"; -import { RptId } from "@pagopa/io-pagopa-commons/lib/pagopa"; -import { Route, useRoute } from "@react-navigation/native"; -import * as E from "fp-ts/lib/Either"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import * as t from "io-ts"; -import * as React from "react"; -import { ComponentProps } from "react"; -import { SafeAreaView, View } from "react-native"; -import { connect } from "react-redux"; -import { Detail_v2Enum } from "../../../../definitions/backend/PaymentProblemJson"; -import { ToolEnum } from "../../../../definitions/content/AssistanceToolConfig"; -import { ZendeskCategory } from "../../../../definitions/content/ZendeskCategory"; -import CopyButtonComponent from "../../../components/CopyButtonComponent"; -import { InfoAltScreenComponent } from "../../../components/InfoAltScreenComponent/InfoAltScreenComponent"; -import { FooterStackButton } from "../../../components/buttons/FooterStackButtons"; -import { H4 } from "../../../components/core/typography/H4"; -import { IOStyles } from "../../../components/core/variables/IOStyles"; -import BaseScreenComponent from "../../../components/screens/BaseScreenComponent"; -import { - zendeskSelectedCategory, - zendeskSupportStart -} from "../../../features/zendesk/store/actions"; -import { useHardwareBackButton } from "../../../hooks/useHardwareBackButton"; -import I18n from "../../../i18n"; -import { navigateToPaymentManualDataInsertion } from "../../../store/actions/navigation"; -import { Dispatch } from "../../../store/actions/types"; -import { - backToEntrypointPayment, - paymentAttiva, - paymentIdPolling, - paymentVerifica -} from "../../../store/actions/wallet/payment"; -import { canShowHelpSelector } from "../../../store/reducers/assistanceTools"; -import { assistanceToolConfigSelector } from "../../../store/reducers/backendStatus"; -import { - PaymentHistory, - paymentsHistorySelector -} from "../../../store/reducers/payments/history"; -import { GlobalState } from "../../../store/reducers/types"; -import { PayloadForAction } from "../../../types/utils"; -import { - ErrorTypes, - getCodiceAvviso, - getPaymentHistoryDetails, - getV2ErrorMainType -} from "../../../utils/payment"; -import { - addTicketCustomField, - appendLog, - assistanceToolRemoteConfig, - resetCustomFields, - zendeskCategoryId, - zendeskPaymentCategory, - zendeskPaymentFailure, - zendeskPaymentNav, - zendeskPaymentOrgFiscalCode, - zendeskPaymentStartOrigin -} from "../../../utils/supportAssistance"; - -export type TransactionErrorScreenNavigationParams = { - error: O.Option< - PayloadForAction< - | (typeof paymentVerifica)["failure"] - | (typeof paymentAttiva)["failure"] - | (typeof paymentIdPolling)["failure"] - > - >; - rptId: RptId; - onCancel: () => void; -}; - -type Props = ReturnType & - ReturnType; - -const imageTimeout: IOPictograms = "ended"; -const imageDefaultFallback: IOPictograms = "fatalError"; -const imageMapping: Record = { - DATA: "attention", - DUPLICATED: "attention", - EC: "attention", - ONGOING: "timing", - UNCOVERED: "umbrellaNew", - REVOKED: "fatalError", - EXPIRED: "ended", - TECHNICAL: "fatalError", - NOT_FOUND: "attention" -}; - -const requestZendeskAssistanceForPaymentFailure = ( - rptId: RptId, - payment?: PaymentHistory -) => { - resetCustomFields(); - // Set pagamenti_pagopa as category - addTicketCustomField(zendeskCategoryId, zendeskPaymentCategory.value); - - // Add organization fiscal code custom field - addTicketCustomField( - zendeskPaymentOrgFiscalCode, - rptId.organizationFiscalCode - ); - // Add rptId custom field - addTicketCustomField(zendeskPaymentNav, getCodiceAvviso(rptId)); - if (payment) { - if (payment.failure) { - // Add failure custom field - addTicketCustomField(zendeskPaymentFailure, payment.failure); - } - // Add start origin custom field - addTicketCustomField(zendeskPaymentStartOrigin, payment.startOrigin); - // Append the payment history details in the log - appendLog(getPaymentHistoryDetails(payment)); - } -}; -type ScreenUIContents = { - image: IOPictograms; - title: string; - subtitle?: React.ReactNode; - footerButtons?: ComponentProps; -}; - -const ErrorCodeCopyComponent = ({ - error -}: { - error: keyof typeof Detail_v2Enum; -}): React.ReactElement => ( - -

- {I18n.t("wallet.errors.assistanceLabel")} -

-

- {error} -

- - -
-); - -/** - * Convert the error code into a user-readable string - * @param maybeError - * @param rptId - * @param onCancel - * @param choosenTool - * @param paymentHistory - * @param canShowHelpButton - * @param handleZendeskRequestAssistance - */ -export const errorTransactionUIElements = ( - maybeError: TransactionErrorScreenNavigationParams["error"], - rptId: RptId, - onCancel: () => void, - choosenTool: ToolEnum, - handleZendeskRequestAssistance: () => void, - canShowHelpButton: boolean, - paymentHistory?: PaymentHistory -): ScreenUIContents => { - const requestAssistance = () => { - switch (choosenTool) { - case ToolEnum.zendesk: - requestZendeskAssistanceForPaymentFailure(rptId, paymentHistory); - handleZendeskRequestAssistance(); - break; - default: - return; - } - }; - - const sendReportButtonConfirm: ComponentProps = { - onPress: requestAssistance, - label: I18n.t("wallet.errors.sendReport"), - accessibilityLabel: I18n.t("wallet.errors.sendReport"), - testID: "sendReportButtonConfirm" - }; - - const closeButtonConfirm: ComponentProps = { - onPress: onCancel, - label: I18n.t("global.buttons.close"), - accessibilityLabel: I18n.t("global.buttons.close"), - testID: "closeButtonConfirm" - }; - - const sendReportButtonCancel: ComponentProps = { - onPress: requestAssistance, - label: I18n.t("wallet.errors.sendReport"), - accessibilityLabel: I18n.t("wallet.errors.sendReport"), - testID: "sendReportButtonCancel" - }; - - const closeButtonCancel: ComponentProps = { - onPress: onCancel, - label: I18n.t("global.buttons.close"), - accessibilityLabel: I18n.t("global.buttons.close"), - testID: "closeButtonCancel" - }; - - const errorORUndefined = O.toUndefined(maybeError); - - if (errorORUndefined === "PAYMENT_ID_TIMEOUT") { - return { - image: imageTimeout, - title: I18n.t("wallet.errors.MISSING_PAYMENT_ID"), - footerButtons: { primaryActionProps: { ...closeButtonCancel } } - }; - } - - const errorMacro = getV2ErrorMainType(errorORUndefined); - const validError = t.keyof(Detail_v2Enum).decode(errorORUndefined); - const genericErrorSubTestID = "generic-error-subtitle"; - const subtitle = pipe( - validError, - E.fold( - _ => ( -

- {I18n.t("wallet.errors.GENERIC_ERROR_SUBTITLE")} -

- ), - error => - ) - ); - - const image = errorMacro ? imageMapping[errorMacro] : imageDefaultFallback; - - switch (errorMacro) { - case "TECHNICAL": - return { - image, - title: I18n.t("wallet.errors.TECHNICAL"), - subtitle, - footerButtons: canShowHelpButton - ? { - primaryActionProps: { ...sendReportButtonConfirm }, - secondaryActionProps: { ...closeButtonCancel } - } - : { primaryActionProps: { ...closeButtonCancel } } - }; - case "DATA": - return { - image, - title: I18n.t("wallet.errors.DATA"), - subtitle, - footerButtons: canShowHelpButton - ? { - primaryActionProps: { ...closeButtonConfirm }, - secondaryActionProps: { ...sendReportButtonCancel } - } - : { primaryActionProps: { ...closeButtonConfirm } } - }; - case "EC": - return { - image, - title: I18n.t("wallet.errors.EC"), - subtitle, - footerButtons: canShowHelpButton - ? { - primaryActionProps: { ...sendReportButtonConfirm }, - secondaryActionProps: { ...closeButtonCancel } - } - : { primaryActionProps: { ...closeButtonCancel } } - }; - case "DUPLICATED": - return { - image, - title: I18n.t("wallet.errors.DUPLICATED"), - footerButtons: { primaryActionProps: { ...closeButtonCancel } } - }; - case "ONGOING": - return { - image, - title: I18n.t("wallet.errors.ONGOING"), - subtitle: ( -

- {I18n.t("wallet.errors.ONGOING_SUBTITLE")} -

- ), - footerButtons: canShowHelpButton - ? { - primaryActionProps: { ...closeButtonConfirm }, - secondaryActionProps: { ...sendReportButtonCancel } - } - : { - primaryActionProps: { ...closeButtonConfirm } - } - }; - case "EXPIRED": - return { - image, - title: I18n.t("wallet.errors.EXPIRED"), - subtitle: ( -

- {I18n.t("wallet.errors.contactECsubtitle")} -

- ), - footerButtons: { primaryActionProps: { ...closeButtonCancel } } - }; - case "REVOKED": - return { - image, - title: I18n.t("wallet.errors.REVOKED"), - subtitle: ( -

- {I18n.t("wallet.errors.contactECsubtitle")} -

- ), - footerButtons: { - primaryActionProps: { ...closeButtonCancel } - } - }; - case "NOT_FOUND": - return { - image, - title: I18n.t("wallet.errors.NOT_FOUND"), - subtitle: ( -

- {I18n.t("wallet.errors.NOT_FOUND_SUBTITLE")} -

- ), - footerButtons: { primaryActionProps: { ...closeButtonConfirm } } - }; - case "UNCOVERED": - default: - return { - image, - title: I18n.t("wallet.errors.GENERIC_ERROR"), - subtitle: ( -

- {I18n.t("wallet.errors.GENERIC_ERROR_SUBTITLE")} -

- ), - footerButtons: canShowHelpButton - ? { - primaryActionProps: { ...closeButtonConfirm }, - secondaryActionProps: { ...sendReportButtonCancel } - } - : { - primaryActionProps: { ...closeButtonConfirm } - } - }; - } -}; - -const TransactionErrorScreen = (props: Props) => { - const { rptId, error, onCancel } = - useRoute< - Route<"PAYMENT_TRANSACTION_ERROR", TransactionErrorScreenNavigationParams> - >().params; - - const { paymentsHistory } = props; - - const codiceAvviso = getCodiceAvviso(rptId); - const organizationFiscalCode = rptId.organizationFiscalCode; - - const paymentHistory = paymentsHistory.find( - p => - codiceAvviso === getCodiceAvviso(p.data) && - organizationFiscalCode === p.data.organizationFiscalCode - ); - - const choosenTool = assistanceToolRemoteConfig(props.assistanceToolConfig); - const { title, subtitle, footerButtons, image } = errorTransactionUIElements( - error, - rptId, - onCancel, - choosenTool, - () => { - props.zendeskSupportWorkunitStart(); - props.zendeskSelectedCategory(zendeskPaymentCategory); - }, - props.canShowHelp, - paymentHistory - ); - const handleBackPress = () => { - props.backToEntrypointPayment(); - return true; - }; - - useHardwareBackButton(handleBackPress); - - return ( - - - - {footerButtons && } - - - ); -}; - -const mapStateToProps = (state: GlobalState) => ({ - paymentsHistory: paymentsHistorySelector(state), - assistanceToolConfig: assistanceToolConfigSelector(state), - canShowHelp: canShowHelpSelector(state) -}); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - navigateToPaymentManualDataInsertion: (isInvalidAmount: boolean) => - navigateToPaymentManualDataInsertion({ isInvalidAmount }), - backToEntrypointPayment: () => dispatch(backToEntrypointPayment()), - zendeskSupportWorkunitStart: () => - dispatch( - zendeskSupportStart({ - startingRoute: "n/a", - assistanceForPayment: true, - assistanceForCard: false, - assistanceForFci: false - }) - ), - zendeskSelectedCategory: (category: ZendeskCategory) => - dispatch(zendeskSelectedCategory(category)) -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(TransactionErrorScreen); diff --git a/ts/screens/wallet/payment/TransactionSummaryScreen.tsx b/ts/screens/wallet/payment/TransactionSummaryScreen.tsx deleted file mode 100644 index fcb3d84be16..00000000000 --- a/ts/screens/wallet/payment/TransactionSummaryScreen.tsx +++ /dev/null @@ -1,429 +0,0 @@ -import { ContentWrapper, IOToast } from "@pagopa/io-app-design-system"; -import { - AmountInEuroCents, - PaymentNoticeNumberFromString, - RptId -} from "@pagopa/io-pagopa-commons/lib/pagopa"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { RouteProp, useNavigation, useRoute } from "@react-navigation/native"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import React, { useCallback, useEffect } from "react"; -import { Alert, ScrollView } from "react-native"; -import { - isError as isRemoteError, - isLoading as isRemoteLoading, - isUndefined -} from "../../../common/model/RemoteValue"; -import { - FooterActions, - FooterActionsMeasurements -} from "../../../components/ui/FooterActions"; -import { - zendeskSelectedCategory, - zendeskSupportStart -} from "../../../features/zendesk/store/actions"; -import { useHeaderSecondLevel } from "../../../hooks/useHeaderSecondLevel"; -import I18n from "../../../i18n"; -import { WalletParamsList } from "../../../navigation/params/WalletParamsList"; -import { navigateToPaymentTransactionErrorScreen } from "../../../store/actions/navigation"; -import { - PaymentStartOrigin, - abortRunningPayment, - backToEntrypointPayment, - paymentAttiva, - paymentCompletedSuccess, - paymentIdPolling, - paymentInitializeState, - paymentVerifica, - runDeleteActivePaymentSaga -} from "../../../store/actions/wallet/payment"; -import { fetchWalletsRequestWithExpBackoff } from "../../../store/actions/wallet/wallets"; -import { useIODispatch, useIOSelector } from "../../../store/hooks"; -import { - bancomatPayConfigSelector, - isPaypalEnabledSelector -} from "../../../store/reducers/backendStatus"; -import { getFavoriteWallet } from "../../../store/reducers/wallet/wallets"; -import { PayloadForAction } from "../../../types/utils"; -import { emptyContextualHelp } from "../../../utils/emptyContextualHelp"; -import { useOnFirstRender } from "../../../utils/hooks/useOnFirstRender"; -import { - DetailV2Keys, - getCodiceAvviso, - getV2ErrorMainType, - isDuplicatedPayment -} from "../../../utils/payment"; -import { - addTicketCustomField, - appendLog, - resetCustomFields, - zendeskCategoryId, - zendeskPaymentCategory, - zendeskPaymentFailure, - zendeskPaymentNav, - zendeskPaymentOrgFiscalCode, - zendeskPaymentStartOrigin -} from "../../../utils/supportAssistance"; -import { useFooterActionsMeasurements } from "../../../hooks/useFooterActionsMeasurements"; -import { TransactionSummary } from "./components/TransactionSummary"; -import { TransactionSummaryErrorDetails } from "./components/TransactionSummaryErrorDetails"; -import { TransactionSummaryStatus } from "./components/TransactionSummaryStatus"; -import { useStartOrResumePayment } from "./hooks/useStartOrResumePayment"; - -export type TransactionSummaryScreenNavigationParams = Readonly<{ - rptId: RptId; - initialAmount: AmountInEuroCents; - paymentStartOrigin: PaymentStartOrigin; - messageId?: string; -}>; - -export type TransactionSummaryErrorContent = PayloadForAction< - | (typeof paymentVerifica)["failure"] - | (typeof paymentAttiva)["failure"] - | (typeof paymentIdPolling)["failure"] ->; - -export type TransactionSummaryError = O.Option; - -const renderFooter = ( - isLoading: boolean, - error: TransactionSummaryError, - continueWithPayment: () => void, - help: () => void, - onMeasure?: (values: FooterActionsMeasurements) => void -) => { - if (O.isSome(error)) { - const errorOrUndefined = O.toUndefined(error); - const errorType = getV2ErrorMainType(errorOrUndefined as DetailV2Keys); - switch (errorType) { - case "TECHNICAL": - case "DATA": - case "UNCOVERED": - return ( - - ); - // There's a strange case where error is 'some' but - // its value is undefined (e.g. network error). - // In this case we fallback to the 'continue' CTA - // so that the user can eventually retry. - case undefined: - break; - default: - return <>; - } - } - - return ( - - ); -}; - -// eslint-disable-next-line complexity,sonarjs/cognitive-complexity -const TransactionSummaryScreen = (): React.ReactElement => { - const route = - useRoute>(); - const navigation = useNavigation(); - const { rptId, paymentStartOrigin, initialAmount, messageId } = route.params; - - const dispatch = useIODispatch(); - const { - verifica: paymentVerification, - attiva, - paymentId, - check, - pspsV2 - } = useIOSelector(state => state.wallet.payment); - - /* Get `FooterActions` measurements */ - const { footerActionsMeasurements, handleFooterActionsMeasurements } = - useFooterActionsMeasurements(); - - const error: TransactionSummaryError = pot.isError(paymentVerification) - ? O.some(paymentVerification.error) - : pot.isError(attiva) - ? O.some(attiva.error) - : pot.isError(paymentId) - ? O.some(paymentId.error) - : pot.isError(check) || isRemoteError(pspsV2.psps) - ? O.some(undefined) - : O.none; - - const { walletById } = useIOSelector(state => state.wallet.wallets); - - const isPaypalEnabled = useIOSelector(isPaypalEnabledSelector); - const { payment: isBPayPaymentEnabled } = useIOSelector( - bancomatPayConfigSelector - ); - const favouriteWallet = pot.toUndefined(useIOSelector(getFavoriteWallet)); - /** - * the favourite will be undefined if one of these condition is true - * - payment method is PayPal & the relative feature flag is not enabled - * - payment method is BPay & the relative feature flag is not enabled - */ - const maybeFavoriteWallet = pipe( - favouriteWallet, - O.fromNullable, - O.filter(fw => { - switch (fw.paymentMethod?.kind) { - case "PayPal": - return isPaypalEnabled; - case "BPay": - return isBPayPaymentEnabled; - default: - return true; - } - }) - ); - - const isLoading = - pot.isLoading(walletById) || - pot.isLoading(paymentVerification) || - pot.isLoading(attiva) || - (O.isNone(error) && pot.isSome(attiva) && pot.isNone(paymentId)) || - pot.isLoading(paymentId) || - (O.isNone(error) && pot.isSome(paymentId) && pot.isNone(check)) || - pot.isLoading(check) || - (O.isSome(maybeFavoriteWallet) && - O.isNone(error) && - pot.isSome(check) && - isUndefined(pspsV2.psps)) || - (O.isSome(maybeFavoriteWallet) && isRemoteLoading(pspsV2.psps)); - - useOnFirstRender(() => { - if (pot.isNone(paymentVerification)) { - verifyPayment(); - } - if (!pot.isSome(walletById)) { - dispatch(fetchWalletsRequestWithExpBackoff()); - } - }); - - const onCancel = useCallback(() => { - dispatch(abortRunningPayment()); - }, [dispatch]); - - const navigateToPaymentTransactionError = useCallback( - (error: TransactionSummaryError) => - navigateToPaymentTransactionErrorScreen({ - error, - onCancel, - rptId - }), - [onCancel, rptId] - ); - - const onDuplicatedPayment = useCallback( - () => - dispatch( - paymentCompletedSuccess({ - rptId, - kind: "DUPLICATED" - }) - ), - [dispatch, rptId] - ); - - // We show inline error status only if the payment starts - // from a message and the verification fails. In all the other - // cases we present the fullscreen error message. - const showsInlineError = paymentStartOrigin === "message"; - - const errorOrUndefined = O.toUndefined(error); - const isError = O.isSome(error); - - const isPaid = isDuplicatedPayment(error); - - useEffect(() => { - if (!isError) { - return; - } - if (isPaid) { - onDuplicatedPayment(); - } - // in case of a payment verification error we should navigate - // to the error screen only if showsInlineError is false - // in any other case we should always navigate to the error screen - if ( - (pot.isError(paymentVerification) && !showsInlineError) || - (!pot.isError(paymentVerification) && isError) - ) { - navigateToPaymentTransactionError(O.fromNullable(errorOrUndefined)); - } - }, [ - isError, - errorOrUndefined, - onDuplicatedPayment, - navigateToPaymentTransactionError, - showsInlineError, - paymentVerification, - isPaid - ]); - - const goBack = () => { - if (pot.isSome(paymentId)) { - // If we have a paymentId (payment check already done) we need to - // ask the user to cancel the payment and in case reset it - Alert.alert( - I18n.t("wallet.ConfirmPayment.confirmCancelTitle"), - undefined, - [ - { - text: I18n.t("wallet.ConfirmPayment.confirmCancelPayment"), - style: "destructive", - onPress: () => { - dispatch(backToEntrypointPayment()); - resetPayment(); - IOToast.success( - I18n.t("wallet.ConfirmPayment.cancelPaymentSuccess") - ); - } - }, - { - text: I18n.t("wallet.ConfirmPayment.confirmContinuePayment"), - style: "cancel" - } - ] - ); - } else { - navigation.goBack(); - } - }; - - const verifyPayment = () => - dispatch( - paymentVerifica.request({ rptId, startOrigin: paymentStartOrigin }) - ); - - const continueWithPayment = useStartOrResumePayment( - rptId, - pot.toOption(paymentVerification), - initialAmount, - maybeFavoriteWallet - ); - - const resetPayment = () => { - dispatch(runDeleteActivePaymentSaga()); - dispatch(paymentInitializeState()); - }; - - const startAssistanceRequest = ( - error: TransactionSummaryError, - messageId: string | undefined - ) => { - resetCustomFields(); - addTicketCustomField(zendeskCategoryId, zendeskPaymentCategory.value); - // Add organization fiscal code custom field - addTicketCustomField( - zendeskPaymentOrgFiscalCode, - rptId.organizationFiscalCode - ); - if (O.isSome(error) && error.value) { - // Add failure custom field - addTicketCustomField(zendeskPaymentFailure, error.value); - } - // Add start origin custom field - addTicketCustomField(zendeskPaymentStartOrigin, paymentStartOrigin); - // Add rptId custom field - addTicketCustomField(zendeskPaymentNav, getCodiceAvviso(rptId)); - appendLog( - JSON.stringify({ - error, - messageId - }) - ); - dispatch( - zendeskSupportStart({ - startingRoute: "n/a", - assistanceForPayment: true, - assistanceForCard: false, - assistanceForFci: false - }) - ); - dispatch(zendeskSelectedCategory(zendeskPaymentCategory)); - }; - - const paymentNoticeNumber = PaymentNoticeNumberFromString.encode( - rptId.paymentNoticeNumber - ); - - /** - * try to show the fiscal code coming from the 'verification' API - * otherwise (it could be an issue with the API) use the rptID coming from - * static data (e.g. message, qrcode, manual insertion, etc.) - */ - const organizationFiscalCode = pipe( - pot.toOption(paymentVerification), - O.chainNullableK( - _ => _.enteBeneficiario?.identificativoUnivocoBeneficiario - ), - O.getOrElse(() => rptId.organizationFiscalCode) - ); - - useHeaderSecondLevel({ - title: I18n.t("wallet.ConfirmPayment.paymentInformations"), - supportRequest: true, - contextualHelp: emptyContextualHelp, - goBack, - backTestID: "back-button-transaction-summary" - }); - - return ( - <> - - {showsInlineError && } - - - - {showsInlineError && pot.isError(paymentVerification) && ( - - )} - - - - {renderFooter( - isLoading, - error, - () => continueWithPayment(), - () => startAssistanceRequest(error, messageId), - handleFooterActionsMeasurements - )} - - ); -}; - -export default TransactionSummaryScreen; diff --git a/ts/screens/wallet/payment/__tests__/ConfirmPaymentMethodScreen.test.tsx b/ts/screens/wallet/payment/__tests__/ConfirmPaymentMethodScreen.test.tsx deleted file mode 100644 index d70afceef1e..00000000000 --- a/ts/screens/wallet/payment/__tests__/ConfirmPaymentMethodScreen.test.tsx +++ /dev/null @@ -1,416 +0,0 @@ -import { createStore, DeepPartial, Store } from "redux"; -import { PspData } from "../../../../../definitions/pagopa/PspData"; -import { PayWebViewModal } from "../../../../components/wallet/PayWebViewModal"; -import { remoteReady } from "../../../../common/model/RemoteValue"; -import I18n from "../../../../i18n"; -import ROUTES from "../../../../navigation/routes"; -import { appReducer } from "../../../../store/reducers/"; -import { - bancomatPayConfigSelector, - isPaypalEnabledSelector -} from "../../../../store/reducers/backendStatus"; -import { GlobalState } from "../../../../store/reducers/types"; -import { pspSelectedV2ListSelector } from "../../../../store/reducers/wallet/payment"; -import { paymentMethodByIdSelector } from "../../../../store/reducers/wallet/wallets"; -import { - BPayPaymentMethod, - CreditCardPaymentMethod, - PayPalPaymentMethod -} from "../../../../types/pagopa"; -import { getTranslatedShortNumericMonthYear } from "../../../../utils/dates"; -import { getLookUpIdPO, newLookUpId } from "../../../../utils/pmLookUpId"; -import { formatNumberCentsToAmount } from "../../../../utils/stringBuilder"; -import { - AuthSeq, - myInitialAmount, - myRptId, - myVerifiedData, - myWallet -} from "../../../../utils/testFaker"; -import { reproduceSequence } from "../../../../utils/tests"; -import { renderScreenWithNavigationStoreContext } from "../../../../utils/testWrapper"; -import ConfirmPaymentMethodScreen, { - ConfirmPaymentMethodScreenNavigationParams -} from "../ConfirmPaymentMethodScreen"; - -// Mock react native share -jest.mock("react-native-share", () => jest.fn()); - -// Mock the PayWebViewModal -jest.mock("../../../../components/wallet/PayWebViewModal", () => { - const actualModule = jest.requireActual( - "../../../../components/wallet/PayWebViewModal" - ); - - return { - __esModule: true, - ...actualModule, - PayWebViewModal: jest.fn(() => null) - }; -}); - -// Mock the internal payment method -jest.mock("../../../../store/reducers/wallet/wallets", () => { - const actualModule = jest.requireActual( - "../../../../store/reducers/wallet/wallets" - ); - - return { - __esModule: true, - ...actualModule, - paymentMethodByIdSelector: jest.fn() - }; -}); - -// Mock payment -jest.mock("../../../../store/reducers/wallet/payment", () => { - const actualModule = jest.requireActual( - "../../../../store/reducers/wallet/payment" - ); - - return { - __esModule: true, - ...actualModule, - pspSelectedV2ListSelector: jest.fn() - }; -}); - -// Mock feature flags -jest.mock("../../../../store/reducers/backendStatus", () => { - const actualModule = jest.requireActual( - "../../../../store/reducers/backendStatus" - ); - - return { - __esModule: true, - ...actualModule, - isPaypalEnabledSelector: jest.fn(), - bancomatPayConfigSelector: jest.fn() - }; -}); - -// Credit card payment method stub -const creditCardPaymentMethod = { - kind: "CreditCard", - info: { - type: "CRD", - holder: "holder", - expireMonth: "03", - expireYear: "2022" - }, - caption: "caption" -} as CreditCardPaymentMethod; - -const pspList: ReadonlyArray = [ - { - id: 1, - codiceAbi: "0001", - defaultPsp: true, - fee: 100, - idPsp: "1", - onboard: true, - privacyUrl: "https://io.italia.it", - ragioneSociale: "PayTipper" - }, - { - id: 2, - codiceAbi: "0002", - defaultPsp: true, - fee: 120, - idPsp: "2", - onboard: true, - privacyUrl: "https://io.italia.it", - ragioneSociale: "PayTipper2" - } -]; - -// PayPal card payment method stub -const paypalEmail = "email@email.com"; -const payPalPaymentMethod = { - kind: "PayPal", - info: { - pspInfo: [ - { - email: paypalEmail, - default: true - } - ] - }, - caption: "caption" -} as DeepPartial; - -const bpayPaymentMethod = { - kind: "BPay", - caption: "BPay caption", - info: { - numberObfuscated: "***123" - } -} as DeepPartial; - -describe("Integration Tests With Actual Store and Simplified Navigation", () => { - afterAll(() => jest.resetAllMocks()); - beforeEach(() => jest.useFakeTimers()); - - const initState = reproduceSequence({} as GlobalState, appReducer, AuthSeq); - - const params: ConfirmPaymentMethodScreenNavigationParams = { - rptId: myRptId, - initialAmount: myInitialAmount, - verifica: myVerifiedData, - idPayment: "hjkdhgkdj", - wallet: myWallet, - psps: pspList - }; - - // Store with the true appReducer - const myStore: Store = createStore(appReducer, initState as any); - - it("should display all the informations correctly for a `CreditCard` payment method", () => { - (paymentMethodByIdSelector as unknown as jest.Mock).mockReturnValue( - creditCardPaymentMethod - ); - - (bancomatPayConfigSelector as unknown as jest.Mock).mockReturnValue({ - display: false, - onboarding: false, - payment: true - }); - - const rendered = renderScreenWithNavigationStoreContext( - ConfirmPaymentMethodScreen, - ROUTES.PAYMENT_CONFIRM_PAYMENT_METHOD, - params, - myStore - ); - - // Should display the payment reason - rendered.getByText(params.verifica.causaleVersamento); - - // Should display the payment amount - rendered.getByText( - formatNumberCentsToAmount(params.verifica.importoSingoloVersamento, true) - ); - - // Should display the payment method - rendered.getByText(creditCardPaymentMethod.caption); - - rendered.getByText( - `${ - creditCardPaymentMethod.info.holder - } · ${getTranslatedShortNumericMonthYear( - creditCardPaymentMethod.info.expireYear, - creditCardPaymentMethod.info.expireMonth - )}` - ); - - // Should render the PSP with the fees - rendered.getByText( - formatNumberCentsToAmount(params.wallet.psp?.fixedCost.amount ?? -1, true) - ); - - rendered.getByText( - `${I18n.t("wallet.ConfirmPayment.providedBy")} ${ - params.wallet.psp?.businessName - }` - ); - - // It should retrieve two `Edit` text - // one for the payment method and one - // for the PSP. - expect( - rendered.getAllByText(I18n.t("wallet.ConfirmPayment.edit")) - ).toHaveLength(2); - }); - - it("should display all the informations correctly for a `PayPal` payment method", () => { - const paypalPspFee = 2; - const paypalPspName = "name"; - const paypalPrivacyUrl = "https://host.com"; - - (paymentMethodByIdSelector as unknown as jest.Mock).mockReturnValue( - payPalPaymentMethod - ); - - (bancomatPayConfigSelector as unknown as jest.Mock).mockReturnValue({ - display: false, - onboarding: false, - payment: true - }); - - (isPaypalEnabledSelector as unknown as jest.Mock).mockReturnValue(true); - - (pspSelectedV2ListSelector as unknown as jest.Mock).mockReturnValue({ - fee: paypalPspFee, - ragioneSociale: paypalPspName, - privacyUrl: paypalPrivacyUrl - }); - - const paypalParams = { - ...params, - wallet: { - ...params.wallet, - paymentMethod: payPalPaymentMethod as PayPalPaymentMethod - } - }; - - const rendered = renderScreenWithNavigationStoreContext( - ConfirmPaymentMethodScreen, - ROUTES.PAYMENT_CONFIRM_PAYMENT_METHOD, - paypalParams, - myStore - ); - - // Should display the payment reason - rendered.getByText(paypalParams.verifica.causaleVersamento); - - // Should display the payment amount - rendered.getByText( - formatNumberCentsToAmount( - paypalParams.verifica.importoSingoloVersamento, - true - ) - ); - - // Should display the payment method - rendered.getByText(I18n.t("wallet.onboarding.paypal.name")); - - rendered.getByText(`${paypalEmail}`); - - // Should render the PSP with the fees - rendered.getByText(formatNumberCentsToAmount(paypalPspFee, true)); - - rendered.getByText( - `${I18n.t("wallet.ConfirmPayment.providedBy")} ${paypalPspName}` - ); - - // Should render the privacy url for PayPal - rendered.getByText( - `${I18n.t( - "wallet.onboarding.paypal.paymentCheckout.privacyDisclaimer" - )} ${I18n.t("wallet.onboarding.paypal.paymentCheckout.privacyTerms")}` - ); - - // It should retrieve one `Edit` text, only - // the one for the payment method. - expect( - rendered.getAllByText(I18n.t("wallet.ConfirmPayment.edit")) - ).toHaveLength(1); - }); - - it("should display all the information correctly for a `BPay` payment method", () => { - (paymentMethodByIdSelector as unknown as jest.Mock).mockReturnValue( - bpayPaymentMethod - ); - - (bancomatPayConfigSelector as unknown as jest.Mock).mockReturnValue({ - display: false, - onboarding: false, - payment: true - }); - - const bpayParams = { - ...params, - wallet: { - ...params.wallet, - paymentMethod: bpayPaymentMethod as BPayPaymentMethod - } - }; - - const rendered = renderScreenWithNavigationStoreContext( - ConfirmPaymentMethodScreen, - ROUTES.PAYMENT_CONFIRM_PAYMENT_METHOD, - bpayParams, - myStore - ); - - // Should display the payment reason - rendered.getByText(bpayParams.verifica.causaleVersamento); - - // Should display the payment amount - rendered.getByText( - formatNumberCentsToAmount( - bpayParams.verifica.importoSingoloVersamento, - true - ) - ); - - // Should display the payment method - rendered.getByText(bpayPaymentMethod.caption!); - - rendered.getByText(bpayPaymentMethod.info!.numberObfuscated!); - - // Should render the PSP with the fees - rendered.getByText( - formatNumberCentsToAmount(params.wallet.psp?.fixedCost.amount ?? -1, true) - ); - - rendered.getByText( - `${I18n.t("wallet.ConfirmPayment.providedBy")} ${ - params.wallet.psp?.businessName - }` - ); - - /** - * It should retrieve two `Edit` CTAs - * one for the payment method. - * one for the PSP - */ - expect( - rendered.getAllByText(I18n.t("wallet.ConfirmPayment.edit")) - ).toHaveLength(2); - }); - - it("should send all the correct informations to the `PayWebViewModal` component", () => { - const PayWebViewModalMock = PayWebViewModal as unknown as jest.Mock; - - newLookUpId(); - - const idPayment = "id"; - const language = "it"; - const idWallet = 123; - const sessionToken = "token"; - - const customInitState = reproduceSequence( - { - wallet: { - payment: { - pmSessionToken: remoteReady(sessionToken), - paymentStartPayload: { - idWallet, - idPayment, - language - } - } - } - } as GlobalState, - appReducer, - AuthSeq - ); - - const customStore: Store = createStore( - appReducer, - customInitState as any - ); - - renderScreenWithNavigationStoreContext( - ConfirmPaymentMethodScreen, - ROUTES.PAYMENT_CONFIRM_PAYMENT_METHOD, - params, - customStore - ); - - const expectedFormDataProp: Record = { - idPayment, - idWallet, - language, - sessionToken, - ...getLookUpIdPO() - }; - - expect(PayWebViewModalMock).toBeCalled(); - - const receivedProps = PayWebViewModalMock.mock.calls[0][0]; - - expect(receivedProps.formData).toMatchObject(expectedFormDataProp); - }); -}); diff --git a/ts/screens/wallet/payment/__tests__/PickPaymentMethodScreen.test.tsx b/ts/screens/wallet/payment/__tests__/PickPaymentMethodScreen.test.tsx deleted file mode 100644 index a4e0ccc569a..00000000000 --- a/ts/screens/wallet/payment/__tests__/PickPaymentMethodScreen.test.tsx +++ /dev/null @@ -1,220 +0,0 @@ -import { AmountInEuroCents, RptId } from "@pagopa/io-pagopa-commons/lib/pagopa"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { fireEvent } from "@testing-library/react-native"; -import { some } from "fp-ts/lib/Option"; - -import { Action, Store } from "redux"; -import configureMockStore from "redux-mock-store"; -import { PaymentRequestsGetResponse } from "../../../../../definitions/backend/PaymentRequestsGetResponse"; -import { WalletTypeEnum } from "../../../../../definitions/pagopa/WalletV2"; - -import { EnableableFunctionsEnum } from "../../../../../definitions/pagopa/EnableableFunctions"; -import I18n from "../../../../i18n"; -import { applicationChangeState } from "../../../../store/actions/application"; -import * as NavigationActions from "../../../../store/actions/navigation"; -import { pspForPaymentV2WithCallbacks } from "../../../../store/actions/wallet/payment"; -import { toIndexed } from "../../../../store/helpers/indexer"; -import { appReducer } from "../../../../store/reducers"; -import { GlobalState } from "../../../../store/reducers/types"; -import { CreditCardPaymentMethod } from "../../../../types/pagopa"; -import { renderScreenWithNavigationStoreContext } from "../../../../utils/testWrapper"; -import { convertWalletV2toWalletV1 } from "../../../../utils/walletv2"; -import PickPaymentMethodScreen from "../PickPaymentMethodScreen"; -import WALLET_ONBOARDING_COBADGE_ROUTES from "../../../../features/wallet/onboarding/cobadge/navigation/routes"; - -const rptId = {} as RptId; -const initialAmount = "300" as AmountInEuroCents; -const verifica = {} as PaymentRequestsGetResponse; -const idPayment = "123"; - -const aCreditCard = { - idWallet: 1, - kind: "CreditCard", - walletType: WalletTypeEnum.Card, - info: { - brand: "VISA", - type: undefined - }, - enableableFunctions: [EnableableFunctionsEnum.pagoPA], - caption: "", - icon: "", - pagoPA: true, - onboardingChannel: "IO" -} as CreditCardPaymentMethod; - -const mockPresentFn = jest.fn(); - -jest.mock("../../../../utils/hooks/bottomSheet", () => { - // eslint-disable-next-line @typescript-eslint/no-var-requires - const react = require("react-native"); - - return { - __esModule: true, - useIOBottomSheetAutoresizableModal: () => ({ - present: mockPresentFn, - bottomSheet: react.View - }) - }; -}); -describe("PickPaymentMethodScreen", () => { - jest.useFakeTimers(); - - const mockStore = configureMockStore(); - // eslint-disable-next-line functional/no-let - let store: ReturnType; - const globalState = appReducer(undefined, applicationChangeState("active")); - - it("should dispatch the navigateBack action if the back button is pressed", () => { - store = mockStore(globalState); - const spy = jest.spyOn(NavigationActions, "navigateBack"); - - const component = renderPickPaymentMethodScreen(store); - - expect(component).not.toBeNull(); - const cancelButton = component.getByText(I18n.t("global.buttons.back")); - - expect(cancelButton).not.toBeNull(); - if (cancelButton !== null) { - fireEvent.press(cancelButton); - expect(spy).toHaveBeenCalled(); - } - }); - it("should dispatch the navigateToAddPaymentMethod action if the 'add payment method' button is pressed", () => { - store = mockStore(globalState); - - const spy = jest.spyOn( - NavigationActions, - "navigateToWalletAddPaymentMethod" - ); - - const component = renderPickPaymentMethodScreen(store); - - const addPaymentMethodButton = component.getByText( - I18n.t("wallet.newPaymentMethod.addButton") - ); - - expect(addPaymentMethodButton).not.toBeNull(); - if (addPaymentMethodButton !== null) { - fireEvent.press(addPaymentMethodButton); - expect(spy).toHaveBeenCalledWith({ - inPayment: some({ - rptId, - initialAmount, - verifica, - idPayment - }) - }); - } - }); - it("should show the no wallet message if there aren't available payment method", () => { - store = mockStore(globalState); - - const component = renderPickPaymentMethodScreen(store); - - expect(component).not.toBeNull(); - const noWalletMessage = component.getByTestId("noWallets"); - - expect(noWalletMessage).not.toBeNull(); - }); - it("should show the availablePaymentMethodList if there is at least one available payment method", () => { - const indexedWalletById = toIndexed( - [aCreditCard].map(convertWalletV2toWalletV1), - pm => pm.idWallet - ); - - store = mockStore({ - ...globalState, - wallet: { - ...globalState.wallet, - wallets: { - ...globalState.wallet.wallets, - walletById: pot.some(indexedWalletById) - } - } - }); - - const component = renderPickPaymentMethodScreen(store); - const availablePaymentMethodList = component.queryByTestId( - "availablePaymentMethodList" - ); - - expect(availablePaymentMethodList).not.toBeNull(); - }); - it("should dispatch the navigateToConfirmOrPickPsp action if an available payment method is pressed", () => { - const indexedWalletById = toIndexed( - [aCreditCard].map(convertWalletV2toWalletV1), - pm => pm.idWallet - ); - - store = mockStore({ - ...globalState, - wallet: { - ...globalState.wallet, - wallets: { - ...globalState.wallet.wallets, - walletById: pot.some(indexedWalletById) - } - } - }); - - const component = renderPickPaymentMethodScreen(store); - const availablePaymentMethodList = component.queryByTestId( - `availableMethod-${aCreditCard.idWallet}` - ); - - expect(availablePaymentMethodList).not.toBeNull(); - - if (availablePaymentMethodList !== null) { - fireEvent.press(availablePaymentMethodList); - - expect(store.getActions()).toEqual([ - pspForPaymentV2WithCallbacks({ - idPayment, - idWallet: aCreditCard.idWallet, - onFailure: expect.any(Function), - onSuccess: expect.any(Function) - }) - ]); - } - }); - - it("should show a credit card if the field onboardingChannel is undefined", () => { - const indexedWalletById = toIndexed( - [{ ...aCreditCard, onboardingChannel: undefined }].map( - convertWalletV2toWalletV1 - ), - pm => pm.idWallet - ); - - store = mockStore({ - ...globalState, - wallet: { - ...globalState.wallet, - wallets: { - ...globalState.wallet.wallets, - walletById: pot.some(indexedWalletById) - } - } - }); - - const component = renderPickPaymentMethodScreen(store); - const availablePaymentMethodList = component.queryByTestId( - "availablePaymentMethodList" - ); - - expect(availablePaymentMethodList).not.toBeNull(); - }); -}); - -const renderPickPaymentMethodScreen = (store: Store) => - renderScreenWithNavigationStoreContext( - PickPaymentMethodScreen, - WALLET_ONBOARDING_COBADGE_ROUTES.SEARCH_AVAILABLE, - { - rptId, - initialAmount, - verifica, - idPayment - }, - store - ); diff --git a/ts/screens/wallet/payment/__tests__/PickPspScreen.test.tsx b/ts/screens/wallet/payment/__tests__/PickPspScreen.test.tsx deleted file mode 100644 index 4474a3980a4..00000000000 --- a/ts/screens/wallet/payment/__tests__/PickPspScreen.test.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { AmountInEuroCents, RptId } from "@pagopa/io-pagopa-commons/lib/pagopa"; -import configureMockStore from "redux-mock-store"; - -import { PaymentRequestsGetResponse } from "../../../../../definitions/backend/PaymentRequestsGetResponse"; -import ROUTES from "../../../../navigation/routes"; -import { applicationChangeState } from "../../../../store/actions/application"; -import { appReducer } from "../../../../store/reducers"; -import { GlobalState } from "../../../../store/reducers/types"; -import { Psp, Wallet } from "../../../../types/pagopa"; -import { renderScreenWithNavigationStoreContext } from "../../../../utils/testWrapper"; -import PickPspScreen from "../PickPspScreen"; - -const rptId = {} as RptId; -const initialAmount = "300" as AmountInEuroCents; -const verifica = {} as PaymentRequestsGetResponse; -const idPayment = "123"; -const psps = [ - { - id: 0, - fixedCost: { amount: 10 }, - logoPSP: - "https://acardste.vaservices.eu:1443/pp-restapi/v1/resources/psp/43188" - } -] as ReadonlyArray; -const wallet = { - idWallet: 38404 -} as Wallet; - -describe("Test PickPspScreen", () => { - jest.useFakeTimers(); - - it("rendering PickPspScreen, all the required components should be defined", () => { - const { component } = renderComponent(); - - expect(component.queryByTestId("PickPspScreen")).not.toBeNull(); - }); - it("should show the pspList if there is at least one psp", () => { - const { component } = renderComponent(); - - const pspList = component.queryByTestId("pspList"); - - expect(pspList).not.toBeNull(); - }); -}); - -const renderComponent = () => { - const globalState = appReducer(undefined, applicationChangeState("active")); - - const mockStore = configureMockStore(); - const store: ReturnType = mockStore({ - ...globalState - } as GlobalState); - - return { - component: renderScreenWithNavigationStoreContext( - PickPspScreen, - ROUTES.PAYMENT_PICK_PSP, - { - rptId, - initialAmount, - verifica, - idPayment, - psps, - wallet - }, - store - ), - store - }; -}; diff --git a/ts/screens/wallet/payment/__tests__/TransactionErrorScreen.test.tsx b/ts/screens/wallet/payment/__tests__/TransactionErrorScreen.test.tsx deleted file mode 100644 index d69839896ce..00000000000 --- a/ts/screens/wallet/payment/__tests__/TransactionErrorScreen.test.tsx +++ /dev/null @@ -1,229 +0,0 @@ -import configureMockStore from "redux-mock-store"; - -import { RptId } from "@pagopa/io-pagopa-commons/lib/pagopa"; -import { Option, some } from "fp-ts/lib/Option"; - -import { Detail_v2Enum } from "../../../../../definitions/backend/PaymentProblemJson"; -import I18n from "../../../../i18n"; -import ROUTES from "../../../../navigation/routes"; -import { applicationChangeState } from "../../../../store/actions/application"; -import { - paymentAttiva, - paymentIdPolling, - paymentVerifica -} from "../../../../store/actions/wallet/payment"; -import { appReducer } from "../../../../store/reducers"; -import { GlobalState } from "../../../../store/reducers/types"; -import { PayloadForAction } from "../../../../types/utils"; -import { renderScreenWithNavigationStoreContext } from "../../../../utils/testWrapper"; -import TransactionErrorScreen from "../TransactionErrorScreen"; -// this is required for TSC, since apparently -// it does not update the typedef unless it is actually -// imported in a test (or a global.d) and not in the jest global config -import "@testing-library/jest-native/extend-expect"; - -const rptId = { - organizationFiscalCode: "00000000005", - paymentNoticeNumber: { - applicationCode: "69", - auxDigit: "0", - checkDigit: "88", - iuv13: "7598658729311" - } -} as RptId; -const onCancel = jest.fn(); - -describe("TransactionErrorScreen", () => { - jest.useFakeTimers(); - - it("Should render technical screen", () => { - const { component } = renderComponent( - some(Detail_v2Enum.PPT_CANALE_DISABILITATO) - ); - expect(component.queryByTestId("error-code-copy-component")).toBeDefined(); - expect(component.queryByTestId("error-code")).toHaveTextContent( - Detail_v2Enum.PPT_CANALE_DISABILITATO - ); - expect(component.queryByTestId("infoScreenTitle")).toHaveTextContent( - I18n.t("wallet.errors.TECHNICAL") - ); - expect(component.queryByTestId("sendReportButtonConfirm")).toBeDefined(); - expect(component.queryByTestId("closeButtonCancel")).toBeDefined(); - }); - - it("Should render data screen", () => { - const { component } = renderComponent( - some(Detail_v2Enum.PPT_SINTASSI_EXTRAXSD) - ); - expect(component.queryByTestId("error-code-copy-component")).toBeDefined(); - expect(component.queryByTestId("error-code")).toHaveTextContent( - Detail_v2Enum.PPT_SINTASSI_EXTRAXSD - ); - expect(component.queryByTestId("infoScreenTitle")).toHaveTextContent( - I18n.t("wallet.errors.DATA") - ); - expect(component.queryByTestId("sendReportButtonCcancel")).toBeDefined(); - expect(component.queryByTestId("backButtonConfirm")).toBeDefined(); - }); - - it("Should render EC screen", () => { - const { component } = renderComponent( - some(Detail_v2Enum.PPT_STAZIONE_INT_PA_TIMEOUT) - ); - expect(component.queryByTestId("error-code-copy-component")).toBeDefined(); - expect(component.queryByTestId("error-code")).toHaveTextContent( - Detail_v2Enum.PPT_STAZIONE_INT_PA_TIMEOUT - ); - expect(component.queryByTestId("infoScreenTitle")).toHaveTextContent( - I18n.t("wallet.errors.EC") - ); - expect(component.queryByTestId("sendReportButtonConfirm")).toBeDefined(); - expect(component.queryByTestId("closeButtonCancel")).toBeDefined(); - }); - - it("Should render ONGOING screen on PAA_PAGAMENTO_IN_CORSO error code", () => { - const { component } = renderComponent( - some(Detail_v2Enum.PAA_PAGAMENTO_IN_CORSO) - ); - expect(component.queryByTestId("error-code-copy-component")).toBeNull(); - expect(component.queryByTestId("infoScreenTitle")).toHaveTextContent( - I18n.t("wallet.errors.ONGOING") - ); - expect(component.queryByTestId("ongoing-subtitle")).toHaveTextContent( - I18n.t("wallet.errors.ONGOING_SUBTITLE") - ); - expect(component.queryByTestId("sendReportButtonCcancel")).toBeDefined(); - expect(component.queryByTestId("closeButtonConfirm")).toBeDefined(); - }); - - it("Should render ONGOING screen on PPT_PAGAMENTO_IN_CORSO error code", () => { - const { component } = renderComponent( - some(Detail_v2Enum.PPT_PAGAMENTO_IN_CORSO) - ); - expect(component.queryByTestId("error-code-copy-component")).toBeNull(); - expect(component.queryByTestId("infoScreenTitle")).toHaveTextContent( - I18n.t("wallet.errors.ONGOING") - ); - expect(component.queryByTestId("ongoing-subtitle")).toHaveTextContent( - I18n.t("wallet.errors.ONGOING_SUBTITLE") - ); - expect(component.queryByTestId("sendReportButtonCcancel")).toBeDefined(); - expect(component.queryByTestId("closeButtonConfirm")).toBeDefined(); - }); - - it("Should render REVOKED screen", () => { - const { component } = renderComponent( - some(Detail_v2Enum.PAA_PAGAMENTO_ANNULLATO) - ); - expect(component.queryByTestId("error-code-copy-component")).toBeNull(); - expect(component.queryByTestId("infoScreenTitle")).toHaveTextContent( - I18n.t("wallet.errors.REVOKED") - ); - expect(component.queryByTestId("revoked-subtitle")).toHaveTextContent( - I18n.t("wallet.errors.contactECsubtitle") - ); - expect(component.queryByTestId("closeButtonCancel")).toBeDefined(); - }); - - it("Should render NOT_FOUND screen", () => { - const { component } = renderComponent( - some(Detail_v2Enum.PAA_PAGAMENTO_SCONOSCIUTO) - ); - expect(component.queryByTestId("error-code-copy-component")).toBeNull(); - expect(component.queryByTestId("infoScreenTitle")).toHaveTextContent( - I18n.t("wallet.errors.NOT_FOUND") - ); - expect(component.queryByTestId("not-found-subtitle")).toHaveTextContent( - I18n.t("wallet.errors.NOT_FOUND_SUBTITLE").replace("\n", " ") - ); - expect(component.queryByTestId("closeButtonConfirm")).toBeDefined(); - }); - - it("Should render EXPIRED screen", () => { - const { component } = renderComponent( - some(Detail_v2Enum.PAA_PAGAMENTO_SCADUTO) - ); - expect(component.queryByTestId("error-code-copy-component")).toBeNull(); - expect(component.queryByTestId("infoScreenTitle")).toHaveTextContent( - I18n.t("wallet.errors.EXPIRED") - ); - expect(component.queryByTestId("expired-subtitle")).toHaveTextContent( - I18n.t("wallet.errors.contactECsubtitle") - ); - expect(component.queryByTestId("closeButtonCancel")).toBeDefined(); - }); - - it("Should render DUPLICATED screen on PAA_PAGAMENTO_DUPLICATO error code", () => { - const { component } = renderComponent( - some(Detail_v2Enum.PAA_PAGAMENTO_DUPLICATO) - ); - expect(component.queryByTestId("error-code-copy-component")).toBeNull(); - expect(component.queryByTestId("infoScreenTitle")).toHaveTextContent( - I18n.t("wallet.errors.DUPLICATED") - ); - expect(component.queryByTestId("revoked-subtitle")).toBeNull(); - expect(component.queryByTestId("expired-subtitle")).toBeNull(); - expect(component.queryByTestId("closeButtonCancel")).toBeDefined(); - }); - - it("Should render DUPLICATED screen on PPT_PAGAMENTO_DUPLICATO error code", () => { - const { component } = renderComponent( - some(Detail_v2Enum.PPT_PAGAMENTO_DUPLICATO) - ); - expect(component.queryByTestId("error-code-copy-component")).toBeNull(); - expect(component.queryByTestId("infoScreenTitle")).toHaveTextContent( - I18n.t("wallet.errors.DUPLICATED") - ); - expect(component.queryByTestId("revoked-subtitle")).toBeNull(); - expect(component.queryByTestId("expired-subtitle")).toBeNull(); - expect(component.queryByTestId("closeButtonCancel")).toBeDefined(); - }); - - it("Should render UNCOVERED screen", () => { - const { component } = renderComponent( - some(Detail_v2Enum.PPT_RT_SCONOSCIUTA) - ); - expect(component.queryByTestId("error-code-copy-component")).toBeNull(); - expect(component.queryByTestId("infoScreenTitle")).toHaveTextContent( - I18n.t("wallet.errors.GENERIC_ERROR") - ); - expect(component.queryByTestId("revoked-subtitle")).toBeNull(); - expect(component.queryByTestId("expired-subtitle")).toBeNull(); - expect(component.queryByTestId("generic-error-subtitle")).toHaveTextContent( - I18n.t("wallet.errors.GENERIC_ERROR_SUBTITLE") - ); - expect(component.queryByTestId("sendReportButtonCcancel")).toBeDefined(); - expect(component.queryByTestId("closeButtonConfirm")).toBeDefined(); - }); -}); - -const renderComponent = ( - error: Option< - PayloadForAction< - | (typeof paymentVerifica)["failure"] - | (typeof paymentAttiva)["failure"] - | (typeof paymentIdPolling)["failure"] - > - > -) => { - const globalState = appReducer(undefined, applicationChangeState("active")); - - const mockStore = configureMockStore(); - const store: ReturnType = mockStore({ - ...globalState - } as GlobalState); - - return { - component: renderScreenWithNavigationStoreContext( - TransactionErrorScreen, - ROUTES.PAYMENT_TRANSACTION_ERROR, - { - error, - rptId, - onCancel - }, - store - ), - store - }; -}; diff --git a/ts/screens/wallet/payment/__tests__/commons.test.ts b/ts/screens/wallet/payment/__tests__/commons.test.ts deleted file mode 100644 index b8cea75c098..00000000000 --- a/ts/screens/wallet/payment/__tests__/commons.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { PspData } from "../../../../../definitions/pagopa/PspData"; -import { filterPspsByPreferredPsps } from "../common"; - -type SimplePspData = Pick; - -type TestCase = { - originalPsps: ReadonlyArray; - remotePreferredPsps: ReadonlyArray | undefined; - fallbackPreferredPsps: ReadonlyArray | undefined; - expectedPsps: ReadonlyArray; -}; - -const PspNP1 = { - idPsp: "PSP_NOT_PREFERRED_1" -}; - -const PspNP2 = { - idPsp: "PSP_NOT_PREFERRED_2" -}; - -const PspP1 = { - idPsp: "PSP_PREFERRED_1" -}; - -const PspP2 = { - idPsp: "PSP_PREFERRED_2" -}; - -const TEST_CASES: ReadonlyArray = [ - { - originalPsps: [PspNP1, PspNP2, PspP1, PspP2], - remotePreferredPsps: undefined, - fallbackPreferredPsps: undefined, - expectedPsps: [PspNP1, PspNP2, PspP1, PspP2] - }, - { - originalPsps: [PspNP1, PspNP2, PspP1, PspP2], - remotePreferredPsps: [], - fallbackPreferredPsps: [], - expectedPsps: [PspNP1, PspNP2, PspP1, PspP2] - }, - { - originalPsps: [PspNP1, PspNP2, PspP1, PspP2], - remotePreferredPsps: [PspP1.idPsp], - fallbackPreferredPsps: undefined, - expectedPsps: [PspP1] - }, - { - originalPsps: [PspNP1, PspNP2, PspP1, PspP2], - remotePreferredPsps: [PspP1.idPsp, PspP2.idPsp], - fallbackPreferredPsps: undefined, - expectedPsps: [PspP1, PspP2] - }, - { - originalPsps: [PspNP1, PspNP2, PspP1, PspP2], - remotePreferredPsps: undefined, - fallbackPreferredPsps: [PspP1.idPsp], - expectedPsps: [PspP1] - }, - { - originalPsps: [PspNP1, PspNP2, PspP1, PspP2], - remotePreferredPsps: undefined, - fallbackPreferredPsps: [PspP1.idPsp, PspP2.idPsp], - expectedPsps: [PspP1, PspP2] - }, - { - originalPsps: [PspNP1, PspNP2, PspP1, PspP2], - remotePreferredPsps: [PspP1.idPsp], - fallbackPreferredPsps: [PspP2.idPsp], - expectedPsps: [PspP1] - } -]; - -describe("filterPspsByAllowedPsps", () => { - it.each(TEST_CASES)( - "should filter original PSPs by preferred PSPs", - ({ - originalPsps, - remotePreferredPsps, - fallbackPreferredPsps, - expectedPsps - }) => { - expect( - filterPspsByPreferredPsps( - originalPsps as unknown as ReadonlyArray, - remotePreferredPsps, - fallbackPreferredPsps - ) - ).toEqual(expectedPsps); - } - ); -}); diff --git a/ts/screens/wallet/payment/common.ts b/ts/screens/wallet/payment/common.ts deleted file mode 100644 index 0ac21128847..00000000000 --- a/ts/screens/wallet/payment/common.ts +++ /dev/null @@ -1,273 +0,0 @@ -import { AmountInEuroCents, RptId } from "@pagopa/io-pagopa-commons/lib/pagopa"; -import * as O from "fp-ts/lib/Option"; -import { ActionType } from "typesafe-actions"; - -import { PaymentRequestsGetResponse } from "../../../../definitions/backend/PaymentRequestsGetResponse"; -import { Config } from "../../../../definitions/content/Config"; -import { PspData } from "../../../../definitions/pagopa/PspData"; -import { POSTE_DATAMATRIX_SCAN_PREFERRED_PSPS } from "../../../config"; -import { - navigateToPaymentConfirmPaymentMethodScreen, - navigateToPaymentPickPaymentMethodScreen, - navigateToPaymentPickPspScreen, - navigateToWalletAddPaymentMethod -} from "../../../store/actions/navigation"; -import { Dispatch } from "../../../store/actions/types"; -import { - PaymentStartOrigin, - paymentUpdateWalletPsp, - pspForPaymentV2WithCallbacks, - pspSelectedForPaymentV2 -} from "../../../store/actions/wallet/payment"; -import { isRawPayPal, Wallet } from "../../../types/pagopa"; -import { walletHasFavoriteAvailablePspData } from "../../../utils/payment"; - -/** - * If needed, filter the PSPs list by the preferred PSPs. - * Preferred PSPs could be defined remotely with a local fallback. - * Remote configuration has priority over local configuration. - */ -export const filterPspsByPreferredPsps = ( - pspList: ReadonlyArray, - remotePreferredPsps: ReadonlyArray | undefined, - fallbackPreferredPsps: ReadonlyArray | undefined -): ReadonlyArray => { - const preferredPsps = remotePreferredPsps ?? fallbackPreferredPsps; - - // If preferredPsps is undefined or empty we return the original list - // because we don't have any filter to apply - if (preferredPsps === undefined || preferredPsps.length === 0) { - return pspList; - } - - // The list of filtered PSPs - const filteredPsps = pspList.filter(psp => preferredPsps.includes(psp.idPsp)); - - // If we have filtered PSPs we return them, otherwise we return the original list - return filteredPsps.length > 0 ? filteredPsps : pspList; -}; - -/** - * Filter the PSPs list by the payment start origin. - */ -const filterPspsByPaymentStartOrigin = ( - paymentsStartOrigin: PaymentStartOrigin, - preferredPspsByOrigin: NonNullable< - Config["payments"]["preferredPspsByOrigin"] - >, - pspList: ReadonlyArray -) => { - switch (paymentsStartOrigin) { - case "poste_datamatrix_scan": - return filterPspsByPreferredPsps( - pspList, - preferredPspsByOrigin.poste_datamatrix_scan, - POSTE_DATAMATRIX_SCAN_PREFERRED_PSPS - ); - - default: - return pspList; - } -}; - -export const getFilteredPspsList = ( - allPsps: ReadonlyArray, - paymentStartOrigin?: PaymentStartOrigin, - preferredPspsByOrigin?: Config["payments"]["preferredPspsByOrigin"] -) => { - // If necessary, filter the PSPs list by the payment start origin - if (paymentStartOrigin !== undefined && preferredPspsByOrigin !== undefined) { - return filterPspsByPaymentStartOrigin( - paymentStartOrigin, - preferredPspsByOrigin, - allPsps - ); - } - return allPsps; -}; - -/** - * Common action dispatchers for payment screens - */ -export const dispatchUpdatePspForWalletAndConfirm = - (dispatch: Dispatch) => - ( - psp: PspData, - wallet: Wallet, - rptId: RptId, - initialAmount: AmountInEuroCents, - verifica: PaymentRequestsGetResponse, - idPayment: string, - psps: ReadonlyArray, - onFailure: () => void - ) => - dispatch( - paymentUpdateWalletPsp.request({ - psp, - wallet, - idPayment, - onSuccess: ( - action: ActionType<(typeof paymentUpdateWalletPsp)["success"]> - ) => { - if (psp !== undefined) { - dispatch(pspSelectedForPaymentV2(psp)); - } - navigateToPaymentConfirmPaymentMethodScreen({ - rptId, - initialAmount, - verifica, - idPayment, - wallet: { ...wallet, psp: action.payload.updatedWallet.psp }, // the updated wallet - psps - }); - }, - onFailure - }) - ); - -/** - * The purpose of this logic is to select a PSP and a Wallet for the payment. - * - * This logic gets executed once we have the available PSPs and (optionally) a - * user preferred o selected Wallet. - * We get the PSPs after activating the payment (i.e. after we have a paymentId) - * and we have the Wallet either when the user has a favorite one or when he - * selects one or when he adds a new one during the payment. - */ -export const dispatchPickPspOrConfirm = - (dispatch: Dispatch) => - ( - rptId: RptId, - initialAmount: AmountInEuroCents, - verifica: PaymentRequestsGetResponse, - idPayment: string, - maybeSelectedWallet: O.Option, - // NO_PSPS_AVAILABLE: the wallet cannot be used for this payment - // FETCH_PSPS_FAILURE: fetching the PSPs for this wallet has failed - onFailure: (reason: "NO_PSPS_AVAILABLE" | "FETCH_PSPS_FAILURE") => void, - hasWallets: boolean = true - // eslint-disable-next-line sonarjs/cognitive-complexity - ) => { - if (O.isSome(maybeSelectedWallet)) { - const selectedWallet = maybeSelectedWallet.value; - // if the paying method is paypal retrieve psp from new API - // see https://pagopa.atlassian.net/wiki/spaces/IOAPP/pages/445844411/Modifiche+al+flusso+di+pagamento - if (isRawPayPal(selectedWallet.paymentMethod)) { - dispatch( - pspForPaymentV2WithCallbacks({ - idPayment, - idWallet: selectedWallet.idWallet, - onFailure: () => onFailure("FETCH_PSPS_FAILURE"), - onSuccess: pspList => { - if (pspList.length === 0) { - onFailure("NO_PSPS_AVAILABLE"); - return; - } - navigateToPaymentConfirmPaymentMethodScreen({ - rptId, - initialAmount, - verifica, - idPayment, - // there should exists only 1 psp that can handle Paypal transactions - psps: pspList.filter(pd => pd.defaultPsp), - wallet: maybeSelectedWallet.value - }); - } - }) - ); - } else { - // credit card or bpay - // the user has selected a wallet (either because it was the favourite one - // or because he just added a new card he wants to use for the payment), so - // there's no need to ask to select a wallet - we can ask pagopa for the - // PSPs that we can use with this wallet. - dispatch( - pspForPaymentV2WithCallbacks({ - idPayment, - idWallet: selectedWallet.idWallet, - onFailure: () => onFailure("FETCH_PSPS_FAILURE"), - onSuccess: pspList => { - const eligiblePsp = pspList.find(p => p.defaultPsp); - if (pspList.length === 0) { - // this payment method cannot be used! - onFailure("NO_PSPS_AVAILABLE"); - } else if ( - walletHasFavoriteAvailablePspData(selectedWallet, pspList) - ) { - // The user already selected a psp in the past for this wallet, and - // that PSP can be used for this payment, in this case we can - // proceed to the confirmation screen - navigateToPaymentConfirmPaymentMethodScreen({ - rptId, - initialAmount, - verifica, - idPayment, - psps: pspList, - wallet: maybeSelectedWallet.value - }); - } else if (eligiblePsp) { - // there is only one PSP available for this payment, we can go ahead - // and associate it to the current wallet without asking the user to - // select it - dispatchUpdatePspForWalletAndConfirm(dispatch)( - eligiblePsp, - selectedWallet, - rptId, - initialAmount, - verifica, - idPayment, - pspList, - () => - // associating the only available psp to the wallet has failed, go - // to the psp selection screen anyway - - navigateToPaymentPickPspScreen({ - rptId, - initialAmount, - verifica, - wallet: selectedWallet, - psps: pspList, - idPayment - }) - ); - } else { - // we have more than one PSP and we cannot select one automatically, - // ask the user to select one PSP - - navigateToPaymentPickPspScreen({ - rptId, - initialAmount, - verifica, - wallet: selectedWallet, - psps: pspList, - idPayment - }); - } - } - }) - ); - } - } else { - if (hasWallets) { - // the user didn't select yet a wallet, ask the user to select one - - navigateToPaymentPickPaymentMethodScreen({ - rptId, - initialAmount, - verifica, - idPayment - }); - } else { - // the user never add a wallet, ask the user to add a new one - - navigateToWalletAddPaymentMethod({ - inPayment: O.some({ - rptId, - initialAmount, - verifica, - idPayment - }) - }); - } - } - }; diff --git a/ts/screens/wallet/payment/components/TransactionSummary.tsx b/ts/screens/wallet/payment/components/TransactionSummary.tsx deleted file mode 100644 index 144e89da71f..00000000000 --- a/ts/screens/wallet/payment/components/TransactionSummary.tsx +++ /dev/null @@ -1,227 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import React, { ComponentProps } from "react"; -import { StyleSheet, View } from "react-native"; -import Placeholder from "rn-placeholder"; -import { - Divider, - IOColors, - IOIcons, - ListItemInfo, - ListItemInfoCopy -} from "@pagopa/io-app-design-system"; -import I18n from "../../../../i18n"; -import { PaymentState } from "../../../../store/reducers/wallet/payment"; -import customVariables from "../../../../theme/variables"; -import { clipboardSetStringWithFeedback } from "../../../../utils/clipboard"; -import { cleanTransactionDescription } from "../../../../utils/payment"; -import { - centsToAmount, - formatNumberAmount -} from "../../../../utils/stringBuilder"; -import { usePaymentAmountInfoBottomSheet } from "../hooks/usePaymentAmountInfoBottomSheet"; -import { getRecepientName } from "../../../../utils/strings"; -import { format } from "../../../../utils/dates"; -import { getAccessibleAmountText } from "../../../../utils/accessibility"; -import { formatPaymentNoticeNumber } from "../../../../features/payments/common/utils"; - -const styles = StyleSheet.create({ - spacer: { - height: customVariables.spacerExtrasmallHeight - }, - placeholder: { - paddingTop: 9 - } -}); - -const LoadingPlaceholder = (props: { size: "full" | "half" }) => ( - -); - -type EndElementProps = ComponentProps["endElement"]; -type RowProps = Readonly<{ - title: string; - value?: string; - icon?: IOIcons; - isLoading?: boolean; - hideSeparator?: boolean; - placeholder?: React.ReactElement; - endElement?: EndElementProps; - onPress?: () => void; -}>; - -export const TransactionSummaryRow = ({ - title, - value, - icon, - isLoading, - placeholder, - endElement, - hideSeparator -}: React.PropsWithChildren): React.ReactElement | null => { - if (!value && !isLoading) { - return null; - } - - const accessibilityLabel = value - ? `${title}, ${getAccessibleAmountText(value)}` - : title; - - return ( - - {placeholder}
- ) - } - endElement={endElement} - /> - {!hideSeparator && } - - ); -}; - -type Props = Readonly<{ - paymentNoticeNumber: string; - organizationFiscalCode: string; - paymentVerification: PaymentState["verifica"]; - isPaid: boolean; -}>; - -export const TransactionSummary = (props: Props): React.ReactElement => { - const isLoading = pot.isLoading(props.paymentVerification); - - const recipient = pipe( - pot.toOption(props.paymentVerification), - O.chainNullableK(_ => _.enteBeneficiario), - O.map(getRecepientName), - O.toUndefined - ); - - const description = pot.toUndefined( - pot.mapNullable(props.paymentVerification, _ => - cleanTransactionDescription(_.causaleVersamento) - ) - ); - - const amount = pot.toUndefined( - pot.map(props.paymentVerification, _ => - formatNumberAmount( - centsToAmount(_.importoSingoloVersamento), - true, - "right" - ) - ) - ); - - const dueDate = pipe( - props.paymentVerification, - pot.toOption, - O.chainNullableK(_ => _.dueDate), - O.map(_ => format(_, "DD/MM/YYYY")), - O.toUndefined - ); - - const formattedPaymentNoticeNumber = formatPaymentNoticeNumber( - props.paymentNoticeNumber - ); - - const { presentPaymentInfoBottomSheet, paymentInfoBottomSheet } = - usePaymentAmountInfoBottomSheet(); - - const amountEndElement: EndElementProps = React.useMemo(() => { - if (props.isPaid && !isLoading) { - return { - type: "badge", - componentProps: { - text: I18n.t("messages.badge.paid"), - variant: "success" - } - }; - } else if (!props.isPaid && !isLoading) { - return { - type: "iconButton", - componentProps: { - icon: "info", - accessibilityLabel: "info", - onPress: presentPaymentInfoBottomSheet - } - }; - } - return undefined; - }, [props.isPaid, isLoading, presentPaymentInfoBottomSheet]); - - return ( - <> - } - /> - - - - - - } - isLoading={isLoading} - /> - } - isLoading={isLoading} - endElement={amountEndElement} - /> - } - isLoading={isLoading} - /> - - - clipboardSetStringWithFeedback(props.paymentNoticeNumber) - } - /> - - - clipboardSetStringWithFeedback(props.organizationFiscalCode) - } - /> - {paymentInfoBottomSheet} - - ); -}; diff --git a/ts/screens/wallet/payment/components/TransactionSummaryErrorDetails.tsx b/ts/screens/wallet/payment/components/TransactionSummaryErrorDetails.tsx deleted file mode 100644 index 438c4169b9e..00000000000 --- a/ts/screens/wallet/payment/components/TransactionSummaryErrorDetails.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { ButtonOutline, Divider } from "@pagopa/io-app-design-system"; -import { View } from "react-native"; -import { Detail_v2Enum } from "../../../../../definitions/backend/PaymentProblemJson"; -import { RawAccordion } from "../../../../components/core/accordion/RawAccordion"; -import { H4 } from "../../../../components/core/typography/H4"; -import I18n from "../../../../i18n"; -import customVariables from "../../../../theme/variables"; -import { clipboardSetStringWithFeedback } from "../../../../utils/clipboard"; -import { isDuplicatedPayment } from "../../../../utils/payment"; -import { TransactionSummaryError } from "../TransactionSummaryScreen"; -import { TransactionSummaryRow } from "./TransactionSummary"; - -type Props = Readonly<{ - error: TransactionSummaryError; - paymentNoticeNumber: string; - organizationFiscalCode: string; - messageId: string | undefined; -}>; - -export const TransactionSummaryErrorDetails = ({ - error, - paymentNoticeNumber, - organizationFiscalCode, - messageId -}: React.PropsWithChildren): React.ReactElement | null => { - const errorOrUndefined = O.toUndefined(error); - if ( - errorOrUndefined === undefined || - isDuplicatedPayment(error) || - !Object.keys(Detail_v2Enum).includes(errorOrUndefined) - ) { - return null; - } - - const messageData: ReadonlyArray<{ key: string; value?: string }> = [ - { - key: I18n.t("payment.noticeCode"), - value: paymentNoticeNumber - }, - { - key: I18n.t("wallet.firstTransactionSummary.entityCode"), - value: organizationFiscalCode - } - ]; - const detailsData: ReadonlyArray<{ key: string; value?: string }> = [ - { - key: I18n.t("wallet.firstTransactionSummary.errorDetails.errorCode"), - value: errorOrUndefined - }, - { - key: I18n.t("wallet.firstTransactionSummary.errorDetails.messageId"), - value: messageId - } - ]; - - const clipboardString = [...messageData, ...detailsData] - .reduce( - (acc: ReadonlyArray, curr: { key: string; value?: string }) => { - if (curr.value) { - return [...acc, `${curr.key}: ${curr.value}`]; - } - return acc; - }, - [] - ) - .join(", "); - return ( - - {I18n.t("wallet.firstTransactionSummary.errorDetails.title")} - } - headerStyle={{ - paddingTop: customVariables.spacerLargeHeight, - paddingBottom: customVariables.spacerSmallHeight - }} - accessibilityLabel={I18n.t( - "wallet.firstTransactionSummary.errorDetails.title" - )} - > - - - {detailsData.map((row, index, array) => - row.value !== undefined ? ( - <> - - {index !== array.length - 1 && } - - ) : undefined - )} - - clipboardSetStringWithFeedback(clipboardString)} - fullWidth - /> - - - - - ); -}; diff --git a/ts/screens/wallet/payment/components/TransactionSummaryStatus.tsx b/ts/screens/wallet/payment/components/TransactionSummaryStatus.tsx deleted file mode 100644 index fe29297017d..00000000000 --- a/ts/screens/wallet/payment/components/TransactionSummaryStatus.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import * as O from "fp-ts/lib/Option"; -import * as React from "react"; -import { View } from "react-native"; -import { IOIcons } from "@pagopa/io-app-design-system"; -import { LabelSmall } from "../../../../components/core/typography/LabelSmall"; -import StatusContent from "../../../../components/SectionStatus/StatusContent"; -import I18n from "../../../../i18n"; -import { getV2ErrorMainType } from "../../../../utils/payment"; -import { TransactionSummaryError } from "../TransactionSummaryScreen"; - -type StatusContentProps = { - viewRef: React.RefObject; - backgroundColor: "orange" | "aqua"; - iconName: IOIcons; - foregroundColor: "white" | "bluegreyDark"; - line1: string; - line2?: string; -}; - -export const renderStatusContent = (props: StatusContentProps) => ( - - - {props.line1} - - {props.line2 && ( - - {"\n"} - {props.line2} - - )} - -); - -export const TransactionSummaryStatus = (props: { - error: TransactionSummaryError; -}) => { - const viewRef = React.createRef(); - - // eslint-disable-next-line functional/no-let - let statusContentProps: StatusContentProps | undefined; - - const errorOrUndefined = O.toUndefined(props.error); - if ( - errorOrUndefined === "PAYMENT_ID_TIMEOUT" || - errorOrUndefined === undefined - ) { - return null; - } - - const errorType = getV2ErrorMainType(errorOrUndefined); - switch (errorType) { - case "EC": - statusContentProps = { - viewRef, - backgroundColor: "orange", - foregroundColor: "white", - iconName: "notice", - line1: I18n.t("wallet.errors.TECHNICAL"), - line2: I18n.t("wallet.errors.contactECsubtitle") - }; - break; - case "REVOKED": - statusContentProps = { - viewRef, - backgroundColor: "orange", - foregroundColor: "white", - iconName: "notice", - line1: I18n.t("wallet.errors.REVOKED"), - line2: I18n.t("wallet.errors.contactECsubtitle") - }; - break; - case "EXPIRED": - statusContentProps = { - viewRef, - backgroundColor: "orange", - foregroundColor: "white", - iconName: "notice", - line1: I18n.t("wallet.errors.EXPIRED"), - line2: I18n.t("wallet.errors.contactECsubtitle") - }; - break; - case "ONGOING": - statusContentProps = { - viewRef, - backgroundColor: "orange", - foregroundColor: "white", - iconName: "notice", - line1: I18n.t("wallet.errors.ONGOING"), - line2: I18n.t("wallet.errors.ONGOING_SUBTITLE") - }; - break; - case "DUPLICATED": - statusContentProps = { - viewRef, - backgroundColor: "aqua", - iconName: "ok", - foregroundColor: "bluegreyDark", - line1: I18n.t("wallet.errors.DUPLICATED") - }; - break; - default: - statusContentProps = { - viewRef, - backgroundColor: "orange", - iconName: "notice", - foregroundColor: "white", - line1: I18n.t("wallet.errors.TECHNICAL"), - line2: I18n.t("wallet.errors.GENERIC_ERROR_SUBTITLE") - }; - break; - } - - return renderStatusContent(statusContentProps); -}; diff --git a/ts/screens/wallet/payment/hooks/usePaymentAmountInfoBottomSheet.tsx b/ts/screens/wallet/payment/hooks/usePaymentAmountInfoBottomSheet.tsx deleted file mode 100644 index dd804dd7218..00000000000 --- a/ts/screens/wallet/payment/hooks/usePaymentAmountInfoBottomSheet.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from "react"; -import { View } from "react-native"; -import { FooterWithButtons } from "@pagopa/io-app-design-system"; -import { Body } from "../../../../components/core/typography/Body"; -import I18n from "../../../../i18n"; -import { useIOBottomSheetAutoresizableModal } from "../../../../utils/hooks/bottomSheet"; - -export const usePaymentAmountInfoBottomSheet = () => { - const { present, bottomSheet, dismiss } = useIOBottomSheetAutoresizableModal( - { - title: I18n.t("wallet.firstTransactionSummary.amountInfo.title"), - component: ( - - {I18n.t("wallet.firstTransactionSummary.amountInfo.message")} - - ), - footer: ( - - dismiss() - } - }} - /> - - ) - }, - 200 - ); - - return { - presentPaymentInfoBottomSheet: present, - paymentInfoBottomSheet: bottomSheet - }; -}; diff --git a/ts/screens/wallet/payment/hooks/useStartOrResumePayment.ts b/ts/screens/wallet/payment/hooks/useStartOrResumePayment.ts deleted file mode 100644 index 6f67538d51f..00000000000 --- a/ts/screens/wallet/payment/hooks/useStartOrResumePayment.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { AmountInEuroCents, RptId } from "@pagopa/io-pagopa-commons/lib/pagopa"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import React from "react"; -import { PaymentRequestsGetResponse } from "../../../../../definitions/backend/PaymentRequestsGetResponse"; -import { isUndefined } from "../../../../common/model/RemoteValue"; -import { useIONavigation } from "../../../../navigation/params/AppParamsList"; -import ROUTES from "../../../../navigation/routes"; -import { - paymentAttiva, - paymentCheck, - paymentIdPolling -} from "../../../../store/actions/wallet/payment"; -import { useIODispatch, useIOSelector } from "../../../../store/hooks"; -import { withPaymentFeatureSelector } from "../../../../store/reducers/wallet/wallets"; -import { Wallet } from "../../../../types/pagopa"; -import { alertNoPayablePaymentMethods } from "../../../../utils/paymentMethod"; -import { dispatchPickPspOrConfirm } from "../common"; - -const useStartOrResumePayment = ( - rptId: RptId, - paymentVerification: O.Option, - initialAmount: AmountInEuroCents, - maybeSelectedWallet: O.Option -) => { - const navigation = useIONavigation(); - const dispatch = useIODispatch(); - - const hasPayableMethods = - useIOSelector(withPaymentFeatureSelector).length > 0; - - const paymentAttivaPot = useIOSelector(_ => _.wallet.payment.attiva); - const paymentIdPot = useIOSelector(_ => _.wallet.payment.paymentId); - const paymentCheckPot = useIOSelector(_ => _.wallet.payment.check); - const pspsRemoteValue = useIOSelector(_ => _.wallet.payment.pspsV2.psps); - - React.useEffect(() => { - pipe( - paymentVerification, - O.map(verifica => { - if (pot.isNone(paymentAttivaPot)) { - // If payment activation has not yet been requested, skip - return; - } - - if (pot.isNone(paymentIdPot) && !pot.isLoading(paymentIdPot)) { - // Poll for payment ID - dispatch(paymentIdPolling.request(verifica)); - } - - if (pot.isSome(paymentIdPot)) { - const idPayment = paymentIdPot.value; - - // "check" the payment - if (pot.isNone(paymentCheckPot) && !pot.isLoading(paymentCheckPot)) { - dispatch(paymentCheck.request(idPayment)); - } - - if (pot.isSome(paymentCheckPot) && isUndefined(pspsRemoteValue)) { - // Navigate to method or PSP selection screen - dispatchPickPspOrConfirm(dispatch)( - rptId, - initialAmount, - verifica, - idPayment, - maybeSelectedWallet, - () => { - // either we cannot use the default payment method for this - // payment, or fetching the PSPs for this payment and the - // default wallet has failed, ask the user to pick a wallet - navigation.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.PAYMENT_PICK_PAYMENT_METHOD, - params: { - rptId, - initialAmount, - verifica, - idPayment - } - }); - }, - hasPayableMethods - ); - } - } - }) - ); - }, [ - dispatch, - navigation, - paymentVerification, - paymentAttivaPot, - paymentIdPot, - paymentCheckPot, - hasPayableMethods, - pspsRemoteValue, - rptId, - initialAmount, - maybeSelectedWallet - ]); - - return React.useCallback(() => { - if (!hasPayableMethods) { - alertNoPayablePaymentMethods(() => - navigation.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.WALLET_ADD_PAYMENT_METHOD, - params: { - inPayment: O.none, - showOnlyPayablePaymentMethods: true - } - }) - ); - - return; - } - - pipe( - paymentVerification, - O.map(verifica => - dispatch( - paymentAttiva.request({ - rptId, - verifica - }) - ) - ) - ); - }, [dispatch, navigation, hasPayableMethods, rptId, paymentVerification]); -}; - -export { useStartOrResumePayment }; diff --git a/ts/store/actions/legacyWallet.ts b/ts/store/actions/legacyWallet.ts new file mode 100644 index 00000000000..c2f827d5f7b --- /dev/null +++ b/ts/store/actions/legacyWallet.ts @@ -0,0 +1,28 @@ +import { ActionType, createAsyncAction } from "typesafe-actions"; +import { RptId } from "@pagopa/io-pagopa-commons/lib/pagopa"; +import { PaymentRequestsGetResponse } from "../../../definitions/backend/PaymentRequestsGetResponse"; +import { Detail_v2Enum as PaymentProblemErrorEnum } from "../../../definitions/backend/PaymentProblemJson"; + +export type PaymentStartOrigin = + | "message" + | "qrcode_scan" + | "poste_datamatrix_scan" + | "manual_insertion" + | "donation"; +// +// verifica +// + +// the error is undefined in case we weren't able to decode it, it should be +// interpreted as a generic error +export const paymentVerifica = createAsyncAction( + "PAYMENT_VERIFICA_REQUEST", + "PAYMENT_VERIFICA_SUCCESS", + "PAYMENT_VERIFICA_FAILURE" +)< + { rptId: RptId; startOrigin: PaymentStartOrigin }, + PaymentRequestsGetResponse, + keyof typeof PaymentProblemErrorEnum | undefined +>(); + +export type PaymentActions = ActionType; diff --git a/ts/store/actions/navigation.ts b/ts/store/actions/navigation.ts index 66735790f57..465344aa00d 100644 --- a/ts/store/actions/navigation.ts +++ b/ts/store/actions/navigation.ts @@ -1,29 +1,8 @@ import { CommonActions } from "@react-navigation/native"; -import { CreditCardDetailScreenNavigationParams } from "../../features/wallet/creditCard/screen/CreditCardDetailScreen"; import NavigationService from "../../navigation/NavigationService"; import ROUTES from "../../navigation/routes"; import { CieCardReaderScreenNavigationParams } from "../../screens/authentication/cie/CieCardReaderScreen"; import { OnboardingServicesPreferenceScreenNavigationParams } from "../../screens/onboarding/OnboardingServicesPreferenceScreen"; -import { AddCardScreenNavigationParams } from "../../screens/wallet/AddCardScreen"; -import { AddCreditCardOutcomeCodeMessageNavigationParams } from "../../screens/wallet/AddCreditCardOutcomeCodeMessage"; -import { AddPaymentMethodScreenNavigationParams } from "../../screens/wallet/AddPaymentMethodScreen"; -import { ConfirmCardDetailsScreenNavigationParams } from "../../screens/wallet/ConfirmCardDetailsScreen"; -import { CreditCardOnboardingAttemptDetailScreenNavigationParams } from "../../screens/wallet/creditCardOnboardingAttempts/CreditCardOnboardingAttemptDetailScreen"; -import { ConfirmPaymentMethodScreenNavigationParams } from "../../screens/wallet/payment/ConfirmPaymentMethodScreen"; -import { ManualDataInsertionScreenNavigationParams } from "../../screens/wallet/payment/ManualDataInsertionScreen"; -import { PaymentOutcomeCodeMessageNavigationParams } from "../../screens/wallet/payment/PaymentOutcomeCodeMessage"; -import { PickPaymentMethodScreenNavigationParams } from "../../screens/wallet/payment/PickPaymentMethodScreen"; -import { PickPspScreenNavigationParams } from "../../screens/wallet/payment/PickPspScreen"; -import { TransactionErrorScreenNavigationParams } from "../../screens/wallet/payment/TransactionErrorScreen"; -import { TransactionSummaryScreenNavigationParams } from "../../screens/wallet/payment/TransactionSummaryScreen"; -import { PaymentHistoryDetailsScreenNavigationParams } from "../../screens/wallet/PaymentHistoryDetailsScreen"; -import { TransactionDetailsScreenNavigationParams } from "../../screens/wallet/TransactionDetailsScreen"; -import { WalletHomeNavigationParams } from "../../screens/wallet/WalletHomeScreen"; -import { - BancomatPaymentMethod, - BPayPaymentMethod, - CreditCardPaymentMethod -} from "../../types/pagopa"; import { SERVICES_ROUTES } from "../../features/services/common/navigation/routes"; import { ServiceDetailsScreenRouteParams } from "../../features/services/details/screens/ServiceDetailsScreen"; @@ -216,258 +195,6 @@ export const navigateToPrivacyShareData = () => }) ); -/** - * Wallet & Payments - */ - -/** - * @deprecated - */ -export const navigateToPaymentTransactionSummaryScreen = ( - params: TransactionSummaryScreenNavigationParams -) => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.PAYMENT_TRANSACTION_SUMMARY, - params - }) - ); - -/** - * @deprecated - */ -export const navigateToPaymentTransactionErrorScreen = ( - params: TransactionErrorScreenNavigationParams -) => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.PAYMENT_TRANSACTION_ERROR, - params - }) - ); - -/** - * @deprecated - */ -export const navigateToPaymentPickPaymentMethodScreen = ( - params: PickPaymentMethodScreenNavigationParams -) => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.PAYMENT_PICK_PAYMENT_METHOD, - params - }) - ); - -/** - * @deprecated - */ -export const navigateToTransactionDetailsScreen = ( - params: TransactionDetailsScreenNavigationParams -) => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.WALLET_TRANSACTION_DETAILS, - params - }) - ); - -/** - * @deprecated - */ -export const navigateToCreditCardDetailScreen = ( - params: CreditCardDetailScreenNavigationParams -) => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.WALLET_CREDIT_CARD_DETAIL, - params - }) - ); - -/** - * @deprecated - */ -export const navigateToBancomatDetailScreen = ( - bancomat: BancomatPaymentMethod -) => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.WALLET_BANCOMAT_DETAIL, - params: { bancomat } - }) - ); - -export const navigateToPayPalDetailScreen = () => - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.WALLET_PAYPAL_DETAIL - }); - -/** - * @deprecated - */ -export const navigateToBPayDetailScreen = (bPay: BPayPaymentMethod) => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.WALLET_BPAY_DETAIL, - params: { bPay } - }) - ); - -/** - * @deprecated - */ -export const navigateToCobadgeDetailScreen = ( - cobadge: CreditCardPaymentMethod -) => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.WALLET_COBADGE_DETAIL, - params: { cobadge } - }) - ); - -/** - * @deprecated - */ -export const navigateToPaymentPickPspScreen = ( - params: PickPspScreenNavigationParams -) => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.PAYMENT_PICK_PSP, - params - }) - ); - -/** - * @deprecated - */ -export const navigateToPaymentConfirmPaymentMethodScreen = ( - params: ConfirmPaymentMethodScreenNavigationParams -) => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.PAYMENT_CONFIRM_PAYMENT_METHOD, - params - }) - ); - -/** - * @deprecated - */ -export const navigateToPaymentHistoryDetail = ( - params: PaymentHistoryDetailsScreenNavigationParams -) => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.PAYMENT_HISTORY_DETAIL_INFO, - params - }) - ); - -/** - * @deprecated - */ -export const navigateToCreditCardOnboardingAttempt = ( - params: CreditCardOnboardingAttemptDetailScreenNavigationParams -) => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.CREDIT_CARD_ONBOARDING_ATTEMPT_DETAIL, - params - }) - ); - -/** - * @deprecated - */ -export const navigateToWalletHome = (params?: WalletHomeNavigationParams) => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.MAIN, { - screen: ROUTES.WALLET_HOME, - params - }) - ); - -/** - * @deprecated - */ -export const navigateToWalletAddPaymentMethod = ( - params: AddPaymentMethodScreenNavigationParams -) => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.WALLET_ADD_PAYMENT_METHOD, - params - }) - ); - -/** - * @deprecated - */ -export const navigateToWalletAddCreditCard = ( - params: AddCardScreenNavigationParams -) => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.WALLET_ADD_CARD, - params - }) - ); - -/** - * @deprecated - */ -export const navigateToWalletConfirmCardDetails = ( - params: ConfirmCardDetailsScreenNavigationParams -) => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.WALLET_CONFIRM_CARD_DETAILS, - params - }) - ); - -/** - * @deprecated - */ -export const navigateToPaymentManualDataInsertion = ( - params?: ManualDataInsertionScreenNavigationParams -) => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.PAYMENT_MANUAL_DATA_INSERTION, - params - }) - ); - -/** - * @deprecated - */ -export const navigateToAddCreditCardOutcomeCode = ( - params: AddCreditCardOutcomeCodeMessageNavigationParams -) => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.ADD_CREDIT_CARD_OUTCOMECODE_MESSAGE, - params - }) - ); - -/** - * @deprecated - */ -export const navigateToPaymentOutcomeCode = ( - params: PaymentOutcomeCodeMessageNavigationParams -) => - NavigationService.dispatchNavigationAction( - CommonActions.navigate(ROUTES.WALLET_NAVIGATOR, { - screen: ROUTES.PAYMENT_OUTCOMECODE_MESSAGE, - params - }) - ); - /** * CIE */ diff --git a/ts/store/actions/payments.ts b/ts/store/actions/payments.ts deleted file mode 100644 index 0c149b7a5e1..00000000000 --- a/ts/store/actions/payments.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ActionType, createStandardAction } from "typesafe-actions"; -import { PaymentsLastDeletedInfo } from "../reducers/payments/lastDeleted"; - -export const paymentsLastDeletedSet = createStandardAction( - "PAYMENTS_LAST_DELETED_SET" -)(); - -export type PaymentsActions = ActionType; diff --git a/ts/store/actions/types.ts b/ts/store/actions/types.ts index 7af0bd88946..03caad1cf17 100644 --- a/ts/store/actions/types.ts +++ b/ts/store/actions/types.ts @@ -18,10 +18,6 @@ import { MessagesActions } from "../../features/messages/store/actions"; import { WalletActions as NewWalletActions } from "../../features/newWallet/store/actions"; import { PaymentsActions as PaymentsFeatureActions } from "../../features/payments/common/store/actions"; import { PnActions } from "../../features/pn/store/actions"; -import { AbiActions } from "../../features/wallet/onboarding/bancomat/store/actions"; -import { BPayActions } from "../../features/wallet/onboarding/bancomatPay/store/actions"; -import { CoBadgeActions } from "../../features/wallet/onboarding/cobadge/store/actions"; -import { PayPalOnboardingActions } from "../../features/wallet/onboarding/paypal/store/actions"; import { ServicesActions } from "../../features/services/common/store/actions"; import { WhatsNewActions } from "../../features/whatsnew/store/actions"; import { ZendeskSupportActions } from "../../features/zendesk/store/actions"; @@ -52,7 +48,6 @@ import { InstallationActions } from "./installation"; import { MixpanelActions } from "./mixpanel"; import { OnboardingActions } from "./onboarding"; import { OrganizationsActions } from "./organizations"; -import { PaymentsActions } from "./payments"; import { PersistedPreferencesActions } from "./persistedPreferences"; import { PinSetActions } from "./pinset"; import { PreferencesActions } from "./preferences"; @@ -61,8 +56,6 @@ import { ProfileEmailValidationAction } from "./profileEmailValidationChange"; import { SearchActions } from "./search"; import { StartupActions } from "./startup"; import { UserDataProcessingActions } from "./userDataProcessing"; -import { WalletActions } from "./wallet"; -import { OutcomeCodeActions } from "./wallet/outcomeCode"; export type Action = | AnalyticsActions @@ -84,25 +77,18 @@ export type Action = | PersistedPreferencesActions | ProfileActions | ServicesActions - | WalletActions | ContentActions | IdentificationActions | InstallationActions | DebugActions | CalendarEventsActions | SearchActions - | PaymentsActions | OrganizationsActions | UserDataProcessingActions | ProfileEmailValidationAction | BonusActions - | AbiActions - | BPayActions - | CoBadgeActions - | PayPalOnboardingActions | CrossSessionsActions | EuCovidCertActions - | OutcomeCodeActions | ZendeskSupportActions | PnActions | StartupActions diff --git a/ts/store/actions/wallet/delete.ts b/ts/store/actions/wallet/delete.ts deleted file mode 100644 index 487e04b2618..00000000000 --- a/ts/store/actions/wallet/delete.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ActionType, createAsyncAction } from "typesafe-actions"; -import { EnableableFunctionsEnum } from "../../../../definitions/pagopa/EnableableFunctions"; -import { Wallet } from "../../../types/pagopa"; - -export type DeleteAllByFunctionSuccess = { - wallets: ReadonlyArray; - deletedMethodsCount: number; -}; - -export type DeleteAllByFunctionError = { - error: Error; - notDeletedMethodsCount?: number; -}; -/** - * used to delete all those payment methods that have a specified function enabled - */ -export const deleteAllPaymentMethodsByFunction = createAsyncAction( - "WALLETS_DELETE_ALL_BY_FUNCTION_REQUEST", - "WALLETS_DELETE_ALL_BY_FUNCTION_SUCCESS", - "WALLETS_DELETE_ALL_BY_FUNCTION_FAILURE" -)< - EnableableFunctionsEnum, - DeleteAllByFunctionSuccess, - DeleteAllByFunctionError ->(); - -export type DeleteActions = ActionType< - typeof deleteAllPaymentMethodsByFunction ->; diff --git a/ts/store/actions/wallet/index.ts b/ts/store/actions/wallet/index.ts deleted file mode 100644 index 1bb5f313a5a..00000000000 --- a/ts/store/actions/wallet/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { PaymentActions } from "./payment"; -import { TransactionsActions } from "./transactions"; -import { WalletsActions } from "./wallets"; -import { DeleteActions } from "./delete"; - -export type WalletActions = - | WalletsActions - | TransactionsActions - | PaymentActions - | DeleteActions; diff --git a/ts/store/actions/wallet/outcomeCode.ts b/ts/store/actions/wallet/outcomeCode.ts deleted file mode 100644 index 000f29ce6d8..00000000000 --- a/ts/store/actions/wallet/outcomeCode.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Action types and action creator related to OutcomeCodeState. - */ -import * as O from "fp-ts/lib/Option"; -import { ActionType, createStandardAction } from "typesafe-actions"; -import { PaymentMethodType } from "./payment"; - -// This action is supposed to be used to update the state with the outcome -// code when the add credit card workflow is finished -export const addCreditCardOutcomeCode = createStandardAction( - "CREDIT_CARD_PAYMENT_OUTCOME_CODE" -)>(); - -// This action is supposed to be used to update the state with the outcome -// code when the payment workflow is finished. It brings also the payment method type used to do the payment -export const paymentOutcomeCode = createStandardAction("PAYMENT_OUTCOME_CODE")<{ - outcome: O.Option; - paymentMethodType: PaymentMethodType; -}>(); - -// Bring the state to the initial value -export const resetLastPaymentOutcomeCode = createStandardAction( - "RESET_LAST_PAYMENT_OUTCOME_CODE" -)(); - -export type OutcomeCodeActions = - | ActionType - | ActionType - | ActionType; diff --git a/ts/store/actions/wallet/payment.ts b/ts/store/actions/wallet/payment.ts deleted file mode 100644 index e8aecf8ddfd..00000000000 --- a/ts/store/actions/wallet/payment.ts +++ /dev/null @@ -1,305 +0,0 @@ -import { - OmitStatusFromResponse, - TypeofApiResponse -} from "@pagopa/ts-commons/lib/requests"; -import { - ActionType, - createAsyncAction, - createStandardAction -} from "typesafe-actions"; -import { RptId } from "@pagopa/io-pagopa-commons/lib/pagopa"; - -import { PaymentActivationsPostResponse } from "../../../../definitions/backend/PaymentActivationsPostResponse"; -import { Detail_v2Enum as PaymentProblemErrorEnum } from "../../../../definitions/backend/PaymentProblemJson"; -import { PaymentRequestsGetResponse } from "../../../../definitions/backend/PaymentRequestsGetResponse"; -import { CheckPaymentUsingGETT } from "../../../../definitions/pagopa/requestTypes"; -import { - PaymentManagerToken, - RawPaymentMethod, - Transaction, - Wallet -} from "../../../types/pagopa"; -import { PayloadForAction } from "../../../types/utils"; -import { - EntrypointRoute, - PaymentStartPayload -} from "../../reducers/wallet/payment"; -import { OutcomeCodesKey } from "../../../types/outcomeCode"; -import { NetworkError } from "../../../utils/errors"; -import { PspData } from "../../../../definitions/pagopa/PspData"; -import { fetchWalletsFailure, fetchWalletsSuccess } from "./wallets"; - -/** - * IMPORTANT! - * - * The payment flow is quite complex and involves more than two actors. - * Please refer to https://docs.google.com/presentation/d/11rEttb7lJYlRqgFpl4QopyjFmjt2Q0K8uis6JhAQaCw/edit#slide=id.p - * and make sure you understand it _before_ working on it. - */ - -export type PaymentStartOrigin = - | "message" - | "qrcode_scan" - | "poste_datamatrix_scan" - | "manual_insertion" - | "donation"; - -/** - * Resets the payment state before starting a new payment - */ -export const paymentInitializeState = createStandardAction( - "PAYMENT_INITIALIZE_STATE" -)(); - -/** - * Track the route whence the payment started - */ -export const paymentInitializeEntrypointRoute = createStandardAction( - "PAYMENT_ENTRYPOINT_ROUTE" -)(); - -/** - * For back to entrypoint (where the payment is initiated) - */ -export const backToEntrypointPayment = createStandardAction( - "BACK_TO_PAYMENT_ENTRYPOINT_ROUTE" -)(); - -// -// verifica -// - -// the error is undefined in case we weren't able to decode it, it should be -// interpreted as a generic error -export const paymentVerifica = createAsyncAction( - "PAYMENT_VERIFICA_REQUEST", - "PAYMENT_VERIFICA_SUCCESS", - "PAYMENT_VERIFICA_FAILURE" -)< - { rptId: RptId; startOrigin: PaymentStartOrigin }, - PaymentRequestsGetResponse, - keyof typeof PaymentProblemErrorEnum | undefined ->(); - -// -// attiva -// - -type paymentAttivaRequestPayload = Readonly<{ - rptId: RptId; - verifica: PaymentRequestsGetResponse; -}>; - -// the error is undefined in case we weren't able to decode it, it should be -// interpreted as a generic error -export const paymentAttiva = createAsyncAction( - "PAYMENT_ATTIVA_REQUEST", - "PAYMENT_ATTIVA_SUCCESS", - "PAYMENT_ATTIVA_FAILURE" -)< - paymentAttivaRequestPayload, - PaymentActivationsPostResponse, - PaymentProblemErrorEnum | undefined ->(); - -// -// paymentId polling -// - -// the error is undefined in case we weren't able to decode it, it should be -// interpreted as a generic error -export const paymentIdPolling = createAsyncAction( - "PAYMENT_ID_POLLING_REQUEST", - "PAYMENT_ID_POLLING_SUCCESS", - "PAYMENT_ID_POLLING_FAILURE" -)(); - -// -// check payment -// - -export const paymentCheck = createAsyncAction( - "PAYMENT_CHECK_REQUEST", - "PAYMENT_CHECK_SUCCESS", - "PAYMENT_CHECK_FAILURE" -)< - string, - true, - | OmitStatusFromResponse, 200> - | undefined - | Error ->(); - -// -// Update Wallet PSP request and responses -// - -type WalletUpdatePspRequestPayload = Readonly<{ - psp: PspData; - wallet: Wallet; - idPayment: string; - onSuccess?: ( - action: ActionType<(typeof paymentUpdateWalletPsp)["success"]> - ) => void; - onFailure?: ( - action: ActionType<(typeof paymentUpdateWalletPsp)["failure"]> - ) => void; -}>; - -export const paymentUpdateWalletPsp = createAsyncAction( - "PAYMENT_UPDATE_WALLET_PSP_REQUEST", - "PAYMENT_UPDATE_WALLET_PSP_SUCCESS", - "PAYMENT_UPDATE_WALLET_PSP_FAILURE" -)< - WalletUpdatePspRequestPayload, - { - wallets: PayloadForAction; - updatedWallet: Wallet; - }, - PayloadForAction ->(); - -// -// execute payment -// - -/** - * user wants to pay - * - request: we already know the idPayment and the idWallet used to pay, we need a fresh PM session token - * - success: we got a fresh PM session token - * - failure: we can't get a fresh PM session token - */ -export const paymentExecuteStart = createAsyncAction( - "PAYMENT_EXECUTE_START_REQUEST", - "PAYMENT_EXECUTE_START_SUCCESS", - "PAYMENT_EXECUTE_START_FAILURE" -)(); - -export type PaymentWebViewEndReason = "USER_ABORT" | "EXIT_PATH"; -export type PaymentMethodType = - | Extract - | "Unknown"; -// event fired when the paywebview ends its challenge (used to reset payment values) -export const paymentWebViewEnd = createStandardAction("PAYMENT_WEB_VIEW_END")<{ - reason: PaymentWebViewEndReason; - paymentMethodType: PaymentMethodType; -}>(); - -// used to accumulate all the urls browsed into the pay webview -export const paymentRedirectionUrls = createStandardAction( - "PAYMENT_NAVIGATION_URLS" -)>(); - -// -// Signal the completion of a payment -// - -type PaymentCompletedSuccessPayload = Readonly< - | { - kind: "COMPLETED"; - // TODO Transaction is not available, add it when PM makes it available again - // see https://www.pivotaltracker.com/story/show/177067134 - transaction: Transaction | undefined; - rptId: RptId; - } - | { - kind: "DUPLICATED"; - rptId: RptId; - } ->; - -export const paymentCompletedSuccess = createStandardAction( - "PAYMENT_COMPLETED_SUCCESS" -)(); - -export const paymentCompletedFailure = createStandardAction( - "PAYMENT_COMPLETED_FAILURE" -)<{ outcomeCode: OutcomeCodesKey | undefined; paymentId: string }>(); - -// -// delete an ongoing payment -// - -type PaymentDeletePaymentRequestPayload = Readonly<{ - paymentId: string; -}>; - -export const paymentDeletePayment = createAsyncAction( - "PAYMENT_DELETE_PAYMENT_REQUEST", - "PAYMENT_DELETE_PAYMENT_SUCCESS", - "PAYMENT_DELETE_PAYMENT_FAILURE" -)(); - -export const runDeleteActivePaymentSaga = createStandardAction( - "PAYMENT_RUN_DELETE_ACTIVE_PAYMENT_SAGA" -)(); - -// abort payment just before pay -export const abortRunningPayment = createStandardAction( - "PAYMENT_ABORT_RUNNING_PAYMENT" -)(); - -/** - * the psp selected for the payment - */ -export const pspSelectedForPaymentV2 = createStandardAction( - "PAYMENT_PSP_V2_SELECTED" -)(); - -/** - * get the list of psp that can handle the payment with the given paymentMethod - */ -export const pspForPaymentV2 = createAsyncAction( - "PAYMENT_PSP_V2_REQUEST", - "PAYMENT_PSP_V2_SUCCESS", - "PAYMENT_PSP_V2_FAILURE" -)< - { idWallet: number; idPayment: string }, - ReadonlyArray, - NetworkError ->(); - -/** - * @deprecated - * this action is used only to mimic the existing payment logic (callbacks hell 😈) - * use {@link pspForPaymentV2} instead - */ -export const pspForPaymentV2WithCallbacks = createStandardAction( - "PAYMENT_PSP_V2_WITH_CALLBACKS" -)<{ - idWallet: number; - idPayment: string; - onSuccess: (psp: ReadonlyArray) => void; - onFailure: () => void; -}>(); - -// This action is used to notify that wallet sagas handlers have been initialized -// Used by the Fast Login sagas to wait before dispatching any pending actions -export const walletPaymentHandlersInitialized = createStandardAction( - "WALLET_PAYMENT_HANDLERS_INITIALIZED" -)(); - -/** - * All possible payment actions - */ -export type PaymentActions = - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType; diff --git a/ts/store/actions/wallet/wallets.ts b/ts/store/actions/wallet/wallets.ts deleted file mode 100644 index 7f8d98e2ab7..00000000000 --- a/ts/store/actions/wallet/wallets.ts +++ /dev/null @@ -1,193 +0,0 @@ -import { - ActionType, - createAsyncAction, - createStandardAction -} from "typesafe-actions"; - -import { - CreditCard, - NullableWallet, - PaymentManagerToken, - Wallet, - WalletResponse -} from "../../../types/pagopa"; -import { PayloadForAction } from "../../../types/utils"; -import { NetworkError } from "../../../utils/errors"; - -// this action load wallets following a backoff retry strategy -export const fetchWalletsRequestWithExpBackoff = createStandardAction( - "WALLETS_LOAD_BACKOFF_REQUEST" -)(); - -export const fetchWalletsRequest = createStandardAction( - "WALLETS_LOAD_REQUEST" -)(); - -export const fetchWalletsSuccess = createStandardAction("WALLETS_LOAD_SUCCESS")< - ReadonlyArray ->(); - -export const fetchWalletsFailure = createStandardAction( - "WALLETS_LOAD_FAILURE" -)(); - -export const addWalletCreditCardInit = createStandardAction( - "WALLET_ADD_CREDITCARD_INIT" -)(); - -type AddWalletCreditCardRequestPayload = Readonly<{ - creditcard: NullableWallet; -}>; - -export const addWalletCreditCardRequest = createStandardAction( - "WALLET_ADD_CREDITCARD_REQUEST" -)(); - -// this action follows a backoff retry strategy -export const addWalletCreditCardWithBackoffRetryRequest = createStandardAction( - "WALLET_ADD_CREDITCARD_WITH_BACKOFF_REQUEST" -)(); - -export const addWalletCreditCardSuccess = createStandardAction( - "WALLET_ADD_CREDITCARD_SUCCESS" -)(); - -// this action describes when a new card is completed onboarded (add + pay + checkout) -// and available in wallets list -export const addWalletNewCreditCardSuccess = createStandardAction( - "WALLET_ADD_NEW_CREDITCARD_SUCCESS" -)(); - -export const addWalletNewCreditCardFailure = createStandardAction( - "WALLET_ADD_NEW_CREDITCARD_FAILURE" -)(); - -export type CreditCardFailure = - | { - kind: "GENERIC_ERROR"; - reason: string; - } - | { - kind: "ALREADY_EXISTS"; - }; - -export const addWalletCreditCardFailure = createStandardAction( - "WALLET_ADD_CREDITCARD_FAILURE" -)(); - -// used to accumulate all the urls browsed into the pay webview -export const creditCardPaymentNavigationUrls = createStandardAction( - "CREDITCARD_PAYMENT_NAVIGATION_URLS" -)>(); - -type DeleteWalletRequestPayload = Readonly<{ - walletId: number; - onSuccess?: (action: ActionType) => void; - onFailure?: (action: ActionType) => void; -}>; - -export const deleteWalletRequest = createStandardAction( - "WALLET_DELETE_REQUEST" -)(); - -export const deleteWalletSuccess = createStandardAction( - "WALLET_DELETE_SUCCESS" -)>(); - -export const deleteWalletFailure = createStandardAction( - "WALLET_DELETE_FAILURE" -)>(); - -export const setFavouriteWalletRequest = createStandardAction( - "WALLET_SET_FAVOURITE_REQUEST" -)(); - -export const setFavouriteWalletSuccess = createStandardAction( - "WALLET_SET_FAVOURITE_SUCCESS" -)(); - -export const setFavouriteWalletFailure = createStandardAction( - "WALLET_SET_FAVOURITE_FAILURE" -)(); - -export const setWalletSessionEnabled = createStandardAction( - "WALLET_SET_SESSION_ENABLED" -)(); - -type StartOrResumeAddCreditCardSagaPayload = Readonly<{ - creditCard: CreditCard; - language?: string; - setAsFavorite: boolean; - onSuccess?: (wallet: Wallet) => void; - onFailure?: (error?: "ALREADY_EXISTS") => void; -}>; - -export const runStartOrResumeAddCreditCardSaga = createStandardAction( - "RUN_ADD_CREDIT_CARD_SAGA" -)(); - -/** - * user wants to pay - * - request: we know the idWallet, we need a fresh PM session token - * - success: we got a fresh PM session token - * - failure: we can't get a fresh PM session token - */ -export const refreshPMTokenWhileAddCreditCard = createAsyncAction( - "REFRESH_PM_TOKEN_WHILE_ADD_CREDIT_CARD_REQUEST", - "REFRESH_PM_TOKEN_WHILE_ADD_CREDIT_CARD_SUCCESS", - "REFRESH_PM_TOKEN_WHILE_ADD_CREDIT_CARD_FAILURE" -)<{ idWallet: number }, PaymentManagerToken, Error>(); - -export type AddCreditCardWebViewEndReason = "USER_ABORT" | "EXIT_PATH"; -// event fired when the paywebview ends its challenge (used to reset pmSessionToken) -export const addCreditCardWebViewEnd = createStandardAction( - "ADD_CREDIT_CARD_WEB_VIEW_END" -)(); - -export const runSendAddCobadgeTrackSaga = createStandardAction( - "RUN_SEND_ADD_COBADGE_MESSAGE_SAGA" -)(); - -export const sendAddCobadgeMessage = createStandardAction( - "SEND_ADD_COBADGE_MESSAGE" -)(); - -export type UpdatePaymentStatusPayload = { - idWallet: number; - paymentEnabled: boolean; -}; -/** - * change the payment status (enable or disable a payment method to pay with pagoPa) - */ -export const updatePaymentStatus = createAsyncAction( - "WALLET_UPDATE_PAYMENT_STATUS_REQUEST", - "WALLET_UPDATE_PAYMENT_STATUS_SUCCESS", - "WALLET_UPDATE_PAYMENT_STATUS_FAILURE" -)(); - -export type WalletsActions = - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType - | ActionType; diff --git a/ts/store/middlewares/analytics.ts b/ts/store/middlewares/analytics.ts index 89b4a23c108..eb6d35dfd9e 100644 --- a/ts/store/middlewares/analytics.ts +++ b/ts/store/middlewares/analytics.ts @@ -1,6 +1,5 @@ /* eslint-disable no-fallthrough */ // disabled in order to allows comments between the switch -import * as O from "fp-ts/lib/Option"; import { getType } from "typesafe-actions"; import trackCdc from "../../features/bonus/cdc/analytics/index"; @@ -9,12 +8,8 @@ import { loadAvailableBonuses } from "../../features/bonus/common/store/actions/ import trackEuCovidCertificateActions from "../../features/euCovidCert/analytics/index"; import trackFciAction from "../../features/fci/analytics"; import { fciEnvironmentSelector } from "../../features/fci/store/reducers/fciEnvironment"; -import { trackBPayAction } from "../../features/wallet/onboarding/bancomatPay/analytics"; -import { trackCoBadgeAction } from "../../features/wallet/onboarding/cobadge/analytics"; -import trackPaypalOnboarding from "../../features/wallet/onboarding/paypal/analytics/index"; import trackZendesk from "../../features/zendesk/analytics/index"; import { mixpanel } from "../../mixpanel"; -import { getNetworkErrorMessage } from "../../utils/errors"; import { analyticsAuthenticationCompleted, analyticsAuthenticationStarted @@ -57,45 +52,6 @@ import { deleteUserDataProcessing, upsertUserDataProcessing } from "../actions/userDataProcessing"; -import { deleteAllPaymentMethodsByFunction } from "../actions/wallet/delete"; -import { - addCreditCardOutcomeCode, - paymentOutcomeCode -} from "../actions/wallet/outcomeCode"; -import { - abortRunningPayment, - paymentAttiva, - paymentCheck, - paymentCompletedSuccess, - paymentDeletePayment, - paymentExecuteStart, - paymentIdPolling, - paymentInitializeState, - paymentUpdateWalletPsp, - paymentVerifica, - paymentWebViewEnd -} from "../actions/wallet/payment"; -import { - fetchTransactionsFailure, - fetchTransactionsRequest, - fetchTransactionsSuccess -} from "../actions/wallet/transactions"; -import { - addCreditCardWebViewEnd, - addWalletCreditCardFailure, - addWalletCreditCardInit, - addWalletCreditCardRequest, - addWalletNewCreditCardFailure, - addWalletNewCreditCardSuccess, - deleteWalletFailure, - deleteWalletRequest, - deleteWalletSuccess, - refreshPMTokenWhileAddCreditCard, - setFavouriteWalletFailure, - setFavouriteWalletRequest, - setFavouriteWalletSuccess, - updatePaymentStatus -} from "../actions/wallet/wallets"; import { buildEventProperties } from "../../utils/analytics"; import { trackServicesAction } from "../../features/services/common/analytics"; import { trackMessagesActionsPostDispatch } from "../../features/messages/analytics"; @@ -131,88 +87,6 @@ const trackAction = case getType(profileEmailValidationChanged): return mp.track(action.type, { isEmailValidated: action.payload }); - case getType(fetchTransactionsSuccess): - return mp.track(action.type, { - count: action.payload.data.length, - total: O.getOrElse(() => -1)(action.payload.total) - }); - // end pay webview Payment (payment + onboarding credit card) actions (with properties) - case getType(addCreditCardWebViewEnd): - return mp.track(action.type, { - exitType: action.payload - }); - case getType(paymentOutcomeCode): - return mp.track(action.type, { - outCome: O.getOrElse(() => "")(action.payload.outcome), - paymentMethodType: action.payload.paymentMethodType - }); - case getType(addCreditCardOutcomeCode): - return mp.track(action.type, { - outCome: O.getOrElse(() => "")(action.payload) - }); - case getType(paymentWebViewEnd): - return mp.track(action.type, { - exitType: action.payload.reason, - paymentMethodType: action.payload.paymentMethodType - }); - // - // Payment actions (with properties) - // - case getType(paymentAttiva.request): - case getType(paymentVerifica.request): - return mp.track(action.type, { - organizationFiscalCode: action.payload.rptId.organizationFiscalCode, - paymentNoticeNumber: action.payload.rptId.paymentNoticeNumber - }); - case getType(paymentVerifica.success): - return mp.track(action.type, { - amount: action.payload.importoSingoloVersamento - }); - case getType(paymentCompletedSuccess): - // PaymentCompletedSuccess may be generated by a completed payment or - // by a verifica operation that return a duplicated payment error. - // Only in the former case we have a transaction and an amount. - if (action.payload.kind === "COMPLETED") { - const amount = action.payload.transaction?.amount.amount; - mp.track(action.type, { - amount, - kind: action.payload.kind - }); - return mp.getPeople().trackCharge(amount ?? -1, {}); - } else { - return mp.track(action.type, { - kind: action.payload.kind - }); - } - // - // Wallet / payment failure actions (reason in the payload) - // - case getType(addWalletCreditCardFailure): - return mp.track(action.type, { - reason: action.payload.kind, - // only GENERIC_ERROR could have details of the error - error: - action.payload.kind === "GENERIC_ERROR" - ? action.payload.reason - : "n/a" - }); - case getType(addWalletNewCreditCardFailure): - return mp.track(action.type); - - case getType(paymentAttiva.failure): - case getType(paymentVerifica.failure): - case getType(paymentIdPolling.failure): - case getType(paymentCheck.failure): - return mp.track(action.type, { - reason: action.payload - }); - case getType(updatePaymentStatus.failure): - return mp.track(action.type, { - reason: getNetworkErrorMessage(action.payload) - }); - - // logout / load message / delete wallets / failure - case getType(deleteAllPaymentMethodsByFunction.failure): case getType(upsertUserDataProcessing.failure): case getType(logoutFailure): return mp.track(action.type, { @@ -225,13 +99,6 @@ const trackAction = case getType(sessionInformationLoadFailure): case getType(profileLoadFailure): case getType(profileUpsert.failure): - case getType(refreshPMTokenWhileAddCreditCard.failure): - case getType(deleteWalletFailure): - case getType(setFavouriteWalletFailure): - case getType(fetchTransactionsFailure): - case getType(paymentDeletePayment.failure): - case getType(paymentUpdateWalletPsp.failure): - case getType(paymentExecuteStart.failure): // Bonus vacanze case getType(loadAvailableBonuses.failure): return mp.track(action.type, { @@ -246,11 +113,6 @@ const trackAction = // download / delete profile case getType(upsertUserDataProcessing.success): return mp.track(action.type, action.payload); - // wallet - case getType(updatePaymentStatus.success): - return mp.track(action.type, { - pagoPA: action.payload.paymentMethod?.pagoPA - }); // // Actions (without properties) // @@ -284,34 +146,6 @@ const trackAction = case getType(profileLoadRequest): // messages case getType(searchMessagesEnabled): - // wallet - case getType(addWalletCreditCardInit): - case getType(addWalletCreditCardRequest): - case getType(addWalletNewCreditCardSuccess): - case getType(deleteAllPaymentMethodsByFunction.request): - case getType(deleteAllPaymentMethodsByFunction.success): - case getType(deleteWalletRequest): - case getType(deleteWalletSuccess): - case getType(setFavouriteWalletRequest): - case getType(setFavouriteWalletSuccess): - case getType(fetchTransactionsRequest): - case getType(refreshPMTokenWhileAddCreditCard.request): - case getType(refreshPMTokenWhileAddCreditCard.success): - case getType(updatePaymentStatus.request): - // payment - case getType(abortRunningPayment): - case getType(paymentInitializeState): - case getType(paymentAttiva.success): - case getType(paymentIdPolling.request): - case getType(paymentIdPolling.success): - case getType(paymentCheck.request): - case getType(paymentCheck.success): - case getType(paymentExecuteStart.request): - case getType(paymentExecuteStart.success): - case getType(paymentUpdateWalletPsp.request): - case getType(paymentUpdateWalletPsp.success): - case getType(paymentDeletePayment.request): - case getType(paymentDeletePayment.success): // profile First time Login case getType(profileFirstLogin): // other @@ -354,13 +188,10 @@ export const actionTracking = // Be aware that, at this point, tracking is called before // the action has been dispatched to the redux store void trackAction(mixpanel)(action); - void trackBPayAction(mixpanel)(action); - void trackCoBadgeAction(mixpanel)(action); void trackCgnAction(mixpanel)(action); void trackContentAction(mixpanel)(action); void trackServicesAction(mixpanel)(action); void trackEuCovidCertificateActions(mixpanel)(action); - void trackPaypalOnboarding(mixpanel)(action); void trackZendesk(mixpanel)(action); void trackCdc(mixpanel)(action); diff --git a/ts/store/reducers/__tests__/allowedSnapshotScreens.test.ts b/ts/store/reducers/__tests__/allowedSnapshotScreens.test.ts index 6976ca899c5..44f5dbe29b6 100644 --- a/ts/store/reducers/__tests__/allowedSnapshotScreens.test.ts +++ b/ts/store/reducers/__tests__/allowedSnapshotScreens.test.ts @@ -1,4 +1,4 @@ -import ROUTES from "../../../navigation/routes"; +/* eslint-disable eslint-comments/no-unlimited-disable */ import { applicationChangeState } from "../../actions/application"; import { setDebugModeEnabled } from "../../actions/debug"; import { @@ -18,10 +18,6 @@ jest.mock("react-native-share", () => ({ describe("allowed Snapshot Screens Selector test", () => { it("Test high level composition", () => { - // with a blacklisted screen, expected false - expect( - isAllowedSnapshotCurrentScreen.resultFunc(ROUTES.WALLET_ADD_CARD, false) - ).toBeFalsy(); // with the debug mode enabled, expected true expect( isAllowedSnapshotCurrentScreen.resultFunc( diff --git a/ts/store/reducers/__tests__/creditCard.test.ts b/ts/store/reducers/__tests__/creditCard.test.ts deleted file mode 100644 index 7252eb21f13..00000000000 --- a/ts/store/reducers/__tests__/creditCard.test.ts +++ /dev/null @@ -1,274 +0,0 @@ -import * as AR from "fp-ts/lib/Array"; -import sha from "sha.js"; -import * as O from "fp-ts/lib/Option"; -import { NullableWallet, WalletResponse } from "../../../types/pagopa"; -import { - CreditCardExpirationMonth, - CreditCardExpirationYear, - CreditCardPan -} from "../../../utils/input"; -import reducer, { - CreditCardInsertionState, - MAX_HISTORY_LENGTH -} from "../wallet/creditCard"; - -import { - addWalletCreditCardFailure, - addWalletCreditCardRequest, - addWalletCreditCardSuccess, - addWalletNewCreditCardSuccess, - creditCardPaymentNavigationUrls -} from "../../actions/wallet/wallets"; -import { Action } from "../../actions/types"; -import { addCreditCardOutcomeCode } from "../../actions/wallet/outcomeCode"; - -const creditCardToAdd: NullableWallet = { - creditCard: { - pan: "abcd1234" as CreditCardPan, - expireMonth: "12" as CreditCardExpirationMonth, - expireYear: "23" as CreditCardExpirationYear, - securityCode: undefined, - holder: "holder" - } -} as NullableWallet; - -const anotherCreditCardToAdd: NullableWallet = { - creditCard: { - pan: "qwerty6789" as CreditCardPan, - expireMonth: "11" as CreditCardExpirationMonth, - expireYear: "22" as CreditCardExpirationYear, - securityCode: undefined, - holder: "holder" - } -} as NullableWallet; - -const walletResponse: WalletResponse = { - data: { - idWallet: 123, - creditCard: { - id: 456, - expireMonth: "01" as CreditCardExpirationMonth, - expireYear: "12" as CreditCardExpirationYear, - brand: "brand" - } - } -} as WalletResponse; - -const addCCAction = addWalletCreditCardRequest({ - creditcard: creditCardToAdd -}); - -const aGenericError = { kind: "GENERIC_ERROR" as const, reason: "a reason" }; - -// eslint-disable-next-line functional/no-let -let salt = "X"; -const getNewCreditCard = () => { - salt += "X"; - return { - creditcard: { - ...creditCardToAdd, - creditCard: { - ...creditCardToAdd.creditCard!, - pan: ((creditCardToAdd.creditCard!.pan as string) + - salt) as CreditCardPan - } - } - }; -}; -describe("credit card history", () => { - it("should add a credit card attempt into the history", () => { - const state1 = runReducer([], addCCAction); - expect(state1.length).toEqual(1); - - const addCCAction2 = addWalletCreditCardRequest(getNewCreditCard()); - const state2 = runReducer(state1, addCCAction2); - expect(state2.length).toEqual(2); - }); - - it("should limit the stored attempts to a maximum", () => { - const finalState = AR.range( - 1, - MAX_HISTORY_LENGTH + 10 - ).reduce( - acc => runReducer(acc, addWalletCreditCardRequest(getNewCreditCard())), - [] - ); - expect(finalState.length).toBeLessThanOrEqual(MAX_HISTORY_LENGTH); - }); - - it("should not add a credit card attempt (already present)", () => { - const state1 = runReducer([], addCCAction); - expect(state1.length).toEqual(1); - const state2 = runReducer(state1, addCCAction); - expect(state2.length).toEqual(1); - }); - - it("should store the credit card attempts info", () => { - const [creditCardInfo] = runReducer([], addCCAction); - expect(creditCardInfo.blurredPan).toEqual("1234"); - expect(creditCardInfo.hashedPan).toEqual( - sha("sha256").update(creditCardToAdd.creditCard!.pan).digest("hex") - ); - expect(creditCardInfo.expireMonth).toEqual( - creditCardToAdd.creditCard!.expireMonth - ); - expect(creditCardInfo.expireYear).toEqual( - creditCardToAdd.creditCard!.expireYear - ); - expect(creditCardInfo.onboardingComplete).toBe(false); - }); - - it("should add a credit card attempt and the relative wallet info (2nd step)", () => { - const [cardItem] = runReducer( - [], - addCCAction, - addWalletCreditCardSuccess(walletResponse) - ); - const walletInfo = cardItem.wallet; - expect(walletInfo).toBeDefined(); - if (walletInfo) { - expect(walletInfo.idWallet).toEqual(walletResponse.data.idWallet); - expect(walletInfo.brand).toEqual(walletResponse.data.creditCard!.brand); - expect(walletInfo.idCreditCard).toEqual( - walletResponse.data.creditCard!.id - ); - } - expect(cardItem.onboardingComplete).toBe(false); - }); - - it("should set wallet on last inserted item", () => { - const state = runReducer( - [], - addCCAction, - addWalletCreditCardRequest({ - creditcard: anotherCreditCardToAdd - }), - addWalletCreditCardSuccess(walletResponse) - ); - - const [anotherCardItem, cardItem] = state; - expect(anotherCardItem.wallet).toBeDefined(); - expect(cardItem.wallet).not.toBeDefined(); - expect(cardItem.onboardingComplete).toBe(false); - }); - - it("should add a credit card in the history and the relative failure reason in case of failure", () => { - const state = runReducer( - [], - addCCAction, - addWalletCreditCardFailure(aGenericError) - ); - - const [cardItem] = state; - - expect(cardItem.failureReason).toEqual(aGenericError); - expect(cardItem.onboardingComplete).toBe(false); - }); - - it("should set failure on last inserted item", () => { - const state = runReducer( - [], - addCCAction, - addWalletCreditCardRequest({ - creditcard: anotherCreditCardToAdd - }), - addWalletCreditCardFailure(aGenericError) - ); - - const [anotherCardItem, cardItem] = state; - expect(anotherCardItem.failureReason).toEqual(aGenericError); - expect(cardItem.failureReason).not.toBeDefined(); - expect(cardItem.onboardingComplete).toBe(false); - }); - - it("should remove failure on new request for the same card", () => { - const state = runReducer( - [], - addCCAction, - addWalletCreditCardFailure(aGenericError), - addCCAction - ); - - const [cardItem] = state; - expect(state.length).toBe(1); - expect(cardItem.failureReason).not.toBeDefined(); - expect(cardItem.onboardingComplete).toBe(false); - }); - - it("should add the history of 3ds urls to a card item", () => { - const state = runReducer( - [], - addCCAction, - addWalletCreditCardSuccess(walletResponse) - ); - - const [cardItem] = state; - - expect(cardItem.wallet).toBeDefined(); - expect(cardItem.onboardingComplete).toBe(false); - }); - - it("should add a failure message when the verification payment fails", () => { - const state = runReducer( - [], - addCCAction, - addWalletCreditCardSuccess(walletResponse) - ); - - const [cardItem] = state; - - expect(cardItem.failureReason).not.toBeDefined(); - expect(cardItem.onboardingComplete).toBe(false); - }); - - it("should add transaction data when the verification payment succeeded", () => { - const state = runReducer( - [], - addCCAction, - addWalletCreditCardSuccess(walletResponse) - ); - - const [cardItem] = state; - expect(cardItem.onboardingComplete).toBe(false); - }); - - it("should mark an item as completed when credit card finish to onboard", () => { - const state = runReducer( - [], - addCCAction, - addWalletCreditCardSuccess(walletResponse), - addWalletNewCreditCardSuccess() - ); - - const [cardItem] = state; - - expect(cardItem.onboardingComplete).toBe(true); - }); - - it("should save the outcome code", () => { - const outComeCode = "123"; - const state = runReducer( - [], - addCCAction, - addCreditCardOutcomeCode(O.some(outComeCode)) - ); - const [cardItem] = state; - expect(cardItem.outcomeCode).toEqual(outComeCode); - }); - - it("should save the navigation urls", () => { - const urls = ["url1", "url2", "url3", "url4"]; - const state = runReducer( - [], - addCCAction, - creditCardPaymentNavigationUrls(urls) - ); - const [cardItem] = state; - expect(cardItem.payNavigationUrls).toEqual(urls); - }); -}); - -const runReducer = ( - initialState: CreditCardInsertionState, - ...actions: Array -): CreditCardInsertionState => actions.reduce(reducer, initialState); diff --git a/ts/store/reducers/allowedSnapshotScreens.ts b/ts/store/reducers/allowedSnapshotScreens.ts index 67bc6d0ff59..b6b2739a69b 100644 --- a/ts/store/reducers/allowedSnapshotScreens.ts +++ b/ts/store/reducers/allowedSnapshotScreens.ts @@ -1,9 +1,11 @@ import { createSelector } from "reselect"; -import ROUTES from "../../navigation/routes"; +import { PaymentsOnboardingRoutes } from "../../features/payments/onboarding/navigation/routes"; import { isDebugModeEnabledSelector } from "./debug"; import { currentRouteSelector } from "./navigation"; -export const screenBlackList = new Set([ROUTES.WALLET_ADD_CARD as string]); +export const screenBlackList = new Set([ + PaymentsOnboardingRoutes.PAYMENT_ONBOARDING_SELECT_METHOD as string +]); /** * Return {true} if a snapshot can be taken in the current screen (android only). diff --git a/ts/store/reducers/backoffErrorConfig.ts b/ts/store/reducers/backoffErrorConfig.ts index 2208e39bc4a..72ee58143f0 100644 --- a/ts/store/reducers/backoffErrorConfig.ts +++ b/ts/store/reducers/backoffErrorConfig.ts @@ -1,16 +1,6 @@ import { getType } from "typesafe-actions"; import _ from "lodash"; import { PayloadAC } from "typesafe-actions/dist/type-helpers"; -import { - fetchTransactionsFailure, - fetchTransactionsSuccess -} from "../actions/wallet/transactions"; -import { - addWalletCreditCardFailure, - addWalletCreditCardSuccess, - fetchWalletsFailure, - fetchWalletsSuccess -} from "../actions/wallet/wallets"; import { euCovidCertificateGet } from "../../features/euCovidCert/store/actions"; /** @@ -21,12 +11,7 @@ import { euCovidCertificateGet } from "../../features/euCovidCert/store/actions" */ const monitoredActions: ReadonlyArray< [failureAction: PayloadAC, successAction: PayloadAC] -> = [ - [addWalletCreditCardFailure, addWalletCreditCardSuccess], - [fetchTransactionsFailure, fetchTransactionsSuccess], - [fetchWalletsFailure, fetchWalletsSuccess], - [euCovidCertificateGet.failure, euCovidCertificateGet.success] -]; +> = [[euCovidCertificateGet.failure, euCovidCertificateGet.success]]; const failureActions = monitoredActions.map(ma => ma[0]); const successActions = monitoredActions.map(ma => ma[1]); diff --git a/ts/store/reducers/entities/payments.ts b/ts/store/reducers/entities/payments.ts index 4746cbfc1a1..9c7cbe52a99 100644 --- a/ts/store/reducers/entities/payments.ts +++ b/ts/store/reducers/entities/payments.ts @@ -4,9 +4,7 @@ * are managed by different global reducers. */ import { getType } from "typesafe-actions"; -import { RptIdFromString } from "@pagopa/io-pagopa-commons/lib/pagopa"; import { Action } from "../../actions/types"; -import { paymentCompletedSuccess as legacyPaymentCompletedSuccess } from "../../actions/wallet/payment"; import { paymentCompletedSuccess } from "../../../features/payments/checkout/store/actions/orchestration"; import { GlobalState } from "../types"; import { differentProfileLoggedIn } from "../../actions/crossSessions"; @@ -37,22 +35,6 @@ export const paymentByRptIdReducer = ( action: Action ): PaymentByRptIdState => { switch (action.type) { - case getType(legacyPaymentCompletedSuccess): - // Use the ID as object key - const rptIdString: string = RptIdFromString.encode(action.payload.rptId); - return { - ...state, - [rptIdString]: - action.payload.kind === "COMPLETED" - ? { - kind: "COMPLETED", - transactionId: action.payload.transaction?.id - } - : { - kind: "DUPLICATED" - } - }; - // New payment flow completed case getType(paymentCompletedSuccess): return { ...state, diff --git a/ts/store/reducers/index.ts b/ts/store/reducers/index.ts index 698fb680bb2..aa462ae6cb7 100644 --- a/ts/store/reducers/index.ts +++ b/ts/store/reducers/index.ts @@ -62,7 +62,6 @@ import identificationReducer, { import installationReducer from "./installation"; import { navigationReducer } from "./navigation"; import onboardingReducer from "./onboarding"; -import paymentsReducer from "./payments"; import persistedPreferencesReducer, { initialPreferencesState } from "./persistedPreferences"; @@ -72,8 +71,6 @@ import searchReducer from "./search"; import startupReducer from "./startup"; import { GlobalState } from "./types"; import userDataProcessingReducer from "./userDataProcessing"; -import walletReducer from "./wallet"; -import { WALLETS_INITIAL_STATE as walletsInitialState } from "./wallet/wallets"; // A custom configuration to store the authentication into the Keychain export const authenticationPersistConfig: PersistConfig = { @@ -127,7 +124,6 @@ export const appReducer: Reducer = combineReducers< appState: appStateReducer, navigation: navigationReducer, backoffError: backoffErrorReducer, - wallet: walletReducer, versionInfo: versionInfoReducer, backendStatus: backendStatusReducer, preferences: preferencesReducer, @@ -164,7 +160,6 @@ export const appReducer: Reducer = combineReducers< debug: debugPersistor, persistedPreferences: persistedPreferencesReducer, installation: installationReducer, - payments: paymentsReducer, content: contentReducer, emailValidation: emailValidationReducer, crossSessions: crossSessionsReducer, @@ -275,10 +270,6 @@ export function createRootReducer( ...state.notifications, _persist: state.notifications._persist }, - // payments must be kept - payments: { - ...state.payments - }, // isMixpanelEnabled must be kept // isFingerprintEnabled must be kept only if true persistedPreferences: { @@ -289,12 +280,6 @@ export function createRootReducer( ? true : undefined }, - wallet: { - wallets: { - ...walletsInitialState, - _persist: state.wallet.wallets._persist - } - }, lollipop: { ...initialLollipopState, keyTag: state.lollipop.keyTag, diff --git a/ts/store/reducers/payments/__tests__/history.test.ts b/ts/store/reducers/payments/__tests__/history.test.ts deleted file mode 100644 index 1f67d3af53b..00000000000 --- a/ts/store/reducers/payments/__tests__/history.test.ts +++ /dev/null @@ -1,174 +0,0 @@ -import * as AR from "fp-ts/lib/Array"; -import * as O from "fp-ts/lib/Option"; -import { OrganizationFiscalCode } from "@pagopa/ts-commons/lib/strings"; - -import { - paymentCompletedSuccess, - paymentIdPolling, - paymentVerifica -} from "../../../actions/wallet/payment"; -import { fetchTransactionSuccess } from "../../../actions/wallet/transactions"; -import reducer, { PaymentsHistoryState, HISTORY_SIZE } from "../history"; -import { isPaymentDoneSuccessfully } from "../utils"; -import { paymentOutcomeCode } from "../../../actions/wallet/outcomeCode"; -import { - paymentVerificaResponseWithMessage, - paymentVerificaRequestWithMessage, - validTransaction -} from "../../../../__mocks__/paymentPayloads"; -import { myRptId } from "../../../../utils/testFaker"; - -const initialState: PaymentsHistoryState = []; - -const paymentVerificaRequest = paymentVerifica.request( - paymentVerificaRequestWithMessage -); - -const paymentVerificaSuccess = paymentVerifica.success( - paymentVerificaResponseWithMessage -); - -describe("history reducer", () => { - describe("when a `paymentVerifica.request` is sent", () => { - it("should add a payment to the history", () => { - const state = reducer(initialState, paymentVerificaRequest); - expect(state.length).toEqual(1); - expect(state[0].data).toEqual(paymentVerificaRequestWithMessage.rptId); - expect(state[0].startOrigin).toEqual( - paymentVerificaRequestWithMessage.startOrigin - ); - }); - - it("should add a payment that is not yet ended", () => { - const state = reducer(initialState, paymentVerificaRequest); - expect(isPaymentDoneSuccessfully(state[0])).toEqual(O.none); - }); - }); - - describe("when two `paymentVerifica.request` are sent", () => { - describe("and they are identical", () => { - it("should add only one payment to the history", () => { - // eslint-disable-next-line functional/no-let - let state = reducer(initialState, paymentVerificaRequest); - state = reducer(state, paymentVerificaRequest); - expect(state.length).toEqual(1); - }); - }); - - describe("and they carry the same notice number but from different organizations", () => { - it("should persist both the payments", () => { - // eslint-disable-next-line functional/no-let - let state = reducer(initialState, paymentVerificaRequest); - state = reducer( - state, - paymentVerifica.request({ - ...paymentVerificaRequestWithMessage, - rptId: { - ...paymentVerificaRequestWithMessage.rptId, - organizationFiscalCode: "00000000001" as OrganizationFiscalCode - } - }) - ); - expect(state.length).toEqual(2); - }); - }); - }); - - describe("when a `paymentVerifica.success` is sent", () => { - it("should update the existing payment history", () => { - // eslint-disable-next-line functional/no-let - let state = reducer([...initialState], paymentVerificaRequest); - state = reducer(state, paymentVerificaSuccess); - expect(state.length).toEqual(1); - expect(state[0].verifiedData).toEqual(paymentVerificaResponseWithMessage); - }); - }); - - describe("when a `paymentVerifica.failure` is sent", () => { - it("should update the existing payment history", () => { - // eslint-disable-next-line functional/no-let - let state = reducer(initialState, paymentVerificaRequest); - state = reducer(state, paymentVerifica.failure("PPT_IMPORTO_ERRATO")); - expect(state.length).toEqual(1); - expect(state[0].failure).toEqual("PPT_IMPORTO_ERRATO"); - }); - }); - - describe("when a `paymentOutcomeCode` is sent", () => { - it("should update the first existing payment with the same code", () => { - const randomCode = Math.random().toString(); - // eslint-disable-next-line functional/no-let - let state = reducer(initialState, paymentVerificaRequest); - state = reducer(state, paymentVerificaSuccess); - state = reducer( - state, - paymentOutcomeCode({ - outcome: O.some(randomCode), - paymentMethodType: "CreditCard" - }) - ); - expect(state.length).toEqual(1); - expect(state[0].outcomeCode).toEqual(randomCode); - }); - }); - - describe("when a `paymentCompletedSuccess` is sent", () => { - it("should update the existing payment in history", () => { - // eslint-disable-next-line functional/no-let - let state = reducer(initialState, paymentVerificaRequest); - state = reducer( - state, - paymentCompletedSuccess({ - rptId: myRptId, - kind: "COMPLETED", - transaction: undefined - }) - ); - expect(state.length).toEqual(1); - expect(state[0].success).toBe(true); - }); - }); - - describe("when a `paymentIdPolling` is sent", () => { - it("should update the existing payment with the payment id", () => { - const paymentId = "123456ABCD"; - // eslint-disable-next-line functional/no-let - let state = reducer(initialState, paymentVerificaRequest); - state = reducer(state, paymentIdPolling.success(paymentId)); - expect(state.length).toEqual(1); - expect(state[0].paymentId).toEqual(paymentId); - }); - }); - - describe("when a `fetchTransactionSuccess` is sent", () => { - it("should update the existing payment history with the transaction", () => { - // eslint-disable-next-line functional/no-let - let state = reducer(initialState, paymentVerificaRequest); - state = reducer(state, fetchTransactionSuccess(validTransaction)); - expect(state.length).toEqual(1); - expect(state[0].transaction).toEqual(validTransaction); - }); - }); - - it(`should limit the payment history insertions to ${HISTORY_SIZE}`, () => { - // eslint-disable-next-line functional/no-let - let state = initialState; - AR.range(1, HISTORY_SIZE + 1).forEach((_, i) => { - state = reducer( - state, - paymentVerifica.request({ - ...paymentVerificaRequestWithMessage, - rptId: { - ...paymentVerificaRequestWithMessage.rptId, - paymentNoticeNumber: { - ...paymentVerificaRequestWithMessage.rptId.paymentNoticeNumber, - auxDigit: i.toString() as any - } - }, - startOrigin: "message" - }) - ); - }); - expect(state.length).toEqual(HISTORY_SIZE); - }); -}); diff --git a/ts/store/reducers/payments/current.ts b/ts/store/reducers/payments/current.ts deleted file mode 100644 index a525c351134..00000000000 --- a/ts/store/reducers/payments/current.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Omit } from "@pagopa/ts-commons/lib/types"; -import { getType } from "typesafe-actions"; -import { RptId } from "@pagopa/io-pagopa-commons/lib/pagopa"; - -import { Action } from "../../actions/types"; -import { - paymentCompletedSuccess, - paymentIdPolling, - paymentInitializeState, - paymentVerifica -} from "../../actions/wallet/payment"; -import { GlobalState } from "../types"; - -type WithoutKind = Omit; - -export type PaymentUnstartedState = { - kind: "UNSTARTED"; -}; - -export type PaymentInitializedState = WithoutKind & { - kind: "INITIALIZED"; - initializationData: { - rptId: RptId; - }; -}; - -export type PaymentActivatedState = WithoutKind & { - kind: "ACTIVATED"; - activationData: { - idPayment: string; - }; -}; - -const INITIAL_STATE: PaymentsCurrentState = { - kind: "UNSTARTED" -}; - -export const paymentsCurrentStateSelector = (state: GlobalState) => - state.payments.current; - -export type PaymentsCurrentState = - | PaymentUnstartedState - | PaymentInitializedState - | PaymentActivatedState; - -const paymentsCurrentReducer = ( - state: PaymentsCurrentState = INITIAL_STATE, - action: Action -): PaymentsCurrentState => { - switch (action.type) { - case getType(paymentVerifica.request): { - if (state.kind === "UNSTARTED") { - return { - ...state, - kind: "INITIALIZED", - initializationData: { - rptId: action.payload.rptId - } - }; - } - - return state; - } - - case getType(paymentIdPolling.success): { - if (state.kind === "INITIALIZED") { - return { - ...state, - kind: "ACTIVATED", - activationData: { - idPayment: action.payload - } - }; - } - - return state; - } - - case getType(paymentInitializeState): - case getType(paymentCompletedSuccess): - return { - kind: "UNSTARTED" - }; - - default: - return state; - } -}; - -export default paymentsCurrentReducer; diff --git a/ts/store/reducers/payments/history.ts b/ts/store/reducers/payments/history.ts deleted file mode 100644 index 5ce18fbeffb..00000000000 --- a/ts/store/reducers/payments/history.ts +++ /dev/null @@ -1,161 +0,0 @@ -/** - * the goal of this reducer is store a fixed amount of payments (requested, done or failed) - * to allow the user to pick one that could be problematic and forward it - * to the customer care assistance - * - * to accomplish this scope we store: - * - started_at the time in ISO format when the payment started - * - "data" coming from: a message, qr code, or manual insertion - * - "verifiedData" coming from the verification of the previous one (see paymentVerifica.request ACTION and related SAGA) - * - "paymentId" coming from payment activation - * - "transaction" coming from payment manager when we ask for info about latest transaction - * - "failure" coming from the failure of a verification (paymentVerifica.failure) - */ -import { RptId } from "@pagopa/io-pagopa-commons/lib/pagopa"; -import * as O from "fp-ts/lib/Option"; -import _ from "lodash"; -import { getType } from "typesafe-actions"; - -import { Detail_v2Enum } from "../../../../definitions/backend/PaymentProblemJson"; -import { PaymentRequestsGetResponse } from "../../../../definitions/backend/PaymentRequestsGetResponse"; -import { Transaction } from "../../../types/pagopa"; -import { getLookUpId } from "../../../utils/pmLookUpId"; -import { differentProfileLoggedIn } from "../../actions/crossSessions"; -import { clearCache } from "../../actions/profile"; -import { Action } from "../../actions/types"; -import { paymentOutcomeCode } from "../../actions/wallet/outcomeCode"; -import { - paymentCompletedSuccess, - paymentIdPolling, - paymentRedirectionUrls, - PaymentStartOrigin, - paymentVerifica, - paymentWebViewEnd, - PaymentWebViewEndReason -} from "../../actions/wallet/payment"; -import { fetchTransactionSuccess } from "../../actions/wallet/transactions"; -import { GlobalState } from "../types"; - -export type PaymentHistory = { - started_at: string; - data: RptId; - paymentId?: string; - // TODO Transaction is not available, add it when PM makes it available again - // see https://www.pivotaltracker.com/story/show/177067134 - transaction?: Transaction; - verifiedData?: PaymentRequestsGetResponse; - failure?: keyof typeof Detail_v2Enum; - outcomeCode?: string; - success?: true; - payNavigationUrls?: ReadonlyArray; - webViewCloseReason?: PaymentWebViewEndReason; - lookupId?: string; - // where the payment started (message, manual insertion, qrcode scan) - startOrigin: PaymentStartOrigin; -}; - -export type PaymentsHistoryState = ReadonlyArray; -const INITIAL_STATE: ReadonlyArray = []; -export const HISTORY_SIZE = 15; - -// replace the last element of the state with the given one -// if the state is empty does nothing -const replaceLastItem = ( - state: PaymentsHistoryState, - newItem: PaymentHistory -): PaymentsHistoryState => { - // it shouldn't never happen since an update actions should come after a create action - if (state.length === 0) { - return state; - } - return state.slice(0, state.length - 1).concat([newItem]); -}; - -const reducer = ( - state: PaymentsHistoryState = INITIAL_STATE, - action: Action -): PaymentsHistoryState => { - switch (action.type) { - case getType(paymentVerifica.request): - // if already in, remove the previous one - const updateState = [...state].filter( - ph => !_.isEqual(ph.data, action.payload.rptId) - ); - // if size exceeded, remove the ones exceeding (here we consider the one we will add in it) - if (updateState.length + 1 >= HISTORY_SIZE) { - // eslint-disable-next-line functional/immutable-data - updateState.splice( - HISTORY_SIZE - 1, - updateState.length + 1 - HISTORY_SIZE - ); - } - return [ - ...updateState, - { - data: { ...action.payload.rptId }, - started_at: new Date().toISOString(), - lookupId: getLookUpId(), - startOrigin: action.payload.startOrigin - } - ]; - case getType(paymentIdPolling.success): - const paymentWithPaymentId: PaymentHistory = { - ...state[state.length - 1], - paymentId: action.payload - }; - return replaceLastItem(state, paymentWithPaymentId); - case getType(fetchTransactionSuccess): - const paymentWithTransaction: PaymentHistory = { - ...state[state.length - 1], - transaction: { ...action.payload } - }; - return replaceLastItem(state, paymentWithTransaction); - case getType(paymentVerifica.success): - const successPayload = action.payload; - const updateHistorySuccess: PaymentHistory = { - ...state[state.length - 1], - verifiedData: successPayload - }; - return replaceLastItem(state, updateHistorySuccess); - case getType(paymentVerifica.failure): - const failurePayload = action.payload; - const updateHistoryFailure: PaymentHistory = { - ...state[state.length - 1], - failure: failurePayload - }; - return replaceLastItem(state, updateHistoryFailure); - case getType(paymentCompletedSuccess): - const updateSuccess: PaymentHistory = { - ...state[state.length - 1], - success: true - }; - return replaceLastItem(state, updateSuccess); - case getType(paymentOutcomeCode): - const updateOutcome: PaymentHistory = { - ...state[state.length - 1], - outcomeCode: O.getOrElse(() => "n/a")(action.payload.outcome) - }; - return replaceLastItem(state, updateOutcome); - case getType(paymentRedirectionUrls): - const navigationUrls: PaymentHistory = { - ...state[state.length - 1], - payNavigationUrls: action.payload - }; - return replaceLastItem(state, navigationUrls); - case getType(paymentWebViewEnd): - return replaceLastItem(state, { - ...state[state.length - 1], - webViewCloseReason: action.payload.reason - }); - case getType(differentProfileLoggedIn): - case getType(clearCache): { - return INITIAL_STATE; - } - } - return state; -}; - -export const paymentsHistorySelector = (state: GlobalState) => - state.payments.history; - -export default reducer; diff --git a/ts/store/reducers/payments/index.ts b/ts/store/reducers/payments/index.ts deleted file mode 100644 index 03c1ec1efbb..00000000000 --- a/ts/store/reducers/payments/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { combineReducers } from "redux"; -import { Action } from "../../actions/types"; -import creditCardHistoryReducer, { - CreditCardInsertionState -} from "../wallet/creditCard"; -import paymentsCurrentReducer, { PaymentsCurrentState } from "./current"; -import paymentsHistoryReducer, { PaymentsHistoryState } from "./history"; -import paymentsLastDeletedReducer, { - PaymentsLastDeletedState -} from "./lastDeleted"; - -export type PaymentsState = { - current: PaymentsCurrentState; - lastDeleted: PaymentsLastDeletedState; - history: PaymentsHistoryState; - creditCardInsertion: CreditCardInsertionState; -}; - -const paymentsReducer = combineReducers({ - current: paymentsCurrentReducer, - lastDeleted: paymentsLastDeletedReducer, - history: paymentsHistoryReducer, - creditCardInsertion: creditCardHistoryReducer -}); - -export default paymentsReducer; diff --git a/ts/store/reducers/payments/lastDeleted.ts b/ts/store/reducers/payments/lastDeleted.ts deleted file mode 100644 index 2dcec0149c7..00000000000 --- a/ts/store/reducers/payments/lastDeleted.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { getType } from "typesafe-actions"; -import { RptId } from "@pagopa/io-pagopa-commons/lib/pagopa"; - -import { paymentsLastDeletedSet } from "../../actions/payments"; -import { Action } from "../../actions/types"; -import { GlobalState } from "../types"; - -export type PaymentsLastDeletedInfo = { - // Timestamp - at: number; - rptId: RptId; - idPayment: string; -}; - -export type PaymentsLastDeletedState = PaymentsLastDeletedInfo | null; - -const INITIAL_STATE: PaymentsLastDeletedState = null; - -export const paymentsLastDeletedStateSelector = (state: GlobalState) => - state.payments.lastDeleted; - -/** - * Store info about the last deleted payment - */ -const paymentsLastDeletedReducer = ( - state: PaymentsLastDeletedState = INITIAL_STATE, - action: Action -): PaymentsLastDeletedState => { - switch (action.type) { - case getType(paymentsLastDeletedSet): - return { ...action.payload }; - - default: - return state; - } -}; - -export default paymentsLastDeletedReducer; diff --git a/ts/store/reducers/payments/utils.ts b/ts/store/reducers/payments/utils.ts deleted file mode 100644 index 86ef3c7fed4..00000000000 --- a/ts/store/reducers/payments/utils.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import { isSuccessTransaction } from "../../../types/pagopa"; -import { PaymentHistory } from "./history"; - -/** - * return some(true) if payment ends successfully - * return some(false) if payment ends with a failure - * return none if payments didn't end (no data to say failure or success) - * @param payment - */ -export const isPaymentDoneSuccessfully = ( - payment: PaymentHistory -): O.Option => { - if (payment.failure) { - return O.some(false); - } - if (payment.success) { - return O.some(true); - } - // if we have an outcomeCode we got an error on pay - return payment.outcomeCode - ? O.some(false) - : pipe( - payment.transaction, - O.fromNullable, - O.map(t => t !== undefined && isSuccessTransaction(t)) - ); -}; diff --git a/ts/store/reducers/types.ts b/ts/store/reducers/types.ts index 76a3aa2bab6..ed9b6135bdc 100644 --- a/ts/store/reducers/types.ts +++ b/ts/store/reducers/types.ts @@ -21,13 +21,11 @@ import { PersistedIdentificationState } from "./identification"; import { InstallationState } from "./installation"; import { NavigationState } from "./navigation"; import { OnboardingState } from "./onboarding"; -import { PaymentsState } from "./payments"; import { PersistedPreferencesState } from "./persistedPreferences"; import { PreferencesState } from "./preferences"; import { ProfileState } from "./profile"; import { SearchState } from "./search"; import { UserDataProcessingState } from "./userDataProcessing"; -import { WalletState } from "./wallet"; import { StartupState } from "./startup"; export type GlobalState = Readonly<{ @@ -42,7 +40,6 @@ export type GlobalState = Readonly<{ onboarding: OnboardingState; profile: ProfileState; userDataProcessing: UserDataProcessingState; - wallet: WalletState; preferences: PreferencesState; persistedPreferences: PersistedPreferencesState; content: ContentState; @@ -50,7 +47,6 @@ export type GlobalState = Readonly<{ installation: InstallationState; debug: PersistedDebugState; search: SearchState; - payments: PaymentsState; emailValidation: EmailValidationState; cie: CieState; bonus: BonusState; diff --git a/ts/store/reducers/wallet/__mocks__/wallets.ts b/ts/store/reducers/wallet/__mocks__/wallets.ts deleted file mode 100644 index 80b19984978..00000000000 --- a/ts/store/reducers/wallet/__mocks__/wallets.ts +++ /dev/null @@ -1,285 +0,0 @@ -// 2 bancomat, 1 credit card. All compliant with pagoPa -import { WalletTypeEnum } from "../../../../../definitions/pagopa/WalletV2"; -import { PaymentMethod, RawBPayPaymentMethod } from "../../../../types/pagopa"; -import { EnableableFunctionsEnum } from "../../../../../definitions/pagopa/EnableableFunctions"; -import { TypeEnum } from "../../../../../definitions/pagopa/walletv2/CardInfo"; - -export const walletsV2_1 = { - data: [ - { - walletType: "Bancomat", - createDate: "2021-08-28", - enableableFunctions: [ - EnableableFunctionsEnum.FA, - EnableableFunctionsEnum.pagoPA - ], - favourite: false, - idWallet: 23190, - info: { - blurredNumber: "0003", - brand: "MASTERCARD", - brandLogo: - "https://wisp2.pagopa.gov.it/wallet/assets/img/creditcard/carta_mc.png", - expireMonth: "8", - expireYear: "2024", - hashPan: - "e105a87731025d54181d8e4c4c04ff344ce82e57d6a3d6c6911e8eadb0348d7b", - holder: "Maria Rossi", - htokenList: ["token1", "token2"], - issuerAbiCode: "00213", - type: "PP" - }, - onboardingChannel: "I", - pagoPA: true, - updateDate: "2020-11-16" - }, - { - walletType: "Bancomat", - createDate: "2021-07-22", - enableableFunctions: [ - EnableableFunctionsEnum.FA, - EnableableFunctionsEnum.pagoPA - ], - favourite: false, - idWallet: 29371, - info: { - blurredNumber: "0004", - brand: "AMEX", - brandLogo: - "https://wisp2.pagopa.gov.it/wallet/assets/img/creditcard/carta_amex.png", - expireMonth: "7", - expireYear: "2024", - hashPan: - "a591ab131bd9492e6df0357f1ac52785a96ddc8e772baddbb02e2169af9474f4", - holder: "Maria Rossi", - htokenList: ["token1", "token2"], - issuerAbiCode: "00289", - type: "PP" - }, - onboardingChannel: "I", - pagoPA: true, - updateDate: "2020-11-16" - }, - { - walletType: "Card", - createDate: "2020-12-28", - enableableFunctions: [ - EnableableFunctionsEnum.FA, - EnableableFunctionsEnum.pagoPA - ], - favourite: false, - idWallet: 23216, - info: { - blurredNumber: "0000", - brand: "DINERS", - brandLogo: - "https://wisp2.pagopa.gov.it/wallet/assets/img/creditcard/carta_diners.png", - expireMonth: "12", - expireYear: "2024", - hashPan: - "853afb770973eb48d5d275778bd124b28f60a684c20bcdf05dc8f0014c7ce871", - holder: "Maria Rossi", - htokenList: ["token1", "token2"], - issuerAbiCode: "00027", - type: "PP" - }, - onboardingChannel: "I", - pagoPA: true, - updateDate: "2020-11-16" - } - ] -}; -// 1 bancomat, 1 credit card. No compliant with pagoPa -export const walletsV2_2 = { - data: [ - { - walletType: "Bancomat", - createDate: "2021-08-28", - enableableFunctions: [ - EnableableFunctionsEnum.FA, - EnableableFunctionsEnum.pagoPA - ], - favourite: false, - idWallet: 23190, - info: { - blurredNumber: "0003", - brand: "MASTERCARD", - brandLogo: - "https://wisp2.pagopa.gov.it/wallet/assets/img/creditcard/carta_mc.png", - expireMonth: "8", - expireYear: "2024", - hashPan: - "e105a87731025d54181d8e4c4c04ff344ce82e57d6a3d6c6911e8eadb0348d7b", - holder: "Maria Rossi", - htokenList: ["token1", "token2"], - issuerAbiCode: "00213", - type: "PP" - }, - onboardingChannel: "I", - pagoPA: false, - updateDate: "2020-11-16" - }, - { - walletType: "Card", - createDate: "2020-12-28", - enableableFunctions: [ - EnableableFunctionsEnum.FA, - EnableableFunctionsEnum.pagoPA - ], - favourite: false, - idWallet: 23216, - info: { - blurredNumber: "0000", - brand: "DINERS", - brandLogo: - "https://wisp2.pagopa.gov.it/wallet/assets/img/creditcard/carta_diners.png", - expireMonth: "12", - expireYear: "2024", - hashPan: - "853afb770973eb48d5d275778bd124b28f60a684c20bcdf05dc8f0014c7ce871", - holder: "Maria Rossi", - htokenList: ["token1", "token2"], - issuerAbiCode: "00027", - type: "PP" - }, - onboardingChannel: "I", - pagoPA: false, - updateDate: "2020-11-16" - } - ] -}; - -// 1 bancomat, 1 bancomatPay, 1 creditCard -export const walletsV2_3 = { - data: [ - { - walletType: "Bancomat", - createDate: "2021-10-22", - enableableFunctions: [ - EnableableFunctionsEnum.FA, - EnableableFunctionsEnum.pagoPA - ], - favourite: false, - idWallet: 20341, - info: { - blurredNumber: "0003", - brand: "MASTERCARD", - brandLogo: - "https://wisp2.pagopa.gov.it/wallet/assets/img/creditcard/carta_mc.png", - expireMonth: "10", - expireYear: "2024", - hashPan: - "e105a87731025d54181d8e4c4c04ff344ce82e57d6a3d6c6911e8eadb0348d7b", - holder: "Maria Rossi", - htokenList: ["token1", "token2"], - issuerAbiCode: "00095", - type: "PP" - }, - onboardingChannel: "I", - pagoPA: true, - updateDate: "2020-11-20" - }, - { - walletType: "Card", - createDate: "2021-04-15", - enableableFunctions: [ - EnableableFunctionsEnum.FA, - EnableableFunctionsEnum.pagoPA - ], - favourite: false, - idWallet: 21750, - info: { - blurredNumber: "0000", - brand: "DINERS", - brandLogo: - "https://wisp2.pagopa.gov.it/wallet/assets/img/creditcard/carta_diners.png", - expireMonth: "4", - expireYear: "2024", - hashPan: - "853afb770973eb48d5d275778bd124b28f60a684c20bcdf05dc8f0014c7ce871", - holder: "Maria Rossi", - htokenList: ["token1", "token2"], - issuerAbiCode: "00352", - type: "PP" - }, - onboardingChannel: "I", - pagoPA: true, - updateDate: "2020-11-20" - }, - { - walletType: "BPay", - createDate: "2021-07-08", - enableableFunctions: [ - EnableableFunctionsEnum.FA, - EnableableFunctionsEnum.pagoPA - ], - favourite: false, - idWallet: 25572, - info: { - bankName: "Denti, Visintin and Galati", - instituteCode: "4", - numberObfuscated: "****0004", - paymentInstruments: [], - uidHash: - "d48a59cdfbe3da7e4fe25e28cbb47d5747720ecc6fc392c87f1636fe95db22f90004" - }, - onboardingChannel: "I", - pagoPA: true, - updateDate: "2020-11-20" - } - ] -}; - -export const rawBPay: RawBPayPaymentMethod = { - walletType: WalletTypeEnum.BPay, - createDate: "2021-07-08", - enableableFunctions: [ - EnableableFunctionsEnum.FA, - EnableableFunctionsEnum.pagoPA - ], - favourite: false, - idWallet: 1, - info: { - bankName: "Denti, Visintin and Galati", - instituteCode: "4", - numberObfuscated: "+3934****0004", - paymentInstruments: [], - uidHash: - "d48a59cdfbe3da7e4fe25e28cbb47d5747720ecc6fc392c87f1636fe95db22f90004" - }, - onboardingChannel: "I", - pagoPA: true, - updateDate: "2020-11-20", - kind: "BPay" -}; - -export const mockCreditCardPaymentMethod: PaymentMethod = { - walletType: WalletTypeEnum.Card, - createDate: "2021-07-08", - enableableFunctions: [ - EnableableFunctionsEnum.FA, - EnableableFunctionsEnum.pagoPA - ], - favourite: false, - idWallet: 25572, - info: { - blurredNumber: "0001", - brand: "Maestro", - brandLogo: - "https://wisp2.pagopa.gov.it/wallet/assets/img/creditcard/carta_maestro.png", - expireMonth: "11", - expireYear: "2024", - hashPan: - "d48a59cdfbe3da7e4fe25e28cbb47d5747720ecc6fc392c87f1636fe95db22f90004", - holder: "Maria Rossi", - htokenList: ["token1", "token2"], - issuerAbiCode: "ABICODE", - type: TypeEnum.DEB - }, - onboardingChannel: "IO", - pagoPA: false, - updateDate: "2020-11-20", - kind: "CreditCard", - caption: "●●●●0001", - icon: 37 -}; diff --git a/ts/store/reducers/wallet/__tests__/wallets.test.ts b/ts/store/reducers/wallet/__tests__/wallets.test.ts deleted file mode 100644 index 0c74d145dd1..00000000000 --- a/ts/store/reducers/wallet/__tests__/wallets.test.ts +++ /dev/null @@ -1,466 +0,0 @@ -import * as E from "fp-ts/lib/Either"; -import { Errors } from "io-ts"; -import * as pot from "@pagopa/ts-commons/lib/pot"; -import _ from "lodash"; -import { pipe } from "fp-ts/lib/function"; -import { remoteUndefined } from "../../../../common/model/RemoteValue"; -import { - CreditCard, - isRawCreditCard, - PatchedWalletV2ListResponse, - RawPaymentMethod, - Wallet -} from "../../../../types/pagopa"; -import { walletsV2_2, walletsV2_1, walletsV2_3 } from "../__mocks__/wallets"; -import { toIndexed } from "../../../helpers/indexer"; -import { - bancomatListSelector, - bPayListSelector, - creditCardListSelector, - creditCardWalletV1Selector, - getFavoriteWallet, - getFavoriteWalletId, - withPaymentFeatureSelector, - getWalletsById, - pagoPaCreditCardWalletV1Selector, - updatingFavouriteWalletSelector -} from "../wallets"; -import { GlobalState } from "../../types"; -import { convertWalletV2toWalletV1 } from "../../../../utils/walletv2"; -import { getPaymentMethodHash } from "../../../../utils/paymentMethod"; -import { appReducer } from "../../index"; -import { applicationChangeState } from "../../../actions/application"; -import { - fetchWalletsSuccess, - setFavouriteWalletFailure, - setFavouriteWalletRequest, - setFavouriteWalletSuccess, - updatePaymentStatus -} from "../../../actions/wallet/wallets"; -import { EnableableFunctionsEnum } from "../../../../../definitions/pagopa/EnableableFunctions"; -import { deleteAllPaymentMethodsByFunction } from "../../../actions/wallet/delete"; -import { TypeEnum } from "../../../../../definitions/pagopa/Wallet"; -import { - CreditCardExpirationMonth, - CreditCardExpirationYear, - CreditCardPan -} from "../../../../utils/input"; - -describe("walletV2 selectors", () => { - const maybeWalletsV2 = PatchedWalletV2ListResponse.decode(walletsV2_1); - const indexedWallets = toIndexed( - pipe( - maybeWalletsV2, - E.map(walletsV2 => walletsV2.data!.map(convertWalletV2toWalletV1)), - E.getOrElseW(() => []) - ), - w => w.idWallet - ); - const globalState = { - wallet: { - wallets: { - walletById: pot.some(indexedWallets) - }, - abi: remoteUndefined - } - } as any as GlobalState; - it("should decode walletv2 list", () => { - expect(E.isRight(maybeWalletsV2)).toBeTruthy(); - }); - - it("should return credit cards", () => { - const maybeCC = creditCardWalletV1Selector(globalState); - expect(pot.isSome(maybeCC)).toBeTruthy(); - if (pot.isSome(maybeCC)) { - expect(maybeCC.value.length).toEqual(1); - const paymentMethod = maybeCC.value[0].paymentMethod; - if (paymentMethod) { - expect(isRawCreditCard(paymentMethod)).toBeTruthy(); - expect(getPaymentMethodHash(paymentMethod)).toEqual( - "853afb770973eb48d5d275778bd124b28f60a684c20bcdf05dc8f0014c7ce871" - ); - } - } - }); - - it("should return bancomat", () => { - const maybeBancomat = bancomatListSelector(globalState); - expect(pot.isSome(maybeBancomat)).toBeTruthy(); - const hpans = [ - "a591ab131bd9492e6df0357f1ac52785a96ddc8e772baddbb02e2169af9474f4", - "e105a87731025d54181d8e4c4c04ff344ce82e57d6a3d6c6911e8eadb0348d7b" - ]; - if (pot.isSome(maybeBancomat)) { - expect(maybeBancomat.value.length).toEqual(2); - maybeBancomat.value.forEach(w => { - expect(hpans.find(h => h === getPaymentMethodHash(w))).toBeDefined(); - }); - } - }); - - it("should return credit card supporting pagoPa payments", () => { - const maybePagoPaCC = pagoPaCreditCardWalletV1Selector(globalState); - expect(pot.isSome(maybePagoPaCC)).toBeTruthy(); - if (pot.isSome(maybePagoPaCC)) { - expect(maybePagoPaCC.value.length).toEqual(1); - const paymentMethod = maybePagoPaCC.value[0].paymentMethod; - if (paymentMethod) { - expect(isRawCreditCard(paymentMethod)).toBeTruthy(); - expect(getPaymentMethodHash(paymentMethod)).toEqual( - "853afb770973eb48d5d275778bd124b28f60a684c20bcdf05dc8f0014c7ce871" - ); - } - } - }); - - it("should return empty list since there is no method compliant with pagoPa", () => { - const maybeWallets = PatchedWalletV2ListResponse.decode(walletsV2_2); - const globalState = mockWalletState(maybeWallets); - const maybePagoPaCC = pagoPaCreditCardWalletV1Selector(globalState); - expect(pot.isSome(maybePagoPaCC)).toBeTruthy(); - if (pot.isSome(maybePagoPaCC)) { - expect(maybePagoPaCC.value.length).toEqual(0); - } - }); - it("should filter credit card and return one", () => { - const maybeWallets = PatchedWalletV2ListResponse.decode(walletsV2_3); - const globalState = mockWalletState(maybeWallets); - const potCreditCard = creditCardListSelector(globalState); - expect(pot.isSome(potCreditCard)).toBeTruthy(); - expect(pot.getOrElse(potCreditCard, undefined)).toBeDefined(); - if (pot.isSome(potCreditCard)) { - expect(potCreditCard.value.length).toEqual(1); - } - }); - it("should filter bancomat and return one", () => { - const maybeWallets = PatchedWalletV2ListResponse.decode(walletsV2_3); - const globalState = mockWalletState(maybeWallets); - const potBancomat = bancomatListSelector(globalState); - expect(pot.isSome(potBancomat)).toBeTruthy(); - expect(pot.getOrElse(potBancomat, undefined)).toBeDefined(); - if (pot.isSome(potBancomat)) { - expect(potBancomat.value.length).toEqual(1); - } - }); - it("should filter BPay and return one", () => { - const maybeWallets = PatchedWalletV2ListResponse.decode(walletsV2_3); - const globalState = mockWalletState(maybeWallets); - const potBPay = bPayListSelector(globalState); - expect(pot.isSome(potBPay)).toBeTruthy(); - expect(pot.getOrElse(potBPay, undefined)).toBeDefined(); - if (pot.isSome(potBPay)) { - expect(potBPay.value.length).toEqual(1); - } - }); -}); - -describe("walletV2 favoriteId Selector", () => { - const maybeWalletsV2 = PatchedWalletV2ListResponse.decode(walletsV2_1); - // set all method to not favourite - const indexedWallets = toIndexed( - pipe( - maybeWalletsV2, - E.map(walletsV2 => - walletsV2 - .data!.map(convertWalletV2toWalletV1) - .map(w => ({ ...w, favourite: false })) - ), - E.getOrElseW(() => []) - ), - w => w.idWallet - ); - - it("should return pot none - no wallets", () => { - const noWallets = { - wallet: { - wallets: { - walletById: pot.none - } - } - } as any as GlobalState; - expect(getFavoriteWalletId(noWallets)).toEqual(pot.none); - expect(getFavoriteWallet(noWallets)).toEqual(pot.none); - }); - - it("should return pot none - no favourite method", () => { - const noFavouriteState = { - wallet: { - wallets: { - walletById: pot.some(indexedWallets) - } - } - } as GlobalState; - expect(getFavoriteWalletId(noFavouriteState)).toEqual(pot.none); - expect(getFavoriteWallet(noFavouriteState)).toEqual(pot.none); - }); - - it("should return the favourite wallet id", () => { - const firstKey = _.keys(indexedWallets)[0]; - const favouriteWallet: Wallet = { - ...(indexedWallets[firstKey] as Wallet), - favourite: true - }; - const aFavourite = _.update( - indexedWallets, - firstKey, - () => favouriteWallet - ); - const aFavoriteState = { - wallet: { - wallets: { - walletById: pot.some(aFavourite) - } - } - } as GlobalState; - expect(getFavoriteWalletId(aFavoriteState)).toEqual( - pot.some(favouriteWallet.idWallet) - ); - expect(getFavoriteWallet(aFavoriteState)).toEqual( - pot.some(favouriteWallet) - ); - }); -}); - -describe("updatePaymentStatus state changes", () => { - const walletsV2 = pipe( - walletsV2_1, - PatchedWalletV2ListResponse.decode, - E.getOrElseW(() => [] as PatchedWalletV2ListResponse) - ); - const globalState = appReducer(undefined, applicationChangeState("active")); - const withWallets = appReducer( - globalState, - fetchWalletsSuccess(walletsV2.data!.map(convertWalletV2toWalletV1)) - ); - expect(pot.isSome(withWallets.wallet.wallets.walletById)).toBeTruthy(); - if (pot.isSome(withWallets.wallet.wallets.walletById)) { - const currentIndexedWallets = withWallets.wallet.wallets.walletById.value; - expect(Object.keys(currentIndexedWallets).length).toEqual( - walletsV2.data!.length - ); - // try to invert payment status on first wallet - const temp = Object.values(currentIndexedWallets)[0]; - const firstWallet = { - ...temp, - paymentMethod: { - ...temp!.paymentMethod, - pagoPA: true - } as RawPaymentMethod - } as Wallet; - const updatePaymentStatusState = appReducer( - globalState, - updatePaymentStatus.success({ - ...firstWallet, - pagoPA: false - } as any) - ); - const updatedState = updatePaymentStatusState.wallet.wallets.walletById; - expect(pot.isSome(updatedState)).toBeTruthy(); - if (pot.isSome(updatedState)) { - const updatedFirstWallet = Object.values(updatedState.value).find( - w => w!.idWallet === firstWallet.idWallet - ); - expect(updatedFirstWallet!.paymentMethod!.pagoPA).toBeTruthy(); - } - } -}); - -describe("getPayablePaymentMethodsSelector", () => { - it("should return false - no payable methods", () => { - const withWallets = appReducer(undefined, fetchWalletsSuccess([])); - expect(withPaymentFeatureSelector(withWallets).length).toEqual(0); - }); - - it("should return false - empty wallet", () => { - const paymentMethods = pipe( - walletsV2_1, - PatchedWalletV2ListResponse.decode, - E.getOrElseW(() => [] as PatchedWalletV2ListResponse) - ); - - const updatedMethods = paymentMethods.data!.map(w => - convertWalletV2toWalletV1({ ...w, enableableFunctions: [] }) - ); - const withWallets = appReducer( - undefined, - fetchWalletsSuccess(updatedMethods) - ); - expect(updatedMethods.length).toBeGreaterThan(0); - expect(withPaymentFeatureSelector(withWallets).length).toEqual(0); - }); - - it("should return true - one payable method", () => { - const paymentMethods = pipe( - walletsV2_1, - PatchedWalletV2ListResponse.decode, - E.getOrElseW(() => [] as PatchedWalletV2ListResponse) - ); - const updatedMethods = [...paymentMethods.data!]; - // eslint-disable-next-line functional/immutable-data - updatedMethods[0] = { - ...updatedMethods[0], - pagoPA: true, - enableableFunctions: [EnableableFunctionsEnum.pagoPA] - }; - const withWallets = appReducer( - undefined, - fetchWalletsSuccess(updatedMethods.map(convertWalletV2toWalletV1)) - ); - expect(updatedMethods.length).toBeGreaterThan(0); - expect(withPaymentFeatureSelector(withWallets).length).toBeGreaterThan(0); - }); -}); - -describe("getPagoPAMethodsSelector", () => { - it("should return false - no payable methods", () => { - const withWallets = appReducer(undefined, fetchWalletsSuccess([])); - expect(withPaymentFeatureSelector(withWallets).length).toEqual(0); - }); - - it("should return true - one pagoPA method", () => { - const paymentMethods = pipe( - walletsV2_1, - PatchedWalletV2ListResponse.decode, - E.getOrElseW(() => [] as PatchedWalletV2ListResponse) - ); - const updatedMethods = [...paymentMethods.data!]; - // eslint-disable-next-line functional/immutable-data - updatedMethods[0] = { - ...updatedMethods[0], - enableableFunctions: [EnableableFunctionsEnum.pagoPA] - }; - const withWallets = appReducer( - undefined, - fetchWalletsSuccess(updatedMethods.map(convertWalletV2toWalletV1)) - ); - expect(updatedMethods.length).toBeGreaterThan(0); - expect(withPaymentFeatureSelector(withWallets).length).toBeGreaterThan(0); - }); -}); - -describe("updatingFavouriteWalletSelector", () => { - it("when empty should return pot.none", () => { - const empty = appReducer(undefined, applicationChangeState("active")); - expect(updatingFavouriteWalletSelector(empty)).toEqual(pot.none); - }); - - it("when a favourite setting request is dispatch, should return pot.someLoading", () => { - const empty = appReducer(undefined, setFavouriteWalletRequest(99)); - expect(updatingFavouriteWalletSelector(empty)).toEqual( - pot.noneUpdating(99) - ); - }); - - it("when a favourite setting request has been successfully, should return pot.some", () => { - const updatedWallet = { - idWallet: 99, - type: TypeEnum.CREDIT_CARD, - favourite: true, - creditCard: { - id: 3, - holder: "Gian Maria Rossi", - pan: "************0000" as CreditCardPan, - expireMonth: "09" as CreditCardExpirationMonth, - expireYear: "22" as CreditCardExpirationYear, - brandLogo: - "https://wisp2.pagopa.gov.it/wallet/assets/img/creditcard/carta_poste.png", - flag3dsVerified: false, - brand: "POSTEPAY", - onUs: false - } as CreditCard, - pspEditable: true, - isPspToIgnore: false, - saved: false, - registeredNexi: false - }; - const empty = appReducer( - undefined, - setFavouriteWalletSuccess(updatedWallet as Wallet) - ); - expect(updatingFavouriteWalletSelector(empty)).toEqual(pot.some(99)); - }); - - it("when a favourite setting request has been failed, should return pot.someError", () => { - const empty = appReducer(undefined, setFavouriteWalletRequest(99)); - const state = appReducer( - empty, - setFavouriteWalletFailure(new Error("setFavouriteWalletFailure error")) - ); - expect(updatingFavouriteWalletSelector(state)).toEqual( - pot.noneError(new Error("setFavouriteWalletFailure error")) - ); - }); -}); - -describe("walletV2 reducer - deleteAllByFunction", () => { - it("should delete only those payment method whose have specified function enabled", () => { - const aPaymentMethod = walletsV2_1.data[0]; - // 2 pagoPA + 1 BPD - const wallet = [ - { - ...aPaymentMethod, - idWallet: 1, - enableableFunctions: [EnableableFunctionsEnum.pagoPA] - }, - { - ...aPaymentMethod, - idWallet: 2, - enableableFunctions: [EnableableFunctionsEnum.pagoPA] - }, - { - ...aPaymentMethod, - idWallet: 3, - enableableFunctions: [] - } - ]; - const maybeWalletsV2 = PatchedWalletV2ListResponse.decode({ data: wallet }); - const convertedWallets = ( - E.getOrElseW(() => [])(maybeWalletsV2) as PatchedWalletV2ListResponse - ).data!.map(convertWalletV2toWalletV1); - - const globalState: GlobalState = appReducer( - undefined, - fetchWalletsSuccess(convertedWallets) - ); - const walletFull = getWalletsById(globalState); - expect(pot.isSome(walletFull)).toBeTruthy(); - if (pot.isSome(walletFull)) { - expect(Object.keys(walletFull.value).length).toEqual( - convertedWallets.length - ); - } - const updatedState: GlobalState = appReducer( - globalState, - deleteAllPaymentMethodsByFunction.success({ - wallets: convertedWallets, - deletedMethodsCount: 1 - }) - ); - const walletUpdated = getWalletsById(updatedState); - expect(pot.isSome(walletUpdated)).toBeTruthy(); - if (pot.isSome(walletUpdated)) { - expect(Object.keys(walletUpdated.value).length).toEqual( - convertedWallets.length - ); - } - }); -}); - -const mockWalletState = ( - walletResponse: E.Either -) => { - const indexedWallets = toIndexed( - E.getOrElseW(() => [] as PatchedWalletV2ListResponse)( - walletResponse - ).data!.map(convertWalletV2toWalletV1), - w => w.idWallet - ); - return { - wallet: { - abi: remoteUndefined, - wallets: { - walletById: pot.some(indexedWallets) - } - } - } as GlobalState; -}; diff --git a/ts/store/reducers/wallet/creditCard.ts b/ts/store/reducers/wallet/creditCard.ts deleted file mode 100644 index 35ac9d96bca..00000000000 --- a/ts/store/reducers/wallet/creditCard.ts +++ /dev/null @@ -1,181 +0,0 @@ -import * as AR from "fp-ts/lib/Array"; -import { pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import { createSelector } from "reselect"; -import sha from "sha.js"; -import { getType } from "typesafe-actions"; -import { getLookUpId } from "../../../utils/pmLookUpId"; -import { clearCache } from "../../actions/profile"; -import { Action } from "../../actions/types"; -import { addCreditCardOutcomeCode } from "../../actions/wallet/outcomeCode"; -import { - addCreditCardWebViewEnd, - AddCreditCardWebViewEndReason, - addWalletCreditCardFailure, - addWalletCreditCardRequest, - addWalletCreditCardSuccess, - addWalletNewCreditCardSuccess, - CreditCardFailure, - creditCardPaymentNavigationUrls -} from "../../actions/wallet/wallets"; -import { GlobalState } from "../types"; - -export type CreditCardInsertion = { - startDate: Date; - hashedPan: string; // hashed PAN - blurredPan: string; // anonymized PAN - expireMonth: string; - expireYear: string; - wallet?: { - idWallet: number; - idCreditCard?: number; - brand?: string; - }; - webViewCloseReason?: AddCreditCardWebViewEndReason; - failureReason?: CreditCardFailure; - payNavigationUrls?: ReadonlyArray; - onboardingComplete: boolean; - outcomeCode?: string; - lookupId?: string; -}; - -// The state is modeled as a stack on which the last element is added at the head -export type CreditCardInsertionState = ReadonlyArray; -export const MAX_HISTORY_LENGTH = 15; - -const trimState = (state: CreditCardInsertionState) => - AR.takeLeft(MAX_HISTORY_LENGTH)([...state]); - -/** - * Given a current state (which is represented by a stack), replace the head with a given item - * - * We expect addWalletCreditCardRequest not to be dispatched twice in a row, - * so the current addWalletCreditCardRequest action refers to the last card added to the history. - * As we don't pass an idientifer for the case, we have no other method do relate the success action to its request. - * @param state the stack og items - * @param updaterFn a functor on an item which transform it according to the change needed - * - * @returns the new stack with the head changed - */ -const updateStateHead = ( - state: CreditCardInsertionState, - updaterFn: (item: CreditCardInsertion) => CreditCardInsertion -): CreditCardInsertionState => - pipe( - AR.lookup(0, [...state]), - O.map(updaterFn), - O.fold( - () => state, - updateItem => { - const [, ...tail] = state; - return trimState([updateItem, ...AR.takeRight(tail.length)(tail)]); - } - ) - ); -const INITIAL_STATE: CreditCardInsertionState = []; -/** - * card insertion follow these step: - * 1. request to add a credit card: addWalletCreditCardRequest - * 2. adding result: addWalletCreditCardSuccess or addWalletCreditCardFailure - * 3. if 2 is addWalletCreditCardSuccess -> creditCardCheckout3dsRequest - * 4. creditCardCheckout3dsSuccess - * 5. credit card payment verification outcome: payCreditCardVerificationSuccess | payCreditCardVerificationFailure - * 6. addWalletNewCreditCardSuccess completed onboarded (add + pay + checkout) - * see: https://docs.google.com/presentation/d/1nikV9vNGCFE_9Mxt31ZQuqzQXeJucMW3kdJvtjoBtC4/edit#slide=id.ga4eb40050a_0_4 - * - * step 1 adds an item into the history. - * all further steps add their info referring the item in the 0-index position - * that is the one added in the step 1 - * - * Please note that actions are meant to be executed in order and the head of the stack is the one to be updated. - */ -const reducer = ( - state: CreditCardInsertionState = INITIAL_STATE, - action: Action -): CreditCardInsertionState => { - switch (action.type) { - case getType(addWalletCreditCardRequest): - const payload = action.payload; - return pipe( - payload.creditcard?.creditCard, - O.fromNullable, - O.fold( - () => state, - c => { - const hashedPan = sha("sha256").update(c.pan).digest("hex"); - // ensure to have only a single item representing the card insertion - const newState = state.filter(c => c.hashedPan !== hashedPan); - const requestedAttempt: CreditCardInsertion = { - startDate: new Date(), - hashedPan, - blurredPan: c.pan.slice(-4), - expireMonth: c.expireMonth, - expireYear: c.expireYear, - onboardingComplete: false, - lookupId: getLookUpId() - }; - return trimState([requestedAttempt, ...newState]); - } - ) - ); - case getType(addWalletCreditCardSuccess): - return updateStateHead(state, attempt => { - const wallet = action.payload.data; - return { - ...attempt, - wallet: { - idWallet: wallet.idWallet, - idCreditCard: wallet.creditCard?.id, - brand: wallet.creditCard?.brand - } - }; - }); - - case getType(addWalletCreditCardFailure): - return updateStateHead(state, attempt => ({ - ...attempt, - failureReason: action.payload - })); - case getType(creditCardPaymentNavigationUrls): - return updateStateHead(state, attempt => ({ - ...attempt, - payNavigationUrls: action.payload - })); - - case getType(addWalletNewCreditCardSuccess): - return updateStateHead(state, attempt => ({ - ...attempt, - onboardingComplete: true - })); - case getType(addCreditCardOutcomeCode): - return updateStateHead(state, attempt => ({ - ...attempt, - outcomeCode: pipe( - action.payload, - O.getOrElse(() => "n/a") - ) - })); - case getType(addCreditCardWebViewEnd): - return updateStateHead(state, attempt => ({ - ...attempt, - webViewCloseReason: action.payload - })); - case getType(clearCache): { - return INITIAL_STATE; - } - - default: - return state; - } -}; - -export default reducer; - -const creditCardAttempts = (state: GlobalState) => - state.payments.creditCardInsertion; - -// return the list of credit card onboarding attempts -export const creditCardAttemptsSelector = createSelector( - creditCardAttempts, - (ca: CreditCardInsertionState): CreditCardInsertionState => ca -); diff --git a/ts/store/reducers/wallet/index.ts b/ts/store/reducers/wallet/index.ts deleted file mode 100644 index 4eef1c754d4..00000000000 --- a/ts/store/reducers/wallet/index.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * A reducer for the wallet, aggregating those for - * transactions and for cards - */ -import AsyncStorage from "@react-native-async-storage/async-storage"; -import { Action, combineReducers } from "redux"; -import { PersistConfig, persistReducer } from "redux-persist"; -import onboardingReducer, { - PaymentMethodOnboardingState -} from "../../../features/wallet/onboarding/store"; -import abiReducer, { - AbiState -} from "../../../features/wallet/onboarding/store/abi"; -import { DateISO8601Transform } from "../../transforms/dateISO8601Tranform"; -import { PotTransform } from "../../transforms/potTransform"; -import outcomeCodeReducer, { OutcomeCodeState } from "./outcomeCode"; -import paymentReducer, { PaymentState } from "./payment"; -import pspsByIdReducer, { PspStateById } from "./pspsById"; -import transactionsReducer, { TransactionsState } from "./transactions"; -import walletsReducer, { PersistedWalletsState, WalletsState } from "./wallets"; - -export type WalletState = Readonly<{ - transactions: TransactionsState; - wallets: PersistedWalletsState; - payment: PaymentState; - pspsById: PspStateById; - // List of banks (abi) found. This data is used atm in the bancomat onboarding - abi: AbiState; - // section used for the onboarding of a new payment method. Each payment have a sub-section - onboarding: PaymentMethodOnboardingState; - // Section used to know the outcome of the last payment, used both when the user pay or add a credit card. - lastPaymentOutcomeCode: OutcomeCodeState; -}>; - -// A custom configuration to store list of wallets -export const walletsPersistConfig: PersistConfig = { - key: "wallets", - storage: AsyncStorage, - whitelist: ["walletById"], - transforms: [DateISO8601Transform, PotTransform] -}; - -const reducer = combineReducers({ - transactions: transactionsReducer, - wallets: persistReducer( - walletsPersistConfig, - walletsReducer - ), - payment: paymentReducer, - pspsById: pspsByIdReducer, - abi: abiReducer, - onboarding: onboardingReducer, - lastPaymentOutcomeCode: outcomeCodeReducer -}); - -export default reducer; diff --git a/ts/store/reducers/wallet/outcomeCode.ts b/ts/store/reducers/wallet/outcomeCode.ts deleted file mode 100644 index 32e3d095d7a..00000000000 --- a/ts/store/reducers/wallet/outcomeCode.ts +++ /dev/null @@ -1,211 +0,0 @@ -import { flow, pipe } from "fp-ts/lib/function"; -import * as O from "fp-ts/lib/Option"; -import * as E from "fp-ts/lib/Either"; -import { getType } from "typesafe-actions"; -import doubtImage from "../../../../img/pictograms/doubt.png"; -import authorizationDenied from "../../../../img/servicesStatus/error-detail-icon.png"; -import genericError from "../../../../img/wallet/errors/generic-error-icon.png"; -import paymentUnavailableIcon from "../../../../img/wallet/errors/payment-unavailable-icon.png"; -import cardProblemOrOperationCanceled from "../../../../img/wallet/errors/payment-unknown-icon.png"; -import I18n from "../../../i18n"; -import { - OutcomeCode, - OutcomeCodes, - OutcomeCodesKey -} from "../../../types/outcomeCode"; -import { Action } from "../../actions/types"; -import { - addCreditCardOutcomeCode, - paymentOutcomeCode, - resetLastPaymentOutcomeCode -} from "../../actions/wallet/outcomeCode"; -import { GlobalState } from "../types"; - -export type OutcomeCodeState = { - outcomeCode: O.Option; -}; -const initialOutcomeCodeState: OutcomeCodeState = { - outcomeCode: O.none -}; - -// TODO: As refinement make the outcomeCodes list remote, like backend status. -// This data structure replicates the future remote data structure. -const OutcomeCodesPrintable = (): OutcomeCodes => ({ - "0": { - status: "success" - }, - "1": { - title: { - "en-EN": I18n.t("wallet.outcomeMessage.code1.title"), - "it-IT": I18n.t("wallet.outcomeMessage.code1.title") - }, - description: { - "en-EN": I18n.t("wallet.outcomeMessage.code1.description"), - "it-IT": I18n.t("wallet.outcomeMessage.code1.description") - }, - icon: genericError, - status: "errorBlocking" - }, - "2": { - title: { - "en-EN": I18n.t("wallet.outcomeMessage.code2.title"), - "it-IT": I18n.t("wallet.outcomeMessage.code2.title") - }, - description: { - "en-EN": I18n.t("wallet.outcomeMessage.code2.description"), - "it-IT": I18n.t("wallet.outcomeMessage.code2.description") - }, - icon: authorizationDenied, - status: "errorBlocking" - }, - "3": { - title: { - "en-EN": I18n.t("wallet.outcomeMessage.code3.title"), - "it-IT": I18n.t("wallet.outcomeMessage.code3.title") - }, - description: { - "en-EN": I18n.t("wallet.outcomeMessage.code3.description"), - "it-IT": I18n.t("wallet.outcomeMessage.code3.description") - }, - icon: doubtImage, - status: "errorBlocking" - }, - "4": { - title: { - "en-EN": I18n.t("wallet.outcomeMessage.code4.title"), - "it-IT": I18n.t("wallet.outcomeMessage.code4.title") - }, - description: { - "en-EN": I18n.t("wallet.outcomeMessage.code4.description"), - "it-IT": I18n.t("wallet.outcomeMessage.code4.description") - }, - icon: paymentUnavailableIcon, - status: "errorBlocking" - }, - "7": { - title: { - "en-EN": I18n.t("wallet.outcomeMessage.code7.title"), - "it-IT": I18n.t("wallet.outcomeMessage.code7.title") - }, - description: { - "en-EN": I18n.t("wallet.outcomeMessage.code7.description"), - "it-IT": I18n.t("wallet.outcomeMessage.code7.description") - }, - icon: cardProblemOrOperationCanceled, - status: "errorBlocking" - }, - "8": { - title: { - "en-EN": I18n.t("wallet.outcomeMessage.code8.title"), - "it-IT": I18n.t("wallet.outcomeMessage.code8.title") - }, - icon: cardProblemOrOperationCanceled, - status: "errorBlocking" - }, - "10": { - title: { - "en-EN": I18n.t("wallet.outcomeMessage.code10.title"), - "it-IT": I18n.t("wallet.outcomeMessage.code10.title") - }, - description: { - "en-EN": I18n.t("wallet.outcomeMessage.code10.description"), - "it-IT": I18n.t("wallet.outcomeMessage.code10.description") - }, - icon: authorizationDenied, - status: "errorBlocking" - }, - "15": { - title: { - "en-EN": I18n.t("wallet.outcomeMessage.code15.title"), - "it-IT": I18n.t("wallet.outcomeMessage.code15.title") - }, - description: { - "en-EN": I18n.t("wallet.outcomeMessage.code15.description"), - "it-IT": I18n.t("wallet.outcomeMessage.code15.description") - }, - icon: doubtImage, - status: "errorBlocking" - }, - "18": { - title: { - "en-EN": I18n.t("wallet.outcomeMessage.code18.title"), - "it-IT": I18n.t("wallet.outcomeMessage.code18.title") - }, - description: { - "en-EN": I18n.t("wallet.outcomeMessage.code18.description"), - "it-IT": I18n.t("wallet.outcomeMessage.code18.description") - }, - icon: authorizationDenied, - status: "errorBlocking" - }, - "19": { - title: { - "en-EN": I18n.t("wallet.outcomeMessage.code19.title"), - "it-IT": I18n.t("wallet.outcomeMessage.code19.title") - }, - description: { - "en-EN": I18n.t("wallet.outcomeMessage.code19.description"), - "it-IT": I18n.t("wallet.outcomeMessage.code19.description") - }, - icon: authorizationDenied, - status: "errorBlocking" - } -}); - -// This fallback is used both for unexpected code and for code that we don't want to map specifically. -const fallbackOutcomeCodes = (): OutcomeCode => ({ - title: { - "en-EN": I18n.t("wallet.outcomeMessage.fallback.title"), - "it-IT": I18n.t("wallet.outcomeMessage.fallback.title") - }, - description: { - "en-EN": I18n.t("wallet.outcomeMessage.fallback.description"), - "it-IT": I18n.t("wallet.outcomeMessage.fallback.description") - }, - icon: genericError, - status: "errorBlocking" -}); - -// This function extracts, given an Option, the outcomeCode object from the OutcomeCodesPrintable object -// that contains the list of outcome codes. -// If the string is none or if the the code is not a key of the OutcomeCodesPrintable the fallback outcome code object is returned. -export const extractOutcomeCode = ( - code: O.Option -): O.Option => - pipe( - code, - O.fold( - () => O.some(fallbackOutcomeCodes()), - flow( - OutcomeCodesKey.decode, - E.fold( - _ => O.some(fallbackOutcomeCodes()), - oCK => O.some(OutcomeCodesPrintable()[oCK]) - ) - ) - ) - ); - -export default function outcomeCodeReducer( - state: OutcomeCodeState = initialOutcomeCodeState, - action: Action -): OutcomeCodeState { - switch (action.type) { - case getType(paymentOutcomeCode): - return { outcomeCode: extractOutcomeCode(action.payload.outcome) }; - case getType(addCreditCardOutcomeCode): - return { outcomeCode: extractOutcomeCode(action.payload) }; - case getType(resetLastPaymentOutcomeCode): - return initialOutcomeCodeState; - default: - return state; - } -} - -export const lastPaymentOutcomeCodeSelector = ( - state: GlobalState -): OutcomeCodeState => state.wallet.lastPaymentOutcomeCode; - -// TODO replace with a selector when this data will be into the store -export const outcomeCodesSelector = (_: GlobalState): OutcomeCodes => - OutcomeCodesPrintable(); diff --git a/ts/store/reducers/wallet/payment.ts b/ts/store/reducers/wallet/payment.ts deleted file mode 100644 index 4408a704a5b..00000000000 --- a/ts/store/reducers/wallet/payment.ts +++ /dev/null @@ -1,336 +0,0 @@ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as O from "fp-ts/lib/Option"; -import { createSelector } from "reselect"; -import { getType } from "typesafe-actions"; -import { PspData } from "../../../../definitions/pagopa/PspData"; -import { Locales } from "../../../../locales/locales"; -import { - remoteError, - remoteLoading, - remoteReady, - remoteUndefined, - RemoteValue -} from "../../../common/model/RemoteValue"; -import { walletAddPaypalRefreshPMToken } from "../../../features/wallet/onboarding/paypal/store/actions"; -import { PaymentManagerToken } from "../../../types/pagopa"; -import { PotFromActions } from "../../../types/utils"; -import { getError } from "../../../utils/errors"; -import { Action } from "../../actions/types"; -import { - paymentAttiva, - paymentCheck, - paymentExecuteStart, - paymentIdPolling, - paymentInitializeEntrypointRoute, - paymentInitializeState, - PaymentStartOrigin, - paymentVerifica, - paymentWebViewEnd, - pspForPaymentV2, - pspSelectedForPaymentV2 -} from "../../actions/wallet/payment"; -import { - addCreditCardWebViewEnd, - refreshPMTokenWhileAddCreditCard -} from "../../actions/wallet/wallets"; -import { GlobalState } from "../types"; - -export type EntrypointRoute = Readonly<{ - name: string; - key: string; -}>; - -export type PaymentStartPayload = Readonly<{ - idWallet: number; - idPayment: string; - language: Locales; -}>; - -export type PaymentStartWebViewPayload = PaymentStartPayload & { - sessionToken: PaymentManagerToken; -}; - -// TODO: instead of keeping one single state, it would be more correct to keep -// a state for each RptId - this will make unnecessary to reset the state -// at the beginning of a new payment flow. -export type PaymentState = Readonly<{ - startOrigin?: PaymentStartOrigin; - verifica: PotFromActions< - (typeof paymentVerifica)["success"], - (typeof paymentVerifica)["failure"] - >; - attiva: PotFromActions< - (typeof paymentAttiva)["success"], - (typeof paymentAttiva)["failure"] - >; - paymentId: PotFromActions< - (typeof paymentIdPolling)["success"], - (typeof paymentIdPolling)["failure"] - >; - check: PotFromActions< - (typeof paymentCheck)["success"], - (typeof paymentCheck)["failure"] - >; - entrypointRoute?: EntrypointRoute; - // id payment, id wallet and locale (used inside paywebview) - paymentStartPayload: PaymentStartPayload | undefined; - // pm fresh session token (used inside paywebview) - pmSessionToken: RemoteValue; - pspsV2: { - psps: RemoteValue, Error>; - // the psp selected for the payment - pspSelected: PspData | undefined; - }; -}>; - -/** - * Returns the payment ID if one has been fetched so far - */ -export const getPaymentIdFromGlobalState = (state: GlobalState) => - pot.toOption(state.wallet.payment.paymentId); - -const pspV2Selector = (state: GlobalState): PaymentState["pspsV2"] => - state.wallet.payment.pspsV2; - -/** - * return the list of pspV2 - */ -export const pspV2ListSelector = createSelector( - pspV2Selector, - ( - pspsV2: PaymentState["pspsV2"] - ): RemoteValue, Error> => pspsV2.psps -); - -/** - * return the selected psp - */ -export const pspSelectedV2ListSelector = createSelector( - pspV2Selector, - (pspsV2: PaymentState["pspsV2"]): PspData | undefined => pspsV2.pspSelected -); - -export const isPaymentOngoingSelector = (state: GlobalState) => - O.isSome(getPaymentIdFromGlobalState(state)); - -export const entrypointRouteSelector = (state: GlobalState) => - state.wallet.payment.entrypointRoute; - -const paymentSelector = (state: GlobalState): PaymentState => - state.wallet.payment; - -export const paymentVerificaSelector = createSelector( - paymentSelector, - (payment: PaymentState): PaymentState["verifica"] => payment.verifica -); - -export const pmSessionTokenSelector = ( - state: GlobalState -): RemoteValue => - state.wallet.payment.pmSessionToken; - -export const paymentIdSelector = ( - state: GlobalState -): PaymentState["paymentId"] => state.wallet.payment.paymentId; - -export const paymentStartPayloadSelector = ( - state: GlobalState -): PaymentStartPayload | undefined => state.wallet.payment.paymentStartPayload; - -export const paymentStartOriginSelector = createSelector( - paymentSelector, - payment => payment.startOrigin -); - -const PAYMENT_INITIAL_STATE: PaymentState = { - verifica: pot.none, - attiva: pot.none, - paymentId: pot.none, - check: pot.none, - entrypointRoute: undefined, - paymentStartPayload: undefined, - pmSessionToken: remoteUndefined, - pspsV2: { - psps: remoteUndefined, - pspSelected: undefined - } -}; - -/** - * Reducer for actions that show the payment summary - */ -// eslint-disable-next-line complexity -const reducer = ( - state: PaymentState = PAYMENT_INITIAL_STATE, - action: Action -): PaymentState => { - switch (action.type) { - // start a new payment from scratch - case getType(paymentInitializeState): - return PAYMENT_INITIAL_STATE; - // track the route whence the payment started - case getType(paymentInitializeEntrypointRoute): - return { - ...state, - entrypointRoute: action.payload - }; - // - // verifica - // - case getType(paymentVerifica.request): - return { - // a verifica operation will generate a new codice contesto pagamento - // effectively starting a new payment session, thus we also invalidate - // the rest of the payment state - ...PAYMENT_INITIAL_STATE, - startOrigin: action.payload.startOrigin, - entrypointRoute: state.entrypointRoute, - verifica: pot.noneLoading - }; - case getType(paymentVerifica.success): - return { - ...state, - verifica: pot.some(action.payload) - }; - case getType(paymentVerifica.failure): - return { - ...state, - verifica: pot.noneError(action.payload) - }; - - // - // attiva - // - case getType(paymentAttiva.request): - return { - ...state, - attiva: pot.noneLoading - }; - case getType(paymentAttiva.success): - return { - ...state, - attiva: pot.some(action.payload) - }; - case getType(paymentAttiva.failure): - return { - ...state, - attiva: pot.noneError(action.payload) - }; - - // - // payment ID polling - // - case getType(paymentIdPolling.request): - return { - ...state, - paymentId: pot.noneLoading - }; - case getType(paymentIdPolling.success): - return { - ...state, - paymentId: pot.some(action.payload) - }; - case getType(paymentIdPolling.failure): - return { - ...state, - paymentId: pot.noneError(action.payload) - }; - - // - // check payment - // - case getType(paymentCheck.request): - return { - ...state, - check: pot.noneLoading - }; - case getType(paymentCheck.success): - return { - ...state, - check: pot.some(true) - }; - case getType(paymentCheck.failure): - return { - ...state, - check: pot.noneError(action.payload) - }; - // start payment or refresh token while add credit card - // - case getType(paymentExecuteStart.request): - return { - ...state, - paymentStartPayload: action.payload, - pmSessionToken: remoteLoading - }; - case getType(walletAddPaypalRefreshPMToken.request): - case getType(refreshPMTokenWhileAddCreditCard.request): - return { - ...state, - pmSessionToken: remoteLoading - }; - case getType(walletAddPaypalRefreshPMToken.success): - case getType(refreshPMTokenWhileAddCreditCard.success): - case getType(paymentExecuteStart.success): - return { - ...state, - pmSessionToken: remoteReady(action.payload) - }; - case getType(walletAddPaypalRefreshPMToken.failure): - case getType(refreshPMTokenWhileAddCreditCard.failure): - case getType(paymentExecuteStart.failure): - return { - ...state, - pmSessionToken: remoteError(action.payload) - }; - - // end payment web view - reset data about payment - case getType(paymentWebViewEnd): - return { - ...state, - paymentStartPayload: PAYMENT_INITIAL_STATE.paymentStartPayload, - pmSessionToken: PAYMENT_INITIAL_STATE.pmSessionToken - }; - // end add credit card web view - reset pm session token data - case getType(addCreditCardWebViewEnd): - return { - ...state, - pmSessionToken: PAYMENT_INITIAL_STATE.pmSessionToken - }; - case getType(pspForPaymentV2.request): - return { - ...state, - pspsV2: { - ...state.pspsV2, - psps: remoteLoading, - pspSelected: undefined - } - }; - case getType(pspForPaymentV2.success): - return { - ...state, - pspsV2: { - ...state.pspsV2, - psps: remoteReady(action.payload) - } - }; - case getType(pspForPaymentV2.failure): - return { - ...state, - pspsV2: { - ...state.pspsV2, - psps: remoteError(getError(action.payload)) - } - }; - case getType(pspSelectedForPaymentV2): - return { - ...state, - pspsV2: { - ...state.pspsV2, - pspSelected: action.payload - } - }; - } - return state; -}; - -export default reducer; diff --git a/ts/store/reducers/wallet/pspsById.ts b/ts/store/reducers/wallet/pspsById.ts deleted file mode 100644 index 3f6c4185d44..00000000000 --- a/ts/store/reducers/wallet/pspsById.ts +++ /dev/null @@ -1,74 +0,0 @@ -/** - * A reducer to store the psps by id - */ - -import * as pot from "@pagopa/ts-commons/lib/pot"; -import { getType } from "typesafe-actions"; - -import { Psp } from "../../../types/pagopa"; -import { Action } from "../../actions/types"; -import { fetchPsp } from "../../actions/wallet/transactions"; -import { GlobalState } from "../types"; - -export type PspState = { - psp: pot.Pot; -}; - -// An object containing Psp keyed by id -export type PspStateById = Readonly<{ - [key: string]: PspState | undefined; -}>; - -const INITIAL_STATE: PspStateById = {}; - -const reducer = ( - state: PspStateById = INITIAL_STATE, - action: Action -): PspStateById => { - switch (action.type) { - case getType(fetchPsp.request): - return { - ...state, - [action.payload.idPsp]: { - psp: pot.noneLoading - } - }; - - case getType(fetchPsp.success): { - const id = action.payload.idPsp; - const prevState = state[id]; - if (prevState === undefined) { - // we can't deal with a success without a request - return state; - } - return { - ...state, - [id]: { ...prevState, psp: pot.some(action.payload.psp) } - }; - } - case getType(fetchPsp.failure): { - const id = action.payload.idPsp; - const prevState = state[id]; - if (prevState === undefined) { - // we can't deal with a failure without a request - return state; - } - return { - ...state, - [id]: { - ...prevState, - psp: pot.noneError(action.payload.error) - } - }; - } - - default: - return state; - } -}; - -// Selectors -export const pspStateByIdSelector = (id: string) => (state: GlobalState) => - state.wallet.pspsById[id]; - -export default reducer; diff --git a/ts/store/reducers/wallet/transactions.ts b/ts/store/reducers/wallet/transactions.ts deleted file mode 100644 index 9fa8d48377a..00000000000 --- a/ts/store/reducers/wallet/transactions.ts +++ /dev/null @@ -1,160 +0,0 @@ -/** - * Reducers, states, selectors and guards for the transactions - */ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import * as O from "fp-ts/lib/Option"; -import { values } from "lodash"; -import { createSelector } from "reselect"; -import { getType } from "typesafe-actions"; - -import { pipe } from "fp-ts/lib/function"; -import { isSuccessTransaction, Transaction } from "../../../types/pagopa"; -import { clearCache } from "../../actions/profile"; -import { Action } from "../../actions/types"; -import { - clearTransactions, - fetchTransactionsFailure, - fetchTransactionsRequest, - fetchTransactionsRequestWithExpBackoff, - fetchTransactionsSuccess -} from "../../actions/wallet/transactions"; -import { IndexedById, toIndexed } from "../../helpers/indexer"; -import { GlobalState } from "../types"; - -/** - * The transactions selector will truncate the list at this length - */ -const MAX_TRANSACTIONS_IN_LIST = 500; - -export type TransactionsState = Readonly<{ - transactions: pot.Pot, Error>; - total: pot.Pot; -}>; - -const TRANSACTIONS_INITIAL_STATE: TransactionsState = { - transactions: pot.none, - total: pot.none -}; - -const potTransactions = (state: GlobalState) => - state.wallet.transactions.transactions; - -// selectors -export const getTransactions = createSelector( - potTransactions, - potTransactions => - pot.map( - potTransactions, - txs => - values(txs).filter(_ => - isSuccessTransaction(_) - ) as ReadonlyArray - ) -); - -export const latestTransactionsSelector = createSelector( - getTransactions, - potTransactions => - pot.map( - potTransactions, - transactions => - [...transactions] - .sort((a, b) => - // FIXME: code here is checking for NaN assuming creation dates may - // be undefined, but since we override the pagoPA Wallet - // type to force creation dates to always be defined and we - // use that new type for parsing responses, we ignore - // wallets with undefined creation dates... so the check - // is unnecessary. - - isNaN(a.created as any) || isNaN(b.created as any) - ? -1 // define behavior for undefined creation dates (pagoPA allows these to be undefined) - : b.created.toISOString().localeCompare(a.created.toISOString()) - ) - .filter(t => t.statusMessage !== "rifiutato") - .slice(0, MAX_TRANSACTIONS_IN_LIST) // WIP no magic numbers - ) -); - -// return true if there are more transactions to load -export const areMoreTransactionsAvailable = (state: GlobalState): boolean => - pot.getOrElse( - pot.map(state.wallet.transactions.transactions, transactions => - pot.getOrElse( - pot.map( - state.wallet.transactions.total, - t => - Object.keys(transactions).length < - Math.min(t, MAX_TRANSACTIONS_IN_LIST) - ), - false - ) - ), - false - ); - -// return the number of transactions loaded -// note transactions loaded should be different (in cardinality) from ones displayed since we operate -// a filter over them (see latestTransactionsSelector) -export const getTransactionsLoadedLength = (state: GlobalState) => - pot.getOrElse( - pot.map( - state.wallet.transactions.transactions, - txs => Object.keys(txs).length - ), - 0 - ); - -// reducer -const reducer = ( - state: TransactionsState = TRANSACTIONS_INITIAL_STATE, - action: Action -): TransactionsState => { - switch (action.type) { - case getType(fetchTransactionsRequestWithExpBackoff): - case getType(fetchTransactionsRequest): - return { - ...state, - transactions: pot.toLoading(state.transactions), - total: pot.toLoading(state.total) - }; - - case getType(fetchTransactionsSuccess): - const prevTransactions = pot.getOrElse>( - state.transactions, - {} - ); - const total = { - ...prevTransactions, - ...toIndexed(action.payload.data, _ => _.id) - }; - return { - ...state, - transactions: pot.some(total), - total: pot.some( - pipe( - action.payload.total, - O.fold( - () => 0, - s => s - ) - ) - ) - }; - - case getType(fetchTransactionsFailure): - return { - ...state, - transactions: pot.toError(state.transactions, action.payload), - total: pot.toError(state.total, action.payload) - }; - case getType(clearTransactions): - case getType(clearCache): - return TRANSACTIONS_INITIAL_STATE; - - default: - return state; - } -}; - -export default reducer; diff --git a/ts/store/reducers/wallet/wallets.ts b/ts/store/reducers/wallet/wallets.ts deleted file mode 100644 index 81c04ec7a3a..00000000000 --- a/ts/store/reducers/wallet/wallets.ts +++ /dev/null @@ -1,565 +0,0 @@ -/** - * Reducers, states, selectors and guards for the cards - */ -import * as pot from "@pagopa/ts-commons/lib/pot"; -import _, { values } from "lodash"; -import { PersistPartial } from "redux-persist"; -import { createSelector } from "reselect"; -import { getType, isOfType } from "typesafe-actions"; -import { WalletTypeEnum } from "../../../../definitions/pagopa/WalletV2"; -import { - RemoteValue, - getValueOrElse, - remoteError, - remoteLoading, - remoteReady, - remoteUndefined -} from "../../../common/model/RemoteValue"; -import { abiSelector } from "../../../features/wallet/onboarding/store/abi"; -import { - BPayPaymentMethod, - BancomatPaymentMethod, - CreditCardPaymentMethod, - PayPalPaymentMethod, - PaymentMethod, - RawCreditCardPaymentMethod, - RawPaymentMethod, - Wallet, - isBPay, - isBancomat, - isCreditCard, - isPayPal, - isRawCreditCard -} from "../../../types/pagopa"; - -import { TypeEnum } from "../../../../definitions/pagopa/walletv2/CardInfo"; -import { PotFromActions } from "../../../types/utils"; -import { getErrorFromNetworkError } from "../../../utils/errors"; -import { isDefined } from "../../../utils/guards"; -import { enhancePaymentMethod } from "../../../utils/paymentMethod"; -import { hasPaymentFeature } from "../../../utils/paymentMethodCapabilities"; -import { sessionExpired, sessionInvalid } from "../../actions/authentication"; -import { clearCache } from "../../actions/profile"; -import { Action } from "../../actions/types"; -import { - DeleteAllByFunctionError, - DeleteAllByFunctionSuccess, - deleteAllPaymentMethodsByFunction -} from "../../actions/wallet/delete"; -import { paymentUpdateWalletPsp } from "../../actions/wallet/payment"; -import { - addWalletCreditCardFailure, - addWalletCreditCardInit, - addWalletCreditCardRequest, - addWalletCreditCardSuccess, - addWalletCreditCardWithBackoffRetryRequest, - deleteWalletFailure, - deleteWalletRequest, - deleteWalletSuccess, - fetchWalletsFailure, - fetchWalletsRequest, - fetchWalletsRequestWithExpBackoff, - fetchWalletsSuccess, - setFavouriteWalletFailure, - setFavouriteWalletRequest, - setFavouriteWalletSuccess, - updatePaymentStatus -} from "../../actions/wallet/wallets"; -import { IndexedById, toIndexed } from "../../helpers/indexer"; -import { GlobalState } from "../types"; - -export type WalletsState = Readonly<{ - walletById: PotFromActions, typeof fetchWalletsFailure>; - creditCardAddWallet: PotFromActions< - typeof addWalletCreditCardSuccess, - typeof addWalletCreditCardFailure - >; - deleteAllByFunction: RemoteValue< - Pick, - DeleteAllByFunctionError - >; - updatingFavouriteWallet: pot.Pot; -}>; - -export type PersistedWalletsState = WalletsState & PersistPartial; - -export const WALLETS_INITIAL_STATE: WalletsState = { - walletById: pot.none, - creditCardAddWallet: pot.none, - deleteAllByFunction: remoteUndefined, - // holds the state of updating a wallet as favourite - updatingFavouriteWallet: pot.none -}; - -// Selectors -export const getAllWallets = (state: GlobalState): WalletsState => - state.wallet.wallets; - -export const getWalletsById = (state: GlobalState) => - state.wallet.wallets.walletById; - -const getWallets = createSelector( - getWalletsById, - (potWx): pot.Pot, Error> => - pot.map(potWx, wx => values(wx).filter(isDefined)) -); - -// return the wallet number that the user is setting as a favourite -export const updatingFavouriteWalletSelector = createSelector( - getAllWallets, - (wallets: WalletsState): pot.Pot => - wallets.updatingFavouriteWallet -); - -// return a pot with the id of the favorite wallet. none otherwise -export const getFavoriteWalletId = createSelector( - getWallets, - (potWx: ReturnType): pot.Pot => - pot.mapNullable( - potWx, - wx => values(wx).find(w => w.favourite === true)?.idWallet - ) -); - -export const getFavoriteWallet = createSelector( - [getFavoriteWalletId, getWalletsById], - ( - favoriteWalletID: pot.Pot, - walletsById: WalletsState["walletById"] - ): pot.Pot => - pot.mapNullable(favoriteWalletID, walletId => - pot.toUndefined(pot.map(walletsById, wx => wx[walletId])) - ) -); - -/** - * Select a payment method using the id (walletId) and extracts the payment status (is the payment enabled on this payment method?) - * Return pot.some(undefined) if no payment methods with the id are found - */ -export const getPaymentStatusById = createSelector( - [getWalletsById, (_: GlobalState, id: number) => id], - (potWalletsById, id): pot.Pot => - pot.map( - potWalletsById, - walletsById => walletsById[id]?.paymentMethod?.pagoPA - ) -); - -/** - * @deprecated Using API v2 this selector is deprecated - * If you are searching for credit card or bancomat use {@link creditCardWalletV1Selector} - * - {@link pagoPaCreditCardWalletV1Selector} {@link bancomatListSelector} instead - */ -export const walletsSelector = createSelector( - getWallets, - // define whether an order among cards needs to be established - // (e.g. by insertion date, expiration date, ...) - ( - potWallets: ReturnType - ): pot.Pot, Error> => - pot.map(potWallets, wallets => - [...wallets].sort( - // sort by date, descending - // if both dates are undefined -> 0 - // if either is undefined, it is considered as used "infinitely" long ago - // (i.e. the non-undefined one is considered as used more recently) - (a, b) => - -(a.lastUsage === undefined - ? b.lastUsage === undefined - ? 0 - : -1 - : b.lastUsage === undefined - ? 1 - : a.lastUsage.getTime() - b.lastUsage.getTime()) - ) - ) -); - -/** - * Get a list of the payment methods in the wallet - */ -export const paymentMethodsSelector = createSelector( - [walletsSelector, abiSelector], - (potWallet, remoteAbi): pot.Pot, Error> => - pot.map(potWallet, wallets => - wallets - .map(w => - w.paymentMethod - ? enhancePaymentMethod( - w.paymentMethod, - getValueOrElse(remoteAbi, {}) - ) - : undefined - ) - .filter(isDefined) - ) -); - -// return those payment methods that can pay with pagoPA -export const withPaymentFeatureSelector = createSelector( - paymentMethodsSelector, - ( - potPm: ReturnType - ): ReadonlyArray => - pot.getOrElse( - pot.map(potPm, pms => pms.filter(hasPaymentFeature)), - [] - ) -); - -/** - * return true if the payment method is visible in the wallet (the onboardingChannel - * is IO or WISP) or if the onboardingChannel is undefined. - * We choose to show cards with onboardingChannel undefined to ensure backward compatibility - * with cards inserted before the field was added. - * Explicitly handling the undefined is a conservative choice, as the field should be an enum (IO, WISP, EXT) - * but it is a string and therefore we cannot be sure that incorrect data will not arrive. - * - * @param pm - */ -const visibleOnboardingChannels = ["IO", "WISP", undefined]; -export const isVisibleInWallet = (pm: PaymentMethod): boolean => - visibleOnboardingChannels.some( - oc => oc === pm.onboardingChannel?.toUpperCase().trim() - ); - -// return those payment methods that have BPD as enabled function and are visible in Wallet - -export const rawCreditCardListSelector = createSelector( - [paymentMethodsSelector], - ( - paymentMethods: pot.Pot, Error> - ): ReadonlyArray => - pot.getOrElse( - pot.map(paymentMethods, w => w.filter(isRawCreditCard)), - [] - ) -); - -/** - * Return a bancomat list enhanced with the additional abi information in the wallet - */ -export const bancomatListSelector = createSelector( - [paymentMethodsSelector], - (paymentMethodPot): pot.Pot, Error> => - pot.map(paymentMethodPot, paymentMethod => paymentMethod.filter(isBancomat)) -); - -/** - * Return a credit card list in the wallet - */ -export const creditCardListSelector = createSelector( - [paymentMethodsSelector], - (paymentMethodPot): pot.Pot, Error> => - pot.map(paymentMethodPot, paymentMethod => - paymentMethod.filter(isCreditCard) - ) -); - -/** - * from a given ID return the relative {@link PaymentMethod} or undefined - */ -export const paymentMethodByIdSelector = createSelector( - [paymentMethodsSelector, (_: GlobalState, id: number) => id], - (potWallets, id): PaymentMethod | undefined => - pot.toUndefined( - pot.map(potWallets, wallets => wallets.find(cc => cc.idWallet === id)) - ) -); - -/** - * Return a {@link CreditCardPaymentMethod} by walletId - * Return undefined if not in list - */ -export const creditCardByIdSelector = createSelector( - [creditCardListSelector, (_: GlobalState, id: number) => id], - (potCreditCardList, id): CreditCardPaymentMethod | undefined => - pot.toUndefined( - pot.map(potCreditCardList, creditCardList => - creditCardList.find(cc => cc.idWallet === id) - ) - ) -); - -/** - * Return the paypal list in the wallet - */ -export const paypalListSelector = createSelector( - [paymentMethodsSelector], - (paymentMethodPot): pot.Pot, Error> => - pot.map(paymentMethodPot, paymentMethod => paymentMethod.filter(isPayPal)) -); - -/** - * Return the paypal (only 1 can be in the wallet) payment method - */ -export const paypalSelector = createSelector( - [paymentMethodsSelector], - (paymentMethodPot): pot.Pot => - pot.mapNullable(paymentMethodPot, paymentMethod => - paymentMethod.find(isPayPal) - ) -); - -/** - * Return a BPay list in the wallet - */ -export const bPayListSelector = createSelector( - [paymentMethodsSelector], - (paymentMethodPot): pot.Pot, Error> => - pot.map(paymentMethodPot, paymentMethod => paymentMethod.filter(isBPay)) -); - -/** - * Return all the payment methods visible in wallet screen. - * First return the payment method that have pagoPA capability - */ -export const paymentMethodListVisibleInWalletSelector = createSelector( - [paymentMethodsSelector], - (paymentMethodsPot): pot.Pot, Error> => - pot.map(paymentMethodsPot, paymentMethodList => - _.sortBy(paymentMethodList.filter(isVisibleInWallet), pm => - hasPaymentFeature(pm) ? -1 : pm.idWallet - ) - ) -); - -/** - * Return a credit card list visible in the wallet - */ -export const creditCardListVisibleInWalletSelector = createSelector( - [creditCardListSelector], - (creditCardListPot): pot.Pot, Error> => - pot.map(creditCardListPot, creditCardList => - creditCardList.filter(isVisibleInWallet) - ) -); - -/** - * Return a bancomat list visible in the wallet - */ -export const bancomatListVisibleInWalletSelector = createSelector( - [bancomatListSelector], - (bancomatListPot): pot.Pot, Error> => - pot.map(bancomatListPot, bancomatList => - bancomatList.filter(isVisibleInWallet) - ) -); - -/** - * Return a BPay list visible in the wallet - */ -export const bPayListVisibleInWalletSelector = createSelector( - [bPayListSelector], - (bPayListPot): pot.Pot, Error> => - pot.map(bPayListPot, bPayList => bPayList.filter(isVisibleInWallet)) -); - -/** - * Return a CoBadge list visible in the wallet - */ -export const cobadgeListVisibleInWalletSelector = createSelector( - [creditCardListVisibleInWalletSelector], - (creditCardListPot): pot.Pot, Error> => - pot.map(creditCardListPot, creditCardList => - creditCardList.filter( - cc => - cc.pagoPA === false && - cc.info.issuerAbiCode !== undefined && - cc.info.type !== TypeEnum.PRV - ) - ) -); - -/** - * Get the list of credit cards using the info contained in v2 (Walletv2) to distinguish - */ -export const creditCardWalletV1Selector = createSelector( - [walletsSelector], - ( - potWallet: pot.Pot, Error> - ): pot.Pot, Error> => - pot.map(potWallet, wallets => - wallets.filter( - w => - w.paymentMethod && w.paymentMethod.walletType === WalletTypeEnum.Card - ) - ) -); - -/** - * Get the list of credit cards usable as payment instrument in pagoPA - * using the info contained in v2 (Walletv2) to distinguish - */ -export const pagoPaCreditCardWalletV1Selector = createSelector( - [creditCardWalletV1Selector], - ( - potCreditCards: pot.Pot, Error> - ): pot.Pot, Error> => - pot.map(potCreditCards, wallets => - wallets.filter(w => w.paymentMethod?.pagoPA === true) - ) -); - -export const deleteAllPaymentMethodsByFunctionSelector = ( - state: GlobalState -): WalletsState["deleteAllByFunction"] => - state.wallet.wallets.deleteAllByFunction; - -// reducer -// eslint-disable-next-line complexity -const reducer = ( - state: WalletsState = WALLETS_INITIAL_STATE, - action: Action -): WalletsState => { - switch (action.type) { - // - // delete all payments method by function - // - case getType(deleteAllPaymentMethodsByFunction.request): - return { ...state, deleteAllByFunction: remoteLoading }; - case getType(deleteAllPaymentMethodsByFunction.success): - return { - ...state, - walletById: pot.some( - toIndexed(action.payload.wallets, _ => _.idWallet) - ), - deleteAllByFunction: remoteReady({ - deletedMethodsCount: action.payload.deletedMethodsCount - }) - }; - case getType(deleteAllPaymentMethodsByFunction.failure): - return { ...state, deleteAllByFunction: remoteError(action.payload) }; - // - // fetch wallets - // - case getType(fetchWalletsRequestWithExpBackoff): - case getType(fetchWalletsRequest): - case getType(paymentUpdateWalletPsp.request): - case getType(updatePaymentStatus.request): - case getType(deleteWalletRequest): - return { - ...state, - walletById: pot.toLoading(state.walletById) - }; - - case getType(updatePaymentStatus.success): - const currentWallets = pot.getOrElse(state.walletById, {}); - // update the received wallet - return { - ...state, - walletById: pot.some({ - ...currentWallets, - [action.payload.idWallet]: action.payload - }) - }; - case getType(fetchWalletsSuccess): - case getType(paymentUpdateWalletPsp.success): - case getType(deleteWalletSuccess): - const wallets = isOfType(getType(paymentUpdateWalletPsp.success), action) - ? action.payload.wallets - : action.payload; - return { - ...state, - walletById: pot.some(toIndexed(wallets, _ => _.idWallet)) - }; - - case getType(fetchWalletsFailure): - case getType(deleteWalletFailure): - case getType(paymentUpdateWalletPsp.failure): - return { - ...state, - walletById: pot.toError(state.walletById, action.payload) - }; - case getType(updatePaymentStatus.failure): - return { - ...state, - walletById: pot.toError( - state.walletById, - getErrorFromNetworkError(action.payload) - ) - }; - - // - // set favourite wallet - // - case getType(setFavouriteWalletRequest): - return { - ...state, - updatingFavouriteWallet: pot.toUpdating( - state.updatingFavouriteWallet, - action.payload - ) - }; - case getType(setFavouriteWalletFailure): - return { - ...state, - updatingFavouriteWallet: pot.toError( - state.updatingFavouriteWallet, - action.payload - ) - }; - case getType(setFavouriteWalletSuccess): - // On success, we update both the favourite wallet ID and the - // corresponding Wallet in walletById. - return { - ...state, - updatingFavouriteWallet: pot.some(action.payload.idWallet), - walletById: pot.map(state.walletById, walletsById => - _.keys(walletsById).reduce>( - (acc, val) => - ({ - ...acc, - [val]: { - ...walletsById[val], - favourite: - action.payload.idWallet === walletsById[val]?.idWallet - } - } as IndexedById), - {} - ) - ) - }; - - // - // add credit card - // - - case getType(addWalletCreditCardInit): - return { - ...state, - creditCardAddWallet: pot.none - }; - - case getType(addWalletCreditCardWithBackoffRetryRequest): - case getType(addWalletCreditCardRequest): - return { - ...state, - creditCardAddWallet: pot.noneLoading - }; - - case getType(addWalletCreditCardSuccess): - return { - ...state, - creditCardAddWallet: pot.some(action.payload) - }; - - case getType(addWalletCreditCardFailure): - return { - ...state, - creditCardAddWallet: pot.noneError(action.payload) - }; - case getType(sessionExpired): - case getType(sessionInvalid): - case getType(clearCache): - return { - ...state, - walletById: WALLETS_INITIAL_STATE.walletById - }; - - default: - return state; - } -}; - -export default reducer; diff --git a/ts/utils/__tests__/input.test.ts b/ts/utils/__tests__/input.test.ts index 6ea95c4e306..b27fad8d293 100644 --- a/ts/utils/__tests__/input.test.ts +++ b/ts/utils/__tests__/input.test.ts @@ -1,7 +1,5 @@ import * as O from "fp-ts/lib/Option"; import * as E from "fp-ts/lib/Either"; -import MockDate from "mockdate"; -import { testableAddCardScreen } from "../../screens/wallet/AddCardScreen"; import { CreditCardCVC, CreditCardExpirationMonth, @@ -129,69 +127,6 @@ describe("CreditCardExpirationYear", () => { }); }); -describe("CreditCardExpirationDate", () => { - MockDate.set("2020-01-01"); - it("should be false since it is not expired", () => { - expect( - testableAddCardScreen?.isCreditCardDateExpiredOrInvalid!(O.some("03/20")) - ).toEqual(O.some(false)); - }); - - it("should be true since it is expired", () => { - expect( - testableAddCardScreen?.isCreditCardDateExpiredOrInvalid!(O.some("12/19")) - ).toEqual(O.some(true)); - }); - - it("should be true since it is not in a valid format", () => { - expect( - testableAddCardScreen?.isCreditCardDateExpiredOrInvalid!(O.some("aa/bb")) - ).toEqual(O.some(true)); - }); - - it("should be true since it is not in a valid format", () => { - expect( - testableAddCardScreen?.isCreditCardDateExpiredOrInvalid!(O.some("1/b")) - ).toEqual(O.some(true)); - }); - - it("should be true since it is not in a valid format", () => { - expect( - testableAddCardScreen?.isCreditCardDateExpiredOrInvalid!(O.some("3/21")) - ).toEqual(O.some(true)); - }); - - it("should be true since it is not in a valid format", () => { - expect( - testableAddCardScreen?.isCreditCardDateExpiredOrInvalid!(O.some("03/2")) - ).toEqual(O.some(true)); - }); - - it("should be true since it is not in a valid format", () => { - expect( - testableAddCardScreen?.isCreditCardDateExpiredOrInvalid!(O.some("a/27")) - ).toEqual(O.some(true)); - }); - - it("should be true since it is not in a valid format", () => { - expect( - testableAddCardScreen?.isCreditCardDateExpiredOrInvalid!(O.some("01/")) - ).toEqual(O.some(true)); - }); - - it("should be none since it is incomplete", () => { - expect( - testableAddCardScreen?.isCreditCardDateExpiredOrInvalid!(O.some("01")) - ).toEqual(O.none); - }); - - it("should be none", () => { - expect( - testableAddCardScreen?.isCreditCardDateExpiredOrInvalid!(O.none) - ).toEqual(O.none); - }); -}); - describe("CreditCardCVC", () => { const validCVCs: ReadonlyArray = ["000", "1234"]; diff --git a/ts/utils/__tests__/walletv2.ts b/ts/utils/__tests__/walletv2.ts deleted file mode 100644 index 69d67b28d20..00000000000 --- a/ts/utils/__tests__/walletv2.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { - isRawBancomat, - isRawBPay, - isRawCreditCard, - PatchedWalletV2 -} from "../../types/pagopa"; -import { - walletsV2_1, - walletsV2_2, - walletsV2_3 -} from "../../store/reducers/wallet/__mocks__/wallets"; -import { convertWalletV2toWalletV1 } from "../walletv2"; - -describe("convert and recognize 2 bancomat and 1 credit card", () => { - const wallets = (walletsV2_1.data as ReadonlyArray).map( - convertWalletV2toWalletV1 - ); - it("should convert walletv2 to walletv1", () => { - expect(wallets.length).toEqual(3); - }); - it("should recognize credit card", () => { - expect( - wallets.filter(w => isRawCreditCard(w.paymentMethod)).length - ).toEqual(1); - }); - it("should recognize bancomat", () => { - expect(wallets.filter(w => isRawBancomat(w.paymentMethod)).length).toEqual( - 2 - ); - }); -}); - -describe("convert and recognize 1 bancomat and 1 credit card", () => { - const wallets = (walletsV2_2.data as ReadonlyArray).map( - convertWalletV2toWalletV1 - ); - it("should convert walletv2 to walletv1", () => { - expect(wallets.length).toEqual(2); - }); - // eslint-disable-next-line sonarjs/no-identical-functions - it("should recognize credit card", () => { - expect( - wallets.filter(w => isRawCreditCard(w.paymentMethod)).length - ).toEqual(1); - }); - it("should recognize bancomat", () => { - expect(wallets.filter(w => isRawBancomat(w.paymentMethod)).length).toEqual( - 1 - ); - }); -}); - -describe("convert and recognize 1 bancomat, 1 bancomat pay, 1 credit card", () => { - const wallets = (walletsV2_3.data as ReadonlyArray).map( - convertWalletV2toWalletV1 - ); - it("should convert walletv2 to walletv1", () => { - expect(wallets.length).toEqual(3); - }); - // eslint-disable-next-line sonarjs/no-identical-functions - it("should recognize credit card", () => { - expect( - wallets.filter(w => isRawCreditCard(w.paymentMethod)).length - ).toEqual(1); - }); - // eslint-disable-next-line sonarjs/no-identical-functions - it("should recognize bancomat", () => { - expect(wallets.filter(w => isRawBancomat(w.paymentMethod)).length).toEqual( - 1 - ); - }); - - it("should recognize bancomatPay", () => { - expect(wallets.filter(w => isRawBPay(w.paymentMethod)).length).toEqual(1); - }); -}); diff --git a/ts/utils/internalLink.ts b/ts/utils/internalLink.ts index 311a4dc6b1d..e3b2e4d87f0 100644 --- a/ts/utils/internalLink.ts +++ b/ts/utils/internalLink.ts @@ -34,10 +34,7 @@ const routesToNavigationLink: Record = { [SERVICES_ROUTES.SERVICES_HOME]: "/main/services", [ROUTES.PROFILE_MAIN]: "/main/profile", [ROUTES.PROFILE_PRIVACY]: "/profile/privacy", - [ROUTES.PROFILE_PRIVACY_MAIN]: "/profile/privacy-main", - [ROUTES.PAYMENTS_HISTORY_SCREEN]: "/wallet/payments-history", - [ROUTES.CREDIT_CARD_ONBOARDING_ATTEMPTS_SCREEN]: - "/wallet/card-onboarding-attempts" + [ROUTES.PROFILE_PRIVACY_MAIN]: "/profile/privacy-main" }; const legacyRoutesToNavigationLink: Record = { diff --git a/ts/utils/payment.ts b/ts/utils/payment.ts index 3065ebf2461..6a407f8d2c4 100644 --- a/ts/utils/payment.ts +++ b/ts/utils/payment.ts @@ -16,12 +16,6 @@ import { } from "../../definitions/backend/PaymentProblemJson"; import { PspData } from "../../definitions/pagopa/PspData"; import I18n from "../i18n"; -import { - paymentAttiva, - paymentIdPolling, - paymentVerifica -} from "../store/actions/wallet/payment"; -import { PaymentHistory } from "../store/reducers/payments/history"; import { OutcomeCode, OutcomeCodes, @@ -34,7 +28,6 @@ import { Transaction, Wallet } from "../types/pagopa"; -import { PayloadForAction } from "../types/utils"; import { getTranslatedShortNumericMonthYear } from "./dates"; import { getFullLocale, getLocalePrimaryWithFallback } from "./locale"; import { maybeInnerProperty } from "./options"; @@ -323,9 +316,6 @@ export const getErrorDescriptionV2 = ( }); }; -export const getPaymentHistoryDetails = (payment: PaymentHistory): string => - JSON.stringify({ payment }); - // return the transaction fee it transaction is defined and its fee property too export const getTransactionFee = ( transaction?: Transaction, @@ -457,24 +447,6 @@ export const getPickPaymentMethodDescription = ( ); }; -export const isDuplicatedPayment = ( - error: O.Option< - PayloadForAction< - | (typeof paymentVerifica)["failure"] - | (typeof paymentAttiva)["failure"] - | (typeof paymentIdPolling)["failure"] - > - > -) => - pipe( - error, - O.exists( - detail => - detail === "PAA_PAGAMENTO_DUPLICATO" || - detail === "PPT_PAGAMENTO_DUPLICATO" - ) - ); - export const isPaidPaymentFromDetailV2Enum = (details: Detail_v2Enum) => details === Detail_v2Enum.PAA_PAGAMENTO_DUPLICATO || details === Detail_v2Enum.PPT_PAGAMENTO_DUPLICATO; diff --git a/ts/utils/paymentMethod.ts b/ts/utils/paymentMethod.ts deleted file mode 100644 index 99d62aec9de..00000000000 --- a/ts/utils/paymentMethod.ts +++ /dev/null @@ -1,271 +0,0 @@ -import * as E from "fp-ts/lib/Either"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; -import { Alert, ImageSourcePropType } from "react-native"; -import { Abi } from "../../definitions/pagopa/walletv2/Abi"; -import { - Card, - ValidityStateEnum -} from "../../definitions/pagopa/walletv2/Card"; -import { - PaymentInstrument, - ValidityStatusEnum -} from "../../definitions/pagopa/walletv2/PaymentInstrument"; -import bPayImage from "../../img/wallet/cards-icons/bPay.png"; -import pagoBancomatImage from "../../img/wallet/cards-icons/pagobancomat.png"; -import paypalImage from "../../img/wallet/cards-icons/paypal.png"; -import { - cardIcons, - getCardIconFromBrandLogo -} from "../components/wallet/card/Logo"; -import { contentRepoUrl } from "../config"; -import I18n from "../i18n"; -import { mixpanelTrack } from "../mixpanel"; -import { IndexedById } from "../store/helpers/indexer"; -import { - BPayPaymentMethod, - BancomatPaymentMethod, - CreditCardPaymentMethod, - PaymentMethod, - RawBPayPaymentMethod, - RawBancomatPaymentMethod, - RawCreditCardPaymentMethod, - RawPayPalPaymentMethod, - RawPaymentMethod, - isRawBPay, - isRawBancomat, - isRawCreditCard, - isRawPayPal -} from "../types/pagopa"; -import { isExpired } from "./dates"; -import { getPaypalAccountEmail } from "./paypal"; -import { FOUR_UNICODE_CIRCLES } from "./wallet"; - -export const getPaymentMethodHash = ( - pm: RawPaymentMethod -): string | undefined => { - switch (pm.kind) { - case "PayPal": - return getPaypalAccountEmail(pm.info); - case "BPay": - return pm.info.uidHash; - case "Bancomat": - case "CreditCard": - return pm.info.hashPan; - } -}; -export const getTitleFromPaymentInstrument = ( - paymentInstrument: PaymentInstrument -) => `${FOUR_UNICODE_CIRCLES} ${paymentInstrument.panPartialNumber ?? ""}`; - -export const getTitleFromCard = (creditCard: RawCreditCardPaymentMethod) => - `${FOUR_UNICODE_CIRCLES} ${creditCard.info.blurredNumber ?? ""}`; - -export const getBancomatAbiIconUrl = (abi: string) => - `${contentRepoUrl}/logos/abi/${abi}.png`; - -export const getPspIconUrlFromAbi = (abi: string) => - `${contentRepoUrl}/logos/abi/${abi}.png`; - -/** - * Choose an image to represent a {@link RawPaymentMethod} - * @param paymentMethod - */ -export const getImageFromPaymentMethod = ( - paymentMethod: RawPaymentMethod -): ImageSourcePropType => { - if (isRawCreditCard(paymentMethod)) { - return getCardIconFromBrandLogo(paymentMethod.info); - } - if (isRawBancomat(paymentMethod)) { - return pagoBancomatImage; - } - if (isRawPayPal(paymentMethod)) { - return paypalImage; - } - if (isRawBPay(paymentMethod)) { - return bPayImage; - } - return cardIcons.UNKNOWN; -}; - -export const getTitleFromBancomat = ( - bancomatInfo: RawBancomatPaymentMethod, - abiList: IndexedById -) => - pipe( - bancomatInfo.info.issuerAbiCode, - O.fromNullable, - O.chain(abiCode => O.fromNullable(abiList[abiCode])), - O.chain(abi => O.fromNullable(abi.name)), - O.getOrElse(() => I18n.t("wallet.methods.bancomat.name")) - ); - -const getTitleForPaypal = (paypal: RawPayPalPaymentMethod) => - getPaypalAccountEmail(paypal.info); -/** - * Choose a textual representation for a {@link PatchedWalletV2} - * @param paymentMethod - * @param abiList - */ -export const getTitleFromPaymentMethod = ( - paymentMethod: RawPaymentMethod, - abiList: IndexedById -) => { - if (isRawCreditCard(paymentMethod)) { - return getTitleFromCard(paymentMethod); - } - if (isRawBancomat(paymentMethod)) { - return getTitleFromBancomat(paymentMethod, abiList); - } - if (isRawBPay(paymentMethod)) { - return ( - pipe( - paymentMethod.info.instituteCode, - O.fromNullable, - O.chain(abiCode => O.fromNullable(abiList[abiCode])), - O.chain(abi => O.fromNullable(abi.name)), - O.toUndefined - ) ?? - paymentMethod.info.bankName ?? - I18n.t("wallet.methods.bancomatPay.name") - ); - } - if (isRawPayPal(paymentMethod)) { - return getTitleForPaypal(paymentMethod); - } - return FOUR_UNICODE_CIRCLES; -}; - -export const enhanceBancomat = ( - bancomat: RawBancomatPaymentMethod, - abiList: IndexedById -): BancomatPaymentMethod => ({ - ...bancomat, - abiInfo: bancomat.info.issuerAbiCode - ? abiList[bancomat.info.issuerAbiCode] - : undefined, - caption: getTitleFromBancomat(bancomat, abiList), - icon: getImageFromPaymentMethod(bancomat) -}); - -export const enhanceBPay = ( - rawBPay: RawBPayPaymentMethod, - abiList: IndexedById -): BPayPaymentMethod => ({ - ...rawBPay, - info: { - ...rawBPay.info, - numberObfuscated: `${rawBPay.info.numberObfuscated?.replace(/\*+/g, "●●●")}` - }, - abiInfo: rawBPay.info.instituteCode - ? abiList[rawBPay.info.instituteCode] - : undefined, - caption: I18n.t("wallet.methods.bancomatPay.name"), - icon: getImageFromPaymentMethod(rawBPay) -}); - -export const enhanceCreditCard = ( - rawCreditCard: RawCreditCardPaymentMethod, - abiList: IndexedById -): CreditCardPaymentMethod => ({ - ...rawCreditCard, - abiInfo: rawCreditCard.info.issuerAbiCode - ? abiList[rawCreditCard.info.issuerAbiCode] - : undefined, - caption: getTitleFromPaymentMethod(rawCreditCard, abiList), - icon: getImageFromPaymentMethod(rawCreditCard) -}); - -export const enhancePaymentMethod = ( - pm: RawPaymentMethod, - abiList: IndexedById -): PaymentMethod | null => { - switch (pm.kind) { - // bancomat need a special handling, we need to include the abi - case "Bancomat": - return enhanceBancomat(pm, abiList); - case "BPay": - return enhanceBPay(pm, abiList); - case "CreditCard": - return enhanceCreditCard(pm, abiList); - case "PayPal": - return { - ...pm, - caption: getTitleFromPaymentMethod(pm, abiList), - icon: getImageFromPaymentMethod(pm) - }; - } -}; - -export const isBancomatBlocked = (pan: Card) => - pan.validityState === ValidityStateEnum.BR; - -export const isCoBadgeBlocked = (pan: PaymentInstrument) => - pan.validityStatus === ValidityStatusEnum.BLOCK_REVERSIBLE; - -/** - * Check if the given payment method is expired - * right(true) if it is expired, right(false) if it is still valid - * left if expiring date can't be evaluated - * @param paymentMethod - */ -export const isPaymentMethodExpired = ( - paymentMethod: RawPaymentMethod -): E.Either => { - switch (paymentMethod.kind) { - case "BPay": - case "PayPal": - return E.right(false); - case "Bancomat": - case "CreditCard": - return isExpired( - paymentMethod.info.expireMonth, - paymentMethod.info.expireYear - ); - } -}; - -// inform the user he/she has no payment methods to pay -export const alertNoPayablePaymentMethods = ( - onContinue: () => void, - onCancel?: () => void -) => { - void mixpanelTrack("NO_PAYABLE_METHODS"); - Alert.alert( - I18n.t("payment.alertNoPaymentMethods.title"), - I18n.t("payment.alertNoPaymentMethods.message"), - [ - { - text: I18n.t("payment.alertNoPaymentMethods.buttons.ko"), - onPress: onCancel - }, - { - text: I18n.t("payment.alertNoPaymentMethods.buttons.ok"), - onPress: onContinue - } - ] - ); -}; - -// inform the user he/she has some payable payment methods but not one of them is active -export const alertNoActivePayablePaymentMethods = ( - onContinue: () => void, - onCancel?: () => void -) => { - void mixpanelTrack("NO_ACTIVE_PAYABLE_METHODS"); - Alert.alert( - I18n.t("payment.alertNoActivePaymentMethods.title"), - I18n.t("payment.alertNoActivePaymentMethods.message"), - [ - { - text: I18n.t("payment.alertNoActivePaymentMethods.buttons.ko"), - onPress: onCancel - }, - { - text: I18n.t("payment.alertNoActivePaymentMethods.buttons.ok"), - onPress: onContinue - } - ] - ); -};