Ценообразование: подписка, потребление, места, freemium
На лендинге написано «990 рублей за пользователя в месяц». Одиннадцать символов. В системе за ними стоит ответ на четыре вопроса, которые кто-то должен решить до первой продажи: кого считать пользователем; на какой момент его считать; что происходит, когда человека завели пятнадцатого числа и удалили двадцатого; и что видит тот, у кого право кончилось прямо сейчас, посреди сохранения документа.
Если эти вопросы не решены явно, они решатся сами — случайным сочетанием того, как написан код и какие поля оказались в таблице. Через полгода клиент напишет: «У нас
сорок записей, но реально работают двенадцать человек, за что мы платим?» — и выяснится, что определения места нет ни в договоре, ни в коде, а есть только
SELECT count(*) FROM users.
Эта глава — про то, как форма цены превращается в инженерное требование, и она сознательно не занимается вопросом «сколько брать». Готовность платить, эластичность спроса, упаковка тарифов — это ценообразование и рост в продуктовом треке; расчёт своей ставки — бизнес-трек. Здесь другое: какую систему вы обязаны построить и содержать, выбрав ту или иную форму цены. Механика движения денег внутри формы — пересчёты, возвраты, налоги, валюты — в следующей главе, Биллинг.
Цена — это функция от того, что вы умеете измерить
Любая форма цены сводится к выражению сумма = f(единица тарификации, количество, период); всё остальное — упаковка. Отсюда вывод: нельзя продавать то, что не умеешь считать воспроизводимо — так, чтобы два независимых пересчёта за один период дали одно число и чтобы его можно было показать клиенту с разбивкой.
Единица тарификации (в англоязычной литературе — value metric) должна удовлетворять трём требованиям сразу, и в реальных продуктах хотя бы одно обычно нарушено.
- Растёт вместе с ценностью для покупателя. Контрпример — цена за гигабайты хранилища у продукта, ценность которого в аналитике: клиент чистит старые данные, ценность не меняется, ваша выручка падает.
- Предсказуема для покупателя до факта. Подписывая договор, человек должен уметь оценить счёт хотя бы до порядка. Единица, зависящая от внутренних решений вашей системы («вычислительные юниты»), это требование нарушает и порождает поток обращений в поддержку.
- Дёшево и бесспорно измеряется вами. «Бесспорно» — ключевое слово: клиент будет пересчитывать. Если ваш счётчик считает вызовы API, а клиент считает строки в логе балансировщика, расхождение неизбежно, и вопрос лишь в том, есть ли у вас разбивка, объясняющая разницу.
Третье требование — инженерное, и его чаще всего оценивают в последний момент. Счётчик потребления — отдельная подсистема со своим классом надёжности, и ошибка в ней дороже ошибки в приложении: неверный счёт видят все клиенты сразу, а исправление — не деплой, а перевыставление документов.
Правый нижний угол — не «плохо», а «дёшево и опасно на тяжёлых клиентах»; левый верхний — не «хорошо», а «честно по затратам и дорого в учёте».
Пять форм по цене владения
Рамка трека — три колонки: что получает покупатель, во что обходится поставщику, какие обязательства создаёт. Третья недооценена сильнее всего: от расхода можно отказаться, от обязательства — нет.
| Форма | Покупателю | Поставщику | Обязательства |
|---|---|---|---|
| Разовая покупка версии | предсказуемо, платишь один раз, работает без вас | учёт прав на скачивание, восстановление доступа, платный апгрейд как отдельный продукт | чинить уязвимости в проданных версиях; не ломать формат данных (обновления) |
| Подписка за период | одна цифра в бюджете, ноль сюрпризов | учёт срока и продления, работа с неуспешными списаниями, деэскалация доступа | уведомлять об изменении цены заранее; отдавать данные при уходе |
| За места | масштабируется вместе с командой, понятно закупщику | определение места, источник истины по составу, доучёт и возврат мест | считать места так, как записано в договоре, и уметь это показать |
| За потребление | платишь за фактическое использование, нулевой порог входа | конвейер счётчиков как отдельная подсистема, дедупликация, поздние события, реестр | воспроизводимость счёта, разбивка по запросу, предупреждение о превышении |
| Бесплатный тариф (freemium) | нулевой риск попробовать, часть задач решается даром | постоянная себестоимость на неплатящих, лимиты, защита от злоупотреблений | не ухудшать бесплатный тариф внезапно; отдавать данные тем, кто никогда не платил |
Подписка за период: дёшево в обслуживании, слепа к себестоимости
Самая простая форма: право живёт до даты, счётчик — календарь, инженерно это две колонки (current_period_end, status) и планировщик. Поэтому с неё начинают — и
поэтому она первой ломается на разнородных клиентах: один грузит документ в неделю, другой гоняет пакетную обработку по ночам, платят одинаково. Пока разброс
себестоимости между клиентами меньше маржи, это не проблема; когда больше — вы финансируете тяжёлых клиентов за счёт лёгких, а лёгкие уходят к тому, кто берёт по факту.
Дешевизна реальна, но не бесплатна: три вещи придётся построить всё равно.
- Жизненный цикл, а не булев флаг.
trial → active → past_due → suspended → offboarding— явные состояния с явными переходами. Схема жизненного цикла тенанта разобрана в Многотенантности; подписка — тот же объект с другой стороны. - Льготный период. Карта не прошла — что происходит в первые сутки, на третьи, на десятые? Мгновенное отключение превращает техническую проблему банка в потерю клиента. Обычная практика — 3–14 дней постепенной деградации; конкретное число записывают в оферту, а не выбирают в момент инцидента.
- Годовой период как отдельный продукт. Годовая оплата со скидкой — не «цена ×12 ×0,8», а другой профиль риска: деньги вперёд, обязательство на год, возврат за неиспользованный остаток при расторжении. Скидка — плата за то, что клиент вас кредитует; её считают через стоимость денег, а не «потому что у всех 20 процентов».
Места: слово, о котором спорят на четвёртом месяце
«Место» (seat) — самое перегруженное слово в тарифах для организаций: определений минимум четыре, и они дают разные суммы за одно и то же поведение людей.
- Именованное место — заведённая учётная запись. Считать проще всего, но клиент платит за уволенных и за сервисные аккаунты интеграций, пока кто-то не почистит список, и через полгода это выглядит как счёт за воздух.
- Активное место — запись, совершившая хотя бы одно значимое действие за период. Честнее для клиента, дороже для вас: нужно определение «значимого действия» (вход? запись? открытие отчёта?), устойчивое к фоновым процессам и к мобильному клиенту, который дёргает синхронизацию сам.
- Одновременное место — пик параллельных сессий. Историческая форма из мира инженерного ПО и терминальных лицензий. Считать дорого: нужны сессии с явным началом и концом, аренда места с TTL и освобождение при падении клиента — по сути распределённая блокировка со всеми её проблемами (время и часы). Цена такого места обычно кратно выше, поэтому «одновременные дешевле» — миф: на схеме выше именно они дают самый большой счёт.
- Место с ролью — платят только за редакторов, читатели бесплатны. Хорошо продаётся, но требует, чтобы роль была настоящим объектом системы авторизации, а не полем в профиле (авторизация).
Инженерное правило: количество мест за период вычисляется из журнала событий, а не хранится в изменяемом счётчике. Счётчик, который увеличивают при добавлении пользователя и уменьшают при удалении, рано или поздно разъедется: откат транзакции, массовый импорт, удаление в обход обычного пути. Историю из него не восстановить, а вопрос «почему в июле было 43 места» задают именно про прошлое.
-- Три разных ответа на вопрос «сколько мест списывать за июль».
-- Источник истины — журналы пользователей и сессий, а не изменяемый счётчик.
WITH period AS (
SELECT tstzrange('2026-07-01 00:00+03', '2026-08-01 00:00+03') AS win
),
named AS ( -- именованные: запись существовала хотя бы часть периода
SELECT count(*) AS seats FROM users u, period p
WHERE u.tenant_id = $1 AND u.created_at < upper(p.win)
AND (u.deleted_at IS NULL OR u.deleted_at >= lower(p.win))
),
active AS ( -- активные: хотя бы одно значимое действие; список видов — из договора
SELECT count(DISTINCT a.user_id) AS seats FROM audit_events a, period p
WHERE a.tenant_id = $1 AND a.occurred_at <@ p.win
AND a.kind IN ('document.write', 'report.export', 'api.call')
),
concurrent AS ( -- одновременные: пик по развёртке событий входа и выхода
SELECT max(running) AS seats FROM (
SELECT sum(delta) OVER (ORDER BY at, delta) AS running -- при равном времени
FROM ( -- сначала выходы: не завышаем пик
SELECT started_at AS at, 1 AS delta FROM sessions WHERE tenant_id = $1
UNION ALL -- открытые сессии закрываем концом периода, иначе пик уедет вверх
SELECT coalesce(ended_at, upper((SELECT win FROM period))), -1 FROM sessions
WHERE tenant_id = $1
) e, period p WHERE e.at <@ p.win
) r
)
SELECT (SELECT seats FROM named) AS named_seats,
(SELECT seats FROM active) AS active_seats,
(SELECT seats FROM concurrent) AS peak_concurrent;
Сложность развёртки пика — $O(n \log n)$ по времени на сортировку $n$ событий сессий за период и $O(1)$ дополнительной памяти сверх сортировки: оконная функция идёт потоком. На объёмах одного корпоративного клиента это секунды; на объёмах всего флота считают инкрементально по дням и складывают, а не пересчитывают месяц целиком.
Три сюжета, которые в местах всегда всплывают позже, чем нужно:
- Доучёт (true-up). Годовой контракт на 100 мест, в марте завели 130. Варианты: считать ежемесячно по факту; выставить разницу в конце года; заблокировать 101-е место. Первый честнее, третий скандальнее, второй чаще всего в договорах — и он требует хранить помесячный срез мест весь год, а не только текущее число.
- Возврат мест. Уменьшение количества мест посреди периода почти всегда откладывают до продления, иначе биллинг превращается в кассу возвратов. Решение записывают в оферту до запуска, а не объясняют в переписке (Биллинг).
- Синхронизация с каталогом клиента. Крупный клиент захочет заводить людей через SCIM или LDAP: с этого момента состав меняет чужая система, и счёт зависит от корректности чужой интеграции. Логируйте источник каждого изменения состава — иначе спор «мы не заводили этих людей» неразрешим.
Потребление: счётчик — это распределённая система, а не колонка
Плата за фактическое использование выглядит справедливой и решает проблему разнородных клиентов: цена сама следует за себестоимостью. Плата за это решение — целая подсистема, у которой требования к надёжности выше, чем у продукта, потому что её ошибки конвертируются в деньги и в документы.
тенант, метрика, количество, время события Note over S,Q: повтор при таймауте отправит
тот же ключ идемпотентности Q->>M: доставка (возможно, дважды) M->>M: отбросить дубликат по ключу M->>L: прибавить в часовое окно Note over M,L: окно закрывается по водяному знаку,
плюс 48 часов на опоздавших B->>L: снимок за расчётный период L-->>B: замороженные суммы с разбивкой B->>B: тарификация по версии плана B--)S: уведомление о превышении порога (перевыставление
счёта — операция с бумагами, а не деплой)
Что здесь обязательно, а не желательно:
- Идемпотентность на уровне события. Отправитель генерирует ключ, приёмник хранит увиденные ключи с TTL, покрывающим окно ретраев. Без этого повтор при таймауте — двойной счёт клиенту (идемпотентность и доставка).
- Разделение «когда произошло» и «когда доехало». Тарифицируют по времени события: иначе перезапуск потребителя переносит потребление в следующий период и портит оба счёта.
- Явное окно для опоздавших. Событие может прийти через час после сбоя сети. Нужен зафиксированный срок и правило для тех, кто опоздал сильнее: отбросить с записью в журнал расхождений или отнести к текущему периоду. Оба варианта допустимы; недопустимо не иметь правила (запаздывания).
- Неизменяемый реестр в целых числах. После закрытия периода агрегаты замораживаются, корректировка — новая запись с обратным знаком и причиной, а не
UPDATE(CQRS и event sourcing). Величины хранят целыми в базовых единицах — байты, миллисекунды, вызовы, токены; округление до валюты делают один раз, при формировании счёта.
# Агрегатор потребления: дедупликация, поздние события, закрытие окна.
# O(1) амортизированно на событие; память — ключи за окно ретраев плюс открытые окна.
# В проде множество ключей выносят во внешнее хранилище с TTL, иначе оно растёт вечно.
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
LATE_WINDOW = timedelta(hours=48) # сколько ждём опоздавшие события
KEY_TTL = timedelta(hours=24) # горизонт защиты от повторов
@dataclass(frozen=True)
class MeterEvent:
idempotency_key: str # уникален у отправителя и переживает ретраи
tenant_id: str
metric: str # 'api_calls' | 'storage_byte_hours' | 'egress_bytes'
quantity: int # целое в базовых единицах, без float
occurred_at: datetime # время у источника, не время приёма
def bucket(ts: datetime) -> datetime: # окно агрегации — час события
return ts.astimezone(timezone.utc).replace(minute=0, second=0, microsecond=0)
class Aggregator:
def __init__(self) -> None:
self._seen: dict[str, datetime] = {} # ключ -> когда увидели
self._open: dict[tuple, int] = {} # (тенант, метрика, час) -> сумма
self._sealed: set[tuple] = set() # закрытые окна
self.late_dropped = 0
def ingest(self, e: MeterEvent, now: datetime) -> str:
if e.idempotency_key in self._seen:
return "duplicate" # повтор — тихо игнорируем
if e.occurred_at < now - LATE_WINDOW:
self.late_dropped += 1 # в журнал расхождений, не в счёт
return "too_late"
key = (e.tenant_id, e.metric, bucket(e.occurred_at))
if key in self._sealed:
return "sealed" # период закрыт: только корректировкой
self._seen[e.idempotency_key] = now
self._open[key] = self._open.get(key, 0) + e.quantity
return "accepted"
def seal(self, now: datetime) -> dict[tuple, int]:
"""Закрывает окна, до которых опоздавшие уже не доедут."""
edge = bucket(now - LATE_WINDOW)
closing = {k: self._open.pop(k) for k in list(self._open) if k[2] < edge}
self._sealed.update(closing) # дальше — только корректировки
self._seen = {k: t for k, t in self._seen.items() if t >= now - KEY_TTL}
return closing
Предсказуемость — главная проблема потребления, и решается она инженерно. Покупатель боится не цены, а неизвестного счёта: один цикл в коде клиента — и месячный бюджет уходит за ночь. Минимум, без которого модель не стоит запускать: оценка счёта в реальном времени вместо «в конце месяца узнаете»; пороговые уведомления (80, 100, 120 процентов от бюджета) администратору, а не только владельцу карты; настраиваемый потолок с явным выбором — остановить работу или продолжить и выставить счёт (выбор клиента, а не ваш). И честность про задержку: между событием и его учётом всегда есть время конвейера, поэтому «гарантированно не больше N» продавать нельзя, а «останавливаем в пределах M минут после превышения» — можно, если M измерено.
Отдельная работа — сверка с собственными затратами: если вы перепродаёте вычисления или трафик, счёт от провайдера и ваши счётчики обязаны сходиться с известной погрешностью, а расхождение больше порога — инцидент. Разбор затрат по тенантам — в стоимости платформы и облачных затратах.
Бесплатный тариф: статья расходов, у которой есть обязательства
«Freemium» скрывает три разные конструкции с разной экономикой и разной инженерией.
| Конструкция | Экономика | Что требует от системы |
|---|---|---|
| Бесплатно навсегда | постоянная себестоимость на неплатящих; конверсия единицы процентов | жёсткие лимиты, деградация функций, защита от массовой регистрации |
| Пробный период с картой | высокая конверсия, повышенный поток возвратов и споров | предупреждение до списания, мгновенная отмена, отзыв права |
| Пробный период без карты | низкая конверсия, спокойная поддержка | дата окончания, поведение на следующий день, повторная выдача |
Инфраструктурная цена одного платящего, пришедшего через бесплатный тариф, считается прямо: если $k_f$ — себестоимость одного бесплатного пользователя за период, а $r$ — доля бесплатных, переходящих в платящие за тот же период, то
$$ C_{fp} = \frac{k_f}{r} $$
При $k_f$ = 30 рублей в месяц и $r$ = 0,02 один платящий обходится в 1500 рублей одной только инфраструктуры — до всякого маркетинга. Если маржа платящего 500 рублей в месяц, бесплатный тариф окупается на третий месяц жизни клиента, и работает это лишь при условии, что клиенты живут дольше трёх месяцев. Арифметика с когортами — в юнит-экономике.
Инженерная часть — прежде всего защита от использования бесплатного ресурса не по назначению: майнинг на сборочных минутах, хранилище как файлообменник, трафик как CDN, вызовы модели как перепродаваемый API. Меры: лимиты по организации, а не по почтовому ящику; подтверждение личности для ресурсоёмких функций; отдельные квоты на трафик и на хранение; ограничение параллелизма. Каждая мера ухудшает опыт добросовестного пользователя, поэтому их вводят по мере появления злоупотреблений — но точки включения проектируют сразу.
Второе — лимит должен быть виден до того, как в него упрутся: счётчик «использовано 4 из 5 проектов» стоит один запрос, а экономит поток обращений «почему не создаётся». Поведение при достижении лимита выбирают явно — жёсткая остановка с понятным кодом ошибки и датой сброса либо мягкая деградация, которую проектируют, а не получают случайно (деградация в SRE).
Третье — обязательства перед теми, кто никогда не платил: бесплатный пользователь загрузил свои данные, и вы обязаны отдать их при закрытии тарифа и предупредить об ухудшении условий заранее. Молчаливое урезание — быстрый способ получить скандал дороже сэкономленной инфраструктуры. Обратный порядок (reverse trial) — полный набор функций на две недели, потом понижение до бесплатного тарифа — даёт лучшую конверсию и требует того же механизма: понижение прав по расписанию с сохранением данных.
Тарифная линейка — это структура данных
Самая дорогая ошибка делается в коде: цена и состав тарифа оказываются константами в приложении, после чего изменение цены — релиз, а история цен — история коммитов. Работающая модель: план версионируется, подписка ссылается на неизменяемую версию плана, а не на план. Тогда повышение цены не трогает действующих клиентов автоматически, и вопрос «по каким условиям обслуживается этот клиент» имеет точный ответ.
Тот же прайс как файл под контролем версий — форма, с которой удобно начинать:
# price-book.yaml — прайс как данные, а не константы в коде.
# Новая версия создаётся целиком; предыдущие версии никогда не редактируются.
plan_version:
plan: team
version: 4
effective_from: 2026-07-01
supersedes: 3
seat_definition: active # то же слово, что и в оферте, буква в букву
prices: # своя цена на рынок, а не пересчёт по курсу
RUB: { base_minor: 490000, per_seat_minor: 90000 } # 4900 и 900 рублей
USD: { base_minor: 4900, per_seat_minor: 900 } # 49,00 и 9,00 USD
included: { api_calls: 200000, storage_gib_month: 50 }
overage: # цена сверх включённого объёма
api_calls: { unit: 1000, RUB: 1200, USD: 12 }
storage_gib_month: { unit: 1, RUB: 1200, USD: 12 }
entitlements: # kind: bool | quota | rate
sso: { kind: bool, value: true }
audit_log: { kind: quota, value: 90, on_exceed: degrade } # дней хранения
api_rate: { kind: rate, value: 100, on_exceed: block } # запросов в секунду
migration: { existing_subscriptions: keep_v3_until_renewal, notice_days: 30 }
Два правила, которые экономят годы. Первое: право (entitlement) выводится из версии плана, а не хранится копией на аккаунте — копия разъезжается с планом при первой же ручной правке в поддержке. Второе: права и флаги функций — разные вещи. Флаг выключает недоделанную функцию для всех, право отвечает на вопрос «этому клиенту оплачено»; смешивать их — гарантированный инцидент, когда выключение флага отнимет оплаченную функцию у платящих (стратегии релиза).
Где именно проверяется право
Проверка права — не строчка в middleware, а несколько разнесённых точек с разным поведением при отказе.
данные доступны на чтение"] B -->|"да"| C{"Версия плана включает функцию?"} C -->|"нет"| Y["Показать тариф с ценой,
не прятать функцию молча"] C -->|"да"| D{"Квота периода исчерпана?"} D -->|"нет"| F["Выполнить"] D -->|"мягкий лимит"| E["Выполнить, записать превышение,
уведомить администратора"] D -->|"жёсткий лимит"| X["429 с датой сброса
и ссылкой на повышение тарифа"] F --> G["Событие потребления
с ключом идемпотентности"] E --> G G --> H["Агрегация → реестр → счёт"] B -.->|"сервис прав недоступен"| W["Решение записано заранее:
пропускать (fail-open) или блокировать"]
Пунктирная ветка — самая забытая. Когда сервис прав недоступен, система обязана вести себя так, как решено заранее, а не так, как получилось. Для функций с прямой себестоимостью (вызовы модели, исходящий трафик) разумно fail-closed; для чтения клиентом собственных данных — почти всегда fail-open, потому что цена ложной блокировки выше цены разового бесплатного чтения. Решение фиксируют в документе и проверяют учением (учения).
Практическая деталь — кэш прав. Ходить в биллинг на каждый запрос дорого, право кэшируют на минуты, и отсюда вопрос: как быстро право отзывается. Клиент вернул деньги, а доступ живёт ещё десять минут — приемлемо; клиент оплатил, а доступа нет десять минут — поток обращений. Асимметрия штатная: выдачу применяют немедленно, инвалидируя кэш по событию оплаты, отзыв — по TTL.
Изменение цены — миграция, а не редактирование числа
Каждая живая версия прайса — ветка поведения, которую придётся содержать: считать счета, объяснять поддержке, тестировать при каждом изменении биллинга. Отсюда первое ограничение: живых версий должно быть три-четыре, и у каждой — дата вывода из обращения, иначе она бессмертна. Дальше — то, что вводят до того, как версий станет двенадцать.
- Сохранение старой цены (grandfathering) — обязательство, а не жест. Обещав «цена никогда не изменится», вы обязуетесь вечно считать по старым правилам, в том числе когда единица тарификации изменилась и старая метрика больше не собирается. «Текущая цена сохраняется до 31 декабря следующего года» стоит дешевле и звучит почти так же хорошо.
- Перевод на новую версию — операция с уведомлением и сроком. Инженерно это отдельная задача: пересчитать состав, показать разницу до и после, дать окно на отказ. Пропорциональные пересчёты при переходе посреди периода — в Биллинге.
- Смена единицы тарификации — самая тяжёлая миграция. Переход с мест на потребление требует месяцами собирать новую метрику параллельно со старой, чтобы показать клиентам счёт в обеих системах и убедиться, что расход не удваивается. Без параллельного периода это череда индивидуальных исключений.
Апгрейд посреди периода накладывает требование и на саму линейку: тарифы должны быть сравнимы. Если на младшем тарифе места именованные, а на старшем активные, пропорциональный пересчёт не имеет однозначного смысла и любая формула будет выглядеть как обман. Единица тарификации одна на всю линейку; различаются включённые объёмы и цены, а не природа счёта.
Валюты, регионы и налог в ценнике
- Цена в каждой валюте — самостоятельное число, а не пересчёт по курсу. Курс скачет, «1043 рубля» выглядит как ошибка, привычные ценовые точки в странах разные. Держат таблицу цен по валютам и правило пересмотра — например, раз в квартал.
- Региональная цена требует признака региона, который вы можете обосновать. Страна карты, адрес плательщика, IP, страна аккаунта в магазине — четыре сигнала разной надёжности. Фиксируйте в момент продажи все, что видели: потом не восстановить. Самый жёсткий вариант задачи — региональные цены в игровых магазинах, где арбитраж между регионами становится бизнесом (дистрибуция игр).
- Налог в витрине — решение, влияющее и на вёрстку, и на расчёт. Цена «с налогом» и «без налога» для одного продукта на разных рынках — норма; в модели это флаг у цены, а не вычисление на лету.
- Комиссия канала входит в эффективную цену. Одна витринная цена через магазин приложений и через свой сайт даёт разную маржу, и считают это на стадии выбора цены (Каналы поставки, Магазины приложений). Ставки комиссий и налоговые правила меняются регулярно: их подставляют из актуальной документации площадки на дату решения, а не из статьи.
Пол цены: себестоимость под единицей
Маржа одной единицы тарификации:
$$ m_u = p_u \cdot (1 - c) - k_u $$
где $p_u$ — цена единицы, $c$ — суммарная доля канала (комиссия площадки, эквайринг, платёжный сервис), $k_u$ — прямая себестоимость обслуживания единицы. Если задать целевую долю маржи $\mu$ от цены, минимальная цена получается из условия $m_u \ge \mu \cdot p_u$:
$$ p_u \ge \frac{k_u}{1 - c - \mu} $$
Пример: обработка гигабайта обходится в 4 рубля прямых затрат, канал забирает 6 процентов, целевая маржа — 60 процентов от цены. Минимальная цена: 4 / (1 − 0,06 − 0,60) ≈ 11,8, то есть 12 рублей за гигабайт. При комиссии магазина 30 процентов вместо 6 та же формула даёт 40 рублей — втрое дороже при неизменной себестоимости. Это и есть механизм, из-за которого одна и та же цена на витрине означает совершенно разный бизнес в разных каналах.
Чтобы формула была инструментом, нужна разбивка затрат по тенантам — иначе $k_u$ остаётся средней температурой; предпосылки описаны в Многотенантности. Регулярный отчёт «маржа по клиентам за квартал» почти всегда обнаруживает хвост убыточных: клиенты на старой версии плана либо клиенты, чей сценарий не тот, под который считалась цена. Реакция не «поднять всем цену», а изменить включённые объёмы в новой версии плана, ввести лимит на сценарий, съедающий маржу, или перевести клиента на другую единицу тарификации.
Что нельзя решить в коде
Инженер отвечает за то, чтобы обещание было технически исполнимо и проверяемо. Правовая часть — работа юриста, и эта глава юридических советов не даёт. К специалисту приходят с конкретными вопросами, а не с «посмотрите тарифы»:
- Что именно я продаю по этому тарифу — экземпляр программы, право использования или услугу доступа? От ответа зависит почти всё, включая возможность возврата.
- Является ли изменение цены односторонним изменением условий договора и какой срок уведомления требуется действующим подписчикам?
- Какие правила действуют для автоматического продления подписки у потребителей: нужно ли отдельное подтверждение, напоминание перед списанием, простая отмена?
- Что обязана содержать цена на витрине — включён ли налог, нужно ли указывать полную стоимость за период, как показывать цену со скидкой и что считается «бесплатно», если бесплатный тариф ограничен по объёму?
- Допустимо ли назначать разные цены по регионам и по типам покупателей и какие ограничения на это накладывает применимое право?
- Какие права потребителя на возврат нельзя ограничить моими условиями, даже если они написаны в оферте?
Ответы становятся требованиями к системе: срок уведомления — отложенная задача, форма отображения цены — шаблон, правило возврата — состояние подписки. Юрисдикционная сторона — в главе Ограничения.
Как это устроено на портале
Живой пример, где выбор формы цены сэкономил целую подсистему: в оферте этого портала продаются разовые услуги и цифровой продукт с фиксированной ценой — ни подписки, ни мест, ни потребления. Следствия видны прямо в документах:
- Нет расчётного периода — значит, нет пропорциональных пересчётов, апгрейда посреди периода, неуспешных списаний и льготных периодов. Целый класс кода не написан, потому что форма цены его не требует.
- Есть фиксация условий на момент продажи: редакция оферты, дата принятия, сведения о заказе. Это тот снимок сделки, который в подписке пришлось бы хранить на каждое продление.
- Есть отдельные условия возврата, различающие «до начала оказания услуги» и «после»; в подписке аналогом были бы правила возврата за неиспользованный
остаток периода. И цена названа в рублях с оговоркой, что верна на момент редакции, а редакция датирована: ручная версия версионирования прайса, документ с датой
вместо таблицы
plan_version.
Вывод не в том, что разовая продажа лучше, а в том, что форма цены — это оценка объёма работ, и делается она до, а не после.
Типичные ошибки
- Продавать то, что не считается воспроизводимо. Тариф за «действия» без определения действия — гарантированный спор с первым же крупным клиентом.
- Хранить число мест в изменяемом счётчике. Восстановить «сколько было в июле» невозможно, а спросят именно об этом.
- Класть цену в код. Изменение цены становится релизом, история цен — историей коммитов, старые клиенты — набором
ifв биллинге. - Ссылаться подпиской на план, а не на версию плана. Первое же повышение цены молча применяется ко всем действующим клиентам.
- Считать бесплатный тариф бесплатным. У него есть себестоимость, поток обращений в поддержку и обязательство не ухудшать условия внезапно.
- Тарифицировать по времени приёма события и не иметь правила для опоздавших. Перезапуск потребителя переносит потребление в следующий период, а каждый сбой сети превращается в ручной разбор расхождений.
- Смешивать флаги функций и права. Выключение флага отнимает оплаченную функцию у платящих клиентов.
- Обещать жёсткий потолок расходов без учёта задержки конвейера. Между превышением и его обнаружением всегда есть минуты, и это называют вслух.
- Проектировать цену без разбивки затрат по клиентам. Средняя себестоимость скрывает хвост убыточных, и он растёт быстрее выручки.
Мини-итог
Форма цены — инженерное решение с ценой владения. Подписка за период дёшева в обслуживании и слепа к разбросу себестоимости. Места дают понятную закупщику линейку и требуют письменного определения места: именованные, активные и одновременные дают разные счета при одном и том же поведении людей. Потребление привязывает цену к затратам и требует конвейера счётчиков с идемпотентностью, окном для опоздавших и неизменяемым реестром. Бесплатный тариф — статья расходов с обязательствами, а не маркетинговый приём. Тарифная линейка живёт как версионированные данные: подписка ссылается на неизменяемую версию, право выводится из плана, изменение цены — миграция с уведомлением и сроком. И над всем этим простое условие: цена единицы должна покрывать себестоимость с учётом доли канала, а чтобы это проверить, счётчики по тенантам ставят раньше, чем понадобится тарификация.
Источники
- Stripe Billing — подписки и потребительский биллинг: https://docs.stripe.com/billing, метрики потребления: https://docs.stripe.com/billing/subscriptions/usage-based, идемпотентные запросы: https://docs.stripe.com/api/idempotent_requests.
- AWS Cost and Usage Reports: как выглядит детализация потребления у крупного поставщика — https://docs.aws.amazon.com/cur/latest/userguide/what-is-cur.html.
- FOCUS — открытая спецификация отчётов о затратах, FinOps Foundation: https://focus.finops.org/. OpenCost — распределение затрат Kubernetes по нагрузкам: https://www.opencost.io/docs/.
- Madhavan Ramanujam, Georg Tacke. «Monetizing Innovation» (Wiley, 2016) — про выбор единицы тарификации до разработки продукта; J. R. Storment, Mike Fuller. «Cloud FinOps» (O’Reilly, 2‑е изд., 2023) — про связь потребления, затрат и цены.
- David Skok, «SaaS Metrics 2.0» — https://www.forentrepreneurs.com/saas-metrics-2/.
- Оферта и условия возврата этого портала как пример зафиксированных условий: /legal/offer/, /legal/refund/.
Что дальше
Биллинг: пробный период, апгрейды, возвраты, налоги и валюты — механика движения денег внутри выбранной формы цены: как считается пропорциональный пересчёт при переходе посреди периода, что технически означает возврат и чем он отличается от спорной операции, какие признаки фиксируют в момент продажи ради налогов и как жить с несколькими валютами.