redline 0.1.4 → 0.2.0
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/.gitignore +2 -1
- data/README.rdoc +43 -20
- data/Rakefile +7 -2
- data/VERSION +1 -1
- data/lib/redline.rb +14 -5
- data/lib/redline/billing/base.rb +11 -0
- data/lib/redline/customer/base.rb +18 -0
- data/lib/redline/{customer.rb → customer/instance.rb} +0 -11
- data/lib/redline/customer/settings.rb +12 -0
- data/lib/redline/subscription/base.rb +16 -0
- data/lib/redline/subscription/instance.rb +42 -0
- data/lib/redline/subscription/settings.rb +24 -0
- data/redline.gemspec +20 -8
- data/spec/billing_spec.rb +121 -0
- data/spec/customer_spec.rb +76 -0
- data/spec/db/models.rb +34 -2
- data/spec/db/schema.rb +35 -14
- data/spec/redline_spec.rb +35 -63
- data/spec/subscription_spec.rb +237 -0
- metadata +17 -5
    
        data/.gitignore
    CHANGED
    
    
    
        data/README.rdoc
    CHANGED
    
    | @@ -2,7 +2,18 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            Braintree via Redline: riding the rails
         | 
| 4 4 |  | 
| 5 | 
            -
            Redline helps you with Braintree on Rails, it automatically makes creation/deletion/update scripts for your model, translating and syncing them with a Braintree customer profile. | 
| 5 | 
            +
            Redline helps you with Braintree on Rails, it automatically makes customer creation/deletion/update scripts for your model, translating and syncing them with a Braintree customer profile.
         | 
| 6 | 
            +
            Redline also includes a lightweight manual subscription billing system, if you desire that functionality.
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            === Please note: this repository may be a prerelease version, please read the README of the version you have installed.
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            == Heads up
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            === This software is delivered "as is" without warranty. As with any piece of code, please study it if you include it in your project, especially for such a critical component of your application.
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            While I believe the tests cover the code well, there may be certain cases that I have not expirienced or predicted. This is a manual billing script, so dreadful things may occur; please help out and add to the tests if you see, or god forbid expirience, any problems.
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            It is my intention to convert to gateway handled subscriptions, once Braintree adds this functionality to their gem.
         | 
| 6 17 |  | 
| 7 18 | 
             
            == Getting started
         | 
| 8 19 |  | 
| @@ -15,34 +26,46 @@ Run your install task: | |
| 15 26 |  | 
| 16 27 | 
             
              $ rake gems:install
         | 
| 17 28 |  | 
| 18 | 
            -
            Configure Braintree normally in an initializer:
         | 
| 19 | 
            -
             | 
| 20 | 
            -
              Braintree::Configuration.environment = CONFIG[:braintree][:environment].to_sym
         | 
| 21 | 
            -
              Braintree::Configuration.merchant_id = CONFIG[:braintree][:merchant_id]
         | 
| 22 | 
            -
              Braintree::Configuration.public_key  = CONFIG[:braintree][:public_key]
         | 
| 23 | 
            -
              Braintree::Configuration.private_key = CONFIG[:braintree][:private_key]
         | 
| 29 | 
            +
            Configure Braintree normally in an initializer and add the following magic columns to a model of your choice (e.g, User):
         | 
| 24 30 |  | 
| 25 | 
            -
             | 
| 31 | 
            +
              add_column :users, :customer_id,      :integer  # Required
         | 
| 32 | 
            +
              
         | 
| 33 | 
            +
              add_column :users, :subscription_key, :string   # Required if User has_a_subscription
         | 
| 34 | 
            +
              add_column :users, :paid_until,       :date     # Required if User has_a_subscription
         | 
| 35 | 
            +
              add_column :users, :trial_until,      :date     # Required if User has_a_subscription and trial length is greater than 0.days
         | 
| 26 36 |  | 
| 27 | 
            -
             | 
| 37 | 
            +
            And the following definition calls:
         | 
| 28 38 |  | 
| 29 | 
            -
               | 
| 30 | 
            -
             | 
| 39 | 
            +
              class User < ActiveRecord::Base
         | 
| 40 | 
            +
              
         | 
| 41 | 
            +
                has_a_braintree_customer
         | 
| 42 | 
            +
              
         | 
| 43 | 
            +
                has_a_subscription do
         | 
| 44 | 
            +
                  plans :mild => {:price => 0.00}, :medium => {:price => 5.00}, :spicy => {:price => 10.00}
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
              
         | 
| 31 47 | 
             
              end
         | 
| 32 48 |  | 
| 33 | 
            -
             | 
| 49 | 
            +
            And then run this daily:
         | 
| 34 50 |  | 
| 35 | 
            -
             | 
| 51 | 
            +
              User.run_billing!
         | 
| 36 52 |  | 
| 37 | 
            -
             | 
| 38 | 
            -
                acts_as_braintree_customer :business_name => :company, :firstname => :first_name, :lastname => :last_name
         | 
| 39 | 
            -
                attr_accessible :firstname, :lastname, :website, :email, :business_name, :password, :password_confirmation
         | 
| 40 | 
            -
              end
         | 
| 53 | 
            +
            == More advanced configuration
         | 
| 41 54 |  | 
| 42 | 
            -
             | 
| 43 | 
            -
              james.customer #=> <Braintree::Customer id: "9999999", first_name: "James", last_name: "Daniels", company: "MarginLeft, LLC", email: "james@jamesdaniels.net", website: "http://www.marginleft.com", ..., addresses: [], credit_cards: []>
         | 
| 55 | 
            +
            If you don't like the default settings or your user needs a field mapping, you can override many assumptions RedLine makes:
         | 
| 44 56 |  | 
| 45 | 
            -
             | 
| 57 | 
            +
              class User < ActiveRecord::Base
         | 
| 58 | 
            +
                has_a_braintree_customer do
         | 
| 59 | 
            +
                  attribute_map :firstname => :first_name, :lastname => :last_name
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                has_a_subscription do
         | 
| 63 | 
            +
                  plans :mild => {:price => 0.00}, :medium => {:price => 5.00}, :spicy => {:price => 10.00}
         | 
| 64 | 
            +
                  default_plan :medium
         | 
| 65 | 
            +
                  billing_frequency 30.days, :grace_period => 7.days
         | 
| 66 | 
            +
                  free_trial 30.days, :reminder => 7.days
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
              end
         | 
| 46 69 |  | 
| 47 70 | 
             
            == Note on Patches/Pull Requests
         | 
| 48 71 |  | 
    
        data/Rakefile
    CHANGED
    
    | @@ -5,8 +5,8 @@ begin | |
| 5 5 | 
             
              require 'jeweler'
         | 
| 6 6 | 
             
              Jeweler::Tasks.new do |gem|
         | 
| 7 7 | 
             
                gem.name = "redline"
         | 
| 8 | 
            -
                gem.summary = %Q{Braintree  | 
| 9 | 
            -
                gem.description = %Q{ | 
| 8 | 
            +
                gem.summary = %Q{Syncs your AR models with Braintree (Payment Gateway) and offers a lightweight reoccurring billing script}
         | 
| 9 | 
            +
                gem.description = %Q{Syncs your AR models with Braintree (Payment Gateway) and offers a lightweight reoccurring billing script}
         | 
| 10 10 | 
             
                gem.email = "james@marginleft.com"
         | 
| 11 11 | 
             
                gem.homepage = "http://github.com/jamesdaniels/redline"
         | 
| 12 12 | 
             
                gem.authors = ["James Daniels"]
         | 
| @@ -21,6 +21,11 @@ rescue LoadError | |
| 21 21 | 
             
              puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
         | 
| 22 22 | 
             
            end
         | 
| 23 23 |  | 
| 24 | 
            +
            begin
         | 
| 25 | 
            +
            	require 'metric_fu'
         | 
| 26 | 
            +
            rescue
         | 
| 27 | 
            +
            end
         | 
| 28 | 
            +
             | 
| 24 29 | 
             
            require 'spec/rake/spectask'
         | 
| 25 30 | 
             
            Spec::Rake::SpecTask.new(:spec) do |spec|
         | 
| 26 31 | 
             
              spec.libs << 'lib' << 'spec'
         | 
    
        data/VERSION
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            0. | 
| 1 | 
            +
            0.2.0
         | 
    
        data/lib/redline.rb
    CHANGED
    
    | @@ -2,12 +2,21 @@ require 'rubygems' | |
| 2 2 | 
             
            require 'active_record'
         | 
| 3 3 |  | 
| 4 4 | 
             
            module RedLine
         | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 5 | 
            +
             | 
| 6 | 
            +
            	autoload :Customer,     'redline/customer/base'
         | 
| 7 | 
            +
            	autoload :Subscription, 'redline/subscription/base'
         | 
| 8 | 
            +
            	autoload :Billing,      'redline/billing/base'
         | 
| 9 | 
            +
            	
         | 
| 10 | 
            +
            	def has_a_braintree_customer
         | 
| 11 | 
            +
            		include RedLine::Customer
         | 
| 12 | 
            +
            		yield if block_given?
         | 
| 13 | 
            +
            		set_default_customer_options
         | 
| 14 | 
            +
            	end
         | 
| 7 15 |  | 
| 8 | 
            -
            	def  | 
| 9 | 
            -
            		 | 
| 10 | 
            -
            		 | 
| 16 | 
            +
            	def has_a_subscription
         | 
| 17 | 
            +
            		include RedLine::Subscription
         | 
| 18 | 
            +
            		yield if block_given?
         | 
| 19 | 
            +
            		set_default_subscription_options
         | 
| 11 20 | 
             
            	end
         | 
| 12 21 |  | 
| 13 22 | 
             
            end
         | 
| @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            module RedLine
         | 
| 2 | 
            +
            	module Customer
         | 
| 3 | 
            +
            		autoload :Settings,        'redline/customer/settings'
         | 
| 4 | 
            +
            		autoload :InstanceMethods, 'redline/customer/instance'
         | 
| 5 | 
            +
            		def self.included(base)
         | 
| 6 | 
            +
            			base.class_eval do
         | 
| 7 | 
            +
            				extend ActiveSupport::Memoizable
         | 
| 8 | 
            +
            				extend RedLine::Customer::Settings
         | 
| 9 | 
            +
            				include InstanceMethods
         | 
| 10 | 
            +
            				memoize :customer
         | 
| 11 | 
            +
            				before_create  :create_customer
         | 
| 12 | 
            +
            				before_update  :update_customer
         | 
| 13 | 
            +
            				before_destroy :delete_customer
         | 
| 14 | 
            +
            				cattr_accessor :braintree_customer
         | 
| 15 | 
            +
            			end
         | 
| 16 | 
            +
            		end
         | 
| 17 | 
            +
            	end
         | 
| 18 | 
            +
            end
         | 
| @@ -1,16 +1,5 @@ | |
| 1 1 | 
             
            module RedLine
         | 
| 2 2 | 
             
            	module Customer
         | 
| 3 | 
            -
            		def self.included(base)
         | 
| 4 | 
            -
            			base.class_eval do
         | 
| 5 | 
            -
            				send :extend, ActiveSupport::Memoizable
         | 
| 6 | 
            -
            				send :include, InstanceMethods
         | 
| 7 | 
            -
            				memoize :customer
         | 
| 8 | 
            -
            				before_create  :create_customer
         | 
| 9 | 
            -
            				before_update  :update_customer
         | 
| 10 | 
            -
            				before_destroy :delete_customer
         | 
| 11 | 
            -
            				cattr_accessor :braintree_customer
         | 
| 12 | 
            -
            			end
         | 
| 13 | 
            -
            		end
         | 
| 14 3 | 
             
            		module InstanceMethods
         | 
| 15 4 | 
             
            			def braintree_customer_attributes
         | 
| 16 5 | 
             
            				wanted_attributes = Braintree::Customer._create_signature.reject{|a| a.is_a? Hash}.reject{|a| a == :id}
         | 
| @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            module RedLine
         | 
| 2 | 
            +
            	module Subscription
         | 
| 3 | 
            +
            		autoload :Settings,        'redline/subscription/settings'
         | 
| 4 | 
            +
            		autoload :InstanceMethods, 'redline/subscription/instance'
         | 
| 5 | 
            +
            		def self.included(base)
         | 
| 6 | 
            +
            			base.class_eval do
         | 
| 7 | 
            +
            				extend ActiveSupport::Memoizable
         | 
| 8 | 
            +
            				extend RedLine::Subscription::Settings
         | 
| 9 | 
            +
            				extend RedLine::Billing
         | 
| 10 | 
            +
            				include InstanceMethods
         | 
| 11 | 
            +
            				before_create :set_plan
         | 
| 12 | 
            +
            				cattr_accessor :subscription_plans, :trial_settings, :billing_settings, :default_subscription_plan
         | 
| 13 | 
            +
            			end
         | 
| 14 | 
            +
            		end
         | 
| 15 | 
            +
            	end
         | 
| 16 | 
            +
            end
         | 
| @@ -0,0 +1,42 @@ | |
| 1 | 
            +
            module RedLine
         | 
| 2 | 
            +
            	module Subscription
         | 
| 3 | 
            +
            		module InstanceMethods
         | 
| 4 | 
            +
            			def plan
         | 
| 5 | 
            +
            				self.class.subscription_plans[(subscription_key || self.class.default_subscription_plan).to_sym]
         | 
| 6 | 
            +
            			end
         | 
| 7 | 
            +
            			def plan=(plan)
         | 
| 8 | 
            +
            				if (plan.is_a?(String) || plan.is_a?(Symbol)) && self.class.subscription_plans.keys.include?(plan.to_sym)
         | 
| 9 | 
            +
            					self.subscription_key = plan
         | 
| 10 | 
            +
            				elsif plan.is_a?(Hash)
         | 
| 11 | 
            +
            					self.subscription_key = self.class.subscription_plans.index(plan)
         | 
| 12 | 
            +
            				end
         | 
| 13 | 
            +
            				self.paid_until = Date.today
         | 
| 14 | 
            +
            				self.trial_until ||= Date.today + self.class.trial_settings[:period] if trial?
         | 
| 15 | 
            +
            			end
         | 
| 16 | 
            +
            			def set_plan
         | 
| 17 | 
            +
            				self.subscription_key ||= self.class.default_subscription_plan
         | 
| 18 | 
            +
            			end
         | 
| 19 | 
            +
            			def pay
         | 
| 20 | 
            +
            				self.customer.sale!(:amount => ("%0.2f" % plan[:price]), :options => {:submit_for_settlement => true})
         | 
| 21 | 
            +
            				self.paid_until = Date.today + self.class.billing_settings[:period] - (past_due || 0)
         | 
| 22 | 
            +
            			end
         | 
| 23 | 
            +
            			def past_grace?
         | 
| 24 | 
            +
            				past_due && past_due >= self.class.billing_settings[:grace_period]
         | 
| 25 | 
            +
            			end
         | 
| 26 | 
            +
            			def paid_plan?
         | 
| 27 | 
            +
            				plan[:price] > 0
         | 
| 28 | 
            +
            			end
         | 
| 29 | 
            +
            			def trial?
         | 
| 30 | 
            +
            				self.class.trial_settings[:period] > 0.days
         | 
| 31 | 
            +
            			end
         | 
| 32 | 
            +
            			def next_due
         | 
| 33 | 
            +
            				paid_plan? && [trial? && trial_until || nil, paid_until, Date.today].compact.max
         | 
| 34 | 
            +
            			end
         | 
| 35 | 
            +
            			def past_due
         | 
| 36 | 
            +
            				due_date = [trial? && trial_until || nil, paid_until].compact.max
         | 
| 37 | 
            +
            				amount = (due_date && (Date.today - due_date) || 0).days
         | 
| 38 | 
            +
            				paid_plan? && amount > 0 && amount
         | 
| 39 | 
            +
            			end
         | 
| 40 | 
            +
            		end
         | 
| 41 | 
            +
            	end
         | 
| 42 | 
            +
            end
         | 
| @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            module RedLine
         | 
| 2 | 
            +
            	module Subscription
         | 
| 3 | 
            +
            		module Settings
         | 
| 4 | 
            +
            			def set_default_subscription_options
         | 
| 5 | 
            +
            				billing_frequency 30.days unless billing_settings
         | 
| 6 | 
            +
            				free_trial        30.days unless trial_settings
         | 
| 7 | 
            +
            				default_plan ((send :subscription_plans).to_a.sort_by{|a| a[1][:price]}.first.first) unless default_subscription_plan
         | 
| 8 | 
            +
            			end
         | 
| 9 | 
            +
            			def plans(subscriptions = {})
         | 
| 10 | 
            +
            				include RedLine::Subscription
         | 
| 11 | 
            +
            				self.subscription_plans = subscriptions
         | 
| 12 | 
            +
            			end
         | 
| 13 | 
            +
            			def free_trial(period, options = {})
         | 
| 14 | 
            +
            				self.trial_settings = {:period => period, :reminder => 5.days}.merge(options)
         | 
| 15 | 
            +
            			end
         | 
| 16 | 
            +
            			def billing_frequency(period, options = {})
         | 
| 17 | 
            +
            				self.billing_settings = {:period => period, :grace_period => 5.days}.merge(options)
         | 
| 18 | 
            +
            			end
         | 
| 19 | 
            +
            			def default_plan(subscription_plan)
         | 
| 20 | 
            +
            				self.default_subscription_plan = subscription_plan
         | 
| 21 | 
            +
            			end
         | 
| 22 | 
            +
            		end
         | 
| 23 | 
            +
            	end
         | 
| 24 | 
            +
            end
         | 
    
        data/redline.gemspec
    CHANGED
    
    | @@ -5,12 +5,12 @@ | |
| 5 5 |  | 
| 6 6 | 
             
            Gem::Specification.new do |s|
         | 
| 7 7 | 
             
              s.name = %q{redline}
         | 
| 8 | 
            -
              s.version = "0. | 
| 8 | 
            +
              s.version = "0.2.0"
         | 
| 9 9 |  | 
| 10 10 | 
             
              s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
         | 
| 11 11 | 
             
              s.authors = ["James Daniels"]
         | 
| 12 | 
            -
              s.date = %q{2010-02- | 
| 13 | 
            -
              s.description = %q{ | 
| 12 | 
            +
              s.date = %q{2010-02-10}
         | 
| 13 | 
            +
              s.description = %q{Syncs your AR models with Braintree (Payment Gateway) and offers a lightweight reoccurring billing script}
         | 
| 14 14 | 
             
              s.email = %q{james@marginleft.com}
         | 
| 15 15 | 
             
              s.extra_rdoc_files = [
         | 
| 16 16 | 
             
                "LICENSE",
         | 
| @@ -24,25 +24,37 @@ Gem::Specification.new do |s| | |
| 24 24 | 
             
                 "Rakefile",
         | 
| 25 25 | 
             
                 "VERSION",
         | 
| 26 26 | 
             
                 "lib/redline.rb",
         | 
| 27 | 
            -
                 "lib/redline/ | 
| 27 | 
            +
                 "lib/redline/billing/base.rb",
         | 
| 28 | 
            +
                 "lib/redline/customer/base.rb",
         | 
| 29 | 
            +
                 "lib/redline/customer/instance.rb",
         | 
| 30 | 
            +
                 "lib/redline/customer/settings.rb",
         | 
| 31 | 
            +
                 "lib/redline/subscription/base.rb",
         | 
| 32 | 
            +
                 "lib/redline/subscription/instance.rb",
         | 
| 33 | 
            +
                 "lib/redline/subscription/settings.rb",
         | 
| 28 34 | 
             
                 "redline.gemspec",
         | 
| 35 | 
            +
                 "spec/billing_spec.rb",
         | 
| 36 | 
            +
                 "spec/customer_spec.rb",
         | 
| 29 37 | 
             
                 "spec/db/database.yml",
         | 
| 30 38 | 
             
                 "spec/db/models.rb",
         | 
| 31 39 | 
             
                 "spec/db/schema.rb",
         | 
| 32 40 | 
             
                 "spec/redline_spec.rb",
         | 
| 33 41 | 
             
                 "spec/spec.opts",
         | 
| 34 | 
            -
                 "spec/spec_helper.rb"
         | 
| 42 | 
            +
                 "spec/spec_helper.rb",
         | 
| 43 | 
            +
                 "spec/subscription_spec.rb"
         | 
| 35 44 | 
             
              ]
         | 
| 36 45 | 
             
              s.homepage = %q{http://github.com/jamesdaniels/redline}
         | 
| 37 46 | 
             
              s.rdoc_options = ["--charset=UTF-8"]
         | 
| 38 47 | 
             
              s.require_paths = ["lib"]
         | 
| 39 48 | 
             
              s.rubygems_version = %q{1.3.5}
         | 
| 40 | 
            -
              s.summary = %q{Braintree  | 
| 49 | 
            +
              s.summary = %q{Syncs your AR models with Braintree (Payment Gateway) and offers a lightweight reoccurring billing script}
         | 
| 41 50 | 
             
              s.test_files = [
         | 
| 42 | 
            -
                "spec/ | 
| 51 | 
            +
                "spec/billing_spec.rb",
         | 
| 52 | 
            +
                 "spec/customer_spec.rb",
         | 
| 53 | 
            +
                 "spec/db/models.rb",
         | 
| 43 54 | 
             
                 "spec/db/schema.rb",
         | 
| 44 55 | 
             
                 "spec/redline_spec.rb",
         | 
| 45 | 
            -
                 "spec/spec_helper.rb"
         | 
| 56 | 
            +
                 "spec/spec_helper.rb",
         | 
| 57 | 
            +
                 "spec/subscription_spec.rb"
         | 
| 46 58 | 
             
              ]
         | 
| 47 59 |  | 
| 48 60 | 
             
              if s.respond_to? :specification_version then
         | 
| @@ -0,0 +1,121 @@ | |
| 1 | 
            +
            require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe User do
         | 
| 4 | 
            +
            	
         | 
| 5 | 
            +
            	def valid_user
         | 
| 6 | 
            +
            		Braintree::Customer.stub!('_create_signature').and_return([:first_name, :last_name, :email])
         | 
| 7 | 
            +
            		@user ||= User.new(
         | 
| 8 | 
            +
            			:first_name => 'James', 
         | 
| 9 | 
            +
            			:last_name => 'Daniels', 
         | 
| 10 | 
            +
            			:email => 'james@marginleft.com', 
         | 
| 11 | 
            +
            			:unused_attribute => 'unused'
         | 
| 12 | 
            +
            		)
         | 
| 13 | 
            +
            	end
         | 
| 14 | 
            +
            	
         | 
| 15 | 
            +
            	def mock_customer
         | 
| 16 | 
            +
            		@customer ||= mock(Braintree::Customer, :id => 1)
         | 
| 17 | 
            +
            	end
         | 
| 18 | 
            +
            	
         | 
| 19 | 
            +
            	it 'should run billing' do
         | 
| 20 | 
            +
            		Braintree::Customer.stub!('_create_signature').and_return([:first_name, :last_name, :email])
         | 
| 21 | 
            +
            		Braintree::Customer.should_receive('create!').with(valid_user.braintree_customer_attributes).exactly(3).and_return(mock_customer)
         | 
| 22 | 
            +
            		Braintree::Customer.should_receive('update!').twice.and_return(mock_customer)
         | 
| 23 | 
            +
            		users = User.subscription_plans.keys.map do |plan|
         | 
| 24 | 
            +
            			user = User.new(
         | 
| 25 | 
            +
            				:first_name => 'James', 
         | 
| 26 | 
            +
            				:last_name => 'Daniels', 
         | 
| 27 | 
            +
            				:email => 'james@marginleft.com', 
         | 
| 28 | 
            +
            				:unused_attribute => 'unused'
         | 
| 29 | 
            +
            			)
         | 
| 30 | 
            +
            			user.plan = plan
         | 
| 31 | 
            +
            			if plan == :mild
         | 
| 32 | 
            +
            				user.should_receive(:next_due).and_return(nil)
         | 
| 33 | 
            +
            				user.should_not_receive(:pay)
         | 
| 34 | 
            +
            			else
         | 
| 35 | 
            +
            				user.should_receive(:next_due).and_return(Date.today)
         | 
| 36 | 
            +
            				user.should_receive(:pay).and_return(true)
         | 
| 37 | 
            +
            			end
         | 
| 38 | 
            +
            			user.save
         | 
| 39 | 
            +
            			user
         | 
| 40 | 
            +
            		end
         | 
| 41 | 
            +
            		User.stub!(:all).and_return(users)
         | 
| 42 | 
            +
            		User.run_billing!
         | 
| 43 | 
            +
            	end
         | 
| 44 | 
            +
            	
         | 
| 45 | 
            +
            	it 'should run billing with failures' do
         | 
| 46 | 
            +
            		Braintree::Customer.stub!('_create_signature').and_return([:first_name, :last_name, :email])
         | 
| 47 | 
            +
            		Braintree::Customer.should_receive('create!').with(valid_user.braintree_customer_attributes).once.and_return(mock_customer)
         | 
| 48 | 
            +
            		Braintree::Customer.should_receive('update!').once.and_return(mock_customer)
         | 
| 49 | 
            +
            		user = User.new(
         | 
| 50 | 
            +
            			:first_name => 'James', 
         | 
| 51 | 
            +
            			:last_name => 'Daniels', 
         | 
| 52 | 
            +
            			:email => 'james@marginleft.com', 
         | 
| 53 | 
            +
            			:unused_attribute => 'unused'
         | 
| 54 | 
            +
            		)
         | 
| 55 | 
            +
            		user.plan = :medium
         | 
| 56 | 
            +
            		user.paid_until = Date.today - 1
         | 
| 57 | 
            +
            		user.trial_until = Date.today - 1
         | 
| 58 | 
            +
            		user.stub!('customer').and_return(mock_customer)
         | 
| 59 | 
            +
            		user.stub!(:pay).and_raise(ArgumentError)
         | 
| 60 | 
            +
            		user.save
         | 
| 61 | 
            +
            		User.stub!(:all).and_return([user])
         | 
| 62 | 
            +
            		User.run_billing!
         | 
| 63 | 
            +
            		user.next_due.should eql(Date.today)
         | 
| 64 | 
            +
            	end
         | 
| 65 | 
            +
            	
         | 
| 66 | 
            +
            	describe 'paid bill' do
         | 
| 67 | 
            +
            		(0).upto(5) do |x|
         | 
| 68 | 
            +
            			it "should push the due date 30 days when #{x} days past due" do
         | 
| 69 | 
            +
            				user = User.new
         | 
| 70 | 
            +
            				mock_customer = mock(Braintree::Customer, :id => 1)
         | 
| 71 | 
            +
            				user.stub!('customer').and_return(mock_customer)
         | 
| 72 | 
            +
            				mock_customer.should_receive('sale!').with({:amount=>"10.00", :options=>{:submit_for_settlement=>true}}).and_return(true)
         | 
| 73 | 
            +
            				user.plan = :spicy
         | 
| 74 | 
            +
            				user.paid_until = Date.today - x.days
         | 
| 75 | 
            +
            				user.trial_until = Date.today - 30.days
         | 
| 76 | 
            +
            				user.pay
         | 
| 77 | 
            +
            				user.next_due.should eql(Date.today + 30.days - x.days)
         | 
| 78 | 
            +
            			end
         | 
| 79 | 
            +
            		end
         | 
| 80 | 
            +
            	end
         | 
| 81 | 
            +
            	
         | 
| 82 | 
            +
            end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
            describe ComplexUser do
         | 
| 85 | 
            +
            	
         | 
| 86 | 
            +
            	describe 'paid bill' do
         | 
| 87 | 
            +
            		(0).upto(5) do |x|
         | 
| 88 | 
            +
            			it "should push the due date 30 days when #{x} days past due" do
         | 
| 89 | 
            +
            				user = ComplexUser.new
         | 
| 90 | 
            +
            				mock_customer = mock(Braintree::Customer, :id => 1)
         | 
| 91 | 
            +
            				user.stub!('customer').and_return(mock_customer)
         | 
| 92 | 
            +
            				mock_customer.should_receive('sale!').with({:amount=>"5.00", :options=>{:submit_for_settlement=>true}}).and_return(true)
         | 
| 93 | 
            +
            				user.plan = :medium
         | 
| 94 | 
            +
            				user.paid_until = Date.today - x.days
         | 
| 95 | 
            +
            				user.trial_until = Date.today - 31.days
         | 
| 96 | 
            +
            				user.pay
         | 
| 97 | 
            +
            				user.next_due.should eql(Date.today + 31.days - x.days)
         | 
| 98 | 
            +
            			end
         | 
| 99 | 
            +
            		end
         | 
| 100 | 
            +
            	end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
            end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
            describe UserWithoutTrial do
         | 
| 105 | 
            +
            	
         | 
| 106 | 
            +
            	describe 'paid bill' do
         | 
| 107 | 
            +
            		(0).upto(5) do |x|
         | 
| 108 | 
            +
            			it "should push the due date 30 days when #{x} days past due" do
         | 
| 109 | 
            +
            				user = UserWithoutTrial.new
         | 
| 110 | 
            +
            				mock_customer = mock(Braintree::Customer, :id => 1)
         | 
| 111 | 
            +
            				user.stub!('customer').and_return(mock_customer)
         | 
| 112 | 
            +
            				mock_customer.should_receive('sale!').with({:amount=>"10.00", :options=>{:submit_for_settlement=>true}}).and_return(true)
         | 
| 113 | 
            +
            				user.plan = :spicy
         | 
| 114 | 
            +
            				user.paid_until = Date.today - x.days
         | 
| 115 | 
            +
            				user.pay
         | 
| 116 | 
            +
            				user.next_due.should eql(Date.today + 30.days - x.days)
         | 
| 117 | 
            +
            			end
         | 
| 118 | 
            +
            		end
         | 
| 119 | 
            +
            	end
         | 
| 120 | 
            +
            	
         | 
| 121 | 
            +
            end
         | 
| @@ -0,0 +1,76 @@ | |
| 1 | 
            +
            require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe User do
         | 
| 4 | 
            +
            	
         | 
| 5 | 
            +
            	def valid_user
         | 
| 6 | 
            +
            		Braintree::Customer.stub!('_create_signature').and_return([:first_name, :last_name, :email])
         | 
| 7 | 
            +
            		@user ||= User.new(
         | 
| 8 | 
            +
            			:first_name => 'James', 
         | 
| 9 | 
            +
            			:last_name => 'Daniels', 
         | 
| 10 | 
            +
            			:email => 'james@marginleft.com', 
         | 
| 11 | 
            +
            			:unused_attribute => 'unused'
         | 
| 12 | 
            +
            		)
         | 
| 13 | 
            +
            	end
         | 
| 14 | 
            +
            	
         | 
| 15 | 
            +
            	def mock_customer
         | 
| 16 | 
            +
            		@customer ||= mock(Braintree::Customer, :id => 1)
         | 
| 17 | 
            +
            	end
         | 
| 18 | 
            +
            	
         | 
| 19 | 
            +
            	it "should have a empty braintree_customer" do
         | 
| 20 | 
            +
            		User.braintree_customer.should eql({})
         | 
| 21 | 
            +
            	end
         | 
| 22 | 
            +
            	
         | 
| 23 | 
            +
              it "should inherit the instance methods" do
         | 
| 24 | 
            +
            		expected_methods = %w(braintree_customer_attributes customer create_customer update_customer delete_customer)
         | 
| 25 | 
            +
            		(User.instance_methods & expected_methods).sort.should eql(expected_methods.sort)
         | 
| 26 | 
            +
            	end
         | 
| 27 | 
            +
            	it "should have proper braintree attributes" do
         | 
| 28 | 
            +
            		valid_user.braintree_customer_attributes.should eql({:first_name=>"James", :last_name=>"Daniels", :email=>"james@marginleft.com"})
         | 
| 29 | 
            +
            	end
         | 
| 30 | 
            +
            	it "should fire Braintree::Customer.create!" do
         | 
| 31 | 
            +
            		Braintree::Customer.should_receive('create!').with(valid_user.braintree_customer_attributes).and_return(mock_customer)
         | 
| 32 | 
            +
            		valid_user.save!
         | 
| 33 | 
            +
            		valid_user.reload
         | 
| 34 | 
            +
            		valid_user.customer_id.should eql(mock_customer.id)
         | 
| 35 | 
            +
            	end
         | 
| 36 | 
            +
            	it "should fire Braintree::Customer.update!" do
         | 
| 37 | 
            +
            		Braintree::Customer.stub!('create!').and_return(mock_customer)
         | 
| 38 | 
            +
            		Braintree::Customer.should_receive('update!').with(mock_customer.id, valid_user.braintree_customer_attributes.merge(:email =>  'james@jamesdaniels.net')).and_return(true)
         | 
| 39 | 
            +
            		valid_user.save!
         | 
| 40 | 
            +
            		valid_user.update_attributes(:email => 'james@jamesdaniels.net')
         | 
| 41 | 
            +
            		valid_user.reload
         | 
| 42 | 
            +
            		valid_user.customer_id.should eql(mock_customer.id)
         | 
| 43 | 
            +
            	end
         | 
| 44 | 
            +
            	it "should fire Braintree::Customer.delete" do
         | 
| 45 | 
            +
            		Braintree::Customer.stub!('create!').and_return(mock_customer)
         | 
| 46 | 
            +
            		Braintree::Customer.stub!('find').and_return(true)
         | 
| 47 | 
            +
            		Braintree::Customer.should_receive('delete').with(mock_customer.id).and_return(true)
         | 
| 48 | 
            +
            		valid_user.save!
         | 
| 49 | 
            +
            		valid_user.destroy
         | 
| 50 | 
            +
            	end
         | 
| 51 | 
            +
            	it "should fire Braintree::Customer.find" do
         | 
| 52 | 
            +
            		Braintree::Customer.stub!('create!').and_return(mock_customer)
         | 
| 53 | 
            +
            		Braintree::Customer.stub!('update!').and_return(true)
         | 
| 54 | 
            +
            		Braintree::Customer.should_receive('find').with(mock_customer.id).and_return(true)
         | 
| 55 | 
            +
            		valid_user.save!
         | 
| 56 | 
            +
            		valid_user.customer
         | 
| 57 | 
            +
            	end
         | 
| 58 | 
            +
            	
         | 
| 59 | 
            +
            end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            describe ComplexUser do
         | 
| 62 | 
            +
            	
         | 
| 63 | 
            +
            	it "should have a empty braintree_customer" do
         | 
| 64 | 
            +
            		ComplexUser.braintree_customer.should eql({:firstname=>:first_name, :lastname=>:last_name})
         | 
| 65 | 
            +
            	end
         | 
| 66 | 
            +
            	it "should have proper braintree attributes" do
         | 
| 67 | 
            +
            		Braintree::Customer.stub!('_create_signature').and_return([:first_name, :last_name, :email])
         | 
| 68 | 
            +
            		ComplexUser.new(
         | 
| 69 | 
            +
            			:firstname => 'James', 
         | 
| 70 | 
            +
            			:lastname => 'Daniels', 
         | 
| 71 | 
            +
            			:email => 'james@marginleft.com', 
         | 
| 72 | 
            +
            			:unused_attribute => 'unused'
         | 
| 73 | 
            +
            		).braintree_customer_attributes.should eql({:first_name=>"James", :last_name=>"Daniels", :email=>"james@marginleft.com"})
         | 
| 74 | 
            +
            	end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
            end
         | 
    
        data/spec/db/models.rb
    CHANGED
    
    | @@ -1,6 +1,10 @@ | |
| 1 1 | 
             
            class User < ActiveRecord::Base
         | 
| 2 2 |  | 
| 3 | 
            -
            	 | 
| 3 | 
            +
            	has_a_braintree_customer
         | 
| 4 | 
            +
            	
         | 
| 5 | 
            +
            	has_a_subscription do
         | 
| 6 | 
            +
            		plans :mild => {:price => 0.00}, :medium => {:price => 5.00}, :spicy => {:price => 10.00}
         | 
| 7 | 
            +
            	end
         | 
| 4 8 |  | 
| 5 9 | 
             
            	validates_presence_of :first_name, :last_name, :unused_attribute, :email
         | 
| 6 10 |  | 
| @@ -8,8 +12,36 @@ end | |
| 8 12 |  | 
| 9 13 | 
             
            class ComplexUser < ActiveRecord::Base
         | 
| 10 14 |  | 
| 11 | 
            -
            	 | 
| 15 | 
            +
            	has_a_braintree_customer do
         | 
| 16 | 
            +
            		attribute_map :firstname => :first_name, :lastname => :last_name
         | 
| 17 | 
            +
            	end
         | 
| 18 | 
            +
            	
         | 
| 19 | 
            +
            	has_a_subscription do
         | 
| 20 | 
            +
            		plans :mild => {:price => 0.00}, :medium => {:price => 5.00}, :spicy => {:price => 10.00}
         | 
| 21 | 
            +
            		default_plan :medium
         | 
| 22 | 
            +
            		billing_frequency 31.days, :grace_period => 6.days
         | 
| 23 | 
            +
            		free_trial 31.days, :reminder => 6.days
         | 
| 24 | 
            +
            	end
         | 
| 12 25 |  | 
| 13 26 | 
             
            	validates_presence_of :firstname, :lastname, :unused_attribute, :email
         | 
| 14 27 |  | 
| 28 | 
            +
            end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            class UserWithoutTrial < ActiveRecord::Base
         | 
| 31 | 
            +
            	
         | 
| 32 | 
            +
            	has_a_braintree_customer do
         | 
| 33 | 
            +
            		attribute_map :firstname => :first_name, :lastname => :last_name
         | 
| 34 | 
            +
            	end
         | 
| 35 | 
            +
            	
         | 
| 36 | 
            +
            	has_a_subscription do
         | 
| 37 | 
            +
            		plans :mild => {:price => 0.00}, :medium => {:price => 5.00}, :spicy => {:price => 10.00}
         | 
| 38 | 
            +
            		free_trial 0.days
         | 
| 39 | 
            +
            	end
         | 
| 40 | 
            +
            	
         | 
| 41 | 
            +
            end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            class UserWithoutSubscription < ActiveRecord::Base
         | 
| 44 | 
            +
             | 
| 45 | 
            +
            	has_a_braintree_customer
         | 
| 46 | 
            +
             | 
| 15 47 | 
             
            end
         | 
    
        data/spec/db/schema.rb
    CHANGED
    
    | @@ -1,16 +1,37 @@ | |
| 1 1 | 
             
            ActiveRecord::Schema.define(:version => 1) do
         | 
| 2 | 
            -
             | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 2 | 
            +
            	create_table :users, :force => true do |t|
         | 
| 3 | 
            +
            		t.column :first_name, :string
         | 
| 4 | 
            +
            		t.column :last_name, :string
         | 
| 5 | 
            +
            		t.column :email, :string
         | 
| 6 | 
            +
            		t.column :unused_attribute, :string
         | 
| 7 | 
            +
            		t.column :customer_id, :integer
         | 
| 8 | 
            +
            		t.column :subscription_key, :string
         | 
| 9 | 
            +
            		t.column :trial_until, :date
         | 
| 10 | 
            +
            		t.column :paid_until, :date
         | 
| 11 | 
            +
            	end
         | 
| 12 | 
            +
            	create_table :complex_users, :force => true do |t|
         | 
| 13 | 
            +
            		t.column :firstname, :string
         | 
| 14 | 
            +
            		t.column :lastname, :string
         | 
| 15 | 
            +
            		t.column :email, :string
         | 
| 16 | 
            +
            		t.column :unused_attribute, :string
         | 
| 17 | 
            +
            		t.column :customer_id, :integer
         | 
| 18 | 
            +
            		t.column :subscription_key, :string
         | 
| 19 | 
            +
            		t.column :trial_until, :date
         | 
| 20 | 
            +
            		t.column :paid_until, :date
         | 
| 21 | 
            +
            	end
         | 
| 22 | 
            +
            	create_table :user_without_trials, :force => true do |t|
         | 
| 23 | 
            +
            		t.column :firstname, :string
         | 
| 24 | 
            +
            		t.column :lastname, :string
         | 
| 25 | 
            +
            		t.column :email, :string
         | 
| 26 | 
            +
            		t.column :unused_attribute, :string
         | 
| 27 | 
            +
            		t.column :customer_id, :integer
         | 
| 28 | 
            +
            		t.column :subscription_key, :string
         | 
| 29 | 
            +
            		t.column :paid_until, :date
         | 
| 30 | 
            +
            	end
         | 
| 31 | 
            +
            	create_table :user_without_subscription, :force => true do |t|
         | 
| 32 | 
            +
            		t.column :firstname, :string
         | 
| 33 | 
            +
            		t.column :lastname, :string
         | 
| 34 | 
            +
            		t.column :email, :string
         | 
| 35 | 
            +
            		t.column :customer_id, :integer
         | 
| 36 | 
            +
            	end
         | 
| 16 37 | 
             
            end
         | 
    
        data/spec/redline_spec.rb
    CHANGED
    
    | @@ -1,70 +1,42 @@ | |
| 1 1 | 
             
            require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
         | 
| 2 2 |  | 
| 3 | 
            -
            describe  | 
| 4 | 
            -
            	
         | 
| 5 | 
            -
             | 
| 6 | 
            -
            		Braintree::Customer.stub!('_create_signature').and_return([:first_name, :last_name, :email])
         | 
| 7 | 
            -
            		@user ||= User.new(
         | 
| 8 | 
            -
            			:first_name => 'James', 
         | 
| 9 | 
            -
            			:last_name => 'Daniels', 
         | 
| 10 | 
            -
            			:email => 'james@marginleft.com', 
         | 
| 11 | 
            -
            			:unused_attribute => 'unused'
         | 
| 12 | 
            -
            		)
         | 
| 13 | 
            -
            	end
         | 
| 14 | 
            -
            	
         | 
| 15 | 
            -
            	def mock_customer
         | 
| 16 | 
            -
            		@customer ||= mock(Braintree::Customer, :id => 1)
         | 
| 17 | 
            -
            	end
         | 
| 18 | 
            -
            	
         | 
| 19 | 
            -
            	it "should have a empty braintree_customer" do
         | 
| 20 | 
            -
            		User.braintree_customer.should eql({})
         | 
| 21 | 
            -
            	end
         | 
| 22 | 
            -
              it "should inherit the instance methods" do
         | 
| 23 | 
            -
            		expected_methods = %w(braintree_customer_attributes customer create_customer update_customer delete_customer)
         | 
| 24 | 
            -
            		(User.instance_methods & expected_methods).sort.should eql(expected_methods.sort)
         | 
| 25 | 
            -
            	end
         | 
| 26 | 
            -
            	it "should have proper braintree attributes" do
         | 
| 27 | 
            -
            		valid_user.braintree_customer_attributes.should eql({:first_name=>"James", :last_name=>"Daniels", :email=>"james@marginleft.com"})
         | 
| 28 | 
            -
            	end
         | 
| 29 | 
            -
            	it "should fire Braintree::Customer.create!" do
         | 
| 30 | 
            -
            		Braintree::Customer.should_receive('create!').with(valid_user.braintree_customer_attributes).and_return(mock_customer)
         | 
| 31 | 
            -
            		valid_user.save!
         | 
| 32 | 
            -
            		valid_user.customer_id.should eql(mock_customer.id)
         | 
| 33 | 
            -
            	end
         | 
| 34 | 
            -
            	it "should fire Braintree::Customer.update!" do
         | 
| 35 | 
            -
            		Braintree::Customer.stub!('create!').and_return(mock_customer)
         | 
| 36 | 
            -
            		Braintree::Customer.should_receive('update!').with(mock_customer.id, valid_user.braintree_customer_attributes.merge(:email =>  'james@jamesdaniels.net')).and_return(true)
         | 
| 37 | 
            -
            		valid_user.save!
         | 
| 38 | 
            -
            		valid_user.update_attributes(:email => 'james@jamesdaniels.net')
         | 
| 39 | 
            -
            		valid_user.customer_id.should eql(mock_customer.id)
         | 
| 40 | 
            -
            	end
         | 
| 41 | 
            -
            	it "should fire Braintree::Customer.delete" do
         | 
| 42 | 
            -
            		Braintree::Customer.stub!('create!').and_return(mock_customer)
         | 
| 43 | 
            -
            		Braintree::Customer.stub!('find').and_return(true)
         | 
| 44 | 
            -
            		Braintree::Customer.should_receive('delete').with(mock_customer.id).and_return(true)
         | 
| 45 | 
            -
            		valid_user.save!
         | 
| 46 | 
            -
            		valid_user.destroy
         | 
| 47 | 
            -
            	end
         | 
| 48 | 
            -
            	it "should fire Braintree::Customer.find" do
         | 
| 49 | 
            -
            		Braintree::Customer.stub!('create!').and_return(mock_customer)
         | 
| 50 | 
            -
            		Braintree::Customer.stub!('update!').and_return(true)
         | 
| 51 | 
            -
            		Braintree::Customer.should_receive('find').with(mock_customer.id).and_return(true)
         | 
| 52 | 
            -
            		valid_user.save!
         | 
| 53 | 
            -
            		valid_user.customer
         | 
| 3 | 
            +
            describe ActiveRecord do
         | 
| 4 | 
            +
            	it 'should extend our module' do
         | 
| 5 | 
            +
            		ActiveRecord::Base.is_a?(RedLine).should be_true
         | 
| 54 6 | 
             
            	end
         | 
| 55 7 | 
             
            end
         | 
| 56 8 |  | 
| 57 | 
            -
             | 
| 58 | 
            -
            	 | 
| 59 | 
            -
            		 | 
| 60 | 
            -
             | 
| 61 | 
            -
             | 
| 62 | 
            -
            		 | 
| 63 | 
            -
             | 
| 64 | 
            -
             | 
| 65 | 
            -
             | 
| 66 | 
            -
            			: | 
| 67 | 
            -
             | 
| 68 | 
            -
            		 | 
| 9 | 
            +
            [User, ComplexUser, UserWithoutTrial, UserWithoutSubscription].each do |klass|
         | 
| 10 | 
            +
            	describe klass do
         | 
| 11 | 
            +
            		it 'should extend our module' do
         | 
| 12 | 
            +
            			klass.is_a?(RedLine).should be_true
         | 
| 13 | 
            +
            		end
         | 
| 14 | 
            +
            		it 'should respond to a customer' do
         | 
| 15 | 
            +
            			klass.respond_to?(:has_a_braintree_customer).should be_true
         | 
| 16 | 
            +
            		end
         | 
| 17 | 
            +
            		it 'should respond to a subscription' do
         | 
| 18 | 
            +
            			klass.respond_to?(:has_a_subscription).should be_true
         | 
| 19 | 
            +
            		end
         | 
| 20 | 
            +
            		it 'should include RedLine::Customer' do
         | 
| 21 | 
            +
            			klass.include?(RedLine::Customer).should be_true
         | 
| 22 | 
            +
            		end
         | 
| 23 | 
            +
            		it 'should extend RedLine::Customer::Settings' do
         | 
| 24 | 
            +
            			klass.is_a?(RedLine::Customer::Settings).should be_true
         | 
| 25 | 
            +
            		end
         | 
| 26 | 
            +
            		it 'should include RedLine::Customer::InstanceMethods' do
         | 
| 27 | 
            +
            			klass.include?(RedLine::Customer::InstanceMethods).should be_true
         | 
| 28 | 
            +
            		end
         | 
| 29 | 
            +
            		it "should#{(klass == UserWithoutSubscription) && ' not' || ''} include RedLine::Subscription" do
         | 
| 30 | 
            +
            			klass.include?(RedLine::Subscription).should eql(klass != UserWithoutSubscription)
         | 
| 31 | 
            +
            		end
         | 
| 32 | 
            +
            		it "should#{(klass == UserWithoutSubscription) && ' not' || ''} extend RedLine::Subscription::Settings" do
         | 
| 33 | 
            +
            			klass.is_a?(RedLine::Subscription::Settings).should eql(klass != UserWithoutSubscription)
         | 
| 34 | 
            +
            		end
         | 
| 35 | 
            +
            		it "should#{(klass == UserWithoutSubscription) && ' not' || ''} include RedLine::Subscription::InstanceMethods" do
         | 
| 36 | 
            +
            			klass.include?(RedLine::Subscription::InstanceMethods).should eql(klass != UserWithoutSubscription)
         | 
| 37 | 
            +
            		end
         | 
| 38 | 
            +
            		it "should#{(klass == UserWithoutSubscription) && ' not' || ''} include RedLine::Billing" do
         | 
| 39 | 
            +
            			klass.is_a?(RedLine::Billing).should eql(klass != UserWithoutSubscription)
         | 
| 40 | 
            +
            		end
         | 
| 69 41 | 
             
            	end
         | 
| 70 42 | 
             
            end
         | 
| @@ -0,0 +1,237 @@ | |
| 1 | 
            +
            require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe User do
         | 
| 4 | 
            +
            	
         | 
| 5 | 
            +
            	def valid_user
         | 
| 6 | 
            +
            		Braintree::Customer.stub!('_create_signature').and_return([:first_name, :last_name, :email])
         | 
| 7 | 
            +
            		@user ||= User.new(
         | 
| 8 | 
            +
            			:first_name => 'James', 
         | 
| 9 | 
            +
            			:last_name => 'Daniels', 
         | 
| 10 | 
            +
            			:email => 'james@marginleft.com', 
         | 
| 11 | 
            +
            			:unused_attribute => 'unused'
         | 
| 12 | 
            +
            		)
         | 
| 13 | 
            +
            	end
         | 
| 14 | 
            +
            	
         | 
| 15 | 
            +
            	def mock_customer
         | 
| 16 | 
            +
            		@customer ||= mock(Braintree::Customer, :id => 1)
         | 
| 17 | 
            +
            	end
         | 
| 18 | 
            +
            	
         | 
| 19 | 
            +
            	it 'should have a default plan' do
         | 
| 20 | 
            +
            		Braintree::Customer.should_receive('create!').with(valid_user.braintree_customer_attributes).and_return(mock_customer)
         | 
| 21 | 
            +
            		user = valid_user
         | 
| 22 | 
            +
            		user.save
         | 
| 23 | 
            +
            		user.reload
         | 
| 24 | 
            +
            		user.subscription_key = :mild
         | 
| 25 | 
            +
            	end
         | 
| 26 | 
            +
            	
         | 
| 27 | 
            +
            	it 'should have possible subscription plans' do
         | 
| 28 | 
            +
            		User.subscription_plans.should == {:mild => {:price => 0.00}, :medium => {:price => 5.00}, :spicy => {:price => 10.00}}
         | 
| 29 | 
            +
            	end
         | 
| 30 | 
            +
            	it 'should have a default plan' do
         | 
| 31 | 
            +
            		User.default_subscription_plan.should eql(:mild)
         | 
| 32 | 
            +
            	end
         | 
| 33 | 
            +
            	it 'should have billing settings' do
         | 
| 34 | 
            +
            		User.billing_settings.should == {:period => 30.days, :grace_period => 5.days}
         | 
| 35 | 
            +
            	end
         | 
| 36 | 
            +
            	it 'should have trial settings' do
         | 
| 37 | 
            +
            		User.trial_settings.should == {:period => 30.days, :reminder=>5.days}
         | 
| 38 | 
            +
            	end
         | 
| 39 | 
            +
            	
         | 
| 40 | 
            +
            	describe 'should handle trial period' do
         | 
| 41 | 
            +
            		it 'sets trial' do
         | 
| 42 | 
            +
            			user = User.new
         | 
| 43 | 
            +
            			user.plan = :mild
         | 
| 44 | 
            +
            			user.trial_until.should eql(Date.today + 30.days)
         | 
| 45 | 
            +
            		end
         | 
| 46 | 
            +
            		it 'upgrades account within trial' do
         | 
| 47 | 
            +
            			user = User.new
         | 
| 48 | 
            +
            			user.plan = :medium
         | 
| 49 | 
            +
            			user.plan = :spicy
         | 
| 50 | 
            +
            			user.trial_until.should eql(Date.today + 30.days)
         | 
| 51 | 
            +
            		end
         | 
| 52 | 
            +
            		it 'upgrades account outside of trial' do
         | 
| 53 | 
            +
            			user = User.new
         | 
| 54 | 
            +
            			user.plan = :medium
         | 
| 55 | 
            +
            			user.trial_until = Date.today-1.days
         | 
| 56 | 
            +
            			user.plan = :spicy
         | 
| 57 | 
            +
            			user.trial_until.should eql(Date.today - 1.days)
         | 
| 58 | 
            +
            		end
         | 
| 59 | 
            +
            		it 'downgrades account within trial' do
         | 
| 60 | 
            +
            			user = User.new
         | 
| 61 | 
            +
            			user.plan = :spicy
         | 
| 62 | 
            +
            			user.plan = :medium
         | 
| 63 | 
            +
            			user.trial_until.should eql(Date.today + 30.days)
         | 
| 64 | 
            +
            		end
         | 
| 65 | 
            +
            		it 'downgrades account outside of trial' do
         | 
| 66 | 
            +
            			user = User.new
         | 
| 67 | 
            +
            			user.plan = :spicy
         | 
| 68 | 
            +
            			user.trial_until = Date.today-1.days
         | 
| 69 | 
            +
            			user.plan = :medium
         | 
| 70 | 
            +
            			user.trial_until.should eql(Date.today - 1.days)
         | 
| 71 | 
            +
            		end
         | 
| 72 | 
            +
            	end
         | 
| 73 | 
            +
            end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
            describe ComplexUser do
         | 
| 76 | 
            +
            	
         | 
| 77 | 
            +
            	it 'should have possible subscription plans' do
         | 
| 78 | 
            +
            		ComplexUser.subscription_plans.should == {:mild => {:price => 0.00}, :medium => {:price => 5.00}, :spicy => {:price => 10.00}}
         | 
| 79 | 
            +
            	end
         | 
| 80 | 
            +
            	it 'should have a default plan' do
         | 
| 81 | 
            +
            		ComplexUser.default_subscription_plan.should eql(:medium)
         | 
| 82 | 
            +
            	end
         | 
| 83 | 
            +
            	it 'should have billing settings' do
         | 
| 84 | 
            +
            		ComplexUser.billing_settings.should == {:period => 31.days, :grace_period => 6.days}
         | 
| 85 | 
            +
            	end
         | 
| 86 | 
            +
            	it 'should have trial settings' do
         | 
| 87 | 
            +
            		ComplexUser.trial_settings.should == {:period => 31.days, :reminder=>6.days}
         | 
| 88 | 
            +
            	end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
            	it 'should hand plan as a hash' do
         | 
| 91 | 
            +
            		user = ComplexUser.new
         | 
| 92 | 
            +
            		user.plan = ComplexUser.subscription_plans.values.first
         | 
| 93 | 
            +
            		user.subscription_key.should eql(ComplexUser.subscription_plans.keys.first)
         | 
| 94 | 
            +
            	end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
            	describe 'should handle next due correctly' do
         | 
| 97 | 
            +
            		it 'on free' do
         | 
| 98 | 
            +
            			user = ComplexUser.new
         | 
| 99 | 
            +
            			user.plan = :mild
         | 
| 100 | 
            +
            			user.next_due.should be_false
         | 
| 101 | 
            +
            		end
         | 
| 102 | 
            +
            		it 'on past due' do
         | 
| 103 | 
            +
            			user = ComplexUser.new
         | 
| 104 | 
            +
            			user.plan = :spicy
         | 
| 105 | 
            +
            			user.paid_until = Date.today - 6.days
         | 
| 106 | 
            +
            			user.trial_until = Date.today - 37.days
         | 
| 107 | 
            +
            			user.next_due.should eql(Date.today)
         | 
| 108 | 
            +
            		end
         | 
| 109 | 
            +
            		it 'on paid' do
         | 
| 110 | 
            +
            			user = ComplexUser.new
         | 
| 111 | 
            +
            			user.plan = :spicy
         | 
| 112 | 
            +
            			user.paid_until = Date.today + 6.days
         | 
| 113 | 
            +
            			user.trial_until = Date.today - 31.days + 6.days
         | 
| 114 | 
            +
            			user.next_due.should eql(Date.today+6.days)
         | 
| 115 | 
            +
            		end
         | 
| 116 | 
            +
            		it 'on upgrade' do
         | 
| 117 | 
            +
            			user = ComplexUser.new
         | 
| 118 | 
            +
            			user.plan = :mild
         | 
| 119 | 
            +
            			user.trial_until = Date.today - 30.days
         | 
| 120 | 
            +
            			user.paid_until = Date.today - 1.days
         | 
| 121 | 
            +
            			user.plan = :spicy
         | 
| 122 | 
            +
            			user.next_due.should eql(Date.today)
         | 
| 123 | 
            +
            		end
         | 
| 124 | 
            +
            		it 'on downgrade' do
         | 
| 125 | 
            +
            			user = ComplexUser.new
         | 
| 126 | 
            +
            			user.plan = :spicy
         | 
| 127 | 
            +
            			user.trial_until = Date.today - 30.days
         | 
| 128 | 
            +
            			user.paid_until = Date.today - 1.days
         | 
| 129 | 
            +
            			user.plan = :medium
         | 
| 130 | 
            +
            			user.next_due.should eql(Date.today)
         | 
| 131 | 
            +
            		end
         | 
| 132 | 
            +
            	end
         | 
| 133 | 
            +
            	
         | 
| 134 | 
            +
            	describe 'should handle trial period' do
         | 
| 135 | 
            +
            		it 'sets trial' do
         | 
| 136 | 
            +
            			user = ComplexUser.new
         | 
| 137 | 
            +
            			user.plan = :mild
         | 
| 138 | 
            +
            			user.trial_until.should eql(Date.today + 31.days)
         | 
| 139 | 
            +
            		end
         | 
| 140 | 
            +
            	end
         | 
| 141 | 
            +
            	
         | 
| 142 | 
            +
            	describe 'should handle grace period' do
         | 
| 143 | 
            +
            		it 'on not past due' do
         | 
| 144 | 
            +
            			user = ComplexUser.new
         | 
| 145 | 
            +
            			user.should_receive(:past_due).and_return(false)
         | 
| 146 | 
            +
            			user.past_grace?.should be_false
         | 
| 147 | 
            +
            		end
         | 
| 148 | 
            +
            		0.upto(6) do |x|
         | 
| 149 | 
            +
            			it "on past due by #{x} days" do
         | 
| 150 | 
            +
            				user = ComplexUser.new
         | 
| 151 | 
            +
            				user.should_receive(:past_due).twice.and_return(x.days)
         | 
| 152 | 
            +
            				user.past_grace?.should eql(x > 5)
         | 
| 153 | 
            +
            			end
         | 
| 154 | 
            +
            		end
         | 
| 155 | 
            +
            	end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
            	describe 'should handle due correctly' do
         | 
| 158 | 
            +
            	 	describe 'past due' do
         | 
| 159 | 
            +
            			it 'should work via past_due method' do
         | 
| 160 | 
            +
            				user = ComplexUser.new
         | 
| 161 | 
            +
            				user.plan = :spicy
         | 
| 162 | 
            +
            				user.paid_until = Date.today - 6.days
         | 
| 163 | 
            +
            				user.trial_until = Date.today - 37.days
         | 
| 164 | 
            +
            				user.past_due.should eql(6.days)
         | 
| 165 | 
            +
            			end
         | 
| 166 | 
            +
            			it 'should handle trials' do
         | 
| 167 | 
            +
            				user = ComplexUser.new
         | 
| 168 | 
            +
            				user.plan = :spicy
         | 
| 169 | 
            +
            				user.paid_until = nil
         | 
| 170 | 
            +
            				user.trial_until = Date.today - 1.days
         | 
| 171 | 
            +
            				user.past_due.should eql(1.days)
         | 
| 172 | 
            +
            			end
         | 
| 173 | 
            +
            		end
         | 
| 174 | 
            +
            		describe 'not overdue' do
         | 
| 175 | 
            +
            			it 'should work via past_due method' do
         | 
| 176 | 
            +
            				user = ComplexUser.new
         | 
| 177 | 
            +
            				user.plan = :spicy
         | 
| 178 | 
            +
            				user.paid_until = Date.today + 6.days
         | 
| 179 | 
            +
            				user.trial_until = Date.today - 31.days + 6.days
         | 
| 180 | 
            +
            				user.past_due.should be_false
         | 
| 181 | 
            +
            			end
         | 
| 182 | 
            +
            			it 'should handle end of trial' do
         | 
| 183 | 
            +
            				user = ComplexUser.new
         | 
| 184 | 
            +
            				user.plan = :spicy
         | 
| 185 | 
            +
            				user.paid_until = nil
         | 
| 186 | 
            +
            				user.trial_until = nil
         | 
| 187 | 
            +
            				user.past_due.should be_false
         | 
| 188 | 
            +
            			end
         | 
| 189 | 
            +
            			it 'should handle trials' do
         | 
| 190 | 
            +
            				user = ComplexUser.new
         | 
| 191 | 
            +
            				user.plan = :spicy
         | 
| 192 | 
            +
            				user.paid_until = nil
         | 
| 193 | 
            +
            				user.trial_until = Date.today + 1.days
         | 
| 194 | 
            +
            				user.past_due.should be_false
         | 
| 195 | 
            +
            			end
         | 
| 196 | 
            +
            		end
         | 
| 197 | 
            +
            		describe 'free account' do
         | 
| 198 | 
            +
            			it 'should work via past_due trial' do
         | 
| 199 | 
            +
            				user = ComplexUser.new
         | 
| 200 | 
            +
            				user.plan = :mild
         | 
| 201 | 
            +
            				user.paid_until = Date.today - 6.days
         | 
| 202 | 
            +
            				user.trial_until = Date.today - 37.days
         | 
| 203 | 
            +
            				user.past_due.should be_false
         | 
| 204 | 
            +
            			end
         | 
| 205 | 
            +
            			it 'should handle past_due trials' do
         | 
| 206 | 
            +
            				user = ComplexUser.new
         | 
| 207 | 
            +
            				user.plan = :mild
         | 
| 208 | 
            +
            				user.paid_until = nil
         | 
| 209 | 
            +
            				user.trial_until = Date.today - 1.days
         | 
| 210 | 
            +
            				user.past_due.should be_false
         | 
| 211 | 
            +
            			end
         | 
| 212 | 
            +
            			it 'should work via trial' do
         | 
| 213 | 
            +
            				user = ComplexUser.new
         | 
| 214 | 
            +
            				user.plan = :mild
         | 
| 215 | 
            +
            				user.paid_until = Date.today + 6.days
         | 
| 216 | 
            +
            				user.trial_until = Date.today - 31.days + 6.days
         | 
| 217 | 
            +
            				user.past_due.should be_false
         | 
| 218 | 
            +
            			end
         | 
| 219 | 
            +
            			it 'should handle trials' do
         | 
| 220 | 
            +
            				user = ComplexUser.new
         | 
| 221 | 
            +
            				user.plan = :mild
         | 
| 222 | 
            +
            				user.paid_until = nil
         | 
| 223 | 
            +
            				user.trial_until = Date.today + 1.days
         | 
| 224 | 
            +
            				user.past_due.should be_false
         | 
| 225 | 
            +
            			end
         | 
| 226 | 
            +
            		end
         | 
| 227 | 
            +
            	end
         | 
| 228 | 
            +
             | 
| 229 | 
            +
            end
         | 
| 230 | 
            +
             | 
| 231 | 
            +
            describe UserWithoutTrial do
         | 
| 232 | 
            +
            	
         | 
| 233 | 
            +
            	it 'has no trial' do
         | 
| 234 | 
            +
            		UserWithoutTrial.trial_settings.should == {:period=>0.days, :reminder=>5.days}
         | 
| 235 | 
            +
            	end
         | 
| 236 | 
            +
             | 
| 237 | 
            +
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification 
         | 
| 2 2 | 
             
            name: redline
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version 
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.2.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors: 
         | 
| 7 7 | 
             
            - James Daniels
         | 
| @@ -9,7 +9,7 @@ autorequire: | |
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 11 |  | 
| 12 | 
            -
            date: 2010-02- | 
| 12 | 
            +
            date: 2010-02-10 00:00:00 -05:00
         | 
| 13 13 | 
             
            default_executable: 
         | 
| 14 14 | 
             
            dependencies: 
         | 
| 15 15 | 
             
            - !ruby/object:Gem::Dependency 
         | 
| @@ -52,7 +52,7 @@ dependencies: | |
| 52 52 | 
             
                  - !ruby/object:Gem::Version 
         | 
| 53 53 | 
             
                    version: "0"
         | 
| 54 54 | 
             
                version: 
         | 
| 55 | 
            -
            description:  | 
| 55 | 
            +
            description: Syncs your AR models with Braintree (Payment Gateway) and offers a lightweight reoccurring billing script
         | 
| 56 56 | 
             
            email: james@marginleft.com
         | 
| 57 57 | 
             
            executables: []
         | 
| 58 58 |  | 
| @@ -69,14 +69,23 @@ files: | |
| 69 69 | 
             
            - Rakefile
         | 
| 70 70 | 
             
            - VERSION
         | 
| 71 71 | 
             
            - lib/redline.rb
         | 
| 72 | 
            -
            - lib/redline/ | 
| 72 | 
            +
            - lib/redline/billing/base.rb
         | 
| 73 | 
            +
            - lib/redline/customer/base.rb
         | 
| 74 | 
            +
            - lib/redline/customer/instance.rb
         | 
| 75 | 
            +
            - lib/redline/customer/settings.rb
         | 
| 76 | 
            +
            - lib/redline/subscription/base.rb
         | 
| 77 | 
            +
            - lib/redline/subscription/instance.rb
         | 
| 78 | 
            +
            - lib/redline/subscription/settings.rb
         | 
| 73 79 | 
             
            - redline.gemspec
         | 
| 80 | 
            +
            - spec/billing_spec.rb
         | 
| 81 | 
            +
            - spec/customer_spec.rb
         | 
| 74 82 | 
             
            - spec/db/database.yml
         | 
| 75 83 | 
             
            - spec/db/models.rb
         | 
| 76 84 | 
             
            - spec/db/schema.rb
         | 
| 77 85 | 
             
            - spec/redline_spec.rb
         | 
| 78 86 | 
             
            - spec/spec.opts
         | 
| 79 87 | 
             
            - spec/spec_helper.rb
         | 
| 88 | 
            +
            - spec/subscription_spec.rb
         | 
| 80 89 | 
             
            has_rdoc: true
         | 
| 81 90 | 
             
            homepage: http://github.com/jamesdaniels/redline
         | 
| 82 91 | 
             
            licenses: []
         | 
| @@ -104,9 +113,12 @@ rubyforge_project: | |
| 104 113 | 
             
            rubygems_version: 1.3.5
         | 
| 105 114 | 
             
            signing_key: 
         | 
| 106 115 | 
             
            specification_version: 3
         | 
| 107 | 
            -
            summary:  | 
| 116 | 
            +
            summary: Syncs your AR models with Braintree (Payment Gateway) and offers a lightweight reoccurring billing script
         | 
| 108 117 | 
             
            test_files: 
         | 
| 118 | 
            +
            - spec/billing_spec.rb
         | 
| 119 | 
            +
            - spec/customer_spec.rb
         | 
| 109 120 | 
             
            - spec/db/models.rb
         | 
| 110 121 | 
             
            - spec/db/schema.rb
         | 
| 111 122 | 
             
            - spec/redline_spec.rb
         | 
| 112 123 | 
             
            - spec/spec_helper.rb
         | 
| 124 | 
            +
            - spec/subscription_spec.rb
         |