Files
OnAPB-Carrere_Demartin/docs/superpowers/specs/2026-04-24-pagos-macro-design.md
T
Laucha1312 90c5f85512 2
2026-06-04 15:15:23 -03:00

14 KiB

Diseño: Sistema de Pagos con Banco Macro

Fecha: 2026-04-24
Estado: Aprobado por el usuario — listo para plan de implementación
Proyecto: OnAPB v2 (Laravel / Hostinger Business)


Contexto

OnAPB v2 es el sistema de gestión de la Asociación Paranaense de Básquet. Se busca implementar un sistema de cobros digitales integrado con Banco Macro (Macro Click de Pago — Botón Integrado), centralizado en la cuenta de la APB.

  • Framework: Laravel (PHP)
  • Roles del sistema: super admin (role=1), admin de club (role=2), jugadores/aficionados (usuarios registrados), anónimos
  • Existía una integración con MercadoPago que fue eliminada
  • Las credenciales del Botón Integrado de Macro fueron solicitadas el 2026-04-23 y están pendientes de recibir
  • Contacto Macro: Diego Dallanora — diegodallanora@macro.com.ar — +54 3794 15-0073

Decisiones de diseño

Decisión Resolución
Pasarela de pago Macro Click de Pago — Botón Integrado
Quién recauda La APB centraliza todos los pagos (un solo merchant Macro)
Entrega tienda Retiro físico en sede (sin envíos)
Sanciones Super admin las carga manualmente sobre un jugador
Modelo de datos Tabla polimórfica de pagos (Opción A)

Comisiones Macro (propuesta comercial vigente)

Medio Comisión Acreditación
Tarjeta de crédito 3.05% 18 días hábiles
Tarjeta de crédito en cuotas 3.05% 18 días hábiles
Tarjeta de débito 3.00% 1 día hábil
DEBIN 3.00% 1 día hábil

Descomposición en sub-proyectos

Implementar en este orden (cada uno depende del anterior):

  1. Motor de pagos — integración Macro base, modelo de datos, webhook
  2. Cobros institucionales — inscripciones, multas/sanciones
  3. Tienda online — catálogo, carrito, checkout

Sección 1 — Modelo de datos

Tablas nuevas

concepto_pagos — plantillas configurables por el super admin

Campo Tipo Notas
id PK
nombre string ej: "Inscripción Anual Jugador 2026"
descripcion text
monto decimal(10,2)
tipo enum inscripcion_jugador, inscripcion_equipo, multa, tienda
temporada string nullable ej: "2026" — para conceptos anuales
activo boolean default true
timestamps

pagos — registro de transacciones (polimórfico)

Campo Tipo Notas
id PK
concepto_pago_id FK → concepto_pagos
pagable_type string App\Models\Jugador / Club / OrdenTienda / Sancion
pagable_id unsignedBigInt
monto decimal(10,2) snapshot al crear el pago
estado enum pendiente, pagado, fallido, cancelado
macro_transaction_id string nullable ID devuelto por Macro
macro_payload json nullable respuesta raw de Macro
paid_at timestamp nullable
iniciado_por_type string nullable quién inició (jugador, admin_user, null=anónimo)
iniciado_por_id unsignedBigInt nullable
timestamps

sanciones — registros disciplinarios

Campo Tipo Notas
id PK
jugador_id FK → jugadores
id_club FK → clubes club al momento de la sanción
motivo string
descripcion text nullable
fecha_sancion date
admin_id FK → admin_users quién la cargó
timestamps

El pago de la sanción vive en pagos con pagable_type = App\Models\Sancion.

productos — catálogo de la tienda

Campo Tipo
id PK
nombre string
descripcion text nullable
precio decimal(10,2)
stock unsignedInt
imagen string nullable
activo boolean default true
timestamps

ordenes_tienda — órdenes de compra

Campo Tipo Notas
id PK
user_id FK nullable → users null si comprador anónimo
nombre_comprador string capturado en checkout
email_comprador string para enviar comprobante
estado enum pendiente_pago, pagado, listo_retiro, retirado, cancelado
timestamps

orden_items — líneas de cada orden

Campo Tipo Notas
id PK
orden_id FK → ordenes_tienda
producto_id FK → productos
cantidad unsignedInt
precio_unitario decimal(10,2) snapshot al momento de compra

Relaciones Eloquent

ConceptoPago hasMany Pago
Pago morphTo pagable (Jugador | Club | OrdenTienda | Sancion)
Sancion belongsTo Jugador, Club, AdminUser
OrdenTienda hasMany OrdenItem
OrdenItem belongsTo Producto

Sección 2 — Flujo de pago con Macro (Botón Integrado)

Flujo estándar (aplica a todos los conceptos de pago)

Usuario decide pagar un concepto
         ↓
Sistema crea registro en `pagos` (estado = pendiente)
         ↓
GET /checkout/{pago}
Página con botón Macro embebido (monto + referencia interna precargados)
         ↓
Usuario interactúa con el formulario de Macro
(tarjeta crédito / débito / DEBIN)
         ↓
         ┌──────────────────┬─────────────────┐
         ↓                  ↓                 ↓
    Pago exitoso       Pago fallido      Abandona
         ↓                  ↓                 ↓
  Macro → /pagos/    Macro → /pagos/   Pago queda pendiente
         exitoso           fallido      (expira por cron)
         ↓
POST /api/macro/webhook  ← fuente de verdad
         ↓
Valida firma de Macro
         ↓
Actualiza pago: estado = pagado, paid_at = now(), macro_payload = {...}
         ↓
Dispara evento post-pago según pagable_type
         ↓
Envía email de comprobante al pagador

Regla crítica: El webhook es la fuente de verdad, no la redirección. Las páginas de éxito/fallo son solo UX.

Eventos post-pago por tipo

pagable_type Acción tras confirmar pago
Jugador (inscripción) Marcar jugador como inscripto en la temporada
Club (inscripción equipo) Marcar equipo/club como habilitado para participar
Sancion Marcar sanción como saldada
OrdenTienda Estado → pagado; notificar al super admin

Rutas nuevas

GET  /checkout/{pago}         → página con botón Macro embebido
GET  /pagos/exitoso           → pantalla de éxito (UX)
GET  /pagos/fallido           → pantalla de fallo (UX)
POST /api/macro/webhook       → receptor del webhook (excluido de CSRF)
GET  /tienda                  → catálogo público
GET  /tienda/carrito          → carrito de compras
POST /tienda/checkout         → genera orden + pago, redirige a /checkout/{pago}

Seguridad del webhook

  • Verificar firma de Macro antes de procesar (algoritmo a confirmar con Macro)
  • El monto siempre se toma de pagos.monto, nunca del payload entrante
  • Idempotente: si llega dos veces el mismo macro_transaction_id, no procesar dos veces
  • Log de cada webhook recibido para auditoría

Sección 3 — Panel de administración

Super admin (role=1) — nuevas secciones

A. Conceptos de pago

  • Listado con filtros por tipo y estado (activo/inactivo)
  • Crear / editar: nombre, descripción, monto, tipo, temporada, activo
  • Desactivar un concepto no elimina ni afecta pagos ya realizados

B. Sanciones

  • Formulario: buscar jugador → seleccionar club → ingresar motivo, descripción, fecha → asociar concepto de pago tipo multa
  • Listado con estado de pago (pendiente / saldada); filtros por club, jugador, estado, fecha

C. Tienda

  • CRUD de productos: nombre, descripción, precio, stock, imagen, activo
  • Listado de órdenes con gestión de estado:
    • pagado → "Marcar listo para retirar" → email automático al comprador
    • listo_retiro → "Marcar como retirado"
  • Filtros por estado y fecha

D. Transacciones / Recaudación

  • Tabla de todos los pagos con filtros: estado, tipo de concepto, rango de fechas
  • Totales agrupados por tipo de concepto
  • Exportar CSV para conciliación con la plataforma de Macro

Admin de club (role=2) — nuevas secciones

A. Sanciones del club

  • Lista de sanciones de los jugadores de su club
  • Estado de cada sanción (pendiente / saldada)
  • Botón para pagar una sanción pendiente (el club abona, no el jugador)

B. Inscripciones del club

  • Conceptos de inscripción de equipo disponibles para la temporada vigente
  • Botón para pagar inscripción de equipo

Sección 4 — Vistas del usuario

Jugador registrado (/panel-usuario)

Se agregan dos bloques al panel existente:

A. Mis pagos pendientes

  • Lista de cobros asignados al jugador: inscripción anual, sanciones
  • Por cada uno: concepto, monto, estado, botón "Pagar ahora"
  • Si ya está pagado: fecha + link al comprobante

B. Historial de pagos

  • Todos los pagos realizados (inscripciones, sanciones, compras en tienda)
  • Filtro por estado y fecha

Tienda pública (/tienda)

  • Accesible desde la navbar para todos (registrados y anónimos)
  • Catálogo: productos activos con stock > 0
  • Carrito en sesión (sin necesidad de cuenta)
  • Checkout: formulario con nombre + email → pago con Macro
  • Comprobante por email con información de retiro en sede
  • Si el usuario tiene cuenta: la orden se vincula a su user_id y aparece en su historial

Invitación a registrarse (opcional, no obligatorio)

En el checkout anónimo se muestra: "¿Tenés cuenta? Ingresá para guardar tu historial de compras."


Sección 5 — Consideraciones técnicas y testing

Pendientes a confirmar con Macro al recibir credenciales

  • URL del endpoint para generar transacción / token de pago
  • Formato exacto del webhook (campos, firma, algoritmo de verificación — probablemente HMAC-SHA256)
  • Si existe entorno sandbox para pruebas (solicitarlo explícitamente)
  • Si el botón es JS embebido, iframe, o redirect

Estructura de clases nuevas en Laravel

app/
  Services/
    MacroService.php              ← toda la comunicación con Macro (stub hasta recibir credenciales)
    PagoService.php               ← crea pagos, dispara eventos post-pago
  Events/
    PagoConfirmado.php
  Listeners/
    EnviarComprobantePago.php
    MarcarJugadorInscripto.php
    MarcarSancionSaldada.php
    ActualizarOrdenTienda.php
  Jobs/
    CancelarPagosPendientesVencidos.php   ← cron diario
  Http/Controllers/
    CheckoutController.php
    MacroWebhookController.php
    TiendaController.php
    Admin/ConceptoPagoController.php
    Admin/SancionAdminController.php
    Admin/ProductoController.php
    Admin/OrdenTiendaController.php
    Admin/RecaudacionController.php

Variables de entorno necesarias

MACRO_MERCHANT_ID=
MACRO_API_KEY=
MACRO_SECRET=
MACRO_WEBHOOK_SECRET=
MACRO_ENV=sandbox              # cambiar a "production" al salir a producción
MACRO_SUCCESS_URL="${APP_URL}/pagos/exitoso"
MACRO_FAILURE_URL="${APP_URL}/pagos/fallido"
MACRO_WEBHOOK_URL="${APP_URL}/api/macro/webhook"

Plan de testing en dos etapas

Etapa 1 — Sin credenciales Macro (desarrollo inmediato)

  • Tests unitarios del modelo de datos: crear pagos, sanciones, órdenes
  • Tests de eventos post-pago con MacroService mockeado
  • Tests del webhook con payload simulado y firma fake
  • Tests de las vistas admin (CRUD de conceptos, productos, sanciones)

Etapa 2 — Con Macro sandbox

  • Pruebas end-to-end con tarjeta de prueba en sandbox
  • Verificar que el webhook llega y el estado se actualiza
  • Verificar idempotencia (enviar webhook dos veces → mismo resultado)
  • Verificar email de comprobante

Orden de implementación recomendado

  1. Migraciones + modelos (ConceptoPago, Pago, Sancion, Producto, OrdenTienda, OrdenItem)
  2. MacroService con métodos stub
  3. Panel admin: conceptos de pago → sanciones → tienda → reportes/recaudación
  4. Panel usuario: pagos pendientes + historial
  5. Tienda pública: catálogo + carrito + checkout
  6. Webhook receptor + eventos post-pago + job de cancelación
  7. Reemplazar stubs con implementación real de Macro al recibir credenciales

Inscripción de jugadores — detalle adicional

Dos tipos de inscripción por temporada

La temporada abarca Torneo Apertura + Clausura. Hay jugadores que se incorporan a mitad de año y solo deben pagar una inscripción reducida. Por lo tanto, el super admin crea dos conceptos por temporada:

Concepto Tipo Ejemplo precio Aplica a
Inscripción Anual Completa 2026 inscripcion_jugador $X Jugadores desde Apertura
Inscripción Reducida 2026 (Clausura) inscripcion_jugador $Y Jugadores que ingresan a mitad de año

Generación masiva de deudas

En el panel admin (super admin), sección Conceptos de pago, se agrega una acción:

"Generar deuda masiva"

  1. Super admin selecciona el concepto (ej: "Inscripción Anual Completa 2026")
  2. Sistema muestra todos los jugadores activos que no tienen ya ese concepto generado para esa temporada
  3. Super admin puede desmarcar jugadores individuales (ej: los que van a recibir la inscripción reducida)
  4. Confirma → sistema crea un registro pagos (estado=pendiente) por cada jugador seleccionado
  5. Cada jugador ve la deuda en su panel usuario

Para los jugadores que se incorporan a mitad de año, el admin repite el proceso con el concepto "Inscripción Reducida".

Regla de negocio: Un jugador no puede tener dos pagos pendientes del mismo concepto simultáneamente (se valida antes de generar).

Preguntas abiertas

  • ¿Tiene Macro un entorno sandbox para desarrollo? (pendiente confirmar con Diego Dallanora)
  • ¿Qué campos exactos envía Macro en el webhook? (se confirma al recibir credenciales)