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

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

Top comments (0)