Корзина и процесс оформления заказа — критически важные элементы интернет-магазина. По статистике, 70% пользователей бросают покупку на этапе оформления из-за сложного или неудобного интерфейса. В MODX с miniShop2 можно создать максимально удобный и конверсионный процесс покупки.
Архитектура корзины в miniShop2
Как работает корзина
miniShop2 использует сессии для хранения данных корзины неавторизованных пользователей и базу данных для зарегистрированных. Это обеспечивает:
- Быстрое добавление товаров без перезагрузки
- Сохранность корзины при закрытии браузера (для авторизованных)
- Синхронизацию между устройствами одного пользователя
- Восстановление брошенных корзин для email-маркетинга
Основные компоненты
// Структура корзины в базе/сессии
$cart = [
'products' => [
[
'id' => 123, // ID товара
'key' => 'abc123', // Уникальный ключ позиции
'price' => 1500.00, // Цена за единицу
'count' => 2, // Количество
'options' => ['color' => 'red'], // Выбранные опции
'weight' => 0.5 // Вес для расчёта доставки
]
],
'total' => [
'cart_count' => 2, // Общее количество позиций
'cart_weight' => 1.0, // Общий вес
'cart_cost' => 3000.00 // Сумма товаров
]
];
Базовая настройка корзины
Системные настройки miniShop2
// Основные параметры корзины
'ms2_cart_context' => 'web' // Контекст корзины
'ms2_cart_max_count' => 1000 // Максимальное количество одного товара
'ms2_price_snippet' => '' // Сниппет для расчёта цен
'ms2_weight_precision' => 3 // Точность веса
'ms2_price_precision' => 2 // Точность цены
// Поведение корзины
'ms2_cart_remove_empty' => true // Удалять позиции с количеством 0
'ms2_cart_js_class_name' => 'miniShop2' // CSS класс для JS
Подключение на странице товара
<!-- Кнопка добавления в корзину -->
<form class="ms2_form" method="post">
<input type="hidden" name="id" value="[[*id]]">
<!-- Опции товара -->
[[msOptions? &product=`[[*id]]` &tpl=`tpl.msOptions.row`]]
<div class="product-quantity">
<label>Количество:</label>
<div class="quantity-controls">
<button type="button" class="qty-minus">−</button>
<input type="number" name="count" value="1" min="1" max="[[+count]]" class="qty-input">
<button type="button" class="qty-plus">+</button>
</div>
</div>
<div class="product-actions">
<button type="submit" name="ms2_action" value="cart/add" class="btn-cart">
<span class="btn-text">В корзину</span>
<span class="btn-price">[[*price]] ₽</span>
</button>
<button type="button" class="btn-favorite" data-id="[[*id]]">
♡ В избранное
</button>
<button type="button" class="btn-compare" data-id="[[*id]]">
⚖ Сравнить
</button>
</div>
</form>
Мини-корзина в шапке сайта
<!-- Чанк: tpl.msCart.mini -->
<div class="mini-cart" id="msCart">
[[!msCart?
&tpl=`tpl.msCart.mini.row`
&tplOuter=`tpl.msCart.mini.outer`
&tplEmpty=`tpl.msCart.mini.empty`
]]
</div>
Внешний шаблон (tpl.msCart.mini.outer):
<div class="cart-toggle" onclick="toggleCart()">
<span class="cart-icon">🛒</span>
<span class="cart-count">[[+total.cart_count]]</span>
<span class="cart-total">[[+total.cart_cost]] ₽</span>
</div>
<div class="cart-dropdown" id="cartDropdown">
<div class="cart-header">
<h3>Корзина ([[+total.cart_count]])</h3>
<button class="cart-close" onclick="toggleCart()">×</button>
</div>
<div class="cart-items">
[[+rows]]
</div>
<div class="cart-footer">
<div class="cart-total-line">
<strong>Итого: [[+total.cart_cost]] ₽</strong>
</div>
<div class="cart-actions">
<a href="[[~[[++ms2_cart_id]]]]" class="btn-cart-full">Корзина</a>
<a href="[[~[[++ms2_order_id]]]]" class="btn-checkout">Оформить</a>
</div>
</div>
</div>
Строка товара (tpl.msCart.mini.row):
<div class="cart-item" data-key="[[+key]]">
<div class="item-image">
[[msGallery? &id=`[[+id]]` &limit=`1` &tpl=`@INLINE <img src="[[+small]]" alt="[[+name]]">`]]
</div>
<div class="item-details">
<div class="item-name">[[+name]]</div>
<div class="item-options">
[[+options:notempty=`<small>[[+options]]</small>`]]
</div>
<div class="item-quantity">[[+count]] × [[+price]] ₽</div>
</div>
<button class="item-remove" onclick="msCart.remove('[[+key]]')">×</button>
</div>
Полная страница корзины
Детальный вид корзины
<!-- Шаблон страницы корзины -->
<div class="cart-page">
<h1>Корзина</h1>
[[!msCart?
&tpl=`tpl.msCart.row`
&tplOuter=`tpl.msCart.outer`
&tplEmpty=`tpl.msCart.empty`
]]
</div>
Внешний шаблон (tpl.msCart.outer):
<div class="cart-table-wrapper">
<form class="cart-form">
<table class="cart-table">
<thead>
<tr>
<th class="col-product">Товар</th>
<th class="col-price">Цена</th>
<th class="col-quantity">Количество</th>
<th class="col-total">Сумма</th>
<th class="col-remove"></th>
</tr>
</thead>
<tbody>
[[+rows]]
</tbody>
</table>
<div class="cart-summary">
<div class="cart-actions">
<button type="button" class="btn-continue-shopping">
← Продолжить покупки
</button>
<button type="button" class="btn-clear-cart" onclick="msCart.clean()">
Очистить корзину
</button>
</div>
<div class="cart-totals">
<div class="total-line">
<span>Товары ([[+total.cart_count]]):</span>
<span>[[+total.cart_cost]] ₽</span>
</div>
[[+total.cart_weight:gt=`0`:then=`
<div class="total-line">
<span>Общий вес:</span>
<span>[[+total.cart_weight]] кг</span>
</div>
`]]
<div class="total-line total-final">
<strong>
<span>Итого:</span>
<span>[[+total.cart_cost]] ₽</span>
</strong>
</div>
<div class="checkout-actions">
<a href="[[~[[++ms2_order_id]]]]" class="btn-checkout btn-large">
Перейти к оформлению
</a>
</div>
</div>
</div>
</form>
</div>
<!-- Рекомендации -->
<div class="recommended-products">
<h2>С этими товарами покупают</h2>
[[msProducts?
&parents=`[[*parent]]`
&limit=`4`
&exclude=`[[msCart.get_ids]]`
&sortby=`RAND()`
&tpl=`tpl.msProducts.row.small`
]]
</div>
Строка корзины (tpl.msCart.row):
<tr class="cart-row" data-key="[[+key]]">
<td class="col-product">
<div class="product-info">
<div class="product-image">
<a href="[[+uri]]">
[[msGallery? &id=`[[+id]]` &limit=`1` &tpl=`@INLINE <img src="[[+medium]]" alt="[[+pagetitle]]">`]]
</a>
</div>
<div class="product-details">
<h3 class="product-name">
<a href="[[+uri]]">[[+pagetitle]]</a>
</h3>
<div class="product-options">
[[+options:notempty=`[[+options]]`]]
</div>
<div class="product-article">
Артикул: [[+article]]
</div>
</div>
</div>
</td>
<td class="col-price">
<span class="price">[[+price]] ₽</span>
[[+old_price:gt=`[[+price]]`:then=`
<span class="old-price">[[+old_price]] ₽</span>
`]]
</td>
<td class="col-quantity">
<div class="quantity-controls">
<button type="button" class="qty-btn qty-minus" onclick="msCart.change('[[+key]]', [[+count]] - 1)">−</button>
<input type="number"
value="[[+count]]"
min="1"
max="[[+count_max:default=`999`]]"
class="qty-input"
onchange="msCart.change('[[+key]]', this.value)">
<button type="button" class="qty-btn qty-plus" onclick="msCart.change('[[+key]]', [[+count]] + 1)">+</button>
</div>
</td>
<td class="col-total">
<strong class="item-total">[[+cost]] ₽</strong>
</td>
<td class="col-remove">
<button type="button"
class="btn-remove"
onclick="msCart.remove('[[+key]]')"
title="Удалить">
🗑
</button>
</td>
</tr>
Многоэтапное оформление заказа
Структура процесса
- Корзина — просмотр и редактирование товаров
- Контактные данные — ввод информации о покупателе
- Доставка — выбор способа доставки и адреса
- Оплата — выбор способа оплаты
- Подтверждение — финальная проверка заказа
Основной шаблон оформления
<!-- Шаблон страницы заказа -->
<div class="checkout-page">
<div class="checkout-progress">
<div class="step active" data-step="1">
<span class="step-number">1</span>
<span class="step-title">Контакты</span>
</div>
<div class="step" data-step="2">
<span class="step-number">2</span>
<span class="step-title">Доставка</span>
</div>
<div class="step" data-step="3">
<span class="step-number">3</span>
<span class="step-title">Оплата</span>
</div>
<div class="step" data-step="4">
<span class="step-number">4</span>
<span class="step-title">Подтверждение</span>
</div>
</div>
<div class="checkout-content">
<div class="checkout-main">
[[!msOrder?
&tplOuter=`tpl.msOrder.outer`
&tplContactInfo=`tpl.msOrder.contact`
&tplDelivery=`tpl.msOrder.delivery`
&tplPayment=`tpl.msOrder.payment`
&tplCart=`tpl.msOrder.cart`
]]
</div>
<div class="checkout-sidebar">
<div class="order-summary">
<h3>Ваш заказ</h3>
[[!msCart?
&tpl=`tpl.msCart.order.row`
&tplOuter=`tpl.msCart.order.outer`
]]
</div>
</div>
</div>
</div>
Шаг 1: Контактные данные
<!-- Чанк: tpl.msOrder.contact -->
<div class="checkout-step" id="step-contact">
<h2>Контактная информация</h2>
[[!+fi.validation_error_message:notempty=`
<div class="alert alert-error">[[+fi.validation_error_message]]</div>
`]]
<!-- Быстрая авторизация -->
[[!+modx.user.id:is=``:then=`
<div class="auth-options">
<div class="auth-tabs">
<button type="button" class="tab-btn active" onclick="showAuthTab('guest')">
Гость
</button>
<button type="button" class="tab-btn" onclick="showAuthTab('login')">
Вход
</button>
<button type="button" class="tab-btn" onclick="showAuthTab('register')">
Регистрация
</button>
</div>
<div class="auth-content">
<div id="guest-form" class="auth-form active">
<!-- Форма для гостей -->
</div>
<div id="login-form" class="auth-form">
[[!Login? &tpl=`tpl.login.checkout`]]
</div>
<div id="register-form" class="auth-form">
[[!Register? &tpl=`tpl.register.checkout`]]
</div>
</div>
</div>
`:else=`
<div class="user-info">
<p>Заказ оформляется на: <strong>[[!+modx.user.fullname]]</strong></p>
<p><a href="[[~[[++logout_id]]]]">Выйти</a></p>
</div>
`]]
<div class="contact-form">
<div class="form-row">
<div class="form-group">
<label for="receiver">Имя получателя *</label>
<input type="text"
name="receiver"
id="receiver"
value="[[!+fi.receiver]]"
required>
<span class="error">[[!+fi.error.receiver]]</span>
</div>
<div class="form-group">
<label for="phone">Телефон *</label>
<input type="tel"
name="phone"
id="phone"
value="[[!+fi.phone]]"
placeholder="+7 (999) 123-45-67"
required>
<span class="error">[[!+fi.error.phone]]</span>
</div>
</div>
<div class="form-group">
<label for="email">Email *</label>
<input type="email"
name="email"
id="email"
value="[[!+fi.email]]"
placeholder="example@mail.ru"
required>
<span class="error">[[!+fi.error.email]]</span>
<small>На этот email придёт подтверждение заказа</small>
</div>
<div class="form-group">
<label for="comment">Комментарий к заказу</label>
<textarea name="comment"
id="comment"
placeholder="Особые пожелания к заказу">[[!+fi.comment]]</textarea>
</div>
<div class="form-actions">
<button type="button" class="btn-next" onclick="nextStep(2)">
Выбрать доставку →
</button>
</div>
</div>
</div>
Шаг 2: Доставка
<!-- Чанк: tpl.msOrder.delivery -->
<div class="checkout-step" id="step-delivery" style="display: none;">
<h2>Способ доставки</h2>
<div class="delivery-methods">
[[!msDelivery?
&tpl=`tpl.msDelivery.row`
&tplOuter=`tpl.msDelivery.outer`
]]
</div>
<!-- Адрес доставки -->
<div class="delivery-address" id="delivery-address">
<h3>Адрес доставки</h3>
<div class="form-row">
<div class="form-group">
<label for="city">Город *</label>
<input type="text" name="city" id="city" value="[[!+fi.city]]" required>
<div class="city-suggestions"></div>
</div>
<div class="form-group">
<label for="metro">Станция метро</label>
<input type="text" name="metro" id="metro" value="[[!+fi.metro]]">
</div>
</div>
<div class="form-group">
<label for="street">Улица *</label>
<input type="text" name="street" id="street" value="[[!+fi.street]]" required>
</div>
<div class="form-row">
<div class="form-group">
<label for="building">Дом *</label>
<input type="text" name="building" id="building" value="[[!+fi.building]]" required>
</div>
<div class="form-group">
<label for="apartment">Квартира</label>
<input type="text" name="apartment" id="apartment" value="[[!+fi.apartment]]">
</div>
</div>
<div class="form-group">
<label for="index">Индекс</label>
<input type="text" name="index" id="index" value="[[!+fi.index]]" placeholder="123456">
</div>
</div>
<!-- Карта для курьерской доставки -->
<div class="delivery-map" id="delivery-map" style="display: none;">
<div id="map" style="height: 400px;"></div>
<div class="map-controls">
<button type="button" onclick="detectLocation()">Определить моё местоположение</button>
</div>
</div>
<div class="form-actions">
<button type="button" class="btn-prev" onclick="prevStep(1)">
← Назад
</button>
<button type="button" class="btn-next" onclick="nextStep(3)">
Выбрать оплату →
</button>
</div>
</div>
Шаг 3: Оплата
<!-- Чанк: tpl.msOrder.payment -->
<div class="checkout-step" id="step-payment" style="display: none;">
<h2>Способ оплаты</h2>
<div class="payment-methods">
[[!msPayment?
&tpl=`tpl.msPayment.row`
&tplOuter=`tpl.msPayment.outer`
]]
</div>
<!-- Дополнительные поля для банковской карты -->
<div class="payment-details" id="card-details" style="display: none;">
<h3>Данные банковской карты</h3>
<div class="card-form">
<div class="form-group">
<label for="card-number">Номер карты *</label>
<input type="text"
id="card-number"
placeholder="1234 5678 9012 3456"
maxlength="19">
</div>
<div class="form-row">
<div class="form-group">
<label for="card-expiry">Срок действия *</label>
<input type="text"
id="card-expiry"
placeholder="ММ/ГГ"
maxlength="5">
</div>
<div class="form-group">
<label for="card-cvv">CVV *</label>
<input type="text"
id="card-cvv"
placeholder="123"
maxlength="3">
</div>
</div>
<div class="form-group">
<label for="card-holder">Имя владельца *</label>
<input type="text"
id="card-holder"
placeholder="IVAN PETROV">
</div>
</div>
</div>
<div class="form-actions">
<button type="button" class="btn-prev" onclick="prevStep(2)">
← Назад
</button>
<button type="button" class="btn-next" onclick="nextStep(4)">
Проверить заказ →
</button>
</div>
</div>
Шаг 4: Подтверждение
<!-- Чанк: tpl.msOrder.confirm -->
<div class="checkout-step" id="step-confirm" style="display: none;">
<h2>Подтверждение заказа</h2>
<div class="order-review">
<div class="review-section">
<h3>Контактные данные</h3>
<div class="review-data" id="review-contact"></div>
<button type="button" class="btn-edit" onclick="goToStep(1)">Изменить</button>
</div>
<div class="review-section">
<h3>Доставка</h3>
<div class="review-data" id="review-delivery"></div>
<button type="button" class="btn-edit" onclick="goToStep(2)">Изменить</button>
</div>
<div class="review-section">
<h3>Оплата</h3>
<div class="review-data" id="review-payment"></div>
<button type="button" class="btn-edit" onclick="goToStep(3)">Изменить</button>
</div>
</div>
<div class="agreement-section">
<label class="agreement-checkbox">
<input type="checkbox" name="agreement" required>
<span>Я согласен с <a href="/agreement/" target="_blank">условиями публичной оферты</a> и <a href="/privacy/" target="_blank">политикой конфиденциальности</a></span>
</label>
<label class="newsletter-checkbox">
<input type="checkbox" name="newsletter" checked>
<span>Получать информацию о скидках и новинках</span>
</label>
</div>
<div class="form-actions">
<button type="button" class="btn-prev" onclick="prevStep(3)">
← Назад
</button>
<button type="submit" name="ms2_action" value="order/submit" class="btn-submit">
Подтвердить заказ
</button>
</div>
</div>
JavaScript для многошагового оформления
// Управление шагами оформления
var CheckoutSteps = {
currentStep: 1,
totalSteps: 4,
init: function() {
this.bindEvents();
this.loadStep(1);
},
bindEvents: function() {
// Валидация при переходе между шагами
$(document).on('click', '.btn-next', function(e) {
e.preventDefault();
var nextStep = parseInt($(this).data('next'));
if (CheckoutSteps.validateStep(CheckoutSteps.currentStep)) {
CheckoutSteps.nextStep(nextStep);
}
});
// Автозаполнение адреса по индексу
$(document).on('input', '#index', CheckoutSteps.debounce(function() {
var index = $(this).val();
if (index.length === 6) {
CheckoutSteps.fillAddressByIndex(index);
}
}, 500));
// Показ/скрытие полей в зависимости от выбранного способа
$(document).on('change', 'input[name="delivery"]', function() {
CheckoutSteps.handleDeliveryChange($(this).val());
});
$(document).on('change', 'input[name="payment"]', function() {
CheckoutSteps.handlePaymentChange($(this).val());
});
// Отправка заказа
$(document).on('submit', '.checkout-form', function(e) {
e.preventDefault();
if (CheckoutSteps.validateStep(4)) {
CheckoutSteps.submitOrder();
}
});
},
nextStep: function(step) {
this.hideStep(this.currentStep);
this.showStep(step);
this.updateProgress(step);
this.currentStep = step;
},
prevStep: function(step) {
this.hideStep(this.currentStep);
this.showStep(step);
this.updateProgress(step);
this.currentStep = step;
},
goToStep: function(step) {
this.hideStep(this.currentStep);
this.showStep(step);
this.updateProgress(step);
this.currentStep = step;
},
showStep: function(step) {
$('#step-' + this.getStepName(step)).fadeIn(300);
},
hideStep: function(step) {
$('#step-' + this.getStepName(step)).hide();
},
updateProgress: function(step) {
$('.checkout-progress .step').removeClass('active completed');
for (var i = 1; i < step; i++) {
$('.checkout-progress .step[data-step="' + i + '"]').addClass('completed');
}
$('.checkout-progress .step[data-step="' + step + '"]').addClass('active');
},
getStepName: function(step) {
var names = ['', 'contact', 'delivery', 'payment', 'confirm'];
return names[step];
},
validateStep: function(step) {
var isValid = true;
var $form = $('#step-' + this.getStepName(step));
// Очищаем предыдущие ошибки
$form.find('.error').text('');
$form.find('.form-group').removeClass('has-error');
// Проверяем обязательные поля
$form.find('[required]').each(function() {
var $field = $(this);
var value = $field.val().trim();
if (!value) {
isValid = false;
$field.closest('.form-group').addClass('has-error');
$field.siblings('.error').text('Обязательное поле');
}
});
// Специфичная валидация для каждого шага
switch(step) {
case 1:
isValid = isValid && this.validateContact();
break;
case 2:
isValid = isValid && this.validateDelivery();
break;
case 3:
isValid = isValid && this.validatePayment();
break;
case 4:
isValid = isValid && this.validateAgreement();
break;
}
return isValid;
},
validateContact: function() {
var email = $('#email').val();
var phone = $('#phone').val();
// Проверка email
if (email && !this.isValidEmail(email)) {
$('#email').closest('.form-group').addClass('has-error');
$('#email').siblings('.error').text('Неверный формат email');
return false;
}
// Проверка телефона
if (phone && !this.isValidPhone(phone)) {
$('#phone').closest('.form-group').addClass('has-error');
$('#phone').siblings('.error').text('Неверный формат телефона');
return false;
}
return true;
},
validateDelivery: function() {
var delivery = $('input[name="delivery"]:checked').val();
if (!delivery) {
alert('Выберите способ доставки');
return false;
}
// Для курьерской доставки проверяем адрес
if (delivery === 'courier') {
var requiredFields = ['city', 'street', 'building'];
for (var i = 0; i < requiredFields.length; i++) {
var field = requiredFields[i];
if (!$('#' + field).val().trim()) {
alert('Заполните адрес доставки');
return false;
}
}
}
return true;
},
validatePayment: function() {
var payment = $('input[name="payment"]:checked').val();
if (!payment) {
alert('Выберите способ оплаты');
return false;
}
return true;
},
validateAgreement: function() {
if (!$('input[name="agreement"]').is(':checked')) {
alert('Необходимо согласие с условиями оферты');
return false;
}
return true;
},
// Вспомогательные функции
isValidEmail: function(email) {
var re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
},
isValidPhone: function(phone) {
var cleaned = phone.replace(/\D/g, '');
return cleaned.length >= 10;
},
debounce: function(func, wait) {
var timeout;
return function() {
var later = function() {
clearTimeout(timeout);
func.apply(this, arguments);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
};
$(document).ready(function() {
CheckoutSteps.init();
});
Улучшения UX
Восстановление брошенных корзин
<?php
// Плагин: AbandonedCartReminder
switch ($modx->event->name) {
case 'OnWebPageInit':
// Сохраняем email если пользователь начал оформление
if ($_POST['ms2_action'] === 'order/submit' && !empty($_POST['email'])) {
$email = $_POST['email'];
$cartData = $modx->getService('minishop2')->cart->get();
if (!empty($cartData['products'])) {
$abandoned = $modx->newObject('AbandonedCart');
$abandoned->set('email', $email);
$abandoned->set('cart_data', json_encode($cartData));
$abandoned->set('created_at', time());
$abandoned->save();
}
}
break;
}
Автосохранение данных формы
// Автосохранение в localStorage
var FormAutosave = {
storageKey: 'checkout_form_data',
save: function() {
var data = {};
$('.checkout-form input, .checkout-form textarea, .checkout-form select').each(function() {
var $field = $(this);
var name = $field.attr('name');
if (name && $field.attr('type') !== 'password') {
if ($field.is(':checkbox') || $field.is(':radio')) {
if ($field.is(':checked')) {
data[name] = $field.val();
}
} else {
data[name] = $field.val();
}
}
});
localStorage.setItem(this.storageKey, JSON.stringify(data));
},
restore: function() {
var data = localStorage.getItem(this.storageKey);
if (!data) return;
try {
data = JSON.parse(data);
Object.keys(data).forEach(function(name) {
var $field = $('[name="' + name + '"]');
if ($field.length) {
if ($field.is(':checkbox') || $field.is(':radio')) {
$field.filter('[value="' + data[name] + '"]').prop('checked', true);
} else {
$field.val(data[name]);
}
}
});
} catch(e) {
console.error('Ошибка восстановления данных формы:', e);
}
},
clear: function() {
localStorage.removeItem(this.storageKey);
}
};
// Автосохранение каждые 10 секунд
setInterval(FormAutosave.save, 10000);
// Сохранение при изменении полей
$(document).on('input change', '.checkout-form input, .checkout-form textarea, .checkout-form select',
FormAutosave.debounce(FormAutosave.save, 1000));
// Восстановление при загрузке
$(document).ready(function() {
FormAutosave.restore();
});
// Очистка после успешного заказа
$(document).on('orderSuccess', function() {
FormAutosave.clear();
});
Заключение
Удобная корзина и процесс оформления заказа критически важны для конверсии интернет-магазина. miniShop2 предоставляет все необходимые инструменты, но успех зависит от правильной кастомизации под потребности пользователей.
Ключевые принципы:
- Минимизируйте количество шагов — чем меньше, тем лучше
- Показывайте прогресс — пользователь должен понимать, сколько ещё осталось
- Предотвращайте ошибки — валидация на лету, подсказки, автозаполнение
- Сохраняйте данные — ничто не раздражает больше, чем потеря введённой информации
Правильно настроенный процесс оформления может повысить конверсию на 30-50% по сравнению со стандартными решениями.
Связанные статьи: Создание каталога товаров на MODX, Интернет-магазин на miniShop2