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
- Paketin ana ürününü bir ürün varyantı olarak oluşturun; birleştirmeden sonra müşterinin gördüğü budur.
- 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.
- Testleri shopify app function run --input tests/two-complete-sets.input.json ile çalıştırın, ardından deploy edin.
- cartTransformCreate ile etkinleştirin ve paketleri $app:function-configuration metafield’ında gönderin.
Kod
shopify.extension.tomlapi_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.graphqlquery 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.
Shopify belgeleri
- 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.
