--- id: INT-005 type: Integration title: "Terminal de pagos Clip" provider: "Clip México" status: Mock integration_type: "Pagos con tarjeta" created_at: 2026-01-10 updated_at: 2026-01-17 simco_version: "4.0.1" tags: - 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 ```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 1. Crear cuenta en [Clip Dashboard](https://dashboard.clip.mx/) 2. Acceder a la seccion de Desarrolladores 3. Generar API Key y Secret Key 4. Obtener Merchant ID de la configuracion de cuenta 5. 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 1. Usuario selecciona "Pagar con tarjeta" en el punto de venta 2. Backend crea una sesion de pago en Clip API 3. Se genera un link/QR para completar el pago 4. Cliente realiza el pago con su tarjeta 5. Clip envia webhook de confirmacion 6. 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 ```typescript const RETRY_DELAYS = [1000, 2000, 4000, 8000]; async function executeWithRetry( operation: () => Promise, maxRetries = 4 ): Promise { 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 ```typescript 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 { 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 ```typescript async createPaymentWithFallback( params: CreatePaymentDto, tenantId: string ): Promise { 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 - [x] **Por Tenant:** Cada tenant puede configurar sus propias credenciales Clip - [x] **Global con fallback:** Credenciales compartidas si tenant no tiene propias ### Almacenamiento ```sql -- 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 ```typescript @Injectable() export class ClipService { async getClientForTenant(tenantId: string): Promise { // 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 { 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 ```typescript 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 1. Acceder a Clip Dashboard > Desarrolladores > Webhooks 2. Agregar endpoint: `https://api.michangarrito.com/webhooks/clip` 3. Seleccionar eventos: `payment.*`, `payment_link.*` 4. Guardar y copiar el Webhook Secret 5. 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 ```bash # 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 ```typescript // 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 - [Clip API Documentation](https://developer.clip.mx/docs) - [Clip Dashboard](https://dashboard.clip.mx/) - [Webhooks Reference](https://developer.clip.mx/docs/webhooks) - [Tarjetas de Prueba](https://developer.clip.mx/docs/testing) ### 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 - [Payments Module](../../apps/backend/src/modules/payments/) - [Sales Module](../../apps/backend/src/modules/sales/) - [Arquitectura Multi-Tenant](../90-transversal/ARQUITECTURA-MULTI-TENANT-INTEGRACIONES.md) ### Integraciones Relacionadas - [INT-002: Stripe](./INT-002-stripe.md) - Suscripciones de MiChangarrito - [INT-004: MercadoPago](./INT-004-mercadopago.md) - Alternativa de pagos - [INT-006: CoDi Banxico](./INT-006-codi-banxico.md) - Pagos QR bancarios --- ## 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