Class: Billingly::BaseCustomer
- Inherits:
-
ActiveRecord::Base
- Object
- ActiveRecord::Base
- Billingly::BaseCustomer
- Defined in:
- app/models/billingly/base_customer.rb
Overview
A Customer is Billingly’s main actor.
-
Customers have a Subscription to your service which entitles them to use it.
-
Customers are invoiced regularly to pay for their Subscription
-
Payments are received on a Customers behalf and credited to their account.
-
Invoices are generated periodically calculating charges a Customer incurred in.
-
Receipts are sent to Customers when their invoices are paid.
Direct Known Subclasses
Constant Summary collapse
- DEACTIVATION_REASONS =
The reason why this customer is deactivated. A customer can be deactivated for one of 3 reasons:
* trial_expired: Their trial period expired. * debtor: They have unpaid invoices. * left_voluntarily: They decided to leave the site.This is important when reactivating their account. If they left in their own terms, we won’t try to reactivate their account when we receive a payment from them. The message shown to them when they reactivate will also be different depending on how they left.
%w(trial_expired debtor left_voluntarily)
Instance Attribute Summary collapse
-
#active_subscription ⇒ Subscription?
readonly
The Subscription for which the customer is currently being charged.
-
#deactivated? ⇒ Boolean
readonly
A customer can be deactivated when they cancel their subscription or when they miss a payment.
-
#deactivated_since ⇒ DateTime
readonly
The Date and Time in which the Customer’s account was deactivated (see #deactivated?).
- #deactivation_reason ⇒ Symbol
-
#debtor? ⇒ Boolean
readonly
Whether this customer is a debtor or not.
-
#doing_trial? ⇒ Boolean
readonly
Whether the user is on an unfinished trial period.
-
#email ⇒ String
Used as contact address, validates format but does not check uniqueness.
-
#invoices ⇒ Array<Invoice>
All invoices ever created for this customer, for any Subscription.
-
#journal_entries ⇒ Array<JournalEntry>
Every JournalEntry ever created for this customer, a #ledger is created from these.
-
#payments ⇒ Array<Payment>
All paymetns that were ever credited for this customer.
-
#subscriptions ⇒ Array<Subscription>
All subscriptions this customer was ever subscribed to.
-
#trial_days_left ⇒ Integer
readonly
When the user is doing a trial, this would be how many days are left until it’s over.
Class Method Summary collapse
-
.debtors ⇒ Object
A customer who has overdue invoices at the time of asking this question is considered a debtor.
Instance Method Summary collapse
-
#add_to_journal(amount, *accounts, extra) ⇒ Object
Shortcut for adding #journal_entries for this customer.
-
#can_subscribe_to?(plan) ⇒ Boolean
Can this customer subscribe to a plan?.
-
#charge_pending_invoices ⇒ Object
Charges all invoices for which the customer has enough balance.
-
#credit_payment(amount) ⇒ Object
Credits an amount of money to customer’s account and then triggers the corresponding actions if a payment was expected from this customer.
-
#deactivate(reason) ⇒ self?
Terminate a customer’s subscription to the service.
- #deactivate_debtor ⇒ Object
- #deactivate_left_voluntarily ⇒ Object
- #deactivate_trial_expired ⇒ Object
-
#do_not_email? ⇒ Boolean
Some customers do not want to be bothered via email, which is understandable.
-
#ledger ⇒ {Symbol => BigDecimal}
Creates a general ledger from journal entries.
-
#on_subscription_success ⇒ Object
Callback called whenever this customer is successfully subscribed to a plan.
-
#reactivate(new_plan = nil) ⇒ self?
Customers whose account has been deactivated can always re-join the service as long as they don’t owe any money.
-
#redeem_special_plan_code(code) ⇒ Object
Customers can subscribe to a plan using a special subscription code which would allow them to access an otherwise hidden plan.
-
#subscribe_to_plan(plan, is_trial_expiring_on = nil) ⇒ Subscription
Customers subscribe to the service under certain conditions referred to as a Plan, and perform periodic payments to continue using it.
Instance Attribute Details
#active_subscription ⇒ Subscription? (readonly)
The Subscription for which the customer is currently being charged.
64 65 66 67 |
# File 'app/models/billingly/base_customer.rb', line 64 def active_subscription last = subscriptions.last last unless last.nil? || last.terminated? end |
#deactivated? ⇒ Boolean (readonly)
A customer can be deactivated when they cancel their subscription or when they miss a payment. Under the hood this function checks the #deactivated_since attribute.
41 42 43 |
# File 'app/models/billingly/base_customer.rb', line 41 def deactivated? not deactivated_since.nil? end |
#deactivated_since ⇒ DateTime (readonly)
The Date and Time in which the Customer’s account was deactivated (see #deactivated?). This field denormalizes the date in which this customer’s last subscription was ended.
30 |
# File 'app/models/billingly/base_customer.rb', line 30 validates :deactivated_since, presence: true, if: :deactivation_reason |
#deactivation_reason ⇒ Symbol
35 |
# File 'app/models/billingly/base_customer.rb', line 35 validates :deactivation_reason, inclusion: DEACTIVATION_REASONS, if: :deactivated? |
#debtor? ⇒ Boolean (readonly)
Returns whether this customer is a debtor or not.
72 73 74 |
# File 'app/models/billingly/base_customer.rb', line 72 def debtor? not self.class.debtors.find_by_id(self.id).nil? end |
#doing_trial? ⇒ Boolean (readonly)
Whether the user is on an unfinished trial period.
95 96 97 |
# File 'app/models/billingly/base_customer.rb', line 95 def doing_trial? active_subscription && active_subscription.trial? end |
#email ⇒ String
Used as contact address, validates format but does not check uniqueness.
48 |
# File 'app/models/billingly/base_customer.rb', line 48 attr_accessible :email |
#invoices ⇒ Array<Invoice>
All invoices ever created for this customer, for any Subscription
79 |
# File 'app/models/billingly/base_customer.rb', line 79 has_many :invoices, foreign_key: 'customer_id' |
#journal_entries ⇒ Array<JournalEntry>
Every JournalEntry ever created for this customer, a #ledger is created from these. See JournalEntry for a description on what they are.
85 |
# File 'app/models/billingly/base_customer.rb', line 85 has_many :journal_entries, foreign_key: 'customer_id' |
#payments ⇒ Array<Payment>
All paymetns that were ever credited for this customer
59 |
# File 'app/models/billingly/base_customer.rb', line 59 has_many :payments, foreign_key: 'customer_id' |
#subscriptions ⇒ Array<Subscription>
All subscriptions this customer was ever subscribed to.
54 |
# File 'app/models/billingly/base_customer.rb', line 54 has_many :subscriptions, foreign_key: 'customer_id' |
#trial_days_left ⇒ Integer (readonly)
When the user is doing a trial, this would be how many days are left until it’s over.
102 103 104 105 |
# File 'app/models/billingly/base_customer.rb', line 102 def trial_days_left return unless doing_trial? (active_subscription.is_trial_expiring_on.to_date - Time.now.utc.to_date).to_i end |
Class Method Details
.debtors ⇒ Object
A customer may be a debtor and still have an active account until Billingly’s rake task goes through the process of deactivating all debtors.
Furthermore, customers may unsubscribe before their invoices become overdue, hence they may be in a deactivated state and not be debtors yet.
A customer who has overdue invoices at the time of asking this question is considered a debtor.
200 201 202 203 204 |
# File 'app/models/billingly/base_customer.rb', line 200 def self.debtors joins(:invoices).readonly(false) .where("#{Billingly::Invoice.table_name}.due_on < ?", Time.now) .where(billingly_invoices: {deleted_on: nil, paid_on: nil}) end |
Instance Method Details
#add_to_journal(amount, *accounts, extra) ⇒ Object
Shortcut for adding #journal_entries for this customer.
179 180 181 182 183 184 185 186 187 188 189 |
# File 'app/models/billingly/base_customer.rb', line 179 def add_to_journal(amount, *accounts, extra) accounts = [] if accounts.nil? unless extra.is_a?(Hash) accounts << extra extra = {} end accounts.each do |account| journal_entries.create!(extra.merge(amount: amount, account: account.to_s)) end end |
#can_subscribe_to?(plan) ⇒ Boolean
Can this customer subscribe to a plan?. You may want to prevent customers from upgrading or downgrading to other plans depending on their usage of your service.
This method is only used in views and controllers to prevent customers from requesting to be upgraded or downgraded to a plan without your consent. The model layer can still subscribe the customer if you so desire.
The default implementation lets Customers upgrade to any if they are currently doing a trial period, and it does not let them re-subscribe to the same plan afterwards. It also always disallows debtors to subscribe to another plan.
302 303 304 305 306 |
# File 'app/models/billingly/base_customer.rb', line 302 def can_subscribe_to?(plan) return false if !doing_trial? && active_subscription && active_subscription.plan == plan return false if debtor? return true end |
#charge_pending_invoices ⇒ Object
Charges all invoices for which the customer has enough balance. Oldest invoices are charged first, newer invoices should not be charged until the oldest ones are paid.
See Invoice#charge for more information on how invoices are charged from the customer’s balance.
285 286 287 288 |
# File 'app/models/billingly/base_customer.rb', line 285 def charge_pending_invoices invoices.where(deleted_on: nil, paid_on: nil).order('period_start') .each{|invoice| break unless invoice.charge} end |
#credit_payment(amount) ⇒ Object
This is the single point of entry for Payments.
If you’re processing payments using http://activemerchant.org you should hook your ‘Incoming Payment Notifications’ to call this method to credit the received amount to the customer’s account.
Credits an amount of money to customer’s account and then triggers the corresponding actions if a payment was expected from this customer.
Apart from creating a Payment object this method will try to charge pending invoices and reactivate a customer who was deactivated for being a debtor.
220 221 222 223 224 |
# File 'app/models/billingly/base_customer.rb', line 220 def credit_payment(amount) Billingly::Payment.credit_for(self, amount) charge_pending_invoices reactivate if deactivated? && deactivation_reason == 'debtor' end |
#deactivate(reason) ⇒ self?
Terminate a customer’s subscription to the service. Customers are deactivated due to lack of payment, because they decide to end their subscription to your service or because their trial period expired.
Use the shortcuts:
{#deactivate_left_voluntarily}, {#deactivate_trial_expired} or {#deactivate_debtor}
Deactivated customers can always be reactivated later.
236 237 238 239 240 241 242 243 |
# File 'app/models/billingly/base_customer.rb', line 236 def deactivate(reason) return if deactivated? active_subscription.terminate(reason) self.deactivated_since = Time.now self.deactivation_reason = reason save! return self end |
#deactivate_debtor ⇒ Object
262 263 264 |
# File 'app/models/billingly/base_customer.rb', line 262 def deactivate_debtor deactivate('debtor') end |
#deactivate_left_voluntarily ⇒ Object
252 253 254 |
# File 'app/models/billingly/base_customer.rb', line 252 def deactivate_left_voluntarily deactivate('left_voluntarily') end |
#deactivate_trial_expired ⇒ Object
257 258 259 |
# File 'app/models/billingly/base_customer.rb', line 257 def deactivate_trial_expired deactivate('trial_expired') end |
#do_not_email? ⇒ Boolean
Some customers do not want to be bothered via email, which is understandable. You can override this method to decide which customers should never be emailed with invoices, receipts or when their trial is over.
It is false by default, this means all of your customers will be emailed.
314 315 316 |
# File 'app/models/billingly/base_customer.rb', line 314 def do_not_email? false end |
#ledger ⇒ {Symbol => BigDecimal}
Due to silly rounding errors on sqlite this implementation needs to convert decimals to float and then to decimals again. :S
Creates a general ledger from journal entries. Every Invoice and Payment involves movements to the customer’s account. which are registered as a JournalEntry. The ledger can tell us whats the cash balance in our customer’s favor and how much money have they paid overall.
165 166 167 168 169 170 171 172 173 174 |
# File 'app/models/billingly/base_customer.rb', line 165 def ledger Hash.new(0.0).tap do |all| journal_entries.group_by(&:account).collect do |account, entries| values = entries.collect(&:amount).collect(&:to_f) all[account.to_sym] = values.inject(0.0) do |sum,item| (BigDecimal.new(sum.to_s) + BigDecimal.new(item.to_s)).to_f end end end end |
#on_subscription_success ⇒ Object
Callback called whenever this customer is successfully subscribed to a plan. This callback does not differentiate if the customer is subscribing for the first time, reactivating his account or just changing from one plan to another. self.active_subscription will be the current subscription when this method is called.
153 154 |
# File 'app/models/billingly/base_customer.rb', line 153 def on_subscription_success end |
#reactivate(new_plan = nil) ⇒ self?
Customers whose account has been deactivated can always re-join the service as long as they don’t owe any money
269 270 271 272 273 274 275 276 277 |
# File 'app/models/billingly/base_customer.rb', line 269 def reactivate(new_plan = nil) new_plan = new_plan || subscriptions.last return if new_plan.nil? return unless deactivated? return if debtor? update_attribute(:deactivated_since, nil) subscribe_to_plan(new_plan) return self end |
#redeem_special_plan_code(code) ⇒ Object
Customers can subscribe to a plan using a special subscription code which would allow them to access an otherwise hidden plan. The SpecialPlanCode can also contain an amount to be redeemed.
142 143 144 145 146 147 |
# File 'app/models/billingly/base_customer.rb', line 142 def redeem_special_plan_code(code) return if code.redeemed? credit_payment(code.bonus_amount) if code.bonus_amount subscribe_to_plan(code.plan) code.update_attributes(customer: self, redeemed_on: Time.now) end |
#subscribe_to_plan(plan, is_trial_expiring_on = nil) ⇒ Subscription
Customers subscribe to the service under certain conditions referred to as a Plan, and perform periodic payments to continue using it. We offer common plans stating how much and how often they should pay, also, if the payment is to be done at the beginning or end of the period (upfront or due-month) Every customer can potentially get a special deal, but we offer common deals as Plans from which a proper Subscription is created. A Subscription is also an acceptable argument, in that case the new one will maintain all the characteristics of that one, except the starting date.
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'app/models/billingly/base_customer.rb', line 117 def subscribe_to_plan(plan, is_trial_expiring_on = nil) subscriptions.last.terminate_changed_subscription if subscriptions.last subscription = subscriptions.build.tap do |new| [:payable_upfront, :description, :periodicity, :amount, :grace_period, :signup_price].each do |k| new[k] = plan[k] end new.plan = plan if plan.is_a?(Billingly::Plan) new.is_trial_expiring_on = is_trial_expiring_on new.subscribed_on = Time.now new.save! new.generate_next_invoice on_subscription_success end self.deactivated_since = nil self.deactivation_reason = nil self.save! return subscription end |