📌 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.85'
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**
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface RCT_EXTERN_MODULE(MilioSdk, RCTEventEmitter)
RCT_EXTERN_METHOD(initializeSdk:(NSString *)token
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(setThemeColors:(NSDictionary *)colorDict
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
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(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",
]
}
@objc func initializeSdk(
_ token: NSString, resolver resolve: RCTPromiseResolveBlock,
rejecter reject: RCTPromiseRejectBlock
) {
authToken = token as String
//TransactionConfigSdk.setTransactionCallback(DelegateAdapter(bridge: self))
delegateAdapter = DelegateAdapter(bridge: self)
TransactionConfigSdk.setTransactionCallback(delegateAdapter!)
resolve("SDK inicializado correctamente con token \(authToken!)")
}
@objc func setThemeColors(
_ colorDict: NSDictionary, resolver resolve: RCTPromiseResolveBlock,
rejecter reject: RCTPromiseRejectBlock
) {
TransactionConfigSdk.setThemeColors(
btnColorEnabled: colorDict["btnColorEnabled"] as? String ?? "#000000",
btnColorDisabled: colorDict["btnColorDisabled"] as? String ?? "#AAAAAA",
btnTextColorEnabled: colorDict["btnTextColorEnabled"] as? String ?? "#FFFFFF",
btnTextColorDisabled: colorDict["btnTextColorDisabled"] as? String ?? "#666666",
layoutBackgroundColor: colorDict["layoutBackgroundColor"] as? String ?? "#F8F8F8",
textViewColor: colorDict["textViewColor"] as? String ?? "#000000"
)
resolve("🎨 Colores aplicados correctamente")
}
@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)
}
private enum TransactionType {
case qr, manual, addFunds, recharge
}
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
}
DispatchQueue.main.async { [weak self] in
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)
}
print("Transacción \(type) iniciada correctamente")
resolve("Transacción \(type) iniciada correctamente")
}
}
class DelegateAdapter: NSObject, MilioSDKDelegate {
weak var bridge: MilioSDKBridge?
init(bridge: MilioSDKBridge) {
self.bridge = bridge
}
func onTransactionError(errorCode: Int, errorMessage: String) {
print("Bridge::::: Nuevo balance: onTransactionError")
bridge?.sendEvent(
withName: "onTransactionError",
body: [
"errorCode": errorCode,
"errorMessage": errorMessage,
])
}
func onTransactionSuccess(amount: Double, message: String) {
print("Bridge::::: Nuevo balance: onTransactionSuccess")
bridge?.sendEvent(
withName: "onTransactionSuccess",
body: [
"amount": amount,
"message": message,
])
}
func onTransactionCompleted(amount: Double, isAddition: Bool) {
print("Bridge::::: Nuevo balance: onTransactionCompleted")
bridge?.sendEvent(
withName: "onTransactionCompleted",
body: [
"amount": amount,
"isAddition": isAddition,
])
}
func didCloseSdk() {
print("🔒 SDK cerrado desde React Native")
print("🔒 SDK cerrado desde React Native")
DispatchQueue.main.async {
self.bridge?.sendEvent(withName: "onTransactionClosed", body: nil)
}
}
func didUpdateBalance(newBalance: Double) {
print("💰 Nuevo balance: \(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.
/**
* Ejemplo de integración del SDK Milio en una aplicación React Native.
*
* Este componente muestra cómo:
* - Inicializar el SDK con un token de autenticación
* - Aplicar colores de tema personalizados al layout del SDK
* - Escuchar eventos emitidos por el SDK (éxito, error, cierre, etc.)
* - Ejecutar operaciones de transferencia con diferentes métodos (QR, manual, etc.)
*
* ⚠️ Importante:
* - El token de autenticación (`authToken`) debe ser válido y generado desde tu backend.
* - Este código usa `NativeModules` y `NativeEventEmitter` para comunicarse con el módulo nativo expuesto por el SDK (`MilioSdk`).
* - Reemplaza los tokens `dummy` por tokens reales en producción.
*
* Este archivo sirve como **base de referencia** para desarrolladores que integran el SDK Milio.
*/
import React, { useEffect, useState } from 'react';
import {
Alert,
Button,
NativeEventEmitter,
NativeModules,
ScrollView,
StyleSheet,
Text,
View,
} from 'react-native';
const { MilioSdk } = NativeModules;
const MilioSdkEmitter = new NativeEventEmitter(MilioSdk);
const App = () => {
const [authToken, setAuthToken] = useState<string | null>(null);
// Simulación de login para obtener token (reemplaza con tu lógica real)
const handleLogin = async () => {
try {
const token = "eyJhbGciOiJIUzI1NiIsIn..."
setAuthToken(token);
await MilioSdk.initializeSdk(token);
await setThemeColors();
} catch (error) {
Alert.alert('Error', 'No se pudo iniciar sesión o inicializar el SDK');
}
};
const setThemeColors = async () => {
try {
const colors = {
btnColorEnabled: '#3498db',
btnColorDisabled: '#95a5a6',
btnTextColorEnabled: '#ffffff',
btnTextColorDisabled: '#7f8c8d',
layoutBackgroundColor: '#ecf0f1',
textViewColor: '#2c3e50',
};
await MilioSdk.setThemeColors(colors);
} catch (error) {
Alert.alert('Error', 'No se pudieron aplicar los colores del tema');
}
};
useEffect(() => {
handleLogin();
const subscriptions = [
MilioSdkEmitter.addListener('onTransactionSuccess', (event) => {
console.log('✅ Transacción exitosa:', event);
Alert.alert('Éxito', event.message);
}),
MilioSdkEmitter.addListener('onTransactionError', (event) => {
console.log('❌ Transacción con error:', event);
Alert.alert('Error en transacción', `${event.errorMessage} (Código: ${event.errorCode})`);
}),
MilioSdkEmitter.addListener('onTransactionCompleted', (event) => {
console.log('📦 Transacción completada:', event);
}),
MilioSdkEmitter.addListener('onTransactionClosed', () => {
console.log('🔒 SDK cerrado por el usuario');
Alert.alert('SDK cerrado', 'El usuario ha cerrado la ventana del SDK.');
}),
];
return () => {
subscriptions.forEach((sub) => sub.remove());
};
}, []);
const initializeSdkAndExecute = async (execute: () => Promise<void>) => {
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');
}
};
const handleTransfer = (methodName: keyof typeof MilioSdk, tokenTransaction: string) => {
initializeSdkAndExecute(() =>
MilioSdk[methodName](tokenTransaction)
.then((res: string) => console.log(`✅ ${methodName}:`, res))
.catch((err: any) => console.error(`❌ ${methodName}:`, err))
);
};
const dummyToken = "eyJhbGciOiJIUzI1NiIsInR5c..."
return (
<ScrollView contentContainerStyle={styles.container}>
<Text style={styles.title}>Transacciones MilioSDK</Text>
<Button
title="Transferencia QR"
onPress={() => handleTransfer('openMilioSdkQr', dummyToken)}
/>
<Button
title="Transferencia Manual"
onPress={() => handleTransfer('openMilioPayManualCard', dummyToken)}
/>
<Button
title="Agregar Fondos Manual"
onPress={() => handleTransfer('openMilioPayAddFoundManualCard', dummyToken)}
/>
<Button
title="Recargar Billetera"
onPress={() => handleTransfer('OpenMilioPayRechargeWallet', dummyToken)}
/>
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
padding: 24,
gap: 16,
flexGrow: 1,
justifyContent: 'center',
backgroundColor: '#fff',
},
title: {
fontSize: 22,
fontWeight: 'bold',
marginBottom: 16,
textAlign: 'center',
},
});
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 |
---|---|
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. |
📌 Ejemplo de personalización
En el código, la función setThemeColors se encarga de aplicar estos cambios:
const setThemeColors = async () => {
try {
const colors = {
btnColorEnabled: "#3498db", // Azul brillante
btnColorDisabled: "#95a5a6", // Gris claro
btnTextColorEnabled: "#ffffff", // Blanco
btnTextColorDisabled: "#7f8c8d", // Gris oscuro
layoutBackgroundColor: "#ecf0f1", // Fondo gris claro
textViewColor: "#2c3e50" // Azul oscuro
};
await MilioSdk.setThemeColors(colors);
} catch (error) {
Alert.alert('Error', 'No se pudieron aplicar los colores');
}
};
📌 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.