Skip to main content

Как мы управляем планами и функциями в нашем приложении SaaS

Автор статьи - Tim Nolet, ссылка на оригинал: How we manage plans & features in our SaaS app. Перевод опубликован с разрешения автора.

Как вы справляетесь с тем, что пользователь может делать в своей учетной записи в приложении SaaS? Может ли Джейн по плану «Стартер» создать еще один виджет, когда она приблизится к пределу своего плана? Что, если она пробный пользователь?

Оказывается, это сочетение различных моментов:

  • Переключение функций
  • Подсчет вещей ™
  • Пользовательское промежуточное ПО API, очень специфичное для вашей ситуации

Как и в нашем последнем посте из этой серии, посвященном созданию базовой модели данных SaaS, не хватает четких примеров того, как справиться с этой распространенной проблемой.

Вот как мы это делаем в Checkly с нашим Node.js, Hapi.js backend. Это, вероятно, будет хорошо переноситься на другие платформы.

Эта проблема

Давайте сделаем это как можно более конкретным, и, как говорится, страница с ценами SaaS стоит тысячи слов.

У нас есть три плана с различными ценами: разработчик, стартер и рост. 
Разные планы позволяют разные объемы и разные функции.

В этом примере:

  • В API и браузер проверяет являются объем ограничен. План разработчика получает 5, план Starter 15, план роста 40.
  • Функция членов команды включена или нет, а при включении также ограничена громкость.
  • Функция триггера CI / CD либо включена, либо нет. Никаких объемных вещей не происходит.

На странице с ценами не видно, что происходит во время пробного периода. В течение нашего 14-дневного пробного периода мы не предоставляем пробным пользователям защищенную с помощью SSL панель мониторинга. По техническим причинам и из-за злоупотреблений это вступает в силу только тогда, когда вы становитесь платящим клиентом.

Кроме того, помните, что у платежных клиентов могут быть проблемы с их кредитной картой, или просто прекратите платить по неизвестным причинам. Мы должны это уловить, но мы также хотим ошибиться, чтобы не разозлить клиентов, у которых только что возникли банковские проблемы.

Давайте сведем его к четырем категориям «вещей, которые мы должны каким-то образом применять и отслеживать» в нашем приложении SaaS.

  1. Судебный процесс против не судебного разбирательства: Вы все еще пинаете шины или заслуженный член нашего маленького клуба?
  2. Платить против Lapsing: Раньше вы платили нам, но не больше...
  3. Переключатели функций на основе плана: позволяет ли ваш план получить доступ к этой функции?
  4. Ограничения объема на основе плана: позволяет ли ваш план создавать больше таких вещей?

Пробный и не пробный период

Каждому зарегистрированному пользователю автоматически назначается пробный план. Модель данных показана ниже. Посмотрите предыдущий пост для более подробной информации.

Модель данных счетов и планов

Проверить это просто, просто сделайте вариацию на вашем языке:

if (account.plan.name === "trial") {
	// do trial things
}

Быть в пробе или нет - довольно бинарная и булева дружественная вещь. Просто убедитесь, что вы переключаете пользователя на другой план, когда он / она начинает платить. Что приводит нас к ...

Оплата против Lapsing

Должно быть легко, правда? Кто-то подписывается на платный план, и вы переключаете флаг с paying = falseна paying = true.

Но что на самом деле означает «платить»? А что если они перестанут платить?

В Checkly «оплата» означает, что ваша учетная запись в нашей базе данных Postgres имеет идентификатор stripe_subscription_id, который не равен NULL, и дата plan_expiry, которая будет в будущем. В коде Javascript:

const paying = account.stripe_subscription_id != null 
&& account.plan_expiry > Date.now()

Оба поля устанавливаются, когда появляется веб-крючок Stripe, сигнализирующий об успешной оплате подписки. Это автоматически отслеживает несвоевременные платежи и отмену подписки. Нет дополнительного кода для обновления произвольного поля «оплаты».

Вывод: «оплата» не является логическим значением, которое вы обновляете явно. Это вычисляемое свойство в зависимости от группы полей. Примите во внимание, что означает платный подписчик / владелец счета в вашем конкретном контексте. Если это SaaS ежемесячно / ежегодно, вам, вероятно, нужно проверить более одного поля данных.

Переключение функций на основе плана

Чтобы проверить, к каким функциям пользователь может получить доступ на основе своего плана, мы храним набор строковых констант для каждой учетной записи в поле, называемом функциями . Это основывается на базовом уровне функций, доступных каждому подписчику. Пустой список функций означает, что у вас есть базовый план. В коде:

const features = ["CI_CD_TRIGGERS", "SOME_OTHER_FEATURE"]

Этот набор функций находится в виде поля массива в каждой записи учетной записи, с которой связан пользователь. Кроме того, это поле доступно для бэкэнда и внешнего интерфейса, разумеется, только для записи бэкэндом. Не обновлять свои собственные функции!

Это поле заполняется или обновляется только в двух случаях:

  1. Пользователь подписывается на пробную версию. Мы заселить особенности поля с функциями суда.
  2. Пользователь обновляет платную учетную запись. Мы обновляем особенности поля с особенностями , поскольку они находятся в соответствующем плане.

У нас нет необычного интерфейса для управления этими функциями. Это не какие-то эксперименты или темные рамки запуска.

Checkly - это одностраничное приложение Vue.js, поддерживаемое бэкендом API Hapi.js. Но это работает, вероятно, в любой системе на основе SPA или не-SPA.

Вот как выглядит наш маршрут для отображения контроллера.

const a = require('../../models/defaults/access-rights')
const f = require('../../models/defaults/features')

  {
    method: 'POST',
    path: '/accounts/triggers/{checkId}',
    config: {
      plugins: {
        policies: [hasAccess([a.OWNER, a.ADMIN]), hasFeature(f.TRIGGERS)]
      },
      handler: TriggerController.createTrigger
    }
  },

Здесь есть два интересных момента.

  • hasAccessфункция, которая проверяет наличие прав доступа пользователей.
  • hasFeatureфункция, которая проверяет наличие признаков.

Обе функции включены мр. Плагин Horse, позволяющий привязывать политики к любому маршруту API. Вы также можете видеть, что мы импортируем канонический список прав доступа и функций из центрального списка значений по умолчанию.

То, что на самом деле происходит в функциях hasAccessи, hasFeatureсильно зависит от того, какой язык / рамки вы используете.

Вот сокращенные версии кода того, как мы делаем это для прав доступа и функций. Обратите внимание, что они оба возвращают функции, которые маршрутизатор http внедряет в цикле запросов http.

const hasAccess = function (accessRights) {

  // Define a function to check access based on request data.
  // in a previous authentication step, the account data was fetched
  // from the database.
  
  const hasSpecificAccess = function (request, reply, next) {
    if (accessRights.includes(access)) {
      next(null, true)
    } else {
      next(null, false)
    }
  }
  return hasSpecificAccess
}

Проверка возможностей...

const hasFeature = function (feature) {
  const hasSpecificFeature = function (request, reply, next) {
  
    // match if the feature is enabled

    return features && features.includes(feature) 
      ? next(null, true) 
      : next(null, false)
  }
  return hasSpecificFeature
}

Плановые ограничения объема

Проверка функций плана довольно аккуратно обрабатывается довольно общим способом подтверждения того, включена ли вещь или нет.

Проверка объемов немного отличается. Почему это отличается? Это отличается тем, что нам нужно указывать состояние конкретных ресурсов, которые мы предлагаем нашим клиентам, а не только флаги в учетной записи.

Это означает, что вы должны активно опрашивать вашу базу данных и считать вещи по каждому запросу. Да, вы можете кешировать немного, и быть выключенным одним может не быть концом света.

В приведенном выше примере страницы с ценами вы можете увидеть, что Checkly предлагает 5 проверок API для одного плана и 15 для другого. Вот как мы утверждаем этот предел громкости в нашем бэкэнд-API

function getVolumeLimits (accountId, delta) {
  const checksCountQuery = Checks.query().where({ accountId }).count()
  const accountLimitsQuery = Account.query().findOne({ accountId })

  return Promise.all([checksCountQuery, accountLimitsQuery])
    .then(res => {
      const count = res[0].count
      const { maxChecks } = res[1]
      const newTotal = parseInt(count) + delta
      return newTotal <= maxChecks
    })
}
  1. Эта функция выполняется после базовой авторизации, но до выполнения какой-либо реальной работы.
  2. Мы извлекаем текущие суммы чеков и план-предел чеков для текущего счета одновременно. Это очень Promise.allутверждение Javascript .
  3. Мы сравниваем текущую сумму с новой общей суммой. В нашем конкретном случае пользователь может создать несколько проверок одновременно, отсюда и deltaаргумент. В этом примере это так, 1но в реальной жизни это может быть любое число выше 0. Нам нужно проверить, вписывается ли общее количество новых «создаваемых вещей» в план.
  4. В конце мы вернемся, если значение newTotalменьше или равно maxChecksнашему пределу плана.

Утверждение того, что пользователи находятся в пределах своих плановых ограничений на бэкэнде, действительно важно по разным причинам, но как мы собираемся делать «хорошо с этим» на внешнем интерфейсе, особенно в настройке типа SPA? Мы не хотим , чтобы ситуация , когда пользователь, к счастью , создавая новую вещь, хиты представить и затем представлены «вы над вашими пределами плана» -message.

А как насчет отдыха?

А как насчет управления доступом на основе ролей? 
Как, черт возьми, ты справляешься с этим на переднем конце?

Да, все очень важные темы. Все они вплетены в материал, рассмотренный выше, поэтому мы рассмотрим его в следующем посте. Призыв к действию => ✨ Подписаться сейчас ✨