Saltar al contenido
Micro Frontend

Definir el usuario y autenticación

El micro-frontend renderiza la tienda de entradas nativamente en tu página. Quién es el usuario proviene de TU backend — la página anfitriona es responsable de autenticar al usuario. Un token de sesión es obligatorio: sin él, el micro-frontend permanece inactivo. Esta página explica por qué y el flujo de sesión servidor a servidor.

La tecnología detrás del micro-frontend

La tienda de entradas ADITUS, entretejida en lugar de teletransportada. El micro-frontend renderiza el proceso de compra completo nativamente en tu página — sin iframe, sin cuerpo extraño visual. Está construido ligero y nativo al anfitrión, de modo que se ve y se siente como tu propio sitio, no como un widget incrustado.

React 19 — compatible hasta React 18

El flujo completo — eventos, artículos, carrito, registro, pago, finalización — se ejecuta como un único flujo React totalmente tipado. Nosotros mismos ya usamos React 19, pero mantenemos deliberadamente la compatibilidad abierta hasta React 18 (peerDependency react >=18), para que el micro-frontend se integre en el mayor número posible de páginas anfitrionas. Ese rango peer es literal: internamente el adaptador usa solo el conjunto de funciones de React 18 (useState, useEffect, useMemo & co.) y ninguna API exclusiva de React 19 — construido y probado bajo React 19, garantizado que funciona bajo React 18. React es solo una peerDependency: el adaptador no aporta ninguna dependencia de runtime propia. Sin biblioteca de UI, sin framework de data-fetching, sin toolkit de estado — solo hooks de React seleccionados a mano. El resultado es un bundle diminuto que no arrastra nada a tu página.

Light DOM en vez de iframe

Sin iframe, sin shadow DOM, sin cuerpo extraño. La tienda se renderiza directamente en el DOM de tu página: hereda tu tipografía, encaja en tu diseño, es totalmente responsiva y accesible — y se lee para tu visitante como una parte nativa de tu sitio web.

Agnóstico al framework: un solo mount(), en todas partes

Una sola llamada — mount(container, config) — adjunta la tienda a cualquier elemento del DOM. Ya sea React, Vue, Angular o HTML puro: el adaptador es neutral al framework y funciona igual en todas partes.

El estilo es tuyo

Cada elemento lleva un hook CSS estable (clases BEM aditus-shop__* más data-state). Estilizas libremente con tu propio CSS — o defines en un instante los colores de tu marca, radios y espaciados con un puñado de design tokens --aditus-*. Opcionalmente con estilos base limpiamente aislados mediante CSS @layer que nunca sobrescriben tu página.

Multilingüe, conmutable en vivo

DE/EN vienen integrados. Un cambio de idioma se envía directamente a la instancia en ejecución mediante handle.update() — sin reiniciar el recorrido del usuario. El carrito y el progreso se conservan.

Seguro por diseño

Sin un token de sesión válido, el adaptador está inactivo — entonces no realiza ni una sola petición. La sesión se crea servidor a servidor con un secreto; ninguna clave llega jamás al browser. Todas las llamadas pasan por un proxy que inyecta las credenciales en el servidor.

Requisitos del entorno anfitrión

En la capa de integración, el micro-frontend es deliberadamente lo más compatible posible — cualquier framework, cualquier CSS anfitrión, React 18+. Los puntos fijos inevitables son pocos e inofensivos: un cliente en el browser, React presente y una sesión con un publicKey en lista blanca.

Lo que se necesita

  • React 18+ y react-dom 18+ en el bundle anfitrión — declarados como peerDependency react >=18; mount() usa createRoot de react-dom/client. El anfitrión decide la versión de React.
  • Un browser con un DOM (lado cliente). El adaptador monta en un elemento real del DOM (light DOM). Los anfitriones SSR están bien mientras mount() se ejecute en el cliente.
  • Un browser evergreen actual — usa API web estándar (fetch, AbortController, ResizeObserver, CSS @layer), baseline desde ~2022. Sin IE.
  • Una sesión más un publicKey — sin sessionToken, el micro-frontend permanece inactivo. Genera la sesión servidor a servidor con el secreto (anfitrión con backend) o desde el browser mediante el endpoint /shop/embed-session filtrado por dominio (anfitrión sin backend). El dominio debe estar en la lista blanca del publicKey.

Lo que deliberadamente NO exigimos

  • Ningún framework en particular — mount(container, config) es neutral al framework: React, Vue, Angular, HTML puro.
  • Sin iframe, sin shadow DOM — se renderiza nativamente en tu página.
  • Ningún CSS anfitrión particular — los estilos base residen en @layer aditus-shop (cualquier regla anfitriona sin capa gana), con especificidad de una sola clase, más una armadura de reset contra un host * { margin:0; padding:0 }. Totalmente desactivable con baseStyles: false.
  • Sin dependencias de runtime — solo las peerDeps de React, nada que colisione en el bundle anfitrión.
  • Ninguna configuración de build particular más allá de un import ESM.

Seguridad de pagos, PCI y CSP

La integración de componentes de terceros como las taquillas obliga tradicionalmente a un compromiso entre experiencia de usuario, rendimiento y seguridad informática. El micro-frontend resuelve ese conflicto con un paradigma híbrido: el flujo de interacción se renderiza en el light DOM nativo de tu página mientras que el paso de pago permanece estrictamente aislado — todo lo inofensivo ocurre en tu página, el pago en sí nunca.

Sin datos de pago en el light DOM

La preparación completa de la compra — evento, entradas, extras, carrito, registro — se renderiza de forma nativa en tu página. En el momento en que el usuario inicia un pago por redirección, el flujo sensible abandona por completo tu página: una navegación top-level normal cede el control a la página de pago externa dedicada del proveedor, y el viaje de vuelta reanuda el recorrido y completa el pedido. Los datos de tarjeta o bancarios se introducen solo allí — el micro-frontend nunca renderiza, transporta ni almacena credenciales de pago en tu página.

Alcance PCI mínimo

Dado que en ningún momento se procesan, transportan ni almacenan datos de tarjeta de crédito o bancarios en tu aplicación anfitriona, tu entorno permanece en el alcance PCI-DSS mínimo del modelo de redirección clásico (típicamente SAQ A — la clasificación vinculante siempre la realiza tu adquirente o QSA). Los métodos de pago sin redirección, como la factura, se completan del lado del servidor — sin ningún dato de pago en el browser.

Content Security Policy ligera

Además de tu política existente, el micro-frontend en sí solo necesita dos permisos: connect-src para el origen de la API ADITUS (cada llamada de la tienda pasa por él) e img-src para las URL de assets de ADITUS si muestras el banner del evento o imágenes de artículos. En particular, no necesita ninguna entrada script-src o frame-src para proveedores de pago, porque el paso de pago es una simple redirección top-level: en tu página nunca se cargan scripts ni frames de pago.

Lo mejor de ambos mundos

Clásicamente había que elegir: un iframe (aislado, pero rígido y un cuerpo extraño visual) o una integración API completa (fluida, pero tu equipo de seguridad asume de golpe todo el alcance PCI). El micro-frontend resuelve ese dilema: la interacción inofensiva — navegación, carrito, configuración de entradas — vive de forma nativa en tu página; la interacción crítica — el cobro — se corta en seco y se aísla externamente.

En qué se diferencia de las alternativas del mercado

Frente al iframe clásico

Los iframes se consideran seguros gracias al estricto aislamiento del browser, pero se comportan como documentos aislados: no heredan ni los estilos globales como la tipografía o las reglas de diseño responsive, ni su contenido es indexable de forma fiable para los rastreadores; para los lectores de pantalla suelen suponer una ruptura de medio. El micro-frontend renderiza toda la preparación de la compra — selección de artículos, carrito, registro — como HTML real directamente en el DOM de tu página, haciéndola tan accesible y responsive como tu propio marcado. El efecto de aislamiento de un iframe solo se recrea en el momento del pago: el flujo de datos crítico se transfiere por completo, mediante una redirección top-level, a la página de pago externa y dedicada.

Flexibilidad frente al Shadow DOM puro

Muchos web components se encapsulan en un shadow DOM para evitar conflictos de especificidad CSS — en la práctica esto suele conducir a un branding incompleto, porque los estilos globales quedan bloqueados y las reglas de diseño hay que transmitirlas laboriosamente mediante atributos part o custom properties. El micro-frontend usa en cambio la cascada CSS nativa: sus estilos base residen en @layer aditus-shop, una capa deliberadamente baja — cada regla CSS sin capa de tu página gana automáticamente. Tu diseño corporativo se hereda nativamente, sin guerras de especificidad contra los componentes de UI de la tienda.

Eficiencia de recursos y tamaño del bundle

Los widgets monolíticos suelen traer sus propios clientes HTTP, gestión de estado y bibliotecas de UI — y pueden forzar al browser a cargar runtimes redundantes en paralelo, perjudicando el tiempo de carga de tu página (Core Web Vitals). El micro-frontend declara React y React-DOM exclusivamente como peerDependency y no tiene ninguna dependencia de runtime propia: si tu página ya usa React 18 o más reciente, se engancha directamente al runtime existente. Para entornos no-React, el web component opcional encapsula la dependencia de React en su propio bundle aislado, sin ocupar variables globales en tu página.

Los enfoques comparados

CriterioIframe clásicoWidget Shadow DOMMicro-frontend ADITUS
SEO y accesibilidadEl contenido vive en un documento separado — a menudo invisible para los rastreadores, más difícil para los lectores de pantalla.Aislado; la visibilidad y la accesibilidad suelen requerir trabajo adicional.Parte nativa de tu DOM — indexable y accesible como tu propio marcado.
Estilo y brandingRígido: sin herencia de CSS, personalización solo a través de las API postMessage.Los estilos globales no llegan al interior; el theming solo mediante propiedades y parts transferidos.Tu CSS gana por definición mediante @layer; el skin base es sobrescribible o totalmente desactivable.
Bundle y rendimientoNormalmente carga una segunda aplicación completa, incluido su runtime, dentro del frame.A menudo trae su propio runtime y dependencias duplicadas.Usa el React de tu página; cero dependencias de runtime propias.
Seguridad de pagos y alcance PCIFuertemente aislado — pero a costa de la UX, el diseño y la capacidad de respuesta.Los datos de pago pasarían por el DOM anfitrión si se capturaran allí.Selección en el light DOM, pago totalmente aislado en la página de pago externa.

Cómo obtener el micro-frontend

El micro-frontend no está en el npm público ni en un CDN. Se entrega como paquete fuente directamente desde tu instancia ADITUS: TypeScript, ESM, totalmente tipado, sin ninguna dependencia de runtime propia — tu bundler (Vite, webpack, Next.js, Nuxt) lo compila junto con tu aplicación como tu propio código. El nombre de import en los snippets de esta página (@workspace/aditus-shop-embed) es el nombre de paquete de nuestra integración de referencia; el paquete descargable se llama @aditus/shop-embed — la API es idéntica.

Qué contiene la entrega

El adaptador completo como código fuente TypeScript legible con declaraciones de tipos para cada opción de config, callback y evento. React y react-dom siguen siendo peerDependencies — tu página los proporciona, así que nada colisiona en tu bundle y no hay un segundo React.

Obtener el paquete

El paquete es idéntico para cada anfitrión y se descarga directamente desde esta instancia, sin credenciales: {{API_BASE_URL}}/api/embed/v1/aditus-shop-embed.tgz. Instálalo directamente desde esa URL (npm install acepta una URL de tarball — usa el destino del enlace en tu instancia, ruta /api/embed/v1/aditus-shop-embed.tgz) o descomprímelo en tu repositorio. Lo que el onboarding realmente proporciona es tu publicKey con su lista blanca de dominios; sin él, el micro-frontend permanece inactivo. Contacto: página de contacto. Los anfitriones sin su propio paso de build (WordPress, HTML puro) no necesitan el paquete fuente en absoluto: usan el bundle Web Component autoalojado descrito en la siguiente sección.

Web Component: sin paso de build

Para páginas sin su propio bundler — WordPress, Typo3, HTML puro — el micro-frontend también se entrega como bundle preconstruido y autoalojado: una sola etiqueta script regista el elemento <aditus-shop>, React incluido, sin npm, sin build. Se entrega desde tu instancia ADITUS bajo una URL versionada (/api/embed/v1/…), no desde un CDN público — para este sistema de demostración es https://developers.aditus.com/api/embed/v1/aditus-shop.js. La URL es deliberadamente pública y no requiere credenciales: el bundle es código publicado sin más, la verdadera barrera es tu clave publicable con su lista blanca de dominios más la sesión. Esta vía es estrictamente opcional y puramente aditiva: los anfitriones con su propio build siguen integrando el paquete fuente mediante mount(), exactamente como se documenta en esta página — mismo recorrido, mismos hooks CSS, mismo theming.

index.html
<!-- 1. Das selbst gehostete Bundle einbinden (einmal pro Seite). -->
<script src="https://ihre-aditus-instanz.de/api/embed/v1/aditus-shop.js" defer></script>

<!-- 2. Das Element platzieren — der Shop rendert nativ an dieser Stelle. -->
<aditus-shop
  public-key="pk_live_ihrkey"
  culture="de-DE"
></aditus-shop>

<!-- 3. Optional: Journey-Events als ganz normale DOM-Events konsumieren. -->
<script>
  document.querySelector("aditus-shop").addEventListener("cart:update", (e) => {
    console.log("Positionen im Warenkorb:", e.detail.itemCount);
  });
</script>
AtributoSignificado
public-keyTu clave publicable (se aplica la lista blanca de dominios). Si solo se define este atributo, el elemento genera él mismo su sesión de navegador — el modo adecuado para páginas sin backend.
session-tokenUna sesión generada por TU backend (servidor a servidor, ver abajo). Si se define, el elemento nunca genera por sí mismo — tu página es dueña del ciclo de vida de la sesión.
api-baseURL base de la API de ADITUS. Por defecto, el origen desde el que se cargó el script del bundle — normalmente nunca lo defines.
cultureIdioma/configuración regional de la tienda, p. ej. de-DE o en-GB. Cambiar el atributo relocaliza en el sitio — el recorrido del usuario se conserva.
article-layout / event-layout / article-select-mode / show-headerLas opciones de presentación planas, mismos valores que la config de mount() (cards, quantity, …). El PUNTO DE ENTRADA (selector de evento vs. un evento fijado) no es un atributo: se fija en el servidor al generar la sesión.
survey-columnsNúmero de columnas (1–4) de las opciones de respuesta de las preguntas RadioButtonList/CheckBoxList — igual que surveyColumns en la config de mount(). Sin atributo se mantiene una columna (o el valor por defecto del cliente en el servidor); las pantallas estrechas vuelven a una columna.
base-stylesCon el valor "false" se elimina el skin neutro por defecto y estilizas cada hook tú mismo.

Sesión y autorreparación

Las reglas de sesión de esta página se aplican sin cambios. Con un backend, genera la sesión servidor a servidor y define session-token — el elemento entonces nunca genera por sí mismo. Sin backend, define solo public-key: el elemento genera una sesión de browser mediante el endpoint filtrado por dominio y la regenera de forma transparente al expirar, de modo que el usuario nunca queda en un callejón sin salida. Light DOM como siempre: sin iframe, sin shadow DOM — tu CSS alcanza cada hook documentado.

Eventos y versionado

El bus de eventos aparece como simples DOM CustomEvents en el elemento (con bubbling, payload en event.detail) — cart:update, checkout:complete y el reflejo analytics, mismos nombres y payloads que handle.on(). La URL lleva la versión mayor: v1 recibe actualizaciones compatibles en el sitio; un cambio incompatible se entrega como /embed/v2/, de modo que nada cambia bajo tu página sin previo aviso. Todo lo que va más allá de los atributos planos — objetos de tema, callbacks como onUserRequired, contenido de tarjetas — sigue siendo por diseño una característica del paquete fuente.

Atributos, no propiedades

El elemento se configura exclusivamente mediante atributos HTML — y los atributos son siempre cadenas. base-styles="false" es la cadena literal "false"; el elemento la analiza, tú nunca asignas propiedades JavaScript. Los frameworks vinculan de todos modos los nombres con guiones como public-key como atributos, así que no se necesita ninguna sintaxis de binding especial. Todo lo que no se puede expresar como una cadena plana — objetos de tema, callbacks, contenido de tarjetas — deliberadamente no es un atributo: para eso, usa el paquete fuente.

Reiniciar el recorrido deliberadamente

La única excepción a «solo atributos»: el elemento expone un método clearJourney() — el equivalente sin build del export clearJourney() del paquete fuente. Llama a document.querySelector("aditus-shop").clearJourney() (p. ej. desde un botón de «empezar de nuevo») antes de volver a añadir o recargar el elemento, y se descarta el registro de recorrido persistido localmente para la página actual — el siguiente mount arranca desde cero en el punto de entrada en lugar de reanudar. Solo se elimina el puntero de reanudación local; cualquier estado de carrito en el servidor queda intacto.

Actualizaciones sin remontar

Cambiar un atributo en el elemento en vivo actualiza la instancia en ejecución en el sitio — exactamente como handle.update() en el paquete fuente. Los atributos de presentación (culture, layouts) preservan el recorrido del usuario; solo un cambio de la identidad de sesión (public-key, session-token, api-base) restablece la sesión. Quitar el elemento del DOM desmonta la tienda limpiamente, incluidos todos los listeners.

Anfitriones SSR: Next.js y Nuxt

El micro-frontend es deliberadamente client-only. Su contenido está en vivo y ligado a la sesión — disponibilidad, precios y el carrito del usuario existen solo para una sesión autenticada en el momento de la petición, así que no hay nada significativo que un servidor anfitrión pudiera pre-renderizar. mount() crea una nueva raíz React del lado del cliente (createRoot); el marcado renderizado en el servidor dentro del contenedor no se hidrata. Eso hace que la integración SSR sea simple y predecible: renderiza un placeholder en el servidor, monta en el cliente y reserva el espacio para que nada salte.

ticketshop-section.tsx
// Next.js (App Router) — das Micro-Frontend ist bewusst client-only.
"use client";
import { useEffect, useRef } from "react";
import { mount, type ShopHandle } from "@workspace/aditus-shop-embed";

export function TicketshopSection({ sessionToken }: { sessionToken: string }) {
  const el = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!el.current) return;
    const handle: ShopHandle = mount(el.current, {
      publicKey: "pk_deine_seite",
      sessionToken, // server-seitig gemintet, als Prop an die Client-Komponente
    });
    return () => handle(); // Unmount beim Routenwechsel
  }, [sessionToken]);

  // Server und Client rendern DENSELBEN Platzhalter — kein Hydration-Mismatch.
  // min-height reserviert den Platz, damit die Seite beim Mount nicht springt (CLS).
  return <div id="aditus-shop" ref={el} style={{ minHeight: 560 }} />;
}

Evitar el desplazamiento de diseño (CLS)

Dale al contenedor de mount una min-height que coincida aproximadamente con la primera vista de la tienda y, opcionalmente, renderiza tu propio skeleton dentro — servidor y cliente producen el mismo placeholder, así que no hay hydration mismatch. El adaptador se renderiza inmediatamente después del mount y reemplaza el placeholder en un solo paint. No intentes hydrateRoot en el contenedor: no hay ningún árbol de tienda renderizado en el servidor al que engancharse.

La sesión en un anfitrión SSR

Los anfitriones SSR tienen una ventaja natural: el runtime del servidor que renderiza la página también puede generar la sesión — servidor a servidor con el secreto, exactamente como el flujo de mint de más abajo (Next.js: Route Handler o Server Component; Nuxt: ruta de servidor). Entrega el token opaco resultante al componente cliente como prop o payload. El secreto nunca aparece en el código cliente, y el token permanece en memoria.

Detalles de Next.js

Marca el componente de mount con "use client" y monta en useEffect (ver snippet). Con el Pages Router, next/dynamic con ssr: false consigue lo mismo. Devuelve el handle desde el efecto como cleanup para que los cambios de ruta del lado del cliente se desmonten limpiamente. Un cambio de culture en la instancia en ejecución pasa por handle.update() sin reiniciar el recorrido; un nuevo token de sesión vuelve a montar (como en el snippet) — el carrito también sobrevive a eso, porque el micro-frontend restaura el recorrido en el mount.

Detalles de Nuxt / Vue

Envuelve el objetivo de mount en <ClientOnly> o monta en onMounted — el adaptador necesita un elemento real del DOM en el browser. Recuerda el requisito del bundle anfitrión: react y react-dom 18+ deben estar instalados como dependencias de tu aplicación Nuxt; la tienda se renderiza en su propio contenedor y no interfiere con el DOM virtual de Vue.

El micro-frontend necesita una sesión

Sin token

Sin token de sesión — el micro-frontend permanece inactivo

Sin un token de sesión, el micro-frontend muestra un aviso neutro y no lanza NINGUNA llamada a la API. No se puede llenar ningún carrito, ni registro, ni pago. Una sesión es obligatoria antes de que la tienda se active — pero no tiene que estar ligada a un usuario conocido: una sesión de invitado basta para navegar y llenar el carrito, y el usuario real puede definirse más tarde en el pago (ver el flujo de usuario opcional más abajo).

mount.ts
import { mount } from "@workspace/aditus-shop-embed";

const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount-Ziel #aditus-shop nicht gefunden");

// Ohne sessionToken bleibt das Micro-Frontend inaktiv: es zeigt nur einen
// neutralen Hinweis und startet KEINE API-Calls — kein Warenkorb,
// keine Registrierung, kein Checkout. Erst eine Session aktiviert den Shop.
mount(el, {
  publicKey: "pk_deine_seite",
  // sessionToken fehlt -> Shop wird nicht aktiviert.
});
Usuario por anfitrión

Token de sesión — un usuario concreto

Tu backend genera un token de corta duración ligado a un solo usuario. El micro-frontend actúa entonces como ese usuario: su carrito, sus datos. El token es opaco, caduca, es mono-usuario y está ligado a tu publicKey — y tu secreto nunca llega al browser.

mount.ts
import {
  mount,
  type ShopHandle,
  type AditusShopConfig,
} from "@workspace/aditus-shop-embed";

// getElementById kann null sein — in TypeScript sauber prüfen statt "!".
const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount-Ziel #aditus-shop nicht gefunden");

// AditusShopConfig typisiert alle Optionen — Autovervollständigung inklusive.
const config: AditusShopConfig = {
  publicKey: "pk_deine_seite", // Pflicht, sobald eine Session im Spiel ist
  sessionToken,                // vom Backend gemintet, nur im Speicher halten
};

const handle: ShopHandle = mount(el, config);

// Das Micro-Frontend sendet bei jedem Shop-Call den Header X-Aditus-Session.
// Beim Logout die Session serverseitig widerrufen und neu mounten/aktualisieren.

Una recarga de página no pierde el recorrido. El micro-frontend recuerda el carrito y el paso localmente, vuelve a cargar el carrito en vivo desde el servidor en el mount y continúa exactamente donde el usuario lo dejó — aunque tu página genere un nuevo token de sesión en cada carga. Si el carrito ha expirado en el servidor, el recorrido simplemente empieza de nuevo. Tu página no tiene que hacer nada para esto.

¿Prefieres un reinicio deliberado — un verdadero botón de «empezar de nuevo»? El paquete exporta clearJourney(): llámalo antes de montar (o volver a montar), y se descarta el registro de recorrido persistido localmente para la página actual, de modo que el siguiente mount arranca desde cero en el punto de entrada. Solo se elimina el puntero de reanudación local; cualquier estado de carrito en el servidor queda intacto.

Opcional: navegar de forma anónima, iniciar sesión en el pago

No tienes que saber de antemano quién es el usuario. Monta con una sesión de invitado y el micro-frontend permite a un visitante anónimo explorar eventos, elegir artículos y llenar el carrito. Solo cuando avanza más allá del carrito — hacia el registro y el pago — te pide definir el usuario real, mediante el callback onUserRequired. El resto del flujo continúa entonces con el carrito preservado.

  1. 1

    Montar con una sesión de invitado

    Sigue siendo necesario un token de sesión para activar la tienda — pero puede ser una sesión de invitado: simplemente genérala SIN el campo email, sin necesidad de una dirección de marcador de posición. El visitante navega y llena el carrito bajo ella.

  2. 2

    Salir del carrito activa onUserRequired

    En el momento en que el visitante avanza más allá del carrito, el micro-frontend llama a tu callback una vez y muestra un estado de espera neutro. Autentica al usuario (login/SSO), luego genera una nueva sesión ligada al usuario exactamente igual que la primera: servidor a servidor con tu secreto. La definición posterior del usuario también pasa por ese mint máquina a máquina — el secreto nunca llega al browser y el usuario nunca se define en el código cliente. Tu callback solo retransmite el token opaco resultante. Fundamental: tu backend deriva esa identidad de SU propia sesión autenticada (cookie/JWT); nunca debe aceptar el correo o el identificador de usuario desde el browser, o un visitante malicioso podría solicitar la sesión de otra persona y leer sus datos personales mediante el prellenado.

  3. 3

    Devuelve el nuevo token — el carrito se conserva

    Devuelve el nuevo token de sesión y el micro-frontend lo adopta y liga al usuario identificado al carrito existente — el carrito, sus artículos y cualquier franja horaria se conservan sin cambios. Luego pasa al registro. Devuelve null para cancelar — el visitante simplemente permanece en el carrito, sin error.

  4. 4

    Omitir onUserRequired para un usuario fijo

    Si omites el callback, nada cambia respecto al flujo clásico: monta con una sesión ligada a un usuario y el micro-frontend actúa como ese usuario desde el primer paso.

page.ts
import {
  mount,
  type ShopHandle,
  type AditusShopConfig,
} from "@workspace/aditus-shop-embed";

const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount-Ziel #aditus-shop nicht gefunden");

// Ein GAST-Session-Token reicht zum Mounten: server-zu-server gemintet wie
// gehabt, nur OHNE das Feld email (keine Platzhalter-Adresse nötig). Der
// Besucher darf browsen UND den Warenkorb füllen, ohne dass ein konkreter
// Nutzer feststeht.
const config: AditusShopConfig = {
  publicKey: "pk_deine_seite",
  sessionToken: guestSessionToken,

  // Wird EINMAL aufgerufen, sobald der Besucher den Warenkorb Richtung
  // Registrierung verlässt. Hier authentifizierst du den Nutzer (Login/SSO)
  // und mintest server-zu-server eine NEUE, nutzergebundene Session.
  // Gib den frischen Token zurück -> das Micro-Frontend übernimmt ihn und baut
  // den Warenkorb unter dem identifizierten Nutzer neu auf (Artikel bleiben
  // erhalten). Gib null zurück, um abzubrechen -> der Besucher bleibt im
  // Warenkorb, ohne Fehler.
  onUserRequired: async () => {
    // Ruft NUR dein Backend auf – ohne Identität im Payload. Dein Backend liest
    // den eingeloggten Nutzer aus SEINER eigenen Session (Cookie/JWT) und mintet
    // server-seitig für GENAU diese Identität. Niemals die E-Mail aus dem Browser
    // übergeben – sonst könnte ein Angreifer hier eine fremde Adresse einsetzen
    // und über den Prefill an deren personenbezogene Daten gelangen.
    const res = await fetch("/api/shop/my-session", { method: "POST" });
    if (res.status === 401) return null;        // nicht eingeloggt -> im Warenkorb bleiben
    const { sessionToken } = (await res.json()) as { sessionToken: string };
    return sessionToken;
  },
};

const handle: ShopHandle = mount(el, config);

La página anfitriona autentica al usuario

ADITUS no autentica a tus usuarios finales. Tu sitio es la única fuente de verdad sobre quién es el usuario — tú operas tu propio login, SSO o sesión. ADITUS solo confía en un token de sesión que tu backend genera con una clave secreta. La cadena de confianza es: tu auth prueba la identidad, tu token generado con el secreto la avala, el micro-frontend la presenta.

  1. 1

    Tu sitio autentica al usuario

    Login, SSO, sesión de miembro — completamente tuyos. ADITUS no interviene aquí.

  2. 2

    Tu backend genera una sesión

    Servidor a servidor con la clave secreta. Pasas como quién debe actuar el micro-frontend (email) y lo ligas a tu publicKey.

  3. 3

    La página monta el micro-frontend

    El token opaco se entrega al browser y se pasa como sessionToken a mount(). El secreto permanece en tu servidor.

  4. 4

    Cada llamada lleva la sesión

    El micro-frontend envía X-Aditus-Session en cada petición; el proxy resuelve el usuario del carrito a partir de la sesión.

  5. 5

    Sin token → el micro-frontend permanece inactivo

    Sin sesión, el micro-frontend nunca se activa: muestra un aviso neutro y no realiza llamadas — sin carrito, sin pago. Genera una sesión para activarlo.

1. Generar en tu backend

server.ts
// SERVER-SEITE deines Hosts — der Secret-Key verlässt NIE den Browser.
// Dein Backend hat den Nutzer bereits selbst authentifiziert (Login/SSO/Session).
// WICHTIG: user stammt aus DIESER Server-Session, nie aus dem Request-Body des
// Browsers — sonst könnte ein Angreifer eine fremde E-Mail unterschieben.
const res = await fetch("https://<dein-host>/api/shop/session", {
  method: "POST",
  headers: {
    // Mint-Secret DEINES Clients (im ADITUS-Admin erzeugt), nur server-seitig.
    Authorization: `Bearer ${process.env.ADITUS_MINT_SECRET}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    publicKey: "pk_deine_seite",      // bindet die Session an deinen Key (Pflicht)
    email: user.email,                 // wer das Micro-Frontend sein soll (weglassen = anonyme Gast-Session)
    externalUserId: user.id,           // optionale Metadaten (deine User-ID)
    ttlSeconds: 1200,                  // optional: Lebensdauer (Default 20 min, max 24 h)
    eventSlug: "ExperienceDaysv52024", // optional: Einstieg direkt in dieses Event (sonst Eventübersicht)
  }),
});

// Antwort-Typ deiner Wahl — sessionToken an den Browser reichen (inline oder fetch).
const { sessionToken }: { sessionToken: string; expiresAt: number } = await res.json();

2. Montar con el token

page.ts
import {
  mount,
  type ShopHandle,
  type AditusShopConfig,
} from "@workspace/aditus-shop-embed";

// getElementById kann null sein — in TypeScript sauber prüfen statt "!".
const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount-Ziel #aditus-shop nicht gefunden");

// AditusShopConfig typisiert alle Optionen — Autovervollständigung inklusive.
const config: AditusShopConfig = {
  publicKey: "pk_deine_seite", // Pflicht, sobald eine Session im Spiel ist
  sessionToken,                // vom Backend gemintet, nur im Speicher halten
};

const handle: ShopHandle = mount(el, config);

// Das Micro-Frontend sendet bei jedem Shop-Call den Header X-Aditus-Session.
// Beim Logout die Session serverseitig widerrufen und neu mounten/aktualisieren.

3. Revocar al cerrar sesión

server.ts
// SERVER-SEITE — z. B. beim Logout deines Nutzers.
await fetch(`https://<dein-host>/api/shop/session/${sessionToken}`, {
  method: "DELETE",
  headers: { Authorization: `Bearer ${process.env.ADITUS_MINT_SECRET}` },
});
// { revoked: true }

El pedido finalizado vuelve a tu página

Cuando se completa una compra de entradas, no necesitas una consulta de pedido aparte. Como el micro-frontend se ejecuta de forma nativa en tu página (light DOM, no un iframe), devuelve el pedido finalizado directamente a tu código mediante el callback onComplete — una llamada JS directa, sin postMessage. El micro-frontend ya ha resuelto el pedido por ti, así que recibes el número de pedido y los enlaces de las entradas, no solo un id.

mount.ts
import {
  mount,
  type ShopHandle,
  type CompletedOrder,
} from "@workspace/aditus-shop-embed";

const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount-Ziel #aditus-shop nicht gefunden");

const handle: ShopHandle = mount(el, {
  publicKey: "pk_deine_seite",
  sessionToken,
  // Wird GENAU EINMAL aufgerufen, sobald die Bestellung platziert ist.
  // Das Micro-Frontend hat die Bestellung bereits für dich aufgelöst — du bekommst
  // die aufgelöste Bestellung, nicht nur eine ID.
  onComplete: (order: CompletedOrder) => {
    order.number;   // Bestellnummer (Order-ID als Fallback) — dein Identifier
    order.status;   // Status der Bestellung
    order.buyer;    // Käufer (Name / E-Mail), falls vorhanden
    order.tickets;  // [{ name, links: [{ kind: "pdf" | "apple" | "google", url }] }]

    // -> Order-ID speichern, Bestätigung zeigen, DOI / Newsletter anstoßen ...
  },
  // Wird bei jedem nicht behebbaren Fehler im Ablauf aufgerufen.
  onError: (err: Error) => {
    console.error("Shop-Fehler:", err.message);
  },
});

Qué recibes

onComplete se dispara una vez por cada pago exitoso con el pedido resuelto: number (el id / número de pedido — tu identificador), status, buyer (nombre y correo cuando están presentes) y tickets — cada entrada lleva enlaces tipados de kind “pdf”, “apple” o “google”. onError se dispara ante cualquier error irrecuperable del flujo.

Métodos de pago con redirección

Los métodos que permanecen en la página (factura, prepago, tarjeta en línea) disparan onComplete de inmediato. Un método que envía el browser a una página de pago externa (p. ej. Saferpay) es gestionado por el micro-frontend de principio a fin: deriva las URL de retorno de la página en la que se ejecuta, envía al usuario fuera y, a la vuelta, finaliza el pedido y dispara onComplete — un viaje de ida y vuelta completo sin nada de código por tu parte. Solo si el retorno debe aterrizar en una página DISTINTA sobrescribes redirectUrls.successUrl / cancelUrl / errorUrl.

Eventos GA4 / e-commerce (opcional)

El micro-frontend puede reportar el embudo de compra a tu analítica — pero solo si se lo pides. No incluye gtag, ni GTM, ni SDK de GA, no carga nada, no establece cookies y no envía nada por su cuenta. Añades un callback opcional, onEvent, y recibes eventos tipados ya formados según el esquema GA4 Enhanced Ecommerce de Google. Omite onEvent y nada cambia. Qué herramienta alimentas, y si tienes consentimiento para enviar, queda enteramente de tu lado.

mount.ts
import {
  mount,
  type ShopHandle,
  type ShopAnalyticsEvent,
} from "@workspace/aditus-shop-embed";

const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount-Ziel #aditus-shop nicht gefunden");

const handle: ShopHandle = mount(el, {
  publicKey: "pk_deine_seite",
  sessionToken,
  // OPTIONAL. Lässt du onEvent weg, ändert sich nichts: das Micro-Frontend
  // sendet nichts, lädt kein gtag/GTM/GA-SDK und setzt keine Cookies.
  // Tool, Consent und Mapping bleiben komplett bei dir — hier GA4 via gtag.
  onEvent: (event: ShopAnalyticsEvent) => {
    // Consent liegt bei dir: erst senden, wenn der Nutzer zugestimmt hat.
    if (!hasAnalyticsConsent()) return;
    // Die Events sind bereits im GA4-Schema (name + items/value/currency ...),
    // also 1:1 an gtag durchreichen.
    const { name, ...params } = event;
    window.gtag?.("event", name, params);
  },
});

Ya con el formato GA4

Cada evento lleva un nombre de evento GA4 más sus parámetros estándar: items (con item_id, item_name, price, quantity, item_category y — cuando aplica — discount y coupon), value y currency; add_payment_info añade payment_type y purchase añade transaction_id. Extrae name por desestructuración y pasa el resto directamente a gtag("event", name, params) — sin necesidad de remapear.

El consentimiento sigue siendo tuyo

onEvent es simplemente una función JavaScript normal de tu página que llamamos directamente. El micro-frontend en sí no envía analítica a ningún sitio — nada sale de la página hasta que tu handler lo envía, así que tu gestión de consentimiento mantiene el control total: condiciona el reenvío al consentimiento, enruta al dataLayer de GTM en lugar de gtag, agrupa o descarta eventos por completo. Cualquier error que lance tu handler se captura y se aísla, de modo que nunca rompe el flujo del usuario — mantén el handler ligero (un push a gtag/dataLayer) ya que se ejecuta inline.

EventoSe dispara cuando
view_item_listEl usuario ve la lista de artículos (se dispara una vez por conjunto mostrado).
add_to_cartSe añade un artículo o complemento al carrito.
remove_from_cartSe elimina una línea del carrito o un complemento.
view_cartSe muestra la vista del carrito (una vez por carrito).
begin_checkoutEl usuario abandona el carrito hacia el registro / la compra.
add_payment_infoSe elige un método de pago (contiene payment_type).
purchaseEl pedido se realiza (contiene transaction_id, value, items).

Bus de eventos: handle.on / handle.off (opcional)

Además de los callbacks de config, el handle devuelto por mount() lleva un pequeño bus de eventos totalmente opcional. Suscríbete con handle.on(event, listener) — devuelve la función de baja correspondiente — y desconéctate con handle.off(event, listener). Si nunca llamas a on(), nada cambia: el bus no añade dependencias, no envía nada y no cuesta nada. Es pura observación para tu página — una insignia de mini-carrito, un banner de confirmación, tu propio logging — y complementa los callbacks en lugar de reemplazarlos.

mount.ts
import {
  mount,
  type ShopHandle,
  type ShopCartUpdate,
} from "@workspace/aditus-shop-embed";

const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount-Ziel #aditus-shop nicht gefunden");

const handle: ShopHandle = mount(el, {
  publicKey: "pk_deine_seite",
  sessionToken,
});

// OPTIONAL. Ohne on() verhält sich das Micro-Frontend exakt wie bisher —
// der Bus ist reine Beobachtung, kein Event ist Pflicht.
// on() liefert die Abmeldefunktion zurück; alternativ handle.off(name, fn).
const offCart = handle.on("cart:update", (cart: ShopCartUpdate) => {
  // Bei jeder Warenkorb-Änderung: Anzahl, Summe, Positionen (GA4-Item-Form).
  updateMiniCartBadge(cart.itemCount); // z. B. Badge im Seiten-Header
});

handle.on("checkout:complete", ({ order }) => {
  // Bestellung platziert — dieselbe aufgelöste Bestellung wie in onComplete.
  showConfirmationBanner(order.number);
});

handle.on("analytics", (event) => {
  // Spiegel des GA4-Funnels (identische Payloads wie config.onEvent).
  console.debug("Funnel:", event.name);
});

// Später gezielt abmelden — der Rest bleibt aktiv:
offCart();
EventoSe dispara cuando
cart:updateEl carrito cambia: creado, artículo añadido o retirado, o vaciado. Contiene cartId, itemCount, value, currency y las líneas como items con formato GA4.
checkout:completeEl pedido se realiza — el mismo momento y el mismo objeto de pedido resuelto que config.onComplete.
analyticsReflejo del funnel GA4 anterior: cada payload de onEvent también se emite en el bus, sin cambios.

Los listeners sobreviven a update()

Las suscripciones viven en el handle, no en un render: handle.update({ ... }) vuelve a renderizar el micro-frontend pero mantiene cada listener adjunto. El desmontaje mediante handle() desconecta todos los listeners automáticamente — solo necesitas off() (o la función de baja devuelta) cuando quieres dejar de escuchar mientras la tienda sigue funcionando.

Aislado, nunca bloqueante

Un listener que lanza un error se captura y se aísla — nunca rompe el flujo del usuario y nunca afecta a otros listeners. El evento “analytics” refleja el embudo GA4 con payloads idénticos a config.onEvent, así que puedes consumir el embudo mediante el bus, el callback o ambos; se aplica la misma regla de consentimiento: nada sale de la página hasta que tu código lo envía.

publicKey vs. sessionToken

Mantén los dos separados. Responden a preguntas diferentes y se emparejan, no son intercambiables.

publicKey — confianza de origen

Publicable. Se entrega en tu código cliente, así que NO es un secreto y no concede nada por sí sola. El backend aplica una lista blanca de dominios por clave: una clave solo funciona desde sus dominios registrados. Responde a «¿qué sitio es este?»

sessionToken — identidad

Generado con el secreto en tu backend, opaco y de corta duración. Responde a «¿quién es el usuario?» Una sesión está ligada al publicKey para el que se generó: una petición de sesión también debe enviar esa clave, y una discrepancia se rechaza (403).

Reglas de seguridad

  • La clave secreta vive solo en tu servidor — nunca en el código cliente, los bundles o los archivos env enviados al browser.
  • Mantén el token solo en memoria. No lo persistas en localStorage; renuévalo volviéndolo a generar.
  • Los tokens son de corta duración (20 minutos por defecto, 24 horas máx) y mono-usuario.
  • Revoca la sesión al cerrar sesión para que un token filtrado no pueda reutilizarse.
  • Empareja siempre la sesión con su publicKey — una clave ausente o que no coincide se rechaza (403).
  • Una sesión inválida o expirada es un 401 rotundo — nunca un repliegue silencioso al usuario de demostración.

Referencia de endpoints y respuestas

POST/api/shop/sessiongenerar — secreto Bearer
DELETE/api/shop/session/:tokenrevocar — secreto Bearer

En esta demo, la generación pasa por el proxy en las rutas de arriba. En producción, la sesión se genera en el núcleo de ADITUS — el contrato es idéntico. Ambas llamadas son servidor a servidor: tu backend se autentica con el secreto de generación de tu cliente (generado en la consola de administración de ADITUS) como token Bearer. Una generación por inicio de sesión es el patrón normal; el browser nunca ve más que el token resultante.

Puedes probar este mismo flujo con tus propias credenciales en el probador de integración — generar, montar, recorrer el recorrido.

Generar una sesión — petición

CabeceraValorSignificado
AuthorizationBearer <mint-secret>El secreto de mint de tu cliente, generado en la consola de administración de ADITUS. Solo en el servidor — nunca debe enviarse a un navegador o a un bundle de aplicación.
Content-Typeapplication/jsonEl cuerpo es un objeto JSON.
Campo del bodyTipoSignificado
publicKeystring · obligatorioLa clave publicable PARA la que se genera la sesión. Debe ser la misma clave con la que monta el micro-frontend — cada llamada de tienda posterior se comprueba contra ella (403 si no coincide). El secreto Bearer debe pertenecer al cliente de esta clave.
emailstring · optionalEl usuario de la tienda como el que actúa la sesión. Tómalo de TU sesión de servidor autenticada (cookie de login/SSO o JWT) — nunca de la petición del navegador, o un visitante podría obtener la sesión de otra persona. OMITIDO se genera una sesión ANÓNIMA: el visitante puede navegar y llenar el carrito, pero el micro-frontend bloquea el paso más allá del carrito hasta que tu callback onUserRequired proporcione una sesión ligada a un usuario (ver el flujo de usuario opcional). Un correo presente pero mal formado sigue siendo rechazado (400).
externalUserIdstring · optionalTu propio identificador de usuario, almacenado con la sesión como metadato (útil para el soporte y la correlación de registros). ADITUS no lo interpreta.
ttlSecondsnumber · optionalDuración de la sesión en segundos. Por defecto 20 minutos, con un tope de 24 horas. Al expirar, las llamadas de tienda devuelven 401 session_expired — genera entonces un token nuevo (p. ej. mediante el hook onSessionExpired).
eventSlugstring · optionalFija el PUNTO DE ENTRADA del recorrido en el servidor: definido, el micro-frontend arranca directamente en la selección de artículos de este evento; omitido, arranca en el resumen de eventos. Como forma parte de la sesión generada, el navegador no puede manipularlo. Caracteres inválidos devuelven 400 invalid_event_slug; un slug que no corresponde a ningún evento en vivo vuelve al resumen de eventos.

Generar una sesión — respuesta (200)

CampoTipoSignificado
sessionTokenstringToken opaco (sess_…). Entrégalo al navegador y pásalo a mount() como sessionToken; el micro-frontend lo envía como X-Aditus-Session en cada llamada de tienda. No contiene datos de usuario y no se puede decodificar.
expiresAtnumberVencimiento como marca de tiempo Unix en milisegundos. Puramente informativo para tu propia planificación — el micro-frontend reacciona por sí mismo al 401.

Revocar una sesión

DELETE /api/shop/session/:token con el mismo secreto Bearer — el secreto debe pertenecer al cliente para el que se generó la sesión. Llámalo al cerrar sesión para que el token muera junto con tu propia sesión. Respuesta: { "revoked": true } (o false si la sesión ya había expirado). La revocación es idempotente y se puede enviar en fire-and-forget.

Los errores son explícitos

EstadoCódigoSignificado
503session_not_configuredminting is disabled for this client: no mint secret has been generated in the admin console yet.
401unauthorizedthe mint secret is wrong or missing. Check that the secret belongs to the client of exactly this public key (rotated secrets invalidate the old one immediately).
400invalid_public_keythe public key is malformed or not registered. It must look like pk_… and belong to a configured client.
400invalid_emailthe shop user email is missing or not a valid address.
400invalid_tokenthe session token in the revoke call is malformed. Pass exactly the sessionToken returned by the mint call.
429rate_limitedtoo many mint/revoke calls from your IP. Wait a minute and try again.
403public_key_invalidthe X-Aditus-Public-Key header is malformed. It must look like pk_… exactly as issued during onboarding.
403public_key_unknownthe X-Aditus-Public-Key is well-formed but not registered (or deactivated). Check for typos and that the key's client is active.
403public_key_origin_unresolvedthe request carried a publicKey but no usable Origin/Referer header, so the domain whitelist cannot be checked. Browsers send Origin automatically; server-side calls must not use the publicKey header.
403public_key_domain_not_allowedthe request's origin domain is not on this publicKey's whitelist. Add the domain during onboarding (or in the admin console) before going live on it.
401invalid_sessionthe shop call carried a malformed X-Aditus-Session token — never a silent fall-back to the demo user. Pass exactly the sessionToken (sess_…) returned by the mint call.
401session_expiredthe X-Aditus-Session token is unknown, revoked or expired — never a silent fall-back to the demo user. Mint a fresh session server-to-server.
403session_requires_public_keya session was sent without its X-Aditus-Public-Key header (origin pinning is mandatory once a session is in play).
403session_key_mismatchthe X-Aditus-Public-Key does not match the key the session was minted for.
403anonymous_sessionthe session was minted WITHOUT an email (anonymous) and only allows browsing and the cart. Registration, payment and checkout require a user-bound session — mint one via onUserRequired.

Ahora hazlo tuyo

La identidad está definida — a continuación, personaliza la marca del micro-frontend. La herramienta de estilo genera una config de tema lista para pegar y explica cada parámetro, con una vista previa en vivo.

Abrir la herramienta de estilo