diff --git a/locales/en/index.yml b/locales/en/index.yml
index eefede14497..9e6b43f426b 100644
--- a/locales/en/index.yml
+++ b/locales/en/index.yml
@@ -3068,7 +3068,8 @@ features:
status:
expired: Scaduta
transactions:
- title: Storico operazioni
+ multiplePayment: Pagamento multiplo
+ title: Ricevute pagoPA
button: Vedi tutte
empty:
title: Qui vedrai le tue ricevute pagoPA
@@ -3778,6 +3779,8 @@ bonusCard:
removed: Rimossa
transaction:
details:
+ error:
+ title: É stato riscontrato un errore
title: Dettaglio operazione
totalAmount: Totale
totalFee: Il totale comprende
@@ -3788,12 +3791,19 @@ transaction:
pspName: Gestore della transazione (PSP)
dateAndHour: Data e ora
transactionId: ID transazione
+ paymentMethod: Metodo di pagamento
+ authCode: Codice autorizzativo
+ rrn: RRN
+ executedBy: Eseguito da
+ headedTo: Intestato a
operation:
amount: Importo
creditor: Ente creditore
debtor: Debitore
iuv: IUV
subject: Oggetto del pagamento
+ noticeCode: Codice avviso
+ taxCode: Codice Fiscale Ente
permissionRequest:
gallery:
title: Consenti a IO di accedere alle tue foto
diff --git a/locales/it/index.yml b/locales/it/index.yml
index 6826572d9a7..b51796adcf7 100644
--- a/locales/it/index.yml
+++ b/locales/it/index.yml
@@ -3068,7 +3068,8 @@ features:
status:
expired: Scaduta
transactions:
- title: Storico operazioni
+ multiplePayment: Pagamento multiplo
+ title: Ricevute pagoPA
button: Vedi tutte
empty:
title: Qui vedrai le tue ricevute pagoPA
@@ -3778,6 +3779,8 @@ bonusCard:
removed: Rimossa
transaction:
details:
+ error:
+ title: É stato riscontrato un errore
title: Dettaglio operazione
totalAmount: Totale
totalFee: Il totale comprende
@@ -3788,12 +3791,19 @@ transaction:
pspName: Gestore della transazione (PSP)
dateAndHour: Data e ora
transactionId: ID transazione
+ paymentMethod: Metodo di pagamento
+ authCode: Codice autorizzativo
+ rrn: RRN
+ executedBy: Eseguito da
+ headedTo: Intestato a
operation:
amount: Importo
creditor: Ente creditore
debtor: Debitore
iuv: IUV
subject: Oggetto del pagamento
+ noticeCode: Codice avviso
+ taxCode: Codice Fiscale Ente
permissionRequest:
gallery:
title: Consenti a IO di accedere alle tue foto
diff --git a/package.json b/package.json
index 7d70bbf4f32..8f25577431f 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
"fast_login_api": "https://raw.githubusercontent.com/pagopa/io-backend/v13.35.0-RELEASE/openapi/generated/api_fast_login.yaml",
"pagopa_api_walletv3": "https://raw.githubusercontent.com/pagopa/pagopa-infra/v1.35.1/src/domains/wallet-app/api/payment-wallet/v1/_openapi.json.tpl",
"pagopa_api_ecommerce": "https://raw.githubusercontent.com/pagopa/pagopa-infra/v1.35.1/src/domains/ecommerce-app/api/ecommerce-io/v1/_openapi.json.tpl",
+ "pagopa_api_biz_events": "https://raw.githubusercontent.com/pagopa/pagopa-infra/0a6784276fd43aaff7709dd90e0d379e77326f28/src/domains/bizevents-app/api/transaction-service/v1/_openapi-jwt.json.tpl",
"trial_system": "https://raw.githubusercontent.com/pagopa/io-backend/features/add-openapi-trial-service/api_trial_system.yaml",
"private": true,
"scripts": {
@@ -82,7 +83,8 @@
"generate:trial-system-api": "rimraf definitions/trial_systwem && mkdir -p definitions/trial_systwem && gen-api-models --api-spec $npm_package_trial_system --out-dir ./definitions/trial_systwem --no-strict --response-decoders --request-types --client",
"generate:pagopa-walletv3-api": "rimraf definitions/pagopa/walletv3 && mkdir -p definitions/pagopa/walletv3 && gen-api-models --api-spec $npm_package_pagopa_api_walletv3 --out-dir ./definitions/pagopa/walletv3 --no-strict --response-decoders --request-types --client",
"generate:pagopa-ecommerce-api": "rimraf definitions/pagopa/ecommerce && mkdir -p definitions/pagopa/ecommerce && gen-api-models --api-spec $npm_package_pagopa_api_ecommerce --out-dir ./definitions/pagopa/ecommerce --no-strict --response-decoders --request-types --client",
- "generate:payments": "npm-run-all generate:pagopa-walletv3-api generate:pagopa-ecommerce-api",
+ "generate:pagopa-biz-events-api": "rimraf definitions/pagopa/biz-events && mkdir -p definitions/pagopa/biz-events && gen-api-models --api-spec $npm_package_pagopa_api_biz_events --out-dir ./definitions/pagopa/biz-events --no-strict --response-decoders --request-types --client",
+ "generate:payments": "npm-run-all generate:pagopa-walletv3-api generate:pagopa-ecommerce-api generate:pagopa-biz-events-api",
"generate": "npm-run-all generate:*",
"locales_unused": "ts-node --skip-project -O '{\"lib\":[\"es2015\"]}' scripts/unused-locales.ts",
"remove_unused_locales": "ts-node --skip-project -O '{\"lib\":[\"es2015\"]}' scripts/remove-unused-locales.ts",
@@ -227,7 +229,7 @@
"@babel/preset-typescript": "^7.16.7",
"@babel/runtime": "^7.15.3",
"@jambit/eslint-plugin-typed-redux-saga": "^0.4.0",
- "@pagopa/openapi-codegen-ts": "^12.2.1",
+ "@pagopa/openapi-codegen-ts": "^13.2.0",
"@react-native-community/eslint-config": "^3.0.1",
"@testing-library/jest-native": "^3.4.3",
"@testing-library/react-native": "^8.0.0",
diff --git a/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsListItemTransaction.tsx b/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsListItemTransaction.tsx
new file mode 100644
index 00000000000..98f7bb299a5
--- /dev/null
+++ b/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsListItemTransaction.tsx
@@ -0,0 +1,86 @@
+import { Avatar, ListItemTransaction } 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 { getAccessibleAmountText } from "../../../../utils/accessibility";
+import { format } from "../../../../utils/dates";
+import { TransactionListItem } from "../../../../../definitions/pagopa/biz-events/TransactionListItem";
+import { getTransactionLogo } from "../../common/utils";
+import { formatAmountText } from "../utils";
+import I18n from "../../../../i18n";
+
+type Props = {
+ transaction: TransactionListItem;
+ onPress?: () => void;
+};
+
+const PaymentsBizEventsListItemTransaction = ({
+ transaction,
+ onPress
+}: Props) => {
+ const recipient = transaction.payeeName || "";
+
+ const amountText = pipe(
+ transaction.amount,
+ O.fromNullable,
+ O.map(amount => formatAmountText(amount)),
+ O.getOrElse(() => "")
+ );
+
+ const datetime: string = pipe(
+ transaction.transactionDate,
+ O.fromNullable,
+ O.map(transactionDate => format(transactionDate, "DD MMM YYYY, HH:mm")),
+ O.getOrElse(() => "")
+ );
+
+ const accessibleDatetime: string = pipe(
+ transaction.transactionDate,
+ O.fromNullable,
+ O.map(transactionDate => format(transactionDate, "DD MMMM YYYY, HH:mm")),
+ O.getOrElse(() => "")
+ );
+
+ const transactionPayeeLogoUri = getTransactionLogo(transaction);
+
+ const accessibleAmountText = getAccessibleAmountText(amountText);
+ const accessibilityLabel = `${recipient}; ${accessibleDatetime}; ${accessibleAmountText}`;
+
+ const TransactionEmptyIcon = () => ;
+
+ const transactionLogo = pipe(
+ transactionPayeeLogoUri,
+ O.map(uri => ({ uri })),
+ O.getOrElseW(() => )
+ );
+
+ if (transaction.isCart) {
+ return (
+ }
+ onPress={onPress}
+ accessible={true}
+ title={I18n.t("features.payments.transactions.multiplePayment")}
+ subtitle={datetime}
+ transactionAmount={amountText}
+ accessibilityLabel={accessibilityLabel}
+ transactionStatus="success"
+ />
+ );
+ }
+
+ return (
+
+ );
+};
+
+export { PaymentsBizEventsListItemTransaction };
diff --git a/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionCartList.tsx b/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionCartList.tsx
new file mode 100644
index 00000000000..b759dc7ff86
--- /dev/null
+++ b/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionCartList.tsx
@@ -0,0 +1,64 @@
+import React from "react";
+import { View } from "react-native";
+import Placeholder from "rn-placeholder";
+import {
+ HSpacer,
+ IOStyles,
+ ListItemTransaction,
+ VSpacer
+} from "@pagopa/io-app-design-system";
+import { CartItem } from "../../../../../definitions/pagopa/biz-events/CartItem";
+import { formatAmountText } from "../utils";
+
+type Props = {
+ carts?: ReadonlyArray;
+ loading: boolean;
+ onPress: (cartItem: CartItem) => void;
+};
+
+/**
+ * This component renders a list of transaction cart details
+ */
+export const PaymentsBizEventsTransactionCartList = ({
+ carts,
+ loading,
+ onPress
+}: Props) => {
+ if (loading) {
+ return ;
+ }
+ if (!carts) {
+ return null;
+ }
+
+ return (
+ <>
+ {carts.map((cartItem, index) => (
+ onPress(cartItem)}
+ />
+ ))}
+ >
+ );
+};
+
+const SkeletonTransactionDetailsList = () => (
+
+
+
+
+
+
+
+
+
+
+);
diff --git a/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionHeadingSection.tsx b/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionHeadingSection.tsx
new file mode 100644
index 00000000000..ce692fc1e3f
--- /dev/null
+++ b/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionHeadingSection.tsx
@@ -0,0 +1,99 @@
+import _ from "lodash";
+import { Body, IOStyles, VSpacer } from "@pagopa/io-app-design-system";
+import { useNavigation } from "@react-navigation/native";
+import React from "react";
+import { View } from "react-native";
+import Placeholder from "rn-placeholder";
+import { CartItem } from "../../../../../definitions/pagopa/biz-events/CartItem";
+import I18n from "../../../../i18n";
+import { TransactionDetailResponse } from "../../../../../definitions/pagopa/biz-events/TransactionDetailResponse";
+import { Psp } from "../../../../types/pagopa";
+import { formatAmountText } from "../utils";
+import { PaymentsTransactionBizEventsStackNavigation } from "../navigation/navigator";
+import { PaymentsTransactionBizEventsRoutes } from "../navigation/routes";
+import { PaymentsBizEventsTransactionCartList } from "./PaymentsBizEventsTransactionCartList";
+import { PaymentsBizEventsTransactionTotalAmount } from "./PaymentsBizEventsTransactionTotalAmount";
+
+type Props = {
+ transaction?: TransactionDetailResponse;
+ psp?: Psp;
+ isLoading: boolean;
+};
+
+export const PaymentsBizEventsTransactionHeadingSection = ({
+ transaction,
+ isLoading
+}: Props) => {
+ const navigation =
+ useNavigation();
+
+ const transactionInfo = transaction?.infoTransaction;
+
+ const handlePressTransactionDetails = (cartItem: CartItem) => {
+ if (transaction) {
+ navigation.navigate(
+ PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_CART_ITEM_DETAILS,
+ {
+ cartItem
+ }
+ );
+ }
+ };
+
+ const FeeAmountSection = () => {
+ if (isLoading) {
+ return (
+
+
+
+
+
+
+ );
+ }
+ if (transactionInfo?.fee !== undefined) {
+ const formattedFee = formatAmountText(transactionInfo.fee);
+ return (
+
+ {I18n.t("transaction.details.totalFee")}{" "}
+ {formattedFee}{" "}
+ {transactionInfo?.pspName
+ ? // we want to make sure no empty string is passed either
+ I18n.t("transaction.details.totalFeePsp", {
+ pspName: transactionInfo.pspName
+ })
+ : I18n.t("transaction.details.totalFeeNoPsp")}
+
+ );
+ }
+ return null;
+ };
+
+ const calculateTotalAmount = () => {
+ if (transactionInfo?.amount && transactionInfo?.fee) {
+ return (
+ _.toNumber(transactionInfo.amount) + _.toNumber(transactionInfo.fee)
+ ).toString();
+ }
+ return transactionInfo?.amount;
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionInfoSection.tsx b/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionInfoSection.tsx
new file mode 100644
index 00000000000..9f464c7dd2f
--- /dev/null
+++ b/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionInfoSection.tsx
@@ -0,0 +1,218 @@
+/* eslint-disable functional/immutable-data */
+import { capitalize } from "lodash";
+import * as React from "react";
+import { StyleSheet, View } from "react-native";
+import Placeholder from "rn-placeholder";
+import {
+ Divider,
+ IOLogoPaymentType,
+ IORadiusScale,
+ IOVisualCostants,
+ ListItemHeader,
+ ListItemInfo,
+ ListItemInfoCopy,
+ VSpacer
+} from "@pagopa/io-app-design-system";
+import { IOStyles } from "../../../../components/core/variables/IOStyles";
+import I18n from "../../../../i18n";
+import { format } from "../../../../utils/dates";
+import { clipboardSetStringWithFeedback } from "../../../../utils/clipboard";
+import TransactionReceiptDivider from "../../../../../img/features/wallet/transaction-receipt-divider.svg";
+import { TransactionDetailResponse } from "../../../../../definitions/pagopa/biz-events/TransactionDetailResponse";
+import { WalletInfo } from "../../../../../definitions/pagopa/biz-events/WalletInfo";
+
+type PaymentsBizEventsTransactionInfoSectionProps = {
+ transaction?: TransactionDetailResponse;
+ loading?: boolean;
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flexGrow: 1,
+ ...IOStyles.horizontalContentPadding
+ },
+ contentCard: {
+ ...IOStyles.horizontalContentPadding,
+ ...IOStyles.bgWhite,
+ borderRadius: IORadiusScale["1"],
+ marginVertical: IOVisualCostants.appMarginDefault
+ }
+});
+
+/**
+ * Component that shows the biz-events transaction info
+ */
+const PaymentsBizEventsTransactionInfoSection = ({
+ transaction,
+ loading
+}: PaymentsBizEventsTransactionInfoSectionProps) => {
+ const transactionInfo = transaction?.infoTransaction;
+ return (
+ <>
+
+
+
+
+ {loading && (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ )}
+ {!loading && transactionInfo && (
+ <>
+ {transactionInfo.payer && (
+ <>
+
+
+ >
+ )}
+ {transactionInfo.paymentMethod && transactionInfo.walletInfo && (
+ <>
+ {renderPaymentMethod(transactionInfo.walletInfo)}
+
+ >
+ )}
+ {(transactionInfo.walletInfo?.maskedEmail ||
+ transactionInfo.walletInfo?.accountHolder) && (
+ <>
+
+
+ >
+ )}
+ {transactionInfo.pspName && (
+ <>
+
+
+ >
+ )}
+ {transactionInfo.transactionDate && (
+ <>
+
+
+ >
+ )}
+ {transactionInfo.rrn && (
+ <>
+
+ clipboardSetStringWithFeedback(transactionInfo.rrn ?? "")
+ }
+ accessibilityLabel={`${I18n.t(
+ "transaction.details.info.rrn"
+ )}: ${transactionInfo.rrn}`}
+ label={I18n.t("transaction.details.info.rrn")}
+ value={transactionInfo.rrn}
+ />
+
+ >
+ )}
+ {transactionInfo.authCode && (
+ <>
+
+ clipboardSetStringWithFeedback(
+ transactionInfo.authCode ?? ""
+ )
+ }
+ accessibilityLabel={`${I18n.t(
+ "transaction.details.info.authCode"
+ )}: ${transactionInfo.authCode}`}
+ label={I18n.t("transaction.details.info.authCode")}
+ value={transactionInfo.authCode}
+ />
+
+ >
+ )}
+ {transactionInfo.transactionId && (
+
+ clipboardSetStringWithFeedback(
+ transactionInfo.transactionId ?? ""
+ )
+ }
+ accessibilityLabel={`${I18n.t(
+ "transaction.details.info.transactionId"
+ )}: ${transactionInfo.transactionId}`}
+ label={I18n.t("transaction.details.info.transactionId")}
+ value={transactionInfo.transactionId}
+ />
+ )}
+ >
+ )}
+
+
+ >
+ );
+};
+
+const renderPaymentMethod = (walletInfo: WalletInfo) => {
+ if (walletInfo.blurredNumber && walletInfo.brand) {
+ return (
+
+ );
+ }
+ if (walletInfo.maskedEmail) {
+ return (
+
+ );
+ }
+ return <>>;
+};
+
+const SkeletonItem = () => (
+
+
+
+
+
+);
+
+export default PaymentsBizEventsTransactionInfoSection;
diff --git a/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionTotalAmount.tsx b/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionTotalAmount.tsx
new file mode 100644
index 00000000000..ef1c2584b95
--- /dev/null
+++ b/ts/features/payments/bizEventsTransaction/components/PaymentsBizEventsTransactionTotalAmount.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+import Placeholder from "rn-placeholder";
+import { View } from "react-native";
+import { H3, H6, IOStyles } from "@pagopa/io-app-design-system";
+import I18n from "../../../../i18n";
+import { formatAmountText } from "../utils";
+
+type TotalAmountSectionProps = {
+ totalAmount?: string;
+ loading?: boolean;
+};
+
+export const PaymentsBizEventsTransactionTotalAmount = ({
+ totalAmount,
+ loading
+}: TotalAmountSectionProps) => (
+
+ {I18n.t("transaction.details.totalAmount")}
+ {loading && (
+
+
+
+ )}
+ {!loading && totalAmount && {formatAmountText(totalAmount)}
}
+
+);
diff --git a/ts/features/payments/bizEventsTransaction/navigation/navigator.tsx b/ts/features/payments/bizEventsTransaction/navigation/navigator.tsx
new file mode 100644
index 00000000000..35a2fb76dbe
--- /dev/null
+++ b/ts/features/payments/bizEventsTransaction/navigation/navigator.tsx
@@ -0,0 +1,59 @@
+import { ParamListBase } from "@react-navigation/native";
+import {
+ createStackNavigator,
+ StackNavigationProp
+} from "@react-navigation/stack";
+import React from "react";
+import { isGestureEnabled } from "../../../../utils/navigation";
+import { PaymentsTransactionBizEventsDetailsScreen } from "../screens/PaymentsTransactionBizEventsDetailsScreen";
+import { PaymentsTransactionBizEventsListScreen } from "../screens/PaymentsTransactionBizEventsListScreen";
+import WalletTransactionCartItemDetailsScreen from "../screens/PaymentsTransactionBizEventsCartItemDetailsScreen";
+import { PaymentsTransactionBizEventsParamsList } from "./params";
+import { PaymentsTransactionBizEventsRoutes } from "./routes";
+
+const Stack = createStackNavigator();
+
+export const PaymentsTransactionBizEventsNavigator = () => (
+
+
+
+
+
+);
+
+export type PaymentsTransactionBizEventsStackNavigationProp<
+ ParamList extends ParamListBase,
+ RouteName extends keyof ParamList = string
+> = StackNavigationProp<
+ PaymentsTransactionBizEventsParamsList & ParamList,
+ RouteName
+>;
+
+export type PaymentsTransactionBizEventsStackNavigation =
+ PaymentsTransactionBizEventsStackNavigationProp<
+ PaymentsTransactionBizEventsParamsList,
+ keyof PaymentsTransactionBizEventsParamsList
+ >;
diff --git a/ts/features/payments/bizEventsTransaction/navigation/params.ts b/ts/features/payments/bizEventsTransaction/navigation/params.ts
new file mode 100644
index 00000000000..28da72bc85e
--- /dev/null
+++ b/ts/features/payments/bizEventsTransaction/navigation/params.ts
@@ -0,0 +1,10 @@
+import { PaymentsTransactionBizEventsDetailsScreenParams } from "../screens/PaymentsTransactionBizEventsDetailsScreen";
+import { PaymentsTransactionBizEventsCartItemDetailsScreenParams } from "../screens/PaymentsTransactionBizEventsCartItemDetailsScreen";
+import { PaymentsTransactionBizEventsRoutes } from "./routes";
+
+export type PaymentsTransactionBizEventsParamsList = {
+ [PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_NAVIGATOR]: undefined;
+ [PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_DETAILS]: PaymentsTransactionBizEventsDetailsScreenParams;
+ [PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_CART_ITEM_DETAILS]: PaymentsTransactionBizEventsCartItemDetailsScreenParams;
+ [PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_LIST_SCREEN]: undefined;
+};
diff --git a/ts/features/payments/bizEventsTransaction/navigation/routes.ts b/ts/features/payments/bizEventsTransaction/navigation/routes.ts
new file mode 100644
index 00000000000..422db8fae7c
--- /dev/null
+++ b/ts/features/payments/bizEventsTransaction/navigation/routes.ts
@@ -0,0 +1,10 @@
+export const PaymentsTransactionBizEventsRoutes = {
+ PAYMENT_TRANSACTION_BIZ_EVENTS_NAVIGATOR:
+ "PAYMENT_TRANSACTION_BIZ_EVENTS_NAVIGATOR",
+ PAYMENT_TRANSACTION_BIZ_EVENTS_DETAILS:
+ "PAYMENT_TRANSACTION_BIZ_EVENTS_DETAILS",
+ PAYMENT_TRANSACTION_BIZ_EVENTS_CART_ITEM_DETAILS:
+ "PAYMENT_TRANSACTION_BIZ_EVENTS_CART_ITEM_DETAILS",
+ PAYMENT_TRANSACTION_BIZ_EVENTS_LIST_SCREEN:
+ "PAYMENT_TRANSACTION_BIZ_EVENTS_LIST_SCREEN"
+} as const;
diff --git a/ts/features/payments/bizEventsTransaction/saga/handleGetBizEventsTransactionDetails.ts b/ts/features/payments/bizEventsTransaction/saga/handleGetBizEventsTransactionDetails.ts
new file mode 100644
index 00000000000..7af433f9d15
--- /dev/null
+++ b/ts/features/payments/bizEventsTransaction/saga/handleGetBizEventsTransactionDetails.ts
@@ -0,0 +1,81 @@
+import * as E from "fp-ts/lib/Either";
+import { call, put } from "typed-redux-saga/macro";
+import { ActionType } from "typesafe-actions";
+import { getPaymentsBizEventsTransactionDetailsAction } from "../store/actions";
+import { getGenericError, getNetworkError } from "../../../../utils/errors";
+import { TransactionClient } from "../../common/api/client";
+import { getOrFetchWalletSessionToken } from "../../checkout/saga/networking/handleWalletPaymentNewSessionToken";
+import { withRefreshApiCall } from "../../../fastLogin/saga/utils";
+import { readablePrivacyReport } from "../../../../utils/reporters";
+import { SagaCallReturnType } from "../../../../types/utils";
+
+/**
+ * Handle the remote call to get the transaction details from the biz events API
+ @param getPaymentMethods
+ * @param action
+ */
+export function* handleGetBizEventsTransactionDetails(
+ getTransactionDetails: TransactionClient["getTransactionDetails"],
+ action: ActionType<
+ (typeof getPaymentsBizEventsTransactionDetailsAction)["request"]
+ >
+) {
+ const sessionToken = yield* getOrFetchWalletSessionToken();
+
+ if (sessionToken === undefined) {
+ yield* put(
+ getPaymentsBizEventsTransactionDetailsAction.failure({
+ ...getGenericError(new Error(`Missing session token`))
+ })
+ );
+ return;
+ }
+ const getTransactionDetailsRequest = getTransactionDetails({
+ Authorization: sessionToken,
+ "transaction-id": action.payload.transactionId
+ });
+
+ try {
+ const getTransactionDetailsResult = (yield* call(
+ withRefreshApiCall,
+ getTransactionDetailsRequest,
+ action
+ )) as unknown as SagaCallReturnType;
+
+ if (E.isLeft(getTransactionDetailsResult)) {
+ yield* put(
+ getPaymentsBizEventsTransactionDetailsAction.failure({
+ ...getGenericError(
+ new Error(readablePrivacyReport(getTransactionDetailsResult.left))
+ )
+ })
+ );
+ return;
+ }
+
+ if (getTransactionDetailsResult.right.status === 200) {
+ yield* put(
+ getPaymentsBizEventsTransactionDetailsAction.success(
+ getTransactionDetailsResult.right.value
+ )
+ );
+ } else if (getTransactionDetailsResult.right.status !== 401) {
+ // The 401 status is handled by the withRefreshApiCall
+ yield* put(
+ getPaymentsBizEventsTransactionDetailsAction.failure({
+ ...getGenericError(
+ new Error(
+ `response status code ${getTransactionDetailsResult.right.status}`
+ )
+ )
+ })
+ );
+ }
+ } catch (e) {
+ yield* put(
+ getPaymentsBizEventsTransactionDetailsAction.failure({
+ ...getNetworkError(e)
+ })
+ );
+ }
+}
diff --git a/ts/features/payments/bizEventsTransaction/saga/handleGetBizEventsTransactions.ts b/ts/features/payments/bizEventsTransaction/saga/handleGetBizEventsTransactions.ts
new file mode 100644
index 00000000000..1e65a4a9ba9
--- /dev/null
+++ b/ts/features/payments/bizEventsTransaction/saga/handleGetBizEventsTransactions.ts
@@ -0,0 +1,86 @@
+import { pipe } from "fp-ts/lib/function";
+import * as E from "fp-ts/lib/Either";
+import { call, put } from "typed-redux-saga/macro";
+import { ActionType } from "typesafe-actions";
+import { getGenericError, getNetworkError } from "../../../../utils/errors";
+import { getPaymentsBizEventsTransactionsAction } from "../store/actions";
+import { TransactionClient } from "../../common/api/client";
+import { getOrFetchWalletSessionToken } from "../../checkout/saga/networking/handleWalletPaymentNewSessionToken";
+import { withRefreshApiCall } from "../../../fastLogin/saga/utils";
+import { SagaCallReturnType } from "../../../../types/utils";
+import { readablePrivacyReport } from "../../../../utils/reporters";
+import { BizEventsHeaders } from "../utils/types";
+
+const DEFAULT_TRANSACTION_LIST_SIZE = 10;
+
+export function* handleGetBizEventsTransactions(
+ getTransactionList: TransactionClient["getTransactionList"],
+ action: ActionType<(typeof getPaymentsBizEventsTransactionsAction)["request"]>
+) {
+ const sessionToken = yield* getOrFetchWalletSessionToken();
+
+ if (sessionToken === undefined) {
+ yield* put(
+ getPaymentsBizEventsTransactionsAction.failure({
+ ...getGenericError(new Error(`Missing session token`))
+ })
+ );
+ return;
+ }
+ const getTransactionListRequest = getTransactionList({
+ size: action.payload.size || DEFAULT_TRANSACTION_LIST_SIZE,
+ Authorization: sessionToken,
+ "x-continuation-token": action.payload.continuationToken
+ });
+
+ try {
+ const getTransactionListResult = (yield* call(
+ withRefreshApiCall,
+ getTransactionListRequest,
+ action
+ )) as unknown as SagaCallReturnType;
+
+ if (E.isLeft(getTransactionListResult)) {
+ yield* put(
+ getPaymentsBizEventsTransactionsAction.failure({
+ ...getGenericError(
+ new Error(readablePrivacyReport(getTransactionListResult.left))
+ )
+ })
+ );
+ return;
+ }
+ if (getTransactionListResult.right.status === 200) {
+ const continuationToken = pipe(
+ getTransactionListResult.right.headers,
+ BizEventsHeaders.decode,
+ E.map(headers => headers.map["x-continuation-token"]),
+ E.getOrElseW(() => undefined)
+ );
+ action.payload.onSuccess?.(continuationToken);
+ yield* put(
+ getPaymentsBizEventsTransactionsAction.success({
+ data: getTransactionListResult.right.value,
+ appendElements: action.payload.firstLoad
+ })
+ );
+ } else if (getTransactionListResult.right.status === 404) {
+ yield* put(getPaymentsBizEventsTransactionsAction.success({ data: [] }));
+ } else if (getTransactionListResult.right.status !== 401) {
+ // The 401 status is handled by the withRefreshApiCall
+ yield* put(
+ getPaymentsBizEventsTransactionsAction.failure({
+ ...getGenericError(
+ new Error(
+ `response status code ${getTransactionListResult.right.status}`
+ )
+ )
+ })
+ );
+ }
+ } catch (e) {
+ yield* put(
+ getPaymentsBizEventsTransactionsAction.failure({ ...getNetworkError(e) })
+ );
+ }
+}
diff --git a/ts/features/payments/bizEventsTransaction/saga/handleGetLatestBizEventsTransactions.ts b/ts/features/payments/bizEventsTransaction/saga/handleGetLatestBizEventsTransactions.ts
new file mode 100644
index 00000000000..90a4075fb67
--- /dev/null
+++ b/ts/features/payments/bizEventsTransaction/saga/handleGetLatestBizEventsTransactions.ts
@@ -0,0 +1,79 @@
+import * as E from "fp-ts/lib/Either";
+import { call, put } from "typed-redux-saga/macro";
+import { ActionType } from "typesafe-actions";
+import { getGenericError, getNetworkError } from "../../../../utils/errors";
+import { getPaymentsLatestBizEventsTransactionsAction } from "../store/actions";
+import { TransactionClient } from "../../common/api/client";
+import { getOrFetchWalletSessionToken } from "../../checkout/saga/networking/handleWalletPaymentNewSessionToken";
+import { withRefreshApiCall } from "../../../fastLogin/saga/utils";
+import { SagaCallReturnType } from "../../../../types/utils";
+import { readablePrivacyReport } from "../../../../utils/reporters";
+
+const DEFAULT_LATEST_TRANSACTION_LIST_SIZE = 5;
+
+export function* handleGetLatestBizEventsTransactions(
+ getTransactionList: TransactionClient["getTransactionList"],
+ action: ActionType<
+ (typeof getPaymentsLatestBizEventsTransactionsAction)["request"]
+ >
+) {
+ const sessionToken = yield* getOrFetchWalletSessionToken();
+
+ if (sessionToken === undefined) {
+ yield* put(
+ getPaymentsLatestBizEventsTransactionsAction.failure({
+ ...getGenericError(new Error(`Missing session token`))
+ })
+ );
+ return;
+ }
+ const getTransactionListRequest = getTransactionList({
+ size: DEFAULT_LATEST_TRANSACTION_LIST_SIZE,
+ Authorization: sessionToken
+ });
+
+ try {
+ const getTransactionListResult = (yield* call(
+ withRefreshApiCall,
+ getTransactionListRequest,
+ action
+ )) as unknown as SagaCallReturnType;
+
+ if (E.isLeft(getTransactionListResult)) {
+ yield* put(
+ getPaymentsLatestBizEventsTransactionsAction.failure({
+ ...getGenericError(
+ new Error(readablePrivacyReport(getTransactionListResult.left))
+ )
+ })
+ );
+ return;
+ }
+ if (getTransactionListResult.right.status === 200) {
+ yield* put(
+ getPaymentsLatestBizEventsTransactionsAction.success(
+ getTransactionListResult.right.value
+ )
+ );
+ } else if (getTransactionListResult.right.status === 404) {
+ yield* put(getPaymentsLatestBizEventsTransactionsAction.success([]));
+ } else if (getTransactionListResult.right.status !== 401) {
+ // The 401 status is handled by the withRefreshApiCall
+ yield* put(
+ getPaymentsLatestBizEventsTransactionsAction.failure({
+ ...getGenericError(
+ new Error(
+ `response status code ${getTransactionListResult.right.status}`
+ )
+ )
+ })
+ );
+ }
+ } catch (e) {
+ yield* put(
+ getPaymentsLatestBizEventsTransactionsAction.failure({
+ ...getNetworkError(e)
+ })
+ );
+ }
+}
diff --git a/ts/features/payments/bizEventsTransaction/saga/index.ts b/ts/features/payments/bizEventsTransaction/saga/index.ts
new file mode 100644
index 00000000000..81cd46e4334
--- /dev/null
+++ b/ts/features/payments/bizEventsTransaction/saga/index.ts
@@ -0,0 +1,38 @@
+import { SagaIterator } from "redux-saga";
+import { takeLatest } from "typed-redux-saga/macro";
+
+import { TransactionClient } from "../../common/api/client";
+import {
+ getPaymentsLatestBizEventsTransactionsAction,
+ getPaymentsBizEventsTransactionDetailsAction,
+ getPaymentsBizEventsTransactionsAction
+} from "../store/actions";
+import { handleGetLatestBizEventsTransactions } from "./handleGetLatestBizEventsTransactions";
+import { handleGetBizEventsTransactions } from "./handleGetBizEventsTransactions";
+import { handleGetBizEventsTransactionDetails } from "./handleGetBizEventsTransactionDetails";
+
+/**
+ * Handle Wallet transaction requests
+ * @param bearerToken
+ */
+export function* watchPaymentsBizEventsTransactionSaga(
+ transactionClient: TransactionClient
+): SagaIterator {
+ yield* takeLatest(
+ getPaymentsBizEventsTransactionsAction.request,
+ handleGetBizEventsTransactions,
+ transactionClient.getTransactionList
+ );
+
+ yield* takeLatest(
+ getPaymentsLatestBizEventsTransactionsAction.request,
+ handleGetLatestBizEventsTransactions,
+ transactionClient.getTransactionList
+ );
+
+ yield* takeLatest(
+ getPaymentsBizEventsTransactionDetailsAction.request,
+ handleGetBizEventsTransactionDetails,
+ transactionClient.getTransactionDetails
+ );
+}
diff --git a/ts/features/payments/bizEventsTransaction/screens/PaymentsTransactionBizEventsCartItemDetailsScreen.tsx b/ts/features/payments/bizEventsTransaction/screens/PaymentsTransactionBizEventsCartItemDetailsScreen.tsx
new file mode 100644
index 00000000000..719d9dcff33
--- /dev/null
+++ b/ts/features/payments/bizEventsTransaction/screens/PaymentsTransactionBizEventsCartItemDetailsScreen.tsx
@@ -0,0 +1,94 @@
+import * as React from "react";
+import { ScrollView } from "react-native";
+import {
+ Divider,
+ H6,
+ IOStyles,
+ ListItemInfo
+} from "@pagopa/io-app-design-system";
+import { RouteProp, useRoute } from "@react-navigation/native";
+import { PaymentsTransactionBizEventsParamsList } from "../navigation/params";
+import I18n from "../../../../i18n";
+import { CartItem } from "../../../../../definitions/pagopa/biz-events/CartItem";
+import { UserDetail } from "../../../../../definitions/pagopa/biz-events/UserDetail";
+import { formatAmountText } from "../utils";
+import { IOScrollViewWithLargeHeader } from "../../../../components/ui/IOScrollViewWithLargeHeader";
+
+export type PaymentsTransactionBizEventsCartItemDetailsScreenParams = {
+ cartItem: CartItem;
+};
+
+export type PaymentsTransactionBizEventsCartItemDetailsScreenProps = RouteProp<
+ PaymentsTransactionBizEventsParamsList,
+ "PAYMENT_TRANSACTION_BIZ_EVENTS_CART_ITEM_DETAILS"
+>;
+
+const PaymentsTransactionBizEventsCartItemDetailsScreen = () => {
+ const route =
+ useRoute();
+ const { cartItem } = route.params;
+
+ const getDebtorText = (debtor: UserDetail) => (
+ <>
+ {debtor.name ? {debtor.name}
: null}
+ {debtor.taxCode ? ({debtor.taxCode})
: null}
+ >
+ );
+
+ return (
+
+
+ {cartItem.amount && (
+ <>
+
+
+ >
+ )}
+ {cartItem.payee && (
+ <>
+
+
+ >
+ )}
+ {cartItem.debtor &&
+ (cartItem.debtor.name || cartItem.debtor.taxCode) && (
+ <>
+
+
+ >
+ )}
+ {cartItem.refNumberValue && (
+ <>
+
+
+ >
+ )}
+ {cartItem.payee && (
+
+ )}
+
+
+ );
+};
+
+export default PaymentsTransactionBizEventsCartItemDetailsScreen;
diff --git a/ts/features/payments/bizEventsTransaction/screens/PaymentsTransactionBizEventsDetailsScreen.tsx b/ts/features/payments/bizEventsTransaction/screens/PaymentsTransactionBizEventsDetailsScreen.tsx
new file mode 100644
index 00000000000..eecd71ebef9
--- /dev/null
+++ b/ts/features/payments/bizEventsTransaction/screens/PaymentsTransactionBizEventsDetailsScreen.tsx
@@ -0,0 +1,118 @@
+import { IOColors } from "@pagopa/io-app-design-system";
+import * as pot from "@pagopa/ts-commons/lib/pot";
+import { RouteProp, useRoute } from "@react-navigation/native";
+import * as React from "react";
+import { Dimensions, StyleSheet, View } from "react-native";
+import FocusAwareStatusBar from "../../../../components/ui/FocusAwareStatusBar";
+import I18n from "../../../../i18n";
+import { useIODispatch, useIOSelector } from "../../../../store/hooks";
+import { emptyContextualHelp } from "../../../../utils/emptyContextualHelp";
+import { useOnFirstRender } from "../../../../utils/hooks/useOnFirstRender";
+import { PaymentsBizEventsTransactionHeadingSection } from "../components/PaymentsBizEventsTransactionHeadingSection";
+import WalletTransactionInfoSection from "../components/PaymentsBizEventsTransactionInfoSection";
+import { PaymentsTransactionBizEventsParamsList } from "../navigation/params";
+import { getPaymentsBizEventsTransactionDetailsAction } from "../store/actions";
+import { walletTransactionBizEventsDetailsPotSelector } from "../store/selectors";
+import { OperationResultScreenContent } from "../../../../components/screens/OperationResultScreenContent";
+import { useIONavigation } from "../../../../navigation/params/AppParamsList";
+import { IOScrollViewWithLargeHeader } from "../../../../components/ui/IOScrollViewWithLargeHeader";
+
+export type PaymentsTransactionBizEventsDetailsScreenParams = {
+ transactionId: string;
+};
+
+export type PaymentsTransactionBizEventsDetailsScreenProps = RouteProp<
+ PaymentsTransactionBizEventsParamsList,
+ "PAYMENT_TRANSACTION_BIZ_EVENTS_DETAILS"
+>;
+
+const windowHeight = Dimensions.get("window").height;
+
+const styles = StyleSheet.create({
+ bottomBackground: {
+ position: "absolute",
+ height: windowHeight,
+ bottom: -windowHeight,
+ left: 0,
+ right: 0,
+ backgroundColor: IOColors["grey-50"]
+ },
+ wrapper: {
+ flexGrow: 1,
+ alignContent: "flex-start",
+ backgroundColor: IOColors["grey-50"]
+ }
+});
+
+const PaymentsTransactionBizEventsDetailsScreen = () => {
+ const dispatch = useIODispatch();
+ const navigation = useIONavigation();
+ const route = useRoute();
+ const { transactionId } = route.params;
+ const transactionDetailsPot = useIOSelector(
+ walletTransactionBizEventsDetailsPotSelector
+ );
+
+ const isLoading = pot.isLoading(transactionDetailsPot);
+ const isError = pot.isError(transactionDetailsPot);
+ const transactionDetails = pot.toUndefined(transactionDetailsPot);
+
+ useOnFirstRender(() => {
+ dispatch(
+ getPaymentsBizEventsTransactionDetailsAction.request({ transactionId })
+ );
+ });
+
+ // eslint-disable-next-line sonarjs/no-identical-functions
+ const handleOnRetry = () => {
+ dispatch(
+ getPaymentsBizEventsTransactionDetailsAction.request({ transactionId })
+ );
+ };
+
+ if (isError) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+ {/* The following line is used to show the background color gray that overlay the basic one which is white */}
+
+
+
+
+
+ );
+};
+
+export { PaymentsTransactionBizEventsDetailsScreen };
diff --git a/ts/features/payments/bizEventsTransaction/screens/PaymentsTransactionBizEventsListScreen.tsx b/ts/features/payments/bizEventsTransaction/screens/PaymentsTransactionBizEventsListScreen.tsx
new file mode 100644
index 00000000000..ddf45a66712
--- /dev/null
+++ b/ts/features/payments/bizEventsTransaction/screens/PaymentsTransactionBizEventsListScreen.tsx
@@ -0,0 +1,203 @@
+import {
+ Divider,
+ H2,
+ IOStyles,
+ ListItemHeader,
+ ListItemTransaction
+} from "@pagopa/io-app-design-system";
+import * as pot from "@pagopa/ts-commons/lib/pot";
+import { RouteProp } from "@react-navigation/native";
+import * as React from "react";
+import {
+ LayoutChangeEvent,
+ SectionList,
+ SectionListData,
+ View
+} from "react-native";
+import Animated, {
+ useAnimatedScrollHandler,
+ useSharedValue
+} from "react-native-reanimated";
+import { useSafeAreaInsets } from "react-native-safe-area-context";
+import { useIODispatch, useIOSelector } from "../../../../store/hooks";
+import { PaymentsTransactionBizEventsParamsList } from "../navigation/params";
+import { getPaymentsBizEventsTransactionsAction } from "../store/actions";
+import { walletTransactionBizEventsListPotSelector } from "../store/selectors";
+import { TransactionListItem } from "../../../../../definitions/pagopa/biz-events/TransactionListItem";
+import { useIONavigation } from "../../../../navigation/params/AppParamsList";
+import { isPaymentsTransactionsEmptySelector } from "../../home/store/selectors";
+import { PaymentsBizEventsListItemTransaction } from "../components/PaymentsBizEventsListItemTransaction";
+import { PaymentsHomeEmptyScreenContent } from "../../home/components/PaymentsHomeEmptyScreenContent";
+import { useHeaderSecondLevel } from "../../../../hooks/useHeaderSecondLevel";
+import { useOnFirstRender } from "../../../../utils/hooks/useOnFirstRender";
+import { groupTransactionsByMonth } from "../utils";
+import I18n from "../../../../i18n";
+import { PaymentsTransactionBizEventsRoutes } from "../navigation/routes";
+
+export type PaymentsTransactionBizEventsListScreenProps = RouteProp<
+ PaymentsTransactionBizEventsParamsList,
+ "PAYMENT_TRANSACTION_BIZ_EVENTS_DETAILS"
+>;
+
+const AnimatedSectionList = Animated.createAnimatedComponent(
+ SectionList as new () => SectionList
+);
+
+const PaymentsTransactionBizEventsListScreen = () => {
+ const dispatch = useIODispatch();
+ const navigation = useIONavigation();
+
+ const scrollTranslationY = useSharedValue(0);
+ const [titleHeight, setTitleHeight] = React.useState(0);
+ const [continuationToken, setContinuationToken] = React.useState<
+ string | undefined
+ >();
+ const [groupedTransactions, setGroupedTransactions] =
+ React.useState>>();
+ const insets = useSafeAreaInsets();
+
+ const transactionsPot = useIOSelector(
+ walletTransactionBizEventsListPotSelector
+ );
+ const isEmpty = useIOSelector(isPaymentsTransactionsEmptySelector);
+
+ const isLoading = pot.isLoading(transactionsPot);
+
+ const handleNavigateToTransactionDetails = (
+ transaction: TransactionListItem
+ ) => {
+ if (transaction.transactionId === undefined) {
+ return;
+ }
+ navigation.navigate(
+ PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_NAVIGATOR,
+ {
+ screen:
+ PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_DETAILS,
+ params: {
+ transactionId: transaction.transactionId
+ }
+ }
+ );
+ };
+
+ const scrollHandler = useAnimatedScrollHandler(({ contentOffset }) => {
+ // eslint-disable-next-line functional/immutable-data
+ scrollTranslationY.value = contentOffset.y;
+ });
+
+ const getTitleHeight = (event: LayoutChangeEvent) => {
+ const { height } = event.nativeEvent.layout;
+ setTitleHeight(height);
+ };
+
+ const handleOnSuccess = (continuationToken?: string) => {
+ setContinuationToken(continuationToken);
+ };
+
+ useOnFirstRender(
+ React.useCallback(() => {
+ dispatch(
+ getPaymentsBizEventsTransactionsAction.request({
+ firstLoad: true,
+ onSuccess: handleOnSuccess
+ })
+ );
+ }, [dispatch])
+ );
+
+ React.useEffect(() => {
+ if (pot.isSome(transactionsPot)) {
+ setGroupedTransactions(groupTransactionsByMonth(transactionsPot.value));
+ }
+ }, [transactionsPot]);
+
+ useHeaderSecondLevel({
+ title: I18n.t("features.payments.transactions.title"),
+ canGoBack: true,
+ supportRequest: true,
+ scrollValues: {
+ contentOffsetY: scrollTranslationY,
+ triggerOffset: titleHeight
+ }
+ });
+
+ const SectionListHeaderTitle = (
+
+
+ {I18n.t("features.payments.transactions.title")}
+
+
+ );
+
+ const renderLoadingFooter = () => (
+ <>
+ {isLoading &&
+ Array.from({ length: 5 }).map((_, index) => (
+
+ ))}
+ >
+ );
+
+ if (isEmpty) {
+ return ;
+ }
+
+ const fetchNextPage = () => {
+ if (!continuationToken || isLoading) {
+ return;
+ }
+ dispatch(
+ getPaymentsBizEventsTransactionsAction.request({
+ continuationToken,
+ onSuccess: handleOnSuccess
+ })
+ );
+ };
+
+ return (
+ (
+
+ )}
+ ListFooterComponent={renderLoadingFooter}
+ keyExtractor={item => `transaction_${item.transactionId}`}
+ renderItem={({ item }) => (
+ handleNavigateToTransactionDetails(item)}
+ transaction={item}
+ />
+ )}
+ />
+ );
+};
+
+export { PaymentsTransactionBizEventsListScreen };
diff --git a/ts/features/payments/bizEventsTransaction/store/actions/index.ts b/ts/features/payments/bizEventsTransaction/store/actions/index.ts
new file mode 100644
index 00000000000..7c33ef7e972
--- /dev/null
+++ b/ts/features/payments/bizEventsTransaction/store/actions/index.ts
@@ -0,0 +1,47 @@
+import { ActionType, createAsyncAction } from "typesafe-actions";
+import { NetworkError } from "../../../../../utils/errors";
+import { TransactionDetailResponse } from "../../../../../../definitions/pagopa/biz-events/TransactionDetailResponse";
+import { TransactionListWrapResponse } from "../../../../../../definitions/pagopa/biz-events/TransactionListWrapResponse";
+
+export type PaymentsGetBizEventsTransactionPayload = {
+ firstLoad?: boolean;
+ size?: number;
+ continuationToken?: string;
+ onSuccess?: (continuationToken?: string) => void;
+};
+
+export type PaymentsGetBizEventsTransactionSuccessPayload = {
+ data: TransactionListWrapResponse;
+ appendElements?: boolean;
+};
+
+export const getPaymentsBizEventsTransactionsAction = createAsyncAction(
+ "PAYMENTS_TRANSACTIONS_LIST_REQUEST",
+ "PAYMENTS_TRANSACTIONS_LIST_SUCCESS",
+ "PAYMENTS_TRANSACTIONS_LIST_FAILURE",
+ "PAYMENTS_TRANSACTIONS_LIST_CANCEL"
+)<
+ PaymentsGetBizEventsTransactionPayload,
+ PaymentsGetBizEventsTransactionSuccessPayload,
+ NetworkError,
+ void
+>();
+
+export const getPaymentsLatestBizEventsTransactionsAction = createAsyncAction(
+ "PAYMENTS_LATEST_TRANSACTIONS_LIST_REQUEST",
+ "PAYMENTS_LATEST_TRANSACTIONS_LIST_SUCCESS",
+ "PAYMENTS_LATEST_TRANSACTIONS_LIST_FAILURE",
+ "PAYMENTS_LATEST_TRANSACTIONS_LIST_CANCEL"
+)();
+
+export const getPaymentsBizEventsTransactionDetailsAction = createAsyncAction(
+ "PAYMENTS_BIZ_EVENTS_TRANSACTION_DETAILS_REQUEST",
+ "PAYMENTS_BIZ_EVENTS_TRANSACTION_DETAILS_SUCCESS",
+ "PAYMENTS_BIZ_EVENTS_TRANSACTION_DETAILS_FAILURE",
+ "PAYMENTS_BIZ_EVENTS_TRANSACTION_DETAILS_CANCEL"
+)<{ transactionId: string }, TransactionDetailResponse, NetworkError, void>();
+
+export type PaymentsTransactionBizEventsActions =
+ | ActionType
+ | ActionType
+ | ActionType;
diff --git a/ts/features/payments/bizEventsTransaction/store/reducers/index.ts b/ts/features/payments/bizEventsTransaction/store/reducers/index.ts
new file mode 100644
index 00000000000..4ae878209d5
--- /dev/null
+++ b/ts/features/payments/bizEventsTransaction/store/reducers/index.ts
@@ -0,0 +1,105 @@
+import * as pot from "@pagopa/ts-commons/lib/pot";
+import { getType } from "typesafe-actions";
+import { Action } from "../../../../../store/actions/types";
+import { NetworkError } from "../../../../../utils/errors";
+
+import {
+ getPaymentsBizEventsTransactionDetailsAction,
+ getPaymentsLatestBizEventsTransactionsAction,
+ getPaymentsBizEventsTransactionsAction
+} from "../actions";
+import { TransactionListItem } from "../../../../../../definitions/pagopa/biz-events/TransactionListItem";
+import { TransactionDetailResponse } from "../../../../../../definitions/pagopa/biz-events/TransactionDetailResponse";
+
+export type PaymentsBizEventsTransactionState = {
+ transactions: pot.Pot, NetworkError>;
+ latestTransactions: pot.Pot, NetworkError>;
+ details: pot.Pot;
+};
+
+const INITIAL_STATE: PaymentsBizEventsTransactionState = {
+ transactions: pot.noneLoading,
+ latestTransactions: pot.noneLoading,
+ details: pot.noneLoading
+};
+
+const reducer = (
+ state: PaymentsBizEventsTransactionState = INITIAL_STATE,
+ action: Action
+): PaymentsBizEventsTransactionState => {
+ switch (action.type) {
+ // GET LATEST TRANSACTIONS LIST
+ case getType(getPaymentsLatestBizEventsTransactionsAction.request):
+ return {
+ ...state,
+ latestTransactions: pot.toLoading(state.latestTransactions)
+ };
+ case getType(getPaymentsLatestBizEventsTransactionsAction.success):
+ return {
+ ...state,
+ latestTransactions: pot.some(action.payload.transactions || [])
+ };
+ case getType(getPaymentsLatestBizEventsTransactionsAction.failure):
+ return {
+ ...state,
+ latestTransactions: pot.toError(
+ state.latestTransactions,
+ action.payload
+ )
+ };
+ case getType(getPaymentsLatestBizEventsTransactionsAction.cancel):
+ return {
+ ...state,
+ latestTransactions: pot.none
+ };
+ // GET TRANSACTIONS LIST
+ case getType(getPaymentsBizEventsTransactionsAction.request):
+ return {
+ ...state,
+ transactions: pot.toLoading(state.transactions)
+ };
+ case getType(getPaymentsBizEventsTransactionsAction.success):
+ const previousTransactions = pot.getOrElse(state.transactions, []);
+ const maybeTransactions = action.payload.data.transactions || [];
+ return {
+ ...state,
+ transactions: !action.payload.appendElements
+ ? pot.some([...previousTransactions, ...maybeTransactions])
+ : pot.some(maybeTransactions)
+ };
+ case getType(getPaymentsBizEventsTransactionsAction.failure):
+ return {
+ ...state,
+ transactions: pot.toError(state.transactions, action.payload)
+ };
+ case getType(getPaymentsBizEventsTransactionsAction.cancel):
+ return {
+ ...state,
+ transactions: pot.none
+ };
+ // GET BIZ-EVENTS TRANSACTION DETAILS
+ case getType(getPaymentsBizEventsTransactionDetailsAction.request):
+ return {
+ ...state,
+ details: pot.toLoading(state.details)
+ };
+ case getType(getPaymentsBizEventsTransactionDetailsAction.success):
+ return {
+ ...state,
+ details: pot.some(action.payload)
+ };
+ case getType(getPaymentsBizEventsTransactionDetailsAction.failure):
+ return {
+ ...state,
+ details: pot.toError(state.details, action.payload)
+ };
+ case getType(getPaymentsBizEventsTransactionDetailsAction.cancel):
+ return {
+ ...state,
+ details: pot.none
+ };
+ }
+ return state;
+};
+
+export default reducer;
diff --git a/ts/features/payments/bizEventsTransaction/store/selectors/index.ts b/ts/features/payments/bizEventsTransaction/store/selectors/index.ts
new file mode 100644
index 00000000000..a962ed3b1c0
--- /dev/null
+++ b/ts/features/payments/bizEventsTransaction/store/selectors/index.ts
@@ -0,0 +1,19 @@
+import { GlobalState } from "../../../../../store/reducers/types";
+
+const walletTransactionBizEventsSelector = (state: GlobalState) =>
+ state.features.payments.bizEventsTransaction;
+
+export const walletTransactionBizEventsDetailsPotSelector = (
+ state: GlobalState
+) => walletTransactionBizEventsSelector(state).details;
+
+export const walletTransactionBizEventsBizEventsDetailsPotSelector = (
+ state: GlobalState
+) => walletTransactionBizEventsSelector(state).details;
+
+export const walletTransactionBizEventsListPotSelector = (state: GlobalState) =>
+ walletTransactionBizEventsSelector(state).transactions;
+
+export const walletLatestTransactionsBizEventsListPotSelector = (
+ state: GlobalState
+) => walletTransactionBizEventsSelector(state).latestTransactions;
diff --git a/ts/features/payments/bizEventsTransaction/utils/index.ts b/ts/features/payments/bizEventsTransaction/utils/index.ts
new file mode 100644
index 00000000000..b6dc0959906
--- /dev/null
+++ b/ts/features/payments/bizEventsTransaction/utils/index.ts
@@ -0,0 +1,46 @@
+import { SectionListData } from "react-native";
+import { TransactionListItem } from "../../../../../definitions/pagopa/biz-events/TransactionListItem";
+
+/**
+ * Function that groups the transactions by month and returns an array of objects with the month as title and the transactions as data
+ * - The year is shown only if it's different from the current year
+ */
+export const groupTransactionsByMonth = (
+ transactions: ReadonlyArray
+): Array> => {
+ const groups = transactions.reduce((acc, element) => {
+ if (element.transactionDate !== undefined) {
+ const isCurrentYear =
+ new Date().getFullYear() ===
+ new Date(element.transactionDate).getFullYear();
+ const month = new Date(element.transactionDate).toLocaleString(
+ "default",
+ {
+ month: "long",
+ year: isCurrentYear ? undefined : "numeric"
+ }
+ );
+ return {
+ ...acc,
+ [month]: [...(acc[month] || []), element]
+ };
+ }
+ return acc;
+ }, {} as { [month: string]: Array });
+
+ return Object.keys(groups).map(month => ({
+ title: month,
+ data: groups[month]
+ }));
+};
+
+// This function formats the text of the amount that is a string composed by "1000.00" in the format "1.000,00 €" allowing only two decimal digits
+export const formatAmountText = (amount: string): string => {
+ const amountNumber = parseFloat(amount);
+ return amountNumber.toLocaleString("it-IT", {
+ style: "currency",
+ currency: "EUR",
+ minimumFractionDigits: 2,
+ maximumFractionDigits: 2
+ });
+};
diff --git a/ts/features/payments/bizEventsTransaction/utils/types.ts b/ts/features/payments/bizEventsTransaction/utils/types.ts
new file mode 100644
index 00000000000..8604fce0182
--- /dev/null
+++ b/ts/features/payments/bizEventsTransaction/utils/types.ts
@@ -0,0 +1,14 @@
+import * as t from "io-ts";
+
+/**
+ * BizEvents headers
+ * This is the type of the headers returned by the BizEvents API
+ * due to resolve wrong type error in the auto-generated client
+ */
+export const BizEventsHeaders = t.type({
+ map: t.type({
+ "x-continuation-token": t.string
+ })
+});
+
+export type BizEventsHeaders = t.TypeOf;
diff --git a/ts/features/payments/common/api/client.ts b/ts/features/payments/common/api/client.ts
index 353050b65cc..6b13f87ff5c 100644
--- a/ts/features/payments/common/api/client.ts
+++ b/ts/features/payments/common/api/client.ts
@@ -1,5 +1,6 @@
import { createClient } from "../../../../../definitions/pagopa/walletv3/client";
import { createClient as createECommerceClient } from "../../../../../definitions/pagopa/ecommerce/client";
+import { createClient as createBizEventsClient } from "../../../../../definitions/pagopa/biz-events/client";
import { defaultRetryingFetch } from "../../../../utils/fetch";
export const createWalletClient = (baseUrl: string, bearerAuth: string) =>
@@ -32,5 +33,21 @@ export const createPaymentClient = (baseUrl: string, token: string) =>
}
});
+export const createTransactionClient = (baseUrl: string, token: string) =>
+ createBizEventsClient<"walletId">({
+ baseUrl,
+ basePath: "/bizevents/bizevents/tx-service-jwt/v1",
+ fetchApi: defaultRetryingFetch(),
+ withDefaults: op => params => {
+ const paramsWithDefaults = {
+ ...params,
+ walletId: token
+ } as Parameters[0];
+
+ return op(paramsWithDefaults);
+ }
+ });
+
export type PaymentClient = ReturnType;
export type WalletClient = ReturnType;
+export type TransactionClient = ReturnType;
diff --git a/ts/features/payments/common/saga/index.ts b/ts/features/payments/common/saga/index.ts
index c68b76f3a6a..2907d30fbfc 100644
--- a/ts/features/payments/common/saga/index.ts
+++ b/ts/features/payments/common/saga/index.ts
@@ -3,11 +3,16 @@ import { fork, select } from "typed-redux-saga/macro";
import { isPagoPATestEnabledSelector } from "../../../../store/reducers/persistedPreferences";
import { watchPaymentsOnboardingSaga } from "../../onboarding/saga";
import { watchPaymentsCheckoutSaga } from "../../checkout/saga";
-import { createPaymentClient, createWalletClient } from "../api/client";
+import {
+ createPaymentClient,
+ createTransactionClient,
+ createWalletClient
+} from "../api/client";
import { walletApiBaseUrl, walletApiUatBaseUrl } from "../../../../config";
import { watchPaymentsMethodDetailsSaga } from "../../details/saga";
import { watchPaymentsTransactionSaga } from "../../transaction/saga";
import { watchPaymentsWalletSaga } from "../../wallet/saga";
+import { watchPaymentsBizEventsTransactionSaga } from "../../bizEventsTransaction/saga";
export function* watchPaymentsSaga(walletToken: string): SagaIterator {
const isPagoPATestEnabled = yield* select(isPagoPATestEnabledSelector);
@@ -18,10 +23,12 @@ export function* watchPaymentsSaga(walletToken: string): SagaIterator {
const walletClient = createWalletClient(walletBaseUrl, walletToken);
const paymentClient = createPaymentClient(walletBaseUrl, walletToken);
+ const transactionClient = createTransactionClient(walletBaseUrl, walletToken);
yield* fork(watchPaymentsWalletSaga, walletClient);
yield* fork(watchPaymentsOnboardingSaga, walletClient);
yield* fork(watchPaymentsMethodDetailsSaga, walletClient);
yield* fork(watchPaymentsTransactionSaga, walletClient);
+ yield* fork(watchPaymentsBizEventsTransactionSaga, transactionClient);
yield* fork(watchPaymentsCheckoutSaga, paymentClient);
}
diff --git a/ts/features/payments/common/store/actions/index.ts b/ts/features/payments/common/store/actions/index.ts
index 5a94e81f706..ebcb68f8cd0 100644
--- a/ts/features/payments/common/store/actions/index.ts
+++ b/ts/features/payments/common/store/actions/index.ts
@@ -5,6 +5,7 @@ import { PaymentsCheckoutActions } from "../../../checkout/store/actions";
import { PaymentsTransactionActions } from "../../../transaction/store/actions";
import { PaymentsHomeActions } from "../../../home/store/actions";
import { PaymentsWalletActions } from "../../../wallet/store/actions";
+import { PaymentsTransactionBizEventsActions } from "../../../bizEventsTransaction/store/actions";
export type PaymentsActions =
| PaymentsOnboardingActions
@@ -13,4 +14,5 @@ export type PaymentsActions =
| PaymentsTransactionActions
| PaymentsHistoryActions
| PaymentsHomeActions
- | PaymentsWalletActions;
+ | PaymentsWalletActions
+ | PaymentsTransactionBizEventsActions;
diff --git a/ts/features/payments/common/store/reducers/index.ts b/ts/features/payments/common/store/reducers/index.ts
index 2d627b2e7cb..00a4377d6cb 100644
--- a/ts/features/payments/common/store/reducers/index.ts
+++ b/ts/features/payments/common/store/reducers/index.ts
@@ -19,6 +19,9 @@ import homeReducer, { PaymentsHomeState } from "../../../home/store/reducers";
import paymentsWalletReducer, {
PaymentsWalletState
} from "../../../wallet/store/reducers";
+import paymentsBizEventsTransactionReducer, {
+ PaymentsBizEventsTransactionState
+} from "../../../bizEventsTransaction/store/reducers";
export type PaymentsState = {
onboarding: PaymentsOnboardingState;
@@ -28,6 +31,7 @@ export type PaymentsState = {
history: PaymentsHistoryState & PersistPartial;
home: PaymentsHomeState & PersistPartial;
wallet: PaymentsWalletState;
+ bizEventsTransaction: PaymentsBizEventsTransactionState;
};
const paymentsReducer = combineReducers({
@@ -37,7 +41,8 @@ const paymentsReducer = combineReducers({
transaction: transactionReducer,
history: historyReducer,
home: homeReducer,
- wallet: paymentsWalletReducer
+ wallet: paymentsWalletReducer,
+ bizEventsTransaction: paymentsBizEventsTransactionReducer
});
export default paymentsReducer;
diff --git a/ts/features/payments/common/utils/index.ts b/ts/features/payments/common/utils/index.ts
index 01f4efef7ac..9fa1f376857 100644
--- a/ts/features/payments/common/utils/index.ts
+++ b/ts/features/payments/common/utils/index.ts
@@ -17,6 +17,10 @@ import { PaymentCardProps } from "../components/PaymentCard";
import { UIWalletInfoDetails } from "../types/UIWalletInfoDetails";
import { findFirstCaseInsensitive } from "../../../../utils/object";
import { WalletCard } from "../../../newWallet/types";
+import { contentRepoUrl } from "../../../../config";
+import { TransactionListItem } from "../../../../../definitions/pagopa/biz-events/TransactionListItem";
+
+export const TRANSACTION_LOGO_CDN = `${contentRepoUrl}/logos/organizations`;
/**
* A simple function to get the corresponding translated badge text,
@@ -162,6 +166,15 @@ export const getPaymentLogoFromWalletDetails = (
}
};
+export const getTransactionLogo = (transaction: TransactionListItem) =>
+ pipe(
+ transaction.payeeTaxCode,
+ O.fromNullable,
+ O.map(
+ taxCode => `${TRANSACTION_LOGO_CDN}/${taxCode.replace(/^0+/, "")}.png`
+ )
+ );
+
export const mapWalletIdToCardKey = (walletId: string) => `method_${walletId}`;
export const mapWalletsToCards = (
diff --git a/ts/features/payments/home/components/PaymentsHomeTransactionsList.tsx b/ts/features/payments/home/components/PaymentsHomeTransactionsList.tsx
index f5fe17648b4..47c2cd33030 100644
--- a/ts/features/payments/home/components/PaymentsHomeTransactionsList.tsx
+++ b/ts/features/payments/home/components/PaymentsHomeTransactionsList.tsx
@@ -1,4 +1,5 @@
import {
+ Divider,
IOStyles,
ListItemHeader,
ListItemTransaction
@@ -9,12 +10,15 @@ import * as React from "react";
import { View } from "react-native";
import Animated, { Layout } from "react-native-reanimated";
import { default as I18n } from "../../../../i18n";
-import { fetchTransactionsRequestWithExpBackoff } from "../../../../store/actions/wallet/transactions";
import { useIODispatch, useIOSelector } from "../../../../store/hooks";
-import { latestTransactionsSelector } from "../../../../store/reducers/wallet/transactions";
-import { isPaymentsTransactionsEmptySelector } from "../store/selectors";
+import { isPaymentsLatestTransactionsEmptySelector } from "../store/selectors";
+import { walletLatestTransactionsBizEventsListPotSelector } from "../../bizEventsTransaction/store/selectors";
+import { getPaymentsLatestBizEventsTransactionsAction } from "../../bizEventsTransaction/store/actions";
+import { PaymentsBizEventsListItemTransaction } from "../../bizEventsTransaction/components/PaymentsBizEventsListItemTransaction";
+import { TransactionListItem } from "../../../../../definitions/pagopa/biz-events/TransactionListItem";
+import { useIONavigation } from "../../../../navigation/params/AppParamsList";
+import { PaymentsTransactionBizEventsRoutes } from "../../bizEventsTransaction/navigation/routes";
import { PaymentsHomeEmptyScreenContent } from "./PaymentsHomeEmptyScreenContent";
-import { PaymentsListItemTransaction } from "./PaymentsListItemTransaction";
type Props = {
enforcedLoadingState?: boolean;
@@ -22,27 +26,67 @@ type Props = {
const PaymentsHomeTransactionsList = ({ enforcedLoadingState }: Props) => {
const dispatch = useIODispatch();
+ const navigation = useIONavigation();
- const transactionsPot = useIOSelector(latestTransactionsSelector);
+ const latestTransactionsPot = useIOSelector(
+ walletLatestTransactionsBizEventsListPotSelector
+ );
- const isLoading = pot.isLoading(transactionsPot) || enforcedLoadingState;
- const isEmpty = useIOSelector(isPaymentsTransactionsEmptySelector);
+ const isLoading =
+ pot.isLoading(latestTransactionsPot) || enforcedLoadingState;
+ const isEmpty = useIOSelector(isPaymentsLatestTransactionsEmptySelector);
useFocusEffect(
React.useCallback(() => {
- dispatch(fetchTransactionsRequestWithExpBackoff({ start: 0 }));
+ dispatch(getPaymentsLatestBizEventsTransactionsAction.request());
}, [dispatch])
);
+ const handleNavigateToTransactionDetails = (
+ transaction: TransactionListItem
+ ) => {
+ if (transaction.transactionId === undefined) {
+ return;
+ }
+ navigation.navigate(
+ PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_NAVIGATOR,
+ {
+ screen:
+ PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_DETAILS,
+ params: {
+ transactionId: transaction.transactionId
+ }
+ }
+ );
+ };
+
+ const handleNavigateToTransactionList = () => {
+ navigation.navigate(
+ PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_NAVIGATOR,
+ {
+ screen:
+ PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_LIST_SCREEN
+ }
+ );
+ };
+
const renderItems = () => {
- if (!isLoading && pot.isSome(transactionsPot)) {
+ if (!isLoading && pot.isSome(latestTransactionsPot)) {
return (
- {transactionsPot.value.map(transaction => (
-
+ {latestTransactionsPot.value.map((latestTransaction, index) => (
+
+
+ handleNavigateToTransactionDetails(latestTransaction)
+ }
+ transaction={latestTransaction}
+ />
+ {index < latestTransactionsPot.value.length - 1 && }
+
))}
);
@@ -73,6 +117,17 @@ const PaymentsHomeTransactionsList = ({ enforcedLoadingState }: Props) => {
{renderItems()}
diff --git a/ts/features/payments/home/screens/PaymentsHomeScreen.tsx b/ts/features/payments/home/screens/PaymentsHomeScreen.tsx
index 67fc4d18854..dd3ed8a845b 100644
--- a/ts/features/payments/home/screens/PaymentsHomeScreen.tsx
+++ b/ts/features/payments/home/screens/PaymentsHomeScreen.tsx
@@ -9,9 +9,9 @@ import { PaymentsHomeEmptyScreenContent } from "../components/PaymentsHomeEmptyS
import { PaymentsHomeTransactionsList } from "../components/PaymentsHomeTransactionsList";
import { PaymentsHomeUserMethodsList } from "../components/PaymentsHomeUserMethodsList";
import {
+ isPaymentsLatestTransactionsEmptySelector,
isPaymentsSectionEmptySelector,
- isPaymentsSectionLoadingSelector,
- isPaymentsTransactionsEmptySelector
+ isPaymentsSectionLoadingSelector
} from "../store/selectors";
const PaymentsHomeScreen = () => {
@@ -19,7 +19,7 @@ const PaymentsHomeScreen = () => {
const isLoading = useIOSelector(isPaymentsSectionLoadingSelector);
const isTransactionsEmpty = useIOSelector(
- isPaymentsTransactionsEmptySelector
+ isPaymentsLatestTransactionsEmptySelector
);
const handleOnPayNoticedPress = () => {
diff --git a/ts/features/payments/home/screens/__tests__/PaymentsHomeScreen.test.tsx b/ts/features/payments/home/screens/__tests__/PaymentsHomeScreen.test.tsx
index 2d13966009a..acd30caac4e 100644
--- a/ts/features/payments/home/screens/__tests__/PaymentsHomeScreen.test.tsx
+++ b/ts/features/payments/home/screens/__tests__/PaymentsHomeScreen.test.tsx
@@ -4,16 +4,14 @@ import configureMockStore from "redux-mock-store";
import { WalletInfo } from "../../../../../../definitions/pagopa/walletv3/WalletInfo";
import { WalletStatusEnum } from "../../../../../../definitions/pagopa/walletv3/WalletStatus";
import { Wallets } from "../../../../../../definitions/pagopa/walletv3/Wallets";
-import { validTransaction } from "../../../../../__mocks__/paymentPayloads";
import ROUTES from "../../../../../navigation/routes";
import { applicationChangeState } from "../../../../../store/actions/application";
-import { IndexedById } from "../../../../../store/helpers/indexer";
import { appReducer } from "../../../../../store/reducers";
import { GlobalState } from "../../../../../store/reducers/types";
-import { Transaction } from "../../../../../types/pagopa";
import { NetworkError } from "../../../../../utils/errors";
import { renderScreenWithNavigationStoreContext } from "../../../../../utils/testWrapper";
import { PaymentsHomeScreen } from "../PaymentsHomeScreen";
+import { TransactionListItem } from "../../../../../../definitions/pagopa/biz-events/TransactionListItem";
jest.mock("react-native-reanimated", () => ({
...require("react-native-reanimated/mock"),
@@ -22,6 +20,15 @@ jest.mock("react-native-reanimated", () => ({
}
}));
+const validTransaction: TransactionListItem = {
+ transactionId: "0e208420-19dc-490c-8f3f-5772b7249643",
+ payeeName: "Hessel, Muller and Kilback",
+ payeeTaxCode: "262700362",
+ amount: "246.53",
+ transactionDate: "2024-05-08T14:32:45.927Z",
+ isCart: true
+};
+
const MOCK_WALLET: WalletInfo = {
walletId: "1",
creationDate: new Date(),
@@ -57,7 +64,7 @@ describe("PaymentsHomeScreen", () => {
it("should render full empty screen content", () => {
const { queryByTestId } = renderComponent({
- transactions: pot.some({}),
+ transactions: pot.some([]),
userMethods: pot.some({ wallets: [] })
});
@@ -76,7 +83,7 @@ describe("PaymentsHomeScreen", () => {
it("should render empty transactions content", () => {
const { queryByTestId } = renderComponent({
- transactions: pot.some({}),
+ transactions: pot.some([]),
userMethods: pot.some({ wallets: [MOCK_WALLET, MOCK_WALLET] })
});
@@ -95,7 +102,7 @@ describe("PaymentsHomeScreen", () => {
it("should render empty methods content", () => {
const { queryByTestId } = renderComponent({
- transactions: pot.some({ [validTransaction.id]: validTransaction }),
+ transactions: pot.some([validTransaction]),
userMethods: pot.some({ wallets: [] }),
shouldShowAddMethodsBanner: true
});
@@ -113,7 +120,7 @@ describe("PaymentsHomeScreen", () => {
it("should not render empty methods content", () => {
const { queryByTestId } = renderComponent({
- transactions: pot.some({ [validTransaction.id]: validTransaction }),
+ transactions: pot.some([validTransaction]),
userMethods: pot.some({ wallets: [] }),
shouldShowAddMethodsBanner: false
});
@@ -131,7 +138,7 @@ describe("PaymentsHomeScreen", () => {
it("should render wallets and transactions content", () => {
const { queryByTestId } = renderComponent({
- transactions: pot.some({ [validTransaction.id]: validTransaction }),
+ transactions: pot.some([validTransaction]),
userMethods: pot.some({ wallets: [MOCK_WALLET] }),
shouldShowAddMethodsBanner: false
});
@@ -154,18 +161,13 @@ const renderComponent = ({
userMethods = pot.none,
shouldShowAddMethodsBanner = true
}: {
- transactions?: pot.Pot, Error>;
+ transactions?: pot.Pot, Error>;
userMethods?: pot.Pot;
shouldShowAddMethodsBanner?: boolean;
}) => {
const globalState = appReducer(undefined, applicationChangeState("active"));
const state: GlobalState = _.merge(null, globalState, {
- wallet: {
- transactions: {
- transactions
- }
- },
features: {
payments: {
wallet: {
@@ -173,6 +175,9 @@ const renderComponent = ({
},
home: {
shouldShowAddMethodsBanner
+ },
+ bizEventsTransaction: {
+ latestTransactions: transactions
}
}
}
diff --git a/ts/features/payments/home/store/selectors/index.ts b/ts/features/payments/home/store/selectors/index.ts
index 272043757c3..626e82679ca 100644
--- a/ts/features/payments/home/store/selectors/index.ts
+++ b/ts/features/payments/home/store/selectors/index.ts
@@ -2,14 +2,17 @@ import * as pot from "@pagopa/ts-commons/lib/pot";
import _ from "lodash";
import { createSelector } from "reselect";
import { GlobalState } from "../../../../../store/reducers/types";
-import { latestTransactionsSelector } from "../../../../../store/reducers/wallet/transactions";
import { paymentsWalletUserMethodsSelector } from "../../../wallet/store/selectors";
+import {
+ walletLatestTransactionsBizEventsListPotSelector,
+ walletTransactionBizEventsListPotSelector
+} from "../../../bizEventsTransaction/store/selectors";
export const isPaymentsSectionLoadingSelector = createSelector(
paymentsWalletUserMethodsSelector,
- latestTransactionsSelector,
- (methodsPot, transactionsPot) =>
- pot.isLoading(methodsPot) || pot.isLoading(transactionsPot)
+ walletLatestTransactionsBizEventsListPotSelector,
+ (methodsPot, latestTransactionsPot) =>
+ pot.isLoading(methodsPot) || pot.isLoading(latestTransactionsPot)
);
export const isPaymentsMethodsEmptySelector = createSelector(
@@ -21,21 +24,27 @@ export const isPaymentsMethodsEmptySelector = createSelector(
)
);
+export const isPaymentsLatestTransactionsEmptySelector = createSelector(
+ walletLatestTransactionsBizEventsListPotSelector,
+ latestTransactionsPot =>
+ pot.getOrElse(
+ pot.map(latestTransactionsPot, transactions => transactions.length === 0),
+ false
+ )
+);
+
export const isPaymentsTransactionsEmptySelector = createSelector(
- latestTransactionsSelector,
+ walletTransactionBizEventsListPotSelector,
transactionsPot =>
pot.getOrElse(
- pot.map(
- transactionsPot,
- transactions => _.values(transactions).length === 0
- ),
+ pot.map(transactionsPot, transactions => transactions.length === 0),
false
)
);
export const isPaymentsSectionEmptySelector = createSelector(
isPaymentsMethodsEmptySelector,
- isPaymentsTransactionsEmptySelector,
+ isPaymentsLatestTransactionsEmptySelector,
(isMethodsEmpty, isTransactionsEmpty) => isMethodsEmpty && isTransactionsEmpty
);
diff --git a/ts/navigation/AuthenticatedStackNavigator.tsx b/ts/navigation/AuthenticatedStackNavigator.tsx
index 885c0a702e1..248b674e8d0 100644
--- a/ts/navigation/AuthenticatedStackNavigator.tsx
+++ b/ts/navigation/AuthenticatedStackNavigator.tsx
@@ -64,6 +64,8 @@ import { UAWebViewScreen } from "../features/uaDonations/screens/UAWebViewScreen
import { ZendeskStackNavigator } from "../features/zendesk/navigation/navigator";
import ZENDESK_ROUTES from "../features/zendesk/navigation/routes";
import { GalleryPermissionInstructionsScreen } from "../screens/misc/GalleryPermissionInstructionsScreen";
+import { PaymentsTransactionBizEventsRoutes } from "../features/payments/bizEventsTransaction/navigation/routes";
+import { PaymentsTransactionBizEventsNavigator } from "../features/payments/bizEventsTransaction/navigation/navigator";
import { useIOSelector } from "../store/hooks";
import {
isCdcEnabledSelector,
@@ -340,6 +342,16 @@ const AuthenticatedStackNavigator = () => {
...hideHeaderOptions
}}
/>
+
;
[PaymentsMethodDetailsRoutes.PAYMENT_METHOD_DETAILS_NAVIGATOR]: NavigatorScreenParams;
[PaymentsTransactionRoutes.PAYMENT_TRANSACTION_NAVIGATOR]: NavigatorScreenParams;
+ [PaymentsTransactionBizEventsRoutes.PAYMENT_TRANSACTION_BIZ_EVENTS_NAVIGATOR]: NavigatorScreenParams;
[ITW_ROUTES.MAIN]: NavigatorScreenParams;
};
diff --git a/yarn.lock b/yarn.lock
index 2a8bdb3ce5c..1ae3c69526b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3340,10 +3340,10 @@
resolved "https://registry.yarnpkg.com/@pagopa/io-react-native-zendesk/-/io-react-native-zendesk-0.3.29.tgz#ada8dab8a9ef15e126275baace2084491148bac8"
integrity sha512-CskFyF0Sz+EC/ZyJNNMFEX/Avjyn6cKRwE2K+XSTGWDKYLImPraA8YozTljclCdv3DOxk3ZklUsieMG25PfnSw==
-"@pagopa/openapi-codegen-ts@^12.2.1":
- version "12.2.1"
- resolved "https://registry.yarnpkg.com/@pagopa/openapi-codegen-ts/-/openapi-codegen-ts-12.2.1.tgz#e2cc9cedcbb23077b4f3f80e970ebd9c55e71afe"
- integrity sha512-NO+z8+FUuR04Kc6I6fZk2mh+PWc1wrrBiTmcmnwuGGAWcObPqt4oF7b0fpAiZF91YuAMf5BOaXsFIIeSQv3i8g==
+"@pagopa/openapi-codegen-ts@^13.2.0":
+ version "13.2.0"
+ resolved "https://registry.yarnpkg.com/@pagopa/openapi-codegen-ts/-/openapi-codegen-ts-13.2.0.tgz#ed55405c979b582dc11a8a9b900adc45b6c7379d"
+ integrity sha512-EKo4CJReQPT8z3a6P35p3eW86VIy9gcLOVrxIj8VR5bWRhVDfFjwAmpth9pIGQ0QXPmGuylbJbWQmWHrbGLnaA==
dependencies:
"@pagopa/ts-commons" "^10.15.0"
fs-extra "^6.0.0"