freemium-ajb 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. data/.coveralls.yml +1 -0
  2. data/.gitignore +27 -0
  3. data/.rspec +1 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +6 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE.md +20 -0
  9. data/README.md +1 -0
  10. data/app/mailers/freemium_mailer.rb +36 -0
  11. data/app/views/subscription_mailer/admin_report.text.erb +4 -0
  12. data/app/views/subscription_mailer/expiration_notice.text.erb +1 -0
  13. data/app/views/subscription_mailer/expiration_warning.text.erb +1 -0
  14. data/app/views/subscription_mailer/invoice.text.erb +5 -0
  15. data/freemium.gemspec +29 -0
  16. data/lib/freemium.rb +25 -0
  17. data/lib/freemium/configuration.rb +27 -0
  18. data/lib/freemium/coupon.rb +37 -0
  19. data/lib/freemium/coupon_redemption.rb +59 -0
  20. data/lib/freemium/credit_card.rb +222 -0
  21. data/lib/freemium/engine.rb +7 -0
  22. data/lib/freemium/gateways/base.rb +65 -0
  23. data/lib/freemium/gateways/brain_tree.rb +175 -0
  24. data/lib/freemium/gateways/test.rb +36 -0
  25. data/lib/freemium/rates.rb +33 -0
  26. data/lib/freemium/response.rb +24 -0
  27. data/lib/freemium/subscription.rb +384 -0
  28. data/lib/freemium/subscription_change.rb +20 -0
  29. data/lib/freemium/subscription_plan.rb +26 -0
  30. data/lib/freemium/testing/app/controllers/application_controller.rb +7 -0
  31. data/lib/freemium/testing/application.rb +46 -0
  32. data/lib/freemium/testing/config/database.yml +11 -0
  33. data/lib/freemium/testing/config/routes.rb +3 -0
  34. data/lib/freemium/transaction.rb +15 -0
  35. data/lib/freemium/version.rb +3 -0
  36. data/lib/generators/freemium/install/install_generator.rb +58 -0
  37. data/lib/generators/freemium/install/templates/db/migrate/create_coupon_redemptions.rb +18 -0
  38. data/lib/generators/freemium/install/templates/db/migrate/create_coupons.rb +28 -0
  39. data/lib/generators/freemium/install/templates/db/migrate/create_credit_cards.rb +14 -0
  40. data/lib/generators/freemium/install/templates/db/migrate/create_subscription_changes.rb +21 -0
  41. data/lib/generators/freemium/install/templates/db/migrate/create_subscription_plans.rb +14 -0
  42. data/lib/generators/freemium/install/templates/db/migrate/create_subscriptions.rb +31 -0
  43. data/lib/generators/freemium/install/templates/db/migrate/create_transactions.rb +17 -0
  44. data/lib/generators/freemium/install/templates/freemium.rb +16 -0
  45. data/lib/generators/freemium/install/templates/models/coupon.rb +3 -0
  46. data/lib/generators/freemium/install/templates/models/coupon_redemption.rb +3 -0
  47. data/lib/generators/freemium/install/templates/models/credit_card.rb +3 -0
  48. data/lib/generators/freemium/install/templates/models/subscription.rb +3 -0
  49. data/lib/generators/freemium/install/templates/models/subscription_change.rb +3 -0
  50. data/lib/generators/freemium/install/templates/models/subscription_plan.rb +3 -0
  51. data/lib/generators/freemium/install/templates/models/transaction.rb +3 -0
  52. data/lib/generators/views/USAGE +3 -0
  53. data/lib/generators/views/views_generator.rb +39 -0
  54. data/lib/tasks/freemium.rake +17 -0
  55. data/spec/dummy/Rakefile +7 -0
  56. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  57. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  58. data/spec/dummy/app/mailers/mailers.rb +1 -0
  59. data/spec/dummy/app/models/models.rb +31 -0
  60. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  61. data/spec/dummy/config.ru +4 -0
  62. data/spec/dummy/config/application.rb +45 -0
  63. data/spec/dummy/config/boot.rb +10 -0
  64. data/spec/dummy/config/database.yml +21 -0
  65. data/spec/dummy/config/environment.rb +6 -0
  66. data/spec/dummy/config/environments/development.rb +26 -0
  67. data/spec/dummy/config/environments/production.rb +49 -0
  68. data/spec/dummy/config/environments/test.rb +36 -0
  69. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  70. data/spec/dummy/config/initializers/freemium.rb +16 -0
  71. data/spec/dummy/config/initializers/inflections.rb +10 -0
  72. data/spec/dummy/config/initializers/mem_db.rb +12 -0
  73. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  74. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  75. data/spec/dummy/config/initializers/session_store.rb +8 -0
  76. data/spec/dummy/config/locales/en.yml +5 -0
  77. data/spec/dummy/config/routes.rb +58 -0
  78. data/spec/dummy/db/schema.rb +90 -0
  79. data/spec/dummy/script/rails +6 -0
  80. data/spec/fixtures/credit_cards.yml +11 -0
  81. data/spec/fixtures/subscription_plans.yml +15 -0
  82. data/spec/fixtures/subscriptions.yml +28 -0
  83. data/spec/fixtures/users.yml +16 -0
  84. data/spec/lib/tasks/run_billing_rake_spec.rb +14 -0
  85. data/spec/models/coupon_redemption_spec.rb +287 -0
  86. data/spec/models/credit_card_spec.rb +124 -0
  87. data/spec/models/manual_billing_spec.rb +165 -0
  88. data/spec/models/subscription_plan_spec.rb +46 -0
  89. data/spec/models/subscription_spec.rb +386 -0
  90. data/spec/spec_helper.rb +19 -0
  91. data/spec/support/helpers.rb +18 -0
  92. data/spec/support/shared_contexts/rake.rb +19 -0
  93. metadata +270 -0
@@ -0,0 +1,165 @@
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.configuration.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.configuration.gateway.stub!(:charge).and_return(
28
+ Transaction.new(
29
+ :billing_key => s.billing_key,
30
+ :amount => s.rate,
31
+ :success => false
32
+ )
33
+ )
34
+ end
35
+
36
+ it "should not change expire_on on failure overdue payment" do
37
+ subscription = create_billable_subscription # should NOT be billable because it's already expiring
38
+ expire_on = Date.today + 2
39
+ paid_through = subscription.paid_through
40
+ subscription.update_attribute :expire_on, expire_on
41
+ subscription.reload
42
+ subscription.expire_on.should eql(expire_on)
43
+ expirable = Subscription.send(:find_billable)
44
+ expirable.size.should eql(1), "Subscriptions in their grace period should be retried"
45
+
46
+ Freemium.configuration.gateway.stub!(:charge).and_return(
47
+ Transaction.new(
48
+ :billing_key => subscription.billing_key,
49
+ :amount => subscription.rate,
50
+ :success => false
51
+ )
52
+ )
53
+
54
+ lambda do
55
+ transaction = subscription.charge!
56
+ subscription.expire_on.should eql(expire_on), "Billing failed on existing overdue account but the expire_on date was changed"
57
+ end.should_not raise_error
58
+ end
59
+
60
+ it "should change expire_on on success overdue payment" do
61
+ subscription = create_billable_subscription # should NOT be billable because it's already expiring
62
+ expire_on = Date.today + 2
63
+ paid_through = subscription.paid_through
64
+ subscription.update_attribute :expire_on, expire_on
65
+
66
+ expirable = Subscription.send(:find_billable)
67
+ expirable.size.should eql(1), "Subscriptions in their grace period should be retried"
68
+
69
+ Freemium.configuration.gateway.stub!(:charge).and_return(
70
+ Transaction.new(
71
+ :billing_key => subscription.billing_key,
72
+ :amount => subscription.rate,
73
+ :success => true
74
+ )
75
+ )
76
+
77
+ assert_nothing_raised do
78
+ transaction = subscription.charge!
79
+ transaction.subscription.paid_through.to_s.should eql((paid_through >> 1).to_s), "extended by a month"
80
+ subscription.expire_on.should be_nil, "Billing succeeded on existing overdue account but the expire_on date was not reset"
81
+ end
82
+ end
83
+
84
+ it "should charge a subscription" do
85
+ subscription = Subscription.first
86
+ subscription.coupon = Coupon.create!(:description => "Complimentary", :discount_percentage => 30)
87
+ subscription.save!
88
+
89
+ paid_through = subscription.paid_through
90
+
91
+ Freemium.configuration.gateway.stub!(:charge).and_return(
92
+ Transaction.new(
93
+ :billing_key => subscription.billing_key,
94
+ :amount => subscription.rate,
95
+ :success => true
96
+ )
97
+ )
98
+
99
+ lambda do
100
+ transaction = subscription.charge!
101
+ transaction.subscription.paid_through.to_s.should eql((paid_through >> 1).to_s)#, "extended by a month"
102
+ end.should_not raise_error
103
+
104
+ subscription = subscription.reload
105
+ subscription.transactions.empty?.should be_false
106
+ subscription.transactions.last.should be_true
107
+ subscription.transactions.last.success?.should be_true
108
+ subscription.transactions.last.message?.should_not be_nil
109
+ ((Time.now - 1.minute) < subscription.last_transaction_at).should be_true
110
+ Transaction.since(Date.today).empty?.should be_false
111
+ subscription.transactions.last.amount.should eql(subscription.rate)
112
+ subscription.reload.paid_through.to_s.should eql((paid_through >> 1).to_s), "extended by a month"
113
+ end
114
+
115
+
116
+ it "should charge an aborted subscription" do
117
+ subscription = Subscription.first
118
+ subscription.coupon = Coupon.create!(:description => "Complimentary", :discount_percentage => 30)
119
+ subscription.save!
120
+
121
+ paid_through = subscription.paid_through
122
+ subscription.transactions.empty?.should be_true
123
+
124
+ Freemium.configuration.gateway.stub!(:charge).and_return(
125
+ Transaction.new(
126
+ :billing_key => subscription.billing_key,
127
+ :amount => subscription.rate,
128
+ :success => true
129
+ )
130
+ )
131
+
132
+ subscription.should_receive(:receive_payment).and_raise(RuntimeError)
133
+ subscription.charge!
134
+ subscription.reload.transactions.empty?.should be_false
135
+ end
136
+
137
+ it "should not charge a subscription" do
138
+ subscription = Subscription.first
139
+ paid_through = subscription.paid_through
140
+ Freemium.configuration.gateway.stub!(:charge).and_return(
141
+ Transaction.new(
142
+ :billing_key => subscription.billing_key,
143
+ :amount => subscription.rate,
144
+ :success => false
145
+ )
146
+ )
147
+
148
+ subscription.expire_on.should be_nil
149
+ lambda { subscription.charge! }.should_not raise_error
150
+ subscription.reload.paid_through.should eql(paid_through), "not extended"
151
+ subscription.expire_on.should_not be_nil
152
+ subscription.transactions.last.success?.should be_false
153
+ end
154
+
155
+ protected
156
+
157
+ def create_billable_subscription(options = {})
158
+ Subscription.create!({
159
+ :subscription_plan => subscription_plans(:premium),
160
+ :subscribable => User.new(:name => 'a'),
161
+ :paid_through => Date.today - 1,
162
+ :credit_card => CreditCard.sample
163
+ }.merge(options))
164
+ end
165
+ end
@@ -0,0 +1,46 @@
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 serialize features" do
31
+ plan = create_plan
32
+ plan.features.should == {}
33
+ plan2 = create_plan(features: {foo: 1})
34
+ plan2.features[:foo].should == 1
35
+ end
36
+
37
+ protected
38
+
39
+ def create_plan(options = {})
40
+ SubscriptionPlan.create({
41
+ :name => 'super-duper-ultra-premium',
42
+ :key => 'super-duper-ultra-premium',
43
+ :rate_cents => 99995
44
+ }.merge(options))
45
+ end
46
+ 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
+ Transaction.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.configuration.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.configuration.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.configuration.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.configuration.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.configuration.expired_plan = subscription_plans(:free)
232
+ Freemium.configuration.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.configuration.expired_plan = nil
246
+ Freemium.configuration.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.configuration.expired_plan = subscription_plans(:free)
258
+ Freemium.configuration.expired_plan.should eql(subscription_plans(:free))
259
+ Freemium.configuration.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.configuration.expired_plan.should eql(subscription_plans(:free))
266
+ Subscription.find_expired.each(&: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.configuration.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.configuration.days_grace)
292
+ end
293
+
294
+ it "should test grace and expiration" do
295
+ Freemium.configuration.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.configuration.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.configuration.gateway.should_receive(:store).with(cc).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.configuration.gateway.should_receive(:update).with(subscription.billing_key, cc).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.configuration.gateway.should_receive(:update).with(subscription.billing_key, cc).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.configuration.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