Skip to content

Commit

Permalink
chore: [IOBP-544] Add usePagoPaPayment custom hook (#5503)
Browse files Browse the repository at this point in the history
## 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 <[email protected]>
  • Loading branch information
mastro993 and forrest57 authored Feb 15, 2024
1 parent 8b52e4b commit b7257ad
Show file tree
Hide file tree
Showing 14 changed files with 185 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
124 changes: 124 additions & 0 deletions ts/features/walletV3/payment/hooks/usePagoPaPayment.ts
Original file line number Diff line number Diff line change
@@ -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<PaymentInitStateParams, "startRoute">;

/**
* 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<AppParamsList>["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 };
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import {
WalletPaymentOutcome,
getWalletPaymentOutcomeEnumByValue
} from "../types/PaymentOutcomeEnum";
import { WalletPaymentFailure } from "../types/failure";
import { WalletPaymentFailure } from "../types/WalletPaymentFailure";

type PaymentFailureSupportModalParams = {
failure?: WalletPaymentFailure;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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";
Expand All @@ -25,7 +23,6 @@ import {
WalletPaymentOutcome,
WalletPaymentOutcomeEnum
} from "../types/PaymentOutcomeEnum";
import { useAvoidHardwareBackButton } from "../../../../utils/useAvoidHardwareBackButton";

type WalletPaymentOutcomeScreenNavigationParams = {
outcome: WalletPaymentOutcome;
Expand All @@ -42,7 +39,7 @@ const WalletPaymentOutcomeScreen = () => {
const { params } = useRoute<WalletPaymentOutcomeRouteProps>();
const { outcome } = params;

const navigation = useNavigation<IOStackNavigationProp<AppParamsList>>();
const navigation = useIONavigation();
const paymentDetailsPot = useIOSelector(walletPaymentDetailsSelector);
const paymentStartRoute = useIOSelector(walletPaymentStartRouteSelector);

Expand Down
3 changes: 2 additions & 1 deletion ts/features/walletV3/payment/store/__tests__/store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
2 changes: 1 addition & 1 deletion ts/features/walletV3/payment/store/actions/networking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
9 changes: 8 additions & 1 deletion ts/features/walletV3/payment/store/actions/orchestration.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
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
* after the payment is completed or cancelled (default is the popToTop route)
*/
export const walletPaymentInitState = createStandardAction(
"WALLET_PAYMENT_INIT_STATE"
)();
)<PaymentInitStateParams>();

export const walletPaymentPickPaymentMethod = createStandardAction(
"WALLET_PAYMENT_PICK_PAYMENT_METHOD"
Expand Down
37 changes: 13 additions & 24 deletions ts/features/walletV3/payment/store/reducers/index.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -49,10 +45,9 @@ export type WalletPaymentState = {
chosenPsp: O.Option<Bundle>;
transaction: pot.Pot<TransactionInfo, NetworkError | WalletPaymentFailure>;
authorizationUrl: pot.Pot<string, NetworkError>;
startRoute?: {
routeName: keyof AppParamsList;
routeKey: NavigatorScreenParams<AppParamsList>["screen"];
};
paymentHistory: PaymentHistory;
startRoute?: PaymentStartRoute;
showTransaction?: boolean;
};

const INITIAL_STATE: WalletPaymentState = {
Expand All @@ -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
Expand All @@ -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<AppParamsList>["screen"]
)
}),
O.toUndefined
);
return {
...INITIAL_STATE,
startRoute
paymentHistory: {
startOrigin: action.payload.startOrigin
},
startRoute: action.payload.startRoute,
showTransaction: action.payload.showTransaction
};

// eCommerce Session token
Expand Down
10 changes: 10 additions & 0 deletions ts/features/walletV3/payment/types/PaymentHistory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export type PaymentStartOrigin =
| "message"
| "qrcode_scan"
| "poste_datamatrix_scan"
| "manual_insertion"
| "donation";

export type PaymentHistory = {
startOrigin?: PaymentStartOrigin;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type WalletPaymentPspSortType = "default" | "name" | "amount";
11 changes: 10 additions & 1 deletion ts/features/walletV3/payment/types/index.ts
Original file line number Diff line number Diff line change
@@ -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<AppParamsList>["screen"];
};
Loading

0 comments on commit b7257ad

Please sign in to comment.