koudoku 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/README.md +19 -3
  2. data/Rakefile +6 -13
  3. data/app/concerns/koudoku/plan.rb +2 -6
  4. data/app/concerns/koudoku/subscription.rb +73 -76
  5. data/app/controllers/koudoku/webhooks_controller.rb +2 -3
  6. data/app/helpers/koudoku/application_helper.rb +22 -0
  7. data/app/views/koudoku/subscriptions/_pricing_table.html.erb +1 -1
  8. data/{test/dummy/public/favicon.ico → config/environment.rb} +0 -0
  9. data/lib/generators/koudoku/install_generator.rb +1 -1
  10. data/lib/koudoku.rb +2 -2
  11. data/lib/koudoku/engine.rb +6 -0
  12. data/lib/koudoku/version.rb +1 -1
  13. data/spec/concerns/koudoku/plan_spec.rb +39 -0
  14. data/spec/concerns/koudoku/subscription_spec.rb +0 -0
  15. data/spec/controllers/koudoku/subscriptions_controller.rb +21 -0
  16. data/spec/controllers/koudoku/webhooks_controller_spec.rb +76 -0
  17. data/{test → spec}/dummy/README.rdoc +0 -0
  18. data/{test → spec}/dummy/Rakefile +0 -0
  19. data/{test → spec}/dummy/app/assets/javascripts/application.js +0 -0
  20. data/{test → spec}/dummy/app/assets/stylesheets/application.css +0 -0
  21. data/{test → spec}/dummy/app/controllers/application_controller.rb +0 -0
  22. data/{test → spec}/dummy/app/helpers/application_helper.rb +0 -0
  23. data/spec/dummy/app/models/coupon.rb +3 -0
  24. data/spec/dummy/app/models/customer.rb +7 -0
  25. data/spec/dummy/app/models/plan.rb +10 -0
  26. data/spec/dummy/app/models/subscription.rb +8 -0
  27. data/spec/dummy/app/views/koudoku/subscriptions/_social_proof.html.erb +11 -0
  28. data/{test → spec}/dummy/app/views/layouts/application.html.erb +0 -0
  29. data/{test → spec}/dummy/config.ru +0 -0
  30. data/{test → spec}/dummy/config/application.rb +2 -0
  31. data/{test → spec}/dummy/config/boot.rb +0 -0
  32. data/{test → spec}/dummy/config/database.yml +0 -0
  33. data/{test → spec}/dummy/config/environment.rb +0 -0
  34. data/{test → spec}/dummy/config/environments/development.rb +0 -0
  35. data/{test → spec}/dummy/config/environments/production.rb +0 -0
  36. data/{test → spec}/dummy/config/environments/test.rb +0 -0
  37. data/{test → spec}/dummy/config/initializers/backtrace_silencers.rb +0 -0
  38. data/spec/dummy/config/initializers/devise.rb +0 -0
  39. data/{test → spec}/dummy/config/initializers/inflections.rb +0 -0
  40. data/spec/dummy/config/initializers/koudoku.rb +7 -0
  41. data/{test → spec}/dummy/config/initializers/mime_types.rb +0 -0
  42. data/{test → spec}/dummy/config/initializers/secret_token.rb +0 -0
  43. data/{test → spec}/dummy/config/initializers/session_store.rb +0 -0
  44. data/{test → spec}/dummy/config/initializers/wrap_parameters.rb +0 -0
  45. data/spec/dummy/config/locales/devise.en.yml +59 -0
  46. data/{test → spec}/dummy/config/locales/en.yml +0 -0
  47. data/spec/dummy/config/routes.rb +4 -0
  48. data/spec/dummy/db/development.sqlite3 +0 -0
  49. data/spec/dummy/db/migrate/20130318201927_create_customers.rb +8 -0
  50. data/spec/dummy/db/migrate/20130318204455_create_subscriptions.rb +16 -0
  51. data/spec/dummy/db/migrate/20130318204458_create_plans.rb +14 -0
  52. data/spec/dummy/db/migrate/20130318204502_create_coupons.rb +10 -0
  53. data/spec/dummy/db/migrate/20130520163946_add_interval_to_plan.rb +5 -0
  54. data/spec/dummy/db/schema.rb +53 -0
  55. data/spec/dummy/db/test.sqlite3 +0 -0
  56. data/spec/dummy/log/development.log +931 -0
  57. data/spec/dummy/log/test.log +2728 -0
  58. data/{test → spec}/dummy/public/404.html +0 -0
  59. data/{test → spec}/dummy/public/422.html +0 -0
  60. data/{test → spec}/dummy/public/500.html +0 -0
  61. data/spec/dummy/public/favicon.ico +0 -0
  62. data/{test → spec}/dummy/script/rails +0 -0
  63. data/spec/dummy/spec/factories/coupons.rb +8 -0
  64. data/spec/dummy/spec/factories/plans.rb +12 -0
  65. data/spec/dummy/spec/factories/subscriptions.rb +14 -0
  66. data/spec/dummy/spec/models/coupon_spec.rb +5 -0
  67. data/spec/dummy/spec/models/plan_spec.rb +5 -0
  68. data/spec/dummy/spec/models/subscription_spec.rb +5 -0
  69. data/spec/dummy/test/fixtures/customers.yml +7 -0
  70. data/{test/integration/navigation_test.rb → spec/dummy/test/unit/customer_test.rb} +1 -4
  71. data/spec/helpers/koudoku/application_helper_spec.rb +29 -0
  72. data/spec/spec_helper.rb +31 -0
  73. metadata +218 -67
  74. data/test/dummy/config/routes.rb +0 -4
  75. data/test/koudoku_test.rb +0 -7
  76. data/test/test_helper.rb +0 -15
data/README.md CHANGED
@@ -15,13 +15,15 @@ After running `bundle install`, you can run a Rails generator to do the rest. Be
15
15
  rails g koudoku:install user
16
16
  rake db:migrate
17
17
 
18
- After installing, you'll need to add some subscription plans. (Note that we highlight the 'Team' plan.)
18
+ After installing, you'll need to add some subscription plans. (You can see an explanation of each of the attributes in the table below.)
19
19
 
20
20
  Plan.create({
21
21
  name: 'Personal',
22
22
  price: 10.00,
23
- stripe_id: '1', # This maps to the plan ID in Stripe.
24
- features: ['1 Project', '1 Page', '1 User', '1 Organization'].join("\n\n"), # Features support Markdown syntax.
23
+ interval: 'month',
24
+ stripe_id: '1',
25
+ features: ['1 Project', '1 Page', '1 User', '1 Organization'].join("\n\n"),
26
+ interval: 'month',
25
27
  display_order: 1
26
28
  })
27
29
 
@@ -29,6 +31,7 @@ After installing, you'll need to add some subscription plans. (Note that we high
29
31
  name: 'Team',
30
32
  highlight: true, # This highlights the plan on the pricing page.
31
33
  price: 30.00,
34
+ interval: 'month',
32
35
  stripe_id: '2',
33
36
  features: ['3 Projects', '3 Pages', '3 Users', '3 Organizations'].join("\n\n"),
34
37
  display_order: 2
@@ -37,11 +40,24 @@ After installing, you'll need to add some subscription plans. (Note that we high
37
40
  Plan.create({
38
41
  name: 'Enterprise',
39
42
  price: 100.00,
43
+ interval: 'month',
40
44
  stripe_id: '3',
41
45
  features: ['10 Projects', '10 Pages', '10 Users', '10 Organizations'].join("\n\n"),
42
46
  display_order: 3
43
47
  })
44
48
 
49
+ To help you understand the attributes:
50
+
51
+ | Attribute | Type | Function |
52
+ | --------------- | ------- | -------- |
53
+ | `name` | string | Name for the plan to be presented to customers. |
54
+ | `price` | float | Price per billing cycle. |
55
+ | `interval` | string | *Optional.* What is the billing cycle? Valid options are `month`, `year`, `week`, `3-month`, `6-month`. Defaults to `month`. |
56
+ | `stripe_id` | string | The Plan ID in Stripe. |
57
+ | `features` | string | A list of features. Supports Markdown syntax. |
58
+ | `display_order` | integer | Order in which to display plans. |
59
+ | `highlight` | boolean | *Optional.* Whether to highlight the plan on the pricing page. |
60
+
45
61
  The only view installed locally into your app by default is the `koudoku/subscriptions/_social_proof.html.erb` partial which is displayed alongside the pricing table. It's designed as a placeholder where you can provide quotes about your product from customers that could positively influence your visitors.
46
62
 
47
63
  ### Configuring Stripe API Keys
data/Rakefile CHANGED
@@ -20,21 +20,14 @@ RDoc::Task.new(:rdoc) do |rdoc|
20
20
  rdoc.rdoc_files.include('lib/**/*.rb')
21
21
  end
22
22
 
23
- APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
23
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
24
24
  load 'rails/tasks/engine.rake'
25
25
 
26
-
27
-
28
26
  Bundler::GemHelper.install_tasks
29
27
 
30
- require 'rake/testtask'
31
-
32
- Rake::TestTask.new(:test) do |t|
33
- t.libs << 'lib'
34
- t.libs << 'test'
35
- t.pattern = 'test/**/*_test.rb'
36
- t.verbose = false
37
- end
38
-
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ desc "Run all specs in spec directory (excluding plugin specs)"
31
+ RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
39
32
 
40
- task :default => :test
33
+ task :default => :spec
@@ -9,12 +9,8 @@ module Koudoku::Plan
9
9
 
10
10
  end
11
11
 
12
- module InstanceMethods
13
-
14
- def is_upgrade_from?(plan)
15
- self.price >= plan.price
16
- end
17
-
12
+ def is_upgrade_from?(plan)
13
+ (price || 0) >= (plan.price || 0)
18
14
  end
19
15
 
20
16
  end
@@ -12,7 +12,8 @@ module Koudoku::Subscription
12
12
  belongs_to :plan
13
13
 
14
14
  # update details.
15
- before_save do
15
+ before_save :processing!
16
+ def processing!
16
17
 
17
18
  # if their package level has changed ..
18
19
  if changing_plans?
@@ -137,103 +138,99 @@ module Koudoku::Subscription
137
138
  module ClassMethods
138
139
  end
139
140
 
140
- module InstanceMethods
141
-
142
- def describe_difference(plan_to_describe)
143
- if plan.nil?
144
- if persisted?
145
- "Upgrade"
146
- else
147
- if Koudoku.free_trial?
148
- "Start Trial"
149
- else
150
- "Upgrade"
151
- end
152
- end
141
+ def describe_difference(plan_to_describe)
142
+ if plan.nil?
143
+ if persisted?
144
+ "Upgrade"
153
145
  else
154
- if plan_to_describe.is_upgrade_from?(plan)
146
+ if Koudoku.free_trial?
147
+ "Start Trial"
148
+ else
155
149
  "Upgrade"
156
- else
157
- "Downgrade"
158
150
  end
159
151
  end
152
+ else
153
+ if plan_to_describe.is_upgrade_from?(plan)
154
+ "Upgrade"
155
+ else
156
+ "Downgrade"
157
+ end
160
158
  end
159
+ end
161
160
 
162
- # Pretty sure this wouldn't conflict with anything someone would put in their model
163
- def subscription_owner
164
- # Return whatever we belong to.
165
- # If this object doesn't respond to 'name', please update owner_description.
166
- send Koudoku.subscriptions_owned_by
167
- end
161
+ # Pretty sure this wouldn't conflict with anything someone would put in their model
162
+ def subscription_owner
163
+ # Return whatever we belong to.
164
+ # If this object doesn't respond to 'name', please update owner_description.
165
+ send Koudoku.subscriptions_owned_by
166
+ end
168
167
 
169
- def subscription_owner_description
170
- # assuming owner responds to name.
171
- # we should check for whether it responds to this or not.
172
- "#{subscription_owner.id}"
173
- end
168
+ def subscription_owner_description
169
+ # assuming owner responds to name.
170
+ # we should check for whether it responds to this or not.
171
+ "#{subscription_owner.id}"
172
+ end
174
173
 
175
- def changing_plans?
176
- plan_id_changed?
177
- end
174
+ def changing_plans?
175
+ plan_id_changed?
176
+ end
178
177
 
179
- def downgrading?
180
- plan.present? and plan_id_was.present? and plan_id_was > self.plan_id
181
- end
178
+ def downgrading?
179
+ plan.present? and plan_id_was.present? and plan_id_was > self.plan_id
180
+ end
182
181
 
183
- def upgrading?
184
- (plan_id_was.present? and plan_id_was < plan_id) or plan_id_was.nil?
185
- end
182
+ def upgrading?
183
+ (plan_id_was.present? and plan_id_was < plan_id) or plan_id_was.nil?
184
+ end
186
185
 
187
- # Template methods.
188
- def prepare_for_plan_change
189
- end
186
+ # Template methods.
187
+ def prepare_for_plan_change
188
+ end
190
189
 
191
- def prepare_for_new_subscription
192
- end
190
+ def prepare_for_new_subscription
191
+ end
193
192
 
194
- def prepare_for_upgrade
195
- end
193
+ def prepare_for_upgrade
194
+ end
196
195
 
197
- def prepare_for_downgrade
198
- end
196
+ def prepare_for_downgrade
197
+ end
199
198
 
200
- def prepare_for_cancelation
201
- end
202
-
203
- def prepare_for_card_update
204
- end
199
+ def prepare_for_cancelation
200
+ end
201
+
202
+ def prepare_for_card_update
203
+ end
205
204
 
206
- def finalize_plan_change!
207
- end
205
+ def finalize_plan_change!
206
+ end
208
207
 
209
- def finalize_new_subscription!
210
- end
208
+ def finalize_new_subscription!
209
+ end
211
210
 
212
- def finalize_upgrade!
213
- end
211
+ def finalize_upgrade!
212
+ end
214
213
 
215
- def finalize_downgrade!
216
- end
214
+ def finalize_downgrade!
215
+ end
217
216
 
218
- def finalize_cancelation!
219
- end
217
+ def finalize_cancelation!
218
+ end
220
219
 
221
- def finalize_card_update!
222
- end
220
+ def finalize_card_update!
221
+ end
223
222
 
224
- def card_was_declined
225
- end
226
-
227
- # stripe web-hook callbacks.
228
- def payment_succeeded(amount)
229
- end
230
-
231
- def charge_failed
232
- end
233
-
234
- def charge_disputed
235
- end
236
-
223
+ def card_was_declined
224
+ end
225
+
226
+ # stripe web-hook callbacks.
227
+ def payment_succeeded(amount)
228
+ end
229
+
230
+ def charge_failed
231
+ end
232
+
233
+ def charge_disputed
237
234
  end
238
235
 
239
236
  end
@@ -2,14 +2,14 @@ module Koudoku
2
2
  class WebhooksController < ApplicationController
3
3
 
4
4
  def create
5
-
5
+
6
6
  raise "API key not configured. For security reasons you must configure this in 'config/koudoku.rb'." unless Koudoku.webhooks_api_key.present?
7
7
  raise "Invalid API key. Be sure the webhooks URL Stripe is configured with includes ?api_key= and the correct key." unless params[:api_key] == Koudoku.webhooks_api_key
8
8
 
9
9
  data_json = JSON.parse request.body.read
10
10
 
11
11
  if data_json['type'] == "invoice.payment_succeeded"
12
-
12
+
13
13
  stripe_id = data_json['data']['object']['customer']
14
14
  amount = data_json['data']['object']['total'].to_f / 100.0
15
15
  subscription = ::Subscription.find_by_stripe_id(stripe_id)
@@ -27,7 +27,6 @@ module Koudoku
27
27
  stripe_id = data_json['data']['object']['customer']
28
28
 
29
29
  subscription = ::Subscription.find_by_stripe_id(stripe_id)
30
- listing = subscription.listing
31
30
  subscription.charge_disputed
32
31
 
33
32
  end
@@ -1,4 +1,26 @@
1
1
  module Koudoku
2
2
  module ApplicationHelper
3
+
4
+ def plan_price(plan)
5
+ "#{number_to_currency(plan.price)}/#{plan_interval(plan)}"
6
+ end
7
+
8
+ def plan_interval(plan)
9
+ case plan.interval
10
+ when "month"
11
+ "month"
12
+ when "year"
13
+ "year"
14
+ when "week"
15
+ "week"
16
+ when "6-month"
17
+ "half-year"
18
+ when "3-month"
19
+ "quarter"
20
+ else
21
+ "month"
22
+ end
23
+ end
24
+
3
25
  end
4
26
  end
@@ -4,7 +4,7 @@
4
4
  <div class="thumbnail">
5
5
  <div class="caption">
6
6
  <h3><%= plan.name %></h3>
7
- <h4><%= number_to_currency(plan.price) %>/month</h4>
7
+ <h4><%= plan_price(plan) %></h4>
8
8
  <div class="call-to-action">
9
9
  <% if @subscription.nil? %>
10
10
  <%= link_to Koudoku.free_trial? ? 'Start Trial' : 'Sign Up', koudoku.new_subscription_path(plan: plan.id), class: "btn btn-success btn-large" %>
@@ -42,7 +42,7 @@ RUBY
42
42
  gsub_file "app/models/subscription.rb", /ActiveRecord::Base/, "ActiveRecord::Base\n include Koudoku::Subscription\n\n belongs_to :#{subscription_owner_model}\n belongs_to :coupon\n"
43
43
 
44
44
  # Add the plans.
45
- generate("model", "plan name:string stripe_id:string price:float features:text highlight:boolean display_order:integer")
45
+ generate("model", "plan name:string stripe_id:string price:float interval:string features:text highlight:boolean display_order:integer")
46
46
  gsub_file "app/models/plan.rb", /ActiveRecord::Base/, "ActiveRecord::Base\n include Koudoku::Plan\n belongs_to :#{subscription_owner_model}\n belongs_to :coupon\n has_many :subscriptions\n"
47
47
 
48
48
  # Add coupons.
data/lib/koudoku.rb CHANGED
@@ -22,8 +22,8 @@ module Koudoku
22
22
  def self.setup
23
23
  yield self
24
24
 
25
- # Configure the Stripe
26
- Stripe.api_key = ENV['STRIPE_SECRET_KEY']
25
+ # Configure the Stripe gem.
26
+ Stripe.api_key = stripe_secret_key
27
27
  end
28
28
 
29
29
  # e.g. :users
@@ -3,5 +3,11 @@ require 'bluecloth'
3
3
  module Koudoku
4
4
  class Engine < ::Rails::Engine
5
5
  isolate_namespace Koudoku
6
+ config.generators do |g|
7
+ g.test_framework :rspec, :fixture => false
8
+ g.fixture_replacement :factory_girl, :dir => 'spec/factories'
9
+ g.assets false
10
+ g.helper false
11
+ end
6
12
  end
7
13
  end
@@ -1,3 +1,3 @@
1
1
  module Koudoku
2
- VERSION = "0.0.8"
2
+ VERSION = "0.0.9"
3
3
  end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe Koudoku::Plan do
4
+ describe '#is_upgrade_from?' do
5
+ before do
6
+ class Plan
7
+ attr_accessor :price
8
+ include Koudoku::Plan
9
+ end
10
+ end
11
+ it 'returns true if the price is higher' do
12
+ plan = Plan.new
13
+ plan.price = 123.23
14
+ cheaper_plan = Plan.new
15
+ cheaper_plan.price = 61.61
16
+ plan.is_upgrade_from?(cheaper_plan).should be_true
17
+ end
18
+ it 'returns true if the price is the same' do
19
+ plan = Plan.new
20
+ plan.price = 123.23
21
+ plan.is_upgrade_from?(plan).should be_true
22
+ end
23
+ it 'returns false if the price is the same or higher' do
24
+ plan = Plan.new
25
+ plan.price = 61.61
26
+ more_expensive_plan = Plan.new
27
+ more_expensive_plan.price = 123.23
28
+ plan.is_upgrade_from?(more_expensive_plan).should be_false
29
+ end
30
+ it 'handles a nil value gracefully' do
31
+ plan = Plan.new
32
+ plan.price = 123.23
33
+ cheaper_plan = Plan.new
34
+ lambda {
35
+ plan.is_upgrade_from?(cheaper_plan).should be_true
36
+ }.should_not raise_error
37
+ end
38
+ end
39
+ end
File without changes
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe Koudoku::SubscriptionsController do
4
+ describe 'when customer is signed in' do
5
+ before do
6
+ @customer = Customer.create(email: 'andrew.culver@gmail.com')
7
+ ApplicationController.any_instance.stub(:current_customer).and_return(@customer)
8
+ end
9
+ it 'works' do
10
+ get :index, use_route: 'koudoku'
11
+ end
12
+ end
13
+ describe 'when customer is not signed in' do
14
+ before do
15
+ ApplicationController.any_instance.stub(:current_customer).and_return(nil)
16
+ end
17
+ it 'works' do
18
+ get :index, use_route: 'koudoku'
19
+ end
20
+ end
21
+ end