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

Sayfa: https://lintelagency.com/tr/kaynaklar/shopify-functions/adede-gore-kademeli-indirim

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

## Bilgiler

- **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:** 2 Kasım 2026

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

```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

```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

```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

```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

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

### tests/order-class-only.input.json

```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

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

### tests/two-tiers.input.json

```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

```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"
      }
    }
  ]
}
```

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

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

## Shopify belgeleri

- [Discount Function API](https://shopify.dev/docs/api/functions/latest/discount)

## İlgili içerikler

- [Bir indirim Function’ını otomatik indirim olarak etkinleştirme](https://lintelagency.com/tr/kaynaklar/graphql/function-tabanli-otomatik-indirim)
- [Metafield ile ayarlanan tek bir indirim Function’ı](https://lintelagency.com/tr/kaynaklar/shopify-functions/metafield-ile-ayarlanan-indirim)
- [Shopify Scripts çalışmayı bıraktı. Her script’in mantığı artık nereye gidiyor?](https://lintelagency.com/tr/blog/shopify-scriptsten-functionsa-gecis)
- [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.
