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.
- data/.coveralls.yml +1 -0
- data/.gitignore +27 -0
- data/.rspec +1 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.md +20 -0
- data/README.md +1 -0
- data/app/mailers/freemium_mailer.rb +36 -0
- data/app/views/subscription_mailer/admin_report.text.erb +4 -0
- data/app/views/subscription_mailer/expiration_notice.text.erb +1 -0
- data/app/views/subscription_mailer/expiration_warning.text.erb +1 -0
- data/app/views/subscription_mailer/invoice.text.erb +5 -0
- data/freemium.gemspec +29 -0
- data/lib/freemium.rb +25 -0
- data/lib/freemium/configuration.rb +27 -0
- data/lib/freemium/coupon.rb +37 -0
- data/lib/freemium/coupon_redemption.rb +59 -0
- data/lib/freemium/credit_card.rb +222 -0
- data/lib/freemium/engine.rb +7 -0
- data/lib/freemium/gateways/base.rb +65 -0
- data/lib/freemium/gateways/brain_tree.rb +175 -0
- data/lib/freemium/gateways/test.rb +36 -0
- data/lib/freemium/rates.rb +33 -0
- data/lib/freemium/response.rb +24 -0
- data/lib/freemium/subscription.rb +384 -0
- data/lib/freemium/subscription_change.rb +20 -0
- data/lib/freemium/subscription_plan.rb +26 -0
- data/lib/freemium/testing/app/controllers/application_controller.rb +7 -0
- data/lib/freemium/testing/application.rb +46 -0
- data/lib/freemium/testing/config/database.yml +11 -0
- data/lib/freemium/testing/config/routes.rb +3 -0
- data/lib/freemium/transaction.rb +15 -0
- data/lib/freemium/version.rb +3 -0
- data/lib/generators/freemium/install/install_generator.rb +58 -0
- data/lib/generators/freemium/install/templates/db/migrate/create_coupon_redemptions.rb +18 -0
- data/lib/generators/freemium/install/templates/db/migrate/create_coupons.rb +28 -0
- data/lib/generators/freemium/install/templates/db/migrate/create_credit_cards.rb +14 -0
- data/lib/generators/freemium/install/templates/db/migrate/create_subscription_changes.rb +21 -0
- data/lib/generators/freemium/install/templates/db/migrate/create_subscription_plans.rb +14 -0
- data/lib/generators/freemium/install/templates/db/migrate/create_subscriptions.rb +31 -0
- data/lib/generators/freemium/install/templates/db/migrate/create_transactions.rb +17 -0
- data/lib/generators/freemium/install/templates/freemium.rb +16 -0
- data/lib/generators/freemium/install/templates/models/coupon.rb +3 -0
- data/lib/generators/freemium/install/templates/models/coupon_redemption.rb +3 -0
- data/lib/generators/freemium/install/templates/models/credit_card.rb +3 -0
- data/lib/generators/freemium/install/templates/models/subscription.rb +3 -0
- data/lib/generators/freemium/install/templates/models/subscription_change.rb +3 -0
- data/lib/generators/freemium/install/templates/models/subscription_plan.rb +3 -0
- data/lib/generators/freemium/install/templates/models/transaction.rb +3 -0
- data/lib/generators/views/USAGE +3 -0
- data/lib/generators/views/views_generator.rb +39 -0
- data/lib/tasks/freemium.rake +17 -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/mailers/mailers.rb +1 -0
- data/spec/dummy/app/models/models.rb +31 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +45 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +21 -0
- data/spec/dummy/config/environment.rb +6 -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 +36 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/freemium.rb +16 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mem_db.rb +12 -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/db/schema.rb +90 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/fixtures/credit_cards.yml +11 -0
- data/spec/fixtures/subscription_plans.yml +15 -0
- data/spec/fixtures/subscriptions.yml +28 -0
- data/spec/fixtures/users.yml +16 -0
- data/spec/lib/tasks/run_billing_rake_spec.rb +14 -0
- data/spec/models/coupon_redemption_spec.rb +287 -0
- data/spec/models/credit_card_spec.rb +124 -0
- data/spec/models/manual_billing_spec.rb +165 -0
- data/spec/models/subscription_plan_spec.rb +46 -0
- data/spec/models/subscription_spec.rb +386 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/support/helpers.rb +18 -0
- data/spec/support/shared_contexts/rake.rb +19 -0
- 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,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,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,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,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
|