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

# الخطافات

> غيّر كيفية سلوك Aftersell Cart: حوّل البنود، وأثرِها ببيانات Storefront، وشكّل خيارات الاشتراك، وتحكّم في الإضافة إلى السلة.

بينما تتيح لك [الأحداث](/ar/aftersell/cart/sdk-events) *التفاعل* مع السلة وتتيح لك [الإجراءات](/ar/aftersell/cart/sdk-actions) *تغييرها*، فإن **الخطافات** تغيّر كيفية سلوك السلة نفسها: كيف تُعرض البنود، وما البيانات التي تحملها، وما الذي يحدث عند الإضافة إلى السلة.

توجد الخطافات تحت `window.aftersell.cart.hooks`.

<Note>
  الخطاف يغيّر ما **يراه** المتسوق؛ والإجراء يغيّر ما هو **في سلته**. إخفاء بند هدية مجانية بتحويل يبقيه في السلة وفي الإجمالي. أما إزالته بـ [`removeItem`](/ar/aftersell/cart/sdk-actions#removeitemkey) فتخرجه فعليًا.
</Note>

<Note>
  الخطافات استدعاءات إعداد، لذا يمكن تسجيلها بأمان في أعلى سكربتك تمامًا، دون حاجة لانتظار `ready()`. سجّلها في سكربت **Initialization** الخاص بسلتك (راجع [السكربتات المخصصة](/ar/aftersell/cart/custom-scripts)).
</Note>

<div id="how-registration-works">
  ## كيف يعمل التسجيل
</div>

كل خطاف هو دالة `register*`. تستدعيها بدالتك؛ وتُرجع **دالة إلغاء تسجيل** يمكنك استدعاؤها لإزالة دالتك.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
const off = window.aftersell.cart.hooks.registerSkipAddToCartRule((form) =>
  form.hasAttribute('data-skip-aftersell')
);

// later: off();
```

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

| الخطاف                                                                                    | ما الذي يفعله                                                                                                   | مع عدة تسجيلات                                 |
| ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- |
| [`registerLineTransform`](#registerlinetransform)                                         | إخفاء بنود فردية أو إعادة تسميتها.                                                                              | تعمل جميعها، بترتيب التسجيل.                   |
| [`registerLineComparator`](#registerlinecomparator)                                       | إعادة ترتيب البنود المعروضة.                                                                                    | تتراكب كفواصل تعادل.                           |
| [`registerCartEnricher`](#registercartenricher)                                           | إرفاق بيانات Storefront إضافية بكل بند.                                                                         | تعمل جميعها؛ كل `id` هو نطاق أسماء مستقل.      |
| [`registerSubscriptionOptionsTransform`](#registersubscriptionoptionstransform)           | إخفاء خطط بيع بند أو إعادة تسميتها.                                                                             | تعمل جميعها؛ تُدمج التعديلات لكل خطة ولكل حقل. |
| [`registerDefaultSubscriptionOptionSelector`](#registerdefaultsubscriptionoptionselector) | اختيار الخطة المحددة مسبقًا.                                                                                    | تفوز أول إجابة غير `null`.                     |
| [`registerSkipAddToCartRule`](#registerskipaddtocartrule)                                 | السماح لنماذج محددة بتجاوز السلة. راجع [اعتراض الإضافة إلى السلة](/ar/aftersell/cart/add-to-cart-interception). | أي قاعدة تُرجع `true` تتجاوز.                  |

الخطاف الذي يرمي خطأً، أو الذي ليس دالة، يُتخطى؛ وتستمر البقية في العمل، وتواصل السلة عملها. لا يمكن لتكامل واحد معطوب أن يُسقط الإضافة إلى السلة أو منتقي الاشتراكات أو الترتيب.

الوجه الآخر هو أن خطافك المعطوب يفشل **بصمت**: لا يصل شيء إلى وحدة تحكم المتصفح. راجع [تصحيح الأخطاء](/ar/aftersell/cart/sdk-overview#debugging) لمعرفة أين تظهر تلك الإخفاقات فعلًا.

***

<div id="registerlinetransform">
  ## registerLineTransform
</div>

يعمل `registerLineTransform(fn)` لكل بند سلة قبل عرضه. استخدمه لإخفاء بند أو تغيير طريقة قراءته، دون المساس بما هو موجود فعليًا في سلة المتسوق.

تستقبل الدالة بندًا للقراءة فقط بالإضافة إلى دوال ضبط (setters). وتُرجع دالة إلغاء تسجيل.

| دالة الضبط                        | التأثير                                                                                                              |
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `setHidden(bool)`                 | إخفاء البند من الدرج. يبقى في السلة وفي الإجمالي.                                                                    |
| `setTitle(string)`                | تغيير العنوان المعروض.                                                                                               |
| `setVariantTitle(string \| null)` | تغيير تسمية المتغير المعروضة.                                                                                        |
| `setInternalProperties(obj)`      | دمج خصائص عرض فقط. لا تُحفظ أبدًا في Shopify. تُستخدم لـ[تجميع بنود الحزم](/ar/aftersell/cart/sdk-use-case-bundles). |

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
// Hide free gift lines from the drawer. The cart total is unaffected.
const off = window.aftersell.cart.hooks.registerLineTransform((line) => {
  if (line.finalLinePrice === 0) {
    line.setHidden(true);
  }
  if (line.sellingPlan) {
    line.setVariantTitle(`Delivered ${line.sellingPlan.name.toLowerCase()}`);
  }
});

// later: off();
```

<Warning>
  التحويل يغيّر فقط ما يُعرض. لا يمكنه تغيير السعر أو الكمية أو هوية البند. استخدم [الإجراءات](/ar/aftersell/cart/sdk-actions) لذلك.
</Warning>

**استخدمه من أجل:** إخفاء بنود الهدية-مع-الشراء أو البنود المحقونة من تطبيقات، وإعادة تسمية بنود الاشتراك، ووسم العناصر المخفَّضة، وإخفاء مكوّنات الحزمة التي لا ينبغي للمتسوق إدارتها فرديًا.

`setInternalProperties` هي دالة الضبط الكامنة خلف تجميع الحزم: ختم خصائص الحزمة القياسية على كل بند هو الطريقة التي تجعل بنود السلة المنفصلة لتطبيق طرف ثالث تُعرض كعنصر واحد. راجع [جمع بنود حزمة من تطبيق آخر](/ar/aftersell/cart/sdk-use-case-bundles).

<div id="registerlinecomparator">
  ## registerLineComparator
</div>

مقارِن بالشكل نفسه الذي تتوقعه `Array.prototype.sort`. يعمل بعد الإخفاء وإعادة التسمية، لذا يرى البنود المحوَّلة.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
// Subscriptions first, then everything else.
window.aftersell.cart.hooks.registerLineComparator((lineA, lineB) => {
  return (lineB.sellingPlan ? 1 : 0) - (lineA.sellingPlan ? 1 : 0);
});
```

المقارِنات **تتراكب كفواصل تعادل**: أول مقارِن يُرجع قيمة غير صفرية يحسم ذلك الزوج، ولا تُستشار البقية إلا عند التعادل. أرجِع `0` للأزواج التي ليس لديك رأي فيها. هذا ما يسلّم القرار إلى المقارِن التالي بدلًا من فرض ترتيب عليه.

**استخدمه من أجل:** رفع الاشتراكات أو العناصر عالية القيمة إلى الأعلى، وإنزال الهدايا المجانية والإضافات إلى الأسفل، وإبقاء منتج مموَّل في المقدمة.

<div id="registercartenricher">
  ## registerCartEnricher
</div>

يجلب `registerCartEnricher(registration)` بيانات إضافية للمنتج أو المتغير من Shopify Storefront API ويرفقها بكل بند سلة مطابق في `line.metadata[id]`. استخدمه لإظهار الحقول الوصفية (metafields) أو الوسوم أو أي شيء آخر تكشفه Storefront API، دون أي تعديل برمجي مطلوب من Aftersell.

| الحقل      | النوع                             | الوصف                                                                                               |
| ---------- | --------------------------------- | --------------------------------------------------------------------------------------------------- |
| `id`       | `string`                          | نطاق أسماء للنتيجة؛ تستقر في `line.metadata[id]`. يجب أن يكون فريدًا؛ يُتجاهل تسجيل ثانٍ بنفس `id`. |
| `onType`   | `'Product'` أو `'ProductVariant'` | العقدة التي يستهدفها المقطع (fragment). وهو أيضًا مفتاح الربط (معرّف المنتج مقابل معرّف المتغير).   |
| `fragment` | `string`                          | تحديد حقول GraphQL (بدون أقواس خارجية) يُدرج في استعلام Storefront. يجب أن تكون الأقواس متوازنة.    |

يُرجع **دالة إلغاء تسجيل**.

كلما حُمِّلت السلة أو تغيّرت، يجلب Aftersell مقطعك لكل منتج أو متغير في السلة ويرفق النتيجة. الجلب غير حاجب: تُعرض السلة فورًا ويُعاد إصدار `cart_updated` بمجرد وصول البيانات. المقطع البطيء أو الفاشل لا يؤخر السلة أو يعطّلها أبدًا.

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

// Read it once the data arrives.
window.aftersell.cart.events.on('cart_updated', (state) => {
  state.items.forEach((line) => {
    const anchor = line.metadata.pricing?.anchorPrice;
    if (anchor) console.log(line.title, 'anchor price', anchor.value);
  });
});
```

لأن الإثراء غير متزامن، احرس القراءة دائمًا، إذ تكون `line.metadata.pricing` قيمتها `undefined` حتى يُحل أول جلب، و`metadata` نفسها تكون افتراضيًا `{}`.

**استخدمه من أجل:** سحب حقل وصفي إلى كل بند (تقدير موعد التسليم، قائمة مكونات، علامة "يُشحن منفصلًا"، مضاعِف ولاء) وعرضه عبر [بلوك Custom code](/ar/aftersell/cart/custom-code-blocks). راجع [عرض بيانات الحقول الوصفية على بنود السلة](/ar/aftersell/cart/sdk-use-case-metafields).

<Note>
  تتعايش عدة مُثريات بسلام، لأن كل `id` هو نطاق أسماء مستقل، لذا لا تتصادم بياناتها أبدًا.
</Note>

<Warning>
  تُعاد القيم المُثراة كما هي من Storefront API وهي **غير** مُعقَّمة. اعرضها كنص، لا كـ HTML خام.
</Warning>

<div id="registersubscriptionoptionstransform">
  ## registerSubscriptionOptionsTransform
</div>

أخفِ خطط البيع المعروضة على بند أو أعد تسميتها. تستقبل دالتك خيارات للقراءة فقط بالإضافة إلى دوال ضبط، ولا تُرجع شيئًا.

| دالة الضبط        | التأثير                  |
| ----------------- | ------------------------ |
| `setHidden(bool)` | إخفاء الخطة من المنتقي.  |
| `setName(string)` | تغيير اسم الخطة المعروض. |

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerSubscriptionOptionsTransform((options, context) => {
  // context: { productId, variantId }
  options.forEach((option) => {
    if (option.discountPercent === 0) option.setHidden(true);
    option.setName(option.name.replace('Every ', ''));
  });
});
```

**دوال ضبط، وليست قائمة مُرجَعة، حتى تتمكن عدة سكربتات من التعايش.** لو كان هذا الخطاف يُرجع مصفوفة، فإن تحويلًا يهتم بخطة واحدة فقط سيكتب بطبيعة الحال `options.filter(...)` ويحذف بصمت خطط كل التطبيقات الأخرى في طريقه. مع دوال الضبط لا يمكنك سوى وصف تعديلاتك أنت: تُدمج التعديلات لكل خطة ولكل حقل، ويفوز آخر كاتب في التعارض الحقيقي على الحقل نفسه من الخطة نفسها. التحويل الذي يرمي خطأً لا يساهم بشيء، وتظل البقية سارية.

كل تحويل يرى الخيارات *الأصلية*، لا نسخة نصف معدَّلة، لذا لا يغيّر ترتيب التسجيل ما تقرؤه.

<Note>
  يبقى ترتيب الخطط كما أرجعته Shopify، لذا لا يمكن للتحويل إعادة الترتيب. للتحكم في الخطة المعروضة أولًا (والتي يشترك فيها زر الترقية من الشراء لمرة واحدة)، استخدم [`registerDefaultSubscriptionOptionSelector`](#registerdefaultsubscriptionoptionselector)، الذي يرفع اختياره إلى المقدمة.
</Note>

كما لا يمكنك *إضافة* خطة أو تغيير سعر: لا توجد دالة ضبط لـ `discountPercent`، لأن خطة لن تحترمها Shopify عند إتمام الشراء ستكون مجرد وعد زائف في المنتقي.

<div id="registerdefaultsubscriptionoptionselector">
  ## registerDefaultSubscriptionOptionSelector
</div>

اختر الخطة المحددة مسبقًا على بند. أرجِع `id` خطة، أو `null` للتنازل عن القرار.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerDefaultSubscriptionOptionSelector((options) => {
  const best = options
    .slice()
    .sort((optionA, optionB) => optionB.discountPercent - optionA.discountPercent)[0];
  return best ? best.id : null;
});
```

**يفوز أول منتقٍ يُرجع معرّف خطة متاحة**، لذا أرجِع `null` للبنود التي لا تهمك بدلًا من التخمين. هذا يسلّم القرار إلى المنتقي التالي بدلًا من تجاوزه. المعرّف الذي لا يطابق أي خطة على البند يُعامل مثل `null` ويتنازل أيضًا، لذا لا يمكن لمعرّف قديم أن يفرغ المنتقي.

تستقبل دالتك `(options, context)`، وهو نفس `context` الذي يستقبله تحويل الخيارات.

<div id="registerskipaddtocartrule">
  ## registerSkipAddToCartRule
</div>

أرجِع `true` للسماح لنموذج منتج محدد بالإضافة إلى السلة بشكل عادي، متجاوزًا Aftersell كليًا. هذا مفيد لنموذج يحتاج إلى إعادة توجيه أو معالجة خاصة به.

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerSkipAddToCartRule((form) =>
  form.hasAttribute('data-skip-aftersell')
);
```

**أي `true` يعني التجاوز**، لذا أبقِ قاعدتك ضيقة، بحيث تطابق النماذج المحددة التي تملكها، وأرجِع `false` لكل ما عداها. تُقيَّم القواعد بترتيب التسجيل وتتوقف عند أول `true`، لذا لا تضع آثارًا جانبية في قاعدة: يعتمد تشغيل قاعدتك أصلًا على ما سُجِّل قبلها.

<Tip>
  إذا كنت تتحكم في ترميز النموذج، فلا تحتاج إلى خطاف إطلاقًا: أضِف الصنف **`aftersell-cart-skip-atc`** إلى `<form>` وسيتركه Aftersell وشأنه. استخدم هذا الخطاف عندما لا تستطيع تحرير الترميز، أو عندما يعتمد القرار على شيء لا يعرفه إلا كودك.
</Tip>

**استخدمه من أجل:** نموذج طلب مسبق أو عرض سعر يحتاج إلى إعادة توجيه خاصة به، أو تدفق مخصص لتطبيق اشتراكات، أو زر "buy it now" ينبغي أن ينتقل مباشرة إلى إتمام الشراء. لإيقاف الاعتراض للصفحة كلها بدلًا من ذلك، استخدم [`skip_add_to_cart_interceptor`](/ar/aftersell/cart/sdk-configure#skip_add_to_cart_interceptor)، لكن فضّل هذا الخطاف، المحدود بالنماذج التي تسمّيها.

<div id="where-to-go-next">
  ## إلى أين تذهب بعد ذلك
</div>

* **[كائن السلة](/ar/aftersell/cart/sdk-cart-object)**: شكل البند الذي يستقبله التحويل.
* **[الأحداث](/ar/aftersell/cart/sdk-events)**: كل ما يمكنك الاشتراك فيه.
* **[الإجراءات](/ar/aftersell/cart/sdk-actions)**: قراءة السلة وتغييرها.
* **[حالات الاستخدام](/ar/aftersell/cart/sdk-use-cases)**: حلول كاملة للطلبات الشائعة.
