Ir al contenido principal

Construyendo un sistema de suscripciones multi-tenant con OIDC y pagos integrados

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:

  1. Aplicaciones SaaS multi-tenant que quieren ofrecer planes de acceso a sus clientes (los tenants).

  2. 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]

  1. Solicitud de tenant

    • El visitante rellena un formulario con sus datos de contacto y el tier deseado.

    • Se guarda una entidad TenantRequest con estado REQUESTED.

  2. Aprobación comercial

    • Un backoffice interno permite aprobar o rechazar la solicitud.

    • Al aprobar, se publica un evento TenantCreationApproved.

  3. 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 el tenantId y la URL del login.

  4. 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:

  1. Catálogo de aplicación → donde un cliente potencial (tenant) puede ver planes y solicitar un espacio propio.

  2. Catálogo de tenant → donde los usuarios finales ven los planes de su tenant.

  3. Suscripción de tenant → el flujo donde un usuario crea un tenant, se registra y paga.

  4. 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