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 fileciteturn0file8turn0file14.
[!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 fileciteturn0file8turn0file16.
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 dependingapaidcuando 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 entrecampaignyplan.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 aactivecuando 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.userIdpara ubicar elUser.items.data[0].price.idpara resolver el plan local.statuspara aplicar la política de acceso.cancel_at_period_endpara reflejar cancelación futura.idpara guardarstripeSubscriptionId.
Behaviors
confirmPaymentAndActivateCampaign— busca laPaymentOrder, evita reprocesar órdenes ya pagadas, la marca comopaid, crea unaPaymentTransaction, activa laCampaigny devuelve el resultado.confirmPaymentAndUpgradePlan— confirma una orden de plan, registra la transacción y actualiza elUsercon el plan y el estado de suscripción.syncSubscriptionFromStripe— sincroniza el estado local del usuario a partir de unaStripe.Subscriptioncompleta y devuelve si la sincronización ocurrió.createPaymentOrder— crea unaPaymentOrdernueva con tipocampaigny estado inicialpending.createPlanPaymentOrder— crea unaPaymentOrdernueva con tipoplany estado inicialpending.
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]
confirmPaymentAndActivateCampaignyconfirmPaymentAndUpgradePlanson idempotentes. Siorder.status === "paid", devuelvenalreadyProcessed: truey no repiten los efectos secundarios fileciteturn0file11turn0file13.
[!WARNING]
confirmPaymentAndUpgradePlanexigesubscriptioncuandoprovider === "stripe"yorder.type === "plan". Si falta, lanza un error antes de tocar la base de datos .
[!NOTE]
syncSubscriptionFromStriperetorna{ synced: false }si la suscripción no traemetadata.userId. Ese dato es obligatorio para ubicar al usuario local .
[!CAUTION]
syncSubscriptionFromStripeusa una política de lista blanca para el acceso.activeytrialingmantienen o asignan plan;past_dueno cambia el plan; cualquier otro estado lo lleva afree.
[!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
activeytrialingcomo estados válidos para otorgar acceso fileciteturn0file0turn0file11.
Related
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— aportagetPlanKeyFromPriceIdygetSubscriptionCurrentPeriodEnd.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.