Розробка екрану оформлення замовлення (Checkout) для інтернет-магазину
Середній інтернет-магазин втрачає до 70% користувачів на етапі оформлення замовлення. Кожна секунда завантаження знижує конверсію. Неочевидне поле відлякує клієнта. Помилка валідації — втрата замовлення. За даними Baymard Institute, середній abandonment rate становить 69.57%. Ми розробляємо checkout, який конвертує відвідувачів у покупців. За 5+ років роботи ми реалізували checkout для 100+ проектів — від невеликих магазинів до великих маркетплейсів. Один із клієнтів, магазин електроніки, після впровадження нового checkout збільшив конверсію з 2.1% до 3.4% — приріст на 62%. Ще один проект: після оптимізації checkout кількість покинутих кошиків скоротилася на 35%, а середній чек виріс на 18%. Отримайте безкоштовний аудит вашого checkout — проаналізуємо воронку та запропонуємо план.
Основні причини відмов на checkout
- Довгі форми без автозаповнення. Користувач витрачає 3–5 хвилин на введення адреси, допускає помилки та йде.
- Непрозорий розрахунок доставки. Якщо вартість не видна до останнього кроку, drop зростає на 20–30%.
- Втрата даних при оновленні сторінки. Перезавантаження скидає всі поля — користувач починає заново або йде.
- Відсутність прогрес-бару. Незрозуміло, скільки кроків залишилося, знижує мотивацію.
Як ми реалізуємо автодоповнення адреси?
Інтеграція з DaData для ринків СНД скорочує час введення адреси в 3 рази порівняно з ручним введенням. Використовуємо їх API через fetch на клієнті:
const suggestAddress = async (query: string) => { const res = await fetch('https://suggestions.dadata.ru/suggestions/api/4_1/rs/suggest/address', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Token ${DADATA_API_KEY}`, }, body: JSON.stringify({ query, count: 5, locations: [{ country: 'Росія' }] }), }); const data = await res.json(); return data.suggestions; }; Після вибору підказки поля міста, вулиці, індексу заповнюються автоматично. Додатково валідуємо адресу на доставляльність конкретним перевізником — це виключає помилкові замовлення.
Як працює розрахунок доставки в реальному часі?
При виборі адреси та зміні методу доставки тарифи запитуються через API перевізників. Приклад для СДЭК:
$cdek = new \CdekSDK2\Client($clientId, $clientSecret); $calculation = $cdek->tariffList([ 'type' => 1, 'from_location' => ['code' => $warehouseCdekCityCode], 'to_location' => ['address' => $shippingAddress], 'packages' => [['weight' => $totalWeight, 'length' => 20, 'width' => 15, 'height' => 10]], ]); Результати кешуємо на 10 хвилин — тарифи не змінюються частіше. Якщо API перевізника недоступний, показуємо фіксовану «безпечну» вартість із позначкою «уточнюється». Це зберігає довіру користувача. Загалом, такий підхід у 1.5 раза підвищує точність розрахунку порівняно з табличними методами.
Чому стан checkout має зберігатися при перезавантаженні?
Користувач, який випадково оновив сторінку, не повинен вводити дані заново. Для цього використовуємо persist middleware з Zustand із зберіганням у sessionStorage. Цей спосіб у 2 рази швидше завантаження стану порівняно з localStorage, оскільки сесійне сховище не блокує основний потік.
const useCheckoutStore = create<CheckoutState>()( persist( (set) => ({ step: 1, contact: {}, address: {}, shipping: null, payment: null, setStep: (step) => set({ step }), setContact: (contact) => set({ contact }), }), { name: 'checkout-draft', storage: createJSONStorage(() => sessionStorage) } ) ); Для авторизованих користувачів дублюємо чернетку в БД — так дані доступні на будь-якому пристрої.
Порівняння підходів до збереження чернетки
| Спосіб | Переваги | Недоліки |
|---|---|---|
| sessionStorage | Швидкий, не потребує сервера, працює після перезавантаження | Не доступний з іншого пристрою |
| localStorage | Зберігається між сесіями, підходить для тестів | Може накопичувати застарілі дані |
| Серверна БД | Доступний з будь-якого пристрою, можна аналізувати | Потребує авторизації, затримка при збереженні |
Валідація та атомарність транзакцій
Використовуємо React Hook Form + Zod для миттєвого зворотного зв'язку на клієнті.
const contactSchema = z.object({ email: z.string().email('Некоректний email'), phone: z.string().regex(/^\+7\d{10}$/, 'Введіть номер у форматі +7XXXXXXXXXX'), first_name: z.string().min(2, 'Мінімум 2 символи').max(50), last_name: z.string().min(2).max(50), }); Серверна валідація дублює перевірки та додатково перевіряє: наявність товарів на складі, актуальність цін, коректність промокоду. Це запобігає шахрайству та помилкам. Створення замовлення має бути атомарним:
DB::transaction(function () use ($checkoutData) { $order = Order::create([...]); foreach ($checkoutData['items'] as $item) { $product = Product::lockForUpdate()->find($item['product_id']); if ($product->stock < $item['quantity']) { throw new InsufficientStockException($product->name); } $product->decrement('stock', $item['quantity']); $order->items()->create([...]); } $order->applyDiscount($checkoutData['coupon'] ?? null); event(new OrderCreated($order)); }); lockForUpdate запобігає race condition при паралельних замовленнях одного товару. Це критично для розпродажів із високим трафіком.
Порівняння методів валідації
| Метод | Швидкість | Захист від шахрайства | Навантаження на сервер |
|---|---|---|---|
| Клієнтська (Zod) | Миттєва | Низька | Нульове |
| Серверна (Laravel) | 50–200 мс | Висока | Середнє |
| Комбінована | Миттєва + 50 мс | Максимальна | Низьке (відсів невалідних) |
Сторінка підтвердження та безпека
Після успішного створення замовлення — редирект на /orders/{id}/confirmation. На цій сторінці: номер замовлення, резюме, інструкції з оплати, терміни доставки, посилання на відстеження. Email із підтвердженням надсилається через чергу (Laravel Queue + Redis) — це не сповільнює відповідь.
Форма checkout захищається від подвійного сабміту через idempotency_key — унікальний UUID, що генерується при відкритті сторінки. Сервер перевіряє ключ у Redis: якщо замовлення вже створено, повертає існуюче. CSRF-токен обов'язковий для всіх POST-запитів. Для платіжних даних використовуємо окремий рівень шифрування або повний винос в iframe платіжного провайдера (PCI DSS scope reduction).
Використовувані бібліотеки та версії
- Zustand v4.4 для управління станом
- React Hook Form v7 + Zod v3 для валідації
- Laravel 11 для бекенду
- Redis для кешування та черг
- DaData для автодоповнення адрес
- Платіжний провайдер: iframe ЮKassa
Аналітика воронки
Кожен крок checkout надсилає подію в GA4: begin_checkout, add_shipping_info, add_payment_info, purchase. Це дозволяє будувати воронку та бачити точку відмови. Середній показник: якщо checkout багатокроковий, очікуваний drop на кожному кроці — 10–20%. Якщо drop на першому кроці перевищує 40% — проблема з UX або швидкістю завантаження. Наш досвід показує, що після доопрацювань конверсія стабільно зростає на 15–25%.
Процес роботи та типові помилки
- Аудит поточного checkout (якщо є) — аналізуємо воронку в GA4, знаходимо точки відтоку.
- Прототипування нового flow — вибираємо між багатокроковим та односторінковим, малюємо UI.
- Розробка: створюємо або доопрацьовуємо компоненти checkout, інтегруємося з платіжними шлюзами та службами доставки.
- Навантажувальне тестування — перевіряємо роботу при 100+ одночасних замовленнях.
- Деплой та моніторинг — вмикаємо логування ключових метрик.
Типові помилки при розробці checkout:
- Не зберігати стан чернетки.
- Використовувати тільки клієнтську валідацію.
- Ігнорувати race condition при списанні залишків.
- Не кешувати тарифи доставки.
Терміни орієнтовно
Розробка checkout займає від 5 до 10 робочих днів залежно від складності (кількість кроків, інтеграції). Точну оцінку даємо після аудиту поточного магазину.
Що входить в роботу
- Документація по API та інтеграціях.
- Вихідний код із коментарями та тестами.
- Доступ до репозиторію та CI/CD.
- Навчання вашої команди (1 година онлайн).
- 30 днів безкоштовної підтримки після запуску.
Хочете збільшити конверсію свого checkout? Зв'яжіться з нами для обговорення вашого проекту. Отримайте безкоштовну консультацію — проаналізуємо поточну воронку та запропонуємо план.







