Servicio de pagos en services/api/src/modules/payments/payments.service.js

Overview

Este archivo concentra la lógica de confirmación y creación de órdenes de pago en el módulo de pagos. Trabaja con PaymentOrder, PaymentTransaction, Campaign y User, y coordina los cambios de estado que siguen a un cobro exitoso .

No expone HTTP directamente. Lo consumen otras piezas del flujo de pagos, y su papel es cerrar la operación cuando ya existe una orden interna y un evento confirmado del proveedor fileciteturn0file8turn0file14.

[!NOTE] Este servicio actúa como frontera de persistencia y reglas de negocio. Recibe datos ya resueltos por otros módulos y aplica el cambio final sobre las entidades internas .

How it works

flowchart TB
    Routes[payments routes] --> CreateCampaign[createPaymentOrder]
    Routes --> CreatePlan[createPlanPaymentOrder]
    Webhooks[payments webhooks] --> ConfirmCampaign[confirmPaymentAndActivateCampaign]
    Webhooks --> ConfirmPlan[confirmPaymentAndUpgradePlan]
    Webhooks --> SyncSubscription[syncSubscriptionFromStripe]

    ConfirmCampaign --> PaymentOrder[PaymentOrder]
    ConfirmCampaign --> PaymentTransaction[PaymentTransaction]
    ConfirmCampaign --> Campaign[Campaign]

    ConfirmPlan --> PaymentOrder
    ConfirmPlan --> PaymentTransaction
    ConfirmPlan --> User[User]

    SyncSubscription --> User
    SyncSubscription --> StripeHelpers[getPlanKeyFromPriceId]
    SyncSubscription --> StripeHelpers2[getSubscriptionCurrentPeriodEnd]

El archivo divide su trabajo en dos caminos principales. Uno confirma pagos de campaña y activa la Campaign asociada. El otro confirma pagos de plan y actualiza el User que creó la orden fileciteturn0file8turn0file16.

También contiene una sincronización directa desde una Stripe.Subscription. Ese camino no confirma una orden concreta. En cambio, actualiza el estado local de suscripción del usuario a partir de un evento completo de Stripe .

Key components

{
    "title": "confirmPaymentAndActivateCampaign",
    "content": "Confirma una PaymentOrder de campa\u00f1a como pagada, registra la PaymentTransaction y activa la Campaign vinculada.",
    "type": "function",
    "filePath": "services/api/src/modules/payments/payments.service.js",
    "badges": [
        "public"
    ]
}
{
    "title": "confirmPaymentAndUpgradePlan",
    "content": "Confirma una PaymentOrder de plan, registra la PaymentTransaction y actualiza el User que cre\u00f3 la orden.",
    "type": "function",
    "filePath": "services/api/src/modules/payments/payments.service.js",
    "badges": [
        "public"
    ]
}
{
    "title": "syncSubscriptionFromStripe",
    "content": "Sincroniza el estado local de la suscripci\u00f3n del usuario a partir de una Stripe.Subscription completa.",
    "type": "function",
    "filePath": "services/api/src/modules/payments/payments.service.js",
    "badges": [
        "public"
    ]
}
{
    "title": "createPaymentOrder",
    "content": "Crea una PaymentOrder pendiente para una campa\u00f1a.",
    "type": "function",
    "filePath": "services/api/src/modules/payments/payments.service.js",
    "badges": [
        "public"
    ]
}
{
    "title": "createPlanPaymentOrder",
    "content": "Crea una PaymentOrder pendiente para una actualizaci\u00f3n de plan.",
    "type": "function",
    "filePath": "services/api/src/modules/payments/payments.service.js",
    "badges": [
        "public"
    ]
}

Data & fields

Parámetros de confirmación

[
    {
        "name": "orderId",
        "type": "string",
        "required": true,
        "description": "Identificador interno de la PaymentOrder."
    },
    {
        "name": "provider",
        "type": "string",
        "required": true,
        "description": "Nombre del proveedor que originó el evento."
    },
    {
        "name": "event",
        "type": "string",
        "required": true,
        "description": "Tipo de evento recibido y guardado en PaymentTransaction."
    },
    {
        "name": "externalId",
        "type": "string",
        "required": true,
        "description": "Identificador externo del proveedor. Se persiste en la orden y en la transacción cuando existe."
    },
    {
        "name": "rawPayload",
        "type": "object",
        "required": true,
        "description": "Payload completo recibido del webhook, guardado para trazabilidad."
    },
    {
        "name": "subscription",
        "type": "Stripe.Subscription",
        "description": "Subscription completa de Stripe, requerida para órdenes de plan en Stripe.",
        "children": [
            {
                "name": "metadata.userId",
                "type": "string",
                "description": "Identificador del usuario al que se sincroniza la suscripción."
            },
            {
                "name": "items.data[0].price.id",
                "type": "string",
                "description": "Price ID usado para resolver el plan local."
            },
            {
                "name": "status",
                "type": "string",
                "description": "Estado de la suscripción. Controla si se mantiene o revoca el plan."
            },
            {
                "name": "cancel_at_period_end",
                "type": "boolean",
                "description": "Indica si la suscripción termina al final del período."
            }
        ]
    }
]

Parámetros de creación

[
    {
        "name": "campaignId",
        "type": "string",
        "required": true,
        "description": "Identificador de la campaña asociada a la orden de tipo campaign."
    },
    {
        "name": "planTarget",
        "type": "string",
        "required": true,
        "description": "Plan destino que se guarda en la orden de tipo plan."
    },
    {
        "name": "createdBy",
        "type": "string",
        "required": true,
        "description": "Identificador del usuario que crea la orden."
    },
    {
        "name": "provider",
        "type": "string",
        "required": true,
        "description": "Proveedor de pago asociado a la orden."
    },
    {
        "name": "amount",
        "type": "number",
        "required": true,
        "description": "Importe total de la orden."
    },
    {
        "name": "currency",
        "type": "string",
        "description": "Moneda de la orden. Por defecto es MXN."
    }
]

Campos persistidos por el servicio

  • PaymentOrder.status [badge:read-only] valor de negocio que pasa de pending a paid cuando se confirma el cobro.
  • PaymentOrder.paidAt [badge:read-only] fecha de confirmación del pago.
  • PaymentOrder.externalId [badge:read-only] identificador externo del proveedor, si existe.
  • PaymentOrder.type [badge:read-only] distingue entre campaign y plan.
  • PaymentOrder.planTarget [badge:read-only] destino de plan que se aplica al usuario.
  • PaymentOrder.campaignId [badge:read-only] referencia a la campaña asociada.
  • PaymentOrder.createdBy [badge:read-only] usuario que originó la orden.
  • PaymentTransaction.paymentOrderId [badge:read-only] referencia a la orden confirmada.
  • PaymentTransaction.provider [badge:read-only] proveedor que emitió el evento.
  • PaymentTransaction.event [badge:read-only] nombre del evento procesado.
  • PaymentTransaction.externalId [badge:read-only] identificador del objeto externo.
  • PaymentTransaction.rawPayload [badge:read-only] payload completo del webhook.
  • Campaign.status [badge:read-only] pasa a active cuando la orden de campaña se confirma.
  • Campaign.paymentOrderId [badge:read-only] enlaza la campaña con la orden pagada.
  • User.plan [badge:read-only] plan activo del usuario.
  • User.stripeCustomerId [badge:read-only] cliente de Stripe asociado.
  • User.stripeSubscriptionId [badge:read-only] suscripción activa en Stripe.
  • User.subscriptionStatus [badge:read-only] estado local de la suscripción.
  • User.currentPeriodEnd [badge:read-only] final del período actual.
  • User.cancelAtPeriodEnd [badge:read-only] bandera de cancelación al final del período.

El flujo syncSubscriptionFromStripe usa estos datos de la suscripción para decidir el estado local:

  • metadata.userId para ubicar el User.
  • items.data[0].price.id para resolver el plan local.
  • status para aplicar la política de acceso.
  • cancel_at_period_end para reflejar cancelación futura.
  • id para guardar stripeSubscriptionId.

Behaviors

  • confirmPaymentAndActivateCampaign — busca la PaymentOrder, evita reprocesar órdenes ya pagadas, la marca como paid, crea una PaymentTransaction, activa la Campaign y devuelve el resultado.
  • confirmPaymentAndUpgradePlan — confirma una orden de plan, registra la transacción y actualiza el User con el plan y el estado de suscripción.
  • syncSubscriptionFromStripe — sincroniza el estado local del usuario a partir de una Stripe.Subscription completa y devuelve si la sincronización ocurrió.
  • createPaymentOrder — crea una PaymentOrder nueva con tipo campaign y estado inicial pending.
  • createPlanPaymentOrder — crea una PaymentOrder nueva con tipo plan y estado inicial pending.

Workflows

1. Crear orden pendiente | `createPaymentOrder` o `createPlanPaymentOrder` guarda una `PaymentOrder` con estado `pending`.
2. Confirmar la orden | `confirmPaymentAndActivateCampaign` o `confirmPaymentAndUpgradePlan` carga la orden por `orderId`.
3. Evitar reproceso | Si la orden ya está `paid`, la función devuelve `alreadyProcessed: true`.
4. Marcar pago | La orden pasa a `paid` y recibe `paidAt`.
5. Registrar evidencia | Se crea una `PaymentTransaction` con `provider`, `event`, `externalId` y `rawPayload`.
6. Aplicar el efecto final | Se activa la `Campaign` o se actualiza el `User`.
7. Sincronizar suscripción | `syncSubscriptionFromStripe` actualiza el estado local desde una `Stripe.Subscription`.

Gotchas & edge cases

[!WARNING] confirmPaymentAndActivateCampaign y confirmPaymentAndUpgradePlan son idempotentes. Si order.status === "paid", devuelven alreadyProcessed: true y no repiten los efectos secundarios fileciteturn0file11turn0file13.

[!WARNING] confirmPaymentAndUpgradePlan exige subscription cuando provider === "stripe" y order.type === "plan". Si falta, lanza un error antes de tocar la base de datos .

[!NOTE] syncSubscriptionFromStripe retorna { synced: false } si la suscripción no trae metadata.userId. Ese dato es obligatorio para ubicar al usuario local .

[!CAUTION] syncSubscriptionFromStripe usa una política de lista blanca para el acceso. active y trialing mantienen o asignan plan; past_due no cambia el plan; cualquier otro estado lo lleva a free .

[!IMPORTANT] En la confirmación de plan, el plan solo se asigna cuando la suscripción de Stripe ya está confirmada. El código trata active y trialing como estados válidos para otorgar acceso fileciteturn0file0turn0file11.

  • services/api/src/models/PaymentOrder.js — almacena las órdenes que este servicio confirma o crea.
  • services/api/src/models/PaymentTransaction.js — guarda la evidencia de cada webhook procesado.
  • services/api/src/models/Campaign.js — recibe la actualización de estado cuando una campaña se paga.
  • services/api/src/models/User.js — recibe la actualización del plan y de la suscripción.
  • services/api/src/modules/payments/providers/stripe.provider.js — aporta getPlanKeyFromPriceId y getSubscriptionCurrentPeriodEnd.
  • services/api/src/modules/payments/payments.routes.js — crea órdenes antes del checkout.
  • services/api/src/modules/payments/payments.webhooks.js — invoca estas funciones cuando llega un evento confirmado.

[!NOTE] En este archivo, la lógica de negocio no depende de la creación del checkout. Solo consume órdenes existentes y eventos ya validados por la capa que llama.