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

# Отображение данных метаполей на строках корзины

> Сценарий использования Cart SDK с registerCartEnricher для получения метаполей из Shopify Storefront API на каждую строку корзины и их отображения.

Корзина знает то, что сообщает ей Ajax API Shopify: заголовки, цены, количества, свойства. Она не знает ваши метаполя. **Cart enricher** получает дополнительные поля товара или варианта из Storefront API и прикрепляет их к каждой подходящей строке, чтобы вы могли показать оценку сроков доставки, предупреждение «отправляется отдельно», список ингредиентов или что угодно ещё, что вы храните на товаре.

Типичные применения: окна доставки по товарам, значки аллергенов или ингредиентов, пользовательский флаг «мало на складе», множители баллов лояльности, цены только для подписчиков.

<div id="register-the-enricher">
  ## Регистрация enricher
</div>

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerCartEnricher({
  id: 'delivery',
  onType: 'Product',
  fragment: `
    deliveryWindow: metafield(namespace: "custom", key: "delivery_window") { value }
    shipsSeparately: metafield(namespace: "custom", key: "ships_separately") { value }
  `,
});
```

Три поля:

| Поле       | Описание                                                                                                                                       |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`       | Пространство имён для результата; он попадает в `line.metadata[id]`. Должно быть уникальным; повторная регистрация с тем же `id` игнорируется. |
| `onType`   | `'Product'` или `'ProductVariant'`. Также ключ соединения: ID товара или ID варианта.                                                          |
| `fragment` | Выборка полей GraphQL (без внешних фигурных скобок), встраиваемая в запрос Storefront. Скобки должны быть сбалансированы.                      |

Каждый раз при загрузке или изменении корзины Aftersell запрашивает ваш фрагмент для каждого товара или варианта в корзине. Запрос **неблокирующий**: корзина отображается немедленно и повторно генерирует `cart_updated`, когда данные приходят. Медленный или сбойный фрагмент никогда не задерживает и не ломает корзину.

<div id="read-the-data">
  ## Чтение данных
</div>

Поскольку обогащение асинхронно, `line.metadata.delivery` равно `undefined`, пока не разрешится первый запрос. Всегда защищайте чтение проверкой.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.events.on('cart_updated', (state) => {
  state.items.forEach((line) => {
    const deliveryWindow = line.metadata.delivery?.deliveryWindow?.value;
    if (!deliveryWindow) return; // not fetched yet

    console.log(line.title, 'arrives in', deliveryWindow);
  });
});
```

<div id="render-it-in-the-cart">
  ## Отображение в корзине
</div>

Обогащение помещает данные на строку; блок [Custom code](/ru/aftersell/cart/custom-code-blocks) в режиме React их отрисовывает. Добавьте блок как **подблок Cart items**, чтобы он отображался один раз на строку и получал эту строку как `props.line`:

```jsx theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
function CustomCode(props) {
  const data = props.line?.metadata?.delivery;
  const window_ = data?.deliveryWindow?.value;

  if (!window_) return null;

  return (
    <div className="cart-external-custom-code_html" style={{ fontSize: '12px', opacity: 0.7 }}>
      Arrives in {window_}
    </div>
  );
}
```

<Warning>
  Обогащённые значения возвращаются из Storefront API **как есть и без санитизации**. Отображайте их как текст, как показано выше, и никогда через `dangerouslySetInnerHTML` или записывая сырой HTML.
</Warning>

<div id="variant-level-data">
  ## Данные на уровне варианта
</div>

Установите `onType: 'ProductVariant'`, когда метаполе находится на варианте, а не на товаре:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerCartEnricher({
  id: 'pricing',
  onType: 'ProductVariant',
  fragment: `
    anchorPrice: metafield(namespace: "custom", key: "anchor_price") { value }
    subscriberPrice: metafield(namespace: "custom", key: "subscriber_price") { value }
  `,
});
```

<div id="more-than-metafields">
  ## Больше, чем метаполя
</div>

Фрагмент встраивается в запрос Storefront API, поэтому работает всё, что API предоставляет на `Product` или `ProductVariant`, а не только метаполя:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerCartEnricher({
  id: 'productInfo',
  onType: 'Product',
  fragment: `
    tags
    vendor
    productType
    availableForSale
  `,
});
```

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
// Then: warn about anything from a drop-ship vendor.
window.aftersell.cart.events.on('cart_updated', (state) => {
  const hasDropship = state.items.some((line) => {
    const tags = line.metadata.productInfo?.tags;
    return tags?.includes('dropship');
  });
});
```

<div id="things-to-get-right">
  ## Что важно сделать правильно
</div>

* **Защищайте каждое чтение.** `metadata` по умолчанию равно `{}`, а ваше пространство имён — `undefined`, пока не разрешится запрос. Корзина всегда отображается до прихода данных.
* **Каждый `id` — собственное пространство имён.** Несколько enricher-ов сосуществуют без коллизий, включая зарегистрированные другими приложениями.
* **Метаполя должны быть видимы для Storefront.** Метаполе, не открытое для Storefront API, возвращает `null`. Проверьте определение в админке Shopify, если ничего не получаете.
* **Держите фрагмент небольшим.** Он выполняется для каждого товара в корзине при каждом её изменении. Запрашивайте поля, которые используете, а не всё подряд.
* **Регистрируйте на этапе настройки.** Это хук, поэтому его место в начале скрипта Initialization.
* **Скобки должны быть сбалансированы.** Без внешних скобок вокруг фрагмента, но любая вложенная выборка требует своей пары. Несбалансированный фрагмент отклоняется.

<div id="where-to-go-next">
  ## Что дальше
</div>

* **[`registerCartEnricher`](/ru/aftersell/cart/sdk-hooks#registercartenricher)**: полный справочник по хуку.
* **[Блоки Custom code](/ru/aftersell/cart/custom-code-blocks)**: отображение данных.
* **[Объект корзины](/ru/aftersell/cart/sdk-cart-object)**: где на строке находится `metadata`.
