--- id: INT-004 type: Integration title: "Mercado Pago" provider: "MercadoPago" status: Pendiente integration_type: "payments" created_at: 2026-01-04 updated_at: 2026-01-17 simco_version: "4.0.1" tags: - mercadopago - payments - point-of-sale - multi-tenant --- # INT-004: Mercado Pago ## Metadata | Campo | Valor | |-------|-------| | **Codigo** | INT-004 | | **Proveedor** | Mercado Libre | | **Tipo** | Terminal de Pago | | **Estado** | Pendiente | | **Multi-tenant** | Si (por tenant) | | **Fecha integracion** | - | | **Ultimo update** | 2026-01-17 | | **Owner** | Backend Team | --- ## 1. Descripcion Mercado Pago es una opcion de terminal de pago fisica para que los duenos de tiendas puedan aceptar pagos con tarjeta. A diferencia de Stripe (que maneja suscripciones), MercadoPago se usa para cobros en punto de venta. **Casos de uso principales:** - Cobro con tarjeta en tienda fisica - QR para cobro - Generacion de links de pago - Integracion con terminal Point --- ## 2. Credenciales Requeridas ### Variables de Entorno | Variable | Descripcion | Tipo | Obligatorio | |----------|-------------|------|-------------| | `MERCADOPAGO_ACCESS_TOKEN` | Access Token de la aplicacion | string | SI | | `MERCADOPAGO_PUBLIC_KEY` | Public Key para frontend | string | SI | | `MERCADOPAGO_WEBHOOK_SECRET` | Secret para webhooks (IPN) | string | SI | ### Ejemplo de .env ```env # Mercado Pago MERCADOPAGO_ACCESS_TOKEN=APP_USR-xxxxxxxx MERCADOPAGO_PUBLIC_KEY=APP_USR-xxxxxxxx MERCADOPAGO_WEBHOOK_SECRET=xxxxxxxx ``` ### Obtencion de Credenciales 1. Crear cuenta en [Mercado Pago Developers](https://www.mercadopago.com.mx/developers/) 2. Crear aplicacion 3. Obtener credenciales de produccion 4. Configurar URL de IPN (webhook) --- ## 3. Endpoints/SDK Utilizados ### Operaciones Planificadas | Operacion | SDK Method | Endpoint | Descripcion | |-----------|------------|----------|-------------| | Crear Preferencia | `preference.create()` | POST `/checkout/preferences` | Link de pago | | Crear QR | `qr.create()` | POST `/instore/qr/seller` | Cobro por QR | | Consultar Pago | `payment.get()` | GET `/v1/payments/{id}` | Estado del pago | | Reembolso | `payment.refund()` | POST `/v1/payments/{id}/refunds` | Devolucion | | Buscar Pagos | `payment.search()` | GET `/v1/payments/search` | Historial de pagos | ### SDK Planificado ```typescript import { MercadoPagoConfig, Preference, Payment } from 'mercadopago'; const client = new MercadoPagoConfig({ accessToken: process.env.MERCADOPAGO_ACCESS_TOKEN, }); const preference = new Preference(client); // Crear link de pago const result = await preference.create({ body: { items: [{ title: 'Venta en tienda', unit_price: 100, quantity: 1, currency_id: 'MXN', }], back_urls: { success: 'https://michangarrito.com/pago/exitoso', failure: 'https://michangarrito.com/pago/fallido', pending: 'https://michangarrito.com/pago/pendiente', }, auto_return: 'approved', external_reference: 'sale_12345', }, }); ``` --- ## 4. Rate Limits | Limite | Valor | Periodo | Accion si excede | |--------|-------|---------|------------------| | API calls (general) | 10,000 | por minuto | 429 Too Many Requests | | Crear preferencias | 1,000 | por minuto | 429 | | Consultar pagos | 5,000 | por minuto | 429 | | Webhooks entrantes | Sin limite | - | N/A | ### Estrategia de Retry ```typescript const delays = [1000, 2000, 4000, 8000]; async function callWithRetry(fn: () => Promise): Promise { for (let attempt = 0; attempt < delays.length; attempt++) { try { return await fn(); } catch (error) { if (error.status === 429) { await sleep(delays[attempt]); continue; } throw error; } } throw new Error('Max retries exceeded'); } // Uso const payment = await callWithRetry(() => paymentService.get(paymentId)); ``` --- ## 5. Manejo de Errores ### Codigos de Error | Codigo | Descripcion | Accion Recomendada | Retry | |--------|-------------|-------------------|-------| | 400 | Parametros invalidos | Validar payload antes de enviar | NO | | 401 | Token invalido o expirado | Regenerar access token | NO | | 402 | Pago rechazado | Informar al usuario, sugerir otro metodo | NO | | 403 | Cuenta no autorizada | Verificar permisos de la aplicacion | NO | | 404 | Recurso no encontrado | Verificar ID de pago/preferencia | NO | | 429 | Rate limit excedido | Backoff exponencial | SI | | 500 | Error interno MercadoPago | Retry con backoff | SI | | 503 | Servicio no disponible | Retry con backoff, activar fallback | SI | ### Ejemplo de Manejo ```typescript import { Logger } from '@nestjs/common'; try { const payment = await mercadopagoService.createPayment(paymentData); return { success: true, paymentId: payment.id }; } catch (error) { const logger = new Logger('MercadoPago'); logger.error('MercadoPago error', { service: 'mercadopago', operation: 'createPayment', status: error.status, code: error.cause?.code, message: error.message, tenantId: context.tenantId, externalReference: paymentData.external_reference, }); // Clasificar error if (error.status === 402) { return { success: false, error: 'payment_rejected', userMessage: 'El pago fue rechazado' }; } if (error.status === 429 || error.status >= 500) { // Encolar para reintento await this.queue.add('mercadopago-retry', { paymentData, tenantId: context.tenantId }); return { success: false, error: 'queued', userMessage: 'Procesando pago, te notificaremos' }; } throw error; } ``` --- ## 6. Fallbacks ### Estrategia de Fallback | Escenario | Fallback | Tiempo maximo | |-----------|----------|---------------| | API caida | Cola Redis para reintentar | 24 horas | | Rate limited | Throttle + prioridad | 1 hora | | Token expirado | Alerta admin + regenerar | Inmediato | | Pago rechazado | Sugerir efectivo o transferencia | N/A | ### Modo Degradado Si MercadoPago no esta disponible: ```typescript async processPayment(saleId: string, amount: number, method: string) { if (await this.mercadopagoHealthCheck()) { return this.mercadopagoService.createPayment({ saleId, amount }); } // Modo degradado: registrar pago pendiente this.logger.warn('MercadoPago unavailable, entering degraded mode', { service: 'mercadopago', saleId, }); // Opciones alternativas return { status: 'degraded', alternatives: [ { method: 'cash', message: 'Aceptar pago en efectivo' }, { method: 'bank_transfer', message: 'Solicitar transferencia bancaria' }, ], pendingQueue: await this.queue.add('payment-pending', { saleId, amount }), }; } ``` ### Recuperacion Automatica - Worker de Redis revisa cada 5 minutos si API esta disponible - Reintentos automaticos de pagos encolados - Notificacion al tenant cuando pago se procesa --- ## 7. Multi-tenant ### Modelo de Credenciales - [x] **Por Tenant:** Cada tenant usa su cuenta MercadoPago Los fondos van directo a la cuenta del dueno de la tienda. ### Almacenamiento ```sql CREATE TABLE sales.tenant_mercadopago_config ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID REFERENCES auth.tenants(id) NOT NULL, access_token TEXT NOT NULL, -- Encriptado con AES-256 public_key VARCHAR(100) NOT NULL, collector_id VARCHAR(50), user_id VARCHAR(50), is_sandbox BOOLEAN DEFAULT false, 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 busquedas CREATE INDEX idx_mercadopago_config_tenant ON sales.tenant_mercadopago_config(tenant_id); ``` ### Contexto en Llamadas ```typescript async createPaymentForTenant( tenantId: UUID, saleId: string, amount: number, description: string ): Promise { const config = await this.getTenantMercadopagoConfig(tenantId); if (!config || !config.is_active) { throw new MercadoPagoNotConfiguredError(tenantId); } // Crear cliente con credenciales del tenant const client = new MercadoPagoConfig({ accessToken: this.decrypt(config.access_token), }); const preference = new Preference(client); return preference.create({ body: { items: [{ title: description, unit_price: amount, quantity: 1, currency_id: 'MXN', }], external_reference: saleId, notification_url: `https://api.michangarrito.com/webhooks/mercadopago/${tenantId}`, }, }); } ``` --- ## 8. Webhooks (IPN) ### Endpoints Registrados | Evento | Endpoint Local | Descripcion | |--------|----------------|-------------| | `payment` | `/webhooks/mercadopago/:tenantId` | Pago recibido/actualizado | | `merchant_order` | `/webhooks/mercadopago/:tenantId` | Orden actualizada | | `point_integration_ipn` | `/webhooks/mercadopago/:tenantId` | Terminal Point | ### Validacion de Firma IPN ```typescript import * as crypto from 'crypto'; function verifyMercadoPagoSignature( xSignature: string, xRequestId: string, dataId: string, webhookSecret: string ): boolean { // Extraer ts y hash del header x-signature // Formato: ts=xxx,v1=hash const parts = xSignature.split(','); const ts = parts.find(p => p.startsWith('ts='))?.split('=')[1]; const hash = parts.find(p => p.startsWith('v1='))?.split('=')[1]; if (!ts || !hash) { return false; } // Construir el manifest para validar const manifest = `id:${dataId};request-id:${xRequestId};ts:${ts};`; // Calcular HMAC const expected = crypto .createHmac('sha256', webhookSecret) .update(manifest) .digest('hex'); return crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(expected)); } // Uso en controller @Post('webhooks/mercadopago/:tenantId') async handleWebhook( @Param('tenantId') tenantId: string, @Headers('x-signature') signature: string, @Headers('x-request-id') requestId: string, @Query('data.id') dataId: string, @Body() body: MercadoPagoWebhookPayload, ) { const config = await this.getTenantConfig(tenantId); if (!verifyMercadoPagoSignature(signature, requestId, dataId, config.webhook_secret)) { throw new UnauthorizedException('Invalid webhook signature'); } // Procesar el evento await this.processWebhookEvent(tenantId, body); return { received: true }; } ``` ### Configuracion en MercadoPago 1. MercadoPago Dashboard → Tu aplicacion → Webhooks 2. URL de notificacion: `https://api.michangarrito.com/webhooks/mercadopago/{tenant_id}` 3. Eventos a suscribir: `payment`, `merchant_order` 4. Guardar y probar --- ## 9. Testing ### Modo Sandbox/Test | Ambiente | Credenciales | Datos | |----------|--------------|-------| | Sandbox | Test Access Token | Tarjetas de prueba MercadoPago | | Production | Production Access Token | Tarjetas reales | ### Tarjetas de Prueba (Mexico) | Numero | CVV | Vencimiento | Resultado | |--------|-----|-------------|-----------| | 5474 9254 3267 0366 | 123 | 11/25 | Aprobado | | 4509 9535 6623 3704 | 123 | 11/25 | Rechazado por fondos | | 3711 803032 57522 | 1234 | 11/25 | Pendiente | ### Comandos de Test ```bash # Test de creacion de preferencia curl -X POST https://api.mercadopago.com/checkout/preferences \ -H "Authorization: Bearer ${MERCADOPAGO_ACCESS_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "items": [{ "title": "Test Product", "quantity": 1, "unit_price": 100, "currency_id": "MXN" }], "external_reference": "test_001" }' # Consultar estado de pago curl -X GET "https://api.mercadopago.com/v1/payments/${PAYMENT_ID}" \ -H "Authorization: Bearer ${MERCADOPAGO_ACCESS_TOKEN}" # Test de webhook local curl -X POST http://localhost:3143/webhooks/mercadopago/test-tenant \ -H "Content-Type: application/json" \ -H "x-signature: ts=1234567890,v1=test" \ -H "x-request-id: test-request-id" \ -d '{"action":"payment.created","data":{"id":"12345678"}}' ``` ### Tests Unitarios ```typescript describe('MercadoPagoService', () => { it('should create preference successfully', async () => { const mockPreference = { id: 'pref_123', init_point: 'https://...' }; jest.spyOn(preferenceClient, 'create').mockResolvedValue(mockPreference); const result = await service.createPaymentLink({ saleId: 'sale_001', amount: 100, description: 'Test sale', }); expect(result.id).toBe('pref_123'); expect(result.init_point).toBeDefined(); }); it('should handle rate limit with retry', async () => { jest.spyOn(paymentClient, 'get') .mockRejectedValueOnce({ status: 429 }) .mockResolvedValueOnce({ id: 'pay_123', status: 'approved' }); const result = await service.getPaymentWithRetry('pay_123'); expect(result.status).toBe('approved'); }); }); ``` --- ## 10. Monitoreo ### Metricas a Monitorear | Metrica | Descripcion | Alerta | |---------|-------------|--------| | Latencia | Tiempo de respuesta API | > 5s | | Error Rate | % de requests fallidos | > 5% | | Payments Created | Pagos creados por hora | < 1 (posible caida) | | Payment Success Rate | % de pagos aprobados | < 70% | | Webhook Delay | Tiempo entre evento y procesamiento | > 30s | | Queue Depth | Pagos pendientes en cola | > 100 | ### Logs Estructurados ```typescript // Pago creado exitosamente this.logger.info('MercadoPago payment created', { service: 'mercadopago', operation: 'createPayment', tenantId: context.tenantId, preferenceId: result.id, externalReference: saleId, amount: amount, currency: 'MXN', duration: durationMs, }); // Webhook recibido this.logger.info('MercadoPago webhook received', { service: 'mercadopago', operation: 'webhook', tenantId: tenantId, action: body.action, paymentId: body.data?.id, type: body.type, }); // Error en pago this.logger.error('MercadoPago payment failed', { service: 'mercadopago', operation: 'createPayment', tenantId: context.tenantId, errorCode: error.cause?.code, errorMessage: error.message, saleId: saleId, }); ``` ### Dashboard Sugerido ```yaml # Grafana dashboard panels panels: - title: "Pagos por Estado" type: pie_chart query: "mercadopago_payments_total BY status" - title: "Latencia API" type: time_series query: "histogram_quantile(0.95, mercadopago_api_duration_seconds)" - title: "Tasa de Errores" type: stat query: "rate(mercadopago_errors_total[5m]) / rate(mercadopago_requests_total[5m])" ``` --- ## 11. Referencias ### Documentacion Oficial - [MercadoPago Developers Mexico](https://www.mercadopago.com.mx/developers/es) - [API Reference](https://www.mercadopago.com.mx/developers/es/reference) - [SDK Node.js](https://github.com/mercadopago/sdk-nodejs) - [Webhooks/IPN](https://www.mercadopago.com.mx/developers/es/docs/your-integrations/notifications/webhooks) - [Checkout Pro](https://www.mercadopago.com.mx/developers/es/docs/checkout-pro/landing) - [Tarjetas de Prueba](https://www.mercadopago.com.mx/developers/es/docs/your-integrations/test/cards) ### Modulos Relacionados - [Sales Module](../../apps/backend/src/modules/sales/) - [Payments Service](../../apps/backend/src/modules/payments/) - [Arquitectura Multi-Tenant](../90-transversal/ARQUITECTURA-MULTI-TENANT-INTEGRACIONES.md) ### Soporte - MercadoPago Business Help: https://www.mercadopago.com.mx/ayuda - Developer Support: https://www.mercadopago.com.mx/developers/es/support --- ## Estado de Implementacion ### Checklist - [ ] Crear modulo MercadoPago en backend - [ ] Implementar servicio de pagos - [ ] Configurar webhooks - [ ] Integrar con flujo de ventas - [ ] Testing en sandbox - [ ] Documentar flujo completo ### Bloqueadores - Requiere onboarding de tenants a MercadoPago - Proceso de verificacion de cuenta --- **Ultima actualizacion:** 2026-01-17 **Estado:** PENDIENTE DE IMPLEMENTACION