Construyendo un sistema de suscripciones multi-tenant con OIDC y pagos integrados
En los últimos meses he estado explorando cómo diseñar un microservicio de suscripciones capaz de cubrir dos escenarios habituales:
-
Aplicaciones SaaS multi-tenant que quieren ofrecer planes de acceso a sus clientes (los tenants).
-
Tenants individuales que, a su vez, desean revender su aplicación a sus propios usuarios finales bajo diferentes niveles de servicio (tiers).
Esto plantea varios retos de arquitectura: ¿dónde modelar los tiers y entitlements?, ¿cómo coordinar identidades con pagos?, ¿cómo automatizar (o semi-automatizar) la creación de tenants?, ¿qué experiencia de usuario debe seguir alguien que quiere suscribirse?
Modelado de base
En el dominio de Suscripciones aparecen varias entidades clave:
-
Catalog: representa la oferta de planes de una aplicación o de un tenant.
-
Tier: cada plan con su precio y limitaciones (ej. número de usuarios, operaciones/mes).
-
Entitlement: los “derechos” que ese plan habilita (ej.
MAX_OPERATIONS_PER_MONTH=1000
). -
Subscription: la relación activa entre un suscriptor (tenant o usuario) y un tier.
-
Charge: los cobros asociados a cada renovación de la suscripción.
El micro de Suscripciones no sólo guarda este modelo, sino que además integra la pasarela de pago (Stripe, Adyen…) y emite eventos a un bus (RabbitMQ) para que otros servicios reaccionen.
Pagos y ciclo de vida de la suscripción
Una suscripción se crea apuntando a un tier
. Cada vez que se renueva:
-
Se registra un Charge con estado
PENDING | PAID | FAILED
. -
Stripe (u otro PSP) devuelve la confirmación con el
payment_intent_id
, el importe y estado. -
No se sobreescribe la suscripción: se mantienen múltiples charges históricos para auditoría.
De esta forma se puede delegar totalmente en la pasarela la lógica de cobro, y el micro de Suscripciones sólo sincroniza estados.
Catálogos y endpoints
Para facilitar la integración con aplicaciones y frontends, el micro expone:
-
GET /catalog/app/:appId
→ muestra los planes de una aplicación. -
GET /catalog/tenant/:tenantId
→ muestra los planes de un tenant para sus usuarios. -
POST /subscriptions
→ crea una suscripción y lanza el flujo de pago. -
Callbacks de pago → reciben las notificaciones de Stripe y actualizan la entidad
Charge
.
Creación de tenants
Uno de los retos más interesantes es la creación de tenants bajo demanda. Los IdP como Keycloak no ofrecen una URL para crear realms en caliente para cualquier visitante. La solución pasa por un flujo semi-automático:
[Cliente] ---> [Página Solicitud Tenant] | v TenantCreationRequested | (RabbitMQ) | v [Backoffice Comercial] | v TenantCreationApproved | v [Provisionador OIDC] | v TenantProvisioned --> [Email invitación]
-
Solicitud de tenant
-
El visitante rellena un formulario con sus datos de contacto y el tier deseado.
-
Se guarda una entidad
TenantRequest
con estadoREQUESTED
.
-
-
Aprobación comercial
-
Un backoffice interno permite aprobar o rechazar la solicitud.
-
Al aprobar, se publica un evento
TenantCreationApproved
.
-
-
Provisionamiento
-
Un worker escucha el evento y crea el tenant en el IdP (sea un realm propio o un atributo en un único realm).
-
Devuelve un evento
TenantProvisioned
con eltenantId
y la URL del login.
-
-
Invitación al cliente
-
El sistema envía un email con el enlace para completar la suscripción y realizar el pago.
-
El usuario hace login ya en su tenant y pasa al checkout.
-
De este modo, el proceso no depende de que el IdP soporte self-service, sino de una orquestación vía eventos entre Suscripciones e Identidades.
Experiencia de usuario
En la práctica, terminamos necesitando cuatro páginas clave:
-
Catálogo de aplicación → donde un cliente potencial (tenant) puede ver planes y solicitar un espacio propio.
-
Catálogo de tenant → donde los usuarios finales ven los planes de su tenant.
-
Suscripción de tenant → el flujo donde un usuario crea un tenant, se registra y paga.
-
Suscripción de usuario → el flujo donde un usuario final se registra y paga dentro del tenant.
A esto se suman las páginas de callback para manejar la respuesta de la pasarela de pago.
[Usuario] --> [Login OIDC] --> [Catálogo] | v [Checkout Pago] | +----------------------+-------------------+ | | PaymentSuccess PaymentFailed | | v v [SubscriptionCreated] [Notificación fallo] [ChargePaid registrado]
Eventos como mecanismo de integración
Para mantener bajo acoplamiento, el micro de Suscripciones emite eventos a RabbitMQ como:
-
TenantCreationRequested
-
TenantCreationApproved
-
TenantProvisioned
-
SubscriptionCreated
-
ChargePaid
Otros micros (Identidades, Notificaciones, CRM interno) pueden reaccionar a estos eventos sin que Suscripciones sepa nada de su implementación.
Conclusión
El resultado es un diseño donde:
-
Suscripciones concentra el catálogo, tiers, suscripciones y pagos.
-
Identidades/OIDC se mantiene intercambiable, sin exponer APIs internas al front.
-
Eventos permiten coordinar tenants, usuarios y pagos de forma desacoplada.
-
La UX está clara: primero login/registro (OIDC), después suscripción/pago.
De esta manera se consigue un sistema multi-tenant escalable, con flexibilidad comercial (tiers para apps y para tenants) y con un proceso de creación de tenants semi-automático que no depende de hacks en el IdP.
👉 ¿Qué opinas de este enfoque? ¿Dejarías la aprobación de tenants 100% automática o prefieres mantener un paso comercial previo?
Comentarios
Publicar un comentario