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"
}'const axios = require('axios');
async function authenticate() {
const response = await axios.post(
'https://api-sandbox.koywe.com/api/v1/auth/sign-in',
{
apiKey: process.env.KOYWE_API_KEY,
secret: process.env.KOYWE_SECRET
}
);
return response.data.token;
}
// Uso
const token = await authenticate();import requests
import os
def authenticate():
response = requests.post(
'https://api-sandbox.koywe.com/api/v1/auth/sign-in',
json={
'apiKey': os.environ['KOYWE_API_KEY'],
'secret': os.environ['KOYWE_SECRET']
}
)
response.raise_for_status()
return response.json()['token']
# Uso
token = authenticate()Respuesta:
{
"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
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¤cySymbol=COP' \
-H 'Authorization: Bearer TU_TOKEN'async function getPaymentMethods(token, countrySymbol, currencySymbol) {
const response = await axios.get(
'https://api-sandbox.koywe.com/api/v1/payment-method',
{
params: {
countrySymbol: countrySymbol, // ej., 'CO' para Colombia
currencySymbol: currencySymbol // ej., 'COP'
},
headers: {
'Authorization': `Bearer ${token}`
}
}
);
return response.data;
}
// Uso - Obtener métodos de pago colombianos
const methods = await getPaymentMethods(token, 'CO', 'COP');
console.log('Métodos disponibles:', methods);
// Respuesta ejemplo
// [
// {
// "method": "PSE",
// "name": "PSE - Pagos Seguros en Línea",
// "supportedCountries": ["CO"],
// "supportedCurrencies": ["COP"],
// "extra": {
// "banks": ["BANCOLOMBIA", "DAVIVIENDA", "BOGOTA"]
// }
// },
// {
// "method": "NEQUI",
// "name": "Nequi",
// "supportedCountries": ["CO"],
// "supportedCurrencies": ["COP"]
// }
// ]def get_payment_methods(token, country_symbol, currency_symbol):
response = requests.get(
'https://api-sandbox.koywe.com/api/v1/payment-method',
params={
'countrySymbol': country_symbol,
'currencySymbol': currency_symbol
},
headers={'Authorization': f'Bearer {token}'}
)
response.raise_for_status()
return response.json()
# Uso
methods = get_payment_methods(token, 'CO', 'COP')
for method in methods:
print(f"Método: {method['method']} - {method['name']}")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`,
{
email: contactData.email, // Opcional pero recomendado
phone: contactData.phone, // Opcional
fullName: contactData.fullName, // Requerido
countrySymbol: contactData.countrySymbol, // Requerido (ej., 'CO')
documentType: contactData.documentType, // Requerido (ej., 'CC')
documentNumber: contactData.documentNumber, // Requerido
businessType: 'PERSON' // 'PERSON' o 'COMPANY'
},
{
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}
);
return response.data;
}
// Uso
const contact = await createContact(token, orgId, merchantId, {
email: 'cliente@ejemplo.com',
phone: '+573001234567',
fullName: 'Juan Pérez',
countrySymbol: 'CO',
documentType: 'CC', // ID colombiano
documentNumber: '1234567890'
});
console.log('Contacto creado:', contact.id);def create_contact(token, org_id, merchant_id, contact_data):
response = requests.post(
f'https://api-sandbox.koywe.com/api/v1/organizations/{org_id}/merchants/{merchant_id}/contacts',
json={
'email': contact_data.get('email'),
'phone': contact_data.get('phone'),
'fullName': contact_data['full_name'],
'countrySymbol': contact_data['country_symbol'],
'documentType': contact_data['document_type'],
'documentNumber': contact_data['document_number'],
'businessType': 'PERSON'
},
headers={
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
}
)
response.raise_for_status()
return response.json()
# Uso
contact = create_contact(token, org_id, merchant_id, {
'email': 'cliente@ejemplo.com',
'phone': '+573001234567',
'full_name': 'Juan Pérez',
'country_symbol': 'CO',
'document_type': 'CC',
'document_number': '1234567890'
})curl -X POST 'https://api-sandbox.koywe.com/api/v1/organizations/TU_ORG_ID/merchants/TU_MERCHANT_ID/contacts' \
-H 'Authorization: Bearer TU_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"email": "cliente@ejemplo.com",
"phone": "+573001234567",
"fullName": "Juan Pérez",
"countrySymbol": "CO",
"documentType": "CC",
"documentNumber": "1234567890",
"businessType": "PERSON"
}'Tipos de Documento por País
| País | Código | Tipo de Documento | Ejemplo |
|---|---|---|---|
| Colombia | CC | Cédula de Ciudadanía | 1234567890 |
| Brasil | CPF | Cadastro de Pessoas Físicas | 12345678900 |
| México | RFC | Registro Federal de Contribuyentes | XAXX010101000 |
| Chile | RUT | Rol Único Tributario | 11111111-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.paymentUrl);
// Redirige al cliente a order.paymentUrldef create_payin_order(token, org_id, merchant_id, order_data):
response = requests.post(
f'https://api-sandbox.koywe.com/api/v1/organizations/{org_id}/merchants/{merchant_id}/orders',
json={
'type': 'PAYIN',
'originCurrencySymbol': order_data['currency'],
'destinationCurrencySymbol': order_data['currency'],
'amountIn': order_data['amount'],
'description': order_data['description'],
'externalId': order_data['external_id'],
'contactId': order_data.get('contact_id'),
'paymentMethods': [
{
'method': order_data['payment_method'],
'extra': order_data.get('extra')
}
],
'successUrl': order_data['success_url'],
'failedUrl': order_data['failed_url']
},
headers={
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
}
)
response.raise_for_status()
return response.json()
# Uso
import time
order = create_payin_order(token, org_id, merchant_id, {
'currency': 'COP',
'amount': 50000,
'description': 'Pago por Orden #12345',
'external_id': f'order-12345-{int(time.time())}',
'contact_id': contact['id'],
'payment_method': 'PSE',
'extra': 'BANCOLOMBIA',
'success_url': 'https://tusitio.com/pago/exitoso',
'failed_url': 'https://tusitio.com/pago/fallido'
})
print(f"ID de Orden: {order['id']}")
print(f"URL de Pago: {order['paymentUrl']}")curl -X POST 'https://api-sandbox.koywe.com/api/v1/organizations/TU_ORG_ID/merchants/TU_MERCHANT_ID/orders' \
-H 'Authorization: Bearer TU_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"type": "PAYIN",
"originCurrencySymbol": "COP",
"destinationCurrencySymbol": "COP",
"amountIn": 50000,
"description": "Pago por Orden #12345",
"externalId": "order-12345-1699999999",
"contactId": "cnt_abc123",
"paymentMethods": [
{
"method": "PSE",
"extra": { "bankAccount": { "name": "BANCOLOMBIA" } }
}
],
"successUrl": "https://tusitio.com/pago/exitoso",
"failedUrl": "https://tusitio.com/pago/fallido"
}'Respuesta:
{
"id": "ord_abc123xyz",
"type": "PAYIN",
"status": "PENDING",
"amountIn": 50000,
"originCurrencySymbol": "COP",
"destinationCurrencySymbol": "COP",
"paymentUrl": "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 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:
// Redirección directa
window.location.href = order.paymentUrl;
// O abrir en nueva ventana
window.open(order.paymentUrl, '_blank');
// O devolver URL al frontend
res.json({
orderId: order.id,
paymentUrl: order.paymentUrl
});function CheckoutButton() {
const handleCheckout = async () => {
try {
// Llamar a tu backend para crear orden
const response = await fetch('/api/create-payment', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
amount: 50000,
description: 'Orden #12345'
})
});
const { paymentUrl } = await response.json();
// Redirigir al pago
window.location.href = paymentUrl;
} catch (error) {
console.error('Error de pago:', error);
}
};
return (
<button onClick={handleCheckout}>
Pagar Ahora
</button>
);
}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);
}
});from flask import Flask, request
import hmac
import hashlib
import json
app = Flask(__name__)
@app.route('/webhooks/koywe', methods=['POST'])
def webhook_handler():
# 1. Verificar firma
signature = request.headers.get('Koywe-Signature')
secret = os.environ['KOYWE_WEBHOOK_SECRET']
expected_signature = hmac.new(
secret.encode(),
request.data,
hashlib.sha256
).hexdigest()
if signature != expected_signature:
return 'Firma inválida', 401
# 2. Parsear evento
event = json.loads(request.data)
event_id = event['id']
# 3. Verificar duplicado
if is_event_processed(event_id):
return 'Ya procesado', 200
# 4. Manejar evento
try:
event_type = event['type']
order_data = event['data']
if event_type == 'order.paid':
update_order_status(order_data['externalId'], 'paid')
send_confirmation_email(order_data['externalId'])
elif event_type == 'order.completed':
update_order_status(order_data['externalId'], 'completed')
# CUMPLIR LA ORDEN AQUÍ
fulfill_order(order_data['externalId'])
elif event_type == 'order.failed':
update_order_status(order_data['externalId'], 'failed')
send_failure_notification(order_data['externalId'])
# Marcar como procesado
mark_event_processed(event_id)
return 'OK', 200
except Exception as error:
print(f'Error webhook: {error}')
log_webhook_error(event, error)
return 'OK', 200 # Devolver 200 para prevenir reintentosCrítico: Siempre verifica las firmas de webhook para asegurar que el webhook proviene de Koywe.