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.
- 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
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.
Nasıl kullanılır
- 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.
- Oluşturulan shopify.extension.toml ve src/ dosyalarını aşağıdakilerle değiştirin.
- Testleri çalıştırın: shopify app function run --input tests/product-and-order.input.json.
- Deploy edin, ardından her kampanyayı discountAutomaticAppCreate (kodlu indirim için discountCodeAppCreate) ile oluşturup JSON’unu metafields içinde gönderin.
- Her indirime yalnızca kullandığı sınıfları verin; örneğin koleksiyon indirimi için discountClasses: [PRODUCT].
Kod
shopify.extension.tomlapi_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.graphqlquery 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// @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{
"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{ "operations": [] }
tests/order-class-only.input.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{
"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{
"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{
"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"
}
}
]
}
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
- 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.
Sık sorulan 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
- 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.
