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

16 KiB

id type title provider status integration_type created_at updated_at simco_version tags
INT-004 Integration Mercado Pago MercadoPago Pendiente payments 2026-01-04 2026-01-17 4.0.1
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

# 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
  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

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

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

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:

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

  • Por Tenant: Cada tenant usa su cuenta MercadoPago

Los fondos van directo a la cuenta del dueno de la tienda.

Almacenamiento

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

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

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

# 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

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

// 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

# 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

Modulos Relacionados

Soporte


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