From f60dc8488003baf5c53f22d4b6644885908ebe80 Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:12:23 +0100 Subject: [PATCH 01/14] feat: handleGetZendeskPaymentConfig --- .../handleGetZendeskPaymentConfig.ts | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 ts/features/zendesk/saga/networking/handleGetZendeskPaymentConfig.ts diff --git a/ts/features/zendesk/saga/networking/handleGetZendeskPaymentConfig.ts b/ts/features/zendesk/saga/networking/handleGetZendeskPaymentConfig.ts new file mode 100644 index 00000000000..bc31c9e27f0 --- /dev/null +++ b/ts/features/zendesk/saga/networking/handleGetZendeskPaymentConfig.ts @@ -0,0 +1,47 @@ +import { readableReport } from "@pagopa/ts-commons/lib/reporters"; +import * as E from "fp-ts/lib/Either"; +import { call, put } from "typed-redux-saga/macro"; +import { ContentClient } from "../../../../api/content"; +import { SagaCallReturnType } from "../../../../types/utils"; +import { getGenericError, getNetworkError } from "../../../../utils/errors"; +import { getZendeskPaymentConfig } from "../../store/actions"; + +// retrieve the zendesk config from the CDN +export function* handleGetZendeskPaymentConfig( + getZendeskPaymentConfigClient: ReturnType< + typeof ContentClient + >["getZendeskPaymentConfig"] +) { + try { + const getZendeskPaymentConfigResult: SagaCallReturnType< + typeof getZendeskPaymentConfigClient + > = yield* call(getZendeskPaymentConfigClient); + if (E.isRight(getZendeskPaymentConfigResult)) { + if (getZendeskPaymentConfigResult.right.status === 200) { + yield* put( + getZendeskPaymentConfig.success( + getZendeskPaymentConfigResult.right.value + ) + ); + } else { + yield* put( + getZendeskPaymentConfig.failure( + getGenericError( + Error( + `response status ${getZendeskPaymentConfigResult.right.status}` + ) + ) + ) + ); + } + } else { + getZendeskPaymentConfig.failure( + getGenericError( + Error(readableReport(getZendeskPaymentConfigResult.left)) + ) + ); + } + } catch (e) { + yield* put(getZendeskPaymentConfig.failure(getNetworkError(e))); + } +} From e5d167ad4e5397612f2f84b089b80ed878b74adb Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:13:32 +0100 Subject: [PATCH 02/14] chore: remove zendesk hardcoded data --- ts/utils/supportAssistance.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/ts/utils/supportAssistance.ts b/ts/utils/supportAssistance.ts index 85bdb50293f..fe323bbc5a8 100644 --- a/ts/utils/supportAssistance.ts +++ b/ts/utils/supportAssistance.ts @@ -115,14 +115,7 @@ export const zendeskidentityProviderId = "4414310934673"; export const zendeskCurrentAppVersionId = "4414316660369"; export const zendeskVersionsHistoryId = "4419641151505"; export const zendeskFciId = "14874226407825"; -export const zendeskPaymentCategory: ZendeskCategory = { - value: "pagamenti_pagopa", - description: { - "it-IT": "Pagamento pagoPA", - "en-EN": "pagoPA payment", - "de-DE": "pagoPA-Zahlung" - } -}; + export const zendeskPaymentMethodCategory: ZendeskCategory = { value: "metodo_di_pagamento", description: { From 41eb1a9e5018948b812e1232ed10519483208031 Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:52:54 +0100 Subject: [PATCH 03/14] feat: getZendeskPaymentConfig api call and store --- ts/api/content.ts | 17 +++++++++++- ts/features/zendesk/analytics/index.ts | 4 +++ ts/features/zendesk/saga/index.ts | 10 ++++++- .../screens/ZendeskSupportHelpCenter.tsx | 2 ++ ts/features/zendesk/store/actions/index.ts | 12 ++++++++- ts/features/zendesk/store/reducers/index.ts | 27 +++++++++++++++++-- 6 files changed, 67 insertions(+), 5 deletions(-) diff --git a/ts/api/content.ts b/ts/api/content.ts index 315c9d5c8f7..c627624ba32 100644 --- a/ts/api/content.ts +++ b/ts/api/content.ts @@ -8,6 +8,7 @@ import { createFetchRequestForApi, IGetApiRequestType } from "@pagopa/ts-commons/lib/requests"; +import { unknown } from "io-ts"; import { BonusesAvailable } from "../../definitions/content/BonusesAvailable"; import { ContextualHelp } from "../../definitions/content/ContextualHelp"; import { Municipality as MunicipalityMedadata } from "../../definitions/content/Municipality"; @@ -140,6 +141,16 @@ const getZendeskConfigT: GetZendeskConfigT = { headers: () => ({}), response_decoder: basicResponseDecoder(Zendesk) }; + +// TODO: update with new definition from related PR +const getZendeskPaymentConfig: any = { + method: "get", + url: () => "/assistanceTools/paymentMap.json", + query: (_: any) => ({}), + headers: () => ({}), + response_decoder: basicResponseDecoder(unknown) +}; + /** * A client for the static content */ @@ -157,6 +168,10 @@ export function ContentClient(fetchApi: typeof fetch = defaultRetryingFetch()) { getCobadgeServices: createFetchRequestForApi(getCobadgeServicesT, options), getVersionInfo: createFetchRequestForApi(getVersionInfoT, options), getIdps: createFetchRequestForApi(getIdpsT, options), - getZendeskConfig: createFetchRequestForApi(getZendeskConfigT, options) + getZendeskConfig: createFetchRequestForApi(getZendeskConfigT, options), + getZendeskPaymentConfig: createFetchRequestForApi( + getZendeskPaymentConfig, + options + ) }; } diff --git a/ts/features/zendesk/analytics/index.ts b/ts/features/zendesk/analytics/index.ts index 9acc4628f76..f91ae63282e 100644 --- a/ts/features/zendesk/analytics/index.ts +++ b/ts/features/zendesk/analytics/index.ts @@ -7,6 +7,7 @@ import { zendeskEnabled } from "../../../config"; import { getNetworkErrorMessage } from "../../../utils/errors"; import { getZendeskConfig, + getZendeskPaymentConfig, zendeskSelectedCategory, zendeskSupportCancel, zendeskSupportCompleted, @@ -22,6 +23,8 @@ const trackZendesk = case getType(zendeskSupportCancel): case getType(getZendeskConfig.request): case getType(getZendeskConfig.success): + case getType(getZendeskPaymentConfig.success): + case getType(getZendeskPaymentConfig.request): return mp.track(action.type); case getType(zendeskSupportStart): return mp.track(action.type, { @@ -38,6 +41,7 @@ const trackZendesk = category: action.payload.value }); case getType(getZendeskConfig.failure): + case getType(getZendeskPaymentConfig.failure): return mp.track(action.type, { reason: getNetworkErrorMessage(action.payload) }); diff --git a/ts/features/zendesk/saga/index.ts b/ts/features/zendesk/saga/index.ts index bd614ca57b9..b9ab66a01f3 100644 --- a/ts/features/zendesk/saga/index.ts +++ b/ts/features/zendesk/saga/index.ts @@ -11,7 +11,8 @@ import { zendeskStartPolling, zendeskSupportCompleted, zendeskSupportStart, - getZendeskToken + getZendeskToken, + getZendeskPaymentConfig } from "../store/actions"; import { ContentClient } from "../../../api/content"; import { dismissSupport } from "../../../utils/supportAssistance"; @@ -33,6 +34,7 @@ import { isDevEnv } from "./../../../utils/environment"; import { zendeskSupport } from "./orchestration"; import { handleGetZendeskConfig } from "./networking/handleGetZendeskConfig"; import { handleHasOpenedTickets } from "./networking/handleHasOpenedTickets"; +import { handleGetZendeskPaymentConfig } from "./networking/handleGetZendeskPaymentConfig"; const ZENDESK_GET_SESSION_POLLING_INTERVAL = ((isDevEnv ? 10 : 60) * 1000) as Millisecond; @@ -89,6 +91,12 @@ export function* watchZendeskSupportSaga() { contentClient.getZendeskConfig ); + yield* takeLatest( + getZendeskPaymentConfig.request, + handleGetZendeskPaymentConfig, + contentClient.getZendeskPaymentConfig + ); + yield* takeLatest(zendeskRequestTicketNumber.request, handleHasOpenedTickets); // close the Zendesk support UI when the identification is requested // this is due since there is a modal clash (iOS only) see https://pagopa.atlassian.net/browse/IABT-1348?filter=10121 diff --git a/ts/features/zendesk/screens/ZendeskSupportHelpCenter.tsx b/ts/features/zendesk/screens/ZendeskSupportHelpCenter.tsx index d2810658499..077487db6e3 100644 --- a/ts/features/zendesk/screens/ZendeskSupportHelpCenter.tsx +++ b/ts/features/zendesk/screens/ZendeskSupportHelpCenter.tsx @@ -63,6 +63,7 @@ import ZENDESK_ROUTES from "../navigation/routes"; import { ZendeskStartPayload, getZendeskConfig, + getZendeskPaymentConfig, getZendeskToken, zendeskSupportCancel } from "../store/actions"; @@ -330,6 +331,7 @@ const ZendeskSupportHelpCenter = () => { */ useEffect(() => { dispatch(getZendeskConfig.request()); + dispatch(getZendeskPaymentConfig.request()); }, [dispatch]); // add the signatureRequestId to the ticket custom fields diff --git a/ts/features/zendesk/store/actions/index.ts b/ts/features/zendesk/store/actions/index.ts index b818305565f..af79a5e28ef 100644 --- a/ts/features/zendesk/store/actions/index.ts +++ b/ts/features/zendesk/store/actions/index.ts @@ -95,6 +95,15 @@ export const getZendeskConfig = createAsyncAction( "ZENDESK_CONFIG_FAILURE" )(); +/** + * Request the zendesk payment config + */ +export const getZendeskPaymentConfig = createAsyncAction( + "ZENDESK_PAYMENT_CONFIG_REQUEST", + "ZENDESK_PAYMENT_CONFIG_SUCCESS", + "ZENDESK_PAYMENT_CONFIG_FAILURE" +)(); + // user selected a category export const zendeskSelectedCategory = createStandardAction( "ZENDESK_SELECTED_CATEGORY" @@ -127,4 +136,5 @@ export type ZendeskSupportActions = | ActionType | ActionType | ActionType - | ActionType; + | ActionType + | ActionType; diff --git a/ts/features/zendesk/store/reducers/index.ts b/ts/features/zendesk/store/reducers/index.ts index 2a2643aa600..9614c1e937e 100644 --- a/ts/features/zendesk/store/reducers/index.ts +++ b/ts/features/zendesk/store/reducers/index.ts @@ -20,7 +20,8 @@ import { zendeskStartPolling, zendeskStopPolling, zendeskSupportStart, - getZendeskToken + getZendeskToken, + getZendeskPaymentConfig } from "../actions"; import { GlobalState } from "../../../../store/reducers/types"; import { ZendeskSubCategory } from "../../../../../definitions/content/ZendeskSubCategory"; @@ -47,11 +48,13 @@ export type ZendeskState = { ticketNumber: pot.Pot; getSessionPollingRunning?: boolean; getZendeskTokenStatus?: ZendeskTokenStatusEnum | "401"; + zendeskMap: any; }; const INITIAL_STATE: ZendeskState = { zendeskConfig: remoteUndefined, - ticketNumber: pot.none + ticketNumber: pot.none, + zendeskMap: remoteUndefined }; const reducer = ( @@ -135,6 +138,21 @@ const reducer = ( ...state, ticketNumber: pot.toError(state.ticketNumber, action.payload) }; + case getType(getZendeskPaymentConfig.request): + return { + ...state, + zendeskMap: remoteLoading + }; + case getType(getZendeskPaymentConfig.success): + return { + ...state, + zendeskMap: remoteReady(action.payload) + }; + case getType(getZendeskPaymentConfig.failure): + return { + ...state, + zendeskMap: remoteError(action.payload) + }; } return state; }; @@ -168,4 +186,9 @@ export const zendeskTicketNumberSelector = createSelector( (ticketNumber: pot.Pot): pot.Pot => ticketNumber ); +export const zendeskMapSelector = createSelector( + [(state: GlobalState) => state.assistanceTools.zendesk.zendeskMap], + (zendeskMap: any): any => zendeskMap.value +); + export default reducer; From 40c77dc770ff1f5eebf09e3d5e88cd0def05d7e5 Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:56:09 +0100 Subject: [PATCH 04/14] refactor: create payment ticket based on fault code value --- .../hooks/usePaymentFailureSupportModal.tsx | 21 ++++++++++++++----- ts/features/payments/checkout/utils/index.ts | 20 ++++++++++++++++++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/ts/features/payments/checkout/hooks/usePaymentFailureSupportModal.tsx b/ts/features/payments/checkout/hooks/usePaymentFailureSupportModal.tsx index 8f68f5a1210..b5c27e6d8aa 100644 --- a/ts/features/payments/checkout/hooks/usePaymentFailureSupportModal.tsx +++ b/ts/features/payments/checkout/hooks/usePaymentFailureSupportModal.tsx @@ -11,7 +11,7 @@ import { import { OrganizationFiscalCode } from "@pagopa/ts-commons/lib/strings"; import * as O from "fp-ts/lib/Option"; import { pipe } from "fp-ts/lib/function"; -import React from "react"; +import React, { useEffect } from "react"; import { Linking } from "react-native"; import { ToolEnum } from "../../../../../definitions/content/AssistanceToolConfig"; import I18n from "../../../../i18n"; @@ -25,17 +25,18 @@ import { appendLog, assistanceToolRemoteConfig, resetCustomFields, - zendeskCategoryId, - zendeskPaymentCategory, zendeskPaymentFailure, zendeskPaymentNav, zendeskPaymentOrgFiscalCode, zendeskPaymentStartOrigin } from "../../../../utils/supportAssistance"; import { + getZendeskPaymentConfig, zendeskSelectedCategory, zendeskSupportStart } from "../../../zendesk/store/actions"; +import { zendeskMapSelector } from "../../../zendesk/store/reducers"; +import { formatPaymentNoticeNumber } from "../../common/utils"; import { selectOngoingPaymentHistory } from "../../history/store/selectors"; import { walletPaymentRptIdSelector } from "../store/selectors"; import { @@ -43,7 +44,7 @@ import { getWalletPaymentOutcomeEnumByValue } from "../types/PaymentOutcomeEnum"; import { WalletPaymentFailure } from "../types/WalletPaymentFailure"; -import { formatPaymentNoticeNumber } from "../../common/utils"; +import { getSubCategoryFromFaultCode } from "../utils"; type PaymentFailureSupportModalParams = { failure?: WalletPaymentFailure; @@ -65,16 +66,26 @@ const usePaymentFailureSupportModal = ({ const choosenTool = assistanceToolRemoteConfig(assistanceToolConfig); const rptId = useIOSelector(walletPaymentRptIdSelector); const paymentHistory = useIOSelector(selectOngoingPaymentHistory); + const zendeskPaymentCategory = useIOSelector(zendeskMapSelector); + const dispatch = useIODispatch(); + useEffect(() => { + dispatch(getZendeskPaymentConfig.request()); + }, [dispatch]); + const faultCodeDetail = failure?.faultCodeDetail || (outcome && getWalletPaymentOutcomeEnumByValue(outcome)) || ""; const zendeskAssistanceLogAndStart = () => { + const { value, zendeskCategoryId } = getSubCategoryFromFaultCode( + zendeskPaymentCategory, + faultCodeDetail + ); resetCustomFields(); - addTicketCustomField(zendeskCategoryId, zendeskPaymentCategory.value); + addTicketCustomField(zendeskCategoryId, value); addTicketCustomField(zendeskPaymentOrgFiscalCode, organizationFiscalCode); addTicketCustomField(zendeskPaymentNav, paymentNoticeNumber); addTicketCustomField(zendeskPaymentFailure, faultCodeDetail); diff --git a/ts/features/payments/checkout/utils/index.ts b/ts/features/payments/checkout/utils/index.ts index d468a23f6af..0995a9fae74 100644 --- a/ts/features/payments/checkout/utils/index.ts +++ b/ts/features/payments/checkout/utils/index.ts @@ -9,6 +9,7 @@ import { PaymentAnalyticsSelectedPspFlag } from "../../common/types/PaymentAnalytics"; import { WalletPaymentStepEnum } from "../types"; +import { zendeskCategoryId } from "../../../../utils/supportAssistance"; export const WALLET_PAYMENT_SHOW_OTHER_CHANNELS_URL = "https://www.pagopa.gov.it/it/cittadini/dove-pagare/"; @@ -78,3 +79,22 @@ export const isDueDateValid = (date: string): string | undefined => { tenYearsFromNow.setFullYear(tenYearsFromNow.getFullYear() + YEARS_TO_EXPIRE); return new Date(date) > tenYearsFromNow ? undefined : formattedDate; }; + +export const getSubCategoryFromFaultCode = (data: any, statusCode: string) => { + // check if there is a subcategory array that includes passed element + const subcategoryKey = Object.keys(data.subcategories).find(key => + data.subcategories[key].includes(statusCode) + ); + // if there is, return the mapped subcategory with the zendesk category id + if (subcategoryKey) { + return { + value: subcategoryKey, + zendeskCategoryId: data.subcategoryId + }; + } + // if not, return the parent category + return { + value: "pagamenti_pagopa", + zendeskCategoryId + }; +}; From 322af1ca0245a587cb88cd77a66034c4b27b149e Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Mon, 9 Dec 2024 12:01:33 +0100 Subject: [PATCH 05/14] refactor: update mock type --- ts/api/content.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ts/api/content.ts b/ts/api/content.ts index c627624ba32..ac5a6415d2e 100644 --- a/ts/api/content.ts +++ b/ts/api/content.ts @@ -142,11 +142,18 @@ const getZendeskConfigT: GetZendeskConfigT = { response_decoder: basicResponseDecoder(Zendesk) }; +type GetZendeskPaymentConfigT = IGetApiRequestType< + void, + never, + never, + BasicResponseType +>; + // TODO: update with new definition from related PR -const getZendeskPaymentConfig: any = { +const getZendeskPaymentConfig: GetZendeskPaymentConfigT = { method: "get", url: () => "/assistanceTools/paymentMap.json", - query: (_: any) => ({}), + query: _ => ({}), headers: () => ({}), response_decoder: basicResponseDecoder(unknown) }; From 0cfa31e52c22266b2341648ba5748ac373cd0532 Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Mon, 9 Dec 2024 12:03:00 +0100 Subject: [PATCH 06/14] test: add missing initial value --- ts/features/zendesk/store/reducers/__tests__/index.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ts/features/zendesk/store/reducers/__tests__/index.test.ts b/ts/features/zendesk/store/reducers/__tests__/index.test.ts index 29128a9d0f5..14fd662d4f7 100644 --- a/ts/features/zendesk/store/reducers/__tests__/index.test.ts +++ b/ts/features/zendesk/store/reducers/__tests__/index.test.ts @@ -24,7 +24,8 @@ import { ZendeskState } from "../index"; const INITIAL_STATE: ZendeskState = { zendeskConfig: remoteUndefined, - ticketNumber: pot.none + ticketNumber: pot.none, + zendeskMap: remoteUndefined }; const mockCategory: ZendeskCategory = { From 46ccead5051d75d88d87c7d54f1f7bc29b173c69 Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:50:23 +0100 Subject: [PATCH 07/14] refactor: update endpoint string --- ts/api/content.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/api/content.ts b/ts/api/content.ts index ac5a6415d2e..4000a68f5d5 100644 --- a/ts/api/content.ts +++ b/ts/api/content.ts @@ -152,7 +152,7 @@ type GetZendeskPaymentConfigT = IGetApiRequestType< // TODO: update with new definition from related PR const getZendeskPaymentConfig: GetZendeskPaymentConfigT = { method: "get", - url: () => "/assistanceTools/paymentMap.json", + url: () => "/assistanceTools/payment/zendeskOutcomeMapping.json", query: _ => ({}), headers: () => ({}), response_decoder: basicResponseDecoder(unknown) From b0b98f957a7d8037ee2dcf441e54bd86eb2add91 Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:52:27 +0100 Subject: [PATCH 08/14] refactor: update getSubCategory function --- ts/features/payments/checkout/utils/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ts/features/payments/checkout/utils/index.ts b/ts/features/payments/checkout/utils/index.ts index 0995a9fae74..1ee0bf7a92d 100644 --- a/ts/features/payments/checkout/utils/index.ts +++ b/ts/features/payments/checkout/utils/index.ts @@ -82,14 +82,14 @@ export const isDueDateValid = (date: string): string | undefined => { export const getSubCategoryFromFaultCode = (data: any, statusCode: string) => { // check if there is a subcategory array that includes passed element - const subcategoryKey = Object.keys(data.subcategories).find(key => - data.subcategories[key].includes(statusCode) + const subcategoryKey = Object.keys(data.payments.subcategories).find(key => + data.payments.subcategories[key].includes(statusCode) ); // if there is, return the mapped subcategory with the zendesk category id if (subcategoryKey) { return { value: subcategoryKey, - zendeskCategoryId: data.subcategoryId + zendeskCategoryId: data.payments.subcategoryId }; } // if not, return the parent category From 6175e9209ec6dd9b426d3359a371cf6968967877 Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Tue, 10 Dec 2024 12:02:49 +0100 Subject: [PATCH 09/14] chore: io-service-metadata v1.0.50 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b2072c28e8d..5ecd9f1eb65 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "io_session_manager_api": "https://raw.githubusercontent.com/pagopa/io-auth-n-identity-domain/io-session-manager@1.0.0/apps/io-session-manager/api/internal.yaml", "io_session_manager_public_api": "https://raw.githubusercontent.com/pagopa/io-auth-n-identity-domain/io-session-manager@1.0.0/apps/io-session-manager/api/public.yaml", "io_public_api": "https://raw.githubusercontent.com/pagopa/io-backend/v16.4.0-RELEASE/api_public.yaml", - "io_content_specs": "https://raw.githubusercontent.com/pagopa/io-services-metadata/1.0.49/definitions.yml", + "io_content_specs": "https://raw.githubusercontent.com/pagopa/io-services-metadata/1.0.50/definitions.yml", "io_cgn_specs": "https://raw.githubusercontent.com/pagopa/io-backend/v16.4.0-RELEASE/api_cgn.yaml", "io_cgn_merchants_specs": "https://raw.githubusercontent.com/pagopa/io-backend/v16.4.0-RELEASE/api_cgn_operator_search.yaml", "api_fci": "https://raw.githubusercontent.com/pagopa/io-backend/v16.4.0-RELEASE/api_io_sign.yaml", From 89d7ec162fb4c39fdbc8be44024db6b45b5dd781 Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:28:46 +0100 Subject: [PATCH 10/14] refactor: add missing types --- ts/api/content.ts | 7 +++---- .../hooks/usePaymentFailureSupportModal.tsx | 11 +++++++++-- ts/features/payments/checkout/utils/index.ts | 14 +++++++++----- ts/features/zendesk/store/actions/index.ts | 3 ++- ts/features/zendesk/store/reducers/index.ts | 10 ++++++++-- ts/utils/supportAssistance.ts | 9 +++++++++ 6 files changed, 40 insertions(+), 14 deletions(-) diff --git a/ts/api/content.ts b/ts/api/content.ts index 4000a68f5d5..399ced6f022 100644 --- a/ts/api/content.ts +++ b/ts/api/content.ts @@ -8,13 +8,13 @@ import { createFetchRequestForApi, IGetApiRequestType } from "@pagopa/ts-commons/lib/requests"; -import { unknown } from "io-ts"; import { BonusesAvailable } from "../../definitions/content/BonusesAvailable"; import { ContextualHelp } from "../../definitions/content/ContextualHelp"; import { Municipality as MunicipalityMedadata } from "../../definitions/content/Municipality"; import { SpidIdps } from "../../definitions/content/SpidIdps"; import { VersionInfo } from "../../definitions/content/VersionInfo"; import { Zendesk } from "../../definitions/content/Zendesk"; +import { ZendeskSubcategoriesErrors } from "../../definitions/content/ZendeskSubcategoriesErrors"; import { CoBadgeServices } from "../../definitions/pagopa/cobadge/configuration/CoBadgeServices"; import { AbiListResponse } from "../../definitions/pagopa/walletv2/AbiListResponse"; import { contentRepoUrl } from "../config"; @@ -146,16 +146,15 @@ type GetZendeskPaymentConfigT = IGetApiRequestType< void, never, never, - BasicResponseType + BasicResponseType >; -// TODO: update with new definition from related PR const getZendeskPaymentConfig: GetZendeskPaymentConfigT = { method: "get", url: () => "/assistanceTools/payment/zendeskOutcomeMapping.json", query: _ => ({}), headers: () => ({}), - response_decoder: basicResponseDecoder(unknown) + response_decoder: basicResponseDecoder(ZendeskSubcategoriesErrors) }; /** diff --git a/ts/features/payments/checkout/hooks/usePaymentFailureSupportModal.tsx b/ts/features/payments/checkout/hooks/usePaymentFailureSupportModal.tsx index 083219601b1..d7a34b61bc4 100644 --- a/ts/features/payments/checkout/hooks/usePaymentFailureSupportModal.tsx +++ b/ts/features/payments/checkout/hooks/usePaymentFailureSupportModal.tsx @@ -24,6 +24,7 @@ import { addTicketCustomField, appendLog, assistanceToolRemoteConfig, + defaultZendeskPaymentCategory, resetCustomFields, zendeskPaymentFailure, zendeskPaymentNav, @@ -49,6 +50,7 @@ import { } from "../types/PaymentOutcomeEnum"; import { WalletPaymentFailure } from "../types/WalletPaymentFailure"; import { getSubCategoryFromFaultCode } from "../utils"; +import { isReady } from "../../../../common/model/RemoteValue"; type PaymentFailureSupportModalParams = { failure?: WalletPaymentFailure; @@ -89,8 +91,13 @@ const usePaymentFailureSupportModal = ({ ""; const zendeskAssistanceLogAndStart = () => { + if (!isReady(zendeskPaymentCategory)) { + return; + } + const { payments } = zendeskPaymentCategory.value; + const { value, zendeskCategoryId } = getSubCategoryFromFaultCode( - zendeskPaymentCategory, + payments, faultCodeDetail ); resetCustomFields(); @@ -111,7 +118,7 @@ const usePaymentFailureSupportModal = ({ assistanceForFci: false }) ); - dispatch(zendeskSelectedCategory(zendeskPaymentCategory)); + dispatch(zendeskSelectedCategory(defaultZendeskPaymentCategory)); }; const handleAskAssistance = () => { diff --git a/ts/features/payments/checkout/utils/index.ts b/ts/features/payments/checkout/utils/index.ts index 1ee0bf7a92d..667c20cfe47 100644 --- a/ts/features/payments/checkout/utils/index.ts +++ b/ts/features/payments/checkout/utils/index.ts @@ -1,15 +1,16 @@ import _ from "lodash"; +import { ZendeskSubCategoriesMap } from "../../../../../definitions/content/ZendeskSubCategoriesMap"; import { Bundle } from "../../../../../definitions/pagopa/ecommerce/Bundle"; import { PaymentMethodManagementTypeEnum } from "../../../../../definitions/pagopa/ecommerce/PaymentMethodManagementType"; import { PaymentMethodResponse } from "../../../../../definitions/pagopa/ecommerce/PaymentMethodResponse"; import { PaymentMethodStatusEnum } from "../../../../../definitions/pagopa/ecommerce/PaymentMethodStatus"; import { format } from "../../../../utils/dates"; +import { zendeskCategoryId } from "../../../../utils/supportAssistance"; import { PaymentAnalyticsPhase, PaymentAnalyticsSelectedPspFlag } from "../../common/types/PaymentAnalytics"; import { WalletPaymentStepEnum } from "../types"; -import { zendeskCategoryId } from "../../../../utils/supportAssistance"; export const WALLET_PAYMENT_SHOW_OTHER_CHANNELS_URL = "https://www.pagopa.gov.it/it/cittadini/dove-pagare/"; @@ -80,16 +81,19 @@ export const isDueDateValid = (date: string): string | undefined => { return new Date(date) > tenYearsFromNow ? undefined : formattedDate; }; -export const getSubCategoryFromFaultCode = (data: any, statusCode: string) => { +export const getSubCategoryFromFaultCode = ( + data: ZendeskSubCategoriesMap, + statusCode: string +) => { // check if there is a subcategory array that includes passed element - const subcategoryKey = Object.keys(data.payments.subcategories).find(key => - data.payments.subcategories[key].includes(statusCode) + const subcategoryKey = Object.keys(data.subcategories).find(key => + data.subcategories[key].includes(statusCode) ); // if there is, return the mapped subcategory with the zendesk category id if (subcategoryKey) { return { value: subcategoryKey, - zendeskCategoryId: data.payments.subcategoryId + zendeskCategoryId: data.subcategoryId }; } // if not, return the parent category diff --git a/ts/features/zendesk/store/actions/index.ts b/ts/features/zendesk/store/actions/index.ts index af79a5e28ef..1150bb1a8a5 100644 --- a/ts/features/zendesk/store/actions/index.ts +++ b/ts/features/zendesk/store/actions/index.ts @@ -6,6 +6,7 @@ import { import { Zendesk } from "../../../../../definitions/content/Zendesk"; import { ZendeskCategory } from "../../../../../definitions/content/ZendeskCategory"; import { ZendeskSubCategory } from "../../../../../definitions/content/ZendeskSubCategory"; +import { ZendeskSubcategoriesErrors } from "../../../../../definitions/content/ZendeskSubcategoriesErrors"; import { ContextualHelpProps, ContextualHelpPropsMarkdown @@ -102,7 +103,7 @@ export const getZendeskPaymentConfig = createAsyncAction( "ZENDESK_PAYMENT_CONFIG_REQUEST", "ZENDESK_PAYMENT_CONFIG_SUCCESS", "ZENDESK_PAYMENT_CONFIG_FAILURE" -)(); +)(); // user selected a category export const zendeskSelectedCategory = createStandardAction( diff --git a/ts/features/zendesk/store/reducers/index.ts b/ts/features/zendesk/store/reducers/index.ts index 9614c1e937e..4c59c5079bb 100644 --- a/ts/features/zendesk/store/reducers/index.ts +++ b/ts/features/zendesk/store/reducers/index.ts @@ -25,6 +25,7 @@ import { } from "../actions"; import { GlobalState } from "../../../../store/reducers/types"; import { ZendeskSubCategory } from "../../../../../definitions/content/ZendeskSubCategory"; +import { ZendeskSubcategoriesErrors } from "../../../../../definitions/content/ZendeskSubcategoriesErrors"; type ZendeskValue = { panicMode: boolean; @@ -35,6 +36,11 @@ type ZendeskValue = { }; export type ZendeskConfig = RemoteValue; +export type ZendeskSubcategoriesErrorsConfig = RemoteValue< + ZendeskSubcategoriesErrors, + NetworkError +>; + export enum ZendeskTokenStatusEnum { SUCCESS = "success", ERROR = "error", @@ -48,7 +54,7 @@ export type ZendeskState = { ticketNumber: pot.Pot; getSessionPollingRunning?: boolean; getZendeskTokenStatus?: ZendeskTokenStatusEnum | "401"; - zendeskMap: any; + zendeskMap: ZendeskSubcategoriesErrorsConfig; }; const INITIAL_STATE: ZendeskState = { @@ -188,7 +194,7 @@ export const zendeskTicketNumberSelector = createSelector( export const zendeskMapSelector = createSelector( [(state: GlobalState) => state.assistanceTools.zendesk.zendeskMap], - (zendeskMap: any): any => zendeskMap.value + (zendeskMap): ZendeskSubcategoriesErrorsConfig => zendeskMap ); export default reducer; diff --git a/ts/utils/supportAssistance.ts b/ts/utils/supportAssistance.ts index fe323bbc5a8..885762d6336 100644 --- a/ts/utils/supportAssistance.ts +++ b/ts/utils/supportAssistance.ts @@ -116,6 +116,15 @@ export const zendeskCurrentAppVersionId = "4414316660369"; export const zendeskVersionsHistoryId = "4419641151505"; export const zendeskFciId = "14874226407825"; +export const defaultZendeskPaymentCategory: ZendeskCategory = { + value: "pagamento", + description: { + "it-IT": "Pagamento pagoPA", + "en-EN": "pagoPA payment", + "de-DE": "pagoPA-Zahlung" + } +}; + export const zendeskPaymentMethodCategory: ZendeskCategory = { value: "metodo_di_pagamento", description: { From 90768105f349fd0ee7214e14df6cd593f1886985 Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:51:25 +0100 Subject: [PATCH 11/14] test: getSubCategoryFromFaultCode utils --- .../checkout/utils/__tests__/index.test.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/ts/features/payments/checkout/utils/__tests__/index.test.ts b/ts/features/payments/checkout/utils/__tests__/index.test.ts index d5981b08bfa..d67648ff089 100644 --- a/ts/features/payments/checkout/utils/__tests__/index.test.ts +++ b/ts/features/payments/checkout/utils/__tests__/index.test.ts @@ -1,11 +1,22 @@ import { getPaymentPhaseFromStep, getPspFlagType, + getSubCategoryFromFaultCode, isDueDateValid, trimAndLimitValue } from ".."; +import { ZendeskSubCategoriesMap } from "../../../../../../definitions/content/ZendeskSubCategoriesMap"; +import { zendeskCategoryId } from "../../../../../utils/supportAssistance"; import { WalletPaymentStepEnum } from "../../types"; +const mockCategories: ZendeskSubCategoriesMap = { + subcategories: { + "12345": ["subcategory1"], + "67890": ["subcategory2"] + }, + subcategoryId: "313" +}; + describe("trimAndLimitValue", () => { it("should remove all spaces from the string", () => { const input = "a b c d e"; @@ -136,3 +147,24 @@ describe("getPaymentPhaseFromStep", () => { expect(result).toBe("verifica"); }); }); + +describe("getSubCategoryFromFaultCode", () => { + it("should return the subcategory if the fault code is in the map", () => { + const result = getSubCategoryFromFaultCode(mockCategories, "subcategory1"); + expect(result).toStrictEqual({ value: "12345", zendeskCategoryId: "313" }); + }); + + it("should return default category if the fault code is not in the map or is empty string", () => { + const result = getSubCategoryFromFaultCode(mockCategories, "subcategory3"); + expect(result).toStrictEqual({ + value: "pagamenti_pagopa", + zendeskCategoryId + }); + + const emptyResult = getSubCategoryFromFaultCode(mockCategories, ""); + expect(emptyResult).toStrictEqual({ + value: "pagamenti_pagopa", + zendeskCategoryId + }); + }); +}); From 72ab77a188150ca1075abd9c6317aa45925a75a8 Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Wed, 11 Dec 2024 12:32:50 +0100 Subject: [PATCH 12/14] refactor: handle nullable subcategory case --- .../hooks/usePaymentFailureSupportModal.tsx | 19 ++++++++++++++----- .../checkout/utils/__tests__/index.test.ts | 18 +++++++----------- ts/features/payments/checkout/utils/index.ts | 10 +++------- ts/utils/supportAssistance.ts | 2 +- 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/ts/features/payments/checkout/hooks/usePaymentFailureSupportModal.tsx b/ts/features/payments/checkout/hooks/usePaymentFailureSupportModal.tsx index d7a34b61bc4..ac74ed8e663 100644 --- a/ts/features/payments/checkout/hooks/usePaymentFailureSupportModal.tsx +++ b/ts/features/payments/checkout/hooks/usePaymentFailureSupportModal.tsx @@ -26,6 +26,7 @@ import { assistanceToolRemoteConfig, defaultZendeskPaymentCategory, resetCustomFields, + zendeskCategoryId, zendeskPaymentFailure, zendeskPaymentNav, zendeskPaymentOrgFiscalCode, @@ -95,13 +96,21 @@ const usePaymentFailureSupportModal = ({ return; } const { payments } = zendeskPaymentCategory.value; - - const { value, zendeskCategoryId } = getSubCategoryFromFaultCode( - payments, - faultCodeDetail + const subCategory = getSubCategoryFromFaultCode(payments, faultCodeDetail); + // attach the main zendesk category to the ticket + addTicketCustomField( + zendeskCategoryId, + defaultZendeskPaymentCategory.value ); + + if (subCategory) { + // if a subcategory is found, we attach its id and value to the ticket + const { value, zendeskSubCategoryId } = subCategory; + addTicketCustomField(zendeskSubCategoryId, value); + return; + } resetCustomFields(); - addTicketCustomField(zendeskCategoryId, value); + addTicketCustomField(zendeskPaymentOrgFiscalCode, organizationFiscalCode); addTicketCustomField(zendeskPaymentNav, paymentNoticeNumber); addTicketCustomField(zendeskPaymentFailure, faultCodeDetail); diff --git a/ts/features/payments/checkout/utils/__tests__/index.test.ts b/ts/features/payments/checkout/utils/__tests__/index.test.ts index d67648ff089..c15aee94415 100644 --- a/ts/features/payments/checkout/utils/__tests__/index.test.ts +++ b/ts/features/payments/checkout/utils/__tests__/index.test.ts @@ -6,7 +6,6 @@ import { trimAndLimitValue } from ".."; import { ZendeskSubCategoriesMap } from "../../../../../../definitions/content/ZendeskSubCategoriesMap"; -import { zendeskCategoryId } from "../../../../../utils/supportAssistance"; import { WalletPaymentStepEnum } from "../../types"; const mockCategories: ZendeskSubCategoriesMap = { @@ -151,20 +150,17 @@ describe("getPaymentPhaseFromStep", () => { describe("getSubCategoryFromFaultCode", () => { it("should return the subcategory if the fault code is in the map", () => { const result = getSubCategoryFromFaultCode(mockCategories, "subcategory1"); - expect(result).toStrictEqual({ value: "12345", zendeskCategoryId: "313" }); + expect(result).toStrictEqual({ + value: "12345", + zendeskSubCategoryId: "313" + }); }); - it("should return default category if the fault code is not in the map or is empty string", () => { + it("should return nullable if the fault code is not in the map or is empty string", () => { const result = getSubCategoryFromFaultCode(mockCategories, "subcategory3"); - expect(result).toStrictEqual({ - value: "pagamenti_pagopa", - zendeskCategoryId - }); + expect(result).toStrictEqual(null); const emptyResult = getSubCategoryFromFaultCode(mockCategories, ""); - expect(emptyResult).toStrictEqual({ - value: "pagamenti_pagopa", - zendeskCategoryId - }); + expect(emptyResult).toStrictEqual(null); }); }); diff --git a/ts/features/payments/checkout/utils/index.ts b/ts/features/payments/checkout/utils/index.ts index 667c20cfe47..e5c44a0ba5b 100644 --- a/ts/features/payments/checkout/utils/index.ts +++ b/ts/features/payments/checkout/utils/index.ts @@ -5,7 +5,6 @@ import { PaymentMethodManagementTypeEnum } from "../../../../../definitions/pago import { PaymentMethodResponse } from "../../../../../definitions/pagopa/ecommerce/PaymentMethodResponse"; import { PaymentMethodStatusEnum } from "../../../../../definitions/pagopa/ecommerce/PaymentMethodStatus"; import { format } from "../../../../utils/dates"; -import { zendeskCategoryId } from "../../../../utils/supportAssistance"; import { PaymentAnalyticsPhase, PaymentAnalyticsSelectedPspFlag @@ -93,12 +92,9 @@ export const getSubCategoryFromFaultCode = ( if (subcategoryKey) { return { value: subcategoryKey, - zendeskCategoryId: data.subcategoryId + zendeskSubCategoryId: data.subcategoryId }; } - // if not, return the parent category - return { - value: "pagamenti_pagopa", - zendeskCategoryId - }; + // if not, return nullable + return null; }; diff --git a/ts/utils/supportAssistance.ts b/ts/utils/supportAssistance.ts index 885762d6336..395105c2ecc 100644 --- a/ts/utils/supportAssistance.ts +++ b/ts/utils/supportAssistance.ts @@ -117,7 +117,7 @@ export const zendeskVersionsHistoryId = "4419641151505"; export const zendeskFciId = "14874226407825"; export const defaultZendeskPaymentCategory: ZendeskCategory = { - value: "pagamento", + value: "io_pagamenti_pagopa", description: { "it-IT": "Pagamento pagoPA", "en-EN": "pagoPA payment", From 773571b83473d5269cba4d523ab685224fb73327 Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:05:44 +0100 Subject: [PATCH 13/14] refactor: change request --- .../payments/checkout/hooks/usePaymentFailureSupportModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ts/features/payments/checkout/hooks/usePaymentFailureSupportModal.tsx b/ts/features/payments/checkout/hooks/usePaymentFailureSupportModal.tsx index ac74ed8e663..94b33c0ab2d 100644 --- a/ts/features/payments/checkout/hooks/usePaymentFailureSupportModal.tsx +++ b/ts/features/payments/checkout/hooks/usePaymentFailureSupportModal.tsx @@ -97,6 +97,8 @@ const usePaymentFailureSupportModal = ({ } const { payments } = zendeskPaymentCategory.value; const subCategory = getSubCategoryFromFaultCode(payments, faultCodeDetail); + + resetCustomFields(); // attach the main zendesk category to the ticket addTicketCustomField( zendeskCategoryId, @@ -107,9 +109,7 @@ const usePaymentFailureSupportModal = ({ // if a subcategory is found, we attach its id and value to the ticket const { value, zendeskSubCategoryId } = subCategory; addTicketCustomField(zendeskSubCategoryId, value); - return; } - resetCustomFields(); addTicketCustomField(zendeskPaymentOrgFiscalCode, organizationFiscalCode); addTicketCustomField(zendeskPaymentNav, paymentNoticeNumber); From 0959a516e2d50e73642a903a368ef28a66a238e3 Mon Sep 17 00:00:00 2001 From: Emanuele Dall'Ara <71103219+LeleDallas@users.noreply.github.com> Date: Wed, 11 Dec 2024 17:11:23 +0100 Subject: [PATCH 14/14] refactor: rename reducer state const --- .../store/reducers/__tests__/index.test.ts | 2 +- ts/features/zendesk/store/reducers/index.ts | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/ts/features/zendesk/store/reducers/__tests__/index.test.ts b/ts/features/zendesk/store/reducers/__tests__/index.test.ts index 14fd662d4f7..fd10dd2a10f 100644 --- a/ts/features/zendesk/store/reducers/__tests__/index.test.ts +++ b/ts/features/zendesk/store/reducers/__tests__/index.test.ts @@ -25,7 +25,7 @@ import { ZendeskState } from "../index"; const INITIAL_STATE: ZendeskState = { zendeskConfig: remoteUndefined, ticketNumber: pot.none, - zendeskMap: remoteUndefined + zendeskSubcategoriesErrorMap: remoteUndefined }; const mockCategory: ZendeskCategory = { diff --git a/ts/features/zendesk/store/reducers/index.ts b/ts/features/zendesk/store/reducers/index.ts index 4c59c5079bb..29142ae533a 100644 --- a/ts/features/zendesk/store/reducers/index.ts +++ b/ts/features/zendesk/store/reducers/index.ts @@ -54,13 +54,13 @@ export type ZendeskState = { ticketNumber: pot.Pot; getSessionPollingRunning?: boolean; getZendeskTokenStatus?: ZendeskTokenStatusEnum | "401"; - zendeskMap: ZendeskSubcategoriesErrorsConfig; + zendeskSubcategoriesErrorMap: ZendeskSubcategoriesErrorsConfig; }; const INITIAL_STATE: ZendeskState = { zendeskConfig: remoteUndefined, ticketNumber: pot.none, - zendeskMap: remoteUndefined + zendeskSubcategoriesErrorMap: remoteUndefined }; const reducer = ( @@ -147,17 +147,17 @@ const reducer = ( case getType(getZendeskPaymentConfig.request): return { ...state, - zendeskMap: remoteLoading + zendeskSubcategoriesErrorMap: remoteLoading }; case getType(getZendeskPaymentConfig.success): return { ...state, - zendeskMap: remoteReady(action.payload) + zendeskSubcategoriesErrorMap: remoteReady(action.payload) }; case getType(getZendeskPaymentConfig.failure): return { ...state, - zendeskMap: remoteError(action.payload) + zendeskSubcategoriesErrorMap: remoteError(action.payload) }; } return state; @@ -193,7 +193,10 @@ export const zendeskTicketNumberSelector = createSelector( ); export const zendeskMapSelector = createSelector( - [(state: GlobalState) => state.assistanceTools.zendesk.zendeskMap], + [ + (state: GlobalState) => + state.assistanceTools.zendesk.zendeskSubcategoriesErrorMap + ], (zendeskMap): ZendeskSubcategoriesErrorsConfig => zendeskMap );