Skip to content

Commit

Permalink
feat: [IOBP-405] Wallet details update services pagoPA capability (#5322
Browse files Browse the repository at this point in the history
)

## Short description
This PR implements a toggle to update the pagoPA capability status
inside the wallet details screen;

## List of changes proposed in this pull request
- Added a new action `walletDetailsPagoPaCapabilityToggle`
- Adapted the `WalletDetailsPagoPaPaymentCapability` component;
- Added a new saga `handleTogglePagoPaCapability` that manages the
toggle of the services that have `PAGOPA` as the service name, updating
the status to `DISABLED` or `ENABLED`
- 
## How to test
- Checkout this PR from io-dev-api-server:
pagopa/io-dev-api-server#331
- Complete the new wallet onboarding process from the `New Wallet
Playground`
- After you add a payment method correctly, you should be able to see
the wallet details with a switch that can be toggled

## Preview


https://github.com/pagopa/io-app/assets/34343582/7884038e-97ab-444e-a02f-e8073e04f1e1
  • Loading branch information
Hantex9 authored Dec 12, 2023
1 parent 91bd4c5 commit 7a9f52b
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 100 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"idpay_api": "https://raw.githubusercontent.com/pagopa/cstar-infrastructure/v6.5.0/src/domains/idpay-app/api/idpay_appio_full/openapi.appio.full.yml",
"lollipop_api": "https://raw.githubusercontent.com/pagopa/io-backend/v13.23.0-RELEASE/api_lollipop_first_consumer.yaml",
"fast_login_api": "https://raw.githubusercontent.com/pagopa/io-backend/v13.23.0-RELEASE/openapi/generated/api_fast_login.yaml",
"pagopa_api_walletv3": "https://raw.githubusercontent.com/pagopa/pagopa-infra/5bd3f60aff52d2653111aa47a3975997295ae210/src/domains/wallet-app/api/payment-wallet/v1/_openapi.json.tpl",
"pagopa_api_walletv3": "https://raw.githubusercontent.com/pagopa/pagopa-infra/4cd111e94432ff62580adc391de78a5462a7128e/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": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,101 +1,56 @@
import * as React from "react";
import { View } from "react-native";
import {
ButtonLink,
ListItemSwitch,
VSpacer
} from "@pagopa/io-app-design-system";
import { constVoid } from "fp-ts/lib/function";
import { ListItemSwitch } from "@pagopa/io-app-design-system";

import { isPaymentSupported } from "../utils";
import { hasPaymentFeature } from "../utils";
import { WalletInfo } from "../../../../../definitions/pagopa/walletv3/WalletInfo";
import TouchableDefaultOpacity from "../../../../components/TouchableDefaultOpacity";
import Markdown from "../../../../components/ui/Markdown";
import I18n from "../../../../i18n";
import { acceptedPaymentMethodsFaqUrl } from "../../../../urls";
import { useIOBottomSheetAutoresizableModal } from "../../../../utils/hooks/bottomSheet";
import { openWebUrl } from "../../../../utils/url";
import { useIODispatch } from "../../../../store/hooks";
import { walletDetailsPagoPaCapabilityToggle } from "../../details/store/actions";
import { IOToast } from "../../../../components/Toast";

type Props = { paymentMethod: WalletInfo };

const getLocales = () => ({
available: I18n.t("wallet.methods.card.pagoPaCapability.active"),
arriving: I18n.t("wallet.methods.card.pagoPaCapability.arriving"),
notAvailable: I18n.t("wallet.methods.card.pagoPaCapability.incompatible"),
onboardableNotImplemented: I18n.t(
"wallet.methods.card.pagoPaCapability.incompatible"
)
});

/**
* Represent the capability to pay in PagoPa of a payment method.
*
* We have 4 possible different cases:
* - The card can pay on IO -> has capability pagoPa
* - The card will be able to pay in the future on IO -> BPay
* - The card is not able to pay on IO, (no pagoPa capability) and type === PRV or Bancomat
* - The card can onboard another card that can pay on IO -> co-badge credit card (no pagoPa capability) and type !== PRV
* @param props
*/
const WalletDetailsPagoPaPaymentCapability: React.FC<Props> = props => {
const onOpenLearnMoreAboutInAppPayments = () =>
openWebUrl(acceptedPaymentMethodsFaqUrl);
const paymentSupported = isPaymentSupported(props.paymentMethod);
const dispatch = useIODispatch();
const [loading, setLoading] = React.useState(false);

const { present, bottomSheet } = useIOBottomSheetAutoresizableModal(
{
component: (
<View>
<Markdown>
{I18n.t("wallet.methods.card.pagoPaCapability.bottomSheetBody")}
</Markdown>
<VSpacer size={24} />
<ButtonLink
label={I18n.t(
"wallet.methods.card.pagoPaCapability.bottomSheetCTA"
)}
onPress={onOpenLearnMoreAboutInAppPayments}
/>
</View>
),
title: I18n.t("wallet.methods.card.pagoPaCapability.bottomSheetTitle")
},
80
);
const handleSwitchSuccess = () => {
setLoading(false);
IOToast.success(
I18n.t("wallet.methods.card.pagoPaCapability.operationCompleted")
);
};

const handleSwitchError = () => {
setLoading(false);
IOToast.error(
I18n.t("wallet.methods.card.pagoPaCapability.operationError")
);
};

const handleSwitchPagoPaCapability = () => {
setLoading(true);
dispatch(
walletDetailsPagoPaCapabilityToggle.request({
walletId: props.paymentMethod.walletId,
onSuccess: handleSwitchSuccess,
onFailure: handleSwitchError
})
);
};

return (
<>
{bottomSheet}
<TouchableDefaultOpacity
onPress={paymentSupported === "available" ? undefined : present}
>
{paymentSupported === "available" && (
<ListItemSwitch
label={I18n.t("wallet.methods.card.pagoPaCapability.title")}
description={I18n.t(
"wallet.methods.card.pagoPaCapability.description"
)}
value={true}
// TODO: Handling the possibility to enable/disable to pay with this payment method in app switching the toggle (PATCH request) - https://pagopa.atlassian.net/browse/IOBP-405
onSwitchValueChange={() => constVoid}
/>
)}
{paymentSupported !== "available" && (
<ListItemSwitch
label={I18n.t("wallet.methods.card.pagoPaCapability.title")}
description={I18n.t(
"wallet.methods.card.pagoPaCapability.description"
)}
badge={{
text: getLocales()[paymentSupported],
variant: "blue",
outline: true,
testID: "paymentMethodStatusBadge"
}}
/>
)}
</TouchableDefaultOpacity>
</>
<ListItemSwitch
label={I18n.t("wallet.methods.card.pagoPaCapability.title")}
description={I18n.t("wallet.methods.card.pagoPaCapability.description")}
value={hasPaymentFeature(props.paymentMethod)}
onSwitchValueChange={handleSwitchPagoPaCapability}
isLoading={loading}
/>
);
};

Expand Down
9 changes: 7 additions & 2 deletions ts/features/walletV3/common/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import { ListItemTransactionStatusWithBadge } from "@pagopa/io-app-design-system
import I18n from "i18n-js";
import { pipe } from "fp-ts/lib/function";

import { WalletInfo } from "../../../../../definitions/pagopa/walletv3/WalletInfo";
import { isExpiredDate } from "../../../../utils/dates";
import { ServiceNameEnum } from "../../../../../definitions/pagopa/walletv3/ServiceName";
import { PaymentSupportStatus } from "../../../../types/paymentMethodCapabilities";
import {
TypeEnum,
WalletInfoDetails1
} from "../../../../../definitions/pagopa/walletv3/WalletInfoDetails";
import { ServiceStatusEnum } from "../../../../../definitions/pagopa/walletv3/ServiceStatus";
import { WalletInfo } from "../../../../../definitions/pagopa/walletv3/WalletInfo";

/**
* A simple function to get the corresponding translated badge text,
Expand Down Expand Up @@ -61,7 +62,11 @@ export const hasServiceEnabled = (
walletService: ServiceNameEnum
): boolean =>
paymentMethod !== undefined &&
paymentMethod.services.some(service => service.name === walletService);
paymentMethod.services.some(
service =>
service.name === walletService &&
service.status === ServiceStatusEnum.ENABLED
);
/**
* return true if the payment method has the payment feature
*/
Expand Down
93 changes: 93 additions & 0 deletions ts/features/walletV3/details/saga/handleTogglePagoPaCapability.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { call, put, select } from "typed-redux-saga/macro";
import * as E from "fp-ts/lib/Either";
import { ActionType } from "typesafe-actions";
import { SagaCallReturnType } from "../../../../types/utils";
import {
walletDetailsGetInstrument,
walletDetailsPagoPaCapabilityToggle
} from "../store/actions";
import { readablePrivacyReport } from "../../../../utils/reporters";
import { getGenericError, getNetworkError } from "../../../../utils/errors";
import { WalletClient } from "../../common/api/client";
import { withRefreshApiCall } from "../../../fastLogin/saga/utils";
import { walletDetailsInstrumentSelector } from "../store";
import { ServiceNameEnum } from "../../../../../definitions/pagopa/walletv3/ServiceName";
import { Service } from "../../../../../definitions/pagopa/walletv3/Service";
import { ServiceStatusEnum } from "../../../../../definitions/pagopa/walletv3/ServiceStatus";
import { WalletService } from "../../../../../definitions/pagopa/walletv3/WalletService";

/**
* Handle the remote call to toggle the Wallet pagopa capability
*/
export function* handleTogglePagoPaCapability(
updateWalletServicesById: WalletClient["updateWalletServicesById"],
action: ActionType<(typeof walletDetailsPagoPaCapabilityToggle)["request"]>
) {
try {
const walletDetails = yield* select(walletDetailsInstrumentSelector);
if (!walletDetails) {
throw new Error("walletDetails is undefined");
}
const updatedServices = walletDetails.services.map(service => ({
...service,
status: updatePagoPaServiceStatus(service)
}));

const updateWalletPagoPaServicesRequest = updateWalletServicesById({
walletId: action.payload.walletId,
body: {
services: updatedServices as Array<WalletService>
}
});
const updateWalletResult = (yield* call(
withRefreshApiCall,
updateWalletPagoPaServicesRequest,
action
)) as unknown as SagaCallReturnType<typeof updateWalletServicesById>;
if (E.isRight(updateWalletResult)) {
if (updateWalletResult.right.status === 204) {
// handled success
const successAction = walletDetailsPagoPaCapabilityToggle.success();
yield* put(successAction);
if (action.payload.onSuccess) {
action.payload.onSuccess(successAction);
}
return;
}
// not handled error codes
const failureAction = walletDetailsPagoPaCapabilityToggle.failure({
...getGenericError(
new Error(`response status code ${updateWalletResult.right.status}`)
)
});
yield* put(failureAction);
if (action.payload.onFailure) {
action.payload.onFailure(failureAction);
}
} else {
// cannot decode response
const failureAction = walletDetailsPagoPaCapabilityToggle.failure({
...getGenericError(
new Error(readablePrivacyReport(updateWalletResult.left))
)
});
yield* put(failureAction);
if (action.payload.onFailure) {
action.payload.onFailure(failureAction);
}
}
} catch (e) {
yield* put(walletDetailsGetInstrument.failure({ ...getNetworkError(e) }));
}
}

const updatePagoPaServiceStatus = (
service: Service
): ServiceStatusEnum | undefined => {
if (service.name === ServiceNameEnum.PAGOPA) {
return service.status === ServiceStatusEnum.DISABLED
? ServiceStatusEnum.ENABLED
: ServiceStatusEnum.DISABLED;
}
return service.status;
};
11 changes: 10 additions & 1 deletion ts/features/walletV3/details/saga/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import { takeLatest } from "typed-redux-saga/macro";
import { WalletClient } from "../../common/api/client";
import {
walletDetailsDeleteInstrument,
walletDetailsGetInstrument
walletDetailsGetInstrument,
walletDetailsPagoPaCapabilityToggle
} from "../store/actions";
import { handleGetWalletDetails } from "./handleGetWalletDetails";
import { handleDeleteWalletDetails } from "./handleDeleteWalletDetails";
import { handleTogglePagoPaCapability } from "./handleTogglePagoPaCapability";

/**
* Handle Wallet onboarding requests
Expand All @@ -29,4 +31,11 @@ export function* watchWalletDetailsSaga(
handleDeleteWalletDetails,
walletClient.deleteWalletById
);

// handle request to a wallet
yield* takeLatest(
walletDetailsPagoPaCapabilityToggle.request,
handleTogglePagoPaCapability,
walletClient.updateWalletServicesById
);
}
19 changes: 10 additions & 9 deletions ts/features/walletV3/details/screens/WalletDetailsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,17 @@ export type WalletDetailsScreenRouteProps = RouteProp<

const generateCardComponent = (walletDetails: WalletInfoDetails) => {
switch (walletDetails.type) {
case TypeEnum.PAYPAL:
const paypalDetails = walletDetails as WalletInfoDetails2;
return (
<PaymentCardBig
testID="CreditCardComponent"
cardType="PAYPAL"
holderEmail={paypalDetails.maskedEmail}
/>
);
case TypeEnum.CARDS:
default:
const cardDetails = walletDetails as WalletInfoDetails1;
return (
<PaymentCardBig
Expand All @@ -50,15 +60,6 @@ const generateCardComponent = (walletDetails: WalletInfoDetails) => {
cardIcon={cardDetails.brand.toLowerCase() as IOLogoPaymentExtType}
/>
);
case TypeEnum.PAYPAL:
const paypalDetails = walletDetails as WalletInfoDetails2;
return (
<PaymentCardBig
testID="CreditCardComponent"
cardType="PAYPAL"
holderEmail={paypalDetails.maskedEmail}
/>
);
}
};

Expand Down
20 changes: 19 additions & 1 deletion ts/features/walletV3/details/store/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,24 @@ export const walletDetailsDeleteInstrument = createAsyncAction(
"WALLET_DETAILS_DELETE_INSTRUMENT_CANCEL"
)<WalletDetailsDeleteinstrumentPayload, void, NetworkError, void>();

export type WalletDetailsPagoPaCapabilityTogglePayload = {
walletId: string;
onSuccess?: (
action: ActionType<typeof walletDetailsPagoPaCapabilityToggle.success>
) => void;
onFailure?: (
action: ActionType<typeof walletDetailsPagoPaCapabilityToggle.failure>
) => void;
};

export const walletDetailsPagoPaCapabilityToggle = createAsyncAction(
"WALLET_DETAILS_PAGOPA_CAPABILITY_TOGGLE_REQUEST",
"WALLET_DETAILS_PAGOPA_CAPABILITY_TOGGLE_SUCCESS",
"WALLET_DETAILS_PAGOPA_CAPABILITY_TOGGLE_FAILURE",
"WALLET_DETAILS_PAGOPA_CAPABILITY_TOGGLE_CANCEL"
)<WalletDetailsPagoPaCapabilityTogglePayload, void, NetworkError, void>();

export type WalletDetailsActions =
| ActionType<typeof walletDetailsGetInstrument>
| ActionType<typeof walletDetailsDeleteInstrument>;
| ActionType<typeof walletDetailsDeleteInstrument>
| ActionType<typeof walletDetailsPagoPaCapabilityToggle>;
Loading

0 comments on commit 7a9f52b

Please sign in to comment.