teamdigitale/italia-app

View on GitHub
ts/features/payments/common/utils/index.ts

Summary

Maintainability
A
2 hrs
Test Coverage
import {
  IOLogoPaymentType,
  IOPaymentLogos,
  ListItemTransactionStatusWithBadge
} from "@pagopa/io-app-design-system";
import * as O from "fp-ts/lib/Option";
import { pipe } from "fp-ts/lib/function";
import _ from "lodash";
import I18n from "../../../../i18n";
import { Bundle } from "../../../../../definitions/pagopa/ecommerce/Bundle";
import { WalletApplicationStatusEnum } from "../../../../../definitions/pagopa/walletv3/WalletApplicationStatus";
import { WalletInfo } from "../../../../../definitions/pagopa/walletv3/WalletInfo";
import { PaymentSupportStatus } from "../../../../types/paymentMethodCapabilities";
import { getDateFromExpiryDate, isExpiredDate } from "../../../../utils/dates";
import { WalletPaymentPspSortType } from "../../checkout/types";
import { PaymentCardProps } from "../components/PaymentCard";
import { UIWalletInfoDetails } from "../types/UIWalletInfoDetails";
import { NoticeListItem } from "../../../../../definitions/pagopa/biz-events/NoticeListItem";
import { findFirstCaseInsensitive } from "../../../../utils/object";
import { WalletCard } from "../../../newWallet/types";
import { contentRepoUrl } from "../../../../config";
import { LevelEnum } from "../../../../../definitions/content/SectionStatus";
import { AlertVariant } from "./types";

export const TRANSACTION_LOGO_CDN = `${contentRepoUrl}/logos/organizations`;

/**
 * A simple function to get the corresponding translated badge text,
 * based on the transaction status.
 */

export const getBadgeTextByTransactionStatus = (
  transactionStatus: ListItemTransactionStatusWithBadge
) => {
  switch (transactionStatus) {
    case "failure":
      return I18n.t("global.badges.failed");
    case "cancelled":
      return I18n.t("global.badges.cancelled");
    case "reversal":
      return I18n.t("global.badges.reversal");
    case "pending":
      return I18n.t("global.badges.onGoing");
    default:
      return "";
  }
};

/**
 * Check if the given payment method is expired
 * right(true) if it is expired, right(false) if it is still valid
 * left if expiring date can't be evaluated
 * @param paymentMethod
 */
export const isPaymentMethodExpired = (
  details?: UIWalletInfoDetails
): boolean =>
  pipe(
    details?.expiryDate,
    O.fromNullable,
    O.chainNullableK(getDateFromExpiryDate),
    O.chainNullableK(isExpiredDate),
    O.getOrElse(() => false)
  );

/**
 * true if the given paymentMethod supports the given walletFunction
 * @param paymentMethod
 * @param walletFunction
 */
export const hasApplicationEnabled = (
  paymentMethod: WalletInfo | undefined,
  walletApplication: string
): boolean =>
  paymentMethod !== undefined &&
  paymentMethod.applications.some(
    application =>
      application.name === walletApplication &&
      application.status === WalletApplicationStatusEnum.ENABLED
  );
/**
 * return true if the payment method has the payment feature
 */
export const hasPaymentFeature = (paymentMethod: WalletInfo): boolean =>
  hasApplicationEnabled(paymentMethod, "PAGOPA");

/**
 * Check if a payment method is supported or not
 * If the payment method have the enableable function pagoPA, can always pay ("available")
 * "available" -> can pay
 * "arriving" -> will pay
 * "notAvailable" -> can't pay
 * "onboardableNotImplemented" -> can onboard a card that can pay but is not yet implemented
 */
export const isPaymentSupported = (
  paymentMethod: WalletInfo
): PaymentSupportStatus => {
  const paymentSupported: O.Option<PaymentSupportStatus> = hasPaymentFeature(
    paymentMethod
  )
    ? O.some("available")
    : O.none;

  const notAvailableCustomRepresentation = O.some(
    "notAvailable" as PaymentSupportStatus
  );

  return pipe(
    paymentSupported,
    O.alt(() => notAvailableCustomRepresentation),
    O.getOrElseW(() => "notAvailable" as const)
  );
};

export const WALLET_PAYMENT_TERMS_AND_CONDITIONS_URL =
  "https://www.pagopa.gov.it/it/prestatori-servizi-di-pagamento/elenco-PSP-attivi/";

/**
 * Function that returns a sorted list of psp based on the given sortType
 * The sortType can be: "name", "amount" or "default"
 */
export const getSortedPspList = (
  pspList: ReadonlyArray<Bundle>,
  sortType: WalletPaymentPspSortType
) => {
  switch (sortType) {
    case "name":
      return _.orderBy(pspList, psp => psp.pspBusinessName);
    case "amount":
      return _.orderBy(
        pspList,
        ["taxPayerFee", "pspBusinessName"],
        ["asc", "asc"]
      );
    case "default":
    default:
      return _.orderBy(
        pspList,
        ["onUs", "taxPayerFee", "pspBusinessName"],
        ["desc", "asc", "asc"]
      );
  }
};

export const getPaymentCardPropsFromWalletInfo = (
  wallet: WalletInfo
): PaymentCardProps => {
  const details = wallet.details as UIWalletInfoDetails;
  const isExpired = isPaymentMethodExpired(details);

  return {
    hpan: details.lastFourDigits,
    brand: details.brand,
    expireDate: getDateFromExpiryDate(details.expiryDate),
    holderEmail: details.maskedEmail,
    holderPhone: details.maskedNumber,
    isExpired
  };
};

export const getPaymentLogoFromWalletDetails = (
  details: UIWalletInfoDetails
): IOLogoPaymentType | undefined => {
  if (details.maskedEmail !== undefined) {
    return "payPal";
  } else if (details.maskedNumber !== undefined) {
    return "bancomatPay";
  } else {
    return pipe(
      details.brand,
      O.fromNullable,
      O.chain(findFirstCaseInsensitive(IOPaymentLogos)),
      // eslint-disable-next-line @typescript-eslint/no-shadow
      O.map(([logoName, _]) => logoName as IOLogoPaymentType),
      O.toUndefined
    );
  }
};

export const getTransactionLogo = (transaction: NoticeListItem) =>
  pipe(
    transaction.payeeTaxCode,
    O.fromNullable,
    O.map(
      taxCode => `${TRANSACTION_LOGO_CDN}/${taxCode.replace(/^0+/, "")}.png`
    )
  );

export const mapWalletIdToCardKey = (walletId: string) => `method_${walletId}`;

export const mapWalletsToCards = (
  wallets: ReadonlyArray<WalletInfo>
): ReadonlyArray<WalletCard> =>
  wallets.map<WalletCard>(wallet => ({
    ...getPaymentCardPropsFromWalletInfo(wallet),
    key: mapWalletIdToCardKey(wallet.walletId),
    type: "payment",
    category: "payment",
    walletId: wallet.walletId
  }));

/**
 * Function that returns a formatted payment notice number
 * by placing two spaces between every four numbers
 */
export const formatPaymentNoticeNumber = (noticeNumber: string) =>
  noticeNumber.replace(/(\d{4})/g, "$1  ").trim();

/**
 * Function that returns the alert variant based on the given LevelEnum provided
 * by the backend config file
 */
export const getAlertVariant = (level: LevelEnum): AlertVariant => {
  switch (level) {
    case LevelEnum.critical:
      return "error";
    case LevelEnum.normal:
      return "info";
    case LevelEnum.warning:
      return "warning";
    default:
      return "info";
  }
};