Ви отримали автомобіль з подряпиною на бампері, але акт прийому-передачі не складали. Довести, що пошкодження не ваше, неможливо. Або верифікація прав займає тиждень, а клієнти йдуть до конкурентів. Ми вирішили ці задачі для 20+ проектів за багаторічний досвід. Наші інженери побудували архітектуру, яку можна масштабувати до тисяч автомобілів. Все починається з моделі даних — саме вона визначає, наскільки гнучкою та надійною буде платформа. За даними J.D. Power, 60% користувачів відмовляються від платформ без верифікації, а впровадження онлайн-огляду знижує суперечки щодо пошкоджень на 40%. Економія на операційних витратах сягає 30% завдяки автоматизації.
Як влаштована модель даних для оренди?
Кожен автомобіль — конкретна одиниця з VIN, номером, історією страховок та техоглядів. Бронювання прив'язується до реального ТЗ, а не до абстрактного класу. Це виключає подвійний продаж. Для геоданих використовуємо PostGIS — він у 100 разів швидший для просторових запитів, ніж звичайні індекси.
CREATE TABLE vehicles (
id BIGSERIAL PRIMARY KEY,
owner_id BIGINT NOT NULL REFERENCES users(id),
make VARCHAR(50) NOT NULL, -- Toyota
model VARCHAR(50) NOT NULL, -- Camry
year SMALLINT NOT NULL,
plate_number VARCHAR(20) UNIQUE NOT NULL,
vin VARCHAR(17) UNIQUE NOT NULL,
transmission VARCHAR(10) NOT NULL, -- auto, manual
fuel_type VARCHAR(15) NOT NULL, -- petrol, diesel, electric, hybrid
seats SMALLINT NOT NULL DEFAULT 5,
mileage_km INT NOT NULL DEFAULT 0,
location GEOGRAPHY(POINT, 4326),
insurance_expiry DATE NOT NULL,
inspection_expiry DATE NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'available'
);
CREATE TABLE rental_bookings (
id BIGSERIAL PRIMARY KEY,
vehicle_id BIGINT NOT NULL REFERENCES vehicles(id),
renter_id BIGINT NOT NULL REFERENCES users(id),
pickup_datetime TIMESTAMPTZ NOT NULL,
return_datetime TIMESTAMPTZ NOT NULL,
pickup_location GEOGRAPHY(POINT, 4326),
return_location GEOGRAPHY(POINT, 4326),
status VARCHAR(20) NOT NULL DEFAULT 'pending',
total_amount NUMERIC(10,2) NOT NULL,
security_deposit NUMERIC(10,2) NOT NULL,
fuel_policy VARCHAR(20) NOT NULL -- 'full_to_full', 'prepaid'
);
Така схема дозволяє будувати складні запити: знайти всі вільні авто поряд з користувачем, перевірити перетин бронювань, розрахувати вартість з урахуванням сезонності. Ми використовуємо транзакції та блокування рядків, щоб виключити state race при бронюванні.
Чому верифікація водія критична для платформи?
Платформа без верифікації несе юридичну відповідальність за дії орендарів. Ми інтегруємо Stripe Identity або Jumio: перевірка документа, селфі, розпізнавання дати закінчення прав. Все займає 30 секунд без участі оператора. Це знижує кількість шахрайських бронювань на 60%.
- Ініціалізація — створюємо VerificationSession через Stripe API.
- Верифікація — користувач завантажує документ і селфі.
- Обробка — webhook оновлює статус користувача.
- Готово — водій може бронювати.
class DriverVerificationService
{
public function initiateVerification(User $user): string
{
// Stripe Identity
$session = $this->stripe->identity->verificationSessions->create([
'type' => 'document',
'options' => [
'document' => [
'allowed_types' => ['driving_license'],
'require_id_number' => true,
'require_live_capture' => true,
'require_matching_selfie' => true,
],
],
'metadata' => ['user_id' => $user->id],
]);
$user->update([
'stripe_verification_session_id' => $session->id,
'verification_status' => 'pending',
]);
return $session->url;
}
public function handleWebhook(array $payload): void
{
$session = $payload['data']['object'];
$user = User::where(
'stripe_verification_session_id',
$session['id']
)->firstOrFail();
match ($session['status']) {
'verified' => $user->update([
'verification_status' => 'verified',
'verified_at' => now(),
'license_expiry' => $session['last_verification_report']['document']['expiration_date'] ?? null,
]),
'requires_input' => $user->update(['verification_status' => 'failed']),
default => null,
};
}
}
Як працює гнучке ціноутворення?
Підтримуємо погодинні, подобові та комбіновані тарифи. Сезонний коефіцієнт автоматично застосовується залежно від класу автомобіля та дати. Для довгострокової оренди — знижки до 20%. Калькулятор миттєво порівнює всі варіанти та показує найкращу ціну. Середня вартість оренди знижується на 15% за рахунок динамічного ціноутворення.
| Тривалість | Тариф | Знижка |
|---|---|---|
| 1-6 годин | погодинний | — |
| 1-6 днів | подобовий | — |
| 7-13 днів | подобовий | 10% |
| 14-29 днів | подобовий | 15% |
| 30+ днів | подобовий | 20% |
class PricingCalculator
{
public function calculate(
Vehicle $vehicle,
Carbon $pickup,
Carbon $return
): PricingResult {
$hours = $pickup->diffInHours($return);
$days = $pickup->diffInDays($return);
// Якщо менше доби — рахуємо погодинно
if ($hours <= 24 && $vehicle->hourly_rate) {
$base = $vehicle->hourly_rate * $hours;
} else {
// Подобово + доплата за неповну добу
$fullDays = floor($hours / 24);
$remainHours = $hours % 24;
$base = $vehicle->daily_rate * $fullDays;
if ($remainHours > 0) {
// Якщо залишок > половини доби — рахувати як добу
$base += $remainHours > 12
? $vehicle->daily_rate
: $vehicle->hourly_rate * $remainHours;
}
}
// Сезонний коефіцієнт
$multiplier = $this->getSeasonMultiplier($pickup, $vehicle->vehicle_class);
// Знижка за тривалу оренду
$discount = match(true) {
$days >= 30 => 0.20,
$days >= 14 => 0.15,
$days >= 7 => 0.10,
default => 0.0,
};
$subtotal = $base * $multiplier * (1 - $discount);
$deposit = $vehicle->deposit_amount;
$fee = $subtotal * $this->platformFeeRate;
return new PricingResult(
base: $base,
multiplier: $multiplier,
discount: $discount,
subtotal: $subtotal,
platform_fee: $fee,
security_deposit: $deposit,
total: $subtotal + $fee,
);
}
}
Як забезпечується фотофіксація?
Перед видачею та після повернення обов'язковий цифровий акт: схема авто з координатами пошкоджень, фотографії, показання одометра, рівень палива. Підпис обох сторін через canvas (signature_pad.js). Акт зберігається в S3 і стає юридично значущим документом.
Приклад кастомного поля damage
```json { "title": "Подряпина на бампері", "coordinates": [55.7558, 37.6176], "photos": ["https://s3.example.com/act/123.jpg"] } ```Чому GPS-трекінг обов'язковий для автопарку?
Для парків від 20+ авто live-трекінг через MQTT — кожні 10 секунд координати. MQTT у 10 разів ефективніший за HTTP-опитування за затримками та трафіком. Геозонування: якщо авто виїжджає за дозволену зону, власник отримує alert. Це знижує ризики викрадення та шахрайства на 40%.
// Обробник MQTT-повідомлень від трекерів
class VehicleTelematicsHandler
{
public function handle(string $topic, string $payload): void
{
// topic: vehicles/{plate}/location
preg_match('/vehicles\/(.+)\/location/', $topic, $matches);
$plate = $matches[1];
$data = json_decode($payload, true);
DB::transaction(function () use ($plate, $data) {
$vehicle = Vehicle::where('plate_number', $plate)->firstOrFail();
$vehicle->update([
'location' => DB::raw(
"ST_MakePoint({$data['lng']}, {$data['lat']})"
),
'mileage_km' => $data['odometer'] ?? $vehicle->mileage_km,
'last_seen_at' => now(),
]);
// Геозонування — перевірка виходу за межі дозволеної зони
if (isset($vehicle->activeRental)) {
$this->checkGeofence($vehicle, $data);
}
});
}
}
Як обробляються страхові депозити?
Депозит блокується на карті при бронюванні через Stripe PaymentIntents з capture_method=manual. При поверненні без пошкоджень hold скасовується. При пошкодженнях списується лише сума збитку. Гарантуємо: жодна копійка не піде без підтвердження.
// Створення hold (authorization hold) без списання
$paymentIntent = $stripe->paymentIntents->create([
'amount' => $booking->security_deposit_cents,
'currency' => 'eur',
'capture_method' => 'manual',
'confirm' => false,
'description' => "Security deposit for booking #{$booking->id}",
]);
// Якщо пошкоджень немає — скасовуємо hold
$stripe->paymentIntents->cancel($paymentIntent->id);
// Якщо є пошкодження — захоплюємо потрібну суму
$stripe->paymentIntents->capture($paymentIntent->id, [
'amount_to_capture' => $damageAmount,
]);
Етапи роботи
Ми дотримуємося прозорого процесу, щоб ви контролювали кожен крок:
- Аналіз — вивчаємо ваш автопарк, цільовий ринок, юридичні вимоги.
- Проектування — створюємо ERD, API-схему, прототип інтерфейсу.
- Розробка — пишемо код, налаштовуємо CI/CD, проводимо code review.
- Тестування — навантажувальні тести, перевірка безпеки, UAT.
- Деплой і моніторинг — розгортаємо на production, підключаємо алерти.
Компоненти рішення
| Компонент | Деталі |
|---|---|
| Бекенд (API) | Laravel / Django + PostgreSQL + Redis |
| Фронтенд | React / Next.js (SSR) з mobile-first дизайном |
| Верифікація | Stripe Identity / Jumio + webhook-обробка |
| Платежі | Stripe (hold/capture, знижки, сезонність) |
| GPS-трекінг | MQTT-сервер + геозонування (опціонально) |
| Акт прийому-передачі | Фото + підписи canvas -> S3 |
| Документація | Swagger-схема, README, опис deployment |
| Підтримка | 1 місяць безкоштовного моніторингу після запуску |
Терміни розробки
- MVP (каталог, бронювання, верифікація, кабінет власника): 8–10 тижнів.
-
- Огляди, GPS, гнучке ціноутворення, депозити: ще 4–5 тижнів.
- Повний запуск (корпоративні акаунти, fleet management, аналітика): 16–18 тижнів сумарно.
Вартість розраховується індивідуально — залежно від складності інтеграцій та обсягу автопарку. В середньому платформа окупається за 3–6 місяців за рахунок автоматизації.
Зв'яжіться з нами для оцінки вашого проекту — ми підготуємо архітектуру та кошторис за 2 дні. Замовте консультацію з архітектури платформи та термінів. Працюємо з проектами будь-якого масштабу — від стартапів до великих автопарків.







