This completes the stripe integration for existing sites

This commit is contained in:
Kyle Drake 2014-04-19 14:02:19 -07:00
parent 4aa57086e0
commit 98496698cb
No known key found for this signature in database
GPG key ID: 8BE721072E1864BE
15 changed files with 364 additions and 33 deletions

81
app.rb
View file

@ -74,6 +74,75 @@ get '/?' do
erb :index, layout: false erb :index, layout: false
end end
get '/plan/?' do
erb :'plan/index'
end
post '/plan/create' do
require_login
DB.transaction do
customer = Stripe::Customer.create(
card: params[:stripe_token],
description: current_site.username,
email: current_site.email,
plan: params[:selected_plan]
)
current_site.stripe_customer_id = customer.id
current_site.plan_ended = false
current_site.save
end
redirect '/plan'
end
def get_plan_name(customer_id)
subscriptions = Stripe::Customer.retrieve(current_site.stripe_customer_id).subscriptions.all
@plan_name = subscriptions.first.plan.name
end
get '/plan/manage' do
require_login
redirect '/plan' unless current_site.supporter? && !current_site.plan_ended
@title = 'Manage Plan'
@plan_name = get_plan_name current_site.stripe_customer_id
erb :'plan/manage'
end
get '/plan/end' do
require_login
redirect '/plan' unless current_site.supporter? && !current_site.plan_ended
@title = 'End Plan'
@plan_name = get_plan_name current_site.stripe_customer_id
erb :'plan/end'
end
post '/plan/end' do
require_login
redirect '/plan' unless current_site.supporter? && !current_site.plan_ended
recaptcha_is_valid = ENV['RACK_ENV'] == 'test' || recaptcha_valid?
if !recaptcha_is_valid
@error = 'Recaptcha was filled out incorrectly, please try re-entering.'
@plan_name = get_plan_name current_site.stripe_customer_id
halt erb :'plan/end'
end
customer = Stripe::Customer.retrieve current_site.stripe_customer_id
subscriptions = customer.subscriptions.all
DB.transaction do
subscriptions.each do |subscription|
customer.subscriptions.retrieve(subscription.id).delete
end
current_site.plan_ended = true
current_site.save
end
redirect '/plan'
end
get '/site/:username/tip' do |username| get '/site/:username/tip' do |username|
@site = Site[username: username] @site = Site[username: username]
@title = "Tip #{@site.title}" @title = "Tip #{@site.title}"
@ -330,7 +399,7 @@ post '/site_files/upload' do
halt http_error_code, 'Did not receive file upload.' halt http_error_code, 'Did not receive file upload.'
end end
if params[:newfile][:tempfile].size > Site::MAX_SPACE || (params[:newfile][:tempfile].size + current_site.total_space) > Site::MAX_SPACE if current_site.file_size_too_large? params[:newfile][:tempfile].size
@errors << 'File size must be smaller than available space.' @errors << 'File size must be smaller than available space.'
halt http_error_code, 'File size must be smaller than available space.' halt http_error_code, 'File size must be smaller than available space.'
end end
@ -390,15 +459,15 @@ post '/site_files/save/:filename' do |filename|
tempfile = Tempfile.new 'neocities_saving_file' tempfile = Tempfile.new 'neocities_saving_file'
if (tempfile.size + current_site.total_space) > Site::MAX_SPACE
halt 'File is too large to fit in your space, it has NOT been saved. Please make a local copy and then try to reduce the size.'
end
input = request.body.read input = request.body.read
tempfile.set_encoding input.encoding tempfile.set_encoding input.encoding
tempfile.write input tempfile.write input
tempfile.close tempfile.close
if current_site.file_site_too_large? tempfile.size
halt 'File is too large to fit in your space, it has NOT been saved. Please make a local copy and then try to reduce the size.'
end
sanitized_filename = filename.gsub(/[^a-zA-Z0-9_\-.]/, '') sanitized_filename = filename.gsub(/[^a-zA-Z0-9_\-.]/, '')
current_site.store_file sanitized_filename, tempfile current_site.store_file sanitized_filename, tempfile
@ -622,7 +691,7 @@ post '/api/upload' do
uploaded_size = files.collect {|f| f[:tempfile].size}.inject{|sum,x| sum + x } uploaded_size = files.collect {|f| f[:tempfile].size}.inject{|sum,x| sum + x }
if (uploaded_size + current_site.total_space) > Site::MAX_SPACE if current_site.file_size_too_large? uploaded_size
api_error 400, 'too_large', 'files are too large to fit in your space, try uploading smaller (or less) files' api_error 400, 'too_large', 'files are too large to fit in your space, try uploading smaller (or less) files'
end end

View file

@ -0,0 +1,9 @@
Sequel.migration do
up {
DB.add_column :sites, :plan_ended, :boolean, default: false
}
down {
DB.drop_column :sites, :plan_ended
}
end

View file

@ -28,7 +28,13 @@ class Site < Sequel::Model
html htm txt text css js jpg jpeg png gif svg md markdown eot ttf woff json html htm txt text css js jpg jpeg png gif svg md markdown eot ttf woff json
geojson csv tsv mf ico pdf asc key pgp xml mid midi geojson csv tsv mf ico pdf asc key pgp xml mid midi
} }
MAX_SPACE = (5242880*2) # 10MB
ONE_MEGABYTE_IN_BYTES = 1048576
FREE_MAXIMUM_IN_MEGABYTES = 10
SUPPORTER_MAXIMUM_IN_MEGABYTES = 200
FREE_MAXIMUM_IN_BYTES = FREE_MAXIMUM_IN_MEGABYTES * ONE_MEGABYTE_IN_BYTES
SUPPORTER_MAXIMUM_IN_BYTES = SUPPORTER_MAXIMUM_IN_MEGABYTES * ONE_MEGABYTE_IN_BYTES
MINIMUM_PASSWORD_LENGTH = 5 MINIMUM_PASSWORD_LENGTH = 5
BAD_USERNAME_REGEX = /[^\w-]/i BAD_USERNAME_REGEX = /[^\w-]/i
VALID_HOSTNAME = /^[a-z0-9][a-z0-9-]+?[a-z0-9]$/i # http://tools.ietf.org/html/rfc1123 VALID_HOSTNAME = /^[a-z0-9][a-z0-9-]+?[a-z0-9]$/i # http://tools.ietf.org/html/rfc1123
@ -294,22 +300,54 @@ class Site < Sequel::Model
Dir.glob(File.join(files_path, '*')).collect {|p| File.basename(p)}.sort.collect {|sitename| SiteFile.new sitename} Dir.glob(File.join(files_path, '*')).collect {|p| File.basename(p)}.sort.collect {|sitename| SiteFile.new sitename}
end end
def total_space def file_size_too_large?(size_in_bytes)
return true if size_in_bytes + used_space_in_bytes > maximum_space_in_bytes
false
end
def used_space_in_bytes
space = Dir.glob(File.join(files_path, '*')).collect {|p| File.size(p)}.inject {|sum,x| sum += x} space = Dir.glob(File.join(files_path, '*')).collect {|p| File.size(p)}.inject {|sum,x| sum += x}
space.nil? ? 0 : space space.nil? ? 0 : space
end end
def total_space_in_megabytes def used_space_in_megabytes
(total_space.to_f / 2**20).round(2) (used_space_in_bytes.to_f / self.class::ONE_MEGABYTE_IN_BYTES).round(2)
end end
def available_space def available_space_in_bytes
remaining = MAX_SPACE - total_space remaining = maximum_space_in_bytes - used_space_in_bytes
remaining < 0 ? 0 : remaining remaining < 0 ? 0 : remaining
end end
def available_space_in_megabytes def available_space_in_megabytes
(available_space.to_f / 2**20).round(2) (available_space_in_bytes.to_f / self.class::ONE_MEGABYTE_IN_BYTES).round(2)
end
def maximum_space_in_bytes
supporter? ? self.class::SUPPORTER_MAXIMUM_IN_BYTES : self.class::FREE_MAXIMUM_IN_BYTES
end
def maximum_space_in_megabytes
supporter? ? self.class::SUPPORTER_MAXIMUM_IN_MEGABYTES : self.class::FREE_MAXIMUM_IN_MEGABYTES
end
def space_percentage_used
((used_space_in_bytes.to_f / maximum_space_in_bytes) * 100).round(1)
end
# This returns true even if they end their support plan.
def supporter?
!values[:stripe_customer_id].nil?
end
# This will return false if they have ended their support plan.
def ended_supporter?
values[:ended_plan]
end
def plan_name
return 'Free Plan' if !supporter? || (supporter? && ended_supporter?)
'Supporter Plan'
end end
def title def title

View file

@ -22,6 +22,9 @@
<li> <li>
<a href="/about" title="About Neocities">About</a> <a href="/about" title="About Neocities">About</a>
</li> </li>
<li>
<a href="/plan" title="Neocities Supporters">Supporters</a>
</li>
</ul> </ul>
<ul class="status-Nav"> <ul class="status-Nav">

View file

@ -21,14 +21,14 @@
</div> </div>
</div> </div>
<div class="col col-40" style="margin-left: 10px"> <div class="col col-50" style="margin-left: 10px">
<h2 class="eps">My Website</h2> <h2 class="eps">My Website</h2>
<p style="font-size:19px; margin-top: -5px; margin-bottom: 8px;"><a href="http://<%= current_site.username %>.neocities.org" target="_blank">http://<%= current_site.username %>.neocities.org</a></p> <p style="font-size:19px; margin-top: -5px; margin-bottom: 8px;"><a href="http://<%= current_site.username %>.neocities.org" target="_blank">http://<%= current_site.username %>.neocities.org</a></p>
<ul> <ul>
<% if current_site.updated_at %> <% if current_site.updated_at %>
<li>Last updated <%= current_site.updated_at %></li> <li>Last updated <%= current_site.updated_at %></li>
<% end %> <% end %>
<li>Using <strong><%= ((current_site.total_space.to_f / Site::MAX_SPACE) * 100).round(1) %>% of your <%= (Site::MAX_SPACE.to_f / 2**20).to_i %> MB</strong> of free space</li> <li>Using <strong><%= current_site.space_percentage_used %>% of your <%= current_site.maximum_space_in_megabytes %> MB</strong> of free space. <% if !current_site.supporter? %>Need more space? <a href="/plan">Become a Supporter</a><% end %></li>
<li><strong><%= current_site.hits.to_s.reverse.gsub(/...(?=.)/,'\&,').reverse %></strong> visitors</li> <li><strong><%= current_site.hits.to_s.reverse.gsub(/...(?=.)/,'\&,').reverse %></strong> visitors</li>
</ul> </ul>
</div> </div>

View file

@ -68,6 +68,9 @@
<li> <li>
<a href="/about" title="About Neocities">About</a> <a href="/about" title="About Neocities">About</a>
</li> </li>
<li>
<a href="/plan" title="Neocities Supporters">Supporters</a>
</li>
</ul> </ul>
<ul class="status-Nav"> <ul class="status-Nav">

View file

@ -20,7 +20,7 @@
<link href="/css/bootstrap.min.css" rel="stylesheet"> <link href="/css/bootstrap.min.css" rel="stylesheet">
<link href="/css/bootstrap-responsive.min.css" rel="stylesheet"> <link href="/css/bootstrap-responsive.min.css" rel="stylesheet">
<link href="assets/css/neo.css" rel="stylesheet" type="text/css" media="all"/> <link href="/assets/css/neo.css" rel="stylesheet" type="text/css" media="all"/>
<link href="/css/font-awesome.min.css" rel="stylesheet" type="text/css" media="all"/> <link href="/css/font-awesome.min.css" rel="stylesheet" type="text/css" media="all"/>
<link href="/css/styles.css" rel="stylesheet" type="text/css" media="all"> <link href="/css/styles.css" rel="stylesheet" type="text/css" media="all">

View file

@ -120,7 +120,7 @@
<p class="tiny"><b>Last thing!</b> Enter these two words correctly (with spaces) so we know you're not a robot (don't worry robots, we still love you).</p> <p class="tiny"><b>Last thing!</b> Enter these two words correctly (with spaces) so we know you're not a robot (don't worry robots, we still love you).</p>
<div class="recaptcha"> <div class="recaptcha">
<%== recaptcha_tag :challenge, ssl: true %> <%== recaptcha_tag :challenge, ssl: request.ssl? %>
</div> </div>
<hr> <hr>

34
views/plan/end.erb Normal file
View file

@ -0,0 +1,34 @@
<div class="header-Outro">
<div class="row content single-Col">
<div class="col col-40">
<h2 class="beta">End Plan</h2>
</div>
<div class="txt-Right">
<img src="/assets/img/support-us.png" width="55">
</div>
</div>
</div>
<div class="content single-Col misc-page">
<h3>Ending the Supporter Plan</h3>
<p>
You currently have the <strong><%= current_site.plan_name %> (<%= current_site.maximum_space_in_megabytes %>MB)</strong> - <%= @plan_name %>.
</p>
<p>
If you need to end the plan, you can do that here. We'll be sorry to see you go. If there's a reason you're ending that we can help with, please <a href="/contact">contact us</a> and we'll see if we can help you with your issue. Regardless, we'll let you keep your site and the extra space. We hope you'll decide to become a supporter again in the future!
</p>
<% if @error %>
<div class="alert alert-block alert-error">
<p><%= @error %></p>
</div>
<% end %>
<form method="POST" action="/plan/end">
<input name="csrf_token" type="hidden" value="<%= csrf_token %>">
<div class="recaptcha">
<%== recaptcha_tag :challenge, ssl: request.ssl? %>
</div>
<input class="btn" type="submit" value="End plan">
</form>
</div>

145
views/plan/index.erb Normal file
View file

@ -0,0 +1,145 @@
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
<script type="text/javascript">
Stripe.setPublishableKey('<%= $config['stripe_publishable_key'] %>');
</script>
<div class="header-Outro">
<div class="row content single-Col">
<div class="col col-50">
<h2 class="beta">
<% if current_site %>
Your Plan
<% else %>
Supporter Plan
<% end %>
</h2>
</div>
<div class="txt-Right">
<img src="/assets/img/support-us.png" width="55">
</div>
</div>
</div>
<div class="content single-Col misc-page">
<h3>Plan Info</h3>
<% if current_site && current_site.supporter? && !current_site.plan_ended %>
<p>
You currently have the <strong><%= current_site.plan_name %> (<%= current_site.maximum_space_in_megabytes %>MB)</strong>.
</p>
<p>
Your support means a lot to us. On behalf of Penelope the cat and everyone at Neocities, thank you. If there's anything we can do to make your experience even better, please don't hesitate to <a href="/contact">contact us</a>.
</p>
<a href="/plan/manage">Manage plan</a>
<% else %>
<% if current_site %>
<p>You currently have the <strong>Free Plan (<%= current_site.maximum_space_in_megabytes %>MB)</strong>. You should upgrade to the <strong>Neocities Supporter Plan!</strong></p>
<h3>Why upgrade?</h3>
<% else %>
<p>The Neocities Supporter Plan is a way to help sustain the site. When you join the Supporter Plan, you are directly helping our quest to bring back the creative, independent web, and to continue to improve Neocities for everyone.
</p>
<h3>Why get the Supporter Plan?</h3>
<% end %>
<ul>
<li>
<strong>You get more space!</strong> Right now supporter plans get up to <strong><%= Site::SUPPORTER_MAXIMUM_IN_MEGABYTES %>MB</strong> (more coming soon)</strong>.
</li>
<li>
<strong>It helps your site.</strong> Funding helps us make your site faster globally, and provide more features.
</li>
<li>
<strong>It helps us build.</strong> It allows us to work on Neocities full-time, without worrying about bills.
</li>
<li>
<strong>It helps the web.</strong> The web needs more independent, creative sites. Neocities is leading the way.
</li>
<li>
<strong>It's Open Source.</strong> Neocities is <a href="https://github.com/neocities" target="_blank">completely open source</a>, and we share code with the community.
</li>
<li><strong>No lock-in.</strong> Redirecting your site is easy, free site downloads, and support for custom domains.
<li>
<strong>It's safe.</strong> We use <a href="https://stripe.com" target="_blank">Stripe</a> for payments, and never store your credit card information directly.
</li>
<li>
<strong>It's affordable.</strong> As low as <strong>$1/month</strong> (billed once every year).
</li>
<li>
<strong>You can cancel anytime.</strong> And if you do, you can keep the extra space!
</li>
</ul>
<% if !current_site %>
<h2 class="txt-Center">Become a Supporter</h2>
<p>If you would like to become a Neocities Supporter, <a href="/signin">Sign In</a> and click the <strong>Settings</strong> link. If you haven't created a site yet, <a href="/new">create your web site now</a>! We would love if you became a Supporter, but base sites on Neocities will always be free.</p>
<% else %>
<h2 class="txt-Center">Upgrade Now</h2>
<div id="plan_container" style="margin-top:20px" class="txt-Center">
<form method="post" action="/plan/create" id="planform">
<input type="hidden" name="csrf_token" value="<%= csrf_token %>">
<input id="stripe_token" name="stripe_token" type="hidden" value="<%= params[:stripe_token] %>">
<div id="plan_error" class="alert alert-block alert-error" style="display:none"></div>
<p class="tiny">
Plan type:
<select name="selected_plan" style="width: 200px">
<option value="plan_one">$12/year ($1/month)</option>
<option value="plan_two" <%= params[:selected_plan].nil? ? 'selected' : '' %>>$24/year ($2/month)</option>
<option value="plan_three">$36/year ($3/month)</option>
<option value="plan_four">$48/year ($4/month)</option>
<option value="plan_five">$60/year ($5/month)</option>
</select>
</p>
<p class="tiny">
Card Number: <input type="text" size="20" data-stripe="number">
CVC: <input type="text" size="4" maxlength="4" data-stripe="cvc" style="width: 60px">
</p>
<p class="tiny">
Expiration:
<input type="text" size="2" data-stripe="exp-month" placeholder="MM" maxlength="2" style="width: 50px">
<input type="text" size="4" data-stripe="exp-year" placeholder="YYYY" maxlength="4" style="width: 60px">
</p>
<input class="btn-Action" type="submit" value="Upgrade!">
</form>
</div>
<% end %>
<% end %>
<h2 class="txt-Center">One-Time Donations</h2>
<p>
Prefer to not use a credit card? Neocities has many other ways to become a supporter! <a href="/donate">Click here</a> to learn about other ways to contribute. We accept <a href="/donate">PayPal, Bitcoin and Gittip</a>, and will be happy to upgrade your account for you if you contribute and <a href="/contact">contact us with your site username</a>.
</p>
</div>
<script>
window.onload = function() {
$('#planform').find(':submit').prop('disabled', false)
$('#planform').submit(function(event) {
if($('#stripe_token').val() != '')
return true
var planError = $('#plan_error')
planError.css('display', 'none')
var planform = $(this)
planform.find(':submit').prop('disabled', true)
Stripe.card.createToken(planform, function(status, response) {
if(response.error) {
planError.text(response.error.message)
planError.css('display', 'block')
planform.find(':submit').prop('disabled', false)
return false
} else {
$('#stripe_token').val(response.id)
planform.submit()
}
})
return false
})
}
</script>

19
views/plan/manage.erb Normal file
View file

@ -0,0 +1,19 @@
<div class="header-Outro">
<div class="row content single-Col">
<div class="col col-40">
<h2 class="beta">Manage Plan</h2>
</div>
<div class="txt-Right">
<img src="/assets/img/support-us.png" width="55">
</div>
</div>
</div>
<div class="content single-Col misc-page">
<h3>Change your Plan</h3>
<p>
You currently have the <strong><%= current_site.plan_name %> (<%= current_site.maximum_space_in_megabytes %>MB)</strong> - <%= @plan_name %>.
</p>
<a href="/plan/end">End plan</a>
</div>

View file

@ -9,6 +9,17 @@
- current_site.errors.each do |error| - current_site.errors.each do |error|
p = error.last.first p = error.last.first
.row
.col.col-100.txt-Center
.content
h2.zeta Neocities Plan
- if current_site.supporter? && !current_site.plan_ended
p.tiny You currently have the <strong>Supporter Plan (#{current_site.maximum_space_in_megabytes}MB)</strong>. Thank you! We love you.
a.btn-Action href="/plan" Manage Plan
- else
p.tiny You currently have the <strong>Free Plan (#{current_site.maximum_space_in_megabytes}MB)</strong>.<br>Need more space, or want to help support Neocities? Upgrade!
a.btn-Action href="/plan" Upgrade Plan
.row .row
.col.col-33 .col.col-33
.content .content

View file

@ -3,18 +3,18 @@
h1 Welcome back! h1 Welcome back!
form method="POST" action="/signin" class="content" form method="POST" action="/signin" class="content"
input name="csrf_token" type="hidden" value="#{csrf_token}" input name="csrf_token" type="hidden" value="#{csrf_token}"
fieldset.col-60 style="margin:0 auto" fieldset.col-60 style="margin:0 auto"
input name="username" type="text" placeholder="Your username" class="input-Area" autocapitalize="off" autocorrect="off" value="#{flash[:username]}" input name="username" type="text" placeholder="Your username" class="input-Area" autocapitalize="off" autocorrect="off" value="#{flash[:username]}"
br br
input name="password" type="password" class="input-Area" placeholder="Your password" input name="password" type="password" class="input-Area" placeholder="Your password"
br br
button class="btn-Action" Sign in button class="btn-Action" Sign in
hr hr
p p
a href="/new" title="Sign me up!" I don't have an account yet. a href="/new" title="Sign me up!" I don't have an account yet.
p p
a href="/password_reset" title="What did I huh?" I forgot my password. a href="/password_reset" title="What did I huh?" I forgot my password.

View file

@ -35,5 +35,5 @@
h4 If the file already exists, <u><b>it will be overwritten without warning</b></u>. h4 If the file already exists, <u><b>it will be overwritten without warning</b></u>.
h4 It has to be <u>legal to share this content in the United States</u>. h4 It has to be <u>legal to share this content in the United States</u>.
h4 It must fit into your home page space (#{(Site::MAX_SPACE.to_f / 2**20).to_i}MB). h4 It must fit into your home page space (#{current_site.available_space_in_megabytes}MB remaining).
h4 The file uploader will automatically scrub any characters not matching: a-z A-Z 0-9 _ - . h4 The file uploader will automatically scrub any characters not matching: a-z A-Z 0-9 _ - .

View file

@ -73,6 +73,6 @@ javascript:
p If the file already exists, <u><b>it will be overwritten without warning</b></u>. p If the file already exists, <u><b>it will be overwritten without warning</b></u>.
p It has to be <u>legal to share this content in the United States</u>. p It has to be <u>legal to share this content in the United States</u>.
p It must fit into your home page space (#{(Site::MAX_SPACE.to_f / 2**20).to_i}MB). p It must fit into your home page space (#{current_site.available_space_in_megabytes}MB remaining).
p The file uploader will automatically scrub any characters not matching: a-z A-Z 0-9 _ - . p The file uploader will automatically scrub any characters not matching: a-z A-Z 0-9 _ - .