koudoku 0.0.8 → 0.0.9

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