Skip to content

Commit

Permalink
Add Stripe payment on signup.
Browse files Browse the repository at this point in the history
Do not ask for credit card with the free option, but still
create a subscription so things are more consistent.

Allow plan to be provided in URL (already present on pricing page).
  • Loading branch information
dracos authored and zarino committed Feb 8, 2017
1 parent b489482 commit 2e4f97a
Show file tree
Hide file tree
Showing 24 changed files with 392 additions and 19 deletions.
4 changes: 4 additions & 0 deletions conf/general.yml-example
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ AREA_SRID: 27700
# Set this to a random string -- the longer, the better.
DJANGO_SECRET_KEY: 'gu^&xc)hoibh3x&s+9009jbn4d$!nq0lz+syx-^x8%z24!kfs4'

# Keys for accepting money via Stripe
STRIPE_SECRET_KEY: ''
STRIPE_PUBLIC_KEY: ''

# Mapped to Django's DEBUG and TEMPLATE_DEBUG settings. Optional, defaults to True.
DEBUG: True

Expand Down
7 changes: 5 additions & 2 deletions mapit_mysociety_org/context_processors.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from django.conf import settings


def contact_email(request):
return {'CONTACT_EMAIL': settings.CONTACT_EMAIL}
def add_settings(request):
return {
'CONTACT_EMAIL': settings.CONTACT_EMAIL,
'PRICING': settings.PRICING,
}
4 changes: 3 additions & 1 deletion mapit_mysociety_org/forms.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import account.forms

from subscriptions.forms import SubscriptionMixin

class SignupForm(account.forms.SignupForm):

class SignupForm(SubscriptionMixin, account.forms.SignupForm):
""" Override account.forms.SignupForm to remove username field """

def __init__(self, *args, **kwargs):
Expand Down
13 changes: 11 additions & 2 deletions mapit_mysociety_org/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# Update a couple of things to suit our changes

INSTALLED_APPS.extend(['django.contrib.sites', 'account', 'api_keys'])
INSTALLED_APPS.extend(['django.contrib.sites', 'account', 'api_keys', 'subscriptions'])

# Insert our project app before mapit and mapit_gb so that the templates
# take precedence
Expand All @@ -14,7 +14,7 @@
old_context_processors = TEMPLATES[0]['OPTIONS']['context_processors']
TEMPLATES[0]['OPTIONS']['context_processors'] = old_context_processors + (
'account.context_processors.account',
'mapit_mysociety_org.context_processors.contact_email',
'mapit_mysociety_org.context_processors.add_settings',
)

MIDDLEWARE_CLASSES.extend([
Expand Down Expand Up @@ -84,3 +84,12 @@
# A list of api keys or IP addresses to exclude from rate limiting
# Take this from Mapit's existing setting for now
API_THROTTLE_UNLIMITED = MAPIT_RATE_LIMIT

STRIPE_SECRET_KEY = config.get('STRIPE_SECRET_KEY')
STRIPE_PUBLIC_KEY = config.get('STRIPE_PUBLIC_KEY')

PRICING = [
{'plan': 'mapit-10k', 'price': 20, 'calls': '10,000'},
{'plan': 'mapit-100k', 'price': 100, 'calls': '100,000'},
{'plan': 'mapit-0k', 'price': 300, 'calls': '0'},
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div class="account-form__field">
<label class="account-form__label">
{{ field }}
{{ field.label }}
</label>
{% if field.errors %}
<div class="account-form__errors">{{ field.errors }}</div>
{% endif %}
</div>
6 changes: 4 additions & 2 deletions mapit_mysociety_org/templates/account/_form_fields.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
This template is used for several different forms, and we want to specify the
ordering of the fields, so we list each field separately
{% endcomment %}
{% if form.email %}
{% if form.charitable_tick %}
{% include 'account/_form_fields_signup.html' %}
{% elif form.email %}
{% include 'account/_form_field.html' with field=form.email %}
{% include 'account/_form_field.html' with field=form.password %}
{% include 'account/_form_field.html' with field=form.password_confirm %}
Expand All @@ -27,4 +29,4 @@
{% endfor %}
{% if redirect_field_value %}
<input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}" />
{% endif %}
{% endif %}
16 changes: 16 additions & 0 deletions mapit_mysociety_org/templates/account/_form_fields_signup.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{% include 'account/_form_field.html' with field=form.email %}
{% include 'account/_form_field.html' with field=form.password %}
{% include 'account/_form_field.html' with field=form.password_confirm %}

{% include 'account/_form_field.html' with field=form.plan %}
{% include 'account/_form_field_checkbox.html' with field=form.charitable_tick %}
<div id="charitable-qns"{% if not form.charitable_tick.value %} style="display:none"{% endif %}>
{% include 'account/_form_field.html' with field=form.charitable %}
<div id="charity-number"{% if not form.charity_number.value and not form.charity_number.errors %} style="display:none"{% endif %}>
{% include 'account/_form_field.html' with field=form.charity_number %}
</div>
<div id="charitable-desc"{% if not form.description.value and not form.description.errors %} style="display:none"{% endif %}>
{% include 'account/_form_field.html' with field=form.description %}
</div>
</div>
{% include 'account/_form_field_checkbox.html' with field=form.tandcs_tick %}
8 changes: 7 additions & 1 deletion mapit_mysociety_org/templates/account/signup.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{% extends "account/base.html" %}

{% load static %}
{% load i18n %}

{% block title %}{% trans "Sign up" %}{% endblock %}
Expand All @@ -21,12 +22,17 @@ <h2>{% trans "Sign up" %}</h2>
<a href="mailto:{{ CONTACT_EMAIL }}">get in touch</a> so we
can discuss how we can provide you with the service you require.
</p>

<form id="signup_form" method="post" action="{% url "account_signup" %}" autocapitalize="off" {% if form.is_multipart %} enctype="multipart/form-data"{% endif %}>
{% csrf_token %}
{% include 'account/_form_fields.html' %}
<div class="account-form__field account-form__field--submit">
<button type="submit" class="btn btn--primary">{% trans "Sign up" %}</button>
<button id="customButton" class="btn btn--primary">{% trans "Sign up" %}</button>
</div>
</form>

<script src="https://checkout.stripe.com/checkout.js"></script>
<script id="js-payment" data-key="{{ STRIPE_PUBLIC_KEY }}" src="{% static "js/payment.js" %}"></script>

</section>
{% endblock %}
2 changes: 1 addition & 1 deletion mapit_mysociety_org/templates/mapit/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ <h3>You show them any of these…</h3>
<div class="container">
<div class="homepage-cta">
<p>
MapIt API access starts at £50/mth,<br>
MapIt API access starts at £{{ PRICING.0.price }}/mth,<br>
and is free for low-volume non-profit use.
</p>
<a href="{% url 'mapit_pricing' %}" class="btn">Show pricing</a>
Expand Down
21 changes: 12 additions & 9 deletions mapit_mysociety_org/templates/pricing.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,52 +25,55 @@ <h2 class="pricing__option__cost">Free</h2>

<div class="pricing__option">
<p class="pricing__option__name">Village</p>
<h2 class="pricing__option__cost">£20/mth</h2>
<h2 class="pricing__option__cost">£{{ PRICING.0.price }}/mth</h2>
<p class="pricing__option__description">
A great place to start if you’re integrating MapIt into a low traffic site.
</p>
<ul class="pricing__option__features">
<li>Max 10,000 calls per month</li>
<li>Max {{ PRICING.0.calls }} calls per month</li>
<li><strong>Free</strong> for charitable users</li>
</ul>
{% if request.user.is_authenticated %}
<a class="btn btn--primary" href="{% url 'subscription_update' %}">Switch plan</a>
{% else %}
<a class="btn btn--primary" href="{% url 'account_signup' %}?plan=mapit-10k">Sign up</a>
<a class="btn btn--primary" href="{% url 'account_signup' %}?plan={{ PRICING.0.plan }}">Sign up</a>
{% endif %}
</div>

<div class="pricing__option pricing__option--featured">
<p class="pricing__option__name">Town</p>
<h2 class="pricing__option__cost">£100/mth</h2>
<h2 class="pricing__option__cost">£{{ PRICING.1.price }}/mth</h2>
<p class="pricing__option__description">
Our most popular plan. Great for busier sites, or bulk boundary lookups.
</p>
<ul class="pricing__option__features">
<li>Max 100,000 calls per month</li>
<li>Max {{ PRICING.1.calls }} calls per month</li>
<li><strong>50% off</strong> for charitable users</li>
</ul>
{% if request.user.is_authenticated %}
<a class="btn btn--primary" href="{% url 'subscription_update' %}">Switch plan</a>
{% else %}
<a class="btn btn--primary" href="{% url 'account_signup' %}?plan=mapit-100k">Sign up</a>
<a class="btn btn--primary" href="{% url 'account_signup' %}?plan={{ PRICING.0.plan }}">Sign up</a>
{% endif %}
</div>

<div class="pricing__option">
<p class="pricing__option__name">Metropolis</p>
<h2 class="pricing__option__cost">£300/mth</h2>
<h2 class="pricing__option__cost">£{{ PRICING.2.price }}/mth</h2>
<p class="pricing__option__description">
Super popular with heavy users like councils and government departments.
</p>
<ul class="pricing__option__features">
<li>Unlimited calls</li>
<li>{% if PRICING.2.calls == '0' %}Unlimited calls
{% else %}Max {{ PRICING.2.calls }} calls per month
{% endif %}
</li>
<li><strong>50% off</strong> for charitable users</li>
</ul>
{% if request.user.is_authenticated %}
<a class="btn btn--primary" href="{% url 'subscription_update' %}">Switch plan</a>
{% else %}
<a class="btn btn--primary" href="{% url 'account_signup' %}?plan=mapit-0k">Sign up</a>
<a class="btn btn--primary" href="{% url 'account_signup' %}?plan={{ PRICING.2.plan }}">Sign up</a>
{% endif %}
</div>

Expand Down
21 changes: 20 additions & 1 deletion mapit_mysociety_org/views.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,45 @@
from django.shortcuts import redirect
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import login
from django.contrib.auth.models import User
from django.http import Http404
import stripe

import account.forms
import account.views
from subscriptions.views import SubscriptionUpdateMixin

from . import forms


stripe.api_key = settings.STRIPE_SECRET_KEY

class LoginView(account.views.LoginView):
""" Override account.views.LoginView to use the email-only version """

form_class = account.forms.LoginEmailForm


class SignupView(account.views.SignupView):
class SignupView(SubscriptionUpdateMixin, account.views.SignupView):
""" Override account.views.SignupView to use our email-only SignupForm """

form_class = forms.SignupForm

def get_initial(self):
initial = super(SignupView, self).get_initial()
initial['plan'] = self.request.GET.get('plan')
return initial

def get_context_data(self, **kwargs):
kwargs['STRIPE_PUBLIC_KEY'] = settings.STRIPE_PUBLIC_KEY
return super(SignupView, self).get_context_data(**kwargs)

def form_valid(self, form):
resp = self.update_subscription(form)
messages.add_message(self.request, messages.INFO, 'Thank you very much!')
return resp

def generate_username(self, form):
# Generate a random username (we used to use the email address
# directly, but Django has a 30 character limit for the username).
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ mock==1.3.0
mockredispy==2.9.0.12
redis==2.10.5
django-apptemplates==1.2
stripe==1.46.0
119 changes: 119 additions & 0 deletions static/js/payment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
document.getElementById('id_charitable_tick').addEventListener('click', function(e) {
if (this.checked) {
document.getElementById('charitable-qns').style.display = 'block';
} else {
document.getElementById('charitable-qns').style.display = 'none';
}
});
document.getElementById('id_charitable_0').addEventListener('change', function(e) {
document.getElementById('charitable-desc').style.display = 'none';
document.getElementById('charity-number').style.display = 'block';
});
document.getElementById('id_charitable_1').addEventListener('change', function(e) {
document.getElementById('charity-number').style.display = 'none';
document.getElementById('charitable-desc').style.display = 'block';
});
document.getElementById('id_charitable_2').addEventListener('change', function(e) {
document.getElementById('charity-number').style.display = 'none';
document.getElementById('charitable-desc').style.display = 'none';
});

var stripe_key = document.getElementById('js-payment').getAttribute('data-key');
var handler = StripeCheckout.configure({
key: stripe_key,
image: 'https://s3.amazonaws.com/stripe-uploads/acct_19EbqNIbP0iBLddtmerchant-icon-1479145884111-mysociety-wheel-logo.png',
locale: 'auto',
token: function(token) {
var form = document.getElementById('signup_form');
form.stripeToken.value = token.id;
form.submit();
}
});

document.getElementById('customButton').addEventListener('click', function(e) {
// Already got a token from Stripe (so password mismatch error or somesuch)
var form = document.getElementById('signup_form');
if (form.stripeToken.value) {
return;
}
e.preventDefault();

function err_highlight(id, err) {
if (err) {
if (id.className.search(/(?:^|\s)error(?!\S)/) === -1) {
id.className += " error";
}
return 1;
} else {
id.className = id.className.replace(/(?:^|\s)error(?!\S)/, '')
return 0;
}
}

function err(field, extra) {
var f = document.getElementById(field);
if (!f) {
return 0;
}
f = f.value;
var label = document.querySelector('label[for=' + field + ']');
return err_highlight(label, extra !== undefined ? extra && !f : !f);
}

var errors = 0;
errors += err('id_email');
errors += err('id_password');
errors += err('id_password_confirm');
var plan = document.querySelector('input[name=plan]:checked');
errors += err_highlight(document.querySelector('label[for=id_plan_0]'), !plan);
var ctick = document.getElementById('id_charitable_tick').checked;
var c = document.querySelector('input[name=charitable]:checked');
errors += err_highlight(document.querySelector('label[for=id_charitable_0]'), ctick && !c);
errors += err('id_charity_number', ctick && c && c.value === 'c');
errors += err('id_description', ctick && c && c.value === 'i');
var tandcs = document.getElementById('id_tandcs_tick');
errors += err_highlight(tandcs.parentNode, !tandcs.checked);
if (errors) {
return;
}

plan = plan.value;
var num = 20;
if (plan === 'mapit-100k') {
num = 100;
} else if (plan === 'mapit-0k') {
num = 300;
}
if (ctick) {
c = c.value;
if (c === 'c' || c === 'i') {
if (num === 20) {
num = 0;
} else {
num = num / 2;
}
}
}
if (num === 0) {
form.submit();
return;
}

var email = document.getElementById('id_email')
? document.getElementById('id_email').value
: document.getElementById('js-payment').getAttribute('data-email');

handler.open({
name: 'mySociety',
description: 'Subscribing to plan ' + plan,
zipCode: true,
currency: 'gbp',
allowRememberMe: false,
email: email,
amount: num * 100
});
});

window.addEventListener('popstate', function() {
handler.close();
});
Loading

0 comments on commit 2e4f97a

Please sign in to comment.