Pular para o conteúdo
Última atualização

Guia Pix Biometria (Integração Apenas com SDK iOS)

Próximo Lançamento

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, a coleta de pagamentos dos 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 individual.

Este guia demonstra a integração usando os métodos de conveniência do SDK iOS 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 apenas com autenticação biométrica.

Uma vez que o registro esteja completo, você pode começar a solicitar pagamentos diretamente do dispositivo do usuário.

Pré-requisitos

Antes de começar, certifique-se de que você tenha:

  1. Gerado suas Chaves de API do Belvo Payments
  2. Configurado Webhooks para receber atualizações de status de pagamento e inscrição
  3. Gerado um Token de Acesso SDK (veja abaixo)
  4. Instalado o SDK iOS da Belvo

Token de Acesso do SDK

Autenticação do 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..."
}
Melhores Práticas de Token

Nunca codifique tokens diretamente no seu aplicativo. Sempre os gere no lado do servidor e implemente lógica de armazenamento seguro e atualização.

Instalação do SDK

Requisitos Mínimos:

  • iOS 15.0 ou superior
  • Swift 5.0 ou superior

Instalação via Swift Package Manager:

  1. No Xcode, selecione FileAdd Packages...
  2. Insira: https://github.com/belvo-finance-opensource/biometric-pix-ios-sdk
  3. Selecione os requisitos de versão e clique em Add Package
  4. Escolha o produto BiometricPixSDK e clique em Add Package

Configuração do App

Adicione permissões ao seu arquivo .entitlements:

<key>com.apple.developer.associated-domains</key>
<array>
  <string>webcredentials:belvo.com</string>
</array>

Adicione a descrição de uso de localização ao Info.plist:

<key>NSLocationWhenInUseUsageDescription</key>
<string>Este app usa localização para fins de segurança e prevenção de fraude.</string>

Compartilhe seu Team ID e Bundle Identifier com a Belvo:

Formato: TEAM_ID.BUNDLE_ID
Exemplo: ABCDEFGHIJ.com.yourcompany.appname

Inicialização do SDK

Inicialize o SDK uma vez em seu aplicativo, normalmente em seu view model ou camada de serviço:

import BiometricPixSDK

class BiometricPixService {
    private let sdk: BiometricPixSDK
    
    init(accessToken: String) {
        self.sdk = BiometricPixSDK(accessToken: accessToken)
    }
    
    deinit {
        sdk.cleanup()
    }
}
Gerenciamento de Recursos

Sempre chame cleanup() quando terminar de usar o SDK (por exemplo, em deinit ou ao fazer logout) para liberar os recursos adequadamente.

Fluxo de Cadastro (7 Etapas)

O processo de cadastro registra o dispositivo de um usuário com sua instituição para pagamentos biométricos.

UserYourAppBiometricPixSDKBelvoAPIInstitutionChamada 1: getPaymentInstitutions()Chamada 2: requestPermission()Chamada 3: createEnrollment()Redirecionar para a InstituiçãoChamada 4: completeEnrollmentAfterRedirection()Chamada 5: getFidoRegistrationOptions()Chamada 6: startRegistration()Chamada 7: confirmEnrollment()Inicia o cadastro1getPaymentInstitutions()2Buscar instituições3Lista de instituições4[Institution]5Exibir seletor de instituição6Seleciona instituição7requestPermission()8Solicitar permissão de localização9Concede permissão10Permissão concedida11createEnrollment(cpf, institution, accountTenure, callbackUrl)12Coletar sinais de risco internamente13POST /enrollments/14Cadastro criado (redirect_url)15Objeto de cadastro16Redirecionar para a instituição (redirect_url)17Aprovar cadastro no app da instituição18Callback OAuth (code, state, id_token)19completeEnrollmentAfterRedirection(callbackUrl)20POST /enrollments/complete-redirection/21Cadastro atualizado22Objeto de cadastro23getFidoRegistrationOptions(enrollmentId)24Consultar opções FIDO (auto-retry)25Opções de registro FIDO26FidoRegistrationOptions27startRegistration(fidoOptions, callback)28Solicitar biometria (Face ID/Touch ID)29Fornece biometria30Credencial via callback31confirmEnrollment(enrollmentId, credential)32POST /enrollments/{id}/confirm/33Registrar credencial FIDO34Registro confirmado35Cadastro SUCEDIDO36Sucesso (Boolean)37Mostrar tela de sucesso38UserYourAppBiometricPixSDKBelvoAPIInstitution

Passo 1: Obter Instituições de Pagamento

Busque a lista de instituições que suportam pagamentos biométricos:

import BiometricPixSDK

class EnrollmentViewModel: ObservableObject {
    private let sdk: BiometricPixSDK
    @Published var institutions: [Institution] = []
    @Published var selectedInstitution: Institution?
    
    init(sdk: BiometricPixSDK) {
        self.sdk = sdk
    }
    
    func loadInstitutions() {
        do {
            institutions = try sdk.getPaymentInstitutions()
        } catch {
            // Tratar erro (rede, autenticação, etc.)
            print("Falha ao carregar instituições: \(error)")
        }
    }
}

Exibir instituições para o usuário:

struct InstitutionPickerView: View {
    @ObservedObject var viewModel: EnrollmentViewModel
    
    var body: some View {
        List(viewModel.institutions) { institution in
            Button(action: { 
                viewModel.selectedInstitution = institution 
            }) {
                HStack {
                    AsyncImage(url: URL(string: institution.iconLogo))
                        .frame(width: 40, height: 40)
                    Text(institution.displayName)
                }
            }
        }
        .onAppear {
            viewModel.loadInstitutions()
        }
    }
}

Passo 2: Solicitar Permissões

Solicite permissões de localização necessárias para avaliação de risco:

func requestPermissions() {
    sdk.requestPermission { granted in
        DispatchQueue.main.async {
            if let permissionGranted = granted as? Bool, permissionGranted {
                // Permissões concedidas, prossiga para o cadastro
                self.startEnrollment()
            } else {
                // Lidar com a negação de permissão
                self.showPermissionDeniedAlert()
            }
        }
    }
}

Passo 3: Criar Inscrição

Crie a inscrição com a seguinte chamada de método (o SDK lida com a coleta de sinais de risco internamente):

func startEnrollment() {
    guard let institution = selectedInstitution else { return }
    
    do {
        let enrollment = try sdk.createEnrollment(
            cpf: userCPF,                           // CPF do usuário
            institution: institution.id,             // ID da instituição selecionada
            accountTenure: customerCreatedDate,      // Formato "YYYY-MM-DD"
            callbackUrl: "https://myapp.com/callback"
        )
        
        // Salvar ID da inscrição e ID do dispositivo para mais tarde
        self.enrollmentId = enrollment.id
        self.deviceId = enrollment.details.riskSignals.deviceId
        
        // Redirecionar usuário para a instituição
        if let redirectUrl = enrollment.details.redirectUrl {
            self.openInstitutionApp(url: redirectUrl)
        }
        
    } catch {
        // Tratar erro
        print("Falha na criação da inscrição: \(error)")
    }
}
Formato de Duração da Conta

O parâmetro accountTenure deve ser a data em que o usuário foi criado como um Cliente Belvo, no formato YYYY-MM-DD. Extraia isso do timestamp created_at do Cliente (primeiros 10 caracteres).

Passo 4: Redirecionar para a Instituição

Abra o aplicativo da instituição usando o redirect_url:

func openInstitutionApp(url: String) {
    guard let url = URL(string: url) else { return }
    
    if UIApplication.shared.canOpenURL(url) {
        UIApplication.shared.open(url)
    }
}

A instituição irá redirecionar de volta para o seu callbackUrl com parâmetros OAuth.

Passo 5: Completar o Cadastro Após Redirecionamento

Trate o callback OAuth no seu aplicativo e complete o cadastro:

// No seu SceneDelegate ou App delegate
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    guard let url = URLContexts.first?.url else { return }
    
    // Passe a URL completa de callback para o SDK
    handleEnrollmentCallback(url: url)
}

func handleEnrollmentCallback(url: URL) {
    do {
        let enrollment = try sdk.completeEnrollmentAfterRedirection(
            callbackUrl: url.absoluteString  // O SDK analisa os parâmetros automaticamente
        )
        
        // Verifique se foi bem-sucedido
        if enrollment.status == "PENDING" {
            // Sucesso - prossiga para o registro FIDO
            self.getFidoOptions(enrollmentId: enrollment.id)
        } else if enrollment.status == "FAILED" {
            // Trate a falha
            self.showEnrollmentError(
                code: enrollment.statusReasonCode,
                message: enrollment.statusReasonMessage
            )
        }
        
    } catch {
        print("Falha ao completar o cadastro: \(error)")
    }
}
Alternativa: Parâmetros Manuais

Se preferir analisar a URL de callback você mesmo, pode passar os parâmetros individualmente:

let enrollment = try sdk.completeEnrollmentAfterRedirection(
    state: stateParam,
    code: codeParam,
    idToken: idTokenParam
)

Passo 6: Obter Opções de Registro FIDO

Recupere as opções FIDO necessárias para o registro biométrico. O SDK faz a verificação automaticamente por até 5 minutos:

func getFidoOptions(enrollmentId: String) {
    // O SDK verifica automaticamente a cada 1 segundo por até 5 minutos
    if let fidoOptions = sdk.getFidoRegistrationOptions(enrollmentId: enrollmentId) {
        // Opções FIDO recebidas, prossiga para o registro biométrico
        self.startBiometricRegistration(fidoOptions: fidoOptions)
    } else {
        // Tempo de verificação esgotado (5 minutos se passaram)
        self.showTimeoutError()
    }
}
Verificação Automática

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.

Passo 7: Registrar Biometria e Confirmar

Solicite ao usuário os dados biométricos e confirme o cadastro:

class EnrollmentViewModel: NSObject, ObservableObject {
    private let sdk: BiometricPixSDK
    private var enrollmentId: String?
    
    func startBiometricRegistration(fidoOptions: FidoRegistrationOptions) {
        do {
            try sdk.startRegistration(
                fidoResponseString: fidoOptions.toJsonString(),
                callback: self
            )
        } catch {
            print("Failed to start registration: \(error)")
        }
    }
}

// Implementar callback FIDO
extension EnrollmentViewModel: FidoRegistrationCallback {
    func onSuccess(credential: PublicKeyCredential, response: AuthenticatorAttestationResponse) {
        // O SDK lida automaticamente com a criação do payload
        guard let enrollmentId = self.enrollmentId else { return }
        
        let success = sdk.confirmEnrollment(
            enrollmentId: enrollmentId,
            credential: credential,
            response: response
        )
        
        if success {
            DispatchQueue.main.async {
                self.showEnrollmentSuccess()
            }
        } else {
            DispatchQueue.main.async {
                self.showEnrollmentError()
            }
        }
    }
    
    func onError(error: String) {
        DispatchQueue.main.async {
            self.showBiometricError(message: error)
        }
    }
}
Ciclo de Vida do View Model

Ao usar view models com callbacks no SwiftUI, declare-os como @ObservedObject ou @StateObject para evitar que sejam destruídos durante as re-renderizações da view:

struct EnrollmentView: View {
    @StateObject private var viewModel: EnrollmentViewModel
}
Cadastro Completo!

O dispositivo agora está cadastrado e pronto para pagamentos biométricos.

Fluxo de Pagamento (4 Etapas)

Uma vez inscrito, iniciar pagamentos requer quatro chamadas de método (como você pode ver no diagrama de sequência abaixo):

UserYourAppBiometricPixSDKBelvoAPIInstitutionChamada 1: listEnrollments()Chamada 2: createPaymentIntent()Chamada 3: startSigning() + collectRiskSignals()Chamada 4: authorizePaymentIntent()Inicia pagamento1listEnrollments(deviceId)2GET /enrollments/?device_id=...3[Enrollment]4Lista de inscrições5Exibir seletor de inscrição6Seleciona inscrição7createPaymentIntent(payload)8POST /payment-intents/9PaymentIntent (com opções FIDO)10Objeto PaymentIntent11startSigning(fidoOptions, callback)12Solicitar biometria (Face ID/Touch ID)13Fornece biometria14Asserção via callback15collectRiskSignals(accountTenure)16RiskSignals17authorizePaymentIntent(paymentIntentId, payload)18POST /payment-intents/{id}/authorize/19Processar pagamento20Pagamento confirmado21Pagamento SUCCEEDED22Sucesso na autorização (Boolean)23Mostrar confirmação de pagamento24UserYourAppBiometricPixSDKBelvoAPIInstitution

Passo 1: Listar Inscrições

Busque todas as inscrições para o dispositivo atual e permita que o usuário selecione uma:

class PaymentViewModel: ObservableObject {
    private let sdk: BiometricPixSDK
    @Published var enrollments: [Enrollment] = []
    @Published var selectedEnrollment: Enrollment?
    
    func loadEnrollments(deviceId: String) {
        do {
            enrollments = try sdk.listEnrollments(deviceId: deviceId)
        } catch {
            print("Falha ao carregar inscrições: \(error)")
        }
    }
}

Exibir inscrições para o usuário:

struct EnrollmentSelectionView: View {
    @ObservedObject var viewModel: PaymentViewModel
    
    var body: some View {
        List(viewModel.enrollments) { enrollment in
            Button(action: { 
                viewModel.selectedEnrollment = enrollment 
            }) {
                HStack {
                    if let institution = enrollment.institution {
                        AsyncImage(url: URL(string: institution.iconLogo))
                            .frame(width: 40, height: 40)
                        VStack(alignment: .leading) {
                            Text(institution.displayName)
                            Text("Status: \(enrollment.status)")
                                .font(.caption)
                                .foregroundColor(.gray)
                        }
                    }
                }
            }
        }
        .onAppear {
            viewModel.loadEnrollments(deviceId: savedDeviceId)
        }
    }
}

Passo 2: Criar Intenção de Pagamento

Crie uma intenção de pagamento com todos os detalhes do pagamento:

func createPayment(amount: Double, enrollmentId: String, beneficiaryAccountId: String) {
    let payload = CreatePaymentIntentPayload(
        amount: amount,
        customer: Customer(identifier: userCPF),  // CPF do usuário
        description: "Pagamento por serviços",
        statementDescription: "Compra ACME Corp",
        allowedPaymentMethodTypes: ["open_finance_biometric_pix"],
        paymentMethodDetails: PaymentMethodDetails(
            openFinanceBiometricPix: OpenFinanceBiometricPixPaymentMethodDetails(
                beneficiaryBankAccount: beneficiaryAccountId,
                enrollment: enrollmentId
            )
        ),
        confirm: true
    )
    
    do {
        let paymentIntent = try sdk.createPaymentIntent(payload: payload)
        
        // Salvar ID da intenção de pagamento
        self.paymentIntentId = paymentIntent.id
        
        // Extrair opções FIDO para o próximo passo
        if let fidoOptions = paymentIntent.paymentMethodInformation?.openFinanceBiometricPix?.fidoOptions {
            self.promptForBiometric(fidoOptions: fidoOptions)
        }
        
    } catch {
        print("Falha ao criar intenção de pagamento: \(error)")
    }
}

Passo 3: Coletar Sinais Biométricos e de Risco

Solicite a autenticação biométrica e colete sinais de risco:

class PaymentViewModel: NSObject, ObservableObject {
    private let sdk: BiometricPixSDK
    private var paymentIntentId: String?
    private var riskSignals: RiskSignals?
    private var assertionResponse: AssertionResponse?
    
    func promptForBiometric(fidoOptions: FidoOptions) {
        // Converter opções FIDO para string JSON
        let fidoJsonString = fidoOptions.toJsonString()
        
        do {
            try sdk.startSigning(
                fidoResponseString: fidoJsonString,
                fallbackCredential: nil,  // Opcional: forneça se você tiver um
                callback: self
            )
        } catch {
            print("Falha ao iniciar assinatura: \(error)")
        }
    }
    
    func collectRiskSignals() {
        do {
            self.riskSignals = try sdk.collectRiskSignals(
                accountTenure: customerCreatedDate  // "YYYY-MM-DD"
            )
            
            // Assim que tivermos tanto a asserção quanto os sinais de risco, autorizar pagamento
            if assertionResponse != nil && riskSignals != nil {
                self.authorizePayment()
            }
        } catch {
            print("Falha ao coletar sinais de risco: \(error)")
        }
    }
}

// Implementar callback de autenticação FIDO
extension PaymentViewModel: FidoAuthenticationCallback {
    func onSuccess(response: AssertionResponse) {
        // Armazenar resposta de asserção
        self.assertionResponse = response
        
        // Coletar sinais de risco
        self.collectRiskSignals()
    }
    
    func onError(error: String) {
        DispatchQueue.main.async {
            self.showPaymentError(message: error)
        }
    }
}

Passo 4: Autorizar Pagamento

Autorize o pagamento com os dados coletados:

func authorizePayment() {
    guard let paymentIntentId = self.paymentIntentId,
          let riskSignals = self.riskSignals,
          let assertion = self.assertionResponse else {
        return
    }
    
    let payload = AuthorizePaymentIntentPayload(
        platform: "ios",
        riskSignals: riskSignals,
        assertion: assertion
    )
    
    let success = sdk.authorizePaymentIntent(
        paymentIntentId: paymentIntentId,
        payload: payload
    )
    
    DispatchQueue.main.async {
        if success {
            self.showPaymentSuccess()
        } else {
            self.showPaymentError(message: "Authorization failed")
        }
    }
}
Fluxo de Pagamento Completo!

O pagamento agora está autorizado e em processamento. Você precisa monitorar eventos de webhook para acompanhar seu status final.

Tratamento de Erros

Todos os métodos do SDK que realizam operações de rede podem lançar exceções. Certifique-se de tratá-las adequadamente:

do {
    let institutions = try sdk.getPaymentInstitutions()
    // Sucesso
} catch let error as BiometricPixSDKError {
    switch error {
    case .networkError(let message):
        // Tratar problemas de rede
        print("Erro de rede: \(message)")
    case .authenticationError:
        // Tratar token inválido ou expirado
        print("Falha na autenticação - o token pode estar expirado")
    case .invalidParameters(let message):
        // Tratar entrada inválida
        print("Parâmetros inválidos: \(message)")
    case .unknown(let message):
        // Tratar erros desconhecidos
        print("Erro: \(message)")
    }
} catch {
    print("Erro inesperado: \(error)")
}

Webhooks

Embora o SDK lide com a maior parte do fluxo de trabalho, você ainda deve ouvir as 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).

Referência de Método SDK

Inicialização

BiometricPixSDK(accessToken: String)

  • Cria uma nova instância do SDK com o access token fornecido
  • Deve ser inicializado uma vez e reutilizado ao longo do seu aplicativo
  • Access token obtido a partir do endpoint /payments/api/widget-token/

cleanup()

  • Libera os recursos do SDK
  • Chame em deinit ou quando o usuário fizer logout

Métodos de Inscrição

getPaymentInstitutions() throws -> [Institution]

  • Busca todas as instituições que suportam pagamentos biométricos
  • Retorna um array de objetos Institution com id, displayName, iconLogo, etc.
  • Lança exceções em erros de rede ou autenticação

createEnrollment(cpf: String, institution: String, accountTenure: String, callbackUrl: String) throws -> Enrollment

  • Cria a inscrição e coleta sinais de risco automaticamente
  • cpf: Número de CPF do usuário
  • institution: ID da instituição obtido de getPaymentInstitutions()
  • accountTenure: Data de criação do cliente no formato "YYYY-MM-DD"
  • callbackUrl: Deep link para o callback OAuth (deve ser registrado em applinks)
  • Retorna um objeto Enrollment com id, redirect_url, device_id

completeEnrollmentAfterRedirection(callbackUrl: String) throws -> Enrollment

  • Completa a inscrição após o callback OAuth da instituição
  • Analisa automaticamente os parâmetros OAuth da URL completa 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 FidoRegistrationOptions quando pronto
  • Retorna nil se o polling expirar

startRegistration(fidoResponseString: String, callback: FidoRegistrationCallback) throws

  • Inicia o fluxo de registro biométrico (Face ID/Touch ID)
  • fidoResponseString: String JSON de FidoRegistrationOptions.toJsonString()
  • callback: Delegate para receber callbacks de sucesso/erro

confirmEnrollment(enrollmentId: String, credential: PublicKeyCredential, response: AuthenticatorAttestationResponse) -> Bool

  • Confirma a inscrição com credencial FIDO
  • Retorna true em caso de sucesso, false em caso de falha

Métodos de Pagamento

listEnrollments(deviceId: String) throws -> [Enrollment]

  • Busca todas as inscrições para um dispositivo
  • Retorna um array de objetos Enrollment com dados enriquecidos da instituição
  • Filtrar por status == "SUCCEEDED" para mostrar apenas inscrições ativas

createPaymentIntent(payload: CreatePaymentIntentPayload) throws -> PaymentIntent

  • Cria uma intenção de pagamento
  • Retorna PaymentIntent com id e paymentMethodInformation.openFinanceBiometricPix.fidoOptions

startSigning(fidoResponseString: String, fallbackCredential: String?, callback: FidoAuthenticationCallback) throws

  • Inicia a autenticação biométrica para pagamento
  • fallbackCredential: Credencial opcional para cenários de nova tentativa
  • callback: Delegado para receber a resposta de asserção

collectRiskSignals(accountTenure: String) throws -> 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 RiskSignals para payload de autorização

authorizePaymentIntent(paymentIntentId: String, payload: AuthorizePaymentIntentPayload) -> Bool

  • Autoriza pagamento com asserção biométrica e sinais de risco
  • Retorna true em caso de sucesso, false em caso de falha