Реалізація Marketplace плагінів/інтеграцій для SaaS-застосунку

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

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

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація Marketplace плагінів/інтеграцій для SaaS-застосунку
Складна
від 2 тижнів до 3 місяців
Часті питання
Наші компетенції:
Етапи розробки
Останні роботи
  • 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

Реалізація маркетплейсу плагінів/інтеграцій для SaaS-додатків

Маркетплейс розширень перетворює продукт на платформу: партнери створюють інтеграції, користувачі встановлюють потрібні. Atlassian Marketplace, Shopify App Store, Figma Plugins — приклади.

Архітектура: два типи розширень

Серверні інтеграції — OAuth-додатки, які взаємодіють з вашим API від імені користувача. Сторонній сервіс (наприклад, Zapier або n8n) авторизується та викликає ваш API.

Клієнтські плагіни — JavaScript-код, що виконується в iframe або Web Worker на стороні клієнта. Figma Plugin Model — приклад.

Реєстр плагінів

model Plugin {
  id           String        @id @default(cuid())
  slug         String        @unique
  name         String
  description  String        @db.Text
  author       String
  authorUrl    String?
  iconUrl      String?
  category     PluginCategory
  installCount Int           @default(0)
  rating       Float?
  isVerified   Boolean       @default(false)
  isPublished  Boolean       @default(false)

  // Для серверних: OAuth-облікові дані
  clientId     String?       @unique
  clientSecret String?       // зашифровано

  // Маніфест
  permissions  String[]      // ['read:projects', 'write:tasks']
  webhookUrl   String?
  oauthConfig  Json?

  installations PluginInstallation[]
  reviews       PluginReview[]
}

model PluginInstallation {
  id          String   @id @default(cuid())
  pluginId    String
  tenantId    String
  installedAt DateTime @default(now())
  config      Json?    // налаштування конкретної установки
  accessToken String?  // OAuth-токен тенанта для плагіна

  plugin Plugin @relation(fields: [pluginId], references: [id])
  tenant Tenant @relation(fields: [tenantId], references: [id])

  @@unique([pluginId, tenantId])
}

Процес установки плагіна

// OAuth-потік для установки серверного плагіна
export async function initiatePluginInstall(
  tenantId: string,
  pluginSlug: string
): Promise<string> {
  const plugin = await db.plugin.findUniqueOrThrow({
    where: { slug: pluginSlug }
  });

  // Генеруємо стан для CSRF-захисту
  const state = await generateState({
    tenantId,
    pluginId: plugin.id,
    action: 'install',
  });

  // Перенаправляємо на OAuth-провайдера плагіна
  const authUrl = new URL(plugin.oauthConfig?.authorizationUrl as string);
  authUrl.searchParams.set('client_id', plugin.clientId!);
  authUrl.searchParams.set('redirect_uri', `${process.env.APP_URL}/marketplace/callback`);
  authUrl.searchParams.set('scope', plugin.permissions.join(' '));
  authUrl.searchParams.set('state', state);
  authUrl.searchParams.set('response_type', 'code');

  return authUrl.toString();
}

// Callback після OAuth-авторизації
export async function completePluginInstall(
  code: string,
  state: string
): Promise<void> {
  const { tenantId, pluginId } = await verifyState(state);

  const plugin = await db.plugin.findUniqueOrThrow({
    where: { id: pluginId }
  });

  // Обмінюємо код на токен
  const tokenResponse = await fetch(plugin.oauthConfig?.tokenUrl as string, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      code,
      client_id: plugin.clientId,
      client_secret: decryptToken(plugin.clientSecret!),
      redirect_uri: `${process.env.APP_URL}/marketplace/callback`,
      grant_type: 'authorization_code',
    }),
  });

  const tokens = await tokenResponse.json();

  await db.pluginInstallation.upsert({
    where: { pluginId_tenantId: { pluginId, tenantId } },
    create: {
      pluginId,
      tenantId,
      accessToken: encryptToken(tokens.access_token),
    },
    update: {
      accessToken: encryptToken(tokens.access_token),
    }
  });

  // Повідомляємо плагін про встановлення
  if (plugin.webhookUrl) {
    await fetch(plugin.webhookUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        event: 'plugin.installed',
        tenantId,
        timestamp: new Date().toISOString(),
      }),
    });
  }

  await db.plugin.update({
    where: { id: pluginId },
    data: { installCount: { increment: 1 } }
  });
}

API для розробників плагінів

// Плагіни взаємодіють через OAuth-авторизовані запити до API
// app/api/v1/[...]/route.ts

export async function validatePluginRequest(request: Request): Promise<{
  plugin: Plugin;
  tenantId: string;
}> {
  const authHeader = request.headers.get('Authorization');
  if (!authHeader?.startsWith('Bearer ')) {
    throw new ApiError(401, 'Missing authorization');
  }

  const token = authHeader.slice(7);

  // Перевіряємо токен
  const installation = await db.pluginInstallation.findFirst({
    where: {
      // Насправді: верифікуємо JWT або шукаємо за хешем токена
      accessToken: encryptToken(token),
    },
    include: { plugin: true }
  });

  if (!installation) {
    throw new ApiError(401, 'Invalid token');
  }

  return {
    plugin: installation.plugin,
    tenantId: installation.tenantId,
  };
}

UI маркетплейсу

// app/marketplace/page.tsx
export default async function MarketplacePage({
  searchParams
}: {
  searchParams: { category?: string; q?: string }
}) {
  const plugins = await db.plugin.findMany({
    where: {
      isPublished: true,
      ...(searchParams.category ? { category: searchParams.category as PluginCategory } : {}),
      ...(searchParams.q ? {
        OR: [
          { name: { contains: searchParams.q, mode: 'insensitive' } },
          { description: { contains: searchParams.q, mode: 'insensitive' } },
        ]
      } : {}),
    },
    orderBy: { installCount: 'desc' },
  });

  const tenant = await getCurrentTenant();
  const installedPluginIds = new Set(
    (await db.pluginInstallation.findMany({
      where: { tenantId: tenant!.id },
      select: { pluginId: true },
    })).map(i => i.pluginId)
  );

  return (
    <div>
      <MarketplaceSearch />
      <CategoryFilter />
      <PluginGrid
        plugins={plugins}
        installedIds={installedPluginIds}
      />
    </div>
  );
}

Розробка маркетплейсу плагінів з OAuth-встановленням та API для розробників — 8–14 робочих днів.