Поставка софта Ценообразование: подписка, потребление, места, freemium
0%

Ценообразование: подписка, потребление, места, freemium

Ценообразование: подписка, потребление, места, freemium

На лендинге написано «990 рублей за пользователя в месяц». Одиннадцать символов. В системе за ними стоит ответ на четыре вопроса, которые кто-то должен решить до первой продажи: кого считать пользователем; на какой момент его считать; что происходит, когда человека завели пятнадцатого числа и удалили двадцатого; и что видит тот, у кого право кончилось прямо сейчас, посреди сохранения документа.

Если эти вопросы не решены явно, они решатся сами — случайным сочетанием того, как написан код и какие поля оказались в таблице. Через полгода клиент напишет: «У нас сорок записей, но реально работают двенадцать человек, за что мы платим?» — и выяснится, что определения места нет ни в договоре, ни в коде, а есть только SELECT count(*) FROM users.

Эта глава — про то, как форма цены превращается в инженерное требование, и она сознательно не занимается вопросом «сколько брать». Готовность платить, эластичность спроса, упаковка тарифов — это ценообразование и рост в продуктовом треке; расчёт своей ставки — бизнес-трек. Здесь другое: какую систему вы обязаны построить и содержать, выбрав ту или иную форму цены. Механика движения денег внутри формы — пересчёты, возвраты, налоги, валюты — в следующей главе, Биллинг.

Цена — это функция от того, что вы умеете измерить

Любая форма цены сводится к выражению сумма = f(единица тарификации, количество, период); всё остальное — упаковка. Отсюда вывод: нельзя продавать то, что не умеешь считать воспроизводимо — так, чтобы два независимых пересчёта за один период дали одно число и чтобы его можно было показать клиенту с разбивкой.

Единица тарификации (в англоязычной литературе — value metric) должна удовлетворять трём требованиям сразу, и в реальных продуктах хотя бы одно обычно нарушено.

  1. Растёт вместе с ценностью для покупателя. Контрпример — цена за гигабайты хранилища у продукта, ценность которого в аналитике: клиент чистит старые данные, ценность не меняется, ваша выручка падает.
  2. Предсказуема для покупателя до факта. Подписывая договор, человек должен уметь оценить счёт хотя бы до порядка. Единица, зависящая от внутренних решений вашей системы («вычислительные юниты»), это требование нарушает и порождает поток обращений в поддержку.
  3. Дёшево и бесспорно измеряется вами. «Бесспорно» — ключевое слово: клиент будет пересчитывать. Если ваш счётчик считает вызовы 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: с этого момента состав меняет чужая система, и счёт зависит от корректности чужой интеграции. Логируйте источник каждого изменения состава — иначе спор «мы не заводили этих людей» неразрешим.

Потребление: счётчик — это распределённая система, а не колонка

Плата за фактическое использование выглядит справедливой и решает проблему разнородных клиентов: цена сама следует за себестоимостью. Плата за это решение — целая подсистема, у которой требования к надёжности выше, чем у продукта, потому что её ошибки конвертируются в деньги и в документы.

Что здесь обязательно, а не желательно:

  • Идемпотентность на уровне события. Отправитель генерирует ключ, приёмник хранит увиденные ключи с 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, а несколько разнесённых точек с разным поведением при отказе.

Пунктирная ветка — самая забытая. Когда сервис прав недоступен, система обязана вести себя так, как решено заранее, а не так, как получилось. Для функций с прямой себестоимостью (вызовы модели, исходящий трафик) разумно 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$ остаётся средней температурой; предпосылки описаны в Многотенантности. Регулярный отчёт «маржа по клиентам за квартал» почти всегда обнаруживает хвост убыточных: клиенты на старой версии плана либо клиенты, чей сценарий не тот, под который считалась цена. Реакция не «поднять всем цену», а изменить включённые объёмы в новой версии плана, ввести лимит на сценарий, съедающий маржу, или перевести клиента на другую единицу тарификации.

Что нельзя решить в коде

Инженер отвечает за то, чтобы обещание было технически исполнимо и проверяемо. Правовая часть — работа юриста, и эта глава юридических советов не даёт. К специалисту приходят с конкретными вопросами, а не с «посмотрите тарифы»:

  1. Что именно я продаю по этому тарифу — экземпляр программы, право использования или услугу доступа? От ответа зависит почти всё, включая возможность возврата.
  2. Является ли изменение цены односторонним изменением условий договора и какой срок уведомления требуется действующим подписчикам?
  3. Какие правила действуют для автоматического продления подписки у потребителей: нужно ли отдельное подтверждение, напоминание перед списанием, простая отмена?
  4. Что обязана содержать цена на витрине — включён ли налог, нужно ли указывать полную стоимость за период, как показывать цену со скидкой и что считается «бесплатно», если бесплатный тариф ограничен по объёму?
  5. Допустимо ли назначать разные цены по регионам и по типам покупателей и какие ограничения на это накладывает применимое право?
  6. Какие права потребителя на возврат нельзя ограничить моими условиями, даже если они написаны в оферте?

Ответы становятся требованиями к системе: срок уведомления — отложенная задача, форма отображения цены — шаблон, правило возврата — состояние подписки. Юрисдикционная сторона — в главе Ограничения.

Как это устроено на портале

Живой пример, где выбор формы цены сэкономил целую подсистему: в оферте этого портала продаются разовые услуги и цифровой продукт с фиксированной ценой — ни подписки, ни мест, ни потребления. Следствия видны прямо в документах:

  • Нет расчётного периода — значит, нет пропорциональных пересчётов, апгрейда посреди периода, неуспешных списаний и льготных периодов. Целый класс кода не написан, потому что форма цены его не требует.
  • Есть фиксация условий на момент продажи: редакция оферты, дата принятия, сведения о заказе. Это тот снимок сделки, который в подписке пришлось бы хранить на каждое продление.
  • Есть отдельные условия возврата, различающие «до начала оказания услуги» и «после»; в подписке аналогом были бы правила возврата за неиспользованный остаток периода. И цена названа в рублях с оговоркой, что верна на момент редакции, а редакция датирована: ручная версия версионирования прайса, документ с датой вместо таблицы plan_version.

Вывод не в том, что разовая продажа лучше, а в том, что форма цены — это оценка объёма работ, и делается она до, а не после.

Типичные ошибки

  1. Продавать то, что не считается воспроизводимо. Тариф за «действия» без определения действия — гарантированный спор с первым же крупным клиентом.
  2. Хранить число мест в изменяемом счётчике. Восстановить «сколько было в июле» невозможно, а спросят именно об этом.
  3. Класть цену в код. Изменение цены становится релизом, история цен — историей коммитов, старые клиенты — набором if в биллинге.
  4. Ссылаться подпиской на план, а не на версию плана. Первое же повышение цены молча применяется ко всем действующим клиентам.
  5. Считать бесплатный тариф бесплатным. У него есть себестоимость, поток обращений в поддержку и обязательство не ухудшать условия внезапно.
  6. Тарифицировать по времени приёма события и не иметь правила для опоздавших. Перезапуск потребителя переносит потребление в следующий период, а каждый сбой сети превращается в ручной разбор расхождений.
  7. Смешивать флаги функций и права. Выключение флага отнимает оплаченную функцию у платящих клиентов.
  8. Обещать жёсткий потолок расходов без учёта задержки конвейера. Между превышением и его обнаружением всегда есть минуты, и это называют вслух.
  9. Проектировать цену без разбивки затрат по клиентам. Средняя себестоимость скрывает хвост убыточных, и он растёт быстрее выручки.

Мини-итог

Форма цены — инженерное решение с ценой владения. Подписка за период дёшева в обслуживании и слепа к разбросу себестоимости. Места дают понятную закупщику линейку и требуют письменного определения места: именованные, активные и одновременные дают разные счета при одном и том же поведении людей. Потребление привязывает цену к затратам и требует конвейера счётчиков с идемпотентностью, окном для опоздавших и неизменяемым реестром. Бесплатный тариф — статья расходов с обязательствами, а не маркетинговый приём. Тарифная линейка живёт как версионированные данные: подписка ссылается на неизменяемую версию, право выводится из плана, изменение цены — миграция с уведомлением и сроком. И над всем этим простое условие: цена единицы должна покрывать себестоимость с учётом доли канала, а чтобы это проверить, счётчики по тенантам ставят раньше, чем понадобится тарификация.

Источники

Что дальше

Биллинг: пробный период, апгрейды, возвраты, налоги и валюты — механика движения денег внутри выбранной формы цены: как считается пропорциональный пересчёт при переходе посреди периода, что технически означает возврат и чем он отличается от спорной операции, какие признаки фиксируют в момент продажи ради налогов и как жить с несколькими валютами.

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов