Aprende a usar Prometeo desde tu móvil

Desde Prometeo buscamos ayudar a las empresas a mejorar su escalabilidad gracias a la automatización y optimización de procesos bancarios.

Aún con los excelentes resultados que hemos logrado, consideramos que se puede hacer mucho más a la hora de enseñar los beneficios del Open Banking.

En este artículo queremos enseñarte cómo crear un chatbot con el que cualquier usuario puede conocer los beneficios de Prometeo.

¿Por qué un Chatbot?
Las dos razones principales son:

  • Es sencillo de implementar, hacer, mantener y compartir.
  • Es simple de usar, ya que las interfaces e interacciones son bien conocidas por la gran mayoría de los usuarios. Lo cual es perfecto para las personas que no tengan un background en el mundo de la tecnología.

En resumen, es fácil y nuestro trabajo en esta guía será mostrarte todo lo que se puede hacer con un chatbot de Prometeo, además de lo sencillo que puede ser desarrollarlo hoy mismo.

Para que te des una idea del alcance de lo aprenderás acá, te dejamos algunos proyectos que puedes realizar después de leer este artículo:

  • Hacer que el bot consulte los movimientos automáticamente cada mes y te notifique los gastos e ingresos totales.
  • Hacer que el bot haga una transferencia mensual constante, como el pago de un servicio.
  • Hacer que el bot consulte tu cuenta cada ciertos días y que te notifique solo si has gastado de más, según tu consideración.
  • Integrar un login con OTP.
  • Agregar consulta de tarjetas de crédito y sus movimientos, y hacer que te notifique las fechas de corte de las tarjetas.

Nota: Se decidió usar Telegram porque no sólo nos brinda seguridad de nuestros datos, sino porque también es muy fácil de usar e implementar para todos.

Antes de empezar:

Si eres nuevo en el campo de la programación o te gustaría conocer un poco más los conceptos básicos de nuestra API, te recomendamos que leas el siguiente artículo: ¿Cómo me puedo conectar a las APIs de Prometeo?.

Al igual que el artículo anterior, esta guía está desarrollada en Python, pero puedes implementar la lógica en el lenguaje de programación que más te guste.

Objetivos a cumplir:

  • Crear un chatbot en Telegram que use la API de prometeo.
  • Obtener información de nuestras cuentas.

Para empezar, creamos un entorno virtual en el cual vamos a instalar los siguientes paquetes:

python -m pip install requests python-telegram-bot --pre

Luego vamos a Telegram y en el buscador escribimos “@BotFather” e iniciamos una conversación con el bot que encontramos:

Al seleccionar Start, se nos desplegará una lista de opciones y comandos, de los cuales solo /newbot nos va a interesar por ahora. Una vez seleccionado vamos a seguir las instrucciones y obtendremos el token access de nuestro Bot.

Por ahora estamos listos con Telegram, pero te invitamos a que revises los otros comandos del Botfather para personalizar tu bot según lo que necesites.

En nuestro caso vamos a cambiar la imagen del bot por algo más… Prometeo.

Lista de comandos para editarLista de comandos para editar

Lista de comandos para editar

Cambio de imagen del botCambio de imagen del bot

Cambio de imagen del bot

Ahora sí, vamos directo al código.Primero vamos a definir la estructura del proyecto, en nuestro caso particular será la siguiente:

Estructura de los archivosEstructura de los archivos

Estructura de los archivos

Dentro del archivo constants.py agregamos las API-KEY que tenemos:python PROMETEO_KEY = 'AAAAAAAAAAAAAAAAAAAAAAAAAA' TELEGRAM_KEY = 'BBBBBBBBBBBBBBBBBBBBBBBBBB' En el archivo main.py vamos a inicializar nuestro bot tal cual se puede observar dentro de la documentación de la librería que estamos usando.```python
from telegram.ext import ApplicationBuilder
from constants import TELEGRAM_KEY

def main() -> None:
"""Run the bot."""
bot = ApplicationBuilder().token(TELEGRAM_KEY).build()
bot.run_polling()

if name == 'main':
main()
Para empezar a ver la magia de nuestro bot, vamos a crear nuestro primer comando.python
from telegram import Update
from telegram.ext import CallbackContext,CommandHandler

async def start(update: Update, context: CallbackContext) -> None:
"""Starts the conversation """
reply_text = """
Hi! I am the Prometeo Telegram Bot\n
Please use /login to continue
"""
await update.message.reply_text(reply_text)

start_command =CommandHandler('start', start)

- Context: Es donde se guardan objetos e información en común del chat de cada usuario.Nuestra función por ahora solo envía un saludo cuando el usuario inicia el comando.Por último, fuera de la función (en la línea 12), creamos una variable que va a ser de tipo _CommandHandler_ y, por lo tanto, va a llevar la lógica interna del comando con respecto a Telegram.Una vez tengamos esto, actualizamos el archivo _**init**.py_ agregando la siguiente línea:```python
from .start import start_command
```Y luego agregamos el comando a nuestro _main.py_```
from telegram.ext import ApplicationBuilder
from constants import TELEGRAM_KEY
#Aca importaremos los comandos que creemos
from commands import start_command

def main() -> None:
    """Run the bot."""
    bot = ApplicationBuilder().token(TELEGRAM_KEY).build()
    #A partir de aca iran los comandos
    bot.add_handler(start_command)
    
    bot.run_polling()


if __name__ == '__main__':
    main()
```Ahora ejecutamos nuestro archivo main y vamos a nuestro bot para ver si todo funciona correctamente:

[block:image]
{
  "images": [
    {
      "image": [
        "https://files.readme.io/3ba6377-Captura_de_pantalla_2022-12-23_a_las_09.51.12.png",
        null,
        null
      ],
      "caption": "El comando start funciona perfectamente"
    }
  ]
}
[/block]
Con esto entendemos el proceder para el resto de los comandos. Lo que realizamos fue:1. Dentro del archivo del comando, escribimos la lógica y dejamos una variable que contenga el punto de entrada del comando, en este caso en particular fue _start_command_.
2. Importamos esta variable dentro del archivo _**init**.py_ para que sea accesible desde el paquete.
3. Importamos el comando en _main.py_ y lo agregamos con _bot.add_handler()_.Con lo anterior en mente, ahora vamos a integrar la API de Prometeo a nuestro bot.Primero, a nuestro archivo de constantes vamos a agregar el link de la API de Prometeo, en este caso al ser un ejemplo agregamos el link al sandbox:

```python
BASE_URL = "https://banking.sandbox.prometeoapi.com"

Luego, creamos un nuevo archivo llamado prometeo.py dentro de la raíz de nuestro proyecto. Adentro vamos a reutilizar el código expuesto en el artículo mencionado en la sección “Antes de empezar”.

import requests
import datetime
from typing import List
from constants import PROMETEO_KEY,BASE_URL


class PrometeoAuth:

    def __init__(self) -> None:
        self.session_key = ''

    def login(self,provider:str,username:str,password:str) -> str:
        url = f"{BASE_URL}/login/"
        data = {
            "provider": provider,
            "username": username,
            "password": password
        }
        headers = {
            "X-API-Key": PROMETEO_KEY
        }
        response = requests.post(url, data=data, headers=headers)
        session_key = ''
        if response.ok:
            response_json = response.json()
            session_key = response_json.get("key",'')
            self.session_key = session_key
        return session_key


    def logout(self) -> bool:
        url = f"{BASE_URL}/logout/"
        params = {
            "key": self.session_key
        }
        headers = {
            "X-API-Key": PROMETEO_KEY
        }
        response = requests.get(url, params=params, headers=headers)
        return response.ok


class PrometeoData:
    @classmethod
    def get_accounts(cls, session_key: str) -> List[dict]:
        url = f"{BASE_URL}/account/"
        params = {
            "key": session_key
        }
        headers = {
            "X-API-Key": PROMETEO_KEY
        }
        response = requests.get(url, params=params, headers=headers)
        if response.ok:
            return response.json().get("accounts", [])

    @classmethod
    def get_account_movements(cls, session_key:str, account_number: str, currency: str, date_start: datetime, date_end: datetime) -> List[dict]:
        url = f"{BASE_URL}/account/{account_number}/movement/"
        params = {
            "accountNumber": account_number,
            "currency": currency,
            "date_start": date_start.strftime("%d/%m/%Y"),
            "date_end": date_end.strftime("%d/%m/%Y"),
            "key": session_key
        }
        headers = {
            "X-API-Key": PROMETEO_KEY
        }
        response = requests.get(url, params=params, headers=headers)
        if response.ok:
            return response.json().get("movements", [])

    @classmethod
    def get_providers(cls)->List[dict]:
        """Get the providers info from the API"""
        url = f'{BASE_URL}/provider/'
        headers = {
            "X-API-Key": PROMETEO_KEY
        }
        response=requests.get(url,headers=headers)
        if response.ok:
            return response.json().get('providers',[])

Como se puede ver, se cambió un par de cosas en el método de login y se agregó el método get_providers que nos servirá más adelante.

Si nos sentamos a pensar en la lógica del login notamos que es un poco más compleja que el ejemplo anterior, ya que necesitamos preguntar el provider, el username y la contraseña para completar todo el proceso. En otras palabras, buscamos algo similar a una conversación.

Para ello, la documentación nos ilustra con un ejemplo. Si lo adaptamos un poco a lo que nos interesa, el archivo de login tendría que quedar similar a:

from telegram import Update,ReplyKeyboardMarkup,ReplyKeyboardRemove
from prometeo import PrometeoAuth, PrometeoData
from telegram.ext import (
    CommandHandler,
    MessageHandler,
    ConversationHandler,
    CallbackContext,
    filters
)

PROVIDER,USERNAME,PASSWORD = range(3)


async def login(update:Update,context:CallbackContext)->int:
    """Login entry"""

    provider_data = PrometeoData.get_providers()
    provider_formated = {provider['name']:provider['code'] for provider in provider_data}
    context.user_data['providers']=provider_formated

    await update.message.reply_text(
        'First of all, you need to select a provider\n\n'
        'Please select your provider:',
        reply_markup=ReplyKeyboardMarkup(
            [[provider] for provider in provider_formated.keys()],
            one_time_keyboard=True,
            input_field_placeholder='Which provider?'),
        )
    return PROVIDER

async def provider(update: Update, context: CallbackContext) -> int:
    """Save select provider"""
    provider_dict = context.user_data['providers']

    context.user_data['login_data']={'provider':provider_dict[update.message.text]}
    await update.message.reply_text(
        f'Thanks, now i need your username for {update.message.text}',
        reply_markup=ReplyKeyboardRemove(),
    )
    return USERNAME

async def username(update: Update, context: CallbackContext) -> int:
    """Save the username"""
    context.user_data['login_data'].update({'username':update.message.text})
    await update.message.reply_text(
        'Perfect, now i need the password please, dont worry i wont look '
    )
    return PASSWORD
    
async def password(update: Update, context: CallbackContext) -> int:
    """Save the password"""
    context.user_data['login_data'].update({'password':update.message.text})
    await context.bot.deleteMessage(message_id=update.message.message_id,chat_id=update.message.chat_id)
    await update.message.reply_text(
        'Thanks, now let me check please'
    )
    return await check_login(update,context)

async def check_login(update:Update, context:CallbackContext)->int:
    """Check the data and create an user and then login"""

    provider = context.user_data['login_data']['provider']
    username = context.user_data['login_data']['username']
    password = context.user_data['login_data']['password']

    prometeo_auth = PrometeoAuth()
    session_key = prometeo_auth.login(provider,username,password)

    if not session_key:
        await update.message.reply_text('Ups, maybe you had a typo?')
    else:
        await update.message.reply_text('Perfect, welcome :)')
        context.user_data['prometeo_auth']=prometeo_auth


    del context.user_data['login_data']
    return ConversationHandler.END

async def cancel(update: Update, context: CallbackContext) -> int:
    """Cancels and ends the conversation."""
    await update.message.reply_text(
        'Bye! Have a nice day', reply_markup=ReplyKeyboardRemove()
    )
    return ConversationHandler.END


login_command = ConversationHandler(
        entry_points=[CommandHandler('login', login)],
        states = {
            PROVIDER: [MessageHandler(filters.TEXT,provider)],
            USERNAME: [MessageHandler(filters.TEXT & ~filters.COMMAND, username)],
            PASSWORD: [MessageHandler(filters.TEXT & ~filters.COMMAND, password)],
        },
        fallbacks=[CommandHandler("cancel", cancel)]
    )

Vamos a destacar las cosas importantes para evitar cualquier tipo de confusión.

La conversación requiere de un punto de entrada, es decir, el trigger que va a indicarle al bot cuando debe continuar recolectando información.

Para nosotros esa sería la función login, la cual vemos que consulta a los proveedores disponibles en Prometeo para poder mostrar una lista de botones dentro del bot y así no tener que revisar los códigos de los bancos que vamos a usar.

Para que el bot conozca cuál es el flujo correcto en el que debe seguir la conversación necesitamos estados, (puntos clave de la conversación), lo que nos interesa obtener del usuario es: provider, usuario, password.

Definimos 3 constantes en la línea 11 que van a representar a los estados que mencionamos anteriormente y procedemos a crear una función por cada uno de ellos.

Notamos que:

  1. Cada función retorna la constante correspondiente al paso siguiente, login-> provider, provider -> username, username ->password.
  2. Cada función guarda la información suministrada por el usuario dentro del diccionario “user_data” dentro del context.
  3. Respecto al punto anterior, nosotros guardamos la información de login dentro de un diccionario llamado “login_data”.
  4. Antes de continuar el proceso, cada función va a preguntar por los datos necesarios para el siguiente paso. Ejemplo: En login se pregunta por el provider, en el provider se pregunta por el user y así hasta el final.
  5. Siguiendo el ejemplo de la documentación, agregamos la opción para que el usuario pueda cancelar en cualquier momento con la función cancel.

Hasta ahora nuestra conversación va a seguir el siguiente flujo:

Flujo de loginFlujo de login

Flujo de login

Una vez completado el flujo, se procede a realizar el login contra la API de Prometeo. Esto se hace instanciando un objeto de la clase PrometeoAuth y llamado al método login con los datos obtenidos.

Si todo salió bien, devolvemos un mensaje de éxito y guardamos el objeto creado para usar la session key en futuras llamadas. Por otro lado, si algo falló, devolvemos un mensaje de error y le pedimos al usuario intentarlo nuevamente.

Al tomarnos muy en serio la seguridad y las credenciales de nuestros clientes, queremos destacar dos pequeñas cosas que aumentan considerablemente la seguridad de nuestro bot.

  • En la línea 53, borramos el registro de la contraseña en el chat de forma inmediata, así prevenimos cualquier robo de identidad o leak de información del historial del chat.
  • En la línea 76, borramos del contexto del usuario toda la información de su login, así nos encargamos de que no se pueda obtener información bancaria de ningún usuario bajo ninguna circunstancia.

Continuamos creando el comando del login, pero esta vez usamos la clase ConversationHandler por las razones comentadas anteriormente.

Vemos que se envía 3 parámetros: entry_point (El comando que da inicio a la conversación), states (Un diccionario con el flujo de la conversación) y fallbacks (El comando a ejecutar para cancelar o salir de la conversación).

Luego procedemos con los respectivos imports de la variable login_command en el init.py y en el archivo main.py y tendremos todo el login completo.

Ejecutamos una vez más y probamos que todo funciona bien.

Seleccionado ProvidersSeleccionado Providers

Seleccionado Providers

Escribiendo la contraseñaEscribiendo la contraseña

Escribiendo la contraseña

Proceso completado con éxitoProceso completado con éxito

Proceso completado con éxito

Continuamos el proceso de autenticación con el comando de logout:

from telegram import Update
from telegram.ext import CallbackContext,CommandHandler

async def logout(update:Update,context:CallbackContext)->None:
    """Logout and delete all the login data saved in the context"""
    prometeo_auth = context.user_data.get('prometeo_auth',None)
    if prometeo_auth:
        prometeo_auth.logout() 
        await update.message.reply_text('Thank you, have a good day')
        del context.user_data['prometeo_auth']


logout_command = CommandHandler('logout', logout)

Notamos que validamos si existe un objeto llamado prometeo_auth dentro del contexto y si es así entonces hace el logout y deja un mensaje.

Como en este punto es probable que ya seas un experto con todo lo anterior, continuamos directo con nuestro último ejemplo: el comando de cuentas.

from telegram import Update
from telegram.ext import CallbackContext, CommandHandler, ConversationHandler

from prometeo import PrometeoData

async def account(update:Update,context:CallbackContext)->int:
    """Selec the get account operation from the API"""

    prometeo_auth = context.user_data.get('prometeo_auth',None)
    if prometeo_auth:
        session_key = prometeo_auth.session_key
        account_data = PrometeoData.get_accounts(session_key)
        for account in account_data:
            account_name = account.get('name')
            account_number = account.get('number')
            account_currency = account.get('currency')
            account_balence = account.get('balance')
            text = f"""
                Account name: {account_name}\nAccount number: {account_number}\nBalance: {account_balence} {account_currency}
            """
            await update.message.reply_text(text)
    return ConversationHandler.END

account_command = CommandHandler('account',account)

Acá volvemos a validar si hay un login disponible y luego llamamos el método get_accounts de la clase PrometeoData y por último convertimos la información recibida.

Una vez importado todo, volvemos a ejecutar para asegurar que todo está bien.

Y listo, ahora tenemos un bot completamente funcional para consultar nuestras cuentas bancarias de forma segura y eficiente gracias a la API de Prometeo.

Y… ¿Ahora qué?
Todo lo que hemos hecho hasta ahora es solo una pequeña parte de todo lo que se puede hacer gracias a las API de Prometeo. A partir de ahora depende de ti completar el proyecto según las necesidades que tengas.