freemium-ajb 0.0.4

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