> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aftersell.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Integrazione personalizzata

> Chiama direttamente la Strategies API per alimentare esperienze personalizzate sulla vetrina: caroselli sulle PDP, upsell in carrelli personalizzati e ovunque le superfici native di Aftersell non arrivino.

<div id="overview">
  ## Panoramica
</div>

Quando né le superfici native di Aftersell (post-acquisto, checkout, Upcart) né un'integrazione preconfezionata fanno al caso tuo, puoi chiamare tu stesso la Strategies API dal tuo tema Shopify e visualizzare i prodotti restituiti come preferisci.

Lo schema è lo stesso in ogni caso: costruisci un payload di contesto da Liquid (in modo che gli attributi Shopify come il prodotto corrente, il contenuto del carrello e i campi del cliente vengano compilati al momento del rendering), fai una `POST` a `/api/public/strategy/evaluate` e visualizzi la risposta.

Questa pagina copre due modelli di implementazione:

* **Contesto PDP** - inserisci una sezione nelle pagine prodotto che chiama l'API con il **prodotto attualmente visualizzato** e mostra un carosello di raccomandazioni restituite.
* **Contesto carrello** - visualizza un blocco di upsell all'interno di un carrello personalizzato che chiama l'API con **tutte le voci attualmente nel carrello** e mostra i prodotti restituiti.

Ciò che cambia tra i due è la forma del **contesto prodotto**: un singolo prodotto sulla PDP, un array di tutte le voci nel carrello.

***

<div id="what-youll-need">
  ## Di cosa avrai bisogno
</div>

1. **La tua Strategy API key.** In Aftersell, vai su **Settings → Product Strategy** e, nella scheda **Security Token**, copia il tuo token (questa è la tua Strategy API key).
2. **Lo Strategy ID.** Apri la Strategy che vuoi eseguire nell'editor delle Strategy di Aftersell e copia il suo ID.
3. **Accesso al codice del tema.** Dovrai aggiungere una sezione Liquid (PDP) o un blocco (carrello personalizzato) al tuo tema Shopify - Online Store → Themes → ... → Edit code.

<Warning>
  La tua Strategy API key risiede nel codice del tema lato client, il che la rende visibile a chiunque visualizzi il codice sorgente della pagina. Trattala come una credenziale pubblica della vetrina e ruotala da Aftersell **Settings → Product Strategy** se dovesse mai essere esposta in un modo non voluto.
</Warning>

***

<div id="pdp-context-section-snippet">
  ## Contesto PDP: snippet della sezione
</div>

Questo modello aggiunge una sezione Shopify alla tua pagina prodotto. Quando la pagina viene renderizzata, Liquid incorpora nel payload gli attributi del prodotto corrente, del carrello e del cliente, poi JavaScript effettua una POST alla Strategies API e mostra i prodotti restituiti in un carosello Splide.

<div id="installing">
  ### Installazione
</div>

1. Nel tuo pannello di amministrazione Shopify, vai su **Online Store → Themes**, clicca su **...** sul tuo tema e seleziona **Edit code**.
2. Nella cartella **Sections**, crea un nuovo file chiamato `aftersell-upsell-carousel.liquid`.
3. Incolla lo snippet qui sotto nel nuovo file e sostituisci `YOUR_STRATEGY_API_KEY` con l'API key di Aftersell.
4. Salva.
5. Apri il tuo template prodotto (di solito `templates/product.json` o `sections/main-product.liquid`) e aggiungi la sezione **Aftersell Carousel** nel punto in cui vuoi che appaia il carosello. Dall'editor del tema puoi anche trascinarla direttamente sulla pagina prodotto.
6. Nelle impostazioni della sezione, incolla il tuo **Strategy ID**.

<div id="what-the-section-sends">
  ### Cosa invia la sezione
</div>

Per ogni visualizzazione della PDP, il payload include:

* **products** - un array con un solo elemento contenente il prodotto attualmente visualizzato (productId, variantId, quantity, price, handle, title, vendor, productType, tags, collections, sellingPlan).
* **cart** - subtotale, numero di articoli e numero di righe del carrello corrente dell'acquirente (omesso se il carrello è vuoto).
* **cartToken** - in modo che l'API possa collegare questa valutazione alla stessa sessione.
* **customer** - tag, paese, provincia, lingua, numero di ordini, spesa totale e flag accepts-marketing, ma **solo se l'acquirente ha effettuato l'accesso**.
* **session** - codice valuta da `shop.currency`.

La sezione non invia i parametri UTM per impostazione predefinita. Se vuoi un targeting basato su UTM sulla PDP, acquisiscili lato client e aggiungili all'oggetto `session` prima della fetch.

<div id="the-snippet">
  ### Lo snippet
</div>

```liquid theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
{% comment %}
  Aftersell Carousel (Splide)
  Type: Section — save to sections/aftersell-upsell-carousel.liquid
{% endcomment %}

{% if product %}

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@splidejs/splide@4.1.4/dist/css/splide-core.min.css">
<script src="https://cdn.jsdelivr.net/npm/@splidejs/splide@4.1.4/dist/js/splide.min.js" defer></script>

<div id="aftersell-upsell-{{ section.id }}" class="aftersell-upsell-carousel" style="display:none" {{ section.shopify_attributes }}>
  <h2 class="aftersell-heading">{{ section.settings.heading | default: 'You might also like' }}</h2>
  <div class="aftersell-carousel-wrapper">
    <button class="aftersell-arrow aftersell-arrow--prev" aria-label="Previous" disabled>&#8592;</button>
    <div class="aftersell-track-container">
      <div class="splide" id="aftersell-splide-{{ section.id }}">
        <div class="splide__track">
          <ul class="splide__list">
            <li class="splide__slide"><div class="aftersell-skeleton"></div></li>
            <li class="splide__slide"><div class="aftersell-skeleton"></div></li>
            <li class="splide__slide"><div class="aftersell-skeleton"></div></li>
            <li class="splide__slide"><div class="aftersell-skeleton"></div></li>
          </ul>
        </div>
      </div>
    </div>
    <button class="aftersell-arrow aftersell-arrow--next" aria-label="Next" disabled>&#8594;</button>
  </div>
</div>

<style>
.aftersell-upsell-carousel { font-family: Modernist, sans-serif; max-width: 1300px; margin: 0 auto; padding: 24px 0 0; box-sizing: border-box; }
.aftersell-heading { font-family: Modernist, sans-serif; font-size: 30px; font-weight: 700; line-height: 45px; color: #0C0A09; margin: 0; }
.aftersell-carousel-wrapper { display: flex; align-items: center; gap: 8px; }
.aftersell-track-container { overflow: hidden; flex: 1; min-width: 0; }
.aftersell-card { display: flex; flex-direction: column; box-sizing: border-box; height: 100%; }
.aftersell-card-link { text-decoration: none; color: inherit; display: block; flex: 1; }
.aftersell-card-image { aspect-ratio: 1; overflow: hidden; background: #f5f5f5; border-radius: 8px 8px 0 0; position: relative; }
.aftersell-card-image img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform 0.3s ease; }
.aftersell-card-image:hover img { transform: scale(1.04); }
.aftersell-card-badge { position: absolute; top: 10px; left: 8px; background: #c60006; color: #fff; font-size: 11px; font-weight: 500; padding: 4px 8px; border-radius: 16px; z-index: 1; }
.aftersell-card-body { padding: 6px 0 0; display: flex; flex-direction: column; }
.aftersell-card-vendor { font-size: 13px; font-weight: 550; text-transform: uppercase; color: #1d4481; margin: 0; }
.aftersell-card-title { font-size: 13px; font-weight: 700; color: #0C0A09; margin: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.aftersell-card-price { font-size: 13px; color: #0C0A09; margin: 0; display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.aftersell-card-price--compare { text-decoration: line-through; color: #595959; }
.aftersell-card-price--sale { font-weight: 700; color: #c60006; }
.aftersell-card-actions { padding: 6px 0 0; display: flex; flex-direction: column; margin-top: auto; }
.aftersell-variant-select { width: 100%; font-size: 13px; padding: 0.35rem 0.5rem; border: 1px solid #DBDBDB; border-radius: 4px; background: #fff; color: #0C0A09; cursor: pointer; }
.aftersell-cta { padding: 16px 12px; background: #c50007; color: #FFFFFF; border: none; border-radius: 5px; font-size: 16px; font-weight: 700; cursor: pointer; width: 100%; display: flex; align-items: center; justify-content: center; transition: background 0.2s, opacity 0.2s; }
.aftersell-cta:hover:not(:disabled) { opacity: 0.85; }
.aftersell-cta:disabled { opacity: 0.5; cursor: default; }
.aftersell-cta--added { background: #1d4481; }
.aftersell-cta--unavailable { background: #999; cursor: default; }
.aftersell-arrow { width: 30px; height: 90px; background: rgba(255,255,255,0.6); border: 1px solid #DBDBDB; border-radius: 5px; cursor: pointer; display: flex; align-items: center; justify-content: center; flex-shrink: 0; padding: 0; }
.aftersell-arrow:disabled { opacity: 0.3; cursor: default; }
.aftersell-skeleton { border-radius: 8px; background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); background-size: 200% 100%; animation: aftersell-shimmer 1.4s infinite; aspect-ratio: 0.75; width: 100%; }
@keyframes aftersell-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }

#aftersell-splide-{{ section.id }} .splide__arrows,
#aftersell-splide-{{ section.id }} .splide__pagination { display: none !important; }

@media (max-width: 768px) {
  .aftersell-upsell-carousel { padding: 16px 16px 0; }
  .aftersell-arrow { display: none; }
  .aftersell-carousel-wrapper { gap: 0; }
}
</style>

<script>
(function() {
  var BACKEND_URL  = 'https://start.aftersell.app';
  var API_KEY      = 'YOUR_STRATEGY_API_KEY';
  var STRATEGY_ID  = {{ section.settings.strategy_id | json }};
  var SHOP_DOMAIN  = {{ shop.permanent_domain | json }};
  var CTA_LABEL    = '{{ section.settings.cta_label | default: "Add to cart" }}';
  var MAX_PRODUCTS = {{ section.settings.max_products | default: 8 }};
  var CURRENCY     = {{ shop.currency | default: "USD" | json }};
  var SECTION_ID   = {{ section.id | json }};
  if (!SHOP_DOMAIN || !STRATEGY_ID) return;

  var fmt;
  try {
    fmt = new Intl.NumberFormat('en-US', { style: 'currency', currency: CURRENCY });
  } catch(e) {
    fmt = { format: function(n) { return '$' + parseFloat(n).toFixed(2); } };
  }
  function money(str) { return fmt.format(parseFloat(str) || 0); }

  function gidToNumeric(gid) {
    return gid ? String(gid).split('/').pop() : null;
  }

  function priceHtml(price, compareAt) {
    var hasSale = compareAt && parseFloat(compareAt) > parseFloat(price);
    return hasSale
      ? '<span class="aftersell-card-price--compare">' + money(compareAt) + '</span>'
        + '<span class="aftersell-card-price--sale">' + money(price) + '</span>'
      : '<span class="aftersell-card-price--current">' + money(price) + '</span>';
  }

  var productContext = {
    productId:   'gid://shopify/Product/{{ product.id }}',
    variantId:   'gid://shopify/ProductVariant/{{ product.selected_or_first_available_variant.id }}',
    quantity:    1,
    price:       {{ product.price | divided_by: 100.0 }},
    handle:      {{ product.handle | json }},
    title:       {{ product.title | json }},
    vendor:      {{ product.vendor | json }},
    productType: {{ product.type | json }},
    tags:        {{ product.tags | json }},
    collections: [{% for col in product.collections %}'gid://shopify/Collection/{{ col.id }}'{% unless forloop.last %},{% endunless %}{% endfor %}],
    sellingPlan: {% if product.selected_selling_plan %}'subscription'{% else %}'one-time'{% endif %}
  };

  var cartContext = {
    subtotal:  {{ cart.total_price | divided_by: 100.0 }},
    itemCount: {{ cart.item_count }},
    lineCount: {{ cart.items.size }}
  };

  {% if customer %}
  var customerContext = {
    customerId:       'gid://shopify/Customer/{{ customer.id }}',
    tags:             {{ customer.tags | json }},
    {% if customer.default_address.country_code %}countryCode: {{ customer.default_address.country_code | json }},{% endif %}
    {% if customer.default_address.province_code %}provinceCode: {{ customer.default_address.province_code | json }},{% endif %}
    locale:           {{ request.locale.iso_code | json }},
    orderCount:       {{ customer.orders_count }},
    totalSpent:       {{ customer.total_spent | times: 1.0 }},
    acceptsMarketing: {{ customer.accepts_marketing }}
  };
  {% endif %}

  var blockEl  = document.getElementById('aftersell-upsell-' + SECTION_ID);
  var splideEl = document.getElementById('aftersell-splide-' + SECTION_ID);
  var list     = splideEl.querySelector('.splide__list');
  var prevBtn  = blockEl.querySelector('.aftersell-arrow--prev');
  var nextBtn  = blockEl.querySelector('.aftersell-arrow--next');
  var splideInstance = null;

  function getCartToken() {
    var t = {{ cart.token | json }};
    if (t) return Promise.resolve(t);
    return fetch('/cart.js').then(function(r){ return r.json(); }).then(function(c){ return c.token || null; }).catch(function(){ return null; });
  }

  function evaluate() {
    getCartToken().then(function(cartToken) {
      if (!cartToken) return;
      return fetch(BACKEND_URL + '/api/public/strategy/evaluate', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'X-Strategy-Api-Key': API_KEY },
        body: JSON.stringify({
          shopDomain:  SHOP_DOMAIN,
          strategyId:  STRATEGY_ID,
          context: {
            products:  [productContext],
            cartToken: cartToken,
            cart:      cartContext.itemCount > 0 ? cartContext : undefined,
            {% if customer %}customer: customerContext,{% endif %}
            session:   { currencyCode: CURRENCY }
          }
        })
      });
    }).then(function(res) { if (!res) return; return res.json(); })
    .then(function(data) {
      if (!data || !data.success || !data.products || !data.products.length) {
        clearSkeletons(); return;
      }
      renderCards(data.products.slice(0, MAX_PRODUCTS));
    }).catch(function(err) {
      console.error('[AfterSell carousel] Error:', err);
      clearSkeletons();
    });
  }

  function renderCards(products) {
    if (!products || !products.length) {
      blockEl.style.display = 'none'; return;
    }

    blockEl.style.display = '';

    list.innerHTML = products.map(function(p, i) {
      var img      = p.images && p.images[0] ? p.images[0].src : '';
      var alt      = (p.images && p.images[0] && p.images[0].altText) || p.title;
      var variants = p.variants || [];
      var availableVariants = variants.filter(function(v) {
        return v.availableForSale !== false;
      });
      var isDefaultVariant = variants.length === 1 && variants[0].title === 'Default Title';
      var showSelect       = !isDefaultVariant && availableVariants.length >= 1;
      var firstVariant     = availableVariants.length ? availableVariants[0] : null;
      var firstVariantId   = firstVariant ? gidToNumeric(firstVariant.variantId) : null;
      var anyAvailable     = availableVariants.length > 0;

      var onSale = p.compareAtPrice && parseFloat(p.compareAtPrice) > parseFloat(p.price);
      var badge  = '';
      if (onSale) {
        var pct = Math.round((1 - parseFloat(p.price) / parseFloat(p.compareAtPrice)) * 100);
        badge = '<span class="aftersell-card-badge">Save ' + pct + '%</span>';
      }
      var vendor = p.vendor ? '<p class="aftersell-card-vendor">' + p.vendor + '</p>' : '';

      var selectHtml = '';
      if (showSelect) {
        var options = availableVariants.map(function(v) {
          return '<option value="' + gidToNumeric(v.variantId) + '"'
            + ' data-price="'   + (v.price         || p.price) + '"'
            + ' data-compare="' + (v.compareAtPrice || '')     + '"'
            + '>' + v.title + '</option>';
        }).join('');
        selectHtml = '<select class="aftersell-variant-select" aria-label="Select variant">' + options + '</select>';
      }

      var atcLabel = anyAvailable ? CTA_LABEL : 'Sold Out';
      var atcClass = anyAvailable ? 'aftersell-cta' : 'aftersell-cta aftersell-cta--unavailable';
      var atcBtn   = '<button class="' + atcClass + '"'
        + (firstVariantId ? ' data-variant-id="' + firstVariantId + '"' : '')
        + (anyAvailable ? '' : ' disabled')
        + '>' + atcLabel + '</button>';

      var productUrl = p.url + (p.url.indexOf('?') > -1 ? '&' : '?') + 'ref=aftersell';

      return '<li class="splide__slide">'
        + '<div class="aftersell-card" data-idx="' + i + '">'
        + '<a class="aftersell-card-link" href="' + productUrl + '">'
        + '<div class="aftersell-card-image">' + badge
        + (img ? '<img src="' + img + '" alt="' + alt + '" loading="lazy">' : '')
        + '</div>'
        + '<div class="aftersell-card-body">'
        + vendor
        + '<p class="aftersell-card-title">' + p.title + '</p>'
        + '<p class="aftersell-card-price" data-price-el>' + priceHtml(p.price, p.compareAtPrice) + '</p>'
        + '</div></a>'
        + '<div class="aftersell-card-actions">'
        + selectHtml
        + atcBtn
        + '</div></div></li>';
    }).join('');

    attachCardEvents();
    initSplide();
  }

  function attachCardEvents() {
    list.addEventListener('change', function(e) {
      if (!e.target.classList.contains('aftersell-variant-select')) return;
      var select  = e.target;
      var card    = select.closest('.aftersell-card');
      var opt     = select.options[select.selectedIndex];
      var priceEl = card.querySelector('[data-price-el]');
      var btn     = card.querySelector('.aftersell-cta');
      priceEl.innerHTML     = priceHtml(opt.getAttribute('data-price'), opt.getAttribute('data-compare'));
      btn.dataset.variantId = opt.value;
      btn.disabled          = false;
      btn.textContent       = CTA_LABEL;
      btn.className         = 'aftersell-cta';
    });

    list.addEventListener('click', function(e) {
      var btn = e.target.closest('.aftersell-cta');
      if (!btn || btn.disabled) return;
      var variantId = btn.dataset.variantId;
      if (!variantId) return;

      e.preventDefault();
      btn.disabled    = true;
      btn.textContent = 'Adding…';

      fetch('/cart/add.js', {
        method: 'POST',
        credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          id: parseInt(variantId, 10),
          quantity: 1,
          properties: {
            "_source": "Aftersell",
            "_attribution": "CTA"
          }
        })
      })
      .then(function(r) {
        if (!r.ok) throw new Error('Cart add failed: ' + r.status);
        return r.json();
      })
      .then(function() {
        btn.textContent = 'Added!';
        btn.classList.add('aftersell-cta--added');
        if (typeof window.theme !== 'undefined' && window.theme.cart && window.theme.cart.open) {
          window.theme.cart.open();
        }
        setTimeout(function() {
          btn.textContent = CTA_LABEL;
          btn.classList.remove('aftersell-cta--added');
          btn.disabled = false;
        }, 2500);
      })
      .catch(function(err) {
        console.error('[AfterSell carousel] Add to cart error:', err);
        btn.textContent = 'Try Again';
        btn.disabled    = false;
      });
    });
  }

  function initSplide() {
    function mount() {
      if (typeof window.Splide === 'undefined') {
        setTimeout(mount, 50);
        return;
      }

      splideInstance = new Splide('#aftersell-splide-' + SECTION_ID, {
        type:       'slide',
        perPage:    4,
        perMove:    4,
        gap:        '10px',
        pagination: false,
        arrows:     false,
        speed:      350,
        drag:       false,
        breakpoints: {
          768: { perPage: 2, perMove: 2, drag: 'free', snap: true },
          480: { perPage: 1, perMove: 1, drag: 'free', snap: true, padding: { right: '25%' } }
        }
      }).mount();

      function updateArrows() {
        var idx = splideInstance.index;
        var end = splideInstance.length - splideInstance.options.perPage;
        prevBtn.disabled = idx <= 0;
        nextBtn.disabled = idx >= end;
      }

      prevBtn.addEventListener('click', function() { splideInstance.go('<'); });
      nextBtn.addEventListener('click', function() { splideInstance.go('>'); });
      splideInstance.on('moved', updateArrows);
      updateArrows();
    }
    mount();
  }

  function clearSkeletons() { list.innerHTML = ''; }

  evaluate();
})();
</script>

{% endif %}

{% schema %}
{
  "name": "Aftersell Carousel",
  "settings": [
    { "type": "text",  "id": "strategy_id",  "label": "AfterSell Strategy ID", "default": "ADD_ID_HERE" },
    { "type": "text",  "id": "heading",      "label": "Heading",               "default": "You might also like" },
    { "type": "text",  "id": "cta_label",    "label": "CTA Button Label",      "default": "Add to cart" },
    { "type": "range", "id": "max_products", "label": "Max Products to Show",  "default": 8, "min": 1, "max": 20, "step": 1 }
  ],
  "presets": [{ "name": "Aftersell Carousel" }]
}
{% endschema %}
```

<Frame>
  <img src="https://mintcdn.com/aftersell/SnVX3h-PpMMxQMDU/images/aftersell/strategy-carousel-example.png?fit=max&auto=format&n=SnVX3h-PpMMxQMDU&q=85&s=8fb7ac08c2ae0c430b5bc4f810c59e40" alt="Un carosello di prodotti alimentato da una Strategy visualizzato su una pagina prodotto Shopify" width="2692" height="1146" data-path="images/aftersell/strategy-carousel-example.png" />
</Frame>

<div id="customizing">
  ### Personalizzazione
</div>

Lo schema della sezione espone quattro impostazioni modificabili dal merchant: **Strategy ID**, **Heading**, **CTA Button Label** e **Max Products to Show**. Aggiungi o rimuovi impostazioni nel blocco `{% schema %}` per esporre più controlli nell'editor del tema.

Il CSS è delimitato sotto nomi di classe `.aftersell-*` e include un carosello Splide a 4 elementi che passa a 2 elementi a 768px e a 1 elemento a 480px. Modificalo liberamente per adattarlo al tuo tema: nulla di tutto ciò è necessario per il funzionamento della chiamata API.

***

<div id="cart-context-custom-cart-upsell-block">
  ## Contesto carrello: blocco di upsell in un carrello personalizzato
</div>

Questo modello è strutturalmente identico a quello della PDP, con una differenza chiave: l'**array del contesto prodotto** viene costruito dalle voci del carrello invece che dal prodotto attualmente visualizzato. La Strategy riceve quindi ogni articolo che l'acquirente ha aggiunto e restituisce raccomandazioni basate sul carrello nel suo insieme.

L'implementazione vive ovunque risieda il codice del tuo carrello personalizzato: una sezione Liquid che renderizza il cart drawer, un blocco personalizzato in una vetrina headless o un template del tema come `cart.liquid`. La forma della chiamata API e la gestione della risposta sono identiche all'esempio della PDP: cambia solo l'array `products`.

La struttura è questa:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
var products = {{ cart.items | json }}.map(function(item) {
  return {
    productId: 'gid://shopify/Product/' + item.product_id,
    variantId: 'gid://shopify/ProductVariant/' + item.variant_id,
    quantity:  item.quantity,
    price:     item.price / 100
    // ...other fields as needed
  };
});
```

Il resto del payload (cart, customer, session, cartToken) e la chiamata `fetch` a `/api/public/strategy/evaluate` rimangono invariati rispetto al modello PDP qui sopra: solo l'array `products` passa da `[productContext]` all'array derivato dal carrello.

***

<div id="what-happens-when-the-strategy-returns">
  ## Cosa succede quando la Strategy restituisce un risultato
</div>

La forma della risposta è la stessa indipendentemente dal contesto che hai inviato:

```json theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
{
  "success": true,
  "products": [ /* enriched recommended products */ ],
  "evaluationId": "f3a1c2e0-...",
  "resolution": { "strategyId": "...", "matchedRuleIds": [...], "fallbackUsed": false },
  "meta": { "servedFromCache": false, "processingTimeMs": 12, "data": {} }
}
```

L'`evaluationId` è un id univoco per questa valutazione. Se lo acquisisci e lo alleghi ai prodotti che visualizzi, puoi attribuire l'ordine risultante esattamente alla raccomandazione che lo ha generato - vedi [Attribuzione](#attribution) qui sotto.

Il modo in cui visualizzi l'array `products` dipende interamente dal codice del tuo tema. Lo snippet PDP qui sopra li mostra come un carosello di schede con selettori di varianti e pulsanti di aggiunta al carrello; un blocco di carrello personalizzato potrebbe visualizzarli come un elenco verticale all'interno del drawer.

Per lo schema completo di richiesta e risposta, consulta il [riferimento API Evaluate Strategy](/it/aftersell/strategies_api_reference_evaluate_strategy).

***

<div id="when-no-product-is-returned">
  ## Quando nessun prodotto viene restituito
</div>

Se la Strategy non restituisce alcun prodotto (`products: []`), spetta al tuo codice decidere come gestirlo. Lo snippet PDP qui sopra nasconde completamente il carosello. Un blocco di carrello personalizzato potrebbe ripiegare sull'elenco di upsell predefinito del carrello, oppure semplicemente non mostrare nulla.

Per evitare una risposta vuota, configura un **Catch all** nella Strategy in modo che ci sia sempre un prodotto di riserva da restituire. Consulta la pagina [Costruire le Strategy](/it/aftersell/strategies_building_in_app) per scoprire come impostare un Catch all.

***

<div id="tips-for-custom-integrations">
  ## Suggerimenti per le integrazioni personalizzate
</div>

* **Costruisci il contesto in Liquid.** Liquid viene eseguito al momento del rendering e ha accesso all'intero grafo degli oggetti Shopify: prodotto, carrello, cliente, negozio, richiesta. Usalo per popolare il payload lato server invece di ricorrere a chiamate lato client.
* **Tieni l'API key fuori dai repository pubblici.** Finirà nel codice del tuo tema, che viene consegnato al browser - e questo va bene. Ma non incollare lo stesso tema in un repository pubblico né condividere il bundle esternamente.
* **Usa un Catch all.** Le esperienze in vetrina sembrano rotte quando uno slot scompare. Un Catch all con un piccolo set di prodotti sicuri di default mantiene l'interfaccia coerente.
* **Metti in cache dove ha senso.** La Strategies API fa un leggero caching lato server (`meta.servedFromCache`), ma per le PDP ad alto traffico potresti anche voler applicare debounce o memoizzazione alle chiamate lato client (es. non richiamare quando lo stesso prodotto viene renderizzato due volte in una sessione).

***

<div id="attribution">
  ## Attribuzione
</div>

Quando un acquirente clicca sul pulsante di aggiunta al carrello nello snippet, la chiamata `/cart/add.js` allega delle **line item properties** all'articolo del carrello:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
properties: {
  "_source": "Aftersell",
  "_attribution": "CTA"
}
```

Queste proprietà viaggiano con la voce del carrello fino all'ordine Shopify, dove compaiono nel record della voce. Puoi usarle a valle per attribuire i ricavi, filtrare gli ordini o alimentare strumenti di analytics che leggono le line item properties.

Le chiavi e i valori sono convenzioni, non requisiti: la chiamata API funziona allo stesso modo indipendentemente da cosa inserisci qui. Modificale per adattarle al tuo modello di attribuzione. Per esempio:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
properties: {
  "_source": "PDP Carousel",
  "_strategy_id": "{{ section.settings.strategy_id }}",
  "_campaign": "summer-2026"
}
```

<Tip>
  Le chiavi di proprietà che iniziano con un trattino basso (`_`) sono nascoste nell'interfaccia del carrello e del checkout ma restano comunque allegate all'ordine. Usa il prefisso con trattino basso per i metadati di sola attribuzione che non vuoi far vedere agli acquirenti.
</Tip>

Applica lo stesso schema nell'implementazione con contesto carrello: qualsiasi chiamata di aggiunta al carrello effettuata da un blocco di upsell personalizzato può portare con sé tutte le proprietà di cui hai bisogno.

<div id="attributing-back-to-the-evaluation">
  ### Attribuire alla valutazione
</div>

Per collegare un ordine alla **valutazione esatta** che ha raccomandato il prodotto - anziché solo a "proviene da una Strategy" - acquisisci l'`evaluationId` dalla risposta e allegalo alla voce del carrello sotto la proprietà `__as_offer_id`. AfterSell legge questa chiave, quindi gli ordini contrassegnati con essa vengono attribuiti alla valutazione specifica nella reportistica.

Nell'handler `evaluate()`, conserva l'id dalla risposta:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
.then(function(data) {
  if (!data || !data.success || !data.products || !data.products.length) {
    clearSkeletons(); return;
  }
  evaluationId = data.evaluationId; // declare `var evaluationId;` alongside your other state
  renderCards(data.products.slice(0, MAX_PRODUCTS));
})
```

Poi includilo nelle proprietà di aggiunta al carrello:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
properties: Object.assign(
  { "_source": "Aftersell", "_attribution": "CTA" },
  evaluationId ? { "__as_offer_id": evaluationId } : {}
)
```

Mantieni il doppio trattino basso su `__as_offer_id`: è la chiave che AfterSell cerca, e il prefisso con trattino basso la tiene nascosta agli acquirenti. Se `evaluationId` è assente (per esempio, se non è stato restituito alcun prodotto), ometti la proprietà anziché inviare un valore vuoto.
