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,58 @@
1
+ Dummy::Application.routes.draw do
2
+ # The priority is based upon order of creation:
3
+ # first created -> highest priority.
4
+
5
+ # Sample of regular route:
6
+ # match 'products/:id' => 'catalog#view'
7
+ # Keep in mind you can assign values other than :controller and :action
8
+
9
+ # Sample of named route:
10
+ # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
11
+ # This route can be invoked with purchase_url(:id => product.id)
12
+
13
+ # Sample resource route (maps HTTP verbs to controller actions automatically):
14
+ # resources :products
15
+
16
+ # Sample resource route with options:
17
+ # resources :products do
18
+ # member do
19
+ # get 'short'
20
+ # post 'toggle'
21
+ # end
22
+ #
23
+ # collection do
24
+ # get 'sold'
25
+ # end
26
+ # end
27
+
28
+ # Sample resource route with sub-resources:
29
+ # resources :products do
30
+ # resources :comments, :sales
31
+ # resource :seller
32
+ # end
33
+
34
+ # Sample resource route with more complex sub-resources
35
+ # resources :products do
36
+ # resources :comments
37
+ # resources :sales do
38
+ # get 'recent', :on => :collection
39
+ # end
40
+ # end
41
+
42
+ # Sample resource route within a namespace:
43
+ # namespace :admin do
44
+ # # Directs /admin/products/* to Admin::ProductsController
45
+ # # (app/controllers/admin/products_controller.rb)
46
+ # resources :products
47
+ # end
48
+
49
+ # You can have the root of your site routed with "root"
50
+ # just remember to delete public/index.html.
51
+ # root :to => "welcome#index"
52
+
53
+ # See how all your routes lay out with "rake routes"
54
+
55
+ # This is a legacy wild controller route that's not recommended for RESTful applications.
56
+ # Note: This route will make all actions in every controller accessible via GET requests.
57
+ # match ':controller(/:action(/:id(.:format)))'
58
+ end
@@ -0,0 +1,4 @@
1
+ # This file is used by Rack-based servers to start the application.
2
+
3
+ require ::File.expand_path('../config/environment', __FILE__)
4
+ run Dummy::Application
@@ -0,0 +1,92 @@
1
+ ActiveRecord::Schema.define(:version => 1) do
2
+ create_table :users, :force => true do |t|
3
+ t.column :name, :string
4
+ t.column :email, :string
5
+ end
6
+
7
+ create_table :subscription_plans, :force => true do |t|
8
+ t.column :name, :string, :null => false
9
+ t.column :redemption_key, :string, :null => false
10
+ t.column :rate_cents, :integer, :null => false
11
+ t.column :feature_set_id, :string, :null => false
12
+ end
13
+
14
+ create_table :subscriptions, :force => true do |t|
15
+ t.column :subscribable_id, :integer, :null => false
16
+ t.column :subscribable_type, :string, :null => false
17
+ t.column :billing_key, :string, :null => true
18
+ t.column :credit_card_id, :integer, :null => true
19
+ t.column :subscription_plan_id, :integer, :null => false
20
+ t.column :paid_through, :date, :null => true
21
+ t.column :expire_on, :date, :null => true
22
+ t.column :billing_key, :string, :null => true
23
+ t.column :started_on, :date, :null => true
24
+ t.column :last_transaction_at, :datetime, :null => true
25
+ t.column :last_transaction_success, :boolean, :null => true
26
+ t.column :in_trial, :boolean, :null => false, :default => false
27
+ end
28
+
29
+ create_table :manual_subscriptions, :force => true do |t|
30
+ t.column :subscribable_id, :integer, :null => false
31
+ t.column :subscribable_type, :string, :null => false
32
+ t.column :billing_key, :string, :null => true
33
+ t.column :credit_card_id, :integer, :null => true
34
+ t.column :subscription_plan_id, :integer, :null => false
35
+ t.column :paid_through, :date, :null => true
36
+ t.column :expire_on, :date, :null => true
37
+ t.column :billing_key, :string, :null => true
38
+ t.column :started_on, :date, :null => true
39
+ t.column :last_transaction_at, :datetime, :null => true
40
+ t.column :last_transaction_success, :boolean, :null => true
41
+ t.column :in_trial, :boolean, :null => false, :default => false
42
+ end
43
+
44
+ create_table :credit_cards, :force => true do |t|
45
+ t.column :display_number, :string, :null => false
46
+ t.column :card_type, :string, :null => false
47
+ t.column :expiration_date, :timestamp, :null => false
48
+ t.column :zip_code, :string, :null => true
49
+ end
50
+
51
+ create_table :coupons, :force => true do |t|
52
+ t.column :description, :string, :null => false
53
+ t.column :discount_percentage, :integer, :null => false
54
+ t.column :redemption_key, :string, :null => true
55
+ t.column :redemption_limit, :integer, :null => true
56
+ t.column :redemption_expiration, :date, :null => true
57
+ t.column :duration_in_months, :integer, :null => true
58
+ end
59
+
60
+ create_table :coupons_subscription_plans, :id => false, :force => true do |t|
61
+ t.column :coupon_id, :integer, :null => false
62
+ t.column :subscription_plan_id, :integer, :null => false
63
+ end
64
+
65
+ create_table :coupon_redemptions, :force => true do |t|
66
+ t.column :subscription_id, :integer, :null => false
67
+ t.column :coupon_id, :integer, :null => false
68
+ t.column :redeemed_on, :date, :null => false
69
+ t.column :expired_on, :date, :null => true
70
+ end
71
+
72
+ create_table :account_transactions, :force => true do |t|
73
+ t.column :subscription_id, :integer, :null => false
74
+ t.column :success, :boolean, :null => false
75
+ t.column :billing_key, :string, :null => false
76
+ t.column :amount_cents, :integer, :null => false
77
+ t.column :message, :string, :null => true
78
+ t.column :created_at, :timestamp, :null => false
79
+ end
80
+
81
+ create_table :subscription_changes, :force => true do |t|
82
+ t.column :subscribable_id, :integer, :null => false
83
+ t.column :subscribable_type, :string, :null => false
84
+ t.column :original_subscription_plan_id, :integer, :null => true
85
+ t.column :new_subscription_plan_id, :integer, :null => true
86
+ t.column :original_rate_cents, :integer, :null => true
87
+ t.column :new_rate_cents, :integer, :null => true
88
+ t.column :reason, :string, :null => false
89
+ t.column :created_at, :timestamp, :null => false
90
+ end
91
+
92
+ end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -0,0 +1,11 @@
1
+ bobs_credit_card:
2
+ id: 1
3
+ expiration_date: <%= (Time.now + 3.years).to_s :db %>
4
+ display_number: xxxx-xxxx-xxxx-2222
5
+ card_type: master
6
+ steves_credit_card:
7
+ id: 2
8
+ expiration_date: <%= (Time.now + 3.years).to_s :db %>
9
+ display_number: xxxx-xxxx-xxxx-1111
10
+ card_type: visa
11
+
@@ -0,0 +1,18 @@
1
+ free:
2
+ id: 1
3
+ redemption_key: free
4
+ feature_set_id: free
5
+ name: Free Access
6
+ rate_cents: 0
7
+ basic:
8
+ id: 2
9
+ redemption_key: basic
10
+ feature_set_id: basic
11
+ name: Basic
12
+ rate_cents: 1295
13
+ premium:
14
+ id: 3
15
+ redemption_key: premium
16
+ feature_set_id: premium
17
+ name: Premium
18
+ rate_cents: 2495
@@ -0,0 +1,29 @@
1
+ bobs_subscription:
2
+ id: 1
3
+ subscribable_id: 1
4
+ subscribable_type: User
5
+ subscription_plan_id: 2
6
+ credit_card_id: 1
7
+ paid_through: <%= (Date.today + 20).to_s :db %>
8
+ billing_key: tc00001
9
+ started_on: <%= (Date.today - 60).to_s :db %>
10
+ last_transaction_at: 2007-09-15 12:34:56
11
+ sues_subscription:
12
+ id: 2
13
+ subscribable_id: 2
14
+ subscribable_type: User
15
+ subscription_plan_id: 1
16
+ billing_key:
17
+ paid_through:
18
+ last_transaction_at:
19
+ started_on: <%= (Date.today - 60).to_s :db %>
20
+ steves_subscription:
21
+ id: 3
22
+ subscribable_id: 4
23
+ subscribable_type: User
24
+ subscription_plan_id: 3
25
+ credit_card_id: 1
26
+ paid_through: <%= (Date.today + 20).to_s :db %>
27
+ billing_key: tc00002
28
+ started_on: <%= (Date.today - 60).to_s :db %>
29
+ last_transaction_at: 2007-09-15 12:34:56
@@ -0,0 +1,16 @@
1
+ bob:
2
+ id: 1
3
+ name: Bob
4
+ email: bob@example.com
5
+ sue:
6
+ id: 2
7
+ name: Sue
8
+ email: sue@example.com
9
+ sally:
10
+ id: 3
11
+ name: Sally
12
+ email: sally@example.com
13
+ steve:
14
+ id: 4
15
+ name: Steve
16
+ email: steve@example.com
@@ -0,0 +1,9 @@
1
+ - id: free
2
+ ads: true
3
+ unlimited: false
4
+ - id: plus
5
+ ads: false
6
+ unlimited: false
7
+ - id: premium
8
+ ads: false
9
+ unlimited: true
@@ -0,0 +1,4 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Freemium" do
4
+ end
@@ -0,0 +1,235 @@
1
+ require 'spec_helper'
2
+
3
+ describe CouponRedemption do
4
+ fixtures :users, :subscriptions, :subscription_plans, :credit_cards
5
+
6
+ before(:each) do
7
+ @subscription = subscriptions(:bobs_subscription)
8
+ @original_price = @subscription.rate
9
+ @coupon = Coupon.create(:description => "30% off", :discount_percentage => 30, :redemption_key => "30OFF")
10
+ end
11
+
12
+ it "should be applied" do
13
+ @subscription.paid_through = Date.today + 30
14
+ @original_remaining_value = @subscription.remaining_value
15
+ @original_daily_rate = @subscription.daily_rate
16
+ @subscription.coupon_key = nil
17
+
18
+ @subscription.coupon_redemptions.create(:coupon => @coupon).should be_true
19
+ @subscription.rate.cents.should eql(@coupon.discount(@original_price).cents)
20
+ @subscription.daily_rate.cents.should eql(@coupon.discount(@original_daily_rate).cents)
21
+ @subscription.remaining_value.cents.should eql((@coupon.discount(@original_daily_rate) * @subscription.remaining_days).cents)
22
+ end
23
+
24
+ it "should be applied using coupon accessor" do
25
+ @subscription = build_subscription(:coupon => @coupon, :credit_card => CreditCard.sample)
26
+ @subscription.save!
27
+
28
+ @subscription.coupon.should_not be_nil
29
+ @subscription.coupon_redemptions.first.coupon.should_not be_nil
30
+ @subscription.coupon_redemptions.first.subscription.should_not be_nil
31
+ @subscription.coupon_redemptions.empty?.should be_false
32
+ @subscription.rate.cents.should eql(@coupon.discount(@subscription.subscription_plan.rate).cents)
33
+ end
34
+
35
+ it "should be applied using coupon key accessor" do
36
+ @subscription = build_subscription(:coupon_key => @coupon.redemption_key, :credit_card => CreditCard.sample)
37
+ @subscription.save!
38
+
39
+ @subscription.coupon.should_not be_nil
40
+ @subscription.coupon_redemptions.first.coupon.should_not be_nil
41
+ @subscription.coupon_redemptions.first.subscription.should_not be_nil
42
+ @subscription.coupon_redemptions.empty?.should be_false
43
+ @subscription.rate.cents.should eql(@coupon.discount(@subscription.subscription_plan.rate).cents)
44
+ end
45
+
46
+ it "should be applied multiple" do
47
+ @coupon_1 = Coupon.new(:description => "10% off", :discount_percentage => 10)
48
+ @subscription.coupon_redemptions.create(:coupon => @coupon_1).should be_true
49
+
50
+ @coupon_2 = Coupon.new(:description => "30% off", :discount_percentage => 30)
51
+ @subscription.coupon_redemptions.create(:coupon => @coupon_2).should be_true
52
+
53
+ @coupon_3 = Coupon.new(:description => "20% off", :discount_percentage => 20)
54
+ @subscription.coupon_redemptions.create(:coupon => @coupon_3).should be_true
55
+
56
+ # Should use the highest discounted coupon
57
+ @subscription.rate.cents.should eql(@coupon_2.discount(@original_price).cents)
58
+ end
59
+
60
+ it "should be destroyed" do
61
+ @subscription.coupon_redemptions.create(:coupon => @coupon).should be_true
62
+ @subscription.rate.cents.should eql(@coupon.discount(@original_price).cents)
63
+
64
+ @coupon.destroy
65
+ @subscription.reload
66
+
67
+ @subscription.coupon_redemptions.empty?.should be_true
68
+ @subscription.rate.cents.should eql(@original_price.cents)
69
+ end
70
+
71
+ it "should test coupon duration" do
72
+ @subscription.coupon_redemptions.create(:coupon => @coupon).should be_true
73
+ @subscription.rate.cents.should eql(@coupon.discount(@original_price).cents)
74
+
75
+ @coupon.duration_in_months = 3
76
+ @coupon.save!
77
+
78
+ @subscription.rate({:date => (Date.today + 3.months - 1)}).cents.should eql(@coupon.discount(@original_price).cents)
79
+ @subscription.rate({:date => (Date.today + 3.months + 1)}).cents.should eql(@original_price.cents)
80
+
81
+ safe_date = Date.today + 3.months - 1
82
+ Date.stub!(:today => safe_date)
83
+ @subscription.rate.cents.should eql(@coupon.discount(@original_price).cents)
84
+
85
+ safe_date = Date.today + 1
86
+ Date.stub!(:today => safe_date)
87
+ @subscription.rate.cents.should eql(@coupon.discount(@original_price).cents)
88
+
89
+ safe_date = Date.today + 1
90
+ Date.stub!(:today => safe_date)
91
+ @subscription.rate.cents.should eql(@original_price.cents)
92
+ end
93
+
94
+ it "should be applied complimentary" do
95
+ @coupon.discount_percentage = 100
96
+
97
+ @subscription = build_subscription(:coupon => @coupon, :credit_card => CreditCard.sample, :subscription_plan => subscription_plans(:premium))
98
+
99
+ @subscription.save.should be_true
100
+ @subscription.coupon.should_not be_nil
101
+ @subscription.rate.cents.should eql(0)
102
+ @subscription.paid?.should be_false
103
+ end
104
+
105
+ ##
106
+ ## Plan-specific coupons
107
+ ##
108
+
109
+ it "should be applied only on new premium plan" do
110
+ set_coupon_to_premium_only
111
+
112
+ @subscription = build_subscription(:coupon => @coupon, :credit_card => CreditCard.sample,
113
+ :subscription_plan => subscription_plans(:premium))
114
+
115
+ @subscription.save.should be_true
116
+ @subscription.coupon.should_not be_nil
117
+
118
+ @subscription = build_subscription(:coupon => @coupon, :credit_card => CreditCard.sample,
119
+ :subscription_plan => subscription_plans(:basic))
120
+
121
+ @subscription.save.should be_false
122
+ @subscription.should have(1).errors_on(:coupon_redemptions)
123
+ end
124
+
125
+ it "should be applied only on existent premium plan" do
126
+ set_coupon_to_premium_only
127
+
128
+ @subscription.coupon = @coupon
129
+
130
+ @subscription.subscription_plan.should eql(subscription_plans(:basic))
131
+ @subscription.save.should be_false
132
+ @subscription.should have(1).errors_on(:coupon_redemptions)
133
+
134
+ @subscription.subscription_plan = subscription_plans(:premium)
135
+
136
+ @subscription.subscription_plan.should eql(subscription_plans(:premium))
137
+ @subscription.save.should be_true
138
+ @subscription.coupon.should_not be_nil
139
+ end
140
+
141
+ ##
142
+ ## applying coupons
143
+ ##
144
+
145
+ it "should be applied to subscription" do
146
+ @subscription.coupon = @coupon
147
+ @subscription.should be_valid
148
+ @subscription.coupon.should_not be_nil
149
+ end
150
+
151
+ it "should validate coupon key" do
152
+ @subscription.coupon_key = @coupon.redemption_key + "xxxxx"
153
+
154
+ @subscription.should_not be_valid
155
+ @subscription.coupon.should be_nil
156
+ @subscription.should have(1).errors_on(:coupon)
157
+ end
158
+
159
+ it "should validate coupon by coupon plan" do
160
+ set_coupon_to_premium_only
161
+
162
+ @subscription.subscription_plan.should_not eql(subscription_plans(:premium))
163
+
164
+ @subscription.coupon = @coupon
165
+ @subscription.should_not be_valid
166
+ @subscription.should have(1).errors_on(:coupon_redemptions)
167
+ end
168
+
169
+ protected
170
+
171
+ def set_coupon_to_premium_only
172
+ @coupon.subscription_plans << subscription_plans(:premium)
173
+ @coupon.save!
174
+ end
175
+
176
+
177
+ public
178
+
179
+ ##
180
+ ## Validation Tests
181
+ ##
182
+
183
+ it "should validate coupon" do
184
+ s = CouponRedemption.new(:subscription => subscriptions(:bobs_subscription))
185
+ s.save.should be_false
186
+ s.should have(1).errors_on(:coupon)
187
+ end
188
+
189
+ it "should not be applied to unpaid subscription" do
190
+ subscriptions(:sues_subscription).paid?.should be_false
191
+ s = CouponRedemption.new(:subscription => subscriptions(:sues_subscription), :coupon => @coupon)
192
+ s.save.should be_false
193
+ s.should have(1).errors_on(:subscription)
194
+ end
195
+
196
+ it "should not be applied twice" do
197
+ s = CouponRedemption.new(:subscription => subscriptions(:bobs_subscription), :coupon => @coupon)
198
+ s.save.should be_true
199
+
200
+ s = CouponRedemption.new(:subscription => subscriptions(:bobs_subscription), :coupon => @coupon)
201
+ s.save.should be_false
202
+ s.should have(1).errors_on(:coupon_id)
203
+ end
204
+
205
+ it "should not be applied with redemption expired coupon" do
206
+ @coupon.redemption_expiration = Date.today-1
207
+ @coupon.save!
208
+
209
+ s = CouponRedemption.new(:subscription => subscriptions(:bobs_subscription), :coupon => @coupon)
210
+ s.save.should be_false
211
+ s.should have(1).errors_on(:coupon)
212
+ end
213
+
214
+ it "should not be applied with too many redemptions" do
215
+ @coupon.redemption_limit = 1
216
+ @coupon.save!
217
+
218
+ s = CouponRedemption.new(:subscription => subscriptions(:bobs_subscription), :coupon => @coupon)
219
+ s.save!
220
+
221
+ s = CouponRedemption.new(:subscription => subscriptions(:steves_subscription), :coupon => @coupon)
222
+ s.save.should be_false
223
+ s.should have(1).errors_on(:coupon)
224
+ end
225
+
226
+ protected
227
+
228
+ def build_subscription(options = {})
229
+ Subscription.new({
230
+ :subscription_plan => subscription_plans(:basic),
231
+ :subscribable => users(:sue)
232
+ }.merge(options))
233
+ end
234
+
235
+ end
@@ -0,0 +1,114 @@
1
+ require 'spec_helper'
2
+
3
+ describe CreditCard do
4
+ fixtures :users, :subscriptions, :subscription_plans, :credit_cards
5
+
6
+ before(:each) do
7
+ @subscription = Subscription.new(:subscription_plan => subscription_plans(:premium), :subscribable => users(:sally))
8
+ @credit_card = CreditCard.new(CreditCard.sample_params.merge(:subscription => @subscription))
9
+ Freemium.gateway = Freemium::Gateways::BrainTree.new
10
+ Freemium.gateway.username = 'demo'
11
+ Freemium.gateway.password = 'password'
12
+
13
+ Freemium.gateway.stub!(:validate => Freemium::Response.new(true))
14
+ end
15
+
16
+ it "should create" do
17
+ @subscription.credit_card = @credit_card
18
+
19
+ @subscription.save.should be_true
20
+ @subscription = Subscription.find(@subscription.id)
21
+ @subscription.billing_key.should_not be_nil
22
+ @subscription.credit_card.display_number.should_not be_nil
23
+ @subscription.credit_card.card_type.should_not be_nil
24
+ @subscription.credit_card.expiration_date.should_not be_nil
25
+ end
26
+
27
+ it "should be created with billing validation failure" do
28
+ response = Freemium::Response.new(false, 'responsetext' => 'FAILED')
29
+ response.message = 'FAILED'
30
+ Freemium.gateway.stub!(:validate => response)
31
+
32
+ @subscription.credit_card = @credit_card
33
+
34
+ @subscription.save.should be_false
35
+ @subscription.should have(1).errors_on(:base)
36
+ end
37
+
38
+ it "should be updated" do
39
+ @subscription.credit_card = @credit_card
40
+
41
+ @subscription.save.should be_true
42
+ @subscription = Subscription.find(@subscription.id)
43
+ @subscription.billing_key.should_not be_nil
44
+
45
+ original_key = @subscription.billing_key
46
+ original_expiration = @subscription.credit_card.expiration_date
47
+
48
+ @subscription.credit_card = CreditCard.new(CreditCard.sample_params.merge(:zip_code => 95060, :number => "5431111111111111", :card_type => "master", :year => 2020))
49
+ @subscription.save.should be_true
50
+ @subscription = Subscription.find(@subscription.id)
51
+ @subscription.billing_key.should eql(original_key)
52
+ original_expiration.should < @subscription.credit_card.expiration_date
53
+ @subscription.credit_card.reload.zip_code.should eql("95060")
54
+ end
55
+
56
+ ##
57
+ ## Test Validations
58
+ ##
59
+
60
+ it "should validate card number" do
61
+ @credit_card.number = "foo"
62
+ @credit_card.should_not be_valid
63
+ @credit_card.save.should be_false
64
+ end
65
+
66
+ it "should validate expiration date of card" do
67
+ @credit_card.year = 2001
68
+ @credit_card.should_not be_valid
69
+ @credit_card.save.should be_false
70
+ end
71
+
72
+ it "should be changed" do
73
+ # We're overriding AR#changed? to include instance vars that aren't persisted to see if a new card is being set
74
+ @credit_card.changed?.should be_true #New card is changed
75
+ end
76
+
77
+ it "should be changed after reload" do
78
+ @credit_card.save!
79
+ @credit_card = CreditCard.find(@credit_card.id)
80
+ @credit_card.reload.changed?.should be_false #Saved card is NOT changed
81
+ end
82
+
83
+ it "should not be chanegd for existent card" do
84
+ credit_cards(:bobs_credit_card).changed?.should be_false
85
+ end
86
+
87
+ it "should be chnaged after update" do
88
+ credit_cards(:bobs_credit_card).number = "foo"
89
+ credit_cards(:bobs_credit_card).changed?.should be_true
90
+ end
91
+
92
+ it "should be valid" do
93
+ @credit_card.should be_valid #New card is valid
94
+ end
95
+
96
+ it "should be valid and unchanged for existent cards" do
97
+ # existing cards on file are valid ...
98
+ credit_cards(:bobs_credit_card).changed?.should be_false #Existing card has not changed
99
+ credit_cards(:bobs_credit_card).should be_valid #Existing card is valid
100
+ end
101
+
102
+ it "should validate number of existent card" do
103
+ # ... unless theres an attempt to update them
104
+ credit_cards(:bobs_credit_card).number = "foo"
105
+ credit_cards(:bobs_credit_card).should_not be_valid #Partially changed existing card is not valid
106
+ end
107
+
108
+ it "should validate card type of existent card" do
109
+ # ... unless theres an attempt to update them
110
+ credit_cards(:bobs_credit_card).card_type = "visa"
111
+ credit_cards(:bobs_credit_card).should_not be_valid #Partially changed existing card is not valid
112
+ end
113
+
114
+ end