freemium 0.0.1

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.
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