Órdenes
Una orden es el movimiento real de dinero: referencia una
cotización, nombra un remitente y un destinatario, y dice
adónde llega el dinero. Su status avanza por un ciclo de vida — consulta
Estado de la orden.
create_order
Esto mueve dinero real. Construye el request a partir del id de una cotización, y
después crea la orden con una idempotency key.
Firma
def create_order(
self,
request: CreateOrderRequest,
idempotency_key: Optional[str] = None,
) -> Order:
Parámetros
| Nombre | Tipo | Requerido | Descripción |
|---|---|---|---|
request | CreateOrderRequest | Sí | La orden a crear. |
idempotency_key | Optional[str] | No | String libre, no un UUID. Por defecto se genera un UUID si se omite — pasa el tuyo (p. ej. un número de factura) para que los reintentos sean seguros y correlacionables. Se manda como X-Idempotency-Key y vuelve como idempotency_key. |
Campos de CreateOrderRequest (constrúyelo con CreateOrderRequest.with_purpose(...) para
pasar purpose como enum):
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
quotation_id | str | Sí | Tiene que ser un UUID — el id de create_quotation/get_quotation. |
sender | OrderSender | Sí | Quién envía el dinero. |
sender.names / sender.last_names | str | Sí | No vacío. |
sender.document | PersonDocument | Sí | type, id (ambos no vacíos), country (2 letras en mayúsculas). |
sender.country | str | Sí | ISO alpha-2 de 2 letras en mayúsculas. |
sender.phone | Optional[str] | No | Formato E.164 (p. ej. "+584121234567"), se valida cuando no es None. Solo vuelve en las lecturas si lo enviaste al crear. |
sender.email | Optional[str] | No | Se valida como dirección de email cuando no es None. Solo vuelve en las lecturas si lo enviaste al crear. |
destination | OrderDestination | Sí | Adónde llega el dinero. |
destination.receiver | OrderReceiver | Sí | names, last_names (no vacíos), document (como arriba), country (2 letras en mayúsculas), email (opcional, se valida si está presente). |
destination.payment_instructions | PaymentInstructions | Sí | Exactamente uno de bank_account o bank_phone_account — si se configuran ambos, o ninguno, se lanza un error. |
destination.payment_instructions.bank_account | Optional[BankAccount] | Uno de los dos | Constrúyelo con BankAccount.of(FinancialEntity, number, BankAccountType). bank_name se valida contra el catálogo FinancialEntity; type es BankAccountType.CHECKING / SAVINGS / CORRIENTE / AHORRO. |
destination.payment_instructions.bank_phone_account | Optional[BankPhoneAccount] | Uno de los dos | Constrúyelo con BankPhoneAccount.of(FinancialEntity, phone_number, document_id=None). phone_number tiene que ser E.164 (p. ej. "+584121234567"); document_id cae de vuelta a destination.receiver.document.id si se omite. |
purpose | OrderPurpose (vía with_purpose) / str | Sí | Uno de FAMILY_SUPPORT, PAYMENT_OF_BILLS, PURCHASE_OF_GOODS, PROFESSIONAL_SERVICES, RENTAL_OF_SPACES, INVESTMENTS, COMMISSIONS_LOANS_CAPITAL_INCREASE, TRANSPORTATION_COSTS, FEES. |
metadata | Optional[Dict[str, Any]] | No | Pares clave/valor libres que vuelven en la orden. |
Retorno
Order:
| Campo | Tipo | Descripción |
|---|---|---|
id | Optional[str] | String opaco, de 1 a 100 caracteres — no necesariamente un UUID. |
type | Optional[str] | Tipo de movimiento interno, p. ej. "WALLET_TO_POG". |
status | Optional[str] | Estado del ciclo de vida — consulta Estado de la orden. |
quotation_id | Optional[str] | La cotización con la que se armó esta orden. |
idempotency_key | Optional[str] | Tu idempotency_key, tal cual vuelve. |
amount / currency | Optional[str] | El monto liquidado, como string decimal. |
metadata | Optional[Dict[str, Any]] | Lo que hayas mandado, tal cual vuelve. |
purpose | Optional[str] | Valor de wire del propósito declarado; usa order.purpose_enum para recuperarlo como OrderPurpose. |
sender / destination | Optional[OrderSender] / Optional[OrderDestination] | Vuelven tal cual, con los mismos modelos todo-opcional que el request. |
execution | Optional[OrderExecution] | Tramos source / destination, cada uno un ExecutionLeg (status, reference_id). |
events | Optional[List[OrderEvent]] | Transiciones del ciclo de vida — from_status, to_status, trigger, reason, message, created_at. |
failure_code / failure_reason | Optional[str] | Se definen cuando status es un estado de falla — consulta Detalle de fallas. |
created_at / updated_at | Optional[datetime] | Timestamps. |
Ejemplo
import os, uuid
from retorna_sdk import (
CreateOrderRequest, OrderSender, OrderReceiver, PersonDocument,
OrderDestination, PaymentInstructions, BankPhoneAccount,
FinancialEntity, OrderPurpose,
)
# quotation is a QuotationDetail from create_quotation(); client is the RetornaClient
# from the overview's RetornaClient.create(...)
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"),
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)
Errores
B2B_QUOTATION_NOT_FOUND—quotation_idno existe o no es tuyo.B2B_QUOTATION_EXPIRED— solicita una cotización nueva.B2B_QUOTATION_MISMATCH— el monto, la moneda o el corredor no coinciden con la cotización.B2B_QUOTATION_ALREADY_USED— una cotización es de un solo uso; consulta la nota en el catálogo de errores antes de reintentar nada.B2B_INSUFFICIENT_BALANCE— recarga la wallet primero.B2B_BENEFICIARY_INVALID— datos de pago inválidos o incompletos para el método elegido.B2B_IDEMPOTENCY_CONFLICT— la mismaidempotency_keyse reutilizó con un body distinto.B2B_PROVIDER_REJECTED/B2B_PROVIDER_UNAVAILABLE— la red de pagos rechazó la operación, o no está disponible.B2B_RATE_LIMITED— reintenta con backoff.B2B_UNAUTHORIZED— token o firma de request inválidos.ValueError— se lanza localmente por un rango de campos faltantes o inválidos (consulta la tabla de Parámetros de arriba) antes de que salga cualquier request — incluyendo unsender.phoneque no es E.164 o unsender.emailque no es una dirección válida — más barato que un round trip para un400.
Notas
- Reutiliza la misma
idempotency_keyal reintentar; el servidor deduplica en vez de pagar dos veces. - Cada modelo del request anidado también funciona como modelo de respuesta con campos todo-opcionales, así que una respuesta parcial del servidor igual deserializa.
Referencia de API
list_orders
Lista las órdenes de tu compañía, paginadas por cursor, con filtros opcionales.
Firma
def list_orders(self, params: Optional[ListOrdersParams] = None) -> OrdersListResponse:
Parámetros
ListOrdersParams (todos opcionales; helpers: ListOrdersParams.first_page(limit=...),
ListOrdersParams.by_idempotency_key(idempotency_key)):
| Campo | Tipo | Descripción |
|---|---|---|
cursor | Optional[str] | Cursor opaco del pagination.next_cursor de una página anterior. Omítelo para la primera página — un string vacío es inválido. |
limit | Optional[int] | De 1 a 100. Queda fijo para todo el recorrido una vez que pasas un cursor. |
status | Optional[str] | Filtro por estado de la orden. |
type | Optional[str] | Filtro por tipo de transacción, ej. "WALLET_TO_POG". Libre, un solo valor — a diferencia de status, no acepta una lista separada por comas. |
idempotency_key | Optional[str] | Filtro por tu idempotency key. |
created_from / created_to | Optional[datetime] | Filtro por rango de fechas. |
Retorno
OrdersListResponse:
| Campo | Tipo | Descripción |
|---|---|---|
data | Optional[List[Order]] | La página de órdenes — consulta la tabla de Order en create_order. |
pagination | Optional[CursorPagination] | next_cursor, has_more, limit, total (nullable). Consulta Paginación. |
Ejemplo
from retorna_sdk import ListOrdersParams
first = client.list_orders(ListOrdersParams.first_page(limit=50))
by_invoice = client.list_orders(ListOrdersParams.by_idempotency_key("invoice-2026-00456"))
for order in first.data or []:
print(order.id, order.status, order.idempotency_key)
Errores
B2B_INVALID_PAGINATION_CURSOR— el cursor estaba mal formado, manipulado, o no fue emitido por este listado.B2B_UNAUTHORIZED— token o firma de request inválidos.ValueError— se lanza localmente cuandolimitestá fuera de 1..100, ocursor/idempotency_keyestá presente pero vacío.
Notas
- No existe un parámetro
page— la paginación por offset se eliminó. Mantén los filtros idénticos a lo largo de un recorrido paginado; consulta Paginación.
Referencia de API
get_order
Trae una orden puntual por su id.
Firma
def get_order(self, order_id: str) -> Order:
Parámetros
| Nombre | Tipo | Requerido | Descripción |
|---|---|---|---|
order_id | str | Sí | Id opaco, de 1 a 100 caracteres — no asumas que tiene forma de UUID. |
Retorno
El mismo Order que create_order — consulta la tabla de arriba.
Ejemplo
order = client.get_order(order.id)
print(order.id, order.status, order.failure_code)
Errores
B2B_ORDER_NOT_FOUND— el id no existe o no es tuyo.B2B_UNAUTHORIZED— token o firma de request inválidos.ValueError— se lanza localmente cuandoorder_idesNoneo su longitud está fuera de 1..100 caracteres.
Notas
- Sondea
status(o leeevents) para seguir un pago después de crearlo — consulta Estado de la orden.