From b9579f16ade27cca916d2673e0e186540578a2b5 Mon Sep 17 00:00:00 2001 From: Federico Mastrini Date: Thu, 18 Jan 2024 15:01:39 +0100 Subject: [PATCH 01/19] chore: io-ts extensions --- ts/types/io-ts-extensions.ts | 74 ++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 ts/types/io-ts-extensions.ts diff --git a/ts/types/io-ts-extensions.ts b/ts/types/io-ts-extensions.ts new file mode 100644 index 00000000000..eba885d0e49 --- /dev/null +++ b/ts/types/io-ts-extensions.ts @@ -0,0 +1,74 @@ +// These extensions were created based on a comment in an issue on io-ts repository +// Issue link: https://github.com/gcanti/io-ts/issues/338#issuecomment-1472770728 + +/* eslint-disable no-underscore-dangle */ +/* eslint-disable functional/immutable-data */ +import * as t from "io-ts"; + +/** + * Helper function to extract the properties of a given io-ts codec. + * @param codec - The io-ts codec from which to extract properties. + * @returns The properties of the io-ts codec. + */ +function getProps(codec: T): t.TypeOf { + switch (codec._tag) { + case "RefinementType": + case "ReadonlyType": + return getProps(codec.type); + case "InterfaceType": + case "StrictType": + case "PartialType": + return codec.props; + case "IntersectionType": + return codec.types.reduce>( + (props, type) => Object.assign(props, getProps(type)), + {} + ); + default: + throw new TypeError("Invalid codec"); + } +} + +/** + * Omit specified properties from an io-ts codec. + * @param codec - The io-ts codec from which to omit properties. + * @param keys - The key or array of keys to omit. + * @returns A new io-ts codec with specified properties omitted. + */ +export const omitProps = >( + codec: C, + keys: K | Array +): t.Type, K>> & + Pick, "_tag" | "props"> => { + const initialProps = getProps(codec); + + const props: Omit, K> = ( + Array.isArray(keys) ? keys : [keys] + ).reduce((acc, key) => { + const { [key]: _prop, ...rest } = acc; + return rest; + }, initialProps); + + return t.type(props); +}; + +/** + * Pick specified properties from an io-ts codec. + * @param codec - The io-ts codec from which to pick properties. + * @param keys - The key or array of keys to pick. + * @returns A new io-ts codec with specified properties picked. + */ +export const pickProps = >( + codec: C, + keys: K | Array +): t.Type, K>> & + Pick, "_tag" | "props"> => { + const initialProps = getProps(codec); + + const props = (Array.isArray(keys) ? keys : [keys]).reduce((acc, key) => { + const { [key]: prop } = initialProps; + return { ...acc, prop }; + }, {} as Pick, K>); + + return t.type(props); +}; From ada7a09ad1a0274e4d74033ee100495b2d6c758a Mon Sep 17 00:00:00 2001 From: Federico Mastrini Date: Thu, 18 Jan 2024 15:44:51 +0100 Subject: [PATCH 02/19] chore: `UIWalletInfoDetails` --- .../details/types/UIWalletInfoDetails.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 ts/features/walletV3/details/types/UIWalletInfoDetails.ts diff --git a/ts/features/walletV3/details/types/UIWalletInfoDetails.ts b/ts/features/walletV3/details/types/UIWalletInfoDetails.ts new file mode 100644 index 00000000000..1fd0a5d84ba --- /dev/null +++ b/ts/features/walletV3/details/types/UIWalletInfoDetails.ts @@ -0,0 +1,29 @@ +import { + PatternString, + WithinRangeString +} from "@pagopa/ts-commons/lib/strings"; +import { enumType } from "@pagopa/ts-commons/lib/types"; +import * as t from "io-ts"; +import { BrandEnum } from "../../../../../definitions/pagopa/walletv3/WalletInfoDetails"; + +const UIWalletInfoDetails = t.partial({ + maskedPan: t.string, + + expiryDate: PatternString("^d{6}$"), + + holder: t.string, + + brand: enumType(BrandEnum, "brand"), + + abi: WithinRangeString(1, 6), + + maskedEmail: t.string, + + maskedNumber: WithinRangeString(1, 21), + + instituteCode: WithinRangeString(1, 6), + + bankName: t.string +}); + +export type UIWalletInfoDetails = t.TypeOf; From 8c15ed820746f12fe45d52c6cc3514adefd1328a Mon Sep 17 00:00:00 2001 From: Federico Mastrini Date: Thu, 18 Jan 2024 16:06:05 +0100 Subject: [PATCH 03/19] refactor: use `UIWalletDetails` type for presentation --- ts/features/walletV3/common/utils/index.ts | 70 ++++++++---------- .../details/screens/WalletDetailsScreen.tsx | 69 ++++++++---------- .../WalletPaymentConfirmContent.tsx | 72 ++++++++----------- .../screens/WalletPaymentPickMethodScreen.tsx | 46 ++++++------ 4 files changed, 112 insertions(+), 145 deletions(-) diff --git a/ts/features/walletV3/common/utils/index.ts b/ts/features/walletV3/common/utils/index.ts index 67866723415..7ecd6d38aed 100644 --- a/ts/features/walletV3/common/utils/index.ts +++ b/ts/features/walletV3/common/utils/index.ts @@ -1,25 +1,20 @@ -import _ from "lodash"; -import * as O from "fp-ts/lib/Option"; import { IOLogoPaymentType, IOPaymentLogos, ListItemTransactionStatusWithBadge } from "@pagopa/io-app-design-system"; -import I18n from "i18n-js"; +import * as O from "fp-ts/lib/Option"; import { pipe } from "fp-ts/lib/function"; - -import { isExpiredDate } from "../../../../utils/dates"; +import I18n from "i18n-js"; +import _ from "lodash"; +import { Bundle } from "../../../../../definitions/pagopa/ecommerce/Bundle"; import { ServiceNameEnum } from "../../../../../definitions/pagopa/walletv3/ServiceName"; -import { PaymentSupportStatus } from "../../../../types/paymentMethodCapabilities"; -import { - TypeEnum, - WalletInfoDetails, - WalletInfoDetails1 -} from "../../../../../definitions/pagopa/walletv3/WalletInfoDetails"; import { ServiceStatusEnum } from "../../../../../definitions/pagopa/walletv3/ServiceStatus"; import { WalletInfo } from "../../../../../definitions/pagopa/walletv3/WalletInfo"; +import { PaymentSupportStatus } from "../../../../types/paymentMethodCapabilities"; +import { isExpiredDate } from "../../../../utils/dates"; import { findFirstCaseInsensitive } from "../../../../utils/object"; -import { Bundle } from "../../../../../definitions/pagopa/ecommerce/Bundle"; +import { UIWalletInfoDetails } from "../../details/types/UIWalletInfoDetails"; import { WalletPaymentPspSortType } from "../../payment/types"; /** @@ -50,16 +45,13 @@ export const getBadgeTextByTransactionStatus = ( * left if expiring date can't be evaluated * @param paymentMethod */ -export const isPaymentMethodExpired = (paymentMethod: WalletInfo): boolean => { - switch (paymentMethod.details?.type) { - case TypeEnum.PAYPAL: - return false; - case TypeEnum.CARDS: - const cardDetails = paymentMethod.details as WalletInfoDetails1; - return isExpiredDate(cardDetails.expiryDate); - } - return false; -}; +export const isPaymentMethodExpired = (details: UIWalletInfoDetails): boolean => + pipe( + details.expiryDate, + O.fromNullable, + O.map(isExpiredDate), + O.getOrElse(() => false) + ); /** * true if the given paymentMethod supports the given walletFunction @@ -111,25 +103,25 @@ export const isPaymentSupported = ( }; export const getPaymentLogo = ( - selectedMethod: WalletInfoDetails + details: UIWalletInfoDetails ): IOLogoPaymentType | undefined => { - switch (selectedMethod.type) { - case TypeEnum.CARDS: - const cardsType = selectedMethod as WalletInfoDetails1; - const { brand } = cardsType; - return pipe( - brand, - findFirstCaseInsensitive(IOPaymentLogos), - O.fold( - () => undefined, - ([logoName, _]) => logoName - ) - ) as IOLogoPaymentType; - case TypeEnum.PAYPAL: - return "payPal"; - default: - return undefined; + if (details.maskedEmail !== undefined) { + return "payPal"; + } else if (details.maskedNumber !== undefined) { + return "bancomatPay"; + } else if (details.maskedPan !== undefined) { + return pipe( + details.brand, + O.fromNullable, + O.chain(findFirstCaseInsensitive(IOPaymentLogos)), + O.fold( + () => undefined, + ([logoName, _]) => logoName + ) + ) as IOLogoPaymentType; } + + return undefined; }; export const WALLET_PAYMENT_TERMS_AND_CONDITIONS_URL = diff --git a/ts/features/walletV3/details/screens/WalletDetailsScreen.tsx b/ts/features/walletV3/details/screens/WalletDetailsScreen.tsx index ea637521529..64c923cf7b8 100644 --- a/ts/features/walletV3/details/screens/WalletDetailsScreen.tsx +++ b/ts/features/walletV3/details/screens/WalletDetailsScreen.tsx @@ -20,12 +20,7 @@ import { walletDetailsInstrumentSelector } from "../store"; import { walletDetailsGetInstrument } from "../store/actions"; -import { - TypeEnum, - WalletInfoDetails, - WalletInfoDetails1, - WalletInfoDetails2 -} from "../../../../../definitions/pagopa/walletv3/WalletInfoDetails"; +import { UIWalletInfoDetails } from "../types/UIWalletInfoDetails"; export type WalletDetailsScreenNavigationParams = Readonly<{ walletId: string; @@ -36,44 +31,38 @@ export type WalletDetailsScreenRouteProps = RouteProp< "WALLET_DETAILS_SCREEN" >; -const generateCardComponent = (walletDetails: WalletInfoDetails) => { - switch (walletDetails.type) { - case TypeEnum.PAYPAL: - const paypalDetails = walletDetails as WalletInfoDetails2; - return ( - - ); - case TypeEnum.CARDS: - default: - const cardDetails = walletDetails as WalletInfoDetails1; - return ( - - ); +const generateCardComponent = (details: UIWalletInfoDetails) => { + if (details.maskedEmail !== undefined) { + return ( + + ); } + + return ( + + ); }; -const generateCardHeaderTitle = (walletDetails?: WalletInfoDetails) => { - switch (walletDetails?.type) { - case TypeEnum.CARDS: - const cardDetails = walletDetails as WalletInfoDetails1; - const capitalizedCardCircuit = capitalize( - cardDetails.brand.toLowerCase() ?? "" - ); - return `${capitalizedCardCircuit} ••${cardDetails.maskedPan}`; - default: - return ""; +const generateCardHeaderTitle = (details?: UIWalletInfoDetails) => { + if (details?.maskedPan !== undefined) { + const capitalizedCardCircuit = capitalize( + details.brand?.toLowerCase() ?? "" + ); + return `${capitalizedCardCircuit} ••${details.maskedPan}`; } + + return ""; }; /** diff --git a/ts/features/walletV3/payment/components/WalletPaymentConfirmContent.tsx b/ts/features/walletV3/payment/components/WalletPaymentConfirmContent.tsx index d7c8a22c6d2..456914c71e8 100644 --- a/ts/features/walletV3/payment/components/WalletPaymentConfirmContent.tsx +++ b/ts/features/walletV3/payment/components/WalletPaymentConfirmContent.tsx @@ -1,4 +1,3 @@ -import React from "react"; import { Body, GradientScrollView, @@ -7,35 +6,29 @@ import { ModuleCheckout, VSpacer } from "@pagopa/io-app-design-system"; -import { useNavigation } from "@react-navigation/native"; import { openAuthenticationSession } from "@pagopa/io-react-native-login-utils"; +import { useNavigation } from "@react-navigation/native"; +import React from "react"; +import { Bundle } from "../../../../../definitions/pagopa/ecommerce/Bundle"; +import { PaymentRequestsGetResponse } from "../../../../../definitions/pagopa/ecommerce/PaymentRequestsGetResponse"; +import I18n from "../../../../i18n"; import { AppParamsList, IOStackNavigationProp } from "../../../../navigation/params/AppParamsList"; -import { WalletPaymentRoutes } from "../navigation/routes"; +import { format } from "../../../../utils/dates"; +import { formatNumberCentsToAmount } from "../../../../utils/stringBuilder"; +import { capitalize } from "../../../../utils/strings"; import { WALLET_PAYMENT_TERMS_AND_CONDITIONS_URL, getPaymentLogo } from "../../common/utils"; -import { format } from "../../../../utils/dates"; -import { capitalize } from "../../../../utils/strings"; -import { PaymentRequestsGetResponse } from "../../../../../definitions/pagopa/ecommerce/PaymentRequestsGetResponse"; -import { Bundle } from "../../../../../definitions/pagopa/ecommerce/Bundle"; -import { - TypeEnum, - WalletInfoDetails, - WalletInfoDetails1, - WalletInfoDetails2, - WalletInfoDetails3 -} from "../../../../../definitions/pagopa/walletv3/WalletInfoDetails"; -import I18n from "../../../../i18n"; - -import { formatNumberCentsToAmount } from "../../../../utils/stringBuilder"; +import { UIWalletInfoDetails } from "../../details/types/UIWalletInfoDetails"; +import { WalletPaymentRoutes } from "../navigation/routes"; import { WalletPaymentTotalAmount } from "./WalletPaymentTotalAmount"; export type WalletPaymentConfirmContentProps = { - paymentMethodDetails: WalletInfoDetails; + paymentMethodDetails: UIWalletInfoDetails; selectedPsp: Bundle; paymentDetails: PaymentRequestsGetResponse; isLoading?: boolean; @@ -127,33 +120,26 @@ export const WalletPaymentConfirmContent = ({ ); }; -const getPaymentSubtitle = (cardDetails: WalletInfoDetails) => { - switch (cardDetails.type) { - case TypeEnum.CARDS: - const cardsDetail = cardDetails as WalletInfoDetails1; - return `${format(cardsDetail.expiryDate, "MM/YY")}`; - case TypeEnum.PAYPAL: - return I18n.t("wallet.onboarding.paypal.name"); - case TypeEnum.BANCOMATPAY: - const bancomatpayDetail = cardDetails as WalletInfoDetails3; - return `${bancomatpayDetail.bankName}`; - default: - return ""; +const getPaymentSubtitle = (details: UIWalletInfoDetails): string => { + if (details.maskedPan !== undefined) { + return `${format(details.expiryDate, "MM/YY")}`; + } else if (details.maskedEmail !== undefined) { + return I18n.t("wallet.onboarding.paypal.name"); + } else if (details.maskedNumber !== undefined) { + return `${details.bankName}`; } + + return ""; }; -const getPaymentTitle = (cardDetails: WalletInfoDetails) => { - switch (cardDetails.type) { - case TypeEnum.CARDS: - const cardsDetail = cardDetails as WalletInfoDetails1; - return `${capitalize(cardsDetail.brand)} ••${cardsDetail.maskedPan}`; - case TypeEnum.PAYPAL: - const paypalDetail = cardDetails as WalletInfoDetails2; - return `${paypalDetail.maskedEmail}`; - case TypeEnum.BANCOMATPAY: - const bancomatpayDetail = cardDetails as WalletInfoDetails3; - return `${bancomatpayDetail.maskedNumber}`; - default: - return ""; +const getPaymentTitle = (details: UIWalletInfoDetails): string => { + if (details.maskedPan !== undefined) { + return `${capitalize(details.brand || "")} ••${details.maskedPan}`; + } else if (details.maskedEmail !== undefined) { + return `${details.maskedEmail}`; + } else if (details.maskedNumber !== undefined) { + return `${details.maskedNumber}`; } + + return ""; }; diff --git a/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx b/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx index 23c53a5caa0..bed3a5835dc 100644 --- a/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx +++ b/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx @@ -20,7 +20,6 @@ import React, { useEffect, useMemo } from "react"; import { View } from "react-native"; import { PaymentMethodResponse } from "../../../../../definitions/pagopa/walletv3/PaymentMethodResponse"; import { WalletInfo } from "../../../../../definitions/pagopa/walletv3/WalletInfo"; -import { WalletInfoDetails1 } from "../../../../../definitions/pagopa/walletv3/WalletInfoDetails"; import { useHeaderSecondLevel } from "../../../../hooks/useHeaderSecondLevel"; import I18n from "../../../../i18n"; import { @@ -43,6 +42,7 @@ import { } from "../store/selectors"; import { WalletPaymentMissingMethodsError } from "../components/WalletPaymentMissingMethodsError"; import { useWalletPaymentGoBackHandler } from "../hooks/useWalletPaymentGoBackHandler"; +import { UIWalletInfoDetails } from "../../details/types/UIWalletInfoDetails"; type SavedMethodState = { kind: "saved"; @@ -265,29 +265,29 @@ const mapGenericToRadioItem = ( const mapSavedToRadioItem = ( method: WalletInfo ): RadioItem | undefined => { - switch (method.details?.type) { - case "CARDS": - const cardDetails = method.details as WalletInfoDetails1; - return { - id: method.walletId, - value: `${capitalize(cardDetails.brand)} ••${cardDetails.maskedPan}`, - startImage: getIconWithFallback(cardDetails.brand) - }; - case "PAYPAL": - return { - id: method.walletId, - value: "PayPal", - startImage: getIconWithFallback("paypal") - }; - case "BANCOMATPAY": - return { - id: method.walletId, - value: "BANCOMAT Pay", - startImage: getIconWithFallback("bancomatpay") - }; - default: - return undefined; + const details = method.details as UIWalletInfoDetails; + + if (details.maskedPan !== undefined) { + return { + id: method.walletId, + value: `${capitalize(details.brand)} ••${details.maskedPan}`, + startImage: getIconWithFallback(details.brand) + }; + } else if (details.maskedEmail !== undefined) { + return { + id: method.walletId, + value: "PayPal", + startImage: getIconWithFallback("paypal") + }; + } else if (details.maskedNumber !== undefined) { + return { + id: method.walletId, + value: "BANCOMAT Pay", + startImage: getIconWithFallback("bancomatpay") + }; } + + return undefined; }; const isMethodDisabledForAmount = ( From 68d45e39c9d0f989d5dbb0115284deec8ce00d69 Mon Sep 17 00:00:00 2001 From: Federico Mastrini Date: Thu, 18 Jan 2024 17:50:01 +0100 Subject: [PATCH 04/19] fix: argument --- .../components/WalletDetailsPaymentMethodFeatures.tsx | 2 +- ts/features/walletV3/common/utils/index.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ts/features/walletV3/common/components/WalletDetailsPaymentMethodFeatures.tsx b/ts/features/walletV3/common/components/WalletDetailsPaymentMethodFeatures.tsx index 959c7e90a41..b3799b6f200 100644 --- a/ts/features/walletV3/common/components/WalletDetailsPaymentMethodFeatures.tsx +++ b/ts/features/walletV3/common/components/WalletDetailsPaymentMethodFeatures.tsx @@ -19,7 +19,7 @@ type Props = { paymentMethod: WalletInfo }; * - global settings (payment capability, favourite, etc.) */ const WalletDetailsPaymentMethodFeatures = ({ paymentMethod }: Props) => { - const isMethodExpired = isPaymentMethodExpired(paymentMethod); + const isMethodExpired = isPaymentMethodExpired(paymentMethod.details); const isIdpayEnabled = useIOSelector(isIdPayEnabledSelector); if (isMethodExpired) { diff --git a/ts/features/walletV3/common/utils/index.ts b/ts/features/walletV3/common/utils/index.ts index 7ecd6d38aed..812c3a3b7b6 100644 --- a/ts/features/walletV3/common/utils/index.ts +++ b/ts/features/walletV3/common/utils/index.ts @@ -45,9 +45,11 @@ export const getBadgeTextByTransactionStatus = ( * left if expiring date can't be evaluated * @param paymentMethod */ -export const isPaymentMethodExpired = (details: UIWalletInfoDetails): boolean => +export const isPaymentMethodExpired = ( + details?: UIWalletInfoDetails +): boolean => pipe( - details.expiryDate, + details?.expiryDate, O.fromNullable, O.map(isExpiredDate), O.getOrElse(() => false) From fd785b43a7bfb6c7cd468d09f9394c152b6231d1 Mon Sep 17 00:00:00 2001 From: Federico Mastrini Date: Fri, 19 Jan 2024 10:02:50 +0100 Subject: [PATCH 05/19] chore: update API definitions --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fd4d0b1467a..e2c1c5a08e8 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "idpay_api": "https://raw.githubusercontent.com/pagopa/cstar-infrastructure/v6.9.1/src/domains/idpay-app/api/idpay_appio_full/openapi.appio.full.yml", "lollipop_api": "https://raw.githubusercontent.com/pagopa/io-backend/v13.25.1-RELEASE/api_lollipop_first_consumer.yaml", "fast_login_api": "https://raw.githubusercontent.com/pagopa/io-backend/v13.25.1-RELEASE/openapi/generated/api_fast_login.yaml", - "pagopa_api_walletv3": "https://raw.githubusercontent.com/pagopa/pagopa-infra/4cd111e94432ff62580adc391de78a5462a7128e/src/domains/wallet-app/api/payment-wallet/v1/_openapi.json.tpl", + "pagopa_api_walletv3": "https://raw.githubusercontent.com/pagopa/pagopa-infra/bc466a042352613669f3e9f3f0512a4ffa3ac00d/src/domains/wallet-app/api/payment-wallet/v1/_openapi.json.tpl", "pagopa_api_ecommerce": "https://raw.githubusercontent.com/pagopa/pagopa-infra/65878f9913fcc0eaff499ba8a1a20427a412c010/src/domains/ecommerce-app/api/ecommerce-io/v1/_openapi.json.tpl", "private": true, "scripts": { From 9a86f257e8dc85c1d1f671ea26d5cae58cc4b465 Mon Sep 17 00:00:00 2001 From: Federico Mastrini Date: Fri, 19 Jan 2024 10:36:55 +0100 Subject: [PATCH 06/19] chore: `UIWalletInfoDetails` --- .../details/types/UIWalletInfoDetails.ts | 60 ++++++++------- ts/types/io-ts-extensions.ts | 74 ------------------- 2 files changed, 34 insertions(+), 100 deletions(-) delete mode 100644 ts/types/io-ts-extensions.ts diff --git a/ts/features/walletV3/details/types/UIWalletInfoDetails.ts b/ts/features/walletV3/details/types/UIWalletInfoDetails.ts index 1fd0a5d84ba..c73ae0675f4 100644 --- a/ts/features/walletV3/details/types/UIWalletInfoDetails.ts +++ b/ts/features/walletV3/details/types/UIWalletInfoDetails.ts @@ -1,29 +1,37 @@ -import { - PatternString, - WithinRangeString -} from "@pagopa/ts-commons/lib/strings"; -import { enumType } from "@pagopa/ts-commons/lib/types"; import * as t from "io-ts"; -import { BrandEnum } from "../../../../../definitions/pagopa/walletv3/WalletInfoDetails"; - -const UIWalletInfoDetails = t.partial({ - maskedPan: t.string, - - expiryDate: PatternString("^d{6}$"), - - holder: t.string, - - brand: enumType(BrandEnum, "brand"), - - abi: WithinRangeString(1, 6), - - maskedEmail: t.string, - - maskedNumber: WithinRangeString(1, 21), - - instituteCode: WithinRangeString(1, 6), - - bankName: t.string -}); +import { + WalletInfoDetails1, + WalletInfoDetails2, + WalletInfoDetails3 +} from "../../../../../definitions/pagopa/walletv3/WalletInfoDetails"; + +/** + * Transforms all required props from WalletInfoDetails1 to partial + */ +export const UIWalletInfoDetails1 = t.partial( + WalletInfoDetails1.types[0].props +); + +/** + * Transforms all required props from WalletInfoDetails2 to partial + */ +export const UIWalletInfoDetails2 = t.partial( + WalletInfoDetails2.types[0].props +); + +/** + * Transforms all required props from WalletInfoDetails3 to partial + */ +export const UIWalletInfoDetails3 = t.partial( + WalletInfoDetails3.types[0].props +); + +/** + * This type is used to bypass the `type` props of {@see WalletInfoDetails} + */ +export const UIWalletInfoDetails = t.intersection( + [UIWalletInfoDetails1, UIWalletInfoDetails2, UIWalletInfoDetails3], + "UIWalletInfoDetails" +); export type UIWalletInfoDetails = t.TypeOf; diff --git a/ts/types/io-ts-extensions.ts b/ts/types/io-ts-extensions.ts deleted file mode 100644 index eba885d0e49..00000000000 --- a/ts/types/io-ts-extensions.ts +++ /dev/null @@ -1,74 +0,0 @@ -// These extensions were created based on a comment in an issue on io-ts repository -// Issue link: https://github.com/gcanti/io-ts/issues/338#issuecomment-1472770728 - -/* eslint-disable no-underscore-dangle */ -/* eslint-disable functional/immutable-data */ -import * as t from "io-ts"; - -/** - * Helper function to extract the properties of a given io-ts codec. - * @param codec - The io-ts codec from which to extract properties. - * @returns The properties of the io-ts codec. - */ -function getProps(codec: T): t.TypeOf { - switch (codec._tag) { - case "RefinementType": - case "ReadonlyType": - return getProps(codec.type); - case "InterfaceType": - case "StrictType": - case "PartialType": - return codec.props; - case "IntersectionType": - return codec.types.reduce>( - (props, type) => Object.assign(props, getProps(type)), - {} - ); - default: - throw new TypeError("Invalid codec"); - } -} - -/** - * Omit specified properties from an io-ts codec. - * @param codec - The io-ts codec from which to omit properties. - * @param keys - The key or array of keys to omit. - * @returns A new io-ts codec with specified properties omitted. - */ -export const omitProps = >( - codec: C, - keys: K | Array -): t.Type, K>> & - Pick, "_tag" | "props"> => { - const initialProps = getProps(codec); - - const props: Omit, K> = ( - Array.isArray(keys) ? keys : [keys] - ).reduce((acc, key) => { - const { [key]: _prop, ...rest } = acc; - return rest; - }, initialProps); - - return t.type(props); -}; - -/** - * Pick specified properties from an io-ts codec. - * @param codec - The io-ts codec from which to pick properties. - * @param keys - The key or array of keys to pick. - * @returns A new io-ts codec with specified properties picked. - */ -export const pickProps = >( - codec: C, - keys: K | Array -): t.Type, K>> & - Pick, "_tag" | "props"> => { - const initialProps = getProps(codec); - - const props = (Array.isArray(keys) ? keys : [keys]).reduce((acc, key) => { - const { [key]: prop } = initialProps; - return { ...acc, prop }; - }, {} as Pick, K>); - - return t.type(props); -}; From a9d879482059f1d3139c2896dfe1340836ed466c Mon Sep 17 00:00:00 2001 From: Federico Mastrini Date: Fri, 19 Jan 2024 10:38:38 +0100 Subject: [PATCH 07/19] chore: type update --- .../details/types/UIWalletInfoDetails.ts | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/ts/features/walletV3/details/types/UIWalletInfoDetails.ts b/ts/features/walletV3/details/types/UIWalletInfoDetails.ts index c73ae0675f4..56f40ccbd47 100644 --- a/ts/features/walletV3/details/types/UIWalletInfoDetails.ts +++ b/ts/features/walletV3/details/types/UIWalletInfoDetails.ts @@ -8,23 +8,26 @@ import { /** * Transforms all required props from WalletInfoDetails1 to partial */ -export const UIWalletInfoDetails1 = t.partial( - WalletInfoDetails1.types[0].props -); +export const UIWalletInfoDetails1 = t.partial({ + ...WalletInfoDetails1.types[0].props, + ...WalletInfoDetails1.types[0].props +}); /** * Transforms all required props from WalletInfoDetails2 to partial */ -export const UIWalletInfoDetails2 = t.partial( - WalletInfoDetails2.types[0].props -); +export const UIWalletInfoDetails2 = t.partial({ + ...WalletInfoDetails2.types[0].props, + ...WalletInfoDetails2.types[1].props +}); /** * Transforms all required props from WalletInfoDetails3 to partial */ -export const UIWalletInfoDetails3 = t.partial( - WalletInfoDetails3.types[0].props -); +export const UIWalletInfoDetails3 = t.partial({ + ...WalletInfoDetails3.types[0].props, + ...WalletInfoDetails3.types[1].props +}); /** * This type is used to bypass the `type` props of {@see WalletInfoDetails} From e519419f70dbd6d0d226e78aaa53be749fd78ff8 Mon Sep 17 00:00:00 2001 From: Federico Mastrini Date: Fri, 19 Jan 2024 10:43:33 +0100 Subject: [PATCH 08/19] chore: update API definitions --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e2c1c5a08e8..24e5e6285df 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "lollipop_api": "https://raw.githubusercontent.com/pagopa/io-backend/v13.25.1-RELEASE/api_lollipop_first_consumer.yaml", "fast_login_api": "https://raw.githubusercontent.com/pagopa/io-backend/v13.25.1-RELEASE/openapi/generated/api_fast_login.yaml", "pagopa_api_walletv3": "https://raw.githubusercontent.com/pagopa/pagopa-infra/bc466a042352613669f3e9f3f0512a4ffa3ac00d/src/domains/wallet-app/api/payment-wallet/v1/_openapi.json.tpl", - "pagopa_api_ecommerce": "https://raw.githubusercontent.com/pagopa/pagopa-infra/65878f9913fcc0eaff499ba8a1a20427a412c010/src/domains/ecommerce-app/api/ecommerce-io/v1/_openapi.json.tpl", + "pagopa_api_ecommerce": "https://raw.githubusercontent.com/pagopa/pagopa-infra/bc466a042352613669f3e9f3f0512a4ffa3ac00d/src/domains/ecommerce-app/api/ecommerce-io/v1/_openapi.json.tpl", "private": true, "scripts": { "start": "react-native start", From e441b81770ce6f9d717d17f9ca73ddf573e2c2f0 Mon Sep 17 00:00:00 2001 From: Alessandro Izzo Date: Fri, 19 Jan 2024 17:27:46 +0100 Subject: [PATCH 09/19] chore: changed the commit to the corrected swagger --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 24e5e6285df..8c95c272193 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,8 @@ "idpay_api": "https://raw.githubusercontent.com/pagopa/cstar-infrastructure/v6.9.1/src/domains/idpay-app/api/idpay_appio_full/openapi.appio.full.yml", "lollipop_api": "https://raw.githubusercontent.com/pagopa/io-backend/v13.25.1-RELEASE/api_lollipop_first_consumer.yaml", "fast_login_api": "https://raw.githubusercontent.com/pagopa/io-backend/v13.25.1-RELEASE/openapi/generated/api_fast_login.yaml", - "pagopa_api_walletv3": "https://raw.githubusercontent.com/pagopa/pagopa-infra/bc466a042352613669f3e9f3f0512a4ffa3ac00d/src/domains/wallet-app/api/payment-wallet/v1/_openapi.json.tpl", - "pagopa_api_ecommerce": "https://raw.githubusercontent.com/pagopa/pagopa-infra/bc466a042352613669f3e9f3f0512a4ffa3ac00d/src/domains/ecommerce-app/api/ecommerce-io/v1/_openapi.json.tpl", + "pagopa_api_walletv3": "https://raw.githubusercontent.com/pagopa/pagopa-infra/740e7dcc5ea2ea19639316fea6797bbd504dd0ae/src/domains/wallet-app/api/payment-wallet/v1/_openapi.json.tpl", + "pagopa_api_ecommerce": "https://raw.githubusercontent.com/pagopa/pagopa-infra/5190135ac34791cf66c1986735d4134bcaf4096f/src/domains/ecommerce-app/api/ecommerce-io/v1/_openapi.json.tpl", "private": true, "scripts": { "start": "react-native start", From 8e28abff9e029757c63bf112f5b6aa57ccecf8b4 Mon Sep 17 00:00:00 2001 From: Federico Mastrini Date: Mon, 22 Jan 2024 12:43:23 +0100 Subject: [PATCH 10/19] chore: move transaction create upon method selection --- .../handleWalletPaymentCreateTransaction.ts | 1 + .../screens/WalletPaymentConfirmScreen.tsx | 22 ++------ .../screens/WalletPaymentPickMethodScreen.tsx | 55 +++++++++++-------- .../payment/store/actions/networking.ts | 2 +- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/ts/features/walletV3/payment/saga/networking/handleWalletPaymentCreateTransaction.ts b/ts/features/walletV3/payment/saga/networking/handleWalletPaymentCreateTransaction.ts index 609df9291e4..97d6ffd9802 100644 --- a/ts/features/walletV3/payment/saga/networking/handleWalletPaymentCreateTransaction.ts +++ b/ts/features/walletV3/payment/saga/networking/handleWalletPaymentCreateTransaction.ts @@ -34,6 +34,7 @@ export function* handleWalletPaymentCreateTransaction( }), ({ status, value }) => { if (status === 200) { + action.payload.onSucces?.(); return walletPaymentCreateTransaction.success(value); } else if (status === 400) { return walletPaymentCreateTransaction.failure({ diff --git a/ts/features/walletV3/payment/screens/WalletPaymentConfirmScreen.tsx b/ts/features/walletV3/payment/screens/WalletPaymentConfirmScreen.tsx index 11c6b47f9b0..b8328f6f70c 100644 --- a/ts/features/walletV3/payment/screens/WalletPaymentConfirmScreen.tsx +++ b/ts/features/walletV3/payment/screens/WalletPaymentConfirmScreen.tsx @@ -5,7 +5,7 @@ import { VSpacer } from "@pagopa/io-app-design-system"; import * as pot from "@pagopa/ts-commons/lib/pot"; -import { useFocusEffect, useNavigation } from "@react-navigation/native"; +import { useNavigation } 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"; @@ -18,11 +18,11 @@ import { AppParamsList, IOStackNavigationProp } from "../../../../navigation/params/AppParamsList"; -import { useIODispatch, useIOSelector } from "../../../../store/hooks"; +import { useIOSelector } from "../../../../store/hooks"; import { emptyContextualHelp } from "../../../../utils/emptyContextualHelp"; import { WalletPaymentConfirmContent } from "../components/WalletPaymentConfirmContent"; +import { useWalletPaymentAuthorizationModal } from "../hooks/useWalletPaymentAuthorizationModal"; import { WalletPaymentRoutes } from "../navigation/routes"; -import { walletPaymentCreateTransaction } from "../store/actions/networking"; import { walletPaymentDetailsSelector, walletPaymentPickedPaymentMethodSelector, @@ -30,10 +30,8 @@ import { walletPaymentTransactionSelector } from "../store/selectors"; import { WalletPaymentOutcome } from "../types/PaymentOutcomeEnum"; -import { useWalletPaymentAuthorizationModal } from "../hooks/useWalletPaymentAuthorizationModal"; const WalletPaymentConfirmScreen = () => { - const dispatch = useIODispatch(); const navigation = useNavigation>(); const paymentDetailsPot = useIOSelector(walletPaymentDetailsSelector); @@ -43,9 +41,6 @@ const WalletPaymentConfirmScreen = () => { ); const selectedPspOption = useIOSelector(walletPaymentPickedPspSelector); - const isTransactionLoading = pot.isLoading(transactionPot); - const isTransactionError = pot.isError(transactionPot); - useHeaderSecondLevel({ title: "", contextualHelp: emptyContextualHelp, @@ -53,12 +48,6 @@ const WalletPaymentConfirmScreen = () => { supportRequest: true }); - useFocusEffect( - React.useCallback(() => { - dispatch(walletPaymentCreateTransaction.request({ paymentNotices: [] })); - }, [dispatch]) - ); - const handleStartPaymentAuthorization = () => pipe( sequenceS(O.Monad)({ @@ -96,9 +85,8 @@ const WalletPaymentConfirmScreen = () => { onAuthorizationOutcome: handleAuthorizationOutcome }); - const isLoading = - isTransactionLoading || isAuthUrlLoading || isPendingAuthorization; - const isError = isTransactionError || isAuthUrlError; + const isLoading = isAuthUrlLoading || isPendingAuthorization; + const isError = isAuthUrlError; if (isError) { // TODO: Failure handling (https://pagopa.atlassian.net/browse/IOBP-471) diff --git a/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx b/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx index bed3a5835dc..0f375679541 100644 --- a/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx +++ b/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx @@ -11,7 +11,7 @@ import { } from "@pagopa/io-app-design-system"; import * as pot from "@pagopa/ts-commons/lib/pot"; import { useFocusEffect, useNavigation } from "@react-navigation/native"; -import { sequenceS } from "fp-ts/lib/Apply"; +import { sequenceS, sequenceT } from "fp-ts/lib/Apply"; import * as A from "fp-ts/lib/Array"; import * as O from "fp-ts/lib/Option"; import { pipe } from "fp-ts/lib/function"; @@ -31,11 +31,15 @@ import { ComponentProps } from "../../../../types/react"; import { emptyContextualHelp } from "../../../../utils/emptyContextualHelp"; import { findFirstCaseInsensitive } from "../../../../utils/object"; import { WalletPaymentRoutes } from "../navigation/routes"; -import { walletPaymentGetUserWallets } from "../store/actions/networking"; +import { + walletPaymentCreateTransaction, + walletPaymentGetUserWallets +} from "../store/actions/networking"; import { walletPaymentPickPaymentMethod } from "../store/actions/orchestration"; import { walletPaymentAllMethodsSelector, walletPaymentAmountSelector, + walletPaymentDetailsSelector, walletPaymentSavedMethodByIdSelector, walletPaymentTransactionSelector, walletPaymentUserWalletsSelector @@ -72,6 +76,7 @@ const WalletPaymentPickMethodScreen = () => { supportRequest: true }); + const paymentDetailsPot = useIOSelector(walletPaymentDetailsSelector); const transactionPot = useIOSelector(walletPaymentTransactionSelector); const getSavedtMethodById = useIOSelector( walletPaymentSavedMethodByIdSelector @@ -84,22 +89,15 @@ const WalletPaymentPickMethodScreen = () => { const alertRef = React.useRef(null); const isLoading = - pot.isLoading(paymentMethodsPot) || - pot.isLoading(userWalletsPots) || - pot.isLoading(transactionPot); + pot.isLoading(paymentMethodsPot) || pot.isLoading(userWalletsPots); + + const isLoadingTransaction = pot.isLoading(transactionPot); const [shouldShowWarningBanner, setShouldShowWarningBanner] = React.useState(false); const [selectedMethod, setSelectedMethod] = React.useState(undefined); - useHeaderSecondLevel({ - title: "", - contextualHelp: emptyContextualHelp, - faqCategories: ["payment"], - supportRequest: true - }); - useFocusEffect( React.useCallback(() => { // dispatch(walletPaymentGetAllMethods.request()); // currently we do not allow onboarding new methods in payment flow @@ -152,19 +150,32 @@ const WalletPaymentPickMethodScreen = () => { }; */ + const navigateToPspSelectionScreen = () => { + navigation.navigate(WalletPaymentRoutes.WALLET_PAYMENT_MAIN, { + screen: WalletPaymentRoutes.WALLET_PAYMENT_PICK_PSP + }); + }; + const handleContinue = () => { // todo:: should handle the case where the user // selects a non saved method if (selectedMethod?.kind === "saved") { pipe( - getSavedtMethodById(selectedMethod.walletId), - O.map(walletPaymentPickPaymentMethod), - O.map(dispatch), - O.map(() => - navigation.navigate(WalletPaymentRoutes.WALLET_PAYMENT_MAIN, { - screen: WalletPaymentRoutes.WALLET_PAYMENT_PICK_PSP - }) - ) + sequenceT(O.Monad)( + getSavedtMethodById(selectedMethod.walletId), + pot.toOption(paymentDetailsPot) + ), + O.map(([method, paymentDetails]) => { + dispatch(walletPaymentPickPaymentMethod(method)); + dispatch( + walletPaymentCreateTransaction.request({ + paymentNotices: [ + { rptId: paymentDetails.rptId, amount: paymentDetails.amount } + ], + onSucces: navigateToPspSelectionScreen + }) + ); + }) ); } }; @@ -189,8 +200,8 @@ const WalletPaymentPickMethodScreen = () => { label: I18n.t("global.buttons.continue"), accessibilityLabel: I18n.t("global.buttons.continue"), onPress: handleContinue, - disabled: isLoading || !canContinue, - loading: isLoading + disabled: isLoading || isLoadingTransaction || !canContinue, + loading: isLoading || isLoadingTransaction }} >

{I18n.t("wallet.payment.methodSelection.header")}

diff --git a/ts/features/walletV3/payment/store/actions/networking.ts b/ts/features/walletV3/payment/store/actions/networking.ts index 4e457460086..2f9b1df4a4e 100644 --- a/ts/features/walletV3/payment/store/actions/networking.ts +++ b/ts/features/walletV3/payment/store/actions/networking.ts @@ -45,7 +45,7 @@ export const walletPaymentCreateTransaction = createAsyncAction( "WALLET_PAYMENT_CREATE_TRANSACTION_SUCCESS", "WALLET_PAYMENT_CREATE_TRANSACTION_FAILURE" )< - NewTransactionRequest, + NewTransactionRequest & { onSucces?: () => void }, NewTransactionResponse, NetworkError | WalletPaymentFailure >(); From f613abb2bd433f395f91a8153ef6e377b8cc12ab Mon Sep 17 00:00:00 2001 From: Federico Mastrini Date: Mon, 22 Jan 2024 12:46:34 +0100 Subject: [PATCH 11/19] fix: authorizatio request payload --- .../networking/handleWalletPaymentAuthorization.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ts/features/walletV3/payment/saga/networking/handleWalletPaymentAuthorization.ts b/ts/features/walletV3/payment/saga/networking/handleWalletPaymentAuthorization.ts index d634e782503..2a1a9614566 100644 --- a/ts/features/walletV3/payment/saga/networking/handleWalletPaymentAuthorization.ts +++ b/ts/features/walletV3/payment/saga/networking/handleWalletPaymentAuthorization.ts @@ -2,16 +2,17 @@ import * as E from "fp-ts/lib/Either"; import { pipe } from "fp-ts/lib/function"; import { call, put } from "typed-redux-saga/macro"; import { ActionType } from "typesafe-actions"; +import { + LanguageEnum, + RequestAuthorizationRequest +} from "../../../../../../definitions/pagopa/ecommerce/RequestAuthorizationRequest"; +import { WalletDetailTypeEnum } from "../../../../../../definitions/pagopa/ecommerce/WalletDetailType"; import { SagaCallReturnType } from "../../../../../types/utils"; import { getGenericError, getNetworkError } from "../../../../../utils/errors"; import { readablePrivacyReport } from "../../../../../utils/reporters"; import { withRefreshApiCall } from "../../../../fastLogin/saga/utils"; import { PaymentClient } from "../../api/client"; import { walletPaymentAuthorization } from "../../store/actions/networking"; -import { - LanguageEnum, - RequestAuthorizationRequest -} from "../../../../../../definitions/pagopa/ecommerce/RequestAuthorizationRequest"; export function* handleWalletPaymentAuthorization( requestTransactionAuthorization: PaymentClient["requestTransactionAuthorization"], @@ -23,7 +24,10 @@ export function* handleWalletPaymentAuthorization( isAllCCP: true, language: LanguageEnum.IT, pspId: action.payload.pspId, - walletId: action.payload.walletId + details: { + detailType: WalletDetailTypeEnum.wallet, + walletId: action.payload.walletId + } }; const requestTransactionAuthorizationRequest = requestTransactionAuthorization({ From 38189ba024dbd81a64fa4e34cdae72f5f964710e Mon Sep 17 00:00:00 2001 From: Federico Mastrini Date: Mon, 22 Jan 2024 14:30:37 +0100 Subject: [PATCH 12/19] chore: add required data in calculate fees request --- .../handleWalletPaymentCalculateFees.ts | 11 +++----- .../screens/WalletPaymentPickMethodScreen.tsx | 1 - .../screens/WalletPaymentPickPspScreen.tsx | 26 +++++++++++++------ .../payment/store/actions/networking.ts | 12 ++++----- .../walletV3/payment/store/selectors/index.ts | 12 +++++++++ 5 files changed, 39 insertions(+), 23 deletions(-) diff --git a/ts/features/walletV3/payment/saga/networking/handleWalletPaymentCalculateFees.ts b/ts/features/walletV3/payment/saga/networking/handleWalletPaymentCalculateFees.ts index b49fa030ad4..9a3a1b9fa26 100644 --- a/ts/features/walletV3/payment/saga/networking/handleWalletPaymentCalculateFees.ts +++ b/ts/features/walletV3/payment/saga/networking/handleWalletPaymentCalculateFees.ts @@ -3,7 +3,6 @@ import * as O from "fp-ts/lib/Option"; import { call, put, select } from "typed-redux-saga/macro"; import { ActionType } from "typesafe-actions"; -import { CalculateFeeRequest } from "../../../../../../definitions/pagopa/ecommerce/CalculateFeeRequest"; import { SagaCallReturnType } from "../../../../../types/utils"; import { getGenericError, getNetworkError } from "../../../../../utils/errors"; import { readablePrivacyReport } from "../../../../../utils/reporters"; @@ -19,14 +18,10 @@ export function* handleWalletPaymentCalculateFees( calculateFees: PaymentClient["calculateFees"], action: ActionType<(typeof walletPaymentCalculateFees)["request"]> ) { - const requestBody: CalculateFeeRequest = { - paymentAmount: action.payload.paymentAmountInCents, - walletId: action.payload.walletId - }; - + const { walletId, ...body } = action.payload; const calculateFeesRequest = calculateFees({ - id: action.payload.walletId, - body: requestBody + id: walletId, + body }); try { diff --git a/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx b/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx index 0f375679541..7e72d607625 100644 --- a/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx +++ b/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx @@ -90,7 +90,6 @@ const WalletPaymentPickMethodScreen = () => { const isLoading = pot.isLoading(paymentMethodsPot) || pot.isLoading(userWalletsPots); - const isLoadingTransaction = pot.isLoading(transactionPot); const [shouldShowWarningBanner, setShouldShowWarningBanner] = diff --git a/ts/features/walletV3/payment/screens/WalletPaymentPickPspScreen.tsx b/ts/features/walletV3/payment/screens/WalletPaymentPickPspScreen.tsx index b331f4c1ddc..a297c8e2777 100644 --- a/ts/features/walletV3/payment/screens/WalletPaymentPickPspScreen.tsx +++ b/ts/features/walletV3/payment/screens/WalletPaymentPickPspScreen.tsx @@ -36,7 +36,8 @@ import { walletPaymentAmountSelector, walletPaymentPickedPaymentMethodSelector, walletPaymentPickedPspSelector, - walletPaymentPspListSelector + walletPaymentPspListSelector, + walletPaymentTransactionTransferListSelector } from "../store/selectors"; import { WalletPaymentPspSortType } from "../types"; @@ -51,8 +52,14 @@ const WalletPaymentPickPspScreen = () => { const [sortType, setSortType] = React.useState("default"); + const transactionTransferListPot = useIOSelector( + walletPaymentTransactionTransferListSelector + ); const pspListPot = useIOSelector(walletPaymentPspListSelector); + const selectedPspOption = useIOSelector(walletPaymentPickedPspSelector); + const isLoading = pot.isLoading(pspListPot); + const canContinue = O.isSome(selectedPspOption); const sortedPspList = pipe( pot.toOption(pspListPot), @@ -60,10 +67,6 @@ const WalletPaymentPickPspScreen = () => { O.toUndefined ); - const selectedPspOption = useIOSelector(walletPaymentPickedPspSelector); - - const canContinue = O.isSome(selectedPspOption); - useHeaderSecondLevel({ title: "", contextualHelp: emptyContextualHelp, @@ -76,18 +79,25 @@ const WalletPaymentPickPspScreen = () => { pipe( sequenceT(O.Monad)( pot.toOption(paymentAmountPot), + pot.toOption(transactionTransferListPot), selectedWalletOption ), - O.map(([paymentAmountInCents, selectedWallet]) => { + O.map(([paymentAmountInCents, transferList, selectedWallet]) => { dispatch( walletPaymentCalculateFees.request({ walletId: selectedWallet.walletId, - paymentAmountInCents + paymentAmount: paymentAmountInCents, + transferList }) ); }) ); - }, [dispatch, paymentAmountPot, selectedWalletOption]) + }, [ + dispatch, + paymentAmountPot, + selectedWalletOption, + transactionTransferListPot + ]) ); React.useEffect( diff --git a/ts/features/walletV3/payment/store/actions/networking.ts b/ts/features/walletV3/payment/store/actions/networking.ts index 2f9b1df4a4e..e5c736548e1 100644 --- a/ts/features/walletV3/payment/store/actions/networking.ts +++ b/ts/features/walletV3/payment/store/actions/networking.ts @@ -10,6 +10,7 @@ import { PaymentMethodsResponse } from "../../../../../../definitions/pagopa/wal import { Wallets } from "../../../../../../definitions/pagopa/walletv3/Wallets"; import { NetworkError } from "../../../../../utils/errors"; import { WalletPaymentFailure } from "../../types/failure"; +import { CalculateFeeRequest } from "../../../../../../definitions/pagopa/ecommerce/CalculateFeeRequest"; export const walletPaymentGetDetails = createAsyncAction( "WALLET_PAYMENT_GET_DETAILS_REQUEST", @@ -29,16 +30,15 @@ export const walletPaymentGetUserWallets = createAsyncAction( "WALLET_PAYMENT_GET_USER_WALLETS_FAILURE" )(); -export type WalletPaymentCalculateFeesPayload = { - walletId: string; - paymentAmountInCents: number; -}; - export const walletPaymentCalculateFees = createAsyncAction( "WALLET_PAYMET_CALCULATE_FEES_REQUEST", "WALLET_PAYMET_CALCULATE_FEES_SUCCESS", "WALLET_PAYMET_CALCULATE_FEES_FAILURE" -)(); +)< + CalculateFeeRequest & { walletId: string }, + CalculateFeeResponse, + NetworkError +>(); export const walletPaymentCreateTransaction = createAsyncAction( "WALLET_PAYMENT_CREATE_TRANSACTION_REQUEST", diff --git a/ts/features/walletV3/payment/store/selectors/index.ts b/ts/features/walletV3/payment/store/selectors/index.ts index 20afcaaf1b8..579b8f159f0 100644 --- a/ts/features/walletV3/payment/store/selectors/index.ts +++ b/ts/features/walletV3/payment/store/selectors/index.ts @@ -3,6 +3,7 @@ import * as O from "fp-ts/lib/Option"; import { pipe } from "fp-ts/lib/function"; import { createSelector } from "reselect"; import { GlobalState } from "../../../../../store/reducers/types"; +import { Transfer } from "../../../../../../definitions/pagopa/ecommerce/Transfer"; const selectWalletPayment = (state: GlobalState) => state.features.wallet.payment; @@ -72,6 +73,17 @@ export const walletPaymentTransactionSelector = createSelector( state => state.transaction ); +export const walletPaymentTransactionTransferListSelector = createSelector( + walletPaymentTransactionSelector, + transaction => + pot.map(transaction, t => + t.payments.reduce( + (a, p) => [...a, ...(p.transferList ?? [])], + [] as ReadonlyArray + ) + ) +); + export const walletPaymentAuthorizationUrlSelector = createSelector( selectWalletPayment, state => state.authorizationUrl From 7f6ab927c307a03a0486730d9a85a0065a238457 Mon Sep 17 00:00:00 2001 From: Federico Mastrini Date: Mon, 22 Jan 2024 15:02:39 +0100 Subject: [PATCH 13/19] chore: remove buttons if no method/psp selected --- .../screens/WalletPaymentPickMethodScreen.tsx | 18 +++++++++++------- .../screens/WalletPaymentPickPspScreen.tsx | 18 +++++++++++------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx b/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx index 7e72d607625..334cc851f1d 100644 --- a/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx +++ b/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx @@ -195,13 +195,17 @@ const WalletPaymentPickMethodScreen = () => { return (

{I18n.t("wallet.payment.methodSelection.header")}

diff --git a/ts/features/walletV3/payment/screens/WalletPaymentPickPspScreen.tsx b/ts/features/walletV3/payment/screens/WalletPaymentPickPspScreen.tsx index a297c8e2777..bd070bb0635 100644 --- a/ts/features/walletV3/payment/screens/WalletPaymentPickPspScreen.tsx +++ b/ts/features/walletV3/payment/screens/WalletPaymentPickPspScreen.tsx @@ -182,13 +182,17 @@ const WalletPaymentPickPspScreen = () => { return ( {!isLoading && ( From a0678274612b827df83485faab2d6070aa913cb2 Mon Sep 17 00:00:00 2001 From: Federico Mastrini Date: Mon, 22 Jan 2024 15:07:45 +0100 Subject: [PATCH 14/19] fix: tests --- .../__tests__/handleWalletPaymentCalculateFees.test.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ts/features/walletV3/payment/saga/networking/__tests__/handleWalletPaymentCalculateFees.test.ts b/ts/features/walletV3/payment/saga/networking/__tests__/handleWalletPaymentCalculateFees.test.ts index 7295c8816bb..9ad9e2c9c79 100644 --- a/ts/features/walletV3/payment/saga/networking/__tests__/handleWalletPaymentCalculateFees.test.ts +++ b/ts/features/walletV3/payment/saga/networking/__tests__/handleWalletPaymentCalculateFees.test.ts @@ -6,16 +6,14 @@ import { PaymentMethodStatusEnum } from "../../../../../../../definitions/pagopa import { getGenericError } from "../../../../../../utils/errors"; import { readablePrivacyReport } from "../../../../../../utils/reporters"; import { withRefreshApiCall } from "../../../../../fastLogin/saga/utils"; -import { - WalletPaymentCalculateFeesPayload, - walletPaymentCalculateFees -} from "../../../store/actions/networking"; +import { walletPaymentCalculateFees } from "../../../store/actions/networking"; import { handleWalletPaymentCalculateFees } from "../handleWalletPaymentCalculateFees"; +import { CalculateFeeRequest } from "../../../../../../../definitions/pagopa/ecommerce/CalculateFeeRequest"; describe("Test handleWalletPaymentCalculateFees saga", () => { - const calculateFeesPayload: WalletPaymentCalculateFeesPayload = { + const calculateFeesPayload: CalculateFeeRequest & { walletId: string } = { walletId: "1234", - paymentAmountInCents: 1234 + paymentAmount: 1234 }; it(`should put ${getType( From 07b24792e76e996271ffd0832d9db3385ec4a27f Mon Sep 17 00:00:00 2001 From: Federico Mastrini Date: Mon, 22 Jan 2024 15:14:18 +0100 Subject: [PATCH 15/19] fix: buttons --- .../walletV3/payment/screens/WalletPaymentPickPspScreen.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/features/walletV3/payment/screens/WalletPaymentPickPspScreen.tsx b/ts/features/walletV3/payment/screens/WalletPaymentPickPspScreen.tsx index bd070bb0635..93c8c5052e7 100644 --- a/ts/features/walletV3/payment/screens/WalletPaymentPickPspScreen.tsx +++ b/ts/features/walletV3/payment/screens/WalletPaymentPickPspScreen.tsx @@ -188,7 +188,7 @@ const WalletPaymentPickPspScreen = () => { label: I18n.t("wallet.payment.psp.continueButton"), accessibilityLabel: I18n.t("wallet.payment.psp.continueButton"), onPress: handleContinue, - disabled: isLoading || !canContinue, + disabled: isLoading, loading: isLoading } : undefined From 7ad44b9f783ad9efff2339321a8dec631c51862f Mon Sep 17 00:00:00 2001 From: Federico Mastrini Date: Wed, 24 Jan 2024 11:49:51 +0100 Subject: [PATCH 16/19] chore: handle errors in method and psp screens --- .../screens/WalletPaymentPickMethodScreen.tsx | 23 ++++++++++++++++--- .../screens/WalletPaymentPickPspScreen.tsx | 14 +++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx b/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx index 334cc851f1d..da133cd3e8a 100644 --- a/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx +++ b/ts/features/walletV3/payment/screens/WalletPaymentPickMethodScreen.tsx @@ -30,6 +30,9 @@ import { useIODispatch, useIOSelector } from "../../../../store/hooks"; import { ComponentProps } from "../../../../types/react"; import { emptyContextualHelp } from "../../../../utils/emptyContextualHelp"; import { findFirstCaseInsensitive } from "../../../../utils/object"; +import { UIWalletInfoDetails } from "../../details/types/UIWalletInfoDetails"; +import { WalletPaymentMissingMethodsError } from "../components/WalletPaymentMissingMethodsError"; +import { useWalletPaymentGoBackHandler } from "../hooks/useWalletPaymentGoBackHandler"; import { WalletPaymentRoutes } from "../navigation/routes"; import { walletPaymentCreateTransaction, @@ -44,9 +47,7 @@ import { walletPaymentTransactionSelector, walletPaymentUserWalletsSelector } from "../store/selectors"; -import { WalletPaymentMissingMethodsError } from "../components/WalletPaymentMissingMethodsError"; -import { useWalletPaymentGoBackHandler } from "../hooks/useWalletPaymentGoBackHandler"; -import { UIWalletInfoDetails } from "../../details/types/UIWalletInfoDetails"; +import { WalletPaymentOutcomeEnum } from "../types/PaymentOutcomeEnum"; type SavedMethodState = { kind: "saved"; @@ -92,6 +93,11 @@ const WalletPaymentPickMethodScreen = () => { pot.isLoading(paymentMethodsPot) || pot.isLoading(userWalletsPots); const isLoadingTransaction = pot.isLoading(transactionPot); + const isError = + pot.isError(transactionPot) || + pot.isError(paymentMethodsPot) || + pot.isError(userWalletsPots); + const [shouldShowWarningBanner, setShouldShowWarningBanner] = React.useState(false); const [selectedMethod, setSelectedMethod] = @@ -104,6 +110,17 @@ const WalletPaymentPickMethodScreen = () => { }, [dispatch]) ); + React.useEffect(() => { + if (isError) { + navigation.navigate(WalletPaymentRoutes.WALLET_PAYMENT_MAIN, { + screen: WalletPaymentRoutes.WALLET_PAYMENT_OUTCOME, + params: { + outcome: WalletPaymentOutcomeEnum.GENERIC_ERROR + } + }); + } + }, [isError, navigation]); + const paymentAmount = pot.getOrElse(paymentAmountPot, undefined); const canContinue = selectedMethod !== undefined; diff --git a/ts/features/walletV3/payment/screens/WalletPaymentPickPspScreen.tsx b/ts/features/walletV3/payment/screens/WalletPaymentPickPspScreen.tsx index 93c8c5052e7..3e269a2efa7 100644 --- a/ts/features/walletV3/payment/screens/WalletPaymentPickPspScreen.tsx +++ b/ts/features/walletV3/payment/screens/WalletPaymentPickPspScreen.tsx @@ -40,6 +40,7 @@ import { walletPaymentTransactionTransferListSelector } from "../store/selectors"; import { WalletPaymentPspSortType } from "../types"; +import { WalletPaymentOutcomeEnum } from "../types/PaymentOutcomeEnum"; const WalletPaymentPickPspScreen = () => { const paymentAmountPot = useIOSelector(walletPaymentAmountSelector); @@ -59,6 +60,8 @@ const WalletPaymentPickPspScreen = () => { const selectedPspOption = useIOSelector(walletPaymentPickedPspSelector); const isLoading = pot.isLoading(pspListPot); + const isError = pot.isError(pspListPot); + const canContinue = O.isSome(selectedPspOption); const sortedPspList = pipe( @@ -67,6 +70,17 @@ const WalletPaymentPickPspScreen = () => { O.toUndefined ); + React.useEffect(() => { + if (isError) { + navigation.navigate(WalletPaymentRoutes.WALLET_PAYMENT_MAIN, { + screen: WalletPaymentRoutes.WALLET_PAYMENT_OUTCOME, + params: { + outcome: WalletPaymentOutcomeEnum.GENERIC_ERROR + } + }); + } + }, [isError, navigation]); + useHeaderSecondLevel({ title: "", contextualHelp: emptyContextualHelp, From 22ef76b059a1d6ce75bf1cb1b04356c8a991c0a2 Mon Sep 17 00:00:00 2001 From: Federico Mastrini Date: Wed, 24 Jan 2024 15:17:39 +0100 Subject: [PATCH 17/19] fix: payment method id --- .../saga/networking/handleWalletPaymentCalculateFees.ts | 4 ++-- .../walletV3/payment/screens/WalletPaymentPickPspScreen.tsx | 2 +- ts/features/walletV3/payment/store/actions/networking.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ts/features/walletV3/payment/saga/networking/handleWalletPaymentCalculateFees.ts b/ts/features/walletV3/payment/saga/networking/handleWalletPaymentCalculateFees.ts index 9a3a1b9fa26..aeda5074d1b 100644 --- a/ts/features/walletV3/payment/saga/networking/handleWalletPaymentCalculateFees.ts +++ b/ts/features/walletV3/payment/saga/networking/handleWalletPaymentCalculateFees.ts @@ -18,9 +18,9 @@ export function* handleWalletPaymentCalculateFees( calculateFees: PaymentClient["calculateFees"], action: ActionType<(typeof walletPaymentCalculateFees)["request"]> ) { - const { walletId, ...body } = action.payload; + const { paymentMethodId, ...body } = action.payload; const calculateFeesRequest = calculateFees({ - id: walletId, + id: paymentMethodId, body }); diff --git a/ts/features/walletV3/payment/screens/WalletPaymentPickPspScreen.tsx b/ts/features/walletV3/payment/screens/WalletPaymentPickPspScreen.tsx index 3e269a2efa7..f2ae6b02aad 100644 --- a/ts/features/walletV3/payment/screens/WalletPaymentPickPspScreen.tsx +++ b/ts/features/walletV3/payment/screens/WalletPaymentPickPspScreen.tsx @@ -99,7 +99,7 @@ const WalletPaymentPickPspScreen = () => { O.map(([paymentAmountInCents, transferList, selectedWallet]) => { dispatch( walletPaymentCalculateFees.request({ - walletId: selectedWallet.walletId, + paymentMethodId: selectedWallet.paymentMethodId, paymentAmount: paymentAmountInCents, transferList }) diff --git a/ts/features/walletV3/payment/store/actions/networking.ts b/ts/features/walletV3/payment/store/actions/networking.ts index e5c736548e1..92b4594490b 100644 --- a/ts/features/walletV3/payment/store/actions/networking.ts +++ b/ts/features/walletV3/payment/store/actions/networking.ts @@ -35,7 +35,7 @@ export const walletPaymentCalculateFees = createAsyncAction( "WALLET_PAYMET_CALCULATE_FEES_SUCCESS", "WALLET_PAYMET_CALCULATE_FEES_FAILURE" )< - CalculateFeeRequest & { walletId: string }, + CalculateFeeRequest & { paymentMethodId: string }, CalculateFeeResponse, NetworkError >(); From 96e163946b6be76fe04cebbfd1c91c3776d91760 Mon Sep 17 00:00:00 2001 From: Alessandro Izzo Date: Wed, 24 Jan 2024 16:07:40 +0100 Subject: [PATCH 18/19] chore: updated ecommerce api definitions --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8c95c272193..845da08b59e 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "lollipop_api": "https://raw.githubusercontent.com/pagopa/io-backend/v13.25.1-RELEASE/api_lollipop_first_consumer.yaml", "fast_login_api": "https://raw.githubusercontent.com/pagopa/io-backend/v13.25.1-RELEASE/openapi/generated/api_fast_login.yaml", "pagopa_api_walletv3": "https://raw.githubusercontent.com/pagopa/pagopa-infra/740e7dcc5ea2ea19639316fea6797bbd504dd0ae/src/domains/wallet-app/api/payment-wallet/v1/_openapi.json.tpl", - "pagopa_api_ecommerce": "https://raw.githubusercontent.com/pagopa/pagopa-infra/5190135ac34791cf66c1986735d4134bcaf4096f/src/domains/ecommerce-app/api/ecommerce-io/v1/_openapi.json.tpl", + "pagopa_api_ecommerce": "https://raw.githubusercontent.com/pagopa/pagopa-infra/a5299db39de86a951d353fdc2bfed48188c0c125/src/domains/ecommerce-app/api/ecommerce-io/v1/_openapi.json.tpl", "private": true, "scripts": { "start": "react-native start", From e8778e7ebe9d9f8114db2bf6dafc6e2d196e2e91 Mon Sep 17 00:00:00 2001 From: Federico Mastrini Date: Wed, 24 Jan 2024 16:26:53 +0100 Subject: [PATCH 19/19] fix: tests --- .../__tests__/handleWalletPaymentCalculateFees.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ts/features/walletV3/payment/saga/networking/__tests__/handleWalletPaymentCalculateFees.test.ts b/ts/features/walletV3/payment/saga/networking/__tests__/handleWalletPaymentCalculateFees.test.ts index 9ad9e2c9c79..85e26423685 100644 --- a/ts/features/walletV3/payment/saga/networking/__tests__/handleWalletPaymentCalculateFees.test.ts +++ b/ts/features/walletV3/payment/saga/networking/__tests__/handleWalletPaymentCalculateFees.test.ts @@ -11,8 +11,10 @@ import { handleWalletPaymentCalculateFees } from "../handleWalletPaymentCalculat import { CalculateFeeRequest } from "../../../../../../../definitions/pagopa/ecommerce/CalculateFeeRequest"; describe("Test handleWalletPaymentCalculateFees saga", () => { - const calculateFeesPayload: CalculateFeeRequest & { walletId: string } = { - walletId: "1234", + const calculateFeesPayload: CalculateFeeRequest & { + paymentMethodId: string; + } = { + paymentMethodId: "1234", paymentAmount: 1234 };