Igor Alexandrov

Programming and something else…

Биллинг в Rails приложениях (часть 1)

| Comments

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

Первой биллинговой системой был TBilling, разработанный для нужд сети VPN Тверского Государсвенного Университета и успешно там до сих пор (насколько мне известно) работающая. Тогда всё было очень просто, мы просто сделали патч к демону pppd, который позволял ему работать с базой данных и добавили логику для ограничения по времени, скорости и так далее. Так я сдал курсовую на третьем курсе и диплом на четвёртом.

Потом (в 2007 году) я устроился на работу в замечательный интернет-провайдер в Твери – Канстелл. Попал я в отдел биллинга, который состоял из меня и моего начальника – Дмитрия Замоткина (гуру русского Smalltalk сообщества). Дальше не нужно говорить, что биллнг был написан на Smalltalk, да? Со временем отдел преобразовался в отдельную компанию и появилась сертифицированная биллинговая система RequestBilling.

Потом я уволился, начал зарабатывать на жизнь занимаясь в основном Ruby, занялся своим делом. Моим третьим биллингом была система для большого SaaS-сервиса LienLog в США. Об этом мы и поговорим подробнее.

Задача

Сервис написан в основном на Rails 3.2. Есть пользователи с компаниями, основные услуги и дополнительные возможности, которые должны продаваться за отдельную плату. Конечно же у пользователей есть пресловутый американский PayPal и разнообразные карты оплаты. Кстати забыл добавить, что биллинг в сервисе должен быть предоплатным (это обычное решение для SaaS), то есть, например 1 января вы платите на 30 (в случае месячной подписки) или 365 (в случае годовой подписки) дней. Вы конечно же можете отказаться от использования сервисе где-нибудь в середине вашего оплаченного интервала, тогда неиспользованные деньги поступают на ваш внутренний (виртуальный) счет. Как их извлечь из сервиса (и вообще возможно ли это) – это уже детали конкретных реализаций.

Что нужно сделать:

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

Реализация

Как это всё должно работать? Самое очевидное решение, что в определенный момент времени должна запускаться какая-то rake задача, которая будет находить все аккаунты, которые надо посчитать, выполняться калькуляцию и создавать счёт (инвойс). Потом с этим полученным счетом вы можете делать всё что угодно – вежливо попросить пользователя его оплатить, удалить его, если пользователь сделал вам что-нибудь очень хорошее, в конце концов можно заблокировать пользователя, если счет не оплачен слишком долго.

Начнем пожалуй с самого простого – класса реализации тарифного плана.

app/models/billing/plan.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
require 'yajl'

class Billing::Plan::Base < ActiveRecord::Base
  self.table_name = 'billing_plans'

  validates :title, :presence => true
  validates :key, :presence => true, :uniqueness => true

  validates :monthly_amount,
            :annual_amount,
            :presence => { :unless => :free? },
            :absence => { :if => :free? },
            :numericality => { :allow_nil => true, :greater_than_or_equal_to => 0 }

  validates :trial, :presence => true, :numericality => { :greater_than_or_equal_to => 0 }

  # специфические для моей реализации параметры плана
  validates :maximum_users_count,
            :maximum_lien_holders_count,
            :free_states_count,
            :maximum_states_count,
            :maximum_open_liens_count,
            :numericality => { :greater_than => 0, :allow_nil => true }

  attr_accessible :key, :company_id, :title, :description,
    :allows_preauction, :allows_foreclosure, :allows_collaboration, :allows_dashboard,
    :free, :individual, :monthly_amount, :monthly_servicing_amount, :annual_amount, :annual_servicing_amount,
    :maximum_users_count, :free_states_count, :maximum_states_count, :maximum_open_liens_count, :maximum_lien_holders_count

  scope :active, where(:active => true)

  def requires_customer?
    !self.free?
  end

  def to_param
    self.key
  end

  def trial(ignore_test_mode=false)
    begin
      Settings.billing.mode == 'test' && !ignore_test_mode ? 2 : self.read_attribute(:trial)
    rescue Settingslogic::MissingSetting => e
      self.read_attribute(:trial)
    end
  end

  def amount_per_day(type=nil)
    return 0.to_d if self.free?

    case type.to_sym
    when :monthly
      (self.monthly_amount / Billing::Subscription::Monthly.billing_interval(true))
    when :annual
      (self.annual_amount / Billing::Subscription::Annual.billing_interval(true))
    else
      raise ArgumentError, "type should be :monthly or :annual"
    end
  end

  def amount(type=nil)
    return 0 if self.free?

    case type.to_sym
    when :monthly
      self.monthly_amount
    when :annual
      self.annual_amount
    else
      raise ArgumentError, "type should be :monthly or :annual"
    end
  end
end

Что должен знать о себе тарифный план?

  • У тарифного плана должно быть название title. Вы же хотите как-то сообщать пользователю о том, что он собирается выбрать.
  • Полезно также иметь у плана уникальный ключ key, по которому вам потом удобно будет этот план искать. Например вот так:
1
  plan = Billing::Plan.find_by_key('basic')
  • Важные атрибуты плана это конечно же месячная monthly_amount и годовая annual_amount стоимости.
  • План должен знать стандартную длительность триального периода trial при подписке на него.
  • Также нужно иметь возможность узнать сколько будет стоить тарифный план за один день (в моём случае один день – это минимальный биллинговый интервал).
1
2
3
4
5
  # стоимость в день месячной подписки
  plan.amount_per_day(:monthly)

  # стоимость в день годовой подписки
  plan.amount_per_day(:annual)
  • Также план может иметь различные специфические атрибуты. В моём случае это:
1
:allows_preauction, :allows_foreclosure, :allows_collaboration, :allows_dashboard, :free, :individual, :monthly_amount, :monthly_servicing_amount, :annual_amount, :annual_servicing_amount, :maximum_users_count, :free_states_count, :maximum_states_count, :maximum_open_liens_count, :maximum_lien_holders_count`

Эти атрибуты определяют набор возможностей и ограничений пользователя после выбора плана. Это может быть размер максимальный размер загруженных файлов или возможность просматривать RailsCasts с пометкой Pro и так далее.

Вот пожалуй всё, что можно рассказать про тарифный план. В следующих частях рассмотрим подписку (самый сложный объект системы), счет, промо коды, провайдеры платежей и транзакции.

Comments