Esta documentación cubre características de nuestro próximo lanzamiento. Aunque la funcionalidad principal y el flujo de trabajo descrito aquí permanecerán sin cambios, puede que notes algunas mejoras antes del lanzamiento final.
Con Pix Biometria de Belvo, la recolección de pagos de los usuarios se vuelve sencilla, eliminando la necesidad de que los usuarios naveguen a su institución financiera para aprobar cada solicitud de pago individual.
Esta guía demuestra la integración utilizando los métodos de conveniencia del SDK de iOS de Belvo. El SDK maneja la comunicación con el backend, los flujos de OAuth y el registro FIDO internamente, proporcionando un camino de integración simplificado.
El primer paso para habilitar la recolección de pagos biométricos es registrar el dispositivo del usuario con su institución. Durante el registro, los datos clave sobre el dispositivo y las credenciales de clave pública del usuario se registran de manera segura con su institución, asegurando que los pagos futuros puedan ser confirmados usando solo la autenticación biométrica.
Una vez que el registro esté completo, puedes comenzar a solicitar pagos directamente desde el dispositivo del usuario.
Antes de comenzar, asegúrate de tener:
- Generadas tus claves de la API de Belvo Payments
- Configurados los Webhooks para recibir actualizaciones de estado de pagos y registros
- Generado un SDK Access Token (ver abajo)
- Instalado el SDK de iOS de Belvo
El SDK de Pix Biometria requiere un token de acceso para autenticar las solicitudes de la API. Genera este token desde tu servidor backend y pásalo al SDK durante la inicialización.
Genera un token de acceso del SDK desde tu 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 codifiques tokens directamente en tu aplicación. Siempre genéralos del lado del servidor e implementa un almacenamiento seguro y lógica de actualización.
Requisitos Mínimos:
- iOS 15.0 o superior
- Swift 5.0 o superior
Instalación a través de Swift Package Manager:
- En Xcode, selecciona File → Add Packages...
- Ingresa:
https://github.com/belvo-finance-opensource/biometric-pix-ios-sdk - Selecciona los requisitos de versión y haz clic en Add Package
- Elige el producto BiometricPixSDK y haz clic en Add Package
Agrega permisos a tu archivo .entitlements:
<key>com.apple.developer.associated-domains</key>
<array>
<string>webcredentials:belvo.com</string>
</array>Agrega la descripción del uso de ubicación a Info.plist:
<key>NSLocationWhenInUseUsageDescription</key>
<string>Esta aplicación utiliza la ubicación para propósitos de seguridad y prevención de fraude.</string>Comparte tu Team ID y Bundle Identifier con Belvo:
Formato: TEAM_ID.BUNDLE_ID
Ejemplo: ABCDEFGHIJ.com.yourcompany.appnameInicializa el SDK una vez en tu aplicación, típicamente en tu modelo de vista o capa de servicio:
import BiometricPixSDK
class BiometricPixService {
private let sdk: BiometricPixSDK
init(accessToken: String) {
self.sdk = BiometricPixSDK(accessToken: accessToken)
}
deinit {
sdk.cleanup()
}
}Siempre llama a cleanup() cuando hayas terminado con el SDK (por ejemplo, en deinit o al cerrar sesión) para liberar adecuadamente los recursos.
El proceso de inscripción registra el dispositivo de un usuario con su institución para pagos biométricos.
Obtén la lista de instituciones que soportan pagos 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 {
// Manejar error (red, autenticación, etc.)
print("Error al cargar instituciones: \(error)")
}
}
}Mostrar instituciones al usuario:
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()
}
}
}Solicitar permisos de ubicación requeridos para la evaluación de riesgos:
func requestPermissions() {
sdk.requestPermission { granted in
DispatchQueue.main.async {
if let permissionGranted = granted as? Bool, permissionGranted {
// Permisos concedidos, proceder con el registro
self.startEnrollment()
} else {
// Manejar la denegación de permisos
self.showPermissionDeniedAlert()
}
}
}
}Crea la inscripción con la siguiente llamada al método (el SDK maneja la recopilación de señales de riesgo internamente):
func startEnrollment() {
guard let institution = selectedInstitution else { return }
do {
let enrollment = try sdk.createEnrollment(
cpf: userCPF, // CPF del usuario
institution: institution.id, // ID de la institución seleccionada
accountTenure: customerCreatedDate, // Formato "YYYY-MM-DD"
callbackUrl: "https://myapp.com/callback"
)
// Guarda el ID de inscripción y el ID de dispositivo para más tarde
self.enrollmentId = enrollment.id
self.deviceId = enrollment.details.riskSignals.deviceId
// Redirige al usuario a la institución
if let redirectUrl = enrollment.details.redirectUrl {
self.openInstitutionApp(url: redirectUrl)
}
} catch {
// Manejar error
print("La creación de la inscripción falló: \(error)")
}
}El parámetro accountTenure debe ser la fecha en que el usuario fue creado como Cliente de Belvo, en formato YYYY-MM-DD. Extrae esto del timestamp created_at del Cliente (primeros 10 caracteres).
Abre la aplicación de la institución usando el redirect_url:
func openInstitutionApp(url: String) {
guard let url = URL(string: url) else { return }
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
}
}La institución redirigirá de vuelta a tu callbackUrl con parámetros OAuth.
Maneja el callback de OAuth en tu aplicación y completa el registro:
// En tu SceneDelegate o App delegate
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let url = URLContexts.first?.url else { return }
// Pasa la URL completa del callback al SDK
handleEnrollmentCallback(url: url)
}
func handleEnrollmentCallback(url: URL) {
do {
let enrollment = try sdk.completeEnrollmentAfterRedirection(
callbackUrl: url.absoluteString // El SDK analiza los parámetros automáticamente
)
// Verifica si fue exitoso
if enrollment.status == "PENDING" {
// Éxito - procede al registro FIDO
self.getFidoOptions(enrollmentId: enrollment.id)
} else if enrollment.status == "FAILED" {
// Maneja el fallo
self.showEnrollmentError(
code: enrollment.statusReasonCode,
message: enrollment.statusReasonMessage
)
}
} catch {
print("Error al completar el registro: \(error)")
}
}Si prefieres analizar la URL del callback tú mismo, puedes pasar los parámetros individualmente:
let enrollment = try sdk.completeEnrollmentAfterRedirection(
state: stateParam,
code: codeParam,
idToken: idTokenParam
)Recupera las opciones FIDO necesarias para el registro biométrico. El SDK realiza sondeos automáticamente durante un máximo de 5 minutos:
func getFidoOptions(enrollmentId: String) {
// El SDK realiza sondeos automáticamente cada 1 segundo durante un máximo de 5 minutos
if let fidoOptions = sdk.getFidoRegistrationOptions(enrollmentId: enrollmentId) {
// Opciones FIDO recibidas, proceder al registro biométrico
self.startBiometricRegistration(fidoOptions: fidoOptions)
} else {
// Tiempo de sondeo agotado (han pasado 5 minutos)
self.showTimeoutError()
}
}El método getFidoRegistrationOptions() maneja toda la lógica de sondeo automáticamente. Verifica cada 1 segundo durante un máximo de 5 minutos, por lo que no necesitas implementar ninguna lógica de reintento.
Solicita al usuario los datos biométricos y confirma la inscripción:
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) {
// El SDK maneja la creación de payload automáticamente
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)
}
}
}Al usar view models con callbacks en SwiftUI, decláralos como @ObservedObject o @StateObject para evitar que sean destruidos durante las re-renderizaciones de la vista:
struct EnrollmentView: View {
@StateObject private var viewModel: EnrollmentViewModel
}El dispositivo ahora está inscrito y listo para pagos biométricos.
Una vez inscrito, iniciar pagos requiere cuatro llamadas a métodos (como puedes ver en el diagrama de secuencia a continuación):
Obtén todas las inscripciones para el dispositivo actual y permite que el usuario seleccione una:
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("Failed to load enrollments: \(error)")
}
}
}Mostrar inscripciones al usuario:
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("Estado: \(enrollment.status)")
.font(.caption)
.foregroundColor(.gray)
}
}
}
}
}
.onAppear {
viewModel.loadEnrollments(deviceId: savedDeviceId)
}
}
}Crea una intención de pago con todos los detalles del pago:
func createPayment(amount: Double, enrollmentId: String, beneficiaryAccountId: String) {
let payload = CreatePaymentIntentPayload(
amount: amount,
customer: Customer(identifier: userCPF), // CPF del usuario
description: "Pago por servicios",
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)
// Guardar ID de la intención de pago
self.paymentIntentId = paymentIntent.id
// Extraer opciones FIDO para el siguiente paso
if let fidoOptions = paymentIntent.paymentMethodInformation?.openFinanceBiometricPix?.fidoOptions {
self.promptForBiometric(fidoOptions: fidoOptions)
}
} catch {
print("Error al crear la intención de pago: \(error)")
}
}Solicitar autenticación biométrica y recopilar señales de riesgo:
class PaymentViewModel: NSObject, ObservableObject {
private let sdk: BiometricPixSDK
private var paymentIntentId: String?
private var riskSignals: RiskSignals?
private var assertionResponse: AssertionResponse?
func promptForBiometric(fidoOptions: FidoOptions) {
// Convertir opciones FIDO a cadena JSON
let fidoJsonString = fidoOptions.toJsonString()
do {
try sdk.startSigning(
fidoResponseString: fidoJsonString,
fallbackCredential: nil, // Opcional: proporcionar si tienes uno
callback: self
)
} catch {
print("Error al iniciar la firma: \(error)")
}
}
func collectRiskSignals() {
do {
self.riskSignals = try sdk.collectRiskSignals(
accountTenure: customerCreatedDate // "YYYY-MM-DD"
)
// Una vez que tenemos tanto la afirmación como las señales de riesgo, autorizar el pago
if assertionResponse != nil && riskSignals != nil {
self.authorizePayment()
}
} catch {
print("Error al recopilar señales de riesgo: \(error)")
}
}
}
// Implementar callback de autenticación FIDO
extension PaymentViewModel: FidoAuthenticationCallback {
func onSuccess(response: AssertionResponse) {
// Almacenar respuesta de afirmación
self.assertionResponse = response
// Recopilar señales de riesgo
self.collectRiskSignals()
}
func onError(error: String) {
DispatchQueue.main.async {
self.showPaymentError(message: error)
}
}
}Autoriza el pago con los datos recopilados:
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")
}
}
}El pago ahora está autorizado y en proceso. Necesitas monitorear los eventos de webhook para rastrear su estado final.
Todos los métodos del SDK que realizan operaciones de red pueden lanzar excepciones. Asegúrate de manejarlas adecuadamente:
do {
let institutions = try sdk.getPaymentInstitutions()
// Éxito
} catch let error as BiometricPixSDKError {
switch error {
case .networkError(let message):
// Manejar problemas de red
print("Error de red: \(message)")
case .authenticationError:
// Manejar token inválido o expirado
print("Autenticación fallida - el token puede estar expirado")
case .invalidParameters(let message):
// Manejar entrada inválida
print("Parámetros inválidos: \(message)")
case .unknown(let message):
// Manejar errores desconocidos
print("Error: \(message)")
}
} catch {
print("Error inesperado: \(error)")
}Aunque el SDK maneja la mayor parte del flujo de trabajo, aún debes escuchar las notificaciones de webhook para manejar actualizaciones asíncronas:
- Cambios en el estado de inscripción: tipo de webhook
ENROLLMENTS - Cambios en el estado de pago: tipo de webhook
PAYMENT_INTENTS
Para obtener la documentación completa de los webhooks, consulta Webhooks de Pagos (Brasil).
BiometricPixSDK(accessToken: String)
- Crea una nueva instancia del SDK con el token de acceso proporcionado
- Debe inicializarse una vez y reutilizarse en toda tu aplicación
- Token de acceso obtenido del endpoint
/payments/api/widget-token/
cleanup()
- Libera los recursos del SDK
- Llamar en
deinito cuando el usuario cierra sesión
getPaymentInstitutions() throws -> [Institution]
- Obtiene todas las instituciones que soportan pagos biométricos
- Devuelve un array de objetos
Institutionconid,displayName,iconLogo, etc. - Lanza excepciones en errores de red o autenticación
createEnrollment(cpf: String, institution: String, accountTenure: String, callbackUrl: String) throws -> Enrollment
- Crea la inscripción y recopila señales de riesgo automáticamente
cpf: Número de CPF del usuarioinstitution: ID de la institución degetPaymentInstitutions()accountTenure: Fecha de creación del cliente en formato "YYYY-MM-DD"callbackUrl: Enlace profundo para el callback de OAuth (debe estar registrado en applinks)- Devuelve un objeto
Enrollmentconid,redirect_url,device_id
completeEnrollmentAfterRedirection(callbackUrl: String) throws -> Enrollment
- Completa la inscripción después del callback de OAuth de la institución
- Analiza automáticamente los parámetros de OAuth desde la URL completa del callback
- Alternativa:
completeEnrollmentAfterRedirection(state: String, code: String, idToken: String)
getFidoRegistrationOptions(enrollmentId: String) -> FidoRegistrationOptions?
- Consulta las opciones de FIDO (reintento automático: intervalo de 1 segundo, tiempo de espera de 5 minutos)
- Devuelve
FidoRegistrationOptionscuando está listo - Devuelve
nilsi el tiempo de espera de la consulta se agota
startRegistration(fidoResponseString: String, callback: FidoRegistrationCallback) throws
- Inicia el flujo de registro biométrico (Face ID/Touch ID)
fidoResponseString: Cadena JSON deFidoRegistrationOptions.toJsonString()callback: Delegado para recibir callbacks de éxito/error
confirmEnrollment(enrollmentId: String, credential: PublicKeyCredential, response: AuthenticatorAttestationResponse) -> Bool
- Confirma la inscripción con la credencial FIDO
- Devuelve
trueen caso de éxito,falseen caso de fallo
listEnrollments(deviceId: String) throws -> [Enrollment]
- Obtiene todas las inscripciones para un dispositivo
- Devuelve un array de objetos
Enrollmentcon datos enriquecidos de la institución - Filtrar por
status == "SUCCEEDED"para mostrar solo inscripciones activas
createPaymentIntent(payload: CreatePaymentIntentPayload) throws -> PaymentIntent
- Crea una intención de pago
- Devuelve
PaymentIntentconidypaymentMethodInformation.openFinanceBiometricPix.fidoOptions
startSigning(fidoResponseString: String, fallbackCredential: String?, callback: FidoAuthenticationCallback) throws
- Inicia la autenticación biométrica para el pago
fallbackCredential: Credencial opcional para escenarios de reintentocallback: Delegado para recibir la respuesta de la afirmación
collectRiskSignals(accountTenure: String) throws -> RiskSignals
- Recoge huellas digitales del dispositivo y señales de seguridad
accountTenure: Fecha de creación del cliente en formato "YYYY-MM-DD"- Devuelve un objeto
RiskSignalspara la carga útil de autorización
authorizePaymentIntent(paymentIntentId: String, payload: AuthorizePaymentIntentPayload) -> Bool
- Autoriza el pago con afirmación biométrica y señales de riesgo
- Devuelve
trueen caso de éxito,falseen caso de fallo