Skip to content

Commit

Permalink
chore: [IOBP-971] Outcome screen for error 404 from server for pspList (
Browse files Browse the repository at this point in the history
#6393)

## Short description
The key changes involve adding support for a new payment error type,
`PAYMENT_METHOD_NOT_AVAILABLE_ERROR` to display an outcome when the api
call for the list of psp is 404

## List of changes proposed in this pull request
- Updated `getPaymentAnalyticsEventFromRequestFailure` to handle
`PAYMENT_METHOD_NOT_AVAILABLE_ERROR`
- Added `PaymentMethodNotAvailableProblemJson` type and integrated it
into the `WalletPaymentFailure` union type
- Updated `paymentsCalculatePaymentFeesAction` to include a cancel
action and handle `notFound` errors
- Modified the reducer to handle the cancel action and map `notFound`
errors to `PAYMENT_METHOD_NOT_AVAILABLE_ERROR`
- Send analytics to mixpanel 

## How to test
With the dev server mock a 404 response for the `calculateFees` function

- Start a payment 
- Select a payment method and continue
- Check if outcome result is correct. Tap on the button and check if it
goes back to payment method selection

## Preview

https://github.com/user-attachments/assets/eabcdabc-8345-4b86-8602-21c21036e2a8
  • Loading branch information
LeleDallas authored Nov 13, 2024
1 parent 48d8d35 commit 0709a97
Show file tree
Hide file tree
Showing 12 changed files with 201 additions and 33 deletions.
4 changes: 4 additions & 0 deletions locales/de/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1814,6 +1814,10 @@ wallet:
PAYMENT_UNKNOWN:
title: "Wir können die Zahlungsmitteilung nicht finden"
subtitle: "Die Zahlungsmitteilung kann bereits bezahlt worden sein. Wende dich bitte an den ausstellende Körperschaft."
PSP_PAYMENT_METHOD_NOT_AVAILABLE_ERROR:
title: "Die ausgewählte Zahlungsmethode ist nicht verfügbar"
subtitle: "Dies kann passieren, wenn der zu zahlende Betrag besonders hoch ist oder wenn du versuchst, eine digitale Steuer- oder Gebührmarke zu bezahlen."
action: "Wähle eine andere Zahlungsmethode"
outcome:
SUCCESS:
title: "Du hast {{amount}} bezahlt"
Expand Down
4 changes: 4 additions & 0 deletions locales/en/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1906,6 +1906,10 @@ wallet:
PAYMENT_UNKNOWN:
title: "Non riusciamo a trovare l’avviso"
subtitle: "L’avviso potrebbe essere stato già pagato. Per ricevere assistenza, contatta l’Ente Creditore che lo ha emesso."
PSP_PAYMENT_METHOD_NOT_AVAILABLE_ERROR:
title: "The selected payment method is not available"
subtitle: "This can happen when the amount to be paid is particularly high, or if you are trying to pay a digital stamp duty."
action: "Select another payment method"
outcome:
SUCCESS:
title: Hai pagato {{amount}}
Expand Down
5 changes: 5 additions & 0 deletions locales/it/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1423,6 +1423,7 @@ wallet:
title: Non abbiamo trovato alcun BANCOMAT Pay attivo
subtitle: "Contatta la tua Banca per attivare il servizio."
primaryAction: Chiudi

alert:
supportedCardPageLinkError: Si è verificato un errore durante l'apertura della pagina di riferimento per le carte supportate.
msgErrorUpdateApp: "Si è verificato un errore durante l'apertura dello store delle app"
Expand Down Expand Up @@ -1906,6 +1907,10 @@ wallet:
PAYMENT_UNKNOWN:
title: "Non riusciamo a trovare l’avviso"
subtitle: "L’avviso potrebbe essere stato già pagato. Per ricevere assistenza, contatta l’Ente Creditore che lo ha emesso."
PSP_PAYMENT_METHOD_NOT_AVAILABLE_ERROR:
title: "Il metodo di pagamento selezionato non è disponibile"
subtitle: "Può succedere quando l’importo da pagare è particolarmente elevato, o se stai cercando di pagare una marca da bollo digitale."
action: "Scegli un altro metodo"
outcome:
SUCCESS:
title: Hai pagato {{amount}}
Expand Down
18 changes: 18 additions & 0 deletions ts/features/payments/checkout/analytics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,3 +353,21 @@ export const trackPaymentStartFlow = (
})
);
};

export const trackPaymentsPspNotAvailableError = (
props: Partial<PaymentAnalyticsProps>
) => {
void mixpanelTrack(
"PAYMENT_PSP_NOT_AVAILABLE_ERROR",
buildEventProperties("KO", "screen_view", props)
);
};

export const trackPaymentsPspNotAvailableSelectNew = (
props: Partial<PaymentAnalyticsProps>
) => {
void mixpanelTrack(
"PAYMENT_PSP_NOT_AVAILABLE_SELECT_NEW",
buildEventProperties("UX", "action", props)
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@ import {
AppParamsList,
IOStackNavigationProp
} from "../../../../navigation/params/AppParamsList";
import { usePaymentFailureSupportModal } from "../hooks/usePaymentFailureSupportModal";
import { WalletPaymentFailure } from "../types/WalletPaymentFailure";
import { useOnFirstRender } from "../../../../utils/hooks/useOnFirstRender";
import { paymentCompletedSuccess } from "../store/actions/orchestration";
import { useIODispatch, useIOSelector } from "../../../../store/hooks";
import { useOnFirstRender } from "../../../../utils/hooks/useOnFirstRender";
import {
selectOngoingPaymentHistory,
paymentAnalyticsDataSelector
paymentAnalyticsDataSelector,
selectOngoingPaymentHistory
} from "../../history/store/selectors";
import * as analytics from "../analytics";
import { usePaymentFailureSupportModal } from "../hooks/usePaymentFailureSupportModal";
import { PaymentsCheckoutRoutes } from "../navigation/routes";
import { paymentsCalculatePaymentFeesAction } from "../store/actions/networking";
import { paymentCompletedSuccess } from "../store/actions/orchestration";
import { selectWalletPaymentCurrentStep } from "../store/selectors";
import { WalletPaymentFailure } from "../types/WalletPaymentFailure";
import { getPaymentPhaseFromStep } from "../utils";

type Props = {
Expand Down Expand Up @@ -51,6 +53,28 @@ const WalletPaymentFailureDetail = ({ failure }: Props) => {
supportModal.present();
};

const handleChangePaymentMethod = () => {
analytics.trackPaymentsPspNotAvailableSelectNew({
amount: paymentAnalyticsData?.formattedAmount,
expiration_date: paymentAnalyticsData?.verifiedData?.dueDate,
organization_name: paymentAnalyticsData?.verifiedData?.paName,
organization_fiscal_code:
paymentAnalyticsData?.verifiedData?.paFiscalCode,
saved_payment_method:
paymentAnalyticsData?.savedPaymentMethods?.length || 0,
service_name: paymentAnalyticsData?.serviceName,
data_entry: paymentAnalyticsData?.startOrigin,
attempt: paymentAnalyticsData?.attempt,
payment_method_selected: paymentAnalyticsData?.selectedPaymentMethod,
selected_psp_flag: paymentAnalyticsData?.selectedPspFlag
});

dispatch(paymentsCalculatePaymentFeesAction.cancel());
navigation.replace(PaymentsCheckoutRoutes.PAYMENT_CHECKOUT_NAVIGATOR, {
screen: PaymentsCheckoutRoutes.PAYMENT_CHECKOUT_MAKE
});
};

const closeAction: OperationResultScreenContentProps["action"] = {
label: I18n.t("global.buttons.close"),
testID: "wallet-payment-failure-close-button",
Expand All @@ -73,6 +97,18 @@ const WalletPaymentFailureDetail = ({ failure }: Props) => {
secondaryAction: contactSupportAction
};

const selectOtherPaymentMethodAction: OperationResultScreenContentProps["action"] =
{
label: I18n.t(
"wallet.payment.failure.PSP_PAYMENT_METHOD_NOT_AVAILABLE_ERROR.action"
),
testID: "wallet-payment-failure-go-back-button",
accessibilityLabel: I18n.t(
"wallet.payment.failure.PSP_PAYMENT_METHOD_NOT_AVAILABLE_ERROR.action"
),
onPress: handleChangePaymentMethod
};

const getPropsFromFailure = ({
faultCodeCategory
}: WalletPaymentFailure): OperationResultScreenContentProps => {
Expand Down Expand Up @@ -147,6 +183,18 @@ const WalletPaymentFailureDetail = ({ failure }: Props) => {
action: closeAction,
secondaryAction: contactSupportAction
};
case "PSP_PAYMENT_METHOD_NOT_AVAILABLE_ERROR":
return {
pictogram: "cardIssue",
title: I18n.t(
"wallet.payment.failure.PSP_PAYMENT_METHOD_NOT_AVAILABLE_ERROR.title"
),
subtitle: I18n.t(
"wallet.payment.failure.PSP_PAYMENT_METHOD_NOT_AVAILABLE_ERROR.subtitle"
),
action: selectOtherPaymentMethodAction
};

default:
return genericErrorProps;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { readablePrivacyReport } from "../../../../../utils/reporters";
import { PaymentClient } from "../../../common/api/client";
import { paymentsCalculatePaymentFeesAction } from "../../store/actions/networking";
import { withPaymentsSessionToken } from "../../../common/utils/withPaymentsSessionToken";
import * as analytics from "../../analytics";
import { paymentAnalyticsDataSelector } from "../../../history/store/selectors";

export function* handleWalletPaymentCalculateFees(
calculateFees: PaymentClient["calculateFeesForIO"],
Expand Down Expand Up @@ -61,11 +63,29 @@ export function* handleWalletPaymentCalculateFees(
return;
} else if (res.status !== 401) {
// The 401 status is handled by the withPaymentsSessionToken
yield* put(
paymentsCalculatePaymentFeesAction.failure(
getGenericError(new Error(`Error: ${res.status}`))
)
);
if (res.status === 404) {
const paymentAnalyticsData = yield* select(
paymentAnalyticsDataSelector
);
analytics.trackPaymentsPspNotAvailableError({
organization_name: paymentAnalyticsData?.verifiedData?.paName,
organization_fiscal_code:
paymentAnalyticsData?.verifiedData?.paFiscalCode,
expiration_date: paymentAnalyticsData?.verifiedData?.dueDate,
attempt: paymentAnalyticsData?.attempt
});
yield* put(
paymentsCalculatePaymentFeesAction.failure({
kind: "notFound"
})
);
} else {
yield* put(
paymentsCalculatePaymentFeesAction.failure(
getGenericError(new Error(`Error: ${res.status}`))
)
);
}
}
}
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ import { sequenceT } from "fp-ts/lib/Apply";
import * as O from "fp-ts/lib/Option";
import { pipe } from "fp-ts/lib/function";
import React from "react";
import _ from "lodash";
import { IOScrollView } from "../../../../components/ui/IOScrollView";
import I18n from "../../../../i18n";
import { useIONavigation } from "../../../../navigation/params/AppParamsList";
import { useIODispatch, useIOSelector } from "../../../../store/hooks";
import { useOnFirstRender } from "../../../../utils/hooks/useOnFirstRender";
import { PaymentAnalyticsSelectedMethodFlag } from "../../common/types/PaymentAnalytics";
import { UIWalletInfoDetails } from "../../common/types/UIWalletInfoDetails";
import { paymentAnalyticsDataSelector } from "../../history/store/selectors";
import * as analytics from "../analytics";
import {
CheckoutPaymentMethodsList,
CheckoutPaymentMethodsListSkeleton
Expand Down Expand Up @@ -37,12 +42,6 @@ import {
walletPaymentTransactionSelector
} from "../store/selectors/transaction";
import { WalletPaymentOutcomeEnum } from "../types/PaymentOutcomeEnum";
import { UIWalletInfoDetails } from "../../common/types/UIWalletInfoDetails";
import * as analytics from "../analytics";
import { useOnFirstRender } from "../../../../utils/hooks/useOnFirstRender";
import { paymentAnalyticsDataSelector } from "../../history/store/selectors";
import { PaymentAnalyticsSelectedMethodFlag } from "../../common/types/PaymentAnalytics";
import { IOScrollView } from "../../../../components/ui/IOScrollView";

const WalletPaymentPickMethodScreen = () => {
const dispatch = useIODispatch();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,24 @@ import { useIONavigation } from "../../../../navigation/params/AppParamsList";
import { useIODispatch, useIOSelector } from "../../../../store/hooks";
import { formatNumberCentsToAmount } from "../../../../utils/stringBuilder";
import { getSortedPspList } from "../../common/utils";
import { paymentAnalyticsDataSelector } from "../../history/store/selectors";
import * as analytics from "../analytics";
import { WalletPspListSkeleton } from "../components/WalletPspListSkeleton";
import { useSortPspBottomSheet } from "../hooks/useSortPspBottomSheet";
import { PaymentsCheckoutRoutes } from "../navigation/routes";
import {
selectPaymentPspAction,
walletPaymentSetCurrentStep
} from "../store/actions/orchestration";
import { selectWalletPaymentCurrentStep } from "../store/selectors";
import {
walletPaymentPspListSelector,
walletPaymentSelectedPspSelector
} from "../store/selectors/psps";
import { WalletPaymentPspSortType, WalletPaymentStepEnum } from "../types";
import { FaultCodeCategoryEnum } from "../types/PspPaymentMethodNotAvailableProblemJson";
import { WalletPaymentOutcomeEnum } from "../types/PaymentOutcomeEnum";
import * as analytics from "../analytics";
import { paymentAnalyticsDataSelector } from "../../history/store/selectors";
import { selectWalletPaymentCurrentStep } from "../store/selectors";
import { WalletPaymentFailure } from "../types/WalletPaymentFailure";

const WalletPaymentPickPspScreen = () => {
const dispatch = useIODispatch();
Expand Down Expand Up @@ -73,14 +75,26 @@ const WalletPaymentPickPspScreen = () => {

React.useEffect(() => {
if (isError) {
navigation.replace(PaymentsCheckoutRoutes.PAYMENT_CHECKOUT_NAVIGATOR, {
screen: PaymentsCheckoutRoutes.PAYMENT_CHECKOUT_OUTCOME,
params: {
outcome: WalletPaymentOutcomeEnum.GENERIC_ERROR
}
});
if (
(pspListPot.error as WalletPaymentFailure)?.faultCodeCategory ===
FaultCodeCategoryEnum.PSP_PAYMENT_METHOD_NOT_AVAILABLE_ERROR
) {
navigation.navigate(PaymentsCheckoutRoutes.PAYMENT_CHECKOUT_NAVIGATOR, {
screen: PaymentsCheckoutRoutes.PAYMENT_CHECKOUT_FAILURE,
params: {
error: pspListPot.error
}
});
} else {
navigation.replace(PaymentsCheckoutRoutes.PAYMENT_CHECKOUT_NAVIGATOR, {
screen: PaymentsCheckoutRoutes.PAYMENT_CHECKOUT_OUTCOME,
params: {
outcome: WalletPaymentOutcomeEnum.GENERIC_ERROR
}
});
}
}
}, [isError, navigation]);
}, [isError, navigation, pspListPot]);

useFocusEffect(
React.useCallback(() => {
Expand Down
8 changes: 6 additions & 2 deletions ts/features/payments/checkout/store/actions/networking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { WalletPaymentFailure } from "../../types/WalletPaymentFailure";
import { WalletInfo } from "../../../../../../definitions/pagopa/ecommerce/WalletInfo";
import { UserLastPaymentMethodResponse } from "../../../../../../definitions/pagopa/ecommerce/UserLastPaymentMethodResponse";

type NotFound = { kind: "notFound" };

export const paymentsGetPaymentDetailsAction = createAsyncAction(
"PAYMENTS_GET_PAYMENT_DETAILS_REQUEST",
"PAYMENTS_GET_PAYMENT_DETAILS_SUCCESS",
Expand Down Expand Up @@ -56,11 +58,13 @@ export type CalculateFeePayload = {
export const paymentsCalculatePaymentFeesAction = createAsyncAction(
"PAYMENTS_CALCULATE_PAYMENT_FEES_REQUEST",
"PAYMENTS_CALCULATE_PAYMENT_FEES_SUCCESS",
"PAYMENTS_CALCULATE_PAYMENT_FEES_FAILURE"
"PAYMENTS_CALCULATE_PAYMENT_FEES_FAILURE",
"PAYMENTS_CALCULATE_PAYMENT_FEES_CANCEL"
)<
CalculateFeeRequest & CalculateFeePayload,
CalculateFeeResponse,
NetworkError
NetworkError | NotFound,
undefined
>();

export type WalletPaymentCreateTransactionPayload = {
Expand Down
21 changes: 19 additions & 2 deletions ts/features/payments/checkout/store/reducers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { NetworkError } from "../../../../../utils/errors";
import { getSortedPspList } from "../../../common/utils";
import { WalletPaymentStepEnum } from "../../types";
import { FaultCodeCategoryEnum } from "../../types/PaymentGenericErrorAfterUserCancellationProblemJson";
import { FaultCodeCategoryEnum as PaymentMethodNotAvailableEnum } from "../../types/PspPaymentMethodNotAvailableProblemJson";
import { WalletPaymentFailure } from "../../types/WalletPaymentFailure";
import {
paymentsCalculatePaymentFeesAction,
Expand Down Expand Up @@ -48,7 +49,7 @@ export type PaymentsCheckoutState = {
userWallets: pot.Pot<Wallets, NetworkError>;
recentUsedPaymentMethod: pot.Pot<UserLastPaymentMethodResponse, NetworkError>;
allPaymentMethods: pot.Pot<PaymentMethodsResponse, NetworkError>;
pspList: pot.Pot<ReadonlyArray<Bundle>, NetworkError>;
pspList: pot.Pot<ReadonlyArray<Bundle>, NetworkError | WalletPaymentFailure>;
selectedWallet: O.Option<WalletInfo>;
selectedPaymentMethod: O.Option<PaymentMethodResponse>;
selectedPsp: O.Option<Bundle>;
Expand Down Expand Up @@ -210,10 +211,26 @@ const reducer = (
currentStep,
selectedPsp
};
case getType(paymentsCalculatePaymentFeesAction.cancel):
return {
...state,
pspList: pot.none,
selectedPaymentMethod: O.none,
currentStep: WalletPaymentStepEnum.PICK_PAYMENT_METHOD
};
case getType(paymentsCalculatePaymentFeesAction.failure):
return {
...state,
pspList: pot.toError(state.pspList, action.payload)
pspList: pot.toError(
state.pspList,
action.payload.kind === "notFound"
? {
faultCodeCategory:
PaymentMethodNotAvailableEnum.PSP_PAYMENT_METHOD_NOT_AVAILABLE_ERROR,
faultCodeDetail: ""
}
: action.payload
)
};

case getType(selectPaymentPspAction):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as t from "io-ts";
import { enumType } from "@pagopa/ts-commons/lib/types";

export enum FaultCodeCategoryEnum {
"PSP_PAYMENT_METHOD_NOT_AVAILABLE_ERROR" = "PSP_PAYMENT_METHOD_NOT_AVAILABLE_ERROR"
}

// required attributes
const PspPaymentMethodNotAvailableProblemJsonR = t.type({
faultCodeCategory: enumType<FaultCodeCategoryEnum>(
FaultCodeCategoryEnum,
"faultCodeCategory"
),

faultCodeDetail: t.string
});

// optional attributes
const PspPaymentMethodNotAvailableProblemJsonO = t.partial({
title: t.string
});

export const PspPaymentMethodNotAvailableProblemJson = t.intersection(
[
PspPaymentMethodNotAvailableProblemJsonR,
PspPaymentMethodNotAvailableProblemJsonO
],
"PspPaymentMethodNotAvailableProblemJson"
);

export type PspPaymentMethodNotAvailableProblemJson = t.TypeOf<
typeof PspPaymentMethodNotAvailableProblemJson
>;
Loading

0 comments on commit 0709a97

Please sign in to comment.