Nesta seção você irá aprender mais sobre os detalhes de cada funcionalidade do SDK Android para mPOS. Veja como fazer o download e configurar as bibliotecas básicas que seguem o protocolo ABECS. Além disso, veja o fluxo de criação de uma transação. Vamos lá!

Download do SDK

O SDK Android pode ser encontrado para ser utilizando através do sistema de build Gradle

Aplicação de Exemplo

No repositório pagarme/mpos-android-app-example há uma aplicação simples que mostra o necessário para uma integração com nosso SDK Android.

Configurando o ambiente

Permissões Android

Adicione as seguintes linhas ao AndroidManifest.xml do seu projeto, no interior da tag <manifest>:

	<uses-permission android:name="android.permission.BLUETOOTH" />
	<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
	<uses-permission android:name="android.permission.INTERNET" />

Essas linhas garantem as permissões necessárias para que o aplicativo e o SDK possam realizar operações com Bluetooth e com conexões à Internet.

Instruções para build:

  1. Dentro do diretório de sua application Android (geralmente '/app'), você encontra os seguintes arquivos:

    - AndroidManifest.xml
    - build.gradle
    

    Após fazer o download em Gradle, os seguintes arquivos ficam disponíveis:

    - mpos-android-native.jar
    - mpos-android.aar
    
  2. Adicione as seguintes linhas ao build.gradle:

	repositories {
		flatDir {
			dirs 'libs'
		}
		maven {
			url "http://dl.bintray.com/vivareal/maven"
		}
	}

	dependencies {
		compile fileTree(dir: 'libs', include: ['*.jar'])
		compile (name:'mpos-android', ext:'aar') {
			transitive = true
		}
		compile 'br.com.vivareal:cuid-android:0.1.0'
	}

Essas linhas incluem os componentes nativos e os componentes Java do SDK para Android no seu projeto, além de uma biblioteca para gerar localmente um identificador da transação, que possibilita desfazimento da mesma, caso o fluxo seja interrompido.

  1. Adicione os arquivos:
    • mpos-android-native.jar
    • mpos-android.aar

ao diretório libs abaixo do diretório de sua application Android.

Fluxo pré-transação

O código abaixo demonstra um fluxo completo que pode ser usado por sua aplicação, para capturar os dados de cartão, gerar o card_hash e usá-lo para criar uma transação com a API Pagar.me a partir de um servidor externo. Vamos lá:

package com.example.pagarme.mpospocs;

import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;

import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import me.pagar.mposandroid.MposPaymentResult;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;

import me.pagar.mposandroid.Mpos;
import me.pagar.mposandroid.MposListener;
import me.pagar.mposandroid.EmvApplication;
import me.pagar.mposandroid.PaymentMethod;


public class MainActivity extends AppCompatActivity {

    private ListView listView;
    private ArrayList<String> mDeviceList = new ArrayList<String>();
    private ArrayList<BluetoothDevice> abecsList = new ArrayList<BluetoothDevice>();
    private BluetoothAdapter mBluetoothAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        listView = (ListView) findViewById(R.id.listView);

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long arg3) {
                view.setSelected(true);
                BluetoothDevice device = abecsList.get(position);
                Log.d("Abecs", "SELECTED DEVICE " + device.getName());

                try {
                    final Mpos mpos = new Mpos(abecsList.get(position), "SUA ENCRYPTION KEY", getApplicationContext());

                    mpos.addListener(new MposListener() {
                        public void bluetoothConnected() {
                            Log.d("Abecs", "Bluetooth connected.");
                            mpos.initialize();
                        }

                        public void bluetoothDisconnected() {
                            Log.d("Abecs", "Bluetooth disconnected.");
                        }

                        public void bluetoothErrored(int error) {
                            Log.d("Abecs", "Received bluetooth error");
                        }

                        public void receiveInitialization() {
                            Log.d("Abecs", "receive initialization!");
                            try {
                                mpos.downloadEMVTablesToDevice(true);
                            } catch (Exception e) {
                                Log.d("Abecs", "Got error in initialization and table update " + e.getMessage());
                            }
                        }

                        public void receiveNotification(String notification) {
                            Log.d("Abecs", "Got Notification " + notification);
                        }

                        public void receiveTableUpdated(boolean loaded) {
                            Log.d("Abecs", "received table updated loaded = " + loaded);
                            EmvApplication visaCredit = new EmvApplication(PaymentMethod.CreditCard, "visa");
                            ArrayList<EmvApplication> l = new ArrayList<EmvApplication>();
                            l.add(visaCredit);
                            EmvApplication masterCredit = new EmvApplication(PaymentMethod.CreditCard, "mastercard");

                            l.add(masterCredit);

                            mpos.payAmount(5000, l, PaymentMethod.CreditCard, false);
                        }

                        public void receiveFinishTransaction() {
                            Log.d("Abecs", "Finished transaction");
                            mpos.close("TRANSACAO APROVADA");
                        }

                        public void receiveClose() {
                            Log.d("Abecs", "Receive close");
                            mpos.closeConnection();
                        }

                        public void receiveCardHash(String cardHash, MposPaymentResult result) {
                            Log.d("Abecs", "Card Hash is " + cardHash);
                            Log.d("Abecs", "Card Brand is " + result.cardBrand);
                            Log.d("Abecs", "FD = " + result.cardFirstDigits + " LD = " + result.cardLastDigits);
                            Log.d("Abecs", "ONL = " + result.isOnline);

                          /* Nesse momento você deve enviar o card_hash para 
                             seu servidor, criar a transação, e devolver um 
                             response para o app com os seguintes campos:
                             
                             * acquirer_response_code
                             * card_emv_response
                             * card_pin_mode
                             
                             Que serão usados no mpos.finishTransaction()
                          */
                          
                          if (result.isOnline) {
                              mpos.finishTransaction(true, Integer.parseInt((String) t.get("acquirer_response_code")), (String) t.get("card_emv_response"));
                          } else {
                              mpos.close("Finished transaction!");
                          }
                        }

                        public void receiveError(int error) {
                            Log.d("ABECS", "Received error " + error);
                        }

                        public void receiveOperationCancelled() {
                            Log.d("ABECS", "Cancel");
                        }
                    });

                    Log.d("Abecs", "Telling to initialize");
                    mpos.openConnection(false);
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

    }
}

Atualizando as tabelas EMV

Para as diferentes aplicações de cartão de crédito (diferentes bandeiras, crédito/débito) existem um conjunto de especificações de como o PinPad deve se comportar, assim como certificados que permitem lidar com elas. Este conjunto é denominado tabelas EMV e pode ser obtido junto ao Pagar.me via SDK.

Para baixar as tabelas EMV, as seguintes operações são realizadas:

  1. Download das tabelas EMV pela API Pagar.me;
  2. Transferência das tabelas baixadas para o PinPad.

A API Pagar.me fornece as tabelas EMV e então as bibliotecas, como no exemplo abaixo, fazem a instalação no PinPad:

public void receiveInitialization() {
    Log.d("Abecs", "receive initialization!");
    try {
        mpos.downloadEMVTablesToDevice(true);
    } catch (Exception e) {
        Log.d("Abecs", "Got error in initialization and table update " + e.getMessage());
    }
}

O valor booleano force dos exemplos especifica o comportamento de atualização de tabelas no PinPad. Caso esteja setado como true, faz o download e instala as tabelas baixadas no PinPad. Caso esteja como false, somente instala as tabelas no PinPad se a versão no PinPad for diferente da versão das tabelas na API Pagar.me, evitando instalações de forma redundante.

O parâmetro loaded nos eventos lançados por ocasião do término de atualização de tabelas indica, se true, que foram instaladas tabelas no PinPad; se false, que essa instalação não foi necessária.

📘

Limpeza do banco de dados local

Sempre que as tabelas são atualizadas, há uma tentativa de limpar as transações mais antigas que o limite estabelecido para o banco de transações local.

Processando os dados de cartão

Para processar e obter um card_hash — que deverá ser enviado à API Pagar.me para que a transação seja criada — o seguinte código pode ser utilizado:

public void receiveTableUpdated(boolean loaded) {
    Log.d("Abecs", "received table updated loaded = " + loaded);

    ArrayList<EmvApplication> l = new ArrayList<EmvApplication>();

    EmvApplication visaCredit = new EmvApplication(PaymentMethod.CreditCard, "visa");
    EmvApplication masterCredit = new EmvApplication(PaymentMethod.CreditCard, "mastercard");

    l.add(visaCredit);
    l.add(masterCredit);

    mpos.payAmount(5000, l, PaymentMethod.CreditCard, false);
}

Este SDK usa uma função de processamento chamada payAmount com três parâmetros: amount, applications, PaymentMethod e contactlessDisabled.

O primeiro é um inteiro representando a quantia a ser cobrada em centavos (no caso dos exemplos, 100 = R$1,00). As opções possíveis são as seguintes:

ParâmetroSignificado
amountQuantia a ser cobrada em centavos (ex. 100 = R$1,00)
applicationsUm conjunto de EMVApplication que sua aplicação deseja selecionar. Uma EMVApplication consiste da combinação bandeira e método de pagamento (ex. Visa Crédito, Master Débito, Amex Crédito). Em caso de valor nulo, a escolha de possíveis aplicações é feita pelo PinPad, ou seja, as aplicações carregadas são todas disponíveis no pinpad.
paymentMethodForma de pagamento selecionada. Possíveis valores: PaymentMethod.CreditCard ou PaymentMethod.DebitCard
contactlessDisabledFlag para desabilitar o contactless (desligar antena NFC). Caso true desliga a antena, em caso de false liga a antena.

🚧

Vale ressaltar

Caso o conjunto de aplicações especificado não seja suportado pelo cartão inserido (ex. applications contém Visa Crédito e o cartão é Master Crédito), o pinpad retorna o erro 70.

Finalizando uma transação

Após a obtenção de um card_hash por meio do processamento no SDK, e seu envio à API Pagar.me para criação de uma transação, é necessário finalizar o procedimento como um todo. Para tal, o seguinte código deve ser usado:

if (result.isOnline) {
    boolean connected = true; /* Define se foi possível se conectar à API Pagar.me para processar a transação */

    String acquirerResponseCode = (String) t.get("acquirer_response_code");
    String cardEmvResponse = (String) t.get("card_emv_response");

    mpos.finishTransaction(connected, Integer.parseInt(acquirerResponseCode), cardEmvResponse);
} else {
    mpos.close("Finished transaction!");
}

Logo, considere a seguinte assinatura para o método finishTransaction:

finishTransaction(boolean connected, int responseCode, String emvData)

Começando com o parâmetro connected, ele define se a conexão com a API Pagar.Me foi bem-sucedida, e os demais podem ser encontrados no response da API Pagar.Me para seu servidor:

{
  "card_pin_mode": "online",
  "acquirer_response_code": "0000",
  "card_emv_response": "124046c481.ca8c"
}

Sendo:

ParâmetroCorrespondência no objeto transaction
responseCodeacquirer_response_code, diz respeito ao código de retorno do adquirente Pagar.Me para sua aplicação.
emvDatacard_emv_response: String responsável por finalizar a comunicação entre PinPad e cartão, e usada somente quando a autenticação foi Pin Online, veja a seguir.

card_pin_mode

Você deve usar este parâmetro para validar se é necessário executar o finishTransaction. Sempre que o retorno estiver como online, é necessário finalizar a transação, caso contrário apenas execute o fechamento da conexão com o PinPad.

🚧

Vale ressaltar

Caso não tenha sido possível fazer uma conexão com a API Pagar.me, para inicializar uma transação (e o parâmetro connected seja false), responseCode deve ser 0 e emvData deve ser null.

❗️

Parâmetro local_time

Ao criar uma transação de mundo físico, deve ser enviado o parâmetro local_time, que refere-se a data e hora do dispositivo que está efetuando a transação, em formato ISO String (2017-10-31T14:53:00.000Z).

Banco de dados local

Há um banco de dados no SDK que armazena um histórico de transações dos últimos dias. Uma instância da classe TransactionStorage (me.pagar.mposandroid.localtransactionsdb) pode ser utilizada para definir o limite de dias de armazenamento de uma transação.

Para definir o limite, chame o método setStorageLimit(int). Por padrão, transações serão armazenadas por 30 dias. As transações devem ser armazenadas por, no mínimo, 1 dia. O limite superior para armazenamento é de 120 dias.

As seguintes informações são salvas no banco, encapsuladas em objetos da classe Transaction (me.pagar.mposandroid.localtransactionsdb) .

CampoDescrição
localTransactionIdIdentificador local da transação. Pode ser utilizado para buscar por uma transação ou para estorná-la, caso seja necessário.
amountValor da transação.
firstDigits6 primeiros dígitos do cartão de crédito da transação.
lastDigits4 últimos dígitos do cartão de crédito da transação.
createdAtData e hora de criação da transação (objeto Date).

A classe TransactionStorage possui, também, o método getSearcher. Ele fornece um objeto da classe Searcher (me.pagar.mposandroid.localtransactionsdb) para buscas no banco de dados.

A classe Searcher provê uma interface fluente para definição de filtros de busca. Com exceção dos métodos de execução da busca, todos os seus métodos públicos retornam uma instância atualizada do mesmo objeto.

Abaixo segue a documentação da classe Searcher:

MétodoDescrição
Searcher localTransactionId(String)Define o local_transaction_id de filtro. Equivalente a WHERE local_transaction_id = .
Searcher amount(int)Define o amount de filtro. Equivalente a WHERE amount = .
Searcher firstDigits(String)Define o card_first_digits de filtro. Equivalente a WHERE first_digits = .
Searcher lastDigits(String)Define o card_last_digits de filtro. Equivalente a WHERE last_digits = .
Searcher createdAt(Date)Define o created_at de filtro. Equivalente a WHERE created_at::timestamp = .
Searcher range(Date start, Date end)Define um intervalo de tempo de filtro. Equivalente a WHERE created_at::timestamp >= *start* AND created_at::timestamp <= *end*
Searcher page(int)Define a página de busca. Por padrão a primeira página é trazida na busca.

Mínimo: 1
Searcher limit(int)Define o limite de elementos trazidos por página de busca. Por padrão 10 elementos são trazidos.

Mínimo: 1
Máximo: 100
ArrayList run()Executa a query com os filtros estabelecidos. Retorna uma ArrayList de objetos Transaction.

A resposta obedece aos parâmetros de paginação definidos ou os valores padrão, caso não haja definição do usuário.

Equivalente a SELECT *.
int count()Retorna a quantidade de elementos que satisfazem os filtros estabelecidos.

Equivalente a SELECT COUNT(*).

Ignora paginação.

Podemos, então, buscar todas as transações feitas pelos cartões com início 516230 cujo valor foi R$ 15,95 da seguinte forma:

TransactionStorage storage = new TransactionStorage();
Searcher searcher = storage.getSearcher();

searcher = searcher.firstDigits('516230').amount(1595);

int resultCount = searcher.count();
ArrayList<Transaction> result = searcher.run();

Caso haja 50 transações nesse formato, podemos encontrar as transações 31-40 da seguinte forma:

TransactionStorage storage = new TransactionStorage();
Searcher searcher = storage.getSearcher();

searcher = searcher.firstDigits('516230').amount(1595);
searcher = searcher.limit(10).page(4);

int resultCount = searcher.count();
ArrayList<Transaction> result = searcher.run();

Próximo

Excelente! Você aprendeu como integrar sua aplicação com nosso SDK para mPOS, mas caso encontre algum erro ao longo do caminho, a seguinte tabela pode te ajudar na resolução, segue: