Налаштування Shopify Functions для кастомної логіки знижок/доставки

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Налаштування Shopify Functions для кастомної логіки знижок/доставки
Складна
~5 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Налаштування Shopify Functions для користувацької логіки знижок/доставки

Shopify Functions — механізм запуску користувацького коду безпосередньо в інфраструктурі Shopify. На відміну від додатків з webhook, Functions виконуються синхронно у процесі чекауту: Shopify викликає функцію, отримує відповідь та застосовує результат.

Де працюють Functions

API функцій Призначення
cart_transform Змінення рядків кошика перед чекаутом (бандли, модифікації)
discounts Користувацькі знижки (фіксовані, відсоток, BXGY)
payment_customization Приховування/перейменування способів оплати
shipping_discount Знижка на доставку
delivery_customization Приховування/перейменування/сортування методів доставки
fulfillment_constraints Обмеження на розбивку за складами
order_routing Маршрутизація замовлень за складами

Functions пишуться на Rust (офіційна мова, компілюється в WebAssembly) або JavaScript/TypeScript (через Javy runtime). Rust переважніший для складної логіки — швидше та економніше за лімітами.

Установка та ініціалізація

# Functions створюються всередині Shopify App
shopify app generate extension --template discount --name custom-volume-discount
cd extensions/custom-volume-discount

Структура extension:

extensions/custom-volume-discount/
├── src/
│   └── run.ts        # (або run.rs для Rust)
├── input.graphql     # Описання вхідних даних
├── shopify.extension.toml
└── package.json

Приклад: скидка за обсяг замовлення (JavaScript)

input.graphql — описує, які дані Shopify передасть функції:

# input.graphql
query RunInput {
  cart {
    lines {
      id
      quantity
      merchandise {
        ... on ProductVariant {
          id
          product {
            id
            tags
            metafield(namespace: "custom", key: "volume_discount_tier") {
              value
            }
          }
        }
      }
    }
  }
  discountNode {
    metafield(namespace: "custom", key: "tiers") {
      value
    }
  }
}

src/run.ts — логіка функції:

import type { RunInput, FunctionRunResult } from "../generated/api";

interface DiscountTier {
  quantity: number;
  percentage: number;
}

export function run(input: RunInput): FunctionRunResult {
  // Читаємо конфіг скидок з метаполя discount node
  const tiersJson = input.discountNode?.metafield?.value;
  const tiers: DiscountTier[] = tiersJson ? JSON.parse(tiersJson) : [];

  if (!tiers.length) return { discounts: [], discountApplicationStrategy: "FIRST" };

  const discounts = [];

  for (const line of input.cart.lines) {
    const variant = line.merchandise;
    if (variant.__typename !== "ProductVariant") continue;

    const product = variant.product;
    // Застосовуємо скидку тільки до товарів з тегом 'volume-eligible'
    if (!product.tags.includes("volume-eligible")) continue;

    // Знаходимо підходящий tier за кількістю
    const applicableTier = tiers
      .filter(t => line.quantity >= t.quantity)
      .sort((a, b) => b.quantity - a.quantity)[0];

    if (!applicableTier) continue;

    discounts.push({
      targets: [{ cartLine: { id: line.id } }],
      value: { percentage: { value: String(applicableTier.percentage) } },
      message: `Знижка ${applicableTier.percentage}% за кількість (від ${applicableTier.quantity} шт.)`,
    });
  }

  return {
    discounts,
    discountApplicationStrategy: "FIRST",
  };
}

Приклад: користувацька логіка доставки (Rust)

Сховати методи доставки залежно від вмісту кошика:

// src/run.rs
use shopify_function::prelude::*;
use shopify_function::Result;

#[shopify_function_target(query_path = "src/run.graphql", schema_path = "schema.graphql")]
fn run(input: input::ResponseData) -> Result<output::FunctionRunResult> {
    let cart_weight_grams: i64 = input.cart.lines.iter()
        .map(|line| {
            let variant = &line.merchandise;
            let weight = variant.weight.unwrap_or(0.0);
            (weight * 1000.0 * line.quantity as f64) as i64
        })
        .sum();

    let operations = input.cart.delivery_groups.iter()
        .flat_map(|group| group.delivery_options.iter().map(|opt| {
            // Приховати «Експрес-доставку» якщо посилка >20кг
            let hide = opt.title.contains("Експрес") && cart_weight_grams > 20_000;
            output::DeliveryCustomizationOperation {
                hide: if hide {
                    Some(output::HideOperation { delivery_option_handle: opt.handle.clone() })
                } else {
                    None
                },
                move_: None,
                rename: None,
            }
        }))
        .collect();

    Ok(output::FunctionRunResult { operations })
}

Конфігурація функції в TOML

# shopify.extension.toml
api_version = "2025-01"

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

[extensions.targeting]
  module = "./src/run.ts"

[[extensions.targeting]]
  target = "purchase.product-discount.run"

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

Активація через Shopify Admin

Functions не працюють автоматично після деплою — потрібно створити Discount (або Customization) в Admin та вибрати функцію як джерело логіки:

// Програмне створення скидки через Admin GraphQL API
const CREATE_DISCOUNT = `
  mutation discountAutomaticAppCreate($automaticAppDiscount: DiscountAutomaticAppInput!) {
    discountAutomaticAppCreate(automaticAppDiscount: $automaticAppDiscount) {
      automaticAppDiscount {
        discountId
        title
        startsAt
      }
      userErrors { field message }
    }
  }
`;

await adminClient.query({
  data: {
    query: CREATE_DISCOUNT,
    variables: {
      automaticAppDiscount: {
        title: "Обсяжна знижка",
        functionId: "gid://shopify/ShopifyFunction/abc123",
        startsAt: new Date().toISOString(),
        metafields: [{
          namespace: "custom",
          key: "tiers",
          type: "json",
          value: JSON.stringify([
            { quantity: 5, percentage: 5 },
            { quantity: 10, percentage: 10 },
            { quantity: 20, percentage: 15 }
          ])
        }]
      }
    }
  }
});

Обмеження Functions

  • Ліміт часу виконання: 5 мс для Rust/WASM, 10 мс для JS
  • Ліміт пам'яті: 10 МБ
  • Немає мережевих запитів: функція не може робити HTTP-виклики — всі дані повинні прийти через input.graphql
  • Немає доступу до БД: складну конфігурацію передаємо через Metafields у запиті вхідних даних
  • Checkout UI Extensions: візуальний UI для чекауту — окремий механізм (не Functions)

Через ліміти на час виконання потрібно максимально спрощувати всю бізнес-логіку. Важкі обчислення перекладаються на підготовчий етап — результат кешується в Metafields та передається у функцію як готові дані.

Тестування

# Запуск тестів функції локально
npm run test  # або cargo test

# Деплой extension до додатку
shopify app deploy

# Перегляд логів виконання (через Shopify Partners)
shopify app logs

Строки

Простої функції знижки (відсоток за тегом або колекцією): 2–4 дні. Складна багаторівнева функція з конфігурацією через метаполя та Admin UI для управління правилами: 1–2 тижні. Функція доставки з геологікою та інтеграцією зовнішніх тарифів (через метаполя): 1–2 тижні.