PaymentOrder

Overview

PaymentOrder es el modelo de Mongoose que persiste las órdenes de pago del sistema. Representa pagos asociados a una campaña o a un plan, y almacena el proveedor, el importe, el estado y los datos de redirección. Su definición vive en services/api/src/models/PaymentOrder.js y usa timestamps: true para guardar createdAt y updatedAt automáticamente

Este modelo actúa como el punto de enlace entre la intención de cobro y la confirmación externa del pago. En el contexto disponible, también aparece como referencia en la lógica de pagos y en scripts de migración que reubican o clasifican relaciones de propiedad del usuario

[!IMPORTANT] La orden puede representar dos escenarios: campaign o plan. Esa decisión cambia qué campos tienen peso operativo, especialmente campaignId y planTarget

How it works

flowchart TB
    PO[PaymentOrder]
    U[User]
    C[Campaign]
    PS[payments service]
    PW[payments webhooks]
    PT[PaymentTransaction]
    M1[migrateLegacyAdmin]
    M2[migratePublicUsers]

    PO --> U
    PO --> C
    PS --> PO
    PS --> PT
    PS --> C
    PW --> PO
    PW --> PT
    M1 --> PO
    M2 --> PO

PaymentOrder define la estructura base que usan los flujos de pago. La lógica de payments.service.js carga la orden, la marca como pagada y registra la transacción del webhook cuando corresponde

payments.webhooks.js se apoya en esta orden para resolver eventos externos de Stripe y MercadoPago, mientras que los scripts migrateLegacyAdmin.js y migratePublicUsers.js la incluyen entre las referencias de propiedad o identidad que recorren durante sus procesos

Key components

{
    "title": "PaymentOrder",
    "content": "Modelo de Mongoose que persiste \u00f3rdenes de pago. Centraliza el tipo de orden, el proveedor, el importe, el estado y las referencias a campa\u00f1a o plan.",
    "type": "class",
    "filePath": "services/api/src/models/PaymentOrder.js",
    "badges": [
        "public"
    ]
}

Data & fields

[
    {
        "name": "type",
        "type": "String",
        "description": "Tipo de orden. Admite \"campaign\" o \"plan\". Su valor por defecto es \"campaign\"."
    },
    {
        "name": "planTarget",
        "type": "String",
        "description": "Destino del plan. Admite \"professional\", \"creator\", \"enterprise\" o cadena vacía. Su valor por defecto es \"\" y se recorta con trim."
    },
    {
        "name": "campaignId",
        "type": "Schema.Types.ObjectId",
        "description": "Referencia a Campaign. Su valor por defecto es null."
    },
    {
        "name": "createdBy",
        "type": "Schema.Types.ObjectId",
        "required": true,
        "description": "Referencia obligatoria al modelo User que creó la orden."
    },
    {
        "name": "provider",
        "type": "String",
        "required": true,
        "description": "Proveedor de pago. Admite \"stripe\" o \"mercadopago\"."
    },
    {
        "name": "amount",
        "type": "Number",
        "required": true,
        "description": "Importe de la orden. Tiene mínimo 0."
    },
    {
        "name": "currency",
        "type": "String",
        "description": "Moneda de cobro. Su valor por defecto es \"MXN\" y se recorta con trim."
    },
    {
        "name": "status",
        "type": "String",
        "description": "Estado de la orden. Admite \"pending\", \"paid\", \"failed\", \"refunded\" o \"cancelled\". Su valor por defecto es \"pending\"."
    },
    {
        "name": "externalId",
        "type": "String",
        "description": "Identificador devuelto por el proveedor, como un Stripe session ID o un Mercado Pago preference ID. Su valor por defecto es \"\"."
    },
    {
        "name": "checkoutUrl",
        "type": "String",
        "description": "URL de pago a la que se redirige al usuario. Su valor por defecto es \"\"."
    },
    {
        "name": "paidAt",
        "type": "Date",
        "description": "Fecha en la que se confirmó el pago. Su valor por defecto es null."
    },
    {
        "name": "createdAt",
        "type": "Date",
        "description": "Marca temporal generada automáticamente por Mongoose."
    },
    {
        "name": "updatedAt",
        "type": "Date",
        "description": "Marca temporal de actualización generada automáticamente por Mongoose."
    }
]
  • type admite campaign y plan
  • planTarget admite professional, creator, enterprise y ""
  • provider admite stripe y mercadopago
  • status admite pending, paid, failed, refunded y cancelled

Los comentarios del esquema conservan enterprise y mercadopago por compatibilidad con órdenes históricas

Behaviors

  • PaymentOrderSchema — define la estructura persistente de la orden y sus restricciones de validación.
  • mongoose.model("PaymentOrder", PaymentOrderSchema) — registra el modelo PaymentOrder en Mongoose para consultas y escrituras
  • timestamps: true — agrega createdAt y updatedAt automáticamente
  • enum en campos clave — restringe valores válidos para type, planTarget, provider y status
  • default en varios campos — crea órdenes listas para el flujo inicial de pago sin completar todos los datos de inmediato

Workflows

1. Crear la orden | `payments.service.js` y el área de pagos preparan una `PaymentOrder` con estado `pending` y los datos del contexto de cobro.
2. Enlazar el origen | La orden conserva el tipo, el proveedor, el importe y la referencia a campaña o plan.
3. Completar el checkout | El flujo de pagos completa `externalId` y `checkoutUrl` cuando el proveedor devuelve la información necesaria.
4. Confirmar el pago | `payments.webhooks.js` y `payments.service.js` usan la orden para marcarla como `paid` y fijar `paidAt`.
5. Resolver el destino | El flujo posterior interpreta `campaignId` o `planTarget` según `type`.

Gotchas & edge cases

[!WARNING] createdBy es obligatorio y referencia a User. El documento no es válido sin ese campo

[!WARNING] provider y amount también son obligatorios. amount además exige un mínimo de 0

[!CAUTION] campaignId puede ser null. Eso encaja con órdenes de tipo plan, donde esa relación no aplica fileciteturn0file6turn0file12

[!NOTE] externalId y checkoutUrl empiezan vacíos. El esquema los reserva para la información devuelta por el proveedor externo

[!IMPORTANT] planTarget permite cadena vacía. Ese valor existe para mantener compatibilidad con órdenes de campaña, donde el destino del plan no se usa fileciteturn0file6turn0file12

  • services/api/src/modules/payments/payments.service.js usa PaymentOrder para confirmar pagos y activar la campaña vinculada. También crea PaymentTransaction durante la confirmación
  • services/api/src/modules/payments/payments.webhooks.js procesa eventos de Stripe y MercadoPago y sincroniza el estado de la orden
  • services/api/src/models/Campaign.js recibe la relación campaignId cuando la orden corresponde a una campaña
  • services/api/src/models/User.js aporta el modelo referenciado por createdBy
  • services/api/src/scripts/migrateLegacyAdmin.js y services/api/src/scripts/migratePublicUsers.js incluyen PaymentOrder entre sus referencias de propiedad e identidad

[!NOTE] Este archivo no define rutas HTTP, controladores ni métodos propios. Su responsabilidad se limita al esquema y al modelo de persistencia de Mongoose