# Товарные опции и дополнительные услуги в корзине витрины

> Сделал товарные опции в библиотеке витрин inSales: компонент выбора, пересчёт цены и операции корзины. Один и тот же вариант с разными дополнениями лежит в корзине отдельными позициями.

- Адрес: https://anu3ev.com/ru/work/product-options/
- Период: Январь – апрель 2025
- Где: inSales
- Роль: Frontend-разработчик: компонент ui-accessories, цены, API корзины, документация
- Навыки: JavaScript, API Development, AJAX, Front-End Development, E-Commerce, Technical Documentation, HTML5, CSS, Debugging

## Задача

Магазины хотели продавать вместе с товаром дополнения и услуги: подарочную упаковку, открытку, ленту. Одни опции обязательны, другие нет, и у каждой группы свои ограничения. Итоговую цену покупатель должен видеть ещё до того, как положит товар в корзину.

Самое сложное было в корзине. Общая библиотека витрин CommonJS считала позицию корзины вариантом товара. С опциями это перестало быть правдой: букет Classic в обычной упаковке и тот же букет в подарочной коробке с открыткой — две разные покупки с разной ценой. Любое место, которое искало товар в корзине по ID варианта, теперь могло попасть не в ту позицию: изменение количества, удаление, счётчики на карточке и проверка остатков.

## Моя роль

Я делал часть на витрине, в CommonJS: компонент `ui-accessories`, пересчёт цены, операции корзины, вывод опций в корзине и документацию для разработчиков магазинов. Метод корзины, который принимает выбранные опции, сделали бэкенд-разработчики. Мой код проходил ревью и QA.

## Что я сделал

### Один компонент для обязательных и необязательных групп

`ui-accessories` подключается через `data-accessories="{{ product.id }}"` и рендерит группы опций по шаблонам из разметки, поэтому работает и в стандартных виджетах, и в собственном дизайне магазина. У каждой группы есть минимальное и максимальное количество. В демо-магазине упаковку выбирают обязательно и только одну, а дополнений можно взять до двух. Я сделал рендер опций по data-атрибутам, а не привязал его к странице товара: тот же рендер был нужен AJAX-товарам, а чтобы добавить товар с обязательными опциями из превью в категории, данные опций тоже нужны.

### Цена зависит от варианта и опций

Цена пересчитывается, когда покупатель меняет вариант или набор опций. В демо-магазине подарочная коробка (+$5) и открытка (+$3) поднимают цену букета Classic с $40 до $48, а те же дополнения для размера Grand — с $60 до $68. Ноль в проверках цены нельзя было считать отсутствием значения: вариант вполне может стоить 0, а обычная упаковка добавляет $0.

### Позиция корзины — это вариант вместе с опциями

Я доработал `Cart.add`, `Cart.set`, `Cart.delete` и очередь задач корзины, чтобы вместе с ID варианта они передавали `accessory_value_ids`, сгруппированные в `accessoriable_variant_ids`. Изменение количества и удаление теперь касаются одной конкретной комбинации, а не всех позиций с этим вариантом. В виджете корзины у каждой позиции видны названия выбранных опций и их цены.

![Корзина, где один вариант букета лежит двумя позициями: в обычной упаковке и в подарочной коробке с открыткой](https://anu3ev.com/images/work/product-options/cart-same-variant-two-lines.webp)
*Демо-магазин: один вариант Classic, две позиции со своими опциями, количеством и ценой.*

### Счётчики и остатки для разных комбинаций

Раньше на два вопроса был один ответ, теперь ответы разошлись. Счётчик на карточке и кнопка «В корзину» следуют за выбранной комбинацией. Если переключиться с обычной упаковки на подарочную, счётчик меняется, а после удаления обычной позиции снова появляется кнопка добавления, хотя подарочная остаётся в корзине. Остаток же относится к варианту: позиции с разными опциями — это всё равно единицы одного товара, поэтому при проверке их количество складывается. Я прошёл по местам, где товар в корзине искали по ID варианта, и в каждом месте развёл эти два вопроса. В виджете уведомлений появилась отдельная ошибка про неверное количество выбранных опций.

![Корзина с 7 единицами в обычной упаковке и 9 в подарочной и предупреждением о максимальном количестве](https://anu3ev.com/images/work/product-options/cart-combined-quantity-limit.webp)
*Демо-магазин: 7 + 9 единиц одного варианта в двух позициях. При попытке добавить ещё появляется предупреждение о лимите.*

### Совместимость и документация

Опции должны были работать в виджете карточки товара, AJAX-товарах, превью в категориях, модальных окнах, мультикорзинах и старых шаблонах. Я составил чек-лист проверки: добавление, изменение количества и удаление обычных товаров, товаров с мультискладом и товаров с опциями во всех этих местах, а также пересчёт цены при смене варианта и опций. Ещё я описал API опций для разработчиков, которые собирают карточки и корзины в собственных шаблонах.

## Результат

- Покупатель выбирает обязательные и дополнительные опции, сразу видит итоговую цену и кладёт в корзину именно эту комбинацию.
- Один вариант может лежать в корзине несколькими позициями, а изменение количества и удаление касаются только выбранной.
- У разработчиков магазинов появились компонент и задокументированный API корзины для опций в собственных шаблонах.
- Опции работают в товарных виджетах и AJAX-товарах. Про библиотеку, в которую всё это встроено, — [кейс о CommonJS](https://anu3ev.com/ru/work/commonjs-storefront-library/).

Скриншоты сделаны в демо-магазине inSales на фотографиях со свободной лицензией. Товары и цены придуманы для примера и не связаны с реальными продажами.
