En esta página
- Problemas de Autenticación
- Error 401 Unauthorized
- Problemas de Creación de Orden
- Método de Pago No Soportado
- Número de Documento Inválido
- Orden Duplicada / Problemas de Idempotencia
- Problemas de Flujo de Pago
- Orden Atascada en PENDING
- Pago Completado pero No se Recibió Webhook
- Verificar endpoint de webhook
- Probar con webhook.site
- Verificar logs de webhook
- Verificar validación de firma
- Implementar sondeo de respaldo
- Firma de Webhook Inválida
- Próximos Pasos
Solución de Problemas para Aceptar Pagos
Soluciones a problemas comunes al integrar la aceptación de pagos.
Problemas de Autenticación
Error 401 Unauthorized
Problema: La API devuelve 401 Unauthorized
Causas:
- API key o secret inválido
- Token expirado
- Token no incluido en la solicitud
Soluciones:
Verificar credenciales
// Verificar que las credenciales son correctas
console.log('API Key:', process.env.KOYWE_API_KEY);
console.log('Secret:', process.env.KOYWE_SECRET ? '***' : 'FALTANTE');
// Probar autenticación
try {
const token = await authenticate();
console.log('✓ Autenticación exitosa');
} catch (error) {
console.error('✗ Autenticación fallida:', error.response?.data);
}Actualización de token
// Implementar caché de token con actualización
class KoyweClient {
constructor(apiKey, secret) {
this.apiKey = apiKey;
this.secret = secret;
this.token = null;
this.tokenExpiry = null;
}
async getToken() {
// Devolver token en caché si aún es válido (con buffer de 5 minutos)
if (this.token && this.tokenExpiry > Date.now() + (5 * 60 * 1000)) {
return this.token;
}
// Obtener nuevo token
const response = await axios.post(
'https://api-sandbox.koywe.com/api/v1/auth/sign-in',
{ apiKey: this.apiKey, secret: this.secret }
);
this.token = response.data.token;
this.tokenExpiry = Date.now() + (60 * 60 * 1000); // 1 hora
return this.token;
}
async request(method, url, data) {
const token = await this.getToken();
try {
return await axios({
method,
url,
data,
headers: { 'Authorization': `Bearer ${token}` }
});
} catch (error) {
// Si 401, token pudo haber expirado, reintentar una vez
if (error.response?.status === 401) {
this.token = null; // Limpiar token
const newToken = await this.getToken();
return await axios({
method,
url,
data,
headers: { 'Authorization': `Bearer ${newToken}` }
});
}
throw error;
}
}
}Problemas de Creación de Orden
Método de Pago No Soportado
Problema: Error “Método de pago no soportado para país/moneda”
Causa: Usar un método de pago que no está disponible para el país o moneda objetivo
Solución:
async function createOrderSafely(country, currency, amount, preferredMethod) {
// 1. Obtener métodos disponibles
const methods = await getPaymentMethods(country, currency);
console.log('Métodos disponibles:', methods.map(m => m.method));
// 2. Verificar si el método preferido está disponible
const methodAvailable = methods.some(m => m.method === preferredMethod);
if (!methodAvailable) {
console.error(`Método ${preferredMethod} no disponible`);
console.log('Usar uno de:', methods.map(m => m.method));
throw new Error('Método de pago no soportado');
}
// 3. Crear orden
return await createPayinOrder(token, orgId, merchantId, {
originCurrencySymbol: currency,
destinationCurrencySymbol: currency,
amountIn: amount,
paymentMethods: [{ method: preferredMethod }]
// ... otros campos
});
}Errores comunes:
- Usar
SPEIpara Colombia (usarPSEen su lugar) - Usar
PSEpara Brasil (usarPIXen su lugar) - Moneda incorrecta para método de pago
Número de Documento Inválido
Problema: Error “Formato de número de documento inválido”
Causa: El número de documento no coincide con el formato esperado para el tipo de documento
Solución:
function validateDocument(country, documentType, documentNumber) {
const validators = {
'CO': {
'CED_CIU': /^\d{1,15}$/, // Cédula de Ciudadanía (CC): solo dígitos
'CED_EXT': /^\d{1,15}$/, // Cédula de Extranjería (CE): solo dígitos
'NIT': /^(?:\d{3}\.\d{3}\.\d{3}-\d|\d{10})$/ // NIT: 900.123.456-1 o 9001234561
},
'BR': {
'CPF': /^\d{11}$/, // Individual: 11 dígitos
'CNPJ': /^[0-9A-Za-z]{12}\d{2}$/ // Empresa: 14 caracteres, numérico o alfanumérico (12 letras/números + 2 dígitos verificadores)
},
'MX': {
'RFC': /^[A-Z]{3,4}\d{6}[A-Z0-9]{3}$/ // Formato ID fiscal
},
'CL': {
'RUT': /^\d{7,8}-[\dkK]$/ // Formato: 12345678-9
}
};
const regex = validators[country]?.[documentType];
if (!regex) {
console.warn(`No hay validador para ${country} ${documentType}`);
return true; // Permitir si no hay validador
}
// CPF y CNPJ se aceptan con o sin formato: se quita antes de validar
const value = country === 'BR' ? documentNumber.replace(/[.\/-]/g, '') : documentNumber;
const isValid = regex.test(value);
if (!isValid) {
console.error(`Formato ${documentType} inválido: ${documentNumber}`);
console.log(`Formato esperado: ${regex}`);
}
return isValid;
}
// Uso
const isValid = validateDocument('CO', 'CED_CIU', '1234567890');
if (!isValid) {
throw new Error('Número de documento inválido');
}Orden Duplicada / Problemas de Idempotencia
Problema: Crear órdenes duplicadas u obtener error “Orden ya existe”
Solución: Usar externalId para idempotencia
async function createOrderIdempotent(internalOrderId, orderData) {
// Usar tu ID de orden interno como externalId
const externalId = `order-${internalOrderId}`;
try {
const order = await createPayinOrder(token, orgId, merchantId, {
...orderData,
externalId: externalId // Mismo externalId = misma orden
});
console.log('Orden creada:', order.id);
return order;
} catch (error) {
if (error.response?.status === 409) {
// Orden ya existe, recuperarla
console.log('Orden ya existe, recuperando...');
const existingOrder = await getOrderByExternalId(externalId);
return existingOrder;
}
throw error;
}
}
// Seguro para reintentar
const order = await createOrderIdempotent('12345', orderData);Problemas de Flujo de Pago
Orden Atascada en PENDING
Problema: La orden permanece en estado PENDING y nunca se completa
Causas en Producción:
- Cliente no ha completado el pago
- URL de pago expiró
- Problemas con proveedor de pago
Causas en Sandbox:
- Usar monto de prueba de fallo (666)
- Problemas de red
Soluciones:
async function checkOrderStatus(orderId) {
const order = await getOrderStatus(token, orgId, merchantId, orderId);
console.log('Estado:', order.status);
console.log('Creada:', order.createdAt);
console.log('Fecha límite:', order.dueDate);
if (order.status === 'PENDING') {
const createdTime = new Date(order.createdAt);
const now = new Date();
const minutesElapsed = (now - createdTime) / 1000 / 60;
console.log(`Orden pendiente por ${minutesElapsed.toFixed(0)} minutos`);
if (minutesElapsed > 30) {
console.warn('Orden pendiente por demasiado tiempo - cliente puede no haber pagado');
// Considerar cancelar o expirar la orden
}
}
return order;
}En Sandbox:
- Las órdenes se completan automáticamente después de 5-30 segundos
- Si usas monto 666, la orden fallará (esto es intencional para pruebas)
Pago Completado pero No se Recibió Webhook
Problema: La orden muestra COMPLETED en API pero no se recibió webhook
Causas:
- Endpoint de webhook no configurado
- Endpoint de webhook no alcanzable
- Endpoint de webhook devolviendo errores
- Firewall bloqueando webhooks
Soluciones:
Verificar endpoint de webhook
Verifica que tu endpoint sea públicamente accesible
Probar con webhook.site
Usa para ver si los webhooks están siendo enviados
Verificar logs de webhook
Consulta intentos de entrega de webhook:
JavaScriptconst deliveries = await getWebhookDeliveries(orderId); console.log(deliveries);Verificar validación de firma
Asegúrate de no estar rechazando webhooks por fallo en verificación de firma
Implementar sondeo de respaldo
Como respaldo, hacer polling del estado de orden:
JavaScriptasync function waitForCompletion(orderId, maxAttempts = 20) { for (let i = 0; i < maxAttempts; i++) { const order = await getOrderStatus(token, orgId, merchantId, orderId); if (order.status === 'COMPLETED') { return order; } if (['FAILED', 'EXPIRED', 'CANCELLED'].includes(order.status)) { throw new Error(`Orden ${order.status}`); } // Esperar 3 segundos antes de próxima verificación await new Promise(resolve => setTimeout(resolve, 3000)); } throw new Error('Tiempo de espera agotado'); }
Firma de Webhook Inválida
Problema: La verificación de firma de webhook falla
Causa: Cálculo de firma incorrecto o secret incorrecto
Solución:
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
// payload debe ser el body crudo (string o Buffer)
// NO JSON parseado
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
const isValid = signature === expectedSignature;
if (!isValid) {
console.error('Firma no coincide');
console.error('Recibida:', signature);
console.error('Esperada:', expectedSignature);
}
return isValid;
}
// Ejemplo Express - DEBE usar body crudo
app.post('/webhooks/koywe',
express.raw({ type: 'application/json' }), // Importante: raw, no json
(req, res) => {
const signature = req.headers['koywe-signature'];
const secret = process.env.KOYWE_WEBHOOK_SECRET;
// req.body es Buffer al usar express.raw
if (!verifyWebhookSignature(req.body, signature, secret)) {
return res.status(401).send('Firma inválida');
}
// Ahora parsear
const event = JSON.parse(req.body);
console.log('Evento webhook válido:', event.type);
// Procesar evento...
res.status(200).send('OK');
}
);