teamdigitale/italia-app

View on GitHub
ts/store/actions/wallet/payment.ts

Summary

Maintainability
A
0 mins
Test Coverage
import {
  OmitStatusFromResponse,
  TypeofApiResponse
} from "@pagopa/ts-commons/lib/requests";
import {
  ActionType,
  createAsyncAction,
  createStandardAction
} from "typesafe-actions";
import { RptId } from "@pagopa/io-pagopa-commons/lib/pagopa";

import { PaymentActivationsPostResponse } from "../../../../definitions/backend/PaymentActivationsPostResponse";
import { Detail_v2Enum as PaymentProblemErrorEnum } from "../../../../definitions/backend/PaymentProblemJson";
import { PaymentRequestsGetResponse } from "../../../../definitions/backend/PaymentRequestsGetResponse";
import { CheckPaymentUsingGETT } from "../../../../definitions/pagopa/requestTypes";
import {
  PaymentManagerToken,
  RawPaymentMethod,
  Transaction,
  Wallet
} from "../../../types/pagopa";
import { PayloadForAction } from "../../../types/utils";
import {
  EntrypointRoute,
  PaymentStartPayload
} from "../../reducers/wallet/payment";
import { OutcomeCodesKey } from "../../../types/outcomeCode";
import { NetworkError } from "../../../utils/errors";
import { PspData } from "../../../../definitions/pagopa/PspData";
import { fetchWalletsFailure, fetchWalletsSuccess } from "./wallets";

/**
 * IMPORTANT!
 *
 * The payment flow is quite complex and involves more than two actors.
 * Please refer to https://docs.google.com/presentation/d/11rEttb7lJYlRqgFpl4QopyjFmjt2Q0K8uis6JhAQaCw/edit#slide=id.p
 * and make sure you understand it _before_ working on it.
 */

export type PaymentStartOrigin =
  | "message"
  | "qrcode_scan"
  | "poste_datamatrix_scan"
  | "manual_insertion"
  | "donation";

/**
 * Resets the payment state before starting a new payment
 */
export const paymentInitializeState = createStandardAction(
  "PAYMENT_INITIALIZE_STATE"
)();

/**
 * Track the route whence the payment started
 */
export const paymentInitializeEntrypointRoute = createStandardAction(
  "PAYMENT_ENTRYPOINT_ROUTE"
)<EntrypointRoute>();

/**
 * For back to entrypoint (where the payment is initiated)
 */
export const backToEntrypointPayment = createStandardAction(
  "BACK_TO_PAYMENT_ENTRYPOINT_ROUTE"
)();

//
// verifica
//

// the error is undefined in case we weren't able to decode it, it should be
// interpreted as a generic error
export const paymentVerifica = createAsyncAction(
  "PAYMENT_VERIFICA_REQUEST",
  "PAYMENT_VERIFICA_SUCCESS",
  "PAYMENT_VERIFICA_FAILURE"
)<
  { rptId: RptId; startOrigin: PaymentStartOrigin },
  PaymentRequestsGetResponse,
  keyof typeof PaymentProblemErrorEnum | undefined
>();

//
// attiva
//

type paymentAttivaRequestPayload = Readonly<{
  rptId: RptId;
  verifica: PaymentRequestsGetResponse;
}>;

// the error is undefined in case we weren't able to decode it, it should be
// interpreted as a generic error
export const paymentAttiva = createAsyncAction(
  "PAYMENT_ATTIVA_REQUEST",
  "PAYMENT_ATTIVA_SUCCESS",
  "PAYMENT_ATTIVA_FAILURE"
)<
  paymentAttivaRequestPayload,
  PaymentActivationsPostResponse,
  PaymentProblemErrorEnum | undefined
>();

//
// paymentId polling
//

// the error is undefined in case we weren't able to decode it, it should be
// interpreted as a generic error
export const paymentIdPolling = createAsyncAction(
  "PAYMENT_ID_POLLING_REQUEST",
  "PAYMENT_ID_POLLING_SUCCESS",
  "PAYMENT_ID_POLLING_FAILURE"
)<PaymentRequestsGetResponse, string, "PAYMENT_ID_TIMEOUT" | undefined>();

//
// check payment
//

export const paymentCheck = createAsyncAction(
  "PAYMENT_CHECK_REQUEST",
  "PAYMENT_CHECK_SUCCESS",
  "PAYMENT_CHECK_FAILURE"
)<
  string,
  true,
  | OmitStatusFromResponse<TypeofApiResponse<CheckPaymentUsingGETT>, 200>
  | undefined
  | Error
>();

//
// Update Wallet PSP request and responses
//

type WalletUpdatePspRequestPayload = Readonly<{
  psp: PspData;
  wallet: Wallet;
  idPayment: string;
  onSuccess?: (
    action: ActionType<(typeof paymentUpdateWalletPsp)["success"]>
  ) => void;
  onFailure?: (
    action: ActionType<(typeof paymentUpdateWalletPsp)["failure"]>
  ) => void;
}>;

export const paymentUpdateWalletPsp = createAsyncAction(
  "PAYMENT_UPDATE_WALLET_PSP_REQUEST",
  "PAYMENT_UPDATE_WALLET_PSP_SUCCESS",
  "PAYMENT_UPDATE_WALLET_PSP_FAILURE"
)<
  WalletUpdatePspRequestPayload,
  {
    wallets: PayloadForAction<typeof fetchWalletsSuccess>;
    updatedWallet: Wallet;
  },
  PayloadForAction<typeof fetchWalletsFailure>
>();

//
// execute payment
//

/**
 * user wants to pay
 * - request: we already know the idPayment and the idWallet used to pay, we need a fresh PM session token
 * - success: we got a fresh PM session token
 * - failure: we can't get a fresh PM session token
 */
export const paymentExecuteStart = createAsyncAction(
  "PAYMENT_EXECUTE_START_REQUEST",
  "PAYMENT_EXECUTE_START_SUCCESS",
  "PAYMENT_EXECUTE_START_FAILURE"
)<PaymentStartPayload, PaymentManagerToken, Error>();

export type PaymentWebViewEndReason = "USER_ABORT" | "EXIT_PATH";
export type PaymentMethodType =
  | Extract<RawPaymentMethod["kind"], "CreditCard" | "PayPal" | "BPay">
  | "Unknown";
// event fired when the paywebview ends its challenge (used to reset payment values)
export const paymentWebViewEnd = createStandardAction("PAYMENT_WEB_VIEW_END")<{
  reason: PaymentWebViewEndReason;
  paymentMethodType: PaymentMethodType;
}>();

// used to accumulate all the urls browsed into the pay webview
export const paymentRedirectionUrls = createStandardAction(
  "PAYMENT_NAVIGATION_URLS"
)<ReadonlyArray<string>>();

//
// Signal the completion of a payment
//

type PaymentCompletedSuccessPayload = Readonly<
  | {
      kind: "COMPLETED";
      // TODO Transaction is not available, add it when PM makes it available again
      //  see https://www.pivotaltracker.com/story/show/177067134
      transaction: Transaction | undefined;
      rptId: RptId;
    }
  | {
      kind: "DUPLICATED";
      rptId: RptId;
    }
>;

export const paymentCompletedSuccess = createStandardAction(
  "PAYMENT_COMPLETED_SUCCESS"
)<PaymentCompletedSuccessPayload>();

export const paymentCompletedFailure = createStandardAction(
  "PAYMENT_COMPLETED_FAILURE"
)<{ outcomeCode: OutcomeCodesKey | undefined; paymentId: string }>();

//
// delete an ongoing payment
//

type PaymentDeletePaymentRequestPayload = Readonly<{
  paymentId: string;
}>;

export const paymentDeletePayment = createAsyncAction(
  "PAYMENT_DELETE_PAYMENT_REQUEST",
  "PAYMENT_DELETE_PAYMENT_SUCCESS",
  "PAYMENT_DELETE_PAYMENT_FAILURE"
)<PaymentDeletePaymentRequestPayload, void, Error>();

export const runDeleteActivePaymentSaga = createStandardAction(
  "PAYMENT_RUN_DELETE_ACTIVE_PAYMENT_SAGA"
)();

// abort payment just before pay
export const abortRunningPayment = createStandardAction(
  "PAYMENT_ABORT_RUNNING_PAYMENT"
)();

/**
 * the psp selected for the payment
 */
export const pspSelectedForPaymentV2 = createStandardAction(
  "PAYMENT_PSP_V2_SELECTED"
)<PspData>();

/**
 * get the list of psp that can handle the payment with the given paymentMethod
 */
export const pspForPaymentV2 = createAsyncAction(
  "PAYMENT_PSP_V2_REQUEST",
  "PAYMENT_PSP_V2_SUCCESS",
  "PAYMENT_PSP_V2_FAILURE"
)<
  { idWallet: number; idPayment: string },
  ReadonlyArray<PspData>,
  NetworkError
>();

/**
 * @deprecated
 * this action is used only to mimic the existing payment logic (callbacks hell 😈)
 * use {@link pspForPaymentV2} instead
 */
export const pspForPaymentV2WithCallbacks = createStandardAction(
  "PAYMENT_PSP_V2_WITH_CALLBACKS"
)<{
  idWallet: number;
  idPayment: string;
  onSuccess: (psp: ReadonlyArray<PspData>) => void;
  onFailure: () => void;
}>();

// This action is used to notify that wallet sagas handlers have been initialized
// Used by the Fast Login sagas to wait before dispatching any pending actions
export const walletPaymentHandlersInitialized = createStandardAction(
  "WALLET_PAYMENT_HANDLERS_INITIALIZED"
)();

/**
 * All possible payment actions
 */
export type PaymentActions =
  | ActionType<typeof paymentInitializeState>
  | ActionType<typeof paymentInitializeEntrypointRoute>
  | ActionType<typeof backToEntrypointPayment>
  | ActionType<typeof paymentUpdateWalletPsp>
  | ActionType<typeof paymentVerifica>
  | ActionType<typeof paymentAttiva>
  | ActionType<typeof paymentIdPolling>
  | ActionType<typeof paymentWebViewEnd>
  | ActionType<typeof paymentCheck>
  | ActionType<typeof paymentExecuteStart>
  | ActionType<typeof paymentCompletedSuccess>
  | ActionType<typeof paymentCompletedFailure>
  | ActionType<typeof paymentDeletePayment>
  | ActionType<typeof runDeleteActivePaymentSaga>
  | ActionType<typeof abortRunningPayment>
  | ActionType<typeof paymentRedirectionUrls>
  | ActionType<typeof pspForPaymentV2>
  | ActionType<typeof pspSelectedForPaymentV2>
  | ActionType<typeof pspForPaymentV2WithCallbacks>
  | ActionType<typeof walletPaymentHandlersInitialized>;