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

# Bundle-Positionen aus einer anderen App gruppieren

> Nutze setInternalProperties, um dem Aftersell Cart mitzuteilen, welche Positionen zum selben Bundle gehören, sodass sie als ein Artikel statt als mehrere zusammenhanglose Positionen gerendert werden.

Die meisten Bundle-Apps bauen ein Bundle, indem sie **jede Komponente als eigene Warenkorbposition** hinzufügen und diese dann mit selbst gestalteten Line-Item-Properties verknüpfen. Shopifys Ajax-API übergibt diese Positionen an den Warenkorb ohne jeden Hinweis darauf, dass sie zusammengehören — standardmäßig zeigt der Drawer ein dreiteiliges Bundle also als drei zusammenhanglose Artikel, jeder mit eigenem Preis und eigenem Mengen-Stepper.

Mit `setInternalProperties` teilst du dem Warenkorb mit, dass sie eine Einheit sind.

<div id="how-grouping-works">
  ## So funktioniert die Gruppierung
</div>

Der Warenkorb gruppiert Positionen anhand von zwei **kanonischen Properties**. Er kennt die Property-Namen deiner Bundle-App nicht, du übersetzt also: Lies, was die App geschrieben hat, und stemple das kanonische Paar mit einer [Line-Transform](/de/aftersell/cart/sdk-hooks#registerlinetransform) auf jede Position.

| Property                      | Erforderlich | Wert                                                                         |
| ----------------------------- | ------------ | ---------------------------------------------------------------------------- |
| `_aftersell_cart_bundle_id`   | Ja           | Eine gemeinsame ID. Alle Positionen mit derselben ID bilden ein Bundle.      |
| `_aftersell_cart_bundle_role` | Nein         | Setze `parent` auf der Position, unter der das Bundle angezeigt werden soll. |

Diese laufen über `setInternalProperties`, nicht über Shopify. Sie sind ein **reines Render-Overlay**: Sie erreichen nie `properties`, werden nie in Shopify gespeichert und erscheinen nie auf der Bestellung.

<div id="step-1-find-out-what-your-app-writes">
  ## Schritt 1: Finde heraus, was deine App schreibt
</div>

Jede Bundle-App benennt ihre Properties anders, beginne also mit einem Blick in einen echten Warenkorb. Füge in deinem Storefront ein Bundle hinzu und führe dann Folgendes in der Browser-Konsole aus:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.actions.getCart().items.forEach((line) => {
  console.log(line.title, line.properties);
});
```

Du suchst nach einer Property, die alle Positionen des Bundles teilen. Meist ist es eine versteckte Property (der Name beginnt mit `_`), die eine ID, eine Referenz oder den Namen des Bundles enthält. Typisch ist etwas wie `_bundle_id`, `_bundle_ref` oder `_parent_id`. Notiere dir den exakten Schlüssel und ob eine Position als Hauptprodukt markiert ist.

<div id="step-2-map-it-onto-the-canonical-properties">
  ## Schritt 2: Auf die kanonischen Properties abbilden
</div>

Füge Folgendes in **Cart settings → Custom script → Initialization** ein und ersetze die Property-Namen durch die, die du gefunden hast:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerLineTransform((line) => {
  const props = line.properties;
  if (!props) return;

  const bundleId = props._bundle_id;
  if (!bundleId) return;

  line.setInternalProperties({
    _aftersell_cart_bundle_id: bundleId,
    // Mark the main product so the bundle renders under it.
    _aftersell_cart_bundle_role: props._bundle_role === 'main' ? 'parent' : 'child',
  });
});
```

Das ist die gesamte Integration. Sobald zwei oder mehr Positionen eine ID teilen, fasst der Warenkorb sie zu einem Bundle zusammen.

<Note>
  Wenn deine App kein Hauptprodukt markiert, lass `_aftersell_cart_bundle_role` komplett weg. Der Warenkorb wählt dann einen Anker für dich.
</Note>

<div id="what-you-get">
  ## Was du bekommst
</div>

Sobald Positionen gruppiert sind, trägt die Anker-Position ein [`bundle`-Objekt](/de/aftersell/cart/sdk-cart-object#bundles), und der Drawer rendert das Bundle als einen einzelnen Artikel:

* **Kinder werden unter dem Anker verschachtelt**, statt als separate Zeilen zu erscheinen.
* **Die Menge ist atomar.** Eine Änderung der Bundle-Menge skaliert alle Mitglieder gemeinsam, anhand des `perAnchorQty`-Verhältnisses jedes Kindes — ein Bundle mit zwei Stück einer Komponente behält also dieses Zwei-zu-eins-Verhältnis.
* **Das Entfernen ist atomar.** Das Entfernen des Bundles entfernt jede Mitgliedsposition in einem einzigen Request, statt verwaiste Komponenten zurückzulassen.
* **Eine Preiszeile.** Was sie zeigt, folgt der **Bundle-Preis**-Einstellung am [Cart items](/de/aftersell/cart/cart-items-block)-Block: die Summe aller Mitglieder oder nur der Preis des Hauptprodukts.

<div id="how-the-anchor-is-chosen">
  ## Wie der Anker gewählt wird
</div>

Der Anker ist die Position, unter der das Bundle angezeigt wird. Der Warenkorb wählt ihn in dieser Reihenfolge:

1. Die Position mit `_aftersell_cart_bundle_role` auf `parent`.
2. Andernfalls das Mitglied mit dem **höchsten Preis**.
3. Andernfalls das erste Mitglied im Warenkorb.

Der Preis-Fallback passt meistens, da Bundle-Apps den Rabatt in der Regel auf das Hauptprodukt legen. Setze die Rolle explizit, wenn das nicht der Fall ist, zum Beispiel wenn das Hauptprodukt der günstigste Artikel oder kostenlos ist.

<div id="rules-worth-knowing">
  ## Regeln, die du kennen solltest
</div>

* **Ein Bundle braucht mindestens zwei Positionen.** Eine einzelne Position mit einer Bundle-ID bleibt unangetastet und rendert normal.
* **Native Shopify-Bundles sind bereits abgedeckt.** Positionen, die Shopify selbst als componentized markiert, werden von dieser Gruppierung übersprungen und automatisch angepasst. Du brauchst das nur für Apps, die separate Positionen hinzufügen.
* **Die Transform läuft bei jedem Render.** Halte sie günstig und frei von Nebeneffekten. Rufe darin keine Actions auf und mache keine Fetches.
* **Das Mergen ist additiv.** Deine Properties werden mit denen anderer Transforms zusammengeführt. Bei einem echten Konflikt um denselben Schlüssel gewinnt die zuletzt registrierte Transform.
* **Die Gruppierung läuft nach dem Ausblenden und Umbenennen** und vor dem Sortieren. Eine Position, die du mit `setHidden` ausblendest, wird also nie Teil eines Bundles, und ein [Comparator](/de/aftersell/cart/sdk-hooks#registerlinecomparator) sieht den Anker, nicht die Kinder.

<Warning>
  **Gruppierte Kinder verlassen `state.items`.** Sobald Positionen zu einem Bundle zusammengefasst sind, erscheint nur der Anker in `getCart().items` und in Event-Payloads; die Kinder wandern nach `anchor.bundle.children`. Sie zählen auch nicht mehr zu `itemCount`.

  Die **Warenkorbsumme bleibt unverändert**, weil Summen direkt von Shopify kommen. Die Gruppierung ändert die Darstellung, nie das, was der Käufer bezahlt.
</Warning>

<div id="reading-a-bundle-back">
  ## Ein Bundle wieder auslesen
</div>

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.events.on('cart_updated', (state) => {
  state.items.forEach((line) => {
    if (!line.bundle) return;
    console.log(line.title, 'is a bundle of', line.bundle.children.length, 'items:');
    line.bundle.children.forEach((child) => {
      console.log('  ', child.quantity, 'x', child.title);
    });
  });
});
```

Um auf die Positionen eines Bundles zuzugreifen, nutze `bundle.memberKeys`, das den `key` jedes Mitglieds inklusive des Ankers enthält.

<div id="using-it-for-other-things">
  ## Für andere Zwecke nutzen
</div>

Bundle-Gruppierung ist der Zweck, für den `setInternalProperties` gebaut wurde, aber das Overlay ist ein allgemeiner Kanal für **rein anzeigebezogene Daten, die du aus einer Position ableitest**. Alles, was du dort ablegst, ist unter `line.internalProperties` und in einem [Custom-Code-Block](/de/aftersell/cart/custom-code-blocks) lesbar, ohne den echten Warenkorb anzufassen:

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerLineTransform((line) => {
  if (line.properties?._preorder_ship_date) {
    line.setInternalProperties({ _badge: `Ships ${line.properties._preorder_ship_date}` });
  }
});
```

Nutze es, wenn der Wert **abgeleitet** und nur zur Anzeige gedacht ist. Wenn die Daten bis zur Bestellung überleben müssen, gehören sie in eine echte Line-Item-Property, die über ein verstecktes `properties[...]`-Input im Produktformular gesetzt wird, sodass sie Shopify erreichen, egal wer das Hinzufügen ausführt.

<div id="where-to-go-next">
  ## Nächste Schritte
</div>

* **[`registerLineTransform`](/de/aftersell/cart/sdk-hooks#registerlinetransform)**: der Hook, über den das läuft.
* **[Cart-Objekt](/de/aftersell/cart/sdk-cart-object#bundles)**: die Struktur von `bundle` und seinen Kindern.
* **[Cart-items-Block](/de/aftersell/cart/cart-items-block)**: die Bundle-Preis-Einstellung.
