Adede göre kademeli indirim

Müşteri bir üründen 3, 5 ya da 10 adet aldıkça %5, %10 veya %15 indirim veren Shopify indirim Function’ı. Kademeler metafield’da; test edilmiş JavaScript.

Function API
Discount
Hedef (target)
cart.lines.discounts.generate.run
İndirim sınıfı
PRODUCT
Plan
Herkese açık uygulamada her plan; özel uygulamada Shopify Plus
Uygulama yetkisi
write_discounts
API sürümü
2026-10
Son kontrol

Kısaca

Bu indirim Function’ı, alınan adet arttıkça büyüyen bir yüzde indirim uygular: varsayılan olarak 3 adetten itibaren %5, 5’ten %10, 10’dan %15. Ürünün tüm varyantları birlikte sayılır. Kademeler bir JSON metafield’da durur. Discount Function API’nin cart.lines.discounts.generate.run hedefini ürün indirim sınıfıyla (PRODUCT) kullanır, sürüm 2026-10.

Nasıl kullanılır

  1. Shopify CLI ile bir uygulama oluşturun ve uzantıyı ekleyin: shopify app generate extension --template discount, dil olarak JavaScript.
  2. Oluşturulan shopify.extension.toml ve src/ dosyalarını aşağıdakilerle değiştirin. Bu örnek yalnızca sepet satırları hedefini kullanır.
  3. Testleri çalıştırın: shopify app function run --input tests/two-tiers.input.json.
  4. shopify app deploy ile deploy edin ve discountAutomaticAppCreate ile discountClasses: [PRODUCT] olan otomatik bir indirim oluşturun.
  5. İsterseniz kendi kademelerinizi indirimin $app:function-configuration metafield’ına yazın: {"tiers": [{"minQuantity": 2, "percentage": 5}]}.

Kod

shopify.extension.toml
api_version = "2026-10"

[[extensions]]
name = "Tiered volume discount"
handle = "tiered-volume-discount"
type = "function"

  [[extensions.targeting]]
  target = "cart.lines.discounts.generate.run"
  input_query = "src/cart_lines_discounts_generate_run.graphql"
  export = "cart-lines-discounts-generate-run"

  [extensions.build]
  command = ""
  path = "dist/function.wasm"
src/cart_lines_discounts_generate_run.graphql
query CartLinesDiscountsGenerateRunInput {
  cart {
    lines {
      id
      quantity
      merchandise {
        __typename
        ... on ProductVariant {
          product {
            id
          }
        }
      }
    }
  }
  discount {
    discountClasses
    metafield(namespace: "$app", key: "function-configuration") {
      jsonValue
    }
  }
}
src/cart_lines_discounts_generate_run.js
// @ts-check

/**
 * @typedef {import("../generated/api").CartLinesDiscountsGenerateRunInput} CartLinesDiscountsGenerateRunInput
 * @typedef {import("../generated/api").CartLinesDiscountsGenerateRunResult} CartLinesDiscountsGenerateRunResult
 */

/** Settings stored as JSON in the discount's metafield. */
const DEFAULT_CONFIG = {
  // Quantity of one product (all its variants together) → percentage off that product.
  tiers: [
    { minQuantity: 3, percentage: 5 },
    { minQuantity: 5, percentage: 10 },
    { minQuantity: 10, percentage: 15 },
  ],
};

/** @type {CartLinesDiscountsGenerateRunResult} */
const NO_DISCOUNT = { operations: [] };

/**
 * @param {CartLinesDiscountsGenerateRunInput} input
 * @returns {CartLinesDiscountsGenerateRunResult}
 */
export function cartLinesDiscountsGenerateRun(input) {
  // A Function may only return operations for the classes the discount was created with.
  if (!input.discount.discountClasses.includes("PRODUCT")) return NO_DISCOUNT;

  const config = { ...DEFAULT_CONFIG, ...input.discount.metafield?.jsonValue };
  const tiers = [...config.tiers].sort((a, b) => b.minQuantity - a.minQuantity);

  /** @type {Map<string, {quantity: number, lineIds: string[]}>} */
  const products = new Map();
  for (const line of input.cart.lines) {
    if (line.merchandise.__typename !== "ProductVariant") continue;
    const entry = products.get(line.merchandise.product.id) ?? { quantity: 0, lineIds: [] };
    entry.quantity += line.quantity;
    entry.lineIds.push(line.id);
    products.set(line.merchandise.product.id, entry);
  }

  const candidates = [];
  for (const { quantity, lineIds } of products.values()) {
    const tier = tiers.find((t) => quantity >= t.minQuantity);
    if (!tier) continue;
    candidates.push({
      message: `${tier.percentage}% off ${tier.minQuantity}+`,
      targets: lineIds.map((id) => ({ cartLine: { id } })),
      value: { percentage: { value: String(tier.percentage) } },
    });
  }
  if (candidates.length === 0) return NO_DISCOUNT;

  // ALL applies every product's candidate; FIRST would discount only one product.
  return { operations: [{ productDiscountsAdd: { candidates, selectionStrategy: "ALL" } }] };
}
Test girdisi ve beklenen çıktı
tests/below-first-tier.input.json
{
  "cart": {
    "lines": [
      { "id": "gid://shopify/CartLine/1", "quantity": 2, "merchandise": { "__typename": "ProductVariant", "product": { "id": "gid://shopify/Product/1234567890" } } }
    ]
  },
  "discount": { "discountClasses": ["PRODUCT"], "metafield": null }
}
tests/below-first-tier.output.json
{ "operations": [] }
tests/order-class-only.input.json
{
  "cart": {
    "lines": [
      { "id": "gid://shopify/CartLine/1", "quantity": 12, "merchandise": { "__typename": "ProductVariant", "product": { "id": "gid://shopify/Product/1234567890" } } }
    ]
  },
  "discount": { "discountClasses": ["ORDER"], "metafield": null }
}
tests/order-class-only.output.json
{ "operations": [] }
tests/two-tiers.input.json
{
  "cart": {
    "lines": [
      { "id": "gid://shopify/CartLine/1", "quantity": 2, "merchandise": { "__typename": "ProductVariant", "product": { "id": "gid://shopify/Product/1234567890" } } },
      { "id": "gid://shopify/CartLine/2", "quantity": 4, "merchandise": { "__typename": "ProductVariant", "product": { "id": "gid://shopify/Product/1234567890" } } },
      { "id": "gid://shopify/CartLine/3", "quantity": 3, "merchandise": { "__typename": "ProductVariant", "product": { "id": "gid://shopify/Product/1234567891" } } },
      { "id": "gid://shopify/CartLine/4", "quantity": 1, "merchandise": { "__typename": "ProductVariant", "product": { "id": "gid://shopify/Product/1234567892" } } }
    ]
  },
  "discount": { "discountClasses": ["PRODUCT"], "metafield": null }
}
tests/two-tiers.output.json
{
  "operations": [
    {
      "productDiscountsAdd": {
        "candidates": [
          {
            "message": "10% off 5+",
            "targets": [
              { "cartLine": { "id": "gid://shopify/CartLine/1" } },
              { "cartLine": { "id": "gid://shopify/CartLine/2" } }
            ],
            "value": { "percentage": { "value": "10" } }
          },
          {
            "message": "5% off 3+",
            "targets": [{ "cartLine": { "id": "gid://shopify/CartLine/3" } }],
            "value": { "percentage": { "value": "5" } }
          }
        ],
        "selectionStrategy": "ALL"
      }
    }
  ]
}

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

  • Function discount.discountClasses’ı kontrol eder ve indirimde PRODUCT sınıfı yoksa hiçbir şey döndürmez; başka bir sınıfın işlemlerini döndürmeye izin verilmez.
  • selectionStrategy ALL her ürünün kademesini uygular. FIRST ile yalnızca tek bir aday kullanılırdı.
  • Diğer indirimlerle birleşip birleşmeyeceğini bu kod değil, indirimin birleştirme ayarları belirler.
  • Bir mağazada en fazla 25 indirim Function’ı etkin olabilir; hepsi aynı anda çalışır ve birbirini görmez.

Sık sorulan sorular

Shopify’da adede göre kademeli indirim nasıl yapılır?
Bu örnekteki gibi bir indirim Function’ı kullanın: tek bir indirim tüm kademeleri taşır ve kademeler, deploy etmeden değiştirebileceğiniz bir metafield’da durur.
Aynı ürünün varyantları birlikte sayılıyor mu?
Evet. Adetler ürün kimliğine (product ID) göre toplanır; aynı tişörtün iki bedeni aynı kademeye sayılır.
Kademeler ürün yerine koleksiyon bazında olabilir mi?
Evet. Satırları ürün kimliği yerine bir koleksiyon kontrolüyle (inAnyCollection) gruplayın; koleksiyon kimlikleri için input değişkenlerini kullanın.
  • 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.