ts/sagas/wallet/pagopaApis.ts
import { RptId, RptIdFromString } from "@pagopa/io-pagopa-commons/lib/pagopa";
import * as E from "fp-ts/lib/Either";
import { pipe } from "fp-ts/lib/function";
import * as O from "fp-ts/lib/Option";
import { call, put, select, take } from "typed-redux-saga/macro";
import { ActionType, isActionOf } from "typesafe-actions";
import { Action } from "redux";
import { IOToast } from "@pagopa/io-app-design-system";
import { BackendClient } from "../../api/backend";
import { PaymentManagerClient } from "../../api/pagopa";
import { mixpanelTrack } from "../../mixpanel";
import { getFilteredPspsList } from "../../screens/wallet/payment/common";
import { checkCurrentSession } from "../../store/actions/authentication";
import { deleteAllPaymentMethodsByFunction } from "../../store/actions/wallet/delete";
import {
paymentAttiva,
paymentCheck,
paymentDeletePayment,
paymentExecuteStart,
paymentIdPolling,
paymentUpdateWalletPsp,
paymentVerifica,
pspForPaymentV2,
pspForPaymentV2WithCallbacks
} from "../../store/actions/wallet/payment";
import {
fetchPsp,
fetchTransactionFailure,
fetchTransactionRequest,
fetchTransactionsFailure,
fetchTransactionsRequest,
fetchTransactionsSuccess,
fetchTransactionSuccess
} from "../../store/actions/wallet/transactions";
import {
addWalletCreditCardFailure,
addWalletCreditCardRequest,
addWalletCreditCardSuccess,
deleteWalletFailure,
deleteWalletRequest,
deleteWalletSuccess,
fetchWalletsFailure,
fetchWalletsSuccess,
setFavouriteWalletFailure,
setFavouriteWalletRequest,
setFavouriteWalletSuccess,
updatePaymentStatus
} from "../../store/actions/wallet/wallets";
import { preferredPspsByOriginSelector } from "../../store/reducers/backendStatus";
import { isPagoPATestEnabledSelector } from "../../store/reducers/persistedPreferences";
import { paymentStartOriginSelector } from "../../store/reducers/wallet/payment";
import { PaymentManagerToken, Wallet } from "../../types/pagopa";
import { ReduxSagaEffect, SagaCallReturnType } from "../../types/utils";
import {
convertUnknownToError,
getError,
getErrorFromNetworkError,
getGenericError,
getNetworkError,
getWalletError,
isTimeoutError
} from "../../utils/errors";
import { readablePrivacyReport } from "../../utils/reporters";
import { SessionManager } from "../../utils/SessionManager";
import { convertWalletV2toWalletV1 } from "../../utils/walletv2";
import I18n from "../../i18n";
import { PaymentRequestsGetResponse } from "../../../definitions/backend/PaymentRequestsGetResponse";
import { Detail_v2Enum } from "../../../definitions/backend/PaymentProblemJson";
import { withRefreshApiCall } from "../../features/fastLogin/saga/utils";
//
// Payment Manager APIs
//
/**
* Handles fetchWalletsRequest
*/
export function* getWallets(
pagoPaClient: PaymentManagerClient,
pmSessionManager: SessionManager<PaymentManagerToken>
): Generator<ReduxSagaEffect, E.Either<Error, ReadonlyArray<Wallet>>, any> {
return yield* call(getWalletsV2, pagoPaClient, pmSessionManager);
}
// load wallet from api /v2/wallet
// it converts walletV2 into walletV1
export function* getWalletsV2(
pagoPaClient: PaymentManagerClient,
pmSessionManager: SessionManager<PaymentManagerToken>
): Generator<ReduxSagaEffect, E.Either<Error, ReadonlyArray<Wallet>>, any> {
try {
void mixpanelTrack("WALLETS_LOAD_REQUEST");
const request = pmSessionManager.withRefresh(pagoPaClient.getWalletsV2);
const getResponse: SagaCallReturnType<typeof request> = yield* call(
request
);
if (E.isRight(getResponse)) {
if (getResponse.right.status === 200) {
const wallets = (getResponse.right.value.data ?? []).map(
convertWalletV2toWalletV1
);
void mixpanelTrack("WALLETS_LOAD_SUCCESS", {
count: wallets.length
});
yield* put(fetchWalletsSuccess(wallets));
return E.right<Error, ReadonlyArray<Wallet>>(wallets);
} else {
throw Error(`response status ${getResponse.right.status}`);
}
} else {
throw Error(readablePrivacyReport(getResponse.left));
}
} catch (e) {
const computedError = convertUnknownToError(e);
// this is required to handle 401 response from PM
// On 401 response sessionManager retries for X attempts to get a valid session
// If it exceeds a fixed threshold of attempts a max retries error will be dispatched
void mixpanelTrack("WALLETS_LOAD_FAILURE", {
reason: computedError.message
});
if (isTimeoutError(getNetworkError(e))) {
// check if also the IO session is expired
yield* put(checkCurrentSession.request());
}
yield* put(fetchWalletsFailure(computedError));
return E.left<Error, ReadonlyArray<Wallet>>(computedError);
}
}
/**
* Handles fetchTransactionsRequest
*/
export function* fetchTransactionsRequestHandler(
pagoPaClient: PaymentManagerClient,
pmSessionManager: SessionManager<PaymentManagerToken>,
action: ActionType<typeof fetchTransactionsRequest>
): Generator<ReduxSagaEffect, void, any> {
const request = pmSessionManager.withRefresh(
pagoPaClient.getTransactions(action.payload.start)
);
try {
const response: SagaCallReturnType<typeof request> = yield* call(request);
if (E.isRight(response)) {
if (response.right.status === 200) {
yield* put(
fetchTransactionsSuccess({
data: response.right.value.data,
total: O.fromNullable(response.right.value.total)
})
);
} else {
throw Error(`response status ${response.right.status}`);
}
} else {
throw Error(readablePrivacyReport(response.left));
}
} catch (e) {
if (isTimeoutError(getNetworkError(e))) {
// check if also the IO session is expired
yield* put(checkCurrentSession.request());
}
yield* put(fetchTransactionsFailure(convertUnknownToError(e)));
}
}
/**
* Handles fetchTransactionRequest
*/
export function* fetchTransactionRequestHandler(
pagoPaClient: PaymentManagerClient,
pmSessionManager: SessionManager<PaymentManagerToken>,
action: ActionType<typeof fetchTransactionRequest>
): Generator<ReduxSagaEffect, void, any> {
const request = pmSessionManager.withRefresh(
pagoPaClient.getTransaction(action.payload)
);
try {
const response: SagaCallReturnType<typeof request> = yield* call(request);
if (E.isRight(response)) {
if (response.right.status === 200) {
yield* put(fetchTransactionSuccess(response.right.value.data));
} else {
throw Error(`response status ${response.right.status}`);
}
} else {
throw Error(readablePrivacyReport(response.left));
}
} catch (e) {
if (isTimeoutError(getNetworkError(e))) {
// check if also the IO session is expired
yield* put(checkCurrentSession.request());
}
yield* put(fetchTransactionFailure(convertUnknownToError(e)));
}
}
/**
* Handles fetchPspRequest
*/
export function* fetchPspRequestHandler(
pagoPaClient: PaymentManagerClient,
pmSessionManager: SessionManager<PaymentManagerToken>,
action: ActionType<(typeof fetchPsp)["request"]>
): Generator<ReduxSagaEffect, void, any> {
const request = pmSessionManager.withRefresh(
pagoPaClient.getPsp(action.payload.idPsp)
);
try {
const response: SagaCallReturnType<typeof request> = yield* call(request);
if (E.isRight(response)) {
if (response.right.status === 200) {
const psp = response.right.value.data;
const successAction = fetchPsp.success({
idPsp: action.payload.idPsp,
psp
});
yield* put(successAction);
if (action.payload.onSuccess) {
action.payload.onSuccess(successAction);
}
} else {
throw Error(`response status ${response.right.status}`);
}
} else {
throw Error(readablePrivacyReport(response.left));
}
} catch (e) {
const failureAction = fetchPsp.failure({
idPsp: action.payload.idPsp,
error: convertUnknownToError(e)
});
if (isTimeoutError(getNetworkError(e))) {
// check if also the IO session is expired
yield* put(checkCurrentSession.request());
}
yield* put(failureAction);
if (action.payload.onFailure) {
action.payload.onFailure(failureAction);
}
}
}
/**
* Handles the update of payment method status
* (enable or disable a payment method to pay with pagoPa)
*/
export function* updatePaymentStatusSaga(
pagoPaClient: PaymentManagerClient,
pmSessionManager: SessionManager<PaymentManagerToken>,
action: ActionType<typeof updatePaymentStatus.request>
): Generator<ReduxSagaEffect, void, any> {
const updatePayment = pagoPaClient.updatePaymentStatus(action.payload);
const request = pmSessionManager.withRefresh(updatePayment);
try {
const response: SagaCallReturnType<typeof request> = yield* call(request);
if (E.isRight(response)) {
if (response.right.status === 200) {
if (response.right.value.data) {
yield* put(
updatePaymentStatus.success(
convertWalletV2toWalletV1(response.right.value.data)
)
);
IOToast.success(
I18n.t("wallet.methods.card.pagoPaCapability.operationCompleted")
);
} else {
// this should not never happen (payload weak typed)
yield* put(
updatePaymentStatus.failure(
getNetworkError(Error("payload is empty"))
)
);
}
} else {
const errorStatus = Error(`response status ${response.right.status}`);
yield* put(updatePaymentStatus.failure(getNetworkError(errorStatus)));
}
} else {
const errorDescription = Error(readablePrivacyReport(response.left));
yield* put(
updatePaymentStatus.failure(getNetworkError(errorDescription))
);
}
} catch (error) {
if (isTimeoutError(getNetworkError(error))) {
// check if also the IO session is expired
yield* put(checkCurrentSession.request());
}
yield* put(updatePaymentStatus.failure(getNetworkError(error)));
}
}
/**
* Handles setFavouriteWalletRequest
*/
export function* setFavouriteWalletRequestHandler(
pagoPaClient: PaymentManagerClient,
pmSessionManager: SessionManager<PaymentManagerToken>,
action: ActionType<typeof setFavouriteWalletRequest>
): Generator<ReduxSagaEffect, void, any> {
const favouriteWalletId = action.payload;
if (favouriteWalletId === undefined) {
// FIXME: currently there is no way to unset a favourite wallet
return;
}
const setFavouriteWallet = pagoPaClient.favouriteWallet(favouriteWalletId);
const request = pmSessionManager.withRefresh(setFavouriteWallet);
try {
const response: SagaCallReturnType<typeof request> = yield* call(request);
if (E.isRight(response)) {
if (response.right.status === 200) {
yield* put(setFavouriteWalletSuccess(response.right.value.data));
} else {
throw Error(`response status ${response.right.status}`);
}
} else {
throw Error(readablePrivacyReport(response.left));
}
} catch (e) {
if (isTimeoutError(getNetworkError(e))) {
// check if also the IO session is expired
yield* put(checkCurrentSession.request());
}
yield* put(setFavouriteWalletFailure(convertUnknownToError(e)));
}
}
/**
* Updates a Wallet with a new favorite PSP
*
* TODO: consider avoiding the fetch, let the application logic decide
*/
// eslint-disable-next-line
export function* updateWalletPspRequestHandler(
pagoPaClient: PaymentManagerClient,
pmSessionManager: SessionManager<PaymentManagerToken>,
action: ActionType<(typeof paymentUpdateWalletPsp)["request"]>
) {
// First update the selected wallet (walletId) with the
// new PSP (action.payload); then request a new list
// of wallets (which will contain the updated PSP)
const { wallet, psp, idPayment } = action.payload;
const { id: idPsp } = psp;
const apiUpdateWalletPsp = pagoPaClient.updateWalletPsp(wallet.idWallet, {
data: { idPsp, idPagamentoFromEC: idPayment }
});
const updateWalletPspWithRefresh =
pmSessionManager.withRefresh(apiUpdateWalletPsp);
try {
const response: SagaCallReturnType<typeof updateWalletPspWithRefresh> =
yield* call(updateWalletPspWithRefresh);
if (E.isRight(response)) {
if (response.right.status === 200) {
const maybeWallets: SagaCallReturnType<typeof getWallets> = yield* call(
getWallets,
pagoPaClient,
pmSessionManager
);
if (E.isRight(maybeWallets)) {
// look for the updated wallet
const updatedWallet = maybeWallets.right.find(
_ => _.idWallet === wallet.idWallet
);
if (updatedWallet !== undefined) {
// the wallet is still there, we can proceed
const successAction = paymentUpdateWalletPsp.success({
wallets: maybeWallets.right,
// attention: updatedWallet is V1
updatedWallet: response.right.value.data
});
yield* put(successAction);
if (action.payload.onSuccess) {
// signal the callee if requested
action.payload.onSuccess(successAction);
}
} else {
// oops, the wallet is not there anymore!
throw Error(`response status ${response.right.status}`);
}
} else {
throw maybeWallets.left;
}
} else {
// oops, the wallet is not there anymore!
throw Error(`response status ${response.right.status}`);
}
} else {
throw Error(readablePrivacyReport(response.left));
}
} catch (e) {
if (isTimeoutError(getNetworkError(e))) {
// check if also the IO session is expired
yield* put(checkCurrentSession.request());
}
const failureAction = paymentUpdateWalletPsp.failure(
convertUnknownToError(e)
);
yield* put(failureAction);
if (action.payload.onFailure) {
// signal the callee if requested
action.payload.onFailure(failureAction);
}
}
}
/**
* delete all those payment methods that have the specified function enabled
* @param pagoPaClient
* @param pmSessionManager
* @param action
*/
export function* deleteAllPaymentMethodsByFunctionRequestHandler(
pagoPaClient: PaymentManagerClient,
pmSessionManager: SessionManager<PaymentManagerToken>,
action: ActionType<typeof deleteAllPaymentMethodsByFunction>
): Generator<ReduxSagaEffect, void, any> {
const deleteAllByFunctionApi = pagoPaClient.deleteAllPaymentMethodsByFunction(
action.payload as string
);
const deleteAllByFunctionApiWithRefresh = pmSessionManager.withRefresh(
deleteAllByFunctionApi
);
try {
const deleteResponse: SagaCallReturnType<
typeof deleteAllByFunctionApiWithRefresh
> = yield* call(deleteAllByFunctionApiWithRefresh);
if (E.isRight(deleteResponse) && deleteResponse.right.status === 200) {
const deletedMethodsCount =
deleteResponse.right.value.data?.deletedWallets ?? -1;
const remainingWallets = (
deleteResponse.right.value.data?.remainingWallets ?? []
).map(convertWalletV2toWalletV1);
const successAction = deleteAllPaymentMethodsByFunction.success({
wallets: remainingWallets,
deletedMethodsCount
});
yield* put(successAction);
} else {
const error = Error(
pipe(
deleteResponse,
E.foldW(
readablePrivacyReport,
({ status }) => `response status ${status}`
)
)
);
yield* put(
deleteAllPaymentMethodsByFunction.failure({
error
})
);
}
} catch (e) {
if (isTimeoutError(getNetworkError(e))) {
// check if also the IO session is expired
yield* put(checkCurrentSession.request());
}
yield* put(
deleteAllPaymentMethodsByFunction.failure({
error: getErrorFromNetworkError(getNetworkError(e))
})
);
}
}
/**
* Handles deleteWalletRequest
*
* TODO: consider avoiding the fetch, let the appliction logic decide
*/
// eslint-disable-next-line
export function* deleteWalletRequestHandler(
pagoPaClient: PaymentManagerClient,
pmSessionManager: SessionManager<PaymentManagerToken>,
action: ActionType<typeof deleteWalletRequest>
): Generator<ReduxSagaEffect, void, any> {
const deleteWalletApi = pagoPaClient.deleteWallet(action.payload.walletId);
const deleteWalletWithRefresh = pmSessionManager.withRefresh(deleteWalletApi);
try {
const deleteResponse: SagaCallReturnType<typeof deleteWalletWithRefresh> =
yield* call(deleteWalletWithRefresh);
if (E.isRight(deleteResponse) && deleteResponse.right.status === 200) {
const maybeWallets: SagaCallReturnType<typeof getWallets> = yield* call(
getWallets,
pagoPaClient,
pmSessionManager
);
if (E.isRight(maybeWallets)) {
const successAction = deleteWalletSuccess(maybeWallets.right);
yield* put(successAction);
if (action.payload.onSuccess) {
action.payload.onSuccess(successAction);
}
} else {
throw maybeWallets.left;
}
} else {
throw Error(
pipe(
deleteResponse,
E.foldW(
readablePrivacyReport,
({ status }) => `response status ${status}`
)
)
);
}
} catch (e) {
if (isTimeoutError(getNetworkError(e))) {
// check if also the IO session is expired
yield* put(checkCurrentSession.request());
}
const failureAction = deleteWalletFailure(
e instanceof Error ? e : new Error()
);
yield* put(failureAction);
if (action.payload.onFailure) {
action.payload.onFailure(failureAction);
}
}
}
/**
* Handles addWalletCreditCardRequest
*/
export function* addWalletCreditCardRequestHandler(
pagoPaClient: PaymentManagerClient,
pmSessionManager: SessionManager<PaymentManagerToken>,
action: ActionType<typeof addWalletCreditCardRequest>
) {
const boardCreditCard = pagoPaClient.addWalletCreditCard(
action.payload.creditcard
);
const boardCreditCardWithRefresh =
pmSessionManager.withRefresh(boardCreditCard);
try {
const response: SagaCallReturnType<typeof boardCreditCardWithRefresh> =
yield* call(boardCreditCardWithRefresh);
if (E.isRight(response)) {
if (response.right.status === 200) {
yield* put(addWalletCreditCardSuccess(response.right.value));
} else if (
response.right.status === 422 &&
response.right.value.message === "creditcard.already_exists"
) {
yield* put(addWalletCreditCardFailure({ kind: "ALREADY_EXISTS" }));
} else {
throw Error(`response status ${response.right.status}`);
}
} else {
throw Error(readablePrivacyReport(response.left));
}
} catch (e) {
if (isTimeoutError(getNetworkError(e))) {
// check if also the IO session is expired
yield* put(checkCurrentSession.request());
}
yield* put(
addWalletCreditCardFailure({
kind: "GENERIC_ERROR",
reason: getError(e).message
})
);
}
}
/**
* Handles paymentCheckRequest
*/
export function* paymentCheckRequestHandler(
pagoPaClient: PaymentManagerClient,
pmSessionManager: SessionManager<PaymentManagerToken>,
action: ActionType<(typeof paymentCheck)["request"]>
): Generator<ReduxSagaEffect, void, any> {
// FIXME: we should not use default pagopa client for checkpayment, need to
// a client that doesn't retry on failure!!! checkpayment is NOT
// idempotent, the 2nd time it will error!
const apiCheckPayment = () => pagoPaClient.checkPayment(action.payload);
const checkPaymentWithRefresh = pmSessionManager.withRefresh(apiCheckPayment);
try {
const response: SagaCallReturnType<typeof checkPaymentWithRefresh> =
yield* call(checkPaymentWithRefresh);
if (E.isRight(response)) {
if (
response.right.status === 200 ||
(response.right.status as number) === 422
) {
// TODO: remove the cast of response.status to number as soon as the
// paymentmanager specs include the 422 status.
// https://www.pivotaltracker.com/story/show/161053093
yield* put(paymentCheck.success(true));
} else {
throw response.right;
}
} else {
throw Error(readablePrivacyReport(response.left));
}
} catch (e) {
if (isTimeoutError(getNetworkError(e))) {
// check if also the IO session is expired
yield* put(checkCurrentSession.request());
}
yield* put(paymentCheck.failure(e instanceof Error ? e : new Error()));
}
}
/**
* handle the start of a payment
* we already know which is the payment (idPayment) and the used wallet to pay (idWallet)
* we need a fresh PM session token to start the challenge into the PayWebViewModal
* @param pmSessionManager
*/
export function* paymentStartRequest(
pmSessionManager: SessionManager<PaymentManagerToken>
) {
const pmSessionToken: O.Option<PaymentManagerToken> = yield* call(
pmSessionManager.getNewToken
);
if (O.isSome(pmSessionToken)) {
yield* put(paymentExecuteStart.success(pmSessionToken.value));
} else {
yield* put(
paymentExecuteStart.failure(
new Error("cannot retrieve a valid PM session token")
)
);
}
}
/**
* Handles paymentDeletePaymentRequest
*/
export function* paymentDeletePaymentRequestHandler(
pagoPaClient: PaymentManagerClient,
pmSessionManager: SessionManager<PaymentManagerToken>,
action: ActionType<(typeof paymentDeletePayment)["request"]>
): Generator<ReduxSagaEffect, void, any> {
const apiPostPayment = pagoPaClient.deletePayment(action.payload.paymentId);
const request = pmSessionManager.withRefresh(apiPostPayment);
try {
const response: SagaCallReturnType<typeof request> = yield* call(request);
if (E.isRight(response)) {
if (response.right.status === 200) {
yield* put(paymentDeletePayment.success());
} else {
throw Error(`response status ${response.right.status}`);
}
} else {
throw Error(readablePrivacyReport(response.left));
}
} catch (e) {
if (isTimeoutError(getNetworkError(e))) {
// check if also the IO session is expired
yield* put(checkCurrentSession.request());
}
yield* put(
paymentDeletePayment.failure(e instanceof Error ? e : new Error())
);
}
}
//
// Nodo APIs
//
/**
* Handles paymentVerificaRequest
*/
export function* paymentVerificaRequestHandler(
getVerificaRpt: ReturnType<typeof BackendClient>["getVerificaRpt"],
action: ActionType<(typeof paymentVerifica)["request"]>
) {
yield* call(
commonPaymentVerificationProcedure,
getVerificaRpt,
action.payload.rptId,
paymentData => paymentVerifica.success(paymentData),
details => paymentVerifica.failure(details),
action
);
}
export function* commonPaymentVerificationProcedure<A extends Action>(
getVerificaRpt: ReturnType<typeof BackendClient>["getVerificaRpt"],
rptId: RptId,
successActionProvider: (paymentData: PaymentRequestsGetResponse) => A,
failureActionProvider: (details: Detail_v2Enum) => A,
action?: ActionType<(typeof paymentVerifica)["request"]>
) {
try {
const isPagoPATestEnabled: ReturnType<typeof isPagoPATestEnabledSelector> =
yield* select(isPagoPATestEnabledSelector);
const request = getVerificaRpt({
rptId: RptIdFromString.encode(rptId),
test: isPagoPATestEnabled
});
const response: SagaCallReturnType<typeof getVerificaRpt> = (yield* call(
withRefreshApiCall,
request,
action
)) as unknown as SagaCallReturnType<typeof getVerificaRpt>;
if (E.isRight(response)) {
if (response.right.status === 200) {
// Verifica succeeded
const paymentData = response.right.value;
const successAction = successActionProvider(paymentData);
yield* put(successAction);
} else if (
response.right.status === 500 ||
response.right.status === 504
) {
// Verifica failed with a 500 or 504, that usually means there was an error
// interacting with pagoPA that we can interpret
const details = response.right.value.detail_v2;
const failureAction = failureActionProvider(details);
yield* put(failureAction);
} else if (response.right.status === 401) {
// This status code does not represent an error to show to the user
// The authentication will be handled by the Fast Login token refresh procedure
} else {
throw Error(`response status ${response.right.status}`);
}
} else {
throw Error(readablePrivacyReport(response.left));
}
} catch (e) {
// Probably a timeout
const details = getWalletError(e);
const failureAction = failureActionProvider(details);
yield* put(failureAction);
}
}
/**
* Handles paymentAttivaRequest
*/
export function* paymentAttivaRequestHandler(
postAttivaRpt: ReturnType<typeof BackendClient>["postAttivaRpt"],
action: ActionType<(typeof paymentAttiva)["request"]>
) {
try {
const isPagoPATestEnabled: ReturnType<typeof isPagoPATestEnabledSelector> =
yield* select(isPagoPATestEnabledSelector);
const request = postAttivaRpt({
body: {
rptId: RptIdFromString.encode(action.payload.rptId),
codiceContestoPagamento:
action.payload.verifica.codiceContestoPagamento,
importoSingoloVersamento:
action.payload.verifica.importoSingoloVersamento
},
test: isPagoPATestEnabled
});
const response: SagaCallReturnType<typeof postAttivaRpt> = (yield* call(
withRefreshApiCall,
request,
action
)) as unknown as SagaCallReturnType<typeof postAttivaRpt>;
if (E.isRight(response)) {
if (response.right.status === 200) {
// Attiva succeeded
yield* put(paymentAttiva.success(response.right.value));
} else if (
response.right.status === 500 ||
response.right.status === 504
) {
// Attiva failed
throw Error(response.right.value.detail_v2);
} else if (response.right.status === 401) {
// This status code does not represent an error to show to the user
// The authentication will be handled by the Fast Login token refresh procedure
} else {
throw Error(`response status ${response.right.status}`);
}
} else {
throw Error(readablePrivacyReport(response.left));
}
} catch (e) {
// Probably a timeout
yield* put(paymentAttiva.failure(getWalletError(e)));
}
}
/**
* Handles paymentIdPollingRequest
*
* Polls the backend for the paymentId linked to the payment context code
*/
export function* paymentIdPollingRequestHandler(
getPaymentIdApi: ReturnType<ReturnType<typeof BackendClient>["getPaymentId"]>,
action: ActionType<(typeof paymentIdPolling)["request"]>
) {
// successfully request the payment activation
// now poll until a paymentId is made available
try {
const isPagoPATestEnabled: ReturnType<typeof isPagoPATestEnabledSelector> =
yield* select(isPagoPATestEnabledSelector);
const getPaymentId = getPaymentIdApi.e2;
const request = getPaymentId({
codiceContestoPagamento: action.payload.codiceContestoPagamento,
test: isPagoPATestEnabled
});
const response: SagaCallReturnType<typeof getPaymentId> = (yield* call(
withRefreshApiCall,
request,
action
)) as unknown as SagaCallReturnType<typeof getPaymentId>;
if (E.isRight(response)) {
// Attiva succeeded
if (response.right.status === 200) {
yield* put(paymentIdPolling.success(response.right.value.idPagamento));
} else if (response.right.status === 400) {
// Attiva failed
throw Error("PAYMENT_ID_TIMEOUT");
} else {
throw Error(`response status ${response.right.status}`);
}
} else {
throw Error(readablePrivacyReport(response.left));
}
} catch (e) {
yield* put(paymentIdPolling.failure("PAYMENT_ID_TIMEOUT"));
}
}
/**
* request the list of psp that can handle a payment from a given idWallet and idPayment
* @param getPspV2
* @param pmSessionManager
* @param action
*/
export function* getPspV2(
getPspV2: ReturnType<typeof PaymentManagerClient>["getPspV2"],
pmSessionManager: SessionManager<PaymentManagerToken>,
action: ActionType<(typeof pspForPaymentV2)["request"]>
) {
const getPspV2Request = getPspV2(action.payload);
const request = pmSessionManager.withRefresh(getPspV2Request);
try {
const response: SagaCallReturnType<typeof request> = yield* call(request);
if (E.isRight(response)) {
if (response.right.status === 200) {
const psps = response.right.value.data;
const paymentStartOrigin = yield* select(paymentStartOriginSelector);
const preferredPspsByOrigin = yield* select(
preferredPspsByOriginSelector
);
const filteredPsps = getFilteredPspsList(
psps,
paymentStartOrigin,
preferredPspsByOrigin
);
yield* put(pspForPaymentV2.success(filteredPsps));
} else {
yield* put(
pspForPaymentV2.failure(
getGenericError(Error(`response status ${response.right.status}`))
)
);
}
} else {
yield* put(
pspForPaymentV2.failure(
getGenericError(Error(readablePrivacyReport(response.left)))
)
);
}
} catch (e) {
if (isTimeoutError(getNetworkError(e))) {
// check if also the IO session is expired
yield* put(checkCurrentSession.request());
}
yield* put(pspForPaymentV2.failure(getNetworkError(e)));
}
}
/**
* @deprecated this function request and handle the psp list by using a callback approach
* it should not be used!
* Use instead {@link pspForPaymentV2} action and relative saga {@link getPspV2} to retrieve the psp list
*/
export function* getPspV2WithCallbacks(
action: ActionType<typeof pspForPaymentV2WithCallbacks>
) {
yield* put(pspForPaymentV2.request(action.payload));
const result = yield* take<
ActionType<typeof pspForPaymentV2.success | typeof pspForPaymentV2.failure>
>([pspForPaymentV2.success, pspForPaymentV2.failure]);
if (isActionOf(pspForPaymentV2.failure, result)) {
action.payload.onFailure();
} else if (isActionOf(pspForPaymentV2.success, result)) {
const psps = result.payload;
const paymentStartOrigin = yield* select(paymentStartOriginSelector);
const preferredPspsByOrigin = yield* select(preferredPspsByOriginSelector);
const filteredPsps = getFilteredPspsList(
psps,
paymentStartOrigin,
preferredPspsByOrigin
);
action.payload.onSuccess(filteredPsps);
}
}