billingly 0.1.1 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Billingly
2
2
 
3
+ ![Travis Build Status](https://secure.travis-ci.org/nubis/billingly.png)
4
+
3
5
  Billingly is a rails 3 engine that manages paid subscriptions and free trials to your web application.
4
6
 
5
7
  Billingly can:
@@ -1,5 +1,8 @@
1
- class BillinglyMailer < ActionMailer::Base
1
+ class Billingly::BaseMailer < ActionMailer::Base
2
2
  default from: 'example@example.com'
3
+
4
+ cattr_accessor :admin_emails
5
+ self.admin_emails = 'admin@example.com'
3
6
 
4
7
  def pending_notification(invoice)
5
8
  @invoice = invoice
@@ -17,4 +20,16 @@ class BillinglyMailer < ActionMailer::Base
17
20
  @invoice = invoice
18
21
  mail(to: invoice.customer.email, subject: I18n.t('billingly.payment_receipt'))
19
22
  end
23
+
24
+ def task_results(runner)
25
+ @runner = runner
26
+ mail to: self.class.admin_emails, subject: "Your Billingly Status Report"
27
+ end
28
+
29
+ # Sends the email about an expired trial.
30
+ # param trial [Subscription] a trial which should be expired.
31
+ def trial_expired_notification(subscription)
32
+ @subscription = subscription
33
+ mail to: subscription.customer.email, subject: I18n.t('billingly.your_trial_has_expired')
34
+ end
20
35
  end
@@ -0,0 +1,3 @@
1
+ # (see Billingly::BaseMailer)
2
+ class Billingly::Mailer < Billingly::BaseMailer
3
+ end
@@ -21,7 +21,7 @@ module Billingly
21
21
  # we won't try to reactivate their account when we receive a payment from them.
22
22
  # The message shown to them when they reactivate will also be different depending on
23
23
  # how they left.
24
- DEACTIVATION_REASONS = [:trial_expired, :debtor, :left_voluntarily]
24
+ DEACTIVATION_REASONS = %w(trial_expired debtor left_voluntarily)
25
25
 
26
26
  # The Date and Time in which the Customer's account was deactivated (see {#deactivated?}).
27
27
  # This field denormalizes the date in which this customer's last subscription was ended.
@@ -101,7 +101,7 @@ module Billingly
101
101
  # @return [Integer]
102
102
  def trial_days_left
103
103
  return unless doing_trial?
104
- (active_subscription.is_trial_expiring_on.to_date - Date.today).to_i
104
+ (active_subscription.is_trial_expiring_on.to_date - Time.now.utc.to_date).to_i
105
105
  end
106
106
 
107
107
  # Customers subscribe to the service under certain conditions referred to as a {Plan},
@@ -115,7 +115,7 @@ module Billingly
115
115
  # @param [Plan, Subscription]
116
116
  # @return [Subscription] The newly created {Subscription}
117
117
  def subscribe_to_plan(plan, is_trial_expiring_on = nil)
118
- subscriptions.last.terminate if subscriptions.last
118
+ subscriptions.last.terminate_changed_subscription if subscriptions.last
119
119
 
120
120
  subscriptions.build.tap do |new|
121
121
  [:payable_upfront, :description, :periodicity,
@@ -173,12 +173,6 @@ module Billingly
173
173
  end
174
174
  end
175
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
176
  # A customer who has overdue invoices at the time of asking this question is
183
177
  # considered a debtor.
184
178
  #
@@ -210,8 +204,8 @@ module Billingly
210
204
  # @param amount [BigDecimal, float] the amount to be credited.
211
205
  def credit_payment(amount)
212
206
  Billingly::Payment.credit_for(self, amount)
213
- Billingly::Invoice.charge_all(self.invoices)
214
- reactivate if deactivated? && deactivation_reason == :debtor
207
+ charge_pending_invoices
208
+ reactivate if deactivated? && deactivation_reason == 'debtor'
215
209
  end
216
210
 
217
211
  # Terminate a customer's subscription to the service.
@@ -226,10 +220,10 @@ module Billingly
226
220
  # @return [self, nil] nil if the account was already deactivated, self otherwise.
227
221
  def deactivate(reason)
228
222
  return if deactivated?
229
- active_subscription.terminate
223
+ active_subscription.terminate(reason)
230
224
  self.deactivated_since = Time.now
231
225
  self.deactivation_reason = reason
232
- self.save!
226
+ save!
233
227
  return self
234
228
  end
235
229
 
@@ -241,17 +235,17 @@ module Billingly
241
235
 
242
236
  # @see #deactivate
243
237
  def deactivate_left_voluntarily
244
- deactivate(:left_voluntarily)
238
+ deactivate('left_voluntarily')
245
239
  end
246
240
 
247
241
  # @see #deactivate
248
242
  def deactivate_trial_expired
249
- deactivate(:trial_expired)
243
+ deactivate('trial_expired')
250
244
  end
251
245
 
252
246
  # @see #deactivate
253
247
  def deactivate_debtor
254
- deactivate(:debtor)
248
+ deactivate('debtor')
255
249
  end
256
250
 
257
251
  # Customers whose account has been {#deactivate deactivated} can always re-join the service
@@ -266,25 +260,18 @@ module Billingly
266
260
  subscribe_to_plan(new_plan)
267
261
  return self
268
262
  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.
263
+
264
+ # Charges all invoices for which the customer has enough balance.
265
+ # Oldest invoices are charged first, newer invoices should not be charged until
266
+ # the oldest ones are paid.
275
267
  #
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
268
+ # See {Billingly::Invoice#charge Invoice#charge} for more information
269
+ # on how invoices are charged from the customer's balance.
270
+ def charge_pending_invoices
271
+ invoices.where(deleted_on: nil, paid_on: nil).order('period_start')
272
+ .each{|invoice| break unless invoice.charge}
286
273
  end
287
-
274
+
288
275
  # Can this customer subscribe to a plan?.
289
276
  # You may want to prevent customers from upgrading or downgrading to other plans
290
277
  # depending on their usage of your service.
@@ -25,7 +25,38 @@ module Billingly
25
25
  # @property subscribed_on
26
26
  # @return [DateTime]
27
27
  validates :subscribed_on, presence: true
28
+
29
+ # Subscriptions are terminated for a reason which could be:
30
+ # * trial_expired: Subscription was a trial and it just expired.
31
+ # * debtor: The customer owed an invoice for this subscription and did not pay.
32
+ # * changed_subscription: This subscription was immediately replaced by another one.
33
+ # * left_voluntarily: This subscription was terminated because the customer left.
34
+ #
35
+ # TERMINATION_REASONS are important for auditing and for the mailing tasks to notify
36
+ # about subscriptions terminated automatically by the system.
37
+ TERMINATION_REASONS = %w(trial_expired debtor changed_subscription left_voluntarily)
38
+
39
+ # The date in which this subscription ended.
40
+ #
41
+ # Every ended subscription ended for a reason, look at {TERMINATION_REASONS}.
42
+ # @property unsubscribed_on
43
+ # @return [DateTime]
44
+ validates :unsubscribed_on, presence: true, if: :unsubscribed_because
28
45
 
46
+ # The reason why this subscription ended.
47
+ #
48
+ # Every ended subscription ended for a reason, look at {TERMINATION_REASONS}.
49
+ # @property unsubscribed_because
50
+ # @return [DateTime]
51
+ validates :unsubscribed_because, inclusion: TERMINATION_REASONS, if: :terminated?
52
+
53
+ # Was this subscription terminated?
54
+ # @property [r] terminated?
55
+ # @return [Boolean] Whether the subscription was terminated or not.
56
+ def terminated?
57
+ not unsubscribed_on.nil?
58
+ end
59
+
29
60
  # The grace period we use when calculating an invoices due date.
30
61
  # If a subscription is payable_upfront, then the customer effectively owes us
31
62
  # since the day in which a given period starts.
@@ -58,7 +89,7 @@ module Billingly
58
89
  belongs_to :plan
59
90
 
60
91
  # (see #is_trial_expiring_on)
61
- # @property trial?
92
+ # @property [r] trial?
62
93
  # @return [Boolean]
63
94
  def trial?
64
95
  not is_trial_expiring_on.nil?
@@ -95,24 +126,38 @@ module Billingly
95
126
 
96
127
  # Terminates this subscription, it could be either because we deactivate a debtor
97
128
  # or because the customer decided to end his subscription on his own terms.
98
- def terminate
129
+ #
130
+ # Use the shortcuts:
131
+ # {#terminate_left_voluntarily}, {#terminate_trial_expired},
132
+ # {#terminate_debtor}, {#terminate_changed_subscription}
133
+ #
134
+ # Once terminated, a subscription cannot be re-open, just create a new one.
135
+ # @param reason [Symbol] the reason to terminate this subscription, see {TERMINATION_REASONS}
136
+ # @return [self, nil] nil if the account was already terminated, self otherwise.
137
+ def terminate(reason)
99
138
  return if terminated?
100
- update_attribute(:unsubscribed_on, Time.now)
139
+ self.unsubscribed_on = Time.now
140
+ self.unsubscribed_because = reason
101
141
  invoices.last.truncate unless trial?
142
+ save!
102
143
  return self
103
144
  end
104
-
105
- def terminated?
106
- not unsubscribed_on.nil?
145
+
146
+ TERMINATION_REASONS.each do |reason|
147
+ define_method("terminate_#{reason}") do
148
+ terminate(reason)
149
+ end
107
150
  end
108
151
 
109
- # This class method is called from a cron job, it creates invoices for all the subscriptions
110
- # that still need their invoice created.
111
- # TODO: This goes through all the active subscriptions, make it smarter so that the batch job runs quicker.
112
- def self.generate_next_invoices
113
- where(is_trial_expiring_on: nil, unsubscribed_on: nil).each do |subscription|
114
- subscription.generate_next_invoice
115
- end
152
+ # When a trial subscription ends the customer is notified about it via email.
153
+ # @return [self, nil] not nil means the notification was sent successfully.
154
+ def notify_trial_expired
155
+ return unless trial?
156
+ return unless terminated? && unsubscribed_because == 'trial_expired'
157
+ return unless notified_trial_expired_on.nil?
158
+ Billingly::Mailer.trial_expired_notification(self).deliver!
159
+ update_attribute(:notified_trial_expired_on, Time.now)
160
+ return self
116
161
  end
117
162
  end
118
163
  end
@@ -55,64 +55,29 @@ module Billingly
55
55
  return self
56
56
  end
57
57
 
58
- # Charges all invoices that can be charged from the existing customer cash balances
59
- def self.charge_all(collection = self)
60
- collection.where(deleted_on: nil, paid_on: nil).order('period_start').each do |invoice|
61
- invoice.charge
62
- end
63
- end
64
-
65
- # This method is called by Billingly's recurring task to notify all pending invoices.
66
- def self.notify_all_pending
67
- where(deleted_on: nil, paid_on: nil, notified_pending_on: nil)
68
- .each do |invoice|
69
- invoice.notify_pending
70
- end
71
- end
72
-
73
58
  def notify_pending
74
59
  return unless notified_pending_on.nil?
75
60
  return if paid?
76
61
  return if deleted?
77
62
  return if due_on > subscription.grace_period.from_now
78
- BillinglyMailer.pending_notification(self).deliver!
63
+ Billingly::Mailer.pending_notification(self).deliver!
79
64
  update_attribute(:notified_pending_on, Time.now)
80
65
  end
81
66
 
82
- # Send the email notifying that this invoice being overdue and the subscription
83
- # being cancelled
84
- def self.notify_all_overdue
85
- where('due_on <= ?', Time.now)
86
- .where(deleted_on: nil, paid_on: nil, notified_overdue_on: nil)
87
- .each do |invoice|
88
- invoice.notify_overdue
89
- end
90
- end
91
-
92
67
  def notify_overdue
93
68
  return unless notified_overdue_on.nil?
94
69
  return if paid?
95
70
  return if deleted?
96
71
  return if due_on > Time.now
97
- BillinglyMailer.overdue_notification(self).deliver!
72
+ Billingly::Mailer.overdue_notification(self).deliver!
98
73
  update_attribute(:notified_overdue_on, Time.now)
99
74
  end
100
75
 
101
- # Notifies that the invoice has been charged successfully.
102
- # Send the email notifying that this invoice being overdue and the subscription
103
- # being cancelled
104
- def self.notify_all_paid
105
- where('paid_on is not null')
106
- .where(deleted_on: nil, notified_paid_on: nil).each do |invoice|
107
- invoice.notify_paid
108
- end
109
- end
110
-
111
76
  def notify_paid
112
77
  return unless paid?
113
78
  return unless notified_paid_on.nil?
114
79
  return if deleted?
115
- BillinglyMailer.paid_notification(self).deliver!
80
+ Billingly::Mailer.paid_notification(self).deliver!
116
81
  update_attribute(:notified_paid_on, Time.now)
117
82
  end
118
83
  end
@@ -0,0 +1,200 @@
1
+ # The Tasks model has all the tasks that should be run periodically through rake.
2
+ # A special log is created for the tasks being run and the results are reported
3
+ # back to the website administrator.
4
+ class Billingly::Tasks
5
+ # The date in which the tasks started running.
6
+ # @!attribute started
7
+ # @return [DateTime]
8
+ attr_accessor :started
9
+
10
+ # The date in which the tasks ended
11
+ # @!attribute ended
12
+ # @return [DateTime]
13
+ attr_accessor :ended
14
+
15
+ # A summary of all the tasks that were run and their overall results.
16
+ # @!attribute summary
17
+ # @return [String]
18
+ attr_accessor :summary
19
+
20
+ # A detailed description of errors that ocurred while running all the tasks.
21
+ #
22
+ # @!attribute extended
23
+ # @return [File]
24
+ attr_accessor :extended
25
+
26
+ # Runs all of Billingly's periodic tasks and creates a report with the results at the end.
27
+ def run_all
28
+ self.started = Time.now
29
+
30
+ generate_next_invoices
31
+ charge_invoices
32
+ deactivate_all_debtors
33
+ deactivate_all_expired_trials
34
+ notify_all_paid
35
+ notify_all_pending
36
+ notify_all_overdue
37
+
38
+ self.ended = Time.now
39
+ self.extended.close unless self.extended.nil?
40
+ Billingly::Mailer.task_results(self).deliver
41
+ end
42
+
43
+ # Writes a line to the {#extended} section of this tasks results report.
44
+ # @param text [String]
45
+ def log_extended(text)
46
+ if self.extended.nil?
47
+ time = Time.now.utc.strftime("%Y%m%d%H%M%S")
48
+ self.extended = File.open("#{Rails.root}/log/billingly_#{time}.log", 'w')
49
+ end
50
+ self.extended.write("#{text}\n\n")
51
+ end
52
+
53
+ # Writes a line to the {#summary} section of this task results report.
54
+ # @param text [String]
55
+ def log_summary(text)
56
+ self.summary ||= ''
57
+ self.summary += "#{text}\n"
58
+ end
59
+
60
+ # The batch runner is a helper function for running a method on each item of a
61
+ # collection logging the results, without aborting excecution if calling the rest of the
62
+ # items if any of them fails.
63
+ #
64
+ # The method called on each item will not receive parameters and should return
65
+ # a Truthy value if successfull, or raise an exception otherwise.
66
+ # Returning nil means that there was nothing to be done on that item.
67
+ #
68
+ # The collection to be used should be returned by a block provided to this method.
69
+ # Any problem fetching the collection will also be universally captured
70
+ #
71
+ # See {#generate_next_invoices} for an example use.
72
+ #
73
+ # @param task_name [String] the name to use for this task in the generated log.
74
+ # @param method [Symbol] the method to call on each one of the given items.
75
+ # @param collection_getter [Proc] a block which should return the collection to use.
76
+ def batch_runner(task_name, method, &collection_getter)
77
+ collection = begin
78
+ collection_getter.call
79
+ rescue Exception => e
80
+ failure += 1
81
+ log_extended("#{task_name}:\nCollection getter failed\n#{e.message}\n\n#{e.backtrace}")
82
+ return
83
+ end
84
+
85
+ success = 0
86
+ failure = 0
87
+
88
+ collection.each do |item|
89
+ begin
90
+ success += 1 if item.send(method)
91
+ rescue Exception => e
92
+ failure += 1
93
+ log_extended("#{task_name}:\n#{e.message}\n#{item.inspect}\n\n#{e.backtrace}")
94
+ end
95
+ end
96
+
97
+ if failure == 0
98
+ log_summary("Success: #{task_name}, #{success} OK.")
99
+ else
100
+ log_summary("Failure: #{task_name}, #{success} OK, #{failure} failed.")
101
+ end
102
+ end
103
+
104
+ # Invoices for running subscriptions which are not trials are generated by this task.
105
+ # See {Billingly::Subscription#generate_next_invoice} for more information about
106
+ # how the next invoice for a subscription is created.
107
+ def generate_next_invoices
108
+ batch_runner('Generating Invoices', :generate_next_invoice) do
109
+ Billingly::Subscription
110
+ .where(is_trial_expiring_on: nil, unsubscribed_on: nil)
111
+ .readonly(false)
112
+ end
113
+ end
114
+
115
+ # Charges all invoices for which the customer has enough balance.
116
+ # Oldest invoices are charged first, newer invoices should not be charged until
117
+ # the oldest ones are paid.
118
+ # See {Billingly::Invoice#charge Invoice#Charge} for more information on
119
+ # how invoices are charged from the customer's balance.
120
+ # @param collection [Array<Invoice>] The list of invoices to attempt charging.
121
+ # Defaults to all invoices in the system.
122
+ def charge_invoices
123
+ batch_runner('Charging pending invoices', :charge_pending_invoices) do
124
+ Billingly::Customer
125
+ .joins(:invoices)
126
+ .where(billingly_invoices: {deleted_on: nil, paid_on: nil})
127
+ .readonly(false)
128
+ end
129
+ end
130
+
131
+ # Notifies invoices that have been charged successfully, sending a receipt.
132
+ # See {Billingly::Invoice#notify_paid Invoice#notify_paid} for more information on
133
+ # how receipts are sent for paid invoices.
134
+ def notify_all_paid
135
+ batch_runner('Notifying Paid Invoices', :notify_paid) do
136
+ Billingly::Invoice
137
+ .where('paid_on is not null')
138
+ .where(deleted_on: nil, notified_paid_on: nil)
139
+ .readonly(false)
140
+ end
141
+ end
142
+
143
+ # Customers are notified about their pending invoices by this task.
144
+ # See {Billingly::Invoice#notify_pending Invoice#notify_pending} for more info
145
+ # on how pending invoices are notified.
146
+ def notify_all_pending
147
+ batch_runner('Notifying Pending Invoices', :notify_pending) do
148
+ Billingly::Invoice
149
+ .where(deleted_on: nil, paid_on: nil, notified_pending_on: nil)
150
+ .readonly(false)
151
+ end
152
+ end
153
+
154
+ # This task notifies customers when one of their invoices is overdue.
155
+ # Overdue invoices go together with account deactivations so the email sent
156
+ # by this task also includes the deactivation notice.
157
+ #
158
+ # This task does not perform the actual deactivation, {#deactivate_all_debtors} does.
159
+ #
160
+ # See {Billingly::Invoice#notify_overdue Invoice#notify_overdue} for more info
161
+ # on how overdue invoices are notified.
162
+ def notify_all_overdue
163
+ batch_runner('Notifying Pending Invoices', :notify_overdue) do
164
+ Billingly::Invoice
165
+ .where('due_on <= ?', Time.now)
166
+ .where(deleted_on: nil, paid_on: nil, notified_overdue_on: nil)
167
+ .readonly(false)
168
+ end
169
+ end
170
+
171
+ # This method will deactivate all customers who have overdue {Billingly::Invoice Invoices}.
172
+ #
173
+ # This only deactivates the debtor, it does not notify them via email.
174
+ # Look at {#notify_all_overdue} to see the email notification customers receive.
175
+ #
176
+ # See {Billingly::Customer#deactivate_debtor Customer#deactivate_debtor} for more info
177
+ # on how debtors are deactivated.
178
+ def deactivate_all_debtors
179
+ batch_runner('Deactivating Debtors', :deactivate_debtor) do
180
+ Billingly::Customer.debtors.where(deactivated_since: nil).readonly(false)
181
+ end
182
+ end
183
+
184
+ # Customers may be subscribed for a trial period, and they are supposed to re-subscribe
185
+ # before their trial expires.
186
+ #
187
+ # When their trial expires and they have not yet subscribed to another plan, we
188
+ # deactivate their account immediately. This method does not email them about the
189
+ # expired trial.
190
+ #
191
+ # See {Billingly::Customer#deactivate_trial_expired Customer#deactivate_trial_expired}
192
+ # for more info on how trials are deactivated.
193
+ def deactivate_all_expired_trials
194
+ batch_runner('Deactivating Expired Trials', :deactivate_trial_expired) do
195
+ Billingly::Customer.joins(:subscriptions).readonly(false)
196
+ .where("#{Billingly::Subscription.table_name}.is_trial_expiring_on < ?", Time.now)
197
+ .where(billingly_subscriptions: {unsubscribed_on: nil})
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,9 @@
1
+ This is a report on Billingly's activity.
2
+ Started on "<%= @runner.started %>" and ended on "<%= @runner.ended %>"
3
+
4
+ Summary:
5
+ <%= @runner.summary %>:
6
+
7
+ Extended description:
8
+ <%= @runner.extended %>:
9
+
@@ -0,0 +1,4 @@
1
+ <%= t( 'billingly.your_trial_has_expired_please_choose_a_plan') % { end_date: @subscription.unsubscribed_on.to_date} %>
2
+
3
+ <%=t 'billingly.please_visit' %> <%= url_for subscriptions_path %>
4
+
@@ -21,3 +21,8 @@ en:
21
21
  paid_on: Paid on
22
22
  plan: Plan
23
23
  amount: Amount
24
+ your_trial_has_expired: Your trial period has expired
25
+ your_trial_has_expired_please_choose_a_plan:
26
+ "Your trial period has ended on %{end_date} please choose a plan and sign up again."
27
+ please_visit: Please visit
28
+
@@ -1,3 +1,3 @@
1
1
  module Billingly
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.4"
3
3
  end
@@ -46,7 +46,10 @@ class CreateBillinglyTables < ActiveRecord::Migration
46
46
  t.boolean 'payable_upfront', null: false, default: false
47
47
  t.decimal 'amount', precision: 11, scale: 2, default: 0.0, null: false
48
48
  t.datetime 'unsubscribed_on'
49
+ t.string 'unsubscribed_because'
49
50
  t.datetime 'is_trial_expiring_on'
51
+ t.datetime 'notified_trial_will_expire_on'
52
+ t.datetime 'notified_trial_expired_on'
50
53
  t.references :plan
51
54
  t.timestamps
52
55
  end
@@ -5,19 +5,6 @@ You can run it as often as you want.
5
5
  """
6
6
  namespace :billingly do
7
7
  task all: :environment do
8
- puts 'Generating invoices'
9
- Billingly::Subscription.generate_next_invoices
10
- puts 'Charging invoices if possible'
11
- Billingly::Invoice.charge_all
12
- puts 'Deactivating debtors'
13
- Billingly::Customer.deactivate_all_debtors
14
- puts 'Deactivating all expired trials'
15
- Billingly::Customer.deactivate_all_expired_trials
16
- puts 'Sending payment receipts'
17
- Billingly::Invoice.notify_all_paid
18
- puts 'Notifying pending invoices'
19
- Billingly::Invoice.notify_all_pending
20
- puts 'Notifying deactivated debtors'
21
- Billingly::Invoice.notify_all_overdue
8
+ Billingly::Tasks.new.run_all
22
9
  end
23
10
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: billingly
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-04 00:00:00.000000000 Z
12
+ date: 2012-10-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
16
- requirement: &70322875786160 !ruby/object:Gem::Requirement
16
+ requirement: &70310037569740 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 3.2.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70322875786160
24
+ version_requirements: *70310037569740
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: validates_email_format_of
27
- requirement: &70322875785740 !ruby/object:Gem::Requirement
27
+ requirement: &70310037569280 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70322875785740
35
+ version_requirements: *70310037569280
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: has_duration
38
- requirement: &70322875785280 !ruby/object:Gem::Requirement
38
+ requirement: &70310037568740 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70322875785280
46
+ version_requirements: *70310037568740
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: timecop
49
- requirement: &70322875784860 !ruby/object:Gem::Requirement
49
+ requirement: &70310037568220 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70322875784860
57
+ version_requirements: *70310037568220
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: sqlite3
60
- requirement: &70322875784440 !ruby/object:Gem::Requirement
60
+ requirement: &70310037608420 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *70322875784440
68
+ version_requirements: *70310037608420
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rspec-rails
71
- requirement: &70322875784020 !ruby/object:Gem::Requirement
71
+ requirement: &70310037607540 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '0'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *70322875784020
79
+ version_requirements: *70310037607540
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: factory_girl_rails
82
- requirement: &70322875783600 !ruby/object:Gem::Requirement
82
+ requirement: &70310037607120 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,7 +87,7 @@ dependencies:
87
87
  version: '0'
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *70322875783600
90
+ version_requirements: *70310037607120
91
91
  description: Rails Engine for SaaS subscription management. Manage subscriptions,
92
92
  plan changes, free trials and more!!!
93
93
  email:
@@ -97,7 +97,8 @@ extensions: []
97
97
  extra_rdoc_files: []
98
98
  files:
99
99
  - app/controllers/billingly/subscriptions_controller.rb
100
- - app/mailers/billingly_mailer.rb
100
+ - app/mailers/billingly/base_mailer.rb
101
+ - app/mailers/billingly/mailer.rb
101
102
  - app/models/billingly/base_customer.rb
102
103
  - app/models/billingly/base_plan.rb
103
104
  - app/models/billingly/base_subscription.rb
@@ -111,6 +112,14 @@ files:
111
112
  - app/models/billingly/plan.rb
112
113
  - app/models/billingly/receipt.rb
113
114
  - app/models/billingly/subscription.rb
115
+ - app/models/billingly/tasks.rb
116
+ - app/views/billingly/mailer/overdue_notification.html.erb
117
+ - app/views/billingly/mailer/paid_notification.html.erb
118
+ - app/views/billingly/mailer/pending_notification.html.erb
119
+ - app/views/billingly/mailer/pending_notification.plain.erb
120
+ - app/views/billingly/mailer/pending_notification.text.erb
121
+ - app/views/billingly/mailer/task_results.text.erb
122
+ - app/views/billingly/mailer/trial_expired_notification.text.erb
114
123
  - app/views/billingly/subscriptions/_current_subscription.html.haml
115
124
  - app/views/billingly/subscriptions/_deactivation_notice.html.haml
116
125
  - app/views/billingly/subscriptions/_invoice_details.html.haml
@@ -120,11 +129,6 @@ files:
120
129
  - app/views/billingly/subscriptions/index.html.haml
121
130
  - app/views/billingly/subscriptions/invoice.html.haml
122
131
  - app/views/billingly/subscriptions/new.html.erb
123
- - app/views/billingly_mailer/overdue_notification.html.erb
124
- - app/views/billingly_mailer/paid_notification.html.erb
125
- - app/views/billingly_mailer/pending_notification.html.erb
126
- - app/views/billingly_mailer/pending_notification.plain.erb
127
- - app/views/billingly_mailer/pending_notification.text.erb
128
132
  - config/locales/en.yml
129
133
  - lib/billingly/engine.rb
130
134
  - lib/billingly/rails/routes.rb
@@ -141,7 +145,21 @@ files:
141
145
  - TUTORIAL.rdoc
142
146
  homepage: http://billing.ly
143
147
  licenses: []
144
- post_install_message:
148
+ post_install_message: ! " Add the following migration:\n \n class CreateBillinglyTables
149
+ < ActiveRecord::Migration\n def up\n add_column :billingly_subscriptions,
150
+ :notified_trial_will_expire_on, :datetime\n add_column :billingly_subscriptions,
151
+ :notified_trial_expired_on, :datetime\n add_column :billingly_subscriptions,
152
+ :unsubscribed_because, :string\n \n Billingly::Subscription.where('unsubscribed_on
153
+ IS NOT NULL').find_each do |s|\n # Notice: You should pre-populate unsubscribed
154
+ because\n # with an appropriate value for each terminated subscription.\n
155
+ \ # \n reason = if s == s.customer.subscriptions.last && s.customer.deactivation_reason\n
156
+ \ s.deactivation_reason\n elsif s.trial? && s.is_trial_expiring_on
157
+ <= s.unsubscribed_on\n 'trial_expired'\n else\n 'changed_subscription'\n
158
+ \ end\n s.update_attribute(unsubscribed_because: reason)\n end\n
159
+ \ end\n \n def down\n remove_column :billingly_subscriptions,
160
+ :notified_trial_will_expire_on\n remove_column :billingly_subscriptions,
161
+ :notified_trial_expired_on\n remove_column :billingly_subscriptions, :unsubscribed_because\n
162
+ \ end\n end\n"
145
163
  rdoc_options: []
146
164
  require_paths:
147
165
  - lib