Saltar para o conteúdo
Micro Frontend

Integrar a bilheteira de forma nativa

O micro-frontend apresenta a bilheteira ADITUS diretamente na sua página, sem iframe. Sites sem back-end carregam o script e definem public-key juntamente com cart-scope="tab". Sites com back-end criam a sessão no servidor e transmitem o token de sessão; o seu servidor decide então quem é o comprador. Sem sessão, o micro-frontend permanece inativo.

É novo aqui? A visão geral mostra o que o Micro Frontend faz, sem detalhes técnicos.

Início rápido

Há dois caminhos para uma loja em funcionamento. Ambos usam o mesmo script e o mesmo elemento; a diferença está em quem cria a sessão.

Sem back-end

O elemento cria a sua própria sessão de navegador para a sua publicKey, limitada aos domínios autorizados. Defina cart-scope="tab": sem identidade de servidor, a predefinição "shared" termina num erro explícito. Cada separador tem então o seu próprio carrinho. A sessão é anónima: os visitantes navegam e enchem o carrinho. Antes do registo, element.config.onUserRequired tem de devolver um token de sessão associado a um utilizador, emitido por um servidor; sem ele a loja para no carrinho com um aviso.

index.html
<!-- Site without a backend: load the script once, place the element. -->
<script src="https://developers.aditus.com/api/embed/v1/aditus-shop.js" defer></script>

<aditus-shop
  public-key="pk_your_site"
  cart-scope="tab"
></aditus-shop>

Com back-end

O seu servidor cria a sessão com o mint secret e decide quem é o comprador. Transmita o token devolvido ao elemento. Com o email do comprador, a predefinição cart-scope "shared" mantém um único carrinho em todos os separadores.

server.js
// 1. On your server: create a session. The mint secret never leaves the server.
const res = await fetch("https://developers.aditus.com/api/shop/session", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ADITUS_MINT_SECRET}`,
    "Content-Type": "application/json",
  },
  // email comes from your own login session, never from the browser request.
  body: JSON.stringify({ publicKey: "pk_your_site", email: user.email }),
});
const { sessionToken } = await res.json();
index.html
<!-- 2. In your page: pass the token from your server to the element. -->
<script src="https://developers.aditus.com/api/embed/v1/aditus-shop.js" defer></script>

<aditus-shop
  public-key="pk_your_site"
  session-token="SESSION_TOKEN_FROM_YOUR_SERVER"
></aditus-shop>

Substitua pk_your_site pela sua chave publicável do admin ADITUS. developers.aditus.com é o endereço de produção do script e da API. Ambos os caminhos são descritos em detalhe mais abaixo.

A tecnologia por trás do micro-frontend

O micro-frontend apresenta todo o processo de compra (eventos, artigos, carrinho, registo, pagamento, conclusão) no DOM da sua página. Usa o layout da página e o seu CSS pode estilizar cada parte.

React 19, compatível com React 18

Todo o percurso funciona como um único fluxo React tipado. É compilado e testado com React 19 e usa apenas as funcionalidades do React 18 (useState, useEffect, useMemo e semelhantes), pelo que o pacote de código-fonte funciona com React 18 ou mais recente (peerDependency react >=18). Além do React tem uma dependência de runtime: libphonenumber-js para validar números de telefone. Não há biblioteca de UI, framework de data fetching nem toolkit de estado. O script alojado pela ADITUS já inclui React, pelo que a sua página não precisa de React próprio.

Light DOM em vez de iframe

Sem iframe nem shadow DOM. A loja é apresentada diretamente no DOM da página, integra-se no layout e é responsiva e acessível. Tipos de letra e cores vêm dos tokens --aditus-*; o tipo de letra predefinido é system-ui. Defina --aditus-font para usar o tipo de letra da sua página.

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

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

O estilo é o seu

Cada elemento tem um hook CSS estável (classes BEM aditus-shop__* e data-state). Pode estilizá-lo com o seu próprio CSS ou definir cores da marca, raios e espaçamentos com alguns design tokens --aditus-*. Os estilos base opcionais estão em CSS @layer aditus-shop, pelo que as suas regras sem layer ganham sempre. Isto também se aplica a regras genéricas do seu site, como button {}, input {} ou a {} de um tema WordPress ou Bootstrap: também alteram a loja. Limite essas regras às suas próprias áreas da página se a loja não as dever adotar.

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 uma sessão válida, a loja permanece inativa e não faz pedidos à loja. A sessão vem do seu servidor (criada com o mint secret, que nunca chega ao navegador) ou, em sites sem back-end, da sessão de navegador restrita ao domínio da sua publicKey. Todas as chamadas passam pela API ADITUS, que acrescenta as credenciais da loja no servidor.

Estilizar em três níveis

Utilize o nível mais geral adequado: os tokens de design estabelecem a marca, os hooks BEM semânticos gerais tornam os papéis recorrentes consistentes e os hooks específicos de componentes existentes ficam reservados para ajustes avançados.

Hooks semânticos gerais

.aditus-shop__section, .aditus-shop__panel, .aditus-shop__list, .aditus-shop__list-item, .aditus-shop__actions e .aditus-shop__action. Estes hooks aditivos descrevem papéis, enquanto cada elemento mantém o seu hook especializado. A análise de URL e capturas do Styling Studio propõe regras exatamente neste nível (contorno vs. sombra, peso dos botões, espaçamento das secções), nunca em hooks específicos de componentes.

Ações e exceções

Quando implementado, data-variant="primary" ou data-variant="secondary" documenta o papel de uma ação. Não deduza papéis semânticos do aninhamento do DOM: reg-form não é um panel documentado. Direcione o seu hook especializado apenas quando esse formulário precisar intencionalmente de uma exceção.

shop-micro-frontend.css
/* 1. Tokens: the broad brand foundation. */
.onsite-shop .aditus-shop {
  --aditus-accent: #c2410c;
  --aditus-surface: #ffffff;
  --aditus-radius: 12px;
}

/* 2. General hooks: consistent roles throughout the journey. */
.onsite-shop .aditus-shop__section { margin-block: 2rem; }
.onsite-shop .aditus-shop__panel {
  background: var(--aditus-surface);
  border: 1px solid var(--aditus-border);
  border-radius: var(--aditus-radius);
  box-shadow: var(--aditus-shadow);
}
.onsite-shop .aditus-shop__list { gap: 0.75rem; }
.onsite-shop .aditus-shop__list-item { padding-block: 0.75rem; }
.onsite-shop .aditus-shop__actions { gap: 0.5rem; }
.onsite-shop .aditus-shop__action[data-variant="primary"] { font-weight: 700; }
.onsite-shop .aditus-shop__action[data-variant="secondary"] { box-shadow: none; }

/* 3. Expert override: only the named component changes. */
.onsite-shop .aditus-shop__article-card {
  border-inline-start: 4px solid var(--aditus-accent);
}

Requisitos para o seu site

Os requisitos dependem da forma de entrega. O script alojado pela ADITUS só precisa de um navegador atual; o pacote de código-fonte precisa também de React 18+ na sua aplicação. Ambos precisam de uma sessão para uma publicKey cuja lista de domínios autorizados inclua o seu domínio.

O que é necessário

  • Apenas no pacote de código-fonte: React 18+ e react-dom 18+ no bundle da aplicação (peerDependency react >=18); mount() usa createRoot de react-dom/client. O script alojado traz o seu próprio React.
  • Um navegador com DOM (lado do cliente). O adaptador recebe o mount em um elemento DOM real (light DOM). Aplicações 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 para a sua publicKey. Sites sem back-end definem public-key juntamente com cart-scope="tab"; o elemento cria então a própria sessão de navegador através de /shop/embed-session, restrito por domínio. Sites com back-end criam a sessão no servidor com o mint secret e transmitem o token. Sem sessão, o micro-frontend permanece inativo. O seu domínio tem de constar da lista de domínios autorizados da publicKey.

O que não é necessário

  • Nenhum framework específico. mount(container, config) funciona com React, Vue, Angular ou HTML simples.
  • Sem iframe, sem shadow DOM – ele é apresentado nativamente na página.
  • Nenhum CSS específico no seu site. Os estilos base estão em @layer aditus-shop com especificidade de uma só classe, pelo que qualquer regra sem layer do seu site ganha. Isto inclui regras genéricas de elementos como button, input ou a: também afetam os controlos da loja; limite-as ao seu próprio conteúdo se não se devem aplicar aí. Box-sizing, padding e espaçamentos estão ainda protegidos contra um reset universal como * { margin:0; padding:0 }. baseStyles: false desativa os estilos base.
  • Nenhuma outra biblioteca no seu site. Além das peerDependencies do React, o pacote de código-fonte traz uma dependência de runtime (libphonenumber-js para validar números de telefone); o script alojado contém tudo.
  • Nenhuma configuração de build para o script alojado; o pacote de código-fonte só precisa de um import ESM.

O seu site é de confiança: o seu servidor autentica o utilizador antes de criar uma sessão identificada. O micro-frontend é apresentado deliberadamente no Light DOM da sua página, sem isolamento iframe. Os scripts da sua página podem aceder ao DOM e ao armazenamento do navegador; por isso, proteja o seu site e os scripts de terceiros. A autorização de origem por chave pública não é uma autenticação do utilizador. Os dados de pagamento só são introduzidos na página externa do prestador de pagamentos.

Segurança dos pagamentos, PCI e CSP

A preparação da compra é apresentada na sua página. O pagamento acontece na página do prestador de pagamentos, pelo que nenhum dado de pagamento passa pela sua página.

Nenhum dado de pagamento na sua página

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 sua página.

Âmbito PCI mínimo

Uma vez que o seu site 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

Se o seu site define uma Content Security Policy, acrescente: script-src para a origem ADITUS (script alojado), connect-src para a origem da API ADITUS (todas as chamadas da loja passam por ela), img-src para a origem da API ADITUS (bandeiras, logótipos de métodos de pagamento, badges de wallet) e os hosts das imagens de eventos e artigos, e style-src 'unsafe-inline', porque os estilos base são inseridos como elemento <style> (não é necessário com baseStyles: false e folha de estilos própria). Não são necessárias entradas script-src nem frame-src para prestadores de pagamento, porque o passo de pagamento é um redirecionamento de nível superior.

Comparação com iframe e shadow DOM

Iframe

Um iframe é um documento separado: os estilos da sua página não se aplicam no interior, a altura tem de ser sincronizada e tem a sua própria ordem de foco. O micro-frontend apresenta, em vez disso, a preparação da compra como HTML no DOM da sua página. Só o pagamento sai da sua página, através de um redirecionamento de nível superior para a página do prestador de pagamentos.

Shadow DOM

Muitos web components encapsulam-se num shadow DOM. Os estilos da sua página só os alcançam então através de custom properties e ::part. O micro-frontend usa, em vez disso, a cascata CSS normal: os estilos base estão em @layer aditus-shop, pelo que qualquer regra sem layer da sua página ganha sem maior especificidade nem !important.

As abordagens comparadas

CritérioIframe clássicoWidget Shadow-DOMMicro-frontend ADITUS
AcessibilidadeDocumento separado com ordem de foco e estrutura próprias.As referências ARIA por ID (aria-labelledby, aria-describedby) não atravessam a fronteira do shadow DOM.HTML no DOM da sua página: uma única ordem de foco e uma única estrutura com a sua 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 sem layer ganha aos estilos base em @layer; estes podem ser substituídos ou desativados.
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.Script alojado: um bundle que inclui React. Pacote de código-fonte: usa o React da sua aplicação e uma dependência de runtime (libphonenumber-js).
Segurança dos pagamentos e âmbito PCIFortemente isolado – mas ao preço de UX, design e capacidade de resposta.Depende do widget: os campos de cartão dentro do widget fazem parte da sua página.Seleção na sua página, pagamento na página do prestador de pagamentos.

A nossa recomendação: JavaScript diretamente a partir do URL da ADITUS

Carregue o bundle JavaScript diretamente a partir da ADITUS. É a opção recomendada para todos os sites, incluindo React e outros frameworks, não apenas para sites HTML ou CMS. A ADITUS melhora a lógica interna da loja de forma centralizada. As melhorias compatíveis chegam à sua integração sem ter de transferir um novo pacote, compilar ou publicar novamente o seu site.

Ver a integração recomendada por script /api/embed/v1/aditus-shop.js

Como chegam as atualizações

O URL v1 fornece versões compatíveis com interfaces estáveis. As novas versões são carregadas em visitas posteriores quando a cache HTTP, atualmente de uma hora, expira ou é revalidada. Isto não acontece necessariamente a cada recarregamento. Um checkout já aberto mantém o código carregado.

Alternativa: gerir a distribuição e as atualizações

Escolha o pacote de código-fonte, a instalação do tarball através de npm ou uma cópia JavaScript alojada por si apenas se pretender gerir a distribuição. A ADITUS não pode atualizar a sua cópia. Terá de acompanhar as versões, transferi-las ou instalá-las, testá-las, compilar quando necessário e publicá-las. O pacote não está publicado no registo público do npm.

Alternativa: pacote de código-fonte (distribuição própria)

Em alternativa, descarregue o pacote de código-fonte da sua instância ADITUS e integre-o na sua aplicação (Vite, webpack, Next.js ou Nuxt). A distribuição fica a seu cargo: cada atualização exige reinstalação, build e publicação. Não está no npm público.

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 site e é descarregado diretamente desta instância, sem necessidade de credenciais:aditus-shop-embed.tgz. Instale-o a partir deste URL (npm install aceita URLs de tarball, caminho /api/embed/v1/aditus-shop-embed.tgz) ou extraia-o para o repositório. O onboarding fornece a publicKey e os domínios autorizados; sem publicKey, o micro-frontend permanece inativo. Contacto: página de contacto. Use este pacote apenas se distribuir e atualizar o seu próprio build.

Mantenha-se atualizado: a faixa v1 recebe atualizações compatíveis no local. Verifique regularmente manifest.json (antes de cada release da sua app, idealmente como tarefa agendada) e compare package.sourceHash com aditus.sourceHash do package.json instalado. Se forem diferentes, reinstale o tarball e execute build e testes; hooks, tokens e eventos mantêm-se estáveis dentro da v1. O bundle do Web Component atualiza-se sozinho através da cache HTTP de uma hora.

Integração por script através do URL da ADITUS

O bundle pré-compilado em https://developers.aditus.com/api/embed/v1/aditus-shop.js funciona em todos os sites, incluindo aplicações com build, WordPress, Typo3 e HTML simples. Uma tag script regista <aditus-shop> com React incluído: não requer npm nem build próprio. Este URL público e versionado dispensa credenciais; a loja continua a exigir uma publicKey autorizada e uma sessão. Um checkout em curso nunca é atualizado a meio; a cache do navegador pode atrasar o novo bundle até uma hora.

index.html
<!-- 1. Include the bundle hosted by ADITUS (once per page). -->
<script src="https://developers.aditus.com/api/embed/v1/aditus-shop.js" defer></script>

<!-- 2. Explicit tab isolation for anonymous sessions created automatically in the browser (not the default).
     For shared: session-token from your backend; anonymous ones also need a server-side sharedCartKey. -->
<aditus-shop
  public-key="pk_live_yourkey"
  api-base="https://developers.aditus.com/api"
  cart-scope="tab"
  culture="en-GB"
></aditus-shop>

<!-- 3. Optional: consume journey events as plain DOM events. -->
<script>
  document.querySelector("aditus-shop").addEventListener("cart:update", (e) => {
    console.log("Items in cart:", e.detail.itemCount);
  });
</script>

O mesmo URL JS alojado regista automaticamente <aditus-shop> E expõe window.AditusShop: mount, themeToCss, clearJourney, mintEmbedSession e onEmbedSessionRenewed, com as assinaturas do pacote fonte. O seu site não precisa de React. O mount alojado, element.config e o pacote fonte aceitam a mesma AditusShopConfig, incluindo temas, eventTheme: false, articleContent e callbacks. Apenas os atributos HTML não podem transmitir funções ou objetos. O pacote fonte continua válido para bundlers e partilha do runtime React.

Âmbito do carrinho, confiança e armazenamento

JS simples: ciclo do token explícito

mount(container, config) é o mesmo mount fonte: sem sessionToken fica inativo e não cria sessões. Defina apiBase explicitamente com o URL completo /api/shop. Após o login assíncrono, o backend cria a sessão entre servidores e entrega apenas o token. host.renewSession e host.revokeSession são funções/rotas próprias a implementar no backend autenticado, não endpoints ADITUS de navegador prontos a usar. Nunca envie segredos ao navegador. onUserRequired mantém o login assíncrono e criação de sessão no servidor, devolvendo token ou null, em todas as variantes JavaScript.

index.html / your own login
<div id="ticketshop-slot"></div>
<script src="https://developers.aditus.com/api/embed/v1/aditus-shop.js"></script>
<script>
  // Called after your async login and server-side session creation, with the returned sessionToken.
  // host.renewSession(staleToken): Promise<string | null>
  // host.revokeSession(token): Promise<void>
  // These are HOST-SPECIFIC backend functions, not built-in ADITUS routes.
  // Your backend authenticates the user; mint secrets never enter the browser.
  window.mountKnownSessionShop = function (sessionToken, host) {
    if (!sessionToken) throw new Error("A sessionToken from your backend is required");
    const slot = document.getElementById("ticketshop-slot");
    if (!slot) throw new Error("Missing mount container");
    let disposed = false;
    let config = {
      apiBase: "https://developers.aditus.com/api/shop",
      publicKey: "pk_your_site",
      sessionToken,
      cartScope: "shared", // default; trusted S2S identity required; immutable after mount
      culture: "en-GB",
      refreshSession: async (staleToken) => {
        const token = await host.renewSession(staleToken);
        if (disposed) return null;
        if (token) config = { ...config, sessionToken: token };
        return token; // Adapter swaps token and retries the failed request once.
      },
      onError: (error) => console.error("Ticketshop:", error.message),
    };
    const handle = window.AditusShop.mount(slot, config);
    return {
      updateCulture(culture) {
        if (disposed) return;
        config = { ...config, culture };
        handle.update(config); // full replacement, not a patch
      },
      async logout() {
        disposed = true;
        handle();
        window.AditusShop.clearJourney(); // local resume pointer only
        await host.revokeSession(config.sessionToken); // HOST-SPECIFIC S2S revoke
      },
      teardown() {
        disposed = true;
        handle(); // SPA route cleanup, not server-side revocation
      },
    };
  };
  // Your site supplies the real token and implementations:
  // const shop = window.mountKnownSessionShop(sessionToken, host);
  // Logout: await shop.logout(); route change: shop.teardown();
</script>

JS simples: sessão no navegador e renovação

Com uma publicKey autorizada, mintEmbedSession(publicKey, apiRoot = "/api", { fresh?, staleToken? }) devolve Promise<string | null> sem segredo. Os helpers e api-base usam a raiz /api; config.apiBase usa a base shop /api/shop. onEmbedSessionRenewed(publicKey, apiRoot, listener) entrega um token e devolve uma função de remoção. O exemplo trata falha inicial, expiração, renovação proativa e limpeza. A remoção afeta apenas este listener, não caches partilhadas, temporizadores ou sessões no servidor; os helpers mantêm continuidade através de sessionStorage.

index.html / browser session
<div id="ticketshop-slot"></div>
<script src="https://developers.aditus.com/api/embed/v1/aditus-shop.js"></script>
<script>
  window.mountBrowserSessionShop = async function () {
    const { mount, mintEmbedSession, onEmbedSessionRenewed } = window.AditusShop;
    const publicKey = "pk_your_site"; // your whitelisted key, not a secret
    const apiRoot = "https://developers.aditus.com/api"; // no /shop for helpers
    const slot = document.getElementById("ticketshop-slot");
    if (!slot) throw new Error("Missing mount container");
    const sessionToken = await mintEmbedSession(publicKey, apiRoot);
    if (!sessionToken) throw new Error("Browser session failed: check key/domain");
    let disposed = false;
    let config = {
      apiBase: apiRoot + "/shop", // mount uses the shop base
      publicKey,
      sessionToken,
      cartScope: "tab", // explicit isolation opt-in: browser-created sessions cannot safely share
      culture: "en-GB",
      refreshSession: async (staleToken) => {
        const token = await mintEmbedSession(publicKey, apiRoot, {
          fresh: true, staleToken,
        });
        if (disposed) return null;
        if (token) config = { ...config, sessionToken: token };
        return token;
      },
      onError: (error) => console.error("Ticketshop:", error.message),
    };
    const handle = mount(slot, config);
    const offRenewed = onEmbedSessionRenewed(publicKey, apiRoot, (token) => {
      if (disposed) return;
      config = { ...config, sessionToken: token };
      handle.update(config); // preserve callbacks and all other options
    });
    return function teardown() {
      disposed = true;
      offRenewed();
      handle();
    };
  };
  // const teardown = await window.mountBrowserSessionShop();
  // Start now; your SPA calls window.teardownTicketshop() on route change.
  window.mountBrowserSessionShop().then((teardown) => {
    window.teardownTicketshop = teardown;
  }).catch((error) => {
    document.getElementById("ticketshop-slot").textContent = error.message;
  });
</script>

Atributos e config JavaScript completa

element.config aceita toda a AditusShopConfig, incluindo funções. As chaves definidas prevalecem sobre atributos, mesmo false; ausentes/undefined mantêm os atributos. Cada atribuição substitui todos os overrides, não é um patch: element.config = {} remove-os. É suportada a atribuição antes do upgrade. api-base é a raiz /api, config.apiBase termina em /api/shop. themeToCss(theme, selector = ".aditus-shop") devolve CSS de tokens em alternativa ao objeto tema.

index.html / element.config
<aditus-shop id="shop" public-key="pk_your_site"
  api-base="https://developers.aditus.com/api" cart-scope="tab" culture="de-DE"></aditus-shop>
<script>
  const element = document.getElementById("shop");
  // Supported before upgrade by the following hosted script, too.
  element.config = {
    apiBase: "https://developers.aditus.com/api/shop",
    culture: "en-GB", // overrides the HTML culture attribute
    cartScope: "tab", // explicit isolation opt-in for this anonymous browser-created example
    theme: { accent: "#c2410c", fontSize: "16px" },
    eventTheme: false,
    onComplete: (order) => console.log("Order completed:", order),
    onError: (error) => console.error("Ticketshop:", error.message),
  };
  // No sessionToken: effective publicKey enables browser session creation.
  // Optional refresh by your site: refreshSession: (staleToken) => host.renewSession(staleToken)
  // host.renewSession is HOST-SPECIFIC; your refresh always takes precedence.
  // Later: element.config = {}; removes overrides, attributes apply again.
  // Cleanup: element.remove();
</script>
<script src="https://developers.aditus.com/api/embed/v1/aditus-shop.js" defer></script>
AtributoSignificado
cart-scopeAditusShopConfig.cartScope?: 'tab' | 'shared' tem 'shared' como predefinição, mesmo se omitido. 'tab' é uma escolha explícita para isolar percursos. window.AditusShop.mount() alojado centralmente, element.config e mount() do pacote fonte/framework aceitam a mesma opção; HTML usa cart-scope="tab" ou cart-scope="shared". Escolha antes da montagem. O âmbito é imutável para a instância de checkout montada: handle.update() rejeita alterações em vez de migrar um carrinho ativo. Mantenha-o nas configurações completas de substituição.
public-keyA sua chave publicável (aplica-se a lista de domínios autorizados). Sem session-token, o elemento cria a sua própria sessão de navegador. Sites sem back-end usam este modo e definem também cart-scope="tab"; com a predefinição "shared", a sessão de navegador termina num erro explícito, porque não existe uma identidade de servidor fiável.
session-tokenUm token de sessão criado pelo seu back-end com o mint secret (ver abaixo). Se estiver definido, o elemento nunca cria uma sessão por si; a sua página gere o ciclo de vida da sessão.
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.
cultureCulture/locale da loja encaminhada para a seleção de recursos ADITUS; valores válidos como it-IT são transmitidos. O texto dos recursos de voucher e upsell mantém-se como fornecido pela ADITUS; o restante da interface usa recursos em alemão/inglês com fallback. Isto não significa uma interface totalmente em italiano. A alteração atualiza a localização sem reiniciar o percurso.
article-layout / article-group-layout / event-layout / article-select-mode / show-headerAs opções simples de apresentação, com os mesmos valores da configuração de mount() (cards, grid, quantity, …). O ponto de entrada (seleção de eventos ou evento fixo) não é um atributo: é definido no servidor quando a sessão é criada.
include-shop-assortmentsCombinação opcional de sortidos; includeShopAssortments em mount(). Por predefinição false: carrega apenas o sortido predefinido. Com true acrescenta os sortidos showInShop=true. Um sortido inicial explícito tem sempre prioridade. Sem atributo aplica-se o valor do cliente; false explícito substitui-o.
survey-columnsNúmero de colunas (1–4) das opções de resposta das perguntas RadioButtonList/CheckBoxList. Corresponde 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.
hide-house-numberOculta o campo do número da porta no bloco de morada dos questionários, em todas as variantes de país; corresponde a hideHouseNumber na configuração de mount(). A rua e o número são então introduzidos juntos num campo «Rua e número» que ocupa a largura libertada; as sugestões de morada escrevem aí a rua e o número na ordem habitual do país (p. ex. "12 Rue Cler" em França), um número pré-preenchido é limpo e a resposta separada do número é sempre enviada como um espaço invisível de largura zero (U+200B), sem erro de validação, mesmo que o questionário a marque como obrigatória. O formulário da morada de faturação segue a mesma regra. Desativado por predefinição; sem atributo aplica-se o valor do cliente, "false" explícito substitui-o.
show-jump-buttonBotão de salto no passo de personalização; corresponde a showJumpButton na config de mount(). Ativo por predefinição: após um envio falhado, uma pequena pílula fica fixa no fundo do widget, mostra quantos dados faltam e salta para o seguinte; com tudo preenchido, volta ao botão sem nunca o enviar. Fica oculta enquanto esse botão ou o destino estão visíveis e durante a escrita. "false" desativa-a; sem atributo aplica-se o valor do cliente.
voucher-code-paramNome de um parâmetro de consulta do URL da sua página que transporta um código de voucher/bilhete (p. ex. "code" para ligações como …/tickets?code=ED24). Corresponde a voucherCodeParam na configuração de mount(). O elemento lê-o uma vez ao montar, remove-o da barra de endereço e resgata o código automaticamente (formulário de voucher ou primeiro carrinho). Apenas para sessões fixadas a um evento.
base-stylesDefina como "false" para remover o tema neutro predefinido e aplicar estilos a cada hook.
autoScroll / articleIconVisible / classPrefixSem atributo: defina estas opções através de element.config (mesmos nomes que na configuração de mount()). autoScroll (predefinição true) volta a mostrar o início da loja após uma mudança de passo; defina false se a sua página gerir o deslocamento. articleIconVisible (predefinição false) mostra uma imagem ou um monograma por artigo na vista de lista. classPrefix (predefinição "aditus-shop") altera o prefixo de todos os hooks CSS.

Sessão e autocura

Um sessionToken efetivo (config ou atributos) desativa a criação automática de sessão. Com publicKey efetiva sem token, o elemento continua a criar a sessão. refreshSession do seu site tem sempre prioridade sobre o integrado. Sem ele, as sessões automáticas usam o helper partilhado para refresh e renovação proativa. Light DOM mantém-se: sem iframe nem shadow DOM.

Eventos e versionamento

Os DOM CustomEvents propagam-se a partir do elemento com payload em event.detail: cart:update, phase:change, checkout:complete e analytics, iguais a handle.on(). Os callbacks de config também estão disponíveis.

Reiniciar deliberadamente o percurso

element.clearJourney() e window.AditusShop.clearJourney() removem apenas o ponteiro local de retoma desta página. Chame antes de reinserir ou montar para começar de novo. Não apagam carrinhos no servidor nem revogam sessões; a revogação no logout pertence ao backend.

Atualizações sem efetuar um novo mount

As configs mount/update são substituições completas, não patches: guarde a config e passe { ...config, culture } a handle.update(). element.config também substitui todos os overrides; atributos não ocultos atualizam no local. A apresentação preserva o percurso; a identidade restabelece a sessão. handle() desmonta; remover o elemento limpa listeners embed e renovação. Remova os seus listeners DOM se conservar o elemento.

Aplicações 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 o seu servidor 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): the micro-frontend is deliberately client-only.
"use client";
import { useEffect, useRef } from "react";
import { mount, type ShopHandle } from "@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_your_site",
      sessionToken, // issued server-side, passed to the client component as a prop
      cartScope: "shared", // the default in frameworks too; trusted server identity
    });
    return () => handle(); // unmount on route change
  }, [sessionToken]);

  // Server and client render the SAME placeholder and so avoid a hydration mismatch.
  // min-height reserves the space so the page does not jump on mount (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 numa aplicação SSR

As aplicações 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 de criação de sessão 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: 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.

Âmbito do carrinho, confiança e armazenamento

cartScope define se os separadores de um visitante partilham o mesmo carrinho. O valor predefinido é 'shared'; 'tab' separa os separadores intencionalmente. Escolha-o antes da montagem; depois não pode ser alterado.

Aspeto'shared' (predefinido)'tab'
CarrinhoUm carrinho para todos os separadores com a mesma origem, publicKey, identidade e eventoCada separador tem o seu próprio carrinho
SessãoEmitida pelo seu backend: utilizador identificado, ou anónimo com sharedCartKey da sua própria sessão de servidor. Nunca aceite a identidade nem a chave a partir do navegador.Qualquer sessão, incluindo uma sessão criada no navegador
Vários separadoresUm separador edita; os outros ficam só de leitura e oferecem «Continuar a editar aqui». Um separador aberto ou recarregado assume automaticamente, nunca durante o pagamento.Independentes
Se faltar o requisitoErro explícito, mesmo sem cartScope indicado; sem mudança silenciosa para 'tab'. Por isso, páginas sem backend definem 'tab'.–
  • Configuração: cartScope em mount() e element.config, cart-scope em HTML. handle.update() rejeita uma alteração; mantenha por isso o valor em cada config de substituição. Atributos · Campos da sessão
  • Armazenamento: Os rascunhos de formulário ficam por separador em sessionStorage e expiram 2 horas após a última edição. Os tokens nunca vão para localStorage. Encomendas e registos confirmados não são afetados.
  • Logout: No logout ou na troca de utilizador, o seu backend revoga a sessão antiga; depois atualize o token ou desmonte o embed e chame clearJourney(). Um novo utilizador nunca herda o carrinho do anterior.
  • Segurança: O embed é apresentado diretamente na sua página, sem iframe. Os scripts da página, incluindo os de terceiros, podem aceder-lhe; inclua por isso apenas scripts de confiança. Os dados de pagamento são introduzidos apenas no prestador de pagamentos.
shared-cart.js
// Load the recommended centrally hosted aditus-shop.js first.
// sessionToken comes from your trusted backend's server-side session, not a browser-created one.
// Identified user: backend authenticates user and derives email/externalUserId.
// Anonymous visitor: backend omits email and MUST derive sharedCartKey
// from its own trusted visitor session. Neither identity nor key is browser input.
const slot = document.getElementById("ticketshop-slot");
if (!slot) throw new Error("Missing mount container");
const config = {
  apiBase: "https://developers.aditus.com/api/shop",
  publicKey: "pk_your_site",
  sessionToken,
  cartScope: "shared",
};
const handle = window.AditusShop.mount(slot, config);
// Full replacement config retains the chosen scope:
handle.update({ ...config, culture: "en-GB" });
// Do not change cartScope on this handle. Route teardown: handle();

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 "@aditus/shop-embed";

const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount target #aditus-shop not found");

// Without a sessionToken the micro-frontend stays inactive: it only shows a
// neutral notice and starts no API calls: no cart, no registration,
// no checkout. Only a session activates the shop.
mount(el, {
  publicKey: "pk_your_site",
  // sessionToken missing -> the shop is not activated.
});
Utilizador do seu site

Token de sessão para 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 "@aditus/shop-embed";

// getElementById can return null. Check it properly in TypeScript instead of using "!".
const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount target #aditus-shop not found");

// AditusShopConfig types every option and gives you autocompletion.
const config: AditusShopConfig = {
  apiBase: "https://developers.aditus.com/api/shop",
  publicKey: "pk_your_site",   // required as soon as a session is involved
  sessionToken,                // issued by your backend, keep it in memory only
  cartScope: "shared",          // default; requires a trusted server identity
};

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

// The micro-frontend sends the X-Aditus-Session header with every shop call.
// On logout, revoke the session on the server and remount/update.

Ao recarregar, um apontador válido permite pedir o carrinho do servidor sob o mesmo âmbito autorizado e identidade de confiança. Não autoriza nem garante recuperação após expiração, eliminação do armazenamento ou mudança de utilizador. tab não junta percursos de outros separadores; shared mantém o bloqueio de checkout com um só escritor.

O regresso do pagamento usa aditus-return (um token opaco, curto e vinculado ao servidor) com aditus-pay. Pode regressar no mesmo separador ou num novo, mas o servidor exige cliente/origem autorizado e identidade de confiança correspondente. Os parâmetros de regresso não provam uma compra. O servidor verifica o pagamento e finaliza a tentativa vinculada ao token; um resultado desconhecido permanece bloqueado para esclarecimento, sem repetir cegamente uma nova compra. Regressos expirados ou não autorizados são erros explícitos, não permissão para criar uma encomenda de substituição.

A atribuição de um fornecedor apenas prepara o pagamento: o resumo continua editável. O bloqueio começa no redirecionamento efetivo, após os consentimentos exigidos. Um parâmetro de regresso cancel/error não prova falha: o backend verifica novamente o carrinho; Completed com order.id confirma a conclusão. Só a remoção bem-sucedida e confirmada da atribuição para esse mesmo carrinho permite repetir; resultados ambíguos ficam bloqueados e podem ser verificados novamente. Isto não garante reembolso nem exclui liquidação tardia.

Pagamento por redirecionamento: o que a sua página tem de fazer com a sessão

  • Sirva o URL de regresso configurado com o embed montado. Preserve aditus-return e aditus-pay no encaminhamento ou redirecionamentos de login necessários; aditus-pay=success nunca é autorização. Forneça uma sessão válida para a mesma identidade de confiança e publicKey. Mantenha cartScope e não apague o percurso nem inicie outro checkout para contornar um regresso pendente, expirado ou incerto.

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.

Ponto de entrada: início do evento, artigos ou voucher

O servidor decide o início ao criar a sessão, nunca o navegador. Sem eventSlug aparece a lista de eventos. Com eventSlug, startMode escolhe artigos (predefinição), início ou formulário de voucher; assortment limita os artigos.

A seleção de artigos e a continuação após um voucher seguem o percurso da loja original baseado em recomendações. Sem oferta upsell utilizável nem venda cruzada, avança-se diretamente para a personalização dos bilhetes através das verificações normais de login/sessão; com venda cruzada, mostra-se o carrinho. Uma oferta upsell utilizável abre primeiro o diálogo. Ignorar leva à personalização sem venda cruzada, ou ao carrinho quando existe. Um upgrade confirmado leva ao carrinho; fechar o diálogo mantém a seleção. O percurso nunca grava o registo em segundo plano.

Os sortidos visíveis podem ter acesso direto. Se um sortido estiver fixado, o início mostra apenas a sua ação de bilhetes, não a lista completa nem a ação de voucher.

startMode: "home" abre a página inicial do evento. Aí escolhe-se Comprar bilhetes ou Resgatar voucher; abrir a página não cria carrinho nem resgata código. Comprar bilhetes abre os artigos (limitados por assortment, se definido); Resgatar voucher abre o formulário do código. Sem eventSlug, aparece primeiro a lista de eventos e depois a página inicial do evento escolhido. Um carrinho de voucher preparado ou percurso retomável tem prioridade.

Predefinição

Início nos artigos

Apenas eventSlug: o micro-frontend abre a seleção de artigos desse evento com todos os sortidos visíveis na loja.

assortment

Apenas um sortido

assortment limita o início nos artigos a UM sortido do evento: o seu id ou nome exato (sem distinção de maiúsculas). Sortidos ocultos são permitidos, o que permite p. ex. oferecer bilhetes de imprensa gratuitos numa página própria. Um valor sem correspondência mostra um erro explícito em vez de listar tudo em silêncio.

startMode: "voucher"

Resgatar voucher / registar bilhete

O percurso abre um formulário de código em vez da lista de artigos. Um código válido reserva o bilhete. Após o resgate bem-sucedido, a configuração ADITUS do evento configuration.redirectAfterRegistration determina se o visitante permanece no ecrã de sucesso ou é encaminhado automaticamente. A continuação segue o mesmo percurso baseado em recomendações que a seleção de artigos, com as verificações normais de login/sessão antes da personalização. Voltar preserva a reserva. «Adicionar mais vouchers» reabre a introdução do código no ecrã voucher para o mesmo carrinho; «Selecionar bilhetes» abre a seleção de artigos. Um código recusado permanece no formulário com o motivo. Um bilhete totalmente coberto omite o pagamento.

Um código promocional introduzido nesse formulário (um código que desconta artigos em vez de cobrir um bilhete) também é tratado: o micro-frontend passa para a seleção de artigos com os preços que esse código produz, indica cada preço alterado (antigo e novo) e mantém o código no carrinho para que o desconto se aplique também aos artigos escolhidos. «Remover código» volta ao formulário com os preços normais.

Onde definir

Criação no servidor ou registo de cliente

As criações servidor-a-servidor enviam startMode / assortment no corpo junto ao eventSlug. As sessões criadas pelo navegador (apenas chave publicável) tomam os três valores do registo de cliente configurado pelo seu contacto ADITUS; um valor no corpo é aí ignorado. Só assortment requer eventSlug (400 start_option_requires_event_slug); startMode também funciona com a escolha do evento; valores desconhecidos dão 400 invalid_start_mode / invalid_assortment. GET /shop/session-context devolve startMode e assortment da sessão.

mint-entry.ts
// Server side: use the same session route as above, only the body varies.
// You build user and your backend's login/session route yourself.
// eventSlug sets the event. startMode also works without a slug (event
// selection first, then the mode); only assortment needs the slug
// (otherwise 400 start_option_requires_event_slug).

// 1) Article start (default): the event's article selection
const bodyArticles = { publicKey, email: user.email, eventSlug: "ExperienceDaysv52024" };

// 2) Assortment start: only one assortment (id or exact name, case-insensitive)
const bodyAssortment = {
  publicKey, email: user.email,
  eventSlug: "ExperienceDaysv52024",
  assortment: "Pressetickets",
};

// 3) Voucher start: "Redeem voucher / register ticket" first
const bodyVoucher = {
  publicKey, email: user.email,
  eventSlug: "ExperienceDaysv52024",
  startMode: "voucher" as const,
};

const res = await fetch("https://developers.aditus.com/api/shop/session", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.ADITUS_MINT_SECRET}`, "Content-Type": "application/json" },
  body: JSON.stringify(bodyVoucher),
});
const { sessionToken }: { sessionToken: string } = await res.json();
voucher-hooks.css
/* Voucher form: the same tokens and role hooks as the rest of the journey */
.aditus-shop__voucher-panel  { /* panel around hints + form (general panel hook) */ }
.aditus-shop__voucher-hints  { /* the two hint lines */ }
.aditus-shop__voucher-form   { /* form */ }
.aditus-shop__voucher-input  { /* code field (also .aditus-shop__field-input) */ }
.aditus-shop__voucher-redeem { /* "Redeem" button (data-variant="primary") */ }
.aditus-shop__field-error    { /* rejection reason, role="alert" */ }
/* Event home page and event actions: structured expert hooks */
.aditus-shop__home             { /* home page; root data-step="home" */ }
.aditus-shop__home-info        { /* event information */ }
.aditus-shop__home-info-head   { /* heading and logo */ }
.aditus-shop__home-info-name   { /* event name */ }
.aditus-shop__home-info-date   { /* date */ }
.aditus-shop__home-info-venue  { /* venue */ }
.aditus-shop__home-info-desc   { /* description */ }
.aditus-shop__home-actions[data-variant="tiles"] { /* tiles, alternatively compact */ }
.aditus-shop__home-action[data-kind="articles"] { /* buy tickets; also home / voucher / assortment */ }
.aditus-shop__home-action-icon    { /* icon */ }
.aditus-shop__home-action-body    { /* text group */ }
.aditus-shop__home-action-title   { /* label */ }
.aditus-shop__home-action-desc    { /* optional description */ }
.aditus-shop__home-action-chevron { /* arrow */ }
/* Phase hook: [data-step="voucher"] on the root */

Os vouchers são frequentemente enviados como ligações para a tua página com o código como parâmetro de consulta, p. ex. …/tickets?code=ED24-ADITUS. O micro-frontend pode assumir esse código e resgatá-lo sem que o visitante escreva nada. Existem duas vias de entrega; ambas terminam na mesma chamada de resgate e comportam-se de forma idêntica depois.

voucherCodeParam: "code"

A) No navegador: o micro-frontend lê o URL

Indica o parâmetro na configuração de mount() (voucherCodeParam) ou no elemento (voucher-code-param). Ao montar, o adaptador lê-o uma vez, remove-o da barra de endereço via history.replaceState (sem recarregar, o resto do URL mantém-se) e resgata o código. Um recarregamento, um marcador ou um URL reencaminhado nunca resgata duas vezes. Adequado a páginas estáticas e à sessão criada no navegador.

voucherCode no corpo de criação

B) No servidor: entregar o código com a sessão

O teu backend lê o parâmetro do pedido recebido e coloca-o no corpo de criação como voucherCode (eventSlug opcional). O código passa a fazer parte da sessão e chega ao micro-frontend apenas através do GET /shop/session-context autenticado, que o entrega apenas uma vez e depois o apaga; a resposta de criação devolve o código aceite para verificação. Define também voucherCodeParam para que o parâmetro desapareça igualmente da barra de endereço. Adequado quando a tua página já é renderizada no servidor ou quando queres validar, registar ou limitar tu próprio as ligações recebidas. Se ambas as vias entregarem um código, vence o do servidor.

O que acontece ao código

Mesmo comportamento em ambas as vias

O código é resgatado exatamente uma vez e apenas num percurso novo (um percurso retomado após recarregar ou regressar do pagamento ignora-o). Com startMode "voucher", o formulário de código funciona como descrito em «Resgatar voucher / registar bilhete». Com o início nos artigos, o código é aplicado ao primeiro carrinho enquanto a vista, a posição de deslocamento e as quantidades se mantêm. Continuar adiciona essas quantidades a esse mesmo carrinho. O micro-frontend nunca passa o código ao onEvent. Sem eventSlug é resgatado após o visitante escolher um evento.

voucher-link.ts
// Voucher links point to your site: https://your-site.com/tickets?code=ED24-ADITUS
// The route, request and authentication below are examples for your own website.
// The parameter name is up to you: here "code".

// Option A (browser): the micro-frontend reads the parameter itself.
import { mount } from "@aditus/shop-embed";

mount(document.getElementById("aditus-shop")!, {
  publicKey: "pk_your_site",
  sessionToken,                 // session with eventSlug (article or voucher start)
  voucherCodeParam: "code",     // read ?code=… once, remove it from the address bar, redeem it
});
// Web Component: <aditus-shop public-key="pk_your_site" voucher-code-param="code"></aditus-shop>

// Option B (server): your backend hands the code over when it creates the session.
// (Express example; the code never goes into the session response, only into the session context.)
app.get("/tickets", async (req, res) => {
  const code = typeof req.query.code === "string" ? req.query.code : undefined;
  const r = await fetch("https://developers.aditus.com/api/shop/session", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.ADITUS_MINT_SECRET}`, "Content-Type": "application/json" },
    body: JSON.stringify({
      publicKey: "pk_your_site",
      email: req.user?.email,
      eventSlug: "ExperienceDaysv52024",
      startMode: "voucher",       // or omit: article start, the code lands in the first cart
      voucherCode: code,          // undefined = normal start without a code
    }),
  });
  const { sessionToken }: { sessionToken: string } = await r.json();
  // The page additionally mounts with voucherCodeParam: "code" so that ?code= disappears
  // from the address bar (the session code wins, it is redeemed only once).
  res.render("tickets", { sessionToken });
});

Opcional: reservar o voucher durante a criação da sessão no servidor

Se pretender tratar uma recusa antes de abrir o micro-frontend, defina redeemVoucherOnMint: true no POST /api/shop/session de confiança. Ao contrário da entrega normal do código, isto cria um carrinho para o utilizador autenticado e reserva efetivamente o voucher antes da emissão da sessão; não é uma verificação adicional. Em caso de sucesso, envie apenas o sessionToken ao navegador. O micro-frontend retoma o mesmo carrinho sem o recriar ou resgatar novamente, nos modos voucher e articles. Sem a opção aplica-se a entrega normal de voucherCode.

Este modo requer voucherCode, eventSlug definido, utilizador identificado (email; não anónimo) e cabeçalho Idempotency-Key. Só está disponível com o segredo de mint no servidor, não por POST /shop/embed-session no navegador. Aceita códigos voucher/bilhete, não PromotionCode. O código fica reservado num carrinho, mas o bilhete não está registado nem emitido. Registo e finalização continuam necessários e verificações posteriores podem falhar.

prepare-voucher.ts
// Server side of your website: one voucher attempt, no additional check call.
// You implement the authentication and storage of this attempt yourself.
// attemptKey is a random id your backend stores durably per logical redemption
// attempt (e.g. crypto.randomUUID()). On network errors, resend the same id
// and the SAME body; never generate a new one before a retry.
async function prepareVoucherSession(
  user: { email: string },
  code: string,
  attemptKey: string,
) {
  const response = await fetch("https://developers.aditus.com/api/shop/session", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.ADITUS_MINT_SECRET}`,
      "Content-Type": "application/json",
      "Idempotency-Key": attemptKey,
    },
    body: JSON.stringify({
      publicKey: "pk_your_site",
      email: user.email, // only from your authenticated server session
      eventSlug: "ExperienceDaysv52024",
      startMode: "voucher", // or "articles"
      voucherCode: code,
      redeemVoucherOnMint: true,
    }),
  });
  const result: {
    sessionToken?: string; expiresAt?: number; voucherRedeemed?: boolean;
    error?: string; reason?: string; upstreamStatus?: number;
  } = await response.json();

  if (response.ok && result.sessionToken && result.voucherRedeemed === true) {
    // Only now pass it to the browser and mount the embed.
    return { sessionToken: result.sessionToken, expiresAt: result.expiresAt };
  }
  if (response.status === 422 && result.error === "voucher_rejected") {
    // Your own UI: handle the business rejection using reason, do not open the embed.
    // Translate only documented reason values into your own texts.
    return { rejected: result.reason };
  }
  if (response.status === 409 && result.error === "voucher_preparation_uncertain") {
    // Uncertain stays uncertain; a repeated POST never turns it into a success.
    // If the Shop API cannot provide the cart id, manual clarification is needed.
    throw new Error("Redemption uncertain; clarify with ADITUS, do not book again.");
  }
  if (response.status === 410 && result.error === "voucher_preparation_expired") {
    throw new Error("Prepared session no longer valid; no blind new session.");
  }
  // 400 = invalid request/key, 409 = conflict/attempt in progress,
  // 502/503 voucher_upstream_error = infrastructure, not a business error.
  throw new Error(`Session creation not completed (${response.status})`);
}

422 voucher_rejected com reason higienizado é uma rejeição de negócio: apresente-a no seu site e não inicie o micro-frontend sem token. 400 indica configuração/chave inválida; 409 idempotency_conflict indica a mesma chave com dados diferentes. Uma operação pendente/incerta, 502/503 ou um timeout não indica um voucher inválido. Guarde um Idempotency-Key aleatório por tentativa lógica no servidor e repita apenas com a mesma chave e dados idênticos; pode já existir uma reserva. Não registe códigos, emails, erros brutos ou tokens.

Uma criação de sessão bem-sucedida pode deixar um carrinho reservado mesmo sem abrir a página. A limpeza de carrinhos conhecidos após recusa/revogação é tentada sem garantia; um processo no arranque e a cada 60 segundos repete até 25 registos por ciclo. A mesma chave e os mesmos dados repetem o resultado concluído ainda válido. Se o token original expirou, foi revogado, concluído, eliminado ou transferido, devolve 410 voucher_preparation_expired: não recrie a reserva às cegas. Um sucessor autorizado pode prolongá-la/transferi-la no primeiro acesso ao carrinho. Apagar o URL não liberta a reserva.

Erros exatos: 400 invalid_redeem_voucher_on_mint / voucher_preparation_requires_user_event_code / invalid_idempotency_key (ou validação normal); 422 voucher_rejected inclui reason e upstreamStatus, nunca código ou mensagem bruta do upstream. Motivos possíveis: unknown · articleLimit · exhausted · ownership · used · reserved · limit · cartLimit · locked · canceled · otherEvent · legitimation · rejected · unsupported_code_type.

409 idempotency_conflict indica dados alterados; voucher_preparation_pending inclui Retry-After: 2 segundos, enquanto voucher_preparation_uncertain continua incerto no retry (não há sucesso automático). 410 voucher_preparation_expired impede repetir a preparação original. 502/503 voucher_upstream_error é falha upstream. Se foi criado um carrinho sem devolver o respetivo ID, a API disponível não permite reconciliação nem libertação automáticas: o estado incerto é retido e não é criado outro carrinho no retry. Contacte a ADITUS para esclarecimento manual antes de nova tentativa. Se session-context falhar, o micro-frontend apresenta erro/retry sem criar outro carrinho.

Experimente as três variantes ao vivo no Testador de integração: crie uma sessão com eventSlug, escolha startMode ou um sortido, monte e observe o registo de pedidos. O sistema de demonstração aceita o voucher de teste ED24-ADITUS em ExperienceDaysv52024 (cada resgate reserva o bilhete cerca de 20 minutos; voltar atrás liberta-o). Abrir o Testador de integração

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 desta criação de sessão entre servidores. 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 "@aditus/shop-embed";

const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount target #aditus-shop not found");

// A guest session token is enough to mount: created server-to-server as
// usual, just without the email field (no placeholder address needed). The
// visitor may browse and fill the cart before a specific user is known.
const config: AditusShopConfig = {
  apiBase: "https://developers.aditus.com/api/shop",
  publicKey: "pk_your_site",
  sessionToken: guestSessionToken,

  // Called once as soon as the visitor leaves the cart towards registration.
  // This is where you authenticate the user (login/SSO) and create a new,
  // user-bound session server-to-server.
  // Return the fresh token -> the micro-frontend adopts it and rebuilds the
  // cart under the identified user (articles are kept). Return null to
  // cancel -> the visitor stays in the cart, without an error.
  onUserRequired: async () => {
    // Calls only your backend, with no identity in the payload. Your backend reads
    // the signed-in user from its own session (cookie/JWT) and creates the session
    // server-side for exactly this identity. Never pass the email from the browser,
    // otherwise an attacker could insert someone else's address here and reach
    // that person's personal data through the prefill.
    // host.loginAndMintSession(): Promise<string | null> is your own function.
    // Implement login/SSO + authenticated backend call + server-side session creation on your website.
    // On cancel the function returns null; on failure it throws explicitly.
    return host.loginAndMintSession();
  },
};

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

A autenticação acontece no seu site

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; a API da loja 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. Nem o carrinho nem o checkout estão disponíveis. Crie uma sessão para o ativar.

1. Criar a sessão no seu back-end

server.ts
// Server side of your website. The secret key stays on the server and never reaches the browser.
// You build user and the login/session route between the browser and your server yourself; ADITUS does not provide them.
// Your backend has already authenticated the user itself (login/SSO/session).
// Important: user comes from this server session, never from the browser's
// request body. Otherwise an attacker could slip in someone else's email.
const res = await fetch("https://developers.aditus.com/api/shop/session", {
  method: "POST",
  headers: {
    // Your client's mint secret (generated in the ADITUS admin), server-side only.
    Authorization: `Bearer ${process.env.ADITUS_MINT_SECRET}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    publicKey: "pk_your_site",        // binds the session to your key (required)
    email: user.email,                 // who the micro-frontend acts as (omit = anonymous guest session)
    externalUserId: user.id,           // optional metadata (your user id)
    // sharedCartKey: visitor.stableRef, // required for anonymous shared carts (default):
    // opaque reference from your trusted server session, never browser input
    ttlSeconds: 1200,                  // optional: lifetime (default 20 min, max 24 h)
    eventSlug: "ExperienceDaysv52024", // optional: start directly in this event (otherwise the event overview)
    // startMode: "voucher",           // optional: start with the voucher/ticket code form (without eventSlug after the event selection)
    // assortment: "Pressetickets",    // optional (with eventSlug): only this assortment (id or name)
    // voucherCode: req.query.code,    // optional: redeem a code from a voucher link in the embed
    // redeemVoucherOnMint: true,      // optional: reserve it here instead (requires email, eventSlug and Idempotency-Key)
  }),
});

// Choose the response type freely and pass sessionToken to the browser (inline or via 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 "@aditus/shop-embed";

// getElementById can return null. Check it properly in TypeScript instead of using "!".
const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount target #aditus-shop not found");

// AditusShopConfig types every option and gives you autocompletion.
const config: AditusShopConfig = {
  apiBase: "https://developers.aditus.com/api/shop",
  publicKey: "pk_your_site",   // required as soon as a session is involved
  sessionToken,                // issued by your backend, keep it in memory only
  cartScope: "shared",          // default; requires a trusted server identity
};

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

// The micro-frontend sends the X-Aditus-Session header with every shop call.
// On logout, revoke the session on the server and remount/update.

3. Revogar ao sair

server.ts
// Server side, e.g. when your user logs out.
// You build the logout route between the browser and your server yourself; only your backend calls ADITUS.
await fetch(`https://developers.aditus.com/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. Trata-se de 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 "@aditus/shop-embed";

const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount target #aditus-shop not found");

const handle: ShopHandle = mount(el, {
  publicKey: "pk_your_site",
  sessionToken,
  // Called exactly once as soon as the order is placed.
  // The micro-frontend has already resolved the order for you. You get
  // the resolved order, not just an id.
  onComplete: (order: CompletedOrder) => {
    order.id;       // order GUID for further processing (server, reconciliation)
    order.number;   // order number for display
    order.status;   // order status
    order.buyer;    // buyer (name / email), if present
    order.tickets;  // [{ name, links: [{ kind: "pdf" | "apple" | "google", url }] }]

    // -> store order.id, show a confirmation, trigger DOI / newsletter ...
  },
  // Called on every unrecoverable error in the flow.
  onError: (err: Error) => {
    console.error("Shop error:", err.message);
  },
});

O que recebe

onComplete é acionado uma vez por cada checkout concluído e recebe a encomenda completa: id (o GUID da encomenda, a chave para o processamento posterior), number (o número da encomenda para apresentação), 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. Este ciclo completo não requer 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 "@aditus/shop-embed";

const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount target #aditus-shop not found");

const handle: ShopHandle = mount(el, {
  publicKey: "pk_your_site",
  sessionToken,
  // Optional. If you leave out onEvent, nothing changes: the micro-frontend
  // sends nothing, loads no gtag/GTM/GA SDK and sets no cookies.
  // Tool, consent and mapping stay entirely with you. This example uses GA4 via gtag.
  onEvent: (event: ShopAnalyticsEvent) => {
    // Consent is yours: only send once the user has agreed.
    if (!hasAnalyticsConsent()) return;
    // The events already follow the GA4 schema (name + items/value/currency ...),
    // so pass them to gtag 1:1.
    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 "@aditus/shop-embed";

const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount target #aditus-shop not found");

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

// Optional. Without on() the micro-frontend works completely normally.
// The bus is pure observation; no event is required.
// on() returns the unsubscribe function; alternatively handle.off(name, fn).
const offCart = handle.on("cart:update", (cart: ShopCartUpdate) => {
  // On every cart change: count, total, line items (GA4 item shape).
  updateMiniCartBadge(cart.itemCount); // e.g. a badge in the page header
});

handle.on("checkout:complete", ({ order }) => {
  // The order is placed. You receive the same resolved order as in onComplete.
  showConfirmationBanner(order.number);
});

handle.on("analytics", (event) => {
  // Mirror of the GA4 funnel (identical payloads to config.onEvent).
  console.debug("Funnel:", event.name);
});

// Unsubscribe selectively later; the rest stays active:
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.
phase:changeO passo visível mudou. O evento inclui phase (events · home · articles · voucher · cart · registration · legitimation · buyer · payment · summary · done, mesmo vocabulário de data-step), previous (null no passo de entrada) e startPhase, o passo de entrada da sessão. Assim a sua página controla os seus próprios botões Voltar / Recomeçar sem ler hooks CSS.
checkout:completeA encomenda é efetuada. É emitido logo após config.onComplete com o mesmo objeto de encomenda completo, antes de o Micro-Frontend mostrar a sua página de confirmação. Chame preventDefault() de forma síncrona para mostrar a sua própria página de confirmação (ver «A sua própria página de confirmação»).
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.

A sua própria página de confirmação

checkout:complete é emitido antes de o Micro-Frontend mostrar a sua própria página de confirmação. Se a sua página mostrar a sua própria confirmação, chame preventDefault() de forma síncrona no listener: event.preventDefault() no elemento <aditus-shop>, preventDefault() do payload com handle.on(), ou completion.preventDefault() do segundo argumento de config.onComplete. O Micro-Frontend deixa então de mostrar a página de confirmação, não faz scroll e não apresenta mais nenhum passo; a sua página assume o controlo. A encomenda é efetuada em qualquer caso. A chamada tem de acontecer no próprio listener, não depois de um await. Sem ela, o Micro-Frontend mostra a sua página de confirmação como antes. Isto aplica-se também ao regresso de uma página de pagamento externa.

Termine depois a sessão. A encomenda está concluída, por isso o seu servidor pode revogar a sessão no mesmo passo com DELETE /api/shop/session/:token e a sua chave secreta (ver «Revogar uma sessão»). A encomenda efetuada e as ligações dos bilhetes no objeto da encomenda continuam válidas, porque as ligações não dependem da sessão. Para uma nova compra, crie uma nova sessão e volte a montar o Micro-Frontend. O Micro-Frontend não termina a sessão por si próprio, porque a revogação requer a sua chave secreta. Mantenha a sessão apenas se a sua página continuar deliberadamente a vender nela.

Comandos: goTo e back

Além de observar, a sua página pode enviar dois pedidos limitados ao Micro Frontend. Ambos devolvem true quando a loja muda de passo e false caso contrário; nunca voltam a montar o componente nem perdem o percurso. goTo("cart") mostra o passo do carrinho para o seu próprio minicarrinho; só é aceite no passo de registo enquanto nada tiver sido gravado. back() recua um passo, exatamente como o botão Voltar da loja. Devolve false sempre que esse botão não existe ou está desativado: no passo de entrada (startPhase), na página de confirmação e enquanto decorre um pagamento ou outra alteração. Ambos existem no handle de mount e em <aditus-shop> (element.back(), element.goTo("cart")).

browser-back.ts
import type { ShopHandle } from "@aditus/shop-embed";

// Optional: the browser's back gesture goes one step back in the shop.
// The micro-frontend itself never writes to the browser history. Your page
// keeps its own history entry while the shop is past its entry step.
export function connectBrowserBack(shop: ShopHandle): () => void {
  let inside = false; // after the entry step and before the completion page
  let entry = false; // our history entry is on top
  const offPhase = shop.on("phase:change", ({ phase, startPhase }) => {
    inside = phase !== startPhase && phase !== "done";
    if (inside && !entry) {
      history.pushState({ aditusShop: true }, "");
      entry = true;
    } else if (!inside && entry) {
      entry = false;
      history.back(); // entry no longer needed
    }
  });
  const onPopState = () => {
    if (!entry) return; // normal navigation of your page
    entry = false;
    // false: the shop's back button is missing or locked (e.g. payment in progress).
    if (!shop.back() && inside) {
      history.pushState({ aditusShop: true }, "");
      entry = true;
    }
  };
  window.addEventListener("popstate", onPopState);
  return () => {
    offPhase();
    window.removeEventListener("popstate", onPopState);
  };
}

// Your own mini cart: back to the cart step, otherwise scroll to the shop.
export function showCart(shop: ShopHandle, container: HTMLElement): void {
  if (!shop.goTo("cart")) container.scrollIntoView({ behavior: "smooth" });
}

O Micro Frontend nunca escreve por si próprio no histórico do navegador: os URL e o router pertencem ao seu site. O exemplo mantém exatamente uma entrada adicional no histórico enquanto a loja está para além do passo de entrada; assim, o gesto Voltar do navegador (também o deslizar no iPhone) recua primeiro um passo na loja e só sai da página a partir do passo de entrada. Se o seu site usar um router do lado do cliente (React Router, Next.js, Vue Router), crie essa entrada através do router em vez de history.pushState, para que o router não a trate como uma navegação externa.

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?» Apenas uma opção explícita e deliberadamente insegura, definida pelo operador para um cliente específico, pode levantar esta lista; o estado ativo, a obrigatoriedade de sessão e os segredos continuam a aplicar-se.

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.
  • Não guarde o token em localStorage nem em cookies; mantenha-o em memória e renove-o criando uma nova sessão. Para recarregamentos e para o percurso de ida e volta do pagamento, o micro-frontend guarda-o no sessionStorage do separador.
  • 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 terminar sessão: um token revogado é recusado de imediato (401). Até expirar ou ser revogado, um token válido é um bearer token. Trate-o como tal.
  • Sempre emparelhe a sessão com a sua publicKey. Uma chave ausente ou incompatível é rejeitada (403).
  • Uma sessão inválida ou expirada devolve 401. Não há recurso a outro utilizador.

O que um token de sessão pode fazer

Um token de sessão é um bearer token. Este é o âmbito exato que a API ADITUS aplica. Utilize estas informações para o seu modelo de ameaças e revisão de segurança.

Token e validade
Aleatório de 256 bits, opaco, não transporta dados e não pode ser descodificado. 20 minutos por predefinição, configurável de 1 minuto a 24 horas na criação. Uma sessão nunca é prolongada. Renove-a criando uma nova.
Vinculação
Vinculada à publicKey para a qual foi criada. Cada pedido de sessão tem de enviar exatamente essa chave, e a origem do pedido é verificada contra a lista branca de domínios da chave. Outra chave ou uma origem estranha é rejeitada (403).
Sessão anónima
Criada sem utilizador: pode navegar por eventos e criar e editar o seu próprio carrinho. Registo, horários, atribuição de pagamento e checkout são recusados no servidor (403) até a sua página a substituir por uma sessão identificada.
Sessão identificada
Adicionalmente: registo, horários, atribuição de pagamento e checkout, sempre limitados aos carrinhos que esta sessão possui. Com protectUserData os dados do comprador guardados para o utilizador estão protegidos: uma escrita que os alteraria é recusada (403).
Propriedade do carrinho
Um carrinho está vinculado ao seu proprietário através de um hash com chave (HMAC): numa sessão anónima ao próprio token, numa sessão identificada à identidade de utilizador criada pelo seu back-end, por publicKey. Sessões de outros utilizadores não têm acesso a ele e as escritas num carrinho são serializadas.
Dados pessoais
O contexto de sessão devolve indicadores, identificadores e o identificador de utilizador criado pelo seu próprio back-end. O seu site já conhece toda esta informação. Os dados do comprador só são acessíveis através das rotas de carrinho e registo, com verificação de propriedade.
Limites de taxa
Valores predefinidos: criação e revogação 300 por minuto por cliente e IP, preenchimento automático de morada, empresa e cidade 60 por minuto por sessão e 3000 por minuto por cliente em todas as sessões. Cada limite é um orçamento próprio do seu cliente; outros clientes nunca o afetam. A ADITUS pode definir limites individuais por cliente a pedido.
Registo de auditoria
Os pedidos API encaminhados são registados e conservados durante 90 dias: cliente, rota normalizada, estado, host de origem, hash abreviado do token de sessão e id de carrinho ou encomenda. Nunca o próprio token, nem e-mail, nem endereço IP.
Revogação
DELETE /api/shop/session/:token invalida a sessão de imediato. Não há cache pelo meio; o pedido seguinte com esse token recebe 401. Chame-o ao terminar sessão.

Referência de endpoint e resposta

POST/api/shop/sessioncriar com segredo Bearer
DELETE/api/shop/session/:tokenrevogar – segredo Bearer

Ambas as chamadas vêm do seu back-end: autentica-se com o mint secret do seu cliente (gerado na consola de administração ADITUS) como token Bearer. Uma sessão por login é o padrão habitual; o navegador só vê o token resultante. Sites sem back-end não usam estes endpoints: o elemento cria a própria sessão de navegador através de POST /api/shop/embed-session, restrito por domínio (public-key com cart-scope="tab").

Pode experimentar este fluxo exato com as suas próprias credenciais notestador de integração. Crie a sessão, efetue o mount e percorra 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.
Idempotency-Keystring · 8–200 visible ASCII chars · required with redeemVoucherOnMintObrigatório quando redeemVoucherOnMint é true: 8–200 caracteres ASCII visíveis. Uma chave estável para uma tentativa de resgate; guarde-a no servidor e reutilize-a com exatamente o mesmo pedido nas novas tentativas. Um pedido diferente com a mesma chave devolve 409 idempotency_conflict. Não envie o segredo nem esta chave do navegador.
Campo do bodyTipoSignificado
sharedCartKeystring · opcionalAs sessões identificadas criadas entre servidores normalmente partilham através da identidade de confiança. Para carrinhos anónimos partilhados, o backend DEVE criar a sessão com sharedCartKey: uma referência opaca e estável do visitante obtida da sua própria sessão de confiança, não um email. É um campo de sessão exclusivo do servidor, não configuração do navegador nem atributo HTML. Nunca aceite emails, identidades ou sharedCartKey arbitrários do navegador. Uma sessão criada no navegador não permite partilha segura; shared sem identidade de confiança devolve um erro explícito de não suporte, mesmo se o âmbito for omitido. O isolamento explícito 'tab' não exige sharedCartKey; não há alternativa silenciosa.
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 · opcionalA sua referência estável de utilizador da sessão autenticada do servidor. Guardada com a sessão e usada para delimitar a identidade quando presente; não a altere na renovação nem a aceite de entradas arbitrárias do navegador. Não autentica por si só.
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. Emita então um novo token; com refreshSession na config, o micro-frontend pede-o automaticamente.
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; com um slug sem evento em direto (404 event_not_found) ou um evento fechado / fora de venda (409 event_not_sellable) a criação falha deliberadamente: sem sessão, sem regresso à vista geral dos eventos.
startModestring · obrigatórioVista de entrada do percurso. "articles" (predefinição) abre a seleção de artigos; "home" abre a página inicial do evento com os seus pontos de entrada (bilhetes, voucher, sortidos publicados); "voucher" abre primeiro o formulário do código voucher/bilhete. O formulário de voucher funciona como descrito em «Resgatar voucher / registar bilhete». Sem eventSlug aparece primeiro a escolha do evento e depois o modo aplica-se ao evento escolhido.
assortmentstring · obrigatórioRestringe o início nos artigos a UM sortido do evento fixado (requer eventSlug; sem efeito com startMode "voucher"). Indique o id do sortido ou o seu nome exato (sem distinção de maiúsculas); sortidos ocultos são permitidos. Um valor sem correspondência mostra um erro explícito no micro-frontend em vez de recorrer silenciosamente à lista completa. Máx. 200 carateres, caso contrário 400 invalid_assortment.
voucherCodestring · obrigatórioCódigo voucher/bilhete entregue com a sessão (ambos os modos). Por predefinição o micro-frontend resgata-o num percurso novo, após a escolha do evento se este não estiver definido; uma rejeição surge no formulário ou num aviso do primeiro carrinho. Com redeemVoucherOnMint: true, é reservado no servidor antes da resposta, para um evento definido e um utilizador identificado. 1–100 caracteres sem espaços ou delimitadores URL, caso contrário 400 invalid_voucher_code.
redeemVoucherOnMintstring · obrigatórioDefina true apenas na criação de sessão no servidor com voucherCode, eventSlug definido, email identificado e Idempotency-Key. O servidor cria um carrinho associado ao utilizador e reserva efetivamente o voucher antes de emitir a sessão. O micro-frontend usa esse carrinho sem o criar nem resgatar de novo; ambos os modos funcionam. Apenas códigos voucher/bilhete, não PromotionCode. Sem a opção aplica-se a entrega normal de voucherCode. O registo e a conclusão continuam necessários.

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.
voucherRedeemedboolean · only with redeemVoucherOnMinttrue se a criação de sessão com reserva prévia do voucher for bem-sucedido. A sessão já tem um carrinho e uma reserva que o micro-frontend reutiliza. O registo do bilhete ainda não terminou.

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.

Leituras servidor a servidor

Um backend que controla o micro-frontend (uma shell de quiosque, uma loja no local) precisa normalmente de dados do tenant antes de criar a sessão: que eventos existem e estão à venda, que idiomas a loja fala, que listas de referência e que branding se aplicam. Quatro endpoints de leitura respondem com as mesmas credenciais da criação (segredo Bearer + cabeçalho X-Aditus-Public-Key, mesmo limite de pedidos, Cache-Control: no-store). São projeções filtradas da Shop API, nunca um reencaminhamento em bruto; ?language= (ou Accept-Language, por defeito de) escolhe o idioma dos nomes. Erros: 400 invalid_public_key, 401 unauthorized, 503 shop_api_not_configured, 502 upstream_error.

EndpointDevolve
GET /api/shop/events?language=deTodos os eventos do tenant: id, slug, name, year, start/end, location, saleState (open · registration_only · closed), as flags em bruto, showInOverview, languages, URLs de ícone/logótipo. É daqui que tiras o slug para criar a sessão.
GET /api/shop/configurationDados do tenant: mandator, os nomes das listas de referência (tratamentos, países, títulos, consentimento de cookies) e websiteMetricTracking. As credenciais dos fornecedores de login nunca são incluídas, propositadamente.
GET /api/shop/languagessystemLanguage mais a lista de idiomas da loja ({ code, name }). Estes são os valores aceites pelo atributo culture do micro-frontend.
GET /api/shop/branding?images=ONSITE_SHOP_LOGO,…O tema Shop do tenant: styles como mapa plano chave → valor, as imagens do tema com URLs e (para até 40 chaves de imagem que indicares (A–Z, 0–9, _ , -)) se a chave existe, o seu URL público e o tipo de conteúdo. Os URLs de imagem não precisam de autenticação. Acrescenta &eventSlug=<slug> ou &event=<GUID> para restringir tema e chaves a um evento (overrides ao nível do evento, chaves só do evento como ONSITE_SHOP_HEADER / ONSITE_SHOP_ATTRACT_*); a resposta indica então o evento resolvido. Slug desconhecido → 404 event_not_found, não é GUID → 400 invalid_event, Shop API indisponível → 503 event_unavailable.

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

Está a trabalhar com um agente de programação? Entregue-lhe o guia de integração do Micro Frontend legível por máquina. Copie o URI canónico abaixo e cole-o no seu agente.

https://developers.aditus.com/ai/micro-frontend.md