Developing Checkout Screen for E-commerce
Checkout — most conversion-critical screen in e-commerce. Every extra second, unclear field, or validation error directly impacts revenue. Building full checkout takes 5–10 business days — one of store's most complex components.
Checkout Structure and Steps
Classical multi-step checkout:
- Contact info — email, phone (for SMS/calls if order issues)
- Shipping address — address fields with autocomplete via DaData or Google Places API
- Shipping method — options with real prices and timeframes
- Payment method — card, cash, installment, e-wallets
- Confirmation — final review, apply coupons, terms agreement
Alternative — single-page checkout (separate service). Multi-step better for complex orders with multiple shipping variants.
Address Autocomplete
DaData integration for CIS markets:
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;
};
After selecting suggestion, city, street, postal code auto-fill. Address validation includes deliverability check by carrier.
Real-Time Shipping Calculation
When address chosen and shipping method changed, rates requested via carrier API. For CDEK:
$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]],
]);
Results cached 10 minutes — rates don't change more often. If carrier API unavailable — show fixed "safe" cost with "TBD" note.
Checkout State Management
Checkout state must survive page reload — user shouldn't re-enter data. Intermediate data saved in sessionStorage or DB (for auth users):
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) }
)
);
Client and Server Validation
Validation both levels. Client — React Hook Form + Zod for instant feedback. Server — re-check before order creation.
Example contact schema:
const contactSchema = z.object({
email: z.string().email('Invalid email'),
phone: z.string().regex(/^\+7\d{10}$/, 'Format: +7XXXXXXXXXX'),
first_name: z.string().min(2, 'Min 2 chars').max(50),
last_name: z.string().min(2).max(50),
});
Server validation uses same rules, additionally checks: product availability, price currency, coupon validity.
Order Creation — Atomic Transaction
Order creation must be atomic. In one transaction:
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 prevents race condition on parallel orders.
Confirmation Page
After successful order creation — redirect to /orders/{id}/confirmation. On this page:
- Order number and brief summary
- Payment instructions (if pay-by-invoice selected)
- Expected shipping timeframes
- Link to track status
Confirmation email sent via queue (Laravel Queue + Redis), not in response.
Security and Duplicate Prevention
Checkout form protected from double-submit via idempotency_key — unique UUID generated on page open, sent with each request. Server checks key in Redis: if order with this key already created, returns existing order without re-creation.
CSRF token mandatory for all POST requests. For payment data — separate encryption level or full iframe payment provider (PCI DSS scope reduction).
Funnel Analytics
Each checkout step sends event to GA4: begin_checkout, add_shipping_info, add_payment_info, purchase. Enables funnel building and drop-off point identification.
Expected: multi-step checkout drop 10–20% per step. If first step > 40% — UX or speed problem.







