Checkout Transparente da Web no Android

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes! Você receberá um email de confirmação. Somente depois de confirma-lo é que eu poderei lhe enviar os conteúdos semanais exclusivos. Os artigos em PDF são entregues somente para os inscritos na lista.

Email inválido.
Blog /Android /Checkout Transparente da Web no Android

Checkout Transparente da Web no Android

Vinícius Thiengo
(12188) (23)
Go-ahead
"O método consciente de tentativa e erro é mais bem-sucedido que o planejamento de um gênio isolado."
Peter Skillman
Prototipagem Android
Capa do curso Prototipagem Profissional de Aplicativos
TítuloAndroid: Prototipagem Profissional de Aplicativos
CategoriasAndroid, Design, Protótipo
AutorVinícius Thiengo
Vídeo aulas186
Tempo15 horas
ExercíciosSim
CertificadoSim
Acessar Curso
Quer aprender a programar para Android? Acesse abaixo o curso gratuito no Blog.
Lendo
TítuloTest-Driven Development: Teste e Design no Mundo Real
CategoriaEngenharia de Software
Autor(es)Mauricio Aniche
EditoraCasa do Código
Edição1
Ano2012
Páginas194
Conteúdo Exclusivo
Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba gratuitamente conteúdos Android sem precedentes!
Email inválido

Tudo bem?

Neste artigo vamos falar, e praticar, sobre pagamentos em aplicativos Android utilizando o conhecido modelo: Checkout Transparente.

Vale ressaltar já aqui que apesar de existirem inúmeras empresas para processamento de pagamentos em ambientes digitais, são poucas as que têm uma API dedicada somente ao ambiente Android.

Confesso que até a época da construção deste artigo nenhuma das empresas mais populares fornecia uma API de checkout transparente, estável, para apps que processam pagamentos no Brasil.

O objetivo deste conteúdo é apresentar uma implementação de checkout transparente no Android, com APIs Web, de maneira que você consiga utilizar o mesmo roteiro para qualquer software Web de pagamento que você tenha escolhido para rodar também no ambiente mobile.

Antes de prosseguir, não esqueça de se inscrever ðŸ“«na lista de e-mails do Blog para receber todos os conteúdos de desenvolvimento Android exclusivos aqui do Blog, além de recebe-los também em versão PDF (somente por lá).

A seguir os tópicos abordados no artigo:

Modelo comum de checkout transparente

As empresas que fornecem o modelo de pagamento onde os usuários de nosso software Web não precisam sair do site para pagar as compras, geralmente essas empresas compartilham o mesmo modelo de checkout transparente, como o da ilustração abaixo:

Modelo comum de checkout transparente

Esses sistemas liberam uma API front-end em JavaScript onde a parte front-end de nossos sistemas Web é responsável por coletar os dados de cartão de crédito do usuário e então utilizar métodos dessa API JavaScript de Pagamento para enviar esses dados aos servidores deles, que consequentemente, caso aprovado, retornam um token representando esses dados de cartão de crédito.

Esse token somente pode ser utilizado uma vez, logo, então novos tokens devem ser gerados para novos pagamentos, mesmo sendo o mesmo usuário e cartão.

Recebido o token, devemos enviá-lo, agora ao nosso back-end Web, juntamente com alguns dados que identifiquem os produtos em compra.

Logo depois novamente utilizamos a API de pagamento, porém agora no back-end. Nessa parte o pagamento é que será validado (anteriormente foram os dados de cartão apenas), caso aprovado recebemos esse status (paid, por exemplo), caso contrário recebemos o status referente a rejeição e o porquê dessa.

Como informado: esse é o modelo mais comumente adotado pelas empresas de pagamento online quando o funcionalidade é a de checkout transparente.

Por que isso, digo, o passo no front-end? Por que não diretamente enviar os dados de cartão para nosso back-end e utilizá-los somente com a versão back-end de API de pagamento?

Enviar os dados de cartão pela rede para seu back-end quando você não passou pelos testes do PCI-DSS é, teoricamente, um "crime".

Pois há vários itens de segurança que devem ser tratados antes que seu sistema possa realizar esse envio direto de dados de cartão.

Lembre-se de que mesmo utilizando criptografia na camada de aplicação (HTTPS), os dados que saem da máquina do usuário e trafegam para seu servidor, esses dados passam antes por vários outros computadores.

Uma das possíveis penalidades, caso mesmo sabendo dos problemas você envie os dados de cartão para seu back-end Web, é o "boicote" ao seu e-commerce ou sistema de vendas Web ou mobile.

Como?

As empresas de cartão de crédito (VISA, MasterCard, Dinners, ...) começam a negar os pagamentos que vem de seu sistema.

Porém utilizando uma empresa de pagamentos online quem será punido é ela, pois são os servidores dela que se comunicam com os servidores das empresas de pagamento.

Logo, muito provavelmente você não encontrará uma dessas empresas de pagamentos online que permita que você faça isso: utilize a API back-end com dados de cartão de crédito.

Por que utilizar também em sistemas mobile?

Existem vários pontos, abaixo listo alguns:

  • Manter o mesmo sistema de pagamento já utilizado em seus sistemas Web. Isso quando o sistema de pagamento não oferece uma API estável para pagamentos no Android;
  • Evitar pagar taxas muito altas em transações mobile. O Android APP e o In-Billing APP, por exemplo, ficam com nada mais nada menos que 30% do valor de venda, ao menos o In-Billing, enquanto sistemas Web de pagamento ficam com aproximadamente 5%;
  • Utilizar o mesmo sistema de pagamento Web no ambiente mobile, isso para que os clientes se sintam seguros em continuar comprando em ambos os ambientes;
  • Devido a facilidade de integração. O modelo que vamos utilizar aqui é provavelmente mais simples do que algumas APIs de pagamento disponíveis para Android.

Construindo o projeto de pagamento

Agora podemos prosseguir com nosso projeto de pagamento, um pequeno, mas completo projeto que aborda o necessário para rodar os métodos de pagamentos Web no Android, com código nativo.

Aqui vou utilizar o sistema da Pagar.me, porém você pode utilizar o que achar melhor, existem vários. Lembrando que o modelo de checkout transparente quase sempre é o mesmo.

Para auxílio ao mini projeto de pagamento vamos utilizar também:

Com isso vamos seguir com o código.

Nosso primeiro passo é atualizar o Gradle APP level, buid:gradle (Module: app), para já incluir as dependências necessárias:

...
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:24.2.0'
compile 'com.android.support:design:24.2.0'
testCompile 'junit:junit:4.12'
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.google.code.gson:gson:2.7'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'cn.carbs.android:MDDialog:1.0.0'
}
...

 

As três primeiras dependências marcadas, negrito, são para o Retrofit e Gson, a última é para o MDDialog.

No AndroidManifest.xml adicione a permissão de Internet:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="br.com.thiengo.pagamentosapp">

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

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

 

Em nosso domínio do problema vamos ter duas classes, Product e CreditCard. Segue os códigos de Product:

public class Product {
private String id;
private String name;
private String description;
private int stock;
private double price;
private int img;

public Product( String ident, String n, String d, int s, double p, int i ){
id = ident;
name = n;
description = d;
stock = s;
price = p;
img = i;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getDescription() {
return description;
}

public String getStockString() {
return "Apenas "+String.valueOf(stock)+" no estoque.";
}

public double getPrice() {
return price;
}

public String getPriceString() {
return "R$ "+String.valueOf(price).replace('.', ',');
}

public int getImg() {
return img;
}
}

 

Nada de novo, apenas uma classe POJO (atributos e métodos de atualização e acesso aos valores desses atributos). Agora a classe CreditCard:

public class CreditCard {
private String cardNumber;
private String name;
private String month;
private String year;
private String cvv;
private int parcels;
private String error;
private String token;

public CreditCard(Observer observer){
addObserver( observer );
}

public String getCardNumber() {
return cardNumber;
}

public void setCardNumber(String cardNumber) {
this.cardNumber = cardNumber;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getMonth() {
return month;
}

public void setMonth(String month) {
this.month = month;
}

public String getYear() {
return year;
}

public void setYear(String year) {
this.year = year;
}

public String getCvv() {
return cvv;
}

public void setCvv(String cvv) {
this.cvv = cvv;
}

public int getParcels() {
return parcels;
}

public void setParcels(int parcels) {
this.parcels = parcels;
}

public String getError() {
return error;
}

public void setError(String error) {
this.error = error;
}

public String getToken() {
return token;
}

public void setToken(String token) {
this.token = token;
}
}

 

Como em Product, outro POJO.

Com isso podemos prosseguir com os códigos de layout. Começando com o layout da MainActivity. Esse está dividido em duas partes, content_main.xml e activity_main.xml. Começando pelo layout activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:background="#fff"
tools:context="br.com.thiengo.pagamentosapp.MainActivity">

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />

</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_main" />
</android.support.design.widget.CoordinatorLayout>

 

E então o layout referenciado dentro de activity_main.xml, content_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/content_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="br.com.thiengo.pagamentosapp.MainActivity"
tools:showIn="@layout/activity_main">

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<ImageView
android:id="@+id/img"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginRight="16dp"
android:scaleType="centerCrop"
android:src="@mipmap/tennis" />

<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/img"
android:layout_toRightOf="@+id/img"
android:textColor="#212121"
android:textSize="24sp" />

<TextView
android:id="@+id/price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/name"
android:layout_toRightOf="@+id/img"
android:textColor="#f00"
android:textSize="22sp" />

<TextView
android:id="@+id/stock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/price"
android:layout_toRightOf="@+id/img"
android:textColor="#555"
android:textSize="18sp" />

<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/stock"
android:layout_marginTop="16dp"
android:textColor="#212121"
android:textSize="16sp" />

<Button
android:id="@+id/button_buy"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#ff5100"
android:gravity="center"
android:padding="10dp"
android:text="@string/button_buy"
android:textColor="#fff" />
</RelativeLayout>
</ScrollView>

 

Com isso, dois dos três principais layouts XML já foram construídos. O Terceiro layout é referente ao pagamento. Esse será apresentado dentro de um dialog para que não seja necessária a troca de Activity para finalizar a compra.

Vamos agora aos códigos de payment.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:padding="10dp">

<LinearLayout
android:id="@+id/ll_card_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:orientation="horizontal">

<ImageView
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_marginRight="5dp"
android:layout_weight="0.4"
android:contentDescription="Cartão VISA"
android:src="@mipmap/visa" />

<ImageView
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_weight="0.4"
android:contentDescription="Cartão Master Card"
android:src="@mipmap/master_card" />

<android.support.design.widget.TextInputLayout
android:id="@+id/til_card_number"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_weight="1.2">

<EditText
android:id="@+id/card_number"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Número do cartão"
android:inputType="number"
android:text="4485203648101323" />
</android.support.design.widget.TextInputLayout>

</LinearLayout>

<android.support.design.widget.TextInputLayout
android:id="@+id/til_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/ll_card_number"
android:layout_marginTop="10dp">

<EditText
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Nome do proprietário cartão"
android:text="Thiengo Calopsita" />
</android.support.design.widget.TextInputLayout>

<LinearLayout
android:id="@+id/ll_expiration"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/til_name"
android:layout_marginTop="10dp"
android:orientation="horizontal">

<android.support.design.widget.TextInputLayout
android:id="@+id/til_month"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">

<EditText
android:id="@+id/month"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Mês"
android:inputType="number"
android:text="01" />
</android.support.design.widget.TextInputLayout>

<android.support.design.widget.TextInputLayout
android:id="@+id/til_year"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_weight="1">

<EditText
android:id="@+id/year"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Ano"
android:inputType="number"
android:text="2023" />
</android.support.design.widget.TextInputLayout>
</LinearLayout>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/ll_expiration"
android:layout_marginTop="10dp"
android:orientation="horizontal">

<android.support.design.widget.TextInputLayout
android:id="@+id/til_parcels"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:layout_weight="1">

<EditText
android:id="@+id/parcels"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Parcelas"
android:inputType="number"
android:text="2" />
</android.support.design.widget.TextInputLayout>

<android.support.design.widget.TextInputLayout
android:id="@+id/til_cvv"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">

<EditText
android:id="@+id/cvv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="CVV"
android:inputType="number"
android:text="253" />
</android.support.design.widget.TextInputLayout>
</LinearLayout>
</RelativeLayout>

 

O layout é realmente um pouco grande, isso para conter todos os campos obrigatórios, ao menos os de cartão de crédito. Para um formulário mais completo, você pode querer colocar dados de endereço também.

Caso esteja programando para vender infoprodutos, não há tanta necessidade em trabalhar com validação de dados por meio de empresas especializadas (quando você precisa dos dados de endereço do usuário).

Com infoprodutos você pode utilizar o pagamento de forma síncrona. Isso pois cada campo a mais no formulário tende a diminuir o número de conversão em seu sistema (Otimização da Página de Entrada).

Se estiver vendendo produtos físicos, a validação do pagamento incluindo endereço e outros dados, se faz necessária, caso contrário, depois do chargeback você pode perder muito dinheiro, pois o produto foi entregue e o pagamento estornado.

Note que pagamentos com validação por meio de empresas especializadas utilizam sistema assíncrono, onde Web hooks serão utilizados. Sistema assíncrono é inevitável também com boletos bancários.

Com isso podemos seguir com o código, agora da MainActivity:

public class MainActivity extends AppCompatActivity {
private Product product;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

initProduct();
initViews( product );
}

private void initProduct(){
product = new Product(
"6658-3324599755412",
"TÊNIS ADIDAS BARRICADE COURT 2",
"Adiwear: Borracha de altíssima durabilidade que permite que a sola não marque o solo./ Adiprene +: Protege a parte dianteira do pé proporcionando./ Adiprene: Proporciona alta absorção de impactos para amortecer e proteger o calcanhar.",
3,
69.90,
R.mipmap.tennis);
}

private void initViews( Product product ){
((ImageView) findViewById(R.id.img)).setImageResource( product.getImg() );
((TextView) findViewById(R.id.name)).setText( product.getName() );
((TextView) findViewById(R.id.description)).setText( product.getDescription() );
((TextView) findViewById(R.id.stock)).setText( product.getStockString() );
((TextView) findViewById(R.id.price)).setText( product.getPriceString() );
}
}

 

Nada de novo até aqui. Um objeto do tipo Product sendo inicializado e logo depois os dados dele sendo utilizados para preencher as Views em content_main.xml.

Agora vamos adicionar um listener de clique para o Button presente em content_main.xml. Vamos adicionar a referência diretamente no XML:

...
<Button
android:id="@+id/button_buy"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#ff5100"
android:gravity="center"
android:onClick="buy"
android:padding="10dp"
android:text="@string/button_buy"
android:textColor="#fff" />
...

 

Agora o método buy() em MainActivity:

...
public void buy( View view ){
new MDDialog.Builder(this)
.setTitle("Pagamento")
.setContentView(R.layout.payment)
.setNegativeButton("Cancelar", new View.OnClickListener() {
@Override
public void onClick(View v) {

}
})
.setPositiveButton("Finalizar", new View.OnClickListener() {
@Override
public void onClick(View v) {
View root = v.getRootView();

CreditCard creditCard = new CreditCard( MainActivity.this );
creditCard.setCardNumber( getViewContent( root, R.id.card_number ) );
creditCard.setName( getViewContent( root, R.id.name ) );
creditCard.setMonth( getViewContent( root, R.id.month ) );
creditCard.setYear( getViewContent( root, R.id.year ) );
creditCard.setCvv( getViewContent( root, R.id.cvv ) );
creditCard.setParcels( Integer.parseInt( getViewContent( root, R.id.parcels ) ) );

getPaymentToken( creditCard );
}
})
.create()
.show();
}
...

 

Adicionalmente já implementamos o conteúdo de onClick() e do setPositiveButton() de MDDialog. Já inicializamos um objeto CreditCard que contém, depois do clique em "Finalizar", os dados dos campos do layout payment.xml.

O método getPaymentToken() é referente ao envio de dados do Android, os dados preenchidos no dialog de pagamento, para o JavaScript de uma página que vamos criar para trabalhar com a API front-end do sistema de pagamento.

Antes de partirmos para a construção desse método, vamos primeiro criar o folder assets caso ele ainda não exista em seu projeto.

Primeiro altere a visualização do projeto Android para Project (o padrão é Android):

Logo depois clique em (ou expanda) app, logo depois em src e assim em main. Clique com o botão direito em cima de main e então em New, logo depois clique em Directory. Coloque o nome assets e então clique em Ok:

Agora clique com o botão direito do mouse em assets, logo depois em New e então clique em File. Preencha o campo nome com index.html:

Assim pode voltar ao modo de visualização Android.

Expanda assets folder, abra o arquivo index.html e coloque nele o HTML necessário para utilizar a versão front-end da API de pagamento que você escolheu.

A empresa de pagamento que você utiliza tem de ter na documentação dele os scripts JavaScript para integrar ao seu sistema.

Abaixo o HTML que utilizo em index.html:

<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://assets.pagar.me/js/pagarme.min.js"></script>
</head>
<body>

<script>
$(document).ready(function() {
PagarMe.encryption_key = "sua api key";

var creditCard = new PagarMe.creditCard();
creditCard.cardHolderName = Android.getName();
creditCard.cardExpirationMonth = Android.getMonth();
creditCard.cardExpirationYear = Android.getYear();
creditCard.cardNumber = Android.getCardNumber();
creditCard.cardCVV = Android.getCvv();

var fieldErrors = creditCard.fieldErrors();
var errors = [], i = 0;
for(var field in fieldErrors) { errors[i++] = field; }

if(errors.length > 0) {
Android.setError( errors );
} else {
/* se não há erros, gera o card_hash... */
creditCard.generateHash(function(cardHash) {
Android.setToken( cardHash );
});
}
});
</script>
</body>
</html>

 

As duas primeiras tags em <head> são referentes a library do jQuery no CDN do Google (lista de libraries no CDN do Google). E então a referente a API de pagamento que utilizo aqui.

No script JavaScript dentro de <body> estou com a chave de testes. Pode ser que o sistema de pagamentos que você utiliza libere uma versão sandbox, onde as chamadas de métodos é que tendem a ser diferentes do modo em produção.

Em sandbox até mesmo a url da API front-end pode ser diferente da url em modo de produção. Adapte o código aqui de acordo com o código que você tem de utilizar no front-end de seu sistema.

Note que a entidade Android é referente a vinculação de interface que temos de realizar nos códigos nativos do Android. Vinculação entre WebView e JavaScript.

Todo o restante é referente a lógica de negócio que utilizei junto a API de pagamento, para melhor atender ao meu domínio do problema no APP Android. O algoritmo que realmente nos interessa, digo, o retorno dele, é: 

...
creditCard.generateHash(function(cardHash) {
Android.setToken( cardHash );
});
...

 

O script acima é responsável por enviar o token de pagamento, gerado no front-end, para nosso código Android.

Em content_main.xml (tem que ser o layout do produto e não o do dialog) vamos adicionar o XML do WebView, logo abaixo do Button de pagamento:

...
<WebView
android:id="@+id/web_view"
android:layout_width="0.1dp"
android:layout_height="0.1dp"
android:layout_below="@+id/button_buy"></WebView>
...

 

Agora vamos colocar o conteúdo do método getPaymentToken(), em MainActivity:

private void getPaymentToken( CreditCard creditCard ){
WebView webView = (WebView) findViewById(R.id.web_view);
webView.getSettings().setJavaScriptEnabled( true );
webView.addJavascriptInterface( creditCard, "Android" );
webView.loadUrl("file:///android_asset/index.html");
}

 

Note como é que referenciamos um arquivo no folder assets. Note também que em addJavaScriptInterface() estamos utilizando, além do label "Android" que é utilizado no JavaScript, um objeto do tipo CreditCard. Mais precisamente o enviado como argumento a partir do onClick() de setPositiveButton() do objeto de dialog, MDDialog.

A classe CreditCard precisa ser atualizada para que os métodos invocados dela no código JavaScript possam funcionar. Para isso devemos colocar @JavascriptInterface nesses métodos:

public class CreditCard {
...

@JavascriptInterface
public String getCardNumber() {
return cardNumber;
}
...

@JavascriptInterface
public String getName() {
return name;
}
...

@JavascriptInterface
public String getMonth() {
return month;
}
...

@JavascriptInterface
public String getYear() {
return year;
}
...

@JavascriptInterface
public String getCvv() {
return cvv;
}
...

@JavascriptInterface
public void setError(String... errors) {
for( String e : errors ){
if( e.equalsIgnoreCase("card_number") ){
error += "Número do cartão, inválido; ";
}
}
}
...

@JavascriptInterface
public void setToken(String token) {
this.token = token;
Log.i("log", "Token: " + token); /* PARA VERIFICAR CRIAÇÃO DE TOKEN */
}
}

 

Veja que também atualizamos o método setError(). Isso para que ele trabalhe com o array de erros que pode ser enviado do JavaScript. Com isso, o código JavaScript que foi apresentado anteriormente já é todo funcional.

Rodando o projeto, temos:

Processando checkout transparente em aplicativo Android

Logo depois, clicando em "Comprar" e então em "Finalizar", temos nos logs do Android Studio que o token está sendo gerado e enviado ao nosso objeto creditCard:

...
...I/log: token: 185468_HWHGF6eWwkn79HDv3H0AsZZhuvbehUWFj3BEJDAyCgFoPrN6N57Ya4weBdTRz8llXJ...
...

 

Show de bola! Agora precisamos enviar esse token e mais alguns dados de produto ao nosso back-end, para que o pagamento seja processado.

Primeiro vamos aplicar o padrão de projeto Observer em nossa classe CreditCard. Ela será a entidade observada por outros objetos que precisam dos dados dela. Mais precisamente do token e da mensagem de erro.

Vamos utilizar as entidades nativas do Java para trabalhar com o padrão Observer, logo atualize os seguintes códigos em CreditCard:

public class CreditCard extends Observable {
...

public CreditCard(Observer observer){
addObserver( observer );
}
...

@JavascriptInterface
public void setError(String... errors) {
for( String e : errors ){
if( e.equalsIgnoreCase("card_number") ){
error += "Número do cartão, inválido; ";
}
/* TODO */
}
Log.i("log", "error: "+error);

setChanged();
notifyObservers();
}
...

@JavascriptInterface
public void setToken(String token) {
this.token = token;
Log.i("log", "Token: "+token);

setChanged();
notifyObservers();
}
}

 

Note que agora temos um construtor onde devemos vincular as entidades observadoras do padrão a nossa entidade observada, ou seja, nossos Observers em nossa classe Subject.

Entre no post do padrão indicado acima para entender como ele funciona, é bem simples.

Dois dados, quando são atualizados, são gatilhos de notificação as classes observadoras, são eles: token e erro. Logo, somente nos métodos de atualização dessas variáveis é que temos as chamadas abaixo:

...
setChanged();
notifyObservers();
...

 

Ok, você deve estar se perguntando: quais serão as entidade observadoras?

Na verdade somente uma. A instância da MainActivity, pois os códigos de conexão, que precisam de token ou erro, vão estar em um método nessa Activity.

Assim atualizamos a assinatura dessa classe para implementar o a Interface Update. Consequentemente atualizamos a instanciação de CreditCard em onClick() de setPositiveButton():

public class MainActivity extends AppCompatActivity implements Observer {
...

public void buy( View view ){
new MDDialog.Builder(this)
.setTitle("Pagamento")
.setContentView(R.layout.payment)
.setNegativeButton("Cancelar", new View.OnClickListener() {
@Override
public void onClick(View v) {

}
})
.setPositiveButton("Finalizar", new View.OnClickListener() {
@Override
public void onClick(View v) {
View root = v.getRootView();

CreditCard creditCard = new CreditCard( MainActivity.this );
...
}
})
.create()
.show();
}

private String getViewContent( View root, int id ){
EditText field = (EditText) root.findViewById(id);
return field.getText().toString();
}

private void getPaymentToken( CreditCard creditCard ){
WebView webView = (WebView) findViewById(R.id.web_view);
webView.getSettings().setJavaScriptEnabled( true );
webView.addJavascriptInterface( creditCard, "Android" );
webView.loadUrl("file:///android_asset/index.html");
}

@Override
public void update(Observable o, Object arg) {
/* TODO */
}
}

 

Antes de prosseguir com o conteúdo do método sobrescrito, update(), vamos ter de criar uma Interface para que seja possível trabalhar com o Retrofit. Segue código de PaymentConnection:

public interface PaymentConnection {
@FormUrlEncoded
@POST("package/ctrl/CtrlPayment.php")
public Call<String> sendPayment(
@Field("product_id") String id,
@Field("value") double value,
@Field("token") String token,
@Field("parcels") int parcels
);
}

 

Vou assumir que o código acima não é uma espécie de "pergaminho criptografado" para você, pois já conhece o conteúdo sobre a Retrofit API.

Agora podemos prosseguir com o conteúdo do método update() na MainActivity:

...
@Override
public void update(Observable o, Object arg) {
CreditCard creditCard = (CreditCard) o;

/* CLÁUSULA DE GUARDA */
if( creditCard.getToken() == null ){
buttonBuying( false );
showMessage( creditCard.getError() );
return;
}

Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://192.168.25.221:8888/android-payment/")
.addConverterFactory( GsonConverterFactory.create() )
.build();

PaymentConnection paymentConnection = retrofit.create(PaymentConnection.class);
Call<String> requester = paymentConnection.sendPayment(
product.getId(),
product.getPrice(),
creditCard.getToken(),
creditCard.getParcels()
);

requester.enqueue(new Callback<String>() {
@Override
public void onResponse(Call<String> call, Response<String> response) {
buttonBuying( false );
showMessage( response.body() );
}

@Override
public void onFailure(Call<String> call, Throwable t) {
buttonBuying( false );
Log.e("log", "Error: "+t.getMessage());
}
});
}
...

 

No código acima, sabemos que ele é acionado somente quando há uma atualização em token ou erro nas instâncias de CreditCard. Logo, nosso Observable é na verdade nossa instância de CreditCard.

Antes de prosseguir com o envio de dados para o back-end Web, utilizamos o padrão Cláusula de Guarda para garantir se os dados de envio, mais precisamente o token que é gerado dinamicamente, estão todos presentes, caso não, mudamos o label do Button de compra para "Comprar" novamente, além de apresentar a mensagem de erro.

Caso tudo ok, ou seja, o token está presente. Inicializamos as configurações do Retrofit e realizamos o envio.

Note que aqui nosso projeto Web está localhost, por isso o IP local de minha máquina na url de conexão.

Vamos prosseguir com a implementação dos métodos referenciados em update(), buttonBuying() e showMessage():

...
private void showMessage( final String message ){
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText( MainActivity.this, message, Toast.LENGTH_LONG ).show();
}
});
}
...
private void buttonBuying( final boolean status ){
runOnUiThread(new Runnable() {
@Override
public void run() {
String label;

label = getResources().getString(R.string.button_buy);
if( status ){
label = getResources().getString(R.string.button_buying);
}

((Button) findViewById(R.id.button_buy)).setText(label);
}
});
}
...

 

showMessage() dispensa comentários, pois faz exatamente o que o nome indica.

buttonBuying() altera o label do Button de compra. Quando o usuário clica em "Finalizar" no dialog de pagamento, o label desse Button altera para "Processando pagamento...", ou seja, o parâmetro status é true.

Caso contrário o label volta ao valor padrão, "Comprar", quando status é false.

Muito provavelmente você deve estar se perguntando: por que a utilização do runOnUiThread()?

Esse código está ali devido as chamadas em update() aos métodos, showMessage() e buttonBuying().

Mais precisamente quando as invocações a esses métodos não são realizadas dentro dos métodos de resposta do retrofit, onResponse() e onFailure() (dentro dos métodos de retrofit é garantida que a Thread sendo utilizada é a Thread principal).

Note que o método update() somente é chamado quando há chamadas aos métodos setToken() ou setError(). Em nosso caso essas chamadas somente ocorrerão no código JavaScript em nossa WebView.

Qual o problema quanto a isso?

Essas execuções em JavaScript ativam nosso objeto do tipo CreditCard, porém fora da Thread principal, logo, atualizar qualquer View fora dessa Thread... nós já sabemos do resultado, crash!

O runOnUiThread() vai funcionar para chamadas dentro ou fora da Thread de UI.

Antes de prosseguir para o back-end para ver como fica nosso código, vamos colocar uma linha de código antes da instanciação de CreditCard, em onClick() de setPositiveButton(). Segue linha a ser a adicionada:

...
buttonBuying( true );
...

Isso, pois é nesse ponto que nosso Button de pagamento já deve ter o label dele atualizado.

Agora podemos prosseguir com o código back-end. Em me caso, o Pagar.me me oferece uma API em PHP, então é ela que utilizo. O objetivo aqui, do back-end, é somente um teste para ver se o pagamento passa sem problemas:

require("../util/pagarme-php/Pagarme.php");

/* API DE PAGAMENTO SENDO UTILIZADA NO BACK-END, COM O TOKEN AO INVÉS DE DADOS DE CARTÃO */
Pagarme::setApiKey("api key de testes");
$transaction = new PagarMe_Transaction(array(
'amount' => ($_POST['value'] * 100),
'card_hash' => $_POST['token']
));
$transaction->charge();
$status = $transaction->status;

/* OPCIONAL, PARA SABER DOS DDOS ENVIADOS A API DE PAGAMENTO */
$file = fopen('token.txt', 'w');
fwrite($file, $_POST['product_id']."\n");
fwrite($file, ($_POST['value'] * 100)."\n");
fwrite($file, $_POST['token']."\n");
fwrite($file, $_POST['parcels']."\n");
fwrite($file, $status."\n");
fclose($file);

/* MENSAGEM DE RETORNO AO ANDROID, O ANDROID ENTENDE COMO OBJETO JSON */
if( strcasecmp($status, 'refused') == 0 ){
echo '"Pagamento recusado. Tente outro cartão."';
}
else{
echo '"Pagamento aprovado. Em breve o produto estará em suas mãos."';
}

 

Note que se seu back-end for em Python, Java, Ruby, ... utilize-o como já utiliza para pagamentos Web, pode até mesmo reaproveitar os mesmos códigos, somente não esqueça de identificar quando o pagamento veio da interface mobile e quando da Web.

Abaixo o projeto sendo executado e com o Button "Finalizar" já pressionado:

Pagamento processado com sucesso no aplicativo Android

Então acessando o Dashboard da empresa de pagamentos online, temos, em modo teste:

Dashboard de pagamento da PagarMe

Com isso integramos o pagamento com o modelo checkout transparente ao nosso projeto Android, projeto em código com linguagem oficial, a linguagem Java.

Muito mais simples que você provavelmente deve ter imaginado que seria.

Antes de prosseguir para a conclusão, não esqueça de se inscrever na ðŸ“« lista de e-mails do Blog para receber semanalmente os conteúdos exclusivos sobre desenvolvimento mobile.

Se inscreva também no canal do Blog em YouTube Thiengo.

Vídeo com a implementação passo a passo

Abaixo o vídeo com a implementação passo a passo do projeto proposto aqui. Ele é um pouco longo, mas é bem completo:

Para acessar o projeto versão Android entre no seguinte GitHub: https://github.com/viniciusthiengo/PagamentosAPP

Para acessar o versão Web entre em: https://github.com/viniciusthiengo/PagamentosAPP-web-version

Conclusão

As principais vantagens na utilização do checkout transparente, com APIs Web, em plataforma mobile estão em:

  • Manter o mesmo meio de pagamento. Caso quando já utilizando a mesma empresa e API na versão Web do projeto;
  • e Em manter as mesmas taxas de cobrança da empresa de pagamentos, independente do ambiente utilizado.

Note que o código, como apresentado aqui, não viola em nada as recomendações das empresas de pagamento, pois nós nos mantemos "não trabalhando" com dados de cartão de crédito em nosso back-end Web.

O ponto negativo no modelo de código apresentado em artigo, a princípio, está em quando o sistema de pagamento utilizado por você já oferece uma API Android que é estável e pode ser integrada mais facilmente.

Neste caso vale ao menos testar essa versão já pronta da API para Android.

Para layouts mais sofisticados para formulários de pagamento, acesse o conteúdo do link a seguir: Android Arsenal (CreditCard).

Caso você tenha dúvidas ou dicas, não deixe de comentar abaixo que logo eu lhe respondo.

Curtiu o conteúdo? Não esqueça de compartilha-lo. E, por fim, se inscreva na 📩 lista de e-mails, respondo às suas dúvidas também por lá.

Abraço.

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes!
Email inválido

Relacionado

Monetização sem Anúncios utilizando a Huq SDKMonetização sem Anúncios utilizando a Huq SDKAndroid
As 33 Coisas que Todo Programador Deve Parar de FazerAs 33 Coisas que Todo Programador Deve Parar de FazerEmpreendedorismo
Padrão de Projeto: Template Method (Método Template)Padrão de Projeto: Template Method (Método Template)Android
ConstraintLayout, Melhor Performance no AndroidConstraintLayout, Melhor Performance no AndroidAndroid

Compartilhar

Comentários Facebook

Comentários Blog (23)

Para código / script, coloque entre [code] e [/code] para receber marcação especifica.
Forneça seu nome válido.
Forneça seu email válido.
Forneça o comentário.
Enviando, aguarde...
Vilquer (1) (0)
20/06/2020
Vinicios, sou novo em fazer aplicativos e já de cara apareceu um baita desafio. EU não gosto de dizer não quando se tem o mínimo de possibilidade de conseguir fazer as coisas, então... vamos lá... Olha só, eu estou implementando um app de vendas e não entendi, no retrofit, pq vc colocou um IP estático, sendo que cada usuário tem seu IP, definido pelo servidor DHCP.
A outra questão é relativo ao PHP back-end fornecido pelo Pagar.me. É necessário implementá-lo?
Muito obrigado, vc tem ajudado muito mesmo.
Responder
Vinícius Thiengo (0) (0)
21/06/2020
Vilquer, tudo bem?

Sobre sua primeira dúvida...

... eu confesso que não me lembro bem o porquê de utilizar o IP do roteador e não algum dos IPs que o próprio roteador libera aos usuários.

Mas se não me engano eu tive problemas com esse IP de usuário, então tive que utilizar o do router.

Mas fique tranquilo quanto a isso e quanto à minha falta de expertise em Redes de Computadores.

Isso, pois o uso de qualquer IP em ambiente de testes é temporário.

Em produção você utilizará uma URL ou o IP direto de seu servidor caso ele não esteja em ambiente compartilhado.

De qualquer forma, quando em ambiente de desenvolvimento, hoje em dia eu costumo utilizar o Ngrok ao invés de algum IP.

Dê uma olhada nele, é excelente e tem versão free: https://ngrok.com/

Sobre sua segunda dúvida...

... na verdade a API backend (PHP) da PagarMe é apenas uma API de checkout transparente que escolhi utilizar.

Ou seja, você pode utilizar qualquer outra API Web de checkout transparente.

O fluxo apresentado no artigo e vídeo aula será o mesmo.

Na época, se me lembro bem, escolhi a PagarMe, pois ela era a única que não me solicitava uma série de dados que eu não tinha em mãos.

Vilquer, é isso.

Surgindo mais dúvidas, pode enviar.

Abraço.
Responder
26/05/2020
Olá Thiengo,

Parabéns pela aula! É realmente muito difícil encontrar um material como esse na internet, até agora só encontrei o seu.

Fiz o download do seu projeto e alterei a as chaves. Instalei o MAMP e fiz o upload do projeto web para a pasta htdocs com o mesmo nome "android-payment", lá tbm fiz as alterações necessárias das chaves. Ao executar o projeto, estava recebendo erro de falha na porta 8888, então verifiquei no MAMP a porta que estava utilizando (80) e fiz a alteração. Resolveu esse problema, conseguiu se conectar e tudo mas não gerou o arquivo .txt do token na pasta "ctrl", porém no log consigo ver o token gerado. Ao clicar em comprar não me retorna nada.

Recebo o seguinte erro no console do android studio:
E/log: Error: null

Poderia me ajudar? Lembrando que estou executando o projeto diretamente do celular conectado via usb ao notebook. Fiz o cadastro normalmente no Pagar.me sem CNPJ/MEI, e eles me deram as chaves de teste normalmente.

Desde já agradeço.
Responder
Vinícius Thiengo (0) (0)
26/05/2020
Oziel, tudo bem?

Show que você curtiu o conteúdo.

Vamos ao problema...

Eu não consegui identificar se você conseguiu a conexão do aplicativo Android (que envia o token gerado) com o backend Web (que concretiza a compra utilizando o token).

Ao menos a conexão do Android com o backend Web aconteceu?

Pergunto isso, pois é comum chegar até mim dúvidas similares a sua, onde na verdade a URL que deve ser utilizada no aplicativo Android para conexão com o backend Web é "bem" diferente do modelo de URL que eu utilizei na vídeo aula.

Isso porque o ambiente de desenvolvimento do desenvolvedor que está estudando o artigo tende a ser um tanto diferente do meu.

Verificado / resolvido essa questão, o projeto tende a rodar sem problemas.

Então, chegou a verificar essa conexão Android -> backend Web?

Abraço.
Responder
26/05/2020
Vlw pela resposta. Obtive o retorno do token, porém não concretiza a compra, recebo um retorno null.
Responder
Vinícius Thiengo (0) (0)
27/05/2020
Oziel, blz.

Então vou assumir que o token está sim sendo enviado com sucesso ao backend Web.

Porém a API Web (PHP) de pagamentos da PagarMe, quando solicitada para validar o token e consequentemente a compra...

... essa solicitação no backend Web está tendo como retorno o valor "null" e o dashboard da PagarMe não mostra compra alguma com o status "finalizada".

Sendo assim é possível assumir que a versão PHP da API de pagamentos no backend Web precisa ser atualizada em relação ao que está sendo apresentado em artigo / vídeo.

Oziel, preciso que você realize essa atualização de acordo com a documentação oficial PagarMe para a API PHP de Checkout Transparente, exatamente como no link a seguir:

https://github.com/pagarme/pagarme-php

Oziel, se mesmo depois da atualização o token ainda não estiver sendo validado no backend Web.

Volte aqui e ao mesmo tempo entre em contato também com o suporte PagarMe, pois há a remota possibilidade da API PHP deles estar bugada.

É isso.

Surgindo mais dúvidas, pode enviar.

Abraço.
Responder
26/01/2019
Olá Thiengo,

esqueci de mandar o restante do código gerado no log do android studio. Segue:


E/log: Error: null
D/Surface: Surface::setBuffersDimensions(this=0x7f78249c00,w=720,h=1280)
V/InputMethodManager: START INPUT: android.webkit.WebView{594dff5 VFEDHVC. .F...... 0,958-1,958 #7f07009f app:id/web_view} ic=null tba=android.view.inputmethod.EditorInfo@374c72d0 controlFlags=#100
D/ActivityThread: ACT-AM_ON_PAUSE_CALLED ActivityRecord{13151917 token=android.os.BinderProxy@c6ae104 {br.com.thiengo.pagamentosapp/br.com.thiengo.pagamentosapp.MainActivity}}
                  ACT-PAUSE_ACTIVITY handled : 0 / android.os.BinderProxy@c6ae104
V/ActivityThread: Finishing stop of ActivityRecord{13151917 token=android.os.BinderProxy@c6ae104 {br.com.thiengo.pagamentosapp/br.com.thiengo.pagamentosapp.MainActivity}}: show=true win=com.android.internal.policy.impl.PhoneWindow@336872c9
D/ActivityThread: ACT-STOP_ACTIVITY_SHOW handled : 0 / android.os.BinderProxy@c6ae104
D/Surface: Surface::setBuffersDimensions(this=0x7f78249c00,w=720,h=1280)
V/ActivityThread: Performing resume of ActivityRecord{13151917 token=android.os.BinderProxy@c6ae104 {br.com.thiengo.pagamentosapp/br.com.thiengo.pagamentosapp.MainActivity}}
D/ActivityThread: ACT-AM_ON_RESUME_CALLED ActivityRecord{13151917 token=android.os.BinderProxy@c6ae104 {br.com.thiengo.pagamentosapp/br.com.thiengo.pagamentosapp.MainActivity}}
V/ActivityThread: Resume ActivityRecord{13151917 token=android.os.BinderProxy@c6ae104 {br.com.thiengo.pagamentosapp/br.com.thiengo.pagamentosapp.MainActivity}} started activity: false, hideForNow: false, finished: false
                  Resuming ActivityRecord{13151917 token=android.os.BinderProxy@c6ae104 {br.com.thiengo.pagamentosapp/br.com.thiengo.pagamentosapp.MainActivity}} with isForward=true
D/FeatureProxyBase: FeatureProxyBase class constructor
                    getService(), serviceName = multiwindow_service_v1
V/PhoneWindow: DecorView setVisiblity: visibility = 0 ,Parent =ViewRoot{502d678 br.com.thiengo.pagamentosapp/br.com.thiengo.pagamentosapp.MainActivity,ident = 0}, this =com.android.internal.policy.impl.PhoneWindow$DecorView{3d5b4c9c V.E..... R.....I. 0,0-720,1280}
V/ActivityThread: Scheduling idle handler for ActivityRecord{13151917 token=android.os.BinderProxy@c6ae104 {br.com.thiengo.pagamentosapp/br.com.thiengo.pagamentosapp.MainActivity}}
D/ActivityThread: ACT-RESUME_ACTIVITY handled : 1 / android.os.BinderProxy@c6ae104
V/InputMethodManager: onWindowFocus: android.webkit.WebView{594dff5 VFEDHVC. .F...... 0,958-1,958 #7f07009f app:id/web_view} softInputMode=272 first=false flags=#81810100
                      START INPUT: android.webkit.WebView{594dff5 VFEDHVC. .F...... 0,958-1,958 #7f07009f app:id/web_view} ic=null tba=android.view.inputmethod.EditorInfo@33f214ef controlFlags=#101
D/Surface: Surface::setBuffersDimensions(this=0x7f78249c00,w=720,h=1280)
D/ActivityThread: ACT-AM_ON_PAUSE_CALLED ActivityRecord{13151917 token=android.os.BinderProxy@c6ae104 {br.com.thiengo.pagamentosapp/br.com.thiengo.pagamentosapp.MainActivity}}
D/ActivityThread: ACT-PAUSE_ACTIVITY handled : 1 / android.os.BinderProxy@c6ae104
D/OpenGLRenderer: Flushing caches (mode 0)
D/Surface: Surface::disconnect(this=0x7f78249c00,api=1)
D/GraphicBuffer: unregister, handle(0x7f63cdbd40) (w:720 h:1280 s:720 f:0x1 u:0x000b00)
D/GraphicBuffer: unregister, handle(0x7f6dda4380) (w:720 h:1280 s:720 f:0x1 u:0x000b00)
D/GraphicBuffer: unregister, handle(0x7f63d73a20) (w:720 h:1280 s:720 f:0x1 u:0x000b00)
D/OpenGLRenderer: Flushing caches (mode 0)
V/ActivityThread: Finishing stop of ActivityRecord{13151917 token=android.os.BinderProxy@c6ae104 {br.com.thiengo.pagamentosapp/br.com.thiengo.pagamentosapp.MainActivity}}: show=false win=com.android.internal.policy.impl.PhoneWindow@336872c9
V/PhoneWindow: DecorView setVisiblity: visibility = 4 ,Parent =ViewRoot{502d678 br.com.thiengo.pagamentosapp/br.com.thiengo.pagamentosapp.MainActivity,ident = 0}, this =com.android.internal.policy.impl.PhoneWindow$DecorView{3d5b4c9c I.E..... R....... 0,0-720,1280}
D/ActivityThread: ACT-STOP_ACTIVITY_HIDE handled : 0 / android.os.BinderProxy@c6ae104
D/OpenGLRenderer: Flushing caches (mode 1)
                  [TaskMgr] 0x7f781f78f0 stop
                  [TaskMgr] Exit thread hwuiTask1 (27073)
                  [TaskMgr] Exit thread hwuiTask2 (27098)
D/OpenGLRenderer: PathCache::clear count = 0

Esse "D/GraphicBuffer: unregister" quer dizer que não estou registrado? Fiz o cadastro normalmente na Pagar.me e foi gerado as chaves da Dashboard.

Conforme vejo no painel da Pagar.me:

"Você preencheu 5% do seu cadastro comercial
Você está usando a versão de teste da Dashboard do Pagar.me - operações feitas nessa versão não geram pagamentos reais. Se você deseja integrar o Pagar.me como um meio de pagamento na sua loja
Clique aqui para entrar em contato com o nosso time comercial. "

Desde já agradeço.
Responder
Vinícius Thiengo (0) (0)
01/02/2019
Diogo, tudo bem?

Somente para constar, novamente, que já me comuniquei com você pelo YouTube e você informou que o problema com o checkout transparente da PagarMe foi resolvido depois de alguns contatos entre você e o suporte da empresa.

Abraço.
Responder
26/01/2019
Olá Thiengo,

Parabéns pela aula! É realmente muito difícil encontrar um material como esse na internet, até agora só encontrei o seu.

Fiz o download do seu projeto e alterei a as chaves. Instalei o MAMP e fiz o upload do projeto web para a pasta htdocs com o mesmo nome "android-payment", lá tbm fiz as alterações necessárias das chaves. Ao executar o projeto, estava recebendo erro de falha na porta 8888, então verifiquei no MAMP a porta que estava utilizando (80) e fiz a alteração. Resolveu esse problema, conseguiu se conectar e tudo mas não gerou o arquivo .txt do token na pasta "ctrl", porém no log consigo ver o token gerado. Ao clicar em comprar não me retorna nada.

Recebo o seguinte erro no console do android studio:
E/log: Error: null

Poderia me ajudar? Lembrando que estou executando o projeto diretamente do celular conectado via usb ao notebook. Fiz o cadastro normalmente no Pagar.me sem CNPJ/MEI, e eles me deram as chaves de teste normalmente.

Desde já agradeço.
Responder
Vinícius Thiengo (0) (0)
01/02/2019
Diogo, tudo bem?

Somente para constar que já me comuniquei com você pelo YouTube e você informou que o problema com o checkout transparente da PagarMe foi resolvido depois de alguns contatos entre você e o suporte da empresa.

Abraço.
Responder
Maicon (1) (0)
07/01/2019
Fala Thiengo, blz?

Estou tentando integrar o pagar.me no modo markeplace, estou me batendo para encontrar uma forma segura de salvar o cartão do usuário, por exemplo: Para cada compra ele tem que digitar todos os dados do cartão novamente, isso faz com que muitos usuários desistam da compra. Você indica algum modo de salvar essas informações, assim como é feito no ifood? Desde já agradeço.
Responder
Vinícius Thiengo (0) (0)
09/01/2019
Maicon, tudo bem?

Esse "salvar os dados de cartões de crédito de clientes" é algo muito crítico, você precisa de algumas certificações para fazer isso, digo: no mundo ideal é o que é esperado de um software / empresa que salva esse tipo de dado.

Se attackers descobrem que seu sistema tem um banco de dados com dados de cartões de crédito e que estes não estão em uma base com criptografia certificada e com segurança certificada, certamente seu sistema vai virar alvo de ataques e se os dados forem obtidos, os attackers seguramente os utilizarão para "n" compras online.

Um outro ponto é que se as empresas de cartões de credito tomam conhecimento de que seu sistema guarda dados bancários sem as certificações de segurança requeridas por elas, essas empresas (VISA e MasterCrad, por exemplo) não mais liberarão pagamentos em seus site / aplicativo.

Se eu não me engano a PagarMe tem uma API que permite reaproveitar um token utilizado em uma compra anterior, dessa forma o seu sistema terá salvo somente o token de pagamento do usuário e não os dados do cartão dele (pode salvar também os últimos quatro números do cartão), esses ficam na PagarMe que é uma empresa de pagamentos e que tem autorização para isso.

O token será válido somente em seu aplicativo.

Maicon, confesso que não lembro a página desta API da PagarMe, mas certamente o suporte deles vai lhe ajudar com isso.

Se realmente você quiser seguir com a ideia de salvar os dados de cartão de crédito, estude o PCI-DSS para entender como proceder:

https://www.pcisecuritystandards.org/pci_security/

Abraço.
Responder
Maicon (1) (0)
09/01/2019
Olá Thiengo, obrigado pela resposta amigo.
Sim, entendo que é algo bem complexo, eu prefiro não salvar os dados, somente seria interessante alguma forma de fazer sem que o usuário tenha que digitar todos os dados novamente. Vou ver se consigo algo com o pagar.me. Mais uma vez obrigado.
Responder
Matheus (1) (0)
03/03/2019
Olá, Maicon! Como o Thiengo mencionou, há sim a geração de um token que pode ser armazenado na sua base de dados. Eles tratam como "card_id" e necessita de um "card_hash" anterior.

https://docs.pagar.me/docs/realizando-uma-transacao-de-cartao-de-credito

(vá até a seção "Criando um cartão para one-click buy")

Estava com o mesmo problema... Espero ter sido útil!
Responder
Maicon (2) (0)
03/03/2019
Matheus, obrigado pela resposta. Ainda não consegui chegar na solução, mas estou vendo a documentação, conforme seu comentário e acredito que seja esse o caminho correto para a solução. Mais uma vez, obrigado!
Responder
24/06/2018
Para quem precisar usar a versão 3.1 do pagarme segue o código do index.html que estou utilizando:
<!DOCTYPE html>
<html lang="en">
<head>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js "></script>
    <script src="https://assets.pagar.me/pagarme-js/3.1/pagarme.min.js "></script>
</head>
<body>
<script>
        $(document).ready(function() { // quando o jQuery estiver carregado...
            pagarme.client.connect({ encryption_key: 'SUA_KEY' })
              .then(client => {
                return client.security.encrypt({
                  card_number: Android.getCardNumber(),
                  card_holder_name: Android.getName(),
                  card_expiration_date: Android.getMonth()+ '/' +Android.getYear(),
                  card_cvv: Android.getCvv(),
                })
              })
              .then(card_hash => Android.setToken(card_hash))
          });
    </script>
</body>
</html>
Responder
Vinícius Thiengo (0) (0)
24/06/2018
Obrigado, Lucas. Abraço.
Responder
27/01/2019
Obrigado Lucas!!! Estou utilizando a versão 3.1 do pagar.me.
Sabe dizer se o código php também muda?
Responder
André Primo (1) (0)
24/10/2017
Se trocarmos a key de test para live, ele fará os pagamentos corretamente? Tipo, usando esse seu exemplo, somente usando os dados que você forneceu de cartão de crédito funcionam, tentei com um de verdade, mesmo que seja só sandbox, e não funcionou.
Responder
Vinícius Thiengo (0) (0)
30/10/2017
André, tudo bem?

Quando eu utilizei o pagar.me em modo produção, tive de alterar as chaves, colocando as ?live" e também tive de solicitar a equipe deles a liberação em modo produção.

Eles fizeram uma entrevista comigo para poder liberar.

A um tempo atrás um dos seguidores do Blog e canal informou que agora está ainda mais rigoroso, tem de comprovar um faturamento mínimo de R$ 3.000,00.

Porém o modelo que apresentei é genérico, serve para qualquer checkout transparente, ou seja, a solução caba por ser, caso você já não tenha os R$ 3.000,00 de faturamento mínimo:

- MercadoPago: https://www.mercadopago.com.br/developers/pt/api-docs/
- PagSeguro: https://pagseguro.uol.com.br/receba-pagamentos.jhtml#checkout-transparent
- Moip: https://moip.com.br/vantagens
- Outras APIs de pagamento com checkout transparente e menos burocracia.

Abraço.
Responder
Guihgo (1) (0)
12/09/2016
em 4:00 min você mostrou o workflow do processo da compra. Nas últimas etapas (quando o sistema de pagamento retorna status da transição para o comprador) tem uma falha, porque se o sistema de pagamento de transição não consegui retorna o status do pagamento (nem se for um erro), a transição será completada ( debitada do cartão do comprador) no sistema de pagamento, mas não haverá confirmação do sucesso da transição para o usuário.   Assim haverá pagamento, mas o usuário não poderá ter o produto.
Responder
Vinícius Thiengo (1) (0)
12/09/2016
Guihgo, blz?
Nesse contexto, seu backend deve ser mais elaborado. No fluxo que apresentei somente tem o caminho perfeito. Esse caso é ponto fora da curva, mas de qualquer forma vc está certo, isso pode ocorrer. Nem mesmo um rollback nos servers de pagamento estaria a tempo de reverter a compra, pois os dados de feedback podem se perder na rede (como falei, ponto fora da curva).

Com essa problemática e um backend um pouco mais trabalhado, teria ao menos um envio de email, ou algo parecido, para vc dev do sistema, informando o problema e o id da compra. Assim é mudar o status da compra no bd de seu sistema e enviar / liberar o produto ou ir no dashboard da empresa de pagamento e aplicar o estorno.

Com isso deve resolver, mas terá trabalho manual. Abraço
Responder
13/09/2016
Interessante seu discurso. E ótima solução. Vou precisar repensa na estrutura do processo. Mas de qualquer forma, muito obrigado pela atenção.
Responder