billingly 0.0.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data/README.md +36 -0
  2. data/TUTORIAL.rdoc +158 -0
  3. data/app/controllers/billingly/subscriptions_controller.rb +33 -13
  4. data/app/models/billingly/base_customer.rb +306 -0
  5. data/app/models/billingly/base_plan.rb +16 -0
  6. data/app/models/billingly/base_subscription.rb +118 -0
  7. data/app/models/billingly/customer.rb +2 -115
  8. data/app/models/billingly/invoice.rb +20 -21
  9. data/app/models/billingly/journal_entry.rb +15 -0
  10. data/app/models/billingly/ledger_entry.rb +2 -3
  11. data/app/models/billingly/payment.rb +1 -1
  12. data/app/models/billingly/plan.rb +2 -4
  13. data/app/models/billingly/subscription.rb +2 -81
  14. data/app/views/billingly/subscriptions/_current_subscription.html.haml +22 -0
  15. data/app/views/billingly/subscriptions/_deactivation_notice.html.haml +12 -0
  16. data/app/views/billingly/subscriptions/_invoice_details.html.haml +9 -0
  17. data/app/views/billingly/subscriptions/_invoices.html.haml +30 -0
  18. data/app/views/billingly/subscriptions/_plans.html.haml +30 -0
  19. data/app/views/billingly/subscriptions/index.html.haml +10 -0
  20. data/app/views/billingly/subscriptions/invoice.html.haml +8 -0
  21. data/app/views/billingly/subscriptions/new.html.erb +1 -1
  22. data/app/views/billingly_mailer/paid_notification.html.erb +1 -1
  23. data/lib/billingly/engine.rb +1 -3
  24. data/lib/billingly/rails/routes.rb +3 -1
  25. data/lib/billingly/version.rb +1 -1
  26. data/lib/generators/billingly_mailer_views_generator.rb +9 -0
  27. data/lib/generators/billingly_views_generator.rb +9 -0
  28. data/lib/generators/templates/create_billingly_tables.rb +11 -16
  29. data/lib/tasks/billingly_tasks.rake +2 -0
  30. metadata +46 -22
  31. data/README.rdoc +0 -3
  32. data/app/views/billingly/subscriptions/index.html.erb +0 -2
data/README.md ADDED
@@ -0,0 +1,36 @@
1
+ # Billingly
2
+
3
+ Billingly is a rails 3 engine that manages paid subscriptions and free trials to your web application.
4
+
5
+ Billingly can:
6
+
7
+ * Subscribe customers to your service:
8
+ * Subscriptions can have an arbitrary length: A year, a month, 90 days ...
9
+ * You can request payments upfront or after the subscription period is over.
10
+
11
+ * Offer standarized subscription plans for self-service sign ups.
12
+
13
+ * Offer special deals on a per-customer basis.
14
+
15
+ * Invoice your customers automatically and send receipts once they pay.
16
+
17
+ * Notify customers about due dates.
18
+
19
+ * Restrict access to debtors, and let them back in once they pay their debt.
20
+
21
+ * Let customers upgrade or downgrade to another plan. Prorating and reimbursing in case there were any upfront payments.
22
+
23
+ * Let you give arbitrary bonuses, vouchers and gifts credited to your customer's account.
24
+
25
+ * Offer a trial period before you require people to become paying customers.
26
+
27
+ Billingly does not receive payments directly (from Paypal, or Worldpay for example). However, you can use [ActiveMerchant](http://activemerchant.org) for handling payment notifications from third party services, and easily hookup Billingly to credit the payment into your customer's account. Billingly will take care of all the rest.
28
+
29
+ # Getting Started
30
+ * Read the [Getting Started Guide](http://rubydoc.info/github/nubis/billingly/master/file/TUTORIAL.rdoc).
31
+ * Check out the [Demo App](http://billing.ly).
32
+ * Explore the [Docs](http://rubydoc.info/github/nubis/billingly/master/frames/file/README.md).
33
+
34
+ # License
35
+ MIT License. Copyright 2012 Nubis (nubis@woobiz.com.ar)
36
+
data/TUTORIAL.rdoc ADDED
@@ -0,0 +1,158 @@
1
+ # @markup markdown
2
+
3
+ # Getting Started
4
+
5
+ ## Preface
6
+
7
+ At its core, billingly has a {Billingly::Customer} class. A {Billingly::Customer} has a {Billingly::Subscription} to your service, for which she will receive {Billingly::Invoice Invoices} regularly via email.
8
+ Billingly keeps a balance for each one of your {Customer customers}, whenever you receive a payment you should {Billingly::BaseCustomer#credit_payment credit the payment} into their account.
9
+
10
+ When a payment is credited, billingly will try to settle outstanding invoices, always starting from the oldest one. If the customer's balance is not enough to cover the last pending invoice then nothing will happen. Once an invoice is settled the customer will be sent a receipt via email.
11
+
12
+ Invoices have a due date, customers will notified about pending invoices before they are overdue. When a customer misses a payment, billingly will immediately deactivate his account and notify via email about the deactivation.
13
+
14
+ Deactivated customers will be redirected forcefully to their subscription page where they can see all their invoices. Once they pay their overdue invoices their account is re-activated.
15
+
16
+ You may change a customers subscription at any point. Under the hood, changing a subscription consist on terminating the current subscription and creating a new one. Any invoices paid for the terminated subscription will be automatically prorated and the remaining balance will be credited back into the customer's account.
17
+
18
+ Each customer can have a completely custom Subscription, but you will usually want people to sign up to a predefined {Billingly::Plan}. Billingly comes with a {Billingly::Plan} model and a {Billingly::SubscriptionsController} which can be extended and enable you to support self-service subscriptions out of the box.
19
+
20
+ Billingly also lets you offer free trial subscriptions. You can configure a trial termination date when subscribing a customer to any type of plan, billingly will deactivate the customers account when the date of expiration comes, and will show them a subscription page from where they can signup to any other full plan.
21
+
22
+ # Installing
23
+
24
+ ## Get the gem
25
+
26
+ gem install billingly
27
+
28
+ gem 'billingly'
29
+
30
+ ## Create the tables
31
+
32
+ Use the provided generator to create the migration that will generate all the required tables.
33
+
34
+ You can add your custom attributes to the customer and plans tables, but changing the table names is not advised. Tables are namespaced with the 'billingly_' prefix.
35
+
36
+ rails g billingly_migration
37
+
38
+ For example, if your application's plans differ in the amount of users they allow, you can
39
+ add a `user_quota` field to your `billingly_plans` table.
40
+
41
+ You may also add a foreign key to your `billingly_customers` table so that each customer points
42
+ to a user.
43
+
44
+ ## Customize the Models
45
+
46
+ Billingly models are abstract classes with default implementations, you don't need to override them but you would probably want to override the {Billingly::Customer} model to provide your implementations for the {Billingly::BaseCustomer#on\_subscription\_success} and {Billingly::BaseCustomer#can\_subscribe\_to?}
47
+
48
+ Continuing with the previous example, this snippet adds a user association to the customer and denormalizes the `user_quota` from the chosen plan into the User for easier lookup. It also prevents customers from subscribing to a plan offering a smaller quota than their current one.
49
+
50
+ # app/models/billingly/customer.rb
51
+ class Billingly::Customer < Billingly::BaseCustomer
52
+ belongs_to :user
53
+
54
+ def on_subscription_success
55
+ # For simplicity, we assume there is always a user.
56
+ user.update_attribute(:user_quota, active_subscription.plan.user_quota)
57
+ end
58
+
59
+ # Return false signifies that the user cannot subscribe to the provided plan
60
+ # if the current plan has a larger storage quota.
61
+ def can_subscribe_to?(plan)
62
+ current_plan = active_subscription.plan
63
+ if current_plan && current_plan.user_quota > plan.user_quota
64
+ return false
65
+ end
66
+ super
67
+ end
68
+ end
69
+
70
+ ## Provide a Customer to your controllers
71
+
72
+ Billingly comes with 2 before filters for requiring the current user to be a customer, and
73
+ requiring the current customer to be active (that is, to not be deactivated because of an expired trial, overdue invoices, etc). These methods are called `requires_customer` and `requires_active_customer` respectively.
74
+
75
+ You provide a customer by overriding `current_customer` on your ApplicationController. Ideally, you would always have a `current_customer` as long as there is a user logged in, even if the currently logged in user has not subscribed to any plan yet. `current_customer` can return `nil` if no concept of a customer is available.
76
+
77
+ The `requires_customer` before\_filter will call `on_empty_customer` when `current_customer` is `nil`. `on_empty_customer` simply redirects to your root_path but you can override it too.
78
+
79
+ `requires_active_customer` redirects to the {Billingly::SubscriptionsController#index}, which presents the user with the steps required to reactivate his account.
80
+
81
+ Continuing with the {https://github.com/plataformatec/devise Devise} compatible examples, here's a snippet that overrides `ApplicationController` to use the customer associated to a user.
82
+
83
+ class ApplicationController < ActionController::Base
84
+ def current_customer
85
+ current_user.customer
86
+ end
87
+ end
88
+
89
+ `current_customer` is also available on your views as a helper method.
90
+
91
+ ## Customize the Controller
92
+
93
+ The {Billingly::SubscriptionsController} can be overriden too. You may want to override {Billingly::SubscriptionsController#on\_subscription\_success #on\_subscription\_success} and {Billingly::SubscriptionsController#on\_reactivation\_success #on\_reactivation\_success}.
94
+
95
+ These are called when your customer subscribes or reactivates his account, respectively.
96
+
97
+ They both redirect to the `index` action by default.
98
+
99
+ Here's a snippet overriding them to go to the root path with a flash notice.
100
+
101
+ # app/controller/custom_subscriptions_controller.rb
102
+ class CustomSubscriptionsController < Billingly::SubscriptionsController
103
+ def on_subscription_success
104
+ redirect_to root_path, notice: "yay, you subscribed!"
105
+ end
106
+
107
+ def on_reactivation_success
108
+ redirect_to root_path, notice: "yay, your account was reactivated!"
109
+ end
110
+ end
111
+
112
+ ## Mount Routes
113
+
114
+ We provide a shortcut so you can add the {Billingly::SubscriptionsController SubscriptionsController} to your `routes.rb`.
115
+
116
+ Here's a snippet showing two different ways of mounting the routes:
117
+
118
+ # your routes.rb
119
+ YourApp::Application.routes.draw do
120
+
121
+ # Will mount the default Billingly::SubscriptionsController in the /subscriptions path.
122
+ add_billingly_routes
123
+
124
+ # Will mount CustomModule::CustomSubscriptionsController in the
125
+ # /namespaced/subscriptions path
126
+ add_billingly_routes 'namespaced', 'custom_module/custom_subscriptions_controller'
127
+ end
128
+
129
+ ## Schedule all the recurring jobs
130
+ All the invoicing, deactivating and emailing is done through a rake task.
131
+
132
+ The tasks are designed to fail gracefully and you can run them as often as you want without getting into undesired or invalid states.
133
+
134
+ Configure a cron job to run this around 4 times a day (should run without failures at least once a day)
135
+
136
+ $ rake billingly:all
137
+
138
+ ## Customize the SubscriptionsController templates
139
+
140
+ Use the provided template generator to copy all of billingly's templates and partials into your application's directory structure.
141
+
142
+ All templates are provided in Haml format, using Twitter Bootstrap compatible markup.
143
+
144
+ These templates are used directly on the {http://billing.ly Demo App}
145
+
146
+ $ rails g billingly_views
147
+
148
+ ## Customize the mailer templates
149
+
150
+ Billingly will send emails for the following scenarios:
151
+
152
+ * An invoice is available to be paid.
153
+ * An invoice is about to go overdue.
154
+ * A payment was processed successfully.
155
+
156
+ You can copy all the built in templates into your app's directory structure and customize them:
157
+
158
+ $ rails g billingly_mailer_views
@@ -1,25 +1,23 @@
1
1
  # This controller takes care of managing subscriptions.
2
2
  class Billingly::SubscriptionsController < ::ApplicationController
3
- before_filter :requires_customer, only: [:index, :reactivate]
4
- before_filter :requires_active_customer, except: [:index, :reactivate]
3
+ before_filter :requires_customer
4
+ before_filter :requires_active_customer, except: [:index, :reactivate, :invoice]
5
5
 
6
6
  # Index shows the current subscription to customers while they are active.
7
7
  # It's also the page that prompts them to reactivate their account when deactivated.
8
8
  # It's likely the only reachable page for deactivated customers.
9
9
  def index
10
10
  @subscription = current_customer.active_subscription
11
- redirect_to(action: :new) unless @subscription
12
- end
13
-
14
- # Should let customers choose a plan to subscribe to, wheter they are subscribing
15
- # for the first time or upgrading their plan.
16
- def new
17
11
  @plans = Billingly::Plan.all
12
+ @invoices = current_customer.invoices.order('created_at DESC')
18
13
  end
19
-
14
+
20
15
  # Subscribe the customer to a plan, or change his current plan.
21
16
  def create
22
17
  plan = Billingly::Plan.find(params[:plan_id])
18
+ unless current_customer.can_subscribe_to?(plan)
19
+ return redirect_to subscriptions_path, notice: 'Cannot subscribe to that plan'
20
+ end
23
21
  current_customer.subscribe_to_plan(plan)
24
22
  on_subscription_success
25
23
  end
@@ -29,20 +27,42 @@ class Billingly::SubscriptionsController < ::ApplicationController
29
27
  # Their account will be reactivated to their old subscription plan immediately.
30
28
  # They can change plans afterwards.
31
29
  def reactivate
32
- return render nothing: true, status: 403 unless current_customer.reactivate
30
+ plan = Billingly::Plan.find_by_id(params[:plan_id])
31
+ if current_customer.reactivate(plan).nil?
32
+ return render nothing: true, status: 403
33
+ end
33
34
  on_reactivation_success
34
35
  end
36
+
37
+ # Unsubscribes the customer from his last subscription and deactivates his account.
38
+ # Performing this action would set the deactivation reason to be 'left_voluntarily'
39
+ def deactivate
40
+ current_customer.deactivate_left_voluntarily
41
+ redirect_to(action: :index)
42
+ end
43
+
44
+ # Shows an invoice.
45
+ # @todo
46
+ # This should actually be the #show action of an InvoicesController but we're lazy ATM.
47
+ def invoice
48
+ @invoice = current_customer.invoices.find(params[:invoice_id])
49
+ end
35
50
 
36
51
  # When a subscription is sucessful this callback is triggered.
37
52
  # Host applications should override it by subclassing this subscriptionscontroller,
38
53
  # and include their own behaviour, and for example grant the privileges associated
39
54
  # to the subscription plan.
55
+ #
56
+ # Redirects to the last invoice by default.
40
57
  def on_subscription_success
41
- redirect_to(action: :index)
58
+ current_customer.reload
59
+ redirect_to(invoice_subscriptions_path(current_customer.active_subscription.invoices.last.id))
42
60
  end
43
61
 
62
+ # Should be overriden to provide a response when the user account is reactivated.
63
+ #
64
+ # Defaults to a redirect to :index
44
65
  def on_reactivation_success
45
- on_subscription_success
66
+ redirect_to(action: :index)
46
67
  end
47
-
48
68
  end
@@ -0,0 +1,306 @@
1
+ require 'validates_email_format_of'
2
+
3
+ module Billingly
4
+
5
+ # A {Customer} is Billingly's main actor.
6
+ # * Customers have a {Subscription} to your service which entitles them to use it.
7
+ # * Customers are {Invoice invoiced} regularly to pay for their {Subscription}
8
+ # * {Payment Payments} are received on a {Customer}s behalf and credited to their account.
9
+ # * Invoices are generated periodically calculating charges a Customer incurred in.
10
+ # * Receipts are sent to Customers when their invoices are paid.
11
+ class BaseCustomer < ActiveRecord::Base
12
+ self.abstract_class = true
13
+ self.table_name = 'billingly_customers'
14
+
15
+ # The reason why this customer is deactivated.
16
+ # A customer can be deactivated for one of 3 reasons:
17
+ # * trial_expired: Their trial period expired.
18
+ # * debtor: They have unpaid invoices.
19
+ # * left_voluntarily: They decided to leave the site.
20
+ # This is important when reactivating their account. If they left in their own terms,
21
+ # we won't try to reactivate their account when we receive a payment from them.
22
+ # The message shown to them when they reactivate will also be different depending on
23
+ # how they left.
24
+ DEACTIVATION_REASONS = [:trial_expired, :debtor, :left_voluntarily]
25
+
26
+ # The Date and Time in which the Customer's account was deactivated (see {#deactivated?}).
27
+ # This field denormalizes the date in which this customer's last subscription was ended.
28
+ # @!attribute [r] deactivated_since
29
+ # @return [DateTime]
30
+ validates :deactivated_since, presence: true, if: :deactivation_reason
31
+
32
+ # (see Customer::DEACTIVATION_REASONS)
33
+ # @return [String]
34
+ # @!attribute deactivation_reason
35
+ validates :deactivation_reason, inclusion: DEACTIVATION_REASONS, if: :deactivated?
36
+
37
+ # A customer can be {#deactivate deactivated} when they cancel their subscription
38
+ # or when they miss a payment. Under the hood this function checks the
39
+ # {#deactivated_since} attribute.
40
+ # @!attribute [r] deactivated?
41
+ def deactivated?
42
+ not deactivated_since.nil?
43
+ end
44
+
45
+ # Used as contact address, validates format but does not check uniqueness.
46
+ # @!attribute email
47
+ # @return [String]
48
+ attr_accessible :email
49
+ validates_email_format_of :email
50
+
51
+ # All subscriptions this customer was ever subscribed to.
52
+ # @!attribute subscriptions
53
+ # @return [Array<Subscription>]
54
+ has_many :subscriptions, foreign_key: 'customer_id'
55
+
56
+ # All paymetns that were ever credited for this customer
57
+ # @!attribute payments
58
+ # @return [Array<Payment>]
59
+ has_many :payments, foreign_key: 'customer_id'
60
+
61
+ # The {Subscription} for which the customer is currently being charged.
62
+ # @!attribute [r] active_subscription
63
+ # @return [Subscription, nil]
64
+ def active_subscription
65
+ last = subscriptions.last
66
+ last unless last.nil? || last.terminated?
67
+ end
68
+
69
+ # (see Customer.debtors)
70
+ # @!attribute [r] debtor?
71
+ # @return [Boolean] whether this customer is a debtor or not.
72
+ def debtor?
73
+ not self.class.debtors.find_by_id(self.id).nil?
74
+ end
75
+
76
+ # All {Invoice invoices} ever created for this customer, for any {Subscription}
77
+ # @!attribute invoices
78
+ # @return [Array<Invoice>]
79
+ has_many :invoices, foreign_key: 'customer_id'
80
+
81
+ # Every {JournalEntry} ever created for this customer, a {#ledger} is created from these.
82
+ # See {JournalEntry} for a description on what they are.
83
+ # @!attribute journal_entries
84
+ # @return [Array<JournalEntry>]
85
+ has_many :journal_entries, foreign_key: 'customer_id'
86
+
87
+ # (see Customer::DEACTIVATION_REASONS)
88
+ # @return [Symbol]
89
+ # @!attribute deactivation_reason
90
+ validates :deactivation_reason, inclusion: DEACTIVATION_REASONS, if: :deactivated?
91
+
92
+ # Whether the user is on an unfinished trial period.
93
+ # @!attribute [r] doing_trial?
94
+ # @return [Boolean]
95
+ def doing_trial?
96
+ active_subscription && active_subscription.trial?
97
+ end
98
+
99
+ # When the user is doing a trial, this would be how many days are left until it's over.
100
+ # @!attribute [r] trial_days_left
101
+ # @return [Integer]
102
+ def trial_days_left
103
+ return unless doing_trial?
104
+ (active_subscription.is_trial_expiring_on.to_date - Date.today).to_i
105
+ end
106
+
107
+ # Customers subscribe to the service under certain conditions referred to as a {Plan},
108
+ # and perform periodic payments to continue using it.
109
+ # We offer common plans stating how much and how often they should pay, also, if the
110
+ # payment is to be done at the beginning or end of the period (upfront or due-month)
111
+ # Every customer can potentially get a special deal, but we offer common
112
+ # deals as {Plan Plans} from which a proper {Subscription} is created.
113
+ # A {Subscription} is also an acceptable argument, in that case the new one
114
+ # will maintain all the characteristics of that one, except the starting date.
115
+ # @param [Plan, Subscription]
116
+ # @return [Subscription] The newly created {Subscription}
117
+ def subscribe_to_plan(plan, is_trial_expiring_on = nil)
118
+ subscriptions.last.terminate if subscriptions.last
119
+
120
+ subscriptions.build.tap do |new|
121
+ [:payable_upfront, :description, :periodicity,
122
+ :amount, :grace_period].each do |k|
123
+ new[k] = plan[k]
124
+ end
125
+ new.plan = plan if plan.is_a?(Billingly::Plan)
126
+ new.is_trial_expiring_on = is_trial_expiring_on
127
+ new.subscribed_on = Time.now
128
+ new.save!
129
+ new.generate_next_invoice
130
+ on_subscription_success
131
+ end
132
+ end
133
+
134
+ # Callback called whenever this customer is successfully subscribed to a plan.
135
+ # This callback does not differentiate if the customer is subscribing for the first time,
136
+ # reactivating his account or just changing from one plan to another.
137
+ # self.active_subscription will be the current subscription when this method is called.
138
+ def on_subscription_success
139
+ end
140
+
141
+ # Creates a general ledger from {JournalEntry journal entries}.
142
+ # Every {Invoice} and {Payment} involves movements to the customer's account.
143
+ # which are registered as a {JournalEntry}.
144
+ # The ledger can tell us whats the cash balance
145
+ # in our customer's favor and how much money have they paid overall.
146
+ # @see JournalEntry
147
+ # @return [{Symbol => BigDecimal}]
148
+ # @todo Due to silly rounding errors on sqlite this implementation needs
149
+ # to convert decimals to float and then to decimals again. :S
150
+ def ledger
151
+ Hash.new(0.0).tap do |all|
152
+ journal_entries.group_by(&:account).collect do |account, entries|
153
+ values = entries.collect(&:amount).collect(&:to_f)
154
+ all[account.to_sym] = values.inject(0.0) do |sum,item|
155
+ (BigDecimal.new(sum.to_s) + BigDecimal.new(item.to_s)).to_f
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ # Shortcut for adding {#journal_entries} for this customer.
162
+ # @note Most likely, you will never add entries to the customer journal yourself.
163
+ # These are created when {Invoice invoicing} or crediting {Payment payments}
164
+ def add_to_journal(amount, *accounts, extra)
165
+ accounts = [] if accounts.nil?
166
+ unless extra.is_a?(Hash)
167
+ accounts << extra
168
+ extra = {}
169
+ end
170
+
171
+ accounts.each do |account|
172
+ journal_entries.create!(extra.merge(amount: amount, account: account.to_s))
173
+ end
174
+ end
175
+
176
+ # This method will deactivate all customers who have overdue {Invoice Invoices}.
177
+ # It's run periodically through Billingly's Rake Task.
178
+ def self.deactivate_all_debtors
179
+ debtors.where(deactivated_since: nil).all.each{|debtor| debtor.deactivate_debtor }
180
+ end
181
+
182
+ # A customer who has overdue invoices at the time of asking this question is
183
+ # considered a debtor.
184
+ #
185
+ # @note
186
+ # A customer may be a debtor and still have an active account until Billingly's
187
+ # rake task goes through the process of {#deactivate_all_debtors deactivating all debtors}.
188
+ #
189
+ # Furthermore, customers may unsubscribe before their {Invoice invoices} become overdue,
190
+ # hence they may be in a deactivated state and not be debtors yet.
191
+ def self.debtors
192
+ joins(:invoices).readonly(false)
193
+ .where("#{Billingly::Invoice.table_name}.due_on < ?", Time.now)
194
+ .where(billingly_invoices: {deleted_on: nil, paid_on: nil})
195
+ end
196
+
197
+ # Credits an amount of money to customer's account and then triggers the corresponding
198
+ # actions if a {Payment payment} was expected from this customer.
199
+ #
200
+ # Apart from creating a {Payment} object this method will try to charge pending invoices
201
+ # and reactivate a customer who was deactivated for being a debtor.
202
+ #
203
+ # @note
204
+ # This is the single point of entry for {Payment Payments}.
205
+ #
206
+ # If you're processing payments using {http://activemerchant.org} you should hook
207
+ # your 'Incoming Payment Notifications' to call this method to credit the received
208
+ # amount to the customer's account.
209
+ #
210
+ # @param amount [BigDecimal, float] the amount to be credited.
211
+ def credit_payment(amount)
212
+ Billingly::Payment.credit_for(self, amount)
213
+ Billingly::Invoice.charge_all(self.invoices)
214
+ reactivate if deactivated? && deactivation_reason == :debtor
215
+ end
216
+
217
+ # Terminate a customer's subscription to the service.
218
+ # Customers are deactivated due to lack of payment, because they decide to end their
219
+ # subscription to your service or because their trial period expired.
220
+ #
221
+ # Use the shortcuts:
222
+ # {#deactivate_left_voluntarily}, {#deactivate_trial_expired} or {#deactivate_debtor}
223
+ #
224
+ # Deactivated customers can always be {#reactivate reactivated} later.
225
+ # @param reason [Symbol] the deactivation reason, see {DEACTIVATION_REASONS}
226
+ # @return [self, nil] nil if the account was already deactivated, self otherwise.
227
+ def deactivate(reason)
228
+ return if deactivated?
229
+ active_subscription.terminate
230
+ self.deactivated_since = Time.now
231
+ self.deactivation_reason = reason
232
+ self.save!
233
+ return self
234
+ end
235
+
236
+ DEACTIVATION_REASONS.each do |reason|
237
+ define_method("deactivate_#{reason}") do
238
+ deactivate(reason.to_sym)
239
+ end
240
+ end
241
+
242
+ # @see #deactivate
243
+ def deactivate_left_voluntarily
244
+ deactivate(:left_voluntarily)
245
+ end
246
+
247
+ # @see #deactivate
248
+ def deactivate_trial_expired
249
+ deactivate(:trial_expired)
250
+ end
251
+
252
+ # @see #deactivate
253
+ def deactivate_debtor
254
+ deactivate(:debtor)
255
+ end
256
+
257
+ # Customers whose account has been {#deactivate deactivated} can always re-join the service
258
+ # as long as they {#debtor? don't owe any money}
259
+ # @return [self, nil] nil if the customer could not be reactivated, self otherwise.
260
+ def reactivate(new_plan = nil)
261
+ new_plan = new_plan || subscriptions.last
262
+ return if new_plan.nil?
263
+ return unless deactivated?
264
+ return if debtor?
265
+ update_attribute(:deactivated_since, nil)
266
+ subscribe_to_plan(new_plan)
267
+ return self
268
+ end
269
+
270
+ # Customers may be subscribed for a trial period, and they are supposed to re-subscribe
271
+ # before their trial expires.
272
+ #
273
+ # When their trial expires and they have not yet subscribed to another plan, we
274
+ # deactivate their account immediately.
275
+ #
276
+ # This method will deactivate all customers whose trial has expired.
277
+ # It's run periodically through Billingly's Rake Task.
278
+ def self.deactivate_all_expired_trials
279
+ customers = joins(:subscriptions).readonly(false)
280
+ .where("#{Billingly::Subscription.table_name}.is_trial_expiring_on < ?", Time.now)
281
+ .where(billingly_subscriptions: {unsubscribed_on: nil})
282
+
283
+ customers.each do |customer|
284
+ customer.deactivate_trial_expired
285
+ end
286
+ end
287
+
288
+ # Can this customer subscribe to a plan?.
289
+ # You may want to prevent customers from upgrading or downgrading to other plans
290
+ # depending on their usage of your service.
291
+ #
292
+ # This method is only used in views and controllers to prevent customers from requesting
293
+ # to be upgraded or downgraded to a plan without your consent.
294
+ # The model layer can still subscribe the customer if you so desire.
295
+ #
296
+ # The default implementation lets Customers upgrade to any if they are currently doing
297
+ # a trial period, and it does not let them re-subscribe to the same plan afterwards.
298
+ # It also always disallows debtors to subscribe to another plan.
299
+ # @param plan [Billingly::Plan]
300
+ def can_subscribe_to?(plan)
301
+ return false if !doing_trial? && active_subscription && active_subscription.plan == plan
302
+ return false if debtor?
303
+ return true
304
+ end
305
+ end
306
+ end