Team billing info and ability to cancel subscription.

master
Nick Sergeant 2015-10-25 22:36:19 -04:00
parent 79d077def9
commit 4127ba79b1
8 changed files with 123 additions and 45 deletions

View File

@ -23,6 +23,14 @@ 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.

View File

@ -23,6 +23,7 @@ if (typeof angular !== 'undefined') {
// Controllers. // Controllers.
controllers.TeamController = function($scope, $timeout, TeamStorage) { controllers.TeamController = function($scope, $timeout, TeamStorage) {
$scope.users = []; $scope.users = [];
$scope.search = '';
$scope.$watch('search', function(val) { $scope.$watch('search', function(val) {
$timeout.cancel($scope.timeout); $timeout.cancel($scope.timeout);

View File

@ -12,6 +12,11 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{% if 'team-cancelled' in request.GET %}
<div class="alert alert-success" style="margin: 30px;">
Your team plan has been succesfully cancelled.
</div>
{% endif %}
<section class="snipts" id="snipts" ng-controller="SniptListController" <section class="snipts" id="snipts" ng-controller="SniptListController"
{% if request.user.profile.list_view == 'C' %} {% if request.user.profile.list_view == 'C' %}
ng-cloak ng-show="$root.account.id" ng-cloak ng-show="$root.account.id"

View File

@ -1,5 +1,7 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load team_tags %}
{% block page-title %}Team Billing{% endblock %} {% block page-title %}Team Billing{% endblock %}
{% block body-class %}account {{ block.super }}{% endblock %} {% block body-class %}account {{ block.super }}{% endblock %}
@ -28,26 +30,29 @@
</aside> </aside>
<section class="content"> <section class="content">
<div class="def" data-title="Plan"> <div class="def" data-title="Plan">
25 users {{ name }}
</div> </div>
<div class="def" data-title="Price"> <div class="def" data-title="Price">
$49.00 USD / month ${{ amount|currency_convert }}.00 USD / {{ interval }}
</div> </div>
<div class="def" data-title="Card"> <div class="def" data-title="Card">
xxxx-xxxx-xxxx-4242 xxxx-xxxx-xxxx-{{ last4 }}
</div> </div>
<div class="def" data-title="Status"> <div class="def" data-title="Status">
Active {{ status }}
</div> </div>
<div class="def" data-title="Team since"> <div class="def" data-title="Team since">
August 29, 2015 {{ team.created|date:'M d, Y' }}
</div> </div>
<div class="def" data-title="Next bill date" ng-show="user.stripeAccount.status != 'inactive'"> <div class="def" data-title="Next bill date" ng-show="user.stripeAccount.status != 'inactive'">
November 29, 2015 {{ nextBill|to_date|date:'M d, Y' }}
</div> </div>
<p class="alert alert-info group" style="padding-right: 8px;"> <form class="alert alert-info group" style="margin: 15px; padding-right: 8px;" id="cancel-team-subscription" action="/{{ team.slug }}/billing/cancel/" method="post">
<a href="/{{ team.slug }}/billing/cancel/" class="btn btn-danger pull-right">Cancel subscription</a> {% csrf_token %}
</p> <button type="submit" class="btn btn-danger pull-right">
Cancel subscription
</button>
</form>
</section> </section>
</section> </section>
{% endblock %} {% endblock %}

View File

@ -1,3 +1,5 @@
import datetime
from django import template from django import template
register = template.Library() register = template.Library()
@ -6,3 +8,13 @@ register = template.Library()
@register.filter @register.filter
def user_is_member(team, user): def user_is_member(team, user):
return team.user_is_member(user) return team.user_is_member(user)
@register.filter
def currency_convert(amount):
return amount / 100
@register.filter
def to_date(timestamp):
return datetime.datetime.fromtimestamp(float(timestamp))

View File

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

View File

@ -20,12 +20,76 @@ def for_teams(request):
return {} return {}
@login_required
@render_to('teams/for-teams-complete.html')
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, 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,
team.email,
str(uuid.uuid4()))
team.user = user
team.save()
return {
'team': team
}
else:
return HttpResponseBadRequest()
@login_required @login_required
@render_to('teams/team-billing.html') @render_to('teams/team-billing.html')
def team_billing(request, username): def team_billing(request, username):
team = get_object_or_404(Team, slug=username, disabled=False) team = get_object_or_404(Team, slug=username, disabled=False)
if team.owner != request.user: if team.owner != request.user:
raise Http404 raise Http404
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 { return {
'team': team 'team': team
} }
@ -72,42 +136,22 @@ def remove_team_member(request, username, member):
@login_required @login_required
@render_to('teams/for-teams-complete.html') def cancel_team_subscription(request, username):
def for_teams_complete(request):
if request.method == 'POST' and request.user.is_authenticated():
token = request.POST['token'] if request.method != 'POST':
stripe.api_key = os.environ.get('STRIPE_SECRET_KEY', raise Http404
settings.STRIPE_SECRET_KEY)
plan = request.POST['plan'] team = get_object_or_404(Team, slug=username, disabled=False)
if team.owner != request.user:
raise Http404
try: stripe.api_key = os.environ.get('STRIPE_SECRET_KEY',
customer = stripe.Customer.create(card=token, settings.STRIPE_SECRET_KEY)
plan=plan, customer = stripe.Customer.retrieve(team.stripe_id)
email=request.user.email) customer.delete()
except stripe.CardError, 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.disabled = True
email=request.POST['email'], team.stripe_id = None
plan=plan, team.save()
owner=request.user)
team.stripe_id = customer.id
team.save()
user = User.objects.create_user(team.slug, return HttpResponseRedirect('/' + team.slug + '/?team-cancelled=true')
team.email,
str(uuid.uuid4()))
team.user = user
team.save()
return {
'team': team
}
else:
return HttpResponseBadRequest()

View File

@ -1,4 +1,4 @@
<a href="/signup" class="snipt-promo"> <a href="/signup" class="snipt-promo">
<button class="btn btn-success btn-large pull-right">Sign up &raquo;</button> <button class="btn btn-success btn-large pull-right">Sign up &raquo;</button>
Sign up for Snipt!<br /><span style="font-size: 16px;">Post public snipts for free. Private snipts just $5/mo.</span> Sign up for Snipt!<br /><span style="font-size: 16px;">Post public snipts for free.</span>
</a> </a>