Make Snipt free for all.

master
Nick Sergeant 2016-03-21 14:48:44 -04:00
parent b105b679cb
commit 4642ba07b6
31 changed files with 21 additions and 840 deletions

View File

@ -137,13 +137,4 @@ class UserProfile(models.Model):
self.user.date_joined.replace(tzinfo=None)
return delta.days
@property
def has_pro(self):
if (self.is_pro or
self.has_teams or
self.is_a_team):
return True
else:
return False
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])

View File

@ -26,9 +26,6 @@
<li ng-class="{active: route.current.scope.section == 'Editor'}">
<a href="/account/editor/">Editor</a>
</li>
<li ng-show="user.has_pro && user.stripe_id && user.stripe_id != 'COMP'" ng-class="{active: route.current.scope.section == 'Billing'}">
<a href="/account/billing/">Billing</a>
</li>
</ul>
</aside>
<section class="content" ng-class="{'with-message': message}">

View File

@ -4,8 +4,4 @@ from django.conf.urls import *
urlpatterns = \
patterns('',
url(r'^stats/$', views.stats, name='account-stats'),
url(r'^cancel-subscription/$', views.cancel_subscription,
name='cancel-subscription'),
url(r'^stripe-account-details/$', views.stripe_account_details,
name='stripe-account-details'),
url(r'^', views.account, name='account-detail'))

View File

@ -1,10 +1,5 @@
import os
import stripe
from annoying.decorators import ajax_request, render_to
from django.conf import settings
from annoying.decorators import render_to
from django.contrib.auth.decorators import login_required
from django.core.mail import send_mail
from snipts.models import Snipt
@ -14,64 +9,6 @@ def account(request):
return {}
@login_required
@ajax_request
def cancel_subscription(request):
if request.user.profile.stripe_id is None:
return {}
else:
stripe.api_key = os.environ.get('STRIPE_SECRET_KEY',
settings.STRIPE_SECRET_KEY)
customer = stripe.Customer.retrieve(request.user.profile.stripe_id)
customer.delete()
profile = request.user.profile
profile.is_pro = False
profile.stripe_id = None
profile.save()
send_mail('[Snipt] User cancelled Pro: {}'.format(request.user.username),
"""
User: https://snipt.net/{}
Email: {}
""".format(request.user.username, request.user.email),
'support@snipt.net',
['nick@snipt.net'],
fail_silently=False)
return {'deleted': True}
@login_required
@ajax_request
def stripe_account_details(request):
if request.user.profile.stripe_id is None:
return {}
else:
stripe.api_key = os.environ.get('STRIPE_SECRET_KEY',
settings.STRIPE_SECRET_KEY)
customer = stripe.Customer.retrieve(request.user.profile.stripe_id)
data = {
'last4': customer.active_card.last4,
'created': customer.created,
'email': customer.email,
}
if customer.subscription:
data['amount'] = customer.subscription.plan.amount
data['interval'] = customer.subscription.plan.interval
data['name'] = customer.subscription.plan.name
data['status'] = customer.subscription.status
data['nextBill'] = customer.subscription.current_period_end
else:
data['status'] = 'inactive'
return data
@login_required
@render_to('stats.html')
def stats(request):

View File

@ -34,7 +34,6 @@ class BlogMiddleware:
get_object_or_404(User, username__iexact=blog_user)
if request.blog_user is None:
# TODO: This needs to check profile.has_pro() instead.
pro_users = User.objects.filter(userprofile__is_pro=True)
for pro_user in pro_users:

View File

@ -77,12 +77,10 @@
<script type="text/javascript" src="//pagead2.googlesyndication.com/pagead/show_ads.js"></script>
</div>
{% endif %}
{% if not blog_user.profile.has_pro %}
<nav class="footer {% if sidebar %}with-sidebar{% endif %}">
<ul class="powered">
<li class="snipt"><a href="https://snipt.net/blogging/">Blog powered by Snipt</a></li>
</ul>
</nav>
{% endif %}
<nav class="footer {% if sidebar %}with-sidebar{% endif %}">
<ul class="powered">
<li class="snipt"><a href="https://snipt.net/blogging/">Blog powered by Snipt</a></li>
</ul>
</nav>
</aside>
{% endblock %}

View File

@ -311,11 +311,6 @@ header.main {
min-width: 100px;
padding-bottom: 7px;
text-transform: uppercase;
span.is-pro {
color: #3299B7;
font-style: italic;
}
}
}
&:hover {

View File

@ -15,10 +15,6 @@ if (typeof angular !== 'undefined') {
templateUrl: '/static/js/src/modules/partials/profile.html',
controller: controllers.ProfileController
});
$routeProvider.when('/account/billing/', {
templateUrl: '/static/js/src/modules/partials/billing.html',
controller: controllers.BillingController
});
$routeProvider.when('/account/blogging/', {
templateUrl: '/static/js/src/modules/partials/blogging.html',
controller: controllers.BloggingController
@ -63,15 +59,6 @@ if (typeof angular !== 'undefined') {
return promise;
},
getStripeAccount: function() {
var promise = $http({
method: 'GET',
url: '/account/stripe-account-details/'
});
return promise;
},
saveAccount: function(user, fields) {
var promise = $http({
@ -173,12 +160,6 @@ if (typeof angular !== 'undefined') {
AccountStorage.getAccount().then(function(response) {
$scope.user = response.data;
if ($scope.user.has_pro && $scope.user.stripe_id && $scope.user.stripe_id !== 'COMP') {
AccountStorage.getStripeAccount().then(function(response) {
$scope.user.stripeAccount = response.data;
});
}
});
$scope.saveFields = function(fields) {

View File

@ -23,14 +23,6 @@ jQuery(function($) {
var pre = $pres.eq(i);
pre.width(pre.parents('section.code').width() - 30);
});
$('form#cancel-team-subscription').submit(function() {
if (confirm('Are you sure you want to cancel your subscription?\n\nYou will no longer be able to create new Snipts under this team. This action is effective immediately and we unfortunately cannot issue any refunds.')) {
return true;
} else {
return false;
}
});
});
// Angular app init.
@ -55,12 +47,6 @@ jQuery(function($) {
if (root.location.pathname === '/account/stats/') {
root.ll('tagEvent', 'Viewed stats page');
}
if (root.location.pathname === '/pro/') {
root.ll('tagEvent', 'Viewed Pro page');
}
if (root.location.pathname === '/pro/signup/') {
root.ll('tagEvent', 'Viewed Pro signup page');
}
}
app.controller('AppController', function($scope) {

View File

@ -1,54 +0,0 @@
<div ng-show="cancelled">
<div ng-show="cancelling">
<p class="alert alert-info group">
Sit tight, we're cancelling your subscription...
</p>
</div>
<div ng-show="!cancelling">
<p class="alert alert-info group">
You have successfully cancelled your subscription.<br />You will no longer be billed.
</p>
<p style="padding: 15px; padding-top: 0; margin-top: 0; line-height: 21px;">
We're very sorry to see you go. Mind dropping us a quick email to <a href="mailto:support@snipt.net">support@snipt.net</a>
to let us know why you cancelled? We'd really appreciate it.
</p>
</div>
</div>
<div ng-show="!cancelled">
<div ng-show="!user.has_pro">
<p class="alert alert-info">
You're not a Pro yet, so we have nothing to show you here.<br />
<br /><a class="btn btn-success" href="/pro/">Signup for Pro &raquo;</a>
</p>
</div>
<div ng-show="user.has_pro">
<div class="def" data-title="Plan" ng-show="user.stripeAccount.status != 'inactive'">
{[{ user.stripeAccount.name || 'Loading...' }]}
</div>
<div class="def" data-title="Plan" ng-show="user.stripeAccount.status == 'inactive'">
One-time payment
</div>
<div class="def" data-title="Price" ng-show="user.stripeAccount.status != 'inactive'">
<span ng-show="user.stripeAccount">${[{ user.stripeAccount.amount / 100 }]}.00 USD / {[{ user.stripeAccount.interval }]}</span>
<span ng-show="!user.stripeAccount">Loading...</span>
</div>
<div class="def" data-title="Card" ng-show="user.stripeAccount.status != 'inactive'">
<span ng-show="user.stripeAccount">xxxx-xxxx-xxxx-{[{ user.stripeAccount.last4 || 'Loading...' }]}</span>
<span ng-show="!user.stripeAccount">Loading...</span>
</div>
<div class="def" data-title="Status" ng-show="user.stripeAccount.status != 'inactive'">
{[{ user.stripeAccount.status || 'Loading...' }]}
</div>
<div class="def" data-title="Pro since">
<span ng-show="user.stripeAccount">{[{ user.stripeAccount.created * 1000 |date:'fullDate' }]}</span>
<span ng-show="!user.stripeAccount">Loading...</span>
</div>
<div class="def" data-title="Next bill date" ng-show="user.stripeAccount.status != 'inactive'">
<span ng-show="user.stripeAccount">{[{ user.stripeAccount.nextBill * 1000 |date:'fullDate' }]}</span>
<span ng-show="!user.stripeAccount">Loading...</span>
</div>
<p class="alert alert-info group" style="padding-right: 8px;" ng-show="user.stripeAccount.status != 'inactive'">
<a ng-click="cancelSubscription()" class="btn btn-danger pull-right">Cancel subscription</a>
</p>
</div>
</div>

View File

@ -17,9 +17,8 @@
<div class="control-group" ng-class="{error: errors.blog_domain}">
<label class="control-label" for="id_blog_domain">Blog domain:</label>
<div class="controls">
<input ng-disabled="!user.has_pro" id="id_blog_domain" type="text" ng-model="user.blog_domain" maxlength="250">
<span ng-show="user.has_pro" class="help-block">Like 'snipt.nicksergeant.com' or 'nicksergeant.com' (without quotes). Set your CNAME to `snipt.net` or A-record to `96.126.110.160`. You can use multiple domains here: separate each domain with a space. The first domain will be your primary domain. All other domains will redirect to your primary domain.</span>
<span ng-show="!user.has_pro" class="help-block"><a href="https://snipt.net/pro/">Go Pro</a> to enable a custom domain for your <a href="https://snipt.net/blogging/">Snipt blog</a>.</span>
<input id="id_blog_domain" type="text" ng-model="user.blog_domain" maxlength="250">
<span class="help-block">Like 'snipt.nicksergeant.com' or 'nicksergeant.com' (without quotes). Set your CNAME to `snipt.net` or A-record to `96.126.110.160`. You can use multiple domains here: separate each domain with a space. The first domain will be your primary domain. All other domains will redirect to your primary domain.</span>
</div>
</div>
</div>

View File

@ -96,62 +96,6 @@
window.ui_halted = false;
});
if (this.$body.hasClass('pro-signup')) {
var $form = $('form#pro-signup');
var $submit = $('button[type="submit"]', $form);
var $cardNumber = $('input#number');
var $expMonth = $('select#exp-month');
var $expYear = $('select#exp-year');
var $cvc = $('input#cvc');
$form.submit(function() {
$submit.attr('disabled', 'disabled');
var errors = false;
if (!Stripe.validateCardNumber($cardNumber.val())) {
$cardNumber.parents('div.control-group').addClass('error');
errors = true;
} else {
$cardNumber.parents('div.control-group').removeClass('error');
}
if (!Stripe.validateExpiry($expMonth.val(), $expYear.val())) {
$expMonth.parents('div.control-group').addClass('error');
errors = true;
} else {
$expMonth.parents('div.control-group').removeClass('error');
}
if (!Stripe.validateCVC($cvc.val())) {
$cvc.parents('div.control-group').addClass('error');
errors = true;
} else {
$cvc.parents('div.control-group').removeClass('error');
}
if (!errors) {
$('.payment-errors').hide();
$('.payment-loading').show();
Stripe.createToken({
number: $cardNumber.val(),
cvc: $cvc.val(),
exp_month: $expMonth.val(),
exp_year: $expYear.val()
}, that.stripeResponseHandler);
} else {
$submit.removeAttr('disabled');
}
return false;
});
}
if (this.$body.hasClass('login')) {
$('input#id_username').focus();
}
@ -248,30 +192,6 @@
$('div.infield label', this.$body).inFieldLabels({
fadeDuration: 200
});
},
stripeResponseHandler: function(status, response) {
var $form = $('form#pro-signup');
if (response.error) {
$('button[type="submit"]', $form).removeAttr('disabled');
$('.payment-loading').hide();
$('.payment-errors').text(response.error.message).show();
} else {
var token = response.id;
// Kill all of the form details so none of it touches our server.
// Note, this is unnecessary, because the inputs themselves do not
// have a name attr, meaning they'll never get sent to begin with.
$('input#name').val('');
$('input#number').val('');
$('select#exp-month').val('');
$('select#exp-year').val('');
$('input#cvc').val('');
$form.append("<input type='hidden' name='token' value='" + token + "'/>");
$form.get(0).submit();
}
}
});

View File

@ -132,13 +132,10 @@
var $checkbox = $(this);
var $label = $checkbox.parent();
$('div.alert-not-pro').hide();
if ($checkbox.is(':checked')) {
$label.removeClass('is-private').addClass('is-public');
if (!window.user_has_pro) $('div.alert-not-pro').hide();
} else {
$label.addClass('is-private').removeClass('is-public');
if (!window.user_has_pro) $('div.alert-not-pro').show();
}
return false;
}).change();
@ -664,8 +661,6 @@
};
}
var has_pro = $user.siblings('span.pro').length ? true : false;
var data = {
code: $('textarea.raw', $el).text(),
description: $('textarea.description', $el).text(),
@ -691,9 +686,7 @@
user: {
absolute_url: $user.attr('href'),
username: $user.text(),
profile: {
has_pro: has_pro
}
profile: {}
}
};
@ -729,9 +722,7 @@
public: true,
user: {
username: '',
profile: {
has_pro: window.user_has_pro
}
profile: {}
}
};

View File

@ -153,13 +153,6 @@ class SniptValidation(Validation):
if (len(bundle.data['title']) > 255):
errors['title-length'] = ("Title must be 255 characters or less.")
if request.user.profile.has_pro is False:
if ('public' not in bundle.data or bundle.data['public'] is False):
errors['not-pro'] = ("You'll need to go Pro "
"(https://snipt.net/pro/) "
"in order to create private "
"snipts.")
return errors
@ -310,7 +303,6 @@ class PrivateUserProfileResource(ModelResource):
bundle.data['username'] = bundle.obj.user.username
bundle.data['user_id'] = bundle.obj.user.id
bundle.data['api_key'] = bundle.obj.user.api_key.key
bundle.data['has_pro'] = bundle.obj.user.profile.has_pro
return bundle
@ -335,7 +327,6 @@ class PrivateUserResource(ModelResource):
bundle.data['email_md5'] = hashlib \
.md5(bundle.obj.email.lower().encode('utf-8')) \
.hexdigest()
bundle.data['has_pro'] = bundle.obj.profile.has_pro
bundle.data['stats'] = {
'public_snipts': Snipt.objects.filter(user=bundle.obj.id,
public=True).count(),

View File

@ -20,9 +20,6 @@
<div class="group">
<div class="container">
<header>
<div class="alert alert-error alert-not-pro" style="display: none; margin: 10px 10px -10px 10px;">
You'll need to go Pro (<a href="https://snipt.net/pro/">https://snipt.net/pro/</a>) in order to create private snipts.
</div>
<h2>&nbsp;</h2>
<h1>
<input maxlength="255" type="text" id="snipt_title" placeholder="Title" value="<% if (snipt.title != 'Untitled') { %><%= snipt.title %><% } %>" />

View File

@ -74,8 +74,6 @@
</a>
<% if (window.teams.indexOf(snipt.user.username) !== -1) { %>
<span class="pro"><a href="/for-teams/">Team</a></span>
<% } else if (window.user_has_pro) { %>
<span class="pro"><a href="/pro/">Pro</a></span>
<% } %>
</li>
<% if (!snipt.new_from_js) { %>

View File

@ -183,8 +183,6 @@
<a href="{{ snipt.user.get_absolute_url }}">{{ snipt.user.username }}</a>
{% if snipt.user.profile.is_a_team %}
<span class="pro"><a href="/for-teams/">Team</a></span>
{% elif snipt.user.profile.has_pro %}
<span class="pro"><a href="/pro/">Pro</a></span>
{% endif %}
{% if snipt.user.profile.gittip_username %}
<span class="gittip"><a href="https://www.gittip.com/{{ snipt.user.profile.gittip_username }}/">Tip</a></span>

View File

@ -10,7 +10,7 @@
{% endblock %}
{% block content %}
<form class="form-horizontal static-box" id="pro-signup" method="post" action="/pro/complete/">
<form class="form-horizontal static-box" id="pro-signup" method="post">
<fieldset>
<div class="info">
Team created successfully.
@ -22,9 +22,6 @@
<li>
<a href="/{{ team.user.username }}/members">Add some members</a>
</li>
<li>
<a href="/{{ team.user.username }}/billing">View billing info</a>
</li>
</ul>
</fieldset>
</form>

View File

@ -4,17 +4,8 @@
{% block body-class %}{{ block.super }} static signup pro pro-signup{% endblock %}
{% block extra-scripts %}
<script type="text/javascript" src="https://js.stripe.com/v1/"></script>
{% endblock %}
{% block js %}
{{ block.super }}
{% if debug %}
Stripe.setPublishableKey('pk_test_cgknmaWRMQeJt2adEdvH3T9l');
{% else %}
Stripe.setPublishableKey('pk_live_gUO2nCl7dhx6j0posz6gnbhA');
{% endif %}
{% endblock %}
{% block breadcrumb %}
@ -36,8 +27,6 @@
<li>Members can create and edit public and private snippets on a team.</li>
<li>Detailed log of changes on a snippet, including user and code diffs.</li>
<li>Maintain a public team profile by posting public snippets.</li>
<li>All team members are automatically granted personal <a href="/pro/"><span class="pro">Pro</span></a> accounts.</li>
<li>Plans from $24/month, each with a 14-day free trial.</li>
</ul>
</div>
{% if not request.user.is_authenticated %}
@ -48,8 +37,6 @@
</div>
{% else %}
<div class="payment-form">
<div class="payment-loading -teams"><span>Please wait&hellip;</span></div>
<div class="payment-errors alert alert-error"></div>
<div class="control-group">
<label class="control-label" for="team-name">Team name:</label>
<div class="controls">
@ -68,84 +55,10 @@
</p>
</div>
</div>
<div class="control-group">
<label class="control-label" for="plan">Payment plan:</label>
<div class="controls">
<select name="plan" type="text" class="input-xlarge" id="plan">
<option value="snipt-teams-25-monthly">25 users - $24/month</option>
<option value="snipt-teams-100-monthly">100 users - $74/month</option>
<option value="snipt-teams-250-monthly">250 users - $149/month</option>
<option value="snipt-teams-unlimited-monthly">Unlimited users - $249/month</option>
<option value="snipt-teams-25-yearly">25 users - $288/year</option>
<option value="snipt-teams-100-yearly">100 users - $888/year</option>
<option value="snipt-teams-250-yearly">250 users - $1,788/year</option>
<option value="snipt-teams-unlimited-yearly">Unlimited users - $2,988/year</option>
</select>
<p class="sub" style="margin-top: 3px; color: #999999;">
Free 14-day trial (your card won't be charged until then, and you can cancel at any time).
</p>
</div>
</div>
<div class="control-group">
<label class="control-label" for="number">Card number:</label>
<div class="controls cards">
<input type="text" class="input-xlarge" id="number" />
<img src="{{ STATIC_URL }}img/card-visa.png" alt="Visa" />
<img src="{{ STATIC_URL }}img/card-mastercard.png" alt="MasterCard" />
<img src="{{ STATIC_URL }}img/card-discover.png" alt="Discover" />
<img src="{{ STATIC_URL }}img/card-american-express.png" alt="American Express" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="exp-month">Expiration date:</label>
<div class="controls">
<select id="exp-month" class="span2 exp-month">
<option value="">----</option>
<option value="01">01 - January</option>
<option value="02">02 - February</option>
<option value="03">03 - March</option>
<option value="04">04 - April</option>
<option value="05">05 - May</option>
<option value="06">06 - June</option>
<option value="07">07 - July</option>
<option value="08">08 - August</option>
<option value="09">09 - September</option>
<option value="10">10 - October</option>
<option value="11">11 - November</option>
<option value="12">12 - December</option>
</select>
<select id="exp-year" class="span2">
<option value="">----</option>
<option value="2015">2015</option>
<option value="2016">2016</option>
<option value="2017">2017</option>
<option value="2018">2018</option>
<option value="2019">2019</option>
<option value="2020">2020</option>
<option value="2021">2021</option>
<option value="2022">2022</option>
<option value="2023">2023</option>
<option value="2024">2024</option>
<option value="2025">2025</option>
</select>
</div>
</div>
<div class="control-group">
<label class="control-label" for="cvc">Security code (CVC):</label>
<div class="controls">
<input type="text" class="input-min span1" id="cvc">
</div>
</div>
</div>
<div class="form-actions">
{% csrf_token %}
<button type="submit" class="btn btn-success">Create team &raquo;</button>
<div class="security">
<a href="https://stripe.com/help/security">Secure</a> by default. Every Snipt page is secure.
</div>
<div class="stripe" style="margin-left: 125px;">
Your credit card is stored securely with <a href="https://stripe.com">Stripe</a> and we use <a href="https://stripe.com/docs/stripe.js">Stripe.js</a> for maximum security.
</div>
</div>
{% endif %}
</fieldset>

View File

@ -1,66 +0,0 @@
{% extends "base.html" %}
{% load team_tags %}
{% block page-title %}Team Billing{% endblock %}
{% block body-class %}account {{ block.super }}{% endblock %}
{% block breadcrumb %}
<li><a href="/{{ team.user.username }}/">{{ team.user.username }}</a></li>
<span class="prompt">/</span> <li><a href="/{{ team.user.username }}/billing/">Billing</a></li>
{% endblock %}
{% block content %}
<section class="snipts" id="snipts"></section>
<section class="profile group">
<aside>
<ul class="nav nav-list ng-cloak" ng-cloak>
<li class="nav-header">Team: {{ team.name }}</li>
<li>
<a href="/{{ team.slug }}/">Profile</a>
</li>
<li>
<a href="/{{ team.slug }}/members/">Members</a>
</li>
<li class="active">
<a href="/{{ team.slug }}/billing/">Billing</a>
</li>
</ul>
</aside>
<section class="content">
<div class="def" data-title="Plan">
{{ name }}
</div>
{% if status != 'inactive' and status != 'promo' %}
<div class="def" data-title="Price">
${{ amount|currency_convert }}.00 USD / {{ interval }}
</div>
<div class="def" data-title="Card">
xxxx-xxxx-xxxx-{{ last4 }}
</div>
<div class="def" data-title="Status">
{{ status }}
</div>
<div class="def" data-title="Team since">
{{ team.created|date:'M d, Y' }}
</div>
<div class="def" data-title="Next bill date" ng-show="user.stripeAccount.status != 'inactive'">
{{ nextBill|to_date|date:'M d, Y' }}
</div>
<form class="alert alert-info group" style="margin: 15px; padding-right: 8px;" id="cancel-team-subscription" action="/{{ team.slug }}/billing/cancel/" method="post">
{% csrf_token %}
<button type="submit" class="btn btn-danger pull-right">
Cancel subscription
</button>
</form>
{% endif %}
</section>
</section>
{% endblock %}
{% block analytics %}
{% if not debug %}
window.ll('tagScreen', 'Team billing view');
{% endif %}
{% endblock %}

View File

@ -24,11 +24,6 @@
<li class="active">
<a href="/{{ team.slug }}/members/">Members</a>
</li>
{% if team.owner == request.user %}
<li>
<a href="/{{ team.slug }}/billing/">Billing</a>
</li>
{% endif %}
</ul>
</aside>
<section class="content">

View File

@ -14,10 +14,4 @@ urlpatterns = \
name='add-team-member'),
url(r'^(?P<username>[^/]+)/members/$',
views.team_members,
name='team-members'),
url(r'^(?P<username>[^/]+)/billing/$',
views.team_billing,
name='team-billing'),
url(r'^(?P<username>[^/]+)/billing/cancel/$',
views.cancel_team_subscription,
name='team-cancel-subscription'))
name='team-members'))

View File

@ -1,5 +1,4 @@
import os
import stripe
import uuid
from annoying.decorators import render_to
@ -26,27 +25,9 @@ def for_teams(request):
def for_teams_complete(request):
if request.method == 'POST' and request.user.is_authenticated():
token = request.POST['token']
stripe.api_key = os.environ.get('STRIPE_SECRET_KEY',
settings.STRIPE_SECRET_KEY)
plan = request.POST['plan']
try:
customer = stripe.Customer.create(card=token,
plan=plan,
email=request.user.email)
except stripe.CardError as e:
error_message = e.json_body['error']['message']
return HttpResponseRedirect('/for-teams/?declined=%s' %
error_message or
'Your card was declined.')
team = Team(name=request.POST['team-name'],
email=request.POST['email'],
plan=plan,
owner=request.user)
team.stripe_id = customer.id
team.save()
user = User.objects.create_user(team.slug,
@ -73,46 +54,6 @@ def for_teams_complete(request):
return HttpResponseBadRequest()
@login_required
@render_to('teams/team-billing.html')
def team_billing(request, username):
team = get_object_or_404(Team, slug=username, disabled=False)
if team.owner != request.user:
raise Http404
if team.stripe_id == 'COMP':
return {
'name': 'Promotional trial',
'team': team,
'status': 'promo'
}
stripe.api_key = os.environ.get('STRIPE_SECRET_KEY',
settings.STRIPE_SECRET_KEY)
customer = stripe.Customer.retrieve(team.stripe_id)
data = {
'last4': customer.active_card.last4,
'created': customer.created,
'email': customer.email,
'team': team
}
if customer.subscription:
data['amount'] = customer.subscription.plan.amount
data['interval'] = customer.subscription.plan.interval
data['name'] = customer.subscription.plan.name
data['status'] = customer.subscription.status
data['nextBill'] = customer.subscription.current_period_end
else:
data['status'] = 'inactive'
return data
return {
'team': team
}
@login_required
@render_to('teams/team-members.html')
def team_members(request, username):
@ -151,35 +92,3 @@ def remove_team_member(request, username, member):
team.members.remove(user)
return HttpResponseRedirect('/' + team.slug + '/members/')
@login_required
def cancel_team_subscription(request, username):
if request.method != 'POST':
raise Http404
team = get_object_or_404(Team, slug=username, disabled=False)
if team.owner != request.user:
raise Http404
stripe.api_key = os.environ.get('STRIPE_SECRET_KEY',
settings.STRIPE_SECRET_KEY)
customer = stripe.Customer.retrieve(team.stripe_id)
customer.delete()
team.disabled = True
team.stripe_id = None
team.plan = None
team.save()
send_mail('[Snipt] Team cancelled plan: {}'.format(team.name),
"""
Team: https://snipt.net/{}
Email: {}
""".format(team.slug, team.email),
'support@snipt.net',
['nick@snipt.net'],
fail_silently=False)
return HttpResponseRedirect('/' + team.slug + '/?team-cancelled=true')

View File

@ -68,7 +68,7 @@
<script src="https://www.google.com/recaptcha/api.js"></script>
</head>
<body class="{% block body-class %}{% endblock %} {% if request.user.profile.has_pro %}is-pro{% endif %}" ng-controller="AppController">
<body class="{% block body-class %}{% endblock %} is-pro" ng-controller="AppController">
{% block header %}
<header class="main">
@ -146,7 +146,7 @@
{% if request.user.profile.has_pro %}
<span class="is-pro">Pro</span>
{% else %}
Basic member
Snipt user
{% endif %}
</span>
</span>
@ -170,14 +170,6 @@
Account
</a>
</li>
{% if not request.user.profile.has_pro %}
<li>
<a href="/pro/">
<i class="icon-star-empty icon-white"></i>
Go Pro
</a>
</li>
{% endif %}
<li>
<a href="/for-teams/">
<i class="icon-star-empty icon-white"></i>
@ -222,7 +214,7 @@
{% block aside %}
<aside class="main">
{% block ad %}
{% if not request.user.profile.has_pro %}
{% if not request.user.is_authenticated %}
{% include 'ad-sidebar.html' %}
{% endif %}
{% endblock %}
@ -236,11 +228,6 @@
<li class="twitter">
<a href="https://twitter.com/#!/snipt"><span>@snipt</span></a>
</li>
{% if not request.user.profile.has_pro %}
<li class="pro">
<a href="/pro/"><span>Go Pro</span></a>
</li>
{% endif %}
<li class="pro">
<a href="/for-teams/"><span>For Teams</span></a>
</li>
@ -395,11 +382,6 @@
window.api_key = '{{ request.user.api_key.key }}';
{% endblock %}
{% if request.user.profile.has_pro %}
window.user_has_pro = true;
{% else %}
window.user_has_pro = false;
{% endif %}
window.default_editor = '{{ request.user.profile.get_default_editor_display|lower }}';
window.editor_theme = '{{ request.user.profile.editor_theme }}';
</script>
@ -455,7 +437,6 @@
custom_data: {
'snipts count': {% snipts_count_for_user %},
'profile link': 'https://snipt.net/{{ request.user.username }}/',
'is pro': window.user_has_pro,
'blog domain': '{{ request.user.profile.blog_domain }}',
'pro date': {% firstof request.user.profile.pro_date|date:"U" 'null' %},
'username': '{{ request.user.username }}'

View File

@ -87,7 +87,7 @@
<div class="inner">
<h2>Team accounts</h2>
<p>
Sign up for a <a href="/for-teams/">Team</a> account and allow your team members to create and
Create a <a href="/for-teams/">Team</a> account and allow your team members to create and
edit private and public code snippets. Search through all your team's snippets, and
view detailed diffs of changes to each snippet.
</p>
@ -97,10 +97,8 @@
<div class="inner">
<h2>Personal accounts</h2>
<p>
Individuals can post public snippets for free. <a href="/pro/">Go Pro</a> for only $5/mo and mark snippets as
<code>private</code>, making them perfect for storing and organizing code you never want to
forget. All <a href="/for-teams/">team</a> members are automatically granted
<span class="pro">Pro</span> accounts.
Individuals can post public and <code>private</code> snippets for free, making them perfect
for storing and organizing code you never want to forget.
</p>
</div>
</div>
@ -115,20 +113,4 @@
</div>
</div>
</section>
<section class="features">
<div class="container">
<div class="row">
<header class="span4 offset4">
<h1>See it in action</h1>
</header>
</div>
<div class="video-container">
<video controls loop autoplay src="{{ STATIC_URL }}/video/demo.mp4">
<source src="{{ STATIC_URL }}/video/demo.mp4" type="video/mp4">
Your browser does not support the video tag.
</video>
</div>
</div>
</div>
</section>
{% endblock %}

View File

@ -1,31 +0,0 @@
{% extends "base.html" %}
{% block page-title %}Sign up for Snipt Pro{% endblock %}
{% block body-class %}{{ block.super }} static pro signup pro-signup pro-signup-complete{% endblock %}
{% block breadcrumb %}
<li><a href="/pro/">Snipt Pro</a></li>
<li><span class="prompt">/</span> <a href="#">Complete</a></li>
{% endblock %}
{% block content %}
<form class="form-horizontal static-box" id="pro-signup" method="post" action="/pro/complete/">
<fieldset>
<div class="info">
You rock.
<p class="sub">
You're now a Snipt Pro.<br /><br />
You should <a target="blank" href="http://twitter.com/intent/tweet?text=I'm now a @Snipt Pro. Are you? https://snipt.net/pro/">tell the world</a>.
If you ever need anything at all, <a href="mailto:support@snipt.net">email support</a>, or use the chat in the bottom right.
</p>
</div>
</fieldset>
</form>
{% endblock %}
{% block analytics %}
{% if not debug %}
window.ll('tagScreen', 'Pro signup complete view');
{% endif %}
{% endblock %}

View File

@ -1,146 +0,0 @@
{% extends "base.html" %}
{% block page-title %}Sign up for Snipt Pro{% endblock %}
{% block body-class %}{{ block.super }} static signup pro pro-signup{% endblock %}
{% block extra-scripts %}
<script type="text/javascript" src="https://js.stripe.com/v1/"></script>
{% endblock %}
{% block js %}
{{ block.super }}
{% if debug %}
Stripe.setPublishableKey('pk_test_cgknmaWRMQeJt2adEdvH3T9l');
{% else %}
Stripe.setPublishableKey('pk_live_gUO2nCl7dhx6j0posz6gnbhA');
{% endif %}
{% endblock %}
{% block breadcrumb %}
<li><a href="/pro/">Snipt Pro</a></li>
{% endblock %}
{% block content %}
{% if request.GET.declined %}
<div class="alert alert-error" style="margin: 30px;">
<strong>{{ request.GET.declined }}</strong> You have not been charged. Please try again.
</div>
{% endif %}
<form class="form-horizontal static-box" id="pro-signup" method="post" action="/pro/complete/{% if 'plan' in request.GET %}?plan={{ request.GET.plan }}{% endif %}">
<fieldset>
<div class="info">
Go <span class="pro">Pro</span> for just <span>$5/month</span>.
<p class="sub">
As a paid Snipt member you'll unlock the ability to post private snipts,
have unrestricted access to the app, <a href="/api/">API</a>,
custom-domain <a href="/blogging/">blogging</a>, instant chat support, and more.
</p>
<p class="sub" style="font-size: .6em; margin-top: 15px;">
Group discounts available. Email <a href="mailto:support@snipt.net">support@snipt.net</a> for details.
</p>
</div>
{% if not request.user.is_authenticated %}
<div class="login-first">
<span>
To go <span class="pro">Pro</span>, <a href="/signup/?next=/pro/">sign up</a> or <a href="/login/?next=/pro/">log in</a>.
</span>
</div>
{% elif request.user.profile.has_pro %}
<div class="login-first">
<span>
{% if request.user.profile.is_pro %}
You're already a <span class="pro">Pro</span>.
<a style="display: block; margin-top: 15px;" href="/account/billing/">View details &raquo;</a>
{% else %}
<p style="line-height: 26px;">You're a member of a team, and all team members automatically have <span class="pro">Pro</span> access.</p>
{% endif %}
</span>
</div>
{% else %}
<div class="payment-form">
<div class="payment-loading"><span>Please wait&hellip;</span></div>
<div class="payment-errors alert alert-error"></div>
<div class="control-group">
<label class="control-label" for="name">Payment plan:</label>
<div class="controls">
<select name="plan" type="text" class="input-medium" id="plan">
<option value="snipt-pro-monthly">$5/month</option>
<option value="snipt-pro-yearly">$60/year</option>
</select>
</div>
</div>
<div class="control-group">
<label class="control-label" for="number">Card number:</label>
<div class="controls cards">
<input type="text" class="input-xlarge" id="number" />
<img src="{{ STATIC_URL }}img/card-visa.png" alt="Visa" />
<img src="{{ STATIC_URL }}img/card-mastercard.png" alt="MasterCard" />
<img src="{{ STATIC_URL }}img/card-discover.png" alt="Discover" />
<img src="{{ STATIC_URL }}img/card-american-express.png" alt="American Express" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="exp-month">Expiration date:</label>
<div class="controls">
<select id="exp-month" class="span2 exp-month">
<option value="">----</option>
<option value="01">01 - January</option>
<option value="02">02 - February</option>
<option value="03">03 - March</option>
<option value="04">04 - April</option>
<option value="05">05 - May</option>
<option value="06">06 - June</option>
<option value="07">07 - July</option>
<option value="08">08 - August</option>
<option value="09">09 - September</option>
<option value="10">10 - October</option>
<option value="11">11 - November</option>
<option value="12">12 - December</option>
</select>
<select id="exp-year" class="span2">
<option value="">----</option>
<option value="2015">2015</option>
<option value="2016">2016</option>
<option value="2017">2017</option>
<option value="2018">2018</option>
<option value="2019">2019</option>
<option value="2020">2020</option>
<option value="2021">2021</option>
<option value="2022">2022</option>
<option value="2023">2023</option>
<option value="2024">2024</option>
<option value="2025">2025</option>
</select>
</div>
</div>
<div class="control-group">
<label class="control-label" for="cvc">Security code (CVC):</label>
<div class="controls">
<input type="text" class="input-min span1" id="cvc">
</div>
</div>
</div>
<div class="form-actions">
{% csrf_token %}
<button type="submit" class="btn btn-success">Subscribe &raquo;</button>
<div class="security">
<a href="https://stripe.com/help/security">Secure</a> by default. Every Snipt page is secure.
</div>
<div class="stripe">
Your credit card is stored securely with <a href="https://stripe.com">Stripe</a> and we use <a href="https://stripe.com/docs/stripe.js">Stripe.js</a> for maximum security.
</div>
</div>
<div class="form-actions" style="color: #A2A2A2;">
Prefer to pay with PayPal? Email <a href="mailto:support@snipt.net">support@snipt.net</a>.
</div>
{% endif %}
</fieldset>
</form>
{% endblock %}
{% block analytics %}
{% if not debug %}
window.ll('tagScreen', 'Pro signup view');
{% endif %}
{% endblock %}

View File

@ -45,7 +45,6 @@
<div class="profile team-settings group">
<div class="meta">
<span class="title">Team settings</span>
<a href="/{{ user.username }}/billing/">Billing &raquo;</a>
<a href="/{{ user.username }}/members/">Members &raquo;</a>
</div>
</div>
@ -75,9 +74,6 @@
</div>
{% endif %}
</div>
{% if user.profile.has_pro %}
<a class="pro" href="/pro/">Pro</a>
{% endif %}
{% if user.profile.gittip_username %}
<a class="gittip" href="https://www.gittip.com/{{ user.profile.gittip_username }}/">Tip</a>
{% endif %}

View File

@ -21,10 +21,7 @@
{% endif %}
{% endif %}
<fieldset>
<div class="info" style="line-height: 35px;">
Free to post public snipts,<br />just <span>$5/month</span> for private snipts.
</div>
<div class="control-group {% if form.errors.username %}error{% endif %}">
<div style="padding-top: 30px;" class="control-group {% if form.errors.username %}error{% endif %}">
<label class="control-label" for="id_username">Username</label>
<div class="controls">
{{ form.username }}

View File

@ -11,8 +11,8 @@ from snipts.api import (PublicSniptResource,
from snipts.views import search
from tastypie.api import Api
from utils.views import SniptRegistrationView
from views import (homepage, lexers, login_redirect, pro, sitemap, tags,
pro_complete, user_api_key)
from views import (homepage, lexers, login_redirect, sitemap, tags,
user_api_key)
public_api = Api(api_name='public')
public_api.register(PublicSniptResource())
@ -43,9 +43,6 @@ urlpatterns = \
url(r'^sitemap.xml$', sitemap),
url(r'^tags/$', tags),
url(r'^pro/$', pro),
url(r'^pro/complete/$', pro_complete),
url(r'^account/', include('accounts.urls')),
url(r'^api/public/lexer/$', lexers),

View File

@ -1,15 +1,9 @@
import datetime
import hashlib
import os
import stripe
from accounts.models import UserProfile
from annoying.decorators import ajax_request, render_to
from blogs.views import blog_list
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.core.mail import send_mail
from django.db.models import Count
from django.http import HttpResponseRedirect, HttpResponseBadRequest
from django.shortcuts import render_to_response
@ -84,57 +78,6 @@ def login_redirect(request):
return HttpResponseRedirect('/')
@render_to('pro.html')
def pro(request):
return {}
@login_required
@render_to('pro-complete.html')
def pro_complete(request):
if request.method == 'POST':
token = request.POST['token']
stripe.api_key = os.environ.get('STRIPE_SECRET_KEY',
settings.STRIPE_SECRET_KEY)
if 'plan' in request.GET:
plan = request.GET['plan']
else:
plan = request.POST['plan']
try:
customer = stripe.Customer.create(card=token,
plan=plan,
email=request.user.email)
except stripe.CardError as e:
error_message = e.json_body['error']['message']
return HttpResponseRedirect('/pro/?declined=%s' % error_message or
'Your card was declined.')
profile = request.user.profile
profile.is_pro = True
profile.pro_date = datetime.datetime.now()
profile.stripe_id = customer.id
profile.save()
send_mail('[Snipt] New Pro signup: {}'.format(request.user.username),
"""
User: https://snipt.net/{}
Email: {}
Plan: {}
""".format(request.user.username, request.user.email, plan),
'support@snipt.net',
['nick@snipt.net'],
fail_silently=False)
return {}
else:
return HttpResponseBadRequest()
def sitemap(request):
tags = Tag.objects.filter(snipt__public=True)