stripe.provider.js
Overview
Este archivo encapsula la integración con Stripe dentro del módulo de pagos. Convierte configuración de entorno y órdenes internas en clientes de Stripe, sesiones de checkout, sesiones del Billing Portal, cambios de suscripción y validación de webhooks.
También actúa como punto de adaptación para otras piezas del módulo. services/api/src/modules/payments/payments.routes.js consume varios helpers de este archivo para checkout, portal y cancelación, services/api/src/modules/payments/payments.service.js usa su mapeo de planes y el cálculo del periodo actual, y services/api/src/modules/payments/payments.webhooks.js usa constructWebhookEvent para validar eventos firmados.
[!IMPORTANT] El módulo depende de
env.stripeSecretKey,env.stripeWebhookSecret,env.appUrl,env.stripePriceProfessionalMonthlyyenv.stripePriceCreatorMonthly. Las tres primeras son críticas para los flujos principales, y las dos últimas son necesarias para los checkouts de plan.
How it works
flowchart TD
Caller[Caller from payments routes or webhooks] --> Provider[stripe.provider]
Provider --> Client[getClient]
Client --> StripeSDK[Stripe SDK]
Provider --> PlanMap[PLAN_PRICE_MAP]
PlanMap --> PriceVars[env stripePriceProfessionalMonthly and env stripePriceCreatorMonthly]
Provider --> Checkout[createCheckout]
Checkout --> CheckoutAPI[Stripe checkout sessions create]
Checkout --> ReturnData[checkoutUrl and externalId]
Provider --> Customer[getOrCreateCustomer]
Customer --> CustomersAPI[Stripe customers create and retrieve]
Provider --> SubscriptionChange[changeSubscriptionPlan]
SubscriptionChange --> SubscriptionsAPI[Stripe subscriptions update]
Provider --> CancelPeriod[cancelSubscriptionAtPeriodEnd]
CancelPeriod --> SubscriptionsAPI
Provider --> Portal[createBillingPortalSession]
Portal --> PortalAPI[Stripe billing portal sessions create]
Provider --> Webhook[constructWebhookEvent]
Webhook --> WebhookAPI[Stripe webhooks constructEvent]
El módulo siempre parte de getClient(). Ese helper valida la clave secreta y crea el cliente de Stripe. Después, PLAN_PRICE_MAP traduce professional y creator a los Price IDs configurados. Con eso, createCheckout decide si usa suscripción o pago único, y los demás helpers operan sobre customers, subscriptions y webhooks.
[!NOTE]
createCheckoutcambia su comportamiento segúnorder.type === "plan". Los planes usanmode: "subscription"y un Price ID preconfigurado. Las órdenes de campaña usanmode: "payment"conprice_datadinámico.
Key components
getClient
{
"title": "getClient",
"content": "Crea una instancia de Stripe con `env.stripeSecretKey`. Lanza un error si la clave no existe.",
"type": "function",
"filePath": "services/api/src/modules/payments/providers/stripe.provider.js",
"badges": [
"read-only"
]
}
getPlanPriceId
{
"title": "getPlanPriceId",
"content": "Devuelve el Price ID de Stripe asociado a un `planTarget` configurado, o `null` si no existe.",
"type": "function",
"filePath": "services/api/src/modules/payments/providers/stripe.provider.js",
"badges": [
"public"
]
}
getPlanKeyFromPriceId
{
"title": "getPlanKeyFromPriceId",
"content": "Convierte un Price ID de Stripe en la clave de plan `professional` o `creator`. Devuelve `null` si no coincide.",
"type": "function",
"filePath": "services/api/src/modules/payments/providers/stripe.provider.js",
"badges": [
"public"
]
}
getOrCreateCustomer
{
"title": "getOrCreateCustomer",
"content": "Reutiliza el Stripe Customer del usuario si sigue siendo v\u00e1lido. Si no existe, crea uno nuevo con metadatos del usuario.",
"type": "function",
"filePath": "services/api/src/modules/payments/providers/stripe.provider.js",
"badges": [
"public"
]
}
changeSubscriptionPlan
{
"title": "changeSubscriptionPlan",
"content": "Actualiza el Price de una suscripci\u00f3n existente sin crear una segunda suscripci\u00f3n. Aplica prorrateo inmediato.",
"type": "function",
"filePath": "services/api/src/modules/payments/providers/stripe.provider.js",
"badges": [
"public"
]
}
cancelSubscriptionAtPeriodEnd
{
"title": "cancelSubscriptionAtPeriodEnd",
"content": "Marca una suscripci\u00f3n para cancelar al final del periodo ya pagado. No corta el acceso de forma inmediata.",
"type": "function",
"filePath": "services/api/src/modules/payments/providers/stripe.provider.js",
"badges": [
"public"
]
}
retrieveSubscription
{
"title": "retrieveSubscription",
"content": "Recupera una suscripci\u00f3n completa desde Stripe para leer estado, periodos y otros campos.",
"type": "function",
"filePath": "services/api/src/modules/payments/providers/stripe.provider.js",
"badges": [
"public"
]
}
getSubscriptionCurrentPeriodEnd
{
"title": "getSubscriptionCurrentPeriodEnd",
"content": "Calcula el fin del periodo actual de una suscripci\u00f3n. Prioriza los `SubscriptionItem` y cae al campo legado si hace falta.",
"type": "function",
"filePath": "services/api/src/modules/payments/providers/stripe.provider.js",
"badges": [
"public"
]
}
createBillingPortalSession
{
"title": "createBillingPortalSession",
"content": "Crea una sesi\u00f3n del Stripe Billing Portal para que el cliente gestione su suscripci\u00f3n en Stripe.",
"type": "function",
"filePath": "services/api/src/modules/payments/providers/stripe.provider.js",
"badges": [
"public"
]
}
createCheckout
{
"title": "createCheckout",
"content": "Crea una Checkout Session de Stripe y devuelve `checkoutUrl` y `externalId`.",
"type": "function",
"filePath": "services/api/src/modules/payments/providers/stripe.provider.js",
"badges": [
"public"
]
}
constructWebhookEvent
{
"title": "constructWebhookEvent",
"content": "Verifica la firma del webhook y devuelve el evento parseado por Stripe.",
"type": "function",
"filePath": "services/api/src/modules/payments/providers/stripe.provider.js",
"badges": [
"public"
]
}
Data & fields
Estos son los valores de entorno y las estructuras que el módulo usa directamente. La mayoría de los flujos dependen de configuración externa y de objetos de Stripe o de la orden interna.
[
{
"name": "env.stripeSecretKey",
"type": "string",
"required": true,
"description": "Clave secreta usada por `getClient()` para instanciar el SDK de Stripe."
},
{
"name": "env.stripeWebhookSecret",
"type": "string",
"required": true,
"description": "Secreto usado por `constructWebhookEvent()` para validar la firma del webhook."
},
{
"name": "env.appUrl",
"type": "string",
"required": true,
"description": "Base URL usada por `createCheckout()` para construir URLs de éxito y cancelación."
},
{
"name": "env.stripePriceProfessionalMonthly",
"type": "string",
"description": "Price ID configurado para el plan `professional`. Si falta, el checkout del plan falla."
},
{
"name": "env.stripePriceCreatorMonthly",
"type": "string",
"description": "Price ID configurado para el plan `creator`. Si falta, el checkout del plan falla."
}
]
PLAN_PRICE_MAP[badge:read-only] — mapeaprofessionalycreatora sus Price IDs configurados.
[
{
"name": "planTarget",
"type": "string",
"required": true,
"description": "Clave de plan usada por `getPlanPriceId()` y `getPlanKeyFromPriceId()`."
},
{
"name": "priceId",
"type": "string",
"description": "Price ID de Stripe usado para encontrar el plan inverso en `getPlanKeyFromPriceId()`."
}
]
[
{
"name": "user",
"type": "object",
"required": true,
"description": "Usuario interno que se sincroniza con Stripe.",
"children": [
{
"name": "id",
"type": "string",
"description": "Identificador del usuario, usado en `metadata.userId`."
},
{
"name": "email",
"type": "string",
"required": true,
"description": "Correo del usuario para crear o reutilizar el Customer."
},
{
"name": "name",
"type": "string",
"required": true,
"description": "Nombre del usuario para el Customer de Stripe."
},
{
"name": "stripeCustomerId",
"type": "string",
"description": "Customer ya guardado en la aplicación."
}
]
}
]
[
{
"name": "subscriptionId",
"type": "string",
"required": true,
"description": "Identificador de la suscripción de Stripe que se va a leer o mutar."
},
{
"name": "newPriceId",
"type": "string",
"required": true,
"description": "Price ID nuevo usado por `changeSubscriptionPlan()`."
}
]
[
{
"name": "order",
"type": "object",
"required": true,
"description": "Orden interna que se transforma en una sesión de checkout.",
"children": [
{
"name": "_id",
"type": "string | ObjectId",
"required": true,
"description": "Identificador de la orden."
},
{
"name": "type",
"type": "string",
"required": true,
"description": "Tipo de orden. El código distingue `plan` y otros pagos."
},
{
"name": "currency",
"type": "string",
"required": true,
"description": "Moneda del cobro."
},
{
"name": "amount",
"type": "number",
"required": true,
"description": "Importe de la orden. Se convierte a centavos."
},
{
"name": "createdBy",
"type": "string | ObjectId",
"required": true,
"description": "Usuario que creó la orden. Se usa en la metadata."
},
{
"name": "planTarget",
"type": "string",
"description": "Clave de plan. Solo se envía cuando `type` es `plan`."
},
{
"name": "campaignId",
"type": "string | ObjectId",
"description": "Campaña asociada. Solo se añade a la metadata si existe."
}
]
},
{
"name": "productName",
"type": "string",
"required": true,
"description": "Nombre visible del producto o concepto en Stripe."
},
{
"name": "customerId",
"type": "string",
"description": "Customer de Stripe reutilizado al crear el checkout."
}
]
[
{
"name": "customerId",
"type": "string",
"required": true,
"description": "Customer de Stripe usado por el Billing Portal."
},
{
"name": "returnUrl",
"type": "string",
"required": true,
"description": "URL de retorno que Stripe usará al cerrar el portal."
}
]
[
{
"name": "rawBody",
"type": "Buffer",
"required": true,
"description": "Cuerpo crudo del webhook recibido."
},
{
"name": "signature",
"type": "string",
"required": true,
"description": "Firma recibida en el encabezado `stripe-signature`."
}
]
[
{
"name": "subscription",
"type": "Stripe.Subscription",
"required": true,
"description": "Objeto completo de suscripción devuelto por Stripe.",
"children": [
{
"name": "items.data[].current_period_end",
"type": "number",
"description": "Fin del periodo por item de suscripción."
},
{
"name": "current_period_end",
"type": "number",
"description": "Campo legado de nivel superior usado como fallback."
}
]
}
]
La función createCheckout() agrega metadatos comunes y específicos del tipo de orden. El código guarda paymentOrderId, userId y source, y añade plan o campaignId cuando corresponde.
paymentOrderId— identifica la orden interna.userId— vincula la sesión con el creador de la orden.source— usa el valor fijoplano_web.plan— solo aparece en órdenes de tipoplan.campaignId— solo aparece si la orden tiene campaña asociada.
Behaviors
getClient()— crea un cliente de Stripe conenv.stripeSecretKeyy lanzaSTRIPE_SECRET_KEY no configurada.si falta la clave.getPlanPriceId(planTarget)— devuelve el Price ID configurado paraprofessionalocreator, onullsi no existe.getPlanKeyFromPriceId(priceId)— invierte el mapeo y devuelve la clave de plan asociada a un Price ID.getOrCreateCustomer(user)— reutilizauser.stripeCustomerIdsi Stripe lo acepta; si no, crea un Customer nuevo con email, nombre ymetadata.userId.changeSubscriptionPlan({ subscriptionId, newPriceId })— recupera la suscripción, toma el primer item y actualiza supricecon prorrateo inmediato.cancelSubscriptionAtPeriodEnd({ subscriptionId })— marca la suscripción para cancelarse al final del periodo ya pagado.retrieveSubscription(subscriptionId)— devuelve la suscripción completa de Stripe.getSubscriptionCurrentPeriodEnd(subscription)— calcula el fin del periodo actual usando el valor más tardío deitems.data[].current_period_end. Si no existe, usacurrent_period_endde nivel superior.createBillingPortalSession({ customerId, returnUrl })— crea una sesión del Billing Portal y devuelve el resultado de Stripe.createCheckout({ order, productName, customerId })— crea una Checkout Session y devuelve{ checkoutUrl, externalId }. Usasubscriptionpara planes ypaymentpara campañas.constructWebhookEvent(rawBody, signature)— valida la firma del webhook y devuelve el evento parseado por Stripe.
[!WARNING]
constructWebhookEvent()necesita el cuerpo crudo comoBuffer. La firma se valida contra ese payload exacto. Si el body cambia, la verificación falla.
[!WARNING]
createCheckout()para planes falla si no existe el Price ID correspondiente enenv.stripePriceProfessionalMonthlyoenv.stripePriceCreatorMonthly. El código corta el flujo con un error de configuración.
[!CAUTION]
changeSubscriptionPlan()aplicaproration_behavior: "create_prorations". El cambio de plan es inmediato y usa prorrateo en la facturación siguiente.
[!NOTE]
getSubscriptionCurrentPeriodEnd()prioriza el modelo moderno de Stripe. El código toma el mayorcurrent_period_enddisponible entre los items de la suscripción.
[!IMPORTANT]
createBillingPortalSession()solo crea la sesión. El portal debe estar configurado en el Dashboard de Stripe para que funcione.
Related
services/api/src/modules/payments/payments.routes.jsimportacreateCheckout as stripeCheckout,getOrCreateCustomer,getPlanPriceId,changeSubscriptionPlan,cancelSubscriptionAtPeriodEnd,createBillingPortalSessionygetSubscriptionCurrentPeriodEnd.services/api/src/modules/payments/payments.service.jsimportagetPlanKeyFromPriceIdygetSubscriptionCurrentPeriodEndpara sincronizar el estado de planes.services/api/src/modules/payments/payments.webhooks.jsusaconstructWebhookEventpara validar eventos de Stripe.
Este archivo también encaja con la lógica de pagos descrita en el módulo. El checkout guarda la referencia externa de Stripe, y los webhooks después usan esos datos para cerrar el ciclo de pago.