freemium 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. data/.gitignore +53 -0
  2. data/Gemfile +4 -0
  3. data/Gemfile.lock +121 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.rdoc +67 -0
  6. data/Rakefile +23 -0
  7. data/autotest/discover.rb +2 -0
  8. data/config/locales/en.yml +2 -0
  9. data/freemium.gemspec +28 -0
  10. data/lib/freemium/address.rb +18 -0
  11. data/lib/freemium/coupon.rb +38 -0
  12. data/lib/freemium/coupon_redemption.rb +48 -0
  13. data/lib/freemium/credit_card.rb +273 -0
  14. data/lib/freemium/feature_set.rb +45 -0
  15. data/lib/freemium/gateways/base.rb +65 -0
  16. data/lib/freemium/gateways/brain_tree.rb +175 -0
  17. data/lib/freemium/gateways/test.rb +34 -0
  18. data/lib/freemium/manual_billing.rb +73 -0
  19. data/lib/freemium/railtie.tb +7 -0
  20. data/lib/freemium/rates.rb +33 -0
  21. data/lib/freemium/recurring_billing.rb +59 -0
  22. data/lib/freemium/response.rb +24 -0
  23. data/lib/freemium/subscription.rb +350 -0
  24. data/lib/freemium/subscription_change.rb +20 -0
  25. data/lib/freemium/subscription_mailer/admin_report.rhtml +4 -0
  26. data/lib/freemium/subscription_mailer/expiration_notice.rhtml +1 -0
  27. data/lib/freemium/subscription_mailer/expiration_warning.rhtml +1 -0
  28. data/lib/freemium/subscription_mailer/invoice.text.plain.erb +5 -0
  29. data/lib/freemium/subscription_mailer.rb +36 -0
  30. data/lib/freemium/subscription_plan.rb +32 -0
  31. data/lib/freemium/transaction.rb +15 -0
  32. data/lib/freemium/version.rb +3 -0
  33. data/lib/freemium.rb +75 -0
  34. data/lib/generators/active_record/freemium_generator.rb +28 -0
  35. data/lib/generators/active_record/templates/migrations/account_transactions.rb +17 -0
  36. data/lib/generators/active_record/templates/migrations/coupon_redemptions.rb +18 -0
  37. data/lib/generators/active_record/templates/migrations/coupons.rb +28 -0
  38. data/lib/generators/active_record/templates/migrations/credit_cards.rb +14 -0
  39. data/lib/generators/active_record/templates/migrations/subscription_changes.rb +18 -0
  40. data/lib/generators/active_record/templates/migrations/subscription_plans.rb +14 -0
  41. data/lib/generators/active_record/templates/migrations/subscriptions.rb +30 -0
  42. data/lib/generators/active_record/templates/models/account_transaction.rb +3 -0
  43. data/lib/generators/active_record/templates/models/coupon.rb +3 -0
  44. data/lib/generators/active_record/templates/models/coupon_redemption.rb +3 -0
  45. data/lib/generators/active_record/templates/models/credit_card.rb +3 -0
  46. data/lib/generators/active_record/templates/models/subscription.rb +3 -0
  47. data/lib/generators/active_record/templates/models/subscription_change.rb +3 -0
  48. data/lib/generators/active_record/templates/models/subscription_plan.rb +3 -0
  49. data/lib/generators/freemium/freemium_generator.rb +15 -0
  50. data/lib/generators/freemium/install_generator.rb +28 -0
  51. data/lib/generators/freemium/orm_helpers.rb +27 -0
  52. data/lib/generators/templates/freemium.rb +43 -0
  53. data/lib/generators/templates/freemium_feature_sets.yml +5 -0
  54. data/spec/dummy/Rakefile +7 -0
  55. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  56. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  57. data/spec/dummy/app/models/models.rb +32 -0
  58. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  59. data/spec/dummy/config/application.rb +45 -0
  60. data/spec/dummy/config/boot.rb +10 -0
  61. data/spec/dummy/config/database.yml +22 -0
  62. data/spec/dummy/config/environment.rb +5 -0
  63. data/spec/dummy/config/environments/development.rb +26 -0
  64. data/spec/dummy/config/environments/production.rb +49 -0
  65. data/spec/dummy/config/environments/test.rb +35 -0
  66. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  67. data/spec/dummy/config/initializers/inflections.rb +10 -0
  68. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  69. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  70. data/spec/dummy/config/initializers/session_store.rb +8 -0
  71. data/spec/dummy/config/locales/en.yml +5 -0
  72. data/spec/dummy/config/routes.rb +58 -0
  73. data/spec/dummy/config.ru +4 -0
  74. data/spec/dummy/db/schema.rb +92 -0
  75. data/spec/dummy/script/rails +6 -0
  76. data/spec/fixtures/credit_cards.yml +11 -0
  77. data/spec/fixtures/subscription_plans.yml +18 -0
  78. data/spec/fixtures/subscriptions.yml +29 -0
  79. data/spec/fixtures/users.yml +16 -0
  80. data/spec/freemium_feature_sets.yml +9 -0
  81. data/spec/freemium_spec.rb +4 -0
  82. data/spec/models/coupon_redemption_spec.rb +235 -0
  83. data/spec/models/credit_card_spec.rb +114 -0
  84. data/spec/models/manual_billing_spec.rb +174 -0
  85. data/spec/models/recurring_billing_spec.rb +92 -0
  86. data/spec/models/subscription_plan_spec.rb +44 -0
  87. data/spec/models/subscription_spec.rb +386 -0
  88. data/spec/spec_helper.rb +38 -0
  89. data/spec/support/helpers.rb +21 -0
  90. metadata +298 -0
@@ -0,0 +1,174 @@
1
+ require 'spec_helper'
2
+
3
+ describe Subscription do
4
+ fixtures :users, :subscriptions, :subscription_plans, :credit_cards
5
+
6
+
7
+ before(:each) do
8
+ Freemium.gateway = Freemium::Gateways::Test.new
9
+ end
10
+
11
+ it "should find billable" do
12
+ # making a one-off fixture set, basically
13
+ create_billable_subscription # this subscription should be billable
14
+ create_billable_subscription(:paid_through => Date.today) # this subscription should be billable
15
+ create_billable_subscription(:coupon => Coupon.create!(:description => "Complimentary", :discount_percentage => 100)) # should NOT be billable because it's free
16
+ create_billable_subscription(:subscription_plan => subscription_plans(:free)) # should NOT be billable because it's free
17
+ create_billable_subscription(:paid_through => Date.today + 1) # should NOT be billable because it's paid far enough out
18
+ s = create_billable_subscription # should be billable because it's past due
19
+ s.update_attribute :expire_on, Date.today + 1
20
+
21
+ expirable = Subscription.send(:find_billable)
22
+ expirable.all? { |subscription| subscription.paid? }.should be_true, "free subscriptions aren't billable"
23
+ expirable.all? { |subscription| !subscription.in_trial? }.should be_true, "subscriptions that have been paid are no longer in the trial period"
24
+ expirable.all? { |subscription| subscription.paid_through <= Date.today }.should be_true, "subscriptions paid through tomorrow aren't billable yet"
25
+ expirable.size.should eql(3)
26
+
27
+ Freemium.gateway.stub!(:charge).and_return(
28
+ AccountTransaction.new(
29
+ :billing_key => s.billing_key,
30
+ :amount => s.rate,
31
+ :success => false
32
+ )
33
+ )
34
+
35
+ Subscription.run_billing.size.should eql(expirable.size)
36
+ end
37
+
38
+ it "should not change expire_on on failure overdue payment" do
39
+ subscription = create_billable_subscription # should NOT be billable because it's already expiring
40
+ expire_on = Date.today + 2
41
+ paid_through = subscription.paid_through
42
+ subscription.update_attribute :expire_on, expire_on
43
+ subscription.reload
44
+ subscription.expire_on.should eql(expire_on)
45
+ expirable = Subscription.send(:find_billable)
46
+ expirable.size.should eql(1), "Subscriptions in their grace period should be retried"
47
+
48
+ Freemium.gateway.stub!(:charge).and_return(
49
+ AccountTransaction.new(
50
+ :billing_key => subscription.billing_key,
51
+ :amount => subscription.rate,
52
+ :success => false
53
+ )
54
+ )
55
+
56
+ lambda do
57
+ transaction = subscription.charge!
58
+ subscription.expire_on.should eql(expire_on), "Billing failed on existing overdue account but the expire_on date was changed"
59
+ end.should_not raise_error
60
+ end
61
+
62
+ it "should change expire_on on success overdue payment" do
63
+ subscription = create_billable_subscription # should NOT be billable because it's already expiring
64
+ expire_on = Date.today + 2
65
+ paid_through = subscription.paid_through
66
+ subscription.update_attribute :expire_on, expire_on
67
+
68
+ expirable = Subscription.send(:find_billable)
69
+ expirable.size.should eql(1), "Subscriptions in their grace period should be retried"
70
+
71
+ Freemium.gateway.stub!(:charge).and_return(
72
+ AccountTransaction.new(
73
+ :billing_key => subscription.billing_key,
74
+ :amount => subscription.rate,
75
+ :success => true
76
+ )
77
+ )
78
+
79
+ assert_nothing_raised do
80
+ transaction = subscription.charge!
81
+ transaction.subscription.paid_through.to_s.should eql((paid_through >> 1).to_s), "extended by a month"
82
+ subscription.expire_on.should be_nil, "Billing succeeded on existing overdue account but the expire_on date was not reset"
83
+ end
84
+ end
85
+
86
+ it "should charge a subscription" do
87
+ subscription = Subscription.first
88
+ subscription.coupon = Coupon.create!(:description => "Complimentary", :discount_percentage => 30)
89
+ subscription.save!
90
+
91
+ paid_through = subscription.paid_through
92
+
93
+ Freemium.gateway.stub!(:charge).and_return(
94
+ AccountTransaction.new(
95
+ :billing_key => subscription.billing_key,
96
+ :amount => subscription.rate,
97
+ :success => true
98
+ )
99
+ )
100
+
101
+ lambda do
102
+ transaction = subscription.charge!
103
+ transaction.subscription.paid_through.to_s.should eql((paid_through >> 1).to_s)#, "extended by a month"
104
+ end.should_not raise_error
105
+
106
+ subscription = subscription.reload
107
+ subscription.transactions.empty?.should be_false
108
+ subscription.transactions.last.should be_true
109
+ subscription.transactions.last.success?.should be_true
110
+ subscription.transactions.last.message?.should_not be_nil
111
+ ((Time.now - 1.minute) < subscription.last_transaction_at).should be_true
112
+ AccountTransaction.since(Date.today).empty?.should be_false
113
+ subscription.transactions.last.amount.should eql(subscription.rate)
114
+ subscription.reload.paid_through.to_s.should eql((paid_through >> 1).to_s), "extended by a month"
115
+ end
116
+
117
+
118
+ it "should charge an aborted subscription" do
119
+ subscription = Subscription.first
120
+ subscription.coupon = Coupon.create!(:description => "Complimentary", :discount_percentage => 30)
121
+ subscription.save!
122
+
123
+ paid_through = subscription.paid_through
124
+ subscription.transactions.empty?.should be_true
125
+
126
+ Freemium.gateway.stub!(:charge).and_return(
127
+ AccountTransaction.new(
128
+ :billing_key => subscription.billing_key,
129
+ :amount => subscription.rate,
130
+ :success => true
131
+ )
132
+ )
133
+
134
+ subscription.should_receive(:receive_payment).and_raise(RuntimeError)
135
+ subscription.charge!
136
+ subscription.reload.transactions.empty?.should be_false
137
+ end
138
+
139
+ it "should not charge a subscription" do
140
+ subscription = Subscription.first
141
+ paid_through = subscription.paid_through
142
+ Freemium.gateway.stub!(:charge).and_return(
143
+ AccountTransaction.new(
144
+ :billing_key => subscription.billing_key,
145
+ :amount => subscription.rate,
146
+ :success => false
147
+ )
148
+ )
149
+
150
+ subscription.expire_on.should be_nil
151
+ lambda { subscription.charge! }.should_not raise_error
152
+ subscription.reload.paid_through.should eql(paid_through), "not extended"
153
+ subscription.expire_on.should_not be_nil
154
+ subscription.transactions.last.success?.should be_false
155
+ end
156
+
157
+ it "should receive charge! on billable when we run billing" do
158
+ subscription = Subscription.first
159
+ Subscription.stub!(:find_billable => [subscription])
160
+ subscription.should_receive(:charge!).once
161
+ Subscription.send :run_billing
162
+ end
163
+
164
+ protected
165
+
166
+ def create_billable_subscription(options = {})
167
+ Subscription.create!({
168
+ :subscription_plan => subscription_plans(:premium),
169
+ :subscribable => User.new(:name => 'a'),
170
+ :paid_through => Date.today - 1,
171
+ :credit_card => CreditCard.sample
172
+ }.merge(options))
173
+ end
174
+ end
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ describe Subscription do
5
+ fixtures :users, :subscriptions, :subscription_plans, :credit_cards
6
+
7
+
8
+ before(:each) do
9
+ class Subscription
10
+ include Freemium::RecurringBilling
11
+ end
12
+ Freemium.gateway = Freemium::Gateways::Test.new
13
+ end
14
+
15
+ it "should run billing" do
16
+ Subscription.should_receive(:process_transactions).once
17
+ Subscription.should_receive(:find_expirable).once.and_return([])
18
+ Subscription.should_receive(:expire).once
19
+ Subscription.run_billing
20
+ end
21
+
22
+ it "should send reports" do
23
+ Subscription.stub!(:process_transactions)
24
+ Freemium.stub!(:admin_report_recipients).and_return("test@example.com")
25
+
26
+ Freemium.mailer.should_receive(:deliver_admin_report)
27
+ Subscription.run_billing
28
+ end
29
+
30
+ it "should find expireable subscriptions" do
31
+ # making a one-off fixture set, basically
32
+ create_billable_subscription # this subscription qualifies
33
+ create_billable_subscription(:subscription_plan => subscription_plans(:free)) # this subscription would qualify, except it's for the free plan
34
+ create_billable_subscription(:paid_through => Date.today) # this subscription would qualify, except it's already paid
35
+ create_billable_subscription(:coupon => Coupon.create!(:description => "Complimentary", :discount_percentage => 100)) # should NOT be billable because it's free
36
+ s = create_billable_subscription # this subscription would qualify, except it's already been set to expire
37
+ s.update_attribute :expire_on, Date.today + 1
38
+
39
+ expirable = Subscription.send(:find_expirable)
40
+ expirable.all? { |subscription| subscription.paid? }.should be_true, "free subscriptions don't expire"
41
+ expirable.all? { |subscription| !subscription.in_trial? }.should be_true, "subscriptions that have been paid are no longer in the trial period"
42
+ expirable.all? { |subscription| subscription.paid_through < Date.today }.should be_true, "paid subscriptions don't expire"
43
+ expirable.all? { |subscription|
44
+ !subscription.expire_on or subscription.expire_on < subscription.paid_through }.should be_true, "subscriptions already expiring aren't included"
45
+
46
+ expirable.size.should eql(1)
47
+ end
48
+
49
+ it "should process new transactions" do
50
+ subscription = subscriptions(:bobs_subscription)
51
+ subscription.coupon = Coupon.create!(:description => "Complimentary", :discount_percentage => 30)
52
+ subscription.save!
53
+
54
+ paid_through = subscription.paid_through
55
+ t = AccountTransaction.new(:billing_key => subscription.billing_key, :amount => subscription.rate, :success => true)
56
+ Subscription.stub!(:new_transactions => [t])
57
+
58
+ # the actual test
59
+ Subscription.send :process_transactions
60
+ subscription.reload.paid_through.to_s.should eql((paid_through + 1.month).to_s), "extended by two months"
61
+ end
62
+
63
+ it "should process a failed transaction" do
64
+ subscription = subscriptions(:bobs_subscription)
65
+ paid_through = subscription.paid_through
66
+ t = AccountTransaction.new(:billing_key => subscription.billing_key, :amount => subscription.rate, :success => false)
67
+ Subscription.stub!(:new_transactions => [t])
68
+
69
+ # the actual test
70
+ subscription.expire_on.should be_nil
71
+ Subscription.send :process_transactions
72
+ subscription.reload.paid_through.should eql(paid_through), "not extended"
73
+ subscription.expire_on.should_not be_nil
74
+ end
75
+
76
+ it "should find new transactions" do
77
+ last_transaction_at = Subscription.maximum(:last_transaction_at)
78
+ method_args = Subscription.send(:new_transactions)
79
+ method_args[:after].should eql(last_transaction_at)
80
+ end
81
+
82
+ protected
83
+
84
+ def create_billable_subscription(options = {})
85
+ Subscription.create!({
86
+ :subscription_plan => subscription_plans(:premium),
87
+ :subscribable => User.new(:name => 'a'),
88
+ :paid_through => Date.today - 1,
89
+ :credit_card => CreditCard.sample
90
+ }.merge(options))
91
+ end
92
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe SubscriptionPlan do
4
+ fixtures :users, :subscriptions, :subscription_plans, :credit_cards
5
+
6
+ it "should have associations" do
7
+ [subscriptions(:bobs_subscription)].should == (subscription_plans(:basic).subscriptions)
8
+ end
9
+
10
+ it "should have rate intervals" do
11
+ plan = SubscriptionPlan.new(:rate_cents => 3041)
12
+ plan.daily_rate.should eql(Money.new(99))
13
+ plan.monthly_rate.should eql(Money.new(3041))
14
+ plan.yearly_rate.should eql(Money.new(36492))
15
+ end
16
+
17
+ it "should create plan" do
18
+ plan = create_plan
19
+ plan.new_record?.should be_false
20
+ end
21
+
22
+ it "should have errors" do
23
+ [:name, :rate_cents].each do |field|
24
+ plan = create_plan(field => nil)
25
+ plan.new_record?.should be_true
26
+ plan.should have(1).errors_on(field)
27
+ end
28
+ end
29
+
30
+ it "should have ads" do
31
+ subscription_plans(:free).features.ads?.should_not be_nil
32
+ end
33
+
34
+ protected
35
+
36
+ def create_plan(options = {})
37
+ SubscriptionPlan.create({
38
+ :name => 'super-duper-ultra-premium',
39
+ :redemption_key => 'super-duper-ultra-premium',
40
+ :rate_cents => 99995,
41
+ :feature_set_id => :premium
42
+ }.merge(options))
43
+ end
44
+ end
@@ -0,0 +1,386 @@
1
+ require 'spec_helper'
2
+
3
+ describe Subscription do
4
+ fixtures :users, :subscriptions, :subscription_plans, :credit_cards
5
+
6
+ def build_subscription(options = {})
7
+ Subscription.new({
8
+ :subscription_plan => subscription_plans(:free),
9
+ :subscribable => users(:sue)
10
+ }.merge(options))
11
+ end
12
+
13
+ def create_transaction_for(amount, subscription)
14
+ AccountTransaction.create :amount => amount, :subscription => subscription, :success => true, :billing_key => 12345
15
+ end
16
+
17
+ def assert_changed(subscribable, reason, original_plan, new_plan)
18
+ changes = SubscriptionChange.where(["subscribable_id = ? AND subscribable_type = ?", subscribable.id, subscribable.class.to_s]).last
19
+ changes.should_not be_nil
20
+ changes.reason.should eql(reason.to_s)
21
+ changes.original_subscription_plan.should eql(original_plan)
22
+ changes.new_subscription_plan.should eq(new_plan)
23
+ changes.original_rate_cents.should eql(original_plan ? original_plan.rate.cents : 0)
24
+ changes.new_rate_cents.should eql(new_plan ? new_plan.rate.cents : 0)
25
+ end
26
+
27
+ it "should create free subscription" do
28
+ subscription = build_subscription(:subscription_plan => subscription_plans(:free))
29
+ subscription.save!
30
+
31
+ subscription.should_not be_new_record
32
+ subscription.reload.started_on.should eql(Date.today)
33
+ subscription.in_trial?.should be_false
34
+ subscription.paid_through.should be_nil
35
+ subscription.paid?.should be_false
36
+
37
+ #TODO understand what is that
38
+ assert_changed(subscription.subscribable, :new, nil, subscription_plans(:free))
39
+ end
40
+
41
+ it "should create paid subscription" do
42
+ Freemium.days_free_trial = 30
43
+
44
+ subscription = build_subscription(:subscription_plan => subscription_plans(:basic), :credit_card => CreditCard.sample)
45
+ subscription.save!
46
+
47
+ subscription.should_not be_new_record
48
+ subscription.reload.started_on.should eql(Date.today)
49
+ subscription.in_trial?.should be_true
50
+ subscription.paid_through.should_not be_nil
51
+ subscription.paid_through.should eql(Date.today + Freemium.days_free_trial)
52
+ subscription.paid?.should be_true
53
+ subscription.billing_key.should_not be_nil
54
+
55
+ assert_changed(subscription.subscribable, :new, nil, subscription_plans(:basic))
56
+ end
57
+
58
+ it "should be upgraded from free" do
59
+ subscription = build_subscription(:subscription_plan => subscription_plans(:free))
60
+ subscription.save!
61
+
62
+ new_date = Date.today + 10.days
63
+ Date.stub!(:today => new_date)
64
+
65
+ subscription.in_trial?.should be_false
66
+ subscription.subscription_plan = subscription_plans(:basic)
67
+ subscription.credit_card = CreditCard.sample
68
+ subscription.save!
69
+
70
+ subscription.reload.started_on.should eql(new_date)
71
+ subscription.paid_through.should_not be_nil
72
+ subscription.in_trial?.should be_false
73
+ subscription.paid_through.should eql(new_date)
74
+ subscription.paid?.should be_true
75
+ subscription.billing_key.should_not be_nil
76
+
77
+ assert_changed(subscription.subscribable, :upgrade, subscription_plans(:free), subscription_plans(:basic))
78
+ end
79
+
80
+ it "should be downgraded" do
81
+ subscription = build_subscription(:subscription_plan => subscription_plans(:basic), :credit_card => CreditCard.sample)
82
+ subscription.save!
83
+
84
+ new_date = Date.today + 10.days
85
+ Date.stub!(:today => new_date)
86
+
87
+ subscription.subscription_plan = subscription_plans(:free)
88
+ subscription.save!
89
+
90
+ subscription.reload.started_on.should eql(new_date)
91
+ subscription.paid_through.should be_nil
92
+ subscription.paid?.should be_false
93
+ subscription.billing_key.should be_nil
94
+ subscription.credit_card.should be_nil
95
+
96
+ assert_changed(subscription.subscribable, :downgrade, subscription_plans(:basic), subscription_plans(:free))
97
+ end
98
+
99
+ it "should have associations" do
100
+ assert_equal users(:bob), subscriptions(:bobs_subscription).subscribable
101
+ assert_equal subscription_plans(:basic), subscriptions(:bobs_subscription).subscription_plan
102
+ end
103
+
104
+ it "should have remaining_days" do
105
+ assert_equal 20, subscriptions(:bobs_subscription).remaining_days
106
+ end
107
+
108
+ it "should have remaining_value" do
109
+ assert_equal Money.new(840), subscriptions(:bobs_subscription).remaining_value
110
+ end
111
+
112
+ ##
113
+ ## Upgrade / Downgrade service credits
114
+ ##
115
+
116
+ it "should upgrade credit" do
117
+ subscription = subscriptions(:bobs_subscription)
118
+ new_plan = subscription_plans(:premium)
119
+
120
+ subscription.remaining_value.cents.should > 0
121
+ expected_paid_through = Date.today + (subscription.remaining_value.cents / new_plan.daily_rate.cents)
122
+ subscription.subscription_plan = subscription_plans(:premium)
123
+ subscription.save!
124
+
125
+ subscription.paid_through.should eql(expected_paid_through)
126
+ end
127
+
128
+ it "should upgrade no credit for free trial" do
129
+ subscription = build_subscription(:subscription_plan => subscription_plans(:premium), :credit_card => CreditCard.sample)
130
+ subscription.save!
131
+
132
+ subscription.paid_through.should eql(Date.today + Freemium.days_free_trial)
133
+ subscription.in_trial?.should be_true
134
+
135
+ subscription.subscription_plan = subscription_plans(:basic)
136
+ subscription.save!
137
+
138
+ subscription.paid_through.should eql(Date.today)
139
+ subscription.in_trial?.should be_false
140
+ end
141
+
142
+ ##
143
+ ## Validations
144
+ ##
145
+
146
+ it "should test missing fields" do
147
+ [:subscription_plan, :subscribable].each do |field|
148
+ subscription = build_subscription(field => nil)
149
+ subscription.save
150
+
151
+ subscription.should be_new_record
152
+ subscription.should have(1).errors_on(field)
153
+ end
154
+ end
155
+
156
+ ##
157
+ ## Receiving payment
158
+ ##
159
+
160
+ it "should receive monthly payment" do
161
+ subscription = subscriptions(:bobs_subscription)
162
+ paid_through = subscription.paid_through
163
+ subscription.credit(subscription_plans(:basic).rate)
164
+ subscription.save!
165
+
166
+ subscription.paid_through.to_s.should eql((paid_through >> 1).to_s) #extended by one month
167
+ subscription.transactions.should_not be_nil
168
+ end
169
+
170
+ its "should receive quarterly payment" do
171
+ subscription = subscriptions(:bobs_subscription)
172
+ paid_through = subscription.paid_through
173
+ subscription.credit(subscription_plans(:basic).rate * 3)
174
+ subscription.save!
175
+ subscription.paid_through.to_s.should eql((paid_through >> 3).to_s) #extended by three months
176
+ end
177
+
178
+ it "should receive partial payment" do
179
+ subscription = subscriptions(:bobs_subscription)
180
+ paid_through = subscription.paid_through
181
+ subscription.credit(subscription_plans(:basic).rate * 0.5)
182
+ subscription.save!
183
+ subscription.paid_through.to_s.should eql((paid_through + 15).to_s) #extended by 15 days
184
+ end
185
+
186
+ it "should send invoice when receiving payment" do
187
+ subscription = subscriptions(:bobs_subscription)
188
+ ActionMailer::Base.deliveries = []
189
+ transaction = create_transaction_for(subscription_plans(:basic).rate, subscription)
190
+ subscription.receive_payment!(transaction)
191
+ ActionMailer::Base.deliveries.size.should eql(1)
192
+ end
193
+
194
+ it "should save transaction message when receiving payment" do
195
+ subscription = subscriptions(:bobs_subscription)
196
+ transaction = create_transaction_for(subscription_plans(:basic).rate, subscription)
197
+ subscription.receive_payment!(transaction)
198
+ transaction.reload.message.should match(/^now paid through/)
199
+ end
200
+
201
+ it "should receive payment though invoice has delivered with error" do
202
+ subscription = subscriptions(:bobs_subscription)
203
+ paid_through = subscription.paid_through
204
+ transaction = create_transaction_for(subscription_plans(:basic).rate, subscription)
205
+ Freemium.mailer.should_receive(:invoice).and_raise(RuntimeError)
206
+ subscription.receive_payment!(transaction)
207
+ subscription = subscription.reload
208
+ subscription.paid_through.to_s.should eql((paid_through >> 1).to_s) #extended by one month
209
+ end
210
+
211
+ ##
212
+ ## Requiring Credit Cards ...
213
+ ##
214
+
215
+ it "should require credit card for pay plan" do
216
+ subscription = build_subscription(:subscription_plan => subscription_plans(:premium))
217
+ subscription.stub!(:credit_card => nil)
218
+ subscription.should have(1).errors_on(:credit_card)
219
+ end
220
+
221
+ it "should not require credit card for free_plan" do
222
+ subscription = build_subscription
223
+ subscription.should have(0).errors_on(:credit_card)
224
+ end
225
+
226
+ ##
227
+ ## Expiration
228
+ ##
229
+
230
+ it "should expire instance" do
231
+ Freemium.expired_plan_key = :free
232
+ Freemium.gateway.should_receive(:cancel).once.and_return(nil)
233
+ ActionMailer::Base.deliveries = []
234
+ subscriptions(:bobs_subscription).expire!
235
+
236
+ ActionMailer::Base.deliveries.size.should eql(1) #notice is sent to user
237
+ subscriptions(:bobs_subscription).reload.subscription_plan.should eql(subscription_plans(:free)) #subscription is downgraded to free
238
+ subscriptions(:bobs_subscription).billing_key.should be_nil #billing key is thrown away
239
+ subscriptions(:bobs_subscription).reload.billing_key.should be_nil #billing key is thrown away
240
+
241
+ assert_changed(subscriptions(:bobs_subscription).subscribable, :expiration, subscription_plans(:basic), subscription_plans(:free))
242
+ end
243
+
244
+ it "should expire instance with expired_plan_key = nil" do
245
+ Freemium.expired_plan_key = nil
246
+ Freemium.gateway.should_receive(:cancel).once.and_return(nil)
247
+ ActionMailer::Base.deliveries = []
248
+ subscriptions(:bobs_subscription).expire!
249
+
250
+ ActionMailer::Base.deliveries.size.should eql(1) #notice is sent to user
251
+ subscriptions(:bobs_subscription).subscription_plan.should eql(subscription_plans(:basic)) #subscription was not changed
252
+ subscriptions(:bobs_subscription).billing_key.should be_nil #billing key is thrown away
253
+ subscriptions(:bobs_subscription).reload.billing_key.should be_nil #billing key is thrown away
254
+ end
255
+
256
+ it "should expire class" do
257
+ Freemium.expired_plan_key = :free
258
+ Freemium.expired_plan_key.should eql(:free)
259
+ Freemium.expired_plan.should_not be_nil
260
+ subscriptions(:bobs_subscription).update_attributes(:paid_through => Date.today - 4, :expire_on => Date.today)
261
+ ActionMailer::Base.deliveries = []
262
+
263
+ subscriptions(:bobs_subscription).subscription_plan.should eql(subscription_plans(:basic))
264
+
265
+ Freemium.expired_plan.should eql(subscription_plans(:free))
266
+ Subscription.expire
267
+
268
+ subscriptions(:bobs_subscription).expire!
269
+ subscriptions(:bobs_subscription).reload.subscription_plan.should eql(subscription_plans(:free))
270
+ subscriptions(:bobs_subscription).reload.started_on.should eql(Date.today)
271
+ ActionMailer::Base.deliveries.size.should > 0
272
+
273
+ assert_changed(subscriptions(:bobs_subscription).subscribable, :expiration, subscription_plans(:basic), subscription_plans(:free))
274
+ end
275
+
276
+ it "should expire after grace " do
277
+ subscriptions(:bobs_subscription).expire_on.should be_nil
278
+ subscriptions(:bobs_subscription).paid_through = Date.today - 2
279
+ ActionMailer::Base.deliveries = []
280
+
281
+ subscriptions(:bobs_subscription).expire_after_grace!
282
+
283
+ ActionMailer::Base.deliveries.size.should eql(1)
284
+ subscriptions(:bobs_subscription).reload.expire_on.should eql(Date.today + Freemium.days_grace)
285
+ end
286
+
287
+ it "should expire after grace with remaining period" do
288
+ subscriptions(:bobs_subscription).paid_through = Date.today + 1
289
+ subscriptions(:bobs_subscription).expire_after_grace!
290
+
291
+ subscriptions(:bobs_subscription).reload.expire_on.should eql(Date.today + 1 + Freemium.days_grace)
292
+ end
293
+
294
+ it "should test grace and expiration" do
295
+ Freemium.days_grace.should eql(3)
296
+
297
+ subscription = Subscription.new(:paid_through => Date.today + 5)
298
+ subscription.in_grace?.should be_false
299
+ subscription.expired?.should be_false
300
+
301
+ # a subscription that's pastdue but hasn't been flagged to expire yet.
302
+ # this could happen if a billing process skips, in which case the subscriber
303
+ # should still get a full grace period beginning from the failed attempt at billing.
304
+ # even so, the subscription is "in grace", even if the grace period hasn't officially started.
305
+ subscription = Subscription.new(:paid_through => Date.today - 5)
306
+ subscription.in_grace?.should be_true
307
+ subscription.expired?.should be_false
308
+
309
+ # expires tomorrow
310
+ subscription = Subscription.new(:paid_through => Date.today - 5, :expire_on => Date.today + 1)
311
+ subscription.remaining_days_of_grace.should eql(0)
312
+ subscription.in_grace?.should be_true
313
+ subscription.expired?.should be_false
314
+
315
+ # expires today
316
+ subscription = Subscription.new(:paid_through => Date.today - 5, :expire_on => Date.today)
317
+ subscription.remaining_days_of_grace.should eql(-1)
318
+ subscription.in_grace?.should be_false
319
+ subscription.expired?.should be_true
320
+ end
321
+
322
+ ##
323
+ ## Deleting (possibly from a cascading delete, such as User.find(5).delete)
324
+ ##
325
+
326
+ it "should delete canceles in gateway" do
327
+ Freemium.gateway.should_receive(:cancel).once.and_return(nil)
328
+ subscriptions(:bobs_subscription).destroy
329
+
330
+ assert_changed(subscriptions(:bobs_subscription).subscribable, :cancellation, subscription_plans(:basic), nil)
331
+ end
332
+
333
+ ##
334
+ ## The Subscription#credit_card= shortcut
335
+ ##
336
+ it "should add credit card" do
337
+ subscription = build_subscription(:subscription_plan => subscription_plans(:premium))
338
+ cc = CreditCard.sample
339
+ response = Freemium::Response.new(true)
340
+ response.billing_key = "alphabravo"
341
+ Freemium.gateway.should_receive(:store).with(cc, cc.address).and_return(response)
342
+
343
+ subscription.credit_card = cc
344
+ lambda { subscription.save! }.should_not raise_error
345
+ subscription.billing_key.should eql("alphabravo")
346
+ end
347
+
348
+ it "should update a credit card" do
349
+ subscription = Subscription.where("billing_key IS NOT NULL").first
350
+ cc = CreditCard.sample
351
+ response = Freemium::Response.new(true)
352
+ response.billing_key = "new code"
353
+ Freemium.gateway.should_receive(:update).with(subscription.billing_key, cc, cc.address).and_return(response)
354
+
355
+ subscription.credit_card = cc
356
+ lambda { subscription.save! }.should_not raise_error
357
+ subscription.billing_key.should eql("new code") #catches any change to the billing key
358
+ end
359
+
360
+ it "should update an expired credit card" do
361
+ subscription = Subscription.where("billing_key IS NOT NULL").first
362
+ cc = CreditCard.sample
363
+ response = Freemium::Response.new(true)
364
+ Freemium.gateway.should_receive(:update).with(subscription.billing_key, cc, cc.address).and_return(response)
365
+
366
+ subscription.expire_on = Time.now
367
+ subscription.save.should be_true
368
+ subscription.reload.expire_on.should_not be_nil
369
+
370
+ subscription.credit_card = cc
371
+ lambda { subscription.save! }.should_not raise_error
372
+ #subscription.expire_on.should be_nil
373
+ #subscription.reload.expire_on.should be_nil
374
+ end
375
+
376
+ it "should fail to add credit card" do
377
+ subscription = build_subscription(:subscription_plan => subscription_plans(:premium))
378
+ cc = CreditCard.sample
379
+ response = Freemium::Response.new(false)
380
+ Freemium.gateway.should_receive(:store).and_return(response)
381
+
382
+ subscription.credit_card = cc
383
+ lambda { subscription.save! }.should raise_error(Freemium::CreditCardStorageError)
384
+ end
385
+
386
+ end