From b7257ad29a0c7ccf0d39a87e22c0a08559286c41 Mon Sep 17 00:00:00 2001 From: Federico Mastrini Date: Thu, 15 Feb 2024 11:25:07 +0100 Subject: [PATCH] chore: [IOBP-544] Add `usePagoPaPayment` custom hook (#5503) ## Short description This PR introduces the `usePagoPaPayment` custom hook, which centralizes the initialization and start of a pagoPA payment flow. ## List of changes proposed in this pull request - Added `usePagoPaPayment` custom hook - Replaced payment initialization inside playgrounds screen - Small refactorings for types ## How to test Within new wallet Payment playgrounds, you should be able to start a payment flow. --------- Co-authored-by: Martino Cesari Tomba <60693085+forrest57@users.noreply.github.com> --- .../components/WalletPaymentFailureDetail.tsx | 2 +- .../payment/hooks/usePagoPaPayment.ts | 124 ++++++++++++++++++ .../hooks/usePaymentFailureSupportModal.tsx | 2 +- .../screens/WalletPaymentDetailScreen.tsx | 2 +- .../screens/WalletPaymentOutcomeScreen.tsx | 11 +- .../payment/store/__tests__/store.test.ts | 3 +- .../payment/store/actions/networking.ts | 2 +- .../payment/store/actions/orchestration.ts | 9 +- .../walletV3/payment/store/reducers/index.ts | 37 ++---- .../walletV3/payment/types/PaymentHistory.ts | 10 ++ .../{failure.ts => WalletPaymentFailure.ts} | 0 .../payment/types/WalletPaymentPspSortType.ts | 1 + ts/features/walletV3/payment/types/index.ts | 11 +- .../playgrounds/WalletPaymentPlayground.tsx | 43 ++---- 14 files changed, 185 insertions(+), 72 deletions(-) create mode 100644 ts/features/walletV3/payment/hooks/usePagoPaPayment.ts create mode 100644 ts/features/walletV3/payment/types/PaymentHistory.ts rename ts/features/walletV3/payment/types/{failure.ts => WalletPaymentFailure.ts} (100%) create mode 100644 ts/features/walletV3/payment/types/WalletPaymentPspSortType.ts diff --git a/ts/features/walletV3/payment/components/WalletPaymentFailureDetail.tsx b/ts/features/walletV3/payment/components/WalletPaymentFailureDetail.tsx index 288590467c6..8e0c21a706f 100644 --- a/ts/features/walletV3/payment/components/WalletPaymentFailureDetail.tsx +++ b/ts/features/walletV3/payment/components/WalletPaymentFailureDetail.tsx @@ -12,7 +12,7 @@ import { IOStackNavigationProp } from "../../../../navigation/params/AppParamsList"; import { usePaymentFailureSupportModal } from "../hooks/usePaymentFailureSupportModal"; -import { WalletPaymentFailure } from "../types/failure"; +import { WalletPaymentFailure } from "../types/WalletPaymentFailure"; type Props = { failure: WalletPaymentFailure; diff --git a/ts/features/walletV3/payment/hooks/usePagoPaPayment.ts b/ts/features/walletV3/payment/hooks/usePagoPaPayment.ts new file mode 100644 index 00000000000..e426593004c --- /dev/null +++ b/ts/features/walletV3/payment/hooks/usePagoPaPayment.ts @@ -0,0 +1,124 @@ +import { + RptId as PagoPaRptId, + RptIdFromString as PagoPaRptIdFromString, + PaymentNoticeNumberFromString +} from "@pagopa/io-pagopa-commons/lib/pagopa"; +import { OrganizationFiscalCode } from "@pagopa/ts-commons/lib/strings"; +import { NavigatorScreenParams, useRoute } from "@react-navigation/native"; +import { sequenceS } from "fp-ts/lib/Apply"; +import * as E from "fp-ts/lib/Either"; +import * as O from "fp-ts/lib/Option"; +import { pipe } from "fp-ts/lib/function"; +import { RptId } from "../../../../../definitions/pagopa/ecommerce/RptId"; +import { + AppParamsList, + useIONavigation +} from "../../../../navigation/params/AppParamsList"; +import { useIODispatch } from "../../../../store/hooks"; +import { WalletPaymentRoutes } from "../navigation/routes"; +import { + PaymentInitStateParams, + walletPaymentInitState +} from "../store/actions/orchestration"; +import { PaymentStartRoute } from "../types"; + +type PagoPaPaymentParams = Omit; + +/** + * A hook for initiating a PagoPA payment flow. + * This hook provides functions to start a payment flow using various input methods. + * @returns An object containing functions to start different types of payment flows. + */ +const usePagoPaPayment = () => { + const route = useRoute(); + const dispatch = useIODispatch(); + const navigation = useIONavigation(); + + /** + * Initializes the payment state based on the provided parameters. + * The initialization includes the store of the current route which allows the app to + * return to it when the payment flow is finished. + * @param {PagoPaPaymentParams} params - Parameters for initializing the payment state. + */ + const initPaymentState = ({ startOrigin }: PagoPaPaymentParams) => { + const startRoute: PaymentStartRoute = { + routeName: route.name as keyof AppParamsList, + routeKey: + route.key as keyof NavigatorScreenParams["screen"] + }; + dispatch( + walletPaymentInitState({ + startRoute, + startOrigin + }) + ); + }; + + /** + * Initiates the payment flow using the provided RptId string and additional parameters. + * @param {RptId} rptId - The RptId for the payment flow. + * @param {PagoPaPaymentParams} params - Additional parameters for the payment flow. + */ + const startPaymentFlow = (rptId: RptId, params: PagoPaPaymentParams = {}) => { + initPaymentState(params); + navigation.navigate(WalletPaymentRoutes.WALLET_PAYMENT_MAIN, { + screen: WalletPaymentRoutes.WALLET_PAYMENT_DETAIL, + params: { + rptId + } + }); + }; + + /** + * Initiates the payment flow using the provided PagoPA RptId and additional parameters. + * @param {PagoPaRptId} rptId - The PagoPA RptId for the payment flow. + * @param {PagoPaPaymentParams} params - Additional parameters for the payment flow. + */ + const startPaymentFlowWithRptId = ( + rptId: PagoPaRptId, + params: PagoPaPaymentParams = {} + ) => { + pipe( + O.fromNullable(rptId), + O.map(PagoPaRptIdFromString.encode), + O.map(RptId.decode), + O.chain(O.fromEither), + O.map(rptIdString => startPaymentFlow(rptIdString, params)) + ); + }; + + /** + * Initiates the payment flow using the provided payment data and additional parameters. + * @param {Object} data - Payment data containing the payment notice number and an organization fiscal code. + * @param {PagoPaPaymentParams} params - Additional parameters for the payment flow. + */ + const startPaymentFlowWithData = ( + data: { + paymentNoticeNumber: string; + organizationFiscalCode: string; + }, + params: PagoPaPaymentParams = {} + ) => { + pipe( + sequenceS(E.Monad)({ + paymentNoticeNumber: PaymentNoticeNumberFromString.decode( + data.paymentNoticeNumber + ), + organizationFiscalCode: OrganizationFiscalCode.decode( + data.organizationFiscalCode + ) + }), + E.map(PagoPaRptIdFromString.encode), + E.chain(RptId.decode), + E.map(rptIdString => startPaymentFlow(rptIdString, params)) + ); + }; + + return { + startPaymentFlow, + startPaymentFlowWithRptId, + startPaymentFlowWithData + }; +}; + +export { usePagoPaPayment }; diff --git a/ts/features/walletV3/payment/hooks/usePaymentFailureSupportModal.tsx b/ts/features/walletV3/payment/hooks/usePaymentFailureSupportModal.tsx index 2dd47feab59..1041a205247 100644 --- a/ts/features/walletV3/payment/hooks/usePaymentFailureSupportModal.tsx +++ b/ts/features/walletV3/payment/hooks/usePaymentFailureSupportModal.tsx @@ -39,7 +39,7 @@ import { WalletPaymentOutcome, getWalletPaymentOutcomeEnumByValue } from "../types/PaymentOutcomeEnum"; -import { WalletPaymentFailure } from "../types/failure"; +import { WalletPaymentFailure } from "../types/WalletPaymentFailure"; type PaymentFailureSupportModalParams = { failure?: WalletPaymentFailure; diff --git a/ts/features/walletV3/payment/screens/WalletPaymentDetailScreen.tsx b/ts/features/walletV3/payment/screens/WalletPaymentDetailScreen.tsx index 7969f2c538d..817543dfe02 100644 --- a/ts/features/walletV3/payment/screens/WalletPaymentDetailScreen.tsx +++ b/ts/features/walletV3/payment/screens/WalletPaymentDetailScreen.tsx @@ -50,7 +50,7 @@ import { WalletPaymentParamsList } from "../navigation/params"; import { WalletPaymentRoutes } from "../navigation/routes"; import { walletPaymentGetDetails } from "../store/actions/networking"; import { walletPaymentDetailsSelector } from "../store/selectors"; -import { WalletPaymentFailure } from "../types/failure"; +import { WalletPaymentFailure } from "../types/WalletPaymentFailure"; type WalletPaymentDetailScreenNavigationParams = { rptId: RptId; diff --git a/ts/features/walletV3/payment/screens/WalletPaymentOutcomeScreen.tsx b/ts/features/walletV3/payment/screens/WalletPaymentOutcomeScreen.tsx index 45a610017c9..3a65a40900a 100644 --- a/ts/features/walletV3/payment/screens/WalletPaymentOutcomeScreen.tsx +++ b/ts/features/walletV3/payment/screens/WalletPaymentOutcomeScreen.tsx @@ -1,5 +1,5 @@ import * as pot from "@pagopa/ts-commons/lib/pot"; -import { RouteProp, useNavigation, useRoute } from "@react-navigation/native"; +import { RouteProp, useRoute } from "@react-navigation/native"; import * as O from "fp-ts/lib/Option"; import { pipe } from "fp-ts/lib/function"; import React from "react"; @@ -8,12 +8,10 @@ import { OperationResultScreenContentProps } from "../../../../components/screens/OperationResultScreenContent"; import I18n from "../../../../i18n"; -import { - AppParamsList, - IOStackNavigationProp -} from "../../../../navigation/params/AppParamsList"; +import { useIONavigation } from "../../../../navigation/params/AppParamsList"; import { useIOSelector } from "../../../../store/hooks"; import { formatNumberCentsToAmount } from "../../../../utils/stringBuilder"; +import { useAvoidHardwareBackButton } from "../../../../utils/useAvoidHardwareBackButton"; import { WalletPaymentFeebackBanner } from "../components/WalletPaymentFeedbackBanner"; import { usePaymentFailureSupportModal } from "../hooks/usePaymentFailureSupportModal"; import { WalletPaymentParamsList } from "../navigation/params"; @@ -25,7 +23,6 @@ import { WalletPaymentOutcome, WalletPaymentOutcomeEnum } from "../types/PaymentOutcomeEnum"; -import { useAvoidHardwareBackButton } from "../../../../utils/useAvoidHardwareBackButton"; type WalletPaymentOutcomeScreenNavigationParams = { outcome: WalletPaymentOutcome; @@ -42,7 +39,7 @@ const WalletPaymentOutcomeScreen = () => { const { params } = useRoute(); const { outcome } = params; - const navigation = useNavigation>(); + const navigation = useIONavigation(); const paymentDetailsPot = useIOSelector(walletPaymentDetailsSelector); const paymentStartRoute = useIOSelector(walletPaymentStartRouteSelector); diff --git a/ts/features/walletV3/payment/store/__tests__/store.test.ts b/ts/features/walletV3/payment/store/__tests__/store.test.ts index 1ea468f021c..7afa77fd22c 100644 --- a/ts/features/walletV3/payment/store/__tests__/store.test.ts +++ b/ts/features/walletV3/payment/store/__tests__/store.test.ts @@ -13,7 +13,8 @@ const INITIAL_STATE: WalletPaymentState = { chosenPaymentMethod: O.none, chosenPsp: O.none, transaction: pot.none, - authorizationUrl: pot.none + authorizationUrl: pot.none, + paymentHistory: {} }; describe("Test Wallet reducer", () => { diff --git a/ts/features/walletV3/payment/store/actions/networking.ts b/ts/features/walletV3/payment/store/actions/networking.ts index c9fd9e58e4d..3e316c075f2 100644 --- a/ts/features/walletV3/payment/store/actions/networking.ts +++ b/ts/features/walletV3/payment/store/actions/networking.ts @@ -16,7 +16,7 @@ import { TransactionInfo } from "../../../../../../definitions/pagopa/ecommerce/ import { Wallets } from "../../../../../../definitions/pagopa/ecommerce/Wallets"; import { PaymentMethodsResponse } from "../../../../../../definitions/pagopa/ecommerce/PaymentMethodsResponse"; import { NetworkError } from "../../../../../utils/errors"; -import { WalletPaymentFailure } from "../../types/failure"; +import { WalletPaymentFailure } from "../../types/WalletPaymentFailure"; export const walletPaymentNewSessionToken = createAsyncAction( "WALLET_PAYMENT_NEW_SESSION_TOKEN_REQUEST", diff --git a/ts/features/walletV3/payment/store/actions/orchestration.ts b/ts/features/walletV3/payment/store/actions/orchestration.ts index 88e3286fe0e..306bd66920b 100644 --- a/ts/features/walletV3/payment/store/actions/orchestration.ts +++ b/ts/features/walletV3/payment/store/actions/orchestration.ts @@ -1,6 +1,13 @@ import { ActionType, createStandardAction } from "typesafe-actions"; import { Bundle } from "../../../../../../definitions/pagopa/ecommerce/Bundle"; import { WalletInfo } from "../../../../../../definitions/pagopa/ecommerce/WalletInfo"; +import { PaymentStartOrigin, PaymentStartRoute } from "../../types"; + +export type PaymentInitStateParams = { + startOrigin?: PaymentStartOrigin; + startRoute?: PaymentStartRoute; + showTransaction?: boolean; +}; /** * Action to initialize the state of a payment, optionally you can specify the route to go back to @@ -8,7 +15,7 @@ import { WalletInfo } from "../../../../../../definitions/pagopa/ecommerce/Walle */ export const walletPaymentInitState = createStandardAction( "WALLET_PAYMENT_INIT_STATE" -)(); +)(); export const walletPaymentPickPaymentMethod = createStandardAction( "WALLET_PAYMENT_PICK_PAYMENT_METHOD" diff --git a/ts/features/walletV3/payment/store/reducers/index.ts b/ts/features/walletV3/payment/store/reducers/index.ts index 11976ca858c..082819d8e82 100644 --- a/ts/features/walletV3/payment/store/reducers/index.ts +++ b/ts/features/walletV3/payment/store/reducers/index.ts @@ -1,21 +1,17 @@ import * as pot from "@pagopa/ts-commons/lib/pot"; -import { NavigatorScreenParams } 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 { getType } from "typesafe-actions"; import { Bundle } from "../../../../../../definitions/pagopa/ecommerce/Bundle"; +import { PaymentMethodsResponse } from "../../../../../../definitions/pagopa/ecommerce/PaymentMethodsResponse"; import { PaymentRequestsGetResponse } from "../../../../../../definitions/pagopa/ecommerce/PaymentRequestsGetResponse"; import { RptId } from "../../../../../../definitions/pagopa/ecommerce/RptId"; import { TransactionInfo } from "../../../../../../definitions/pagopa/ecommerce/TransactionInfo"; -import { PaymentMethodsResponse } from "../../../../../../definitions/pagopa/ecommerce/PaymentMethodsResponse"; import { WalletInfo } from "../../../../../../definitions/pagopa/ecommerce/WalletInfo"; import { Wallets } from "../../../../../../definitions/pagopa/ecommerce/Wallets"; -import NavigationService from "../../../../../navigation/NavigationService"; -import { AppParamsList } from "../../../../../navigation/params/AppParamsList"; import { Action } from "../../../../../store/actions/types"; import { NetworkError } from "../../../../../utils/errors"; -import { WalletPaymentFailure } from "../../types/failure"; +import { PaymentHistory, PaymentStartRoute } from "../../types"; +import { WalletPaymentFailure } from "../../types/WalletPaymentFailure"; import { walletPaymentAuthorization, walletPaymentCalculateFees, @@ -49,10 +45,9 @@ export type WalletPaymentState = { chosenPsp: O.Option; transaction: pot.Pot; authorizationUrl: pot.Pot; - startRoute?: { - routeName: keyof AppParamsList; - routeKey: NavigatorScreenParams["screen"]; - }; + paymentHistory: PaymentHistory; + startRoute?: PaymentStartRoute; + showTransaction?: boolean; }; const INITIAL_STATE: WalletPaymentState = { @@ -64,7 +59,8 @@ const INITIAL_STATE: WalletPaymentState = { chosenPaymentMethod: O.none, chosenPsp: O.none, transaction: pot.none, - authorizationUrl: pot.none + authorizationUrl: pot.none, + paymentHistory: {} }; // eslint-disable-next-line complexity @@ -74,20 +70,13 @@ const reducer = ( ): WalletPaymentState => { switch (action.type) { case getType(walletPaymentInitState): - const startRoute = pipe( - sequenceS(O.Monad)({ - routeName: O.fromNullable( - NavigationService.getCurrentRouteName() as keyof AppParamsList - ), - routeKey: O.fromNullable( - NavigationService.getCurrentRouteKey() as keyof NavigatorScreenParams["screen"] - ) - }), - O.toUndefined - ); return { ...INITIAL_STATE, - startRoute + paymentHistory: { + startOrigin: action.payload.startOrigin + }, + startRoute: action.payload.startRoute, + showTransaction: action.payload.showTransaction }; // eCommerce Session token diff --git a/ts/features/walletV3/payment/types/PaymentHistory.ts b/ts/features/walletV3/payment/types/PaymentHistory.ts new file mode 100644 index 00000000000..e72b6466a35 --- /dev/null +++ b/ts/features/walletV3/payment/types/PaymentHistory.ts @@ -0,0 +1,10 @@ +export type PaymentStartOrigin = + | "message" + | "qrcode_scan" + | "poste_datamatrix_scan" + | "manual_insertion" + | "donation"; + +export type PaymentHistory = { + startOrigin?: PaymentStartOrigin; +}; diff --git a/ts/features/walletV3/payment/types/failure.ts b/ts/features/walletV3/payment/types/WalletPaymentFailure.ts similarity index 100% rename from ts/features/walletV3/payment/types/failure.ts rename to ts/features/walletV3/payment/types/WalletPaymentFailure.ts diff --git a/ts/features/walletV3/payment/types/WalletPaymentPspSortType.ts b/ts/features/walletV3/payment/types/WalletPaymentPspSortType.ts new file mode 100644 index 00000000000..caa0f9e1cd8 --- /dev/null +++ b/ts/features/walletV3/payment/types/WalletPaymentPspSortType.ts @@ -0,0 +1 @@ +export type WalletPaymentPspSortType = "default" | "name" | "amount"; diff --git a/ts/features/walletV3/payment/types/index.ts b/ts/features/walletV3/payment/types/index.ts index caa0f9e1cd8..adf77278be6 100644 --- a/ts/features/walletV3/payment/types/index.ts +++ b/ts/features/walletV3/payment/types/index.ts @@ -1 +1,10 @@ -export type WalletPaymentPspSortType = "default" | "name" | "amount"; +import { NavigatorScreenParams } from "@react-navigation/native"; +import { AppParamsList } from "../../../../navigation/params/AppParamsList"; + +export type { WalletPaymentPspSortType } from "./WalletPaymentPspSortType"; +export type { PaymentStartOrigin, PaymentHistory } from "./PaymentHistory"; + +export type PaymentStartRoute = { + routeName: keyof AppParamsList; + routeKey: NavigatorScreenParams["screen"]; +}; diff --git a/ts/screens/profile/playgrounds/WalletPaymentPlayground.tsx b/ts/screens/profile/playgrounds/WalletPaymentPlayground.tsx index 0af4be895dd..35c1a059de6 100644 --- a/ts/screens/profile/playgrounds/WalletPaymentPlayground.tsx +++ b/ts/screens/profile/playgrounds/WalletPaymentPlayground.tsx @@ -5,33 +5,22 @@ import { } from "@pagopa/io-app-design-system"; import { RptId as PagoPaRptId, - PaymentNoticeNumberFromString, - RptIdFromString as PagoPaRptIdFromString + RptIdFromString as PagoPaRptIdFromString, + PaymentNoticeNumberFromString } from "@pagopa/io-pagopa-commons/lib/pagopa"; -import { useNavigation } from "@react-navigation/native"; -import { sequenceS } from "fp-ts/lib/Apply"; 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 from "react"; -import { RptId } from "../../../../definitions/pagopa/ecommerce/RptId"; import { validateOrganizationFiscalCode, validatePaymentNoticeNumber } from "../../../features/walletV3/common/utils/validation"; -import { WalletPaymentRoutes } from "../../../features/walletV3/payment/navigation/routes"; -import { walletPaymentInitState } from "../../../features/walletV3/payment/store/actions/orchestration"; +import { usePagoPaPayment } from "../../../features/walletV3/payment/hooks/usePagoPaPayment"; import { useHeaderSecondLevel } from "../../../hooks/useHeaderSecondLevel"; import I18n from "../../../i18n"; -import { - AppParamsList, - IOStackNavigationProp -} from "../../../navigation/params/AppParamsList"; -import { useIODispatch } from "../../../store/hooks"; const WalletPaymentPlayground = () => { - const dispatch = useIODispatch(); - const navigation = useNavigation>(); + const { startPaymentFlowWithData } = usePagoPaPayment(); const [rptId, setRptId] = React.useState(); const [paymentNoticeNumber, setPaymentNoticeNumber] = @@ -54,31 +43,17 @@ const WalletPaymentPlayground = () => { React.useEffect(() => { pipe( - sequenceS(E.Monad)({ - paymentNoticeNumber: E.right(paymentNoticeNumber), - organizationFiscalCode: E.right(organizationFiscalCode) - }), - E.chain(PagoPaRptId.decode), + PagoPaRptId.decode({ paymentNoticeNumber, organizationFiscalCode }), E.map(setRptId), E.getOrElse(() => setRptId(undefined)) ); }, [paymentNoticeNumber, organizationFiscalCode]); const navigateToWalletPayment = () => { - pipe( - rptId, - O.fromNullable, - O.map(PagoPaRptIdFromString.encode), - O.map(rptId => { - dispatch(walletPaymentInitState()); - navigation.navigate(WalletPaymentRoutes.WALLET_PAYMENT_MAIN, { - screen: WalletPaymentRoutes.WALLET_PAYMENT_DETAIL, - params: { - rptId: rptId as RptId - } - }); - }) - ); + startPaymentFlowWithData({ + paymentNoticeNumber, + organizationFiscalCode + }); }; const generateValidRandomRptId = () => {