Vai al contenuto
Micro Frontend

Integrare il ticketshop in modo nativo

Il micro-frontend mostra il ticketshop ADITUS direttamente nella tua pagina, senza iframe. I siti senza backend caricano lo script e impostano public-key insieme a cart-scope="tab". I siti con backend creano la sessione sul proprio server e passano il token di sessione; è poi il tuo server a stabilire chi è l'acquirente. Senza sessione il micro-frontend resta inattivo.

Sei nuovo? La panoramica mostra cosa fa il Micro Frontend, senza dettagli tecnici.

Avvio rapido

Ci sono due modi per avere un negozio funzionante. Entrambi usano lo stesso script e lo stesso elemento; cambia chi crea la sessione.

Senza backend

L'elemento crea da solo una sessione del browser per la tua publicKey, limitata ai domini consentiti. Imposta cart-scope="tab": senza identità server, il valore predefinito "shared" termina con un errore esplicito. Ogni scheda del browser ha quindi il proprio carrello. La sessione è anonima: i visitatori navigano e riempiono il carrello. Prima della registrazione, element.config.onUserRequired deve restituire un token di sessione legato a un utente emesso da un server; senza di esso il negozio si ferma al carrello con un avviso.

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>

Con backend

Il tuo server crea la sessione con il mint secret e decide chi è l'acquirente. Passa il token restituito all'elemento. Con l'email dell'acquirente, il valore predefinito cart-scope "shared" mantiene un unico carrello in tutte le schede.

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>

Sostituisci pk_your_site con la tua chiave pubblicabile dall'admin ADITUS. developers.aditus.com è l'indirizzo di produzione per lo script e l'API. Entrambi i modi sono descritti in dettaglio più sotto.

La tecnologia dietro il micro-frontend

Il micro-frontend mostra l'intero processo d'acquisto (eventi, articoli, carrello, registrazione, pagamento, conclusione) nel DOM della tua pagina. Usa il layout della tua pagina e il tuo CSS può dare stile a ogni sua parte.

React 19, compatibile con React 18

L'intero flusso funziona come un unico flusso React tipizzato. È compilato e testato con React 19 e usa solo le funzionalità di React 18 (useState, useEffect, useMemo e simili), quindi il pacchetto sorgente funziona con React 18 o successivo (peerDependency react >=18). Oltre a React ha una dipendenza di runtime: libphonenumber-js per la validazione dei numeri di telefono. Nessuna libreria UI, nessun framework di data fetching, nessun toolkit di stato. Lo script ospitato da ADITUS include già React, quindi la tua pagina non ha bisogno di un proprio React.

DOM leggero invece di iframe

Niente iframe, niente shadow DOM. Il negozio viene mostrato direttamente nel DOM della tua pagina, si inserisce nel tuo layout ed è responsive e accessibile. Font e colori provengono dai token --aditus-*; il font predefinito è system-ui. Imposta --aditus-font per usare il font della tua pagina.

Indipendente dal framework: un mount(), ovunque

Una sola chiamata (mount(container, config)) collega il negozio a qualsiasi elemento DOM. React, Vue, Angular o HTML semplice: l'adattatore è indipendente dal framework e funziona ovunque allo stesso modo.

Lo stile è tuo

Ogni elemento ha un hook CSS stabile (classi BEM aditus-shop__* più data-state). Lo stilizzi con il tuo CSS oppure imposti colori del brand, raggi e spaziature con alcuni design token --aditus-*. Gli stili di base opzionali si trovano in CSS @layer aditus-shop, quindi le tue regole senza layer vincono sempre. Questo vale anche per le regole generiche del tuo sito come button {}, input {} o a {} di un tema WordPress o Bootstrap: modificano anche il negozio. Limita queste regole alle tue aree della pagina se il negozio non deve adottarle.

Multilingue, commutabile in tempo reale

DE/EN sono integrati. Il cambio di lingua viene inserito direttamente nell'istanza in esecuzione tramite handle.update(), senza reimpostare il percorso dell'utente. Carrello e progressi vengono preservati.

Sicuro fin dalla progettazione

Senza una sessione valida il negozio resta inattivo e non invia richieste al negozio. La sessione proviene dal tuo server (creata con il mint secret, che non raggiunge mai il browser) oppure, nei siti senza backend, dalla sessione browser limitata al dominio del tuo publicKey. Tutte le chiamate passano per l'API ADITUS, che aggiunge lato server le credenziali del negozio.

Stile su tre livelli

Usa il livello più generale adatto: i token di design definiscono il brand, gli hook BEM semantici generali rendono coerenti i ruoli ricorrenti e gli hook specifici dei componenti esistenti sono riservati agli override per esperti.

Hook semantici generali

.aditus-shop__section, .aditus-shop__panel, .aditus-shop__list, .aditus-shop__list-item, .aditus-shop__actions e .aditus-shop__action. Questi hook additivi descrivono i ruoli, mentre ogni elemento conserva il proprio hook specializzato. L’analisi URL e screenshot dello Styling Studio propone regole esattamente a questo livello (bordo vs. ombra, peso dei pulsanti, spaziatura delle sezioni), mai su hook specifici dei componenti.

Azioni ed eccezioni

Dove implementato, data-variant="primary" o data-variant="secondary" documenta il ruolo di un'azione. Non dedurre i ruoli semantici dall'annidamento del DOM: reg-form non è un panel documentato. Usa il suo hook specializzato solo quando quel modulo richiede intenzionalmente un'eccezione.

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

Requisiti per il tuo sito

I requisiti dipendono dalla modalità di distribuzione. Lo script ospitato da ADITUS richiede solo un browser aggiornato; il pacchetto sorgente richiede inoltre React 18+ nella tua app. Entrambi richiedono una sessione per un publicKey la cui whitelist contiene il tuo dominio.

Cosa è richiesto

  • Solo per il pacchetto sorgente: React 18+ e react-dom 18+ nel bundle della tua app (peerDependency react >=18); mount() usa createRoot da react-dom/client. Lo script ospitato include il proprio React.
  • Un browser con un DOM (lato client). L'adattatore si monta in un vero elemento DOM (dom leggero). Le app SSR vanno bene finché mount() viene eseguito sul client.
  • Un browser sempreverde attuale: utilizza API Web standard (fetch, AbortController, ResizeObserver, CSS @layer), linea di base dal 2022 circa. Niente IE.
  • Una sessione per il tuo publicKey. I siti senza backend impostano public-key insieme a cart-scope="tab"; l'elemento crea poi da solo la propria sessione browser tramite /shop/embed-session, limitato al dominio. I siti con backend creano la sessione sul proprio server con il mint secret e passano il token. Senza sessione il micro-frontend resta inattivo. Il tuo dominio deve essere nella whitelist del publicKey.

Cosa non serve

  • Nessun framework particolare. mount(container, config) funziona con React, Vue, Angular o HTML semplice.
  • Nessun iframe, nessuno shadow DOM: viene visualizzato in modo nativo nella tua pagina.
  • Nessun CSS particolare sul tuo sito. Gli stili di base si trovano in @layer aditus-shop con specificità di una sola classe, quindi qualsiasi regola senza layer del tuo sito vince. Questo vale anche per regole generiche sugli elementi come button, input o a: raggiungono anche i controlli del negozio; limitale ai tuoi contenuti se non devono applicarsi lì. Box-sizing, padding e spaziature sono inoltre protetti da un reset universale come * { margin:0; padding:0 }. baseStyles: false disattiva gli stili di base.
  • Nessun'altra libreria sul tuo sito. Oltre alle peerDependencies di React, il pacchetto sorgente porta una dipendenza di runtime (libphonenumber-js per la validazione dei numeri di telefono); lo script ospitato contiene tutto.
  • Nessuna configurazione di build per lo script ospitato; il pacchetto sorgente richiede solo un import ESM.

Il tuo sito è fidato: il tuo server autentica l'utente prima di creare una sessione identificata. Il micro-frontend viene mostrato deliberatamente nel Light DOM della tua pagina, senza isolamento iframe. Gli script della tua pagina possono accedere al DOM e allo storage del browser; proteggi quindi il tuo sito e gli script di terze parti. L'autorizzazione dell'origine tramite chiave pubblica non è un'autenticazione dell'utente. I dati di pagamento vengono inseriti solo sulla pagina esterna del fornitore di pagamento.

Sicurezza dei pagamenti, PCI e CSP

La preparazione dell'acquisto viene mostrata nella tua pagina. Il pagamento avviene sulla pagina del fornitore di pagamento, quindi nessun dato di pagamento passa dalla tua pagina.

Nessun dato di pagamento nella tua pagina

La preparazione completa dell'acquisto (evento, biglietti, componenti aggiuntivi, carrello, registrazione) viene visualizzata in modo nativo nella tua pagina. Nel momento in cui l'utente avvia un pagamento di reindirizzamento, il flusso sensibile lascia completamente la tua pagina: una normale navigazione di primo livello passa alla pagina di pagamento esterna dedicata del fornitore di pagamenti, e il viaggio di ritorno riprende il viaggio e completa l'ordine. I dati della carta o della banca vengono inseriti solo lì: il micro-frontend non visualizza, trasporta o memorizza mai le credenziali di pagamento nella tua pagina.

Ambito PCI minimo

Poiché nessun dato relativo a carte di credito o banche viene mai elaborato, trasportato o archiviato nel tuo sito, il tuo ambiente rimane nell'ambito PCI-DSS minimo del modello di reindirizzamento classico (in genere SAQ A: la classificazione vincolante viene sempre effettuata dall'acquirente o dal QSA). Metodi di pagamento senza reindirizzamento, come fattura, completi lato server, senza dati di pagamento nel browser.

Content Security Policy

Se il tuo sito imposta una Content Security Policy, aggiungi: script-src per l'origine ADITUS (script ospitato), connect-src per l'origine dell'API ADITUS (ogni chiamata del negozio passa da lì), img-src per l'origine dell'API ADITUS (bandiere, loghi dei metodi di pagamento, badge wallet) e per gli host delle immagini di eventi e articoli, e style-src 'unsafe-inline', perché gli stili di base vengono inseriti come elemento <style> (non serve con baseStyles: false e un tuo foglio di stile). Non servono voci script-src o frame-src per i fornitori di pagamento, perché il passaggio di pagamento è un reindirizzamento di primo livello.

Confronto con iframe e shadow DOM

Iframe

Un iframe è un documento separato: gli stili della tua pagina non si applicano al suo interno, la sua altezza va sincronizzata e ha un proprio ordine di focus. Il micro-frontend invece mostra la preparazione dell'acquisto come HTML nel DOM della tua pagina. Solo il pagamento lascia la tua pagina, tramite un reindirizzamento di primo livello alla pagina del fornitore di pagamento.

Shadow DOM

Molti web component si incapsulano in uno shadow DOM. Gli stili della tua pagina li raggiungono allora solo tramite custom properties e ::part. Il micro-frontend usa invece la normale cascata CSS: i suoi stili di base si trovano in @layer aditus-shop, quindi ogni regola senza layer della tua pagina vince senza specificità maggiore né !important.

Gli approcci a confronto

CriterioIFrame classicoWidget Shadow-DOMMicrofrontend ADITUS
AccessibilitàDocumento separato con un proprio ordine di focus e una propria struttura.I riferimenti ARIA basati su ID (aria-labelledby, aria-describedby) non attraversano il confine dello shadow DOM.HTML nel DOM della tua pagina: un unico ordine di focus e un'unica struttura con il tuo markup.
Stile e marchioRigido: nessuna ereditarietà CSS, personalizzazione solo tramite API postMessage.Gli stili globali non arrivano all'interno; tematizzazione solo tramite proprietà e parti passate.Il tuo CSS senza layer vince sugli stili di base in @layer; questi si possono sovrascrivere o disattivare.
Pacchetto e prestazioniIn genere carica una seconda app completa, incluso il relativo runtime all'interno del frame.Spesso fornisce il proprio runtime e dipendenze duplicate.Script ospitato: un bundle che include React. Pacchetto sorgente: usa il React della tua app più una dipendenza di runtime (libphonenumber-js).
Sicurezza dei pagamenti e ambito PCIFortemente isolato, ma al prezzo di UX, design e reattività.Dipende dal widget: i campi della carta nel widget fanno parte della tua pagina.Selezione nella tua pagina, pagamento sulla pagina del fornitore di pagamento.

Il nostro consiglio: JavaScript direttamente dall’URL ADITUS

Carica il bundle JavaScript direttamente da ADITUS. È la soluzione consigliata per tutti i siti, anche con React o altri framework, non solo per siti HTML o CMS. ADITUS migliora centralmente la logica interna del negozio. I miglioramenti compatibili raggiungono la tua integrazione senza scaricare un nuovo pacchetto, ricompilare o pubblicare il tuo sito.

Vedi l’integrazione consigliata tramite script /api/embed/v1/aditus-shop.js

Come ricevi gli aggiornamenti

L’URL v1 distribuisce versioni compatibili con interfacce stabili. Le nuove versioni vengono caricate alle visite successive dopo la scadenza o la riconvalida della cache HTTP, attualmente di un’ora, non necessariamente a ogni ricaricamento. Un checkout già aperto conserva il codice caricato.

Alternativa: gestire distribuzione e aggiornamenti

Scegli il pacchetto sorgente, l’installazione del tarball tramite npm o una copia JavaScript ospitata da te solo se vuoi gestire la distribuzione. ADITUS non può aggiornare la tua copia. Devi monitorare le versioni, scaricarle o installarle, testarle, ricompilare quando necessario e pubblicarle. Il pacchetto non è pubblicato nel registro npm pubblico.

Alternativa: pacchetto sorgente (distribuzione autonoma)

In alternativa, scarica il pacchetto sorgente dalla tua istanza ADITUS e integralo nella tua app (Vite, webpack, Next.js o Nuxt). La distribuzione è a tuo carico: per ogni aggiornamento devi reinstallare, ricompilare e pubblicare. Non è su npm pubblico.

Cosa contiene la consegna

L'adattatore completo come sorgente TypeScript leggibile con dichiarazioni di tipo per ogni opzione di configurazione, callback ed evento. React e react-dom rimangono peerDependencies: la tua pagina le fornisce, quindi nulla si scontra nel tuo bundle e non esiste un secondo React.

Ricevere il pacco

Il pacchetto è identico per ogni sito e viene scaricato direttamente da questa istanza, senza credenziali richieste:aditus-shop-embed.tgz. Installa direttamente da questo URL (npm install accetta URL tarball, percorso /api/embed/v1/aditus-shop-embed.tgz) o estrai nel repository. L'onboarding fornisce la publicKey e i domini autorizzati; senza publicKey l'embed resta inattivo. Contatto: pagina dei contatti. Usa questo pacchetto solo se distribuisci e aggiorni la tua build.

Resta aggiornato: il canale v1 riceve aggiornamenti compatibili sul posto. Controlla regolarmente manifest.json (prima di ogni release della tua app, idealmente come job pianificato) e confronta package.sourceHash con aditus.sourceHash nel package.json installato. Se differiscono, reinstalla il tarball ed esegui build e test; hook, token ed eventi restano stabili all'interno di v1. Il bundle del Web Component si aggiorna da solo tramite la cache HTTP di un'ora.

Integrazione tramite script dall’URL ADITUS

Il bundle precompilato su https://developers.aditus.com/api/embed/v1/aditus-shop.js funziona su ogni sito, incluse app con build, WordPress, Typo3 e HTML semplice. Un tag script registra <aditus-shop> con React incluso: nessun npm né build propria. Questo URL pubblico e versionato non richiede credenziali; il negozio richiede comunque una publicKey autorizzata e una sessione. Un checkout in corso non viene mai sostituito a caldo; la cache del browser può ritardare il nuovo bundle fino a un'ora.

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>

Lo stesso URL JS ospitato registra automaticamente <aditus-shop> ED espone window.AditusShop: mount, themeToCss, clearJourney, mintEmbedSession e onEmbedSessionRenewed, con le firme del pacchetto sorgente. Il tuo sito non richiede React. Il mount ospitato, element.config e il pacchetto sorgente accettano la stessa AditusShopConfig, inclusi temi, eventTheme: false, articleContent e callback. Solo gli attributi HTML non trasmettono funzioni o oggetti. Il sorgente resta valido per bundler e condivisione del runtime React.

Ambito del carrello, fiducia e archiviazione

JS semplice: ciclo del token esplicito

mount(container, config) è lo stesso mount sorgente: senza sessionToken resta inattivo e non genera sessioni. Imposta apiBase esplicitamente all'URL completo /api/shop. Dopo il login asincrono, il backend genera la sessione server-to-server e passa solo il token. host.renewSession e host.revokeSession sono funzioni/route tue da implementare nel backend autenticato, non endpoint ADITUS pronti per il browser. I segreti non arrivano mai al browser. onUserRequired resta un callback asincrono di login e creazione della sessione lato server che restituisce token o null in ogni variante 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 semplice: sessione nel browser e rinnovo

Con una publicKey autorizzata, mintEmbedSession(publicKey, apiRoot = "/api", { fresh?, staleToken? }) restituisce Promise<string | null> senza segreto. Helper e attributo api-base usano la radice /api; config.apiBase usa la base shop /api/shop. onEmbedSessionRenewed(publicKey, apiRoot, listener) passa un token e restituisce una funzione di disiscrizione. L'esempio gestisce errore iniziale, scadenza, rinnovo proattivo e pulizia. La disiscrizione rimuove solo questo listener, non cache condivise, timer o sessioni server; gli helper mantengono continuità via 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>

Attributi e config JavaScript completa

element.config accetta tutta AditusShopConfig, funzioni incluse. Le chiavi definite prevalgono sugli attributi, anche false; quelle assenti/undefined mantengono gli attributi. Ogni assegnazione sostituisce tutti gli override, non è una patch: element.config = {} li rimuove. È supportata l'assegnazione prima dell'upgrade. api-base è la radice /api, config.apiBase termina con /api/shop. themeToCss(theme, selector = ".aditus-shop") restituisce CSS di token in alternativa all'oggetto 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>
AttributoSenso
cart-scopeAditusShopConfig.cartScope?: 'tab' | 'shared' usa 'shared' per impostazione predefinita, anche se omesso. 'tab' è una scelta esplicita per isolare i percorsi. window.AditusShop.mount() ospitato centralmente, element.config e mount() del pacchetto sorgente/framework accettano la stessa opzione; HTML usa cart-scope="tab" o cart-scope="shared". Scegli prima del mount. L'ambito è immutabile per l'istanza di checkout montata: handle.update() rifiuta le modifiche invece di migrare un carrello attivo. Mantienilo nelle configurazioni complete di sostituzione.
public-keyLa tua chiave pubblicabile (si applica la whitelist dei domini). Senza session-token l'elemento crea da solo la propria sessione del browser. I siti senza backend usano questa modalità e impostano anche cart-scope="tab"; con il valore predefinito "shared" la sessione del browser termina con un errore esplicito, perché manca un'identità server affidabile.
session-tokenUn token di sessione creato dal tuo backend con il mint secret (vedi sotto). Se è impostato, l'elemento non crea mai una sessione da solo; la tua pagina gestisce il ciclo di vita della sessione.
api-baseURL di base dell'API ADITUS. Il valore predefinito è l'origine da cui è stato caricato lo script del bundle: di solito non lo imposti mai.
cultureCulture/locale del negozio inoltrata per la selezione delle risorse ADITUS; i valori validi come it-IT vengono trasmessi. I testi delle risorse voucher e upsell restano come forniti da ADITUS; il resto dell'interfaccia usa risorse tedesche/inglesi con fallback. Ciò non implica un'interfaccia interamente in italiano. La modifica aggiorna la lingua senza reimpostare il percorso.
article-layout / article-group-layout / event-layout / article-select-mode / show-headerLe opzioni di presentazione flat, stessi valori della configurazione mount() (cards, grid, quantity, ...). Il punto di ingresso (selettore eventi rispetto a un evento bloccato) non è un attributo: è fisso lato server quando viene creata la sessione.
include-shop-assortmentsUnione facoltativa degli assortimenti; includeShopAssortments in mount(). Predefinito false: carica solo l'assortimento predefinito. Con true aggiunge gli assortimenti showInShop=true. Un assortimento iniziale esplicito ha sempre priorità. Senza attributo vale il default del client; false esplicito lo sostituisce.
survey-columnsColonne di opzioni di risposta (1–4) per le domande del sondaggio RadioButtonList/CheckBoxList. Corrisponde a SurveyColumns nella configurazione mount(). Unset mantiene l'impostazione predefinita a colonna singola (o l'impostazione predefinita del client memorizzata sul lato server); le finestre strette vengono compresse nuovamente in una colonna.
hide-house-numberNasconde il campo del numero civico nel blocco indirizzo dei questionari, in ogni variante di paese; corrisponde a hideHouseNumber nella configurazione mount(). Via e numero civico si inseriscono insieme in un campo «Via e numero civico» che occupa la larghezza liberata; i suggerimenti di indirizzo vi inseriscono via e numero nell'ordine abituale del paese (es. "12 Rue Cler" in Francia), un numero precompilato viene svuotato e la risposta separata del numero viene sempre inviata come spazio invisibile a larghezza zero (U+200B), senza errori di validazione, anche se il questionario la rende obbligatoria. Il modulo dell'indirizzo di fatturazione segue la stessa regola. Disattivato per impostazione predefinita; senza attributo vale il default del client, "false" esplicito lo sostituisce.
show-jump-buttonPulsante di salto nel passaggio di personalizzazione; corrisponde a showJumpButton nella config di mount(). Attivo per impostazione predefinita: dopo un invio non riuscito una piccola pillola resta in basso nel widget, mostra quante informazioni mancano e porta alla successiva; quando è tutto compilato riporta al pulsante senza mai inviarlo. È nascosta finché quel pulsante o la destinazione sono visibili e durante la digitazione. "false" la disattiva; senza attributo vale il default del client.
voucher-code-paramNome di un parametro di query dell'URL della tua pagina che contiene un codice voucher/biglietto (es. "code" per link come …/tickets?code=ED24). Corrisponde a voucherCodeParam nella configurazione mount(). L'elemento lo legge una volta al montaggio, lo rimuove dalla barra degli indirizzi e riscatta il codice automaticamente (modulo voucher o primo carrello). Solo per sessioni fissate su un evento.
base-stylesImposta su "false" per eliminare la skin predefinita neutra e modellare tu stesso ogni hook.
autoScroll / articleIconVisible / classPrefixNessun attributo: imposta queste opzioni tramite element.config (stessi nomi della configurazione mount()). autoScroll (predefinito true) riporta in vista l'inizio del negozio dopo un cambio di passaggio; imposta false se la tua pagina gestisce lo scorrimento. articleIconVisible (predefinito false) mostra un'immagine o un monogramma per articolo nella vista a elenco. classPrefix (predefinito "aditus-shop") cambia il prefisso di tutti gli hook CSS.

Sessione e autoguarigione

Un sessionToken effettivo (config o attributi) disattiva la creazione automatica della sessione. Con publicKey effettiva senza token, l'elemento genera ancora la sessione. refreshSession del tuo sito prevale sempre sul refresh integrato. Senza, le sessioni automatiche usano l'helper condiviso per refresh e rinnovo proattivo. Light DOM resta invariato: niente iframe o shadow DOM.

Eventi e versioni

I DOM CustomEvents risalgono dall'elemento con payload in event.detail: cart:update, phase:change, checkout:complete e analytics, identici a handle.on(). Sono disponibili anche i callback di config.

Ripristinare deliberatamente il viaggio

element.clearJourney() e window.AditusShop.clearJourney() rimuovono solo il puntatore locale di ripresa della pagina. Chiamali prima di reinserire o rimontare per iniziare da zero. Non eliminano carrelli server né revocano sessioni; la revoca al logout spetta al backend.

Aggiornamenti senza rimontaggio

Le config mount/update sono sostituzioni complete, non patch: conserva la config e passa { ...config, culture } a handle.update(). element.config sostituisce tutti gli override; gli attributi non coperti aggiornano in loco. La presentazione preserva il percorso; l'identità ristabilisce la sessione. handle() smonta; rimuovere l'elemento pulisce listener embed e rinnovo. Rimuovi i tuoi listener DOM se conservi l'elemento.

App SSR: Next.js e Nuxt

Il micro-frontend è volutamente solo client. Il suo contenuto è live e legato alla sessione: disponibilità, prezzi e carrello dell'utente esistono solo per una sessione autenticata al momento della richiesta, quindi non c'è nulla di significativo che il tuo server possa pre-renderizzare. mount() crea una nuova root React lato client (createRoot); Il markup rappresentato dal server all'interno del contenitore non è idratato. Ciò rende l'integrazione SSR semplice e prevedibile: esegui il rendering di un segnaposto sul server, montalo sul client e riserva lo spazio in modo che nulla salti.

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 }} />;
}

Evitare lo spostamento del layout (CLS)

Assegna al contenitore di montaggio un'altezza minima che corrisponda all'incirca alla prima visualizzazione del negozio e, facoltativamente, esegui il rendering del tuo scheletro al suo interno: server e client producono lo stesso segnaposto, quindi non vi è alcuna mancata corrispondenza di idratazione. L'adattatore esegue il rendering immediatamente dopo il montaggio e sostituisce il segnaposto in una vernice. Non tentare hydraRoot sul contenitore: non esiste un albero del negozio renderizzato dal server a cui collegarsi.

La sessione in un'app SSR

Le app SSR hanno un vantaggio naturale: il runtime del server che esegue il rendering della pagina può anche creare la sessione, da server a server con il segreto, esattamente come il flusso di creazione della sessione riportato di seguito (Next.js: gestore del percorso o componente server; Nuxt: percorso del server). Consegnare il token opaco risultante al componente client come supporto o carico utile. Il segreto non viene mai visualizzato nel codice client e il token rimane in memoria.

Specifiche di Next.js

Contrassegna il componente di montaggio "usa client" e montalo in useEffect (vedi snippet). Con il Pages Router, next/dynamic con ssr: false ottiene lo stesso risultato. Restituisce l'handle dall'effetto come pulizia in modo che le modifiche al percorso lato client vengano smontate in modo pulito. Un cambio di cultura sull'istanza in esecuzione passa attraverso handle.update() senza reimpostare il percorso; un nuovo token di sessione si rimonta (come nello snippet): anche il carrello sopravvive, perché il micro-frontend ripristina il viaggio sul monte.

Specifiche Nuxt/Vue

Racchiudi la destinazione del montaggio in <ClientOnly> o monta in onMounted: l'adattatore necessita di un vero elemento DOM nel browser. Ricorda i requisiti del bundle: react e react-dom 18+ devono essere installati come dipendenze della tua app Nuxt; il negozio viene visualizzato nel proprio contenitore e non interferisce con il DOM virtuale di Vue.

Ambito del carrello, fiducia e archiviazione

cartScope stabilisce se le schede di un visitatore condividono lo stesso carrello. Il valore predefinito è 'shared'; 'tab' separa volutamente le schede. Sceglilo prima del mount; in seguito non può più cambiare.

Aspetto'shared' (predefinito)'tab'
CarrelloUn carrello per tutte le schede con stessa origine, publicKey, identità ed eventoOgni scheda ha il proprio carrello
SessioneEmessa dal tuo backend: utente identificato, oppure anonimo con sharedCartKey dalla tua sessione server. Non prendere mai identità o chiave dal browser.Qualsiasi sessione, inclusa una sessione creata nel browser
Più schedeUna scheda modifica; le altre sono in sola lettura e offrono «Continua a modificare qui». Una scheda aperta o ricaricata subentra automaticamente, mai durante il pagamento.Indipendenti
Se manca il requisitoErrore esplicito, anche se cartScope è omesso; nessun passaggio silenzioso a 'tab'. Le pagine senza backend impostano quindi 'tab'.–
  • Configurazione: cartScope in mount() ed element.config, cart-scope in HTML. handle.update() rifiuta una modifica, quindi mantieni il valore in ogni config sostitutiva. Attributi · Campi della sessione
  • Archiviazione: Le bozze dei moduli restano per scheda in sessionStorage e scadono 2 ore dopo l'ultima modifica. I token non finiscono mai in localStorage. Ordini e registrazioni confermati non sono interessati.
  • Logout: Al logout o al cambio utente, il tuo backend revoca la vecchia sessione; poi aggiorna il token oppure smonta l'embed e chiama clearJourney(). Un nuovo utente non eredita mai il carrello del precedente.
  • Sicurezza: L'embed viene visualizzato direttamente nella tua pagina, senza iframe. Gli script della pagina, anche di terze parti, possono accedervi; includi quindi solo script affidabili. I dati di pagamento si inseriscono solo presso il fornitore di pagamento.
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();

Il micro-frontend necessita di una sessione

Nessun gettone

Nessun token di sessione: il micro-frontend rimane inattivo

Senza un token di sessione, il micro-frontend fornisce un avviso neutro e non avvia alcuna chiamata API. Nessun carrello può essere riempito, nessuna registrazione, nessun pagamento. Una sessione è obbligatoria prima dell'attivazione del negozio, ma non deve essere legata a un utente conosciuto: una sessione ospite è sufficiente per navigare e riempire il carrello, e l'utente reale può essere stabilito successivamente al momento del pagamento (vedi il flusso utente facoltativo di seguito).

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.
});
Utente del tuo sito

Token di sessione: un utente specifico

Il tuo backend emette un token di breve durata legato a un utente. Il micro-frontend agisce quindi come quell'utente: il suo carrello, i suoi dati. Il token è opaco, con scadenza, monoutente e legato alla tua chiave pubblica e il tuo segreto non raggiunge mai il browser.

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.

Al reload, un puntatore valido permette di richiedere il carrello server con lo stesso ambito autorizzato e identità fidata. Non è un'autorizzazione né una garanzia di ripristino dopo scadenza, cancellazione dello storage o cambio utente. tab non unisce i percorsi delle altre schede; shared mantiene il blocco checkout con un solo writer.

Il ritorno dal pagamento usa aditus-return (un token opaco, breve e legato al server) insieme ad aditus-pay. Può tornare nella stessa scheda o in una nuova, ma il server richiede client/origine autorizzati e identità fidata corrispondente. I parametri di ritorno non provano un acquisto. Il server verifica il pagamento e finalizza il tentativo legato al token; un esito sconosciuto resta bloccato per riconciliazione, senza ripetere alla cieca un nuovo acquisto. Ritorni scaduti o non autorizzati sono errori espliciti, non il permesso di creare un ordine sostitutivo.

Assegnare un fornitore prepara soltanto il pagamento: il riepilogo resta modificabile. Il blocco inizia al redirect effettivo, dopo i consensi richiesti. Un parametro di ritorno cancel/error non prova un fallimento: il backend ricontrolla il carrello; Completed con order.id conferma il completamento. Un nuovo tentativo richiede la rimozione riuscita e confermata dell'assegnazione per quello stesso carrello; gli esiti ambigui restano bloccati e possono essere ricontrollati. Questo non garantisce un rimborso né esclude una liquidazione tardiva.

Pagamento con reindirizzamento: cosa deve fare la tua pagina per la sessione

  • Servi l'URL di ritorno configurato con l'embed montato. Preserva aditus-return e aditus-pay nel routing o nei redirect di login necessari; aditus-pay=success non è mai un'autorizzazione. Fornisci una sessione valida per la stessa identità fidata e publicKey. Mantieni cartScope e non cancellare il percorso né avviare un secondo checkout per aggirare un ritorno pendente, scaduto o incerto.

Vuoi invece un nuovo inizio deliberato: un vero pulsante "ricomincia da capo"? Il pacchetto esporta clearJourney(): chiamalo prima del montaggio (o del rimontaggio) e il record del viaggio persistente localmente per la pagina corrente viene eliminato, quindi il montaggio successivo inizia da capo dal punto di ingresso. Viene rimosso solo il puntatore di ripristino locale; qualsiasi stato del carrello lato server rimane intatto.

Punto di ingresso: home evento, articoli o voucher

Il punto di ingresso è deciso dal server alla creazione della sessione, mai dal browser. Senza eventSlug compare la panoramica eventi. Con eventSlug, startMode sceglie gli articoli (predefinito), la home o il modulo voucher; assortment limita gli articoli.

La selezione articoli e la continuazione dopo un voucher seguono il percorso basato sui suggerimenti del negozio originale. Senza offerte upsell utilizzabili né cross-selling, si passa direttamente alla personalizzazione dei biglietti tramite i normali controlli di login/sessione; con cross-selling, si mostra il carrello. Un'offerta upsell utilizzabile apre prima il dialogo. Salta porta alla personalizzazione senza cross-selling, oppure al carrello con cross-selling. Un upgrade confermato porta al carrello; chiudere il dialogo lascia invariata la selezione. Il percorso non salva mai la registrazione in background.

Gli assortimenti visibili possono avere accessi diretti. Se ne viene fissato uno, la home mostra solo la sua azione biglietti, non l'elenco completo né l'azione voucher.

startMode: "home" apre la pagina iniziale dell'evento. Qui si sceglie Acquista biglietti o Riscatta voucher; aprirla non crea carrelli né riscatta codici. Acquista biglietti apre gli articoli (limitati da assortment, se impostato); Riscatta voucher apre il modulo del codice. Senza eventSlug compare prima la lista eventi e poi la home dell'evento scelto. Un carrello voucher preparato o un percorso ripristinabile ha la precedenza.

Predefinito

Avvio sugli articoli

Solo eventSlug: il micro-frontend apre la selezione degli articoli di quell'evento con tutti gli assortimenti visibili nello shop.

assortment

Un solo assortimento

assortment limita l'avvio sugli articoli a un assortimento dell'evento: id o nome esatto (senza distinzione tra maiuscole e minuscole). Gli assortimenti nascosti sono ammessi, così si possono offrire ad es. biglietti stampa gratuiti su una pagina dedicata. Un valore senza corrispondenza mostra un errore esplicito invece di elencare tutto in silenzio.

startMode: "voucher"

Riscatta voucher / registra biglietto

Il percorso si apre su un modulo per il codice invece dell'elenco articoli. Un codice valido riserva il biglietto. Dopo il riscatto riuscito, la configurazione ADITUS dell'evento configuration.redirectAfterRegistration determina se il visitatore resta nella schermata di successo o viene inoltrato automaticamente. Si prosegue con lo stesso percorso basato sui suggerimenti della selezione articoli, con i normali controlli di login/sessione prima della personalizzazione. Indietro conserva la prenotazione. «Aggiungi altri voucher» riapre l'inserimento del codice nella schermata voucher per lo stesso carrello; «Seleziona biglietti» apre la selezione degli articoli. Un codice rifiutato resta nel modulo con la motivazione. Un biglietto interamente coperto salta il pagamento.

Anche un codice promozionale inserito in quel modulo (un codice che sconta gli articoli invece di coprire un biglietto) viene gestito: il micro-frontend passa alla selezione articoli con i prezzi prodotti da quel codice, indica ogni prezzo modificato (vecchio e nuovo) e mantiene il codice sul carrello così che lo sconto valga anche per gli articoli scelti. «Rimuovi codice» riporta al modulo con i prezzi normali.

Dove impostarlo

Sessione lato server o record client

Le sessioni create lato server passano startMode / assortment nel body accanto a eventSlug. Le sessioni generate dal browser (solo publishable key) prendono tutti e tre i valori dal record client configurato dal tuo referente ADITUS; un valore nel body viene ignorato. Solo assortment richiede eventSlug (400 start_option_requires_event_slug); startMode funziona anche con la scelta dell'evento; valori sconosciuti danno 400 invalid_start_mode / invalid_assortment. GET /shop/session-context restituisce startMode e assortment della sessione.

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 */

I voucher vengono spesso inviati come link alla tua pagina con il codice come parametro di query, es. …/tickets?code=ED24-ADITUS. Il micro-frontend può prendere in carico quel codice e riscattarlo senza che il visitatore digiti nulla. Esistono due vie di consegna; entrambe finiscono nella stessa chiamata di riscatto e si comportano poi in modo identico.

voucherCodeParam: "code"

A) Nel browser: il micro-frontend legge l'URL

Indica il parametro nella configurazione mount() (voucherCodeParam) o sull'elemento (voucher-code-param). Al montaggio l'adapter lo legge una volta, lo rimuove dalla barra degli indirizzi tramite history.replaceState (nessun ricaricamento, il resto dell'URL resta) e riscatta il codice. Un ricaricamento, un segnalibro o un URL inoltrato non riscatta quindi mai due volte. Adatto a pagine statiche e alla sessione generata nel browser.

voucherCode nel body della sessione

B) Sul server: consegnare il codice con la sessione

Il tuo backend legge il parametro dalla richiesta in ingresso e lo inserisce nel body della sessione come voucherCode (eventSlug facoltativo). Il codice diventa parte della sessione e raggiunge il micro-frontend solo tramite la GET /shop/session-context autenticata, che lo consegna una sola volta e poi lo cancella; la risposta della sessione restituisce il codice accettato per verifica. Imposta anche voucherCodeParam così il parametro sparisce pure dalla barra degli indirizzi. Adatto quando la tua pagina è comunque renderizzata lato server o vuoi validare, registrare o limitare tu stesso i link in ingresso. Se entrambe le vie consegnano un codice, vince quello lato server.

Cosa succede al codice

Stesso comportamento su entrambe le vie

Il codice viene riscattato una sola volta e solo in un percorso nuovo (un percorso ripreso dopo ricaricamento o ritorno dal pagamento lo ignora). Con startMode "voucher", il modulo del codice funziona come descritto in «Riscatta voucher / registra biglietto». Con l'avvio sugli articoli, il codice viene applicato al primo carrello mantenendo la vista, la posizione di scorrimento e le quantità. Continua aggiunge tali quantità allo stesso carrello. Il micro-frontend non passa mai il codice a onEvent. Senza eventSlug viene riscattato dopo la scelta dell'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 });
});

Opzionale: riservare il voucher durante la creazione della sessione lato server

Se il tuo sito vuole gestire il rifiuto prima di aprire il micro-frontend, imposta redeemVoucherOnMint: true nel POST /api/shop/session fidato. A differenza della normale consegna del codice, viene creato un carrello per l'utente autenticato e il voucher vi viene riservato realmente prima di emettere la sessione: non è una richiesta di verifica aggiuntiva. Se riesce, passa al browser solo sessionToken. Il micro-frontend riprende lo stesso carrello senza crearne un altro né riscattare due volte, in modalità voucher o articles. Senza l'opzione vale la normale consegna di voucherCode.

Questa modalità richiede voucherCode, eventSlug fissato, utente identificato (email, non anonimo) e header Idempotency-Key. È disponibile solo sul backend con mint secret, non tramite POST /shop/embed-session nel browser. Supporta codici voucher/biglietto, non PromotionCode. Il codice è riservato nel carrello, ma il biglietto non è ancora registrato o emesso: servono ancora registrazione e checkout, che potrebbero essere rifiutati più avanti.

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 con reason sanificato è un rifiuto di dominio: mostralo sul tuo sito, senza montare il micro-frontend senza token. 400 indica configurazione/chiave invalida; 409 idempotency_conflict indica la stessa chiave usata con dati diversi. Un'operazione pendente/incerta, 502/503 o un timeout non sono un voucher invalido. Conserva Idempotency-Key casuale per ciascun tentativo logico sul server; ritenta solo con la stessa chiave e gli stessi dati: la prenotazione potrebbe già esistere. Non registrare codici, email, errori grezzi o token.

Una creazione di sessione riuscita può lasciare un carrello prenotato anche se la pagina non viene aperta. La pulizia dei carrelli noti dopo rifiuto/revoca è best effort; un processo all'avvio e ogni 60 secondi riprova fino a 25 record per ciclo. Stessa chiave e stessi dati restituiscono il risultato completato ancora valido. Se il token originale è scaduto, revocato, completato, cancellato o trasferito, ritorna 410 voucher_preparation_expired: non ricreare la prenotazione alla cieca. Un successore autorizzato può estendere/trasferire la preparazione al primo accesso al carrello. Cancellare l'URL non libera la prenotazione.

Errori esatti: 400 invalid_redeem_voucher_on_mint / voucher_preparation_requires_user_event_code / invalid_idempotency_key (o normali errori di validazione); 422 voucher_rejected contiene reason e upstreamStatus, mai codice o messaggio upstream grezzo. Possibili motivi: unknown · articleLimit · exhausted · ownership · used · reserved · limit · cartLimit · locked · canceled · otherEvent · legitimation · rejected · unsupported_code_type.

409 idempotency_conflict significa input diversi; voucher_preparation_pending include Retry-After: 2 secondi, mentre voucher_preparation_uncertain resta incerto anche nei retry (nessun successo automatico successivo). 410 voucher_preparation_expired impedisce di ripetere la preparazione originale. 502/503 voucher_upstream_error indica un errore upstream. Se è stato creato un carrello ma non è arrivato il suo ID, l'API disponibile non permette riconciliazione o rilascio automatico: lo stato incerto viene conservato e il server non crea un altro carrello al retry. Occorre una verifica manuale con ADITUS prima di un nuovo tentativo. Se session-context non si carica, il micro-frontend mostra un errore con retry senza creare un nuovo carrello.

Prova tutte e tre le varianti dal vivo nell'Integration Tester: genera una sessione con eventSlug, scegli startMode o un assortimento, monta e osserva il log delle richieste. Il sistema demo accetta il voucher di prova ED24-ADITUS su ExperienceDaysv52024 (ogni riscatto riserva il biglietto per circa 20 minuti; tornare indietro lo libera). Apri l'Integration Tester

Facoltativo: naviga in modo anonimo, accedi al momento del pagamento

Non è necessario sapere in anticipo chi è l'utente. Monta con una sessione ospite e il micro-frontend consente a un visitatore anonimo di sfogliare eventi, scegliere articoli e riempire il carrello. Solo quando superano il carrello, verso la registrazione e il pagamento, ti viene chiesto di stabilire l'utente reale, tramite il callback onUserRequired. Il resto del flusso prosegue poi con il carro conservato.

  1. 1

    Montare con una sessione ospite

    Per attivare il negozio è comunque necessario un token di sessione, ma può essere una sessione ospite: basta crearla senza il campo e-mail, non è necessario alcun indirizzo segnaposto. Il visitatore naviga e riempie il carrello sottostante.

  2. 2

    L'abbandono del carrello attiva l'onUserRequired

    Nel momento in cui il visitatore avanza oltre il carrello, il micro-frontend richiama una volta la tua richiamata e mostra uno stato in sospeso neutro. Autentica l'utente (login/SSO), quindi crea una nuova sessione vincolata all'utente esattamente nello stesso modo della prima: server-server con il tuo segreto. Anche l'utente successivo viene stabilito tramite la creazione della sessione da server a server: il segreto non raggiunge mai il browser e l'utente non viene mai impostato nel codice client. La tua richiamata trasmette solo il token opaco risultante. Fondamentalmente, il tuo backend deriva quell'identità dalla propria sessione autenticata (cookie/JWT); non deve mai accettare l'e-mail o l'ID utente dal browser, altrimenti un visitatore malintenzionato potrebbe richiedere la sessione di qualcun altro e leggere i suoi dati personali tramite la precompilazione.

  3. 3

    Restituisci il nuovo gettone: il carrello viene trasferito

    Restituisci il nuovo token di sessione e il micro-frontend lo scambia e lega l'utente identificato al carrello esistente: il carrello, i suoi articoli e le eventuali fasce orarie rimangono invariati. Quindi entra nella registrazione. Restituisci null per interrompere: il visitatore rimane semplicemente sul carrello, nessun errore.

  4. 4

    Omettere onUserRequired per un utente fisso

    Se si lascia fuori il callback, non cambia nulla rispetto al flusso classico: si monta una sessione associata all'utente e il micro-frontend agisce come quell'utente fin dal primo passaggio.

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

L'autenticazione avviene sul tuo sito

ADITUS non autentica i tuoi utenti finali. Il tuo sito è l'unica fonte di verità per chi è l'utente: esegui il tuo accesso, SSO o sessione. ADITUS si fida solo di un token di sessione che il tuo backend emette con una chiave segreta. La catena di fiducia è: la tua autenticazione dimostra l'identità, il tuo token emesso con il secret lo garantisce, il micro-frontend lo presenta.

  1. 1

    Il tuo sito autentica l'utente

    Accesso, SSO, sessione membro: tutto tuo. ADITUS non è coinvolto qui.

  2. 2

    Il tuo backend crea una sessione

    Server-to-server con la chiave segreta. Passi chi dovrebbe agire il micro-frontend (e-mail) e lo colleghi alla tua chiave pubblica.

  3. 3

    La pagina monta il micro-frontend

    Il token opaco viene consegnato al browser e passato come sessionToken a mount(). Il segreto rimane sul tuo server.

  4. 4

    Ogni chiamata porta con sé la sessione

    Il micro-frontend invia X-Aditus-Session ad ogni richiesta; l'API shop risolve l'utente del carrello dalla sessione.

  5. 5

    Nessun token → il micro-frontend rimane inattivo

    Senza una sessione il micro-frontend non si attiva mai: mostra un avviso neutro e non effettua chiamate: niente carrello, niente cassa. Crea una sessione per accenderlo.

1. Crea la sessione sul tuo backend

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. Montare con il gettone

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. Revoca al logout

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 }

L'ordine finito ritorna alla tua pagina

Una volta completato l'acquisto di un biglietto, non è necessaria una ricerca dell'ordine separata. Poiché il micro-frontend viene eseguito in modo nativo nella tua pagina (dom leggero, non un iframe), restituisce l'ordine finito direttamente al tuo codice tramite il callback onComplete: una chiamata JS diretta, nessun postMessage. Il micro-frontend ha già risolto l'ordine per te, quindi riceverai il numero dell'ordine e i collegamenti al biglietto, non solo un 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);
  },
});

Quello che ricevi

onComplete si attiva una volta per ogni checkout andato a buon fine con l'ordine risolto: id (il GUID dell'ordine, la tua chiave per l'elaborazione successiva), number (il numero d'ordine da mostrare), stato, acquirente (nome ed e-mail se presente) e biglietti: ogni biglietto contiene collegamenti digitati con il tipo "pdf", "apple" o "google". onError si attiva in caso di errore irreversibile nel flusso.

Metodi di pagamento reindirizzati

I metodi che rimangono sulla pagina (fattura, pagamento anticipato, carta in linea) si attivano immediatamente con Completamento. Un metodo che invia il browser a una pagina di pagamento esterna (ad esempio Saferpay) è gestito dal Micro-Frontend end-to-end: deriva gli URL di ritorno dalla pagina su cui viene eseguito, invia l'utente e sulla via del ritorno finalizza l'ordine e attiva il comando Complete: un viaggio di andata e ritorno completo senza codice dalla tua parte. Solo se il reso deve arrivare a una pagina diversa, sovrascrivi reindirizzamentoUrls.successUrl / cancelUrl / errorUrl.

GA4/eventi e-commerce (facoltativo)

Il micro-frontend può segnalare la canalizzazione dello shopping alle tue analisi, ma solo se lo chiedi. Non include gtag, GTM e GA SDK, non carica nulla, non imposta cookie e non invia nulla da solo. Aggiungi una richiamata facoltativa, onEvent, e ricevi eventi digitati già modellati nello schema di e-commerce avanzato GA4 di Google. Ometti onEvent e non cambia nulla. Quale strumento fornisci e se hai il consenso per l'invio, rimane interamente dalla tua parte.

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

Già nella forma GA4

Ogni evento porta un nome evento GA4 più i suoi parametri standard: articoli (con item_id, item_name, prezzo, quantità, item_category e, ove applicabile, sconto e coupon), valore e valuta; add_payment_info aggiunge payment_type e Purchase aggiunge Transaction_id. Destruttura il nome e passa il resto direttamente a gtag("event", name, params). Non è necessaria una nuova mappatura.

Il consenso resta tuo

onEvent è semplicemente una normale funzione JavaScript nella tua pagina che chiamiamo direttamente. Il Micro-Frontend stesso non invia analisi da nessuna parte: nulla lascia la pagina finché il tuo gestore non lo invia, quindi la gestione del consenso mantiene il pieno controllo: controlla l'inoltro del consenso, instrada al dataLayer di GTM invece di gtag, batch o elimina completamente gli eventi. Qualsiasi errore generato dal gestore viene rilevato e isolato, quindi non interrompe mai il flusso dell'utente: mantieni il gestore leggero (un push gtag/dataLayer) poiché viene eseguito in linea.

EventoSpara quando
view_item_listL'utente vede l'elenco degli articoli (si attiva una volta per ogni set mostrato).
add_to_cartUn articolo o un componente aggiuntivo viene aggiunto al carrello.
remove_from_cartUna riga del carrello o un componente aggiuntivo viene rimosso.
view_cartViene mostrata la visualizzazione del carrello (una volta per carrello).
begin_checkoutL'utente abbandona il carrello verso la registrazione/checkout.
add_payment_infoViene scelto un metodo di pagamento (trasporta payment_type).
purchaseL'ordine viene effettuato (include transaction_id, value e items).

Bus eventi: handle.on / handle.off (opzionale)

Oltre ai callback di configurazione, l'handle restituito da mount() trasporta un piccolo bus di eventi completamente opzionale. Iscriviti con handle.on(event, listener) (restituisce la funzione di annullamento dell'iscrizione corrispondente) e disconnettiti con handle.off(event, listener). Se non chiami mai on(), non cambia nulla: il bus non aggiunge dipendenze, non invia nulla e non costa nulla. È pura osservazione della tua pagina – un badge mini-carrello, un banner di conferma, la tua registrazione – e integra i callback anziché sostituirli.

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();
EventoSpara quando
cart:updateIl carrello cambia: creato, articolo aggiunto o rimosso o svuotato. Contiene cartId, itemCount, valore, valuta e le righe come elementi a forma di GA4.
phase:changeIl passo visibile è cambiato. L’evento contiene phase (events · home · articles · voucher · cart · registration · legitimation · buyer · payment · summary · done, stesso vocabolario di data-step), previous (null per il passo iniziale) e startPhase, il passo iniziale della sessione. Così la tua pagina pilota i propri pulsanti Indietro / Ricomincia senza leggere hook CSS.
checkout:completeL’ordine viene effettuato. Viene emesso subito dopo config.onComplete con lo stesso oggetto dell’ordine risolto, prima che il Micro-Frontend mostri la sua pagina di conferma. Chiama preventDefault() in modo sincrono per mostrare invece la tua pagina di conferma (vedi «La tua pagina di conferma»).
analyticsSpecchio del funnel GA4 sopra: ogni payload onEvent viene emesso anche sul bus, invariato.

Gli ascoltatori sopravvivono all'aggiornamento()

Le sottoscrizioni risiedono sull'handle, non su un rendering: handle.update({ ... }) esegue nuovamente il rendering del Micro-Frontend ma mantiene collegato ogni ascoltatore. Lo smontaggio tramite handle() scollega automaticamente tutti gli ascoltatori: è necessario solo off() (o l'annullamento dell'iscrizione restituito) quando si desidera interrompere l'ascolto mentre il negozio continua a funzionare.

Isolati, mai bloccanti

Un ascoltatore che lancia viene catturato e isolato: non interrompe mai il flusso dell'utente e non influisce mai sugli altri ascoltatori. L'evento "analytics" rispecchia la canalizzazione GA4 con payload identici a config.onEvent, quindi puoi consumare la canalizzazione tramite il bus, il callback o entrambi; si applica la stessa regola di consenso: nulla lascia la pagina finché il tuo codice non lo invia.

La tua pagina di conferma

checkout:complete viene emesso prima che il Micro-Frontend mostri la propria pagina di conferma. Se la tua pagina mostra una propria conferma, chiama preventDefault() in modo sincrono nel listener: event.preventDefault() sull’elemento <aditus-shop>, preventDefault() dal payload con handle.on() oppure completion.preventDefault() dal secondo argomento di config.onComplete. Il Micro-Frontend non mostra quindi alcuna pagina di conferma, non scorre e non visualizza altri passi; subentra la tua pagina. L’ordine viene effettuato in ogni caso. La chiamata deve avvenire nel listener stesso, non dopo un await. Senza di essa, il Micro-Frontend mostra la sua pagina di conferma come prima. Vale anche per il ritorno da una pagina di pagamento esterna.

Termina poi la sessione. L’ordine è concluso, quindi il tuo server può revocare la sessione nello stesso passaggio con DELETE /api/shop/session/:token e la tua chiave segreta (vedi «Revoca una sessione»). L’ordine effettuato e i link dei biglietti nell’oggetto dell’ordine restano validi, perché i link non dipendono dalla sessione. Per un nuovo acquisto, crea una nuova sessione e monta di nuovo il Micro-Frontend. Il Micro-Frontend non termina la sessione da solo, perché la revoca richiede la tua chiave segreta. Mantieni la sessione solo se la tua pagina continua deliberatamente a vendere in essa.

Comandi: goTo e back

Oltre a osservare, la tua pagina può inviare due richieste mirate al Micro Frontend. Entrambe restituiscono true se lo shop ha cambiato passo, altrimenti false; non rimontano mai il componente e non perdono mai il percorso. goTo("cart") mostra il passo del carrello per il tuo mini-carrello; viene accettato solo nel passo di registrazione finché non è stato scritto nulla. back() torna indietro di un passo, esattamente come il pulsante Indietro dello shop. Restituisce false ovunque quel pulsante manchi o sia disattivato: nel passo di ingresso (startPhase), nella pagina di conferma e mentre è in corso un pagamento o un'altra modifica. Entrambi sono disponibili sull'handle di mount e su <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" });
}

Il Micro Frontend non scrive mai da solo nella cronologia del browser: gli URL e il router appartengono al tuo sito. L'esempio mantiene esattamente una voce di cronologia in più finché lo shop ha superato il passo di ingresso, così il gesto Indietro del browser (anche lo swipe su iPhone) torna prima indietro di un passo nello shop ed esce dalla pagina solo dal passo di ingresso. Se il tuo sito usa un router lato client (React Router, Next.js, Vue Router), crea questa voce tramite il router invece che con history.pushState, così il router non la tratta come una navigazione estranea.

publicKey e sessionToken

Mantieni i due separati. Rispondono a domande diverse e sono accoppiati, non intercambiabili.

publicKey: fiducia nell'origine

Pubblicabile. Viene fornito nel tuo codice cliente, quindi non è un segreto e non garantisce nulla di per sé. Il backend applica una whitelist di domini per chiave: una chiave funziona solo dai suoi domini registrati. Risponde “che sito è questo?” Solo un'opzione esplicita, volutamente non sicura, impostata dall'operatore per un singolo client può sospendere questa whitelist; lo stato attivo, l'obbligo di sessione e i segreti restano comunque validi.

sessionToken: identità

Creato con il secret sul tuo backend, opaco e di breve durata. Risponde "chi è l'utente?" Una sessione è legata alla publicKey per la quale è stata creata: anche una richiesta di sessione deve inviare quella chiave, e una mancata corrispondenza viene rifiutata (403).

Regole di sicurezza

  • La chiave segreta risiede solo sul tuo server, mai nel codice client, nei bundle o nei file env inviati al browser.
  • Non salvare tu il token in localStorage o nei cookie; tienilo in memoria e rinnovalo creando una nuova sessione. Per i ricaricamenti e il percorso di andata e ritorno del pagamento, il micro-frontend lo conserva da sé nel sessionStorage della scheda.
  • I token sono di breve durata (predefinito 20 minuti, massimo 24 ore) e per utente singolo.
  • Revoca la sessione al logout: un token revocato viene rifiutato immediatamente (401). Finché non scade o viene revocato, un token valido è un bearer token. Trattalo come tale.
  • Accoppia sempre la sessione con la sua publicKey: una chiave mancante o non corrispondente viene rifiutata (403).
  • Una sessione non valida o scaduta restituisce 401. Non c'è alcun ripiego su un altro utente.

Cosa può fare un token di sessione

Un token di sessione è un bearer token. Questo è l'ambito esatto che l'API ADITUS applica. Usa queste informazioni per il tuo threat model e la revisione di sicurezza.

Token e durata
Casuale a 256 bit, opaco, non contiene dati e non può essere decodificato. 20 minuti per impostazione predefinita, configurabile da 1 minuto a 24 ore alla creazione. Una sessione non viene mai prolungata. Rinnovala creandone una nuova.
Vincolo
Vincolata alla publicKey per cui è stata creata. Ogni richiesta di sessione deve inviare esattamente quella chiave e l'origine della richiesta viene verificata rispetto alla whitelist di domini della chiave. Una chiave diversa o un'origine estranea viene rifiutata (403).
Sessione anonima
Creata senza utente: può sfogliare gli eventi e creare e modificare il proprio carrello. Registrazione, fasce orarie, assegnazione del pagamento e checkout vengono rifiutati lato server (403) finché la tua pagina non la sostituisce con una sessione identificata.
Sessione identificata
In aggiunta: registrazione, fasce orarie, assegnazione del pagamento e checkout, sempre limitati ai carrelli di cui questa sessione è proprietaria. Con protectUserData i dati acquirente memorizzati per l'utente sono protetti: una scrittura che li modificherebbe viene rifiutata (403).
Proprietà del carrello
Un carrello è legato al suo proprietario tramite un hash con chiave (HMAC): per una sessione anonima il token stesso, per una sessione identificata l'identità utente creata dal tuo backend, per publicKey. Le sessioni di altri utenti non vi hanno accesso e le scritture su un carrello sono serializzate.
Dati personali
Il contesto di sessione restituisce flag, id e l'identificativo utente creato dal tuo stesso backend. Il tuo sito conosce già tutte queste informazioni. I dati acquirente sono raggiungibili solo tramite le route carrello e registrazione, soggette al controllo di proprietà.
Limiti di frequenza
Valori predefiniti: creazione e revoca 300 al minuto per client e IP, completamento automatico di indirizzo, azienda e città 60 al minuto per sessione e 3000 al minuto per client su tutte le sessioni. Ogni limite è un budget proprio del tuo client, gli altri client non lo influenzano mai. Su richiesta ADITUS può impostare limiti individuali per client.
Log di audit
Le richieste API inoltrate vengono registrate e conservate per 90 giorni: client, route normalizzata, stato, host di origine, hash abbreviato del token di sessione e id di carrello o ordine. Mai il token stesso, nessuna e-mail, nessun indirizzo IP.
Revoca
DELETE /api/shop/session/:token invalida la sessione immediatamente. Non c'è alcuna cache intermedia; la richiesta successiva con quel token riceve un 401. Chiamalo al logout.

Riferimento all'endpoint e alla risposta

POST/api/shop/sessioncrea con il secret Bearer
DELETE/api/shop/session/:tokenrevoke with Bearer secret

Entrambe le chiamate partono dal tuo backend: si autentica con il mint secret del tuo client (generato nella console di amministrazione ADITUS) come token Bearer. Una sessione per login è lo schema normale; il browser vede solo il token risultante. I siti senza backend non usano questi endpoint: l'elemento crea da sé la propria sessione browser tramite POST /api/shop/embed-session, limitato al dominio (public-key con cart-scope="tab").

Puoi provare questo flusso esatto con le tue credenziali nel filetester di integrazione. Crea una sessione, esegui il mount e percorri il flusso.

Creare una sessione: richiesta

IntestazioneValoreSenso
AuthorizationBearer <mint-secret>Il mint secret del tuo client, generato nella console di amministrazione ADITUS. Deve restare esclusivamente lato server e non deve mai essere incluso nel browser o nel bundle dell'app.
Content-Typeapplication/jsonIl corpo è un oggetto JSON.
Idempotency-Keystring · 8–200 visible ASCII chars · required with redeemVoucherOnMintObbligatorio quando redeemVoucherOnMint è true: 8–200 caratteri ASCII visibili. Una chiave stabile per un tentativo di riscatto: conservala sul server e riutilizzala con la stessa richiesta in caso di retry. Una richiesta diversa con la stessa chiave restituisce 409 idempotency_conflict. Non inviare il segreto né la chiave dal browser.
Campo corporeoTipoSenso
sharedCartKeystring · optionalLe sessioni identificate create server-to-server condividono normalmente tramite l'identità fidata. Per carrelli anonimi condivisi, il backend DEVE creare la sessione con sharedCartKey: un riferimento visitatore opaco e stabile ricavato dalla propria sessione fidata, non un'email. È un campo di sessione solo server, non una configurazione browser né un attributo HTML. Non accettare mai email, identità o sharedCartKey arbitrari dal browser. Una sessione creata nel browser da sola non consente condivisione sicura; shared senza identità fidata restituisce un errore esplicito di mancato supporto, anche se l'ambito è omesso. L'isolamento esplicito 'tab' non richiede sharedCartKey; nessun ripiego silenzioso.
publicKeystring · requiredLa chiave pubblicabile per la quale viene creata la sessione. Deve essere la stessa chiave con cui è montato il micro-frontend: ogni chiamata successiva al negozio viene confrontata con essa (403 in caso di mancata corrispondenza). Il segreto del portatore deve appartenere al client di questa chiave.
emailstring · optionalL'utente del negozio come agisce la sessione. Prendilo dalla tua sessione del server autenticato (login/cookie SSO o JWT) - mai dalla richiesta del browser, altrimenti un visitatore potrebbe ottenere la sessione di qualcun altro. Omettilo per creare una sessione anonima: il visitatore può navigare e riempire il carrello, ma il micro-frontend blocca il passaggio oltre il carrello finché la richiamata onUserRequired non fornisce una sessione associata all'utente (vedere il flusso utente facoltativo). Un'e-mail presente ma non valida viene comunque rifiutata (400).
externalUserIdstring · optionalIl tuo riferimento utente stabile dalla sessione server autenticata. Conservato con la sessione e usato per delimitare l'identità se presente; non cambiarlo al rinnovo né accettarlo da input browser arbitrari. Non autentica da solo.
ttlSecondsnumber · optionalDurata della sessione in secondi. Valore predefinito: 20 minuti, fino a un massimo di 24 ore. Alla scadenza, le chiamate al ticketshop restituiscono 401 session_expired: emetti quindi un nuovo token; con refreshSession nella config il micro-frontend lo richiede automaticamente.
eventSlugstring · optionalFissa lato server il punto di ingresso del percorso: impostato, il micro-frontend parte direttamente dalla selezione dell'articolo di questo evento; omesso, inizia dalla panoramica degli eventi. Poiché fa parte della sessione creata, il browser non può manipolarlo. I caratteri non validi restituiscono 400 invalid_event_slug; con uno slug senza evento live (404 event_not_found) o un evento chiuso / non in vendita (409 event_not_sellable) la creazione della sessione fallisce deliberatamente: nessuna sessione, nessun ritorno alla panoramica degli eventi.
startMode"articles" Vista iniziale del percorso. "articles" (predefinito) apre la selezione degli articoli; "home" apre la pagina iniziale dell'evento con i suoi accessi (biglietti, voucher, assortimenti pubblicati); "voucher" apre prima il modulo del codice voucher/biglietto. Il modulo voucher funziona come descritto in «Riscatta voucher / registra biglietto». Senza eventSlug viene prima la scelta dell'evento, poi la modalità si applica all'evento scelto.
assortmentstring · optionalLimita l'avvio sugli articoli a un assortimento dell'evento fissato (richiede eventSlug; senza effetto con startMode "voucher"). Indica l'id dell'assortimento o il suo nome esatto (senza distinzione tra maiuscole e minuscole); gli assortimenti nascosti sono ammessi. Un valore senza corrispondenza mostra un errore esplicito nel micro-frontend invece di tornare silenziosamente all'elenco completo. Max 200 caratteri, altrimenti 400 invalid_assortment.
voucherCodestring · optionalCodice voucher/biglietto consegnato con la sessione (entrambe le modalità). Per impostazione predefinita il micro-frontend lo riscatta in un percorso nuovo, dopo la scelta dell'evento se non fissato; un rifiuto appare nel modulo o in un avviso sul primo carrello. Con redeemVoucherOnMint: true viene riservato lato server prima della risposta, per evento e utente identificati. 1–100 caratteri senza spazi o delimitatori URL, altrimenti 400 invalid_voucher_code.
redeemVoucherOnMintboolean · optional (default false)Imposta true solo per la creazione della sessione lato server con voucherCode, eventSlug fissato, email identificata e Idempotency-Key. Il server crea un carrello associato all'utente e riserva effettivamente il voucher prima di emettere la sessione. Il micro-frontend adotta quel carrello senza crearne un altro né riscattare il codice due volte; valgono entrambe le modalità di avvio. Solo codici voucher/biglietto, non PromotionCode. Senza l'opzione vale la normale consegna di voucherCode. Registrazione e checkout restano necessari.

Creare una sessione: risposta (200)

CampoTipoSenso
sessionTokenstringToken opaco (sess_…). Consegnatelo al browser e passatelo a mount() come sessionToken; il micro-frontend lo invia come X-Aditus-Session ad ogni chiamata in negozio. Non contiene dati utente e non può essere decodificato.
expiresAtnumberScadenza come timestamp Unix in millisecondi. Puramente informativo per la tua pianificazione: il micro-frontend reagisce da solo al 401.
voucherRedeemedboolean · only with redeemVoucherOnMinttrue se la creazione della sessione con voucher preparato riesce. La sessione possiede già un carrello e una prenotazione che il micro-frontend riutilizza. La registrazione del biglietto non è ancora completa.

Revoca una sessione

DELETE /api/shop/session/:token بنفس سر Bearer. يجب أن ينتمي السر إلى العميل الذي أُنشئت الجلسة له. استدعِه عند تسجيل الخروج أو إعادة ضبط الكشك حتى ينتهي الرمز مع جلستك. بالنسبة للجلسات المجهولة (المرتبطة بالرمز) يحذف الاستدعاء أيضًا سلال الجلسة في Shop API، وتُحرَّر حجوزات القسائم التي تحتفظ بها تلك السلال فورًا وليس عند انتهاء صلاحية السلة. الاستجابة: { "revoked": true, "carts": { "scope": "token", "deleted": 1, "failed": 0 } } (revoked false إذا كانت الجلسة منتهية بالفعل؛ scope "identity" للجلسات المرتبطة بمستخدم، التي تبقى سلالها بعد رمز واحد ولا تُمس). الإلغاء متكرر الأثر ويمكن إرساله دون انتظار النتيجة.

Letture server-to-server

Un backend che pilota il micro-frontend (una shell per chiosco, uno shop in loco) di solito ha bisogno di dati del tenant prima di creare una sessione: quali eventi esistono e sono in vendita, quali lingue parla lo shop, quali liste di riferimento e quale branding si applicano. Quattro endpoint di lettura rispondono con le stesse credenziali usate per creare la sessione (segreto Bearer + header X-Aditus-Public-Key, stesso rate limit, Cache-Control: no-store). Sono proiezioni filtrate della Shop API, mai un inoltro grezzo; ?language= (o Accept-Language, predefinito de) sceglie la lingua dei nomi. Errori: 400 invalid_public_key, 401 unauthorized, 503 shop_api_not_configured, 502 upstream_error.

EndpointRestituisce
GET /api/shop/events?language=deTutti gli eventi del tenant: id, slug, name, year, start/end, location, saleState (open · registration_only · closed), i flag grezzi, showInOverview, languages, URL di icona/logo. Da qui prendi lo slug per la sessione.
GET /api/shop/configurationDati del tenant: mandator, i nomi delle liste di riferimento (appellativi, paesi, titoli, consenso cookie) e websiteMetricTracking. Le credenziali dei provider di login non sono mai incluse, per scelta.
GET /api/shop/languagessystemLanguage più l'elenco delle lingue dello shop ({ code, name }). Questi sono i valori accettati dall'attributo culture del micro-frontend.
GET /api/shop/branding?images=ONSITE_SHOP_LOGO,…Il tema Shop del tenant: styles come mappa piatta chiave → valore, le immagini del tema con URL e (per fino a 40 chiavi immagine che indichi (A–Z, 0–9, _ , -)) se la chiave esiste, il suo URL pubblico e il content type. Gli URL delle immagini non richiedono autenticazione. Aggiungi &eventSlug=<slug> o &event=<GUID> per limitare tema e chiavi a un evento (override a livello evento, chiavi solo dell'evento come ONSITE_SHOP_HEADER / ONSITE_SHOP_ATTRACT_*); la risposta indica quindi l'evento risolto. Slug sconosciuto → 404 event_not_found, non GUID → 400 invalid_event, Shop API non raggiungibile → 503 event_unavailable.

Gli errori sono espliciti

StatoCodiceSenso
503session_not_configuredLe sessioni embed non sono configurate per questo client.
401unauthorizedIl mint secret utilizzato per creare la sessione non è valido o è assente.
400invalid_public_keyLa public key fornita non è valida.
400invalid_emailL'indirizzo email fornito non è valido.
400invalid_tokenIl session token fornito non è valido.
429rate_limitedTroppe richieste. Riprova tra poco.
403public_key_invalidLa public key fornita ha un formato non valido.
403public_key_unknownLa public key fornita non è riconosciuta.
403public_key_origin_unresolvedNon è stato possibile determinare l'origine della richiesta.
403public_key_domain_not_allowedIl dominio della richiesta non è autorizzato per questa public key.
401invalid_sessionLa sessione fornita non è valida.
401session_expiredLa sessione fornita è scaduta.
403session_requires_public_keyQuesta sessione richiede una public key.
403session_key_mismatchLa public key non corrisponde alla sessione.
403anonymous_sessionQuesta operazione non è disponibile per una sessione anonima.

Adesso fallo tuo

L'identità è impostata: successivamente, marchia il micro-frontend. Lo strumento di styling genera una configurazione del tema pronta da incollare e spiega ogni parametro, con un'anteprima dal vivo.

Apri lo strumento di styling

Stai lavorando con un agente di programmazione? Consegnagli la guida all’integrazione del Micro Frontend leggibile dalle macchine: copia l’URI canonico qui sotto e incollalo nel tuo agente.

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