NEQUI
  • Colombia

  • Panamá

  • Cómo usar el SDK
  • Creación de un cliente
  • Autenticación en Nequi Conecta
  • Servicio de validación de un cliente
  • Servicio de Recargas o Cash In
  • Servicio de Consulta de retiro o Cash Out
  • Servicio de Retiro o Cash Out

Consumo del API de Cash In/Out en Java o Android

Cómo usar el SDK

Para poder consumir el API de Nequi en su proyecto Java debe agregar el SDK como una dependencia de maven, el id del artefacto cambia según si el ambiente es QA o producción.

Por favor incluya en la parte de dependencias de su pom.xml lo siguiente:

  • Ambiente de pruebas o QA:
    <dependency>
        <groupid>com.nequi</groupid>
        <artifactid>nequi-banking-agents-sdk-qa</artifactid>
        <version>1.0.1</version>
    </dependency>
    
  • Ambiente de producción:
    <dependency>
        <groupid>com.nequi</groupid>
        <artifactid>nequi-banking-agents-sdk</artifactid>
        <version>1.0.1</version>
    </dependency>
    

Si no va a usar el artefacto de maven sino que el equipo de NEQUI le entregó los jars requeridos, agregue estos al classpath de su proyecto; o en caso que  tenga las fuentes del SDK de Java en un .zip, descomprímalo y en la raíz donde se encuentra el archivo pom.xml ejecute el comando mvn install para instalar el paquete en su repositorio local de maven.

Creación de un cliente

Ya teniendo el SDK integrado en nuestro proyecto, lo siguiente para consumir el API de NEQUI es instanciar un nuevo cliente mediante el objeto ApiClientFactory y la interfaz NequiBankingAgentsGatewayClient que representa el contrato de servicio con las operaciones que se pueden hacer sobre el API de Nequi.

Para este proceso va a necesitar el apiKey, accessKey y secretKey que le ha suministrado el equipo de Nequi como sus credenciales de acceso al API para poder configurar la autorización mediante la clase NequiAuth.

En el siguiente código en java podemos ver un ejemplo de cómo instanciar un cliente del API:

    
//Se inicializa la fábrica de clientes 
ApiClientFactory factory = new ApiClientFactory(); 
//Se setea el api key 
factory.apiKey(API_KEY); 
//Se instancia un nuevo cliente del api a partir del contrato establecido por NequiBankingAgentsGatewayClient
NequiBankingAgentsGatewayClient apiClient = factory.build(NequiBankingAgentsGatewayClient.class);
    

Ya con el contrato o interfaz NequiGatewayClient instanciada podemos ejecutar de manera sincrónica cualquiera de los siguientes métodos:

    
public interface NequiGatewayClient {
    JsonObject servicesCashinserviceCashinPost(JsonObject body, String authorization);
    JsonObject servicesCashoutserviceCashoutPost(JsonObject body, String authorization);
    JsonObject servicesCashoutserviceCashoutconsultPost(JsonObject body, String authorization);
    JsonObject servicesClientserviceValidateclientPost(JsonObject body, String authorization);
    JsonObject servicesReverseservicesReversetransactionPost(JsonObject body, String authorization);
    String servicesKeysserviceGetpublicPost(String authorization);
}
  

Como se puede ver en la anterior código, cada método o servicio del API recibe un JsonObject que representa el cuerpo de la petición y un String con el token de autorización que requiere el servicio,  y retorna un JsonObject que representa la respuesta de dicho servicio.

Autenticación en Nequi Conecta

Todos los servicios ofrecidos por el API necesitan de un token de autenticación, el cuál podrá obtener mediante la clase NequiAuth. Dicha autenticación se puede hacer mediante 2 medios:

1. Variables de entorno: Esta es la opción recomendada, siempre que pueda suministrar las variables de entorno tal cual se mencionan a continuación:

  • NEQUI_CLIENT_ID: Proveida por Conecta Nequi en la sección de credenciales.
  • NEQUI_CLIENT_SECRET: Proveida por Conecta Nequi en la sección de credenciales.
  • NEQUI_AUTH_URI: Es la URI del endpoint que debe consumir para autenticarse, para producción es "https://oauth.nequi.com/oauth2/token"(sin las comillas).
  • NEQUI_AUTH_GRANT_TYPE: Tipo de acceso a los recursos del API, el valor recomendado es "client_credentials"(sin las comillas).

Una vez listas las variables de entorno, basta con instanciar la clase de la siguiente forma:

Ejemplo

NequiAuth nequiAuth = NequiAuth.getInstance().fromEnvVars();

2. Asignación manual de los datos: Con este mecanismo podrá asignar manualmente los valores que se necesitan para autenticarse usando alguno de los siguientes métodos de la clase NequiAuth:

  • NequiAuth.getInstance().with: Este método recibe los siguientes valores:
    • String clientId: Proveida por Conecta Nequi en la sección de credenciales.
    • String clientSecret: Proveida por Conecta Nequi en la sección de credenciales.
    • String authUri: Es la URI del endpoint que debe consumir para autenticarse, para producción es "https://oauth.nequi.com/oauth2/token"(sin las comillas).
    • String authGranType: Tipo de acceso a los recursos del API, el valor recomendado es "client_credentials"(sin las comillas).

Ejemplos:

// Opción 1: Se omite el argumento "authGranType" para que tome el valor por defecto 
NequiAuth nequiAuth = NequiAuth.getInstance().with("myClientId", "myClienteSecret", "https://oauth.sandbox.nequi.com/oauth2/token"); 

// Opción 2: Se pasan todos los argumentos necesarios
NequiAuth nequiAuth = NequiAuth.getInstance().with("myClientId", "myClienteSecret", "https://oauth.sandbox.nequi.com/oauth2/token", "client_credentials");
  • Usando los métodos .withXYZ: La clase NequiAuth cuenta con 4 métodos concatenables que permiten asignar los valores manualmente:
    • .withClientId(String clientId)
    • .withClientSecret(String clientSecret)
    • .withAuthUri(String authUri)
    • .withAuthGranType(String authGranType)

Ejemplos:

// Opción 1: Se instancia pasando cada dato en particular 
NequiAuth nequiAuth = NequiAuth.getInstance()
    .withClientId("myClientId")
    .withClientSecret("myClientSecret")
    .withAuthUri("https://oauth.sandbox.nequi.com/oauth2/token")
    .withAuthGranType("client_credentials");

// Opción 2: Se instancia a partir de las variables de entorno, pero se puede sobre-escribir cualquier valor 
NequiAuth nequiAuth = NequiAuth.getInstance().fromEnvVars()
    .withClientId("myClientId")
    .withClientSecret("myClientSecret");

Una vez instanciada la clase NequiAuth, puede obtener un token de autorización usando el método .getToken();, el cual puede recibir un flag que indica si se desea obtener concatenado el tipo de token y el token(el resultado es la cabecera HTTP "Authorization" ya lista); o si solo se desea el token. Si no se pasa el flag, por defecto será true.

Ejemplo:

// Retorna el tipo de token y el token concatenados, lo que conforma la cabecera HTTP "Authorization"
// Se obtiene el mismo resultado que invocar nequiAuth.getToken(true); 
String token = nequiAuth.getToken();

 

Servicio de validación de un cliente

El servicio de validación de un cliente NEQUI expuesto en el api permite a partir de un número celular y valor a recargar (requerido pero puede ser 0) verificar que exista un cliente o cuenta en NEQUI con ese número, validar que la cuenta esté activa y evaluar si el valor a recargar no supera el monto permitido para el cliente NEQUI.

De los métodos que ofrece el NequiGatewayClient vamos a utilizar servicesClientserviceValidateclientPost para consumir el servicio de validación de un cliente.

En código Java sería de la siguiente forma:

//Inicialización del cliente API 
ApiClientFactory factory = new ApiClientFactory(); 
factory.apiKey(_API_KEY); 
//Se instancia un nuevo cliente 
NequiBankingAgentsGatewayClient apiClient = factory.build(NequiBankingAgentsGatewayClient.class); 
//Se instancia la clase para autenticarse 
NequiAuth auth = NequiAuth.getInstance().fromEnvVars();

Long phone_number = 123;
Integer value = 0; 

//Consumo del servicio, retorna un JsonObject con la respuesta del servicio
JsonObject response = apiClient.servicesClientserviceValidateclientPost(BodyUtils.getBodyValidationClient(phone_number, value), auth.getToken());
   

Al revisar el código anterior podemos ver cómo primero se inicializa el cliente del API y luego se invoca el método que representa el servicio de validación de un cliente retornando un JsonObject con la respuesta del servicio.

Es necesario destacar que en el ejemplo anterior se le pasa al método servicesClientserviceValidateclientPost como parámetro, “body” o cuerpo el resultado de un método estático que no está codificado en el ejemplo que se llama BodyUtils.getBodyValidationClient. Este método debería retornar un JsonObject que requiere el servicio con la siguiente estructura en JSON:

{
    "RequestMessage": {
        "RequestHeader": {
            "Channel": CONSUMER_ID, //ID del consumidor para trazabilidad
            "RequestDate": DATE, //Timestamp completo, con milisegundos precisión 3
            "MessageID": ID, //Identificador único de la transacción
            "ClientID": NUMBER, //Número de celular del merchant ó identificador de la terminal 
            "Address": {
                //Los siguientes campos son requeridos pero pueden ir vacios
                "DeviceAddress": STRING, //IP del dispositivo desde donde se consume el servicio
                "NetworkAddress": STRING //IP de la red desde donde se consume el servicio
            },
            "Destination": {
                "ServiceName": "RechargeService", //Servicio que se quiere consumir
                "ServiceOperation": "validateClient", //Operación del servicio a consumir
                "ServiceRegion": "C001", //Región a la que pertenece el servicio, para panamá P001
                "ServiceVersion": "1.4.0" //Versión del servicio
            }
        },
        "RequestBody": {
            "any": {
                "validateClientRQ": {
                    "phoneNumber": NUMBER, //Número de celular a verificar, requerido
                    "value": VALUE //Valor a recargar, requerido        
                }
            }
        }
    }
}

Para construir las cabeceras del cuerpo de la petición o  RequestHeader va a necesitar el Channel que le ha sido suministrado por Nequi, generar un datestamp a partir de la fecha en que se realiza la petición para la propiedad RequestDate y definir una forma única de identificar la transacción mediante un alfanumérico de 10 para asignar al campo MessageID. Además, va a requerir la IP de la red desde donde se consume el servicio y la IP que tiene ese dispositivo en esa red. Un ejemplo de la codificación del método sería:

// Canal entregado por el equipo de Nequi
public static final String CHANNEL = "MF-001";
public static final String CLIENTID = "123";
// Construye a partir del celular y valor a recargar el mensaje de entrada
public static JsonObject getBodyCashIn(Long phone, Integer value) {
    Gson gson = new Gson();    
    //Fecha de la petición
    Date d = new Date();
    String date = new SimpleDateFormat("yyyy-MM-dd:HH-mm-ss").format(d);
    String miliseconds = Long.toString(System.currentTimeMillis());
    String input = 
        "{\"RequestMessage\":"
            + "{\"RequestHeader\":{"
                //Canal
                + "\"Channel\":\""+CHANNEL+"\","
                //Fecha de la petición
                + "\"RequestDate\":\""+date+"\","
                //Identificador único de la transacción
                + "\"MessageID\":\""+ miliseconds.substring(miliseconds.length()-9)+"\","
                //Número de celular del merchant ó identificador de la terminal
                + "\"ClientID\":\"" + CLIENTID + "\""
                + ", \"Address\": {"
                    //IP del dispositivo en la red
                    +    "\"DeviceAddress\": \"1.1.1.1\""
                    //IP de la red
                    +    ", \"NetworkAddress\": \"1.1.1.1\""
                + "}"                   
                + ", \"Destination\": {"
                    //Nombre del servicio
                    +    "\"ServiceName\": \"RechargeService\""
                    //Operación del servicio
                    +    ", \"ServiceOperation\": \"validateClient\""
                    //Region del servicio
                    +    ", \"ServiceRegion\": \"C001\""
                    //Versión del servicio
                    +    ", \"ServiceVersion\": \"1.4.0\""
                + "}"
            + "},"
            + "\"RequestBody\":{"
                + "\"any\":{"
                    + "\"validateClientRQ\":{"
                        //Número del cliente a verificar
                        + "\"phoneNumber\":\""+ phone + "\","
                        //Valor a recargar
                        + "\"value\":\"" + value + "\""
                    + "}"
                + "}"
            + "}"
        + "}}";
    return gson.fromJson(input, JsonObject.class);
}

Al consumir el anterior servicio en el response de la petición se puede verificar si fue exitoso u ocurrió algún error en el atributo Status. Cuando el atributo StatusCode es igual a 0 significa que en NEQUI si existe un una cuenta o cliente con ese celular, no está bloqueado y el monto a recargar no supera el monto permitido,  en caso contrario ha ocurrido un error y más detalle de este se puede ver en el atributo  StatusDesc. La estructura de respuesta del servicio es la siguiente:

{
    "ResponseMessage": {
        "ResponseHeader": {
            "Channel": CHANNEL, //Canal por donde se realizó el cash In
            "ResponseDate": DATE, //Fecha de la respuesta
            "Status": {
                "StatusCode": "0", //Resultado de la transacción
                "StatusDesc": "SUCCESS" //Mensaje con información respecto la transacción
            },
            "MessageID": ID, //Identificador único de la transacción
            //Número de celular del merchant ó identificador de la terminal
            "ClientID": NUMBER,
            //Datos técnicos que no deberían ser procesados o almacenados
            "Destination": {
                "ServiceName": " RechargeService ", // Nombre del Servicio
                "ServiceOperation": " validateClient " //Nombre de la operación
                "ServiceRegion": "C001", //Región a la que pertenece el servicio, para panamá P001
                "ServiceVersion": "1.4.0" //Versión del servicio
            }
        },
        "ResponseBody": {
            "any": {
                //Presente solo cuando StatusCode es igual a cero
                "validateClientRS": {
                          "availableLimit": "100.54",//Permite saber lo máximo que puede recargar
                          "customerName": "Nombre del cliente" //Nombre completo del cliente                   
                }
            }
        }
    }
}

Cuando la operación del servicio falla, no el consumo HTTP del servicio REST, sino la operación de negocio, el campo StatusCode será diferente de cero y por consiguiente en ResponseBody.any no vendrá el objeto  validateClientRS sino que any será un elemento o cadena vacía.

Servicio de Recargas o Cash In

El servicio de recargas o cash in expuesto en el api permite recargar a una cuenta Nequi determinado monto de dinero.

De los métodos que ofrece el NequiGatewayClient vamos a utilizar servicesCashinserviceCashinPost para consumir el servicio de cash in.

En código Java sería de la siguiente forma:

    
//Inicialización del cliente API
ApiClientFactory factory = new ApiClientFactory();
factory.apiKey(_API_KEY);
//Se instancia un nuevo cliente 
NequiBankingAgentsGatewayClient apiClient = factory.build(NequiBankingAgentsGatewayClient.class);
//Se instancia la clase para autenticarse
NequiAuth auth = NequiAuth.getInstance().fromEnvVars();

Long phone_number = 123;
Integer mount = 1; 

//Consumo del servicio, retorna un JsonObject con la respuesta del servicio
JsonObject response = apiClient.servicesCashinserviceCashinPost(BodyUtils.getBodyCashIn(phone_number, mount), auth.getToken());

Al revisar el código anterior podemos ver cómo primero se inicializa el cliente del API y luego se invoca el método que representa el servicio de cash in retornando un JsonObject con la respuesta del servicio.

Es necesario destacar que en el ejemplo anterior se le pasa al método servicesCashinserviceCashinPost como parámetro, “body” o cuerpo el resultado de un método estático que no está codificado en el ejemplo que se llama  BodyUtils.getBodyCashIn. Esta función debería retornar un JsonObject que requiere el servicio de cash in, con la siguiente estructura en JSON:

{
    "RequestMessage": {
        "RequestHeader": {
            "Channel": CONSUMER_ID, //ID del consumidor para trazabilidad
            "RequestDate": DATE, //Timestamp completo, con milisegundos precisión 3
            "MessageID": ID, //Identificador único de la transacción, timestamp o alguno único
            "ClientID": NUMBER //Número de celular del merchant ó identificador de la terminal
            "Destination": {
                "ServiceName": "CashInService", //Servicio que se quiere consumir
                "ServiceOperation": "cashIn", //Operación del servicio a consumir
                "ServiceRegion": "C001", //Región a la que pertenece el servicio, para panamá P001
                "ServiceVersion": "1.0.0" //Versión del servicio
            }
        },
        "RequestBody": {
            "any": {
                "cashInRQ": {
                    "phoneNumber": NUMBER, //Número de celular al que se le va a hacer la recarga
                    "code": CODE, //Código o NIT del comercio incluido el dígito de verificación
                    "value": MOUNT //Monto a recargar
                }
            }
        }
    }
}

Para construir las cabeceras del cuerpo de la petición o  RequestHeader va a necesitar el Channel que le ha sido suministrado por Nequi, generar un datestamp a partir de la fecha en que se realiza la petición para la propiedad RequestDate y definir una forma única de identificar la transacción mediante un alfanumérico de 10 para asignar al campo MessageID. Un ejemplo de la codificación del método sería:

// Canal entregado por el equipo de Nequi
public static final String CHANNEL = "MF-001";
public static final String CLIENTID = "123";
// Construye a partir del celular y monto a recargar el mensaje de entrada
public static JsonObject getBodyCashIn(Long phone, Integer mount) {
    Gson gson = new Gson();    
    //Fecha de la petición
    Date d = new Date();
    String date = new SimpleDateFormat("yyyy-MM-dd:HH-mm-ss").format(d);
    String miliseconds = Long.toString(System.currentTimeMillis());        
    String input = 
        "{\"RequestMessage\":"
            + "{\"RequestHeader\":{"
                //Canal
                + "\"Channel\":\""+CHANNEL+"\","
                //Fecha de la petición
                + "\"RequestDate\":\""+date+"\","
                //Identificador único de la transacción
                + "\"MessageID\":\""+ miliseconds.substring(miliseconds.length()-9)+"\","
                //Número de celular del merchant ó identificador de la terminal
                + "\"ClientID\":\"" + CLIENTID + "\""
                + ", \"Destination\": {"                    
                    +    "\"ServiceName\": \"CashInService\""
                    +    ", \"ServiceOperation\": \"cashIn\""
                    +    ", \"ServiceRegion\": \"C001\""
                    +    ", \"ServiceVersion\": \"1.0.0\""
                + "}"
            + "},"
            + "\"RequestBody\":{"
                + "\"any\":{"
                    + "\"cashInRQ\":{"
                        //Número del cliente al que se le va a hacer la recarga
                        + "\"phoneNumber\":\""+ phone + "\","
                        //Código o NIT del comercio
                        + "\"code\":\"1\","
                        //Valor de la recarga
                        + "\"value\":\"" + mount + "\""
                    + "}"
                + "}"
            + "}"
        + "}}";
    return gson.fromJson(input, JsonObject.class);
}

Al consumir el anterior servicio en el response de la petición se puede verificar si fue exitoso u ocurrió algún error en el atributo Status. Cuando el atributo StatusCode es igual a 0 significa que la recarga fue realizada con éxito en caso contrario ha ocurrido un error y más detalle de este se puede ver en el atributo  StatusDesc. La estructura de respuesta del servicio es la siguiente:

{
    "ResponseMessage": {
        "ResponseHeader": {
            "Channel": CHANNEL, //Canal por donde se realizó el cash In
            "ResponseDate": DATE, //Fecha de la respuesta
            "Status": {
                "StatusCode": "0", //Resultado de la transacción
                "StatusDesc": "SUCCESS" //Mensaje con información respecto la transacción
            },
            "MessageID": ID, //Identificador único de la transacción
            //Número de celular del merchant ó identificador de la terminal
            "ClientID": NUMBER,
            //Datos técnicos que no deberían ser procesados o almacenados
            "Destination": {
                "ServiceName": "CashInService", //Nombre del servicio
                "ServiceOperation": "cashIn",   //Nombre de la operación 
                "ServiceRegion": "C001",       //Región a la que pertenece el servicio, para panamá P001 
                "ServiceVersion": "1.0.0"       //Versión del servicio
            }
        },
        "ResponseBody": {
            "any": {
                //Presente solo cuando StatusCode es igual a cero
                "cashInRS": {
                    "trnId": "M48", //Identificador de la transacción
                    "phoneNumber": NUMBER, //Numero al que se le realizo la recarga
                    "date": DATE, //Fecha en que se realizó la recarga
                    "value": "500", //Valor de la recarga
                    "name": "El Ranchero" //Nombre del establecimiento o lugar donde se recargo
                }
            }
        }
    }
}

Cuando la operación del servicio falla, no el consumo HTTP del servicio REST, sino la operación de negocio, el campo StatusCode será diferente de cero y por consiguiente en ResponseBody.any no vendrá el objeto cashInRS sino que any será un elemento o cadena vacía.

Servicio de Consulta de retiro o Cash Out

El servicio de consulta de retiro o cash out permite verificar a partir del número de celular de un usuario NEQUI si éste ha generado una solicitud de retiro desde su celular, para luego proceder a realizar el cash out o retiro del dinero.

De los métodos que ofrece el NequiGatewayClient vamos a utilizar servicesCashoutserviceCashoutconsultPost para consumir el servicio de consulta de cash out.

En código Java sería de la siguiente forma:

//Inicialización del cliente API
ApiClientFactory factory = new ApiClientFactory();
factory.apiKey(_API_KEY);
//Se instancia un nuevo cliente 
NequiBankingAgentsGatewayClient apiClient = factory.build(NequiBankingAgentsGatewayClient.class);
//Se instancia la clase para autenticarse
NequiAuth auth = NequiAuth.getInstance().fromEnvVars();

Long phone_number = 123;

//Consumo del servicio, retorna un JsonObject con la respuesta del servicio
JsonObject response = apiClient. servicesCashoutserviceCashoutconsultPost (BodyUtils.getBodyCashOutConsult(phone_number), auth.getToken());

Al revisar el código anterior podemos ver cómo primero se inicializa el cliente del API y luego se invoca el método que representa el servicio de consulta de cash out retornando un JsonObject con la respuesta del servicio.

Es necesario destacar que en el ejemplo anterior se le pasa al método servicesCashoutserviceCashoutconsultPost como parámetro, “body” o cuerpo el resultado de un método estático que no está codificado en el ejemplo que se llama  BodyUtils.getBodyCashOutConsult. Esta función debería retornar un JsonObject que requiere el servicio de consulta de cash out, con la siguiente estructura en JSON:

{
    "RequestMessage": {
        "RequestHeader": {
            "Channel": CHANNEL, //ID del consumidor o canal para trazabilidad
            "RequestDate": DATE, //Timestamp completo, con milisegundos precisión 3
            "MessageID": ID, //Identificador único de la transacción, timestamp o alguno único
            “ClientID": NUMBER //Número de celular del merchant ó identificador de la terminal
            "Destination": {
                "ServiceName": "CashOutServices", //Servicio que se quiere consumir
                "ServiceOperation": "cashOutConsult", //Operación del servicio a consumir
                "ServiceRegion": "C001", //Región a la que pertenece servicio, para panamá P001
                "ServiceVersion": "1.0.0" //Versión del servicio
            }
        },
        "RequestBody": {
            "any": {
                "cashOutConsultRQ": {
                    //Número de celular para consultar si tiene un cashout pendiente
                    "phoneNumber": NUMBER,
                }
            }
        }
    }
}

Para construir las cabeceras del cuerpo de la petición o  RequestHeader va a necesitar el Channel que le ha sido suministrado por Nequi, generar un datestamp a partir de la fecha en que se realiza la petición para la propiedad RequestDate y definir una forma única de identificar la transacción mediante un alfanumérico de 10 para asignar al campo MessageID. Un ejemplo de la codificación del método sería:

// Canal entregado por el equipo de Nequi
public static final String CHANNEL = "MF-001";
public static final String CLIENTID = "123";
// Construye a partir del celular el mensaje o cuerpo de la petición
public static JsonObject getBodyCashOutConsult(Long phone) {
    Gson gson = new Gson();    
    //Fecha de la petición
    Date d = new Date();
    String date = new SimpleDateFormat("yyyy-MM-dd:HH-mm-ss").format(d);      
    String miliseconds = Long.toString(System.currentTimeMillis());  
    String input = 
        "{\"RequestMessage\":"
            + "{\"RequestHeader\":{"
                //Canal
                + "\"Channel\":\""+CHANNEL+"\","
                //Fecha de la petición
                + "\"RequestDate\":\""+date+"\","
                //Identificador único de la transacción
                + "\"MessageID\":\""+ miliseconds.substring(miliseconds.length()-9)+"\","
                //Número de celular del merchant ó identificador de la terminal
                + "\"ClientID\":\"" + CLIENTID + "\""
                + ", \"Destination\": {"                    
                    +    "\"ServiceName\": \"CashOutServices\""
                    +    ", \"ServiceOperation\": \"cashOutConsult\""
                    +    ", \"ServiceRegion\": \"C001\""
                    +    ", \"ServiceVersion\": \"1.0.0\""
                + "}"
            + "\"},"
            + "\"RequestBody\":{"
                + "\"any\":{"
                    + "\"cashOutConsultRQ\":{"
                        //Número de celular para consultar si tiene un cashout pendiente
                        + "\"phoneNumber\":\""+ phone + "\""
                    + "}"
                + "}"
            + "}"
        + "}}";
    return gson.fromJson(input, JsonObject.class);
}

Al consumir el anterior servicio en el response de la petición se puede verificar si fue exitoso u ocurrió algún error en el atributo Status. Cuando el atributo StatusCode es igual a 0 significa que el usuario tiene pendiente un cashout y en caso contrario ha ocurrido un error y más detalle de este se puede ver en el atributo  StatusDesc. La estructura de respuesta del servicio es la siguiente:

{
    "ResponseMessage": {
        "ResponseHeader": {
            "Channel": CHANNEL, //Canal por donde se realizó la consulta de cash out
            "ResponseDate": DATE, //Fecha de la respuesta
            "Status": {
                "StatusCode": "0", //Resultado de la transacción
                "StatusDesc": "SUCCESS" //Mensaje con información respecto la transacción
            },
            "MessageID": ID, //Identificador único de la transacción
            //Número de celular del merchant ó identificador de la terminal
            "ClientID": NUMBER,
            //Datos técnicos que no deberían ser procesados o almacenados
            "Destination": {
                "ServiceName": "CashOutServices", //Nombre del Servicio
                "ServiceOperation": "cashOutConsult" //Nombre de la operación
                "ServiceRegion": "C001", //Región a la que pertenece el servicio, para panamá P001
                "ServiceVersion": "1.0.0" //Versión del servicio
            }
        },
        "ResponseBody": {
            "any": {
                //Presente solo cuando StatusCode es igual a cero
                "cashOutConsultRS": {
                    "phoneNumber": NUMBER, //Número del cliente a hacer cash out
                    "value": VALUE, //Valor del cash out
                    "description": "", //Descripción de la transacción
                    "reference": REFERENCE, //Referencia u origen del cash out 
                    //Disponible o tipo y nombre de bolsillo
                }
            }
        }
    }
}

Cuando la operación del servicio falla, no el consumo HTTP del servicio REST, sino la operación de negocio, el campo StatusCode será diferente de cero y por consiguiente en ResponseBody.any no vendrá el objeto  cashOutConsultRS sino que any será un elemento o cadena vacía.

Es de suma importancia tener presente que  para el consumo del servicio de cash out que se tratara en el siguiente capítulo se debe tomar el valor de reference del response de este servicio para mandarlo en el cuerpo de la petición del servicio de cash out.

Servicio de Retiro o Cash Out

El servicio de retiro o cash out permite a través del API de NEQUI autorizar el desembolso o retiro en efectivo de determinado valor de una cuenta NEQUI.

Para consumir este servicio es necesario que antes el cliente NEQUI haga la solicitud mediante la funcionalidad en el APP para sacar dinero y le entregue al corresponsal o punto NEQUI donde se va a realizar el retiro el token o código que le aparece en su celular.

Además, para consumir este servicio se requiere que el token que el usuario entrega vaya cifrado mediante el algoritmo de RSA con la llave pública que obtiene al consumir el método expuesto en la interfaz del API de Nequi llamado servicesKeysserviceGetpublicPost.

De los métodos que ofrece el NequiGatewayClient vamos a utilizar servicesCashoutserviceCashoutPost para consumir el servicio de cash out.

En código Java sería de la siguiente forma:

    
//Inicialización del cliente API
ApiClientFactory factory = new ApiClientFactory();
factory.apiKey(_API_KEY);
//Se instancia un nuevo cliente 
NequiBankingAgentsGatewayClient apiClient = factory.build(NequiBankingAgentsGatewayClient.class);
//Se instancia la clase para autenticarse
NequiAuth auth = NequiAuth.getInstance().fromEnvVars();

Long phone_number = 123;
Integer value = 100;
String reference = " ";
String token = "123456";
String publicKey = apiClient.servicesKeysserviceGetpublicPost();

//Consumo del servicio, retorna un JsonObject con la respuesta del servicio
JsonObject response = apiClient. servicesCashoutserviceCashoutPost(BodyUtils.getBodyCashOut(phone_number, value, reference, token, publicKey), auth.getToken());

Al revisar el código anterior podemos ver cómo primero se inicializa el cliente del API, se invoca el método que retorna la llave pública para realizar la encripción del token y luego se invoca el método que representa el servicio de  cash out retornando un JsonObject con la respuesta del servicio. Es necesario destacar que en el ejemplo anterior se le pasa al método servicesCashoutserviceCashoutPost como parámetro, “body” o cuerpo el resultado de un método estático que no está codificado en el ejemplo que se llama  BodyUtils.getBodyCashOut. Esta función debería retornar un JsonObject que requiere el servicio de cash out, con la siguiente estructura en JSON:

{
    "RequestMessage": {
        "RequestHeader": {
            "Channel": CHANNEL, //ID del consumidor o canal para trazabilidad
            "RequestDate": DATE, //Timestamp completo, con milisegundos precisión 3
            "MessageID": ID, //Identificador único de la transacción, timestamp o alguno único
            “ClientID": NUMBER //Número de celular del merchant ó identificador de la terminal
            "Destination": {
                "ServiceName": "CashOutServices", //Servicio que se quiere consumir
                "ServiceOperation": "cashOut", //Operación del servicio a consumir
                "ServiceRegion": "C001", //Región a la que pertenece servicio, para panamá P001
                "ServiceVersion": "1.0.0" //Versión del servicio
            }
        },
        "RequestBody": {
            "any": {
                "cashOutRQ": {
                    //Número de celular del cliente NEQUI que va a retirar
                    "phoneNumber": NUMBER,
                    "value": NUMBER, //Valor a retirar
                    "token": STRING, //Token encriptado con RSA
                    "code": STRING, //Código identificador de la cuenta merchant o corresponsal
                    "reference": STRING, //Lugar o bolsillo de donde va a salir el dinero,
                    //Valor que se obtiene al consumir el servicio de consulta de cash out
                    "externalData": {
                        //Datos personalizados solicitados por un proveedor, no siempre requeridos.
                        "tranPart": "XXX", //Identificador único de la transacción
                        "idTerminal": "123" //Identificador de la terminal
                    }
                }
            }
        }
    }
}

Es de suma importancia tener presente que el valor que debe ir en el atributo reference sale del response al consumir el servicio tratado en el anterior capítulo.

Para construir las cabeceras del cuerpo de la petición o  RequestHeader va a necesitar el Channel que le ha sido suministrado por Nequi, generar un datestamp a partir de la fecha en que se realiza la petición para la propiedad RequestDate y definir una forma única de identificar la transacción mediante un alfanumérico de 10 para asignar al campo MessageID. Un ejemplo de la codificación del método sería:

// Canal entregado por el equipo de Nequi
public static final String CHANNEL = "MF-001";
public static final String CLIENTID = "123";
// Construye a partir del celular el mensaje o cuerpo de la petición
public static JsonObject getBodyCashOutConsult(Long phone, Integer value, String reference, String token, String publicKey) {
    Gson gson = new Gson();    
    //Fecha de la petición
    Date d = new Date();
    String date = new SimpleDateFormat("yyyy-MM-dd:HH-mm-ss").format(d);
    String miliseconds = Long.toString(System.currentTimeMillis());      
    String tokenEncrypted = encryptToken(token, publicKey); 
    String input = 
        "{\"RequestMessage\":"
            + "{\"RequestHeader\":{"
                //Canal
                + "\"Channel\":\""+CHANNEL+"\","
                //Fecha de la petición
                + "\"RequestDate\":\""+date+"\","
                //Identificador único de la transacción
                +"\"MessageID\":\""+miliseconds.substring(miliseconds.length()-9)+"\","
                //Número de celular del merchant ó identificador de la terminal
                + "\"ClientID\":\"" + CLIENTID + "\""
                + ", \"Destination\": {"
                    +    "\"ServiceName\": \"CashOutServices\""
                    +    ", \"ServiceOperation\": \"cashOut\""
                    +    ", \"ServiceRegion\": \"C001\""
                    +    ", \"ServiceVersion\": \"1.0.0\""
                + "}"
            + "},"            
            + "\"RequestBody\":{"
                + "\"any\":{"
                    + "\"cashOutRQ\":{"
                        + "\"phoneNumber\":\""+ phone + "\","
                        + "\"code\":\"1\","
                        + "\"value\":\"" + value + "\","
                        + "\"reference\":\""+ reference + "\","
                        + "\"token\":\""+ tokenEncrypted + "\","
                        + "\"externalData\":{}}"
                + "}"
            + "}"
        + "}}";
    return gson.fromJson(input, JsonObject.class);
}

Si revisamos el anterior código podemos ver que para encriptar el token se usa el método  encryptToken, a continuación lo revisamos en detalle:

    
/**
* Crea a partir del string de la llave publica el objeto PublicKey que recibe el método
* de encripcion ofrecido por el SDK de Nequi y encripta el token.
* @param token
* @return
* @throws Exception
*/
private static String encryptToken(String token, String publicKeyString) throws Exception {
    String pubKey = publicKeyString.replaceAll("(-+BEGIN PUBLIC KEY-+\\r?\\n|-+END PUBLIC KEY-+\\r?\\n?)", "");
    X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.decode(pubKey));
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    PublicKey publicKey = keyFactory.generatePublic(spec);
    String tokenEncrypted = NequiEncrypter.encryptRSA(token, publicKey);
    return tokenEncrypted;
}

Al analizar el código anterior vemos que se realiza lo siguiente:

  • Se recibe un string como token y otro string que representa la llave pública que fue retornada por el servicio servicesKeysserviceGetpublicPost.
  • Luego se decodifica de base64 la llave pública y se genera una especificación de ésta.
  • Se crea la fábrica de llaves para el algoritmo de RSA, KeyFactory, y el objeto PublicKey, que representa la llave publica, a partir de la anterior especificación.
  • Se encripta el token utilizando el método encryptRSA, que se encuentra en la clase expuesta en el SDK de NEQUI llamada NequiEncrypter, el cual recibe el string a cifrar y la llave pública con que se va a realizar la encripción.
  • Se retorna el token encriptado.

Con base en el anterior código, se puede generar el cuerpo de la petición con el token encriptado y así, al consumir el servicio de cash out se puede verificar en el response si fue exitoso u ocurrió algún error en el atributo Status. Cuando el atributo StatusCode es igual a 0 significa que el retiro fue exitoso y en caso contrario ha ocurrido un error y más detalle de este se puede ver en el atributo  StatusDesc. La estructura de respuesta del servicio es la siguiente:

    
{
    "ResponseMessage": {
        "ResponseHeader": {
            "Channel": CHANNEL, //Canal por donde se realizó la consulta de cash out
            "ResponseDate": DATE, //Fecha de la respuesta
            "Status": {
                "StatusCode": "0", //Resultado de la transacción
                "StatusDesc": "SUCCESS" //Mensaje con información respecto la transacción
            },
            "MessageID": ID, //Identificador único de la transacción
            //Número de celular del merchant ó identificador de la terminal
            "ClientID": NUMBER,
            //Datos técnicos que no deberían ser procesados o almacenados
            "Destination": {
                "ServiceName": "CashOutServices", //Servicio que se quiere consumir
                "ServiceOperation": "cashOut", //Operación del servicio a consumir
                "ServiceRegion": "C001", //Región a la que pertenece el servicio, para panamá P001
                "ServiceVersion": "1.0.0" //Versión del servicio
            }
        },
        "ResponseBody": {
            "any": {
                //Presente solo cuando StatusCode es igual a cero
                "cashOutRS": {}
            }
        }
    }
}

Cuando la operación del servicio falla, no el consumo HTTP del servicio REST, sino la operación de negocio, el campo StatusCode será diferente de cero y por consiguiente en ResponseBody.any no vendrá el objeto cashOutRS sino que any será un elemento o cadena vacía.

Si tiene dudas técnicas sobre la implementación y uso del API de NEQUI revise el código de la aplicación de ejemplo que se le entregó con esta guía o póngase en contacto con el equipo de NEQUI.