📌 Documentación del SDK Milio para React Native (iOS)
🚀 Requisitos Previos
Antes de comenzar, asegúrate de cumplir con los siguientes requisitos:
- ✅ React Native
>=0.60
(se recomienda usar versiones actuales). - ✅ Xcode
>=14.0
instalado. - ✅ CocoaPods (
pod --version
debería devolver una versión válida). - ✅ Token de transaccion proporcionado por Milio.
- ✅ Version minima iOS 14.
1. Integración del SDK de iOS en React Native
✅ 1️⃣ Instalar la Dependencia del SDK con CocoaPods
Asegúrate de que CocoaPods esté instalado:
sudo gem install cocoapods
Luego, abre la carpeta ios y actualiza las dependencias:
pod deintegrate es opcional , este comando es para empezar desde cero con las dependencias nativas.
cd ios
pod deintegrate
Instalacion automatica
Si el SDK de Milio se distribuye a través de un repositorio privado de CocoaPods, agrégalo en tu Podfile (ios/Podfile):
source '[email protected]:miliopay/miliopods-specs.git'
source 'https://cdn.cocoapods.org/'
platform :ios, '14.0'
use_frameworks!
target 'SDKTestAppSpecsPrivate' do
pod 'sdk-mobile-ios',
:git => 'https://bitbucket.org/miliopay/sdk-mobile-ios.git',
:tag => '0.0.95'
end
Finalmente, instala las dependencias:
pod install --repo-update
2. Crear el Bridge entre React Native y el SDK de iOS
Para exponer el SDK de iOS a React Native, crea el archivo:
**ios/MilioSdkModule.m**
// ios/MilioSdk.m
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface RCT_EXTERN_MODULE(MilioSdk, RCTEventEmitter)
// Inicialización
RCT_EXTERN_METHOD(initializeSdk:(NSString *)token
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
// Configuración de tema
RCT_EXTERN_METHOD(setThemeColors:(NSDictionary *)colorDict
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
// Gestión de logo
RCT_EXTERN_METHOD(setLogoUrl:(NSString *)url
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(clearLogoUrl:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
// Configuración de fuente (iOS only)
RCT_EXTERN_METHOD(setFont:(NSString *)fontOption
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
// Configuración de ambiente (iOS only)
RCT_EXTERN_METHOD(setEnvironment:(NSString *)environment
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
// Métodos de transacción
RCT_EXTERN_METHOD(openMilioSdkQr:(NSString *)tokenTransaction
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(openMilioPayManualCard:(NSString *)tokenTransaction
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(openMilioPayAddFoundManualCard:(NSString *)tokenTransaction
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(openMilioPayRechargeWallet:(NSString *)tokenTransaction
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(openPaymentAliasConfiguration:(NSString *)tokenTransaction
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(openCrossBorderRequest:(NSString *)tokenTransaction
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
// Debug
RCT_EXTERN_METHOD(debugAvailableImages:(NSString *)imageNameToTest)
@end
Ahora, crea el archivo Swift para implementar los métodos nativos:
3. Enlazar el Módulo en React Native
Nueva actualizacion del cliente React native iOS
Resumen de Mejoras en la Nueva Versión del Cliente Milio SDK
Inicialización Automática del SDK
El SDK se inicializa automáticamente al iniciar sesión con el token de autenticación.
Configuración Dinámica de Colores
Se aplica un tema visual personalizado desde React Native para adaptarse al branding del cliente.
Soporte Completo de Eventos del SDK
El cliente ahora puede recibir notificaciones en tiempo real:
- Éxito o error en la transacción
- Finalización completa de la operación
- Cierre del SDK por el usuario
ios/MilioSdkModule.swift
import Foundation
import React
import UIKit
import sdk_mobile_ios
@objc(MilioSdk)
class MilioSDKBridge: RCTEventEmitter {
private var delegateAdapter: DelegateAdapter?
@objc func debugAvailableImages(_ imageName: NSString) {
TransactionConfigSdk.debugImagesInResourceBundle()
}
override static func requiresMainQueueSetup() -> Bool {
return true
}
@objc override static func moduleName() -> String {
return "MilioSdk"
}
private var hasListeners = false
private var authToken: String?
override func startObserving() {
hasListeners = true
}
override func stopObserving() {
hasListeners = false
}
override func supportedEvents() -> [String]! {
return [
"onTransactionError",
"onTransactionSuccess",
"onTransactionCompleted",
"onTransactionClosed",
"onTransactionBalanceUpdated",
"MilioSdkDidClose",
"MilioSdkDidUpdateBalance"
]
}
@objc func initializeSdk(
_ token: NSString,
resolver resolve: RCTPromiseResolveBlock,
rejecter reject: RCTPromiseRejectBlock
) {
authToken = token as String
delegateAdapter = DelegateAdapter(bridge: self)
TransactionConfigSdk.setTransactionCallback(delegateAdapter!)
resolve("SDK inicializado correctamente con token")
}
@objc func setThemeColors(
_ colorDict: NSDictionary,
resolver resolve: RCTPromiseResolveBlock,
rejecter reject: RCTPromiseRejectBlock
) {
TransactionConfigSdk.setThemeColors(
primaryColor: colorDict["primaryColor"] as? String ?? "#fd135a",
secondaryColor: colorDict["secondaryColor"] as? String ?? "#fadc30",
btnColorEnabled: colorDict["btnColorEnabled"] as? String ?? "#641f5e",
btnColorDisabled: colorDict["btnColorDisabled"] as? String ?? "#D3D3D3",
btnTextColorEnabled: colorDict["btnTextColorEnabled"] as? String ?? "#FFFFFF",
btnTextColorDisabled: colorDict["btnTextColorDisabled"] as? String ?? "#888888",
layoutBackgroundColor: colorDict["layoutBackgroundColor"] as? String ?? "#fafafa",
textViewColor: colorDict["textViewColor"] as? String ?? "#3cc683",
inputBorderColor: colorDict["inputBorderColor"] as? String ?? "#39a9f3",
textSelectionColor: colorDict["textSelectionColor"] as? String ?? "#e954b6"
)
resolve("Colores aplicados correctamente")
}
@objc func setLogoUrl(
_ url: NSString,
resolver resolve: RCTPromiseResolveBlock,
rejecter reject: RCTPromiseRejectBlock
) {
let str = (url as String).trimmingCharacters(in: .whitespacesAndNewlines)
if str.isEmpty {
TransactionConfigSdk.setLogoURL(nil)
resolve("Logo URL removida correctamente")
return
}
guard URL(string: str) != nil else {
reject("INVALID_URL", "El string proporcionado no es una URL válida", nil)
return
}
TransactionConfigSdk.setLogoURL(str)
resolve("Logo URL guardada correctamente")
}
@objc func clearLogoUrl(
_ resolve: RCTPromiseResolveBlock,
rejecter reject: RCTPromiseRejectBlock
) {
TransactionConfigSdk.setLogoURL(nil)
resolve("Logo URL removida correctamente")
}
@objc func setFont(
_ fontOption: NSString,
resolver resolve: RCTPromiseResolveBlock,
rejecter reject: RCTPromiseRejectBlock
) {
let fontString = fontOption as String
switch fontString.lowercased() {
case "satoshi":
TransactionConfigSdk.setFont(option: .satoshi)
default:
// Solo usar satoshi como default ya que system no existe
TransactionConfigSdk.setFont(option: .satoshi)
}
resolve("Font configurada: \(fontString)")
}
@objc func setEnvironment(
_ environment: NSString,
resolver resolve: RCTPromiseResolveBlock,
rejecter reject: RCTPromiseRejectBlock
) {
TransactionConfigSdk.setEnvironment(environment as String)
resolve("Ambiente configurado: \(environment)")
}
@objc func openMilioSdkQr(
_ tokenTransaction: NSString,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock
) {
openTransaction(
type: .qr,
tokenTransaction: tokenTransaction as String,
resolve: resolve,
reject: reject
)
}
@objc func openMilioPayManualCard(
_ tokenTransaction: NSString,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock
) {
openTransaction(
type: .manual,
tokenTransaction: tokenTransaction as String,
resolve: resolve,
reject: reject
)
}
@objc func openMilioPayAddFoundManualCard(
_ tokenTransaction: NSString,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock
) {
openTransaction(
type: .addFunds,
tokenTransaction: tokenTransaction as String,
resolve: resolve,
reject: reject
)
}
@objc func openMilioPayRechargeWallet(
_ tokenTransaction: NSString,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock
) {
openTransaction(
type: .recharge,
tokenTransaction: tokenTransaction as String,
resolve: resolve,
reject: reject
)
}
@objc func openPaymentAliasConfiguration(
_ tokenTransaction: NSString,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock
) {
openTransaction(
type: .aliasConfiguration,
tokenTransaction: tokenTransaction as String,
resolve: resolve,
reject: reject
)
}
@objc func openCrossBorderRequest(
_ tokenTransaction: NSString,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock
) {
openTransaction(
type: .crossBorder,
tokenTransaction: tokenTransaction as String,
resolve: resolve,
reject: reject
)
}
private enum TransactionType {
case qr
case manual
case addFunds
case recharge
case aliasConfiguration
case crossBorder
}
private func openTransaction(
type: TransactionType,
tokenTransaction: String,
resolve: @escaping RCTPromiseResolveBlock,
reject: @escaping RCTPromiseRejectBlock
) {
guard let token = authToken else {
reject("NO_TOKEN", "Token no inicializado", nil)
return
}
print("Iniciando transacción tipo: \(type) con token: \(tokenTransaction.prefix(20))...")
DispatchQueue.main.async {
guard
let rootVC = UIApplication.shared.connectedScenes
.compactMap({ $0 as? UIWindowScene })
.flatMap({ $0.windows })
.first(where: { $0.isKeyWindow })?.rootViewController
else {
reject("NO_VIEW_CONTROLLER", "No se encontró un controlador de vista válido", nil)
return
}
switch type {
case .qr:
TransactionConfigSdk.openMilioSdkQr(
from: rootVC,
token: token,
tokenTransaction: tokenTransaction
)
case .manual:
TransactionConfigSdk.openMilioPayManualCard(
from: rootVC,
token: token,
tokenTransaction: tokenTransaction
)
case .addFunds:
TransactionConfigSdk.openMilioPayAddFoundManualCard(
from: rootVC,
token: token,
tokenTransaction: tokenTransaction
)
case .recharge:
TransactionConfigSdk.openMilioPayRechargeWallet(
from: rootVC,
token: token,
tokenTransaction: tokenTransaction
)
case .aliasConfiguration:
TransactionConfigSdk.openPaymentAliasConfigurationSheet(
from: rootVC,
token: token,
tokenTransaction: tokenTransaction
)
case .crossBorder:
TransactionConfigSdk.openTransferGlobal(
from: rootVC,
token: token,
tokenTransaction: tokenTransaction
)
}
print("Transacción \(type) iniciada correctamente")
resolve("Transacción \(type) iniciada correctamente")
}
}
// MARK: - Delegate Adapter Class
class DelegateAdapter: NSObject, MilioSDKDelegate {
weak var bridge: MilioSDKBridge?
init(bridge: MilioSDKBridge) {
self.bridge = bridge
}
func onTransactionError(errorCode: Int, errorMessage: String) {
print("Bridge: Error en transacción - Código: \(errorCode), Mensaje: \(errorMessage)")
bridge?.sendEvent(
withName: "onTransactionError",
body: [
"errorCode": errorCode,
"errorMessage": errorMessage
]
)
}
func onTransactionSuccess(amount: Double, message: String) {
print("Bridge: Transacción exitosa - Monto: \(amount), Mensaje: \(message)")
bridge?.sendEvent(
withName: "onTransactionSuccess",
body: [
"amount": amount,
"message": message
]
)
}
func onTransactionCompleted(amount: Double, isAddition: Bool) {
print("Bridge: Transacción completada - Monto: \(amount), Es adición: \(isAddition)")
bridge?.sendEvent(
withName: "onTransactionCompleted",
body: [
"amount": amount,
"isAddition": isAddition
]
)
}
func didCloseSdk() {
print("Bridge: SDK cerrado")
DispatchQueue.main.async {
self.bridge?.sendEvent(withName: "MilioSdkDidClose", body: nil)
self.bridge?.sendEvent(withName: "onTransactionClosed", body: nil)
}
}
func didUpdateBalance(newBalance: Double) {
print("Bridge: Balance actualizado - Nuevo balance: \(newBalance)")
bridge?.sendEvent(
withName: "MilioSdkDidUpdateBalance",
body: newBalance
)
bridge?.sendEvent(
withName: "onTransactionBalanceUpdated",
body: ["newBalance": newBalance]
)
}
}
}
Codigo de javascript
Esto deja claro que:
- Es un ejemplo funcional, no producción.
- Explica el uso de initializeSdk, los colores, y los eventos.
- Ayuda a otros devs a saber dónde personalizar e integrar.
import React, { useEffect, useState } from 'react';
import {
SafeAreaView,
View,
Text,
StyleSheet,
Image,
TouchableOpacity,
NativeEventEmitter,
StatusBar,
Alert
} from 'react-native';
import WalletIcon from './assets/wallet.svg';
import Logo from './assets/logo.svg';
import MilioSdk from './MilioSdk';
const MilioSdkEmitter = new NativeEventEmitter(MilioSdk);
const App = () => {
const [loading, setLoading] = useState(false);
const [authToken, setAuthToken] = useState("");
const themeConfig = {
primaryColor: '#fd135a', // rojo
secondaryColor: '#fadc30', // amarillo
btnColorEnabled: '#641f5e', // morado
btnColorDisabled: '#D3D3D3',
btnTextColorEnabled: '#FFFFFF',
btnTextColorDisabled: '#888888',
layoutBackgroundColor: '#fafafa', // fondo
textViewColor: '#3cc683', //verde
inputBorderColor: '#39a9f3', // azul
textSelectionColor: '#e954b6' // rosa
};
useEffect(() => {
// 1) Aplico los colores
MilioSdk.setThemeColors(themeConfig)
.then(() => {
})
.then(msg => {
})
.catch(err => {
Alert.alert('SDK Error', err.message || err);
});
const url = "https://i.ibb.co/yBsc66yP/logo-milio.png";
MilioSdk.setLogoUrl(url)
.then(() => console.log("Logo URL seteada:", url))
.catch(err => Alert.alert('SDK Error', err.message || err));
}, []);
const initializeSdkAndExecute = async (execute: () => Promise<void>) => {
try {
await MilioSdk.initializeSdk("eyJhbGciOiJIUzI1NiIsInR5cC...");
await execute();
} catch (error) {
console.error("Error en SDK o ejecución:", error);
}
};
const handleRecargarManual = async () => {
await initializeSdkAndExecute(async () => {
var tokenTransaction = "eyJhbGciOiJIUzI1NiIsInTE1LTlkN2Q8RN7mfgwPACgQwFSF22bnwWKCgMD8LdU40Pyw80..."
try {
await MilioSdk.openMilioPayManualCard(tokenTransaction);
} catch (err) {
console.error("Error en openMilioPayManualCard:", err);
}
});
};
const handleTransferenciaInmediata = async () => {
await initializeSdkAndExecute(async () => {
const tokenTransaction = "eyJhbGciOiJIUzI1NiIsIIzNMjB9.3vkdip6MXfjM-FBlZwhDmp_fOxT61ELlJELxRvDyH4M..."
try {
await MilioSdk.openMilioPayAddFoundManualCard(tokenTransaction);
} catch (err) {
console.error("Error en openMilioPayManualCard:", err);
}
});
};
const handleRecargarBilletera = async () => {
await initializeSdkAndExecute(async () => {
const tokenTransaction = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
await MilioSdk.openMilioPayRechargeWallet(tokenTransaction);
});
};
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="light-content" backgroundColor="#0048ff" />
{/* Fondo Azul */}
<View style={styles.headerBackground}>
{/* Encabezado */}
<View style={styles.header}>
<Text style={styles.greeting}>Hola, Daniel</Text>
<Logo fill="white" style={{ color: "white", marginRight: 10 }} width={40} height={40} />
</View>
{/* Saldo */}
<Text style={styles.balanceText}>Saldo</Text>
<Text style={styles.balance}>$ 7.979.010,00</Text>
{/* Tarjeta */}
<View style={styles.card}>
<Text style={styles.cardText}>VISA •••• 5850</Text>
<Text style={styles.cardBrand}>milio</Text>
</View>
</View>
{/* Botones de acciones handleTransferenciaQr*/}
<View style={styles.actions}>
<TouchableOpacity style={styles.actionButton} onPress={handleRecargarManual}>
<Text style={styles.actionText}>Recargar manual</Text>
<WalletIcon width={24} height={24} />
</TouchableOpacity>
<TouchableOpacity style={styles.actionButton} onPress={handleTransferenciaInmediata}>
<Text style={styles.actionText}>Transferencia inmediata</Text>
<WalletIcon width={24} height={24} />
</TouchableOpacity>
<TouchableOpacity style={styles.actionButton} onPress={handleRecargarBilletera}>
<Text style={styles.actionText}>Recargar mi billetera</Text>
<WalletIcon width={24} height={24} />
</TouchableOpacity>
</View>
<Text style={styles.sectionTitle}>Transacciones</Text>
<View style={styles.transactionsPlaceholder}>
<View style={styles.skeleton} />
<View style={styles.skeleton} />
</View>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f8f9fa',
},
headerBackground: {
backgroundColor: '#0048ff',
padding: 20,
paddingTop: 20, // Para asegurar espacio en la parte superior
paddingBottom: 1,
borderBottomLeftRadius: 25,
borderBottomRightRadius: 25,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
greeting: {
fontSize: 22,
fontWeight: 'bold',
color: '#ffffff',
},
balanceText: {
marginTop: 10,
fontSize: 16,
color: '#ffffff',
},
balance: {
fontSize: 30,
fontWeight: 'bold',
color: '#ffffff',
paddingBottom: 20
},
card: {
backgroundColor: '#000000',
borderRadius: 10,
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
padding: 20,
paddingBottom: 30,
marginHorizontal: 5,
marginTop: 10, // Para superponerlo sobre el fondo azul
flexDirection: 'row',
justifyContent: 'space-between',
},
cardText: {
fontSize: 18,
color: '#ffffff',
},
cardBrand: {
fontSize: 20,
fontWeight: 'bold',
color: '#ffffff',
},
actions: {
marginTop: 20,
paddingHorizontal: 20,
},
actionButton: {
backgroundColor: '#ffffff',
borderRadius: 10,
padding: 30,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 10,
},
actionText: {
fontSize: 16,
fontWeight: '500',
color: '#333',
},
sectionTitle: {
fontSize: 18,
fontWeight: 'bold',
marginVertical: 10,
paddingHorizontal: 20,
},
transactionsPlaceholder: {
backgroundColor: '#f0f0f0',
padding: 20,
borderRadius: 10,
marginHorizontal: 20,
},
skeleton: {
height: 20,
backgroundColor: '#ddd',
borderRadius: 5,
marginBottom: 10,
},
icon: {
width: 24,
height: 24,
resizeMode: 'contain',
}
});
export default App;
Personalización del SDK con Colores
El SDK de Milio permite personalizar la apariencia de los elementos de la interfaz de usuario mediante la función setThemeColors. Esto permite adaptar el diseño del SDK a la identidad visual de cada aplicación.
🎨 ¿Qué puedes cambiar?
Al llamar a setThemeColors, puedes definir los siguientes colores:
Propiedad | Descripción |
---|---|
primaryColor | Color primario titulos. |
secondaryColor | Color para los botones secundario |
btnColorEnabled | Color del botón cuando está habilitado. |
btnColorDisabled | Color del botón cuando está deshabilitado. |
btnTextColorEnabled | Color del texto cuando el botón está habilitado. |
btnTextColorDisabled | Color del texto cuando el botón está deshabilitado. |
layoutBackgroundColor | Color de fondo del SDK. |
textViewColor | Color del texto en la interfaz del SDK. |
inputBorderColor | Color de los bordes |
textSelectionColor | Color de seleccion de los switch checkbox |
📌 Ejemplo de personalización
En el código, la función setThemeColors se encarga de aplicar estos cambios:
const setThemeColors = async () => {
try {
const colors = {
primaryColor: '#fd135a',
secondaryColor: '#fadc30',
btnColorEnabled: '#641f5e',
btnColorDisabled: '#D3D3D3',
btnTextColorEnabled: '#FFFFFF',
btnTextColorDisabled: '#888888',
layoutBackgroundColor: '#fafafa',
textViewColor: '#3498db',
inputBorderColor: '#3498db',
textSelectionColor: '#3498db'
};
await MilioSdk.setThemeColors(colors);
} catch (error) {
Alert.alert('Error', 'No se pudieron aplicar los colores');
}
};
Representacion grafica de los colores

Logo corporativo
Prepara tu imagen PNG
Nuestro ImageView nativo ocupa 120 dp × 36 dp. Para que el logo se vea siempre nítido, el PNG deberá cumplir al menos con estas resoluciones (en píxeles) según la densidad de la pantalla:
Densidad | Factor | Ancho (px) | Alto (px) |
---|---|---|---|
mdpi | 1× | 544 | 200 |
hdpi | 1.5× | 544 | 300 |
xhdpi | 2× | 1088 | 400 |
xxhdpi | 3× | 1632 | 600 |
xxxhdpi | 4× | 2176 | 800 |
const url = "https://i.ibb.co/yBsc66yP/logo-milio.png";
MilioSdk.setLogoUrl(url)
.then(() => console.log("Logo URL seteada:", url))
.catch(err => Alert.alert('SDK Error', err.message || err));
📌 Métodos Disponibles
🔐 Proceso de Encriptación y Seguridad en el SDK
El sistema de transferencias y recargas del SDK utiliza un mecanismo de encriptación de datos basado en criptografía de clave pública para garantizar la seguridad de la información en cada transacción.
🔑 1. ¿Por qué se usa una llave pública?
La llave pública, generada por un endpoint especializado, es la herramienta que permite cifrar la información antes de enviarla a través del SDK. Al encriptar los datos con esta llave, aseguramos que solo el servidor que tiene la llave privada correspondiente pueda descifrarlos, evitando accesos no autorizados o modificaciones en la información.
📲 2. Procesos que requieren encriptación con la llave pública
Cada una de las transacciones en el SDK debe pasar por este proceso de encriptación antes de ser enviada al servidor:
1️⃣ Transferencia con QR
Se escanea un código QR con los datos de la transacción.
La información se encripta con la llave pública antes de enviarla.
El servidor recibe los datos cifrados y los desencripta con su llave privada.
2️⃣ Transferencia Manual
El usuario ingresa manualmente los datos de la transacción (monto, cuenta destino, etc.).
Antes de enviarlos al SDK, los datos se cifran con la llave pública.
El servidor procesa la información tras desencriptarla.
3️⃣ Transferencia Inmediata
Similar a la transferencia manual, pero con una ejecución más rápida.
La información viaja encriptada con la llave pública para evitar manipulación en el proceso.
4️⃣ Recarga de Billetera
Para recargar fondos en la billetera digital, los datos (monto, origen, destino) se protegen con encriptación.
Solo el servidor, con su llave privada, podrá acceder a la información real y procesar la recarga.
🚀 3. Beneficios de este proceso de seguridad
✅ Protección de datos sensibles en cada transacción.
✅ Evita que terceros accedan o modifiquen la información.
✅ Cumple con estándares de seguridad y auditoría en encriptación.
Cada vez que se realiza una transferencia o recarga en el SDK, los datos deben encriptarse con la llave pública antes de enviarse. Solo el servidor con la llave privada correcta podrá descifrarlos y procesar la transacción, garantizando un entorno seguro y confiable para el usuario. 🔒
🔹 Inicializar el SDK
Importante: La inicialización del SDK solo debe hacerse una vez antes de utilizar cualquier flujo de transacción.
Antes de ejecutar initializeSdkAndExecute, es obligatorio iniciar sesión y obtener el authToken. Si este token no está disponible, el SDK no podrá inicializarse y las transacciones no podrán realizarse.
1️⃣ El usuario inicia sesión y se obtiene el authToken desde el backend.
2️⃣ Se almacena el authToken de manera segura.
3️⃣ Luego, se llama a initializeSdkAndExecute, pasándole el token.
const initializeSdkAndExecute = async (execute) => {
if (!authToken) {
Alert.alert('Error', 'Token no disponible');
return;
}
try {
await MilioSdk.initializeSdk(authToken);
await execute();
} catch (error) {
Alert.alert('Error', 'Hubo un problema con el SDK');
}
};
🔄 Flujos de Transferencia Disponibles
Una vez que el SDK está inicializado, puedes escoger cualquiera de los siguientes flujos para realizar transacciones:
Importante: Recuerda utilizar los metodos criptográficos correspondientes, puedes encontrarlos en Gestión de llaves para encriptación de datos
Recuerda que para activar el SDK, es necesario siempre encriptar los datos del lado del servidor. Este proceso implica encriptar de manera segura diversos datos sensibles, como la información de la tarjeta, los datos del titular o tercero, y tarjetas tokenizadas, entre otros. La encriptación es fundamental para garantizar la protección de la información durante su transmisión y evitar accesos no autorizados. A continuación, se muestran algunos ejemplos de los datos que deberías encriptar en el servidor:
- Información de la tarjeta (número, fecha de vencimiento, código de seguridad).
- Datos personales del titular o tercero.
- Tarjetas tokenizadas u otros identificadores sensibles.
Este procedimiento asegura que, aun en caso de una brecha de seguridad, la información permanezca protegida y cumpla con los estándares de seguridad exigidos.
Nuevas funcionalidades
- Tokenizacion
- Open link (Cross Border)
- Envio de dinero (OutBound)
Tener en cuenta que tienes que actualizar los archivos MilioSdkModule.m , MilioSDKBridge.swift .
Documentación de tokenizacion de tarjeta.
Este documento ofrece una guía detallada y secuencial para instalar e integrar el flujo de tokenización de tarjetas.

Generar token para levantar el SDK - Caso de uso tokenización de tarjetas
El SDK de Milio permite registrar tarjetas de manera segura desde el lado del cliente (frontend o app) sin que la información sensible pase por el cliente, la información llega directamente a Milio a la infraestructura de manera segura.
Pasos previos
- Configurar los pares de llaves
Nota: Si ya lo tienes no volver a realizar este paso. - Registro o actualización de usuario que realiza la solicitud.
Desde tu backend, realiza una llamada al endpoint de registro o actualización de terceros. Esto habilita al tercero para usar el SDK y registrar tarjetas de forma segura.
Preparar data y generar el token para levantar el SDK
- Cifrar la solicitud.
Desde el backend del cliente, se debe encriptar la información del solicitante, usando la llave pública entregada por Milio. - Generar token para levantar SDK
La información debe contener el uuid del tercero, un webhook al que Milio enviará el resultado del registro de tarjeta y el tipo de la solicitud (opción 7).
Una vez tu backend ha generado el token, el siguiente paso es invocar la función que abre la ventana nativa de tokenización dentro de tu app.
Levantar el sdk con la opción de tokenización
Crear la función
En tu proyecto RN, añade un helper para inicializar el SDK y luego ejecutar la llamada a configuración de alias. Por ejemplo en milioSdk:
aliasInputData es justo el token que tu backend ha generado (el “key token” de tokenización). Al pasarlo a await MilioSdk.openPaymentAliasConfiguration(aliasInputData);
const handleAliasConfig = () => {
const aliasInputData = "eyJhbGciOiJIU..."
initializeSdkAndExecute(async () => {
await MilioSdk.openPaymentAliasConfiguration(aliasInputData);
});
};
- Llamar desde tu UI en React Native
Agrega un botón que dispara la función (handleAliasConfig) tokenización de la tarjeta.
<TouchableOpacity style={styles.actionButton} onPress={handleAliasConfig}>
<Text style={styles.actionText}>Tokenización de tarjetas</Text>
</TouchableOpacity>
Documentación de Cross Border .
Este documento ofrece una guía detallada y secuencial para instalar e integrar el flujo de cross border.

Generar token para levantar el SDK
El SDK de Milio permite iniciar flujos de operaciones cross-border (envíos internacionales) directamente desde la app del cliente, sin exponer información sensible y garantizando la seguridad de extremo a extremo.
Pasos previos
- Configurar los pares de llaves
Nota: Si ya lo tienes no volver a realizar este paso. - Registro o actualización de usuario que realiza la solicitud.
Desde tu backend, realiza una llamada al endpoint de registro o actualización de terceros. Esto habilita al tercero para usar el SDK y registrar tarjetas de forma segura. - Tokenización de tarjeta el tercero (opción 7)
Preparar data y generar el token para levantar el SDK
-
Desde el backend del cliente, se debe encriptar la información del solicitante, usando la llave pública entregada por Milio.Cifrar la solicitud. -
La información debe incluir el uuid del tercero, el uuid de la tarjeta asociada, el webhook al que Milio enviará el resultado de la operación, el tipo de solicitud (opción 8) y, de forma opcional, una referencia personalizada.
Una vez tu backend ha generado el token, el siguiente paso es invocar la función que abre la ventana nativa de tokenización dentro de tu app.
Levantar el sdk con la opción solicitud de Cross-Border (open link - request to pay)
Si ya tienes una implementación del sdk, por favor actualizarse a esta nueva versión para poder levantar el formulario de Cross Border Request.
Crear la función
En tu proyecto RN, añade un helper para inicializar el SDK y luego ejecutar la llamada a configuración de alias. Por ejemplo en milioSdk:
token es justo el token que tu backend ha generado (el “key token” de request to pay). Al pasarlo a await MilioSdk.openCrossBorderRequest(token);
const handleCrossBorderRequest = () => {
const token = "eyJhbGciOiJIUzI1NiIsInRTkxM..."
initializeSdkAndExecute(async () => {
await MilioSdk.openCrossBorderRequest(token);
});
};
- Llamar desde tu UI en React Native.
Agrega un botón que dispara la función (handleCrossBorder).
<TouchableOpacity style={styles.actionButton} onPress={handleCrossBorderRequest}>
<Text style={styles.actionText}>Cross Border Request</Text>
</TouchableOpacity>
Documentación Envio de Dinero OutBound .
Este documento ofrece una guía detallada y secuencial para instalar e integrar el flujo de OutBound.

Funcionamiento de OutBound (envio de dinero)
En la ventana de enviar debemos tener un valor diferente de 0(input del valor a enviar), para poder cambiar a nuestro criterio la divisa origen.
-
En el input de valor a enviar, se ira actualizado el cargo o costo que tiene la transacción. porque por defecto el coste aparece aplicado al remitente.
-
Al escribir un valor, automáticamente se refleja la conversión en el texto del 'Destinatario recibe'
-
Como se explicó en el punto 2, que se va actualizando el coste de la transacción, y por el contrario queremos trasladar el costo no al remitente, sino al destinatario, vamos al aparte '¿Quién asume costo de envío?' y seleccionamos la lista y escogemos Destinatario, en ese caso, automáticamente el costo será cargado al destinatario.
-
Generar token para levantar el SDK
El SDK de Milio permite iniciar flujos de operaciones OutBound (envio de dinero) directamente desde la app del cliente, sin exponer información sensible y garantizando la seguridad de extremo a extremo.
Pasos previos
- Configurar los pares de llaves
Nota: Si ya lo tienes no volver a realizar este paso. - Registro o actualización de usuario que realiza la solicitud.
Desde tu backend, realiza una llamada al endpoint de registro o actualización de terceros. Esto habilita al tercero para usar el SDK y registrar tarjetas de forma segura. - Tokenización de tarjeta el tercero (opción 7)
Preparar data y generar el token para levantar el SDK
-
Desde el backend del cliente, se debe encriptar la información del solicitante, usando la llave pública entregada por Milio.Cifrar la solicitud. -
La información debe incluir el uuid del tercero, el uuid de la tarjeta asociada, el webhook al que Milio enviará el resultado de la operación, el tipo de solicitud (opción 9) y, de forma opcional, una referencia personalizada.
Una vez tu backend ha generado el token, el siguiente paso es invocar la función que abre la ventana nativa de tokenización dentro de tu app.
Levantar el sdk con la opción OutBound
Si ya tienes una implementación del sdk, por favor actualizarse a esta nueva versión para poder levantar el formulario de OutBound.
Asegúrate de tener en tu módulo de Android la dependencia correcta:
Crear la función
-
Actualizar MilioSDKBridge.swift
import Foundation import React import UIKit import sdk_mobile_ios @objc(MilioSdk) class MilioSDKBridge: RCTEventEmitter { private var delegateAdapter: DelegateAdapter? @objc func debugAvailableImages(_ imageName: NSString) { TransactionConfigSdk.debugImagesInResourceBundle() } override static func requiresMainQueueSetup() -> Bool { return true } @objc override static func moduleName() -> String { return "MilioSdk" } private var hasListeners = false private var authToken: String? override func startObserving() { hasListeners = true } override func stopObserving() { hasListeners = false } override func supportedEvents() -> [String]! { return [ "onTransactionError", "onTransactionSuccess", "onTransactionCompleted", "onTransactionClosed", "onTransactionBalanceUpdated", "MilioSdkDidClose", "MilioSdkDidUpdateBalance" ] } @objc func initializeSdk( _ token: NSString, resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock ) { authToken = token as String delegateAdapter = DelegateAdapter(bridge: self) TransactionConfigSdk.setTransactionCallback(delegateAdapter!) resolve("SDK inicializado correctamente con token") } @objc func setThemeColors( _ colorDict: NSDictionary, resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock ) { TransactionConfigSdk.setThemeColors( primaryColor: colorDict["primaryColor"] as? String ?? "#fd135a", secondaryColor: colorDict["secondaryColor"] as? String ?? "#fadc30", btnColorEnabled: colorDict["btnColorEnabled"] as? String ?? "#641f5e", btnColorDisabled: colorDict["btnColorDisabled"] as? String ?? "#D3D3D3", btnTextColorEnabled: colorDict["btnTextColorEnabled"] as? String ?? "#FFFFFF", btnTextColorDisabled: colorDict["btnTextColorDisabled"] as? String ?? "#888888", layoutBackgroundColor: colorDict["layoutBackgroundColor"] as? String ?? "#fafafa", textViewColor: colorDict["textViewColor"] as? String ?? "#3cc683", inputBorderColor: colorDict["inputBorderColor"] as? String ?? "#39a9f3", textSelectionColor: colorDict["textSelectionColor"] as? String ?? "#e954b6" ) resolve("Colores aplicados correctamente") } @objc func setLogoUrl( _ url: NSString, resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock ) { let str = (url as String).trimmingCharacters(in: .whitespacesAndNewlines) if str.isEmpty { TransactionConfigSdk.setLogoURL(nil) resolve("Logo URL removida correctamente") return } guard URL(string: str) != nil else { reject("INVALID_URL", "El string proporcionado no es una URL válida", nil) return } TransactionConfigSdk.setLogoURL(str) resolve("Logo URL guardada correctamente") } @objc func clearLogoUrl( _ resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock ) { TransactionConfigSdk.setLogoURL(nil) resolve("Logo URL removida correctamente") } @objc func setFont( _ fontOption: NSString, resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock ) { let fontString = fontOption as String switch fontString.lowercased() { case "satoshi": TransactionConfigSdk.setFont(option: .satoshi) default: // Solo usar satoshi como default ya que system no existe TransactionConfigSdk.setFont(option: .satoshi) } resolve("Font configurada: \(fontString)") } @objc func setEnvironment( _ environment: NSString, resolver resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock ) { TransactionConfigSdk.setEnvironment(environment as String) resolve("Ambiente configurado: \(environment)") } @objc func openMilioSdkQr( _ tokenTransaction: NSString, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock ) { openTransaction( type: .qr, tokenTransaction: tokenTransaction as String, resolve: resolve, reject: reject ) } @objc func openMilioPayManualCard( _ tokenTransaction: NSString, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock ) { openTransaction( type: .manual, tokenTransaction: tokenTransaction as String, resolve: resolve, reject: reject ) } @objc func openMilioPayAddFoundManualCard( _ tokenTransaction: NSString, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock ) { openTransaction( type: .addFunds, tokenTransaction: tokenTransaction as String, resolve: resolve, reject: reject ) } @objc func openMilioPayRechargeWallet( _ tokenTransaction: NSString, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock ) { openTransaction( type: .recharge, tokenTransaction: tokenTransaction as String, resolve: resolve, reject: reject ) } @objc func openPaymentAliasConfiguration( _ tokenTransaction: NSString, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock ) { openTransaction( type: .aliasConfiguration, tokenTransaction: tokenTransaction as String, resolve: resolve, reject: reject ) } @objc func openCrossBorderRequest( _ tokenTransaction: NSString, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock ) { openTransaction( type: .crossBorder, tokenTransaction: tokenTransaction as String, resolve: resolve, reject: reject ) } // Agregar este método después de openCrossBorderRequest @objc func openSendingMoneyRequest( _ tokenTransaction: NSString, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock ) { openTransaction( type: .sendingMoney, tokenTransaction: tokenTransaction as String, resolve: resolve, reject: reject ) } private enum TransactionType { case qr case manual case addFunds case recharge case aliasConfiguration case crossBorder case sendingMoney } private func openTransaction( type: TransactionType, tokenTransaction: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock ) { guard let token = authToken else { reject("NO_TOKEN", "Token no inicializado", nil) return } print("Iniciando transacción tipo: \(type) con token: \(tokenTransaction.prefix(20))...") DispatchQueue.main.async { guard let rootVC = UIApplication.shared.connectedScenes .compactMap({ $0 as? UIWindowScene }) .flatMap({ $0.windows }) .first(where: { $0.isKeyWindow })?.rootViewController else { reject("NO_VIEW_CONTROLLER", "No se encontró un controlador de vista válido", nil) return } switch type { case .qr: TransactionConfigSdk.openMilioSdkQr( from: rootVC, token: token, tokenTransaction: tokenTransaction ) case .manual: TransactionConfigSdk.openMilioPayManualCard( from: rootVC, token: token, tokenTransaction: tokenTransaction ) case .addFunds: TransactionConfigSdk.openMilioPayAddFoundManualCard( from: rootVC, token: token, tokenTransaction: tokenTransaction ) case .recharge: TransactionConfigSdk.openMilioPayRechargeWallet( from: rootVC, token: token, tokenTransaction: tokenTransaction ) case .aliasConfiguration: TransactionConfigSdk.openPaymentAliasConfigurationSheet( from: rootVC, token: token, tokenTransaction: tokenTransaction ) case .crossBorder: TransactionConfigSdk.openTransferGlobal( from: rootVC, token: token, tokenTransaction: tokenTransaction ) case .sendingMoney: TransactionConfigSdk.openSendingMoneyRequest( from: rootVC, token: token, tokenTransaction: tokenTransaction ) } print("Transacción \(type) iniciada correctamente") resolve("Transacción \(type) iniciada correctamente") } } // MARK: - Delegate Adapter Class class DelegateAdapter: NSObject, MilioSDKDelegate { weak var bridge: MilioSDKBridge? init(bridge: MilioSDKBridge) { self.bridge = bridge } func onTransactionError(errorCode: Int, errorMessage: String) { print("Bridge: Error en transacción - Código: \(errorCode), Mensaje: \(errorMessage)") bridge?.sendEvent( withName: "onTransactionError", body: [ "errorCode": errorCode, "errorMessage": errorMessage ] ) } func onTransactionSuccess(amount: Double, message: String) { print("Bridge: Transacción exitosa - Monto: \(amount), Mensaje: \(message)") bridge?.sendEvent( withName: "onTransactionSuccess", body: [ "amount": amount, "message": message ] ) } func onTransactionCompleted(amount: Double, isAddition: Bool) { print("Bridge: Transacción completada - Monto: \(amount), Es adición: \(isAddition)") bridge?.sendEvent( withName: "onTransactionCompleted", body: [ "amount": amount, "isAddition": isAddition ] ) } func didCloseSdk() { print("Bridge: SDK cerrado") DispatchQueue.main.async { self.bridge?.sendEvent(withName: "MilioSdkDidClose", body: nil) self.bridge?.sendEvent(withName: "onTransactionClosed", body: nil) } } func didUpdateBalance(newBalance: Double) { print("Bridge: Balance actualizado - Nuevo balance: \(newBalance)") bridge?.sendEvent( withName: "MilioSdkDidUpdateBalance", body: newBalance ) bridge?.sendEvent( withName: "onTransactionBalanceUpdated", body: ["newBalance": newBalance] ) } } }
-
Actualizar MilioSdkModule.m
// ios/MilioSdk.m #import <React/RCTBridgeModule.h> #import <React/RCTEventEmitter.h> @interface RCT_EXTERN_MODULE(MilioSdk, RCTEventEmitter) // Inicialización RCT_EXTERN_METHOD(initializeSdk:(NSString *)token resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) // Configuración de tema RCT_EXTERN_METHOD(setThemeColors:(NSDictionary *)colorDict resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) // Gestión de logo RCT_EXTERN_METHOD(setLogoUrl:(NSString *)url resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(clearLogoUrl:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) // Configuración de fuente (iOS only) RCT_EXTERN_METHOD(setFont:(NSString *)fontOption resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) // Configuración de ambiente (iOS only) RCT_EXTERN_METHOD(setEnvironment:(NSString *)environment resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) // Métodos de transacción RCT_EXTERN_METHOD(openMilioSdkQr:(NSString *)tokenTransaction resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(openMilioPayManualCard:(NSString *)tokenTransaction resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(openMilioPayAddFoundManualCard:(NSString *)tokenTransaction resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(openMilioPayRechargeWallet:(NSString *)tokenTransaction resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(openPaymentAliasConfiguration:(NSString *)tokenTransaction resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(openCrossBorderRequest:(NSString *)tokenTransaction resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) RCT_EXTERN_METHOD(openSendingMoneyRequest:(NSString *)tokenTransaction resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) // Debug RCT_EXTERN_METHOD(debugAvailableImages:(NSString *)imageNameToTest) @end
En tu proyecto RN, añade un helper para inicializar el SDK y luego ejecutar la llamada a configuración de alias. Por ejemplo en milioSdk:
token es justo el token que tu backend ha generado (el “key token” de outbound). Al pasarlo a await MilioSdk.openSendingMoneyRequest(token);
const handleSendingMoneyRequest = () => {
const token = "eyJhbGciOiJIUzI1NiIsInRTkxM..."
initializeSdkAndExecute(async () => {
await MilioSdk.openSendingMoneyRequest(token);
});
};
- Llamar desde tu UI en React Native.
Agrega un botón que dispara la función (handleSendingMoneyRequest).
<TouchableOpacity style={styles.actionButton} onPress={handleSendingMoneyRequest}>
<Text style={styles.actionText}>Envio de dinero</Text>
<Icon name="send" size={24} color="#666" />
</TouchableOpacity>