Первый SaaS из Самары в 2013 году: от идеи до первого платящего клиента
Осенью 2012 мы обслуживали несколько небольших сервисных компаний (ремонт бытовой техники, установка окон, клининг) и наблюдали один и тот же паттерн: заявки от клиентов записывались в тетрадь или Excel, мастера получали адрес по звонку, статус выполнения не отслеживался. Если мастер болел - клиент звонил и никто не знал, что с его заявкой.
Задача выглядела чёткой: простой веб-сервис для учёта заявок, который стоит меньше, чем нанять ещё одного диспетчера.
Стек и архитектура
PHP 5.4, потому что вышел в марте 2012 и принёс трейты и сокращённый синтаксис [...] для массивов - важные для нас улучшения. CodeIgniter 2.2 - мы его хорошо знали. Bootstrap 2.3 - только появился и давал готовый responsive-грид. MySQL 5.5, хостинг на Selectel (Санкт-Петербург).
Мультитенантность - главное архитектурное решение:
// Вариант А: отдельная БД на каждого клиента
// ✓ Полная изоляция данных
// ✗ Сложно масштабировать (N баз данных, N коннектов)
// Вариант Б: одна БД, tenant_id в каждой таблице (выбрали этот)
// ✓ Просто в реализации
// ✓ Легко делать аналитику по всем клиентам
// ✗ Риск утечки данных между тенантами (требует дисциплины)
// Все таблицы имеют tenant_id:
CREATE TABLE requests (
id INT AUTO_INCREMENT PRIMARY KEY,
tenant_id INT NOT NULL, -- Компания-клиент нашего SaaS
title VARCHAR(300) NOT NULL,
client_name VARCHAR(200),
client_phone VARCHAR(20),
address TEXT,
master_id INT, -- Мастер, которому назначена заявка
status ENUM('new','assigned','in_work','done','cancelled') DEFAULT 'new',
scheduled_at DATETIME,
created_at DATETIME DEFAULT NOW(),
INDEX idx_tenant (tenant_id),
INDEX idx_tenant_status (tenant_id, status),
INDEX idx_master (tenant_id, master_id)
) ENGINE=InnoDB;
// BaseModel: tenant_id подставляется автоматически во все запросы
class BaseModel extends CI_Model {
protected string $table = '';
protected function tenantScope(): string {
return "tenant_id = " . (int)$this->session->userdata('tenant_id');
}
public function getAll(array $where = []): array {
$this->db->where($this->tenantScope());
foreach ($where as $key => $value) {
$this->db->where($key, $value);
}
return $this->db->get($this->table)->result_array();
}
public function findById(int $id): ?array {
$this->db->where($this->tenantScope()); // Всегда проверяем tenant
$this->db->where('id', $id);
$result = $this->db->get($this->table)->row_array();
return $result ?: null;
}
}
Правило, которое мы нарушили бы только один раз до инцидента: ни один запрос не должен идти без tenant_id в WHERE. Мы добавили линтер-правило в code review: PR с SELECT * FROM requests без tenant_id в WHERE - автоматический rejection.
Регистрация и онбординг
Первая сессия работы нового клиента с сервисом - критическая. Мы упростили до предела:
// Регистрация: только email + пароль + название компании
// Никаких подтверждений телефона, никаких документов
public function register() {
$email = $this->input->post('email');
$password = $this->input->post('password');
$company = $this->input->post('company_name');
// Создать тенанта
$tenantId = $this->Tenants_model->create([
'company_name' => $company,
'email' => $email,
'plan' => 'trial', // 14 дней бесплатно
'trial_until' => date('Y-m-d', strtotime('+14 days')),
]);
// Создать первого пользователя - сразу администратора
$userId = $this->Users_model->create([
'tenant_id' => $tenantId,
'email' => $email,
'password' => password_hash($password, PASSWORD_DEFAULT), // PHP 5.4+
'role' => 'admin',
'name' => $company . ' (администратор)',
]);
// Создать демо-заявку чтобы интерфейс не был пустым
$this->Requests_model->create([
'tenant_id' => $tenantId,
'title' => 'Пример заявки - замена крана',
'client_name' => 'Иванов Иван',
'client_phone' => '+7 900 123-45-67',
'status' => 'new',
]);
// Войти автоматически без дополнительного шага
$this->session->set_userdata(['user_id' => $userId, 'tenant_id' => $tenantId]);
redirect('/dashboard');
}
Пустой дашборд убивает конверсию. Демо-заявка при регистрации - простое решение.
Как нашли первого платящего клиента
Через 3 месяца после запуска было 47 бесплатных тестовых аккаунтов. Платящих - ноль. Конверсия из бесплатного в платное: 0%.
Мы перестали ждать конверсии через сайт и пошли на улицу. Буквально: прошлись по сервисным центрам бытовой техники на Ново-Садовой в Самаре. Зашли в 12 точек. Объяснили что делает сервис. Предложили бесплатный первый месяц после пробного периода.
Результат: 3 из 12 согласились попробовать. Один из трёх через месяц заплатил 990 рублей (первый тариф).
Техническая реализация приёма оплаты в тот момент: Яндекс.Деньги, ручное выставление счёта. Автоматической системы подписки не было - при оплате мы вручную продлевали тариф в админ-панели.
// Ручное продление тарифа (потом автоматизировали через YooKassa API)
public function extendPlan(int $tenantId, string $plan, int $months): void {
$tenant = $this->Tenants_model->findById($tenantId);
$current = max(new DateTime(), new DateTime($tenant['plan_until'] ?? 'now'));
$newDate = (clone $current)->modify("+{$months} months");
$this->Tenants_model->update($tenantId, [
'plan' => $plan,
'plan_until' => $newDate->format('Y-m-d'),
]);
$this->PaymentLog_model->create([
'tenant_id' => $tenantId,
'plan' => $plan,
'months' => $months,
'amount' => PLAN_PRICES[$plan] * $months,
'activated_by' => $this->session->userdata('user_id'),
]);
}
Чему научил первый год
Sales-цикл для B2B SaaS не автоматический. Особенно для малого бизнеса. Владелец сервисного центра не будет регистрироваться сам - нужно объяснить ценность лично. Первые 20 платящих клиентов мы привели «руками».
Pricing важнее функционала. 990 рублей/месяц - барьер входа. Когда мы добавили план за 490 рублей с ограничением на 50 заявок в месяц, конверсия выросла. Люди скорее попробуют за полцены, даже если потом ограничения станут неудобными.
Поддержка = удержание. Наш отток был 8% в месяц (высокий). Причина: клиенты не понимали как добавить мастера или настроить статусы. Добавили телефон поддержки (личный мобильный). Отток упал до 3%.
SaaS в 2013 в России - это не «сделал сайт и получай деньги». Это b2b-продажи, работа с возражениями, личный контакт с клиентами. Технология - инструмент, а не продукт сам по себе.