Saltar al contenido principal

SDK de Python

Un cliente tipado y fluido para la API B2B de Retorna: crea una cotización, conviértela en una orden, revisa tu wallet y lista las rutas habilitadas para tu compañía — sin construir requests HTTP ni firmar nada a mano. Está fijado a la versión 1.0.4, y esta página sigue esa versión.

Instalación​

pip install retorna-gateway-sdk
# or
uv add retorna-gateway-sdk
# or
poetry add retorna-gateway-sdk

Necesitas Python 3.9+. La única dependencia de runtime es cryptography, usada para parsear la clave RSA y firmar los requests — todo lo demás es librería estándar. El paquete se llama retorna-gateway-sdk; el módulo que importas es retorna_sdk.

Configurar el cliente​

Todos los ejemplos de esta página corren contra sandbox. Crea el cliente una sola vez y reutilízalo — guarda tus credenciales y un pool de conexiones, no un request puntual.

import os
from retorna_sdk import RetornaClient

client = RetornaClient.create(
environment="SANDBOX",
client_id=os.environ["RETORNA_CLIENT_ID"],
client_secret=os.environ["RETORNA_CLIENT_SECRET"],
private_key=os.environ["RETORNA_PRIVATE_KEY"], # PKCS#8 PEM
)

También existe un builder, si prefieres construir la configuración paso a paso — RetornaClientBuilder().environment("SANDBOX")...build_client() te da el mismo cliente. Presta atención al nombre: build_client() devuelve un RetornaClient listo para usar; el más corto build() solo devuelve el objeto de configuración resuelto, no un cliente que puedas invocar.

create() acepta estos argumentos:

OpciónDefaultNotas
environmentPRODUCTIONConfigúralo explícitamente — el default no es sandbox
scopedefault del ambienteScope de OAuth2, rara vez se sobreescribe
logging_levelERRORVerbosidad del logging interno del SDK
retries3Reintentos automáticos ante fallas transitorias
backoff_ms200Delay base entre reintentos
connect_timeout10.0Segundos
request_timeout10.0Segundos
ssl_contextningunossl.SSLContext personalizado, para configuraciones TLS avanzadas
base_url_overrideningunoApunta a un host no estándar, por ejemplo para testing local

Ambientes​

AmbienteURL base
DEVELOPhttps://api.gateway.dev.retorna.app
SANDBOXhttps://api.gateway.sandbox.retorna.app
PRODUCTIONhttps://api.gateway.retorna.app

Tu primer llamado​

Confirma que el cliente quedó bien configurado antes de hacer cualquier otra cosa:

me = client.get_my_client()
print(me.client.name, me.client.is_active, me.client.permissions)

Cotización → orden​

Una cotización fija un tipo de cambio por una ventana corta; referencia su id al crear la orden. La documentación completa de cada método — todos los campos, valores de retorno y códigos de error — vive en las páginas de Cotizaciones y Órdenes; aquí va la forma de todo el flujo en un solo ejemplo:

import os, uuid
from retorna_sdk import (
CreateQuotationRequest, QuotationSource, QuotationDestination,
QuotationPayoutMethod, QuotationQuote, QuoteMode, PayoutMethodType,
CreateOrderRequest, OrderSender, OrderReceiver, PersonDocument,
OrderDestination, PaymentInstructions, BankPhoneAccount,
FinancialEntity, OrderPurpose,
)

quotation = client.create_quotation(CreateQuotationRequest(
source=QuotationSource(currency="USDR"),
destination=QuotationDestination(
country="VE", currency="VES",
payout_method=QuotationPayoutMethod(type=PayoutMethodType.P2P_PHONE_TRANSFER)),
quote=QuotationQuote(mode=QuoteMode.SEND_EXACT, amount="56.00"),
))

request = CreateOrderRequest.with_purpose(
quotation_id=quotation.id,
sender=OrderSender(names="Ana María", last_names="Pérez Gómez",
document=PersonDocument(type="CC", id="1020304050", country="CO"),
country="CO"),
destination=OrderDestination(
receiver=OrderReceiver(names="Carlos Andrés", last_names="Rodríguez López",
document=PersonDocument(type="V", id="12345678", country="VE"),
email="[email protected]", country="VE"),
payment_instructions=PaymentInstructions.of_bank_phone_account(
BankPhoneAccount.of(FinancialEntity.BNC_VE, "+584121234567", "12345678"))),
purpose=OrderPurpose.FAMILY_SUPPORT,
)

idempotency_key = f"invoice-{uuid.uuid4()}" # free-form str, NOT a uuid.UUID
if os.environ.get("RETORNA_CREATE_ORDER") == "yes":
order = client.create_order(request, idempotency_key)
print(order.id, order.status, order.idempotency_key)

QuoteMode.SEND_EXACT significa que quien envía se desprende de exactamente 56.00; RECEIVE_EXACT describe lo que recibe el destinatario en cambio. idempotency_key es un str común, no un tipo UUID, y vuelve como idempotency_key en la orden — si reutilizas esa key con un body distinto, la respuesta es B2B_IDEMPOTENCY_CONFLICT (409) en vez de la orden original. La verificación de RETORNA_CREATE_ORDER de arriba solo está para que este snippet se pueda copiar y ejecutar sin mover dinero real por accidente — quítalo de tu propio código cuando estés listo.

Objetos​

Cotizaciones — fijan un tipo de cambio, comisiones y montos antes de crear una orden. Consulta Cotizaciones para create_quotation y get_quotation.

Órdenes — el movimiento real de dinero, creado a partir de una cotización. Consulta Órdenes para create_order, list_orders y get_order.

Rutas — los corredores (país, moneda y método de pago de destino) habilitados para tu compañía. Consulta Rutas para get_routes.

Wallets — el balance de tu compañía y su historial de recargas. Consulta Wallets para get_my_wallet y get_my_wallet_transactions.

Cliente — a quién pertenecen las credenciales. Consulta Cliente para get_my_client.

Errores​

Todo error que lanza el SDK es una de tres clases, todas terminadas en Error — no Exception — y todas importables desde el paquete de nivel superior. RetornaAuthError, RetornaB2BError y RetornaSdkError extienden todas de RetornaError, así que un solo except RetornaError atrapa todo lo que lanza el SDK:

from retorna_sdk import RetornaAuthError, RetornaB2BError, RetornaSdkError

try:
order = client.create_order(request, idempotency_key)
except RetornaB2BError as e:
if e.code == "B2B_IDEMPOTENCY_CONFLICT":
... # same key, different payload
elif e.is_retryable:
... # a TRANSIENT category error
print(e.code, e.code_enum, e.http_status, e.correlation_id)
except RetornaAuthError as e:
... # token OR signature — the API never says which
except RetornaSdkError as e:
print(e.context, e.cause) # transport / JSON / unstructured 5xx

RetornaB2BError lleva el code de la API tal cual, como string, más code_enum para hacer coincidencias exhaustivas e is_retryable para decidir rápido si conviene reintentar. Para la lista completa de códigos y qué significa cada uno, consulta el catálogo de errores y la guía de manejo de errores — esta página no duplica esa tabla a propósito.

Trampas comunes​

La clave tiene que ser PKCS#8

private_key tiene que ser un PEM PKCS#8 (-----BEGIN PRIVATE KEY-----). Una clave PKCS#1 (-----BEGIN RSA PRIVATE KEY-----) falla inmediatamente con un error accionable al construir el cliente — conviértela primero:

openssl pkcs8 -topk8 -nocrypt -in key.pem -out key-pkcs8.pem
El dinero es un string

Los montos siempre son str en la API — "56.00", nunca 56.0. No los coloques en un float: IEEE-754 no puede representar 0.1 exactamente, y un error de redondeo en un monto de pago es un bug real. Usa retorna_sdk.core.money.parse / .format cuando necesites hacer aritmética real, y vuelve a str antes de enviar el valor.

Todo 401 se ve igual

Un bearer token inválido y una firma de request inválida vuelven como el mismo RetornaAuthError con un 401 escueto. Ni el SDK ni la API te dicen cuál de los dos falló. Si te pasa esto, revisa ambos por separado en vez de asumir que siempre es el token.

Próximos pasos​