# Sepet satırlarını pakette birleştirme

> Setin tamamı sepetteyse bileşen satırlarını tek bir indirimli paket satırında birleştiren Shopify cart transform Function’ı; test edilmiş JavaScript.

Sayfa: https://lintelagency.com/tr/kaynaklar/shopify-functions/sepet-satirlarini-pakette-birlestirme

## Kısaca

Bu cart transform Function’ı, bir setin tüm bileşenleri sepetteyse ayrı satırları tek bir paket satırında birleştirir; isteğe bağlı yüzde indirim ve özel bir ad da verir. Paketler cart transform üzerindeki bir JSON metafield’da tanımlanır. Cart Transform API’nin cart.transform.run hedefindeki linesMerge işlemini kullanır, sürüm 2026-10.

## Bilgiler

- **Function API:** Cart Transform
- **Hedef (target):** cart.transform.run
- **İşlem (operation):** linesMerge
- **Plan:** Herkese açık uygulamada her plan; özel uygulamada Shopify Plus
- **Uygulama yetkisi:** write_cart_transforms
- **API sürümü:** 2026-10
- **Son kontrol:** 2 Kasım 2026

## Nasıl kullanılır

1. Paketin ana ürününü bir ürün varyantı olarak oluşturun; birleştirmeden sonra müşterinin gördüğü budur.
2. Shopify CLI ile bir uygulama oluşturun, uzantıyı ekleyin (shopify app generate extension --template cart_transform) ve dosyalarını aşağıdakilerle değiştirin.
3. Testleri shopify app function run --input tests/two-complete-sets.input.json ile çalıştırın, ardından deploy edin.
4. cartTransformCreate ile etkinleştirin ve paketleri $app:function-configuration metafield’ında gönderin.

## Kod

### shopify.extension.toml

```toml
api_version = "2026-10"

[[extensions]]
name = "Merge cart lines into a bundle"
handle = "merge-lines-into-bundle"
type = "function"

  [[extensions.targeting]]
  target = "cart.transform.run"
  input_query = "src/cart_transform_run.graphql"
  export = "cart-transform-run"

  [extensions.build]
  command = ""
  path = "dist/function.wasm"
```

### src/cart_transform_run.graphql

```graphql
query CartTransformRunInput {
  cart {
    lines {
      id
      quantity
      merchandise {
        __typename
        ... on ProductVariant {
          id
        }
      }
    }
  }
  cartTransform {
    metafield(namespace: "$app", key: "function-configuration") {
      jsonValue
    }
  }
}
```

### src/cart_transform_run.js

```js
// @ts-check

/**
 * @typedef {import("../generated/api").CartTransformRunInput} CartTransformRunInput
 * @typedef {import("../generated/api").CartTransformRunResult} CartTransformRunResult
 */

/**
 * Bundles stored as JSON in the cart transform's metafield. Each bundle has a
 * parent variant (the product customers see after the merge), its components
 * and an optional discount and title.
 * @typedef {{
 *   parentVariantId: string,
 *   components: {variantId: string, quantity: number}[],
 *   percentageDecrease?: number,
 *   title?: string,
 * }} BundleRule
 */

/**
 * @param {CartTransformRunInput} input
 * @returns {CartTransformRunResult}
 */
export function cartTransformRun(input) {
  /** @type {BundleRule[]} */
  const bundles = input.cartTransform?.metafield?.jsonValue?.bundles ?? [];

  // Units still free to merge, per line; a line used by one bundle isn't
  // counted again for the next.
  const available = new Map(input.cart.lines.map((line) => [line.id, line.quantity]));
  /** @param {string} variantId */
  const lineFor = (variantId) =>
    input.cart.lines.find(
      (line) => line.merchandise.__typename === "ProductVariant" && line.merchandise.id === variantId && (available.get(line.id) ?? 0) > 0,
    );

  const operations = [];
  for (const bundle of bundles) {
    // A variant listed twice would be merged from the same line twice; put the total in one component instead.
    const variantIds = bundle.components.map((component) => component.variantId);
    if (new Set(variantIds).size !== variantIds.length) continue;
    const lines = bundle.components.map((component) => lineFor(component.variantId));
    if (lines.some((line) => line === undefined)) continue;
    // How many complete bundles the cart holds.
    const sets = Math.min(
      ...bundle.components.map((component, i) => Math.floor((available.get(lines[i]?.id ?? "") ?? 0) / component.quantity)),
    );
    if (sets < 1) continue;

    const cartLines = bundle.components.map((component, i) => {
      const cartLineId = lines[i]?.id ?? "";
      const quantity = component.quantity * sets;
      available.set(cartLineId, (available.get(cartLineId) ?? 0) - quantity);
      return { cartLineId, quantity };
    });
    operations.push({
      linesMerge: {
        cartLines,
        parentVariantId: bundle.parentVariantId,
        ...(bundle.title ? { title: bundle.title } : {}),
        ...(bundle.percentageDecrease ? { price: { percentageDecrease: { value: String(bundle.percentageDecrease) } } } : {}),
      },
    });
  }

  return { operations };
}
```

## Test girdisi ve beklenen çıktı

### tests/incomplete-set.input.json

```json
{
  "cart": {
    "lines": [
      { "id": "gid://shopify/CartLine/1", "quantity": 1, "merchandise": { "__typename": "ProductVariant", "id": "gid://shopify/ProductVariant/1111111111" } },
      { "id": "gid://shopify/CartLine/2", "quantity": 1, "merchandise": { "__typename": "ProductVariant", "id": "gid://shopify/ProductVariant/2222222222" } }
    ]
  },
  "cartTransform": {
    "metafield": {
      "jsonValue": {
        "bundles": [
          {
            "parentVariantId": "gid://shopify/ProductVariant/9999999999",
            "title": "Morning Routine Set",
            "percentageDecrease": 10,
            "components": [
              { "variantId": "gid://shopify/ProductVariant/1111111111", "quantity": 1 },
              { "variantId": "gid://shopify/ProductVariant/2222222222", "quantity": 2 }
            ]
          }
        ]
      }
    }
  }
}
```

### tests/incomplete-set.output.json

```json
{ "operations": [] }
```

### tests/not-configured.input.json

```json
{
  "cart": {
    "lines": [
      { "id": "gid://shopify/CartLine/1", "quantity": 1, "merchandise": { "__typename": "ProductVariant", "id": "gid://shopify/ProductVariant/1111111111" } }
    ]
  },
  "cartTransform": { "metafield": null }
}
```

### tests/not-configured.output.json

```json
{ "operations": [] }
```

### tests/two-complete-sets.input.json

```json
{
  "cart": {
    "lines": [
      { "id": "gid://shopify/CartLine/1", "quantity": 3, "merchandise": { "__typename": "ProductVariant", "id": "gid://shopify/ProductVariant/1111111111" } },
      { "id": "gid://shopify/CartLine/2", "quantity": 4, "merchandise": { "__typename": "ProductVariant", "id": "gid://shopify/ProductVariant/2222222222" } }
    ]
  },
  "cartTransform": {
    "metafield": {
      "jsonValue": {
        "bundles": [
          {
            "parentVariantId": "gid://shopify/ProductVariant/9999999999",
            "title": "Morning Routine Set",
            "percentageDecrease": 10,
            "components": [
              { "variantId": "gid://shopify/ProductVariant/1111111111", "quantity": 1 },
              { "variantId": "gid://shopify/ProductVariant/2222222222", "quantity": 2 }
            ]
          }
        ]
      }
    }
  }
}
```

### tests/two-complete-sets.output.json

```json
{
  "operations": [
    {
      "linesMerge": {
        "cartLines": [
          { "cartLineId": "gid://shopify/CartLine/1", "quantity": 2 },
          { "cartLineId": "gid://shopify/CartLine/2", "quantity": 4 }
        ],
        "parentVariantId": "gid://shopify/ProductVariant/9999999999",
        "title": "Morning Routine Set",
        "price": { "percentageDecrease": { "value": "10" } }
      }
    }
  ]
}
```

## Sınırlar ve özel durumlar

- Birden fazla tam set, adedi yüksek tek bir satırda birleşir; artan adetler normal satır olarak kalır.
- Paket fiyatı, bileşen fiyatlarının toplamından yüzde indirim düşülerek bulunur; linesMerge’de fiyat ayarı bu toplama göre yapılır.
- Satırda satış planı varsa ya da satırdaki adet birleştirmenin istediğinden azsa Shopify birleştirmeyi reddeder.
- Bir pakette kullanılan satır, listedeki sonraki paket için yeniden sayılmaz.
- Her bileşen farklı bir varyant olmalı ve bir set, her varyant için tek bir sepet satırından sayılır; aynı varyantın farklı satırlara bölünmüş adetleri (örneğin farklı satır özellikleriyle eklenmişse) toplanmaz.

## Sorular

### linesMerge ile lineExpand arasındaki fark nedir?

linesMerge, müşterinin ayrı ayrı eklediği satırları tek bir paket satırında birleştirir. lineExpand tersini yapar: müşteri tek bir paket ürünü ekler, checkout bileşenlerini gösterir.

### Siparişte bileşen ürünler yer alıyor mu?

Evet. Shopify’ın paket dokümanına göre sipariş satırları, paket ana ürününe referansla bileşenleri döndürür.

### Paket görselini değiştirebilir miyim?

Evet. linesMerge bir görsel kabul eder; ancak adres Shopify’ın CDN’inde olmalıdır, yoksa işlem invalid_image_url hatasıyla başarısız olur.

## Shopify belgeleri

- [Cart Transform Function API](https://shopify.dev/docs/api/functions/latest/cart-transform)

## İlgili içerikler

- [Shopify’da Cart Transform ile ürün paketleri: Plus ne zaman gerekir?](https://lintelagency.com/tr/blog/shopify-paketleri-ve-cart-transform)
- [Paketi bileşenlerine ayırma](https://lintelagency.com/tr/kaynaklar/shopify-functions/paketi-bilesenlerine-ayirma)
- [Function’ları metafield (meta alan) ile yapılandırmak: kuralı yeni sürüm yayınlamadan değiştirin](https://lintelagency.com/tr/blog/shopify-functions-metafield-ile-yapilandirma)

Lisans: MIT. Kendi mağazanızda da müşteri işlerinizde de ücretsiz kullanabilirsiniz.
