| id |
type |
title |
provider |
status |
integration_type |
created_at |
updated_at |
simco_version |
tags |
| INT-005 |
Integration |
Terminal de pagos Clip |
Clip México |
Mock |
Pagos con tarjeta |
2026-01-10 |
2026-01-17 |
4.0.1 |
| pagos |
| tarjeta |
| clip |
| pos |
| fintech |
|
INT-005: Terminal de pagos Clip
Metadata
| Campo |
Valor |
| Codigo |
INT-005 |
| Proveedor |
Clip México |
| Tipo |
Pagos con tarjeta |
| Estado |
Mock (pendiente de implementar) |
| Multi-tenant |
Si |
| Fecha planeada |
2026-Q1 |
| Owner |
Backend Team |
1. Descripcion
Integracion con el sistema de terminales punto de venta (TPV) de Clip Mexico para procesar pagos con tarjeta de credito y debito. Clip es una de las soluciones de pago mas populares en Mexico para pequenos comercios, permitiendo aceptar pagos con tarjeta sin necesidad de una cuenta bancaria empresarial tradicional.
Casos de uso principales:
- Cobro presencial con tarjeta de credito/debito en el changarro
- Generacion de links de pago para cobros a distancia
- Consulta de historial de transacciones y conciliacion
- Gestion de reembolsos y cancelaciones
- Reportes de ventas por periodo
2. Credenciales Requeridas
Variables de Entorno
| Variable |
Descripcion |
Tipo |
Obligatorio |
| CLIP_API_KEY |
Llave de API proporcionada por Clip |
string |
SI |
| CLIP_SECRET_KEY |
Llave secreta para firmar requests |
string |
SI |
| CLIP_MERCHANT_ID |
Identificador unico del comercio en Clip |
string |
SI |
| CLIP_WEBHOOK_SECRET |
Secret para validar webhooks de Clip |
string |
SI |
| CLIP_ENVIRONMENT |
Ambiente (sandbox/production) |
string |
SI |
Ejemplo de .env
# Terminal de pagos Clip
CLIP_API_KEY=clip_api_xxxxxxxxxxxxxxxx
CLIP_SECRET_KEY=clip_secret_xxxxxxxxxxxxxxxx
CLIP_MERCHANT_ID=mer_xxxxxxxxxxxxxxxx
CLIP_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxx
CLIP_ENVIRONMENT=sandbox
Obtencion de Credenciales
- Crear cuenta en Clip Dashboard
- Acceder a la seccion de Desarrolladores
- Generar API Key y Secret Key
- Obtener Merchant ID de la configuracion de cuenta
- Configurar Webhook URL y obtener Webhook Secret
3. Arquitectura
┌─────────────────────────────────────────────────────────────────┐
│ MiChangarrito │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ Frontend │───▶│ API Server │───▶│ ClipService │ │
│ │ (Ventas) │ │ │ │ │ │
│ └──────────────┘ └──────────────┘ └────────┬─────────┘ │
└────────────────────────────────────────────────────┼────────────┘
│
▼
┌──────────────────┐
│ Clip API │
│ (México) │
│ │
│ - Pagos │
│ - Reembolsos │
│ - Consultas │
└──────────────────┘
Flujo de Pago
- Usuario selecciona "Pagar con tarjeta" en el punto de venta
- Backend crea una sesion de pago en Clip API
- Se genera un link/QR para completar el pago
- Cliente realiza el pago con su tarjeta
- Clip envia webhook de confirmacion
- Backend actualiza estado de la venta
4. Endpoints
Endpoints Consumidos (Clip API)
| Metodo |
Endpoint |
Descripcion |
| POST |
/v1/payments |
Crear un nuevo pago |
| GET |
/v1/payments/{id} |
Consultar estado de pago |
| POST |
/v1/payments/{id}/refund |
Procesar reembolso |
| GET |
/v1/transactions |
Listar transacciones |
| GET |
/v1/merchants/{id}/balance |
Consultar balance |
| POST |
/v1/payment-links |
Crear link de pago |
| GET |
/v1/payment-links/{id} |
Consultar link de pago |
Endpoints Expuestos (MiChangarrito)
| Metodo |
Endpoint |
Descripcion |
| POST |
/api/v1/payments/clip/create |
Iniciar pago con Clip |
| GET |
/api/v1/payments/clip/{id} |
Consultar pago |
| POST |
/api/v1/payments/clip/{id}/refund |
Solicitar reembolso |
| POST |
/api/v1/payments/clip/payment-link |
Crear link de pago |
| POST |
/api/v1/webhooks/clip |
Recibir notificaciones de Clip |
5. Rate Limits
| Limite |
Valor |
Periodo |
Accion si excede |
| Requests API |
100 |
por minuto |
429 + Retry-After |
| Crear pagos |
60 |
por minuto |
429 + backoff |
| Consultas |
200 |
por minuto |
429 + Retry-After |
| Webhooks |
Sin limite |
- |
N/A |
Estrategia de Retry
const RETRY_DELAYS = [1000, 2000, 4000, 8000];
async function executeWithRetry<T>(
operation: () => Promise<T>,
maxRetries = 4
): Promise<T> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
if (error.response?.status === 429 && attempt < maxRetries - 1) {
const retryAfter = error.response.headers['retry-after'];
const delay = retryAfter
? parseInt(retryAfter) * 1000
: RETRY_DELAYS[attempt];
await sleep(delay);
continue;
}
throw error;
}
}
throw new Error('Max retries exceeded');
}
6. Manejo de Errores
Codigos de Error
| Codigo |
Descripcion |
Accion Recomendada |
Retry |
| 400 |
Request invalido |
Validar parametros enviados |
NO |
| 401 |
Credenciales invalidas |
Verificar API Key y Secret |
NO |
| 402 |
Pago rechazado |
Informar al usuario, ofrecer alternativa |
NO |
| 403 |
Operacion no permitida |
Verificar permisos del merchant |
NO |
| 404 |
Recurso no encontrado |
Verificar ID de pago/transaccion |
NO |
| 429 |
Rate limit excedido |
Esperar y reintentar con backoff |
SI |
| 500 |
Error interno de Clip |
Reintentar con backoff exponencial |
SI |
Ejemplo de Manejo
import { Injectable, Logger, BadRequestException } from '@nestjs/common';
@Injectable()
export class ClipService {
private readonly logger = new Logger(ClipService.name);
async createPayment(params: CreatePaymentDto, tenantId: string): Promise<ClipPayment> {
try {
const response = await this.clipClient.post('/v1/payments', params);
this.logger.log('Clip payment created', {
service: 'clip',
operation: 'createPayment',
paymentId: response.data.id,
amount: params.amount,
tenantId,
});
return response.data;
} catch (error) {
const clipError = error.response?.data;
this.logger.error('Clip payment failed', {
service: 'clip',
operation: 'createPayment',
code: clipError?.code || error.response?.status,
message: clipError?.message || error.message,
tenantId,
});
switch (error.response?.status) {
case 400:
throw new BadRequestException(clipError?.message || 'Parametros de pago invalidos');
case 401:
throw new Error('Credenciales de Clip invalidas');
case 402:
throw new BadRequestException('Pago rechazado: ' + clipError?.decline_reason);
case 429:
// Encolar para reintento
await this.queue.add('clip-payment-retry', { params, tenantId });
throw new Error('Servicio ocupado, reintentando...');
default:
throw error;
}
}
}
}
7. Fallbacks
Estrategia de Fallback
| Escenario |
Fallback |
Tiempo maximo |
| Clip API caida |
Encolar pago para reintento |
24 horas |
| Rate limited |
Throttle + cola prioritaria |
1 hora |
| Pago rechazado |
Ofrecer link de pago alternativo |
Inmediato |
| Timeout |
Verificar estado y reintentar |
3 intentos |
Modo Degradado
Si Clip no esta disponible:
- Pagos presenciales se encolan en Redis para reintento
- Usuario puede optar por efectivo y registrar manualmente
- Links de pago existentes siguen funcionando
- Notificacion a admin sobre estado del servicio
async createPaymentWithFallback(
params: CreatePaymentDto,
tenantId: string
): Promise<PaymentResult> {
try {
return await this.createPayment(params, tenantId);
} catch (error) {
if (this.isClipUnavailable(error)) {
// Encolar para reintento posterior
const jobId = await this.queue.add('clip-payment-pending', {
params,
tenantId,
createdAt: new Date().toISOString(),
});
return {
status: 'pending_retry',
jobId,
message: 'Pago encolado, se procesara cuando Clip este disponible',
};
}
throw error;
}
}
8. Multi-tenant
Modelo de Credenciales
Almacenamiento
-- En schema payments
CREATE TABLE payments.tenant_clip_config (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID REFERENCES auth.tenants(id) NOT NULL,
api_key TEXT NOT NULL, -- Encriptado con AES-256
secret_key TEXT NOT NULL, -- Encriptado con AES-256
merchant_id VARCHAR(50) NOT NULL,
webhook_secret TEXT, -- Encriptado
environment VARCHAR(20) DEFAULT 'sandbox',
commission_rate DECIMAL(5,4) DEFAULT 0.0360, -- 3.6% default
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(tenant_id)
);
-- Indice para busqueda rapida
CREATE INDEX idx_tenant_clip_config_tenant ON payments.tenant_clip_config(tenant_id);
Contexto en Llamadas
@Injectable()
export class ClipService {
async getClientForTenant(tenantId: string): Promise<ClipClient> {
// Buscar configuracion del tenant
const config = await this.configRepo.findOne({ where: { tenantId } });
if (config?.is_active) {
return new ClipClient({
apiKey: this.decrypt(config.api_key),
secretKey: this.decrypt(config.secret_key),
merchantId: config.merchant_id,
environment: config.environment,
});
}
// Fallback a credenciales globales
return new ClipClient({
apiKey: this.configService.get('CLIP_API_KEY'),
secretKey: this.configService.get('CLIP_SECRET_KEY'),
merchantId: this.configService.get('CLIP_MERCHANT_ID'),
environment: this.configService.get('CLIP_ENVIRONMENT'),
});
}
async createPaymentForTenant(
tenantId: string,
params: CreatePaymentDto
): Promise<ClipPayment> {
const client = await this.getClientForTenant(tenantId);
return client.payments.create(params);
}
}
9. Webhooks
Endpoints Registrados
| Evento |
Endpoint Local |
Descripcion |
payment.created |
/webhooks/clip |
Pago creado |
payment.approved |
/webhooks/clip |
Pago aprobado |
payment.declined |
/webhooks/clip |
Pago rechazado |
payment.refunded |
/webhooks/clip |
Pago reembolsado |
payment_link.paid |
/webhooks/clip |
Link de pago completado |
Validacion HMAC de Firma
import * as crypto from 'crypto';
function verifyClipWebhookSignature(
payload: string,
signature: string,
webhookSecret: string
): boolean {
const expectedSignature = crypto
.createHmac('sha256', webhookSecret)
.update(payload, 'utf8')
.digest('hex');
// Comparacion segura contra timing attacks
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// Controller
@Post('/webhooks/clip')
async handleClipWebhook(
@Body() body: any,
@Headers('X-Clip-Signature') signature: string,
@Req() request: Request
): Promise<{ received: boolean }> {
const rawBody = (request as any).rawBody;
// Determinar webhook secret (puede ser por tenant)
const webhookSecret = await this.getWebhookSecret(body.merchant_id);
if (!verifyClipWebhookSignature(rawBody, signature, webhookSecret)) {
throw new UnauthorizedException('Invalid webhook signature');
}
await this.processWebhookEvent(body);
return { received: true };
}
Configuracion en Clip Dashboard
- Acceder a Clip Dashboard > Desarrolladores > Webhooks
- Agregar endpoint:
https://api.michangarrito.com/webhooks/clip
- Seleccionar eventos:
payment.*, payment_link.*
- Guardar y copiar el Webhook Secret
- Configurar en variable de entorno
CLIP_WEBHOOK_SECRET
10. Testing
Modo Sandbox
| Ambiente |
Credenciales |
Comportamiento |
| Sandbox |
clip_api_test_* |
Pagos simulados, sin cargos reales |
| Production |
clip_api_live_* |
Pagos reales con tarjetas |
Tarjetas de Prueba Clip
# Pago exitoso
4242 4242 4242 4242 Exp: 12/28 CVV: 123
# Pago rechazado - Fondos insuficientes
4000 0000 0000 0002 Exp: 12/28 CVV: 123
# Pago rechazado - Tarjeta robada
4000 0000 0000 0069 Exp: 12/28 CVV: 123
# Requiere autenticacion 3D Secure
4000 0027 6000 3184 Exp: 12/28 CVV: 123
Comandos de Test
# Test crear pago
curl -X POST https://api-sandbox.clip.mx/v1/payments \
-H "Authorization: Bearer ${CLIP_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"amount": 150.00,
"currency": "MXN",
"description": "Venta de prueba MiChangarrito",
"reference": "test-order-001"
}'
# Test consultar pago
curl -X GET "https://api-sandbox.clip.mx/v1/payments/${PAYMENT_ID}" \
-H "Authorization: Bearer ${CLIP_API_KEY}"
# Test crear link de pago
curl -X POST https://api-sandbox.clip.mx/v1/payment-links \
-H "Authorization: Bearer ${CLIP_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"amount": 299.00,
"currency": "MXN",
"description": "Pedido a domicilio",
"expires_at": "2026-01-20T23:59:59Z"
}'
# Test webhook local (simular evento)
curl -X POST http://localhost:3143/webhooks/clip \
-H "Content-Type: application/json" \
-H "X-Clip-Signature: $(echo -n '{"event":"payment.approved","data":{"id":"pay_123"}}' | openssl dgst -sha256 -hmac ${CLIP_WEBHOOK_SECRET})" \
-d '{"event":"payment.approved","data":{"id":"pay_123","amount":150.00,"status":"approved"}}'
11. Monitoreo
Metricas a Monitorear
| Metrica |
Descripcion |
Alerta |
| Latencia API |
Tiempo de respuesta Clip API |
> 5s |
| Error Rate |
% de requests fallidos |
> 5% |
| Decline Rate |
% de pagos rechazados |
> 15% |
| Webhook Delay |
Tiempo entre pago y webhook |
> 30s |
| Success Rate |
% de pagos exitosos |
< 85% |
| Daily Volume |
Volumen diario en MXN |
Anomalia |
Logs Estructurados
// Pago exitoso
this.logger.info('Clip payment successful', {
service: 'clip',
operation: 'createPayment',
tenantId: context.tenantId,
paymentId: result.id,
amount: params.amount,
currency: 'MXN',
duration: durationMs,
});
// Pago rechazado
this.logger.warn('Clip payment declined', {
service: 'clip',
operation: 'createPayment',
tenantId: context.tenantId,
declineCode: error.decline_code,
declineReason: error.decline_reason,
amount: params.amount,
});
// Webhook recibido
this.logger.info('Clip webhook received', {
service: 'clip',
operation: 'webhook',
event: payload.event,
paymentId: payload.data.id,
merchantId: payload.merchant_id,
});
Dashboard Recomendado
Configurar en Grafana/DataDog:
- Grafica de volumen de pagos por hora
- Grafica de tasa de exito/rechazo
- Alertas de latencia y errores
- Resumen de comisiones acumuladas
12. Referencias
Documentacion Oficial Clip
Informacion de Clip Mexico
- Comision estandar: 3.6% + IVA por transaccion
- Depositos: 24-48 horas habiles
- Soporte: soporte@clip.mx
- Telefono: 55 4162 5252
Modulos Relacionados
Integraciones Relacionadas
13. Notas de Implementacion
- La integracion requiere cuenta de desarrollador en Clip Portal
- Clip cobra comision del 3.6% + IVA por transaccion
- Los fondos se depositan en 24-48 horas habiles
- Implementar idempotency keys para evitar cobros duplicados
- Validar firma HMAC en todos los webhooks recibidos
- Manejar los estados de pago: pending, approved, declined, refunded
- Considerar limites de rate limiting de la API (100 req/min)
- En ambiente sandbox usar tarjetas de prueba proporcionadas por Clip
Ultima actualizacion: 2026-01-17
Autor: Backend Team