koudoku 0.0.11 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +56 -17
  3. data/Rakefile +1 -32
  4. data/app/concerns/koudoku/plan.rb +0 -8
  5. data/app/concerns/koudoku/subscription.rb +36 -20
  6. data/app/controllers/koudoku/subscriptions_controller.rb +45 -11
  7. data/app/views/koudoku/subscriptions/_card.html.erb +1 -1
  8. data/app/views/koudoku/subscriptions/show.html.erb +6 -6
  9. data/config/initializers/stripe_event.rb +27 -0
  10. data/config/routes.rb +3 -1
  11. data/lib/generators/koudoku/install_generator.rb +27 -31
  12. data/lib/generators/koudoku/templates/app/models/coupon.rb +5 -0
  13. data/lib/generators/koudoku/templates/app/models/plan.rb +6 -0
  14. data/lib/generators/koudoku/templates/app/models/subscription.rb +8 -0
  15. data/lib/generators/koudoku/templates/config/initializers/koudoku.rb +13 -0
  16. data/lib/generators/koudoku/views_generator.rb +5 -2
  17. data/lib/koudoku/version.rb +1 -1
  18. data/lib/koudoku.rb +28 -10
  19. data/spec/concerns/koudoku/plan_spec.rb +13 -13
  20. data/spec/dummy/app/models/plan.rb +0 -1
  21. data/spec/dummy/config/application.rb +0 -6
  22. data/spec/dummy/config/environments/development.rb +2 -7
  23. data/spec/dummy/config/environments/production.rb +2 -0
  24. data/spec/dummy/config/environments/test.rb +2 -3
  25. data/spec/dummy/config/initializers/koudoku.rb +1 -1
  26. data/spec/dummy/db/development.sqlite3 +0 -0
  27. data/spec/dummy/db/test.sqlite3 +0 -0
  28. data/spec/dummy/log/development.log +48 -0
  29. data/spec/dummy/log/test.log +203 -0
  30. metadata +60 -48
  31. data/app/controllers/koudoku/webhooks_controller.rb +0 -39
  32. data/spec/controllers/koudoku/webhooks_controller_spec.rb +0 -76
  33. data/spec/dummy/spec/factories/coupons.rb +0 -8
  34. data/spec/dummy/spec/factories/plans.rb +0 -12
  35. data/spec/dummy/spec/factories/subscriptions.rb +0 -14
  36. data/spec/dummy/spec/models/coupon_spec.rb +0 -5
  37. data/spec/dummy/spec/models/plan_spec.rb +0 -5
  38. data/spec/dummy/spec/models/subscription_spec.rb +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1d46eda181a6f5e58abc651b8ca10206c732ddf0
4
- data.tar.gz: d0e19555a3872b3dd149dc828b7722bbcb44af44
3
+ metadata.gz: af40753143d0ae4408359497d0bacc28b7975dff
4
+ data.tar.gz: f001cb6c98006da1c42ec89f0b5fe85f9fee70a5
5
5
  SHA512:
6
- metadata.gz: 9a07862153a860e36853302ea963002511df5df50257f19795b7d838bb34e72095f4cb3b71caeaf1aed108d9eac9d16b6fb41f210e681002e6ffa75d0497536f
7
- data.tar.gz: 239b6f176976989b1bacd3a51facbae0714bd6dccbfbc62800d6a225fcb396a1737f3891bb1df94e49696447e5e375ec6bd292979eb3cafce68e91cd13da9f5c
6
+ metadata.gz: 0643bf16d6395998c02a1d7e19410fa11b87cdec49a9107f80d89b568e2195d78d01ed72fedc344fdd017104ff8e6b031800f978e55a48b1f75335351e550951
7
+ data.tar.gz: 7cef2125edb8e27ce4a45232222079fd0574000726295b33ea44eb14c30c7cf1ca5bf5b7cf50c4984d120856fddbbb5816ef03199b767f119966aec29f97085b
data/README.md CHANGED
@@ -1,28 +1,40 @@
1
1
  # Koudoku
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/koudoku.png)](https://rubygems.org/gems/koudoku) [![Code Climate](https://codeclimate.com/github/andrewculver/koudoku.png)](https://codeclimate.com/github/andrewculver/koudoku) [![Build Status](https://travis-ci.org/andrewculver/koudoku.png)](https://travis-ci.org/andrewculver/koudoku)
4
+
3
5
  Robust subscription support for Ruby on Rails apps using [Stripe](https://stripe.com), including out-of-the-box pricing pages, payment pages, and subscription management for your customers. Also makes it easy to manage logic related to new subscriptions, upgrades, downgrades, cancellations, payment failures, and streamlines hooking up notifications, metrics logging, etc.
4
6
 
5
7
  To see an example of Koudoku in action, please visit [Koudoku.org](http://koudoku.org/).
6
8
 
9
+ <small><a href="http://churnbuster.io"><img src="https://s3.amazonaws.com/andrew-culver-images/churn-buster/koudoku-readme.png" width="196" height="38" alt="Churn Buster" /></a><br>Koudoku is authored and maintained by [Andrew Culver](http://twitter.com/andrewculver). If you find it useful, consider checking out [Churn Buster](http://churnbuster.io). It's designed to help you handle the pain points you'll run into when doing payments at scale.</small>
10
+
7
11
  ## Installation
8
12
 
9
13
  Include the following in your `Gemfile`:
10
14
 
11
- gem 'koudoku'
12
-
15
+ ```ruby
16
+ gem 'koudoku', :git => 'https://github.com/andrewculver/koudoku.git'
17
+ ```
18
+ **Note: ** For the time being, please use this repository directly and do *NOT* use the gem from RubyGems. The gem is really out of date and causing all kinds of trouble
19
+
13
20
  After running `bundle install`, you can run a Rails generator to do the rest. Before installing, the model you'd like to have own subscriptions must already exist. (In many cases this will be `user`. It may also be something like `company`, etc.)
14
21
 
22
+ ```ruby
15
23
  rails g koudoku:install user
16
24
  rake db:migrate
25
+ ```
17
26
 
18
27
  Add the following to `app/views/layouts/application.html.erb` before your `<head>` tag closes:
19
28
 
29
+ ```ruby
20
30
  <%= yield :koudoku %>
31
+ ```
21
32
 
22
33
  (This allows us to inject a Stripe `<script>` tag in the correct place. If you don't, the payment form will not work.)
23
34
 
24
35
  After installing, you'll need to add some subscription plans. (You can see an explanation of each of the attributes in the table below.)
25
36
 
37
+ ```ruby
26
38
  Plan.create({
27
39
  name: 'Personal',
28
40
  price: 10.00,
@@ -50,7 +62,8 @@ After installing, you'll need to add some subscription plans. (You can see an ex
50
62
  features: ['10 Projects', '10 Pages', '10 Users', '10 Organizations'].join("\n\n"),
51
63
  display_order: 3
52
64
  })
53
-
65
+ ```
66
+
54
67
  To help you understand the attributes:
55
68
 
56
69
  | Attribute | Type | Function |
@@ -71,56 +84,74 @@ You can supply your publishable and secret API keys in `config/initializers/koud
71
84
 
72
85
  In a bash shell, you can set them in `~/.bash_profile` like so:
73
86
 
87
+ ```bash
74
88
  export STRIPE_PUBLISHABLE_KEY=pk_0CJwDH9sdh98f79FDHDOjdiOxQob0
75
89
  export STRIPE_SECRET_KEY=sk_0CJwFDIUshdfh97JDJOjZ5OIDjOCH
90
+ ```
76
91
 
77
92
  (Reload your terminal for these settings to take effect.)
78
93
 
79
94
  On Heroku you accomplish this same effect with [Config Vars](https://devcenter.heroku.com/articles/config-vars):
80
95
 
96
+ ```bash
81
97
  heroku config:add STRIPE_PUBLISHABLE_KEY=pk_0CJwDH9sdh98f79FDHDOjdiOxQob0
82
98
  heroku config:add STRIPE_SECRET_KEY=sk_0CJwFDIUshdfh97JDJOjZ5OIDjOCH
83
-
99
+ ```
100
+
84
101
  ## User-Facing Subscription Management
85
102
 
86
103
  By default a `pricing_path` route is defined which you can link to in order to show visitors a pricing table. If a user is signed in, this pricing table will take into account their current plan. For example, you can link to this page like so:
87
104
 
105
+ ```ruby
88
106
  <%= link_to 'Pricing', main_app.pricing_path %>
89
-
107
+ ```
108
+
90
109
  (Note: Koudoku uses the application layout, so it's important that application paths referenced in that layout are prefixed with "`main_app.`" like you see above or Rails will try to look the paths up in the Koudoku engine instead of your application.)
91
110
 
92
111
  Existing users can view available plans, select a plan, enter credit card details, review their subscription, change plans, and cancel at the following route:
93
112
 
113
+ ```ruby
94
114
  koudoku.owner_subscriptions_path(@user)
95
-
115
+ ```
116
+
96
117
  In these paths, `owner` refers to `User` by default, or whatever model has been configured to be the owner of the `Subscription` model.
97
118
 
98
119
  A number of views are provided by default. To customize the views, use the following generator:
99
120
 
121
+ ```ruby
100
122
  rails g koudoku:views
123
+ ```
101
124
 
102
125
  ### Pricing Table
103
126
 
104
127
  Koudoku ships with a stock pricing table. By default it depends on Twitter Bootstrap, but also has some additional styles required. In order to import these styles, add the following to your `app/assets/stylesheets/application.css`:
105
128
 
129
+ ```css
106
130
  *= require 'koudoku/pricing-table'
107
-
131
+ ```
132
+
108
133
  Or, if you've replaced your `application.css` with an `application.scss` (like I always do):
109
134
 
135
+ ```css
110
136
  @import "koudoku/pricing-table"
111
-
137
+ ```
138
+
112
139
  ## Using Coupons
113
140
 
114
141
  While more robust coupon support is expected in the future, the simple way to use a coupon is to first create it:
115
142
 
143
+ ```ruby
116
144
  coupon = Coupon.create(code: '30-days-free', free_trial_length: 30)
117
-
145
+ ```
146
+
118
147
  Then assign it to a _new_ subscription before saving:
119
148
 
149
+ ```ruby
120
150
  subscription = Subscription.new(...)
121
151
  subscription.coupon = coupon
122
152
  subscription.save
123
-
153
+ ```
154
+
124
155
  It should be noted that these coupons are different from the coupons provided natively by Stripe.
125
156
 
126
157
  ## Implementing Logging, Notifications, etc.
@@ -145,14 +176,22 @@ Be sure to include a call to `super` in each of your implementations, especially
145
176
 
146
177
  Between `prepare_for_*` and `finalize_*`, so far I've used `finalize_*` almost exclusively. The difference is that `prepare_for_*` runs before we settle things with Stripe, and `finalize_*` runs after everything is settled in Stripe. For that reason, please be sure not to implement anything in `finalize_*` implementations that might cause issues with ActiveRecord saving the updated state of the subscription.
147
178
 
148
- ### Stripe Webhooks
179
+ ### Webhooks
149
180
 
150
- During the installation process, a path and API key will be printed out that you can configure in the Stripe account panel that will allow your application to receive information about recurring transactions as they happen. (These are useful for reacting to expired credit cards on file and revenue tracking.)
181
+ We use [stripe_event](https://github.com/integrallis/stripe_event) under the hood to support webhooks.
182
+ The default webhooks URL is `/koudoku/webhooks`.
151
183
 
152
- Support for Stripe Webhooks is still quite limited. You can implement the following template methods of the `Subscription`:
184
+ You can add your own webhooks using the (reduced) stripe_event syntax in the `config/initializers/koudoku.rb` file:
153
185
 
154
- - `payment_succeeded(amount)`
155
- - `charge_failed`
156
- - `charge_disputed`
186
+ ```
187
+ # /config/initializers/koudoku.rb
188
+ Koudoku.setup do |config|
189
+ config.subscriptions_owned_by = :user
190
+ config.stripe_publishable_key = ENV['STRIPE_PUBLISHABLE_KEY']
191
+ config.stripe_secret_key = ENV['STRIPE_SECRET_KEY']
157
192
 
158
- As mentioned before, be sure to call `super` in your implementations. No additional information from Stripe is currently handled from the webhook request body.
193
+ # add webhooks
194
+ config.subscribe 'charge.failed', YourChargeFailed
195
+ end
196
+
197
+ ```
data/Rakefile CHANGED
@@ -1,33 +1,2 @@
1
1
  #!/usr/bin/env rake
2
- begin
3
- require 'bundler/setup'
4
- rescue LoadError
5
- puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
- end
7
- begin
8
- require 'rdoc/task'
9
- rescue LoadError
10
- require 'rdoc/rdoc'
11
- require 'rake/rdoctask'
12
- RDoc::Task = Rake::RDocTask
13
- end
14
-
15
- RDoc::Task.new(:rdoc) do |rdoc|
16
- rdoc.rdoc_dir = 'rdoc'
17
- rdoc.title = 'Koudoku'
18
- rdoc.options << '--line-numbers'
19
- rdoc.rdoc_files.include('README.rdoc')
20
- rdoc.rdoc_files.include('lib/**/*.rb')
21
- end
22
-
23
- APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
24
- load 'rails/tasks/engine.rake'
25
-
26
- Bundler::GemHelper.install_tasks
27
-
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')
32
-
33
- task :default => :spec
2
+ require "bundler/gem_tasks"
@@ -1,14 +1,6 @@
1
1
  module Koudoku::Plan
2
2
  extend ActiveSupport::Concern
3
3
 
4
- included do
5
-
6
- end
7
-
8
- module ClassMethods
9
-
10
- end
11
-
12
4
  def is_upgrade_from?(plan)
13
5
  (price || 0) >= (plan.price || 0)
14
6
  end
@@ -6,7 +6,7 @@ module Koudoku::Subscription
6
6
  # We don't store these one-time use tokens, but this is what Stripe provides
7
7
  # client-side after storing the credit card information.
8
8
  attr_accessor :credit_card_token
9
-
9
+
10
10
  belongs_to :plan
11
11
 
12
12
  # update details.
@@ -14,7 +14,7 @@ module Koudoku::Subscription
14
14
  def processing!
15
15
 
16
16
  # if their package level has changed ..
17
- if changing_plans?
17
+ if changing_plans?
18
18
 
19
19
  prepare_for_plan_change
20
20
 
@@ -34,7 +34,7 @@ module Koudoku::Subscription
34
34
  prepare_for_upgrade if upgrading?
35
35
 
36
36
  # update the package level with stripe.
37
- customer.update_subscription(:plan => self.plan.stripe_id)
37
+ customer.update_subscription(:plan => self.plan.stripe_id, :prorate => Koudoku.prorate)
38
38
 
39
39
  finalize_downgrade! if downgrading?
40
40
  finalize_upgrade! if upgrading?
@@ -70,8 +70,7 @@ module Koudoku::Subscription
70
70
  customer_attributes = {
71
71
  description: subscription_owner_description,
72
72
  email: subscription_owner_email,
73
- card: credit_card_token, # obtained with Stripe.js
74
- plan: plan.stripe_id
73
+ card: credit_card_token # obtained with Stripe.js
75
74
  }
76
75
 
77
76
  # If the class we're being included in supports coupons ..
@@ -80,10 +79,15 @@ module Koudoku::Subscription
80
79
  customer_attributes[:trial_end] = coupon.free_trial_ends.to_i
81
80
  end
82
81
  end
82
+
83
+ customer_attributes[:coupon] = @coupon_code if @coupon_code
83
84
 
84
85
  # create a customer at that package level.
85
86
  customer = Stripe::Customer.create(customer_attributes)
86
-
87
+
88
+ finalize_new_customer!(customer.id, plan.price)
89
+ customer.update_subscription(:plan => self.plan.stripe_id, :prorate => Koudoku.prorate)
90
+
87
91
  rescue Stripe::CardError => card_error
88
92
  errors[:base] << card_error.message
89
93
  card_was_declined
@@ -111,12 +115,12 @@ module Koudoku::Subscription
111
115
  end
112
116
 
113
117
  finalize_plan_change!
114
-
118
+
115
119
  # if they're updating their credit card details.
116
120
  elsif self.credit_card_token.present?
117
-
121
+
118
122
  prepare_for_card_update
119
-
123
+
120
124
  # fetch the customer.
121
125
  customer = Stripe::Customer.retrieve(self.stripe_id)
122
126
  customer.card = self.credit_card_token
@@ -131,10 +135,8 @@ module Koudoku::Subscription
131
135
  end
132
136
 
133
137
  end
134
-
135
- module ClassMethods
136
- end
137
-
138
+
139
+
138
140
  def describe_difference(plan_to_describe)
139
141
  if plan.nil?
140
142
  if persisted?
@@ -142,7 +144,7 @@ module Koudoku::Subscription
142
144
  else
143
145
  if Koudoku.free_trial?
144
146
  "Start Trial"
145
- else
147
+ else
146
148
  "Upgrade"
147
149
  end
148
150
  end
@@ -154,6 +156,12 @@ module Koudoku::Subscription
154
156
  end
155
157
  end
156
158
  end
159
+
160
+ # Set a Stripe coupon code that will be used when a new Stripe customer (a.k.a. Koudoku subscription)
161
+ # is created
162
+ def coupon_code=(new_code)
163
+ @coupon_code = new_code
164
+ end
157
165
 
158
166
  # Pretty sure this wouldn't conflict with anything someone would put in their model
159
167
  def subscription_owner
@@ -162,14 +170,19 @@ module Koudoku::Subscription
162
170
  send Koudoku.subscriptions_owned_by
163
171
  end
164
172
 
173
+ def subscription_owner=(owner)
174
+ # e.g. @subscription.user = @owner
175
+ send Koudoku.owner_assignment_sym, owner
176
+ end
177
+
165
178
  def subscription_owner_description
166
179
  # assuming owner responds to name.
167
180
  # we should check for whether it responds to this or not.
168
- "#{subscription_owner.id}"
181
+ "#{subscription_owner.try(:name) || subscription_owner.try(:id)}"
169
182
  end
170
183
 
171
184
  def subscription_owner_email
172
- nil
185
+ "#{subscription_owner.try(:email)}"
173
186
  end
174
187
 
175
188
  def changing_plans?
@@ -199,7 +212,7 @@ module Koudoku::Subscription
199
212
 
200
213
  def prepare_for_cancelation
201
214
  end
202
-
215
+
203
216
  def prepare_for_card_update
204
217
  end
205
218
 
@@ -209,6 +222,9 @@ module Koudoku::Subscription
209
222
  def finalize_new_subscription!
210
223
  end
211
224
 
225
+ def finalize_new_customer!(customer_id, amount)
226
+ end
227
+
212
228
  def finalize_upgrade!
213
229
  end
214
230
 
@@ -223,14 +239,14 @@ module Koudoku::Subscription
223
239
 
224
240
  def card_was_declined
225
241
  end
226
-
242
+
227
243
  # stripe web-hook callbacks.
228
244
  def payment_succeeded(amount)
229
245
  end
230
-
246
+
231
247
  def charge_failed
232
248
  end
233
-
249
+
234
250
  def charge_disputed
235
251
  end
236
252
 
@@ -16,8 +16,27 @@ module Koudoku
16
16
 
17
17
  def load_owner
18
18
  unless params[:owner_id].nil?
19
- if current_owner.try(:id) == params[:owner_id].try(:to_i)
20
- @owner = current_owner
19
+ if current_owner.present?
20
+
21
+ # we need to try and look this owner up via the find method so that we're
22
+ # taking advantage of any override of the find method that would be provided
23
+ # by older versions of friendly_id. (support for newer versions default behavior
24
+ # below.)
25
+ searched_owner = current_owner.class.find(params[:owner_id]) rescue nil
26
+
27
+ # if we couldn't find them that way, check whether there is a new version of
28
+ # friendly_id in place that we can use to look them up by their slug.
29
+ # in christoph's words, "why?!" in my words, "warum?!!!"
30
+ # (we debugged this together on skype.)
31
+ if searched_owner.nil? && current_owner.class.respond_to?(:friendly)
32
+ searched_owner = current_owner.class.friendly.find(params[:owner_id]) rescue nil
33
+ end
34
+
35
+ if current_owner.try(:id) == searched_owner.try(:id)
36
+ @owner = current_owner
37
+ else
38
+ return unauthorized
39
+ end
21
40
  else
22
41
  return unauthorized
23
42
  end
@@ -29,7 +48,7 @@ module Koudoku
29
48
  end
30
49
 
31
50
  def load_subscription
32
- ownership_attribute = (Koudoku.subscriptions_owned_by.to_s + "_id").to_sym
51
+ ownership_attribute = :"#{Koudoku.subscriptions_owned_by}_id"
33
52
  @subscription = ::Subscription.where(ownership_attribute => current_owner.id).find_by_id(params[:id])
34
53
  return @subscription.present? ? @subscription : unauthorized
35
54
  end
@@ -38,11 +57,13 @@ module Koudoku
38
57
  # by default these support devise, but they can be overriden to support others.
39
58
  def current_owner
40
59
  # e.g. "self.current_user"
41
- send "current_#{Koudoku.subscriptions_owned_by.to_s}"
60
+ send "current_#{Koudoku.subscriptions_owned_by}"
42
61
  end
43
62
 
44
63
  def redirect_to_sign_up
45
- session["#{Koudoku.subscriptions_owned_by.to_s}_return_to"] = new_subscription_path(plan: params[:plan])
64
+ # this is a Devise default variable and thus should not change its name
65
+ # when we change subscription owners from :user to :company
66
+ session["user_return_to"] = new_subscription_path(plan: params[:plan])
46
67
  redirect_to new_registration_path(Koudoku.subscriptions_owned_by.to_s)
47
68
  end
48
69
 
@@ -60,8 +81,7 @@ module Koudoku
60
81
  unless no_owner?
61
82
  # we should also set the owner of the subscription here.
62
83
  @subscription = ::Subscription.new({Koudoku.owner_id_sym => @owner.id})
63
- # e.g. @subscription.user = @owner
64
- @subscription.send Koudoku.owner_assignment_sym, @owner
84
+ @subscription.subscription_owner = @owner
65
85
  end
66
86
 
67
87
  end
@@ -96,10 +116,12 @@ module Koudoku
96
116
 
97
117
  def create
98
118
  @subscription = ::Subscription.new(subscription_params)
99
- @subscription.user = @owner
119
+ @subscription.subscription_owner = @owner
120
+ @subscription.coupon_code = session[:koudoku_coupon_code]
121
+
100
122
  if @subscription.save
101
- flash[:notice] = "You've been successfully upgraded."
102
- redirect_to owner_subscription_path(@owner, @subscription)
123
+ flash[:notice] = after_new_subscription_message
124
+ redirect_to after_new_subscription_path
103
125
  else
104
126
  flash[:error] = 'There was a problem processing this transaction.'
105
127
  render :new
@@ -141,5 +163,17 @@ module Koudoku
141
163
  end
142
164
 
143
165
  end
166
+
167
+ def after_new_subscription_path
168
+ return super(@owner, @subscription) if defined?(super)
169
+ owner_subscription_path(@owner, @subscription)
170
+ end
171
+
172
+ def after_new_subscription_message
173
+ controller = ::ApplicationController.new
174
+ controller.respond_to?(:new_subscription_notice_message) ?
175
+ controller.try(:new_subscription_notice_message) :
176
+ "You've been successfully upgraded."
177
+ end
144
178
  end
145
- end
179
+ end
@@ -1,5 +1,5 @@
1
1
  <% content_for :koudoku do %>
2
- <script type="text/javascript" src="https://js.stripe.com/v1/"></script>
2
+ <script type="text/javascript" src="https://js.stripe.com/v2/"></script>
3
3
  <% end %>
4
4
 
5
5
  <%= form_for @subscription, url: url, html: {id: 'payment-form', class: 'form-horizontal'} do |f| %>
@@ -1,11 +1,11 @@
1
1
  <% if @subscription.plan.present? %>
2
- <h2>You're Subscribed!</h2>
3
- <p>Your currently subscribed to the <%= @subscription.plan.name %> plan.</p>
4
- <%= link_to 'Choose Another Plan', edit_owner_subscription_path(@owner, @subscription), class: 'btn' %>
2
+ <h2>You're Subscribed!</h2>
3
+ <p>You're currently subscribed to the <%= @subscription.plan.name %> plan.</p>
4
+ <%= link_to 'Choose Another Plan', edit_owner_subscription_path(@owner, @subscription), class: 'btn' %>
5
5
  <% else %>
6
- <h2>No Subscription</h2>
7
- <p>You are not subscribed to a paid plan.</p>
8
- <%= link_to 'Choose A Plan', edit_owner_subscription_path(@owner, @subscription), class: 'btn' %>
6
+ <h2>No Subscription</h2>
7
+ <p>You are not subscribed to a paid plan.</p>
8
+ <%= link_to 'Choose A Plan', edit_owner_subscription_path(@owner, @subscription), class: 'btn' %>
9
9
  <% end %>
10
10
 
11
11
  <br><br>
@@ -0,0 +1,27 @@
1
+ StripeEvent.configure do |events|
2
+ events.subscribe 'charge.failed' do |event|
3
+ stripe_id = event.data.object['customer']
4
+
5
+ subscription = ::Subscription.find_by_stripe_id(stripe_id)
6
+ subscription.charge_failed
7
+ end
8
+
9
+ events.subscribe 'invoice.payment_succeeded' do |event|
10
+ stripe_id = event.data.object['customer']
11
+ amount = event.data.object['total'].to_f / 100.0
12
+ subscription = ::Subscription.find_by_stripe_id(stripe_id)
13
+ subscription.payment_succeeded(amount)
14
+ end
15
+
16
+ events.subscribe 'charge.dispute.created' do |event|
17
+ stripe_id = event.data.object['customer']
18
+ subscription = ::Subscription.find_by_stripe_id(stripe_id)
19
+ subscription.charge_disputed
20
+ end
21
+
22
+ events.subscribe 'customer.subscription.deleted' do |event|
23
+ stripe_id = event.data.object['customer']
24
+ subscription = ::Subscription.find_by_stripe_id(stripe_id)
25
+ subscription.subscription_owner.try(:cancel)
26
+ end
27
+ end
data/config/routes.rb CHANGED
@@ -8,5 +8,7 @@ Koudoku::Engine.routes.draw do
8
8
  end
9
9
  end
10
10
  end
11
- resources :webhooks, only: [:create]
11
+
12
+ mount StripeEvent::Engine => '/webhooks'
13
+
12
14
  end
@@ -9,57 +9,53 @@ require 'rails/generators'
9
9
  module Koudoku
10
10
  class InstallGenerator < Rails::Generators::Base
11
11
 
12
- # Not sure what this does.
13
- source_root File.expand_path("../../../../app/views/koudoku/subscriptions", __FILE__)
12
+ def self.source_paths
13
+ [Koudoku::Engine.root, File.expand_path("../templates", __FILE__)]
14
+ end
14
15
 
15
16
  include Rails::Generators::Migration
16
17
 
17
18
  argument :subscription_owner_model, :type => :string, :required => true, :desc => "Owner of the subscription"
18
19
  desc "Koudoku installation generator"
19
20
 
21
+ # Override the attr_accessor generated by 'argument' so that
22
+ # subscription_owner_model is always returned lowercase.
23
+ def subscription_owner_model
24
+ @subscription_owner_model.downcase
25
+ end
26
+
27
+
20
28
  def install
21
-
29
+
22
30
  unless defined?(Koudoku)
23
31
  gem("koudoku")
24
32
  end
25
-
33
+
26
34
  require "securerandom"
27
- api_key = SecureRandom.uuid
28
- create_file 'config/initializers/koudoku.rb' do
29
- <<-RUBY
30
- Koudoku.setup do |config|
31
- config.webhooks_api_key = "#{api_key}"
32
- config.subscriptions_owned_by = :user
33
- config.stripe_publishable_key = ENV['STRIPE_PUBLISHABLE_KEY']
34
- config.stripe_secret_key = ENV['STRIPE_SECRET_KEY']
35
- # config.free_trial_length = 30
36
- end
37
- RUBY
38
- end
35
+ template "config/initializers/koudoku.rb"
39
36
 
40
37
  # Generate subscription.
41
38
  generate("model", "subscription stripe_id:string plan_id:integer last_four:string coupon_id:integer card_type:string current_price:float #{subscription_owner_model}_id:integer")
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
-
39
+ template "app/models/subscription.rb"
40
+
44
41
  # Add the plans.
45
42
  generate("model", "plan name:string stripe_id:string price:float interval:string features:text highlight:boolean display_order:integer")
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"
43
+ template "app/models/plan.rb"
47
44
 
48
45
  # Add coupons.
49
46
  generate("model coupon code:string free_trial_length:string")
50
- gsub_file "app/models/coupon.rb", /ActiveRecord::Base/, "ActiveRecord::Base\n has_many :subscriptions\n"
51
-
47
+ template "app/models/coupon.rb"
48
+
52
49
  # Update the owner relationship.
53
- gsub_file "app/models/#{subscription_owner_model}.rb", /ActiveRecord::Base/, "ActiveRecord::Base\n\n # Added by Koudoku.\n has_one :subscription\n\n"
54
-
50
+ inject_into_class "app/models/#{subscription_owner_model}.rb", Plan,
51
+ "# Added by Koudoku.\n has_one :subscription\n\n"
52
+
55
53
  # Install the pricing table.
56
- ["_social_proof.html.erb"].each do |file|
57
- copy_file file, "app/views/koudoku/subscriptions/#{file}"
58
- end
54
+ copy_file "app/views/koudoku/subscriptions/_social_proof.html.erb"
59
55
 
60
56
  # Add webhooks to the route.
61
- gsub_file "config/routes.rb", /Application.routes.draw do/, <<-RUBY
62
- Application.routes.draw do
57
+
58
+ route <<-RUBY
63
59
 
64
60
  # Added by Koudoku.
65
61
  mount Koudoku::Engine, at: 'koudoku'
@@ -68,10 +64,10 @@ Application.routes.draw do
68
64
  end
69
65
 
70
66
  RUBY
71
-
67
+
72
68
  # Show the user the API key we generated.
73
- say "\nTo enable support for Stripe webhooks, point it to \"/koudoku/webhooks?api_key=#{api_key}\". This API key has been randomly generated, so it's unique to your application.\n\n"
74
-
69
+ say "\nTo enable support for Stripe webhooks, point it to \"/koudoku/events\"."
70
+
75
71
  end
76
72
 
77
73
  end
@@ -0,0 +1,5 @@
1
+ class Coupon < ActiveRecord::Base
2
+
3
+ has_many :subscriptions
4
+
5
+ end
@@ -0,0 +1,6 @@
1
+ class Plan < ActiveRecord::Base
2
+ has_many :subscriptions
3
+
4
+ include Koudoku::Plan
5
+ attr_accessible :display_order, :features, :highlight, :interval, :name, :price, :stripe_id
6
+ end
@@ -0,0 +1,8 @@
1
+ class Subscription < ActiveRecord::Base
2
+ include Koudoku::Subscription
3
+
4
+ <%= "attr_accessible :credit_card_token" if Rails::VERSION::MAJOR == 3 %>
5
+ belongs_to :<%= subscription_owner_model %>
6
+ belongs_to :coupon
7
+
8
+ end