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.
- data/.gitignore +53 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +121 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +67 -0
- data/Rakefile +23 -0
- data/autotest/discover.rb +2 -0
- data/config/locales/en.yml +2 -0
- data/freemium.gemspec +28 -0
- data/lib/freemium/address.rb +18 -0
- data/lib/freemium/coupon.rb +38 -0
- data/lib/freemium/coupon_redemption.rb +48 -0
- data/lib/freemium/credit_card.rb +273 -0
- data/lib/freemium/feature_set.rb +45 -0
- data/lib/freemium/gateways/base.rb +65 -0
- data/lib/freemium/gateways/brain_tree.rb +175 -0
- data/lib/freemium/gateways/test.rb +34 -0
- data/lib/freemium/manual_billing.rb +73 -0
- data/lib/freemium/railtie.tb +7 -0
- data/lib/freemium/rates.rb +33 -0
- data/lib/freemium/recurring_billing.rb +59 -0
- data/lib/freemium/response.rb +24 -0
- data/lib/freemium/subscription.rb +350 -0
- data/lib/freemium/subscription_change.rb +20 -0
- data/lib/freemium/subscription_mailer/admin_report.rhtml +4 -0
- data/lib/freemium/subscription_mailer/expiration_notice.rhtml +1 -0
- data/lib/freemium/subscription_mailer/expiration_warning.rhtml +1 -0
- data/lib/freemium/subscription_mailer/invoice.text.plain.erb +5 -0
- data/lib/freemium/subscription_mailer.rb +36 -0
- data/lib/freemium/subscription_plan.rb +32 -0
- data/lib/freemium/transaction.rb +15 -0
- data/lib/freemium/version.rb +3 -0
- data/lib/freemium.rb +75 -0
- data/lib/generators/active_record/freemium_generator.rb +28 -0
- data/lib/generators/active_record/templates/migrations/account_transactions.rb +17 -0
- data/lib/generators/active_record/templates/migrations/coupon_redemptions.rb +18 -0
- data/lib/generators/active_record/templates/migrations/coupons.rb +28 -0
- data/lib/generators/active_record/templates/migrations/credit_cards.rb +14 -0
- data/lib/generators/active_record/templates/migrations/subscription_changes.rb +18 -0
- data/lib/generators/active_record/templates/migrations/subscription_plans.rb +14 -0
- data/lib/generators/active_record/templates/migrations/subscriptions.rb +30 -0
- data/lib/generators/active_record/templates/models/account_transaction.rb +3 -0
- data/lib/generators/active_record/templates/models/coupon.rb +3 -0
- data/lib/generators/active_record/templates/models/coupon_redemption.rb +3 -0
- data/lib/generators/active_record/templates/models/credit_card.rb +3 -0
- data/lib/generators/active_record/templates/models/subscription.rb +3 -0
- data/lib/generators/active_record/templates/models/subscription_change.rb +3 -0
- data/lib/generators/active_record/templates/models/subscription_plan.rb +3 -0
- data/lib/generators/freemium/freemium_generator.rb +15 -0
- data/lib/generators/freemium/install_generator.rb +28 -0
- data/lib/generators/freemium/orm_helpers.rb +27 -0
- data/lib/generators/templates/freemium.rb +43 -0
- data/lib/generators/templates/freemium_feature_sets.yml +5 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/models.rb +32 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config/application.rb +45 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +22 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +26 -0
- data/spec/dummy/config/environments/production.rb +49 -0
- data/spec/dummy/config/environments/test.rb +35 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +58 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/schema.rb +92 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/fixtures/credit_cards.yml +11 -0
- data/spec/fixtures/subscription_plans.yml +18 -0
- data/spec/fixtures/subscriptions.yml +29 -0
- data/spec/fixtures/users.yml +16 -0
- data/spec/freemium_feature_sets.yml +9 -0
- data/spec/freemium_spec.rb +4 -0
- data/spec/models/coupon_redemption_spec.rb +235 -0
- data/spec/models/credit_card_spec.rb +114 -0
- data/spec/models/manual_billing_spec.rb +174 -0
- data/spec/models/recurring_billing_spec.rb +92 -0
- data/spec/models/subscription_plan_spec.rb +44 -0
- data/spec/models/subscription_spec.rb +386 -0
- data/spec/spec_helper.rb +38 -0
- data/spec/support/helpers.rb +21 -0
- 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,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,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
|