Розроблення екрана оформлення заказу (Checkout) для інтернет-магазину
Checkout — найбільш конверсійно критичний екран в e-commerce. Будь-яка зайва секунда завантаження, незрозумілое поле чи помилка валідації напряму впливають на дохід. Розроблення повноцінного checkout займає від 5 до 10 робочих днів: це один із найскладніших компонентів магазину.
Структура та кроки checkout
Класичний багатокроковий checkout складається з таких етапів:
- Контактні дані — email, телефон (для SMS/дзвінків при проблемах з заказом)
- Адреса доставки — поля адреси з автодоповненням через DaData чи Google Places API
- Способ доставки — список варіантів з реальними цінами та строками
- Способ оплати — карта, готівка, розстрочка, електронні гаманці
- Підтвердження — итоговий перегляд, застосування купонів, згода з умовами
Альтернатива — однострашневий checkout (див. окрему послугу). Багатокроковий краще працює для складних заказів з кількома варіантами доставки.
Автодоповнення адреси
Інтеграція з DaData для ринків СНГ:
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: 'Russia' }] }),
});
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 перевізника недоступен — показуємо фіксовану «безпечну» вартість з пометкою «уточнюється».
Управління станом checkout
Стан checkout повинен переживати перезавантаження сторінки — користувач не повинен вводити дані заново. Для цього промежуточні дані зберігаються в sessionStorage чи в БД (для авторизованих):
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) }
)
);
Валідація на клієнті та сервері
Валідація виконується на обох рівнях. На клієнті — 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 при паралельних заказах одного товару.
Сторінка підтвердження
Після успішного створення заказу — redirect на /orders/{id}/confirmation. На цій сторінці:
- Номер заказу та коротке резюме
- Інструкції по оплаті (якщо вибрана оплата по рахунку)
- Очікувані сроки доставки
- Посилання на відстеження статусу
Email з підтвердженням уходить через чергу (Laravel Queue + Redis), не у момент відповіді на запит.
Безпека та запобігання дублюванню
Форма checkout захищена від двійного сабміту через idempotency_key — унікальний UUID, генеруємий при відкритті сторінки та відправляємий з кожним запитом. Сервер перевіряє ключ у Redis: якщо заказ з таким ключем вже створений, повертає існуючий заказ без повторного створення.
CSRF-токен обов'язковий для всіх POST-запитів. Для платіжних даних — окремий рівень шифрування чи повний винос в iframe платіжного провайдера (PCI DSS scope reduction).
Аналітика воронки
Кожен крок checkout відправляє подію в GA4: begin_checkout, add_shipping_info, add_payment_info, purchase. Це дозволяє будувати воронку та видити точку відмови.
Середній показник: якщо checkout багатокроковий, очікуваний drop на кожному кроці — 10–20%. Якщо drop на першому кроці перевищує 40% — проблема з UX чи швидкістю завантаження.







