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.
Antes de começar, certifique-se de que você tenha:
- Gerado suas Chaves de API do Belvo Payments
- Configurado Webhooks para receber atualizações de status de pagamento e inscrição
- Gerado um Token de Acesso SDK (veja abaixo)
- Instalado o SDK iOS da Belvo
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 de armazenamento seguro e atualização.
Requisitos Mínimos:
- iOS 15.0 ou superior
- Swift 5.0 ou superior
Instalação via Swift Package Manager:
- No Xcode, selecione File → Add Packages...
- Insira:
https://github.com/belvo-finance-opensource/biometric-pix-ios-sdk - Selecione os requisitos de versão e clique em Add Package
- Escolha o produto BiometricPixSDK e clique em Add Package
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.appnameInicialize 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()
}
}Sempre chame cleanup() quando terminar de usar o SDK (por exemplo, em deinit ou ao fazer logout) para liberar os recursos adequadamente.
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 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()
}
}
}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()
}
}
}
}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)")
}
}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).
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.
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)")
}
}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
)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()
}
}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 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)
}
}
}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
}O dispositivo agora está cadastrado e pronto para pagamentos biométricos.
Uma vez inscrito, iniciar pagamentos requer quatro chamadas de método (como você pode ver no diagrama de sequência abaixo):
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)
}
}
}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)")
}
}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)
}
}
}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")
}
}
}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:
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)")
}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).
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
deinitou quando o usuário fizer logout
getPaymentInstitutions() throws -> [Institution]
- Busca todas as instituições que suportam pagamentos biométricos
- Retorna um array 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) throws -> Enrollment
- Cria a inscrição e coleta sinais de risco automaticamente
cpf: Número de CPF do usuárioinstitution: ID da instituição obtido degetPaymentInstitutions()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
Enrollmentcomid,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
FidoRegistrationOptionsquando pronto - Retorna
nilse o polling expirar
startRegistration(fidoResponseString: String, callback: FidoRegistrationCallback) throws
- Inicia o fluxo de registro biométrico (Face ID/Touch ID)
fidoResponseString: String JSON deFidoRegistrationOptions.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
trueem caso de sucesso,falseem caso de falha
listEnrollments(deviceId: String) throws -> [Enrollment]
- Busca todas as inscrições para um dispositivo
- Retorna um array de objetos
Enrollmentcom 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
PaymentIntentcomidepaymentMethodInformation.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 tentativacallback: 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
RiskSignalspara payload de autorização
authorizePaymentIntent(paymentIntentId: String, payload: AuthorizePaymentIntentPayload) -> Bool
- Autoriza pagamento com asserção biométrica e sinais de risco
- Retorna
trueem caso de sucesso,falseem caso de falha