teamdigitale/italia-app

View on GitHub
ts/features/payments/checkout/store/reducers/index.ts

Summary

Maintainability
C
7 hrs
Test Coverage
import * as pot from "@pagopa/ts-commons/lib/pot";
import * as O from "fp-ts/lib/Option";
import { pipe } from "fp-ts/lib/function";
import _ from "lodash";
import { getType } from "typesafe-actions";
import { Bundle } from "../../../../../../definitions/pagopa/ecommerce/Bundle";
import { PaymentMethodResponse } from "../../../../../../definitions/pagopa/ecommerce/PaymentMethodResponse";
import { PaymentMethodsResponse } from "../../../../../../definitions/pagopa/ecommerce/PaymentMethodsResponse";
import { PaymentRequestsGetResponse } from "../../../../../../definitions/pagopa/ecommerce/PaymentRequestsGetResponse";
import { RptId } from "../../../../../../definitions/pagopa/ecommerce/RptId";
import { TransactionInfo } from "../../../../../../definitions/pagopa/ecommerce/TransactionInfo";
import { WalletInfo } from "../../../../../../definitions/pagopa/ecommerce/WalletInfo";
import { Wallets } from "../../../../../../definitions/pagopa/ecommerce/Wallets";
import { Action } from "../../../../../store/actions/types";
import { NetworkError } from "../../../../../utils/errors";
import { getSortedPspList } from "../../../common/utils";
import { WalletPaymentStepEnum } from "../../types";
import { WalletPaymentFailure } from "../../types/WalletPaymentFailure";
import {
  paymentsCalculatePaymentFeesAction,
  paymentsCreateTransactionAction,
  paymentsDeleteTransactionAction,
  paymentsGetPaymentDetailsAction,
  paymentsGetPaymentMethodsAction,
  paymentsGetPaymentTransactionInfoAction,
  paymentsGetPaymentUserMethodsAction,
  paymentsGetRecentPaymentMethodUsedAction,
  paymentsStartPaymentAuthorizationAction
} from "../actions/networking";
import {
  OnPaymentSuccessAction,
  initPaymentStateAction,
  selectPaymentMethodAction,
  selectPaymentPspAction,
  walletPaymentSetCurrentStep
} from "../actions/orchestration";
import { UserLastPaymentMethodResponse } from "../../../../../../definitions/pagopa/ecommerce/UserLastPaymentMethodResponse";
export const WALLET_PAYMENT_STEP_MAX = 4;

export type PaymentsCheckoutState = {
  currentStep: WalletPaymentStepEnum;
  rptId?: RptId;
  paymentDetails: pot.Pot<
    PaymentRequestsGetResponse,
    NetworkError | WalletPaymentFailure
  >;
  userWallets: pot.Pot<Wallets, NetworkError>;
  recentUsedPaymentMethod: pot.Pot<UserLastPaymentMethodResponse, NetworkError>;
  allPaymentMethods: pot.Pot<PaymentMethodsResponse, NetworkError>;
  pspList: pot.Pot<ReadonlyArray<Bundle>, NetworkError>;
  selectedWallet: O.Option<WalletInfo>;
  selectedPaymentMethod: O.Option<PaymentMethodResponse>;
  selectedPsp: O.Option<Bundle>;
  transaction: pot.Pot<TransactionInfo, NetworkError | WalletPaymentFailure>;
  authorizationUrl: pot.Pot<string, NetworkError>;
  onSuccess?: OnPaymentSuccessAction;
};

const INITIAL_STATE: PaymentsCheckoutState = {
  currentStep: WalletPaymentStepEnum.NONE,
  paymentDetails: pot.none,
  userWallets: pot.none,
  recentUsedPaymentMethod: pot.none,
  allPaymentMethods: pot.none,
  pspList: pot.none,
  selectedWallet: O.none,
  selectedPaymentMethod: O.none,
  selectedPsp: O.none,
  transaction: pot.none,
  authorizationUrl: pot.none
};

// eslint-disable-next-line complexity
const reducer = (
  state: PaymentsCheckoutState = INITIAL_STATE,
  action: Action
): PaymentsCheckoutState => {
  switch (action.type) {
    case getType(initPaymentStateAction):
      return {
        ...INITIAL_STATE,
        onSuccess: action.payload.onSuccess
      };

    case getType(walletPaymentSetCurrentStep):
      return {
        ...state,
        currentStep: _.clamp(action.payload, 1, WALLET_PAYMENT_STEP_MAX)
      };

    // Payment verification and details
    case getType(paymentsGetPaymentDetailsAction.request):
      return {
        ...state,
        rptId: action.payload,
        recentUsedPaymentMethod: pot.none,
        selectedPaymentMethod: O.none,
        selectedWallet: O.none,
        paymentDetails: pot.toLoading(state.paymentDetails)
      };
    case getType(paymentsGetPaymentDetailsAction.success):
      return {
        ...state,
        paymentDetails: pot.some(action.payload)
      };
    case getType(paymentsGetPaymentDetailsAction.failure):
      return {
        ...state,
        paymentDetails: pot.toError(state.paymentDetails, action.payload)
      };

    // User payment methods
    case getType(paymentsGetPaymentUserMethodsAction.request):
      return {
        ...state,
        userWallets: pot.toLoading(state.userWallets)
      };
    case getType(paymentsGetPaymentUserMethodsAction.success):
      return {
        ...state,
        userWallets: pot.some(action.payload)
      };
    case getType(paymentsGetPaymentUserMethodsAction.failure):
      return {
        ...state,
        userWallets: pot.toError(state.userWallets, action.payload)
      };

    // Available payment method
    case getType(paymentsGetPaymentMethodsAction.request):
      return {
        ...state,
        allPaymentMethods: pot.toLoading(state.allPaymentMethods)
      };
    case getType(paymentsGetPaymentMethodsAction.success):
      return {
        ...state,
        allPaymentMethods: pot.some(action.payload)
      };
    case getType(paymentsGetPaymentMethodsAction.failure):
      return {
        ...state,
        allPaymentMethods: pot.toError(state.allPaymentMethods, action.payload)
      };

    // Recent payment method
    case getType(paymentsGetRecentPaymentMethodUsedAction.request):
      return {
        ...state,
        recentUsedPaymentMethod: pot.toLoading(state.recentUsedPaymentMethod)
      };
    case getType(paymentsGetRecentPaymentMethodUsedAction.success):
      return {
        ...state,
        recentUsedPaymentMethod: pot.some(action.payload)
      };
    case getType(paymentsGetRecentPaymentMethodUsedAction.failure):
      return {
        ...state,
        recentUsedPaymentMethod: pot.toError(
          state.recentUsedPaymentMethod,
          action.payload
        )
      };

    case getType(selectPaymentMethodAction):
      return {
        ...state,
        selectedWallet: O.fromNullable(action.payload.userWallet),
        selectedPaymentMethod: O.fromNullable(action.payload.paymentMethod),
        // If payment method changes, reset PSP list
        selectedPsp: O.none,
        pspList: pot.none
      };

    // PSP list
    case getType(paymentsCalculatePaymentFeesAction.request):
      return {
        ...state,
        pspList: pot.toLoading(state.pspList)
      };
    case getType(paymentsCalculatePaymentFeesAction.success):
      const bundles = action.payload.bundles;
      // We choose the next step based on the PSP list lenght
      // If only 1 PSP we do not have the need to selected one
      const currentStep =
        bundles.length > 1
          ? WalletPaymentStepEnum.PICK_PSP
          : WalletPaymentStepEnum.CONFIRM_TRANSACTION;

      // Bundles are stored sorted by default sort rule
      const sortedBundles = getSortedPspList(bundles, "default");

      // Automatically select PSP if only 1 received or with `onUs` property
      const preselectedPsp =
        sortedBundles.length === 1 || sortedBundles[0]?.onUs
          ? O.some(sortedBundles[0])
          : state.selectedPsp;

      // Use the preselected PSP only if there isn't any already selected PSP
      const selectedPsp = pipe(
        state.selectedPsp,
        O.alt(() => preselectedPsp)
      );

      return {
        ...state,
        pspList: pot.some(sortedBundles),
        currentStep,
        selectedPsp
      };
    case getType(paymentsCalculatePaymentFeesAction.failure):
      return {
        ...state,
        pspList: pot.toError(state.pspList, action.payload)
      };

    case getType(selectPaymentPspAction):
      return {
        ...state,
        selectedPsp: O.some(action.payload)
      };

    // Transaction
    case getType(paymentsCreateTransactionAction.request):
    case getType(paymentsGetPaymentTransactionInfoAction.request):
    case getType(paymentsDeleteTransactionAction.request):
      return {
        ...state,
        transaction: pot.toLoading(state.transaction)
      };
    case getType(paymentsCreateTransactionAction.success):
    case getType(paymentsGetPaymentTransactionInfoAction.success):
      return {
        ...state,
        transaction: pot.some(action.payload)
      };
    case getType(paymentsCreateTransactionAction.failure):
    case getType(paymentsDeleteTransactionAction.success):
      return {
        ...state,
        transaction: pot.none
      };
    case getType(paymentsGetPaymentTransactionInfoAction.failure):
    case getType(paymentsDeleteTransactionAction.failure):
      return {
        ...state,
        transaction: pot.toError(state.transaction, action.payload)
      };

    // Authorization url
    case getType(paymentsStartPaymentAuthorizationAction.request):
      return {
        ...state,
        authorizationUrl: pot.toLoading(state.authorizationUrl)
      };
    case getType(paymentsStartPaymentAuthorizationAction.success):
      return {
        ...state,
        authorizationUrl: pot.some(action.payload.authorizationUrl)
      };
    case getType(paymentsStartPaymentAuthorizationAction.failure):
      return {
        ...state,
        authorizationUrl: pot.toError(state.authorizationUrl, action.payload)
      };
    case getType(paymentsStartPaymentAuthorizationAction.cancel):
      return {
        ...state,
        authorizationUrl: pot.none
      };
  }
  return state;
};

export default reducer;