Saltar a contenido

Multi-Producto

Este SaaS está preparado para vender más de un producto sin duplicar el framework. La jerarquía base es:

Account -> producto -> instancia -> subscription

En la demo incluida hay dos productos:

  • bot: producto principal Help Center Builder.
  • website: producto secundario de demostración para validar billing multi-producto.

smart_form queda como placeholder visual. No incluye billing ni tabla funcional en esta demo.

Modelo de Datos

Los planes se separan por plans.product_type.

select name, product_type, price, stripe_price_id
from plans
order by product_type, price;

Las suscripciones también guardan product_type. Para bots se usa subscriptions.bot_id; para websites se usa subscriptions.website_id.

Stripe

Cada producto real debe tener su propio Stripe Product y Stripe Price. El stripe_price_id se guarda en plans.stripe_price_id.

Metadata mínima recomendada en Checkout:

{
  "product_type": "website",
  "resource_type": "website",
  "website_id": "uuid",
  "account_id": "uuid",
  "plan_id": "uuid"
}

El webhook acepta explícitamente product_type bot y website. Cualquier otro tipo se rechaza para evitar estados ambiguos.

Cómo Agregar un Producto

  1. Crear tabla de instancia, por ejemplo courses, con id, account_id, name, status, timestamps.
  2. Crear planes en plans con product_type='courses', features, translations y stripe_price_id.
  3. Crear endpoint de checkout que genere metadata con product_type='courses' y course_id.
  4. Extender el webhook para aceptar courses y persistir subscriptions.course_id.
  5. Agregar la sección visual en dashboard/superadmin filtrando por product_type.

Traducciones de Planes

Cada plan puede exponer textos comerciales por idioma:

{
  "es": {
    "display_name": "SmartWebsite Standard",
    "description": "Producto website demo con billing Stripe funcional.",
    "features": [
      "1 instancia de website",
      "Demo de landing responsive",
      "Preparado para dominio propio",
      "Billing Stripe habilitado"
    ]
  }
}

La UI usa translations[locale] primero y cae a features si falta la traducción.