koudoku 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,6 +1,8 @@
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, payment failures, as well as hooking up notifications, metrics logging, coupons, etc.
3
+ 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
+
5
+ To see an example of Koudoku in action, please visit [Koudoku.org](http://koudoku.org/).
4
6
 
5
7
  ## Installation
6
8
 
@@ -12,11 +14,34 @@ After running `bundle install`, you can run a Rails generator to do the rest. Be
12
14
 
13
15
  rails g koudoku:install user
14
16
 
15
- After installing, you'll need to add some subscription plans.
16
-
17
- Plan.create(name: 'Personal', price: 10.00, stripe_id: '1')
18
- Plan.create(name: 'Team', price: 30.00, stripe_id: '2')
19
- Plan.create(name: 'Enterprise', price: 100.00, stripe_id: '3')
17
+ After installing, you'll need to add some subscription plans. (Note that we highlight the 'Team' plan.)
18
+
19
+ Plan.create({
20
+ name: 'Personal',
21
+ price: 10.00,
22
+ stripe_id: '1', # This maps to the plan ID in Stripe.
23
+ features: ['1 Project', '1 Page', '1 User', '1 Organization'].join("\n\n"), # Features support Markdown syntax.
24
+ display_order: 1
25
+ })
26
+
27
+ Plan.create({
28
+ name: 'Team',
29
+ highlight: true, # This highlights the plan on the pricing page.
30
+ price: 30.00,
31
+ stripe_id: '2',
32
+ features: ['3 Projects', '3 Pages', '3 Users', '3 Organizations'].join("\n\n"),
33
+ display_order: 2
34
+ })
35
+
36
+ Plan.create({
37
+ name: 'Enterprise',
38
+ price: 100.00,
39
+ stripe_id: '3',
40
+ features: ['10 Projects', '10 Pages', '10 Users', '10 Organizations'].join("\n\n"),
41
+ display_order: 3
42
+ })
43
+
44
+ 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.
20
45
 
21
46
  ### Configuring Stripe API Keys
22
47
 
@@ -36,9 +61,13 @@ On Heroku you accomplish this same effect with [Config Vars](https://devcenter.h
36
61
 
37
62
  ## User-Facing Subscription Management
38
63
 
39
- Users can view available plans, select a plan, enter credit card details, review their subscription, change plans, and cancel at the following route:
64
+ 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:
40
65
 
41
- koudoku.owner_subscriptions_path(@user)
66
+ <%= link_to 'Pricing', pricing_path %>
67
+
68
+ Existing users can view available plans, select a plan, enter credit card details, review their subscription, change plans, and cancel at the following route:
69
+
70
+ koudoku.owner_subscriptions_path(@user)
42
71
 
43
72
  In these paths, `owner` refers to `User` by default, or whatever model has been configured to be the owner of the `Subscription` model.
44
73
 
@@ -56,7 +85,6 @@ Or, if you've replaced your `application.css` with an `application.scss` (like I
56
85
 
57
86
  @import "koudoku/pricing-table"
58
87
 
59
-
60
88
  ## Using Coupons
61
89
 
62
90
  While more robust coupon support is expected in the future, the simple way to use a coupon is to first create it:
@@ -69,6 +97,8 @@ Then assign it to a _new_ subscription before saving:
69
97
  subscription.coupon = coupon
70
98
  subscription.save
71
99
 
100
+ It should be noted that these coupons are different from the coupons provided natively by Stripe.
101
+
72
102
  ## Implementing Logging, Notifications, etc.
73
103
 
74
104
  The included module defines the following empty "template methods" which you're able to provide an implementation for in `Subscription`:
@@ -78,11 +108,13 @@ The included module defines the following empty "template methods" which you're
78
108
  - `prepare_for_upgrade`
79
109
  - `prepare_for_downgrade`
80
110
  - `prepare_for_cancelation`
111
+ - `prepare_for_card_update`
81
112
  - `finalize_plan_change!`
82
113
  - `finalize_new_subscription!`
83
114
  - `finalize_upgrade!`
84
115
  - `finalize_downgrade!`
85
116
  - `finalize_cancelation!`
117
+ - `finalize_card_update!`
86
118
  - `card_was_declined`
87
119
 
88
120
  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.
@@ -1,5 +1,5 @@
1
1
  ul.koudoku-pricing-table {
2
-
2
+
3
3
  position: relative;
4
4
  margin: 20px 0;
5
5
  margin-left: 25px;
@@ -17,7 +17,6 @@ ul.koudoku-pricing-table {
17
17
  position: relative;
18
18
  z-index: 99;
19
19
 
20
-
21
20
  &.plan-primary {
22
21
  background-color: #f9f9f9;
23
22
  margin-top: 0px;
@@ -26,15 +25,17 @@ ul.koudoku-pricing-table {
26
25
  margin-right: -5px;
27
26
 
28
27
  z-index: 100;
29
-
30
- h2 {
31
- margin-top: 5px;
32
- padding-bottom: 7px;
33
- font-size: 2.5em;
28
+
29
+ h3 {
30
+ font-size: 32.5px;
31
+ line-height: 51px;
32
+ }
33
+ h4 {
34
+ line-height: 22px;
34
35
  }
35
36
  a, input[type=submit] {
36
- font-size: 2em;
37
- font-size: 14pt;
37
+ font-size: 21.5px;
38
+ line-height: 26px;
38
39
  }
39
40
  ul.features {
40
41
  margin-bottom: 12px;
@@ -112,6 +112,21 @@ module Koudoku::Subscription
112
112
  end
113
113
 
114
114
  finalize_plan_change!
115
+
116
+ # if they're updating their credit card details.
117
+ elsif self.credit_card_token.present?
118
+
119
+ prepare_for_card_update
120
+
121
+ # fetch the customer.
122
+ customer = Stripe::Customer.retrieve(self.stripe_id)
123
+ customer.card = self.credit_card_token
124
+ customer.save
125
+
126
+ # update the last four based on this new card.
127
+ self.last_four = customer.active_card.last4
128
+
129
+ finalize_card_update!
115
130
 
116
131
  end
117
132
 
@@ -184,6 +199,9 @@ module Koudoku::Subscription
184
199
 
185
200
  def prepare_for_cancelation
186
201
  end
202
+
203
+ def prepare_for_card_update
204
+ end
187
205
 
188
206
  def finalize_plan_change!
189
207
  end
@@ -200,6 +218,9 @@ module Koudoku::Subscription
200
218
  def finalize_cancelation!
201
219
  end
202
220
 
221
+ def finalize_card_update!
222
+ end
223
+
203
224
  def card_was_declined
204
225
  end
205
226
 
@@ -212,7 +233,7 @@ module Koudoku::Subscription
212
233
 
213
234
  def charge_disputed
214
235
  end
215
-
236
+
216
237
  end
217
238
 
218
239
  end
@@ -1,5 +1,5 @@
1
1
  module Koudoku
2
- class ApplicationController < ActionController::Base
2
+ class ApplicationController < ::ApplicationController
3
3
  layout 'application'
4
4
  end
5
5
  end
@@ -1,34 +1,87 @@
1
1
  module Koudoku
2
2
  class SubscriptionsController < ApplicationController
3
3
  before_filter :load_owner
4
- before_filter :show_existing_subscription, only: [:index, :new, :create]
4
+ before_filter :show_existing_subscription, only: [:index, :new, :create], unless: :no_owner?
5
5
  before_filter :load_subscription, only: [:show, :cancel, :edit, :update]
6
6
  before_filter :load_plans, only: [:index, :edit]
7
+
7
8
  def load_plans
8
9
  @plans = ::Plan.order(:price)
9
10
  end
11
+
10
12
  def load_owner
11
13
  # e.g. User.find(params[:user_id])
12
- @owner = Koudoku.owner_class.find(params[:owner_id])
14
+ @owner = Koudoku.owner_class.find(params[:owner_id]) unless params[:owner_id].nil?
13
15
  end
16
+
17
+ def no_owner?
18
+ @owner.nil?
19
+ end
20
+
14
21
  def load_subscription
15
22
  @subscription = ::Subscription.find(params[:id])
16
23
  end
24
+
25
+ # the following two methods allow us to show the pricing table before someone has an account.
26
+ # by default these support devise, but they can be overriden to support others.
27
+ def current_owner
28
+ # e.g. "self.current_user"
29
+ send "current_#{Koudoku.subscriptions_owned_by.to_s}"
30
+ end
31
+
32
+ def redirect_to_sign_up
33
+ session["#{Koudoku.subscriptions_owned_by.to_s}_return_to"] = new_subscription_path(plan: params[:plan])
34
+ redirect_to new_registration_path(Koudoku.subscriptions_owned_by.to_s)
35
+ end
36
+
17
37
  def index
18
- # we should also set the owner of the subscription here.
19
- @subscription = ::Subscription.new({Koudoku.owner_id_sym => @owner.id})
20
- # e.g. @subscription.user = @owner
21
- @subscription.send Koudoku.owner_assignment_sym, @owner
38
+
39
+ # don't bother showing the index if they've already got a subscription.
40
+ if current_owner and current_owner.subscription.present?
41
+ redirect_to koudoku.edit_owner_subscription_path(current_owner, current_owner.subscription)
42
+ end
43
+
44
+ # Load all plans.
45
+ @plans = ::Plan.order(:display_order).all
46
+
47
+ # Don't prep a subscription unless a user is authenticated.
48
+ unless no_owner?
49
+ # we should also set the owner of the subscription here.
50
+ @subscription = ::Subscription.new({Koudoku.owner_id_sym => @owner.id})
51
+ # e.g. @subscription.user = @owner
52
+ @subscription.send Koudoku.owner_assignment_sym, @owner
53
+ end
54
+
22
55
  end
56
+
23
57
  def new
24
- @subscription = ::Subscription.new
25
- @subscription.plan = ::Plan.find(params[:plan])
58
+ if no_owner?
59
+
60
+ if defined?(Devise)
61
+
62
+ # by default these methods support devise.
63
+ if current_owner
64
+ redirect_to new_owner_subscription_path(current_owner, plan: params[:plan])
65
+ else
66
+ redirect_to_sign_up
67
+ end
68
+
69
+ else
70
+ raise "This feature depends on Devise for authentication."
71
+ end
72
+
73
+ else
74
+ @subscription = ::Subscription.new
75
+ @subscription.plan = ::Plan.find(params[:plan])
76
+ end
26
77
  end
78
+
27
79
  def show_existing_subscription
28
80
  if @owner.subscription.present?
29
81
  redirect_to owner_subscription_path(@owner, @owner.subscription)
30
82
  end
31
83
  end
84
+
32
85
  def create
33
86
  @subscription = ::Subscription.new(params[:subscription])
34
87
  @subscription.user = @owner
@@ -40,16 +93,20 @@ module Koudoku
40
93
  render :new
41
94
  end
42
95
  end
96
+
43
97
  def show
44
98
  end
99
+
45
100
  def cancel
46
101
  flash[:success] = "You've successfully cancelled your subscription."
47
102
  @subscription.plan_id = nil
48
103
  @subscription.save
49
104
  redirect_to owner_subscription_path(@owner, @subscription)
50
105
  end
106
+
51
107
  def edit
52
108
  end
109
+
53
110
  def update
54
111
  if @subscription.update_attributes(params[:subscription])
55
112
  flash[:success] = "You've been successfully upgraded."
@@ -59,5 +116,6 @@ module Koudoku
59
116
  render :edit
60
117
  end
61
118
  end
119
+
62
120
  end
63
121
  end
@@ -0,0 +1,97 @@
1
+ <script type="text/javascript" src="https://js.stripe.com/v1/"></script>
2
+
3
+ <%= form_for @subscription, url: url, html: {id: 'payment-form', class: 'form-horizontal'} do |f| %>
4
+
5
+ <fieldset>
6
+
7
+ <legend><%= title %></legend>
8
+
9
+ <div class="control-group">
10
+ <label class="control-label">Card Number</label>
11
+ <div class="controls">
12
+ <input type="text" size="20" autocomplete="off" class="card-number"/>
13
+ </div>
14
+ </div>
15
+
16
+ <div class="control-group">
17
+ <label class="control-label">Expiration (MM/YYYY)</label>
18
+ <div class="controls">
19
+ <input type="text" size="2" class="card-expiry-month input-mini"/>
20
+ <span> / </span>
21
+ <input type="text" size="4" class="card-expiry-year input-mini"/>
22
+ </div>
23
+ </div>
24
+
25
+ <div class="control-group">
26
+ <label class="control-label">CVC</label>
27
+ <div class="controls">
28
+ <input type="text" size="4" autocomplete="off" class="card-cvc input-small"/>
29
+ </div>
30
+ </div>
31
+
32
+ <div class="alert alert-error payment-errors"></div>
33
+ <%= f.hidden_field :plan_id %>
34
+
35
+ </fieldset>
36
+
37
+ <div class="control-group">
38
+ <div class="controls">
39
+ <% if Koudoku.free_trial? %>
40
+ <button type="submit" class="btn btn-primary submit-button">Save Billing Information</button>
41
+ <% else %>
42
+ <button type="submit" class="btn btn-primary submit-button">Upgrade Your Account</button>
43
+ <% end %>
44
+ <%= link_to "Cancel", owner_subscriptions_path(@owner), class: 'btn' %>
45
+ </div>
46
+ </div>
47
+
48
+ <% end %>
49
+
50
+ <script type="text/javascript">
51
+
52
+ // All this code taken from Stripe's own examples at:
53
+ // https://stripe.com/docs/tutorials/forms .
54
+
55
+ Stripe.setPublishableKey("<%= Koudoku.stripe_publishable_key %>");
56
+
57
+ function stripeResponseHandler(status, response) {
58
+
59
+ if (response.error) {
60
+ // show the errors on the form
61
+ $(".payment-errors").text(response.error.message).show();
62
+ $(".submit-button").removeAttr("disabled");
63
+ } else {
64
+ var form$ = $("#payment-form");
65
+ // token contains id, last4, and card type
66
+ // insert the token into the form so it gets submitted to the server
67
+ form$.append("<input type='hidden' name='subscription[credit_card_token]' value='" + response['id'] + "'/>");
68
+ form$.append("<input type='hidden' name='subscription[last_four]' value='" + response['last4'] + "'/>");
69
+ form$.append("<input type='hidden' name='subscription[card_type]' value='" + response['card_type'] + "'/>");
70
+ // and submit
71
+ form$.get(0).submit();
72
+ }
73
+ }
74
+
75
+ $(document).ready(function() {
76
+
77
+ // By default, don't show errors.
78
+ $(".payment-errors").hide()
79
+
80
+ $("#payment-form").submit(function(event) {
81
+
82
+ // disable the submit button to prevent repeated clicks
83
+ $('.submit-button').attr("disabled", "disabled");
84
+
85
+ Stripe.createToken({
86
+ number: $('.card-number').val(),
87
+ cvc: $('.card-cvc').val(),
88
+ exp_month: $('.card-expiry-month').val(),
89
+ exp_year: $('.card-expiry-year').val()
90
+ }, stripeResponseHandler);
91
+
92
+ // prevent the form from submitting with the default action
93
+ return false;
94
+ });
95
+ });
96
+
97
+ </script>
@@ -1,12 +1,14 @@
1
1
  <ul class="thumbnails koudoku-pricing-table">
2
2
  <% @plans.each do |plan| %>
3
- <li class="span3 plan <%= 'plan-primary' if plan.id == 2 %>">
3
+ <li class="span3 plan <%= 'plan-primary' if plan.highlight? %>">
4
4
  <div class="thumbnail">
5
5
  <div class="caption">
6
- <h2><%= plan.name %></h2>
7
- <h3><%= number_to_currency(plan.price) %>/month</h3>
6
+ <h3><%= plan.name %></h3>
7
+ <h4><%= number_to_currency(plan.price) %>/month</h4>
8
8
  <div class="call-to-action">
9
- <% if @subscription.persisted? %>
9
+ <% if @subscription.nil? %>
10
+ <%= link_to Koudoku.free_trial? ? 'Start Trial' : 'Sign Up', koudoku.new_subscription_path(plan: plan.id), class: "btn btn-success btn-large" %>
11
+ <% elsif @subscription.persisted? %>
10
12
  <% if @subscription.plan == plan %>
11
13
  <%= form_for @subscription, url: owner_subscription_path(@owner, @subscription) do |f| %>
12
14
  <%= f.submit 'Selected', class: "btn btn-large", disabled: 'disabled' %>
@@ -25,10 +27,7 @@
25
27
  <% if Koudoku.free_trial? %>
26
28
  <li class='muted'><%= Koudoku.free_trial_length %>-day Free Trial</li>
27
29
  <% end %>
28
- <li>Testing.</li>
29
- <li>Testing.</li>
30
- <li>Testing.</li>
31
- <li>Testing.</li>
30
+ <%= BlueCloth.new(plan.features.gsub(/\n/, "\n\n")).to_html.gsub(/<(\/?)p>/, '<\1li>').html_safe %>
32
31
  </ul>
33
32
  </div>
34
33
  </div>
@@ -36,18 +35,6 @@
36
35
  <% end %>
37
36
 
38
37
  <li class="span3 social-proof">
39
-
40
- <blockquote>
41
- <h3>"Some Great Quote"</h3> &mdash; Person at Place
42
- </blockquote>
43
-
44
- <blockquote>
45
- <h4>"This is a great service and you've been the best ever! Thanks!"</h4> &mdash; Person at Place
46
- </blockquote>
47
-
48
- <blockquote>
49
- <h4>"I love you guys and you're the best! You're the best!"</h4> &mdash; Person at Place
50
- </blockquote>
51
-
38
+ <%= render 'koudoku/subscriptions/social_proof' %>
52
39
  </li>
53
40
  </ul>
@@ -0,0 +1,11 @@
1
+ <blockquote>
2
+ <h4>"Some Great Quote"</h4> &mdash; Person at Place
3
+ </blockquote>
4
+
5
+ <blockquote>
6
+ <h4>"This is a great service and you've been the best ever! Thanks!"</h4> &mdash; Person at Place
7
+ </blockquote>
8
+
9
+ <blockquote>
10
+ <h4>"I love you guys and you're the best! You're the best!"</h4> &mdash; Person at Place
11
+ </blockquote>
@@ -1,3 +1,7 @@
1
- <h1>What Plan Is Best For You?</h1>
2
- <%= render 'pricing_table' %>
3
- <p>You can also <%= link_to 'cancel your subscription', cancel_owner_subscription_path(@owner, @subscription), method: :post %>.</p>
1
+ <% if params['update'] == 'card' %>
2
+ <%= render 'card', title: "Update Your Payment Information", url: owner_subscription_path(@owner, @subscription) %>
3
+ <% else %>
4
+ <h1>What Plan Is Best For You?</h1>
5
+ <%= render 'pricing_table' %>
6
+ <p>You can also <%= link_to 'cancel your subscription', cancel_owner_subscription_path(@owner, @subscription), method: :post %>.</p>
7
+ <% end %>
@@ -1,99 +1 @@
1
- <script type="text/javascript" src="https://js.stripe.com/v1/"></script>
2
-
3
- <%= form_for @subscription, url: @subscription.persisted? ? owner_subscription_path(@owner, @subscription) : owner_subscriptions_path(@owner), html: {id: 'payment-form', class: 'form-horizontal'} do |f| %>
4
- <% if Koudoku.free_trial? %>
5
- <h1>Start Your Free Trial</h1>
6
- <% else %>
7
- <h1>Upgrade Your Account</h1>
8
- <% end %>
9
-
10
- <fieldset>
11
-
12
- <div class="alert alert-error payment-errors"></div>
13
-
14
- <%= f.hidden_field :plan_id %>
15
-
16
- <div class="control-group">
17
- <label class="control-label">Card Number</label>
18
- <div class="controls">
19
- <input type="text" size="20" autocomplete="off" class="card-number"/>
20
- </div>
21
- </div>
22
-
23
- <div class="control-group">
24
- <label class="control-label">Expiration (MM/YYYY)</label>
25
- <div class="controls">
26
- <input type="text" size="2" class="card-expiry-month input-mini"/>
27
- <span> / </span>
28
- <input type="text" size="4" class="card-expiry-year input-mini"/>
29
- </div>
30
- </div>
31
-
32
- <div class="control-group">
33
- <label class="control-label">CVC</label>
34
- <div class="controls">
35
- <input type="text" size="4" autocomplete="off" class="card-cvc input-small"/>
36
- </div>
37
- </div>
38
-
39
- </fieldset>
40
-
41
- <div class="form-actions">
42
- <% if Koudoku.free_trial? %>
43
- <button type="submit" class="btn btn-primary submit-button">Save Billing Information</button>
44
- <% else %>
45
- <button type="submit" class="btn btn-primary submit-button">Upgrade Your Account</button>
46
- <% end %>
47
- <%= link_to "Cancel", owner_subscriptions_path(@owner), class: 'btn' %>
48
- </div>
49
-
50
- <% end %>
51
-
52
- <script type="text/javascript">
53
-
54
- // All this code taken from Stripe's own examples at:
55
- // https://stripe.com/docs/tutorials/forms .
56
-
57
- Stripe.setPublishableKey("<%= Koudoku.stripe_publishable_key %>");
58
-
59
- function stripeResponseHandler(status, response) {
60
-
61
- if (response.error) {
62
- // show the errors on the form
63
- $(".payment-errors").text(response.error.message).show();
64
- $(".submit-button").removeAttr("disabled");
65
- } else {
66
- var form$ = $("#payment-form");
67
- // token contains id, last4, and card type
68
- // insert the token into the form so it gets submitted to the server
69
- form$.append("<input type='hidden' name='subscription[credit_card_token]' value='" + response['id'] + "'/>");
70
- form$.append("<input type='hidden' name='subscription[last_four]' value='" + response['last4'] + "'/>");
71
- form$.append("<input type='hidden' name='subscription[card_type]' value='" + response['card_type'] + "'/>");
72
- // and submit
73
- form$.get(0).submit();
74
- }
75
- }
76
-
77
- $(document).ready(function() {
78
-
79
- // By default, don't show errors.
80
- $(".payment-errors").hide()
81
-
82
- $("#payment-form").submit(function(event) {
83
-
84
- // disable the submit button to prevent repeated clicks
85
- $('.submit-button').attr("disabled", "disabled");
86
-
87
- Stripe.createToken({
88
- number: $('.card-number').val(),
89
- cvc: $('.card-cvc').val(),
90
- exp_month: $('.card-expiry-month').val(),
91
- exp_year: $('.card-expiry-year').val()
92
- }, stripeResponseHandler);
93
-
94
- // prevent the form from submitting with the default action
95
- return false;
96
- });
97
- });
98
-
99
- </script>
1
+ <%= render 'card', title: (Koudoku.free_trial? ? "Start Your Free Trial" : "Upgrade Your Account"), url: owner_subscriptions_path(@owner) %>
@@ -1,8 +1,15 @@
1
- <h1>Thanks!</h1>
2
1
  <% if @subscription.plan.present? %>
3
- <p>You're subscribed to the <%= @subscription.plan.name %> plan!</p>
4
- <%= link_to 'Choose another plan', edit_owner_subscription_path(@owner, @subscription) %>
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' %>
5
5
  <% else %>
6
+ <h2>No Subscription</h2>
6
7
  <p>You are not subscribed to a paid plan.</p>
7
- <%= link_to 'Choose a plan', edit_owner_subscription_path(@owner, @subscription) %>
8
- <% end %>
8
+ <%= link_to 'Choose A Plan', edit_owner_subscription_path(@owner, @subscription), class: 'btn' %>
9
+ <% end %>
10
+
11
+ <br><br>
12
+
13
+ <h4>Payment Information</h4>
14
+ <p>The card on file for your account ends with <%= @subscription.last_four %>.</p>
15
+ <%= link_to 'Update Payment Information', edit_owner_subscription_path(@owner, @subscription, update: 'card'), class: 'btn' %>
data/config/routes.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  Koudoku::Engine.routes.draw do
2
2
  # e.g. :users
3
+ resources :subscriptions, only: [:new]
3
4
  resources Koudoku.owner_resource, as: :owner do
4
5
  resources :subscriptions do
5
6
  member do
@@ -10,7 +10,7 @@ module Koudoku
10
10
  class InstallGenerator < Rails::Generators::Base
11
11
 
12
12
  # Not sure what this does.
13
- source_root File.expand_path("../templates", __FILE__)
13
+ source_root File.expand_path("../../../../app/views/koudoku/subscriptions", __FILE__)
14
14
 
15
15
  include Rails::Generators::Migration
16
16
 
@@ -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")
45
+ generate("model", "plan name:string stripe_id:string price:float 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.
@@ -54,9 +54,14 @@ RUBY
54
54
 
55
55
  # Update the owner relationship.
56
56
  gsub_file "app/models/#{subscription_owner_model}.rb", /ActiveRecord::Base/, "ActiveRecord::Base\n\n # Added by Koudoku.\n has_one :subscription\n\n"
57
+
58
+ # Install the pricing table.
59
+ ["_social_proof.html.erb"].each do |file|
60
+ copy_file "#{}file", "app/views/koudoku/subscriptions/#{file}"
61
+ end
57
62
 
58
63
  # Add webhooks to the route.
59
- gsub_file "config/routes.rb", /Application.routes.draw do/, "Application.routes.draw do\n\n # Added by Koudoku.\n mount Koudoku::Engine, at: \"koudoku\"\n\n"
64
+ gsub_file "config/routes.rb", /Application.routes.draw do/, "Application.routes.draw do\n\n # Added by Koudoku.\n mount Koudoku::Engine, at: 'koudoku'\n match 'pricing' => 'koudoku::subscriptions#index', as: 'pricing'\n\n"
60
65
 
61
66
  # Show the user the API key we generated.
62
67
  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"
@@ -1,4 +1,5 @@
1
1
  require 'stripe'
2
+ require 'bluecloth'
2
3
  module Koudoku
3
4
  class Engine < ::Rails::Engine
4
5
  isolate_namespace Koudoku
@@ -1,3 +1,3 @@
1
1
  module Koudoku
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  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
4
+ version: 0.0.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-10 00:00:00.000000000 Z
12
+ date: 2013-01-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -18,7 +18,7 @@ dependencies:
18
18
  requirements:
19
19
  - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: 3.2.10
21
+ version: 3.2.11
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -26,7 +26,7 @@ dependencies:
26
26
  requirements:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
- version: 3.2.10
29
+ version: 3.2.11
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: stripe
32
32
  requirement: !ruby/object:Gem::Requirement
@@ -43,6 +43,22 @@ dependencies:
43
43
  - - ! '>='
44
44
  - !ruby/object:Gem::Version
45
45
  version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: bluecloth
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
46
62
  - !ruby/object:Gem::Dependency
47
63
  name: sqlite3
48
64
  requirement: !ruby/object:Gem::Requirement
@@ -76,7 +92,10 @@ files:
76
92
  - app/controllers/koudoku/subscriptions_controller.rb
77
93
  - app/controllers/koudoku/webhooks_controller.rb
78
94
  - app/helpers/koudoku/application_helper.rb
95
+ - app/views/koudoku/subscriptions/_card.html.erb
79
96
  - app/views/koudoku/subscriptions/_pricing_table.html.erb
97
+ - app/views/koudoku/subscriptions/_social_proof.html.erb
98
+ - app/views/koudoku/subscriptions/_stripe_js.html.erb
80
99
  - app/views/koudoku/subscriptions/edit.html.erb
81
100
  - app/views/koudoku/subscriptions/index.html.erb
82
101
  - app/views/koudoku/subscriptions/new.html.erb
@@ -137,7 +156,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
137
156
  version: '0'
138
157
  segments:
139
158
  - 0
140
- hash: -3376003182273526818
159
+ hash: 1525558269552426845
141
160
  required_rubygems_version: !ruby/object:Gem::Requirement
142
161
  none: false
143
162
  requirements:
@@ -146,7 +165,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
146
165
  version: '0'
147
166
  segments:
148
167
  - 0
149
- hash: -3376003182273526818
168
+ hash: 1525558269552426845
150
169
  requirements: []
151
170
  rubyforge_project:
152
171
  rubygems_version: 1.8.24