Saltar para o conteúdo
Micro Frontend

Definir o utilizador e a autenticação

O micro-frontend apresenta a bilheteira nativamente na página. A identidade do utilizador é fornecida pelo SEU back-end; a página anfitriã é responsável pela respetiva autenticação. É obrigatório um token de sessão: sem ele, o micro-frontend permanece inativo. Esta página explica o motivo e o fluxo de sessão servidor a servidor.

A tecnologia por trás do micro-frontend

A bilhética ADITUS, integrada em vez de sobreposta. O micro-frontend apresenta nativamente na página todo o percurso de compra — sem iframe nem elementos visuais estranhos. Foi concebido para ser leve e nativo do host, para ter o aspeto e o comportamento do próprio site, e não de um widget incorporado.

React 19 – compatível até React 18

Todo o percurso — eventos, artigos, carrinho, registo, pagamento e conclusão — funciona como um único fluxo React com tipagem integral. Utilizamos React 19, mas mantemos deliberadamente a compatibilidade com React 18 (peerDependency react >=18), para permitir a integração no maior número possível de páginas host. Este intervalo peer é literal: internamente, o adaptador utiliza apenas as funcionalidades do React 18 (useState, useEffect, useMemo, etc.) e nenhuma API exclusiva do React 19 — é compilado e testado com React 19 e tem execução garantida com React 18. React é apenas uma peerDependency: o adaptador não inclui dependências de runtime próprias. Sem biblioteca de UI, framework de data fetching ou toolkit de estado — apenas hooks React criteriosamente selecionados. O resultado é um bundle reduzido, sem dependências desnecessárias na página.

Light DOM em vez de iframe

Sem iframe, shadow DOM ou elementos estranhos. A loja é apresentada diretamente no DOM da página: herda a tipografia, adapta-se ao layout, é totalmente responsiva e acessível — e o visitante reconhece-a como parte nativa do site.

Independente do framework: um único mount(), em qualquer contexto

Uma única chamada — mount(container, config) — anexa a loja a qualquer elemento DOM. Seja React, Vue, Angular ou HTML simples: o adaptador é independente do framework e funciona da mesma forma em qualquer lugar.

O estilo é o seu

Cada elemento disponibiliza um hook CSS estável (classes BEM aditus-shop__* e data-state). Pode aplicar livremente o seu CSS ou definir rapidamente as cores da marca, os raios e os espaçamentos com alguns design tokens --aditus-*. Opcionalmente, pode usar estilos base devidamente isolados através de CSS @layer, que nunca se sobrepõem aos estilos da página.

Multilingue, com alteração imediata

DE/EN estão incluídos. Uma alteração de idioma é enviada diretamente para a instância em execução através de handle.update(), sem reiniciar o percurso do utilizador. O carrinho e o progresso são preservados.

Seguro desde a conceção

Sem um sessionToken válido, o adaptador permanece inativo e não efetua qualquer pedido. A sessão é criada entre servidores com um segredo; nenhuma chave chega ao navegador. Todas as chamadas passam por um proxy que injeta as credenciais no servidor.

Requisitos do ambiente host

Na camada de integração, o micro-frontend foi concebido para oferecer compatibilidade máxima — qualquer framework, qualquer CSS do host e React 18+. Os poucos requisitos inevitáveis são simples: execução no navegador, React disponível e uma sessão com uma publicKey incluída na lista de domínios autorizados.

O que é necessário

  • React 18+ e react-dom 18+ no bundle do host — declarado como peerDependency react >=18; mount() usa createRoot de react-dom/client. O host decide a versão do React.
  • Um navegador com DOM (lado do cliente). O adaptador recebe o mount em um elemento DOM real (light DOM). Hosts SSR funcionam bem, desde que mount() seja executado no cliente.
  • Um navegador evergreen moderno, com suporte para APIs Web standard (fetch, AbortController, ResizeObserver e CSS @layer), disponíveis de forma generalizada desde cerca de 2022. O IE não é suportado.
  • Uma sessão e uma publicKey — sem sessionToken, o micro-frontend permanece inativo. Crie a sessão entre servidores com o segredo (host com backend) ou no navegador através de /shop/embed-session, com restrição por domínio (host sem backend). O domínio tem de constar da lista de permissões da publicKey.

O que deliberadamente NÃO exigimos

  • Nenhum framework específico - mount(container, config) é neutra em termos de estrutura: React, Vue, Angular, HTML simples.
  • Sem iframe, sem shadow DOM – ele é apresentado nativamente na página.
  • Nenhum CSS de host específico - os estilos básicos residem em @layer aditus-shop (qualquer regra de host sem camadas vence), com especificidade de classe única, além de reset-armor contra um host * { margin:0; padding:0 }. Totalmente desligável com baseStyles: false.
  • Sem dependências de runtime - apenas React peerDeps, nada que colida no bundle do host.
  • Nenhuma configuração de build específica além de uma importação do ESM.

Segurança dos pagamentos, PCI e CSP

A integração de componentes de terceiros, como sistemas de bilhética, obriga tradicionalmente a equilibrar a experiência do utilizador, o desempenho e a segurança informática. O micro-frontend resolve este conflito com um modelo híbrido: o fluxo de interação é apresentado no light DOM nativo da página, enquanto a etapa de pagamento permanece estritamente isolada — todas as operações não sensíveis decorrem na página; o pagamento nunca.

Nenhum dado de pagamento no light DOM

Toda a preparação da compra — evento, bilhetes, complementos, carrinho e registo — é apresentada nativamente na página. Quando o utilizador inicia um pagamento por redirecionamento, o fluxo sensível abandona por completo a página: uma navegação top-level normal transfere o controlo para a página externa do fornecedor de pagamentos e, no regresso, o percurso é retomado e a encomenda concluída. Os dados do cartão ou da conta bancária só são introduzidos nessa página — o micro-frontend nunca apresenta, transporta nem guarda credenciais de pagamento na página host.

Âmbito PCI mínimo

Uma vez que a sua aplicação host nunca processa, transporta nem armazena dados bancários ou de cartões de crédito, o seu ambiente mantém-se no âmbito PCI-DSS mínimo do modelo de redirecionamento clássico (normalmente SAQ A — a classificação vinculativa cabe sempre ao seu adquirente ou QSA). Os métodos de pagamento sem redirecionamento, como a faturação, são concluídos no servidor, sem quaisquer dados de pagamento no navegador.

Content Security Policy simplificada

Além da política existente, o micro-frontend precisa apenas de duas permissões: connect-src para a origem da API ADITUS (utilizada por todas as chamadas à loja) e img-src para os URLs dos recursos ADITUS, caso sejam apresentados o banner do evento ou imagens dos artigos. Em particular, não precisa de entradas script-src nem frame-src para fornecedores de pagamentos, porque esta etapa utiliza um simples redirecionamento top-level: não são carregados scripts nem frames de pagamento na página.

O melhor dos dois mundos

Tradicionalmente, era necessário escolher entre um iframe (isolado, mas rígido e visualmente estranho) e uma integração completa por API (fluida, mas com todo o âmbito PCI a cargo da equipa de segurança). O micro-frontend resolve este dilema: as interações não sensíveis — navegação, carrinho e configuração dos bilhetes — decorrem nativamente na página; a interação crítica — a cobrança — é totalmente separada e isolada no exterior.

Como isso difere das alternativas de mercado

Contra o iframe clássico

Os iframes são considerados seguros graças ao isolamento rigoroso do navegador, mas comportam-se como documentos separados: não herdam estilos globais, como a tipografia ou as regras de layout responsivo, e o respetivo conteúdo nem sempre é indexável pelos rastreadores; para os leitores de ecrã, representam muitas vezes uma quebra de contexto. O micro-frontend apresenta toda a preparação da compra — seleção de artigos, carrinho e registo — como HTML real diretamente no DOM da página, com a mesma acessibilidade e capacidade de resposta do restante markup. O isolamento de um iframe só é recriado no momento do pagamento: o fluxo de dados crítico é totalmente transferido, através de um redirecionamento top-level, para a página externa dedicada ao pagamento.

Flexibilidade versus Shadow DOM puro

Muitos Web Components encapsulam-se em um shadow DOM para evitar conflitos de especificidade de CSS — na prática, isso geralmente leva a uma marca incompleta, porque os estilos globais são bloqueados e as regras de design precisam ser passadas laboriosamente por meio de atributos de peças ou propriedades personalizadas. Em vez disso, o micro-frontend usa a cascata CSS nativa: os respetivos estilos básicos residem em @layer aditus-shop, uma camada deliberadamente baixa – cada regra CSS sem camadas da página vence automaticamente. O o seu design corporativo é herdado nativamente, sem guerras de especificidade contra os componentes de UI da loja.

Eficiência de recursos e tamanho do bundle

Os widgets monolíticos incluem frequentemente clientes HTTP, gestão de estado e bibliotecas de UI próprios — e podem obrigar o navegador a carregar runtimes redundantes em paralelo, prejudicando o tempo de carregamento da página (Core Web Vitals). O micro-frontend declara React e React-DOM exclusivamente como peerDependency e não tem dependências de runtime próprias: se a página já utilizar React 18 ou posterior, liga-se diretamente ao runtime existente. Em ambientes sem React, o Web Component opcional inclui a dependência React num bundle isolado, sem ocupar variáveis globais na página.

As abordagens comparadas

CritérioIframe clássicoWidget Shadow-DOMMicro-frontend ADITUS
SEO e acessibilidadeO conteúdo fica num documento separado – muitas vezes invisível para os rastreadores, mas mais difícil para os leitores de ecrã.Isolado; a descoberta e a acessibilidade exigem normalmente trabalho extra.Parte nativa do seu DOM — indexável e acessível como a sua própria marcação.
Estilo e marcaRígido: sem herança CSS, personalização apenas através das APIs postMessage.Os estilos globais não chegam ao interior; o theming só é possível através das properties e parts expostas.O seu CSS ganha por definição via @layer; a camada base é substituível ou totalmente removida.
Bundle e desempenhoNormalmente, carrega uma segunda aplicação completa dentro do frame, incluindo o respetivo runtime.Envia frequentemente o seu próprio runtime e dependências duplicadas.Usa o React da tua página; zero dependências de runtime próprias.
Segurança dos pagamentos e âmbito PCIFortemente isolado – mas ao preço de UX, design e capacidade de resposta.Os dados de pagamento fluiriam pelo DOM do host se fossem aí captados.Seleção no light DOM, pagamento totalmente isolado na página de pagamento externo.

Como obter o micro-frontend

O micro-frontend não está disponível no npm público nem numa CDN. É fornecido como pacote de código-fonte diretamente pela sua instância ADITUS: TypeScript, ESM, totalmente tipado e sem dependências próprias em runtime. O seu bundler (Vite, webpack, Next.js ou Nuxt) compila-o juntamente com a sua aplicação, como se fosse código seu. O nome de importação nos excertos desta página (@workspace/aditus-shop-embed) corresponde ao pacote da nossa integração de referência; o pacote transferível chama-se @aditus/shop-embed — a API é idêntica.

O que a entrega contém

O adaptador completo é fornecido como código-fonte TypeScript legível, com declarações de tipos para todas as opções de configuração, callbacks e eventos. React e react-dom mantêm-se como peerDependencies — são fornecidos pela página, pelo que não existem conflitos no bundle nem uma segunda instância de React.

Obtendo o pacote

O pacote é idêntico para cada host e é descarregado diretamente desta instância, sem necessidade de credenciais:{{API_BASE_URL}}/api/embed/v1/aditus-shop-embed.tgz. Instale-o diretamente a partir desse URL (npm install aceita o URL de um tarball — utilize o destino da ligação na instância, no caminho /api/embed/v1/aditus-shop-embed.tgz) ou extraia-o para o repositório. O onboarding fornece a publicKey com a respetiva lista de domínios autorizados; sem ela, o micro-frontend permanece inativo. Contacto: página de contacto. Os hosts sem etapa de build própria (WordPress ou HTML simples) não precisam do pacote de código-fonte: utilizam o bundle de Web Components alojado localmente descrito na secção seguinte.

Web Component: nenhuma etapa de build necessária

Para páginas sno seu próprio bundler - WordPress, Typo3, HTML simples - o micro-frontend também é fornecido como um bundle pré-compilado e auto-hospedado: uma tag de script registra o elemento <aditus-shop>, React incluído, sem npm, sem build. Ele é entregue a partir da sua instância ADITUS sob uma URL versionada (/api/embed/v1/…), não de um CDN público — para este sistema de demonstração é https://developers.aditus.com/api/embed/v1/aditus-shop.js. A URL é deliberadamente pública e não requer credenciais: o bundle contém apenas código publicado, a porta real é a sua chave publicável com a sua lista de domínios autorizados mais a sessão. Este caminho é estritamente opcional e puramente aditivo: hosts com a o seu próprio processo de build continuam integrando o pacote fonte via mount() exatamente como documentado nesta página — mesma percurso, mesmos hooks CSS, mesmo tema.

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-keyA chave publicável (sujeita à lista de domínios autorizados). Quando apenas este atributo está definido, o elemento cria a própria sessão no navegador — o modo adequado para páginas sem back-end.
session-tokenUma sessão criada pelo SEU back-end (servidor a servidor; ver abaixo). Quando este atributo está definido, o elemento nunca cria uma sessão por si próprio — o ciclo de vida da sessão fica a cargo da página.
api-baseURL base da API ADITUS. Por predefinição, é a origem a partir da qual o script do bundle foi carregado — normalmente não é necessário definir este atributo.
cultureIdioma/região da loja, por exemplo de-DE ou en-GB. Alterar o atributo atualiza a localização sem recriar o elemento — o percurso do utilizador é preservado.
article-layout / event-layout / article-select-mode / show-headerAs opções simples de apresentação, com os mesmos valores da configuração de mount() (cards, quantity, …). O PONTO DE ENTRADA (seleção de eventos ou evento fixo) não é um atributo: é definido no servidor quando a sessão é criada.
survey-columnsNúmero de colunas (1–4) das opções de resposta das perguntas RadioButtonList/CheckBoxList — igual a surveyColumns na configuração de mount(). Sem o atributo mantém-se uma coluna (ou o valor predefinido do cliente no servidor); ecrãs estreitos regressam a uma coluna.
base-stylesDefina como "false" para remover o tema neutro predefinido e aplicar estilos a cada hook.

Sessão e autocura

As regras de sessão desta página mantêm-se. Com um back-end, crie a sessão entre servidores e defina session-token — o elemento nunca cria a sessão autonomamente. Sem back-end, defina apenas public-key: o elemento cria uma sessão no navegador através do endpoint limitado ao domínio e renova-a de forma transparente quando expira, evitando que o utilizador fique bloqueado. Como sempre, utiliza light DOM: sem iframe nem shadow DOM — o CSS da página chega a todos os hooks documentados.

Eventos e versionamento

O barramento de eventos é exposto no elemento através de DOM CustomEvents normais (com bubbling e payload em event.detail): cart:update, checkout:complete e o espelho de analytics, com os mesmos nomes e payloads de handle.on(). O URL inclui a versão principal: v1 recebe atualizações compatíveis; uma alteração incompatível é publicada em /embed/v2/, para que nada mude sem aviso. Tudo o que exceda atributos simples — objetos de tema, callbacks como onUserRequired e conteúdo dos cartões — continua, por opção de design, reservado ao pacote de código-fonte.

Atributos, não propriedades

O elemento é configurado exclusivamente através de atributos HTML, que são sempre strings. base-styles="false" é a string literal "false"; o elemento interpreta-a e não é necessário atribuir properties JavaScript. Os frameworks associam nomes com hífen, como public-key, diretamente a atributos, pelo que não é necessária uma sintaxe especial. Tudo o que não possa ser expresso como uma string simples — objetos de tema, callbacks ou conteúdo dos cartões — não é deliberadamente um atributo; para esses casos, utilize o pacote de código-fonte.

Reiniciar deliberadamente o percurso

A única exceção à regra de «apenas atributos» é o método clearJourney() exposto pelo elemento — o equivalente sem build da exportação clearJourney() do pacote de código-fonte. Chame document.querySelector("aditus-shop").clearJourney() (por exemplo, através de um botão «recomeçar») antes de voltar a adicionar ou recarregar o elemento; o registo do percurso guardado localmente para a página atual é eliminado e o mount seguinte começa de novo no ponto de entrada, em vez de retomar. Apenas é removido o ponteiro de retoma local; o estado do carrinho no servidor não é alterado.

Atualizações sem efetuar um novo mount

Alterar um atributo no elemento ativo atualiza a instância em execução - exatamente como handle.update() no pacote fonte. Atributos de apresentação (cultura, layouts) preservam a percurso do utilizador; apenas alterar a identidade da sessão (public-key, session-token, api-base) restabelece a sessão. A remoção do elemento do DOM faz o unmount de a loja de forma limpa, incluindo todos os listeners.

Hosts SSR: Next.js e Nuxt

O micro-frontend é deliberadamente apenas para o cliente. O o respetivo conteúdo é ativo e vinculado à sessão — a disponibilidade, os preços e o carrinho do utilizador existem apenas para uma sessão autenticada no momento da pedido, portanto, não há nada significativo que um servidor host possa pré-apresentar. mount() cria uma nova raiz React do lado do cliente (createRoot); a marcação apresentada pelo servidor dentro do contêiner não é hidratada. Isso torna a integração SSR simples e previsível: apresente um placeholder no servidor, faça o mount no cliente e reserve o espaço 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 }} />;
}

Evitando mudança de layout (CLS)

Dê ao contêiner de mount uma altura mínima que corresponda aproximadamente à primeira visualização da loja e, opcionalmente, apresente o seu próprio skeleton dentro dele - o servidor e o cliente produzem o mesmo placeholder, para que não haja incompatibilidade de hidratação. O adaptador é apresentado imediatamente após a mount e substitui o placeholder numa única apresentação. Não tente hydrateRoot no contêiner: não há nenhuma árvore de loja apresentada pelo servidor para anexar.

A sessão em um host SSR

Os hosts SSR têm uma vantagem natural: o runtime do servidor que apresenta a página também pode criar a sessão - servidor a servidor com o segredo, exatamente como o fluxo mint abaixo (Next.js: Route Handler ou Server Component; Nuxt: rota do servidor). Entregue o token opaco resultante ao componente cliente como prop ou payload. O segredo nunca aparece no código do cliente e o token permanece na memória.

Especificidades do Next.js

Marque o componente que efetua o mount com "use client" e faça o mount em useEffect (consulte o snippet). Com o Pages Router, next/dynamic com ssr: false produz o mesmo resultado. Devolva o handle a partir do efeito como função de cleanup, para que as alterações de rota no cliente efetuem o unmount corretamente. Uma alteração de culture na instância em execução utiliza handle.update() sem reiniciar o percurso; um novo sessionToken provoca um novo mount (como no snippet) — o carrinho também é preservado, porque o micro-frontend restaura o percurso durante o mount.

Especificidades do Nuxt/Vue

Envolva o destino de mount em <ClientOnly> ou efetue a montagem em onMounted — o adaptador precisa de um elemento DOM real no navegador. Tenha em conta o requisito do bundle do host: react e react-dom 18+ têm de estar instalados como dependências da sua aplicação Nuxt; a loja é apresentada no seu próprio contentor e não interfere com o DOM virtual do Vue.

O micro-frontend precisa de uma sessão

Sem token

Nenhum token de sessão – o micro-frontend permanece inativo

Sem sessionToken, o micro-frontend apresenta um aviso neutro e NÃO inicia qualquer chamada à API. Não é possível preencher o carrinho, efetuar o registo ou iniciar o checkout. É necessária uma sessão para ativar a loja, mas esta não tem de estar associada a um utilizador conhecido: uma sessão de convidado é suficiente para consultar e preencher o carrinho; o utilizador real pode ser identificado mais tarde, no checkout (consulte abaixo o fluxo de utilizador opcional).

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.
});
Utilizador por anfitrião

Token de sessão — um utilizador específico

O seu back-end cria um token de curta duração vinculado a um utilizador. O micro-frontend então atua como esse utilizador: o seu carrinho, seus dados. O token é opaco, expira, é de utilizador único e está vinculado à a sua publicKey — e o seu segredo nunca chega ao navegador.

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.

Recarregar a página não elimina o percurso. O micro-frontend guarda localmente o carrinho e a etapa atual, volta a obter o carrinho em direto do servidor durante o mount e retoma exatamente no ponto em que o utilizador ficou — mesmo que a página crie um novo sessionToken em cada carregamento. Se o carrinho tiver expirado no servidor, o percurso recomeça. A página não precisa de efetuar qualquer ação adicional.

Se pretender começar deliberadamente de novo — com um verdadeiro botão «recomeçar» — o pacote exporta clearJourney(). Chame esta função antes do mount (ou de um novo mount) para eliminar o registo do percurso guardado localmente para a página atual; o mount seguinte começará de novo no ponto de entrada, em vez de retomar. Apenas é removido o ponteiro de retoma local; o estado do carrinho no servidor não é alterado.

Opcional: navegar anonimamente e iniciar sessão no checkout

Não é necessário conhecer antecipadamente a identidade do utilizador. Faça o mount com uma sessão de convidado e o micro-frontend permitirá a um visitante anónimo consultar eventos, escolher artigos e preencher o carrinho. Só quando avança para além do carrinho — em direção ao registo e ao checkout — é solicitado o utilizador real através do callback onUserRequired. O restante fluxo prossegue sem perder o carrinho.

  1. 1

    Faça o mount com uma sessão de convidado

    Um token de sessão ainda é necessário para ativar a loja – mas pode ser uma sessão de convidado: basta criá-lo SEM o campo de email, sem necessidade de endereço fictício. O visitante navega e preenche o carrinho nessa sessão.

  2. 2

    Deixar o carrinho aciona onUserRequired

    Quando o visitante avança para além do carrinho, o micro-frontend chama o callback uma vez e apresenta um estado neutro de espera. Autentique o utilizador (login/SSO) e crie uma nova sessão associada ao utilizador exatamente como a primeira: entre servidores, com o segredo. A identificação posterior do utilizador também é feita através deste mint machine-to-machine — o segredo nunca chega ao navegador e o utilizador nunca é definido no código do cliente. O callback apenas devolve o token opaco resultante. É essencial que o back-end obtenha esta identidade a partir da SUA própria sessão autenticada (cookie/JWT); nunca deve aceitar o email ou o ID do utilizador enviados pelo navegador, pois um visitante mal-intencionado poderia pedir a sessão de outra pessoa e aceder aos respetivos dados pessoais através do preenchimento automático.

  3. 3

    Devolva o novo token – o carrinho continua

    Devolva o novo sessionToken e o micro-frontend substitui o anterior e associa o utilizador identificado ao carrinho existente — o carrinho, os artigos e quaisquer intervalos horários permanecem inalterados. Em seguida, avança para o registo. Devolva null para cancelar — o visitante permanece simplesmente no carrinho, sem erro.

  4. 4

    Omitir onUserRequired para um utilizador fixo

    Se o utilizador deixar o callback de fora, nada muda em relação ao fluxo clássico: faça o mount com uma sessão vinculada ao utilizador e o microfrontend atua como esse utilizador desde a primeira etapa.

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);

A página anfitriã autentica o utilizador

A ADITUS não autentica os utilizadores finais. O seu site é a única fonte fidedigna da identidade do utilizador — o login, o SSO e a sessão são geridos pelo seu sistema. A ADITUS confia apenas num sessionToken criado pelo back-end com uma chave secreta. A cadeia de confiança é a seguinte: a autenticação do seu sistema comprova a identidade, o token criado com o segredo confirma-a e o micro-frontend apresenta esse token.

  1. 1

    O seu site autentica o utilizador

    Login, SSO, sessão de membro – inteiramente a sua. ADITUS não está envolvido aqui.

  2. 2

    O seu back-end cria uma sessão

    Servidor para servidor com a chave secreta. Indique quem o micro-frontend deve atuar (email) e vincula-o à a sua publicKey.

  3. 3

    A página monta o micro-frontend

    O token opaco é entregue ao navegador e passado como sessionToken para mount(). O segredo permanece no seu servidor.

  4. 4

    Cada chamada carrega a sessão

    O micro-frontend envia X-Aditus-Session em cada pedido; o proxy resolve o utilizador do carrinho da sessão.

  5. 5

    Nenhum token → micro-frontend permanece inativo

    Sem uma sessão, o micro-frontend nunca é ativado: apresenta um aviso neutro e não efetua chamadas — sem carrinho nem checkout. Crie uma sessão para o ativar.

1. Mint no seu back-end

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. Faça o mount com o 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. Revogar ao sair

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 }

A encomenda concluída regressa à página

Quando uma compra de bilhetes é concluída, não é necessária uma consulta separada da encomenda. Como o micro-frontend é executado nativamente na página (light DOM, não um iframe), entrega a encomenda concluída diretamente ao código através do callback onComplete — uma chamada JS direta, sem postMessage. O micro-frontend já obteve a encomenda completa, pelo que recebe o número da encomenda e as ligações dos bilhetes, e não apenas um 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);
  },
});

O que recebe

onComplete é acionado uma vez por cada checkout concluído e recebe a encomenda completa: number (o ID ou número da encomenda — o seu identificador), status, buyer (nome e email, quando disponíveis) e tickets — cada bilhete inclui ligações tipadas cujo kind é “pdf”, “apple” ou “google”. onError é acionado perante qualquer erro irrecuperável no fluxo.

Redirecionar métodos de pagamento

Os métodos que permanecem na página (fatura, pagamento antecipado ou cartão integrado) acionam onComplete de imediato. Um método que envia o navegador para uma página de pagamento externa (por exemplo, Saferpay) é gerido pelo Micro-Frontend de ponta a ponta: este determina os URLs de regresso a partir da página em que é executado, redireciona o utilizador e, no regresso, conclui a encomenda e aciona onComplete — um ciclo completo sem código adicional na integração. Só é necessário substituir redirectUrls.successUrl / cancelUrl / errorUrl se o regresso tiver de ocorrer numa página DIFERENTE.

GA4/eventos de comércio eletrónico (opcional)

O micro-frontend pode comunicar o funil de compras ao sistema de analytics, mas apenas se essa opção for configurada. Não inclui gtag, GTM nem o SDK do GA, não carrega recursos, não define cookies e não envia dados autonomamente. Adicione o callback opcional onEvent para receber eventos tipados, já estruturados segundo o esquema Enhanced Ecommerce do GA4. Se omitir onEvent, nada muda. A ferramenta de destino e a validação do consentimento ficam inteiramente a cargo da integração.

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);
  },
});

Já no formato GA4

Cada evento inclui um nome de evento GA4 e os respetivos parâmetros standard: items (com item_id, item_name, price, quantity, item_category e, quando aplicável, discount e coupon), value e currency; add_payment_info acrescenta payment_type e purchase acrescenta transaction_id. Extraia name e passe os restantes valores diretamente para gtag("event", name, params) — não é necessário qualquer remapeamento.

O consentimento permanece o seu

onEvent é apenas uma função JavaScript normal na página que chamamos diretamente. O próprio micro-frontend não envia analytics para lugar nenhum - nada sai da página até que o seu handler o envie, para que o seu gestão de consentimento permaneça sob controle total: condicione o envio ao consentimento, encaminhe para o dataLayer do GTM em vez de gtag, agrupe ou descarte totalmente os eventos. Qualquer erro que o seu handler lançar é capturado e isolado, portanto, nunca interrompe o fluxo do utilizador - mantenha o handler leve (um push gtag/dataLayer), pois ele é executado inline.

EventoÉ acionado quando
view_item_listO utilizador vê a lista de artigos (dispara uma vez por conjunto apresentado).
add_to_cartUm artigo ou complemento é adicionado ao carrinho.
remove_from_cartÉ removida uma linha do carrinho ou um complemento.
view_cartA visualização do carrinho é mostrada (uma vez por carrinho).
begin_checkoutO utilizador sai do carrinho em direção ao registo/checkout.
add_payment_infoÉ escolhido um método de pagamento (inclui payment_type).
purchaseA encomenda é efetuada (inclui transaction_id, value e items).

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

Além dos callbacks de configuração, o handle devolvido por mount() carrega um pequeno barramento de eventos totalmente opcional. Registe um listener com handle.on(event, listener) — ele devolve a função de remoção do listener correspondente — e remova-o com handle.off(event, listener). Se nunca chamar on(), nada muda: o barramento não adiciona dependências, não envia nada e não custa nada. É pura observação para a página – um badge do minicarrinho, um banner de confirmação, o seu próprio registo – e complementa os callbacks em vez de substituí-los.

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();
EventoÉ acionado quando
cart:updateO carrinho é alterado: é criado, recebe ou perde um artigo, ou é esvaziado. Inclui cartId, itemCount, value, currency e as linhas como items no formato GA4.
checkout:completeA encomenda é efetuada — no mesmo momento e com o mesmo objeto de encomenda completo que em config.onComplete.
analyticsEspelho do funil GA4 acima: cada payload de onEvent também é emitido no barramento, sem alterações.

Os listeners sobrevivem à atualização()

Os listeners ficam no handle, não em uma apresentação: handle.update({ ... }) apresenta novamente o Micro-Frontend, mas mantém todos os listeners anexados. A desmount via handle() remove todos os listeners automaticamente — só precisa de off() (ou cancelar a assinatura retornada) quando quiser deixar de receber eventos enquanto a loja continua funcionando.

Isolado, nunca bloqueando

Um listener que gera uma exceção é capturado e isolado – nunca interrompe o fluxo do utilizador e nunca afeta outros listeners. O evento “analítico” espelha o funil GA4 com cargas idênticas a config.onEvent, para que o utilizador possa consumir o funil por meio do barramento, do callback ou de ambos; a mesma regra de consentimento se aplica: nada sai da página até que o seu código o envie.

publicKey vs. sessionToken

Mantenha os dois separados. Eles respondem a perguntas diferentes e estão emparelhados, não intercambiáveis.

publicKey — confiança de origem

Publicável. É incluída no código do cliente, pelo que NÃO é um segredo e, por si só, não concede acesso. O back-end aplica uma lista de domínios autorizados por chave: cada chave só funciona nos domínios registados. Responde à pergunta «que site é este?»

sessionToken — identidade

Criado em segredo pelo back-end, opaco e de curta duração. Responde à pergunta «quem é o utilizador?». Cada sessão está associada à publicKey para a qual foi criada: um pedido de sessão também tem de enviar essa chave; qualquer incompatibilidade é rejeitada (403).

Regras de segurança

  • A chave secreta reside apenas no seu servidor – nunca no código do cliente, pacotes ou ficheiros env enviados ao navegador.
  • Mantenha o token apenas na memória. Não persista no localStorage; atualize-o voltando a criar.
  • Os tokens têm vida curta (20 minutos por predefinição, máximo 24 horas) e são de utilizador único.
  • Revogue a sessão ao sair para que um token vazado não possa ser reproduzido.
  • Sempre emparelhe a sessão com a sua publicKey — uma chave ausente ou incompatível é rejeitada (403).
  • Uma sessão ruim ou expirada é um 401 difícil – nunca um fallback silencioso para o utilizador de demonstração.

Referência de endpoint e resposta

POST/api/shop/sessionmint — segredo Bearer
DELETE/api/shop/session/:tokenrevogar – segredo Bearer

Nesta demonstração, o mint é efetuado pelo proxy nos caminhos acima. Em produção, a sessão é criada no núcleo ADITUS — o contrato é idêntico. Ambas as chamadas são efetuadas entre servidores: o back-end autentica-se com o segredo mint do cliente (gerado na consola de administração ADITUS), utilizado como token Bearer. Um mint por login é o padrão habitual; o navegador vê apenas o token resultante.

Pode experimentar este fluxo exato com as suas próprias credenciais notestador de integração — criar a sessão, efetuar o mount e percorrer o fluxo.

Criar uma sessão — pedido

CabeçalhoValorSignificado
AuthorizationBearer <mint-secret>O segredo mint do cliente, gerado na consola de administração ADITUS. Deve permanecer no servidor — nunca pode ser enviado para um navegador ou incluído num bundle da aplicação.
Content-Typeapplication/jsonO corpo é um objeto JSON.
Campo do bodyTipoSignificado
publicKeystring · obrigatórioA chave publicável PARA a qual a sessão é criada. Tem de ser a mesma publicKey usada no mount do micro-frontend — todas as chamadas posteriores à loja são validadas em relação a ela (403 em caso de incompatibilidade). O segredo Bearer tem de pertencer ao cliente desta chave.
emailstring · obrigatórioO utilizador da loja em nome do qual a sessão atua. Obtenha-o a partir da SUA sessão autenticada no servidor (cookie de login/SSO ou JWT) — nunca a partir do pedido do navegador, pois um visitante poderia obter a sessão de outra pessoa. OMITA este campo para criar uma sessão ANÓNIMA: o visitante pode navegar e preencher o carrinho, mas o micro-frontend impede que avance até o callback onUserRequired fornecer uma sessão associada a um utilizador (consulte o fluxo de utilizador opcional). Um email presente mas malformado continua a ser rejeitado (400).
externalUserIdstring · obrigatórioO identificador de utilizador do seu sistema, guardado com a sessão como metadado (útil para suporte e correlação de registos). A ADITUS não o interpreta.
ttlSecondsstring · obrigatórioDuração da sessão em segundos. Por predefinição, 20 minutos, até ao máximo de 24 horas. Quando expira, as chamadas à loja devolvem 401 session_expired — crie então um novo token (por exemplo, através do hook onSessionExpired).
eventSlugstring · obrigatórioDefine o PONTO DE ENTRADA do percurso no servidor: quando presente, o micro-frontend começa diretamente na seleção de artigos deste evento; quando omitido, começa na vista geral dos eventos. Como faz parte da sessão criada, não pode ser manipulado no navegador. Carateres inválidos devolvem 400 invalid_event_slug; um slug sem correspondência com um evento em direto faz regressar à vista geral dos eventos.

Criar uma sessão — resposta (200)

CampoTipoSignificado
sessionTokenstringToken opaco (sess_…). Envie-o para o navegador e passe-o a mount() como sessionToken; o micro-frontend envia-o como X-Aditus-Session em todas as chamadas à loja. Não contém dados do utilizador e não pode ser descodificado.
expiresAtnumberValidade como timestamp Unix em milissegundos. Este valor é meramente informativo para efeitos de agendamento — o micro-frontend reage autonomamente ao 401.

Revogar uma sessão

DELETE /api/shop/session/:token com o mesmo segredo Bearer — o segredo tem de pertencer ao cliente para o qual a sessão foi criada. Chame este endpoint ao terminar sessão para invalidar o token juntamente com a sessão do seu sistema. Resposta: { "revoked": true } (ou false se a sessão já tiver expirado). A revogação é idempotente e pode ser efetuada em modo fire-and-forget.

Erros são explícitos

StatusCódigoSignificado
503session_not_configuredA configuração de sessões de incorporação não está disponível para este cliente.
401unauthorizedO segredo de criação de sessão é inválido ou está em falta.
400invalid_public_keyA chave pública indicada é inválida.
400invalid_emailO endereço de correio eletrónico indicado é inválido.
400invalid_tokenO token de sessão indicado é inválido.
429rate_limitedForam efetuados demasiados pedidos. Tente novamente dentro de momentos.
403public_key_invalidA chave pública indicada tem um formato inválido.
403public_key_unknownA chave pública indicada não é reconhecida.
403public_key_origin_unresolvedNão foi possível determinar a origem deste pedido.
403public_key_domain_not_allowedO domínio deste pedido não está autorizado para esta chave pública.
401invalid_sessionA sessão indicada é inválida.
401session_expiredA sessão indicada expirou.
403session_requires_public_keyEsta sessão requer uma chave pública.
403session_key_mismatchA chave pública não corresponde à sessão.
403anonymous_sessionEsta operação não está disponível para uma sessão anónima.

Agora faça com que seja o seu

A identidade está definida — aplique agora a identidade visual ao micro-frontend. A ferramenta de estilo gera uma configuração de tema pronta a colar e explica todos os parâmetros, com uma pré-visualização em direto.

Abra a ferramenta de estilo