Разработка интернет-магазина на Spree Commerce
Spree — Rails-движок с открытым исходным кодом, существующий с 2008 года. В отличие от Vendure и commercetools, Spree — monolithic-first: он встраивается в Rails-приложение как Engine и работает с той же базой данных. Начиная с версии 4.3 добавился headless-режим через REST API v2, что позволяет использовать Spree как backend для React/Vue фронтенда.
Два режима использования
Monolith (классический): Spree работает как Rails Engine внутри вашего Rails-приложения. Storefront рендерится сервером (ERB + Turbo), Admin UI встроен. Подходит для команд с Rails-экспертизой и средним трафиком.
Headless:
Spree предоставляет REST API v2 (/api/v2/storefront) и стандартный React storefront (spree/storefront). Storefront деплоится отдельно. Этот режим становится основным с Spree 5.x.
Архитектура Rails Engine
# Gemfile
gem 'spree', '~> 4.10'
gem 'spree_auth_devise', '~> 4.6'
gem 'spree_gateway', '~> 3.10'
gem 'spree_backend', '~> 4.10' # Admin UI
gem 'spree_sample', '~> 4.10' # Тестовые данные
# Для headless:
gem 'spree_api', '~> 4.10'
bundle install
bin/rails g spree:install
bin/rails g spree:auth:install
bin/rails db:migrate
bin/rails db:seed
После установки доступны:
-
/admin— панель управления -
/api/v2/storefront— REST API для фронтенда -
/— классический storefront (если установленspree_frontend)
Модель данных
Spree имеет устоявшуюся схему:
Spree::Store # Мультимагазинность
├── Spree::Taxon # Категории (вложенные через ancestry)
├── Spree::Product
│ ├── Spree::Variant # Варианты = SKU (цена, остаток, опции)
│ ├── Spree::Price # Цена per variant per currency
│ └── Spree::Image
├── Spree::Order
│ ├── Spree::LineItem
│ ├── Spree::Payment
│ └── Spree::Shipment
└── Spree::User # через spree_auth_devise
REST API v2: Headless режим
// lib/spreeClient.ts
import { makeClient } from "@spree/storefront-api-v2-sdk";
export const client = makeClient({
host: process.env.NEXT_PUBLIC_SPREE_URL!,
});
// Получить список товаров
const products = await client.products.list(
{},
{
include: "default_variant,images,taxons",
filter: { taxons: taxonId },
sort: "name",
page: 1,
per_page: 24,
}
);
// Создать корзину
const cart = await client.cart.create();
const orderToken = cart.success().data.attributes.token;
// Добавить товар
await client.cart.addItem(
{ orderToken },
{
variant_id: variantId,
quantity: 1,
}
);
// Checkout: адрес
await client.checkout.orderUpdate(
{ orderToken },
{
order: {
ship_address_attributes: {
firstname: "Иван",
lastname: "Петров",
address1: "ул. Ленина 1",
city: "Москва",
country_iso: "RU",
zipcode: "101000",
phone: "+79001234567",
},
},
}
);
Кастомизация бизнес-логики
Spree использует Decorator pattern для расширения моделей без форка:
# app/models/spree/product_decorator.rb
module Spree
module ProductDecorator
def self.prepended(base)
base.has_many :bundle_parts, class_name: "Spree::BundlePart",
foreign_key: :bundle_product_id
end
def bundle?
bundle_parts.any?
end
def effective_price_for(quantity)
# Скидка за объём
if quantity >= 10
price * 0.9
elsif quantity >= 5
price * 0.95
else
price
end
end
end
end
Spree::Product.prepend(Spree::ProductDecorator)
Промоакции — отдельная система (Spree::Promotion):
promotion = Spree::Promotion.create!(
name: "Летняя скидка 15%",
code: "SUMMER15",
starts_at: Date.today,
expires_at: 3.months.from_now,
usage_limit: 1000
)
promotion.actions.create!(
type: "Spree::Promotion::Actions::CreateAdjustment",
calculator: Spree::Calculator::FlatPercentItemTotal.create!(
preferred_flat_percent: 15.0
)
)
promotion.rules.create!(
type: "Spree::Promotion::Rules::ItemTotal",
preferred_operator: "gte",
preferred_amount: 2000.0
)
Интеграция платёжных систем
spree_gateway предоставляет готовые адаптеры для Stripe, Braintree, PayPal. Для YooKassa или Тинькофф пишется кастомный gateway:
# app/models/spree/gateway/yookassa.rb
module Spree
class Gateway::Yookassa < Gateway
preference :shop_id, :string
preference :secret_key, :string
def provider_class
::YooKassa::Client
end
def purchase(money, creditcard, gateway_options)
response = provider.create_payment(
amount: { value: (money / 100.0).to_s, currency: gateway_options[:currency] },
capture: true,
confirmation: { type: "redirect", return_url: gateway_options[:return_url] },
description: "Заказ #{gateway_options[:order_id]}",
idempotence_key: gateway_options[:order_id]
)
if response.status == "pending"
ActiveMerchant::Billing::Response.new(
false,
"Redirect required",
{ confirmation_url: response.confirmation.confirmation_url },
{ requires_redirect: true }
)
else
ActiveMerchant::Billing::Response.new(true, "Payment created", {}, {})
end
end
end
end
Мультивалютность и локализация
# config/initializers/spree.rb
Spree.config do |config|
config.currency = "RUB"
config.currency_symbol_position = :before
config.prices_inc_tax = false
# Для мультивалюты
config.supported_currencies = "RUB,USD,EUR"
end
# Spree Store
store = Spree::Store.default
store.update!(
name: "My Shop",
url: "myshop.ru",
mail_from_address: "[email protected]",
default_currency: "RUB",
supported_currencies: "RUB,USD",
default_locale: "ru",
supported_locales: "ru,en"
)
Этапы разработки
| Этап | Описание | Срок |
|---|---|---|
| Setup + конфигурация | Rails app, Spree install, БД | 2–3 дня |
| Каталог + импорт товаров | Rake задачи, CSV/API импорт | 4–8 дней |
| Кастомная бизнес-логика | Декораторы, промоции, доставка | 5–10 дней |
| Фронтенд (Headless) | Next.js + Spree SDK | 10–20 дней |
| Платёжные интеграции | 2–3 провайдера | 4–6 дней |
| Admin UI кастомизация | Дополнительные разделы | 3–5 дней |
| Итого | 28–52 дня |
Технический стек проекта
- Backend: Ruby 3.2 + Rails 7.1 + Spree 4.10
- БД: PostgreSQL 15 (сложные промоции, ancestry деревья категорий)
- Кэш: Redis (Solid Cache или Redis-store)
- Фонт: Sidekiq для фоновых задач (email, синхронизация)
-
Поиск: Elasticsearch через
searchkickили pg_search для базового поиска -
Фронтенд: Next.js 14 +
@spree/storefront-api-v2-sdk







