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

# 他社アプリのバンドルラインをグループ化する

> setInternalPropertiesを使って、どのラインが同じバンドルに属するかをAftersell Cartに伝え、複数の無関係なラインではなく1つのアイテムとしてレンダリングします。

ほとんどのバンドルアプリは、**各コンポーネントを個別のカートラインとして追加**し、独自設計のラインアイテムプロパティでそれらをリンクすることでバンドルを構築します。ShopifyのAjax APIはそれらのラインが一緒に属していることを示さないままカートに渡すため、デフォルトではドロワーは3点構成のバンドルを、それぞれ独自の価格と数量ステッパーを持つ3つの無関係なアイテムとして表示します。

`setInternalProperties`は、それらが1つのものであることをカートに伝える方法です。

<div id="how-grouping-works">
  ## グループ化の仕組み
</div>

カートは2つの**正規プロパティ**に基づいてラインをグループ化します。カートはバンドルアプリのプロパティ名を知らないため、あなたが変換します。アプリが書き込んだ内容を読み取り、[ライントランスフォーム](/ja/aftersell/cart/sdk-hooks#registerlinetransform)で正規のペアを各ラインに刻印します。

| プロパティ                         | 必須  | 値                                 |
| ----------------------------- | --- | --------------------------------- |
| `_aftersell_cart_bundle_id`   | はい  | 共有ID。同じIDを持つすべてのラインが1つのバンドルになります。 |
| `_aftersell_cart_bundle_role` | いいえ | バンドルの表示元となるラインに`parent`を設定します。    |

これらはShopify経由ではなく、`setInternalProperties`を通じて設定します。これらは**レンダリング専用のオーバーレイ**です。`properties`には決して届かず、Shopifyに永続化されることも、注文に表示されることもありません。

<div id="step-1-find-out-what-your-app-writes">
  ## ステップ1：アプリが何を書き込んでいるか確認する
</div>

バンドルアプリごとにプロパティの命名は異なるため、まず実際のカートを見ることから始めます。ストアフロントでバンドルを追加し、ブラウザのコンソールで次を実行してください。

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

探すのは、バンドルのライン間で共有されているプロパティです。通常はIDや参照、バンドル名を保持する非表示プロパティ（名前が`_`で始まる）です。`_bundle_id`、`_bundle_ref`、`_parent_id`などが典型的です。正確なキーと、いずれかのラインがメイン商品としてマークされているかどうかをメモしてください。

<div id="step-2-map-it-onto-the-canonical-properties">
  ## ステップ2：正規プロパティにマッピングする
</div>

**Cart settings → Custom script → Initialization**に貼り付け、プロパティ名を見つけたものに置き換えてください。

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

これが連携のすべてです。2つ以上のラインがIDを共有すると、カートはそれらを1つのバンドルにまとめます。

<Note>
  アプリがメイン商品をマークしていない場合は、`_aftersell_cart_bundle_role`を完全に省略してください。カートが自動的にアンカーを選択します。
</Note>

<div id="what-you-get">
  ## 得られるもの
</div>

ラインがグループ化されると、アンカーラインは[`bundle`オブジェクト](/ja/aftersell/cart/sdk-cart-object#bundles)を持ち、ドロワーはバンドルを単一のアイテムとしてレンダリングします。

* **子は個別の行として表示される代わりに、アンカーの下にネストされます。**
* **数量はアトミックです。** バンドルの数量を変更すると、各子の`perAnchorQty`比率を使ってすべてのメンバーが一緒にスケーリングされます。あるコンポーネントを2つ含むバンドルは、その2対1の関係を維持します。
* **削除はアトミックです。** バンドルを削除すると、孤立したコンポーネントを残すことなく、1回のリクエストですべてのメンバーラインが削除されます。
* **価格行は1つです。** 表示内容は[Cart items](/ja/aftersell/cart/cart-items-block)ブロックの**バンドル価格**設定に従います。全メンバーの合計か、メイン商品の価格のみかを選べます。

<div id="how-the-anchor-is-chosen">
  ## アンカーの選ばれ方
</div>

アンカーはバンドルの表示元となるラインです。カートは次の順序で選択します。

1. `_aftersell_cart_bundle_role`が`parent`に設定されたライン。
2. それがなければ、**最も価格の高い**メンバー。
3. それもなければ、カート内の最初のメンバー。

バンドルアプリはディスカウントをメイン商品に付ける傾向があるため、価格によるフォールバックはたいてい正しく機能します。そうでない場合、たとえばメイン商品が最安のアイテムや無料の場合は、ロールを明示的に設定してください。

<div id="rules-worth-knowing">
  ## 知っておくべきルール
</div>

* **バンドルには少なくとも2つのラインが必要です。** バンドルIDを持つ単一のラインはそのまま残され、通常どおりレンダリングされます。
* **Shopifyネイティブのバンドルはすでに処理されています。** Shopify自体がコンポーネント化としてマークしたラインは、このグループ化ではスキップされ、自動的に適応されます。これが必要なのは、個別のラインを追加するアプリの場合のみです。
* **トランスフォームはレンダリングのたびに実行されます。** 軽量に保ち、副作用を避けてください。その内部からアクションを呼び出したりフェッチしたりしないでください。
* **マージは加算的です。** あなたのプロパティは、他のトランスフォームが設定したものとマージされます。同じキーで真の競合が発生した場合は、最後に登録されたトランスフォームが優先されます。
* **グループ化は非表示化とリネームの後、ソートの前に実行されます。** そのため、`setHidden`で非表示にしたラインがバンドルの一部になることはなく、[コンパレーター](/ja/aftersell/cart/sdk-hooks#registerlinecomparator)は子ではなくアンカーを参照します。

<Warning>
  **グループ化された子は`state.items`から外れます。** ラインがバンドルにまとめられると、`getCart().items`とイベントペイロードにはアンカーのみが表示され、子は`anchor.bundle.children`に移動します。また、`itemCount`にもカウントされなくなります。

  **カート合計には影響しません。** 合計はShopifyから直接取得されるためです。グループ化が変えるのは表示のみで、購入者が支払う金額は決して変わりません。
</Warning>

<div id="reading-a-bundle-back">
  ## バンドルの読み取り
</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);
    });
  });
});
```

バンドルのラインに対して操作を行うには、アンカーを含むすべてのメンバーの`key`を保持する`bundle.memberKeys`を使用してください。

<div id="using-it-for-other-things">
  ## 他の用途での活用
</div>

バンドルのグループ化は`setInternalProperties`が作られた目的ですが、このオーバーレイは**ラインから導出したレンダリング専用データ**のための汎用チャネルです。ここに置いたものは、実際のカートに触れることなく、`line.internalProperties`と[カスタムコードブロック](/ja/aftersell/cart/custom-code-blocks)で読み取れます。

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

値が**導出されたもの**で表示専用の場合に使用してください。データが注文まで残る必要がある場合は、実際のラインアイテムプロパティに置くべきです。商品フォームに `properties[...]` の hidden input を追加すれば、追加を実行するのが誰であれ、Shopify にその値が届きます。

<div id="where-to-go-next">
  ## 次に読むべきページ
</div>

* **[`registerLineTransform`](/ja/aftersell/cart/sdk-hooks#registerlinetransform)**：この処理が実行されるフック。
* **[カートオブジェクト](/ja/aftersell/cart/sdk-cart-object#bundles)**：`bundle`とその子の構造。
* **[Cart itemsブロック](/ja/aftersell/cart/cart-items-block)**：バンドル価格の設定。
