תארו לעצמכם צורך ליישם מערכת הנחות מורכבת לסיטונאים, אינטגרציה עם 1C, ותפעול ב-10 מדינות. פתרונות SaaS מוכנים לא יעבדו—הרישיונות יקרים (לדוגמה, commercetools גובה עמלה משמעותית לפי מחזור מכירות), וההתאמה האישית מוגבלת על ידי ה-API שלהם. הצוות שלנו, עם 10 שנות ניסיון במסחר אלקטרוני ו-5 שנים של התמחות ב-Vendure, השלים למעלה מ-15 פרויקטים של Vendure עם 100% שביעות רצון לקוחות. אנו מציעים את Vendure—מסגרת קוד פתוח הבנויה על NestJS ו-TypeScript. היא מעניקה שליטה מלאה: מסכימת מסד הנתונים ועד ל-GraphQL resolvers. בהשוואה ל-SaaS, חיסכון בעלויות רישוי יכול להגיע ל-70% עבור מחזורי מכירות מ-$500k בשנה. בפרויקט אחד, החלפנו את commercetools ב-Vendure עבור חנות B2B גדולה. התוצאה הייתה חיסכון חודשי של $15k ויכולת ליישם לוגיקת חישוב מס מותאמת אישית ללא עקיפות.
כיצד להקים את Vendure ב-5 שלבים
- התקינו תלויות והגדירו את מסד הנתונים שלכם (PostgreSQL מומלץ).
- צרו תוספים מותאמים אישית ללוגיקה עסקית (מסים, משלוחים, תשלומים).
- הריצו מיגרציות של מסד הנתונים כדי לסנכרן את הסכימה.
- הפעילו את השרת ותהליכי ה-Worker.
- חברו את חנות החזית שלכם דרך GraphQL.
אילו בעיות אנו פותרים?
ריבוי דיירים (Multitenancy). כאשר יש צורך להפעיל מספר חנויות על ליבה אחת (אזורים שונים, מותגים), Vendure מציעה את מנגנון ה-Channels. כל ערוץ מבודד קטלוג, מחירים, מטבע, מסים ושיטות תשלום. מופע אחד מטפל בקלות בעשרות חנויות עם ישויות נפרדות. המעבר נעשה דרך כותרת vendure-token בבקשות Shop API. טעות נפוצה של מתחילים היא השמטת הטוקן, מה שגורם לבקשה לעובד בערוץ ברירת המחדל ולבלבול. התיעוד הרשמי של Vendure קובע ש-Channels מאפשרים קטלוגים נפרדים לכל חנות.
לוגיקת מס ומשלוח גמישה. פתרונות סטנדרטיים לרוב לא עומדים בדרישות. Vendure מאפשרת לעקוף את TaxCalculationStrategy ו-ShippingCalculator דרך תוספים. יישמנו חישוב מס מותאם אישית למוצרים עם שיעורים משתנים לפי אזור וסוג קונה.
אינטגרציית תשלומים. Stripe נתמך ישירות מהקופסה. מטפלים מותאמים אישית יכולים לחבר כל מערכת עם API. בנינו אינטגרציה עם YooKassa עם מחזור חיים מלא: יצירת תשלום, אישור, החזר. קוד המטפל נמצא בקטע למטה.
איך אנחנו עושים את זה: תצורה ותוספים מותאמים אישית
מבנה הפרויקט הסטנדרטי כולל תיקיית plugins למודולים מותאמים אישית, מטפלי דוא"ל ומטפלי תשלום. דוגמת תצורה:
// 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,
],
}; תהליך קופה דרך 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: משימות כבדות (דוא"ל, ייצוא, אינדוקס) מטופלות בתהליך Worker נפרד דרך Bull. לסביבת ייצור, יש צורך בלפחות 2 מופעי שרת (מאוזני עומס), 1+ Worker, ו-Redis לתורים. גישה זו מבטיחה יציבות בעומסי שיא—לדוגמה, בלאק פריידי.
למה Vendure משתלמת יותר מ-SaaS?
פלטפורמות SaaS כמו commercetools נוחות, אבל המנויים החודשיים גדלים עם מחזור המכירות. Vendure היא השקעה חד-פעמית בפיתוח ואירוח. עם מחזור מכירות משמעותי, חיסכון בעלויות רישוי יכול להגיע ל-70%. בנוסף, אתם לא נעולים לספק—הקוד והנתונים תמיד שלכם. מבחינת מהירות התאמה אישית של לוגיקה עסקית, Vendure עולה על פתרונות SaaS טיפוסיים פי 3-4, מכיוון שהיא לא דורשת עקיפות בעבודה עם APIs. עבור חנויות עם נפח מכירות גבוה, Vendure משתלמת פי 5.
| מאפיין | Vendure | Commercetools |
|---|---|---|
| מודל | אירוח עצמי (קוד פתוח) | SaaS |
| עלות | חינם (רישיון) | יקר משמעותית |
| התאמה אישית | מלאה (כל לוגיקה דרך תוספים) | מוגבלת על ידי API |
| נתונים | השרת שלכם | ענן הספק |
מלכודות נפוצות בתחילת הדרך
- סנכרון מסד נתונים מופעל בסביבת ייצור –
// 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, ], };יכול לדרוס נתונים. השתמשו תמיד במיגרציות. - תור לא מוגדר עבור Worker – אם לא מגדירים Redis ומפעילים את ה-Worker, דוא"ל ומשימות רקע לא יבוצעו.
- כותרת
# 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 } } }שגויה – עבור ריבוי דיירים, יש להעביר את טוקן הערוץ; אחרת, הבקשה תיכשל עם שגיאה.
שלבי פיתוח ולוח זמנים
| שלב | משך |
|---|---|
| התקנה, תצורה, מסד נתונים, מיגרציות | 2–3 ימים |
| ייבוא קטלוג (מוצרים, וריאנטים, נכסים) | 3–7 ימים |
| תוספים מותאמים אישית (מסים, משלוחים, קודי קופון) | 5–10 ימים |
| חנות חזית (Next.js + GraphQL) | 10–20 ימים |
| אינטגרציות תשלום (2–3 ספקים) | 4–6 ימים |
| התאמת ממשק ניהול | 2–4 ימים |
| סה"כ | 26–50 ימים |
מה כלול
- תיעוד מלא: מדריך פריסה, README תצורה, מפרט API ב-GraphQL Playground.
- גישה: שרת ייעודי (או המלצות ענן), מאגר קוד, פאנל ניהול.
- הכשרת צוות: סדנה על ניהול Vendure ו-Channels.
- תמיכה: שבועיים לאחר השחרור—תיקוני באגים, עזרה בעדכוני פריסה.
- המפתחים שלנו מחזיקים בהסמכות ב-NestJS ו-GraphQL.
- אנו מספקים אחריות של 14 יום לאחר ההשקה על תיקוני באגים.
- אנו מציעים אחריות שביעות רצון: אם הפרויקט לא נמסר בזמן, אנו מספקים הארכת תמיכה חינם.
יש לנו ניסיון רב ביישום Vendure ועשרות פרויקטים מוצלחים—מחנויות בגדים ועד פורטלי B2B עם אלפי SKUs. המומחיות שלנו במסחר TypeScript עם Vendure מבטיחה פתרונות חזקים. לפיתוח מסחר אלקטרוני, אנו מנצלים את הארכיטקטורה של Vendure כדי לספק חנויות מותאמות אישית וסקלביליות. קבלו ייעוץ: נעריך את הפרויקט שלכם ונציע ארכיטקטורת מפתח. צרו קשר להערכת עלות ולוח זמנים ראשוניים.
מידע נוסף על תכונות Vendure ניתן למצוא בתיעוד הרשמי ב-GitHub.







