Cotizaciones y Tasas de Cambio
Las Cotizaciones proporcionan información de tasas de cambio y te permiten bloquear tasas por un período específico antes de crear órdenes.
¿Qué son las Cotizaciones?
Una Cotización es una solicitud de información de tasa de cambio que devuelve:
- Tasa de cambio actual entre monedas
- Desglose de comisiones e impuestos
- Tiempo de expiración (cuánto tiempo es válida la tasa)
- Montos esperados (entrada y salida)
Tiempo de Vida (TTL) de Cotización
Validez Corta: Las cotizaciones son válidas por 10-15 segundos solamente. Después de la expiración, debes solicitar una nueva cotización. Siempre verifica el campo validForSeconds o validUntil.
¿Por qué tan corto?
- Los mercados de cripto y forex son volátiles
- Asegura precios precisos y en tiempo real
- Protege contra manipulación de precios
- Previene uso de tasas obsoletas
Cuándo Usar Cotizaciones
Usa cotizaciones cuando:
- Necesites mostrar a los clientes el monto exacto que recibirán
- Conviertas entre monedas (BALANCE_TRANSFER)
- Compres o vendas cripto (ONRAMP/OFFRAMP)
- Quieras transparencia en las tasas
Opcional pero Recomendado: Las cotizaciones son opcionales para la mayoría de tipos de orden pero muy recomendadas para transparencia y mejor experiencia de usuario.
Crear una Cotización
Solicitud Básica de Cotización
async function getQuote(token, orgId, merchantId, quoteData) {
const response = await axios.post(
`https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/quotes`,
{
orderType: quoteData.orderType, // Tipo de orden (PAYIN, PAYOUT, BALANCE_TRANSFER, ONRAMP, OFFRAMP)
executable: true, // Si la cotización puede usarse para crear una orden
originCurrencySymbol: quoteData.from, // Moneda origen
destinationCurrencySymbol: quoteData.to, // Moneda destino
amountIn: quoteData.amount // Monto a convertir
},
{
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}
);
return response.data;
}
// Uso - Obtener cotización para BALANCE_TRANSFER
const quote = await getQuote(token, orgId, merchantId, {
orderType: 'BALANCE_TRANSFER',
from: 'COP',
to: 'USD',
amount: 1000000 // 1,000,000 COP
});
console.log('Tasa de cambio:', quote.exchangeRate);
console.log('Recibirás:', quote.finalAmountOut, 'USD');
console.log('Válido por:', quote.validForSeconds, 'segundos');def get_quote(token, org_id, merchant_id, quote_data):
response = requests.post(
f'https://api-sandbox.koywe.com/api/v1/organizations/{org_id}/merchants/{merchant_id}/quotes',
json={
'orderType': quote_data['order_type'],
'executable': True,
'originCurrencySymbol': quote_data['from'],
'destinationCurrencySymbol': quote_data['to'],
'amountIn': quote_data['amount']
},
headers={
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
}
)
return response.json()
# Uso
quote = get_quote(token, org_id, merchant_id, {
'order_type': 'BALANCE_TRANSFER',
'from': 'COP',
'to': 'USD',
'amount': 1000000
})
print(f"Tasa de cambio: {quote['exchangeRate']}")
print(f"Recibirás: {quote['finalAmountOut']} USD")curl -X POST 'https://api-sandbox.koywe.com/api/v1/organizations/TU_ORG_ID/merchants/TU_MERCHANT_ID/quotes' \
-H 'Authorization: Bearer TU_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"orderType": "BALANCE_TRANSFER",
"executable": true,
"originCurrencySymbol": "COP",
"destinationCurrencySymbol": "USD",
"amountIn": 1000000
}'Respuesta:
{
"id": "qte_abc123xyz",
"orderType": "BALANCE_TRANSFER",
"originCurrencySymbol": "COP",
"destinationCurrencySymbol": "USD",
"requestedAmountIn": 1000000,
"finalAmountIn": 1000000,
"finalAmountOut": 250,
"exchangeRate": 4000,
"fee": 5000,
"taxes": 0,
"validForSeconds": 300,
"validUntil": "2025-11-13T15:05:00Z",
"components": [
{ "type": "FEE", "code": "PROCESSING_FEE", "name": "Processing Fee", "currency": "COP", "calculatedAmount": 5000 }
],
"summary": {
"totalBaseFeesInOriginCurrency": 5000,
"totalTaxOnFeeInOriginCurrency": 0,
"totalEffectiveFeeInOriginCurrency": 5000,
"amountToBeConverted": 995000
}
}Campos de Respuesta de Cotización
| Campo | Tipo | Descripción |
|---|---|---|
id | string | Identificador único de cotización (usar en órdenes) |
orderType | string | Tipo de orden (PAYIN, BALANCE_TRANSFER, etc.) |
originCurrencySymbol | string | Moneda origen |
destinationCurrencySymbol | string | Moneda destino |
requestedAmountIn | number | Monto de entrada solicitado originalmente |
requestedAmountOut | number | Monto de salida solicitado originalmente |
finalAmountIn | number | Monto total que paga el usuario (moneda origen) |
finalAmountOut | number | Monto total que recibe el usuario (moneda destino) |
exchangeRate | number | Tasa de cambio efectiva aplicada |
fee | number | Total de comisiones cobradas (comisiones base + impuesto sobre comisiones, en moneda origen) |
taxes | number | Total de impuestos aplicados al monto de la transacción |
validForSeconds | number | Segundos hasta la expiración |
validUntil | string | Timestamp ISO de expiración |
components | array | Desglose detallado de comisiones e impuestos |
summary | object | Resumen de cálculos financieros |
Usar Cotizaciones en Órdenes
Bloqueo de Tasa
Cuando creas una orden con un quoteId, la tasa de cambio se bloquea:
async function createOrderWithQuote(token, orgId, merchantId) {
// 1. Obtener cotización
const quote = await axios.post(
`https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/quotes`,
{
orderType: 'BALANCE_TRANSFER',
executable: true,
originCurrencySymbol: 'COP',
destinationCurrencySymbol: 'USD',
amountIn: 1000000
},
{ headers: { 'Authorization': `Bearer ${token}` } }
);
console.log('Tasa bloqueada en:', quote.data.exchangeRate);
console.log('Válido por:', quote.data.validForSeconds, 'segundos');
// 2. Crear orden con cotización (debe ser dentro del período validFor)
const order = await axios.post(
`https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/orders`,
{
type: 'BALANCE_TRANSFER',
originCurrencySymbol: 'COP',
destinationCurrencySymbol: 'USD',
amountIn: 1000000,
quoteId: quote.data.id // Bloquear la tasa de la cotización
},
{ headers: { 'Authorization': `Bearer ${token}` } }
);
console.log('Orden creada con tasa bloqueada');
return order.data;
}Expiración de Cotización: Las cotizaciones son válidas por un tiempo limitado (típicamente 5 minutos). Si la cotización expira antes de crear la orden, necesitarás obtener una nueva cotización.
Tipos de Cotización por Tipo de Orden
Cotizaciones PAYIN
Para aceptar pagos (típicamente misma moneda, por lo que tasa = 1):
{
"orderType": "PAYIN",
"executable": true,
"originCurrencySymbol": "COP",
"destinationCurrencySymbol": "COP",
"amountIn": 50000
}Respuesta:
{
"exchangeRate": 1,
"finalAmountIn": 50000,
"finalAmountOut": 50000,
"fee": 500,
"taxes": 0
}Cotizaciones BALANCE_TRANSFER
Para conversión de moneda:
{
"orderType": "BALANCE_TRANSFER",
"executable": true,
"originCurrencySymbol": "COP",
"destinationCurrencySymbol": "USD",
"amountIn": 1000000
}Respuesta:
{
"exchangeRate": 4000, // 4,000 COP = 1 USD
"finalAmountOut": 250, // Recibirás 250 USD
"fee": 5000, // 5,000 COP total comisiones
"taxes": 0
}Cotizaciones ONRAMP
Para comprar cripto:
Red Requerida: Las cotizaciones ONRAMP requieren un parámetro network para especificar la red blockchain del activo cripto.
{
"orderType": "ONRAMP",
"executable": true,
"originCurrencySymbol": "COP",
"destinationCurrencySymbol": "USDC",
"amountIn": 50000,
"network": "ETHEREUM" // Requerido para ONRAMP/OFFRAMP
}Redes soportadas:
ETHEREUM(para USDC, USDT, ETH)POLYGON(para USDC, USDT, MATIC)BSC(para USDC, USDT, BNB)BITCOIN(para BTC)TRON(para USDT)
Respuesta:
{
"exchangeRate": 4050, // Incluye precio de cripto
"finalAmountOut": 12.34, // Recibirás 12.34 USDC
"fee": 2500, // Total comisiones (incluye comisión de red)
"taxes": 0
}Cotizaciones OFFRAMP
Para vender cripto:
Red Requerida: Las cotizaciones OFFRAMP requieren un parámetro network para especificar la red blockchain del activo cripto.
{
"orderType": "OFFRAMP",
"executable": true,
"originCurrencySymbol": "USDC",
"destinationCurrencySymbol": "COP",
"amountIn": 10, // 10 USDC
"network": "ETHEREUM" // Requerido para ONRAMP/OFFRAMP
}Respuesta:
{
"exchangeRate": 3950, // Ligeramente menor que tasa de compra (spread)
"finalAmountOut": 39500, // Recibirás 39,500 COP
"fee": 500, // Total comisiones
"taxes": 0
}Comprender las Comisiones
Desglose de Comisiones
Cálculo ejemplo:
Monto Entrada (finalAmountIn): 1,000,000 COP
- Comisiones (fee): -5,000 COP
- Impuestos (taxes): 0 COP
= Monto Neto: 995,000 COP
÷ Tasa Cambio: ÷4,000 COP/USD
= Monto Salida (finalAmountOut): 248.75 USDEl array components en la respuesta proporciona el desglose detallado de cada comisión e impuesto aplicado. El objeto summary proporciona los totales.
Tipos de Comisiones
| Tipo de Comisión | Cuándo se Aplica | Rango Típico |
|---|---|---|
| Comisión de Procesamiento | Todas las transacciones | 0.5% - 2% |
| Comisión Red | Transacciones cripto (ONRAMP/OFFRAMP) | Variable (comisiones blockchain) |
| Impuesto sobre Monto | Según país (ej., IVA) | Varía por país |
| Impuesto sobre Comisión | Según país | Varía por país |
Mostrar Tasas a Usuarios
Visualización Amigable
async function displayQuoteToUser(token, orgId, merchantId, amount) {
const quote = await getQuote(token, orgId, merchantId, {
orderType: 'BALANCE_TRANSFER',
from: 'COP',
to: 'USD',
amount: amount
});
// Formatear para mostrar al usuario
const display = {
amountToSend: `${amount.toLocaleString()} COP`,
willReceive: `${quote.finalAmountOut.toFixed(2)} USD`,
exchangeRate: `1 USD = ${quote.exchangeRate.toLocaleString()} COP`,
totalFees: `${quote.fee.toLocaleString()} COP`,
expiresIn: `${Math.floor(quote.validForSeconds / 60)} minutos`,
effectiveRate: (amount / quote.finalAmountOut).toFixed(2) + ' COP/USD'
};
console.log('Envías:', display.amountToSend);
console.log('Recibes:', display.willReceive);
console.log('Tasa de cambio:', display.exchangeRate);
console.log('Comisiones totales:', display.totalFees);
console.log('Tasa expira en:', display.expiresIn);
console.log('Tasa efectiva:', display.effectiveRate);
return quote;
}
// Uso
await displayQuoteToUser(token, orgId, merchantId, 1000000);Salida:
Envías: 1,000,000 COP
Recibes: 248.75 USD
Tasa de cambio: 1 USD = 4,000 COP
Comisiones totales: 5,000 COP
Tasa expira en: 5 minutos
Tasa efectiva: 4020.00 COP/USDRecuperar una Cotización
Obtener detalles de una cotización existente:
async function getQuoteById(token, orgId, merchantId, quoteId) {
const response = await axios.get(
`https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/quotes/${quoteId}`,
{
headers: { 'Authorization': `Bearer ${token}` }
}
);
return response.data;
}
// Uso
const quote = await getQuoteById(token, orgId, merchantId, 'qte_abc123');
console.log('Estado de cotización:', quote);Mejores Prácticas
Cuándo Obtener una Cotización
Siempre obtén una cotización para:
- Conversiones de moneda (BALANCE_TRANSFER)
- Operaciones cripto (ONRAMP/OFFRAMP)
- Mostrar tasa a usuarios antes de que confirmen
Visualización de Tasa
Muestra a los usuarios:
- Tasa de cambio en términos familiares (1 USD = X COP)
- Comisiones totales claramente separadas
- Tasa efectiva (incluyendo comisiones)
- Tiempo de expiración
Manejo de Expiración
async function createOrderWithAutoRetry(token, orgId, merchantId, orderData) {
let quote = await getQuote(token, orgId, merchantId, {
orderType: orderData.orderType,
from: orderData.from,
to: orderData.to,
amount: orderData.amount
});
try {
// Intentar crear orden con cotización
return await createOrderWithQuote(token, orgId, merchantId, quote.id);
} catch (error) {
if (error.message.includes('expired') || error.message.includes('invalid quote')) {
// Cotización expirada, obtener nueva cotización y reintentar
console.log('Cotización expirada, obteniendo nueva cotización...');
quote = await getQuote(token, orgId, merchantId, {
orderType: orderData.orderType,
from: orderData.from,
to: orderData.to,
amount: orderData.amount
});
return await createOrderWithQuote(token, orgId, merchantId, quote.id);
}
throw error;
}
}Cotización vs Sin Cotización
Con Cotización
// 1. Obtener cotización
const quote = await getQuote(...);
// 2. Mostrar tasa al usuario
console.log('Tasa:', quote.exchangeRate);
// 3. Usuario confirma
// 4. Crear orden con tasa bloqueada
const order = await createOrder({
...orderData,
quoteId: quote.id
});Beneficios:
- Tasa bloqueada
- Usuario sabe exactamente qué recibirá
- Mejor transparencia
Sin Cotización
// Crear orden directamente (usa tasa actual)
const order = await createOrder({
type: 'BALANCE_TRANSFER',
originCurrencySymbol: 'COP',
destinationCurrencySymbol: 'USD',
amountIn: 1000000
// Sin quoteId - usa tasa actual
});Consideraciones:
- Tasa determinada al momento de creación de orden
- Pequeña fluctuación de tasa posible
- Más rápido (una llamada API menos)
Escenarios Comunes
Escenario 1: Mostrar Tasa Antes de Pago
// Usuario ve página de checkout
const quote = await getQuote(token, orgId, merchantId, {
orderType: 'PAYIN',
from: 'COP',
to: 'COP',
amount: 50000
});
// Mostrar al usuario
console.log(`Pagar ${quote.finalAmountIn} COP`);
console.log(`Comisión: ${quote.fee} COP`);
console.log(`Total: ${quote.finalAmountIn} COP`); // finalAmountIn ya incluye comisiones
// Usuario hace clic en "Pagar"
const order = await createOrder({
...orderData,
quoteId: quote.id
});Escenario 2: Herramienta Conversora de Moneda
// Convertidor de moneda en tiempo real
async function convertCurrency(amount, from, to) {
const quote = await getQuote(token, orgId, merchantId, {
orderType: 'BALANCE_TRANSFER',
from: from,
to: to,
amount: amount
});
return {
from: `${amount} ${from}`,
to: `${quote.finalAmountOut} ${to}`,
rate: `1 ${to} = ${quote.exchangeRate} ${from}`,
fee: `${quote.fee} ${from}`
};
}
// Uso
const result = await convertCurrency(1000000, 'COP', 'USD');
console.log(result);
// { from: "1000000 COP", to: "248.75 USD", rate: "1 USD = 4000 COP", fee: "5000 COP" }