Представьте: нужно внедрить сложную систему скидок для оптовиков, интегрироваться с 1С и обеспечить работу в 10 странах. Готовые SaaS-решения не подходят — лицензии дороги (например, commercetools берет существенную плату с оборота), а кастомизация ограничена API. Мы, команда с 10-летним опытом в e-commerce, часто сталкиваемся с такими задачами и предлагаем Vendure — open-source фреймворк на NestJS и TypeScript. Он дает полный контроль: от схемы БД до GraphQL-резолверов. Экономия на лицензиях по сравнению с SaaS может составлять до 70% при оборотах от $500k в год. В одном из проектов мы заменили commercetools на Vendure для крупного b2b-магазина. Результат — экономия $15k в месяц и возможность реализовать специфическую логику расчета налогов без костылей.
Какие проблемы решаем?
Мультитенантность. Когда нужно вести несколько магазинов на одном ядре (разные регионы, бренды), Vendure предлагает механизм Channels. Каждый канал изолирует каталог, цены, валюту, налоги и платёжные методы. Один инстанс легко обслуживает десятки магазинов с раздельными сущностями. Переключение происходит через заголовок vendure-token в запросах к Shop API. Типичная ошибка новичков — не указать токен, тогда запрос обрабатывается в канале по умолчанию, что ведет к путанице.
Гибкая логика налогов и доставки. Стандартные решения не всегда покрывают требования. Vendure позволяет подменять TaxCalculationStrategy и ShippingCalculator через плагины. Мы реализовали кастомный расчет налогов для товаров с разными ставками в зависимости от региона и типа покупателя.
Интеграция с платежными системами. Из коробки — Stripe. Через кастомные обработчики можно подключить любую систему с API. Мы сделали интеграцию с YooKassa с полным циклом: создание платежа, подтверждение, возврат. Код обработчика — в разделе ниже.
Как мы это делаем: конфигурация и кастомные плагины
Стандартная структура проекта включает каталог 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 | 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 недели после релиза — исправление багов, помощь в деплое обновлений.
Мы обладаем многолетним опытом внедрения Vendure и десятками успешных проектов — от магазинов одежды до b2b-порталов с тысячами позиций. Получите консультацию: оценим ваш проект и предложим архитектуру под ключ. Свяжитесь с нами для предварительной оценки стоимости и сроков.
Подробнее о возможностях Vendure читайте в официальной документации Vendure на GitHub.







