stripe_saas 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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.asc +159 -0
- data/Rakefile +37 -0
- data/app/assets/javascripts/stripe_saas/application.js +13 -0
- data/app/assets/stylesheets/stripe_saas/application.css +15 -0
- data/app/concerns/stripe_saas/feature.rb +23 -0
- data/app/concerns/stripe_saas/plan.rb +45 -0
- data/app/concerns/stripe_saas/plan_feature.rb +25 -0
- data/app/concerns/stripe_saas/subscription.rb +182 -0
- data/app/controllers/stripe_saas/application_controller.rb +5 -0
- data/app/controllers/stripe_saas/subscriptions_controller.rb +200 -0
- data/app/helpers/stripe_saas/application_helper.rb +26 -0
- data/app/views/stripe_saas/subscriptions/_card.html.erb +51 -0
- data/app/views/stripe_saas/subscriptions/_card_form.html.erb +42 -0
- data/app/views/stripe_saas/subscriptions/_pricing_table.html.erb +43 -0
- data/app/views/stripe_saas/subscriptions/edit.html.erb +7 -0
- data/app/views/stripe_saas/subscriptions/index.html.erb +2 -0
- data/app/views/stripe_saas/subscriptions/new.html.erb +1 -0
- data/app/views/stripe_saas/subscriptions/show.html.erb +15 -0
- data/app/views/stripe_saas/subscriptions/unauthorized.html.erb +1 -0
- data/config/environment.rb +0 -0
- data/config/routes.rb +10 -0
- data/lib/generators/stripe_saas/install_generator.rb +60 -0
- data/lib/generators/stripe_saas/templates/app/models/feature.rb +8 -0
- data/lib/generators/stripe_saas/templates/app/models/plan.rb +9 -0
- data/lib/generators/stripe_saas/templates/app/models/plan_feature.rb +6 -0
- data/lib/generators/stripe_saas/templates/app/models/subscription.rb +5 -0
- data/lib/generators/stripe_saas/templates/config/initializers/stripe_saas.rb +7 -0
- data/lib/generators/stripe_saas/views_generator.rb +20 -0
- data/lib/stripe_saas.rb +9 -0
- data/lib/stripe_saas/configuration.rb +51 -0
- data/lib/stripe_saas/engine.rb +41 -0
- data/lib/stripe_saas/version.rb +3 -0
- data/lib/tasks/stripe_saas_tasks.rake +11 -0
- data/spec/concerns/plan_spec.rb +50 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/plan.rb +7 -0
- data/spec/dummy/app/models/subscription.rb +5 -0
- data/spec/dummy/app/models/user.rb +8 -0
- data/spec/dummy/app/views/layouts/application.html.erb +16 -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.ru +4 -0
- data/spec/dummy/config/application.rb +27 -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 +41 -0
- data/spec/dummy/config/environments/production.rb +79 -0
- data/spec/dummy/config/environments/test.rb +42 -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/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/stripe_saas.rb +7 -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 +10 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20150101233243_devise_create_users.rb +42 -0
- data/spec/dummy/db/migrate/20150102001921_create_subscriptions.rb +14 -0
- data/spec/dummy/db/migrate/20150102001930_create_plans.rb +19 -0
- data/spec/dummy/db/schema.rb +61 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +170 -0
- data/spec/dummy/log/test.log +110 -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/favicon.ico +0 -0
- data/spec/integration/navigation_test.rb +9 -0
- data/spec/rails_helper.rb +23 -0
- data/spec/spec_helper.rb +22 -0
- metadata +278 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c193fb3ee82f3bcec3747eb5bf791125b143615f
|
4
|
+
data.tar.gz: a1dd02c5730217bbfa261ee773b3213e537e1f9d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 196b684766f5a7f1e2e9ca9ae43a374bf923b64557b58e7ce1ec9d7aaba373f3d6ff3827f4374cacb3c55e70e038b6c3c25fff8d95cfd66f53f196f965fde747
|
7
|
+
data.tar.gz: 112b6bbb7df2e6548e6669d5b009befe9272d58d0e27e9576faf784a5f3213d08f979f574e3de59ee6636643679fded38663d529a63665fc381ff4debe38b71f
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2015 Brian Sam-Bodden
|
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.asc
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
== StripeSaas
|
2
|
+
|
3
|
+
A Rails 4 Engine providing Stripe subscription management for SaaS applications.
|
4
|
+
Based on the work of Andrew Culver in Koudoku (https://github.com/andrewculver/koudoku).
|
5
|
+
|
6
|
+
== Installation
|
7
|
+
|
8
|
+
=== Add gem dependency
|
9
|
+
|
10
|
+
Include the stripe_saas gem in your Gemfile and bundle (install):
|
11
|
+
|
12
|
+
[source,ruby]
|
13
|
+
-------------------------------------------
|
14
|
+
gem 'stripe_saas'
|
15
|
+
-------------------------------------------
|
16
|
+
|
17
|
+
=== Install subscriptions management on a model.
|
18
|
+
|
19
|
+
A rails generator is provided to install the StripeSaas models:
|
20
|
+
|
21
|
+
[source,ruby]
|
22
|
+
-------------------------------------------
|
23
|
+
rails g stripe_saas:install user
|
24
|
+
-------------------------------------------
|
25
|
+
|
26
|
+
==== Stripe Subscriptions
|
27
|
+
|
28
|
+
A model that mirrors a Stripe subscription (https://stripe.com/docs/api/ruby#subscriptions)
|
29
|
+
is generated and a one-to-one relationship between it and one of your
|
30
|
+
application's models as the owner of the subscription.
|
31
|
+
|
32
|
+
In the example above the generated StripeSaas::Subscription 'belongs to' your
|
33
|
+
application's User:
|
34
|
+
|
35
|
+
[source,ruby]
|
36
|
+
-------------------------------------------
|
37
|
+
class Subscription < ActiveRecord::Base
|
38
|
+
include StripeSaas::Subscription
|
39
|
+
|
40
|
+
belongs_to :user
|
41
|
+
end
|
42
|
+
-------------------------------------------
|
43
|
+
|
44
|
+
and the User class will 'has one' subscription:
|
45
|
+
|
46
|
+
[source,ruby]
|
47
|
+
-------------------------------------------
|
48
|
+
has_one :subscription
|
49
|
+
-------------------------------------------
|
50
|
+
|
51
|
+
==== Stripe Plans
|
52
|
+
|
53
|
+
A model that mirrors a Stripe plan (https://stripe.com/docs/api/ruby#plans) is
|
54
|
+
generated.
|
55
|
+
|
56
|
+
[source,ruby]
|
57
|
+
-------------------------------------------
|
58
|
+
class Plan < ActiveRecord::Base
|
59
|
+
has_many :subscriptions
|
60
|
+
has_many :plan_features
|
61
|
+
has_many :features, through: :plan_features
|
62
|
+
|
63
|
+
default_scope { order(:display_order) }
|
64
|
+
|
65
|
+
include StripeSaas::Plan
|
66
|
+
end
|
67
|
+
-------------------------------------------
|
68
|
+
|
69
|
+
Plans have PlanFeatures which in turn are join table/model between Plan and Feature:
|
70
|
+
|
71
|
+
[source,ruby]
|
72
|
+
-------------------------------------------
|
73
|
+
class Feature < ActiveRecord::Base
|
74
|
+
has_many :plan_features
|
75
|
+
has_many :plans, through: :plan_features
|
76
|
+
|
77
|
+
default_scope { order(:display_order) }
|
78
|
+
|
79
|
+
include StripeSaas::Feature
|
80
|
+
end
|
81
|
+
-------------------------------------------
|
82
|
+
|
83
|
+
To create a Feature for example, you could use:
|
84
|
+
|
85
|
+
[source,ruby]
|
86
|
+
-------------------------------------------
|
87
|
+
Feature.find_or_create_by(name: 'signals').update({
|
88
|
+
description: "Inbound Signals",
|
89
|
+
feature_type: :number,
|
90
|
+
unit: "signals",
|
91
|
+
display_order: 1
|
92
|
+
})
|
93
|
+
-------------------------------------------
|
94
|
+
|
95
|
+
Where the feature type can be one of:
|
96
|
+
|
97
|
+
[source,ruby]
|
98
|
+
-------------------------------------------
|
99
|
+
FEATURE_TYPES = {
|
100
|
+
boolean: 'Boolean',
|
101
|
+
interval: 'Interval (in seconds)',
|
102
|
+
filesize: 'Filesize (in bytes)',
|
103
|
+
number: 'Number',
|
104
|
+
percentage: 'Percentage (%)'
|
105
|
+
}
|
106
|
+
-------------------------------------------
|
107
|
+
|
108
|
+
To create a plan (in your seeds for example) with a set of features you could use
|
109
|
+
something like:
|
110
|
+
|
111
|
+
[source,ruby]
|
112
|
+
-------------------------------------------
|
113
|
+
developer_plan = Plan.find_or_create_by(stripe_id: 'developer')
|
114
|
+
developer_plan.update({
|
115
|
+
name: 'Developer',
|
116
|
+
price: 0.0,
|
117
|
+
interval: 'month',
|
118
|
+
interval_count: 1,
|
119
|
+
statement_descriptor: 'Binnacle Developer Plan',
|
120
|
+
trial_period_days: 30,
|
121
|
+
display_order: 1
|
122
|
+
})
|
123
|
+
|
124
|
+
developer_plan.add_feature(:signals, 50000)
|
125
|
+
-------------------------------------------
|
126
|
+
|
127
|
+
Any plan with a price of 0.0 is considered a free plan in StripeSaas which will
|
128
|
+
not require the user to enter credit card information.
|
129
|
+
|
130
|
+
After running the installer you will have to migrate your database:
|
131
|
+
|
132
|
+
[source,ruby]
|
133
|
+
-------------------------------------------
|
134
|
+
rake db:migrate
|
135
|
+
-------------------------------------------
|
136
|
+
|
137
|
+
== Configuration
|
138
|
+
|
139
|
+
As part of the installation procedure an initializer is generated under config/initializers/stripe_saas.rb:
|
140
|
+
|
141
|
+
[source,ruby]
|
142
|
+
-------------------------------------------
|
143
|
+
StripeSaas.setup do |config|
|
144
|
+
config.subscriptions_owned_by = :user
|
145
|
+
# config.devise_scope = :user
|
146
|
+
config.stripe_publishable_key = ENV['STRIPE_PUBLISHABLE_KEY']
|
147
|
+
config.stripe_secret_key = ENV['STRIPE_SECRET_KEY']
|
148
|
+
config.create_plans_in_stripe = false
|
149
|
+
end
|
150
|
+
-------------------------------------------
|
151
|
+
|
152
|
+
* _subscriptions_owned_by_: The symbol of the class that owns the subscription
|
153
|
+
* _devise_scope_: If using Devise and the subscription is not owned by the devise
|
154
|
+
class (user/customer). For example, if users have accounts, and accounts have
|
155
|
+
subscriptions. Then config.subscriptions_owned_by = :account and config.devise_scope = :user
|
156
|
+
* _stripe_publishable_key_: Your Stripe Publishable Key https://stripe.com/docs/tutorials/dashboard#api-keys
|
157
|
+
* _stripe_secret_key_: Your Stripe Secret Key https://stripe.com/docs/tutorials/dashboard#api-keys
|
158
|
+
* _create_plans_in_stripe_: Whether to autogenerate the local Plans in Stripe and
|
159
|
+
keep then in synch
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
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 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'StripeSaas'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
|
21
|
+
load 'rails/tasks/statistics.rake'
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
Bundler::GemHelper.install_tasks
|
26
|
+
|
27
|
+
require 'rake/testtask'
|
28
|
+
|
29
|
+
Rake::TestTask.new(:test) do |t|
|
30
|
+
t.libs << 'lib'
|
31
|
+
t.libs << 'test'
|
32
|
+
t.pattern = 'test/**/*_test.rb'
|
33
|
+
t.verbose = false
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
task default: :test
|
@@ -0,0 +1,13 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require_tree .
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any styles
|
10
|
+
* defined in the other CSS/SCSS files in this directory. It is generally better to create a new
|
11
|
+
* file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module StripeSaas::Feature
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
FEATURE_TYPES = {
|
5
|
+
boolean: 'Boolean',
|
6
|
+
interval: 'Interval (in seconds)',
|
7
|
+
filesize: 'Filesize (in bytes)',
|
8
|
+
number: 'Number',
|
9
|
+
percentage: 'Percentage (%)'
|
10
|
+
}
|
11
|
+
|
12
|
+
def feature_type=(val)
|
13
|
+
val = val.to_sym
|
14
|
+
raise(ArgumentError, "#{val} is not a valid feature type") unless FEATURE_TYPES.keys.include?(val)
|
15
|
+
self[:feature_type] = val
|
16
|
+
self[:unit] = FEATURE_TYPES[val]
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
name
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module StripeSaas::Plan
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
monetize :price_cents
|
6
|
+
end
|
7
|
+
|
8
|
+
def is_upgrade_from?(plan)
|
9
|
+
(price || 0) >= (plan.price || 0)
|
10
|
+
end
|
11
|
+
|
12
|
+
def is_downgrade_from?(plan)
|
13
|
+
!is_upgrade_from?(plan)
|
14
|
+
end
|
15
|
+
|
16
|
+
def free?
|
17
|
+
price == 0.0
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_feature(feature, value, display_value=nil)
|
21
|
+
feature = Feature.find_by(name: feature.to_s) if feature.is_a?(String) || feature.is_a?(Symbol)
|
22
|
+
|
23
|
+
plan_features.find_or_create_by(feature: feature).update({
|
24
|
+
value: value,
|
25
|
+
display_value: display_value
|
26
|
+
})
|
27
|
+
end
|
28
|
+
|
29
|
+
def has_feature?(feature)
|
30
|
+
if feature.is_a?(String)
|
31
|
+
!features.find_by(name: feature).first.nil?
|
32
|
+
else
|
33
|
+
features.any? { |f| f.id == feature.id }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def metadata
|
38
|
+
metadata_as_json.present? ? JSON::parse(metadata_as_json) : {}
|
39
|
+
end
|
40
|
+
|
41
|
+
def metadata=(metadata_hash)
|
42
|
+
metadata_as_json = metadata_hash.to_json
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module StripeSaas::PlanFeature
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def value=(val)
|
5
|
+
super(val.to_s)
|
6
|
+
end
|
7
|
+
|
8
|
+
def value
|
9
|
+
case feature.feature_type.to_sym
|
10
|
+
when :boolean
|
11
|
+
self[:value] == 'true'
|
12
|
+
when :number, :percentage, :filesize, :interval
|
13
|
+
self[:value].to_i
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
case feature.feature_type
|
19
|
+
when :boolean
|
20
|
+
feature.name
|
21
|
+
when :number, :percentage, :filesize, :interval
|
22
|
+
"#{self[:value]} #{feature.description}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
module StripeSaas::Subscription
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
monetize :current_price_cents, allow_nil: true
|
6
|
+
|
7
|
+
# We don't store these one-time use tokens, but this is what Stripe provides
|
8
|
+
# client-side after storing the credit card information.
|
9
|
+
attr_accessor :credit_card_token
|
10
|
+
|
11
|
+
belongs_to :plan
|
12
|
+
|
13
|
+
# update details.
|
14
|
+
before_save :processing!
|
15
|
+
|
16
|
+
def processing!
|
17
|
+
# if their package level has changed ..
|
18
|
+
if changing_plans?
|
19
|
+
# and a customer exists in stripe ..
|
20
|
+
if stripe_id.present?
|
21
|
+
# fetch the customer.
|
22
|
+
customer = Stripe::Customer.retrieve(stripe_id)
|
23
|
+
|
24
|
+
if customer.default_card.nil? && !credit_card_token.nil?
|
25
|
+
customer.card = credit_card_token
|
26
|
+
customer.save
|
27
|
+
end
|
28
|
+
|
29
|
+
# if a new plan has been selected
|
30
|
+
if self.plan.present?
|
31
|
+
|
32
|
+
# Record the new plan pricing.
|
33
|
+
self.current_price = self.plan.price
|
34
|
+
|
35
|
+
# update the package level with stripe.
|
36
|
+
customer.update_subscription(:plan => self.plan.stripe_id)
|
37
|
+
|
38
|
+
# if no plan has been selected.
|
39
|
+
else
|
40
|
+
|
41
|
+
# Remove the current pricing.
|
42
|
+
self.current_price = nil
|
43
|
+
|
44
|
+
# delete the subscription.
|
45
|
+
customer.cancel_subscription
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
# otherwise
|
50
|
+
else
|
51
|
+
# if a new plan has been selected
|
52
|
+
if self.plan.present?
|
53
|
+
# Record the new plan pricing.
|
54
|
+
self.current_price = self.plan.price
|
55
|
+
|
56
|
+
begin
|
57
|
+
customer_attributes = if self.plan.free?
|
58
|
+
{
|
59
|
+
description: subscription_owner_description,
|
60
|
+
email: subscription_owner_email,
|
61
|
+
}
|
62
|
+
else
|
63
|
+
{
|
64
|
+
description: subscription_owner_description,
|
65
|
+
email: subscription_owner_email,
|
66
|
+
card: credit_card_token
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
# create a customer at that package level.
|
71
|
+
customer = Stripe::Customer.create(customer_attributes)
|
72
|
+
customer.update_subscription(:plan => plan.stripe_id)
|
73
|
+
rescue Stripe::CardError => card_error
|
74
|
+
errors[:base] << card_error.message
|
75
|
+
card_was_declined
|
76
|
+
return false
|
77
|
+
end
|
78
|
+
|
79
|
+
# store the customer id.
|
80
|
+
self.stripe_id = customer.id
|
81
|
+
unless self.plan.free?
|
82
|
+
self.last_four = customer.cards.retrieve(customer.default_card).last4
|
83
|
+
end
|
84
|
+
else
|
85
|
+
|
86
|
+
# This should never happen.
|
87
|
+
|
88
|
+
self.plan_id = nil
|
89
|
+
|
90
|
+
# Remove any plan pricing.
|
91
|
+
self.current_price = nil
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
# if they're updating their credit card details.
|
98
|
+
elsif self.credit_card_token.present?
|
99
|
+
# fetch the customer.
|
100
|
+
customer = Stripe::Customer.retrieve(self.stripe_id)
|
101
|
+
customer.card = self.credit_card_token
|
102
|
+
customer.save
|
103
|
+
|
104
|
+
# update the last four based on this new card.
|
105
|
+
self.last_four = customer.cards.retrieve(customer.default_card).last4
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Set a Stripe coupon code that will be used when a new Stripe customer (a.k.a. StripeSaas subscription)
|
111
|
+
# is created
|
112
|
+
def coupon_code=(new_code)
|
113
|
+
@coupon_code = new_code
|
114
|
+
end
|
115
|
+
|
116
|
+
# Pretty sure this wouldn't conflict with anything someone would put in their model
|
117
|
+
def subscription_owner
|
118
|
+
StripeSaas::Subscription.find_customer(self)
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.find_customer(subscription_or_owner)
|
122
|
+
if subscription_or_owner.class.to_s.downcase.to_sym == StripeSaas.subscriptions_owned_by
|
123
|
+
owner = subscription_or_owner
|
124
|
+
else
|
125
|
+
owner = subscription_or_owner.send StripeSaas.subscriptions_owned_by
|
126
|
+
end
|
127
|
+
# Return whatever we belong to.
|
128
|
+
# If this object doesn't respond to 'name', please update owner_description.
|
129
|
+
if StripeSaas.customer_accessor
|
130
|
+
if StripeSaas.customer_accessor.kind_of?(Array)
|
131
|
+
StripeSaas.customer_accessor.inject(subscription_or_owner) {|o, a| o.send(a); o }
|
132
|
+
else
|
133
|
+
owner.send StripeSaas.customer_accessor
|
134
|
+
end
|
135
|
+
else
|
136
|
+
owner
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def subscription_owner=(owner)
|
141
|
+
# e.g. @subscription.user = @owner
|
142
|
+
send StripeSaas.owner_assignment_sym, owner
|
143
|
+
end
|
144
|
+
|
145
|
+
def subscription_owner_description
|
146
|
+
# assuming owner responds to name.
|
147
|
+
# we should check for whether it responds to this or not.
|
148
|
+
"#{subscription_owner.try(:name) || subscription_owner.try(:id)}"
|
149
|
+
end
|
150
|
+
|
151
|
+
def subscription_owner_email
|
152
|
+
"#{subscription_owner.try(:email)}"
|
153
|
+
end
|
154
|
+
|
155
|
+
def changing_plans?
|
156
|
+
plan_id_changed?
|
157
|
+
end
|
158
|
+
|
159
|
+
def downgrading?
|
160
|
+
plan.present? and plan_id_was.present? and plan_id_was > self.plan_id
|
161
|
+
end
|
162
|
+
|
163
|
+
def upgrading?
|
164
|
+
(plan_id_was.present? and plan_id_was < plan_id) or plan_id_was.nil?
|
165
|
+
end
|
166
|
+
|
167
|
+
# TODO: this does not belong in here - need a presenter
|
168
|
+
def describe_difference(plan_to_describe)
|
169
|
+
if plan.nil?
|
170
|
+
if persisted?
|
171
|
+
"Upgrade"
|
172
|
+
end
|
173
|
+
else
|
174
|
+
if plan_to_describe.is_upgrade_from?(plan)
|
175
|
+
"Upgrade"
|
176
|
+
else
|
177
|
+
"Downgrade"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|