Koywe
En esta página

Guía de Integración para Recibir Pagos

Esta guía completa te lleva a través de la integración de aceptación de pagos en tu aplicación para uso en producción.

Requisitos Previos

Antes de comenzar:

  • Credenciales API (key, secret, organizationId, merchantId)
  • Endpoint webhook configurado (recomendado)
  • Acceso a entorno de pruebas
  • Comprensión de Conceptos Clave
  • Familiaridad con Tipos de Orden

Resumen de Integración

Dibujando diagrama…

Paso 1: Autenticar

Obtén un token de acceso para todas las solicitudes posteriores:

curl -X POST 'https://api-sandbox.koywe.com/api/v1/auth/sign-in' \
  -H 'Content-Type: application/json' \
  -d '{
    "apiKey": "tu_api_key",
    "secret": "tu_secret"
  }'

Respuesta:

JSON
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Gestión de Tokens: Los tokens expiran después de 1 hora. Implementa lógica de caché y actualización de tokens en producción.

Gestión de Tokens en Producción

JavaScript
class KoyweClient {
  constructor(apiKey, secret, baseUrl) {
    this.apiKey = apiKey;
    this.secret = secret;
    this.baseUrl = baseUrl;
    this.token = null;
    this.tokenExpiry = null;
  }
  
  async getToken() {
    // Devolver token en caché si aún es válido
    if (this.token && this.tokenExpiry > Date.now()) {
      return this.token;
    }
    
    // Obtener nuevo token
    const response = await axios.post(`${this.baseUrl}/auth/sign-in`, {
      apiKey: this.apiKey,
      secret: this.secret
    });
    
    this.token = response.data.token;
    // Establecer vencimiento a 55 minutos (buffer de 5 min)
    this.tokenExpiry = Date.now() + (55 * 60 * 1000);
    
    return this.token;
  }
}

Paso 2: Obtener Métodos de Pago Disponibles

Consulta qué métodos de pago están disponibles para tu país y moneda objetivo:

curl -X GET 'https://api-sandbox.koywe.com/api/v1/payment-method?countrySymbol=CO&currencySymbol=COP' \
  -H 'Authorization: Bearer TU_TOKEN'

Caché: Almacena métodos de pago en caché por país/moneda para reducir llamadas API. Los métodos no cambian frecuentemente.


Paso 3: Crear Contacto (Opcional pero Recomendado)

Crea un contacto para el cliente para rastrear el historial de pagos y cumplir con requisitos de cumplimiento:

async function createContact(token, orgId, merchantId, contactData) {
  const response = await axios.post(
    `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/contacts`,
    {
      firstName: contactData.firstName,            // Requerido
      lastName: contactData.lastName,              // Opcional
      email: contactData.email,                    // Opcional pero recomendado
      phone: contactData.phone,                    // Opcional
      countrySymbol: contactData.countrySymbol,    // Requerido (ej., 'CO')
      documentType: contactData.documentType,      // Opcional (ej., 'CED_CIU')
      documentNumber: contactData.documentNumber,  // Opcional (requerido si se provee documentType)
      businessType: 'PERSON',                      // 'PERSON' o 'COMPANY'
      type: 'PERSON'                               // Requerido: PERSON, BUSINESS, GOVERNMENT, NGO, FOREIGN
    },
    {
      headers: { 
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      }
    }
  );
  
  return response.data;
}
 
// Uso
const contact = await createContact(token, orgId, merchantId, {
  firstName: 'Juan',
  lastName: 'Pérez',
  email: 'cliente@ejemplo.com',
  phone: '+573001234567',
  countrySymbol: 'CO',
  documentType: 'CED_CIU',  // Cédula de Ciudadanía (opcional)
  documentNumber: '1234567890'
});
 
console.log('Contacto creado:', contact.id);

Tipos de Documento por País

PaísCódigoTipo de DocumentoEjemplo
ColombiaCED_CIUCédula de Ciudadanía (CC)1234567890
BrasilCPFCadastro de Pessoas Físicas12345678900
MéxicoRFCRegistro Federal de ContribuyentesXAXX010101000
ChileRUTRol Único Tributario11111111-1

Referencia completa de tipos de documento →


Paso 4: Crear Orden PAYIN

Crea la orden de pago con todos los detalles requeridos:

async function createPayinOrder(token, orgId, merchantId, orderData) {
  const response = await axios.post(
    `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/orders`,
    {
      type: 'PAYIN',                                // Tipo de orden
      originCurrencySymbol: orderData.currency,     // ej., 'COP'
      destinationCurrencySymbol: orderData.currency, // Misma que origen para PAYIN
      amountIn: orderData.amount,                   // Monto a cobrar
      description: orderData.description,           // Descripción visible para cliente
      externalId: orderData.externalId,             // Tu referencia interna (para idempotencia)
      contactId: orderData.contactId,               // Contacto del Paso 3 (opcional)
      paymentMethods: [                             // Al menos un método de pago requerido
        {
          method: orderData.paymentMethod,          // ej., 'PSE', 'PIX', 'SPEI'
          extra: orderData.extra                    // Datos adicionales (ej., banco para PSE)
        }
      ],
      successUrl: orderData.successUrl,             // Redirección después de éxito
      failedUrl: orderData.failedUrl                // Redirección después de fallo
    },
    {
      headers: { 
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      }
    }
  );
  
  return response.data;
}
 
// Uso
const order = await createPayinOrder(token, orgId, merchantId, {
  currency: 'COP',
  amount: 50000,                        // 50,000 COP
  description: 'Pago por Orden #12345',
  externalId: `order-12345-${Date.now()}`,  // Único por intento
  contactId: contact.id,
  paymentMethod: 'PSE',
  extra: { bankAccount: { name: 'BANCOLOMBIA' } },  // Selección de banco para PSE (extras por método)
  successUrl: 'https://tusitio.com/pago/exitoso',
  failedUrl: 'https://tusitio.com/pago/fallido'
});
 
console.log('ID de Orden:', order.id);
console.log('URL de Pago:', order.providedAction);
// Redirige al cliente a order.providedAction

Respuesta:

JSON
{
  "id": "ord_abc123xyz",
  "type": "PAYIN",
  "status": "PENDING",
  "amountIn": 50000,
  "originCurrencySymbol": "COP",
  "destinationCurrencySymbol": "COP",
  "providedAction": "https://checkout.koywe.com/pay/ord_abc123xyz",
  "externalId": "order-12345-1699999999",
  "description": "Pago por Orden #12345",
  "createdAt": "2025-11-13T10:00:00Z"
}

URL de Pago Generada: La providedAction es donde debes redirigir a tu cliente para completar el pago.


Paso 5: Redirigir Cliente al Pago

Envía al cliente a la URL de pago:

// Redirección directa
window.location.href = order.providedAction;
 
// O abrir en nueva ventana
window.open(order.providedAction, '_blank');
 
// O devolver URL al frontend
res.json({ 
  orderId: order.id,
  providedAction: order.providedAction 
});

Paso 6: Manejar Webhooks

Escucha eventos webhook para rastrear el estado del pago:

const express = require('express');
const crypto = require('crypto');
 
app.post('/webhooks/koywe', express.raw({type: 'application/json'}), async (req, res) => {
  // Convertir body raw a string una vez
  const rawBody = req.body.toString();
  
  // 1. Verificar firma de webhook
  const signature = req.headers['koywe-signature'];
  const secret = process.env.KOYWE_WEBHOOK_SECRET;
  
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  
  if (signature !== expectedSignature) {
    console.error('Firma de webhook inválida');
    return res.status(401).send('Firma inválida');
  }
  
  // 2. Parsear evento desde string
  const event = JSON.parse(rawBody);
  const eventId = event.id;
  
  // 3. Verificar duplicado (idempotencia)
  if (await isEventProcessed(eventId)) {
    return res.status(200).send('Ya procesado');
  }
  
  // 4. Manejar evento basado en tipo
  try {
    switch (event.type) {
      case 'order.created':
        console.log('Orden creada:', event.data.orderId);
        break;
        
      case 'order.pending':
        console.log('Orden pendiente:', event.data.orderId);
        await updateOrderStatus(event.data.externalId, 'pending');
        break;
        
      case 'order.processing':
        console.log('Pago en proceso:', event.data.orderId);
        await updateOrderStatus(event.data.externalId, 'processing');
        break;
        
      case 'order.paid':
        console.log('Pago confirmado:', event.data.orderId);
        await updateOrderStatus(event.data.externalId, 'paid');
        await sendConfirmationEmail(event.data.externalId);
        break;
        
      case 'order.completed':
        console.log('Fondos acreditados:', event.data.orderId);
        await updateOrderStatus(event.data.externalId, 'completed');
        // CUMPLIR LA ORDEN AQUÍ
        await fulfillOrder(event.data.externalId);
        await sendShippingNotification(event.data.externalId);
        break;
        
      case 'order.failed':
        console.log('Pago falló:', event.data.orderId);
        await updateOrderStatus(event.data.externalId, 'failed');
        await sendFailureNotification(event.data.externalId);
        break;
        
      case 'order.expired':
        console.log('Orden expiró:', event.data.orderId);
        await updateOrderStatus(event.data.externalId, 'expired');
        break;
        
      case 'order.cancelled':
        console.log('Orden cancelada:', event.data.orderId);
        await updateOrderStatus(event.data.externalId, 'cancelled');
        break;
    }
    
    // 5. Marcar evento como procesado
    await markEventProcessed(eventId);
    
    // 6. Responder rápidamente (< 5 segundos)
    res.status(200).send('OK');
    
  } catch (error) {
    console.error('Error procesando webhook:', error);
    // Aún devolver 200 para prevenir reintentos por errores de procesamiento
    res.status(200).send('OK');
    // Registrar error para revisión manual
    await logWebhookError(event, error);
  }
});

Crítico: Siempre verifica las firmas de webhook para asegurar que el webhook proviene de Koywe.

Guía completa de webhooks →


Próximos Pasos