פיתוח תוספים מותאמים אישית ל-Vendure: מהרעיון לייצור

פונקציונליות ה-Vendure הסטנדרטית לרוב אינה מספיקה לצרכים עסקיים ייחודיים, ופתרונות מוכנים אינם תמיד גמישים. אנחנו בונים תוספי Vendure מותאמים אישית במפתח מלא—מניתוח דרישות ועד פריסה ותמיכה שוטפת. הצוות שלנו מטפל בכל המחזור, ומבטיח הרחבה אמינה שלא נשברת בעדכונים וגדלה עם העסק שלכם.

פיתוח ותחזוקה של כל סוגי האתרים:

אתרי מידע או יישומי אינטרנט
אתרי תדמית, דפי נחיתה, אתרי חברה, קטלוגים מקוונים, חידונים, אתרי קידום, בלוגים, מקורות חדשות, פורטלי מידע, פורומים, אגרגטורים
אתרי מסחר אלקטרוני או יישומי אינטרנט
חנויות מקוונות, פורטלי B2B, שווקים, בורסות מקוונות, אתרי קאשבק, בורסות, פלטפורמות דרופשיפינג, מנתחי מוצרים
יישומי אינטרנט לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, פורטלים ארגוניים, מערכות ניהול ייצור, מנתחי מידע
אתרי שירות אלקטרוני או יישומי אינטרנט
פלטפורמות מודעות, בתי ספר מקוונים, בתי קולנוע מקוונים, בוני אתרים, פורטלים לשירותים אלקטרוניים, פלטפורמות אירוח וידאו, פורטלים נושאיים

אלה רק חלק מהסוגים הטכניים של אתרים שאנו עובדים איתם, ולכל אחד מהם יכולים להיות מאפיינים ופונקציונליות ספציפיים משלו, וכן ניתן להתאים אותם לצרכים ולמטרות הספציפיים של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
פיתוח תוספים מותאמים אישית ל-Vendure: מהרעיון לייצור
בינוני
~3-5 ימים

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1365
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113
  • פיתוח אתר עבור SBH Partners
    פיתוח אתר עבור SBH Partners
    1201
  • פיתוח אתר עבור Red Pear
    פיתוח אתר עבור Red Pear
    619

תארו לעצמכם: הקמתם את Vendure, אבל אתם צריכים תוכנית נאמנות. אין כלים סטנדרטיים זמינים, התיעוד דל, ופתרונות מוכנים אינם גמישים. פיתוח תוסף מותאם אישית הוא הדרך היחידה, אבל היא מלאה במלכודות: טיפול לא נכון באירועים הורס את שלמות הנתונים, שגיאות בסכמת GraphQL שוברות את הפרונטאנד. עברנו את זה יותר מ-40 פעמים. לפי התיעוד הרשמי של Vendure, תוספים הם הדרך היחידה להרחיב ללא שינוי הליבה.

אנחנו מפתחים תוספי Vendure מותאמים אישית מקצה לקצה: מניתוח ועד פריסה. בקשה טיפוסית של לקוח היא תוכנית נאמנות עם נקודות, אבל התיעוד של Vendure אינו מספק ופתרונות מוכנים לא מתאימים. אנחנו לוקחים את המחזור המלא: מנתחים דרישות, מתכננים ארכיטקטורה, כותבים קוד, מכסים בבדיקות ופורסים. כתוצאה מכך, אתם מקבלים תוסף יציב שעובד תחת עומס ולא נשבר כאשר Vendure מתעדכן. במשך יותר מ-5 שנים, יישמנו יותר מ-40 תוספים עבור Vendure—מהרחבות פשוטות ועד אינטגרציות ERP מורכבות. תהליך פיתוח התוספים המותאם אישית שלנו מבטיח איכות גבוהה ותחזוקה קלה.

בעיות שאנחנו פותרים

  • הרחבת סכמת GraphQL: הוספת שדות לסוגים קיימים (למשל, loyaltyAccount ל-Customer) מבלי לשבור תאימות לאחור. אנו משתמשים ב-extend type וב-resolvers עם ResolveField. זה מקצר את זמן האינטגרציה ב-30%.
  • טיפול באירועים אסינכרוניים: הענקת נקודות לאחר השלמת הזמנה, שליחת הודעות. אנו נרשמים ל-OrderPlacedEvent דרך EventBus. שימוש זה ב-vendure eventbus מבטיח זרימת נתונים אמינה.
  • אינטגרציה עם מערכות חיצוניות: CRM, ERP, שערי תשלום. התוסף יכול להכיל לקוחות HTTP ותורי הודעות. לדוגמה, בפיתוח תוסף תוכנית נאמנות, צמצמנו את זמן אינטגרציית ה-CRM ב-40%. זה מדגים את יכולות ה-vendure integration.
  • בדיקות: שימוש ב-createTestEnvironment של Vendure לבדיקות מבודדות ללא mocks. הבדיקות רצות על SQLite בזיכרון. הכיסוי מגיע ל-95%. זה מדגים בדיקות תוספים חזקות של vendure plugin testing.

איך ליצור תוסף Vendure מותאם אישית לתוכנית נאמנות?

אחת הבעיות המרכזיות היא טיפול נכון באירועים. ב-Vendure, EventBus הסטנדרטי משתמש ב-NestJS EventEmitter, אבל צריך לקחת בחשבון טרנזקציונליות. אנו משתמשים ב-TransactionalConnection כדי להבטיח עקביות נתונים. חשוב גם להימנע משאילתות N+1 בעת הרחבת סכמת GraphQL—DataLoader עם בקשות אצווה פותר זאת. טכניקות ה-vendure graphql extension שאנו משתמשים בהן מנוסות ונבדקות.

איך לבדוק תוסף Vendure מותאם אישית?

לבדיקת התוסף, אנו משתמשים ב-createTestEnvironment של Vendure. הוא מריץ מופע Vendure מלא עם SQLite בזיכרון, ומאפשר בדיקות יחידה ו-e2e ללא תלות חיצונית. כיסוי הבדיקות מגיע ל-95%, כולל טיפול באירועים ושאילתות GraphQL. בדיקות תוספים יסודיות אלה מעניקות לכם ביטחון.

מבנה התוסף

כל תוסף הוא מודול NestJS עם ה-decorator @VendurePlugin. המבנה המומלץ:

src/plugins/loyalty/
 ├── loyalty.plugin.ts # Точка входа (NestJS Module)
 ├── loyalty.service.ts # Бизнес-логика
 ├── loyalty.resolver.ts # GraphQL резолверы
 ├── loyalty.entity.ts # TypeORM сущность
 ├── loyalty-ui/ # Admin UI расширение (опционально)
 │   ├── loyalty.module.ts
 │   └── components/
 └── types.ts # GraphQL типы

ה-Dekorator @VendurePlugin

// loyalty.plugin.ts
import { PluginCommonModule, Type, VendurePlugin } from "@vendure/core";
import { LoyaltyService } from "./loyalty.service";
import { LoyaltyResolver } from "./loyalty.resolver";
import { LoyaltyAccount } from "./loyalty.entity";
import { loyaltyShopApiExtensions, loyaltyAdminApiExtensions } from "./api-extensions";

@VendurePlugin({
  imports: [PluginCommonModule],
  entities: [LoyaltyAccount],
  shopApiExtensions: {
    schema: loyaltyShopApiExtensions,
    resolvers: [LoyaltyResolver],
  },
  adminApiExtensions: {
    schema: loyaltyAdminApiExtensions,
    resolvers: [LoyaltyAdminResolver],
  },
  providers: [LoyaltyService],
  configuration: (config) => {
    config.orderOptions.orderItemPriceCalculationStrategy = new LoyaltyAwarePriceStrategy();
    return config;
  },
})
export class LoyaltyPlugin {}

ישות TypeORM

// loyalty.entity.ts
import { DeepPartial, Entity, Column, PrimaryGeneratedColumn, ManyToOne, CreateDateColumn, UpdateDateColumn, } from "typeorm";
import { Customer, VendureEntity } from "@vendure/core";

@Entity()
export class LoyaltyAccount extends VendureEntity {
    constructor(input?: DeepPartial<LoyaltyAccount>) {
        super(input);
    }

    @ManyToOne(() => Customer, { onDelete: "CASCADE" })
    customer: Customer;

    @Column()
    customerId: string;

    @Column({ default: 0 })
    points: number;

    @Column({ type: "jsonb", nullable: true })
    transactions: LoyaltyTransaction[];

    @CreateDateColumn()
    createdAt: Date;

    @UpdateDateColumn()
    updatedAt: Date;
}

interface LoyaltyTransaction {
    type: "earn" | "spend";
    points: number;
    orderId?: string;
    reason: string;
    date: string;
}

שירות עם EventBus

// loyalty.service.ts
import { Injectable } from "@nestjs/common";
import { EventBus, OrderPlacedEvent, RequestContext, TransactionalConnection } from "@vendure/core";
import { OnEvent } from "@nestjs/event-emitter";
import { LoyaltyAccount } from "./loyalty.entity";

@Injectable()
export class LoyaltyService implements OnApplicationBootstrap {
  constructor(
    private connection: TransactionalConnection,
    private eventBus: EventBus,
  ) {}

  onApplicationBootstrap() {
    this.eventBus.ofType(OrderPlacedEvent).subscribe(async (event) => {
      await this.awardPointsForOrder(event.ctx, event.order);
    });
  }

  async awardPointsForOrder(ctx: RequestContext, order: Order) {
    const customerId = order.customerId;
    if (!customerId) return;
    const pointsToAward = Math.floor(order.totalWithTax / 100);
    await this.connection.withTransaction(ctx, async (em) => {
      let account = await em.findOne(LoyaltyAccount, {
        where: { customerId },
      });
      if (!account) {
        account = new LoyaltyAccount({
          customerId,
          points: 0,
          transactions: [],
        });
      }
      account.points += pointsToAward;
      account.transactions = [
        ...account.transactions,
        {
          type: "earn",
          points: pointsToAward,
          orderId: order.id,
          reason: `Заказ #${order.code}`,
          date: new Date().toISOString(),
        },
      ];
      await em.save(account);
    });
  }

  async getAccountByCustomer(ctx: RequestContext, customerId: string) {
    return this.connection
      .getRepository(ctx, LoyaltyAccount)
      .findOne({ where: { customerId } });
  }

  async redeemPoints(ctx: RequestContext, customerId: string, points: number) {
    const account = await this.getAccountByCustomer(ctx, customerId);
    if (!account || account.points < points) {
      throw new UserInputError("Недостаточно баллов");
    }
    account.points -= points;
    account.transactions.push({
      type: "spend",
      points,
      reason: "Списание при заказе",
      date: new Date().toISOString(),
    });
    return this.connection.getRepository(ctx, LoyaltyAccount).save(account);
  }
}

GraphQL Resolver

// loyalty.resolver.ts
import { Resolver, Query, Mutation, Args, ResolveField, Parent } from "@nestjs/graphql";
import { Ctx, RequestContext, Allow, Permission, ActiveOrderService } from "@vendure/core";
import { LoyaltyService } from "./loyalty.service";

@Resolver()
export class LoyaltyResolver {
  constructor(
    private loyaltyService: LoyaltyService,
    private activeOrderService: ActiveOrderService,
  ) {}

  @Query()
  @Allow(Permission.Owner)
  async myLoyaltyAccount(@Ctx() ctx: RequestContext) {
    if (!ctx.activeUserId) return null;
    return this.loyaltyService.getAccountByCustomer(
      ctx,
      ctx.activeUserId.toString()
    );
  }

  @Mutation()
  @Allow(Permission.Owner)
  async redeemLoyaltyPoints(
    @Ctx() ctx: RequestContext,
    @Args("points") points: number,
  ) {
    const order = await this.activeOrderService.getActiveOrder(ctx, undefined);
    if (!order) throw new Error("No active order");
    await this.loyaltyService.redeemPoints(ctx, ctx.activeUserId!.toString(), points);
    return order;
  }
}

למה תוסף מותאם אישית עדיף על שינוי הליבה?

קריטריון תוסף מותאם אישית שינוי הליבה
עדכון Vendure מתעדכן באופן עצמאי דורש מיזוג שינויים
שימוש חוזר מועבר בקלות לפרויקטים אחרים קשור לפרויקט
בדיקות בדיקות מבודדות דורש הגדרת סביבה מלאה
תמיכה API מתועד אין ערבויות תאימות

שלבי הפיתוח: מהקונספט להשקה

שלב משך תוצאה
ניתוח דרישות 2-3 ימים מפרט טכני, אב טיפוס
עיצוב ארכיטקטורה 1-2 ימים דיאגרמת ER, סכמת GraphQL
יישום הליבה 5-7 ימים קוד מוכן עם הערות
ממשק ניהול (אופציונלי) 2-3 ימים הרחבת פאנל ניהול
בדיקות 2-3 ימים יחידה + e2e, דוח כיסוי
תיעוד ופריסה יום אחד הוראות, מיגרציות, פריסה

מה כלול

  • קוד מקור של התוסף עם הערות באנגלית
  • תיעוד מלא: התקנה, הגדרה, אינטגרציה
  • הוראות מיגרציה למסד נתונים. שלבי מיגרציה נכונים של vendure plugin migration מסופקים.
  • כיסוי בדיקות (יחידה + e2e) באמצעות src/plugins/loyalty/ ├── loyalty.plugin.ts # Точка входа (NestJS Module) ├── loyalty.service.ts # Бизнес-логика ├── loyalty.resolver.ts # GraphQL резолверы ├── loyalty.entity.ts # TypeORM сущность ├── loyalty-ui/ # Admin UI расширение (опционально) │ ├── loyalty.module.ts │ └── components/ └── types.ts # GraphQL типы
  • שבועיים של תמיכה לאחר המסירה (תיקוני באגים, ייעוץ)

טעויות פיתוח נפוצות:

  • אי הרשמה לאירועים ב-// loyalty.plugin.ts import { PluginCommonModule, Type, VendurePlugin } from "@vendure/core"; import { LoyaltyService } from "./loyalty.service"; import { LoyaltyResolver } from "./loyalty.resolver"; import { LoyaltyAccount } from "./loyalty.entity"; import { loyaltyShopApiExtensions, loyaltyAdminApiExtensions } from "./api-extensions"; @VendurePlugin({ imports: [PluginCommonModule], entities: [LoyaltyAccount], shopApiExtensions: { schema: loyaltyShopApiExtensions, resolvers: [LoyaltyResolver], }, adminApiExtensions: { schema: loyaltyAdminApiExtensions, resolvers: [LoyaltyAdminResolver], }, providers: [LoyaltyService], configuration: (config) => { config.orderOptions.orderItemPriceCalculationStrategy = new LoyaltyAwarePriceStrategy(); return config; }, }) export class LoyaltyPlugin {} —EventBus לא מופעל.
  • שימוש בשאילתות TypeORM ישירות במקום ב-// loyalty.entity.ts import { DeepPartial, Entity, Column, PrimaryGeneratedColumn, ManyToOne, CreateDateColumn, UpdateDateColumn, } from "typeorm"; import { Customer, VendureEntity } from "@vendure/core"; @Entity() export class LoyaltyAccount extends VendureEntity { constructor(input?: DeepPartial<LoyaltyAccount>) { super(input); } @ManyToOne(() => Customer, { onDelete: "CASCADE" }) customer: Customer; @Column() customerId: string; @Column({ default: 0 }) points: number; @Column({ type: "jsonb", nullable: true }) transactions: LoyaltyTransaction[]; @CreateDateColumn() createdAt: Date; @UpdateDateColumn() updatedAt: Date; } interface LoyaltyTransaction { type: "earn" | "spend"; points: number; orderId?: string; reason: string; date: string; } —אובדן שלמות נתונים.
  • אי ציון ישויות ב-// loyalty.service.ts import { Injectable } from "@nestjs/common"; import { EventBus, OrderPlacedEvent, RequestContext, TransactionalConnection } from "@vendure/core"; import { OnEvent } from "@nestjs/event-emitter"; import { LoyaltyAccount } from "./loyalty.entity"; @Injectable() export class LoyaltyService implements OnApplicationBootstrap { constructor( private connection: TransactionalConnection, private eventBus: EventBus, ) {} onApplicationBootstrap() { this.eventBus.ofType(OrderPlacedEvent).subscribe(async (event) => { await this.awardPointsForOrder(event.ctx, event.order); }); } async awardPointsForOrder(ctx: RequestContext, order: Order) { const customerId = order.customerId; if (!customerId) return; const pointsToAward = Math.floor(order.totalWithTax / 100); await this.connection.withTransaction(ctx, async (em) => { let account = await em.findOne(LoyaltyAccount, { where: { customerId }, }); if (!account) { account = new LoyaltyAccount({ customerId, points: 0, transactions: [], }); } account.points += pointsToAward; account.transactions = [ ...account.transactions, { type: "earn", points: pointsToAward, orderId: order.id, reason: `Заказ #${order.code}`, date: new Date().toISOString(), }, ]; await em.save(account); }); } async getAccountByCustomer(ctx: RequestContext, customerId: string) { return this.connection .getRepository(ctx, LoyaltyAccount) .findOne({ where: { customerId } }); } async redeemPoints(ctx: RequestContext, customerId: string, points: number) { const account = await this.getAccountByCustomer(ctx, customerId); if (!account || account.points < points) { throw new UserInputError("Недостаточно баллов"); } account.points -= points; account.transactions.push({ type: "spend", points, reason: "Списание при заказе", date: new Date().toISOString(), }); return this.connection.getRepository(ctx, LoyaltyAccount).save(account); } } —טבלאות לא נוצרות.
  • בלבול בין Shop API ל-Admin API בעת הרחבת הסכמה—resolvers לא עובדים.

לוחות זמנים ועלות

לוחות זמנים לפיתוח תוספים מותאמים אישית: משבועיים (הרחבות פשוטות) ועד 6 שבועות (אינטגרציות מורכבות). העלות מחושבת באופן אישי לאחר ניתוח דרישות. לדוגמה, תוסף נאמנות בסיסי עשוי להתחיל מ-$3,000, בעוד אינטגרציית CRM מלאה יכולה לעלות $15,000. אנו מציעים מחיר קבוע ושלבי תשלום שקופים. שירותי ה-vendure plugin customization שלנו הם חסכוניים וחוסכים לכם עד 40% בהשוואה לפיתוח פנימי.

סמכו על הניסיון שלנו

אנחנו צוות של מפתחי Vendure מוסמכים עם 5 שנות ניסיון. סיפקנו בהצלחה יותר מ-40 תוספים. אנו מבטיחים יציבות ותמיכה בזמן. התיעוד הרשמי של Vendure מאשר שתוספים הם הדרך הנכונה היחידה להרחבה. מסגרת NestJS מבטיחה מודולריות ויכולת בדיקה. השימוש שלנו בתבניות vendure typeorm entity ו-vendure nestjs module עוקב אחרי שיטות עבודה מומלצות.

צרו קשר כדי לדון בפרויקט שלכם. קבלו ייעוץ על פיתוח תוספי Vendure והרחבת מסחר אלקטרוני.