koudoku 0.0.2 → 0.0.3
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.
- data/README.md +13 -69
- data/docs/INSTALLATION.md +0 -0
- data/lib/generators/koudoku/install_generator.rb +63 -0
- data/lib/generators/koudoku/templates/insert_default_plans.rb +0 -0
- data/lib/koudoku.rb +13 -0
- data/lib/koudoku/subscription.rb +10 -0
- data/lib/koudoku/version.rb +1 -1
- data/lib/koudoku/webhooks_controller.rb +37 -0
- data/lib/tasks/koudoku.rake +8 -0
- metadata +22 -7
data/README.md
CHANGED
@@ -1,80 +1,16 @@
|
|
1
1
|
# Koudoku
|
2
2
|
|
3
|
-
Robust subscription support for Ruby on Rails apps using [Stripe](https://stripe.com). Makes it easy to manage actions related to new subscriptions, upgrades, downgrades, cancelations, as well as hooking up notifications, metrics logging, coupons, etc.
|
3
|
+
Robust subscription support for Ruby on Rails apps using [Stripe](https://stripe.com). Makes it easy to manage actions related to new subscriptions, upgrades, downgrades, cancelations, payment failures, as well as hooking up notifications, metrics logging, coupons, etc.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
7
7
|
Include the following in your `Gemfile`:
|
8
8
|
|
9
9
|
gem 'koudoku'
|
10
|
-
|
11
|
-
## Usage
|
12
|
-
|
13
|
-
There are no generators at the moment, so you'll need to generate plans, subscriptions, and (optionally) coupons on your own:
|
14
|
-
|
15
|
-
### Subscriptions
|
16
|
-
|
17
|
-
rails g model subscription stripe_id:string plan_id:integer last_four:string coupon_id:integer current_price:float user_id:integer
|
18
10
|
|
19
|
-
|
20
|
-
|
21
|
-
Then, dress up your subscription model by including the `Koudoku::Subscription` module and defining some essential relationships:
|
22
|
-
|
23
|
-
class Subscription < ActiveRecord::Base
|
24
|
-
include Koudoku::Subscription
|
25
|
-
|
26
|
-
# Belongs to user. (This is the default.)
|
27
|
-
attr_accessible :user_id
|
28
|
-
belongs_to :user
|
29
|
-
|
30
|
-
# Supports coupons.
|
31
|
-
attr_accessible :coupon_id
|
32
|
-
belongs_to :coupon
|
33
|
-
|
34
|
-
end
|
35
|
-
|
36
|
-
### Plans
|
37
|
-
|
38
|
-
rails g model plan name:string stripe_id:string price:float
|
39
|
-
|
40
|
-
The `stripe_id` for each plan must match the ID from Stripe. The price here isn't affected by (nor does it affect) the price in Stripe.
|
41
|
-
|
42
|
-
You'll need to create a few plans to start. (You don't need to create a plan to represent "free" accounts.)
|
43
|
-
|
44
|
-
Plan.create(name: 'Personal', price: '10.00')
|
45
|
-
Plan.create(name: 'Team', price: '30.00')
|
46
|
-
Plan.create(name: 'Enterprise', price: '100.00')
|
47
|
-
|
48
|
-
### Coupons
|
49
|
-
|
50
|
-
Again, this is only required if you want to support coupons:
|
51
|
-
|
52
|
-
rails g model coupon code:string free_trial_length:string
|
53
|
-
|
54
|
-
|
55
|
-
## Subscriptions That Belong to Models Other than User
|
56
|
-
|
57
|
-
Here's an example of a subscription that belongs to a company rather than a user:
|
58
|
-
|
59
|
-
class Subscription < ActiveRecord::Base
|
60
|
-
include Koudoku::Subscription
|
61
|
-
|
62
|
-
# Ownership.
|
63
|
-
attr_accessible :company_id
|
64
|
-
belongs_to :company
|
65
|
-
|
66
|
-
# Inform Koudoku::Subscription how to identify the owner of the subscription.
|
67
|
-
def subscription_owner
|
68
|
-
company
|
69
|
-
end
|
70
|
-
|
71
|
-
# Inform Koudoku::Subscription how to represent the owner in emails, etc.
|
72
|
-
def subscription_owner_description
|
73
|
-
"#{company.name} (#{company.primary_contact_name})"
|
74
|
-
end
|
75
|
-
|
76
|
-
end
|
11
|
+
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`".)
|
77
12
|
|
13
|
+
rails g koudoku:install user
|
78
14
|
|
79
15
|
## Using Coupons
|
80
16
|
|
@@ -88,7 +24,6 @@ Then assign it to a _new_ subscription before saving:
|
|
88
24
|
subscription.coupon = coupon
|
89
25
|
subscription.save
|
90
26
|
|
91
|
-
|
92
27
|
## Implementing Logging, Notifications, etc.
|
93
28
|
|
94
29
|
The included module defined the following empty "template methods" which you're able to provide an implementation for:
|
@@ -104,8 +39,17 @@ The included module defined the following empty "template methods" which you're
|
|
104
39
|
- `finalize_downgrade!`
|
105
40
|
- `finalize_cancelation!`
|
106
41
|
- `card_was_declined`
|
107
|
-
|
42
|
+
|
108
43
|
Be sure to include a call to `super` in each of your implementations, especially if you're using multiple concerns to break all this logic into smaller pieces.
|
109
44
|
|
110
45
|
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.
|
111
46
|
|
47
|
+
### Stripe Webhooks
|
48
|
+
|
49
|
+
Support for Stripe Webhooks is still quite limited. To react to some of the webhooks that are currently handled, you can implement the following template methods of the `Subscription` class:
|
50
|
+
|
51
|
+
- `payment_succeeded(amount)`
|
52
|
+
- `charge_failed`
|
53
|
+
- `charge_disputed`
|
54
|
+
|
55
|
+
No additional information is currently handled from the webhook request body.
|
File without changes
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# this generator based on rails_admin's install generator.
|
2
|
+
# https://www.github.com/sferik/rails_admin/master/lib/generators/rails_admin/install_generator.rb
|
3
|
+
|
4
|
+
require 'rails/generators'
|
5
|
+
|
6
|
+
# http://guides.rubyonrails.org/generators.html
|
7
|
+
# http://rdoc.info/github/wycats/thor/master/Thor/Actions.html
|
8
|
+
|
9
|
+
module Koudoku
|
10
|
+
class InstallGenerator < Rails::Generators::Base
|
11
|
+
|
12
|
+
# Not sure what this does.
|
13
|
+
source_root File.expand_path("../templates", __FILE__)
|
14
|
+
|
15
|
+
include Rails::Generators::Migration
|
16
|
+
|
17
|
+
argument :subscription_owner_model, :type => :string, :required => true, :desc => "Owner of the subscription"
|
18
|
+
desc "Koudoku installation generator"
|
19
|
+
|
20
|
+
def install
|
21
|
+
|
22
|
+
unless defined?(Koudoku)
|
23
|
+
gem("koudoku")
|
24
|
+
end
|
25
|
+
|
26
|
+
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
|
+
end
|
33
|
+
RUBY
|
34
|
+
end
|
35
|
+
|
36
|
+
# Generate subscription.
|
37
|
+
generate("model", "subscription stripe_id:string plan_id:integer last_four:string coupon_id:integer current_price:float #{subscription_owner_model}_id:integer")
|
38
|
+
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"
|
39
|
+
|
40
|
+
# Add the plans.
|
41
|
+
generate("model", "plan name:string stripe_id:string price:float")
|
42
|
+
gsub_file "app/models/plan.rb", /ActiveRecord::Base/, "ActiveRecord::Base\n belongs_to :#{subscription_owner_model}\n belongs_to :coupon\n"
|
43
|
+
|
44
|
+
# Add coupons.
|
45
|
+
generate("model coupon code:string free_trial_length:string")
|
46
|
+
gsub_file "app/models/plan.rb", /ActiveRecord::Base/, "ActiveRecord::Base\n has_many :subscriptions\n"
|
47
|
+
|
48
|
+
# Update the owner relationship.
|
49
|
+
gsub_file "app/models/#{subscription_owner_model}.rb", /ActiveRecord::Base/, "ActiveRecord::Base\n\n # Added by Koudoku.\n has_one :subscription\n\n"
|
50
|
+
|
51
|
+
# Update the owner relationship.
|
52
|
+
gsub_file "app/models/#{subscription_owner_model}.rb", /ActiveRecord::Base/, "ActiveRecord::Base\n\n # Added by Koudoku.\n has_one :subscription\n\n"
|
53
|
+
|
54
|
+
# Add webhooks to the route.
|
55
|
+
gsub_file "config/routes.rb", /Application.routes.draw do/, "Application.routes.draw do\n\n # Added by Koudoku.\n namespace :koudoku do\n match 'webhooks' => 'webhooks#process'\n end\n\n"
|
56
|
+
|
57
|
+
# Show the user the API key we generated.
|
58
|
+
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"
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
File without changes
|
data/lib/koudoku.rb
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
require "koudoku/version"
|
2
2
|
require "koudoku/subscription"
|
3
|
+
require "koudoku/webhooks_controller"
|
4
|
+
require "generators/koudoku/install_generator"
|
5
|
+
|
6
|
+
# Load all rake tasks.
|
7
|
+
Dir["tasks/**/*.rake"].each { |ext| load ext } if defined?(Rake)
|
3
8
|
|
4
9
|
module Koudoku
|
10
|
+
|
11
|
+
mattr_accessor :webhooks_api_key
|
12
|
+
@@webhooks_api_key = nil
|
13
|
+
|
14
|
+
def self.setup
|
15
|
+
yield self
|
16
|
+
end
|
17
|
+
|
5
18
|
end
|
data/lib/koudoku/subscription.rb
CHANGED
data/lib/koudoku/version.rb
CHANGED
@@ -0,0 +1,37 @@
|
|
1
|
+
class Koudoku::WebhooksController < ActionController::Base
|
2
|
+
|
3
|
+
# I have no idea why this parameter is required to be here.
|
4
|
+
# If we don't do this, we get "wrong number of arguments (1 for 0)"
|
5
|
+
def process(parameter = nil)
|
6
|
+
|
7
|
+
raise "API key not configured. For security reasons you must configure this in 'config/koudoku.rb'." unless Koudoku.webhooks_api_key.present?
|
8
|
+
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
|
9
|
+
|
10
|
+
data_json = JSON.parse request.body.read
|
11
|
+
|
12
|
+
if data_json['type'] == "invoice.payment_succeeded"
|
13
|
+
|
14
|
+
stripe_id = data_json['data']['object']['customer']
|
15
|
+
amount = data_json['data']['object']['total'].to_f / 100.0
|
16
|
+
subscription = Subscription.find_by_stripe_id(stripe_id)
|
17
|
+
subscription.payment_succeeded(amount)
|
18
|
+
|
19
|
+
elsif data_json['type'] == "charge.failed"
|
20
|
+
|
21
|
+
stripe_id = data_json['data']['object']['customer']
|
22
|
+
subscription = Subscription.find_by_stripe_id(stripe_id)
|
23
|
+
subscription.charge_failed
|
24
|
+
|
25
|
+
elsif data_json['type'] == "charge.dispute.created"
|
26
|
+
|
27
|
+
stripe_id = data_json['data']['object']['customer']
|
28
|
+
|
29
|
+
puts "Finding listing with Stripe ID #{stripe_id}"
|
30
|
+
subscription = Subscription.find_by_stripe_id(stripe_id)
|
31
|
+
listing = subscription.listing
|
32
|
+
subscription.charge_disputed
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: koudoku
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-12-
|
12
|
+
date: 2012-12-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,15 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
25
30
|
- !ruby/object:Gem::Dependency
|
26
31
|
name: stripe
|
27
|
-
requirement:
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
28
33
|
none: false
|
29
34
|
requirements:
|
30
35
|
- - ! '>='
|
@@ -32,7 +37,12 @@ dependencies:
|
|
32
37
|
version: '0'
|
33
38
|
type: :runtime
|
34
39
|
prerelease: false
|
35
|
-
version_requirements:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
36
46
|
description: Robust subscription support for Rails with Stripe. Provides package levels,
|
37
47
|
coupons, logging, notifications, etc.
|
38
48
|
email:
|
@@ -45,10 +55,15 @@ files:
|
|
45
55
|
- Gemfile
|
46
56
|
- README.md
|
47
57
|
- Rakefile
|
58
|
+
- docs/INSTALLATION.md
|
48
59
|
- koudoku.gemspec
|
60
|
+
- lib/generators/koudoku/install_generator.rb
|
61
|
+
- lib/generators/koudoku/templates/insert_default_plans.rb
|
49
62
|
- lib/koudoku.rb
|
50
63
|
- lib/koudoku/subscription.rb
|
51
64
|
- lib/koudoku/version.rb
|
65
|
+
- lib/koudoku/webhooks_controller.rb
|
66
|
+
- lib/tasks/koudoku.rake
|
52
67
|
homepage: http://github.com/andrewculver/koudoku
|
53
68
|
licenses: []
|
54
69
|
post_install_message:
|
@@ -69,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
84
|
version: '0'
|
70
85
|
requirements: []
|
71
86
|
rubyforge_project: koudoku
|
72
|
-
rubygems_version: 1.8.
|
87
|
+
rubygems_version: 1.8.24
|
73
88
|
signing_key:
|
74
89
|
specification_version: 3
|
75
90
|
summary: Robust subscription support for Rails with Stripe.
|