Skip to content

Instantly share code, notes, and snippets.

@gusribeiro
Last active April 8, 2025 20:36
Show Gist options
  • Save gusribeiro/263a165db774f5d78251 to your computer and use it in GitHub Desktop.
Save gusribeiro/263a165db774f5d78251 to your computer and use it in GitHub Desktop.
Regex para identificar bandeiras do cartão de crédito
Amex: ^3[47][0-9]{13}$
Aura: /^((?!504175))^((?!5067))(^50[0-9])/
Banese Card: '^636117'
Cabal: '(60420[1-9]|6042[1-9][0-9]|6043[0-9]{2}|604400)'
Diners: '(36[0-8][0-9]{3}|369[0-8][0-9]{2}|3699[0-8][0-9]|36999[0-9])
Discover: /^6(?:011|5[0-9]{2})[0-9]{12}/
Elo: /^4011(78|79)|^43(1274|8935)|^45(1416|7393|763(1|2))|^50(4175|6699|67[0-6][0-9]|677[0-8]|9[0-8][0-9]{2}|99[0-8][0-9]|999[0-9])|^627780|^63(6297|6368|6369)|^65(0(0(3([1-3]|[5-9])|4([0-9])|5[0-1])|4(0[5-9]|[1-3][0-9]|8[5-9]|9[0-9])|5([0-2][0-9]|3[0-8]|4[1-9]|[5-8][0-9]|9[0-8])|7(0[0-9]|1[0-8]|2[0-7])|9(0[1-9]|[1-6][0-9]|7[0-8]))|16(5[2-9]|[6-7][0-9])|50(0[0-9]|1[0-9]|2[1-9]|[3-4][0-9]|5[0-8]))/
Fort Brasil: '^628167'
GrandCard: '^605032'
Hipercard: ^606282|^3841(?:[0|4|6]{1})0
JCB: /^(?:2131|1800|35\d{3})\d{11}/
Mastercard: ^((5(([1-2]|[4-5])[0-9]{8}|0((1|6)([0-9]{7}))|3(0(4((0|[2-9])[0-9]{5})|([0-3]|[5-9])[0-9]{6})|[1-9][0-9]{7})))|((508116)\\d{4,10})|((502121)\\d{4,10})|((589916)\\d{4,10})|(2[0-9]{15})|(67[0-9]{14})|(506387)\\d{4,10})
Personal Card: '^636085'
Sorocred: '^627892|^636414'
Valecard: '^606444|^606458|^606482'
Visa: ^4[0-9]{15}$
@Fer-code
Copy link

Fer-code commented Nov 14, 2023

Ola pessoal, fiz as correcoes que vcs falaram. Porem percebi que HIPERCARD esta desatualizado. Por isso fiz um novo validando todos que estao nesse site: "https://bincheck.io/pt/br/itau-unibanco,-s.a./hipercard".


HIPERCARD ATUALIZADO: /^(606282\d{10}(\d{3})?)|(3841\d{12})|(637\d{13})$/


AO TODO:

'AMERICAN_EXPRESS': /^3[47]\d{13}$/,

 'DINERS_CLUB': /^3(?:0[0-5]|[68]\d)\d{11}$/,

'ELO': /(4011|431274|438935|451416|457393|4576|457631|457632|504175|627780|636297|636368|636369|(6503[1-3])|(6500(3[5-9]|4[0-9]|5[0-1]))|(6504(0[5-9]|1[0-9]|2[0-9]|3[0-9]))|(650(48[5-9]|49[0-9]|50[0-9]|51[1-9]|52[0-9]|53[0-7]))|(6505(4[0-9]|5[0-9]|6[0-9]|7[0-9]|8[0-9]|9[0-8]))|(6507(0[0-9]|1[0-8]))|(6507(2[0-7]))|(650(90[1-9]|91[0-9]|920))|(6516(5[2-9]|6[0-9]|7[0-9]))|(6550(0[0-9]|1[1-9]))|(6550(2[1-9]|3[0-9]|4[0-9]|5[0-8]))|(506(699|77[0-8]|7[1-6][0-9))|(509([0-9][0-9][0-9])))/,

 'HIPERCARD': /^(606282\d{10}(\d{3})?)|(3841\d{12})|(637\d{13})$/,

'MASTERCARD': /^5[1-5]\d{14}$|^2(?:2(?:2[1-9]|[3-9]\d)|[3-6]\d\d|7(?:[01]\d|20))\d{12}$/,

 'VISA': /^(?!504175|506699|5067|509|6500|6501|4011(78|79)|43(1274|8935)|45(1416|7393|763(1|2))|50(4175|6699|67[0-6][0-9]|677[0-8]|9[0-8][0-9]{2}|99[0-8][0-9]|999[0-9])|627780|63(6297|6368|6369)|65(0(0(3([1-3]|[5-9])|4([0-9])|5[0-1])|4(0[5-9]|[1-3][0-9]|8[5-9]|9[0-9])|5([0-2][0-9]|3[0-8]|4[1-9]|[5-8][0-9]|9[0-8])|7(0[0-9]|1[0-8]|2[0-7])|9(0[1-9]|[1-6][0-9]|7[0-8]))|16(5[2-9]|[6-7][0-9])|50(0[0-9]|1[0-9]|2[1-9]|[3-4][0-9]|5[0-8])))4[0-9]{12}(?:[0-9]{3})?$/

@IkaroFox
Copy link

IkaroFox commented Nov 15, 2023

@Fer-code , show de bola, ficou top viu, vou até utilizar kkk

image

@Fer-code
Copy link

@Fer-code , show de bola, ficou top viu, vou até utilizar kkk

image

Valeuuu! Mais atualizações vamos mandando ai :)

@MMortari
Copy link

Boa tarde pessoal,

Minha pequena contribuição, segue uma lista com alguns cartões que estou colecionando de alguns provedores para teste.

const cards = {
  // PagSeguro
  // https://dev.pagbank.uol.com.br/reference/cartoes-de-teste
  '4539620659922097': EnumCardBrand.VISA,
  '4929291898380766': EnumCardBrand.VISA,
  '5240082975622454': EnumCardBrand.MASTERCARD,
  '5530062640663264': EnumCardBrand.MASTERCARD,
  '345817690311361': EnumCardBrand.AMEX,
  '372938001199778': EnumCardBrand.AMEX,
  '4514161122113757': EnumCardBrand.ELO,
  '4389350446134811': EnumCardBrand.ELO,
  '6062828598919021': EnumCardBrand.HIPERCARD,
  '6062822916014409': EnumCardBrand.HIPERCARD,
  // https://dev.pagbank.uol.com.br/reference/criar-pagar-pedido-com-3ds-validacao-pagbank
  '4000000000002701': EnumCardBrand.VISA,
  '5200000000001005': EnumCardBrand.MASTERCARD,
  '6505050000001000': EnumCardBrand.ELO,
  '4000000000002503': EnumCardBrand.VISA,
  '5200000000001096': EnumCardBrand.MASTERCARD,
  '6505050000001091': EnumCardBrand.ELO,
  '4000000000002925': EnumCardBrand.VISA,
  '5200000000001013': EnumCardBrand.MASTERCARD,
  '6505050000001018': EnumCardBrand.ELO,
  '4000000000002370': EnumCardBrand.VISA,
  '5200000000001104': EnumCardBrand.MASTERCARD,
  '6505050000001109': EnumCardBrand.ELO,
  // Mercado pago
  // https://www.mercadopago.com.br/developers/pt/docs/checkout-api/additional-content/your-integrations/test/cards
  '5067766783888311': EnumCardBrand.ELO,
  '5031433215406351': EnumCardBrand.MASTERCARD,
  '4235647728025682': EnumCardBrand.VISA,
  '375365153556885': EnumCardBrand.AMEX,
  // Gerados
  // https://www.vccgenerator.org/
  '5098176533461570': EnumCardBrand.ELO,
  '5067349319312260': EnumCardBrand.ELO,
  '5098193894226609': EnumCardBrand.ELO,
  '5091084515027541': EnumCardBrand.ELO,
  '5098177024386094': EnumCardBrand.ELO,
  '4391395999142147': EnumCardBrand.VISA,
  '4103922157715577': EnumCardBrand.VISA,
  '4380208328771358': EnumCardBrand.VISA,
  '4297672575052840': EnumCardBrand.VISA,
  '4380277454970936': EnumCardBrand.VISA,
  '5551555128049017': EnumCardBrand.MASTERCARD,
  '5268003770982262': EnumCardBrand.MASTERCARD,
  '5448382804657501': EnumCardBrand.MASTERCARD,
  '5582852950163914': EnumCardBrand.MASTERCARD,
  '5448384161742298': EnumCardBrand.MASTERCARD,
  '340205640990176': EnumCardBrand.AMEX,
  '340205958483061': EnumCardBrand.AMEX,
  '340205254489929': EnumCardBrand.AMEX,
  '340205493225258': EnumCardBrand.AMEX,
  '340205571301401': EnumCardBrand.AMEX,
};

@GabrielLasso
Copy link

Implementei essa função em typescript e testei os cartões que o @MMortari mandou, alguns falharam:

export function getCardBrandName (cardNumber: string): string | null {
  const numbers = cardNumber.replace(/[^0-9]+/g, '')

  const cards = {
    amex: /^3[47][0-9]{13}$/,
    aura: /^((?!504175))^((?!5067))(^50[0-9])/,
    banese_card: /^636117/,
    cabal: /(60420[1-9]|6042[1-9][0-9]|6043[0-9]{2}|604400)/,
    diners: /(36[0-8][0-9]{3}|369[0-8][0-9]{2}|3699[0-8][0-9]|36999[0-9])/,
    discover: /^6(?:011|5[0-9]{2})[0-9]{12}/,
    elo: /^4011(78|79)|^43(1274|8935)|^45(1416|7393|763(1|2))|^50(4175|6699|67[0-6][0-9]|677[0-8]|9[0-8][0-9]{2}|99[0-8][0-9]|999[0-9])|^627780|^63(6297|6368|6369)|^65(0(0(3([1-3]|[5-9])|4([0-9])|5[0-1])|4(0[5-9]|[1-3][0-9]|8[5-9]|9[0-9])|5([0-2][0-9]|3[0-8]|4[1-9]|[5-8][0-9]|9[0-8])|7(0[0-9]|1[0-8]|2[0-7])|9(0[1-9]|[1-6][0-9]|7[0-8]))|16(5[2-9]|[6-7][0-9])|50(0[0-9]|1[0-9]|2[1-9]|[3-4][0-9]|5[0-8]))/,
    fort_brasil: /^628167/,
    grand_card: /^605032/,
    hipercard: /^606282|^3841(?:[0|4|6]{1})0/,
    jbc: /^(?:2131|1800|35\d{3})\d{11}/,
    mastercard: /^((5(([1-2]|[4-5])[0-9]{8}|0((1|6)([0-9]{7}))|3(0(4((0|[2-9])[0-9]{5})|([0-3]|[5-9])[0-9]{6})|[1-9][0-9]{7})))|((508116)\\d{4,10})|((502121)\\d{4,10})|((589916)\\d{4,10})|(2[0-9]{15})|(67[0-9]{14})|(506387)\\d{4,10})/,
    personal_card: /^636085/,
    sorocred: /^627892|^636414/,
    valecard: /^606444|^606458|^606482/,
    visa: /^4[0-9]{15}$/
  }

  const match = Object.entries(cards).find(([_, value]) => {
    if (value.test(numbers)) {
      return true
    }
    return false
  })

  if (match) {
    return match[0]
  }

  return null
}

6505050000001000: Esperado: Elo. Resultado: Discover
6505050000001091: Esperado: Elo. Resultado: Discover
6505050000001018: Esperado: Elo. Resultado: Discover
6505050000001109: Esperado: Elo. Resultado: Discover
5031433215406351: Esperado: Mastercard. Resultado: Aura
5098176533461570: Esperado: Elo. Resultado: Aura
5098193894226609: Esperado: Elo. Resultado: Aura
5091084515027541: Esperado: Elo. Resultado: Aura
5098177024386094: Esperado: Elo. Resultado: Aura

@GabrielLasso
Copy link

Comlementando, os cartões que deram errado passam na REGEX da bandeira esperada, mas também passam na da Discover/Aura. Eu comentei essas duas bandeiras e a maioria passou, só o 5031433215406351 que não está passando na REGEX do mastercard

@IkaroFox
Copy link

IkaroFox commented Apr 5, 2024

Implementei essa função em typescript e testei os cartões que o @MMortari mandou, alguns falharam:

export function getCardBrandName (cardNumber: string): string | null {
  const numbers = cardNumber.replace(/[^0-9]+/g, '')

  const cards = {
    amex: /^3[47][0-9]{13}$/,
    aura: /^((?!504175))^((?!5067))(^50[0-9])/,
    banese_card: /^636117/,
    cabal: /(60420[1-9]|6042[1-9][0-9]|6043[0-9]{2}|604400)/,
    diners: /(36[0-8][0-9]{3}|369[0-8][0-9]{2}|3699[0-8][0-9]|36999[0-9])/,
    discover: /^6(?:011|5[0-9]{2})[0-9]{12}/,
    elo: /^4011(78|79)|^43(1274|8935)|^45(1416|7393|763(1|2))|^50(4175|6699|67[0-6][0-9]|677[0-8]|9[0-8][0-9]{2}|99[0-8][0-9]|999[0-9])|^627780|^63(6297|6368|6369)|^65(0(0(3([1-3]|[5-9])|4([0-9])|5[0-1])|4(0[5-9]|[1-3][0-9]|8[5-9]|9[0-9])|5([0-2][0-9]|3[0-8]|4[1-9]|[5-8][0-9]|9[0-8])|7(0[0-9]|1[0-8]|2[0-7])|9(0[1-9]|[1-6][0-9]|7[0-8]))|16(5[2-9]|[6-7][0-9])|50(0[0-9]|1[0-9]|2[1-9]|[3-4][0-9]|5[0-8]))/,
    fort_brasil: /^628167/,
    grand_card: /^605032/,
    hipercard: /^606282|^3841(?:[0|4|6]{1})0/,
    jbc: /^(?:2131|1800|35\d{3})\d{11}/,
    mastercard: /^((5(([1-2]|[4-5])[0-9]{8}|0((1|6)([0-9]{7}))|3(0(4((0|[2-9])[0-9]{5})|([0-3]|[5-9])[0-9]{6})|[1-9][0-9]{7})))|((508116)\\d{4,10})|((502121)\\d{4,10})|((589916)\\d{4,10})|(2[0-9]{15})|(67[0-9]{14})|(506387)\\d{4,10})/,
    personal_card: /^636085/,
    sorocred: /^627892|^636414/,
    valecard: /^606444|^606458|^606482/,
    visa: /^4[0-9]{15}$/
  }

  const match = Object.entries(cards).find(([_, value]) => {
    if (value.test(numbers)) {
      return true
    }
    return false
  })

  if (match) {
    return match[0]
  }

  return null
}

6505050000001000: Esperado: Elo. Resultado: Discover 6505050000001091: Esperado: Elo. Resultado: Discover 6505050000001018: Esperado: Elo. Resultado: Discover 6505050000001109: Esperado: Elo. Resultado: Discover 5031433215406351: Esperado: Mastercard. Resultado: Aura 5098176533461570: Esperado: Elo. Resultado: Aura 5098193894226609: Esperado: Elo. Resultado: Aura 5091084515027541: Esperado: Elo. Resultado: Aura 5098177024386094: Esperado: Elo. Resultado: Aura

Interessante esse comportamento, vou fazer alguns testes aqui e quem sabe com um negative lookahead ele funcione corretamente, posteriormente comentarei com um retorno ...

@gfucci
Copy link

gfucci commented Dec 9, 2024

Comlementando, os cartões que deram errado passam na REGEX da bandeira esperada, mas também passam na da Discover/Aura. Eu comentei essas duas bandeiras e a maioria passou, só o 5031433215406351 que não está passando na REGEX do mastercard

Isso acontece porque o padrão desses cartões ELO realmente batem com o padrão da discover, porém por algum motivo a caixa registrou o BIN antes (não sei como funciona esses registros de BIN). Essas exceções já estão no regex da elo, podemos ver na parte "|6505[0-2][0-9]". Se jogarmos no bincheck.io inicialmente ele também da discover, mas se fizer o POST pra verificar ele irá retornar elo.

pra resolver podemos criar um switch e dar break antes para não continuar a verificação, e sempre verificar o ELO primeiro. Um exemplo em PHP

$bandeira = match (1) {
            preg_match(
                '/^4011(78|79)|^43(1274|8935)|^45(1416|7393|763(1|2))|^504175|^627780|^63(6297|6368|6369)|(65003[5-9]|65004[0-9]|65005[01])|(65040[5-9]|6504[1-3][0-9])|(65048[5-9]|65049[0-9]|6505[0-2][0-9]|65053[0-8])|(65054[1-9]|6505[5-8][0-9]|65059[0-8])|(65070[0-9]|65071[0-8])|(65072[0-7])|(65090[1-9]|6509[1-6][0-9]|65097[0-8])|(65165[2-9]|6516[67][0-9])|(65500[0-9]|65501[0-9])|(65502[1-9]|6550[34][0-9]|65505[0-8])|^(506699|5067[0-6][0-9]|50677[0-8])|^(509[0-8][0-9]{2}|5099[0-8][0-9]|50999[0-9])|^65003[1-3]|^(65003[5-9]|65004\d|65005[0-1])|^(65040[5-9]|6504[1-3]\d)|^(65048[5-9]|65049\d|6505[0-2]\d|65053[0-8])|^(65054[1-9]|6505[5-8]\d|65059[0-8])|^(65070\d|65071[0-8])|^65072[0-7]|^(65090[1-9]|65091\d|650920)|^(65165[2-9]|6516[6-7]\d)|^(65500\d|65501\d)|^(65502[1-9]|6550[3-4]\d|65505[0-8])/', 
                $cartaoNumero
            ) => 'elo',
            preg_match('/^4[0-9]{12}(?:[0-9]{3})?/', $cartaoNumero) => 'visa',
            preg_match('/^((5(([1-2]|[4-5])[0-9]{8}|0((1|6)([0-9]{7}))|3(0(4((0|[2-9])[0-9]{5})|([0-3]|[5-9])[0-9]{6})|[1-9][0-9]{7})))|((508116)\\d{4,10})|((502121)\\d{4,10})|((589916)\\d{4,10})|(2[0-9]{15})|(67[0-9]{14})|(506387)\\d{4,10})/', $cartaoNumero) => 'mastercard',
            preg_match('/^3[47][0-9]{13}/', $cartaoNumero) => 'amexs',
            preg_match('/^65[4-9][0-9]{13}|64[4-9][0-9]{13}|6011[0-9]{12}|(622(?:12[6-9]|1[3-9][0-9]|[2-8][0-9][0-9]|9[01][0-9]|92[0-5])[0-9]{10})/', $cartaoNumero) => 'discover',
            preg_match('/^3(?:0[0-5]|[68][0-9])[0-9]{11}/', $cartaoNumero) => 'diners',
            preg_match('/^(?:2131|1800|35\d{3})\d{11}/', $cartaoNumero) => 'jcb',
            preg_match('/^(606282\d{10}(\d{3})?)|(3841\d{12})|(637\d{13})/', $cartaoNumero) => 'hipercard',
            default => ''
        };

Agora todos os números vão bater corretamente!

Esta bandeira 5031433215406351 no bincheck.io deu que não existe, então pode ser retirada.

@Quost
Copy link

Quost commented Jan 15, 2025

Eu pequei os cartões dessa resposta do @MMortari :

Boa tarde pessoal,

Minha pequena contribuição, segue uma lista com alguns cartões que estou colecionando de alguns provedores para teste.

const cards = {
  // PagSeguro
  // https://dev.pagbank.uol.com.br/reference/cartoes-de-teste
  '4539620659922097': EnumCardBrand.VISA,
  '4929291898380766': EnumCardBrand.VISA,
  '5240082975622454': EnumCardBrand.MASTERCARD,
  '5530062640663264': EnumCardBrand.MASTERCARD,
  '345817690311361': EnumCardBrand.AMEX,
  '372938001199778': EnumCardBrand.AMEX,
  '4514161122113757': EnumCardBrand.ELO,
  '4389350446134811': EnumCardBrand.ELO,
  '6062828598919021': EnumCardBrand.HIPERCARD,
  '6062822916014409': EnumCardBrand.HIPERCARD,
  // https://dev.pagbank.uol.com.br/reference/criar-pagar-pedido-com-3ds-validacao-pagbank
  '4000000000002701': EnumCardBrand.VISA,
  '5200000000001005': EnumCardBrand.MASTERCARD,
  '6505050000001000': EnumCardBrand.ELO,
  '4000000000002503': EnumCardBrand.VISA,
  '5200000000001096': EnumCardBrand.MASTERCARD,
  '6505050000001091': EnumCardBrand.ELO,
  '4000000000002925': EnumCardBrand.VISA,
  '5200000000001013': EnumCardBrand.MASTERCARD,
  '6505050000001018': EnumCardBrand.ELO,
  '4000000000002370': EnumCardBrand.VISA,
  '5200000000001104': EnumCardBrand.MASTERCARD,
  '6505050000001109': EnumCardBrand.ELO,
  // Mercado pago
  // https://www.mercadopago.com.br/developers/pt/docs/checkout-api/additional-content/your-integrations/test/cards
  '5067766783888311': EnumCardBrand.ELO,
  '5031433215406351': EnumCardBrand.MASTERCARD,
  '4235647728025682': EnumCardBrand.VISA,
  '375365153556885': EnumCardBrand.AMEX,
  // Gerados
  // https://www.vccgenerator.org/
  '5098176533461570': EnumCardBrand.ELO,
  '5067349319312260': EnumCardBrand.ELO,
  '5098193894226609': EnumCardBrand.ELO,
  '5091084515027541': EnumCardBrand.ELO,
  '5098177024386094': EnumCardBrand.ELO,
  '4391395999142147': EnumCardBrand.VISA,
  '4103922157715577': EnumCardBrand.VISA,
  '4380208328771358': EnumCardBrand.VISA,
  '4297672575052840': EnumCardBrand.VISA,
  '4380277454970936': EnumCardBrand.VISA,
  '5551555128049017': EnumCardBrand.MASTERCARD,
  '5268003770982262': EnumCardBrand.MASTERCARD,
  '5448382804657501': EnumCardBrand.MASTERCARD,
  '5582852950163914': EnumCardBrand.MASTERCARD,
  '5448384161742298': EnumCardBrand.MASTERCARD,
  '340205640990176': EnumCardBrand.AMEX,
  '340205958483061': EnumCardBrand.AMEX,
  '340205254489929': EnumCardBrand.AMEX,
  '340205493225258': EnumCardBrand.AMEX,
  '340205571301401': EnumCardBrand.AMEX,
};

E usei os regex da última resposta, conforme o @gfucci colocou em sua resposta:

Comlementando, os cartões que deram errado passam na REGEX da bandeira esperada, mas também passam na da Discover/Aura. Eu comentei essas duas bandeiras e a maioria passou, só o 5031433215406351 que não está passando na REGEX do mastercard

Isso acontece porque o padrão desses cartões ELO realmente batem com o padrão da discover, porém por algum motivo a caixa registrou o BIN antes (não sei como funciona esses registros de BIN). Essas exceções já estão no regex da elo, podemos ver na parte "|6505[0-2][0-9]". Se jogarmos no bincheck.io inicialmente ele também da discover, mas se fizer o POST pra verificar ele irá retornar elo.

pra resolver podemos criar um switch e dar break antes para não continuar a verificação, e sempre verificar o ELO primeiro. Um exemplo em PHP

$bandeira = match (1) {
            preg_match(
                '/^4011(78|79)|^43(1274|8935)|^45(1416|7393|763(1|2))|^504175|^627780|^63(6297|6368|6369)|(65003[5-9]|65004[0-9]|65005[01])|(65040[5-9]|6504[1-3][0-9])|(65048[5-9]|65049[0-9]|6505[0-2][0-9]|65053[0-8])|(65054[1-9]|6505[5-8][0-9]|65059[0-8])|(65070[0-9]|65071[0-8])|(65072[0-7])|(65090[1-9]|6509[1-6][0-9]|65097[0-8])|(65165[2-9]|6516[67][0-9])|(65500[0-9]|65501[0-9])|(65502[1-9]|6550[34][0-9]|65505[0-8])|^(506699|5067[0-6][0-9]|50677[0-8])|^(509[0-8][0-9]{2}|5099[0-8][0-9]|50999[0-9])|^65003[1-3]|^(65003[5-9]|65004\d|65005[0-1])|^(65040[5-9]|6504[1-3]\d)|^(65048[5-9]|65049\d|6505[0-2]\d|65053[0-8])|^(65054[1-9]|6505[5-8]\d|65059[0-8])|^(65070\d|65071[0-8])|^65072[0-7]|^(65090[1-9]|65091\d|650920)|^(65165[2-9]|6516[6-7]\d)|^(65500\d|65501\d)|^(65502[1-9]|6550[3-4]\d|65505[0-8])/', 
                $cartaoNumero
            ) => 'elo',
            preg_match('/^4[0-9]{12}(?:[0-9]{3})?/', $cartaoNumero) => 'visa',
            preg_match('/^((5(([1-2]|[4-5])[0-9]{8}|0((1|6)([0-9]{7}))|3(0(4((0|[2-9])[0-9]{5})|([0-3]|[5-9])[0-9]{6})|[1-9][0-9]{7})))|((508116)\\d{4,10})|((502121)\\d{4,10})|((589916)\\d{4,10})|(2[0-9]{15})|(67[0-9]{14})|(506387)\\d{4,10})/', $cartaoNumero) => 'mastercard',
            preg_match('/^3[47][0-9]{13}/', $cartaoNumero) => 'amexs',
            preg_match('/^65[4-9][0-9]{13}|64[4-9][0-9]{13}|6011[0-9]{12}|(622(?:12[6-9]|1[3-9][0-9]|[2-8][0-9][0-9]|9[01][0-9]|92[0-5])[0-9]{10})/', $cartaoNumero) => 'discover',
            preg_match('/^3(?:0[0-5]|[68][0-9])[0-9]{11}/', $cartaoNumero) => 'diners',
            preg_match('/^(?:2131|1800|35\d{3})\d{11}/', $cartaoNumero) => 'jcb',
            preg_match('/^(606282\d{10}(\d{3})?)|(3841\d{12})|(637\d{13})/', $cartaoNumero) => 'hipercard',
            default => ''
        };

Agora todos os números vão bater corretamente!

Esta bandeira 5031433215406351 no bincheck.io deu que não existe, então pode ser retirada.

Aparentemente está falhando para alguns ainda... Olha o resultado que eu tive:

image

@GabrielLasso
Copy link

Comlementando, os cartões que deram errado passam na REGEX da bandeira esperada, mas também passam na da Discover/Aura. Eu comentei essas duas bandeiras e a maioria passou, só o 5031433215406351 que não está passando na REGEX do mastercard

Isso acontece porque o padrão desses cartões ELO realmente batem com o padrão da discover, porém por algum motivo a caixa registrou o BIN antes (não sei como funciona esses registros de BIN). Essas exceções já estão no regex da elo, podemos ver na parte "|6505[0-2][0-9]". Se jogarmos no bincheck.io inicialmente ele também da discover, mas se fizer o POST pra verificar ele irá retornar elo.

pra resolver podemos criar um switch e dar break antes para não continuar a verificação, e sempre verificar o ELO primeiro. Um exemplo em PHP

$bandeira = match (1) {
            preg_match(
                '/^4011(78|79)|^43(1274|8935)|^45(1416|7393|763(1|2))|^504175|^627780|^63(6297|6368|6369)|(65003[5-9]|65004[0-9]|65005[01])|(65040[5-9]|6504[1-3][0-9])|(65048[5-9]|65049[0-9]|6505[0-2][0-9]|65053[0-8])|(65054[1-9]|6505[5-8][0-9]|65059[0-8])|(65070[0-9]|65071[0-8])|(65072[0-7])|(65090[1-9]|6509[1-6][0-9]|65097[0-8])|(65165[2-9]|6516[67][0-9])|(65500[0-9]|65501[0-9])|(65502[1-9]|6550[34][0-9]|65505[0-8])|^(506699|5067[0-6][0-9]|50677[0-8])|^(509[0-8][0-9]{2}|5099[0-8][0-9]|50999[0-9])|^65003[1-3]|^(65003[5-9]|65004\d|65005[0-1])|^(65040[5-9]|6504[1-3]\d)|^(65048[5-9]|65049\d|6505[0-2]\d|65053[0-8])|^(65054[1-9]|6505[5-8]\d|65059[0-8])|^(65070\d|65071[0-8])|^65072[0-7]|^(65090[1-9]|65091\d|650920)|^(65165[2-9]|6516[6-7]\d)|^(65500\d|65501\d)|^(65502[1-9]|6550[3-4]\d|65505[0-8])/', 
                $cartaoNumero
            ) => 'elo',
            preg_match('/^4[0-9]{12}(?:[0-9]{3})?/', $cartaoNumero) => 'visa',
            preg_match('/^((5(([1-2]|[4-5])[0-9]{8}|0((1|6)([0-9]{7}))|3(0(4((0|[2-9])[0-9]{5})|([0-3]|[5-9])[0-9]{6})|[1-9][0-9]{7})))|((508116)\\d{4,10})|((502121)\\d{4,10})|((589916)\\d{4,10})|(2[0-9]{15})|(67[0-9]{14})|(506387)\\d{4,10})/', $cartaoNumero) => 'mastercard',
            preg_match('/^3[47][0-9]{13}/', $cartaoNumero) => 'amexs',
            preg_match('/^65[4-9][0-9]{13}|64[4-9][0-9]{13}|6011[0-9]{12}|(622(?:12[6-9]|1[3-9][0-9]|[2-8][0-9][0-9]|9[01][0-9]|92[0-5])[0-9]{10})/', $cartaoNumero) => 'discover',
            preg_match('/^3(?:0[0-5]|[68][0-9])[0-9]{11}/', $cartaoNumero) => 'diners',
            preg_match('/^(?:2131|1800|35\d{3})\d{11}/', $cartaoNumero) => 'jcb',
            preg_match('/^(606282\d{10}(\d{3})?)|(3841\d{12})|(637\d{13})/', $cartaoNumero) => 'hipercard',
            default => ''
        };

Agora todos os números vão bater corretamente!

Esta bandeira 5031433215406351 no bincheck.io deu que não existe, então pode ser retirada.

Opa, consegui fazer funcionar. Só joguei a linha do elo para ser a primeira regex para checar e funcionou em todos (após remover o 5031433215406351)

Segue o código completo:

export function getCardBrandName (cardNumber: string): string | null {
  const numbers = cardNumber.replace(/[^0-9]+/g, '')

  const brands = {
    elo: /^4011(78|79)|^43(1274|8935)|^45(1416|7393|763(1|2))|^50(4175|6699|67[0-6][0-9]|677[0-8]|9[0-8][0-9]{2}|99[0-8][0-9]|999[0-9])|^627780|^63(6297|6368|6369)|^65(0(0(3([1-3]|[5-9])|4([0-9])|5[0-1])|4(0[5-9]|[1-3][0-9]|8[5-9]|9[0-9])|5([0-2][0-9]|3[0-8]|4[1-9]|[5-8][0-9]|9[0-8])|7(0[0-9]|1[0-8]|2[0-7])|9(0[1-9]|[1-6][0-9]|7[0-8]))|16(5[2-9]|[6-7][0-9])|50(0[0-9]|1[0-9]|2[1-9]|[3-4][0-9]|5[0-8]))/,
    amex: /^3[47][0-9]{13}$/,
    aura: /^((?!504175))^((?!5067))(^50[0-9])/,
    banese_card: /^636117/,
    cabal: /(60420[1-9]|6042[1-9][0-9]|6043[0-9]{2}|604400)/,
    diners: /(36[0-8][0-9]{3}|369[0-8][0-9]{2}|3699[0-8][0-9]|36999[0-9])/,
    discover: /^6(?:011|5[0-9]{2})[0-9]{12}/,
    fort_brasil: /^628167/,
    grand_card: /^605032/,
    hipercard: /^606282|^3841(?:[0|4|6]{1})0/,
    jbc: /^(?:2131|1800|35\d{3})\d{11}/,
    mastercard: /^((5(([1-2]|[4-5])[0-9]{8}|0((1|6)([0-9]{7}))|3(0(4((0|[2-9])[0-9]{5})|([0-3]|[5-9])[0-9]{6})|[1-9][0-9]{7})))|((508116)\\d{4,10})|((502121)\\d{4,10})|((589916)\\d{4,10})|(2[0-9]{15})|(67[0-9]{14})|(506387)\\d{4,10})/,
    personal_card: /^636085/,
    sorocred: /^627892|^636414/,
    valecard: /^606444|^606458|^606482/,
    visa: /^4[0-9]{15}$/
  }

  const match = Object.entries(brands).find(([_, value]) => {
    if (value.test(numbers)) {
      return true
    }
    return false
  })

  if (match) {
    return match[0]
  }

  return null
}

const cards = {
  // PagSeguro
  // https://dev.pagbank.uol.com.br/reference/cartoes-de-teste
  '4539620659922097': 'visa',
  '4929291898380766': 'visa',
  '5240082975622454': 'mastercard',
  '5530062640663264': 'mastercard',
  '345817690311361': 'amex',
  '372938001199778': 'amex',
  '4514161122113757': 'elo',
  '4389350446134811': 'elo',
  '6062828598919021': 'hipercard',
  '6062822916014409': 'hipercard',
  // https://dev.pagbank.uol.com.br/reference/criar-pagar-pedido-com-3ds-validacao-pagbank
  '4000000000002701': 'visa',
  '5200000000001005': 'mastercard',
  '6505050000001000': 'elo',
  '4000000000002503': 'visa',
  '5200000000001096': 'mastercard',
  '6505050000001091': 'elo',
  '4000000000002925': 'visa',
  '5200000000001013': 'mastercard',
  '6505050000001018': 'elo',
  '4000000000002370': 'visa',
  '5200000000001104': 'mastercard',
  '6505050000001109': 'elo',
  // Mercado pago
  // https://www.mercadopago.com.br/developers/pt/docs/checkout-api/additional-content/your-integrations/test/cards
  '5067766783888311': 'elo',
  '4235647728025682': 'visa',
  '375365153556885': 'amex',
  // Gerados
  // https://www.vccgenerator.org/
  '5098176533461570': 'elo',
  '5067349319312260': 'elo',
  '5098193894226609': 'elo',
  '5091084515027541': 'elo',
  '5098177024386094': 'elo',
  '4391395999142147': 'visa',
  '4103922157715577': 'visa',
  '4380208328771358': 'visa',
  '4297672575052840': 'visa',
  '4380277454970936': 'visa',
  '5551555128049017': 'mastercard',
  '5268003770982262': 'mastercard',
  '5448382804657501': 'mastercard',
  '5582852950163914': 'mastercard',
  '5448384161742298': 'mastercard',
  '340205640990176': 'amex',
  '340205958483061': 'amex',
  '340205254489929': 'amex',
  '340205493225258': 'amex',
  '340205571301401': 'amex',
};

for (const card in cards) {
    const expectedBrand = cards[card]
    const calculatedBrand = getCardBrandName(card)
    if (expectedBrand == calculatedBrand) {
        console.log(`${card} OK`)
    } else {
        console.log(`${card} ERROR. Expected ${expectedBrand}, got ${calculatedBrand}`)
    }
}

@GabrielLasso
Copy link

Eu pequei os cartões dessa resposta do @MMortari :

Boa tarde pessoal,
Minha pequena contribuição, segue uma lista com alguns cartões que estou colecionando de alguns provedores para teste.

const cards = {
  // PagSeguro
  // https://dev.pagbank.uol.com.br/reference/cartoes-de-teste
  '4539620659922097': EnumCardBrand.VISA,
  '4929291898380766': EnumCardBrand.VISA,
  '5240082975622454': EnumCardBrand.MASTERCARD,
  '5530062640663264': EnumCardBrand.MASTERCARD,
  '345817690311361': EnumCardBrand.AMEX,
  '372938001199778': EnumCardBrand.AMEX,
  '4514161122113757': EnumCardBrand.ELO,
  '4389350446134811': EnumCardBrand.ELO,
  '6062828598919021': EnumCardBrand.HIPERCARD,
  '6062822916014409': EnumCardBrand.HIPERCARD,
  // https://dev.pagbank.uol.com.br/reference/criar-pagar-pedido-com-3ds-validacao-pagbank
  '4000000000002701': EnumCardBrand.VISA,
  '5200000000001005': EnumCardBrand.MASTERCARD,
  '6505050000001000': EnumCardBrand.ELO,
  '4000000000002503': EnumCardBrand.VISA,
  '5200000000001096': EnumCardBrand.MASTERCARD,
  '6505050000001091': EnumCardBrand.ELO,
  '4000000000002925': EnumCardBrand.VISA,
  '5200000000001013': EnumCardBrand.MASTERCARD,
  '6505050000001018': EnumCardBrand.ELO,
  '4000000000002370': EnumCardBrand.VISA,
  '5200000000001104': EnumCardBrand.MASTERCARD,
  '6505050000001109': EnumCardBrand.ELO,
  // Mercado pago
  // https://www.mercadopago.com.br/developers/pt/docs/checkout-api/additional-content/your-integrations/test/cards
  '5067766783888311': EnumCardBrand.ELO,
  '5031433215406351': EnumCardBrand.MASTERCARD,
  '4235647728025682': EnumCardBrand.VISA,
  '375365153556885': EnumCardBrand.AMEX,
  // Gerados
  // https://www.vccgenerator.org/
  '5098176533461570': EnumCardBrand.ELO,
  '5067349319312260': EnumCardBrand.ELO,
  '5098193894226609': EnumCardBrand.ELO,
  '5091084515027541': EnumCardBrand.ELO,
  '5098177024386094': EnumCardBrand.ELO,
  '4391395999142147': EnumCardBrand.VISA,
  '4103922157715577': EnumCardBrand.VISA,
  '4380208328771358': EnumCardBrand.VISA,
  '4297672575052840': EnumCardBrand.VISA,
  '4380277454970936': EnumCardBrand.VISA,
  '5551555128049017': EnumCardBrand.MASTERCARD,
  '5268003770982262': EnumCardBrand.MASTERCARD,
  '5448382804657501': EnumCardBrand.MASTERCARD,
  '5582852950163914': EnumCardBrand.MASTERCARD,
  '5448384161742298': EnumCardBrand.MASTERCARD,
  '340205640990176': EnumCardBrand.AMEX,
  '340205958483061': EnumCardBrand.AMEX,
  '340205254489929': EnumCardBrand.AMEX,
  '340205493225258': EnumCardBrand.AMEX,
  '340205571301401': EnumCardBrand.AMEX,
};

E usei os regex da última resposta, conforme o @gfucci colocou em sua resposta:

Comlementando, os cartões que deram errado passam na REGEX da bandeira esperada, mas também passam na da Discover/Aura. Eu comentei essas duas bandeiras e a maioria passou, só o 5031433215406351 que não está passando na REGEX do mastercard

Isso acontece porque o padrão desses cartões ELO realmente batem com o padrão da discover, porém por algum motivo a caixa registrou o BIN antes (não sei como funciona esses registros de BIN). Essas exceções já estão no regex da elo, podemos ver na parte "|6505[0-2][0-9]". Se jogarmos no bincheck.io inicialmente ele também da discover, mas se fizer o POST pra verificar ele irá retornar elo.
pra resolver podemos criar um switch e dar break antes para não continuar a verificação, e sempre verificar o ELO primeiro. Um exemplo em PHP

$bandeira = match (1) {
            preg_match(
                '/^4011(78|79)|^43(1274|8935)|^45(1416|7393|763(1|2))|^504175|^627780|^63(6297|6368|6369)|(65003[5-9]|65004[0-9]|65005[01])|(65040[5-9]|6504[1-3][0-9])|(65048[5-9]|65049[0-9]|6505[0-2][0-9]|65053[0-8])|(65054[1-9]|6505[5-8][0-9]|65059[0-8])|(65070[0-9]|65071[0-8])|(65072[0-7])|(65090[1-9]|6509[1-6][0-9]|65097[0-8])|(65165[2-9]|6516[67][0-9])|(65500[0-9]|65501[0-9])|(65502[1-9]|6550[34][0-9]|65505[0-8])|^(506699|5067[0-6][0-9]|50677[0-8])|^(509[0-8][0-9]{2}|5099[0-8][0-9]|50999[0-9])|^65003[1-3]|^(65003[5-9]|65004\d|65005[0-1])|^(65040[5-9]|6504[1-3]\d)|^(65048[5-9]|65049\d|6505[0-2]\d|65053[0-8])|^(65054[1-9]|6505[5-8]\d|65059[0-8])|^(65070\d|65071[0-8])|^65072[0-7]|^(65090[1-9]|65091\d|650920)|^(65165[2-9]|6516[6-7]\d)|^(65500\d|65501\d)|^(65502[1-9]|6550[3-4]\d|65505[0-8])/', 
                $cartaoNumero
            ) => 'elo',
            preg_match('/^4[0-9]{12}(?:[0-9]{3})?/', $cartaoNumero) => 'visa',
            preg_match('/^((5(([1-2]|[4-5])[0-9]{8}|0((1|6)([0-9]{7}))|3(0(4((0|[2-9])[0-9]{5})|([0-3]|[5-9])[0-9]{6})|[1-9][0-9]{7})))|((508116)\\d{4,10})|((502121)\\d{4,10})|((589916)\\d{4,10})|(2[0-9]{15})|(67[0-9]{14})|(506387)\\d{4,10})/', $cartaoNumero) => 'mastercard',
            preg_match('/^3[47][0-9]{13}/', $cartaoNumero) => 'amexs',
            preg_match('/^65[4-9][0-9]{13}|64[4-9][0-9]{13}|6011[0-9]{12}|(622(?:12[6-9]|1[3-9][0-9]|[2-8][0-9][0-9]|9[01][0-9]|92[0-5])[0-9]{10})/', $cartaoNumero) => 'discover',
            preg_match('/^3(?:0[0-5]|[68][0-9])[0-9]{11}/', $cartaoNumero) => 'diners',
            preg_match('/^(?:2131|1800|35\d{3})\d{11}/', $cartaoNumero) => 'jcb',
            preg_match('/^(606282\d{10}(\d{3})?)|(3841\d{12})|(637\d{13})/', $cartaoNumero) => 'hipercard',
            default => ''
        };

Agora todos os números vão bater corretamente!
Esta bandeira 5031433215406351 no bincheck.io deu que não existe, então pode ser retirada.

Aparentemente está falhando para alguns ainda... Olha o resultado que eu tive:

image

Qual código você tá usando?

No meu exemplo em TypeScript funcionou todos os cartões

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment