Class: Billingly::BaseSubscription

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
app/models/billingly/base_subscription.rb

Overview

A customer will always have at least one subscription to your application. Everytime there is a change in a customer’s subscription, the current one is terminated immediately and a new one is created.

For example, changing a Plan consists on terminating the current subscription and creating a new one for the new plan. Also, a new subscription is created when customers reactivate their accounts after being deactivated.

The most recent subscription is the one currently being charged for, unless the customer is deactivated at the moment, in which case the last subscription should not be considered to be active.

Direct Known Subclasses

Subscription

Constant Summary collapse

TERMINATION_REASONS =

Subscriptions are terminated for a reason which could be:

* trial_expired: Subscription was a trial and it just expired.
* debtor: The customer owed an invoice for this subscription and did not pay.
* changed_subscription: This subscription was immediately replaced by another one.
* left_voluntarily: This subscription was terminated because the customer left.

TERMINATION_REASONS are important for auditing and for the mailing tasks to notify about subscriptions terminated automatically by the system.

%w(trial_expired debtor changed_subscription left_voluntarily)
GENERATE_AHEAD =

Invoices will be generated before their due_date, as soon as possible, but not sooner than GENERATE_AHEAD days.

3.days

Instance Method Summary collapse

Instance Method Details

#generate_next_invoiceObject

The invoice generation process should run frequently, at least on a daily basis. It will create invoices some time before they are due, to give customers a chance to pay and settle them. If there is any #signup_price then the first invoice will be for that amount instead of the regular amount. This method is idempotent, if an upcoming invoice for a subscription already exists, it does not create yet another one.



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'app/models/billingly/base_subscription.rb', line 125

def generate_next_invoice
  return if terminated?
  return if trial?
  from = invoices.empty? ? subscribed_on : invoices.last.period_end
  to = from + periodicity
  due_on = (payable_upfront ? from : to) + grace_period
  return if GENERATE_AHEAD.from_now < from

  payable_amount = (invoices.empty? && ) ?  : amount
  
  invoice = invoices.create!(customer: customer, amount: payable_amount,
    due_on: due_on, period_start: from, period_end: to)
  invoice.charge
  return invoice
end

#grace_periodActiveSupport::Duration

The grace period we use when calculating an invoices due date. If a subscription is payable_upfront, then the customer effectively owes us since the day in which a given period starts. If a subscription is payable on ‘due-month’, then the customer effectively owes us money since the date in which a given period ended. When we invoice for a given period we will set the due_date a few days ahead of the date in which the debt was made effective, we call this a grace_period.

Returns:

  • (ActiveSupport::Duration)

    It’s what you get by doing ‘1.month’, ‘10.days’, etc.



70
# File 'app/models/billingly/base_subscription.rb', line 70

has_duration :grace_period

#notify_trial_expiredself?

When a trial subscription ends the customer is notified about it via email.

Returns:

  • (self, nil)

    not nil means the notification was sent successfully.



168
169
170
171
172
173
174
175
176
# File 'app/models/billingly/base_subscription.rb', line 168

def notify_trial_expired
  return unless trial?
  return unless terminated? && unsubscribed_because == 'trial_expired'
  return unless notified_trial_expired_on.nil?
  return if customer.do_not_email?
  Billingly::Mailer.trial_expired_notification(self).deliver!
  update_attribute(:notified_trial_expired_on, Time.now)
  return self
end

#planBillingly::Plan?

When a subscription was started from a Plan a reference to the plan is saved. Although, all the plan’s fields are denormalized in this subscription.

If the subscription was not started from a plan, then this will be nil.

Returns:



89
# File 'app/models/billingly/base_subscription.rb', line 89

belongs_to :plan

#subscribed_onDateTime

The date in which this subscription started. This subscription’s first invoice will have it’s period_start date matching the date in which the subscription started.

Returns:

  • (DateTime)


27
# File 'app/models/billingly/base_subscription.rb', line 27

validates :subscribed_on, presence: true

#terminate(reason) ⇒ self?

Terminates this subscription, it could be either because we deactivate a debtor or because the customer decided to end his subscription on his own terms.

Use the shortcuts:

{#terminate_left_voluntarily}, {#terminate_trial_expired},
{#terminate_debtor}, {#terminate_changed_subscription}

Once terminated, a subscription cannot be re-open, just create a new one.

Parameters:

Returns:

  • (self, nil)

    nil if the account was already terminated, self otherwise.



151
152
153
154
155
156
157
158
# File 'app/models/billingly/base_subscription.rb', line 151

def terminate(reason)
  return if terminated?
  self.unsubscribed_on = Time.now
  self.unsubscribed_because = reason
  invoices.last.truncate unless trial?
  save!
  return self
end

#terminated?Boolean

Was this subscription terminated?

Returns:

  • (Boolean)

    Whether the subscription was terminated or not.



56
57
58
# File 'app/models/billingly/base_subscription.rb', line 56

def terminated?
  not unsubscribed_on.nil?
end

#trial?Boolean

Returns:

  • (Boolean)


94
95
96
# File 'app/models/billingly/base_subscription.rb', line 94

def trial?
  not is_trial_expiring_on.nil?
end

#unsubscribed_becauseDateTime

The reason why this subscription ended.

Every ended subscription ended for a reason, look at TERMINATION_REASONS.

Returns:

  • (DateTime)


51
# File 'app/models/billingly/base_subscription.rb', line 51

validates :unsubscribed_because, inclusion: TERMINATION_REASONS, if: :terminated?

#unsubscribed_onDateTime

The date in which this subscription ended.

Every ended subscription ended for a reason, look at TERMINATION_REASONS.

Returns:

  • (DateTime)


44
# File 'app/models/billingly/base_subscription.rb', line 44

validates :unsubscribed_on, presence: true, if: :unsubscribed_because