DEV Community

Cover image for Como identificar o emissor  do cartão de crédito e validar o número utilizando o Framework Flutter
gepdm
gepdm

Posted on

1 1

Como identificar o emissor do cartão de crédito e validar o número utilizando o Framework Flutter

Olá, dev!

Neste tutorial, vamos desenvolver uma aplicação para validação do emissor e do número do cartão de crédito utilizando a linguagem Dart e o Framework Flutter.


Tópicos

  1. Pré-requisitos
  2. Visão geral
  3. Criando o projeto
  4. Algoritmo de Luhn

Pré-requisitos

 

Instalação do Dart, Flutter e IDE (Integrated Development Environment)

Para começar, caso não tenha o Dart e o Flutter instalados, siga as instruções na documentação para prosseguir. Você também precisa de um editor de código ou IDE, nesse tutorial utilizaremos o Visual Studio Code.


Visão geral

Uma funcionalidade comum nos sistemas de pagamentos é a identificação do emissor e a validação do cartão de crédito ou débito. Para fazer isso, existem alguns algoritmos que facilitam a validação do cartão do lado do cliente.

O algoritmo que iremos utilizar neste tutorial, é o Algoritmo de Luhn.


Criando o projeto

Vamos iniciar, abra o seu terminal e crie um novo projeto com o Flutter command-line tool, substitua "my_app" por qualquer nome que queira dar ao aplicativo:

$ flutter create my_app
Enter fullscreen mode Exit fullscreen mode

Entre no diretório e abra-o com o seu editor de código ou IDE preferida.

$ cd my_app
$ code .
Enter fullscreen mode Exit fullscreen mode

Nossa estrutura final do projeto ficará semelhante a isso. Se você começou criando o projeto pelo Flutter cli, você não terá o diretório pages e assets, além dos arquivos abaixo deles por enquanto, veremos isso mais a frente.

.
├── android/
├── build/
├── ios/
├── lib/
│   ├── assets/
│   │   └── images/
│   │       ├── amex.png
│   │       ├── mastercard.png
│   │       └── visa.png
│   ├── pages/
│   │   └── card_page.dart
│   └── main.dart
├── test/
├── web/
├── .gitignore
├── .metadata
├── .packages
├── my_app.iml
├── README.md
└── pubspec.yaml
Enter fullscreen mode Exit fullscreen mode

No diretório lib, vamos até main.dart, e em seguida remova o código desnecessário e deixe apenas a função main e a classe MyApp.

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
        title: Text("CardValidator"),
        ),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Agora, crie um diretório pages dentro do diretório lib e adicione um novo arquivo com o nome card_page.dart. Feito isso, importe a biblioteca de UI/widgets do Flutter material.dart e crie um widget de estado para que o componente _CardPageState possa guardar informações.

Aqui vale um adendo. O que são widgets sem estado (StatelessWidget) e widgets de estado (StatefulWidget) ? Quando você tem um widget que nunca muda suas informações, ele não precisa ter estado e, logo, pode ser StatelessWidget. No caso de widgets que mudam informações quando o usuário interage com ele, esse widget precisa ser dinâmico, ou seja, ele pode mudar a aparência ou dados de acordo com eventos, dessa forma, você precisa de um StatefulWidget.

import 'package:flutter/material.dart';

class CardPage extends StatefulWidget {
  @override
  _CardPageState createState() => _CardPageState();
}

class _CardPageState extends State<CardPage> {
  @override
  Widget build(BuildContext context) {

  }
}
Enter fullscreen mode Exit fullscreen mode

Em seguida, vamos adicionar um formulário (Form), uma estrutura básica de layout do material (Scaffold), vamos adicionar o widget SafeArea para evitar transbordamento do layout para outras partes do sistema operacional e uma Scroll View para rolar a tela.

class _CardPageState extends State<CardPage> {
  @override
  Widget build(BuildContext context) {
    return Form(
      child: Scaffold(
        body: SafeArea(
          child: SingleChildScrollView(

          ),
        ),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Agora que temos a estrutura básica pronta, volte para a classe MyApp, importe o arquivo card_page.dart e troque a home do MaterialApp para a classe CardPage.

class MyApp extends StatelessWidget {
  /* ... */
  return MaterialApp(
        title: 'CardValidator',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: CardPage(),
      );
  /* ... */
}
Enter fullscreen mode Exit fullscreen mode

Continuando com o _CardPageState, adicione um preenchimento (Padding) para cima, vamos centralizar com o widget (Center) e colocar uma caixa com um tamanho especificado (SizedBox) e, por último, vamos adicionar uma coluna (Column).

class _CardPageState extends State<CardPage> {
/* ... */
  child: SingleChildScrollView(
    child: Padding(
      padding: EdgeInsets.only(top: 50),
      child: Center(
        child: SizedBox(
          width: 300,
          child: Column(
            mainAxisSize: MainAxisSize.max,
            mainAxisAlignment: MainAxisAlignment.start,
            crossAxisAlignment: CrossAxisAlignment.center

          ),
        ),
      ),
    ),
  ),
/* ... */
}
Enter fullscreen mode Exit fullscreen mode

O widget Column aceita um array de widgets, iremos adicionar como primeiro widget do array o widget AspectRatio e uma pilha de outros filhos como segundo filho do Container, o qual irá criar nosso card.

/* ... */
child: Column(
  mainAxisSize: MainAxisSize.max,
  mainAxisAlignment: MainAxisAlignment.start,
  crossAxisAlignment: CrossAxisAlignment.center
  children: [
    AspectRatio(
     aspectRatio: 1.586,
     child: Container(
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(10),
        boxShadow: [
          BoxShadow(
            color: Colors.grey.withOpacity(0.5),
            spreadRadius: 8,
            blurRadius: 100
          )
        ],
      ),
      child: Stack(
        children: [

         ],
       ),
     ),
   ),
 ],
),
/* ... */
Enter fullscreen mode Exit fullscreen mode

Como filhos do widget Stack, adicionaremos dois widgets Positioned, para indicar onde eles serão posicionados dentro do card.

/* ... */
child: Stack(
  children: [
    Positioned(
      top: 90,
      left: 20,
      child: Text(
        "XXXX XXXX XXXX XXXX",
        style: TextStyle(
          fontSize: 20, fontWeight: FontWeight.w600
        ),
      ),
    ),
    Positioned(
      top: 20,
      right: 20,
      child: Text("Bandeira"),
    )
  ],
),
/* ... */
Enter fullscreen mode Exit fullscreen mode

Agora, vamos criar o input de texto, instale as dependências que iremos utilizar e importe no arquivo card_page.dart.

Com o terminal, instale a dependência de máscara para o número do cartão.

flutter pub add easy_mask
Enter fullscreen mode Exit fullscreen mode

Importe no arquivo card_page.dart

import 'package:flutter/services.dart';
import 'package:easy_mask/easy_mask.dart';
/* ... */
Enter fullscreen mode Exit fullscreen mode

O segundo filho do widget Column, será o input, vamos adicionar um padding do cartão e formatar o tamanho do input e label.

/* ... */
child: Column(
  children: [
    AspectRatio(/* ... */),
    Padding(
    padding: EdgeInsets.only(top: 40),
    child: TextFormField(
      keyboardType: TextInputType.number,
      inputFormatters: [
        TextInputMask(mask: '9999 9999 9999 9999')
      ],
      onChanged: (_) {
        setState(() {});
      },
      obscureText: false,
      decoration: InputDecoration(
        errorBorder: OutlineInputBorder(
          borderSide: BorderSide(
            color: Colors.green,
            width: 1
          )
        ),
        labelText: 'Número',
        hintText: 'XXXX XXXX XXXX XXXX',
        errorStyle: TextStyle(
          color: Colors.green
        ),
        focusedErrorBorder: OutlineInputBorder(
          borderSide: BorderSide(
            color: Colors.green,
            width: 1
          ),
        ),
        enabledBorder: OutlineInputBorder(
          borderSide: BorderSide(
            color: Color(0xFF646464),
            width: 1
          ),
          borderRadius: BorderRadius.circular(5),
        ),
        focusedBorder: OutlineInputBorder(
          borderSide: BorderSide(
            color: Color(0xFF646464),
            width: 1,
          ),
          borderRadius: BorderRadius.circular(5),
        ),
        contentPadding: EdgeInsets.symmetric(
          vertical: 25,
          horizontal: 15
        ),
      ),
    ),
  ),
)
/* ... */
Enter fullscreen mode Exit fullscreen mode

Por último, vamos criar o botão de validação. Por enquanto ele não irá fazer nada, dado que nenhum estado foi alterado. Em seguida iremos criar as funções que serão disparadas ao evento de toque.

/* ... */
child: Column(
  children: [
    AspectRatio(/* ... */),
    Padding(/* ... */),
    Padding(
      padding: const EdgeInsets.only(top: 25),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          ElevatedButton(
            style: ElevatedButton.styleFrom(
              primary: Colors.lightBlue[900],
              minimumSize: Size(120, 40),
            ),
            onPressed: () {},
            child: Text(
              'Validar',
              style: TextStyle(fontSize: 15),
            ),
          ),
        ],
      ),
    ),
  ],
),
/* ... */
Enter fullscreen mode Exit fullscreen mode

Algoritmo de Luhn

O algoritmo Luhn é utilizado para validar uma variedade de números de identificação, como números de cartão de crédito que são aplicados a sites de comércio eletrônico, por exemplo. O número de cartão parece ser aleatório, mas cada parte tem um significado, eles são divididos em grupos responsáveis por identificar dados importantes do emissor, da indústria e da conta.

Exemplo de grupos de um cartão de crédito

Os grupos são:

  1. Identificador principal da indústria (MII - Major Industry Identifier)
  2. Número de identificação do emissor (IIN - Issuer Identification Number)
  3. Número da conta
  4. Soma de verificação

Assim, o Algoritmo de Luhn determina a validade de um número de cartão utilizando o número da conta e a soma de verificação. Os números de posição par devem ser multiplicados por dois; caso o resultado dessa multiplicação seja maior que 9, devemos somar os dígitos (16 => 1+6=7), posteriormente somar todos os valores. Assim, a soma com o resto da divisão por 10 sendo igual a zero, o número do cartão será válido, caso contrário, será inválido.

Exemplo de validação do emissor e do cartão de crédito

Fonte: Laboratoria

Implementação do Algoritmo de Luhn

Bom, sabendo da teoria vamos ao código. Acima da classe CardPage, crie uma função que recebe como parâmetro o número do cartão e retorna um resultado booleano (true/false), caso seja válido ou inválido.

bool isCardValid(String cardNumber) {
  int sum = 0;
  if (cardNumber != null && cardNumber.length >= 13) {
    List<String> card = cardNumber.replaceAll(new RegExp(r"\s+"), "").split("");
    int i = 0;
    card.reversed.forEach((num) {
      int digit = int.parse(num);
      i.isEven
          ? sum += digit
          : digit >= 5
              ? sum += (digit * 2) - 9
              : sum += digit * 2;
      i++;
    });
  }
  return sum % 10 == 0 && sum != 0;
}
Enter fullscreen mode Exit fullscreen mode

Agora, precisamos criar os estados (states) para guardar os dados de input e validação. O TextEditingControllervai notificar seus ouvintes para que possam ler o valor do texto toda vez que for editado. Vamos criar uma key para o formulário, um widget de bandeira para ser trocado após a identificação do emissor e, por último, um booleano para identificar se o número é válido ou inválido, esse vai começar como false.

Crie um método initState, esse método irá ser chamado apenas uma vez quando o widget é inserido na ávore de widgets do Flutter, então atribua ao textController o TextEditingController.

class _CardPageState extends State<CardPage> {
  TextEditingController textController = TextEditingController();
  final _formKey = GlobalKey<FormState>();
  Widget bandeira = Container();
  bool isValid = false;

  @override
  void initState() {
    super.initState();
    textController = TextEditingController();
  }

  Widget build(BuildContext context) { /* ... */ }
Enter fullscreen mode Exit fullscreen mode

Para colocar a bandeira do emissor, precisamos das imagens. Dessa forma, é necessário que você tenha as imagens em um diretório local e importe pelo path relativo. Também, é preciso adicionar ao arquivo pubspec.yaml o path relativo das imagens na lista assets. Você pode baixar as imagens por aqui.

flutter:
  assets:
    - lib/assets/images/amex.png
    - lib/assets/images/mastercard.png
    - lib/assets/images/visa.png
Enter fullscreen mode Exit fullscreen mode

Vamos criar a função que valida o emissor do cartão e nos retorne um widget da imagem correspondente. Vamos criar uma função assíncrona checkCardBanner que recebe o número do cartão e retorna um widget, utilizaremos uma máscara de regex correspondente ao padrão que o emissor utiliza em seus cartões para validar.

Future<Widget> checkCardBanner(String card) async {
  card = card.replaceAll(new RegExp(r"\s+"), "");
  if (RegExp(r'^4\d{12}(\d{3})?$').hasMatch(card))
    return Image.asset('lib/assets/images/visa.png', height: 30);
  if (RegExp(r'^5[1-5]\d{14}$').hasMatch(card))
    return Image.asset('lib/assets/images/mastercard.png', height: 50);
  if (RegExp(r'^3[47]\d{13}$').hasMatch(card))
    return Image.asset('lib/assets/images/amex.png', height: 60);
  return Container();
}
Enter fullscreen mode Exit fullscreen mode

Feito isso, vamos alterar algumas coisas para que os elementos mudem conforme os dados são modificados. Primeiro, adicione ao widget Form da classe _CardPageState a _formKey que criamos acima.

/* ... */
    return Form(
      key: _formKey,
      /* ... */
    )
/* ... */
Enter fullscreen mode Exit fullscreen mode

No input (TextFormField) atribua a propriedade controller o controlador de texto (textController).

/* ... */
TextFormField(
  controller: textController,
  /* ... */
)
/* ... */
Enter fullscreen mode Exit fullscreen mode

No onChanged do botão, a cada modificação será disparado um evento que irá modificar o estado da variável bandeira, vamos verificar o emissor passando o valor do input como argumento e na resposta da função assíncrona, vamos atribuir à variável bandeira a imagem correspondente ao emissor.

/* ... */
onChanged: (_) {
  setState(() {
    checkCardBanner(textController.text).then(
        (image) => bandeira = image);
  });
/* ... */
Enter fullscreen mode Exit fullscreen mode

No card, podemos mostrar o número do cartão conforme é alterado ou o padrão de formatação.

/* ... */
Positioned(
  top: 90,
  left: 20,
  child: Text(
    textController.text.isNotEmpty ?
    textController.text :
    'XXXX XXXX XXXX XXXX',
    style: TextStyle(
        fontSize: 20,
        fontWeight: FontWeight.w600),
  ),
),
/* ... */
Enter fullscreen mode Exit fullscreen mode

Em seguida, para a bandeira do emissor aparecer, vá até o widget da bandeira e adicione como child a variável bandeira.

/* ... */
Positioned(
  top: 20,
  right: 20,
  child: bandeira,
)
/* ... */
Enter fullscreen mode Exit fullscreen mode

O input também aceita uma função validator, aqui vamos validar o número de cartão chamando a função do algoritmo de Luhn e passar o valor do input, como resultado vamos obter um booleano. Adicione esse trecho de código antes ou após a função onChanged do widget de input.

/* ... */
Padding(
  child: TextFormField(
    validator: (value) {
      isValid = isCardValid(value);
      return isValid
      ? 'Cartão válido'
      : 'Cartão inválido';
    },
  ),
),
/* ... */
Enter fullscreen mode Exit fullscreen mode

Para dar feedback ao usuário sobre a validade ou não do input, precisamos verificar se o formulário foi preenchido. Podemos fazer isso adicionando um setState ao onPressed do botão. A partir da key do Form, conseguimos acessar os estados criados automaticamente pelo FormState no montar o formulário e, assim, validar cada campo do formulário.

/* ... */
Padding(
  child: Row(
    ElevatedButton(
      onPressed: () {
        setState(() =>
          _formKey.currentState.validate()
        );
      },
    ),
  ),
),
/* ... */
Enter fullscreen mode Exit fullscreen mode

Ainda no input, vamos adicionar uma funcionalidade de limpar o input e algumas customizações. Quando o input não estiver vazio, vamos adicionar um ícone para limpar o campo de texto.

/* ... */
Padding(
  child: TextFormField(
    decoration: InputDecoration(
      suffixIcon:
        textController.text.isNotEmpty
        ? InkWell(
          onTap: () => setState(() => textController.clear()),
          child: Icon(
            Icons.clear,
            color: Color(0xFF757575),
            size: 22,
          ),
        )
        : null,
    )
  ),
),
/* ... */
Enter fullscreen mode Exit fullscreen mode

Agora, vamos fazer uma condicional para trocar as cores das bordas do input para quando o número for válido ou inválido.

/* ... */
focusedErrorBorder: OutlineInputBorder(
  borderSide: BorderSide(
    color: isValid ? Colors.green : Colors.red,
  )
)
/* ... */
Enter fullscreen mode Exit fullscreen mode

Também, vamos alterar a cor na borda de erro do input.

/* ... */
decoration: InputDecoration(
  errorBorder: OutlineInputBorder(
    borderSide: BorderSide(
      color: isValid ? Colors.green : Colors.red,
    ),
  ),
),
/* ... */
Enter fullscreen mode Exit fullscreen mode

Para finalizar, vamos alterar o estilo do input para quando o cartão for inválido.

/* ... */
decoration: InputDecoration(
  errorStyle: TextStyle(
    color: isValid ? Colors.green : Colors.red,
  ),
),
/* ... */
Enter fullscreen mode Exit fullscreen mode

Finalizamos a aplicação, para ver o código completo clique aqui. Para fazer build execute no terminal o comando a seguir.

flutter build apk # android
flutter build ios # apple
Enter fullscreen mode Exit fullscreen mode

Muito obrigado por ler! 👋


Referências

Hussein, Khalid Waleed, et al. Enhance Luhn Algorithm for Validation of Credit Cards Numbers. 2013.

Laboratoria

Um guia completo para validar e formatar cartões de crédito

Qodo Takeover

Introducing Qodo Gen 1.0: Transform Your Workflow with Agentic AI

While many AI coding tools operate as simple command-response systems, Qodo Gen 1.0 represents the next generation: autonomous, multi-step problem-solving agents that work alongside you.

Read full post →

Top comments (0)

Qodo Takeover

Introducing Qodo Gen 1.0: Transform Your Workflow with Agentic AI

Rather than just generating snippets, our agents understand your entire project context, can make decisions, use tools, and carry out tasks autonomously.

Read full post