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