Module: Freemium::Subscription
Defined Under Namespace
Modules: ClassMethods
Class Method Summary collapse
Instance Method Summary collapse
- #coupon(date = Date.today) ⇒ Object
- #coupon=(coupon) ⇒ Object
- #coupon_exist ⇒ Object
-
#coupon_key=(coupon_key) ⇒ Object
Coupon Redemption.
- #coupon_redemption(date = Date.today) ⇒ Object
- #credit(amount) ⇒ Object
-
#expire! ⇒ Object
sends an expiration email, then downgrades to a free plan.
-
#expire_after_grace!(transaction = nil) ⇒ Object
sets the expiration for the subscription based on today and the configured grace period.
- #expired? ⇒ Boolean
- #gateway ⇒ Object
- #in_grace? ⇒ Boolean
- #original_plan ⇒ Object
- #paid? ⇒ Boolean
-
#rate(options = {}) ⇒ Object
Rate.
-
#receive_payment(transaction) ⇒ Object
extends the paid_through period according to how much money was received.
-
#receive_payment!(transaction) ⇒ Object
receives payment and saves the record.
-
#remaining_days ⇒ Object
if paid through today, returns zero.
-
#remaining_days_of_grace ⇒ Object
if under grace through today, returns zero.
-
#remaining_value(plan = self.subscription_plan) ⇒ Object
returns the value of the time between now and paid_through.
-
#store_credit_card? ⇒ Boolean
Allow for more complex logic to decide if a card should be stored.
Methods included from Rates
#daily_rate, #monthly_rate, #yearly_rate
Class Method Details
.included(base) ⇒ Object
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 |
# File 'lib/freemium/subscription.rb', line 12 def self.included(base) base.class_eval do belongs_to :subscription_plan, :class_name => "SubscriptionPlan" belongs_to :subscribable, :polymorphic => true belongs_to :credit_card, :dependent => :destroy, :class_name => "CreditCard" has_many :coupon_redemptions, :conditions => "coupon_redemptions.expired_on IS NULL", :class_name => "CouponRedemption", :foreign_key => :subscription_id, :dependent => :destroy has_many :coupons, :through => :coupon_redemptions, :conditions => "coupon_redemptions.expired_on IS NULL" # Auditing has_many :transactions, :class_name => "AccountTransaction", :foreign_key => :subscription_id scope :paid, includes(:subscription_plan).where("subscription_plans.rate_cents > 0") scope :due, lambda { where(['paid_through <= ?', Date.today]) # could use the concept of a next retry date } scope :expired, lambda { where(['expire_on >= paid_through AND expire_on <= ?', Date.today]) } before_validation :set_paid_through before_validation :set_started_on before_save :store_credit_card_offsite before_save :discard_credit_card_unless_paid before_destroy :cancel_in_remote_system after_create :audit_create after_update :audit_update after_destroy :audit_destroy validates_presence_of :subscribable validates_associated :subscribable validates_presence_of :subscription_plan validates_presence_of :paid_through, :if => :paid? validates_presence_of :started_on validates_presence_of :credit_card, :if => :store_credit_card? validates_associated :credit_card#, :if => :store_credit_card? validate :gateway_validates_credit_card validate :coupon_exist end base.extend ClassMethods end |
Instance Method Details
#coupon(date = Date.today) ⇒ Object
240 241 242 |
# File 'lib/freemium/subscription.rb', line 240 def coupon(date = Date.today) coupon_redemption(date).coupon rescue nil end |
#coupon=(coupon) ⇒ Object
233 234 235 236 237 238 |
# File 'lib/freemium/subscription.rb', line 233 def coupon=(coupon) if coupon s = ::CouponRedemption.new(:subscription => self, :coupon => coupon) coupon_redemptions << s end end |
#coupon_exist ⇒ Object
229 230 231 |
# File 'lib/freemium/subscription.rb', line 229 def coupon_exist self.errors.add :coupon, "could not be found for '#{@coupon_key}'" if !@coupon_key.blank? && ::Coupon.find_by_redemption_key(@coupon_key).nil? end |
#coupon_key=(coupon_key) ⇒ Object
Coupon Redemption
224 225 226 227 |
# File 'lib/freemium/subscription.rb', line 224 def coupon_key=(coupon_key) @coupon_key = coupon_key ? coupon_key.downcase : nil self.coupon = ::Coupon.find_by_redemption_key(@coupon_key) unless @coupon_key.blank? end |
#coupon_redemption(date = Date.today) ⇒ Object
244 245 246 247 248 249 |
# File 'lib/freemium/subscription.rb', line 244 def coupon_redemption(date = Date.today) return nil if coupon_redemptions.empty? active_coupons = coupon_redemptions.select{|c| c.active?(date)} return nil if active_coupons.empty? active_coupons.sort_by{|c| c.coupon.discount_percentage }.reverse.first end |
#credit(amount) ⇒ Object
337 338 339 340 341 342 343 344 345 346 347 |
# File 'lib/freemium/subscription.rb', line 337 def credit(amount) self.paid_through = if amount.cents % rate.cents == 0 self.paid_through + (amount.cents / rate.cents).months else self.paid_through + (amount.cents / daily_rate.cents).days end # if they've paid again, then reset expiration self.expire_on = nil self.in_trial = false end |
#expire! ⇒ Object
sends an expiration email, then downgrades to a free plan
294 295 296 297 298 299 300 301 |
# File 'lib/freemium/subscription.rb', line 294 def expire! Freemium.mailer.expiration_notice(self).deliver # downgrade to a free plan self.expire_on = Date.today self.subscription_plan = Freemium.expired_plan if Freemium.expired_plan self.destroy_credit_card self.save! end |
#expire_after_grace!(transaction = nil) ⇒ Object
sets the expiration for the subscription based on today and the configured grace period.
284 285 286 287 288 289 290 291 |
# File 'lib/freemium/subscription.rb', line 284 def expire_after_grace!(transaction = nil) return unless self.expire_on.nil? # You only set this once subsequent failed transactions shouldn't affect expiration self.expire_on = [Date.today, paid_through].max + Freemium.days_grace transaction. = "now set to expire on #{self.expire_on}" if transaction Freemium.mailer.expiration_warning(self).deliver transaction.save! if transaction save! end |
#expired? ⇒ Boolean
303 304 305 |
# File 'lib/freemium/subscription.rb', line 303 def expired? expire_on and expire_on <= Date.today end |
#gateway ⇒ Object
59 60 61 |
# File 'lib/freemium/subscription.rb', line 59 def gateway Freemium.gateway end |
#in_grace? ⇒ Boolean
275 276 277 |
# File 'lib/freemium/subscription.rb', line 275 def in_grace? remaining_days < 0 and not expired? end |
#original_plan ⇒ Object
55 56 57 |
# File 'lib/freemium/subscription.rb', line 55 def original_plan @original_plan ||= ::SubscriptionPlan.find_by_id(subscription_plan_id_was) unless subscription_plan_id_was.nil? end |
#paid? ⇒ Boolean
210 211 212 213 |
# File 'lib/freemium/subscription.rb', line 210 def paid? return false unless rate rate.cents > 0 end |
#rate(options = {}) ⇒ Object
Rate
201 202 203 204 205 206 207 208 |
# File 'lib/freemium/subscription.rb', line 201 def rate( = {}) = {:date => Date.today, :plan => self.subscription_plan}.merge() return nil unless [:plan] value = [:plan].rate value = self.coupon([:date]).discount(value) if self.coupon([:date]) value end |
#receive_payment(transaction) ⇒ Object
extends the paid_through period according to how much money was received. when possible, avoids the days-per-month problem by checking if the money received is a multiple of the plan’s rate.
really, i expect the case where the received payment does not match the subscription plan’s rate to be very much an edge case.
324 325 326 327 328 329 330 331 332 333 334 335 |
# File 'lib/freemium/subscription.rb', line 324 def receive_payment(transaction) self.credit(transaction.amount) self.save! transaction.subscription.reload # reloaded to that the paid_through date is correct transaction. = "now paid through #{self.paid_through}" begin Freemium.mailer.invoice(transaction).deliver rescue => e transaction. = "error sending invoice: #{e}" end end |
#receive_payment!(transaction) ⇒ Object
receives payment and saves the record
312 313 314 315 316 |
# File 'lib/freemium/subscription.rb', line 312 def receive_payment!(transaction) receive_payment(transaction) transaction.save! self.save! end |
#remaining_days ⇒ Object
if paid through today, returns zero
262 263 264 |
# File 'lib/freemium/subscription.rb', line 262 def remaining_days (self.paid_through - Date.today) end |
#remaining_days_of_grace ⇒ Object
if under grace through today, returns zero
271 272 273 |
# File 'lib/freemium/subscription.rb', line 271 def remaining_days_of_grace (self.expire_on - Date.today - 1).to_i end |
#remaining_value(plan = self.subscription_plan) ⇒ Object
returns the value of the time between now and paid_through. will optionally interpret the time according to a certain subscription plan.
257 258 259 |
# File 'lib/freemium/subscription.rb', line 257 def remaining_value(plan = self.subscription_plan) self.daily_rate(:plan => plan) * remaining_days end |
#store_credit_card? ⇒ Boolean
Allow for more complex logic to decide if a card should be stored
216 217 218 |
# File 'lib/freemium/subscription.rb', line 216 def store_credit_card? paid? end |