paid_up 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +46 -0
- data/LICENSE.txt +20 -0
- data/MIT-LICENSE +20 -0
- data/README.md +64 -0
- data/Rakefile +48 -0
- data/VERSION +1 -0
- data/app/controllers/paid_up/paid_up_controller.rb +12 -0
- data/app/controllers/paid_up/plans_controller.rb +7 -0
- data/app/controllers/paid_up/subscriptions_controller.rb +37 -0
- data/app/helpers/paid_up/features_helper.rb +65 -0
- data/app/helpers/paid_up/paid_up_helper.rb +14 -0
- data/app/helpers/paid_up/plans_helper.rb +53 -0
- data/app/helpers/paid_up/subscriptions_helper.rb +21 -0
- data/app/models/ability.rb +37 -0
- data/app/models/paid_up/feature.rb +19 -0
- data/app/models/paid_up/features_plan.rb +14 -0
- data/app/models/paid_up/plan.rb +99 -0
- data/app/views/devise/confirmations/new.html.haml +13 -0
- data/app/views/devise/passwords/edit.html.haml +19 -0
- data/app/views/devise/passwords/new.html.haml +14 -0
- data/app/views/devise/registrations/_new_form.html.haml +23 -0
- data/app/views/devise/registrations/edit.html.haml +39 -0
- data/app/views/devise/registrations/new.html.haml +11 -0
- data/app/views/devise/sessions/_new_form.html.haml +21 -0
- data/app/views/devise/sessions/new.html.haml +11 -0
- data/app/views/devise/shared/_links.html.haml +14 -0
- data/app/views/devise/unlocks/new.html.haml +14 -0
- data/app/views/paid_up/features/_abilities_table.html.haml +22 -0
- data/app/views/paid_up/features/_table.html.haml +16 -0
- data/app/views/paid_up/plans/index.html.haml +18 -0
- data/app/views/paid_up/subscriptions/index.html.haml +16 -0
- data/app/views/paid_up/subscriptions/new.html.haml +101 -0
- data/bin/rails +12 -0
- data/config/initializers/stripe.rb +6 -0
- data/config/locales/en.yml +85 -0
- data/config/routes.rb +7 -0
- data/coverage/.last_run.json +5 -0
- data/coverage/.resultset.json +1488 -0
- data/coverage/.resultset.json.lock +0 -0
- data/db/migrate/20150407105900_create_paid_up_features_plans_table.rb +9 -0
- data/db/migrate/20150407110100_create_paid_up_features_table.rb +11 -0
- data/db/migrate/20150407110101_create_paid_up_plans_table.rb +14 -0
- data/lib/generators/paid_up/install/install_generator.rb +59 -0
- data/lib/generators/paid_up/install/templates/initializer.rb.erb +5 -0
- data/lib/generators/paid_up/utils.rb +16 -0
- data/lib/paid_up/configuration.rb +22 -0
- data/lib/paid_up/engine.rb +17 -0
- data/lib/paid_up/integer.rb +9 -0
- data/lib/paid_up/localization.rb +31 -0
- data/lib/paid_up/mixins.rb +139 -0
- data/lib/paid_up/railtie.rb +4 -0
- data/lib/paid_up/stripe_extensions.rb +17 -0
- data/lib/paid_up/table_validator.rb +9 -0
- data/lib/paid_up/unlimited.rb +17 -0
- data/lib/paid_up/version.rb +7 -0
- data/lib/paid_up.rb +27 -0
- data/paid_up.gemspec +248 -0
- data/spec/controllers/paid_up/plans_spec.rb +26 -0
- data/spec/controllers/paid_up/subscriptions_spec.rb +187 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +16 -0
- data/spec/dummy/app/assets/stylesheets/application.css.scss +16 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/models/group.rb +3 -0
- data/spec/dummy/app/models/user.rb +9 -0
- data/spec/dummy/app/views/layouts/application.html.haml +31 -0
- data/spec/dummy/app/views/pages/index.html.haml +1 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +29 -0
- data/spec/dummy/config/application.rb +32 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +46 -0
- data/spec/dummy/config/environments/production.rb +79 -0
- data/spec/dummy/config/environments/test.rb +45 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/devise.rb +259 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/high_voltage.rb +3 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/paid_up.rb +5 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/devise.en.yml +60 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +4 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20150406154440_create_users_table.rb +7 -0
- data/spec/dummy/db/migrate/20150517175135_create_groups_table.rb +11 -0
- data/spec/dummy/db/migrate/20150518000915_add_devise_to_users.rb +49 -0
- data/spec/dummy/db/migrate/20150518000917_create_paid_up_features_plans_table.paid_up.rb +10 -0
- data/spec/dummy/db/migrate/20150518000918_create_paid_up_features_table.paid_up.rb +12 -0
- data/spec/dummy/db/migrate/20150518000919_create_paid_up_plans_table.paid_up.rb +15 -0
- data/spec/dummy/db/migrate/20150518000920_add_stripe_id_column_to_user.rb +6 -0
- data/spec/dummy/db/schema.rb +75 -0
- data/spec/dummy/db/seeds/features.seeds.rb +19 -0
- data/spec/dummy/db/seeds/features_plans.seeds.rb +51 -0
- data/spec/dummy/db/seeds/plans.seeds.rb +91 -0
- data/spec/dummy/db/seeds.rb +0 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/log/development.log +108393 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/assets/.sprockets-manifest-34b01376fc56586f4f4fd19e7b1c0e35.json +1 -0
- data/spec/dummy/public/assets/application-27f004bb902952dbbaad25f0f28c312f29ffee315c94fa51dea6b5ec6dc993e6.css +13 -0
- data/spec/dummy/public/assets/application-3a226fbacd7ba9a2b9f7972fafdd7b2486c34707d96a60c26f3bbe4579c29ca6.js +116 -0
- data/spec/dummy/public/assets/bootstrap/glyphicons-halflings-regular-13634da87d9e23f8c3ed9108ce1724d183a39ad072e73e1b3d8cbf646d2d0407.eot +0 -0
- data/spec/dummy/public/assets/bootstrap/glyphicons-halflings-regular-42f60659d265c1a3c30f9fa42abcbb56bd4a53af4d83d316d6dd7a36903c43e5.svg +288 -0
- data/spec/dummy/public/assets/bootstrap/glyphicons-halflings-regular-a26394f7ede100ca118eff2eda08596275a9839b959c226e15439557a5a80742.woff +0 -0
- data/spec/dummy/public/assets/bootstrap/glyphicons-halflings-regular-e395044093757d82afcb138957d06a1ea9361bdcf0b442d06a18a8051af57456.ttf +0 -0
- data/spec/dummy/public/assets/bootstrap/glyphicons-halflings-regular-fe185d11a49676890d47bb783312a0cda5a44c4039214094e7957b4c040ef11c.woff2 +0 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/test/controllers/plans_controller_controller_test.rb +7 -0
- data/spec/factories/feature.rb +7 -0
- data/spec/factories/features_plan.rb +7 -0
- data/spec/factories/plan.rb +7 -0
- data/spec/factories/user.rb +7 -0
- data/spec/models/paid_up/feature_spec.rb +21 -0
- data/spec/models/paid_up/features_plan_spec.rb +7 -0
- data/spec/models/paid_up/plan_spec.rb +156 -0
- data/spec/models/user_spec.rb +304 -0
- data/spec/paid_up_spec.rb +7 -0
- data/spec/rails_helper.rb +65 -0
- data/spec/routing/paid_up/plans_spec.rb +11 -0
- data/spec/routing/paid_up/subscription_spec.rb +23 -0
- data/spec/spec_helper.rb +87 -0
- data/spec/support/controller_macros.rb +12 -0
- data/spec/support/factory_girl.rb +3 -0
- data/spec/support/plans_and_features.rb +209 -0
- data/spec/support/stripe.rb +23 -0
- data/spec/support/subscribers.rb +44 -0
- data/spec/views/paid_up/plans_spec.rb +54 -0
- data/spec/views/paid_up/subscriptions_spec.rb +58 -0
- metadata +488 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8749a10b3993668ec11892a4a49391b4235db01c
|
4
|
+
data.tar.gz: 6f348418bef2d0fdc4c5b4b485050574bb8dbec9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c58032ff450db87621d911fb5162b148359fa0c0af89a584e0dbd5b14c985ac58b226ec16d6b8390809a85dda234bdd1a524769edb700d874f726f9f125e5a68
|
7
|
+
data.tar.gz: 199c1168d660868995886ded446078f81403f91fa3ae144cf714417bffdc3ac13baddf09aeaa6d9336c134d84108a5ac5597cf1612de83cb135d076ce24bf0d3
|
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
gem 'rails', '>= 4.2'
|
4
|
+
gem 'rails-i18n'
|
5
|
+
gem 'haml-rails'
|
6
|
+
gem 'jquery-rails'
|
7
|
+
gem 'uglifier'
|
8
|
+
gem 'jeweler'
|
9
|
+
|
10
|
+
gem 'bootstrap_leather'
|
11
|
+
|
12
|
+
gem 'seedbank'
|
13
|
+
gem 'chronic'
|
14
|
+
gem 'money'
|
15
|
+
|
16
|
+
gem 'devise'
|
17
|
+
gem 'cancan'
|
18
|
+
gem 'stripe'
|
19
|
+
|
20
|
+
group :test, :development do
|
21
|
+
gem 'rspec-rails'
|
22
|
+
gem 'rspec-collection_matchers'
|
23
|
+
gem 'database_cleaner'
|
24
|
+
gem 'sqlite3'
|
25
|
+
gem 'high_voltage'
|
26
|
+
gem 'forgery'
|
27
|
+
gem 'bootstrap-sass', '~> 3.3.4'
|
28
|
+
gem 'sass-rails', '>= 3.2'
|
29
|
+
end
|
30
|
+
|
31
|
+
group :test do
|
32
|
+
gem 'cucumber-rails', require: false
|
33
|
+
gem 'shoulda-matchers'
|
34
|
+
gem 'minitest', '~>5.5.1'
|
35
|
+
gem "launchy", "~> 2.1.2", require: false
|
36
|
+
gem 'factory_girl_rails', require: false
|
37
|
+
gem 'coveralls', require: false
|
38
|
+
end
|
39
|
+
|
40
|
+
# Declare any dependencies that are still in development here instead of in
|
41
|
+
# your gemspec. These might include edge Rails or gems from your path or
|
42
|
+
# Git. Remember to move these dependencies to your gemspec before releasing
|
43
|
+
# your gem to rubygems.org.
|
44
|
+
|
45
|
+
# To use debugger
|
46
|
+
# gem 'debugger'
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2015 Gem Vein
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2015 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
Paid Up
|
2
|
+
===========
|
3
|
+
|
4
|
+
[![GitHub version](https://badge.fury.io/gh/gemvein%2Fpaid_up.svg)](http://badge.fury.io/gh/gemvein%2Fpaid_up)
|
5
|
+
[![Build Status](https://travis-ci.org/gemvein/paid_up.svg)](https://travis-ci.org/gemvein/paid_up)
|
6
|
+
[![Coverage Status](https://coveralls.io/repos/gemvein/paid_up/badge.png)](https://coveralls.io/r/gemvein/paid_up)
|
7
|
+
|
8
|
+
Paid Up is a start-to-finish Stripe subscription engine. You set up the plans you want on Stripe, and the gem gives you a way to tie those plans to authenticated users, granting them abilities based on the features outlined for their plan.
|
9
|
+
|
10
|
+
* Ruby 2, Rails 4
|
11
|
+
* Authentication by Devise
|
12
|
+
* Authorization by CanCan
|
13
|
+
* Subscription by Stripe
|
14
|
+
* Assumes you will be using some variety of Bootstrap, and designed to be quite responsive out of the box, but included views can be overridden with custom views.
|
15
|
+
|
16
|
+
Installation
|
17
|
+
----------------------------
|
18
|
+
First, add paid_up to your `Gemfile`:
|
19
|
+
|
20
|
+
gem 'paid_up'
|
21
|
+
|
22
|
+
To start with, you will need a User model and the corresponding table, set up with your business rules for users, such as profile fields or theme configuration.
|
23
|
+
|
24
|
+
Next, install PaidUp for your user model by executing these commands:
|
25
|
+
|
26
|
+
bundle install
|
27
|
+
rails g paid_up:install
|
28
|
+
rake db:migrate
|
29
|
+
|
30
|
+
Configuration
|
31
|
+
----------------------------
|
32
|
+
|
33
|
+
Edit your config file at `config/initializers/paid_up.rb` to set up some other key details.
|
34
|
+
|
35
|
+
Set your environment variables with your STRIPE_PUBLISHABLE_KEY and your STRIPE_SECRET_KEY. (Check your operating system or IDE's documentation for details)
|
36
|
+
|
37
|
+
Stripe Setup
|
38
|
+
----------------------------
|
39
|
+
|
40
|
+
Using your own code or Stripe's convenient web interface, add the plans you intend to offer. Each will also need a record in your own database, so for each `Stripe::Plan` you create, note the `id` and use it as the `stripe_id` in the corresponding `PaidUp::Plan`. At a minimum, you will need an anonymous plan, a free plan, both with a cost amount of `0`; and also at least one paid plan.
|
41
|
+
|
42
|
+
Next, add a `Stripe::Customer` to serve as the Anonymous User, and subscribe that customer to the anonymous plan. Note the customer's `id` and copy that into your stripe configuration file.
|
43
|
+
|
44
|
+
Features Setup
|
45
|
+
----------------------------
|
46
|
+
|
47
|
+
Set up each `PaidUp::Feature` using your own admin, the console, or seed data. Associate the features with the corresponding plans using the `PaidUp::FeaturesPlan` joining table model. For example, check out the seeds files in [`spec/dummy/db/seeds/`](spec/dummy/db/seeds/)
|
48
|
+
|
49
|
+
Contributing to Paid Up
|
50
|
+
----------------------------
|
51
|
+
|
52
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
53
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
54
|
+
* Fork the project.
|
55
|
+
* Start a feature/bugfix branch.
|
56
|
+
* Commit and push until you are happy with your contribution.
|
57
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
58
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
59
|
+
|
60
|
+
Copyright
|
61
|
+
---------
|
62
|
+
|
63
|
+
Copyright (c) 2015 Gem Vein. See LICENSE.txt for further details.
|
64
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'jeweler'
|
8
|
+
Jeweler::Tasks.new do |gem|
|
9
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
10
|
+
gem.name = "paid_up"
|
11
|
+
gem.homepage = "http://www.gemvein.com/museum/cases/paid_up"
|
12
|
+
gem.license = "MIT"
|
13
|
+
gem.summary = %Q{Allows a model of your choosing to subscribe to a plan, which enables features.}
|
14
|
+
gem.description = %Q{Allows a model of your choosing (such as users) to subscribe to a plan, which enables features.}
|
15
|
+
gem.email = "karen.e.lundgren@gmail.com"
|
16
|
+
gem.authors = ["Karen Lundgren"]
|
17
|
+
# dependencies defined in Gemfile
|
18
|
+
end
|
19
|
+
Jeweler::RubygemsDotOrgTasks.new
|
20
|
+
|
21
|
+
require 'rspec/core'
|
22
|
+
require 'rspec/core/rake_task'
|
23
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
24
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
25
|
+
end
|
26
|
+
|
27
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
28
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
29
|
+
spec.rcov = true
|
30
|
+
end
|
31
|
+
|
32
|
+
task :default => :spec
|
33
|
+
|
34
|
+
require 'rdoc/task'
|
35
|
+
|
36
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
37
|
+
rdoc.rdoc_dir = 'rdoc'
|
38
|
+
rdoc.title = 'PaidUp'
|
39
|
+
rdoc.options << '--line-numbers'
|
40
|
+
rdoc.rdoc_files.include('README.rdoc')
|
41
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
Bundler::GemHelper.install_tasks
|
48
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module PaidUp
|
2
|
+
class SubscriptionsController < PaidUpController
|
3
|
+
before_filter :authenticate_user!
|
4
|
+
before_filter :set_plan, only: [:new, :create]
|
5
|
+
|
6
|
+
def index
|
7
|
+
# nothing to do, everything we need is in current_user
|
8
|
+
end
|
9
|
+
|
10
|
+
def new
|
11
|
+
# nothing to do, @plan set by #set_plan
|
12
|
+
if current_user.can_downgrade_to? @plan || @plan.amount == 0
|
13
|
+
create
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def create
|
18
|
+
# @plan set by #set_plan
|
19
|
+
if current_user.subscribe_to_plan(@plan, params[:stripeToken])
|
20
|
+
redirect_to subscriptions_path, flash: { notice: :you_are_now_subscribed_to_the_plan.l(plan_name: current_user.plan.name) }
|
21
|
+
else
|
22
|
+
redirect_to new_plan_subscription_path @plan, flash: { error: current_user.errors.full_messages || :could_not_subscribe_to_plan.l(plan: @plan.name) }
|
23
|
+
end
|
24
|
+
rescue Stripe::InvalidRequestError => e
|
25
|
+
flash[:error] = e.message
|
26
|
+
redirect_to plans_path
|
27
|
+
rescue Stripe::CardError => e
|
28
|
+
flash[:error] = e.message
|
29
|
+
redirect_to new_plan_subscription_path
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def set_plan
|
34
|
+
@plan = PaidUp::Plan.find(params[:plan_id])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module PaidUp
|
2
|
+
module FeaturesHelper
|
3
|
+
include PaidUp::PaidUpHelper
|
4
|
+
|
5
|
+
def features_dl(plan)
|
6
|
+
data = {}
|
7
|
+
features = PaidUp::Feature.all
|
8
|
+
|
9
|
+
for feature in features
|
10
|
+
data[feature.title] = feature_display feature, plan
|
11
|
+
end
|
12
|
+
|
13
|
+
dl data
|
14
|
+
end
|
15
|
+
|
16
|
+
def feature_display(feature, plan)
|
17
|
+
if feature.setting_type == 'boolean'
|
18
|
+
if plan.feature_setting(feature.id)
|
19
|
+
icon 'ok'
|
20
|
+
else
|
21
|
+
icon 'remove'
|
22
|
+
end
|
23
|
+
elsif plan.feature_unlimited?(feature.id)
|
24
|
+
:unlimited.l
|
25
|
+
else
|
26
|
+
plan.feature_setting(feature.id)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def features_table(options = {})
|
31
|
+
features = PaidUp::Feature.all
|
32
|
+
|
33
|
+
if !options[:should_add_buttons].nil?
|
34
|
+
should_add_buttons = options[:should_add_buttons]
|
35
|
+
options.delete(:should_add_buttons)
|
36
|
+
else
|
37
|
+
should_add_buttons = true
|
38
|
+
end
|
39
|
+
|
40
|
+
plans = PaidUp::Plan.subscribable
|
41
|
+
if options[:only].present?
|
42
|
+
plans = plans.where('id IN (?)', options[:only])
|
43
|
+
options.delete(:only)
|
44
|
+
end
|
45
|
+
if options[:except].present?
|
46
|
+
plans = plans.where('NOT ( id IN (?) )', options[:except])
|
47
|
+
options.delete(:except)
|
48
|
+
end
|
49
|
+
|
50
|
+
if options[:highlight].present?
|
51
|
+
highlight_plan = options[:highlight]
|
52
|
+
options.delete(:highlight)
|
53
|
+
else
|
54
|
+
highlight_plan = nil
|
55
|
+
end
|
56
|
+
|
57
|
+
render(partial: 'paid_up/features/table', locals: { should_add_buttons: should_add_buttons, plans: plans, features: features, highlight_plan: highlight_plan, html_options: options})
|
58
|
+
end
|
59
|
+
|
60
|
+
def feature_abilities_table(options = {})
|
61
|
+
features = PaidUp::Feature.all
|
62
|
+
render(partial: 'paid_up/features/abilities_table', locals: { features: features, html_options: options})
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module PaidUp
|
2
|
+
module PaidUpHelper
|
3
|
+
def date_range(start_date, end_date)
|
4
|
+
dates = []
|
5
|
+
if start_date.present? && (start_date.is_a?(Date) || start_date.is_a?(Integer))
|
6
|
+
dates << start_date.to_date
|
7
|
+
end
|
8
|
+
if end_date.present? && (end_date.is_a?(Date) || end_date.is_a?(Integer))
|
9
|
+
dates << end_date.to_date
|
10
|
+
end
|
11
|
+
dates.join('—').html_safe
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module PaidUp
|
2
|
+
module PlansHelper
|
3
|
+
include ::ActionView::Helpers::NumberHelper
|
4
|
+
|
5
|
+
def plan_charge_human(plan)
|
6
|
+
plan.money.format + '/' + plan_period_phrase(plan)
|
7
|
+
end
|
8
|
+
|
9
|
+
def plan_period_phrase(plan)
|
10
|
+
period_phrase = ''
|
11
|
+
if plan.interval_count > 1
|
12
|
+
period_phrase += plan.interval_count.to_s
|
13
|
+
period_phrase += ' '
|
14
|
+
end
|
15
|
+
period_phrase + plan.interval.capitalize
|
16
|
+
end
|
17
|
+
|
18
|
+
def plan_button(plan, text = nil, html_options = {})
|
19
|
+
data = {}
|
20
|
+
css_class = 'btn '
|
21
|
+
disabled_state = false
|
22
|
+
link = paid_up.new_plan_subscription_path(plan)
|
23
|
+
if user_signed_in?
|
24
|
+
text ||= :subscribe.l
|
25
|
+
if current_user.can_upgrade_to? plan
|
26
|
+
icon_class = 'arrow-up'
|
27
|
+
css_class += 'btn-success'
|
28
|
+
elsif current_user.can_downgrade_to? plan
|
29
|
+
icon_class = 'arrow-down'
|
30
|
+
css_class += 'btn-danger'
|
31
|
+
data[:confirm] = :are_you_sure.l
|
32
|
+
elsif current_user.is_subscribed_to? plan
|
33
|
+
icon_class = 'ok'
|
34
|
+
css_class += 'btn-disabled'
|
35
|
+
disabled_state = true
|
36
|
+
link = '#'
|
37
|
+
text = :already_subscribed.l
|
38
|
+
else # Plans are equal in sort_order
|
39
|
+
icon_class = 'arrow-right'
|
40
|
+
css_class += 'btn-info'
|
41
|
+
end
|
42
|
+
else
|
43
|
+
text ||= :sign_up.l
|
44
|
+
icon_class = 'arrow-right'
|
45
|
+
css_class += 'btn-success'
|
46
|
+
end
|
47
|
+
html_options[:method] ||= :get
|
48
|
+
html_options[:disabled] ||= disabled_state
|
49
|
+
icon_button_to css_class, icon_class, text, link, html_options
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module PaidUp
|
2
|
+
module SubscriptionsHelper
|
3
|
+
include PaidUp::PaidUpHelper
|
4
|
+
|
5
|
+
def subscription_dl(subscription)
|
6
|
+
data = {}
|
7
|
+
|
8
|
+
data[:status.l] = subscription.status
|
9
|
+
|
10
|
+
if subscription.current_period_start || subscription.current_period_end
|
11
|
+
data[:paid_thru.l]= date_range(subscription.current_period_start, subscription.current_period_end)
|
12
|
+
end
|
13
|
+
|
14
|
+
if subscription.trial_start || subscription.trial_end
|
15
|
+
data[:trial_period.l]= date_range(subscription.trial_start, subscription.trial_end)
|
16
|
+
end
|
17
|
+
|
18
|
+
dl data
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Ability
|
2
|
+
include CanCan::Ability
|
3
|
+
|
4
|
+
def initialize(user)
|
5
|
+
user ||= User.new # anonymous user (not logged in)
|
6
|
+
|
7
|
+
# Rails Application's initialization could go here.
|
8
|
+
|
9
|
+
initialize_paid_up(user)
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize_paid_up(user)
|
13
|
+
features = PaidUp::Feature.all
|
14
|
+
|
15
|
+
for feature in features
|
16
|
+
|
17
|
+
case feature.setting_type
|
18
|
+
when 'table_rows'
|
19
|
+
model = feature.name.classify.constantize
|
20
|
+
if user.table_rows_allowed(feature.name) > 0 || user.table_rows_allowed(feature.name) == -1
|
21
|
+
can :manage, model, :user => user
|
22
|
+
can :own, model
|
23
|
+
unless user.table_rows_remaining(feature.name) > 0
|
24
|
+
cannot :create, model
|
25
|
+
end
|
26
|
+
end
|
27
|
+
can :read, model
|
28
|
+
when 'boolean'
|
29
|
+
if user.plan.feature_setting feature.id
|
30
|
+
can :use, feature.name.to_sym
|
31
|
+
end
|
32
|
+
else
|
33
|
+
raise(:unknown_feature_type.l)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class PaidUp::Feature < ActiveRecord::Base
|
2
|
+
has_many :features_plans, class_name: 'PaidUp::FeaturesPlan'
|
3
|
+
has_many :plans, :through => :features_plans, class_name: 'PaidUp::Plan'
|
4
|
+
|
5
|
+
validates_presence_of :name, :title, :setting_type
|
6
|
+
|
7
|
+
validates_with PaidUp::TableValidator, field: 'setting_type', comparison: 'table_rows', found_in: 'name'
|
8
|
+
|
9
|
+
def feature_model_name
|
10
|
+
if setting_type != 'table_rows'
|
11
|
+
raise :no_conversion_of_type_features_to_table_rows.l type: setting_type
|
12
|
+
end
|
13
|
+
name.classify
|
14
|
+
end
|
15
|
+
|
16
|
+
def feature_model
|
17
|
+
feature_model_name.constantize
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class PaidUp::FeaturesPlan < ActiveRecord::Base
|
2
|
+
belongs_to :plan, class_name: 'PaidUp::Plan'
|
3
|
+
belongs_to :feature, class_name: 'PaidUp::Feature'
|
4
|
+
validates_presence_of :setting
|
5
|
+
|
6
|
+
after_initialize :catch_unlimited_in_setting
|
7
|
+
|
8
|
+
private
|
9
|
+
def catch_unlimited_in_setting
|
10
|
+
if setting == PaidUp::Unlimited.to_i(:db)
|
11
|
+
self.setting = PaidUp::Unlimited
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
class PaidUp::Plan < ActiveRecord::Base
|
2
|
+
has_many :features_plans, class_name: 'PaidUp::FeaturesPlan'
|
3
|
+
has_many :features, :through => :features_plans, class_name: 'PaidUp::Feature'
|
4
|
+
|
5
|
+
validates_presence_of :description, :name
|
6
|
+
|
7
|
+
after_initialize :load_stripe_data
|
8
|
+
|
9
|
+
attr_accessor :stripe_data
|
10
|
+
|
11
|
+
default_scope { order('sort_order ASC') }
|
12
|
+
scope :subscribable, -> { where('sort_order >= ?', 0) }
|
13
|
+
scope :free, -> { find_by_stripe_id(PaidUp.configuration.free_plan_stripe_id) }
|
14
|
+
|
15
|
+
def reload(*args, &blk)
|
16
|
+
super *args, &blk
|
17
|
+
load_stripe_data
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def feature_setting_by_name(feature_name)
|
22
|
+
feature = PaidUp::Feature.find_by_name(feature_name) || raise(:feature_not_found.l)
|
23
|
+
feature_setting(feature.id)
|
24
|
+
end
|
25
|
+
|
26
|
+
def feature_setting(feature_id)
|
27
|
+
feature = PaidUp::Feature.find(feature_id) || raise(:feature_not_found.l)
|
28
|
+
raw = features_plans.where(feature_id: feature_id)
|
29
|
+
case feature.setting_type
|
30
|
+
when 'boolean'
|
31
|
+
if raw.empty?
|
32
|
+
false
|
33
|
+
else
|
34
|
+
raw.first.setting != 0
|
35
|
+
end
|
36
|
+
when 'table_rows'
|
37
|
+
if raw.empty?
|
38
|
+
0
|
39
|
+
else
|
40
|
+
raw.first.setting
|
41
|
+
end
|
42
|
+
else
|
43
|
+
raise :error_handling_feature_setting.l feature: feature
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def feature_unlimited?(feature_id)
|
48
|
+
feature_setting(feature_id) == PaidUp::Unlimited.to_i
|
49
|
+
end
|
50
|
+
|
51
|
+
def interval
|
52
|
+
if stripe_data.present?
|
53
|
+
stripe_data.interval
|
54
|
+
else
|
55
|
+
:default_interval.l
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def interval_count
|
60
|
+
if stripe_data.present?
|
61
|
+
stripe_data.interval_count
|
62
|
+
else
|
63
|
+
1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def amount
|
68
|
+
if stripe_data.present?
|
69
|
+
stripe_data.amount
|
70
|
+
else
|
71
|
+
0
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def money
|
76
|
+
Money.new(amount, currency)
|
77
|
+
end
|
78
|
+
|
79
|
+
def charge
|
80
|
+
money.amount
|
81
|
+
end
|
82
|
+
|
83
|
+
def currency
|
84
|
+
if stripe_data.present?
|
85
|
+
stripe_data.currency.upcase
|
86
|
+
else
|
87
|
+
:default_currency.l.upcase
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def load_stripe_data
|
94
|
+
if stripe_id.present?
|
95
|
+
self.stripe_data = Stripe::Plan.retrieve stripe_id
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
- add_title :resend_confirmation_instructions.l
|
2
|
+
= form_for(resource, as: resource_name, url: confirmation_path(resource_name), class: 'form-horizontal', html: { method: :post }) do |f|
|
3
|
+
= devise_error_messages!
|
4
|
+
.form-group
|
5
|
+
= f.label :email, class: 'col-sm-2 control-label'
|
6
|
+
.col-sm-2
|
7
|
+
= f.email_field :email, autofocus: true, class: 'col-sm-offset-2 form-control'
|
8
|
+
.form-group
|
9
|
+
.col-sm-2
|
10
|
+
.col-sm-offset-2
|
11
|
+
= f.submit :resend_confirmation_instructions.l
|
12
|
+
- add_widget do
|
13
|
+
= render "devise/shared/links"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
- add_title :change_your_password.l
|
2
|
+
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put, class: 'form-horizontal' }) do |f|
|
3
|
+
= devise_error_messages!
|
4
|
+
= f.hidden_field :reset_password_token
|
5
|
+
.form-group
|
6
|
+
= f.label :password, "New password", class: 'col-sm-2 control-label'
|
7
|
+
.col-sm-2
|
8
|
+
= f.password_field :password, autofocus: true, autocomplete: "off", class: 'col-sm-offset-2 form-control'
|
9
|
+
.form-group
|
10
|
+
= f.label :password_confirmation, "Confirm new password", class: 'col-sm-2 control-label'
|
11
|
+
.col-sm-2
|
12
|
+
= f.password_field :password_confirmation, autocomplete: "off", class: 'col-sm-offset-2 form-control'
|
13
|
+
.form-group
|
14
|
+
.col-sm-2
|
15
|
+
.col-sm-2
|
16
|
+
.col-sm-offset-2
|
17
|
+
= f.submit :change_your_password.l, class: 'btn btn-primary'
|
18
|
+
- add_widget do
|
19
|
+
= render "devise/shared/links"
|
@@ -0,0 +1,14 @@
|
|
1
|
+
- add_title :forgot_your_password.l
|
2
|
+
= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'form-horizontal' }) do |f|
|
3
|
+
= devise_error_messages!
|
4
|
+
.form-group
|
5
|
+
= f.label :email, class: 'col-sm-2 control-label'
|
6
|
+
.col-sm-2
|
7
|
+
= f.email_field :email, autofocus: true, class: 'col-sm-offset-2 form-control'
|
8
|
+
.form-group
|
9
|
+
.col-sm-2
|
10
|
+
.col-sm-2
|
11
|
+
.col-sm-offset-2
|
12
|
+
= f.submit :send_me_reset_password_instructions.l, class: 'btn btn-primary'
|
13
|
+
- add_widget do
|
14
|
+
= render "devise/shared/links"
|