API Reference

Instalación Android

Requisitos Previos

Antes de comenzar, asegúrate de cumplir con los siguientes requisitos:

  • React Native >=0.60 (se recomienda usar versiones actuales).
  • Android SDK y herramientas de desarrollo configuradas.
  • Gradle y Kotlin en versiones compatibles (1.6.21+).
  • Token de autenticación proporcionado por Milio (authToken).

Integración del SDK de Android en React Native usando JitPack


Paso 1: Configurar el archivo android/build.gradle

Asegúrate de que en android/build.gradle la configuración sea compatible:

compileSdkVersion y targetSdkVersion deben ser lo más altos posible para compatibilidad con Google Play.
minSdkVersion depende del público objetivo (reducirlo permite más compatibilidad, pero aumenta complejidad).
Si hay errores de compilación, verifica que el SDK correspondiente esté instalado en Android Studio.

buildscript {
    ext {
        buildToolsVersion = "36.0.0"
        minSdkVersion = 24
        compileSdkVersion = 36
        targetSdkVersion = 35
        ndkVersion = "xxxxxxxxxxxxx"
        kotlinVersion = "xxxxxx"
    }
...........

Si deseas usar el .aar este se debe ubicar en android/app/libs/sdkmilio-v0.4.18.aar

buildscript {
    ...........
    repositories {
        google()
        mavenCentral()
        flatDir { dirs "$rootDir/libs" } // <— carpeta del AAR
    }
...........

🔹 Si tienes problemas con dependencias, verifica que el repositorio correcto esté incluido. 🚀🔥


Paso 2: Agregar la Dependencia del SDK en android/app/build.gradle

Abre android/app/build.gradle y en la sección dependencies agrega tu SDK desde JitPack:

dependencies {
    implementation("org.bitbucket.miliopay:sdk-mobile-android:v0.4.18") // <— recuerda que la versión irá cambiando....
}

NOTA: En caso de usar la libreria de JitPack se debe configurar authToken en android/gradle.properties

authToken=XXXXXXXXXXXXXXXXXXX

Instanciar la URL de JitPack y authToken

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
    repositories {
        google()
        mavenCentral()
         maven {
            url = uri("https://jitpack.io")
            credentials { username authToken }
        }
    }
}

En caso de usar .aar no se requieren las configuraciones anteriores de este paso 2

dependencies {
    implementation(files("libs/sdkmilio-v0.4.18.aar")) // <— recuerda que la versión irá cambiando....
}

Documentación del SDK Milio para React Native

Descripción

Este módulo permite a las aplicaciones React Native interactuar con el SDK de Milio para realizar diversas operaciones financieras, como Transferencia con QR, Transferencia Manual, Transferencia Inmediata, Recargas de Billetera (Tarjeta), Tokenización de Tarjetas, Transferencia Global (OpenLink, Request to Pay), Envío de Dinero (Crossborder).

El módulo está basado en una implementación nativa en Android y se expone a JavaScript a través del puente de React Native.


1. Crear el package nativo en MilioSdkPackage.kt

Crear el archivo android/app/src/main/java/com/tuempresa/MilioSdkPackage.kt y agrega:

package com.tuempresa

import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager

class MilioSdkPackage : ReactPackage {
    override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
        return listOf(MilioSdkModule(reactContext))
    }

    override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
        return emptyList()
    }
}

2. Vincular el módulo nativo en MainApplication.kt

Agregar al archivo android/app/src/main/java/com/tuempresa/MainApplication.kt el código add(MilioSdkPackage())

class MainApplication : Application(), ReactApplication {

  override val reactNativeHost: ReactNativeHost =
      object : DefaultReactNativeHost(this) {
        override fun getPackages(): List<ReactPackage> =
            PackageList(this).packages.apply {
              add(MilioSdkPackage()) // <— Agregar el paquete del SDK local
            }

Implementación en Android (Kotlin)

Código de MilioSdkModule.kt

Crear el archivo android/app/src/main/java/com/tuempresa/MilioSdkModule.kt y agrega:

package com.tuempresa

import android.app.Activity
import android.app.Application
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.facebook.react.bridge.*
import com.facebook.react.modules.core.DeviceEventManagerModule
import com.milio.sdkmilio.TransactionCallback
import com.milio.sdkmilio.TransactionConfigSdk
import com.milio.sdkmilio.core.LocaleHelper

class MilioSdkModule(reactContext: ReactApplicationContext) :
  ReactContextBaseJavaModule(reactContext),
  LifecycleEventListener {

  companion object { 
    private const val TAG = "MilioSdkModule"
  }
 
  private var sdkCallback: TransactionCallback? = null

  init {
    reactContext.addLifecycleEventListener(this)
    initializeCallback()
  }

  override fun getName(): String = "MilioSdk"

  @ReactMethod fun addListener(eventName: String) {}
  @ReactMethod fun removeListeners(count: Int) {}

  private fun initializeCallback() {
    if (sdkCallback == null) {
      sdkCallback = object : TransactionCallback {
        override fun onTransactionSuccess(amount: Double, message: String) {
          Log.d(TAG, "✅ onTransactionSuccess: $amount")
          emitEvent("onTransactionSuccess", Arguments.createMap().apply {
            putDouble("amount", amount)
            putString("message", message)
          })
        }
        
        override fun didUpdateBalance(newBalance: Double) {
          Log.d(TAG, "✅ didUpdateBalance: $newBalance")
          emitEvent("MilioSdkDidUpdateBalance", newBalance)
        }
        
        override fun onTransactionCompleted(amount: Double, isAddition: Boolean) {
          Log.d(TAG, "✅ onTransactionCompleted: $amount")
          emitEvent("onTransactionCompleted", Arguments.createMap().apply {
            putDouble("amount", amount)
            putBoolean("isAddition", isAddition)
          })
        }
        
        override fun onTransactionError(errorCode: Int, errorMessage: String) {
          Log.e(TAG, "❌ onTransactionError: $errorCode - $errorMessage")
          emitEvent("onTransactionError", Arguments.createMap().apply {
            putInt("errorCode", errorCode)
            putString("errorMessage", errorMessage)
          })
        }
        
        override fun didCloseSdk() {
          Log.d(TAG, "🚪 didCloseSdk")
          emitEvent("MilioSdkDidClose", null)
        }
      }
      
      TransactionConfigSdk.setTransactionCallback(sdkCallback!!)
      Log.d(TAG, "✅ Callback inicializado")
    }
  }

  // ✅ Aplica locale antes de abrir cualquier ventana del SDK
  private fun applyLocale(activity: Activity) {
    try {
      LocaleHelper.updateActivityResources(activity)
      Log.d(TAG, "✅ Locale aplicado")
    } catch (e: Exception) {
      Log.w(TAG, "⚠️ No se pudo aplicar locale: ${e.message}")
    }
  }

  @ReactMethod
  fun initializeSdk(promise: Promise) {
    try {
      initializeCallback()
      Log.d(TAG, "✅ SDK Inicializado")
      promise.resolve("SDK Inicializado correctamente")
    } catch (e: Exception) {
      Log.e(TAG, "❌ Error", e)
      promise.reject("ERROR", e.message, e)
    }
  }

  @ReactMethod
  fun setThemeColors(config: ReadableMap, promise: Promise) {
    val activity = getCurrentActivity() as? AppCompatActivity
    ?: return promise.reject("ERROR", "No activity")

    try {
      TransactionConfigSdk.setThemeColors(
        context = activity,
        primaryColor = config.getString("primaryColor") ?: "#3C64A7",
        secondaryColor = config.getString("secondaryColor") ?: "#0e6655",
        btnColorEnabled = config.getString("btnColorEnabled") ?: "#0e6655",
        btnColorDisabled = config.getString("btnColorDisabled") ?: "#D3D3D3",
        btnTextColorEnabled = config.getString("btnTextColorEnabled") ?: "#ffffff",
        btnTextColorDisabled = config.getString("btnTextColorDisabled") ?: "#888888",
        layoutBackgroundColor = config.getString("layoutBackgroundColor") ?: "#FFFFFF",
        textViewColor = config.getString("textViewColor") ?: "#000000",
        inputBorderColor = config.getString("inputBorderColor") ?: "#8C8C8C",
        textSelectionColor = config.getString("textSelectionColor") ?: "#3b5998"
      )
      promise.resolve("OK")
    } catch (e: Exception) {
      promise.reject("ERROR", e.message, e)
    }
  }


  @ReactMethod
  fun openMilioPayManualCard(jsonData: String, promise: Promise) {
    Log.d(TAG, "🚀 openMilioPayManualCard")
    
    if (jsonData.isBlank()) {
      return promise.reject("ERROR", "Token vacío")
    }
    
    val activity = getCurrentActivity() as? AppCompatActivity
      ?: return promise.reject("ERROR", "No activity")
    
    try {
      initializeCallback()
      
      UiThreadUtil.runOnUiThread {
        try {
          applyLocale(activity)
          TransactionConfigSdk.openMilioPayManualCard(activity, jsonData)
          
          Log.d(TAG, "✅ Add Found abierto")
          Handler(Looper.getMainLooper()).postDelayed({
            promise.resolve("OK")
          }, 100)
        } catch (e: Exception) {
          Log.e(TAG, "❌ Error", e)
          promise.reject("ERROR", e.message, e)
        }
      }
    } catch (e: Exception) {
      promise.reject("ERROR", e.message, e)
    }
  }


  @ReactMethod
  fun openMilioPayAddFoundManualCard(jsonData: String, promise: Promise) {
    Log.d(TAG, "🚀 openMilioPayAddFoundManualCard")
    
    if (jsonData.isBlank()) {
      return promise.reject("ERROR", "Token vacío")
    }
    
    val activity = getCurrentActivity() as? AppCompatActivity
      ?: return promise.reject("ERROR", "No activity")
    
    try {
      initializeCallback()
      
      UiThreadUtil.runOnUiThread {
        try {
          applyLocale(activity)
          TransactionConfigSdk.openMilioPayAddFoundManualCard(activity, jsonData)
          
          Log.d(TAG, "✅ Add Found abierto")
          Handler(Looper.getMainLooper()).postDelayed({
            promise.resolve("OK")
          }, 100)
        } catch (e: Exception) {
          Log.e(TAG, "❌ Error", e)
          promise.reject("ERROR", e.message, e)
        }
      }
    } catch (e: Exception) {
      promise.reject("ERROR", e.message, e)
    }
  }

  @ReactMethod
  fun openMilioPayRechargeWallet(jsonData: String, promise: Promise) {
    Log.d(TAG, "🚀 openMilioPayRechargeWallet")
    
    if (jsonData.isBlank()) {
      return promise.reject("ERROR", "Token vacío")
    }
    
    val activity = getCurrentActivity() as? AppCompatActivity
      ?: return promise.reject("ERROR", "No activity")
    
    try {
      initializeCallback()
      
      UiThreadUtil.runOnUiThread {
        try {
          applyLocale(activity)
          TransactionConfigSdk.openMilioPayRechargeWallet(
            activity,
            activity.supportFragmentManager,
            jsonData
          )
          
          Log.d(TAG, "✅ Recharge abierto")
          Handler(Looper.getMainLooper()).postDelayed({
            promise.resolve("OK")
          }, 100)
        } catch (e: Exception) {
          Log.e(TAG, "❌ Error", e)
          promise.reject("ERROR", e.message, e)
        }
      }
    } catch (e: Exception) {
      promise.reject("ERROR", e.message, e)
    }
  }

  @ReactMethod
  fun openPaymentAliasConfiguration(jsonData: String?, promise: Promise) {
    Log.d(TAG, "🚀 openPaymentAliasConfiguration")
    
    val activity = getCurrentActivity() as? AppCompatActivity
      ?: return promise.reject("ERROR", "No activity")
    
    try {
      initializeCallback()
      
      UiThreadUtil.runOnUiThread {
        try {
          applyLocale(activity)
          TransactionConfigSdk.openPaymentAliasConfigurationSheet(activity, jsonData)
          
          Log.d(TAG, "✅ Alias Config abierto")
          Handler(Looper.getMainLooper()).postDelayed({
            promise.resolve("OK")
          }, 100)
        } catch (e: Exception) {
          Log.e(TAG, "❌ Error", e)
          promise.reject("ERROR", e.message, e)
        }
      }
    } catch (e: Exception) {
      promise.reject("ERROR", e.message, e)
    }
  }

  @ReactMethod
  fun openCrossBorderRequest(jsonData: String?, promise: Promise) {
    Log.d(TAG, "🚀 openCrossBorderRequest")
    
    val activity = getCurrentActivity() as? AppCompatActivity
      ?: return promise.reject("ERROR", "No activity")
    
    try {
      initializeCallback()
      
      UiThreadUtil.runOnUiThread {
        try {
          applyLocale(activity)
          TransactionConfigSdk.openCrossBorderRequest(activity, jsonData)
          
          Log.d(TAG, "✅ Cross Border abierto")
          Handler(Looper.getMainLooper()).postDelayed({
            promise.resolve("OK")
          }, 100)
        } catch (e: Exception) {
          Log.e(TAG, "❌ Error", e)
          promise.reject("ERROR", e.message, e)
        }
      }
    } catch (e: Exception) {
      promise.reject("ERROR", e.message, e)
    }
  }

  @ReactMethod
  fun openSendingMoneyRequest(jsonData: String, promise: Promise) {
    Log.d(TAG, "🚀 openSendingMoneyRequest")
    
    if (jsonData.isBlank()) {
      return promise.reject("ERROR", "Token vacío")
    }
    
    val activity = getCurrentActivity() as? AppCompatActivity
      ?: return promise.reject("ERROR", "No activity")
    
    try {
      initializeCallback()
      
      UiThreadUtil.runOnUiThread {
        try {
          applyLocale(activity)
          TransactionConfigSdk.openSendingMoneyRequest(activity, jsonData)
          
          Log.d(TAG, "✅ Add Found abierto")
          Handler(Looper.getMainLooper()).postDelayed({
            promise.resolve("OK")
          }, 100)
        } catch (e: Exception) {
          Log.e(TAG, "❌ Error", e)
          promise.reject("ERROR", e.message, e)
        }
      }
    } catch (e: Exception) {
      promise.reject("ERROR", e.message, e)
    }
  }

  @ReactMethod
  fun openMilioSdkQr(jsonData: String, promise: Promise) {
    Log.d(TAG, "🚀 openSendingMoneyRequest")
    
    if (jsonData.isBlank()) {
      return promise.reject("ERROR", "Token vacío")
    }
    
    val activity = getCurrentActivity() as? AppCompatActivity
      ?: return promise.reject("ERROR", "No activity")
    
    try {
      initializeCallback()
      
      UiThreadUtil.runOnUiThread {
        try {
          applyLocale(activity)
          TransactionConfigSdk.openMilioSdkQr(activity, jsonData)
          
          Log.d(TAG, "✅ Add Found abierto")
          Handler(Looper.getMainLooper()).postDelayed({
            promise.resolve("OK")
          }, 100)
        } catch (e: Exception) {
          Log.e(TAG, "❌ Error", e)
          promise.reject("ERROR", e.message, e)
        }
      }
    } catch (e: Exception) {
      promise.reject("ERROR", e.message, e)
    }
  }

  @ReactMethod
  fun setLogoUrl(url: String?, promise: Promise) {
    try {
      TransactionConfigSdk.setLogoUrl(reactApplicationContext, url)
      promise.resolve(null)
    } catch (e: Exception) {
      promise.reject("ERROR", e.message, e)
    }
  }

  @ReactMethod
  fun clearLogoUrl(promise: Promise) {
    try {
      TransactionConfigSdk.clearLogoUrl(reactApplicationContext)
      promise.resolve(null)
    } catch (e: Exception) {
      promise.reject("ERROR", e.message, e)
    }
  }

  @ReactMethod
  fun setLanguage(languageCode: String, promise: Promise) {
    try {
      TransactionConfigSdk.setLanguage(reactApplicationContext, languageCode)
      promise.resolve("OK")
    } catch (e: Exception) {
      promise.reject("ERROR", e.message, e)
    }
  }

  @ReactMethod
  fun setEnvironment(env: String, promise: Promise) {
    try {
      TransactionConfigSdk.setEnvironment(env)
      promise.resolve("OK")
    } catch (e: Exception) {
      promise.reject("ERROR", e.message, e)
    }
  }

  @ReactMethod
  fun setWidthLogo(width: Int, promise: Promise) {
    val activity = getCurrentActivity() as? AppCompatActivity
      ?: return promise.reject("ERROR", "No activity")

    try {
      TransactionConfigSdk.setWidthLogo(activity, width)
      promise.resolve("OK")
    } catch (e: Exception) {
      promise.reject("ERROR", e.message, e)
    }
  }

  @ReactMethod
  fun setFont(fontOption: String, promise: Promise) {
    promise.resolve("No disponible")
  }

  private fun emitEvent(event: String, payload: Any?) {
    UiThreadUtil.runOnUiThread {
      try {
        reactApplicationContext
          .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
          .emit(event, payload)
      } catch (e: Exception) {
        Log.e(TAG, "❌ Error emitiendo evento", e)
      }
    }
  }

  override fun onHostResume() {
    sdkCallback?.let { TransactionConfigSdk.setTransactionCallback(it) }
  }
  
  override fun onHostPause() {}
  override fun onHostDestroy() {
    sdkCallback = null
  }
}

Instanciar y utilizar la clase MilioSdk el cual se comunica MilioSdkModule.

crear el archivo MilioSdk.js en la raíz del proyecto de React Native

import { NativeModules } from 'react-native';

const { MilioSdk } = NativeModules;

if (!MilioSdk) {
  throw new Error(
    'MilioSdkModule no se encuentra disponible. ¿Está correctamente enlazado?',
  );
}

export default MilioSdk;

App.tsx

import React, { useEffect, useState } from 'react';
import {
  SafeAreaView,
  ScrollView,
  View,
  Text,
  StyleSheet,
  TouchableOpacity,
  StatusBar,
  Alert,
  NativeEventEmitter,
  Platform,
} from 'react-native';

import Icon from '@react-native-vector-icons/material-icons';

import MilioSdk from './MilioSdk';

const MilioSdkEmitter = new NativeEventEmitter(MilioSdk);

// ============================================
// MARK: - Types & Interfaces
// ============================================

interface ThemeConfig {
  id: string;
  name: string;
  icon: string;
  primaryColor: string;
  secondaryColor: string;
  btnColorEnabled: string;
  btnColorDisabled: string;
  btnTextColorEnabled: string;
  btnTextColorDisabled: string;
  layoutBackgroundColor: string;
  textViewColor: string;
  inputBorderColor: string;
  textSelectionColor: string;
}

interface EnvironmentConfig {
  id: string;
  name: string;
  icon: string;
  envString: string;
  description: string;
}

interface LanguageConfig {
  id: string;
  code: string;
  name: string;
  flag: string;
}

// ============================================
// MARK: - Configuration Data
// ============================================

const AVAILABLE_THEMES: ThemeConfig[] = [
  {
    id: 'default',
    name: 'Default (Blue)',
    icon: 'palette',
    primaryColor: '#808080',
    secondaryColor: '#242424',
    btnColorEnabled: '#809BFF',
    btnColorDisabled: '#D3D3D3',
    btnTextColorEnabled: '#FFFFFF',
    btnTextColorDisabled: '#888888',
    layoutBackgroundColor: '#ffffff',
    textViewColor: '#242424',
    inputBorderColor: '#808080',
    textSelectionColor: '#809BFF',
  },
  {
    id: 'dark',
    name: 'Dark Mode',
    icon: 'brightness-3',
    primaryColor: '#CCCCCC',
    secondaryColor: '#FFFFFF',
    btnColorEnabled: '#1E1E1E',
    btnColorDisabled: '#3A3A3A',
    btnTextColorEnabled: '#FFFFFF',
    btnTextColorDisabled: '#888888',
    layoutBackgroundColor: '#121212',
    textViewColor: '#FFFFFF',
    inputBorderColor: '#444444',
    textSelectionColor: '#BB86FC',
  },
  {
    id: 'green',
    name: 'Green Nature',
    icon: 'eco',
    primaryColor: '#6B8E23',
    secondaryColor: '#2F4F2F',
    btnColorEnabled: '#32CD32',
    btnColorDisabled: '#90EE90',
    btnTextColorEnabled: '#FFFFFF',
    btnTextColorDisabled: '#808080',
    layoutBackgroundColor: '#F0FFF0',
    textViewColor: '#2F4F2F',
    inputBorderColor: '#6B8E23',
    textSelectionColor: '#32CD32',
  },
  {
    id: 'purple',
    name: 'Purple Dream',
    icon: 'stars',
    primaryColor: '#9370DB',
    secondaryColor: '#4B0082',
    btnColorEnabled: '#8A2BE2',
    btnColorDisabled: '#DDA0DD',
    btnTextColorEnabled: '#FFFFFF',
    btnTextColorDisabled: '#888888',
    layoutBackgroundColor: '#F8F4FF',
    textViewColor: '#4B0082',
    inputBorderColor: '#9370DB',
    textSelectionColor: '#8A2BE2',
  },
  {
    id: 'orange',
    name: 'Sunset Orange',
    icon: 'wb-sunny',
    primaryColor: '#FF8C00',
    secondaryColor: '#8B4513',
    btnColorEnabled: '#FF6347',
    btnColorDisabled: '#FFB6C1',
    btnTextColorEnabled: '#FFFFFF',
    btnTextColorDisabled: '#888888',
    layoutBackgroundColor: '#FFF5EE',
    textViewColor: '#8B4513',
    inputBorderColor: '#FF8C00',
    textSelectionColor: '#FF6347',
  },
];

const AVAILABLE_ENVIRONMENTS: EnvironmentConfig[] = [
  {
    id: 'sandbox',
    name: 'Sandbox',
    icon: 'science',
    envString: 'SANDBOX',
    description: 'Ambiente aislado para testing',
  },
  {
    id: 'prod',
    name: 'Production',
    icon: 'cloud-done',
    envString: 'PROD',
    description: '⚠️ Ambiente de producción',
  },
];

const AVAILABLE_LANGUAGES: LanguageConfig[] = [
  { id: 'en', code: 'en', name: 'English', flag: '🇺🇸' },
  { id: 'es', code: 'es', name: 'Español', flag: '🇪🇸' },
];

// ============================================
// MARK: - Main Component
// ============================================

const App = () => {
  // State
  const [loading, setLoading] = useState(false);
  const [selectedEnvironment, setSelectedEnvironment] = useState('sandbox');
  const [selectedTheme, setSelectedTheme] = useState('default');
  const [selectedLanguage, setSelectedLanguage] = useState('es');
  const [showConfig, setShowConfig] = useState(false);

  // ============================================
  // MARK: - SDK Event Listeners
  // ============================================

  useEffect(() => {
    console.log('📡 Configurando listeners de eventos del SDK');

    const s1 = MilioSdkEmitter.addListener(
      'onTransactionSuccess',
      ({ message, amount }) => {
        console.log('✅ Evento onTransactionSuccess recibido:', {
          message,
          amount,
        });
        Alert.alert('✅ Transacción Exitosa', `Monto: $${amount}\n${message}`);
      },
    );

    const s2 = MilioSdkEmitter.addListener(
      'onTransactionCompleted',
      ({ amount, isAddition }) => {
        console.log('✅ Evento onTransactionCompleted recibido:', {
          amount,
          isAddition,
        });
        Alert.alert(
          '✅ Transacción Completada',
          `Monto: $${amount}\n${isAddition ? 'Suma' : 'Resta'}`,
        );
      },
    );

    const s3 = MilioSdkEmitter.addListener(
      'onTransactionError',
      ({ errorCode, errorMessage }) => {
        console.log('❌ Evento onTransactionError recibido:', {
          errorCode,
          errorMessage,
        });
        Alert.alert(
          '❌ Error en Transacción',
          `Código: ${errorCode}\n${errorMessage}`,
        );
      },
    );

    const s4 = MilioSdkEmitter.addListener('MilioSdkDidClose', () => {
      console.log(
        '🚪 Evento MilioSdkDidClose recibido - SDK cerrado por el usuario',
      );
    });

    return () => {
      console.log('🔌 Limpiando listeners de eventos del SDK');
      s1.remove();
      s2.remove();
      s3.remove();
      s4.remove();
    };
  }, []);

  // ============================================
  // MARK: - Initialization
  // ============================================

  useEffect(() => {
    const bootstrap = async () => {
      try {
        console.log('🚀 Iniciando bootstrap de la aplicación');

        await MilioSdk.initializeSdk();
        console.log('✅ SDK inicializado');

        // Aplicar configuración inicial
        await applyTheme(AVAILABLE_THEMES[0]);
        await MilioSdk.setEnvironment('SANDBOX');
        await MilioSdk.setLanguage('en');
        await MilioSdk.setFont('satoshi');

        // Configurar logo
        const logoUrl =
          'https://ruta del logo de la empresa.png';
        await MilioSdk.setLogoUrl(logoUrl);
        await MilioSdk.setWidthLogo(100);
        console.log('✅ Logo URL configurada:', logoUrl);
        console.log('✅ Bootstrap completado exitosamente');
      } catch (err: any) {
        console.error('❌ Error en bootstrap:', err);
        Alert.alert('Error al iniciar', err.message || String(err));
      }
    };

    bootstrap();
  }, []);

  // ============================================
  // MARK: - Configuration Handlers
  // ============================================

  const applyTheme = async (theme: ThemeConfig) => {
    try {
      await MilioSdk.setThemeColors({
        primaryColor: theme.primaryColor,
        secondaryColor: theme.secondaryColor,
        btnColorEnabled: theme.btnColorEnabled,
        btnColorDisabled: theme.btnColorDisabled,
        btnTextColorEnabled: theme.btnTextColorEnabled,
        btnTextColorDisabled: theme.btnTextColorDisabled,
        layoutBackgroundColor: theme.layoutBackgroundColor,
        textViewColor: theme.textViewColor,
        inputBorderColor: theme.inputBorderColor,
        textSelectionColor: theme.textSelectionColor,
      });
      setSelectedTheme(theme.id);
      console.log('🎨 Tema aplicado:', theme.name);
      Alert.alert('🎨 Tema Aplicado', theme.name);
    } catch (error) {
      console.error('❌ Error aplicando tema:', error);
    }
  };

  const changeEnvironment = async (env: EnvironmentConfig) => {
    try {
      await MilioSdk.setEnvironment(env.envString);
      setSelectedEnvironment(env.id);
      console.log('🌍 Ambiente cambiado a:', env.name);
      Alert.alert('🌍 Ambiente Cambiado', env.name);
    } catch (error) {
      console.error('❌ Error cambiando ambiente:', error);
    }
  };

  const changeLanguage = async (lang: LanguageConfig) => {
    try {
      await MilioSdk.setLanguage(lang.code);
      setSelectedLanguage(lang.code);
      console.log('🌐 Idioma cambiado a:', lang.name);
      Alert.alert('🌐 Idioma Cambiado', lang.name);
    } catch (error) {
      console.error('❌ Error cambiando idioma:', error);
    }
  };

  // ============================================
  // MARK: - Transaction Handlers
  // ============================================

  const openMilioPayManualCard = async () => {
    console.log('🚀 [openMilioPayManualCard] Iniciando...');

    console.log('MODULES ', MilioSdk);

    try {
			//Se requiere el argumento jsonData, este debe contener la información encriptada para la transacción
      const jsonData = "eyJhbGciOiJIU..."
      await MilioSdk.openMilioPayManualCard(jsonData);
      console.log('✅ [openMilioPayManualCard] SDK abierto exitosamente');
    } catch (error) {
      console.error('❌ [openMilioPayManualCard] Error general:', error);
      Alert.alert('Error', 'No se pudo ejecutar la operación');
    }
  };

  const handleTransferenciaInmediata = async () => {
    console.log('🚀 [openMilioPayAddFoundManualCard] Iniciando...');

    console.log('MODULES ', MilioSdk);

    try {
			//Se requiere el argumento jsonData, este debe contener la información encriptada para la transacción
      const jsonData = "eyJhbGciOiJIU..."
      await MilioSdk.openMilioPayAddFoundManualCard(jsonData);
      console.log('✅ [openMilioPayAddFoundManualCard] SDK abierto exitosamente');
    } catch (error) {
      console.error('❌ [openMilioPayAddFoundManualCard] Error general:', error);
      Alert.alert('Error', 'No se pudo ejecutar la operación');
    }
  };

  const handleRecargarBilletera = async () => {
    console.log('🚀 [openMilioPayRechargeWallet] Iniciando...');

    console.log('MODULES ', MilioSdk);

    try {
			//Se requiere el argumento jsonData, este debe contener la información encriptada para la transacción
      const jsonData = "eyJhbGciOiJIU..."
      await MilioSdk.openMilioPayRechargeWallet(jsonData);
      console.log('✅ [openMilioPayRechargeWallet] SDK abierto exitosamente');
    } catch (error) {
      console.error('❌ [openMilioPayRechargeWallet] Error general:', error);
      Alert.alert('Error', 'No se pudo ejecutar la operación');
    }
  };

  const handleAliasConfig = async () => {
    console.log('🚀 [openPaymentAliasConfiguration] Iniciando...');

    console.log('MODULES ', MilioSdk);

    try {
			//Se requiere el argumento jsonData, este debe contener la información encriptada para la  transacción
      const jsonData = "eyJhbGciOiJIU..."
      await MilioSdk.openPaymentAliasConfiguration(jsonData);
      console.log('✅ [openPaymentAliasConfiguration] SDK abierto exitosamente');
    } catch (error) {
      console.error('❌ [openPaymentAliasConfiguration] Error general:', error);
      Alert.alert('Error', 'No se pudo ejecutar la operación');
    }
  };

  const handleCrossBorderRequest = async () => {
    console.log('🚀 [openCrossBorderRequest] Iniciando...');

    console.log('MODULES ', MilioSdk);

    try {
			//Se requiere el argumento jsonData, este debe contener la información encriptada para la transacción
      const jsonData = "eyJhbGciOiJIU..."
      await MilioSdk.openCrossBorderRequest(jsonData);
      console.log('✅ [openCrossBorderRequest] SDK abierto exitosamente');
    } catch (error) {
      console.error('❌ [openCrossBorderRequest] Error general:', error);
      Alert.alert('Error', 'No se pudo ejecutar la operación');
    }
  };

  const handleSendingMoneyRequest = async () => {
    console.log('🚀 [openSendingMoneyRequest] Iniciando...');

    console.log('MODULES ', MilioSdk);

    try {
			//Se requiere el argumento jsonData, este debe contener la información encriptada para la transacción
      const jsonData = "eyJhbGciOiJIU..."
      await MilioSdk.openSendingMoneyRequest(jsonData);
      console.log('✅ [openSendingMoneyRequest] SDK abierto exitosamente');
    } catch (error) {
      console.error('❌ [openSendingMoneyRequest] Error general:', error);
      Alert.alert('Error', 'No se pudo ejecutar la operación');
    }
  };

  // ============================================
  // MARK: - Render
  // ============================================

  return (
    <>
      <SafeAreaView style={{ flex: 0, backgroundColor: '#0048ff' }} />
      <SafeAreaView style={styles.container}>
        <StatusBar backgroundColor="#f8f9fa" barStyle="dark-content" />

        {/* Header */}
        <View style={styles.headerBackground}>
          <View style={styles.header}>
            <Text style={styles.greeting}>Hola, Daniel</Text>
            <TouchableOpacity onPress={() => setShowConfig(!showConfig)}>
              <Icon name="settings" size={28} color="#ffffff" />
            </TouchableOpacity>
          </View>
          <Text style={styles.balanceText}>Saldo</Text>
          <Text style={styles.balance}>$ 7.979.010,00</Text>
          <View style={styles.card}>
            <Text style={styles.cardText}>VISA •••• 5850</Text>
            <Text style={styles.cardBrand}>milio</Text>
          </View>
        </View>

        {/* Scroll Content */}
        <ScrollView contentContainerStyle={styles.scrollContent}>
          {/* Configuration Panel */}
          {showConfig && (
            <View style={styles.configPanel}>
              {/* Environments */}
              <Text style={styles.configSectionTitle}>🌍 Ambiente</Text>
              {AVAILABLE_ENVIRONMENTS.map(env => (
                <TouchableOpacity
                  key={env.id}
                  style={[
                    styles.configItem,
                    selectedEnvironment === env.id && styles.configItemSelected,
                  ]}
                  onPress={() => changeEnvironment(env)}
                >
                  <Icon name={env.icon} size={20} color="#666" />
                  <Text style={styles.configItemText}>{env.name}</Text>
                  {selectedEnvironment === env.id && (
                    <Icon name="check-circle" size={20} color="#0048ff" />
                  )}
                </TouchableOpacity>
              ))}

              {/* Themes */}
              <Text style={[styles.configSectionTitle, { marginTop: 20 }]}>
                🎨 Tema
              </Text>
              {AVAILABLE_THEMES.map(theme => (
                <TouchableOpacity
                  key={theme.id}
                  style={[
                    styles.configItem,
                    selectedTheme === theme.id && styles.configItemSelected,
                  ]}
                  onPress={() => applyTheme(theme)}
                >
                  <Icon name={theme.icon} size={20} color="#666" />
                  <Text style={styles.configItemText}>{theme.name}</Text>
                  {selectedTheme === theme.id && (
                    <Icon name="check-circle" size={20} color="#0048ff" />
                  )}
                </TouchableOpacity>
              ))}

              {/* Languages */}
              <Text style={[styles.configSectionTitle, { marginTop: 20 }]}>
                🌐 Idioma
              </Text>
              {AVAILABLE_LANGUAGES.map(lang => (
                <TouchableOpacity
                  key={lang.id}
                  style={[
                    styles.configItem,
                    selectedLanguage === lang.code && styles.configItemSelected,
                  ]}
                  onPress={() => changeLanguage(lang)}
                >
                  <Text style={{ fontSize: 20 }}>{lang.flag}</Text>
                  <Text style={styles.configItemText}>{lang.name}</Text>
                  {selectedLanguage === lang.code && (
                    <Icon name="check-circle" size={20} color="#0048ff" />
                  )}
                </TouchableOpacity>
              ))}
            </View>
          )}

          {/* Transaction Actions */}
          <View style={styles.actions}>
            <TouchableOpacity
              style={styles.actionButton}
              onPress={openMilioPayManualCard}
            >
              <Text style={styles.actionText}>Transferencia manual</Text>
              <Icon name="credit-card" size={24} color="#666" />
            </TouchableOpacity>

            <TouchableOpacity
              style={styles.actionButton}
              onPress={handleTransferenciaInmediata}
            >
              <Text style={styles.actionText}>Transferencia inmediata</Text>
              <Icon name="flash-on" size={24} color="#666" />
            </TouchableOpacity>

            <TouchableOpacity
              style={styles.actionButton}
              onPress={handleRecargarBilletera}
            >
              <Text style={styles.actionText}>Recargar mi billetera</Text>
              <Icon name="account-balance-wallet" size={24} color="#666" />
            </TouchableOpacity>

            <TouchableOpacity
              style={styles.actionButton}
              onPress={handleAliasConfig}
            >
              <Text style={styles.actionText}>Tokenización de tarjetas</Text>
              <Icon name="credit-card" size={24} color="#666" />
            </TouchableOpacity>

            <TouchableOpacity
              style={styles.actionButton}
              onPress={handleCrossBorderRequest}
            >
              <Text style={styles.actionText}>Transferencia global</Text>
              <Icon name="public" size={24} color="#666" />
            </TouchableOpacity>

            <Text style={styles.sectionDivider}>Envío de Dinero</Text>

            <TouchableOpacity
              style={styles.actionButton}
              onPress={handleSendingMoneyRequest}
            >
              <Text style={styles.actionText}>Envío de dinero</Text>
              <Icon name="send" size={24} color="#666" />
            </TouchableOpacity>
          </View>

          {/* Transactions Section */}
          <Text style={styles.sectionTitle}>Transacciones</Text>
          <View style={styles.transactionsPlaceholder}>
            <View style={styles.skeleton} />
            <View style={styles.skeleton} />
          </View>
        </ScrollView>
      </SafeAreaView>
    </>
  );
};

// ============================================
// MARK: - Styles
// ============================================

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f8f9fa',
  },
  headerBackground: {
    backgroundColor: '#0048ff',
    padding: 20,
    paddingTop: 20,
    paddingBottom: 1,
    borderBottomLeftRadius: 25,
    borderBottomRightRadius: 25,
  },
  header: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
  },
  scrollContent: {
    paddingBottom: 40,
  },
  greeting: {
    fontSize: 22,
    fontWeight: 'bold',
    color: '#ffffff',
    fontFamily: Platform.OS === 'ios' ? 'System' : 'Roboto',
  },
  balanceText: {
    marginTop: 10,
    fontSize: 16,
    color: '#ffffff',
    fontFamily: Platform.OS === 'ios' ? 'System' : 'Roboto',
  },
  balance: {
    fontSize: 30,
    fontWeight: 'bold',
    color: '#ffffff',
    paddingBottom: 20,
    fontFamily: Platform.OS === 'ios' ? 'System' : 'Roboto',
  },
  card: {
    backgroundColor: '#000',
    borderRadius: 10,
    borderBottomLeftRadius: 0,
    borderBottomRightRadius: 0,
    padding: 25,
    paddingTop: 30,
    paddingBottom: 50,
    marginHorizontal: 5,
    marginTop: 10,
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
  cardText: {
    fontSize: 18,
    color: '#fff',
    fontFamily: Platform.OS === 'ios' ? 'System' : 'Roboto',
  },
  cardBrand: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#fff',
    fontFamily: Platform.OS === 'ios' ? 'System' : 'Roboto',
  },
  configPanel: {
    backgroundColor: '#fff',
    margin: 20,
    padding: 20,
    borderRadius: 10,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 3,
    elevation: 3,
  },
  configSectionTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#333',
    marginBottom: 10,
  },
  configItem: {
    flexDirection: 'row',
    alignItems: 'center',
    padding: 12,
    borderRadius: 8,
    marginBottom: 8,
    backgroundColor: '#f8f9fa',
  },
  configItemSelected: {
    backgroundColor: '#e3f2fd',
    borderWidth: 1,
    borderColor: '#0048ff',
  },
  configItemText: {
    flex: 1,
    marginLeft: 12,
    fontSize: 14,
    color: '#333',
  },
  actions: {
    marginTop: 20,
    paddingHorizontal: 20,
  },
  actionButton: {
    backgroundColor: '#fff',
    borderRadius: 10,
    padding: 30,
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 10,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 3,
    elevation: 3,
  },
  actionText: {
    fontSize: 16,
    fontWeight: '500',
    color: '#333',
    fontFamily: Platform.OS === 'ios' ? 'System' : 'Roboto',
  },
  sectionDivider: {
    fontSize: 14,
    fontWeight: 'bold',
    color: '#999',
    textAlign: 'center',
    marginVertical: 15,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    marginVertical: 10,
    paddingHorizontal: 20,
    fontFamily: Platform.OS === 'ios' ? 'System' : 'Roboto',
  },
  transactionsPlaceholder: {
    backgroundColor: '#f0f0f0',
    padding: 20,
    borderRadius: 10,
    marginHorizontal: 20,
  },
  skeleton: {
    height: 20,
    backgroundColor: '#ddd',
    borderRadius: 5,
    marginBottom: 10,
  },
});

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:

PropiedadDescripción
primaryColorColor primario titulos.
secondaryColorColor para los botones secundario
btnColorEnabledColor del botón cuando está habilitado.
btnColorDisabledColor del botón cuando está deshabilitado.
btnTextColorEnabledColor del texto cuando el botón está habilitado.
btnTextColorDisabledColor del texto cuando el botón está deshabilitado.
layoutBackgroundColorColor de fondo del SDK.
textViewColorColor del texto en la interfaz del SDK.
inputBorderColorColor de los bordes
textSelectionColorColor 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');
  }
};

Representación 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:

DensidadFactorAncho (px)Alto (px)
mdpi544200
hdpi1.5×544300
xhdpi1088400
xxhdpi1632600
xxxhdpi2176800
 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));

Configurar Idioma

await MilioSdk.setLanguage('es');

Parámetros:

  • languageCode (string): Código ISO 639-1 del idioma

Idiomas soportados:

  • es - Español
  • en - Inglés

Configurar Ancho del Logo

await MilioSdk.setWidthLogo(130);

Parámetros:

  • width (number): Ancho del logo en dp (recomendado: 80-200)

Nota: La altura se ajusta automáticamente manteniendo la proporción.


Limpiar Logo Personalizado

await MilioSdk.clearLogoUrl();

Restaura el logo predeterminado del SDK.



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.
  1. 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.
  1. 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.
  1. 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.
  1. Tokenización de Tarjetas
Proceso que sustituye los datos reales de la tarjeta (número, CVV, etc.) por un token único y seguro, facilitando la creación y uso de medios de pago sin exponer información sensible.

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.