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.

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

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.

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
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
query CartTransformRunInput {
  cart {
    lines {
      id
      quantity
      merchandise {
        __typename
        ... on ProductVariant {
          id
        }
      }
    }
  }
  cartTransform {
    metafield(namespace: "$app", key: "function-configuration") {
      jsonValue
    }
  }
}
src/cart_transform_run.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
{
  "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
{ "operations": [] }
tests/not-configured.input.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
{ "operations": [] }
tests/two-complete-sets.input.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
{
  "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" } }
      }
    }
  ]
}

Lintel tarafından yazıldı. MIT lisansı: kendi mağazanızda da müşteri işlerinizde de ücretsiz kullanabilirsiniz.

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.

Sık sorulan 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.
  • Function API sürümü 2026-10 için yazıldı ve gösterilen tarihte Shopify’ın başvuru belgeleriyle karşılaştırıldı. Shopify her çeyrekte yeni bir sürüm yayımlar.
  • Function içeren özel (custom) uygulamalar yalnızca Shopify Plus mağazalarında çalışır. Aynı Function herkese açık bir uygulamada, sayfada aksi yazmıyorsa her planda çalışır.
  • Her örnek test verileriyle gelir ve yayımlamadan önce bu testleri çalıştırırız. Kodu değiştirdikten sonra testleri tekrar çalıştırın: shopify app function run.
  • MIT lisansı. Kodu kendi mağazanızda da müşteri işlerinizde de kullanabilirsiniz.

Mağazanız için kurulmasını, test edilmesini ve bakımını isterseniz: Shopify Functions geliştirme.

Geliştirmek istediğiniz bir şey mi var? Gelin, birlikte konuşalım.

Aynı anda sınırlı sayıda proje alıyoruz.

Gencer KarakayaShopify mühendisi

Proje özetinizi bir mühendis okur; size e-postayla yanıt veririz.