Разработка корзины на React для 1С-Битрикс

Наша компания занимается разработкой, поддержкой и обслуживанием решений на Битрикс и Битрикс24 любой сложности. От простых одностраничных сайтов до сложных интернет магазинов, CRM систем с интеграцией 1С и телефонии. Опыт разработчиков подтвержден сертификатами от вендора.
Предлагаемые услуги
Показано 1 из 1 услугВсе 1626 услуг
Разработка корзины на React для 1С-Битрикс
Средняя
~1-2 недели
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1177
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Разработка на базе Битрикс, Битрикс24, 1С для компании Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Разработка на базе 1С Предприятие для компании МИРСАНБЕЛ
    747
  • image_crm_dolbimby_434_0.webp
    Разработка сайта на CRM Битрикс24 для компании DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Разработка на базе Битрикс24 для компании ТЕХНОТОРГКОМПЛЕКС
    976

Разработка корзины на React для 1С-Битрикс

Корзина — это не просто список товаров. Это критически важный компонент, от которого напрямую зависит конверсия в заказ. Стандартная корзина Битрикса работает с полной перезагрузкой страницы или через устаревший Ajax-компонент. React-корзина обновляется мгновенно, синхронизируется с бэкендом без мигания интерфейса и работает одинаково во всплывающей панели и на полной странице оформления заказа.

Архитектура: корзина как общее состояние

Корзина в SPA — это глобальное состояние, которое доступно из любого компонента: кнопка «В корзину» на карточке товара, иконка в шапке, боковая панель корзины, страница оформления. Все они должны показывать одни и те же данные.

// /src/store/cart.ts — Zustand store
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';

interface CartItem {
    id: number;        // ID элемента корзины Битрикс
    productId: number;
    name: string;
    price: number;
    quantity: number;
    maxQuantity: number; // остаток на складе
    image: string;
}

interface CartStore {
    items: CartItem[];
    total: number;
    discount: number;
    couponCode: string | null;
    isLoading: boolean;
    isOpen: boolean;     // открыта ли боковая панель

    // Действия
    fetchCart: () => Promise<void>;
    addItem: (productId: number, quantity?: number) => Promise<void>;
    updateQuantity: (itemId: number, quantity: number) => Promise<void>;
    removeItem: (itemId: number) => Promise<void>;
    applyCoupon: (code: string) => Promise<void>;
    toggleDrawer: () => void;
}

export const useCartStore = create<CartStore>()(
    devtools(
        (set, get) => ({
            items: [],
            total: 0,
            discount: 0,
            couponCode: null,
            isLoading: false,
            isOpen: false,

            fetchCart: async () => {
                set({ isLoading: true });
                try {
                    const data = await bitrixApi.get<CartData>('cart.get');
                    set({
                        items: data.items,
                        total: data.total,
                        discount: data.discount,
                        couponCode: data.coupon_code,
                    });
                } finally {
                    set({ isLoading: false });
                }
            },

            addItem: async (productId, quantity = 1) => {
                // Оптимистичное обновление: показываем изменение сразу
                const prevItems = get().items;
                const existing = prevItems.find(i => i.productId === productId);

                if (existing) {
                    set(state => ({
                        items: state.items.map(i =>
                            i.productId === productId
                                ? { ...i, quantity: i.quantity + quantity }
                                : i
                        ),
                    }));
                }

                try {
                    const data = await bitrixApi.post<CartData>('cart.add', {
                        product_id: productId,
                        quantity,
                    });
                    set({ items: data.items, total: data.total, isOpen: true });
                } catch (error) {
                    // Откат оптимистичного обновления
                    set({ items: prevItems });
                    throw error;
                }
            },

            updateQuantity: async (itemId, quantity) => {
                if (quantity < 1) {
                    return get().removeItem(itemId);
                }
                const data = await bitrixApi.post<CartData>('cart.update', {
                    item_id: itemId,
                    quantity,
                });
                set({ items: data.items, total: data.total });
            },

            removeItem: async (itemId) => {
                const data = await bitrixApi.post<CartData>('cart.remove', {
                    item_id: itemId,
                });
                set({ items: data.items, total: data.total });
            },

            applyCoupon: async (code) => {
                const data = await bitrixApi.post<CartData>('cart.coupon', {
                    coupon: code,
                });
                set({ items: data.items, total: data.total, discount: data.discount,
                      couponCode: data.coupon_code });
            },

            toggleDrawer: () => set(s => ({ isOpen: !s.isOpen })),
        })
    )
);

PHP-бэкенд для корзины

Операции с корзиной на Битрикс используют модуль sale:

// /local/ajax/api.php
CModule::IncludeModule('sale');
CModule::IncludeModule('catalog');

function getCartData(): array {
    $basket = \Bitrix\Sale\Basket::loadItemsForFUser(
        \Bitrix\Sale\Fuser::getId(), SITE_ID
    );

    $items = [];
    foreach ($basket as $item) {
        $items[] = [
            'id'          => $item->getId(),
            'product_id'  => $item->getProductId(),
            'name'        => $item->getField('NAME'),
            'price'       => $item->getPrice(),
            'quantity'    => $item->getQuantity(),
            'max_quantity'=> getProductStock($item->getProductId()),
            'image'       => getProductImage($item->getProductId()),
        ];
    }

    return [
        'items'      => $items,
        'total'      => $basket->getPrice(),
        'discount'   => $basket->getBasePrice() - $basket->getPrice(),
        'coupon_code'=> getAppliedCoupon(),
    ];
}

case 'cart.add':
    $basket = \Bitrix\Sale\Basket::loadItemsForFUser(...);
    $item   = \Bitrix\Sale\BasketItem::create($basket, 'catalog', (int)$_POST['product_id']);
    $item->setFields([
        'QUANTITY' => max(1, (int)$_POST['quantity']),
        'CURRENCY' => \Bitrix\Currency\CurrencyManager::getBaseCurrency(),
        'LID'      => SITE_ID,
        'PRODUCT_PROVIDER_CLASS' => '\CCatalogProductProvider',
    ]);
    $basket->save();
    echo json_encode(['result' => getCartData()]);
    break;

Компонент корзины

// CartDrawer.tsx — боковая панель корзины
import { useCartStore } from '../store/cart';

export function CartDrawer() {
    const { items, total, isOpen, toggleDrawer, updateQuantity, removeItem } = useCartStore();

    return (
        <aside className={`cart-drawer ${isOpen ? 'cart-drawer--open' : ''}`}>
            <div className="cart-drawer__header">
                <h2>Корзина ({items.length})</h2>
                <button onClick={toggleDrawer} aria-label="Закрыть">✕</button>
            </div>

            <div className="cart-drawer__items">
                {items.map(item => (
                    <CartItem
                        key={item.id}
                        item={item}
                        onQuantityChange={(q) => updateQuantity(item.id, q)}
                        onRemove={() => removeItem(item.id)}
                    />
                ))}
            </div>

            <div className="cart-drawer__footer">
                <div className="cart-total">Итого: {formatPrice(total)}</div>
                <a href="/order/" className="btn btn-primary btn-full">
                    Оформить заказ
                </a>
            </div>
        </aside>
    );
}

Синхронизация с серверной корзиной

Корзина в Битрикс хранится на сервере (привязана к fUser — анонимному пользователю до авторизации). При авторизации анонимная корзина должна объединиться с корзиной пользователя — это делает Битрикс автоматически в обработчике OnUserLoginExternal.

Синхронизация при восстановлении сессии (пользователь открыл новую вкладку):

// При монтировании приложения
useEffect(() => {
    useCartStore.getState().fetchCart();
}, []);

// При возвращении пользователя на вкладку
document.addEventListener('visibilitychange', () => {
    if (!document.hidden) {
        useCartStore.getState().fetchCart();
    }
});

React-корзина с оптимистичными обновлениями даёт мгновенный отклик UI и скрывает задержку сетевых запросов от пользователя. В сочетании с правильным PHP-бэкендом на Битрикс-корзине это решение работает надёжно и не требует переписывания логики заказов — Битрикс по-прежнему управляет всем процессом продажи.