Smart Storages - Mercado Pago Integration Guide
1. Arquitectura y Flujo (Architecture & Flow)
El sistema utiliza una arquitectura Multi-Tenant donde cada organización (Inquilino) configura sus propias credenciales de Mercado Pago.
Diagrama de Secuencia Simplificado
sequenceDiagram
participant User as Cliente
participant FE as Frontend (Smart Storages)
participant BE as Backend (Django)
participant MP as Mercado Pago API
Note over User, MP: Configuración (Setup)
User->>FE: Ingresa Public Key & Access Token
FE->>BE: Guarda Credenciales (Encriptadas)
Note over User, MP: Pago con QR (QR Payment)
User->>FE: Click "Pagar con QR"
FE->>BE: Mutation GenerateQRPayment(saleId)
BE->>MP: POST /instore/orders (Crea Orden en Caja)
MP-->>BE: 201 Created
BE-->>FE: Success
FE->>User: Muestra "Escanea el QR ahora"
User->>MP: Escanea QR con App MP
MP->>BE: Webhook (topic=merchant_order)
BE->>BE: Procesa Pago y Actualiza Venta2. Configuración (Configuration)
2.1 Modelo de Datos (MercadoPagoIntegration)
Ubicación: backend/payments/models.py
| Campo | Descripción |
|---|---|
organization | FK a la Organización (Tenant) dueña de la cuenta. |
access_token_encrypted | Token de MP (Production o Sandbox) encriptado con Fernet. |
webhook_secret_encrypted | Secret para validar la firma HMAC de notificaciones (IPN). |
environment | sandbox (Pruebas) o production (Dinero real). |
2.2 Gestión de Credenciales
- Interfaz:
Settings > Mercado Pago - Seguridad: Los tokens nunca se devuelven en texto plano al frontend. Se usa
*************para indicar que existe valor.
3. Webhooks & Seguridad
3.1 Endpoint
POST /api/payments/webhook/mp/?schema={schema_name}
El parámetro ?schema= es CRÍTICO para enrutar la notificación al tenant correcto en la base de datos (PostgreSQL Schemas).
3.2 Validación de Firma (HMAC)
Ubicación: backend/payments/services.py > validate_webhook_signature
Cada notificación debe incluir el header x-signature. El sistema calcula el HMAC-SHA256 usando el webhook_secret del tenant y lo compara.
- Producción: Si la firma falla, se rechaza con
403 Forbidden. - Sandbox/Local: Si la firma falla, se loguea una advertencia (
WARNING) pero se procesa para facilitar el desarrollo (p.ej. usando Ngrok).
4. Reglas de Negocio (Business Rules)
4.1 Generación de QR
- Validación de Saldo: No se puede generar un QR por un monto mayor al saldo pendiente de la venta.
- Venta Pagada: Si la venta ya está pagada (
is_paid=True), el sistema bloquea la generación de nuevos QRs. - Modelo Híbrido: Usamos QRs Estáticos (imagen fija impresa) pero con Órdenes Dinámicas (el monto se inyecta al momento de la venta). Por eso las Cajas se crean con
fixed_amount=False.
4.2 Webhook Processing
El webhook maneja actualizaciones de estado de pagos:
approved: Crea un registroSalePaymenty, si cubre el total, marca la venta como pagada.refunded: NUEVO. Si se detecta un reembolso, busca el pago original y actualiza el estado (ej. desmarcais_paidenSalePayment) para reflejar que la deuda se mantiene.
5. Salida a Producción (Go Live)
Paso 1: Obtener Credenciales
El cliente final debe:
- Ir a Mercado Pago Developers.
- Crear una Aplicación.
- Copiar
Production Access TokenyPublic Key.
Paso 2: Configurar en Smart Storages
- Ir a
Configuración > Integraciones. - Cambiar Entorno a
Production. - Pegar las credenciales.
- Configurar el Webhook Url en Mercado Pago:
https://{dominio}/api/payments/webhook/mp/?schema={tenant}. - Copiar el
Webhook Secretde MP y pegarlo en Smart Storages.
Paso 3: Validar
Realizar una compra real de monto bajo ($10) y verificar que impacte en el sistema. Luego se puede reembolsar desde el panel de MP.
6. Testing (QA)
Herramientas
- Sandbox Mode: Habilitar en configuración. Usa
Test Access Token. - Tarjetas de Prueba: Usar las provistas en la documentación oficial.
- Refund API: Usar la mutación
refundPayment(paymentId)para simular devoluciones sin ir al panel de MP.
Mutation: Refund Payment (Test Helper)
mutation {
refundPayment(paymentId: "1234567890") {
success
status
}
}Esta documentación describe la implementación completa de la integración de Mercado Pago con el sistema Smart Storages, incluyendo arquitectura multi-tenant, webhooks y seguridad.
Índice
- Arquitectura General
- Configuración (Nuevo Modelo)
- Flujo de Pago
- Webhooks
- Actualización en Tiempo Real
- Seguridad
- Multi-Tenancy
- Archivos Clave
- Variables de Entorno
- Troubleshooting
Arquitectura General
┌─────────────────┐ ┌────────────────┐ ┌─────────────────┐
│ Frontend │ │ Mercado Pago │ │ Backend │
│ (Next.js) │ │ (API) │ │ (Django) │
└────────┬────────┘ └───────┬────────┘ └────────┬────────┘
│ │ │
│ 1. Crear preferencia │ │
│──────────────────────────────────────────────▶│
│ │ │
│ 2. URL de checkout │ │
│◀──────────────────────────────────────────────│
│ │ │
│ 3. Redirect a MP │ │
│──────────────────────▶│ │
│ │ │
│ │ 4. Webhook POST │
│ │───────────────────────▶│
│ │ │
│ │ │ 5. Identificar Tenant
│ │ │ via ?schema=xxx
│ │ │
│ │ │ 6. Verificar pago
│ │ │ via API MP
│ │ │
│ │ │ 7. Registrar
│ │ │ SalePaymentConfiguración (Nuevo Modelo)
Modelo MercadoPagoIntegration
Se ha migrado de un modelo monolítico MercadoPagoConfig a un sistema modular MercadoPagoIntegration que permite mútiples configuraciones por tenant:
# payments/models.py
class MercadoPagoIntegration(TimeStampedModel):
INTEGRATION_TYPES = [
('checkout_pro', 'Checkout Pro'),
('qr_code', 'Código QR'),
]
organization = models.ForeignKey(...)
integration_type = models.CharField(...)
public_key = models.CharField(...)
access_token_encrypted = models.TextField() # Encriptado
webhook_secret_encrypted = models.TextField() # Encriptado
environment = models.CharField(choices=[('sandbox', 'Sandbox'), ('production', 'Production')])
is_active = models.BooleanField(default=True)
# ... metadata adicionalGestión de Credenciales
Las credenciales se gestionan desde el panel de administración (/settings/integrations/mercadopago) que ahora unifica:
- Credenciales: Checkout Pro y Webhook Secrets.
- QR & Sucursales: Gestión de Cajas (POS) y códigos QR físicos.
Las credenciales sensibles se encriptan usando Fernet antes de guardarse en la base de datos.
Flujo de Pago
3. API & Schema
Mutations
generatePaymentLink(Admin/User Authenticated)- Input:
saleId(ID!) - Output:
initPoint,sandboxInitPoint,preferenceId - Use Case: Used by internal users or admins to generate links manually.
- Input:
generatePublicPaymentLink(Public/Anonymous)- Input:
saleToken(String!) - Output:
initPoint,sandboxInitPoint - Use Case: Used by the public
shopfrontend after order creation. - Security: Validates
saleTokenmatches a valid, unpaid sale.
- Input:
refundPayment- Input:
paymentId(String!) - Output:
status,success
- Input:
Webhooks
- Endpoint:
/api/payments/webhook/ - Method:
POST - Query Param:
?schema={schema_name}(Required for multi-tenancy)
2. Tenant Context en Preferencia
Para asegurar que los webhooks identifiquen correctamente al tenant, se anexa el esquema en la URL de notificación:
# payments/api/schema.py
notification_url = f"{settings.WEBHOOK_BASE_URL}/api/v1/payments/webhook/?schema={connection.schema_name}"
preference_data = {
"items": [...],
"external_reference": enc_ref, # Tenant + Sale ID encriptado (Backup de seguridad)
"notification_url": notification_url,
# ...
}Webhooks
URL del Webhook
POST /api/v1/payments/webhook/?schema={tenant_slug}Procesamiento del Webhook (PaymentWebhookView)
Identificación del Tenant:
- Prioridad 1: Parámetro
?schema=xxxen la URL. - Prioridad 2 (Fallback):
democontext.
- Prioridad 1: Parámetro
Obtención de Credenciales:
- Se busca una
MercadoPagoIntegrationactiva del tipocheckout_propara ese tenant. - Se desencripta el Access Token.
- Se busca una
Validación con API MP:
- Se consulta
sdk.payment().get(id)para verificar autenticidad. - Se desencripta el
external_referencecomo capa de seguridad adicional.
- Se consulta
Registro:
- Se crea/actualiza el registro
SalePayment. - Se actualiza el saldo de la
Sale.
- Se crea/actualiza el registro
Actualización en Tiempo Real
Para mejorar la experiencia de usuario, el frontend implementa Polling Inteligente en lugar de WebSockets (por simplicidad de infraestructura).
1. Detalle de Venta (/sales/[id])
- Lógica: El componente
SaleDetailPageverifica el estado del pago cada 3 segundos. - Condiciones de Activación:
- Venta NO cancelada.
- Venta con saldo pendiente.
- Limpieza: El polling se detiene automáticamente (
stopPolling) si:- El pago se completa.
- Se sale de la página (unmount).
- La venta se cancela.
2. Cajón de Pago QR (QRPaymentDrawer)
- Lógica: Verifica el estado de la orden QR (
GetPendingQROrder) cada 3 segundos. - Optimización: SOLO inicia el polling después de haber generado un QR exitosamente. Se detiene inmediatamente al cerrar el cajón.
Seguridad
Capas de Seguridad Implementadas
- Validación de Origen (Tenant Context): El webhook url explícitamente indica a qué tenant pertenece el pago vía
?schema=. - Verificación API: NUNCA confiamos en los datos del body del webhook ciegamente. Siempre consultamos a
GET /v1/payments/{id}usando nuestras credenciales. - External Reference Encriptado: El
external_referencecontienejson({"s": schema, "i": sale_id})encriptado con Fernet. Esto impide que un atacante inyecte pagos a otros tenants. - Encriptación de Base de Datos: Access Tokens y Webhook Secrets están encriptados en reposo.
HMAC Validation
Estado: Deshabilitado por incompatibilidad con
notification_urldinámico.Al definir una
notification_urlpersonalizada por pago (necesario para multi-tenancy dinámico), Mercado Pago firma los webhooks con una clave distinta o deshabilita ciertas validaciones de firma estándar. La seguridad recae en la Verificación API obligatoria.
Multi-Tenancy
Identificación del Tenant (Doble Factor)
- Vía URL:
?schema={tenant}permite al middleware de Django enrutar la request a la base de datos correcta. - Vía Payload: Al consultar la API de MP, el
external_referencedesencriptado confirma que el pago realmente pertenece a ese tenant y esa venta.
Archivos Clave
| Archivo | Descripción |
|---|---|
payments/models.py | Modelo MercadoPagoIntegration |
payments/views.py | PaymentWebhookView (Lógica de recepción y ruteo) |
payments/services.py | MercadoPagoService (SDK, encriptación, validación) |
payments/api/schema.py | Mutations GraphQL (GeneratePaymentLink, GenerateQRPayment) |
frontend/app/sales/[id]/page.tsx | UI de Venta + Polling |
frontend/components/sales/QRPaymentDrawer.tsx | Componente UI para cobro QR |
Troubleshooting
Webhook falla con "Tenant not found"
- Causa: La
notification_urlno tenía el parámetro?schema=. - Solución: Verificar que
GeneratePaymentLinkenschema.pyesté anexando correctamente el parámetro.
Polling "eterno" en el frontend
- Causa: Verificación incondicional en
useQuery. - Solución: Asegurar que
pollIntervalsea 0 por defecto y usarstartPolling/stopPollingbasado en condiciones lógicas.
Error "Invalid Credentials" al procesar webhook
- Causa: El tenant tiene una integración configurada pero el Access Token es inválido o de otro ambiente (Sandbox vs Prod).
- Solución: Ir a
/settings/integrations/mercadopago, re-validar las credenciales usando el botón "Probar Conexión".
Changelog
| Fecha | Cambio |
|---|---|
| 2026-02-06 | Refactorización a MercadoPagoIntegration (Modular) |
| 2026-02-06 | Fix de Webhook Tenant Context (?schema=) |
| 2026-02-06 | Implementación de Polling Inteligente (Frontend) |
| 2026-02-06 | Consolidación de UI de configuración |
| 2026-02-10 | Corrección de redirección en Checkout (Auto Return & HTTPS) |
| 2026-02-10 | Centralización de Traducciones (i18n) para Métodos de Pago |
| 2026-02-10 | Visualización de detalles de pago en UI (Método específico) |
7. Mejoras Recientes (2026-02-10)
7.1 Manejo de URL y Redirección
- Ambiente Local vs Producción: Se implementó una lógica robusta en
GeneratePublicPaymentLinkpara resolverFRONTEND_URL.- Local: Se fuerza IP
127.0.0.1en lugar delocalhostpara evitar rechazos de MP.auto_returnse deshabilita para facilitar debugging. - Producción: Se asegura HTTPS en
back_urlsy se habilitaauto_returnpara una experiencia fluida.
- Local: Se fuerza IP
7.2 Internacionalización (i18n)
- Centralización: Todas las traducciones de métodos de pago (
account_money,credit_card, etc.) se movieron afrontend/core/i18n/context.tsx. - Visualización: El componente de ventas ahora parsea las notas del pago para extraer y traducir el método específico (ej: "Dinero en cuenta") en lugar de mostrar texto técnico o genérico.
7.3 Registro de Pagos
- Tipificación: El backend ahora guarda el
payment_type_idespecífico (ej:debit_card) en lugar de un string genérico "Mercado Pago". Esto permite reportes más detallados y una mejor UI.
7.4 Visualización de Detalles (PaymentDetailsDrawer)
- Componente: Se creó
PaymentDetailsDrawer.tsxpara mostrar metadatos extendidos recuperados directamente de la tabla de pagos de Mercado Pago (mpPayments). - Mapeo de Datos: Debido a que los pagos genéricos del sistema (
SalePayment) se crean vía webhook, la relación con los datos específicos de MP se realiza dinámicamente en el frontend usando la funciónfindMpPayment. Esta función busca un "Ref ID" en las notas del pago y lo vincula con el arraympPaymentsde la venta. - Campos Visualizados: Estado de acreditación, monto neto recibido, detalle de comisiones (fees), fecha de liberación de fondos, y datos de la tarjeta (últimos 4 dígitos).
8. Robustez del Schema GraphQL
Para que la interfaz de ventas funcione correctamente, la consulta GET_SALE_DETAIL DEBE incluir los siguientes campos de control de flujo:
currentStatus.allowedActions: Lista de acciones permitidas para el usuario según el estado actual. Sin este campo, los botones como "Registrar Pago" o "Facturar" no se renderizan.workflow: Objeto que contiene la lista de estados y su orden. Es vital para el componenteWorkflowTracker(Steps).qrCode: Necesario para generar el código de entrega dinámica.client.ivaCondition: Requerido por el motor de facturación (InvoiceDrawer).
9. Archivos Clave
| Archivo | Descripción |
|---|---|
payments/models.py | Modelo MercadoPagoIntegration |
payments/views.py | PaymentWebhookView (Lógica de recepción y ruteo) |
payments/services.py | MercadoPagoService (SDK, encriptación, validación) |
payments/api/schema.py | Mutations GraphQL (GeneratePaymentLink, GenerateQRPayment) |
frontend/app/sales/[id]/page.tsx | UI de Venta + Polling + Integración de Drawer |
frontend/components/sales/PaymentDetailsDrawer.tsx | UI para ver detalles extendidos de MP |
frontend/components/sales/QRPaymentDrawer.tsx | Componente UI para cobro QR |
Changelog
| Fecha | Cambio |
|---|---|
| 2026-02-06 | Refactorización a MercadoPagoIntegration (Modular) |
| 2026-02-06 | Fix de Webhook Tenant Context (?schema=) |
| 2026-02-06 | Implementación de Polling Inteligente (Frontend) |
| 2026-02-06 | Consolidación de UI de configuración |
| 2026-02-10 | Corrección de redirección en Checkout (Auto Return & HTTPS) |
| 2026-02-10 | Centralización de Traducciones (i18n) para Métodos de Pago |
| 2026-02-10 | Visualización de detalles de pago en UI (Método específico) |
| 2026-02-11 | Schema Fix: Restauración de campos críticos (allowedActions, workflow). |
| 2026-02-11 | Documentación: Detalle técnico de PaymentDetailsDrawer y requisitos de esquema. |