Skip to Content
Aceptar PagosSolución de Problemas

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 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); }
// 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 SPEI para Colombia (usar PSE en su lugar)
  • Usar PSE para Brasil (usar PIX en 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': { 'CC': /^\d{6,10}$/, // ID colombiano: 6-10 dígitos 'CE': /^\d{6,7}$/, // ID extranjero: 6-7 dígitos 'NIT': /^\d{9,10}$/ // ID fiscal: 9-10 dígitos }, 'BR': { 'CPF': /^\d{11}$/, // Individual: 11 dígitos 'CNPJ': /^\d{14}$/ // Empresa: 14 dígitos }, '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 } const isValid = regex.test(documentNumber); if (!isValid) { console.error(`Formato ${documentType} inválido: ${documentNumber}`); console.log(`Formato esperado: ${regex}`); } return isValid; } // Uso const isValid = validateDocument('CO', 'CC', '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 https://webhook.site  para ver si los webhooks están siendo enviados

Verificar logs de webhook

Consulta intentos de entrega de webhook:

const 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:

async 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'); } );

Próximos Pasos

Last updated on