Make Snipt free for all.
parent
b105b679cb
commit
4642ba07b6
|
@ -137,13 +137,4 @@ class UserProfile(models.Model):
|
||||||
self.user.date_joined.replace(tzinfo=None)
|
self.user.date_joined.replace(tzinfo=None)
|
||||||
return delta.days
|
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])
|
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])
|
||||||
|
|
|
@ -26,9 +26,6 @@
|
||||||
<li ng-class="{active: route.current.scope.section == 'Editor'}">
|
<li ng-class="{active: route.current.scope.section == 'Editor'}">
|
||||||
<a href="/account/editor/">Editor</a>
|
<a href="/account/editor/">Editor</a>
|
||||||
</li>
|
</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>
|
</ul>
|
||||||
</aside>
|
</aside>
|
||||||
<section class="content" ng-class="{'with-message': message}">
|
<section class="content" ng-class="{'with-message': message}">
|
||||||
|
|
|
@ -4,8 +4,4 @@ from django.conf.urls import *
|
||||||
urlpatterns = \
|
urlpatterns = \
|
||||||
patterns('',
|
patterns('',
|
||||||
url(r'^stats/$', views.stats, name='account-stats'),
|
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'))
|
url(r'^', views.account, name='account-detail'))
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
import os
|
from annoying.decorators import render_to
|
||||||
import stripe
|
|
||||||
|
|
||||||
from annoying.decorators import ajax_request, render_to
|
|
||||||
from django.conf import settings
|
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.core.mail import send_mail
|
|
||||||
from snipts.models import Snipt
|
from snipts.models import Snipt
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,64 +9,6 @@ def account(request):
|
||||||
return {}
|
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
|
@login_required
|
||||||
@render_to('stats.html')
|
@render_to('stats.html')
|
||||||
def stats(request):
|
def stats(request):
|
||||||
|
|
|
@ -34,7 +34,6 @@ class BlogMiddleware:
|
||||||
get_object_or_404(User, username__iexact=blog_user)
|
get_object_or_404(User, username__iexact=blog_user)
|
||||||
|
|
||||||
if request.blog_user is None:
|
if request.blog_user is None:
|
||||||
# TODO: This needs to check profile.has_pro() instead.
|
|
||||||
pro_users = User.objects.filter(userprofile__is_pro=True)
|
pro_users = User.objects.filter(userprofile__is_pro=True)
|
||||||
|
|
||||||
for pro_user in pro_users:
|
for pro_user in pro_users:
|
||||||
|
|
|
@ -77,12 +77,10 @@
|
||||||
<script type="text/javascript" src="//pagead2.googlesyndication.com/pagead/show_ads.js"></script>
|
<script type="text/javascript" src="//pagead2.googlesyndication.com/pagead/show_ads.js"></script>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if not blog_user.profile.has_pro %}
|
|
||||||
<nav class="footer {% if sidebar %}with-sidebar{% endif %}">
|
<nav class="footer {% if sidebar %}with-sidebar{% endif %}">
|
||||||
<ul class="powered">
|
<ul class="powered">
|
||||||
<li class="snipt"><a href="https://snipt.net/blogging/">Blog powered by Snipt</a></li>
|
<li class="snipt"><a href="https://snipt.net/blogging/">Blog powered by Snipt</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
{% endif %}
|
|
||||||
</aside>
|
</aside>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -311,11 +311,6 @@ header.main {
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
padding-bottom: 7px;
|
padding-bottom: 7px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
|
||||||
span.is-pro {
|
|
||||||
color: #3299B7;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|
|
@ -15,10 +15,6 @@ if (typeof angular !== 'undefined') {
|
||||||
templateUrl: '/static/js/src/modules/partials/profile.html',
|
templateUrl: '/static/js/src/modules/partials/profile.html',
|
||||||
controller: controllers.ProfileController
|
controller: controllers.ProfileController
|
||||||
});
|
});
|
||||||
$routeProvider.when('/account/billing/', {
|
|
||||||
templateUrl: '/static/js/src/modules/partials/billing.html',
|
|
||||||
controller: controllers.BillingController
|
|
||||||
});
|
|
||||||
$routeProvider.when('/account/blogging/', {
|
$routeProvider.when('/account/blogging/', {
|
||||||
templateUrl: '/static/js/src/modules/partials/blogging.html',
|
templateUrl: '/static/js/src/modules/partials/blogging.html',
|
||||||
controller: controllers.BloggingController
|
controller: controllers.BloggingController
|
||||||
|
@ -63,15 +59,6 @@ if (typeof angular !== 'undefined') {
|
||||||
|
|
||||||
return promise;
|
return promise;
|
||||||
},
|
},
|
||||||
getStripeAccount: function() {
|
|
||||||
|
|
||||||
var promise = $http({
|
|
||||||
method: 'GET',
|
|
||||||
url: '/account/stripe-account-details/'
|
|
||||||
});
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
},
|
|
||||||
saveAccount: function(user, fields) {
|
saveAccount: function(user, fields) {
|
||||||
|
|
||||||
var promise = $http({
|
var promise = $http({
|
||||||
|
@ -173,12 +160,6 @@ if (typeof angular !== 'undefined') {
|
||||||
|
|
||||||
AccountStorage.getAccount().then(function(response) {
|
AccountStorage.getAccount().then(function(response) {
|
||||||
$scope.user = response.data;
|
$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) {
|
$scope.saveFields = function(fields) {
|
||||||
|
|
|
@ -23,14 +23,6 @@ jQuery(function($) {
|
||||||
var pre = $pres.eq(i);
|
var pre = $pres.eq(i);
|
||||||
pre.width(pre.parents('section.code').width() - 30);
|
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.
|
// Angular app init.
|
||||||
|
@ -55,12 +47,6 @@ jQuery(function($) {
|
||||||
if (root.location.pathname === '/account/stats/') {
|
if (root.location.pathname === '/account/stats/') {
|
||||||
root.ll('tagEvent', 'Viewed stats page');
|
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) {
|
app.controller('AppController', function($scope) {
|
||||||
|
|
|
@ -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 »</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>
|
|
|
@ -17,9 +17,8 @@
|
||||||
<div class="control-group" ng-class="{error: errors.blog_domain}">
|
<div class="control-group" ng-class="{error: errors.blog_domain}">
|
||||||
<label class="control-label" for="id_blog_domain">Blog domain:</label>
|
<label class="control-label" for="id_blog_domain">Blog domain:</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<input ng-disabled="!user.has_pro" id="id_blog_domain" type="text" ng-model="user.blog_domain" maxlength="250">
|
<input 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 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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -96,62 +96,6 @@
|
||||||
window.ui_halted = false;
|
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')) {
|
if (this.$body.hasClass('login')) {
|
||||||
$('input#id_username').focus();
|
$('input#id_username').focus();
|
||||||
}
|
}
|
||||||
|
@ -248,30 +192,6 @@
|
||||||
$('div.infield label', this.$body).inFieldLabels({
|
$('div.infield label', this.$body).inFieldLabels({
|
||||||
fadeDuration: 200
|
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -132,13 +132,10 @@
|
||||||
var $checkbox = $(this);
|
var $checkbox = $(this);
|
||||||
var $label = $checkbox.parent();
|
var $label = $checkbox.parent();
|
||||||
|
|
||||||
$('div.alert-not-pro').hide();
|
|
||||||
if ($checkbox.is(':checked')) {
|
if ($checkbox.is(':checked')) {
|
||||||
$label.removeClass('is-private').addClass('is-public');
|
$label.removeClass('is-private').addClass('is-public');
|
||||||
if (!window.user_has_pro) $('div.alert-not-pro').hide();
|
|
||||||
} else {
|
} else {
|
||||||
$label.addClass('is-private').removeClass('is-public');
|
$label.addClass('is-private').removeClass('is-public');
|
||||||
if (!window.user_has_pro) $('div.alert-not-pro').show();
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}).change();
|
}).change();
|
||||||
|
@ -664,8 +661,6 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var has_pro = $user.siblings('span.pro').length ? true : false;
|
|
||||||
|
|
||||||
var data = {
|
var data = {
|
||||||
code: $('textarea.raw', $el).text(),
|
code: $('textarea.raw', $el).text(),
|
||||||
description: $('textarea.description', $el).text(),
|
description: $('textarea.description', $el).text(),
|
||||||
|
@ -691,9 +686,7 @@
|
||||||
user: {
|
user: {
|
||||||
absolute_url: $user.attr('href'),
|
absolute_url: $user.attr('href'),
|
||||||
username: $user.text(),
|
username: $user.text(),
|
||||||
profile: {
|
profile: {}
|
||||||
has_pro: has_pro
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -729,9 +722,7 @@
|
||||||
public: true,
|
public: true,
|
||||||
user: {
|
user: {
|
||||||
username: '',
|
username: '',
|
||||||
profile: {
|
profile: {}
|
||||||
has_pro: window.user_has_pro
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -153,13 +153,6 @@ class SniptValidation(Validation):
|
||||||
if (len(bundle.data['title']) > 255):
|
if (len(bundle.data['title']) > 255):
|
||||||
errors['title-length'] = ("Title must be 255 characters or less.")
|
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
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
@ -310,7 +303,6 @@ class PrivateUserProfileResource(ModelResource):
|
||||||
bundle.data['username'] = bundle.obj.user.username
|
bundle.data['username'] = bundle.obj.user.username
|
||||||
bundle.data['user_id'] = bundle.obj.user.id
|
bundle.data['user_id'] = bundle.obj.user.id
|
||||||
bundle.data['api_key'] = bundle.obj.user.api_key.key
|
bundle.data['api_key'] = bundle.obj.user.api_key.key
|
||||||
bundle.data['has_pro'] = bundle.obj.user.profile.has_pro
|
|
||||||
return bundle
|
return bundle
|
||||||
|
|
||||||
|
|
||||||
|
@ -335,7 +327,6 @@ class PrivateUserResource(ModelResource):
|
||||||
bundle.data['email_md5'] = hashlib \
|
bundle.data['email_md5'] = hashlib \
|
||||||
.md5(bundle.obj.email.lower().encode('utf-8')) \
|
.md5(bundle.obj.email.lower().encode('utf-8')) \
|
||||||
.hexdigest()
|
.hexdigest()
|
||||||
bundle.data['has_pro'] = bundle.obj.profile.has_pro
|
|
||||||
bundle.data['stats'] = {
|
bundle.data['stats'] = {
|
||||||
'public_snipts': Snipt.objects.filter(user=bundle.obj.id,
|
'public_snipts': Snipt.objects.filter(user=bundle.obj.id,
|
||||||
public=True).count(),
|
public=True).count(),
|
||||||
|
|
|
@ -20,9 +20,6 @@
|
||||||
<div class="group">
|
<div class="group">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<header>
|
<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> </h2>
|
<h2> </h2>
|
||||||
<h1>
|
<h1>
|
||||||
<input maxlength="255" type="text" id="snipt_title" placeholder="Title" value="<% if (snipt.title != 'Untitled') { %><%= snipt.title %><% } %>" />
|
<input maxlength="255" type="text" id="snipt_title" placeholder="Title" value="<% if (snipt.title != 'Untitled') { %><%= snipt.title %><% } %>" />
|
||||||
|
|
|
@ -74,8 +74,6 @@
|
||||||
</a>
|
</a>
|
||||||
<% if (window.teams.indexOf(snipt.user.username) !== -1) { %>
|
<% if (window.teams.indexOf(snipt.user.username) !== -1) { %>
|
||||||
<span class="pro"><a href="/for-teams/">Team</a></span>
|
<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>
|
</li>
|
||||||
<% if (!snipt.new_from_js) { %>
|
<% if (!snipt.new_from_js) { %>
|
||||||
|
|
|
@ -183,8 +183,6 @@
|
||||||
<a href="{{ snipt.user.get_absolute_url }}">{{ snipt.user.username }}</a>
|
<a href="{{ snipt.user.get_absolute_url }}">{{ snipt.user.username }}</a>
|
||||||
{% if snipt.user.profile.is_a_team %}
|
{% if snipt.user.profile.is_a_team %}
|
||||||
<span class="pro"><a href="/for-teams/">Team</a></span>
|
<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 %}
|
{% endif %}
|
||||||
{% if snipt.user.profile.gittip_username %}
|
{% if snipt.user.profile.gittip_username %}
|
||||||
<span class="gittip"><a href="https://www.gittip.com/{{ snipt.user.profile.gittip_username }}/">Tip</a></span>
|
<span class="gittip"><a href="https://www.gittip.com/{{ snipt.user.profile.gittip_username }}/">Tip</a></span>
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% 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>
|
<fieldset>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
Team created successfully.
|
Team created successfully.
|
||||||
|
@ -22,9 +22,6 @@
|
||||||
<li>
|
<li>
|
||||||
<a href="/{{ team.user.username }}/members">Add some members</a>
|
<a href="/{{ team.user.username }}/members">Add some members</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<a href="/{{ team.user.username }}/billing">View billing info</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -4,17 +4,8 @@
|
||||||
|
|
||||||
{% block body-class %}{{ block.super }} static signup pro pro-signup{% 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 js %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{% if debug %}
|
|
||||||
Stripe.setPublishableKey('pk_test_cgknmaWRMQeJt2adEdvH3T9l');
|
|
||||||
{% else %}
|
|
||||||
Stripe.setPublishableKey('pk_live_gUO2nCl7dhx6j0posz6gnbhA');
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block breadcrumb %}
|
{% block breadcrumb %}
|
||||||
|
@ -36,8 +27,6 @@
|
||||||
<li>Members can create and edit public and private snippets on a team.</li>
|
<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>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>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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
{% if not request.user.is_authenticated %}
|
{% if not request.user.is_authenticated %}
|
||||||
|
@ -48,8 +37,6 @@
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="payment-form">
|
<div class="payment-form">
|
||||||
<div class="payment-loading -teams"><span>Please wait…</span></div>
|
|
||||||
<div class="payment-errors alert alert-error"></div>
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<label class="control-label" for="team-name">Team name:</label>
|
<label class="control-label" for="team-name">Team name:</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
|
@ -68,84 +55,10 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type="submit" class="btn btn-success">Create team »</button>
|
<button type="submit" class="btn btn-success">Create team »</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>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
|
@ -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 %}
|
|
|
@ -24,11 +24,6 @@
|
||||||
<li class="active">
|
<li class="active">
|
||||||
<a href="/{{ team.slug }}/members/">Members</a>
|
<a href="/{{ team.slug }}/members/">Members</a>
|
||||||
</li>
|
</li>
|
||||||
{% if team.owner == request.user %}
|
|
||||||
<li>
|
|
||||||
<a href="/{{ team.slug }}/billing/">Billing</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
</ul>
|
||||||
</aside>
|
</aside>
|
||||||
<section class="content">
|
<section class="content">
|
||||||
|
|
|
@ -14,10 +14,4 @@ urlpatterns = \
|
||||||
name='add-team-member'),
|
name='add-team-member'),
|
||||||
url(r'^(?P<username>[^/]+)/members/$',
|
url(r'^(?P<username>[^/]+)/members/$',
|
||||||
views.team_members,
|
views.team_members,
|
||||||
name='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'))
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import os
|
import os
|
||||||
import stripe
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from annoying.decorators import render_to
|
from annoying.decorators import render_to
|
||||||
|
@ -26,27 +25,9 @@ def for_teams(request):
|
||||||
def for_teams_complete(request):
|
def for_teams_complete(request):
|
||||||
if request.method == 'POST' and request.user.is_authenticated():
|
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'],
|
team = Team(name=request.POST['team-name'],
|
||||||
email=request.POST['email'],
|
email=request.POST['email'],
|
||||||
plan=plan,
|
|
||||||
owner=request.user)
|
owner=request.user)
|
||||||
team.stripe_id = customer.id
|
|
||||||
team.save()
|
team.save()
|
||||||
|
|
||||||
user = User.objects.create_user(team.slug,
|
user = User.objects.create_user(team.slug,
|
||||||
|
@ -73,46 +54,6 @@ def for_teams_complete(request):
|
||||||
return HttpResponseBadRequest()
|
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
|
@login_required
|
||||||
@render_to('teams/team-members.html')
|
@render_to('teams/team-members.html')
|
||||||
def team_members(request, username):
|
def team_members(request, username):
|
||||||
|
@ -151,35 +92,3 @@ def remove_team_member(request, username, member):
|
||||||
team.members.remove(user)
|
team.members.remove(user)
|
||||||
|
|
||||||
return HttpResponseRedirect('/' + team.slug + '/members/')
|
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')
|
|
||||||
|
|
|
@ -68,7 +68,7 @@
|
||||||
<script src="https://www.google.com/recaptcha/api.js"></script>
|
<script src="https://www.google.com/recaptcha/api.js"></script>
|
||||||
|
|
||||||
</head>
|
</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 %}
|
{% block header %}
|
||||||
<header class="main">
|
<header class="main">
|
||||||
|
@ -146,7 +146,7 @@
|
||||||
{% if request.user.profile.has_pro %}
|
{% if request.user.profile.has_pro %}
|
||||||
<span class="is-pro">Pro</span>
|
<span class="is-pro">Pro</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
Basic member
|
Snipt user
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
@ -170,14 +170,6 @@
|
||||||
Account
|
Account
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</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>
|
<li>
|
||||||
<a href="/for-teams/">
|
<a href="/for-teams/">
|
||||||
<i class="icon-star-empty icon-white"></i>
|
<i class="icon-star-empty icon-white"></i>
|
||||||
|
@ -222,7 +214,7 @@
|
||||||
{% block aside %}
|
{% block aside %}
|
||||||
<aside class="main">
|
<aside class="main">
|
||||||
{% block ad %}
|
{% block ad %}
|
||||||
{% if not request.user.profile.has_pro %}
|
{% if not request.user.is_authenticated %}
|
||||||
{% include 'ad-sidebar.html' %}
|
{% include 'ad-sidebar.html' %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -236,11 +228,6 @@
|
||||||
<li class="twitter">
|
<li class="twitter">
|
||||||
<a href="https://twitter.com/#!/snipt"><span>@snipt</span></a>
|
<a href="https://twitter.com/#!/snipt"><span>@snipt</span></a>
|
||||||
</li>
|
</li>
|
||||||
{% if not request.user.profile.has_pro %}
|
|
||||||
<li class="pro">
|
|
||||||
<a href="/pro/"><span>Go Pro</span></a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
<li class="pro">
|
<li class="pro">
|
||||||
<a href="/for-teams/"><span>For Teams</span></a>
|
<a href="/for-teams/"><span>For Teams</span></a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -395,11 +382,6 @@
|
||||||
|
|
||||||
window.api_key = '{{ request.user.api_key.key }}';
|
window.api_key = '{{ request.user.api_key.key }}';
|
||||||
{% endblock %}
|
{% 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.default_editor = '{{ request.user.profile.get_default_editor_display|lower }}';
|
||||||
window.editor_theme = '{{ request.user.profile.editor_theme }}';
|
window.editor_theme = '{{ request.user.profile.editor_theme }}';
|
||||||
</script>
|
</script>
|
||||||
|
@ -455,7 +437,6 @@
|
||||||
custom_data: {
|
custom_data: {
|
||||||
'snipts count': {% snipts_count_for_user %},
|
'snipts count': {% snipts_count_for_user %},
|
||||||
'profile link': 'https://snipt.net/{{ request.user.username }}/',
|
'profile link': 'https://snipt.net/{{ request.user.username }}/',
|
||||||
'is pro': window.user_has_pro,
|
|
||||||
'blog domain': '{{ request.user.profile.blog_domain }}',
|
'blog domain': '{{ request.user.profile.blog_domain }}',
|
||||||
'pro date': {% firstof request.user.profile.pro_date|date:"U" 'null' %},
|
'pro date': {% firstof request.user.profile.pro_date|date:"U" 'null' %},
|
||||||
'username': '{{ request.user.username }}'
|
'username': '{{ request.user.username }}'
|
||||||
|
|
|
@ -87,7 +87,7 @@
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<h2>Team accounts</h2>
|
<h2>Team accounts</h2>
|
||||||
<p>
|
<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
|
edit private and public code snippets. Search through all your team's snippets, and
|
||||||
view detailed diffs of changes to each snippet.
|
view detailed diffs of changes to each snippet.
|
||||||
</p>
|
</p>
|
||||||
|
@ -97,10 +97,8 @@
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<h2>Personal accounts</h2>
|
<h2>Personal accounts</h2>
|
||||||
<p>
|
<p>
|
||||||
Individuals can post public snippets for free. <a href="/pro/">Go Pro</a> for only $5/mo and mark snippets as
|
Individuals can post public and <code>private</code> snippets for free, making them perfect
|
||||||
<code>private</code>, making them perfect for storing and organizing code you never want to
|
for storing and organizing code you never want to forget.
|
||||||
forget. All <a href="/for-teams/">team</a> members are automatically granted
|
|
||||||
<span class="pro">Pro</span> accounts.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -115,20 +113,4 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</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 %}
|
{% endblock %}
|
||||||
|
|
|
@ -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 %}
|
|
|
@ -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 »</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…</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 »</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 %}
|
|
|
@ -45,7 +45,6 @@
|
||||||
<div class="profile team-settings group">
|
<div class="profile team-settings group">
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
<span class="title">Team settings</span>
|
<span class="title">Team settings</span>
|
||||||
<a href="/{{ user.username }}/billing/">Billing »</a>
|
|
||||||
<a href="/{{ user.username }}/members/">Members »</a>
|
<a href="/{{ user.username }}/members/">Members »</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -75,9 +74,6 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if user.profile.has_pro %}
|
|
||||||
<a class="pro" href="/pro/">Pro</a>
|
|
||||||
{% endif %}
|
|
||||||
{% if user.profile.gittip_username %}
|
{% if user.profile.gittip_username %}
|
||||||
<a class="gittip" href="https://www.gittip.com/{{ user.profile.gittip_username }}/">Tip</a>
|
<a class="gittip" href="https://www.gittip.com/{{ user.profile.gittip_username }}/">Tip</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -21,10 +21,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="info" style="line-height: 35px;">
|
<div style="padding-top: 30px;" class="control-group {% if form.errors.username %}error{% endif %}">
|
||||||
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 %}">
|
|
||||||
<label class="control-label" for="id_username">Username</label>
|
<label class="control-label" for="id_username">Username</label>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
{{ form.username }}
|
{{ form.username }}
|
||||||
|
|
7
urls.py
7
urls.py
|
@ -11,8 +11,8 @@ from snipts.api import (PublicSniptResource,
|
||||||
from snipts.views import search
|
from snipts.views import search
|
||||||
from tastypie.api import Api
|
from tastypie.api import Api
|
||||||
from utils.views import SniptRegistrationView
|
from utils.views import SniptRegistrationView
|
||||||
from views import (homepage, lexers, login_redirect, pro, sitemap, tags,
|
from views import (homepage, lexers, login_redirect, sitemap, tags,
|
||||||
pro_complete, user_api_key)
|
user_api_key)
|
||||||
|
|
||||||
public_api = Api(api_name='public')
|
public_api = Api(api_name='public')
|
||||||
public_api.register(PublicSniptResource())
|
public_api.register(PublicSniptResource())
|
||||||
|
@ -43,9 +43,6 @@ urlpatterns = \
|
||||||
url(r'^sitemap.xml$', sitemap),
|
url(r'^sitemap.xml$', sitemap),
|
||||||
url(r'^tags/$', tags),
|
url(r'^tags/$', tags),
|
||||||
|
|
||||||
url(r'^pro/$', pro),
|
|
||||||
url(r'^pro/complete/$', pro_complete),
|
|
||||||
|
|
||||||
url(r'^account/', include('accounts.urls')),
|
url(r'^account/', include('accounts.urls')),
|
||||||
|
|
||||||
url(r'^api/public/lexer/$', lexers),
|
url(r'^api/public/lexer/$', lexers),
|
||||||
|
|
57
views.py
57
views.py
|
@ -1,15 +1,9 @@
|
||||||
import datetime
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import os
|
|
||||||
import stripe
|
|
||||||
|
|
||||||
from accounts.models import UserProfile
|
from accounts.models import UserProfile
|
||||||
from annoying.decorators import ajax_request, render_to
|
from annoying.decorators import ajax_request, render_to
|
||||||
from blogs.views import blog_list
|
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.contrib.auth.models import User
|
||||||
from django.core.mail import send_mail
|
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from django.http import HttpResponseRedirect, HttpResponseBadRequest
|
from django.http import HttpResponseRedirect, HttpResponseBadRequest
|
||||||
from django.shortcuts import render_to_response
|
from django.shortcuts import render_to_response
|
||||||
|
@ -84,57 +78,6 @@ def login_redirect(request):
|
||||||
return HttpResponseRedirect('/')
|
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):
|
def sitemap(request):
|
||||||
|
|
||||||
tags = Tag.objects.filter(snipt__public=True)
|
tags = Tag.objects.filter(snipt__public=True)
|
||||||
|
|
Loading…
Reference in New Issue