taiga-family / taiga-ui

Angular UI Kit and components library for awesome people
https://taiga-ui.dev
Apache License 2.0
3.25k stars 452 forks source link

🚀 - Better payment system detection #2755

Closed AlexXanderGrib closed 1 year ago

AlexXanderGrib commented 2 years ago

Which @taiga-ui/* package(s) are relevant/releated to the feature request?

addon-commerce

Description

My solution is code down below. There are it's advantages

  1. Declarative
  2. Detects co-badged cards

What to do:

  1. Make TuiPaymentSystem enum-alike for better readability and add more payment systems:

    const enum TuiPaymentSystem {
    Mastercard = "mastercard",
    Electron = "electron",
    Visa = "visa",
    Maestro = "maestro",
    Mir = "mir",
    AmericanExpress = "amex",
    DinersClub = "dinersclub",
    Discover = "discover",
    JCB = "jcb",
    UnionPay = "unionpay",
    }
  2. Change detection algorithm:

// https://en.wikipedia.org/wiki/Payment_card_number#Issuer_identification_number_(IIN)
const paymentSystems: readonly [string, TuiPaymentSystem][] = [
  ["2200-2204", TuiPaymentSystem.Mir],
  ["2221-2720", TuiPaymentSystem.Mastercard],
  ["3528-3589", TuiPaymentSystem.JCB],
  ["34", TuiPaymentSystem.AmericanExpress],
  ["37", TuiPaymentSystem.AmericanExpress],
  ["36", TuiPaymentSystem.DinersClub],
  ["417500", TuiPaymentSystem.Electron],
  ["4026", TuiPaymentSystem.Electron],
  ["4508", TuiPaymentSystem.Electron],
  ["4844", TuiPaymentSystem.Electron],
  ["4913", TuiPaymentSystem.Electron],
  ["4917", TuiPaymentSystem.Electron],
  ["4", TuiPaymentSystem.Visa],
  ["5018", TuiPaymentSystem.Maestro],
  ["5020", TuiPaymentSystem.Maestro],
  ["5038", TuiPaymentSystem.Maestro],
  ["5893", TuiPaymentSystem.Maestro],
  ["54", TuiPaymentSystem.DinersClub],
  ["51-55", TuiPaymentSystem.Mastercard],
  ["622126-622925", TuiPaymentSystem.Discover],
  ["6011", TuiPaymentSystem.Discover],
  ["6304", TuiPaymentSystem.Maestro],
  ["6759", TuiPaymentSystem.Maestro],
  ["6761", TuiPaymentSystem.Maestro],
  ["6762", TuiPaymentSystem.Maestro],
  ["6763", TuiPaymentSystem.Maestro],
  ["676770", TuiPaymentSystem.Maestro],
  ["676774", TuiPaymentSystem.Maestro],
  ["644-649", TuiPaymentSystem.Discover],
  ["65", TuiPaymentSystem.Discover],

  // this is for detection of Mir-UnionPay cards. 629157 is only bin i know
  ["629157", TuiPaymentSystem.Mir], 
  ["62", TuiPaymentSystem.UnionPay],
];

function matchRange(range: string, cardNumber: string): boolean {
  if (!range.includes("-")) {
    return cardNumber.startsWith(range);
  }

  const rangeStart = parseInt(range.split("-")[0], 10);
  const rangeEnd = parseInt(range.split("-")[1], 10);
  const cardNumberNumeric = parseInt(cardNumber.slice(0, rangeStart.toString().length), 10);

  return cardNumberNumeric >= rangeStart && cardNumberNumeric <= rangeEnd;
}

function detectPaymentSystems(cardNumber: string, available?: TuiPaymentSystem[]) {
  if (typeof cardNumber !== "string" || cardNumber === "") return [];

  const paymentSystem = paymentSystems
    .filter(([range, system]) => {
      if (Array.isArray(available) && !available.includes(system)) {
        return false;
      }

      return matchRange(range, cardNumber);
    })
    .map((x) => x[1]);

  return paymentSystem;
}
  1. Add backwards compatibility
function cardNumberIs(cardNumber: string, paymentSystem: TuiPaymentSystem) {
  return detectPaymentSystems(cardNumber, [paymentSystem]).length === 1;
}

export function tuiGetPaymentSystem(
  cardNumber: string,
  available: TuiPaymentSystem[] = [
    TuiPaymentSystem.Maestro,
    TuiPaymentSystem.Mastercard,
    TuiPaymentSystem.Electron,
    TuiPaymentSystem.Visa,
    TuiPaymentSystem.Mir
  ]
) {
  return detectPaymentSystems(cardNumber, available)[0];
}

export function tuiIsMaestro(cardNumber: string) {
  return cardNumberIs(cardNumber, TuiPaymentSystem.Maestro);
}

export function tuiIsMastercard(cardNumber: string) {
  return cardNumberIs(cardNumber, TuiPaymentSystem.Mastercard);
}

export function tuiIsMir(cardNumber: string) {
  return cardNumberIs(cardNumber, TuiPaymentSystem.Mir);
}

export function tuiIsElectron(cardNumber: string) {
  return cardNumberIs(cardNumber, TuiPaymentSystem.Electron);
}

export function tuiIsVisa(cardNumber: string) {
  return cardNumberIs(cardNumber, TuiPaymentSystem.Visa);
}
waterplea commented 2 years ago

That's an interesting approach. Do you want to make a PR for it? Payment systems used to be a enum but we switch to union type so it's easier to use in templates as input. I'd like to stick to that for now. Another comment is that split methods create an array and therefore a heavy operations not fit for getters. As well as filter and map. Keep that in mind.