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.HttpClientpropio 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();
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
| Constante | URL base |
|---|---|
RetornaEnvironment.SANDBOX | https://api.gateway.sandbox.retorna.app |
RetornaEnvironment.PRODUCTION | https://api.gateway.retorna.app |
RetornaEnvironment.DEVELOP | Uso 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"),
"[email protected]", "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
- Autenticación — qué hace el SDK por detrás en el intercambio de token.
- Firma de requests — las reglas de firma que el SDK aplica en cada llamada.
- Idempotencia — las reglas completas detrás
de la clave de
createOrder. - Paginación — el contrato de cursor compartido por cada listado paginado.
- Códigos de error y Manejo de errores — la referencia completa de errores.