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

# تكامل مخصص

> استدعِ Strategies API مباشرة لتشغيل تجارب واجهة متجر مخصصة - عروض دوّارة في صفحات المنتجات، وعروض بيع إضافي في سلات مخصصة، وأي مكان آخر لا تصل إليه الأسطح الأصلية لـ Aftersell.

<div id="overview">
  ## نظرة عامة
</div>

عندما لا تناسبك الأسطح الأصلية لـ Aftersell (ما بعد الشراء، صفحة الدفع، Upcart) ولا تكامل جاهز، يمكنك استدعاء Strategies API بنفسك من قالب Shopify الخاص بك وعرض المنتجات المُعادة بالطريقة التي تريدها.

النمط هو نفسه في كل الحالات: ابنِ حمولة سياق من Liquid (بحيث تُملأ خصائص Shopify مثل المنتج الحالي ومحتويات السلة وحقول العميل وقت العرض)، ثم أرسل `POST` إلى `/api/public/strategy/evaluate`، واعرض الاستجابة.

تغطي هذه الصفحة نمطي تنفيذ:

* **سياق صفحة المنتج (PDP)** - أضف قسمًا إلى صفحات المنتجات يستدعي واجهة برمجة التطبيقات مع **المنتج المعروض حاليًا** ويعرض عرضًا دوّارًا للتوصيات المُعادة.
* **سياق السلة** - اعرض كتلة بيع إضافي داخل سلة مخصصة تستدعي واجهة برمجة التطبيقات مع **جميع بنود السلة الحالية** وتعرض المنتجات المُعادة.

الاختلاف بين الاثنين هو شكل **سياق المنتج**: منتج واحد في صفحة المنتج، ومصفوفة بجميع البنود في السلة.

***

<div id="what-youll-need">
  ## ما ستحتاج إليه
</div>

1. **مفتاح Strategy API الخاص بك.** في Aftersell، انتقل إلى **Settings → Product Strategy**، وفي بطاقة **Security Token**، انسخ الرمز الخاص بك (هذا هو مفتاح Strategy API).
2. **معرّف الاستراتيجية.** افتح الاستراتيجية التي تريد تشغيلها في محرر الاستراتيجيات في Aftersell وانسخ معرّفها.
3. **الوصول إلى كود القالب.** ستضيف قسم Liquid (لصفحة المنتج) أو كتلة (للسلة المخصصة) إلى قالب Shopify الخاص بك - Online Store → Themes → ... → Edit code.

<Warning>
  يوجد مفتاح Strategy API الخاص بك في كود القالب على جانب العميل، مما يجعله مرئيًا لأي شخص يعرض مصدر الصفحة. تعامل معه كبيانات اعتماد عامة لواجهة المتجر، وقم بتدويره من **Settings → Product Strategy** في Aftersell إذا تم كشفه بأي طريقة لم تكن مقصودة.
</Warning>

***

<div id="pdp-context-section-snippet">
  ## سياق صفحة المنتج: مقتطف القسم
</div>

يضيف هذا النمط قسم Shopify إلى صفحة منتجك. عند عرض الصفحة، يضمّن Liquid خصائص المنتج الحالي والسلة والعميل في الحمولة، ثم يرسل JavaScript الطلب إلى Strategies API ويعرض المنتجات المُعادة في عرض Splide دوّار.

<div id="installing">
  ### التثبيت
</div>

1. في لوحة تحكم Shopify، انتقل إلى **Online Store → Themes**، وانقر على **...** في قالبك، واختر **Edit code**.
2. ضمن مجلد **Sections**، أنشئ ملفًا جديدًا باسم `aftersell-upsell-carousel.liquid`.
3. الصق المقتطف أدناه في الملف الجديد واستبدل `YOUR_STRATEGY_API_KEY` بمفتاح API من Aftersell.
4. احفظ.
5. افتح قالب المنتج الخاص بك (عادةً `templates/product.json` أو `sections/main-product.liquid`) وأضف قسم **Aftersell Carousel** حيث تريد أن يظهر العرض الدوّار. من محرر القالب، يمكنك أيضًا سحبه إلى صفحة المنتج مباشرة.
6. في إعدادات القسم، الصق **معرّف الاستراتيجية (Strategy ID)** الخاص بك.

<div id="what-the-section-sends">
  ### ما يرسله القسم
</div>

لكل مشاهدة لصفحة المنتج، تتضمن الحمولة:

* **products** - مصفوفة من عنصر واحد تحتوي على المنتج المعروض حاليًا (productId وvariantId وquantity وprice وhandle وtitle وvendor وproductType وtags وcollections وsellingPlan).
* **cart** - الإجمالي الفرعي وعدد العناصر وعدد البنود لسلة المتسوق الحالية (تُحذف إذا كانت السلة فارغة).
* **cartToken** - لتتمكن واجهة برمجة التطبيقات من ربط هذا التقييم بالجلسة نفسها.
* **customer** - الوسوم والبلد والمقاطعة واللغة المحلية وعدد الطلبات وإجمالي الإنفاق وعلامة قبول التسويق، ولكن **فقط إذا كان المتسوق مسجّل الدخول**.
* **session** - رمز العملة من `shop.currency`.

لا يرسل القسم معلمات UTM بشكل افتراضي. إذا كنت تريد استهدافًا معتمدًا على UTM في صفحة المنتج، التقطها على جانب العميل وأضفها إلى كائن `session` قبل الاستدعاء عبر fetch.

<div id="the-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="عرض دوّار للمنتجات مدعوم باستراتيجية معروض في صفحة منتج على Shopify" width="2692" height="1146" data-path="images/aftersell/strategy-carousel-example.png" />
</Frame>

<div id="customizing">
  ### التخصيص
</div>

يكشف مخطط القسم عن أربعة إعدادات قابلة للتعديل من قبل التاجر: **Strategy ID** و**Heading** و**CTA Button Label** و**Max Products to Show**. أضف إعدادات أو أزلها في كتلة `{% schema %}` لكشف المزيد من الخيارات لمحرر القالب.

أنماط CSS محصورة ضمن أسماء فئات `.aftersell-*` وتتضمن عرضًا دوّارًا مدعومًا بـ Splide بأربعة عناصر ينخفض إلى عنصرين عند 768 بكسل وإلى عنصر واحد عند 480 بكسل. عدّلها بحرية لتناسب قالبك - لا شيء منها مطلوب لعمل استدعاء واجهة برمجة التطبيقات.

***

<div id="cart-context-custom-cart-upsell-block">
  ## سياق السلة: كتلة بيع إضافي في سلة مخصصة
</div>

هذا النمط مطابق هيكليًا لنمط صفحة المنتج، مع اختلاف رئيسي واحد: **مصفوفة سياق المنتج** تُبنى من بنود السلة بدلاً من المنتج المعروض حاليًا. تستقبل الاستراتيجية بعد ذلك كل عنصر أضافه المتسوق وتُعيد التوصيات بناءً على السلة ككل.

يوجد التنفيذ حيثما يوجد كود سلتك المخصصة - قسم Liquid يعرض درج السلة (cart drawer)، أو كتلة مخصصة في واجهة متجر headless، أو قالب مثل `cart.liquid`. شكل استدعاء واجهة برمجة التطبيقات ومعالجة الاستجابة مطابقان لمثال صفحة المنتج - يختلف فقط مصفوفة `products`.

يبدو الهيكل كما يلي:

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

بقية الحمولة (cart وcustomer وsession وcartToken) واستدعاء `fetch` إلى `/api/public/strategy/evaluate` لم يتغيرا عن نمط صفحة المنتج أعلاه - فقط مصفوفة `products` تتبدل من `[productContext]` إلى المصفوفة المشتقة من السلة.

***

<div id="what-happens-when-the-strategy-returns">
  ## ماذا يحدث عندما تُعيد الاستراتيجية نتيجة
</div>

شكل الاستجابة هو نفسه بغض النظر عن السياق الذي أرسلته:

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

`evaluationId` هو معرّف فريد لهذا التقييم. إذا التقطته وأرفقته بالمنتجات التي تعرضها، يمكنك إسناد الطلب الناتج إلى التوصية الدقيقة التي أنتجته - راجع [الإسناد](#attribution) أدناه.

طريقة عرض مصفوفة `products` متروكة بالكامل لكود قالبك. يعرضها مقتطف صفحة المنتج أعلاه كعرض دوّار من البطاقات مع منتقيات المتغيرات وأزرار الإضافة إلى السلة؛ وقد تعرضها كتلة سلة مخصصة كقائمة عمودية داخل الدرج.

للاطلاع على مخطط الطلب والاستجابة الكامل، راجع [مرجع Evaluate Strategy API](/ar/aftersell/strategies_api_reference_evaluate_strategy).

***

<div id="when-no-product-is-returned">
  ## عندما لا يُعاد أي منتج
</div>

إذا لم تُعِد الاستراتيجية أي منتجات (`products: []`)، فإن طريقة التعامل مع ذلك متروكة لكودك. يخفي مقتطف صفحة المنتج أعلاه العرض الدوّار بالكامل. وقد تعود كتلة السلة المخصصة إلى قائمة البيع الإضافي الافتراضية للسلة، أو ببساطة لا تعرض شيئًا.

لتجنب استجابة فارغة، قم بإعداد **Catch all** في الاستراتيجية بحيث يكون هناك دائمًا منتج احتياطي يُعاد. راجع صفحة [بناء الاستراتيجيات](/ar/aftersell/strategies_building_in_app) لمعرفة كيفية إعداد Catch all.

***

<div id="tips-for-custom-integrations">
  ## نصائح للتكاملات المخصصة
</div>

* **ابنِ السياق في Liquid.** يعمل Liquid وقت العرض ولديه وصول إلى كامل مخطط كائنات Shopify - المنتج والسلة والعميل والمتجر والطلب. استخدمه لتعبئة الحمولة على جانب الخادم بدلاً من اللجوء إلى استدعاءات على جانب العميل.
* **أبقِ مفتاح API خارج المستودعات العامة.** سينتهي به المطاف في كود قالبك، الذي يُرسل إلى المتصفح - لا بأس بذلك. لكن لا تلصق القالب نفسه في مستودع عام ولا تشارك الحزمة خارجيًا.
* **استخدم Catch all.** تبدو تجارب واجهة المتجر معطوبة عندما تختفي خانة ما. Catch all يحتوي على مجموعة صغيرة من الخيارات الافتراضية الآمنة يُبقي واجهة المستخدم متسقة.
* **خزّن مؤقتًا حيث يكون ذلك منطقيًا.** تقوم Strategies API بتخزين مؤقت خفيف على جانب الخادم (`meta.servedFromCache`)، ولكن لصفحات المنتجات ذات الزيارات المرتفعة قد ترغب أيضًا في تقليل تكرار الاستدعاءات أو حفظ نتائجها على جانب العميل (على سبيل المثال، لا تعِد الاستدعاء عند عرض المنتج نفسه مرتين في جلسة واحدة).

***

<div id="attribution">
  ## الإسناد
</div>

عندما ينقر المتسوق على زر الإضافة إلى السلة في المقتطف، يُرفق استدعاء `/cart/add.js` **خصائص بند (line item properties)** بعنصر السلة:

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

تنتقل هذه الخصائص مع البند طوال الطريق إلى طلب Shopify، حيث تظهر في سجل البند. يمكنك استخدامها لاحقًا لإسناد الإيرادات أو تصفية الطلبات أو تغذية أدوات التحليلات التي تقرأ خصائص البنود.

المفاتيح والقيم اصطلاحات وليست متطلبات - يعمل استدعاء واجهة برمجة التطبيقات بالطريقة نفسها بغض النظر عما تضعه هنا. غيّرها لتناسب نموذج الإسناد الخاص بك. على سبيل المثال:

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

<Tip>
  مفاتيح الخصائص التي تبدأ بشرطة سفلية (`_`) تُخفى من واجهة السلة وصفحة الدفع لكنها تظل مرفقة بالطلب. استخدم بادئة الشرطة السفلية لبيانات الإسناد الوصفية التي لا تريد أن يراها المتسوقون.
</Tip>

طبّق النمط نفسه في تنفيذ سياق السلة - أي استدعاء إضافة إلى السلة تجريه من كتلة بيع إضافي مخصصة يمكن أن يحمل أي خصائص تحتاجها.

<div id="attributing-back-to-the-evaluation">
  ### الإسناد إلى التقييم
</div>

لربط طلب بـ **التقييم الدقيق** الذي أوصى بالمنتج - بدلاً من مجرد "جاء من استراتيجية" - التقط `evaluationId` من الاستجابة وأرفقه بالبند تحت الخاصية `__as_offer_id`. يقرأ AfterSell هذا المفتاح، لذا تُسند الطلبات الموسومة به إلى التقييم المحدد في التقارير.

في معالج `evaluate()`، احتفظ بالمعرّف من الاستجابة:

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

ثم ضمّنه في خصائص الإضافة إلى السلة:

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

حافظ على الشرطة السفلية المزدوجة في `__as_offer_id` - فهو المفتاح الذي يبحث عنه AfterSell، وبادئة الشرطة السفلية تبقيه مخفيًا عن المتسوقين. إذا كان `evaluationId` غائبًا (على سبيل المثال، لم تُعَد أي منتجات)، فتخطَّ الخاصية بدلاً من إرسال قيمة فارغة.
