Розробка односторінкового 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, Tinkoff, CloudPayments). Форма карти — iframe провайдера прямо всередині checkout, без редіректу:

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 години, після цього чернетка видаляється.