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.

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

Fase 1: Integrar el SDK en React Native

Agregar JitPack en 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.

ext {
    buildToolsVersion = "34.0.0"
    minSdkVersion = 24
    compileSdkVersion = 33
    targetSdkVersion = 33
}

Abre el archivo android/build.gradle y agrega JitPack en la sección repositories:

allprojects {
    repositories {
        google()
        mavenCentral()
        maven { url 'https://jitpack.io' }
    }
}

google() es obligatorio para AndroidX y Google Services.
mavenCentral() es la opción principal para bibliotecas de terceros.
jitpack.io permite usar dependencias directamente desde GitHub.

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


✅ 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.0")
}

📌 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 transferencias con QR , manuales, inmediatas y recargas de billetera.

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. Vincular el módulo nativo en MainApplication.java

Agregar android/settings.gradle

Para utilizar el SDK de Milio en un proyecto React Native, es necesario configurarlo en settings.gradle y en el sistema de dependencias de Android.

Para poder acceder a los artefactos hospedados en el repositorio privado de Milio, asegúrate de configurar correctamente tu settings.gradle con autenticación vía token

⚠️ Nota: authToken debe ser una variable que contenga el token de acceso provisto por Milio. Este token debe mantenerse privado y nunca ser versionado en control de código (por ejemplo, Git).

include ':sdkmilio'
project(':sdkmilio').projectDir = new File(rootProject.projectDir, '../node_modules/milio-sdk/android')

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

        }
    }
}

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

import com.tuempresa.MilioSdkPackage; // Importa el paquete

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()
    }
}

Abre android/app/src/main/java/com/tuempresa/MainApplication.java y agrega:


@Override
protected List<ReactPackage> getPackages() {
    return Arrays.<ReactPackage>asList(
        new MainReactPackage(),
        new MilioSdkPackage() // Agrega el paquete de Milio SDK
    );
}

MainActivity es la actividad principal que conecta React Native con el código nativo de Android, la inicialización del SDK en este punto garantiza que:
✅ El SDK esté listo antes de que cualquier componente de React Native lo use.
✅ Se eviten errores de falta de inicialización cuando se intente invocar el SDK.
✅ Se mantenga una única instancia del SDK durante la ejecución de la app.

Abre android/app/src/main/java/com/tuempresa/MainActivity.java y agrega TransactionConfigSdk.initialize(this)

class MainActivity : ReactActivity() { 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        TransactionConfigSdk.initialize(this) // inicializacion del sdk
    }
}

📌 Implementación en Android (Kotlin)


📍 Código de MilioSdkModule.kt

package com.prueba40

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.bridge.UiThreadUtil
import com.facebook.react.modules.core.DeviceEventManagerModule
import com.milio.sdkmilio.TransactionCallback
import com.milio.sdkmilio.TransactionConfigSdk

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

  companion object { private const val TAG = "MilioSdkModule" }

  private var authToken: String? = null
  private var sdkCallback: TransactionCallback? = null

  init {
    reactContext.addLifecycleEventListener(this)
  }

  override fun getName(): String = "MilioSdk"

  // ---------- Obligatorios para NativeEventEmitter ----------
  @ReactMethod fun addListener(eventName: String) { /* no-op */ }
  @ReactMethod fun removeListeners(count: Int) { /* no-op */ }

  // ---------- Inicialización del SDK y callback persistente ----------
  @ReactMethod
  fun initializeSdk(token: String, promise: Promise) {
    authToken = token
    val activity = currentActivity as? AppCompatActivity
      ?: return promise.reject("NO_ACTIVITY", "No hay Activity activa")
 

    // Crea y guarda el callback una única vez
    if (sdkCallback == null) {
      sdkCallback = object : TransactionCallback {
        override fun onTransactionSuccess(amount: Double, message: String) {
          val m = Arguments.createMap().apply {
            putDouble("amount", amount); putString("message", message)
          }
          emitOnMain("onTransactionSuccess", m)
        }
        override fun didUpdateBalance(newBalance: Double) {
          emitOnMain("MilioSdkDidUpdateBalance", newBalance)
        }
        override fun onTransactionCompleted(amount: Double, isAddition: Boolean) {
          val m = Arguments.createMap().apply {
            putDouble("amount", amount); putBoolean("isAddition", isAddition)
          }
          emitOnMain("onTransactionCompleted", m)
        }
        override fun onTransactionError(errorCode: Int, errorMessage: String) {
          val m = Arguments.createMap().apply {
            putInt("errorCode", errorCode); putString("errorMessage", errorMessage)
          }
          emitOnMain("onTransactionError", m)
        }
        override fun didCloseSdk() {
          emitOnMain("MilioSdkDidClose", null)
        }
      }
    }

    // Registra SIEMPRE el callback guardado
    TransactionConfigSdk.setTransactionCallback(sdkCallback!!)

    Log.d(TAG, "SDK Inicializado con token")
    promise.resolve("SDK Inicializado correctamente")
  }

  // ---------- Theming ----------
  @ReactMethod
  fun setThemeColors(config: ReadableMap, promise: Promise) {
    val activity = currentActivity ?: return promise.reject("ERROR", "Actividad nula o incompatible")
    try {
      val primaryColor = config.getString("primaryColor") ?: "#3C64A7"
      val secondaryColor = config.getString("secondaryColor") ?: "#0e6655"
      val btnColorEnabled = config.getString("btnColorEnabled") ?: "#0e6655"
      val btnColorDisabled = config.getString("btnColorDisabled") ?: "#D3D3D3"
      val btnTextColorEnabled = config.getString("btnTextColorEnabled") ?: "#ffffff"
      val btnTextColorDisabled = config.getString("btnTextColorDisabled") ?: "#888888"
      val layoutBackgroundColor = config.getString("layoutBackgroundColor") ?: "#a2d9ce"
      val textViewColor = config.getString("textViewColor") ?: "#0b5345"
      val inputBorderColor = config.getString("inputBorderColor") ?: "#8C8C8C"
      val textSelectionColor = config.getString("textSelectionColor") ?: "#3b5998"

      TransactionConfigSdk.setThemeColors(
        context = activity,
        primaryColor = primaryColor,
        secondaryColor = secondaryColor,
        btnColorEnabled = btnColorEnabled,
        btnColorDisabled = btnColorDisabled,
        btnTextColorEnabled = btnTextColorEnabled,
        btnTextColorDisabled = btnTextColorDisabled,
        layoutBackgroundColor = layoutBackgroundColor,
        textViewColor = textViewColor,
        inputBorderColor = inputBorderColor,
        textSelectionColor = textSelectionColor
      )
      Log.d(TAG, "Colores aplicados correctamente")
      promise.resolve("Colores aplicados correctamente")
    } catch (e: Exception) {
      Log.e(TAG, "Error al aplicar colores", e)
      promise.reject("ERROR", "Error al aplicar colores: ${e.localizedMessage}")
    }
  }

  // ---------- Abrir flujos ----------
  @ReactMethod
  fun openMilioSdkQr(jsonData: String, promise: Promise) {
    val activity = currentActivity as? AppCompatActivity
      ?: return promise.reject("ERROR", "Actividad nula o incompatible")
    val token = authToken ?: return promise.reject("ERROR", "Token no disponible")
    try {
      // initialize() ya fue llamado en initializeSdk, pero no estorba si lo reusas
      TransactionConfigSdk.openMilioSdkQr(activity, jsonData)
      Log.d(TAG, "Escáner QR abierto correctamente")
      promise.resolve("Escáner QR abierto correctamente")
    } catch (e: Exception) {
      Log.e(TAG, "Error al abrir el escáner QR", e)
      promise.reject("ERROR", "Error al abrir el escáner QR: ${e.localizedMessage}")
    }
  }

  @ReactMethod
  fun openMilioPayManualCard(jsonData: String, promise: Promise) {
    val activity = currentActivity as? AppCompatActivity
      ?: return promise.reject("ERROR", "Actividad nula o incompatible")
    val token = authToken ?: return promise.reject("ERROR", "Token no disponible")
    try {
      TransactionConfigSdk.openMilioPayManualCard(activity, token, jsonData)
      Log.d(TAG, "Transacción iniciada con éxito")
      promise.resolve("Transacción iniciada con éxito")
    } catch (e: Exception) {
      Log.e(TAG, "Error en la transacción", e)
      promise.reject("ERROR", "Error en la transacción: ${e.localizedMessage}")
    }
  }

  @ReactMethod
  fun openMilioPayAddFoundManualCard(jsonData: String, promise: Promise) {
    val activity = currentActivity as? AppCompatActivity
      ?: return promise.reject("ERROR", "Actividad nula o incompatible")
    val token = authToken ?: return promise.reject("ERROR", "Token no disponible")
    try {
      TransactionConfigSdk.openMilioPayAddFoundManualCard(activity, token, jsonData)
      Handler(Looper.getMainLooper()).postDelayed(
        { promise.resolve("Pago de fondos iniciado con éxito") },
        1000
      )
    } catch (e: Exception) {
      Log.e(TAG, "Error al iniciar el pago de fondos", e)
      promise.reject("ERROR", "Error al iniciar el pago de fondos: ${e.localizedMessage}")
    }
  }

  @ReactMethod
  fun openMilioPayRechargeWallet(jsonData: String, promise: Promise) {
    val activity = currentActivity as? AppCompatActivity
      ?: return promise.reject("ERROR", "Actividad nula o incompatible")
    val token = authToken ?: return promise.reject("ERROR", "Token no disponible")
    try {
      TransactionConfigSdk.openMilioPayRechargeWallet(
        activity,
        activity.supportFragmentManager,
        token,
        jsonData
      )
      Log.d(TAG, "Recarga de billetera iniciada correctamente")
      promise.resolve("Recarga de billetera iniciada correctamente")
    } catch (e: Exception) {
      Log.e(TAG, "Error al iniciar recarga de billetera", e)
      promise.reject("ERROR", "Error al iniciar recarga de billetera: ${e.localizedMessage}")
    }
  }

  @ReactMethod
  fun openPaymentAliasConfiguration(jsonData: String?, promise: Promise) {
    val activity = currentActivity as? AppCompatActivity
      ?: return promise.reject("ERROR", "Actividad nula o incompatible")
    val token = authToken ?: return promise.reject("ERROR", "Token no disponible")
    try {
      TransactionConfigSdk.openPaymentAliasConfigurationSheet(activity, token, jsonData)
      Log.d(TAG, "Ventana de tokenización abierta correctamente")
      Handler(Looper.getMainLooper()).post {
        promise.resolve("Tokenización abierta correctamente")
      }
    } catch (e: Exception) {
      Log.e(TAG, "Error al abrir la ventana de tokenización", e)
      promise.reject("ERROR", "Error al abrir la tokenización: ${e.localizedMessage}")
    }
  }

  @ReactMethod
  fun openCrossBorderRequest(jsonData: String?, promise: Promise) {
    val activity = currentActivity as? AppCompatActivity
      ?: return promise.reject("ERROR", "Actividad nula o incompatible")
    val token = authToken ?: return promise.reject("ERROR", "Token no disponible")
    try {
      TransactionConfigSdk.openCrossBorderRequest(activity, token, jsonData)
      Log.d(TAG, "Ventana de cross border request abierta correctamente")
      Handler(Looper.getMainLooper()).post {
        promise.resolve("Cross Border Request abierta correctamente")
      }
    } catch (e: Exception) {
      Log.e(TAG, "Error al abrir la ventana de cross border request", e)
      promise.reject("ERROR", "Error al abrir la Cross Border Request: ${e.localizedMessage}")
    }
  }

  // ---------- Logo helpers ----------
  @ReactMethod
  fun setLogoUrl(url: String?, promise: Promise) {
    try {
      TransactionConfigSdk.setLogoUrl(reactApplicationContext, url)
      Log.d(TAG, "Logo URL guardada vía RN: $url")
      promise.resolve(null)
    } catch (e: Exception) {
      Log.e(TAG, "Error guardando logo URL", e)
      promise.reject("SET_LOGO_URL_ERROR", "No se pudo guardar el logo URL", e)
    }
  }

  @ReactMethod
  fun clearLogoUrl(promise: Promise) {
    try {
      TransactionConfigSdk.clearLogoUrl(reactApplicationContext)
      Log.d(TAG, "Logo URL delete cache vía RN")
      promise.resolve(null)
    } catch (e: Exception) {
      Log.e(TAG, "Error limpiando logo URL", e)
      promise.reject("SET_LOGO_URL_ERROR", "No se pudo limpiar el logo URL", e)
    }
  }

  // ---------- Emisores ----------
  private fun emitOnMain(event: String, payload: Any?) {
    UiThreadUtil.runOnUiThread {
      reactApplicationContext
        .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
        .emit(event, payload)
    }
  }

  private fun sendSuccessToReactNative(amount: Double, message: String) {
    val m = Arguments.createMap().apply {
      putDouble("amount", amount); putString("message", message)
    }
    emitOnMain("onTransactionSuccess", m)
  }

  private fun sendSuccessToReactNative(message: String) {
    val m = Arguments.createMap().apply { putString("message", message) }
    emitOnMain("onTransactionSuccess", m)
  }

  private fun sendErrorToReactNative(errorCode: Int, errorMessage: String) {
    val m = Arguments.createMap().apply {
      putInt("errorCode", errorCode); putString("errorMessage", errorMessage)
    }
    emitOnMain("onTransactionError", m)
  }

  // ---------- Lifecycle ----------
  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

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.js

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 initializeSdkAndExecute = async (execute: () => Promise<void>) => {
    try {
      await MilioSdk.initializeSdk("eyJhbGciOiJIUzI1NiIsInR..");
      await execute();
    } catch (error) {
      console.error("Error en SDK o ejecución:", error);
    }
  };

 


  const handleRecargarManual= async () => {
    await initializeSdkAndExecute(async () => {
      var token = "eyJhbGciOiJIUzI1NiIsInR5cC..."
      try {
        await MilioSdk.openMilioPayManualCard(token);
      } catch (err) {
        console.error("Error en openMilioPayManualCard:", err);
      }
    });
  };


  const handleTransferenciaInmediata= async () => {
    await initializeSdkAndExecute(async () => {
      var token = "eyJhbGciOiJIUzI1NiIsInR5c.."
      try {
        await MilioSdk.openMilioPayAddFoundManualCard(token);
      } catch (err) {
        console.error("Error en openMilioPayManualCard:", err);
      }
    });
  };


   const handleRecargarBilletera = async () => {
    await initializeSdkAndExecute(async () => {
      var token = "eyJhbGciOiJIUzI1NiIsInR5cCI.."
      try {
        await MilioSdk.openMilioPayRechargeWallet(token);
      } catch (err) {
        console.error("Error en OpenMilioPayRechargeWallet:", err);
      }
    });
  };

 const handleAliasConfig = () => {
    const aliasInputData = "eyJhbGciOiJIUzI1NiIsInR5cCI6..."
    initializeSdkAndExecute(async () => {
      await MilioSdk.openPaymentAliasConfiguration(aliasInputData);
    });
  };


  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>

        <TouchableOpacity style={styles.actionButton} onPress={handleAliasConfig}>
          <Text style={styles.actionText}>Tokenización de tarjetas</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:

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');
  }
};

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:

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));

📌 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.

🚀 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 la información permanezca protegida y cumpla con los estándares de seguridad exigidos.

Version 0.4.0

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

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.

  1. Levantar el sdk con la opción de tokenización

Si ya tienes una implementación del sdk, por favor actualizarse a esta nueva versión para poder levantar el formulario de tokenización.

Asegúrate de tener en tu módulo de Android la dependencia correcta:
build.gradle (Module: app)

dependencies {
     implementation "org.bitbucket.miliopay:sdk-mobile-android:v0.4.0"
} 

Vista completa de las dependencias.

dependencies {
   // The version of react-native is set by the React Native Gradle Plugin
   implementation("com.facebook.react:react-android")
   implementation("org.bitbucket.miliopay:sdk-mobile-android:v0.4.0")


   if (hermesEnabled.toBoolean()) {
       implementation("com.facebook.react:hermes-android")
   } else {
       implementation jscFlavor
   }
}


  1. Actualizar MilioSdkModule.kt

En tu carpeta de módulos nativos de Android (ej. android/app/…/tuapp/milio/), localiza MilioSdkModule.kt. Asegúrate de agregar la función openPaymentAliasConfiguration.

@ReactMethod
   fun openPaymentAliasConfiguration(jsonData: String?, promise: Promise) {
       val activity = currentActivity as? AppCompatActivity
       if (activity == null) {
           promise.reject("ERROR", "Actividad nula o incompatible")
           Log.e(TAG, "Activity is null or not AppCompatActivity")
           return
       }
       if (authToken.isNullOrEmpty()) {
           promise.reject("ERROR", "Token no disponible")
           Log.e(TAG, "authToken is null or empty")
           return
       }
       try {
           TransactionConfigSdk.openPaymentAliasConfigurationSheet(
               activity,
               authToken!!,
               jsonData
           )
           Log.d(TAG, "Ventana de tokenización abierta correctamente")
           // Opcional: si quieres retrasar un poco antes de resolver
           Handler(Looper.getMainLooper()).post {
               promise.resolve("Tokenización abierta correctamente")
           }
       } catch (e: Exception) {
           Log.e(TAG, "Error al abrir la ventana de tokenización", e)
           promise.reject("ERROR", "Error al abrir la tokenización: ${e.localizedMessage}")
       }
   }

Agregar las funciones de emisión sendSuccessToReactNative 2 parámetros , sendSuccessToReactNative 1 parámetro.

agregar estados dos funciones recargadas.

 private fun sendSuccessToReactNative(amount: Double, message: String) {
        val params =
                Arguments.createMap().apply {
                    putDouble("amount", amount)
                    putString("message", message)
                }
        reactApplicationContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
                .emit("onTransactionSuccess", params)
    }

    private fun sendSuccessToReactNative(message: String) {
        val params = Arguments.createMap().apply { putString("message", message) }
        reactApplicationContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
                .emit("onTransactionSuccess", params)
    }

Reemplazar la función initializeSdk

    @ReactMethod
    fun initializeSdk(token: String, promise: Promise) {
        authToken = token
        Log.d(TAG, "SDK Inicializado con token: $authToken")
        promise.resolve("SDK Inicializado correctamente")
        TransactionConfigSdk.setTransactionCallback(
                object : TransactionCallback {
                    override fun onTransactionSuccess(amount: Double, message: String) {
                        sendSuccessToReactNative(amount, message)
                    }
                    override fun didUpdateBalance(newBalance: Double) {
                        reactApplicationContext
                                .getJSModule(
                                        DeviceEventManagerModule.RCTDeviceEventEmitter::class.java
                                )
                                .emit("MilioSdkDidUpdateBalance", newBalance)
                    }
                    override fun onTransactionCompleted(amount: Double, isAddition: Boolean) {
                        sendSuccessToReactNative(amount, "onTransactionCompleted")
                    }
                    override fun onTransactionError(errorCode: Int, errorMessage: String) { 
                        sendErrorToReactNative(
                                errorCode,
                                errorMessage
                        )  
                    }
                    override fun didCloseSdk() { 
                        reactApplicationContext
                                .getJSModule(
                                        DeviceEventManagerModule.RCTDeviceEventEmitter::class.java
                                )
                                .emit("MilioSdkDidClose", null)
                    }
                }
        )
    }


  1. 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);
   });
 };


  1. 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>


Version 0.4.2

Documentación de Cross Border .

Este documento ofrece una guía detallada y secuencial para instalar e integrar el flujo de cross border.


  1. 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

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.
  • 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.

  1. 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.

Asegúrate de tener en tu módulo de Android la dependencia correcta:

En tu **android/app/build.gradle:**
dependencies {
     implementation "org.bitbucket.miliopay:sdk-mobile-android:v0.4.2"
}

Codigo completo

dependencies {
   // The version of react-native is set by the React Native Gradle Plugin
   implementation("com.facebook.react:react-android")
   implementation("org.bitbucket.miliopay:sdk-mobile-android:v0.4.2")


   if (hermesEnabled.toBoolean()) {
       implementation("com.facebook.react:hermes-android")
   } else {
       implementation jscFlavor
   }
}


  1. Actualizar MilioSdkModule.kt

En tu carpeta de módulos nativos de Android (ej. android/app/…/tuapp/milio/), localiza MilioSdkModule.kt. Asegúrate de agregar la función openCrossBorderRequest.

@ReactMethod
   fun openCrossBorderRequest(jsonData: String?, promise: Promise) {
       val activity = currentActivity as? AppCompatActivity
       if (activity == null) {
           promise.reject("ERROR", "Actividad nula o incompatible")
           Log.e(TAG, "Activity is null or not AppCompatActivity")
           return
       }
       if (authToken.isNullOrEmpty()) {
           promise.reject("ERROR", "Token no disponible")
           Log.e(TAG, "authToken is null or empty")
           return
       }
       try {
           TransactionConfigSdk.openCrossBorderRequest(
               activity,
               authToken!!,
               jsonData
           )
           Log.d(TAG, "Ventana de cross border request abierta correctamente")
           // Opcional: si quieres retrasar un poco antes de resolver
           Handler(Looper.getMainLooper()).post {
               promise.resolve("Cross Border Request abierta correctamente")
           }
       } catch (e: Exception) {
           Log.e(TAG, "Error al abrir la ventana de cross border request", e)
           promise.reject("ERROR", "Error al abrir la Cross Border Request: ${e.localizedMessage}")
       }
   }

Agregar las funciones de emisión sendSuccessToReactNative 2 parámetros , sendSuccessToReactNative 1 parámetro.

 private fun sendSuccessToReactNative(amount: Double, message: String) {
        val params =
                Arguments.createMap().apply {
                    putDouble("amount", amount)
                    putString("message", message)
                }
        reactApplicationContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
                .emit("onTransactionSuccess", params)
    }

    private fun sendSuccessToReactNative(message: String) {
        val params = Arguments.createMap().apply { putString("message", message) }
        reactApplicationContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
                .emit("onTransactionSuccess", params)
    }

Reemplazar la función initializeSdk

  @ReactMethod
    fun initializeSdk(token: String, promise: Promise) {
        authToken = token
        Log.d(TAG, "SDK Inicializado con token: $authToken")
        promise.resolve("SDK Inicializado correctamente")
        TransactionConfigSdk.setTransactionCallback(
                object : TransactionCallback {
                    override fun onTransactionSuccess(amount: Double, message: String) {
                        sendSuccessToReactNative(amount, message)
                    }
                    override fun didUpdateBalance(newBalance: Double) {
                        reactApplicationContext
                                .getJSModule(
                                        DeviceEventManagerModule.RCTDeviceEventEmitter::class.java
                                )
                                .emit("MilioSdkDidUpdateBalance", newBalance)
                    }
                    override fun onTransactionCompleted(amount: Double, isAddition: Boolean) {
                        sendSuccessToReactNative(amount, "onTransactionCompleted")
                    }
                    override fun onTransactionError(errorCode: Int, errorMessage: String) { 
                        sendErrorToReactNative(
                                errorCode,
                                errorMessage
                        )  
                    }
                    override fun didCloseSdk() { 
                        reactApplicationContext
                                .getJSModule(
                                        DeviceEventManagerModule.RCTDeviceEventEmitter::class.java
                                )
                                .emit("MilioSdkDidClose", null)
                    }
                }
        )
    }


  1. 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);
   });
 };

  1. 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>