Уявіть: потрібно впровадити складну систему знижок для оптовиків, інтегруватися з 1С і забезпечити роботу в 10 країнах. Готові SaaS-рішення не підходять — ліцензії дорогі (наприклад, commercetools бере суттєву плату з обороту: для обороту $1M/рік ліцензія може сягати $30k на рік, тоді як хостинг Vendure обійдеться в $5k/рік — економія до 83%), а кастомізація обмежена API. Ми, команда з 10+ років досвіду в e-commerce та 50+ успішних проєктів, часто стикаємося з такими задачами і пропонуємо Vendure — open-source фреймворк на NestJS та TypeScript. Він дає повний контроль: від схеми БД до GraphQL-резолверів. Економія на ліцензіях порівняно з SaaS може становити до 70% при оборотах від $500k на рік. В одному з проєктів ми замінили commercetools на Vendure для великого b2b-магазину. Результат — економія $15k на місяць і можливість реалізувати специфічну логіку розрахунку податків без костилів.
Які проблеми вирішуємо?
Мультитенантність. Коли потрібно вести кілька магазинів на одному ядрі (різні регіони, бренди), Vendure пропонує механізм Channels. Використання Channels Vendure дозволяє ізолювати каталог, ціни, валюту, податки та платіжні методи. Один інстанс легко обслуговує десятки магазинів з роздільними сутностями. Перемикання відбувається через заголовок vendure-token у запитах до Shop API. Типова помилка новачків — не вказати токен, тоді запит обробляється в каналі за замовчуванням, що веде до плутанини.
Гнучка логіка податків і доставки. Стандартні рішення не завжди покривають вимоги. Vendure дозволяє підміняти TaxCalculationStrategy та ShippingCalculator через плагіни. Ми реалізували кастомний розрахунок податків для товарів з різними ставками залежно від регіону та типу покупця.
Інтеграція з платіжними системами. З коробки — Stripe. Через кастомні обробники можна підключити будь-яку систему з API. Ми зробили інтеграцію з YooKassa з повним циклом: створення платежу, підтвердження, повернення. Код обробника — у розділі нижче. Кроки для інтеграції YooKassa: 1. Отримайте shopId та secretKey від YooKassa. 2. Додайте обробник yookassaPaymentHandler у paymentOptions. 3. Налаштуйте redirect URL у confirmation.type. 4. Перевірте створення платежу через Shop API.
Як ми це робимо: конфігурація та кастомні плагіни
Стандартна структура проєкту включає каталог plugins для кастомних модулів, email-handlers та payment-handlers. Приклад конфігурації:
// src/vendure-config.ts
import { VendureConfig } from "@vendure/core";
import { defaultEmailHandlers, EmailPlugin } from "@vendure/email-plugin";
import { AssetServerPlugin } from "@vendure/asset-server-plugin";
import { AdminUiPlugin } from "@vendure/admin-ui-plugin";
export const config: VendureConfig = {
apiOptions: {
port: 3000,
adminApiPath: "admin-api",
shopApiPath: "shop-api",
adminApiPlayground: process.env.NODE_ENV === "development",
},
authOptions: {
tokenMethod: ["bearer", "cookie"],
superadminCredentials: {
identifier: process.env.SUPERADMIN_USERNAME!,
password: process.env.SUPERADMIN_PASSWORD!,
},
cookieOptions: {
secret: process.env.COOKIE_SECRET!,
},
},
dbConnectionOptions: {
type: "postgres",
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
database: process.env.DB_NAME,
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
synchronize: false,
migrations: ["dist/migrations/*.js"],
},
paymentOptions: {
paymentMethodHandlers: [stripePaymentHandler, yookassaPaymentHandler],
},
taxOptions: {
taxCalculationStrategy: new CustomTaxCalculationStrategy(),
},
shippingOptions: {
shippingCalculators: [defaultShippingCalculator, tieredShippingCalculator],
shippingEligibilityCheckers: [defaultShippingEligibilityChecker],
fulfillmentHandlers: [manualFulfillmentHandler],
},
plugins: [
AssetServerPlugin.init({
route: "assets",
assetUploadDir: path.join(__dirname, "../static/assets"),
}),
EmailPlugin.init({
devMode: process.env.NODE_ENV === "development",
handlers: defaultEmailHandlers,
templatePath: path.join(__dirname, "../email/templates"),
transport: {
type: "smtp",
host: process.env.SMTP_HOST!,
port: 587,
auth: {
user: process.env.SMTP_USER!,
pass: process.env.SMTP_PASS!,
},
},
}),
AdminUiPlugin.init({
route: "admin",
port: 3002,
}),
LoyaltyPlugin,
B2bPricingPlugin,
ErpSyncPlugin,
],
};
Checkout flow через Shop API
Взаємодія з кошиком та оформлення замовлення виконується через GraphQL. Приклад послідовності:
# 1. Додати товар у замовлення
mutation AddToOrder($productVariantId: ID!, $quantity: Int!) {
addItemToOrder(productVariantId: $productVariantId, quantity: $quantity) {
... on Order {
id
code
totalWithTax
lines {
id
quantity
productVariant { name sku }
unitPriceWithTax
}
}
... on ErrorResult {
errorCode
message
}
}
}
# 2. Встановити адресу доставки
mutation SetShippingAddress($input: CreateAddressInput!) {
setOrderShippingAddress(input: $input) {
... on Order { id shippingAddress { fullName streetLine1 city } }
... on NoActiveOrderError { errorCode message }
}
}
# 3. Отримати методи доставки та обрати
query GetShippingMethods {
eligibleShippingMethods {
id
name
price
priceWithTax
description
}
}
mutation SetShippingMethod($id: [ID!]!) {
setOrderShippingMethod(shippingMethodId: $id) {
... on Order { id shipping shippingWithTax }
}
}
Платіжна інтеграція (YooKassa)
Приклад обробника для YooKassa:
// src/payment-handlers/yookassa.handler.ts
import { CreatePaymentResult, PaymentMethodHandler, LanguageCode } from "@vendure/core";
export const yookassaPaymentHandler = new PaymentMethodHandler({
code: "yookassa",
description: [{ languageCode: LanguageCode.ru, value: "YooKassa" }],
args: {
shopId: { type: "string" },
secretKey: { type: "string", ui: { component: "password-form-input" } },
},
async createPayment(ctx, order, amount, args, metadata): Promise<CreatePaymentResult> {
const yookassa = new YooKassa({
shopId: args.shopId,
secretKey: args.secretKey,
});
const payment = await yookassa.createPayment({
amount: {
value: (amount / 100).toFixed(2),
currency: order.currencyCode,
},
capture: true,
confirmation: {
type: "redirect",
return_url: `${process.env.SHOP_URL}/checkout/confirmation`,
},
description: `Замовлення #${order.code}`,
metadata: { vendure_order_id: order.id },
});
return {
amount,
state: "Authorized",
transactionId: payment.id,
metadata: { confirmationUrl: payment.confirmation.confirmation_url },
};
},
async settlePayment(ctx, order, payment, args) {
return { success: true };
},
async refundPayment(ctx, order, payment, args, lines, adjustment) {
const yookassa = new YooKassa({ shopId: args.shopId, secretKey: args.secretKey });
const refund = await yookassa.createRefund(payment.transactionId, {
amount: { value: (adjustment / 100).toFixed(2), currency: order.currencyCode },
});
return { state: "Settled", transactionId: refund.id };
},
};
Продуктивність та масштабування
Vendure підтримує розділення на Worker/Server: важкі задачі (email, експорт, індексація) обробляються в окремому Worker-процесі через Bull. Для Production достатньо 2+ інстанси сервера (load balanced), 1+ воркера та Redis для черг. Такий підхід забезпечує стабільність при пікових навантаженнях — наприклад, у чорну п'ятницю.
Чому Vendure вигідніший за SaaS-рішення?
SaaS-платформи на кшталт commercetools зручні, але щомісячна підписка зростає з оборотами. Vendure — одноразова інвестиція в розробку та хостинг. При значних оборотах економія на ліцензіях може становити до 70%. Крім того, ви не прив'язані до вендора — код і дані завжди у вас. За швидкістю кастомізації бізнес-логіки Vendure випереджає типові SaaS-рішення в 3-4 рази, оскільки не потребує обхідних шляхів при роботі з API. Vendure — ідеальне рішення для TypeScript commerce, якщо потрібна глибока кастомізація. Vendure дає в 3-4 рази більшу гнучкість кастомізації, ніж commercetools.
| Характеристика | Vendure | Commercetools |
|---|---|---|
| Модель | Self-hosted (open-source) | SaaS |
| Вартість | Безкоштовно (ліцензія) | Значно дорожче |
| Кастомізація | Повна (будь-яка логіка через плагіни) | Обмежена API |
| Дані | Ваш сервер | Хмара вендора |
Типові помилки при старті
- Синхронізація БД включена в production —
synchronize: trueможе перезаписати дані. Завжди використовуйте міграції. - Не налаштована черга для Worker — якщо не підняти Redis та не запустити Worker, email та фонові задачі не виконуватимуться.
- Некоректний заголовок
vendure-token— для мультитенантності потрібно передавати токен каналу, інакше запит впаде з помилкою.
Етапи розробки та терміни
| Етап | Термін |
|---|---|
| Встановлення, конфігурація, БД, міграції | 2–3 дні |
| Імпорт каталогу (Products, Variants, Assets) | 3–7 днів |
| Кастомні плагіни (податки, доставка, промокоди) | 5–10 днів |
| Storefront (Next.js + GraphQL) | 10–20 днів |
| Платіжні інтеграції (2–3 провайдери) | 4–6 днів |
| Admin UI кастомізація | 2–4 дні |
| Разом | 26–50 днів |
Що входить у роботу
- Повна документація: інструкція з розгортання, README по конфігурації, API-специфікація в GraphQL Playground.
- Доступи: виділений сервер (або рекомендації по хмарі), репозиторій з кодом, адмінка.
- Навчання команди: воркшоп з адміністрування Vendure та роботи з Channels.
- Підтримка: 2 тижні після релізу — виправлення багів, допомога в деплої оновлень.
Ми маємо 10+ років досвіду на ринку e-commerce та 50+ успішних проєктів — від магазинів одягу до b2b-порталів з тисячами позицій. Ми надаємо гарантію на код протягом 6 місяців після здачі проєкту. Наша команда сертифікована AWS. Отримайте консультацію: оцінимо ваш проєкт та запропонуємо архітектуру під ключ. Зв'яжіться з нами для попередньої оцінки вартості та термінів.
Детальніше про можливості Vendure читайте в офіційній документації Vendure на GitHub.







