Разработка интернет-магазина на Medusa.js

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка интернет-магазина на Medusa.js
Сложная
от 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

Разработка интернет-магазина на Medusa.js

Medusa — Node.js e-commerce фреймворк с открытым исходным кодом, позиционирующийся как альтернатива Shopify для разработчиков. Версия 2.x (released 2024) полностью переписана: модульная архитектура на основе Medusa Modules, новый IoC-контейнер, улучшенный workflow-engine. Стек: TypeScript, Node.js 20+, PostgreSQL, Redis.

Архитектура Medusa 2.x

В отличие от монолитной v1, Medusa 2.x строится на независимых модулях:

┌─────────────────────────────────────────┐
│             Medusa Application           │
├─────────────┬────────────┬──────────────┤
│  HTTP Layer │  Workflows │  Subscribers │
│  (API + MW) │  (Sagas)   │  (Events)    │
├─────────────┴────────────┴──────────────┤
│            Module Container              │
├──────────┬──────────┬───────────────────┤
│  Product │  Order   │  Cart   │  Auth   │
│  Module  │  Module  │  Module │  Module │
├──────────┴──────────┴─────────┴─────────┤
│          Infrastructure Layer            │
│     PostgreSQL + Redis + S3              │
└─────────────────────────────────────────┘

Каждый модуль — независимый пакет с собственной схемой БД, сервисами и событиями. Это позволяет заменять отдельные части системы (например, заменить встроенный Product Module на кастомный с другой структурой данных).

Установка и базовая конфигурация

npx create-medusa-app@latest mystore --db-url postgresql://user:pass@localhost/medusa

cd mystore
# Структура проекта
# src/
#   api/          — кастомные роуты
#   workflows/    — бизнес-логика
#   modules/      — кастомные модули
#   subscribers/  — обработчики событий
#   jobs/         — scheduled tasks
# medusa-config.ts

Конфигурационный файл medusa-config.ts:

import { defineConfig, loadEnv } from '@medusajs/framework/config';

loadEnv(process.env.NODE_ENV || 'development', process.cwd());

export default defineConfig({
  projectConfig: {
    databaseUrl: process.env.DATABASE_URL,
    redisUrl: process.env.REDIS_URL,
    http: {
      storeCors: process.env.STORE_CORS,
      adminCors: process.env.ADMIN_CORS,
      authCors: process.env.AUTH_CORS,
      jwtSecret: process.env.JWT_SECRET,
      cookieSecret: process.env.COOKIE_SECRET,
    },
  },
  modules: [
    {
      resolve: '@medusajs/medusa/fulfillment',
      options: {
        providers: [
          {
            resolve: '@medusajs/fulfillment-manual',
            id: 'manual',
          },
        ],
      },
    },
    {
      resolve: '@medusajs/medusa/payment',
      options: {
        providers: [
          {
            resolve: '@medusajs/payment-stripe',
            id: 'stripe',
            options: {
              apiKey: process.env.STRIPE_API_KEY,
              webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
            },
          },
        ],
      },
    },
    {
      resolve: '@medusajs/medusa/file',
      options: {
        providers: [
          {
            resolve: '@medusajs/file-s3',
            id: 's3',
            options: {
              file_url: process.env.S3_FILE_URL,
              access_key_id: process.env.S3_ACCESS_KEY_ID,
              secret_access_key: process.env.S3_SECRET_ACCESS_KEY,
              region: process.env.S3_REGION,
              bucket: process.env.S3_BUCKET,
            },
          },
        ],
      },
    },
  ],
});

Модели данных и работа с каталогом

Medusa 2.x использует MikroORM под капотом. Продукты строятся через Product → ProductVariant → Price иерархию:

// Создание продукта с вариантами через API или сервис
const productModuleService = container.resolve('product');

const product = await productModuleService.createProducts({
  title: 'Куртка зимняя',
  handle: 'kurtka-zimnyaya',
  status: ProductStatus.PUBLISHED,
  options: [
    { title: 'Размер', values: ['S', 'M', 'L', 'XL'] },
    { title: 'Цвет', values: ['Чёрный', 'Синий', 'Красный'] },
  ],
  variants: [
    {
      title: 'S / Чёрный',
      sku: 'JACKET-S-BLACK',
      options: { Размер: 'S', Цвет: 'Чёрный' },
      manage_inventory: true,
      prices: [
        { amount: 299900, currency_code: 'rub' },
        { amount: 2999,   currency_code: 'usd' },
      ],
    },
    // ...остальные варианты
  ],
  images: [{ url: 'https://cdn.example.com/jacket-black.jpg' }],
  collection_id: 'col_winter_2024',
});

Workflows и бизнес-логика

Workflows — ключевая концепция Medusa 2.x для сложной бизнес-логики. Это саги с компенсациями (rollback):

// src/workflows/custom-order-workflow.ts
import { createWorkflow, WorkflowResponse } from '@medusajs/framework/workflows-sdk';
import { createStep, StepResponse } from '@medusajs/framework/workflows-sdk';

const validateInventoryStep = createStep(
  'validate-inventory',
  async ({ variantId, quantity }: { variantId: string; quantity: number }, context) => {
    const inventoryService = context.container.resolve('inventory');
    const available = await inventoryService.retrieveAvailableQuantity(variantId, []);

    if (available < quantity) {
      throw new Error(`Недостаточно товара: доступно ${available}, запрошено ${quantity}`);
    }

    return new StepResponse({ available });
  }
);

const reserveInventoryStep = createStep(
  'reserve-inventory',
  async ({ variantId, quantity, locationId }, context) => {
    const inventoryService = context.container.resolve('inventory');
    const reservation = await inventoryService.createReservationItems([{
      inventory_item_id: variantId,
      location_id: locationId,
      quantity,
    }]);

    return new StepResponse(
      { reservationId: reservation[0].id },
      // Компенсация — выполняется при откате
      { reservationId: reservation[0].id }
    );
  },
  // Компенсирующая функция (rollback)
  async ({ reservationId }, context) => {
    const inventoryService = context.container.resolve('inventory');
    await inventoryService.deleteReservationItems([reservationId]);
  }
);

export const customOrderWorkflow = createWorkflow(
  'custom-order-workflow',
  function (input: { variantId: string; quantity: number; locationId: string }) {
    const { available } = validateInventoryStep(input);
    const { reservationId } = reserveInventoryStep(input);
    return new WorkflowResponse({ reservationId, available });
  }
);

Кастомные API-роуты

// src/api/store/recommendations/route.ts
import type { MedusaRequest, MedusaResponse } from '@medusajs/framework/http';
import { container } from '@medusajs/framework';

export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
  const productId = req.params.id;
  const productService = container.resolve('product');

  // Логика рекомендаций
  const product = await productService.retrieveProduct(productId, {
    relations: ['collection', 'tags'],
  });

  const related = await productService.listProducts({
    collection_id: [product.collection_id],
    id: { $ne: productId },
  }, {
    take: 6,
    order: { created_at: 'DESC' },
  });

  res.json({ products: related });
};

Подписчики событий

// src/subscribers/order-placed.ts
import { IOrderModuleService } from '@medusajs/framework/types';
import { SubscriberArgs, SubscriberConfig } from '@medusajs/framework';
import { sendOrderConfirmationWorkflow } from '../workflows/send-order-confirmation';

export default async function orderPlacedHandler({
  event: { data },
  container,
}: SubscriberArgs<{ id: string }>) {
  const orderId = data.id;

  await sendOrderConfirmationWorkflow(container).run({
    input: { orderId },
  });
}

export const config: SubscriberConfig = {
  event: 'order.placed',
};

Интеграция платёжных систем

Помимо Stripe, Medusa 2.x поддерживает кастомные Payment Provider:

// src/modules/payment-cloudpayments/service.ts
import {
  AbstractPaymentProvider,
  PaymentProviderContext,
  PaymentSessionStatus,
} from '@medusajs/framework/utils';

class CloudPaymentsPaymentProvider extends AbstractPaymentProvider {
  static identifier = 'cloudpayments';

  async initiatePayment(context: PaymentProviderContext) {
    const { amount, currency_code, context: { customer } } = context;

    return {
      id: `cp_${Date.now()}`,
      data: {
        publicId: process.env.CLOUDPAYMENTS_PUBLIC_ID,
        description: 'Оплата заказа',
        amount: amount / 100, // Medusa хранит в копейках
        currency: currency_code.toUpperCase(),
        accountId: customer?.email,
      },
    };
  }

  async getPaymentStatus(paymentSessionData: Record<string, unknown>) {
    const response = await this.cloudpaymentsClient.checkPayment(
      paymentSessionData.transactionId as string
    );
    return response.success ? PaymentSessionStatus.AUTHORIZED : PaymentSessionStatus.PENDING;
  }

  async capturePayment(paymentData: Record<string, unknown>) {
    // CloudPayments — capture при confirm
    return { ...paymentData, captured: true };
  }

  async refundPayment(paymentData: Record<string, unknown>, refundAmount: number) {
    await this.cloudpaymentsClient.refund({
      TransactionId: paymentData.transactionId,
      Amount: refundAmount / 100,
    });
    return paymentData;
  }
}

Деплой и production-конфигурация

# docker-compose.prod.yml
services:
  medusa:
    build: .
    environment:
      NODE_ENV: production
      DATABASE_URL: postgresql://user:pass@postgres:5432/medusa
      REDIS_URL: redis://redis:6379
      JWT_SECRET: ${JWT_SECRET}
    command: >
      sh -c "npx medusa db:migrate && npx medusa start"
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started

  medusa-worker:
    build: .
    environment:
      NODE_ENV: production
      MEDUSA_WORKER_MODE: worker  # только обработка очереди
    command: npx medusa start

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: medusa
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d medusa"]
      interval: 5s

  redis:
    image: redis:7-alpine
    command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru

Сроки разработки

  • Базовый магазин: бэкенд Medusa + Next.js Storefront starter: 3–4 недели
  • Магазин с кастомными модулями, интеграциями CRM/ERP, кастомными workflow: 8–14 недель
  • Headless enterprise-решение с несколькими регионами, мультивалютностью, B2B-логикой: 16–24 недели