michangarrito/docs/02-integraciones/INT-004-mercadopago.md
rckrdmrd 15852f2d6a [MCH-DOC-VAL] docs: Mejorar integraciones INT-004, INT-005, INT-006 con SIMCO 4.0.1
Actualiza documentación de integraciones de pagos a estructura estándar
de 11-13 secciones siguiendo template de INT-001.

Cambios por integración:

INT-004-mercadopago.md (+458 líneas):
- Actualizado simco_version a 4.0.1
- Agregado: Rate Limits con retry strategy
- Agregado: Manejo de Errores completo (8 códigos)
- Agregado: Fallbacks y modo degradado
- Mejorado: Multi-tenant con SQL schema
- Agregado: Webhooks IPN con validación de firma
- Agregado: Testing con tarjetas de prueba MX
- Agregado: Monitoreo con métricas y logs

INT-005-clip.md (+485 líneas):
- Actualizado simco_version a 4.0.1
- Agregado: Rate Limits (100 req/min)
- Agregado: Manejo de Errores (7 códigos)
- Agregado: Fallbacks con cola Redis
- Agregado: Multi-tenant con tenant_clip_config
- Agregado: Webhooks con HMAC validation
- Agregado: Testing con tarjetas Clip MX
- Agregado: Monitoreo y Referencias

INT-006-codi-banxico.md (+694 líneas):
- Actualizado simco_version a 4.0.1
- Agregado: Rate Limits STP/Banxico
- Agregado: Manejo de Errores CoDi/STP
- Agregado: Fallbacks (QR alternativo, manual)
- Agregado: Multi-tenant con CLABEs por tenant
- Agregado: Webhooks STP con firma RSA-SHA256
- Agregado: Testing con CLABEs sandbox
- Agregado: Monitoreo y normatividad mexicana

Total: +1,573 líneas de documentación técnica.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 04:46:57 -06:00

585 lines
16 KiB
Markdown

---
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<T>(fn: () => Promise<T>): Promise<T> {
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<PaymentResult> {
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