Разработка одностраничного Checkout для интернет-магазина

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

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка одностраничного Checkout для интернет-магазина
Средняя
~5 рабочих дней
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • 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

Разработка одностраничного Checkout для интернет-магазина

Одностраничный checkout размещает все этапы оформления заказа на одном экране — без переходов между шагами. Это снижает количество HTTP-запросов, устраняет потерю данных при навигации браузера и сокращает воспринимаемое время оформления. В среднем конверсия выше на 10–25% по сравнению с многошаговым вариантом на мобильных устройствах. Разработка занимает 5–8 рабочих дней.

Компоновка экрана

Стандартный layout одностраничного checkout:

┌─────────────────────────────┬──────────────────────┐
│  Контакты                   │                      │
│  Адрес доставки             │   Состав заказа      │
│  Способ доставки            │   Промокод           │
│  Способ оплаты              │   Итоговая сумма     │
│  [Оформить заказ]           │                      │
└─────────────────────────────┴──────────────────────┘

На мобильных — одна колонка, правая панель с итогом прокручивается вверх после состава заказа. Sticky-кнопка «Оформить» фиксируется внизу экрана.

Динамическое обновление правой панели

Правая панель пересчитывается при каждом изменении: выбор метода доставки меняет итоговую сумму, ввод промокода — скидку. Используем debounced реактивность:

const { shipping, coupon } = useCheckoutStore();

const { data: summary } = useQuery({
  queryKey: ['checkout-summary', shipping?.id, coupon],
  queryFn: () => api.post('/checkout/summary', { shipping_id: shipping?.id, coupon }),
  staleTime: 30_000,
  enabled: !!shipping,
});

Запрос к серверу идёт только при реальных изменениях, не на каждый keystroke в поле адреса.

Условная видимость полей

Поля оплаты и доставки появляются по мере заполнения предыдущих блоков. Логика управляется состоянием формы:

const { watch } = useFormContext();
const email = watch('contact.email');
const addressFilled = watch(['address.city', 'address.street', 'address.house'])
  .every(Boolean);

return (
  <>
    <ContactBlock />
    {email && <AddressBlock />}
    {addressFilled && <ShippingBlock />}
    {selectedShipping && <PaymentBlock />}
  </>
);

Такой подход снижает когнитивную нагрузку — пользователь не видит весь экран сразу, а заполняет последовательно.

Валидация без блокировки

В одностраничном checkout важно не блокировать кнопку «Оформить» до полного заполнения формы — это создаёт ощущение тупика. Вместо этого:

  • Поля валидируются при onBlur, не onChange
  • Кнопка всегда активна
  • При клике срабатывает trigger() из React Hook Form, подсвечиваются все незаполненные поля и страница скроллится к первой ошибке
const handleSubmit = async () => {
  const valid = await form.trigger();
  if (!valid) {
    const firstError = Object.keys(form.formState.errors)[0];
    document.querySelector(`[name="${firstError}"]`)?.scrollIntoView({ behavior: 'smooth' });
    return;
  }
  await placeOrder(form.getValues());
};

Автозаполнение из профиля

Для авторизованных пользователей форма предзаполняется данными из профиля:

useEffect(() => {
  if (user) {
    form.reset({
      contact: { email: user.email, phone: user.phone },
      address: user.defaultAddress ?? {},
    });
  }
}, [user]);

Если у пользователя несколько сохранённых адресов — показываем dropdown «Выбрать сохранённый адрес», при выборе подставляем поля.

Inline-выбор доставки

Методы доставки отображаются карточками с иконкой перевозчика, ценой и сроком. При переключении между методами правая панель немедленно обновляет итог. Если адрес ещё не введён — показываем skeleton-заглушки с «от X ₽».

<RadioGroup value={selectedShipping?.id} onValueChange={handleShippingChange}>
  {shippingOptions.map(option => (
    <RadioGroupItem key={option.id} value={option.id}>
      <span>{option.carrier_name}</span>
      <span>{option.estimated_days} дн.</span>
      <span className="font-bold">{option.price === 0 ? 'Бесплатно' : `${option.price} ₽`}</span>
    </RadioGroupItem>
  ))}
</RadioGroup>

Встроенные платёжные формы

Для банковских карт используем JS SDK платёжного провайдера (ЮKassa, Тinkoff, CloudPayments). Форма карты — iframe провайдера прямо внутри checkout, без редиректа:

// CloudPayments
const widget = new cp.CloudPayments();
widget.pay('charge', {
  publicId: PUBLIC_ID,
  amount: summary.total,
  currency: 'RUB',
  invoiceId: order.id,
  email: form.getValues('contact.email'),
}, {
  onSuccess: (options) => router.push(`/orders/${order.id}/confirmation`),
  onFail: (reason) => toast.error(`Платёж не прошёл: ${reason}`),
});

Производительность

Одностраничный checkout весит больше многошагового — все блоки монтируются сразу. Оптимизации:

  • Code splitting платёжного SDK — загружается только при выборе метода «Карта»
  • Lazy import компонентов карты, не нужных при первом рендере
  • Preconnect к API DaData и платёжного провайдера в <head>

Целевое время до интерактивности — менее 2 секунд на 4G.

Сохранение прогресса

Данные формы сохраняются в sessionStorage через Zustand persist. Если пользователь случайно закрыл вкладку — при возврате форма восстанавливается. TTL сессии — 2 часа, после этого черновик удаляется.