diff --git a/package.json b/package.json index c39998dad14..fb620b2160f 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,8 @@ "services_api": "https://raw.githubusercontent.com/pagopa/io-backend/v14.3.0-RELEASE/api_services_app_backend.yaml", "lollipop_api": "https://raw.githubusercontent.com/pagopa/io-backend/v14.3.0-RELEASE/api_lollipop_first_consumer.yaml", "fast_login_api": "https://raw.githubusercontent.com/pagopa/io-auth-n-identity-domain/io-session-manager@1.0.0/apps/io-session-manager/api/fast-login.yaml", - "pagopa_api_walletv3": "https://raw.githubusercontent.com/pagopa/pagopa-infra/v1.162.2/src/domains/pay-wallet-app/api/io-payment-wallet/v1/_openapi.json.tpl", - "pagopa_api_ecommerce": "https://raw.githubusercontent.com/pagopa/pagopa-infra/v1.162.2/src/domains/ecommerce-app/api/ecommerce-io/v2/_openapi.json.tpl", + "pagopa_api_walletv3": "https://raw.githubusercontent.com/pagopa/pagopa-infra/v1.202.0/src/domains/pay-wallet-app/api/io-payment-wallet/v1/_openapi.json.tpl", + "pagopa_api_ecommerce": "https://raw.githubusercontent.com/pagopa/pagopa-infra/v1.202.0/src/domains/ecommerce-app/api/ecommerce-io/v2/_openapi.json.tpl", "pagopa_api_biz_events": "https://raw.githubusercontent.com/pagopa/pagopa-biz-events-service/0.1.37/openapi/openapi_io_patch.json", "pagopa_api_platform": "https://raw.githubusercontent.com/pagopa/pagopa-infra/v1.64.0/src/domains/shared-app/api/session-wallet/v1/_openapi.json.tpl", "trial_system": "https://raw.githubusercontent.com/pagopa/io-backend/v14.3.0-RELEASE/api_trial_system.yaml", diff --git a/ts/features/payments/checkout/components/CheckoutPaymentMethodsList.tsx b/ts/features/payments/checkout/components/CheckoutPaymentMethodsList.tsx index 027e010d979..905a0b75443 100644 --- a/ts/features/payments/checkout/components/CheckoutPaymentMethodsList.tsx +++ b/ts/features/payments/checkout/components/CheckoutPaymentMethodsList.tsx @@ -22,9 +22,11 @@ import { walletPaymentAllMethodsSelector, walletPaymentEnabledUserWalletsSelector, walletPaymentSelectedPaymentMethodIdOptionSelector, - walletPaymentSelectedWalletIdOptionSelector + walletPaymentSelectedWalletIdOptionSelector, + walletRecentPaymentMethodSelector } from "../store/selectors/paymentMethods"; import { getPaymentLogoFromWalletDetails } from "../../common/utils"; +import { WalletStatusEnum } from "../../../../../definitions/pagopa/ecommerce/WalletStatus"; const CheckoutPaymentMethodsList = () => { const dispatch = useIODispatch(); @@ -35,6 +37,9 @@ const CheckoutPaymentMethodsList = () => { const paymentAmountPot = useIOSelector(walletPaymentAmountSelector); const allPaymentMethods = useIOSelector(walletPaymentAllMethodsSelector); const userWallets = useIOSelector(walletPaymentEnabledUserWalletsSelector); + const recentUsedPaymentMethod = useIOSelector( + walletRecentPaymentMethodSelector + ); const selectedUserWalletIdOption = useIOSelector( walletPaymentSelectedWalletIdOptionSelector @@ -49,6 +54,23 @@ const CheckoutPaymentMethodsList = () => { O.getOrElse(() => 0) ); + const recentPaymentMethodListItem = useMemo( + () => + pipe( + recentUsedPaymentMethod, + O.fromNullable, + O.chainNullableK(a => { + if (a.status === WalletStatusEnum.VALIDATED) { + return mapUserWalletToRadioItem(a); + } + return mapPaymentMethodToRadioItem(a, paymentAmount); + }), + O.map(A.of), + O.getOrElse(() => [] as Array>) + ), + [recentUsedPaymentMethod, paymentAmount] + ); + const userPaymentMethodListItems = useMemo( () => pipe( @@ -57,9 +79,15 @@ const CheckoutPaymentMethodsList = () => { O.map(methods => methods.map(mapUserWalletToRadioItem)), O.map(A.map(O.fromNullable)), O.map(A.compact), + O.map( + A.filter( + method => + !recentPaymentMethodListItem.some(item => item.id === method.id) + ) + ), O.getOrElse(() => [] as Array>) ), - [userWallets] + [userWallets, recentPaymentMethodListItem] ); const allPaymentMethodListItems = useMemo( @@ -70,18 +98,30 @@ const CheckoutPaymentMethodsList = () => { O.map(methods => methods.map(item => mapPaymentMethodToRadioItem(item, paymentAmount)) ), + O.map( + A.filter( + method => + !recentPaymentMethodListItem.some(item => item.id === method.id) + ) + ), O.getOrElse(() => [] as Array>) ), - [allPaymentMethods, paymentAmount] + [allPaymentMethods, paymentAmount, recentPaymentMethodListItem] ); useEffect(() => { const hasDisabledMethods = - [...userPaymentMethodListItems, ...allPaymentMethodListItems].find( - item => item.disabled - ) !== undefined; + [ + ...userPaymentMethodListItems, + ...allPaymentMethodListItems, + ...recentPaymentMethodListItem + ].find(item => item.disabled) !== undefined; setShouldShowWarningBanner(hasDisabledMethods); - }, [userPaymentMethodListItems, allPaymentMethodListItems]); + }, [ + userPaymentMethodListItems, + allPaymentMethodListItems, + recentPaymentMethodListItem + ]); const handleSelectUserWallet = (walletId: string) => pipe( @@ -115,6 +155,11 @@ const CheckoutPaymentMethodsList = () => { }) ); + const handleOnSelectRecentPaymentMethod = (walletId: string) => + recentUsedPaymentMethod?.status === WalletStatusEnum.VALIDATED + ? handleSelectUserWallet(walletId) + : handleSelectPaymentMethod(walletId); + const selectedWalletId = O.toUndefined(selectedUserWalletIdOption); const selectedPaymentMethodId = O.toUndefined(selectedPaymentMethodIdOption); @@ -128,6 +173,17 @@ const CheckoutPaymentMethodsList = () => { action={I18n.t("wallet.payment.methodSelection.alert.cta")} /> )} + {!_.isEmpty(recentPaymentMethodListItem) && ( + + )} + + type="radioListItem" + selectedItem={selectedWalletId || selectedPaymentMethodId} + items={recentPaymentMethodListItem} + onPress={handleOnSelectRecentPaymentMethod} + /> {!_.isEmpty(userPaymentMethodListItems) && ( +) { + try { + const getAllPaymentMethodsResult = yield* withPaymentsSessionToken( + getUserLastPaymentMethod, + action, + {}, + "pagoPAPlatformSessionToken" + ); + + if (E.isLeft(getAllPaymentMethodsResult)) { + yield* put( + paymentsGetRecentPaymentMethodUsedAction.failure({ + ...getGenericError( + new Error(readablePrivacyReport(getAllPaymentMethodsResult.left)) + ) + }) + ); + return; + } + const res = getAllPaymentMethodsResult.right; + if (res.status === 200) { + yield* put(paymentsGetRecentPaymentMethodUsedAction.success(res.value)); + } else if (res.status !== 401) { + // The 401 status is handled by the withPaymentsSessionToken + yield* put( + paymentsGetRecentPaymentMethodUsedAction.failure({ + ...getGenericError(new Error(`Error: ${res.status}`)) + }) + ); + } + } catch (e) { + yield* put( + paymentsGetRecentPaymentMethodUsedAction.failure({ + ...getNetworkError(e) + }) + ); + } +} diff --git a/ts/features/payments/checkout/screens/WalletPaymentPickMethodScreen.tsx b/ts/features/payments/checkout/screens/WalletPaymentPickMethodScreen.tsx index 30e303239f2..c9d81ed6e7e 100644 --- a/ts/features/payments/checkout/screens/WalletPaymentPickMethodScreen.tsx +++ b/ts/features/payments/checkout/screens/WalletPaymentPickMethodScreen.tsx @@ -18,7 +18,8 @@ import { PaymentsCheckoutRoutes } from "../navigation/routes"; import { paymentsCalculatePaymentFeesAction, paymentsCreateTransactionAction, - paymentsGetPaymentMethodsAction + paymentsGetPaymentMethodsAction, + paymentsGetRecentPaymentMethodUsedAction } from "../store/actions/networking"; import { walletPaymentAmountSelector, @@ -75,6 +76,10 @@ const WalletPaymentPickMethodScreen = () => { }, [dispatch]) ); + useOnFirstRender(() => { + dispatch(paymentsGetRecentPaymentMethodUsedAction.request()); + }); + const calculateFeesForSelectedPaymentMethod = React.useCallback(() => { pipe( sequenceT(O.Monad)( diff --git a/ts/features/payments/checkout/store/__tests__/store.test.ts b/ts/features/payments/checkout/store/__tests__/store.test.ts index 9e284345053..f03afe634b1 100644 --- a/ts/features/payments/checkout/store/__tests__/store.test.ts +++ b/ts/features/payments/checkout/store/__tests__/store.test.ts @@ -11,6 +11,7 @@ const INITIAL_STATE: PaymentsCheckoutState = { paymentDetails: pot.none, userWallets: pot.none, allPaymentMethods: pot.none, + recentUsedPaymentMethod: pot.none, pspList: pot.none, selectedWallet: O.none, selectedPaymentMethod: O.none, diff --git a/ts/features/payments/checkout/store/actions/networking.ts b/ts/features/payments/checkout/store/actions/networking.ts index f472e17659d..308cf406c15 100644 --- a/ts/features/payments/checkout/store/actions/networking.ts +++ b/ts/features/payments/checkout/store/actions/networking.ts @@ -14,6 +14,7 @@ import { Wallets } from "../../../../../../definitions/pagopa/ecommerce/Wallets" import { NetworkError } from "../../../../../utils/errors"; import { WalletPaymentFailure } from "../../types/WalletPaymentFailure"; import { WalletInfo } from "../../../../../../definitions/pagopa/ecommerce/WalletInfo"; +import { UserLastPaymentMethodResponse } from "../../../../../../definitions/pagopa/ecommerce/UserLastPaymentMethodResponse"; export const paymentsGetPaymentDetailsAction = createAsyncAction( "PAYMENTS_GET_PAYMENT_DETAILS_REQUEST", @@ -37,6 +38,12 @@ export const paymentsGetPaymentUserMethodsAction = createAsyncAction( "PAYMENTS_GET_PAYMENT_USER_METHODS_FAILURE" )(); +export const paymentsGetRecentPaymentMethodUsedAction = createAsyncAction( + "PAYMENTS_GET_RECENT_PAYMENT_METHOD_REQUEST", + "PAYMENTS_GET_RECENT_PAYMENT_METHOD_SUCCESS", + "PAYMENTS_GET_RECENT_PAYMENT_METHOD_FAILURE" +)(); + export type CalculateFeePayload = { paymentMethodId: string; idPsp?: string; @@ -110,4 +117,5 @@ export type PaymentsCheckoutNetworkingActions = | ActionType | ActionType | ActionType + | ActionType | ActionType; diff --git a/ts/features/payments/checkout/store/reducers/index.ts b/ts/features/payments/checkout/store/reducers/index.ts index 9c8b2e98012..1f5a677e6d9 100644 --- a/ts/features/payments/checkout/store/reducers/index.ts +++ b/ts/features/payments/checkout/store/reducers/index.ts @@ -24,6 +24,7 @@ import { paymentsGetPaymentMethodsAction, paymentsGetPaymentTransactionInfoAction, paymentsGetPaymentUserMethodsAction, + paymentsGetRecentPaymentMethodUsedAction, paymentsStartPaymentAuthorizationAction } from "../actions/networking"; import { @@ -33,6 +34,7 @@ import { selectPaymentPspAction, walletPaymentSetCurrentStep } from "../actions/orchestration"; +import { UserLastPaymentMethodResponse } from "../../../../../../definitions/pagopa/ecommerce/UserLastPaymentMethodResponse"; export const WALLET_PAYMENT_STEP_MAX = 4; export type PaymentsCheckoutState = { @@ -43,6 +45,7 @@ export type PaymentsCheckoutState = { NetworkError | WalletPaymentFailure >; userWallets: pot.Pot; + recentUsedPaymentMethod: pot.Pot; allPaymentMethods: pot.Pot; pspList: pot.Pot, NetworkError>; selectedWallet: O.Option; @@ -57,6 +60,7 @@ const INITIAL_STATE: PaymentsCheckoutState = { currentStep: WalletPaymentStepEnum.PICK_PAYMENT_METHOD, paymentDetails: pot.none, userWallets: pot.none, + recentUsedPaymentMethod: pot.none, allPaymentMethods: pot.none, pspList: pot.none, selectedWallet: O.none, @@ -89,6 +93,9 @@ const reducer = ( return { ...state, rptId: action.payload, + recentUsedPaymentMethod: pot.none, + selectedPaymentMethod: O.none, + selectedWallet: O.none, paymentDetails: pot.toLoading(state.paymentDetails) }; case getType(paymentsGetPaymentDetailsAction.success): @@ -136,6 +143,26 @@ const reducer = ( allPaymentMethods: pot.toError(state.allPaymentMethods, action.payload) }; + // Recent payment method + case getType(paymentsGetRecentPaymentMethodUsedAction.request): + return { + ...state, + recentUsedPaymentMethod: pot.toLoading(state.recentUsedPaymentMethod) + }; + case getType(paymentsGetRecentPaymentMethodUsedAction.success): + return { + ...state, + recentUsedPaymentMethod: pot.some(action.payload) + }; + case getType(paymentsGetRecentPaymentMethodUsedAction.failure): + return { + ...state, + recentUsedPaymentMethod: pot.toError( + state.recentUsedPaymentMethod, + action.payload + ) + }; + case getType(selectPaymentMethodAction): return { ...state, diff --git a/ts/features/payments/checkout/store/selectors/paymentMethods.ts b/ts/features/payments/checkout/store/selectors/paymentMethods.ts index 677bd9e1295..4bd8eae94c9 100644 --- a/ts/features/payments/checkout/store/selectors/paymentMethods.ts +++ b/ts/features/payments/checkout/store/selectors/paymentMethods.ts @@ -3,9 +3,10 @@ import * as pot from "@pagopa/ts-commons/lib/pot"; import * as O from "fp-ts/lib/Option"; import { pipe } from "fp-ts/lib/function"; import { createSelector } from "reselect"; -import { getLatestUsedWallet, isValidPaymentMethod } from "../../utils"; +import { isValidPaymentMethod } from "../../utils"; import { Wallets } from "../../../../../../definitions/pagopa/ecommerce/Wallets"; import { WalletApplicationStatusEnum } from "../../../../../../definitions/pagopa/ecommerce/WalletApplicationStatus"; +import { WalletLastUsageTypeEnum } from "../../../../../../definitions/pagopa/ecommerce/WalletLastUsageType"; import { WalletApplicationNameEnum } from "../../../../../../definitions/pagopa/ecommerce/WalletApplicationName"; import { UIWalletInfoDetails } from "../../../common/types/UIWalletInfoDetails"; import { isPaymentMethodExpired } from "../../../common/utils"; @@ -34,13 +35,6 @@ export const walletPaymentEnabledUserWalletsSelector = createSelector( ) ); -// Get from the enabled userwallets the wallet with the attribute lastUpdated more recent -export const walletPaymentUserWalletLastUpdatedSelector = createSelector( - walletPaymentEnabledUserWalletsSelector, - userWalletsPot => - pipe(userWalletsPot, pot.toOption, O.chain(getLatestUsedWallet)) -); - export const walletPaymentAllMethodsSelector = createSelector( selectPaymentsCheckoutState, state => @@ -50,6 +44,39 @@ export const walletPaymentAllMethodsSelector = createSelector( ) ); +export const walletRecentPaymentMethodSelector = createSelector( + selectPaymentsCheckoutState, + walletPaymentEnabledUserWalletsSelector, + walletPaymentAllMethodsSelector, + ({ recentUsedPaymentMethod }, userWallets, allPaymentMethods) => { + const recentPaymentMethod = pot.toUndefined(recentUsedPaymentMethod); + if (!recentPaymentMethod) { + return undefined; + } + return recentPaymentMethod?.type === WalletLastUsageTypeEnum.wallet + ? pot + .toUndefined(userWallets) + ?.find(wallet => wallet.walletId === recentPaymentMethod.walletId) + : pot + .toUndefined(allPaymentMethods) + ?.find(method => method.id === recentPaymentMethod.paymentMethodId); + } + // pot.isSome(state.recentUsedPaymentMethod) + // ? state.recentUsedPaymentMethod.type === WalletLastUsageTypeEnum.wallet + // ? pot + // .toUndefined(userWallets) + // ?.find(wallet => wallet.id === recentUsedPaymentMethod.walletId) + // : pot + // .toUndefined(allPaymentMethods) + // ?.find( + // method => + // method.paymentMethodId === + // recentUsedPaymentMethod.paymentMethodId + // ) + // : undefined; + // } +); + export const walletPaymentMethodByIdSelector = createSelector( walletPaymentAllMethodsSelector, methodsPot => (id: string) => diff --git a/ts/features/payments/checkout/utils/index.ts b/ts/features/payments/checkout/utils/index.ts index 79c5bd29450..c9f7f426275 100644 --- a/ts/features/payments/checkout/utils/index.ts +++ b/ts/features/payments/checkout/utils/index.ts @@ -1,11 +1,6 @@ -import * as RA from "fp-ts/lib/ReadonlyArray"; -import * as O from "fp-ts/lib/Option"; -import { pipe } from "fp-ts/lib/function"; import _ from "lodash"; import { PaymentMethodManagementTypeEnum } from "../../../../../definitions/pagopa/ecommerce/PaymentMethodManagementType"; import { PaymentMethodResponse } from "../../../../../definitions/pagopa/ecommerce/PaymentMethodResponse"; -import { WalletInfo } from "../../../../../definitions/pagopa/ecommerce/WalletInfo"; -import { WalletClientStatusEnum } from "../../../../../definitions/pagopa/walletv3/WalletClientStatus"; import { PaymentMethodStatusEnum } from "../../../../../definitions/pagopa/ecommerce/PaymentMethodStatus"; import { WalletPaymentStepEnum } from "../types"; import { Bundle } from "../../../../../definitions/pagopa/ecommerce/Bundle"; @@ -29,24 +24,6 @@ export const isValidPaymentMethod = (method: PaymentMethodResponse) => ].includes(method.methodManagement) && method.status === PaymentMethodStatusEnum.ENABLED; -export const getLatestUsedWallet = ( - wallets: ReadonlyArray -): O.Option => - pipe( - wallets, - RA.filter( - wallet => wallet.clients.IO.status === WalletClientStatusEnum.ENABLED - ), - RA.reduce(undefined, (acc, curr) => - acc?.clients.IO?.lastUsage && - curr.clients.IO?.lastUsage && - acc.clients.IO.lastUsage > curr.clients.IO.lastUsage - ? acc - : curr - ), - O.fromNullable - ); - export const WalletPaymentStepScreenNames = { [WalletPaymentStepEnum.PICK_PAYMENT_METHOD]: "PICK_PAYMENT_METHOD", [WalletPaymentStepEnum.PICK_PSP]: "PICK_PSP",