> ## 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.

# Integração personalizada

> Chame a API de Strategies diretamente para alimentar experiências personalizadas na vitrine - carrosséis de PDP, upsells de carrinho personalizados e qualquer outro lugar que as superfícies nativas do Aftersell não alcancem.

<div id="overview">
  ## Visão geral
</div>

Quando nem as superfícies nativas do Aftersell (pós-compra, checkout, Upcart) nem uma integração pronta atendem, você pode chamar a API de Strategies por conta própria a partir do seu tema da Shopify e renderizar os produtos retornados da forma que quiser.

O padrão é o mesmo em todos os casos: monte um payload de contexto a partir do Liquid (para que atributos da Shopify como o produto atual, o conteúdo do carrinho e os campos do cliente sejam preenchidos no momento da renderização), faça um `POST` para `/api/public/strategy/evaluate` e renderize a resposta.

Esta página cobre dois padrões de implementação:

* **Contexto de PDP** - adicione uma seção às páginas de produto que chama a API com o **produto visualizado no momento** e renderiza um carrossel com as recomendações retornadas.
* **Contexto de carrinho** - renderize um bloco de upsell dentro de um carrinho personalizado que chama a API com **todos os itens de linha atuais do carrinho** e renderiza os produtos retornados.

O formato do **contexto de produto** é o que difere entre os dois: um único produto na PDP, um array com todos os itens de linha no carrinho.

***

<div id="what-youll-need">
  ## O que você vai precisar
</div>

1. **Sua chave de API da Strategy.** No Aftersell, vá em **Settings → Product Strategy** e, no card **Security Token**, copie seu token (essa é a sua chave de API da Strategy).
2. **O ID da Strategy.** Abra a Strategy que você quer executar no editor de Strategy do Aftersell e copie o ID dela.
3. **Acesso ao código do tema.** Você adicionará uma seção Liquid (PDP) ou um bloco (carrinho personalizado) ao seu tema da Shopify - Online Store → Themes → ... → Edit code.

<Warning>
  Sua chave de API da Strategy fica no código do tema do lado do cliente, o que a torna visível para qualquer pessoa que visualizar o código-fonte da página. Trate-a como uma credencial pública da vitrine e rotacione-a no Aftersell em **Settings → Product Strategy** se ela for exposta de uma forma que você não pretendia.
</Warning>

***

<div id="pdp-context-section-snippet">
  ## Contexto de PDP: snippet de seção
</div>

Esse padrão adiciona uma seção da Shopify à sua página de produto. Quando a página é renderizada, o Liquid incorpora os atributos do produto atual, do carrinho e do cliente no payload; em seguida, o JavaScript envia um post para a API de Strategies e renderiza os produtos retornados em um carrossel Splide.

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

1. No seu admin da Shopify, vá em **Online Store → Themes**, clique em **...** no seu tema e selecione **Edit code**.
2. Na pasta **Sections**, crie um novo arquivo chamado `aftersell-upsell-carousel.liquid`.
3. Cole o snippet abaixo no novo arquivo e substitua `YOUR_STRATEGY_API_KEY` pela chave de API do Aftersell.
4. Salve.
5. Abra o template do seu produto (normalmente `templates/product.json` ou `sections/main-product.liquid`) e adicione a seção **Aftersell Carousel** onde você quer que o carrossel apareça. Pelo editor de tema, você também pode arrastá-la diretamente para a página de produto.
6. Nas configurações da seção, cole o seu **Strategy ID**.

<div id="what-the-section-sends">
  ### O que a seção envia
</div>

Para cada visualização de PDP, o payload inclui:

* **products** - um array de um único elemento contendo o produto visualizado no momento (productId, variantId, quantity, price, handle, title, vendor, productType, tags, collections, sellingPlan).
* **cart** - subtotal, contagem de itens e contagem de linhas do carrinho atual do comprador (omitido se o carrinho estiver vazio).
* **cartToken** - para que a API possa vincular essa avaliação à mesma sessão.
* **customer** - tags, país, estado, localidade, contagem de pedidos, total gasto e o indicador aceita-marketing, mas **apenas se o comprador estiver logado**.
* **session** - código de moeda de `shop.currency`.

A seção não envia parâmetros UTM por padrão. Se você quiser segmentação baseada em UTM na PDP, capture-os no lado do cliente e adicione-os ao objeto `session` antes do fetch.

<div id="the-snippet">
  ### O 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="Um carrossel de produtos alimentado por Strategy renderizado em uma página de produto da Shopify" width="2692" height="1146" data-path="images/aftersell/strategy-carousel-example.png" />
</Frame>

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

O schema da seção expõe quatro configurações editáveis pelo lojista: **Strategy ID**, **Heading**, **CTA Button Label** e **Max Products to Show**. Adicione ou remova configurações no bloco `{% schema %}` para expor mais opções ao editor de tema.

O CSS está limitado a nomes de classe `.aftersell-*` e inclui um carrossel de 4 itens controlado pelo Splide que passa para 2 itens em 768px e 1 item em 480px. Edite-o livremente para combinar com o seu tema - nada dele é necessário para a chamada de API funcionar.

***

<div id="cart-context-custom-cart-upsell-block">
  ## Contexto de carrinho: bloco de upsell de carrinho personalizado
</div>

Esse padrão é estruturalmente igual ao da PDP, com uma diferença fundamental: o **array de contexto de produto** é construído a partir dos itens de linha do carrinho, em vez do produto visualizado no momento. A Strategy então recebe todos os itens que o comprador adicionou e retorna recomendações com base no carrinho como um todo.

A implementação fica onde estiver o código do seu carrinho personalizado - uma seção Liquid que renderiza o cart drawer, um bloco personalizado em uma vitrine headless ou um template de tema como `cart.liquid`. O formato da chamada de API e o tratamento da resposta são idênticos ao exemplo da PDP - apenas o array `products` é diferente.

A estrutura fica assim:

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

O restante do payload (cart, customer, session, cartToken) e a chamada `fetch` para `/api/public/strategy/evaluate` permanecem inalterados em relação ao padrão da PDP acima - apenas o array `products` muda de `[productContext]` para o array derivado do carrinho.

***

<div id="what-happens-when-the-strategy-returns">
  ## O que acontece quando a Strategy retorna
</div>

O formato da resposta é o mesmo independentemente do contexto que você enviou:

```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": {} }
}
```

O `evaluationId` é um id único para essa avaliação. Se você capturá-lo e anexá-lo aos produtos que renderiza, poderá atribuir o pedido resultante à recomendação exata que o gerou - veja [Atribuição](#attribution) abaixo.

Como você renderiza o array `products` fica totalmente a cargo do código do seu tema. O snippet da PDP acima os renderiza como um carrossel de cards com seletores de variante e botões de adicionar ao carrinho; um bloco de carrinho personalizado poderia renderizá-los como uma lista vertical dentro do drawer.

Para o schema completo de requisição e resposta, consulte a [referência da API Evaluate Strategy](/pt/aftersell/strategies_api_reference_evaluate_strategy).

***

<div id="when-no-product-is-returned">
  ## Quando nenhum produto é retornado
</div>

Se a Strategy não retornar produtos (`products: []`), cabe ao seu código decidir como lidar com isso. O snippet da PDP acima oculta o carrossel por completo. Um bloco de carrinho personalizado poderia recorrer à lista de upsell padrão do carrinho, ou simplesmente não renderizar nada.

Para evitar uma resposta vazia, configure um **Catch all** na Strategy para que sempre haja um produto de fallback para retornar. Consulte a página [Criando Strategies](/pt/aftersell/strategies_building_in_app) para saber como configurar um Catch all.

***

<div id="tips-for-custom-integrations">
  ## Dicas para integrações personalizadas
</div>

* **Monte o contexto no Liquid.** O Liquid é executado no momento da renderização e tem acesso ao grafo completo de objetos da Shopify - product, cart, customer, shop, request. Use-o para preencher o payload no lado do servidor em vez de recorrer a chamadas do lado do cliente.
* **Mantenha a chave de API fora de repositórios públicos.** Ela vai acabar no código do seu tema, que é enviado ao navegador - isso não é um problema. Mas não cole o mesmo tema em um repositório público nem compartilhe o bundle externamente.
* **Use um Catch all.** Experiências na vitrine parecem quebradas quando um slot desaparece. Um Catch all com um pequeno conjunto de padrões seguros mantém a interface consistente.
* **Faça cache onde fizer sentido.** A API de Strategies faz um cache leve no lado do servidor (`meta.servedFromCache`), mas, para PDPs de alto tráfego, você também pode querer aplicar debounce ou memoizar chamadas no cliente (por exemplo, não chamar novamente quando o mesmo produto é renderizado duas vezes em uma sessão).

***

<div id="attribution">
  ## Atribuição
</div>

Quando um comprador clica no botão de adicionar ao carrinho no snippet, a chamada `/cart/add.js` anexa **propriedades de item de linha** ao item do carrinho:

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

Essas propriedades acompanham o item de linha até o pedido da Shopify, onde aparecem no registro do item de linha. Você pode usá-las mais adiante para atribuir receita, filtrar pedidos ou alimentar ferramentas de análise que leem propriedades de item de linha.

As chaves e os valores são convenções, não requisitos - a chamada de API funciona da mesma forma independentemente do que você colocar aqui. Altere-os para se adequarem ao seu próprio modelo de atribuição. Por exemplo:

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

<Tip>
  Chaves de propriedade que começam com um sublinhado (`_`) ficam ocultas na interface do carrinho e do checkout, mas ainda assim são anexadas ao pedido. Use o prefixo de sublinhado para metadados exclusivos de atribuição que você não quer que os compradores vejam.
</Tip>

Aplique o mesmo padrão na implementação de contexto de carrinho - qualquer chamada de adicionar ao carrinho que você fizer a partir de um bloco de upsell personalizado pode carregar as propriedades que você precisar.

<div id="attributing-back-to-the-evaluation">
  ### Atribuindo de volta à avaliação
</div>

Para vincular um pedido à **avaliação exata** que recomendou o produto - em vez de apenas "veio de uma Strategy" - capture o `evaluationId` da resposta e anexe-o ao item de linha na propriedade `__as_offer_id`. O AfterSell lê essa chave, então pedidos marcados com ela são atribuídos à avaliação específica nos relatórios.

No handler `evaluate()`, guarde o id da resposta:

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

Em seguida, inclua-o nas propriedades de adicionar ao carrinho:

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

Mantenha o sublinhado duplo em `__as_offer_id` - é a chave que o AfterSell procura, e o prefixo de sublinhado a mantém oculta dos compradores. Se o `evaluationId` estiver ausente (por exemplo, se nenhum produto foi retornado), omita a propriedade em vez de enviar um valor vazio.
