Solución de Problemas - Aceptar Pagos

Problemas comunes y soluciones

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:

1// Verificar que las credenciales son correctas
2console.log('API Key:', process.env.KOYWE_API_KEY);
3console.log('Secret:', process.env.KOYWE_SECRET ? '***' : 'FALTANTE');
4
5// Probar autenticación
6try {
7 const token = await authenticate();
8 console.log('✓ Autenticación exitosa');
9} catch (error) {
10 console.error('✗ Autenticación fallida:', error.response?.data);
11}

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:

Consultar métodos disponibles primero
1async function createOrderSafely(country, currency, amount, preferredMethod) {
2 // 1. Obtener métodos disponibles
3 const methods = await getPaymentMethods(country, currency);
4 console.log('Métodos disponibles:', methods.map(m => m.method));
5
6 // 2. Verificar si el método preferido está disponible
7 const methodAvailable = methods.some(m => m.method === preferredMethod);
8
9 if (!methodAvailable) {
10 console.error(`Método ${preferredMethod} no disponible`);
11 console.log('Usar uno de:', methods.map(m => m.method));
12 throw new Error('Método de pago no soportado');
13 }
14
15 // 3. Crear orden
16 return await createPayinOrder(token, orgId, merchantId, {
17 originCurrencySymbol: currency,
18 destinationCurrencySymbol: currency,
19 amountIn: amount,
20 paymentMethods: [{ method: preferredMethod }]
21 // ... otros campos
22 });
23}

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:

Validar formatos de documento
1function validateDocument(country, documentType, documentNumber) {
2 const validators = {
3 'CO': {
4 'CC': /^\d{6,10}$/, // ID colombiano: 6-10 dígitos
5 'CE': /^\d{6,7}$/, // ID extranjero: 6-7 dígitos
6 'NIT': /^\d{9,10}$/ // ID fiscal: 9-10 dígitos
7 },
8 'BR': {
9 'CPF': /^\d{11}$/, // Individual: 11 dígitos
10 'CNPJ': /^\d{14}$/ // Empresa: 14 dígitos
11 },
12 'MX': {
13 'RFC': /^[A-Z]{3,4}\d{6}[A-Z0-9]{3}$/ // Formato ID fiscal
14 },
15 'CL': {
16 'RUT': /^\d{7,8}-[\dkK]$/ // Formato: 12345678-9
17 }
18 };
19
20 const regex = validators[country]?.[documentType];
21 if (!regex) {
22 console.warn(`No hay validador para ${country} ${documentType}`);
23 return true; // Permitir si no hay validador
24 }
25
26 const isValid = regex.test(documentNumber);
27 if (!isValid) {
28 console.error(`Formato ${documentType} inválido: ${documentNumber}`);
29 console.log(`Formato esperado: ${regex}`);
30 }
31
32 return isValid;
33}
34
35// Uso
36const isValid = validateDocument('CO', 'CC', '1234567890');
37if (!isValid) {
38 throw new Error('Número de documento inválido');
39}

Orden Duplicada / Problemas de Idempotencia

Problema: Crear órdenes duplicadas u obtener error “Orden ya existe”

Solución: Usar externalId para idempotencia

Idempotencia adecuada
1async function createOrderIdempotent(internalOrderId, orderData) {
2 // Usar tu ID de orden interno como externalId
3 const externalId = `order-${internalOrderId}`;
4
5 try {
6 const order = await createPayinOrder(token, orgId, merchantId, {
7 ...orderData,
8 externalId: externalId // Mismo externalId = misma orden
9 });
10
11 console.log('Orden creada:', order.id);
12 return order;
13
14 } catch (error) {
15 if (error.response?.status === 409) {
16 // Orden ya existe, recuperarla
17 console.log('Orden ya existe, recuperando...');
18 const existingOrder = await getOrderByExternalId(externalId);
19 return existingOrder;
20 }
21 throw error;
22 }
23}
24
25// Seguro para reintentar
26const 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:

Verificar estado de orden
1async function checkOrderStatus(orderId) {
2 const order = await getOrderStatus(token, orgId, merchantId, orderId);
3
4 console.log('Estado:', order.status);
5 console.log('Creada:', order.createdAt);
6 console.log('Fecha límite:', order.dueDate);
7
8 if (order.status === 'PENDING') {
9 const createdTime = new Date(order.createdAt);
10 const now = new Date();
11 const minutesElapsed = (now - createdTime) / 1000 / 60;
12
13 console.log(`Orden pendiente por ${minutesElapsed.toFixed(0)} minutos`);
14
15 if (minutesElapsed > 30) {
16 console.warn('Orden pendiente por demasiado tiempo - cliente puede no haber pagado');
17 // Considerar cancelar o expirar la orden
18 }
19 }
20
21 return order;
22}

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:

1

Verificar endpoint de webhook

Verifica que tu endpoint sea públicamente accesible

2

Probar con webhook.site

Usa https://webhook.site para ver si los webhooks están siendo enviados

3

Verificar logs de webhook

Consulta intentos de entrega de webhook:

1const deliveries = await getWebhookDeliveries(orderId);
2console.log(deliveries);
4

Verificar validación de firma

Asegúrate de no estar rechazando webhooks por fallo en verificación de firma

5

Implementar sondeo de respaldo

Como respaldo, hacer polling del estado de orden:

1async function waitForCompletion(orderId, maxAttempts = 20) {
2 for (let i = 0; i < maxAttempts; i++) {
3 const order = await getOrderStatus(token, orgId, merchantId, orderId);
4
5 if (order.status === 'COMPLETED') {
6 return order;
7 }
8
9 if (['FAILED', 'EXPIRED', 'CANCELLED'].includes(order.status)) {
10 throw new Error(`Orden ${order.status}`);
11 }
12
13 // Esperar 3 segundos antes de próxima verificación
14 await new Promise(resolve => setTimeout(resolve, 3000));
15 }
16
17 throw new Error('Tiempo de espera agotado');
18}

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:

Verificación correcta de firma
1const crypto = require('crypto');
2
3function verifyWebhookSignature(payload, signature, secret) {
4 // payload debe ser el body crudo (string o Buffer)
5 // NO JSON parseado
6
7 const expectedSignature = crypto
8 .createHmac('sha256', secret)
9 .update(payload)
10 .digest('hex');
11
12 const isValid = signature === expectedSignature;
13
14 if (!isValid) {
15 console.error('Firma no coincide');
16 console.error('Recibida:', signature);
17 console.error('Esperada:', expectedSignature);
18 }
19
20 return isValid;
21}
22
23// Ejemplo Express - DEBE usar body crudo
24app.post('/webhooks/koywe',
25 express.raw({ type: 'application/json' }), // Importante: raw, no json
26 (req, res) => {
27 const signature = req.headers['koywe-signature'];
28 const secret = process.env.KOYWE_WEBHOOK_SECRET;
29
30 // req.body es Buffer al usar express.raw
31 if (!verifyWebhookSignature(req.body, signature, secret)) {
32 return res.status(401).send('Firma inválida');
33 }
34
35 // Ahora parsear
36 const event = JSON.parse(req.body);
37 console.log('Evento webhook válido:', event.type);
38
39 // Procesar evento...
40
41 res.status(200).send('OK');
42 }
43);

Próximos Pasos