Recibir Pagos - Guía de Integración

Integración completa paso a paso para producción

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


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:

1{
2 "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
3}

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

Node.js
1class KoyweClient {
2 constructor(apiKey, secret, baseUrl) {
3 this.apiKey = apiKey;
4 this.secret = secret;
5 this.baseUrl = baseUrl;
6 this.token = null;
7 this.tokenExpiry = null;
8 }
9
10 async getToken() {
11 // Devolver token en caché si aún es válido
12 if (this.token && this.tokenExpiry > Date.now()) {
13 return this.token;
14 }
15
16 // Obtener nuevo token
17 const response = await axios.post(`${this.baseUrl}/auth/sign-in`, {
18 apiKey: this.apiKey,
19 secret: this.secret
20 });
21
22 this.token = response.data.token;
23 // Establecer vencimiento a 55 minutos (buffer de 5 min)
24 this.tokenExpiry = Date.now() + (55 * 60 * 1000);
25
26 return this.token;
27 }
28}

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-methods?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:

1async function createContact(token, orgId, merchantId, contactData) {
2 const response = await axios.post(
3 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/contacts`,
4 {
5 email: contactData.email, // Opcional pero recomendado
6 phone: contactData.phone, // Opcional
7 fullName: contactData.fullName, // Requerido
8 countrySymbol: contactData.countrySymbol, // Requerido (ej., 'CO')
9 documentType: contactData.documentType, // Requerido (ej., 'CC')
10 documentNumber: contactData.documentNumber, // Requerido
11 businessType: 'PERSON' // 'PERSON' o 'COMPANY'
12 },
13 {
14 headers: {
15 'Authorization': `Bearer ${token}`,
16 'Content-Type': 'application/json'
17 }
18 }
19 );
20
21 return response.data;
22}
23
24// Uso
25const contact = await createContact(token, orgId, merchantId, {
26 email: 'cliente@ejemplo.com',
27 phone: '+573001234567',
28 fullName: 'Juan Pérez',
29 countrySymbol: 'CO',
30 documentType: 'CC', // ID colombiano
31 documentNumber: '1234567890'
32});
33
34console.log('Contacto creado:', contact.id);

Tipos de Documento por País

PaísCódigoTipo de DocumentoEjemplo
ColombiaCCCédula de Ciudadanía1234567890
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:

1async function createPayinOrder(token, orgId, merchantId, orderData) {
2 const response = await axios.post(
3 `https://api-sandbox.koywe.com/api/v1/organizations/${orgId}/merchants/${merchantId}/orders`,
4 {
5 type: 'PAYIN', // Tipo de orden
6 originCurrencySymbol: orderData.currency, // ej., 'COP'
7 destinationCurrencySymbol: orderData.currency, // Misma que origen para PAYIN
8 amountIn: orderData.amount, // Monto a cobrar
9 description: orderData.description, // Descripción visible para cliente
10 externalId: orderData.externalId, // Tu referencia interna (para idempotencia)
11 contactId: orderData.contactId, // Contacto del Paso 3 (opcional)
12 paymentMethods: [ // Al menos un método de pago requerido
13 {
14 method: orderData.paymentMethod, // ej., 'PSE', 'PIX', 'SPEI'
15 extra: orderData.extra // Datos adicionales (ej., banco para PSE)
16 }
17 ],
18 successUrl: orderData.successUrl, // Redirección después de éxito
19 failedUrl: orderData.failedUrl // Redirección después de fallo
20 },
21 {
22 headers: {
23 'Authorization': `Bearer ${token}`,
24 'Content-Type': 'application/json'
25 }
26 }
27 );
28
29 return response.data;
30}
31
32// Uso
33const order = await createPayinOrder(token, orgId, merchantId, {
34 currency: 'COP',
35 amount: 50000, // 50,000 COP
36 description: 'Pago por Orden #12345',
37 externalId: `order-12345-${Date.now()}`, // Único por intento
38 contactId: contact.id,
39 paymentMethod: 'PSE',
40 extra: 'BANCOLOMBIA', // Selección de banco para PSE
41 successUrl: 'https://tusitio.com/pago/exitoso',
42 failedUrl: 'https://tusitio.com/pago/fallido'
43});
44
45console.log('ID de Orden:', order.id);
46console.log('URL de Pago:', order.paymentUrl);
47// Redirige al cliente a order.paymentUrl

Respuesta:

1{
2 "id": "ord_abc123xyz",
3 "type": "PAYIN",
4 "status": "PENDING",
5 "amountIn": 50000,
6 "originCurrencySymbol": "COP",
7 "destinationCurrencySymbol": "COP",
8 "paymentUrl": "https://checkout.koywe.com/pay/ord_abc123xyz",
9 "externalId": "order-12345-1699999999",
10 "description": "Pago por Orden #12345",
11 "createdAt": "2025-11-13T10:00:00Z"
12}

URL de Pago Generada: La paymentUrl 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:

1// Redirección directa
2window.location.href = order.paymentUrl;
3
4// O abrir en nueva ventana
5window.open(order.paymentUrl, '_blank');
6
7// O devolver URL al frontend
8res.json({
9 orderId: order.id,
10 paymentUrl: order.paymentUrl
11});

Paso 6: Manejar Webhooks

Escucha eventos webhook para rastrear el estado del pago:

1const express = require('express');
2const crypto = require('crypto');
3
4app.post('/webhooks/koywe', express.raw({type: 'application/json'}), async (req, res) => {
5 // Convertir body raw a string una vez
6 const rawBody = req.body.toString();
7
8 // 1. Verificar firma de webhook
9 const signature = req.headers['koywe-signature'];
10 const secret = process.env.KOYWE_WEBHOOK_SECRET;
11
12 const expectedSignature = crypto
13 .createHmac('sha256', secret)
14 .update(rawBody)
15 .digest('hex');
16
17 if (signature !== expectedSignature) {
18 console.error('Firma de webhook inválida');
19 return res.status(401).send('Firma inválida');
20 }
21
22 // 2. Parsear evento desde string
23 const event = JSON.parse(rawBody);
24 const eventId = event.id;
25
26 // 3. Verificar duplicado (idempotencia)
27 if (await isEventProcessed(eventId)) {
28 return res.status(200).send('Ya procesado');
29 }
30
31 // 4. Manejar evento basado en tipo
32 try {
33 switch (event.type) {
34 case 'order.created':
35 console.log('Orden creada:', event.data.orderId);
36 break;
37
38 case 'order.pending':
39 console.log('Orden pendiente:', event.data.orderId);
40 await updateOrderStatus(event.data.externalId, 'pending');
41 break;
42
43 case 'order.processing':
44 console.log('Pago en proceso:', event.data.orderId);
45 await updateOrderStatus(event.data.externalId, 'processing');
46 break;
47
48 case 'order.paid':
49 console.log('Pago confirmado:', event.data.orderId);
50 await updateOrderStatus(event.data.externalId, 'paid');
51 await sendConfirmationEmail(event.data.externalId);
52 break;
53
54 case 'order.completed':
55 console.log('Fondos acreditados:', event.data.orderId);
56 await updateOrderStatus(event.data.externalId, 'completed');
57 // CUMPLIR LA ORDEN AQUÍ
58 await fulfillOrder(event.data.externalId);
59 await sendShippingNotification(event.data.externalId);
60 break;
61
62 case 'order.failed':
63 console.log('Pago falló:', event.data.orderId);
64 await updateOrderStatus(event.data.externalId, 'failed');
65 await sendFailureNotification(event.data.externalId);
66 break;
67
68 case 'order.expired':
69 console.log('Orden expiró:', event.data.orderId);
70 await updateOrderStatus(event.data.externalId, 'expired');
71 break;
72
73 case 'order.cancelled':
74 console.log('Orden cancelada:', event.data.orderId);
75 await updateOrderStatus(event.data.externalId, 'cancelled');
76 break;
77 }
78
79 // 5. Marcar evento como procesado
80 await markEventProcessed(eventId);
81
82 // 6. Responder rápidamente (< 5 segundos)
83 res.status(200).send('OK');
84
85 } catch (error) {
86 console.error('Error procesando webhook:', error);
87 // Aún devolver 200 para prevenir reintentos por errores de procesamiento
88 res.status(200).send('OK');
89 // Registrar error para revisión manual
90 await logWebhookError(event, error);
91 }
92});

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

Guía completa de webhooks →


Próximos Pasos