Esta documentação cobre recursos do nosso próximo lançamento. Embora a funcionalidade principal e o fluxo de trabalho descritos aqui permaneçam inalterados, você pode notar algumas melhorias antes do lançamento final.
Com o Pix Biometria da Belvo, coletar pagamentos de usuários se torna simples, eliminando a necessidade de os usuários navegarem até sua instituição financeira para aprovar cada solicitação de pagamento individualmente.
Este guia demonstra a integração usando os métodos de conveniência do SDK Android da Belvo. O SDK lida com a comunicação de backend, fluxos OAuth e registro FIDO internamente, proporcionando um caminho de integração simplificado.
O primeiro passo para habilitar a coleta de pagamentos biométricos é registrar o dispositivo do usuário com sua instituição. Durante o registro, dados chave sobre o dispositivo e as credenciais de chave pública do usuário são registrados de forma segura com sua instituição, garantindo que pagamentos futuros possam ser confirmados usando apenas autenticação biométrica.
Uma vez concluído o registro, você pode começar a solicitar pagamentos diretamente do dispositivo do usuário.
Antes de começar, certifique-se de que você:
- Gerou suas Chaves de API do Belvo Payments
- Configurou Webhooks para receber atualizações de status de pagamento e inscrição
- Gerou um Token de Acesso SDK (veja abaixo)
- Instalou o Belvo Android SDK
O SDK do Pix Biometria requer um token de acesso para autenticar solicitações da API. Gere este token a partir do seu servidor backend e passe-o para o SDK durante a inicialização.
Gere um token de acesso do SDK a partir do seu backend:
POST https://api.belvo.com/payments/api/widget-token/
Authorization: Basic <base64_encoded_secret_id:secret_password>
Content-Type: application/json{
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Nunca codifique tokens diretamente no seu aplicativo. Sempre os gere no lado do servidor e implemente lógica segura de armazenamento e atualização.
Requisitos Mínimos:
- Android API Level 24 (Android 7.0) ou superior
- Kotlin 1.9.0 ou superior
Adicione ao seu build.gradle.kts (nível do Módulo):
dependencies {
implementation("com.belvo:biometric-pix-core:1.0.0")
}Sincronize seu projeto e você está pronto para começar!
Adicione permissões ao AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />Adicione suporte a App Links para callbacks OAuth:
<activity android:name=".MainActivity">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="yourdomain.com"
android:pathPrefix="/callback" />
</intent-filter>
</activity>Compartilhe o nome do seu pacote e a impressão digital do certificado SHA-256 com a Belvo:
# Obtenha sua impressão digital SHA-256
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
# Formato para compartilhar com a Belvo:
# Package: com.yourcompany.appname
# SHA-256: AA:BB:CC:DD:EE:FF:...As permissões devem ser solicitadas antes da inicialização do SDK. Além disso, a solicitação de permissão só pode ser chamada a partir de um contexto de Activity.
Passo 1: Solicite Permissões Primeiro
import com.belvo.biometricpixsdk.BiometricPixSDK
import androidx.activity.ComponentActivity
class EnrollmentActivity : ComponentActivity() {
private lateinit var sdk: BiometricPixSDK
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// DEVE solicitar permissões ANTES de inicializar o SDK
requestPermissions()
}
private fun requestPermissions() {
BiometricPixSDK.requestPermission(this) { granted ->
if (granted) {
// Permissões concedidas, agora inicialize o SDK
initializeSDK()
} else {
// Lidar com a negação de permissão
showError("Permissão de localização é necessária para o cadastro")
}
}
}
private fun initializeSDK() {
// Inicializar APÓS as permissões serem concedidas
sdk = BiometricPixSDK(
context = this,
accessToken = getAccessToken() // Do seu backend
)
// Agora pronto para prosseguir com o cadastro
}
override fun onDestroy() {
super.onDestroy()
if (::sdk.isInitialized) {
sdk.cleanup()
}
}
}Sempre chame cleanup() quando terminar de usar o SDK (por exemplo, quando a activity for destruída ou o usuário fizer logout) para liberar adequadamente os recursos e limpar tarefas em segundo plano.
O processo de cadastro registra o dispositivo de um usuário com sua instituição para pagamentos biométricos.
Busque a lista de instituições que suportam pagamentos biométricos:
import com.belvo.biometricpixsdk.BiometricPixSDK
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class EnrollmentViewModel(
private val sdk: BiometricPixSDK
) : ViewModel() {
private val _institutions = MutableStateFlow<List<Institution>>(emptyList())
val institutions: StateFlow<List<Institution>> = _institutions
fun loadInstitutions() {
viewModelScope.launch {
try {
_institutions.value = sdk.getPaymentInstitutions()
} catch (e: Exception) {
// Tratar erro (rede, autenticação, etc.)
_error.value = "Falha ao carregar instituições: ${e.message}"
}
}
}
}Exibir instituições para o usuário:
@Composable
fun InstitutionPickerScreen(
viewModel: EnrollmentViewModel
) {
val institutions by viewModel.institutions.collectAsState()
LazyColumn {
items(institutions) { institution ->
InstitutionItem(
institution = institution,
onClick = { viewModel.selectInstitution(institution) }
)
}
}
LaunchedEffect(Unit) {
viewModel.loadInstitutions()
}
}
@Composable
fun InstitutionItem(
institution: Institution,
onClick: () -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(16.dp)
) {
AsyncImage(
model = institution.iconLogo,
contentDescription = institution.displayName,
modifier = Modifier.size(40.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Text(text = institution.displayName)
}
}Crie a inscrição e imediatamente abra o app da instituição:
fun startEnrollment() {
val institution = _selectedInstitution.value ?: return
viewModelScope.launch {
try {
// Criar inscrição
val enrollment = sdk.createEnrollment(
cpf = userCPF, // CPF do usuário
institution = institution.id, // ID da instituição selecionada
accountTenure = customerCreatedDate, // Formato "YYYY-MM-DD"
callbackUrl = "https://yourdomain.com/callback"
)
// Salvar ID da inscrição e ID do dispositivo para uso posterior
_enrollmentId.value = enrollment.id
_deviceId.value = enrollment.details.riskSignals.deviceId
// Imediatamente abrir o app da instituição usando a URL de redirecionamento
enrollment.details.redirectUrl?.let { url ->
sdk.openRedirectUrl(context, url)
} ?: run {
_error.value = "Nenhuma URL de redirecionamento recebida da inscrição"
}
} catch (e: Exception) {
_error.value = "Falha na criação da inscrição: ${e.message}"
}
}
}O parâmetro accountTenure deve ser a data em que o usuário foi criado como Cliente Belvo, no formato YYYY-MM-DD. Extraia isso do timestamp created_at do Cliente (primeiros 10 caracteres).
O método openRedirectUrl() do SDK lida com a abertura do app da instituição automaticamente. A instituição redirecionará de volta para o seu callbackUrl com parâmetros OAuth.
Trate o callback OAuth na sua atividade e complete o cadastro:
// No seu MainActivity
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intent?.data?.let { uri ->
handleEnrollmentCallback(uri)
}
}
fun handleEnrollmentCallback(uri: Uri) {
viewModelScope.launch {
try {
val enrollment = sdk.completeEnrollmentAfterRedirection(
callbackUrl = uri.toString() // SDK analisa os parâmetros automaticamente
)
// Verifique se foi bem-sucedido
when (enrollment.status) {
"PENDING" -> {
// Sucesso - prossiga para o registro FIDO
getFidoOptions(enrollment.id)
}
"FAILED" -> {
// Trate a falha
_error.value = "${enrollment.statusReasonCode}: ${enrollment.statusReasonMessage}"
}
}
} catch (e: Exception) {
_error.value = "Falha ao completar o cadastro: ${e.message}"
}
}
}Se preferir analisar a URL de callback você mesmo, pode passar os parâmetros individualmente:
val enrollment = sdk.completeEnrollmentAfterRedirection(
state = stateParam,
code = codeParam,
idToken = idTokenParam
)Recupere as opções FIDO necessárias para o registro biométrico. O SDK faz a verificação automaticamente por até 5 minutos:
fun getFidoOptions(enrollmentId: String) {
viewModelScope.launch {
// O SDK verifica automaticamente a cada 1 segundo por até 5 minutos
val fidoOptions = sdk.getFidoRegistrationOptions(enrollmentId)
if (fidoOptions != null) {
// Opções FIDO recebidas, prossiga para o registro biométrico
startBiometricRegistration(fidoOptions)
} else {
// Tempo de verificação esgotado (5 minutos se passaram)
_error.value = "Tempo esgotado aguardando as opções FIDO. Por favor, tente novamente."
}
}
}O método getFidoRegistrationOptions() lida com toda a lógica de verificação automaticamente. Ele verifica a cada 1 segundo por até 5 minutos, então você não precisa implementar nenhuma lógica de tentativa novamente.
Solicite ao usuário os dados biométricos e confirme o registro:
class EnrollmentViewModel(
private val sdk: BiometricPixSDK
) : ViewModel() {
private var enrollmentId: String? = null
fun startBiometricRegistration(fidoOptions: FidoRegistrationOptions) {
try {
sdk.startRegistration(
fidoOptions = fidoOptions.toJsonString(),
callback = object : FidoRegistrationCallback {
override fun onSuccess(credential: PublicKeyCredential, response: AuthenticatorAttestationResponse) {
// SDK lida com a criação do payload automaticamente
confirmEnrollment(credential, response)
}
override fun onError(error: String) {
_error.value = "Registro biométrico falhou: $error"
}
}
)
} catch (e: Exception) {
_error.value = "Falha ao iniciar o registro: ${e.message}"
}
}
private fun confirmEnrollment(
credential: PublicKeyCredential,
response: AuthenticatorAttestationResponse
) {
val enrollmentId = this.enrollmentId ?: return
viewModelScope.launch {
val success = sdk.confirmEnrollment(
enrollmentId = enrollmentId,
credential = credential,
response = response
)
if (success) {
_state.value = EnrollmentState.Success
} else {
_error.value = "Confirmação de registro falhou"
}
}
}
}O dispositivo agora está registrado e pronto para pagamentos biométricos.
Uma vez inscrito, iniciar pagamentos requer quatro chamadas de método:
Busque todas as inscrições para o dispositivo atual e permita que o usuário selecione uma:
class PaymentViewModel(
private val sdk: BiometricPixSDK
) : ViewModel() {
private val _enrollments = MutableStateFlow<List<Enrollment>>(emptyList())
val enrollments: StateFlow<List<Enrollment>> = _enrollments
fun loadEnrollments(deviceId: String) {
viewModelScope.launch {
try {
_enrollments.value = sdk.listEnrollments(deviceId)
} catch (e: Exception) {
_error.value = "Falha ao carregar inscrições: ${e.message}"
}
}
}
}Exibir inscrições para o usuário:
@Composable
fun EnrollmentSelectionScreen(
viewModel: PaymentViewModel
) {
val enrollments by viewModel.enrollments.collectAsState()
LazyColumn {
items(enrollments) { enrollment ->
enrollment.institution?.let { institution ->
EnrollmentItem(
enrollment = enrollment,
institution = institution,
onClick = { viewModel.selectEnrollment(enrollment) }
)
}
}
}
LaunchedEffect(Unit) {
viewModel.loadEnrollments(savedDeviceId)
}
}
@Composable
fun EnrollmentItem(
enrollment: Enrollment,
institution: Institution,
onClick: () -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(16.dp)
) {
AsyncImage(
model = institution.iconLogo,
contentDescription = institution.displayName,
modifier = Modifier.size(40.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Column {
Text(text = institution.displayName)
Text(
text = "Status: ${enrollment.status}",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}Crie uma intenção de pagamento com todos os detalhes do pagamento:
fun createPayment(
amount: Double,
enrollmentId: String,
beneficiaryAccountId: String
) {
val payload = CreatePaymentIntentPayload(
amount = amount,
customer = Customer(identifier = userCPF), // CPF do usuário
description = "Pagamento por serviços",
statementDescription = "Compra ACME Corp",
allowedPaymentMethodTypes = listOf("open_finance_biometric_pix"),
paymentMethodDetails = PaymentMethodDetails(
openFinanceBiometricPix = OpenFinanceBiometricPixPaymentMethodDetails(
beneficiaryBankAccount = beneficiaryAccountId,
enrollment = enrollmentId
)
),
confirm = true
)
viewModelScope.launch {
try {
val paymentIntent = sdk.createPaymentIntent(payload)
// Salvar ID da intenção de pagamento
_paymentIntentId.value = paymentIntent.id
// Extrair opções FIDO para o próximo passo
paymentIntent.paymentMethodInformation?.openFinanceBiometricPix?.fidoOptions?.let { fidoOptions ->
promptForBiometric(fidoOptions)
}
} catch (e: Exception) {
_error.value = "Falha ao criar intenção de pagamento: ${e.message}"
}
}
}Solicite a autenticação biométrica e colete sinais de risco:
class PaymentViewModel(
private val sdk: BiometricPixSDK
) : ViewModel() {
private var paymentIntentId: String? = null
private var riskSignals: RiskSignals? = null
private var assertionResponse: AssertionResponse? = null
fun promptForBiometric(fidoOptions: FidoOptions) {
try {
sdk.startSigning(
fidoOptions = fidoOptions.toJsonString(),
fallbackCredential = null, // Opcional: forneça se você tiver um
callback = object : FidoAuthenticationCallback {
override fun onSuccess(response: AssertionResponse) {
// Armazene a resposta de asserção
assertionResponse = response
// Colete sinais de risco
collectRiskSignals()
}
override fun onError(error: String) {
_error.value = "Falha na autenticação biométrica: $error"
}
}
)
} catch (e: Exception) {
_error.value = "Falha ao iniciar a assinatura: ${e.message}"
}
}
private fun collectRiskSignals() {
try {
riskSignals = sdk.collectRiskSignals(
accountTenure = customerCreatedDate // "YYYY-MM-DD"
)
// Assim que tivermos tanto a asserção quanto os sinais de risco, autorize o pagamento
if (assertionResponse != null && riskSignals != null) {
authorizePayment()
}
} catch (e: Exception) {
_error.value = "Falha ao coletar sinais de risco: ${e.message}"
}
}
}Autorize o pagamento com os dados coletados:
private fun authorizePayment() {
val paymentIntentId = this.paymentIntentId ?: return
val riskSignals = this.riskSignals ?: return
val assertion = this.assertionResponse ?: return
val payload = AuthorizePaymentIntentPayload(
platform = "android",
riskSignals = riskSignals,
assertion = assertion
)
viewModelScope.launch {
val success = sdk.authorizePaymentIntent(
paymentIntentId = paymentIntentId,
payload = payload
)
if (success) {
_state.value = PaymentState.Success
} else {
_error.value = "Falha na autorização do pagamento"
}
}
}O pagamento agora está autorizado e em processamento. Você precisa monitorar eventos de webhook para acompanhar seu status final.
Todos os métodos do SDK que realizam operações de rede podem lançar exceções. Certifique-se de tratá-las adequadamente:
try {
val institutions = sdk.getPaymentInstitutions()
// Sucesso
} catch (e: BiometricPixSDKException) {
when (e) {
is BiometricPixSDKException.NetworkError -> {
// Tratar problemas de rede
Log.e(TAG, "Erro de rede: ${e.message}")
}
is BiometricPixSDKException.AuthenticationError -> {
// Tratar token inválido ou expirado
Log.e(TAG, "Falha na autenticação - o token pode estar expirado")
}
is BiometricPixSDKException.InvalidParametersError -> {
// Tratar entrada inválida
Log.e(TAG, "Parâmetros inválidos: ${e.message}")
}
is BiometricPixSDKException.UnknownError -> {
// Tratar erros desconhecidos
Log.e(TAG, "Erro: ${e.message}")
}
}
} catch (e: Exception) {
Log.e(TAG, "Erro inesperado: ${e.message}")
}Embora o SDK gerencie a maior parte do fluxo de trabalho, você ainda deve escutar notificações de webhook para lidar com atualizações assíncronas:
- Mudanças no status de inscrição: tipo de webhook
ENROLLMENTS - Mudanças no status de pagamento: tipo de webhook
PAYMENT_INTENTS
Para a documentação completa de webhooks, veja Webhooks de Pagamentos (Brasil).
BiometricPixSDK(context: Context, accessToken: String)
- Cria uma nova instância do SDK com o contexto e o access token fornecidos
- Deve ser inicializado uma vez e reutilizado em todo o seu aplicativo
- Access token obtido a partir do endpoint
/payments/api/widget-token/
cleanup()
- Libera os recursos do SDK e limpa as tarefas em segundo plano
- Chame em
onCleared()ou quando o usuário fizer logout
getPaymentInstitutions(): List<Institution>
- Busca todas as instituições que suportam pagamentos biométricos
- Retorna uma lista de objetos
Institutioncomid,displayName,iconLogo, etc. - Lança exceções em erros de rede ou autenticação
createEnrollment(cpf: String, institution: String, accountTenure: String, callbackUrl: String): Enrollment
- Cria a inscrição e coleta sinais de risco automaticamente
cpf: Número de CPF do usuárioinstitution: ID da instituição degetPaymentInstitutions()accountTenure: Data de criação do cliente no formato "YYYY-MM-DD"callbackUrl: Deep link para callback OAuth (deve ser registrado como App Link)- Retorna um objeto
Enrollmentcomid,redirect_url,device_id
openRedirectUrl(context: Context, url: String)
- Abre o aplicativo da instituição usando o URL de redirecionamento fornecido
- Lida com o redirecionamento automaticamente, incluindo deep linking
- Deve ser chamado imediatamente após
createEnrollment()com oredirect_urlda resposta de inscrição context: Contexto da Activity ou Applicationurl: O URL de redirecionamento do objeto de inscrição
completeEnrollmentAfterRedirection(callbackUrl: String): Enrollment
- Completa a inscrição após o callback OAuth da instituição
- Analisa automaticamente os parâmetros OAuth do URL completo de callback
- Alternativa:
completeEnrollmentAfterRedirection(state: String, code: String, idToken: String)
getFidoRegistrationOptions(enrollmentId: String): FidoRegistrationOptions?
- Faz polling para opções FIDO (tentativa automática: intervalo de 1 segundo, timeout de 5 minutos)
- Retorna
FidoRegistrationOptionsquando pronto - Retorna
nullse o polling expirar
startRegistration(fidoOptions: String, callback: FidoRegistrationCallback)
- Inicia o fluxo de registro biométrico (reconhecimento de impressão digital/face)
fidoOptions: String JSON deFidoRegistrationOptions.toJsonString()callback: Interface para receber callbacks de sucesso/erro
confirmEnrollment(enrollmentId: String, credential: PublicKeyCredential, response: AuthenticatorAttestationResponse): Boolean
- Confirma a inscrição com credencial FIDO
- Retorna
trueem caso de sucesso,falseem caso de falha
listEnrollments(deviceId: String): List<Enrollment>
- Busca todas as inscrições para um dispositivo
- Retorna uma lista de objetos
Enrollmentcom dados enriquecidos da instituição - Filtre por
status == "SUCCEEDED"para mostrar apenas inscrições ativas
createPaymentIntent(payload: CreatePaymentIntentPayload): PaymentIntent
- Cria uma intenção de pagamento
- Retorna
PaymentIntentcomidepaymentMethodInformation.openFinanceBiometricPix.fidoOptions
startSigning(fidoOptions: String, fallbackCredential: String?, callback: FidoAuthenticationCallback)
- Inicia a autenticação biométrica para pagamento
fallbackCredential: Credencial opcional para cenários de tentativa novamentecallback: Interface para receber a resposta de asserção
collectRiskSignals(accountTenure: String): RiskSignals
- Coleta impressões digitais do dispositivo e sinais de segurança
accountTenure: Data de criação do cliente no formato "YYYY-MM-DD"- Retorna objeto
RiskSignalspara carga de autorização
authorizePaymentIntent(paymentIntentId: String, payload: AuthorizePaymentIntentPayload): Boolean
- Autoriza pagamento com asserção biométrica e sinais de risco
- Retorna
simem caso de sucesso,falseem caso de falha