Saltar al contenido principal

SDK de Java

Un cliente tipado para la API de Retorna B2B. Envuelve las llamadas HTTP crudas del quickstart para que dejes de construir a mano el intercambio de token OAuth2 y la firma de cada request: el SDK obtiene y cachea el token por ti, y firma cada request saliente automáticamente. Si quieres entender qué está haciendo en tu nombre, lee autenticación y firma de requests — el SDK sigue esas reglas al pie de la letra, simplemente te ahorra implementarlas.

Requisitos​

  • Java 17 o superior.
  • Jackson es la única dependencia de runtime (jackson-databind + jackson-datatype-jsr310), incluida de forma transitiva — no la declaras tú mismo.
  • No trae ninguna librería HTTP embebida. Los requests salen por el java.net.http.HttpClient propio del JDK, así que no tienes nada más que agregar.

Instalación​

Maven

<dependency>
<groupId>app.retorna</groupId>
<artifactId>retorna-gateway-sdk</artifactId>
<version>1.0.3</version>
</dependency>

Gradle (Kotlin DSL)

implementation("app.retorna:retorna-gateway-sdk:1.0.3")

Gradle (Groovy DSL)

implementation 'app.retorna:retorna-gateway-sdk:1.0.3'

Configura el cliente​

import app.retorna.sdk.RetornaClient;
import app.retorna.sdk.RetornaClientBuilder;
import app.retorna.sdk.core.environment.RetornaEnvironment;

RetornaClient client = new RetornaClientBuilder()
.environment(RetornaEnvironment.SANDBOX)
.clientId(System.getenv("RETORNA_CLIENT_ID"))
.clientSecret(System.getenv("RETORNA_CLIENT_SECRET"))
.privateKey(System.getenv("RETORNA_PRIVATE_KEY")) // PKCS#8 PEM
.retries(3)
.backoffMs(200L)
.buildClient();
La clave privada no es opcional

clientId/clientSecret te consiguen un token; la clave privada firma cada request. Son dos verificaciones independientes, así que un cliente construido sin clave igual recibe un 401 — uno que se lee exactamente como credenciales inválidas, aunque el token estuviera perfecto. Configura los tres.

La clave tiene que ser un PEM PKCS#8 (-----BEGIN PRIVATE KEY-----). Funcionan los saltos de línea reales, los escapados como \n y también la forma aplanada en una sola línea — lo que sea que te entregue tu gestor de secretos funciona. Se decodifica una sola vez, al construir el cliente, así que una clave mal formada falla de inmediato en lugar de en tu primer request.

build() no es buildClient()

RetornaClientBuilder tiene dos métodos terminales. build() solo valida tu configuración y devuelve un SdkConfig — no te da un cliente funcional. buildClient() hace las dos cosas: valida y devuelve el RetornaClient que realmente vas a usar. Usa buildClient().

Entornos​

ConstanteURL base
RetornaEnvironment.SANDBOXhttps://api.gateway.sandbox.retorna.app
RetornaEnvironment.PRODUCTIONhttps://api.gateway.retorna.app
RetornaEnvironment.DEVELOPUso interno únicamente — sin URL pública.

Elegir un entorno es el único paso de configuración: el SDK resuelve la URL base correcta por ti, así que nada más en tu código cambia entre sandbox y producción salvo el valor del enum y tus credenciales.

Sub-clientes​

Cada método que ves abajo también se puede llamar a través de un sub-cliente específico del dominio, y las dos formas ejecutan el mismo código:

client.createQuotation(request); // flat method
client.quotations().createQuotation(request); // same call, through the sub-client

RetornaClient expone un accesor por cada objeto de dominio: quotations(), orders(), wallets(), routes(), clients(). Usa el sub-cliente cuando necesitas pasar el cliente de dominio como su propia dependencia (un campo QuotationsClient, por ejemplo) en lugar de todo el RetornaClient; en cualquier otro caso los métodos planos funcionan igual de bien.

Primera llamada​

Una forma rápida de confirmar que tus credenciales y tu firma son correctas antes de tocar movimiento de dinero:

ClientResponse me = client.getMyClient();
System.out.printf("%s (active=%s)%n", me.client().name(), me.client().isActive());

Si esto lanza una excepción, salta a Manejo de errores más abajo — la excepción te dice si el problema fue tu token o tu firma.

Cotización → orden​

Todo pago empieza con una cotización, y después una orden que la referencia:

QuotationDetail quote = client.createQuotation(new CreateQuotationRequest(
new QuotationSource("USDR"),
new QuotationDestination("VE", "VES",
new QuotationPayoutMethod(PayoutMethodType.P2P_PHONE_TRANSFER)),
new QuotationQuote(QuoteMode.SEND_EXACT, "10.00")));

System.out.printf("rate=%s, receiver gets %s %s%n",
quote.exchangeRate().value(), quote.target().amount(), quote.target().currency());

String idempotencyKey = "invoice-2026-0042"; // your own reference

Order order = client.createOrder(
CreateOrderRequest.withPurpose(
quote.id(),
null, // metadata, echoed back untouched
OrderPurpose.FAMILY_SUPPORT, // required, closed catalog
new OrderSender("Ana María", "Pérez Gómez",
new PersonDocument("CC", "1020304050", "CO"), "CO"),
new OrderDestination(
new OrderReceiver("Carlos Andrés", "Rodríguez López",
new PersonDocument("V", "12345678", "VE"),
PaymentInstructions.ofBankPhoneAccount(
BankPhoneAccount.of(FinancialEntity.BNC_VE,
"+584121234567", "12345678")))),
idempotencyKey);

System.out.printf("Order %s status=%s idempotencyKey=%s%n",
order.id(), order.status(), order.idempotencyKey());

Una cotización es de un solo uso: respalda exactamente una orden, para siempre. Pasa una clave de idempotencia real en createOrder — un número de factura es mejor que un UUID al azar, porque vuelve como order.idempotencyKey() y te deja encontrar esta orden después. Reutiliza la misma clave si reintentas; el servidor deduplica por ti. No existe un campo idempotencyKey en el request — solo en la respuesta — así que no intentes mandar uno.

El bankName de la cuenta bancaria sale de un catálogo cerrado: constantes de FinancialEntity como BANCO_BANESCO_VE, nunca el nombre comercial del banco. Consulta la lista completa en entidades bancarias.

Objetos​

Cada objeto de dominio tiene su propia página con todos sus métodos, sus parámetros, la forma de su respuesta y los errores que puede lanzar:

  • Cotizaciones — cotiza un corredor y un método de pago antes de comprometerte a una transferencia: createQuotation, getQuotation.
  • Órdenes — crea, lee y lista los pagos en sí: createOrder, getOrder, listOrders.
  • Rutas — los corredores que tu compañía tiene configurados para enviar, con sus tasas, comisiones y límites: getRoutes.
  • Wallets — el balance de tu compañía y su historial de recargas: getMyWallet, getMyWalletTransactions.
  • Cliente — identidad y permisos del cliente autenticado: getMyClient.

Manejo de errores​

El SDK lanza uno de tres tipos de excepción, todas subclases de RetornaException:

try {
Order order = client.createOrder(request, idempotencyKey);
} catch (RetornaB2BException e) {
if (e.codeEnum().isRetryable()) {
// TRANSIENT — retry with the SAME idempotency key
} else {
System.err.printf("%s [%s, http=%d] %s (correlationId=%s)%n",
e.code(), e.category(), e.httpStatus(), e.getMessage(), e.correlationId());
}
} catch (RetornaAuthException e) {
System.err.printf("Auth failed (HTTP %d): %s%n", e.httpStatus(), e.getMessage());
} catch (RetornaSdkException e) {
System.err.printf("SDK error in %s: %s%n", e.context(), e.getMessage());
}

Ramifica según code() — el string crudo que viaja en el wire — en vez de getMessage(), que está escrito para humanos y puede cambiar. codeEnum() cae con gracia a un valor desconocido y no reintentable cuando el backend agrega un código que esta versión del SDK todavía no conoce, así que tu lógica de ramificación nunca se rompe con una actualización.

Esta página no repite el catálogo de errores. Consulta códigos de error para la lista completa de códigos, estados HTTP y categorías, y manejo de errores para entender cómo se relacionan las distintas formas de falla.

Un cliente, muchos threads​

Construye un único RetornaClient cuando arranca tu aplicación y reutilízalo en cada request — no construyas uno por llamada. Por debajo mantiene una única instancia de java.net.http.HttpClient, creada una sola vez al construirse y reutilizada para todas las llamadas salientes. Es seguro compartirlo: la caché del token se refresca de forma segura bajo acceso concurrente, y firmar un request no toca ningún estado mutable compartido. RetornaClient implementa AutoCloseable, así que try-with-resources funciona, aunque hoy cerrarlo no libera nada.

Cada request saliente también lleva un header X-Retorna-Client que identifica al SDK y su versión — un dato útil si alguna vez necesitas referenciar un request en una conversación de soporte.

Hacia dónde seguir​