freemium-ajb 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. data/.coveralls.yml +1 -0
  2. data/.gitignore +27 -0
  3. data/.rspec +1 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +6 -0
  7. data/Gemfile +4 -0
  8. data/LICENSE.md +20 -0
  9. data/README.md +1 -0
  10. data/app/mailers/freemium_mailer.rb +36 -0
  11. data/app/views/subscription_mailer/admin_report.text.erb +4 -0
  12. data/app/views/subscription_mailer/expiration_notice.text.erb +1 -0
  13. data/app/views/subscription_mailer/expiration_warning.text.erb +1 -0
  14. data/app/views/subscription_mailer/invoice.text.erb +5 -0
  15. data/freemium.gemspec +29 -0
  16. data/lib/freemium.rb +25 -0
  17. data/lib/freemium/configuration.rb +27 -0
  18. data/lib/freemium/coupon.rb +37 -0
  19. data/lib/freemium/coupon_redemption.rb +59 -0
  20. data/lib/freemium/credit_card.rb +222 -0
  21. data/lib/freemium/engine.rb +7 -0
  22. data/lib/freemium/gateways/base.rb +65 -0
  23. data/lib/freemium/gateways/brain_tree.rb +175 -0
  24. data/lib/freemium/gateways/test.rb +36 -0
  25. data/lib/freemium/rates.rb +33 -0
  26. data/lib/freemium/response.rb +24 -0
  27. data/lib/freemium/subscription.rb +384 -0
  28. data/lib/freemium/subscription_change.rb +20 -0
  29. data/lib/freemium/subscription_plan.rb +26 -0
  30. data/lib/freemium/testing/app/controllers/application_controller.rb +7 -0
  31. data/lib/freemium/testing/application.rb +46 -0
  32. data/lib/freemium/testing/config/database.yml +11 -0
  33. data/lib/freemium/testing/config/routes.rb +3 -0
  34. data/lib/freemium/transaction.rb +15 -0
  35. data/lib/freemium/version.rb +3 -0
  36. data/lib/generators/freemium/install/install_generator.rb +58 -0
  37. data/lib/generators/freemium/install/templates/db/migrate/create_coupon_redemptions.rb +18 -0
  38. data/lib/generators/freemium/install/templates/db/migrate/create_coupons.rb +28 -0
  39. data/lib/generators/freemium/install/templates/db/migrate/create_credit_cards.rb +14 -0
  40. data/lib/generators/freemium/install/templates/db/migrate/create_subscription_changes.rb +21 -0
  41. data/lib/generators/freemium/install/templates/db/migrate/create_subscription_plans.rb +14 -0
  42. data/lib/generators/freemium/install/templates/db/migrate/create_subscriptions.rb +31 -0
  43. data/lib/generators/freemium/install/templates/db/migrate/create_transactions.rb +17 -0
  44. data/lib/generators/freemium/install/templates/freemium.rb +16 -0
  45. data/lib/generators/freemium/install/templates/models/coupon.rb +3 -0
  46. data/lib/generators/freemium/install/templates/models/coupon_redemption.rb +3 -0
  47. data/lib/generators/freemium/install/templates/models/credit_card.rb +3 -0
  48. data/lib/generators/freemium/install/templates/models/subscription.rb +3 -0
  49. data/lib/generators/freemium/install/templates/models/subscription_change.rb +3 -0
  50. data/lib/generators/freemium/install/templates/models/subscription_plan.rb +3 -0
  51. data/lib/generators/freemium/install/templates/models/transaction.rb +3 -0
  52. data/lib/generators/views/USAGE +3 -0
  53. data/lib/generators/views/views_generator.rb +39 -0
  54. data/lib/tasks/freemium.rake +17 -0
  55. data/spec/dummy/Rakefile +7 -0
  56. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  57. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  58. data/spec/dummy/app/mailers/mailers.rb +1 -0
  59. data/spec/dummy/app/models/models.rb +31 -0
  60. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  61. data/spec/dummy/config.ru +4 -0
  62. data/spec/dummy/config/application.rb +45 -0
  63. data/spec/dummy/config/boot.rb +10 -0
  64. data/spec/dummy/config/database.yml +21 -0
  65. data/spec/dummy/config/environment.rb +6 -0
  66. data/spec/dummy/config/environments/development.rb +26 -0
  67. data/spec/dummy/config/environments/production.rb +49 -0
  68. data/spec/dummy/config/environments/test.rb +36 -0
  69. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  70. data/spec/dummy/config/initializers/freemium.rb +16 -0
  71. data/spec/dummy/config/initializers/inflections.rb +10 -0
  72. data/spec/dummy/config/initializers/mem_db.rb +12 -0
  73. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  74. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  75. data/spec/dummy/config/initializers/session_store.rb +8 -0
  76. data/spec/dummy/config/locales/en.yml +5 -0
  77. data/spec/dummy/config/routes.rb +58 -0
  78. data/spec/dummy/db/schema.rb +90 -0
  79. data/spec/dummy/script/rails +6 -0
  80. data/spec/fixtures/credit_cards.yml +11 -0
  81. data/spec/fixtures/subscription_plans.yml +15 -0
  82. data/spec/fixtures/subscriptions.yml +28 -0
  83. data/spec/fixtures/users.yml +16 -0
  84. data/spec/lib/tasks/run_billing_rake_spec.rb +14 -0
  85. data/spec/models/coupon_redemption_spec.rb +287 -0
  86. data/spec/models/credit_card_spec.rb +124 -0
  87. data/spec/models/manual_billing_spec.rb +165 -0
  88. data/spec/models/subscription_plan_spec.rb +46 -0
  89. data/spec/models/subscription_spec.rb +386 -0
  90. data/spec/spec_helper.rb +19 -0
  91. data/spec/support/helpers.rb +18 -0
  92. data/spec/support/shared_contexts/rake.rb +19 -0
  93. metadata +270 -0
@@ -0,0 +1,20 @@
1
+ module Freemium
2
+ module SubscriptionChange
3
+
4
+ def self.included(base)
5
+ base.class_eval do
6
+ belongs_to :subscribable, polymorphic: true
7
+
8
+ belongs_to :original_subscription_plan, class_name: "SubscriptionPlan"
9
+ belongs_to :new_subscription_plan, class_name: "SubscriptionPlan"
10
+
11
+ composed_of :new_rate, class_name: 'Money', mapping: [ %w(new_rate_cents cents) ], allow_nil: true
12
+ composed_of :original_rate, class_name: 'Money', mapping: [ %w(original_rate_cents cents) ], allow_nil: true
13
+
14
+ validates_presence_of :reason
15
+ validates_inclusion_of :reason, :in => %w(new upgrade downgrade expiration cancellation)
16
+ end
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ # name
2
+ # key
3
+ # rate_cents
4
+ module Freemium
5
+ module SubscriptionPlan
6
+ include Rates
7
+
8
+ def self.included(base)
9
+ base.class_eval do
10
+ # yes, subscriptions.subscription_plan_id may not be null, but
11
+ # this at least makes the delete not happen if there are any active.
12
+ has_many :subscriptions, dependent: :nullify
13
+ has_and_belongs_to_many :coupons
14
+
15
+ composed_of :rate, class_name: 'Money', mapping: [ %w(rate_cents cents) ], allow_nil: true
16
+
17
+ validates :name, presence: true
18
+ validates :key, presence: true
19
+ validates :rate_cents, presence: true
20
+
21
+ serialize :features, Hash
22
+ end
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,7 @@
1
+ class ApplicationController < ActionController::Base
2
+ include Freemium::Controller
3
+
4
+ def show
5
+ render :text => '', :layout => 'application'
6
+ end
7
+ end
@@ -0,0 +1,46 @@
1
+ require 'rails/all'
2
+
3
+ module Freemium
4
+ module Testing
5
+ APP_ROOT = File.expand_path('..', __FILE__).freeze
6
+
7
+ class Application < Rails::Application
8
+ config.encoding = "utf-8"
9
+ config.action_mailer.default_url_options = { :host => 'localhost' }
10
+
11
+ if Rails::VERSION::MAJOR >= 3 && Rails::VERSION::MINOR >= 1
12
+ config.paths['config/database'] = "#{APP_ROOT}/config/database.yml"
13
+ config.paths['config/routes'] << "#{APP_ROOT}/config/routes.rb"
14
+ config.paths['app/controllers'] << "#{APP_ROOT}/app/controllers"
15
+ config.paths['app/views'] << "#{APP_ROOT}/app/views"
16
+ config.paths['log'] = "tmp/log/development.log"
17
+ config.assets.enabled = true
18
+ else
19
+ config.paths.config.database = "#{APP_ROOT}/config/database.yml"
20
+ config.paths.config.routes << "#{APP_ROOT}/config/routes.rb"
21
+ config.paths.app.controllers << "#{APP_ROOT}/app/controllers"
22
+ config.paths.app.views << "#{APP_ROOT}/app/views"
23
+ config.paths.log = "tmp/log"
24
+ end
25
+
26
+ config.cache_classes = true
27
+ config.whiny_nils = true
28
+ config.consider_all_requests_local = true
29
+ config.action_controller.perform_caching = false
30
+ config.action_dispatch.show_exceptions = false
31
+ config.action_controller.allow_forgery_protection = false
32
+ config.action_mailer.delivery_method = :test
33
+ config.active_support.deprecation = :stderr
34
+ config.secret_token = "SECRET_TOKEN_IS_MIN_30_CHARS_LONG"
35
+
36
+ def require_environment!
37
+ initialize!
38
+ end
39
+
40
+ def initialize!
41
+ FileUtils.mkdir_p(Rails.root.join("db").to_s)
42
+ super unless @initialized
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,11 @@
1
+ development:
2
+ adapter: sqlite3
3
+ database: db/development.sqlite3
4
+ pool: 5
5
+ timeout: 5000
6
+
7
+ test:
8
+ adapter: sqlite3
9
+ database: db/test.sqlite3
10
+ pool: 5
11
+ timeout: 5000
@@ -0,0 +1,3 @@
1
+ Rails.application.routes.draw do
2
+ root :to => "application#show"
3
+ end
@@ -0,0 +1,15 @@
1
+ module Freemium
2
+ module Transaction
3
+
4
+ def self.included(base)
5
+ base.class_eval do
6
+ scope :since, lambda { |time| where(["created_at >= ?", time]) }
7
+
8
+ belongs_to :subscription
9
+
10
+ composed_of :amount, :class_name => 'Money', :mapping => [ %w(amount_cents cents) ], :allow_nil => true
11
+ end
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module Freemium
2
+ VERSION = '0.0.4'
3
+ end
@@ -0,0 +1,58 @@
1
+ require 'rails/generators/base'
2
+ require 'rails/generators/active_record'
3
+
4
+ module Freemium
5
+ module Generators
6
+ MODELS = %w(transaction coupon coupon_redemption credit_card
7
+ subscription subscription_change subscription_plan)
8
+
9
+ class InstallGenerator < Rails::Generators::Base
10
+ include Rails::Generators::Migration
11
+ source_root File.expand_path('../templates', __FILE__)
12
+
13
+ def create_freemium_initializer
14
+ copy_file 'freemium.rb', 'config/initializers/freemium.rb'
15
+ end
16
+
17
+ def create_models
18
+ Freemium::Generators::MODELS.each do |model_name|
19
+ if !File.exists?("app/models/#{model_name}.rb")
20
+ copy_file "models/#{model_name}.rb", "app/models/#{model_name}.rb"
21
+ create_migration "create_#{model_name.pluralize}.rb"
22
+ end
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def create_migration(migration_name, config = {})
29
+ unless migration_exists?(migration_name)
30
+ migration_template(
31
+ "db/migrate/#{migration_name}",
32
+ "db/migrate/#{migration_name}",
33
+ config
34
+ )
35
+ end
36
+ end
37
+
38
+ def migration_exists?(name)
39
+ existing_migrations.include?(name)
40
+ end
41
+
42
+ def existing_migrations
43
+ @existing_migrations ||= Dir.glob("db/migrate/*.rb").map do |file|
44
+ migration_name_without_timestamp(file)
45
+ end
46
+ end
47
+
48
+ def migration_name_without_timestamp(file)
49
+ file.sub(%r{^.*(db/migrate/)(?:\d+_)?}, '')
50
+ end
51
+
52
+ # for generating a timestamp when using `create_migration`
53
+ def self.next_migration_number(dir)
54
+ ActiveRecord::Generators::Base.next_migration_number(dir)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,18 @@
1
+ class CreateCouponRedemptions < ActiveRecord::Migration
2
+ def self.up
3
+
4
+ create_table :coupon_redemptions, :force => true do |t|
5
+ t.column :subscription_id, :integer, :null => false
6
+ t.column :coupon_id, :integer, :null => false
7
+ t.column :redeemed_on, :date, :null => false
8
+ t.column :expired_on, :date, :null => true
9
+ end
10
+
11
+ add_index :coupon_redemptions, :subscription_id
12
+
13
+ end
14
+
15
+ def self.down
16
+ drop_table :coupon_redemptions
17
+ end
18
+ end
@@ -0,0 +1,28 @@
1
+ class CreateCoupons < ActiveRecord::Migration
2
+ def self.up
3
+
4
+ create_table :coupons, :force => true do |t|
5
+ t.column :description, :string, :null => false
6
+ t.column :discount_percentage, :integer, :null => false
7
+ t.column :redemption_key, :string, :null => true
8
+ t.column :redemption_limit, :integer, :null => true
9
+ t.column :redemption_expiration, :date, :null => true
10
+ t.column :duration_in_months, :integer, :null => true
11
+ end
12
+
13
+ create_table :coupons_subscription_plans, :id => false, :force => true do |t|
14
+ t.column :coupon_id, :integer, :null => false
15
+ t.column :subscription_plan_id, :integer, :null => false
16
+ end
17
+
18
+
19
+ add_index :coupons_subscription_plans, :coupon_id, :name => :on_coupon_id
20
+ add_index :coupons_subscription_plans, :subscription_plan_id, :name => :on_subscription_plan_id
21
+
22
+ end
23
+
24
+ def self.down
25
+ drop_table :coupons
26
+ drop_table :coupons_subscription_plans
27
+ end
28
+ end
@@ -0,0 +1,14 @@
1
+ class CreateCreditCards < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :credit_cards, :force => true do |t|
4
+ t.column :display_number, :string, :null => false
5
+ t.column :card_type, :string, :null => false
6
+ t.column :expiration_date, :timestamp, :null => false
7
+ t.column :zip_code, :string, :null => true
8
+ end
9
+ end
10
+
11
+ def self.down
12
+ drop_table :credit_cards
13
+ end
14
+ end
@@ -0,0 +1,21 @@
1
+ class CreateSubscriptionChanges < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :subscription_changes, :force => true do |t|
4
+ t.column :subscribable_id, :integer, :null => false
5
+ t.column :subscribable_type, :string, :null => false
6
+ t.column :original_subscription_plan_id, :integer, :null => true
7
+ t.column :new_subscription_plan_id, :integer, :null => true
8
+ t.column :original_rate_cents, :integer, :null => true
9
+ t.column :new_rate_cents, :integer, :null => true
10
+ t.column :reason, :string, :null => false
11
+ t.column :created_at, :timestamp, :null => false
12
+ end
13
+
14
+ add_index :subscription_changes, :reason
15
+ add_index :subscription_changes, [:subscribable_id, :subscribable_type], name: :subscription_changes_on_subscribable
16
+ end
17
+
18
+ def self.down
19
+ drop_table :subscription_changes
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ class CreateSubscriptionPlans < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :subscription_plans, :force => true do |t|
4
+ t.column :name, :string, :null => false
5
+ t.column :key, :string, :null => false
6
+ t.column :rate_cents, :integer, :null => false
7
+ t.column :features, :text
8
+ end
9
+ end
10
+
11
+ def self.down
12
+ drop_table :subscription_plans
13
+ end
14
+ end
@@ -0,0 +1,31 @@
1
+ class CreateSubscriptions < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :subscriptions, :force => true do |t|
4
+ t.column :subscribable_id, :integer, :null => false
5
+ t.column :subscribable_type, :string, :null => false
6
+ t.column :billing_key, :string, :null => true
7
+ t.column :credit_card_id, :integer, :null => true
8
+ t.column :subscription_plan_id, :integer, :null => false
9
+ t.column :paid_through, :date, :null => true
10
+ t.column :expire_on, :date, :null => true
11
+ t.column :billing_key, :string, :null => true
12
+ t.column :started_on, :date, :null => true
13
+ t.column :last_transaction_at, :datetime, :null => true
14
+ t.column :last_transaction_success, :boolean, :null => true
15
+ t.column :in_trial, :boolean, :null => false, :default => false
16
+ end
17
+ # for polymorphic association queries
18
+ add_index :subscriptions, :subscribable_id
19
+ add_index :subscriptions, :subscribable_type
20
+ add_index :subscriptions, [:subscribable_id, :subscribable_type], name: :subscriptions_on_subscribable
21
+ # for finding due, pastdue, and expiring subscriptions
22
+ add_index :subscriptions, :paid_through
23
+ add_index :subscriptions, :expire_on
24
+ # for applying transactions from automated recurring billing
25
+ add_index :subscriptions, :billing_key
26
+ end
27
+
28
+ def self.down
29
+ drop_table :subscriptions
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ class CreateTransactions < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :transactions, :force => true do |t|
4
+ t.column :subscription_id, :integer, :null => false
5
+ t.column :success, :boolean, :null => false
6
+ t.column :billing_key, :string, :null => false
7
+ t.column :amount_cents, :integer, :null => false
8
+ t.column :message, :string, :null => true
9
+ t.column :created_at, :timestamp, :null => false
10
+ end
11
+ add_index :transactions, :subscription_id
12
+ end
13
+
14
+ def self.down
15
+ drop_table :transactions
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ Freemium.configure do |c|
2
+ if Rails.env.test?
3
+ c.gateway = Freemium::Gateways::Test.new
4
+ else
5
+ c.gateway = Freemium::Gateways::BrainTree.new
6
+ c.gateway.username = "demo"
7
+ c.gateway.password = "password"
8
+ end
9
+
10
+ c.admin_report_recipients = ["your@email.com"]
11
+
12
+ # c.days_grace = 3
13
+ # c.days_free_trial = 0
14
+ # c.mailer = FreemiumMailer
15
+ # c.expired_plan = nil
16
+ end
@@ -0,0 +1,3 @@
1
+ class Coupon < ActiveRecord::Base
2
+ include Freemium::Coupon
3
+ end
@@ -0,0 +1,3 @@
1
+ class CouponRedemption < ActiveRecord::Base
2
+ include Freemium::CouponRedemption
3
+ end
@@ -0,0 +1,3 @@
1
+ class CreditCard < ActiveRecord::Base
2
+ include Freemium::CreditCard
3
+ end
@@ -0,0 +1,3 @@
1
+ class Subscription < ActiveRecord::Base
2
+ include Freemium::Subscription
3
+ end
@@ -0,0 +1,3 @@
1
+ class SubscriptionChange < ActiveRecord::Base
2
+ include Freemium::SubscriptionChange
3
+ end
@@ -0,0 +1,3 @@
1
+ class SubscriptionPlan < ActiveRecord::Base
2
+ include Freemium::SubscriptionPlan
3
+ end
@@ -0,0 +1,3 @@
1
+ class Transaction < ActiveRecord::Base
2
+ include Freemium::Transaction
3
+ end
@@ -0,0 +1,3 @@
1
+ Description:
2
+ Override the default freemum views. This generator will copy all off the
3
+ base freemium views into your project.
@@ -0,0 +1,39 @@
1
+ require 'rails/generators/base'
2
+
3
+ module Freemium
4
+ module Generators
5
+ class ViewsGenerator < Rails::Generators::Base
6
+ source_root Freemium.root
7
+
8
+ def create_views
9
+ views.each do |view|
10
+ copy_file view
11
+ end
12
+ end
13
+
14
+ def create_locales
15
+ locales.each do |locale|
16
+ copy_file locale
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def views
23
+ files_within_root('.', 'app/views/**/*.*')
24
+ end
25
+
26
+ def locales
27
+ files_within_root('.', 'config/locales/**/*.*')
28
+ end
29
+
30
+ def files_within_root(prefix, glob)
31
+ root = "#{self.class.source_root}/#{prefix}"
32
+
33
+ Dir["#{root}/#{glob}"].sort.map do |full_path|
34
+ full_path.sub(root, '.').gsub('/./', '/')
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end