koudoku 0.0.11 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +56 -17
- data/Rakefile +1 -32
- data/app/concerns/koudoku/plan.rb +0 -8
- data/app/concerns/koudoku/subscription.rb +36 -20
- data/app/controllers/koudoku/subscriptions_controller.rb +45 -11
- data/app/views/koudoku/subscriptions/_card.html.erb +1 -1
- data/app/views/koudoku/subscriptions/show.html.erb +6 -6
- data/config/initializers/stripe_event.rb +27 -0
- data/config/routes.rb +3 -1
- data/lib/generators/koudoku/install_generator.rb +27 -31
- data/lib/generators/koudoku/templates/app/models/coupon.rb +5 -0
- data/lib/generators/koudoku/templates/app/models/plan.rb +6 -0
- data/lib/generators/koudoku/templates/app/models/subscription.rb +8 -0
- data/lib/generators/koudoku/templates/config/initializers/koudoku.rb +13 -0
- data/lib/generators/koudoku/views_generator.rb +5 -2
- data/lib/koudoku/version.rb +1 -1
- data/lib/koudoku.rb +28 -10
- data/spec/concerns/koudoku/plan_spec.rb +13 -13
- data/spec/dummy/app/models/plan.rb +0 -1
- data/spec/dummy/config/application.rb +0 -6
- data/spec/dummy/config/environments/development.rb +2 -7
- data/spec/dummy/config/environments/production.rb +2 -0
- data/spec/dummy/config/environments/test.rb +2 -3
- data/spec/dummy/config/initializers/koudoku.rb +1 -1
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +48 -0
- data/spec/dummy/log/test.log +203 -0
- metadata +60 -48
- data/app/controllers/koudoku/webhooks_controller.rb +0 -39
- data/spec/controllers/koudoku/webhooks_controller_spec.rb +0 -76
- data/spec/dummy/spec/factories/coupons.rb +0 -8
- data/spec/dummy/spec/factories/plans.rb +0 -12
- data/spec/dummy/spec/factories/subscriptions.rb +0 -14
- data/spec/dummy/spec/models/coupon_spec.rb +0 -5
- data/spec/dummy/spec/models/plan_spec.rb +0 -5
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: af40753143d0ae4408359497d0bacc28b7975dff
|
4
|
+
data.tar.gz: f001cb6c98006da1c42ec89f0b5fe85f9fee70a5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
###
|
179
|
+
### Webhooks
|
149
180
|
|
150
|
-
|
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
|
-
|
184
|
+
You can add your own webhooks using the (reduced) stripe_event syntax in the `config/initializers/koudoku.rb` file:
|
153
185
|
|
154
|
-
|
155
|
-
|
156
|
-
|
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
|
-
|
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
|
-
|
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"
|
@@ -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
|
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
|
-
|
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
|
-
|
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.
|
20
|
-
|
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 =
|
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
|
60
|
+
send "current_#{Koudoku.subscriptions_owned_by}"
|
42
61
|
end
|
43
62
|
|
44
63
|
def redirect_to_sign_up
|
45
|
-
|
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
|
-
|
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.
|
119
|
+
@subscription.subscription_owner = @owner
|
120
|
+
@subscription.coupon_code = session[:koudoku_coupon_code]
|
121
|
+
|
100
122
|
if @subscription.save
|
101
|
-
flash[:notice] =
|
102
|
-
redirect_to
|
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/
|
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>
|
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
@@ -9,57 +9,53 @@ require 'rails/generators'
|
|
9
9
|
module Koudoku
|
10
10
|
class InstallGenerator < Rails::Generators::Base
|
11
11
|
|
12
|
-
|
13
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
43
|
+
template "app/models/plan.rb"
|
47
44
|
|
48
45
|
# Add coupons.
|
49
46
|
generate("model coupon code:string free_trial_length:string")
|
50
|
-
|
51
|
-
|
47
|
+
template "app/models/coupon.rb"
|
48
|
+
|
52
49
|
# Update the owner relationship.
|
53
|
-
|
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
|
-
|
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
|
-
|
62
|
-
|
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/
|
74
|
-
|
69
|
+
say "\nTo enable support for Stripe webhooks, point it to \"/koudoku/events\"."
|
70
|
+
|
75
71
|
end
|
76
72
|
|
77
73
|
end
|