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>
585 lines
16 KiB
Markdown
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
|