# Metafield ile ayarlanan tek bir indirim Function’ı

> Ürün ve sipariş indirimleri her indirimde ayrı bir JSON metafield’dan okunan Shopify indirim Function’ı; tek deploy ile birçok kampanya.

Sayfa: https://lintelagency.com/tr/kaynaklar/shopify-functions/metafield-ile-ayarlanan-indirim

## Kısaca

Bu indirim Function’ı tüm kampanyasını her indirimdeki bir JSON metafield’dan okur: seçili koleksiyonlardaki ürünlere yüzde indirim, belirli bir tutarın üstündeki siparişlere sabit indirim ya da ikisi birden. Tek bir deploy birçok indirime hizmet eder. Discount Function API’nin cart.lines.discounts.generate.run hedefini kullanır (2026-10) ve her indirim için yalnızca o indirimin sınıflarını döndürür.

## Bilgiler

- **Function API:** Discount
- **Hedef (target):** cart.lines.discounts.generate.run
- **İndirim sınıfları:** PRODUCT, ORDER
- **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 4.9.0 ya da üstüyle 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.
3. Testleri çalıştırın: shopify app function run --input tests/product-and-order.input.json.
4. Deploy edin, ardından her kampanyayı discountAutomaticAppCreate (kodlu indirim için discountCodeAppCreate) ile oluşturup JSON’unu metafields içinde gönderin.
5. Her indirime yalnızca kullandığı sınıfları verin; örneğin koleksiyon indirimi için discountClasses: [PRODUCT].

## Kod

### shopify.extension.toml

```toml
api_version = "2026-10"

[[extensions]]
name = "Discount configured by metafield"
handle = "metafield-configured-discount"
type = "function"

  [[extensions.targeting]]
  target = "cart.lines.discounts.generate.run"
  input_query = "src/cart_lines_discounts_generate_run.graphql"
  input_variables = { namespace = "$app", key = "function-configuration" }
  export = "cart-lines-discounts-generate-run"

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

### src/cart_lines_discounts_generate_run.graphql

```graphql
query CartLinesDiscountsGenerateRunInput($collectionIds: [ID!]) {
  cart {
    lines {
      id
      merchandise {
        __typename
        ... on ProductVariant {
          product {
            inAnyCollection(ids: $collectionIds)
          }
        }
      }
    }
  }
  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
 */

/**
 * One Function, many campaigns: each discount created from it carries its own
 * JSON in the $app:function-configuration metafield. `collectionIds` in that
 * JSON also feeds the query variable (see input_variables in the toml).
 * Missing parts switch that part off.
 */
const DEFAULT_CONFIG = {
  // { percentage, message }; applies to products in collectionIds, or to all products when the list is empty.
  product: /** @type {{percentage: number, message?: string} | null} */ (null),
  collectionIds: /** @type {string[]} */ ([]),
  // { amount, minimumSubtotal, message }; a fixed amount off orders above minimumSubtotal (store currency).
  order: /** @type {{amount: number, minimumSubtotal?: number, message?: string} | null} */ (null),
};

/**
 * @param {CartLinesDiscountsGenerateRunInput} input
 * @returns {CartLinesDiscountsGenerateRunResult}
 */
export function cartLinesDiscountsGenerateRun(input) {
  const config = { ...DEFAULT_CONFIG, ...input.discount.metafield?.jsonValue };
  const classes = input.discount.discountClasses;
  const operations = [];

  // A Function may only return operations for the classes the discount was created with.
  if (config.product && classes.includes("PRODUCT")) {
    const targets = input.cart.lines
      .filter(
        (line) =>
          line.merchandise.__typename === "ProductVariant" &&
          (config.collectionIds.length === 0 || line.merchandise.product.inAnyCollection),
      )
      .map((line) => ({ cartLine: { id: line.id } }));
    if (targets.length > 0) {
      operations.push({
        productDiscountsAdd: {
          candidates: [
            {
              message: config.product.message ?? `${config.product.percentage}% off`,
              targets,
              value: { percentage: { value: String(config.product.percentage) } },
            },
          ],
          selectionStrategy: "FIRST",
        },
      });
    }
  }

  if (config.order && classes.includes("ORDER")) {
    // Shopify checks the minimum itself (in the store's currency), so the
    // candidate is offered and only applied when the condition holds.
    const conditions = config.order.minimumSubtotal
      ? [{ orderMinimumSubtotal: { minimumAmount: String(config.order.minimumSubtotal), excludedCartLineIds: [] } }]
      : [];
    operations.push({
      orderDiscountsAdd: {
        candidates: [
          {
            message: config.order.message ?? `${config.order.amount} off your order`,
            conditions,
            targets: [{ orderSubtotal: { excludedCartLineIds: [] } }],
            value: { fixedAmount: { amount: String(config.order.amount) } },
          },
        ],
        selectionStrategy: "FIRST",
      },
    });
  }

  return { operations };
}
```

## Test girdisi ve beklenen çıktı

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

```json
{
  "cart": { "lines": [
      { "id": "gid://shopify/CartLine/1", "merchandise": { "__typename": "ProductVariant", "product": { "inAnyCollection": true } } },
      { "id": "gid://shopify/CartLine/2", "merchandise": { "__typename": "ProductVariant", "product": { "inAnyCollection": false } } }
    ] },
  "discount": { "discountClasses": ["PRODUCT", "ORDER"], "metafield": null }
}
```

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

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

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

```json
{
  "cart": { "lines": [
      { "id": "gid://shopify/CartLine/1", "merchandise": { "__typename": "ProductVariant", "product": { "inAnyCollection": true } } },
      { "id": "gid://shopify/CartLine/2", "merchandise": { "__typename": "ProductVariant", "product": { "inAnyCollection": false } } }
    ] },
  "discount": {
    "discountClasses": ["ORDER"],
    "metafield": { "jsonValue": {
        "product": { "percentage": 20, "message": "20% off summer collection" },
        "collectionIds": ["gid://shopify/Collection/1234567890"],
        "order": { "amount": 50, "minimumSubtotal": 500, "message": "50 off orders over 500" }
      } }
  }
}
```

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

```json
{
  "operations": [
    {
      "orderDiscountsAdd": {
        "candidates": [
          {
            "message": "50 off orders over 500",
            "conditions": [{ "orderMinimumSubtotal": { "minimumAmount": "500", "excludedCartLineIds": [] } }],
            "targets": [{ "orderSubtotal": { "excludedCartLineIds": [] } }],
            "value": { "fixedAmount": { "amount": "50" } }
          }
        ],
        "selectionStrategy": "FIRST"
      }
    }
  ]
}
```

### tests/product-and-order.input.json

```json
{
  "cart": { "lines": [
      { "id": "gid://shopify/CartLine/1", "merchandise": { "__typename": "ProductVariant", "product": { "inAnyCollection": true } } },
      { "id": "gid://shopify/CartLine/2", "merchandise": { "__typename": "ProductVariant", "product": { "inAnyCollection": false } } }
    ] },
  "discount": {
    "discountClasses": ["PRODUCT", "ORDER"],
    "metafield": { "jsonValue": {
        "product": { "percentage": 20, "message": "20% off summer collection" },
        "collectionIds": ["gid://shopify/Collection/1234567890"],
        "order": { "amount": 50, "minimumSubtotal": 500, "message": "50 off orders over 500" }
      } }
  }
}
```

### tests/product-and-order.output.json

```json
{
  "operations": [
    {
      "productDiscountsAdd": {
        "candidates": [
          {
            "message": "20% off summer collection",
            "targets": [{ "cartLine": { "id": "gid://shopify/CartLine/1" } }],
            "value": { "percentage": { "value": "20" } }
          }
        ],
        "selectionStrategy": "FIRST"
      }
    },
    {
      "orderDiscountsAdd": {
        "candidates": [
          {
            "message": "50 off orders over 500",
            "conditions": [{ "orderMinimumSubtotal": { "minimumAmount": "500", "excludedCartLineIds": [] } }],
            "targets": [{ "orderSubtotal": { "excludedCartLineIds": [] } }],
            "value": { "fixedAmount": { "amount": "50" } }
          }
        ],
        "selectionStrategy": "FIRST"
      }
    }
  ]
}
```

## Sınırlar ve özel durumlar

- Sipariş minimumu, Shopify’ın mağaza para biriminde kontrol ettiği bir orderMinimumSubtotal koşulu olarak gönderilir.
- collectionIds listesi boşsa ürün indirimi tüm ürünlere uygulanır.
- 10.000 baytı aşan metafield’lar Function’lara gelmez; kampanyaları küçük tutun ya da bölün.

## Sorular

### İndirim Function’ı neden bir metafield ile ayarlanmalı?

Metafield olmadan her değişiklik kod düzenleyip deploy etmek demektir. Metafield ile mağaza sahibi ya da uygulamanızın yönetim sayfası rakamları indirim bazında değiştirir; Function aynı kalır.

### Yapılandırmanın kullandığı bir sınıf indirimde yoksa ne olur?

O kısım atlanır. Function, ürün ya da sipariş işlemi döndürmeden önce discount.discountClasses’ı kontrol eder.

### Mağaza sahibi yapılandırmayı nasıl değiştirir?

GraphQL Admin API’deki metafieldsSet ile ya da uygulamanızın kendi ayar sayfasından. Sık değişiyorsa küçük bir form yapın.

## Shopify belgeleri

- [Discount Function API](https://shopify.dev/docs/api/functions/latest/discount)
- [Variables in input queries](https://shopify.dev/docs/apps/build/functions/input-queries/use-variables-input-queries)

## İlgili içerikler

- [Bir indirim Function’ını otomatik indirim olarak etkinleştirme](https://lintelagency.com/tr/kaynaklar/graphql/function-tabanli-otomatik-indirim)
- [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)
- [Adede göre kademeli indirim](https://lintelagency.com/tr/kaynaklar/shopify-functions/adede-gore-kademeli-indirim)
- [Özel uygulama mı, App Store uygulaması mı? Hangisi ne zaman?](https://lintelagency.com/tr/blog/ozel-uygulama-mi-app-store-uygulamasi-mi)

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