Saltar a contenido

mod.billing — Stripe Checkout (pago único)

Community · Categoría core (siempre activo)

Qué hace

Monetización de cursos con pago único vía Stripe Checkout: el admin liga cada curso a un precio de Stripe (o lo crea desde Didacta), el alumno pulsa comprar, el backend crea la Checkout Session y redirige al checkout hosted. Al confirmarse el pago, el webhook completa la orden y emite billing.order.completed, que mod.learning escucha para matricular con origen PURCHASE.

Incluye además el viaje de compra pública: un visitante sin cuenta compra desde el catálogo público (/catalogo) o la ficha de venta del curso (/catalogo/<slug>); la orden nace PENDING sin dueño y el fulfillment materializa la cuenta con el email confirmado por Stripe (find-or-create + email de bienvenida con enlace «Define tu contraseña», plantilla billing.welcome, editable por tenant en Administración → Emails). Las páginas de retorno del pago público son /catalogo/checkout/success y /catalogo/checkout/cancel.

Cómo funciona

  • Idempotencia explícita: cada evento evt_* de Stripe se persiste y no se reprocesa — la reentrega de un checkout anónimo no duplica cuenta ni matrícula.
  • Guardas previas al cobro: 404 curso inexistente, 409 no publicado, 409 si ya tienes acceso.
  • Sin Stripe configurado, el módulo no arranca: el catálogo público devuelve lista vacía y el checkout responde 503 con mensaje claro. El resto de la plataforma no se ve afectado.
  • Los reembolsos se lanzan desde el dashboard de Stripe; el webhook charge.refunded sí se procesa y marca la orden.

Dependencias

Duras: mod.learning, mod.courses.

Modelo de datos

mod_billing_product (curso vendible ↔ precio, unicidad por curso y por precio) · mod_billing_order (compra: PENDING → COMPLETED | CANCELLED | FAILED | REFUNDED, user_id nullable para compra anónima) · mod_billing_webhook_event (log idempotente, PK = stripe_event_id).

API

Prefijo /modules/billing: checkout autenticado, superficie pública (public/catalog, public/offer, public/checkout), CRUD admin de productos y webhook. Detalle en Referencia → Pagos.

Eventos

Emite: billing.order.created/completed/failed/refunded. No consume.

Configuración

Stripe se configura por tenant en Administración → Pagos (credenciales cifradas), compartidas con mod.subscriptions — un único par de claves por academia. mod.billing se registra siempre; sin credenciales (ni de tenant ni de instancia) el checkout responde 503, el resto de la app sigue operativa.

Variable Para qué
STRIPE_SECRET_KEY / STRIPE_WEBHOOK_SECRET Fallback de instancia: solo se usan si el tenant no configuró las suyas en el panel.
BILLING_SUCCESS_URL_BASE / BILLING_CANCEL_URL_BASE Retorno del checkout autenticado (default: /cursos del dominio de la instancia). El checkout público las ignora: siempre vuelve a /catalogo/checkout/success y /catalogo/checkout/cancel sobre el dominio del tenant.