diff --git a/conf/general.yml-example b/conf/general.yml-example
index ddb787f..120713d 100644
--- a/conf/general.yml-example
+++ b/conf/general.yml-example
@@ -56,6 +56,12 @@ STRIPE_TAX_RATE: ''
BULK_LOOKUP_AMOUNT: 50
BULK_LOOKUP_PRICE_ID: 'price_123'
+PRICING_TIER_1_ID: 'price_123'
+PRICING_TIER_2_ID: 'price_456'
+PRICING_TIER_3_ID: 'price_789'
+PRICING_TIER_1_AMOUNT: '20'
+PRICING_TIER_2_AMOUNT: '100'
+PRICING_TIER_3_AMOUNT: '300'
# Mapped to Django's DEBUG and TEMPLATE_DEBUG settings. Optional, defaults to True.
DEBUG: True
diff --git a/mapit_mysociety_org/management/commands/add_mapit_user.py b/mapit_mysociety_org/management/commands/add_mapit_user.py
index d0ee52a..bf588a8 100644
--- a/mapit_mysociety_org/management/commands/add_mapit_user.py
+++ b/mapit_mysociety_org/management/commands/add_mapit_user.py
@@ -17,8 +17,8 @@ class Command(BaseCommand):
help = "Create a new user with associated Stripe subscription"
def add_arguments(self, parser):
- prices = stripe.Price.list(limit=100)
- price_ids = [price.id for price in prices.data if price.id.startswith('mapit')]
+ products = stripe.Product.list(limit=100)
+ price_ids = [p.name for p in products if p.name.startswith('MapIt')]
coupons = stripe.Coupon.list()
self.coupon_ids = [coupon['id'] for coupon in coupons if coupon['id'].startswith('charitable')]
parser.add_argument('--email', required=True)
@@ -31,6 +31,9 @@ def handle(self, *args, **options):
coupon = options['coupon']
price = options['price']
+ products = stripe.Product.list(limit=100, expand=['data.default_price'])
+ price = [p for p in products if p.name == price][0].default_price
+
if coupon not in self.coupon_ids:
# coupon ID of the form charitableN(-Nmonths)
m = re.match(r'charitable(\d+)(?:-(\d+)month)?', coupon)
@@ -53,7 +56,11 @@ def handle(self, *args, **options):
customer = stripe.Customer.create(email=email).id
stripe_sub = stripe.Subscription.create(
- customer=customer, items=[{"price": price}], coupon=coupon, trial_period_days=options['trial']).id
+ default_tax_rates=[settings.STRIPE_TAX_RATE],
+ customer=customer,
+ items=[{"price": price.id}],
+ coupon=coupon,
+ trial_period_days=options['trial']).id
sub = Subscription.objects.create(user=user, stripe_id=stripe_sub)
sub.redis_update_max(price)
diff --git a/mapit_mysociety_org/settings.py b/mapit_mysociety_org/settings.py
index ddde5ec..fdee44f 100644
--- a/mapit_mysociety_org/settings.py
+++ b/mapit_mysociety_org/settings.py
@@ -182,8 +182,15 @@ def allow_migrate(self, db, app_label, model_name=None, **hints):
BULK_LOOKUP_PRICE_ID = config.get('BULK_LOOKUP_PRICE_ID')
# API subscriptions
-PRICING = [
- {'id': 'mapit-10k-v', 'price': 20, 'calls': '10,000'},
- {'id': 'mapit-100k-v', 'price': 100, 'calls': '100,000'},
- {'id': 'mapit-0k-v', 'price': 300, 'calls': '0'},
-]
+if 'test' in sys.argv:
+ PRICING = [
+ {'id': 'price_123', 'price': 20, 'calls': '10,000'},
+ {'id': 'price_456', 'price': 100, 'calls': '100,000'},
+ {'id': 'price_789', 'price': 300, 'calls': '0'},
+ ]
+else:
+ PRICING = [
+ {'id': config.get('PRICING_TIER_1_ID'), 'price': config.get('PRICING_TIER_1_AMOUNT'), 'calls': '10,000'},
+ {'id': config.get('PRICING_TIER_2_ID'), 'price': config.get('PRICING_TIER_2_AMOUNT'), 'calls': '100,000'},
+ {'id': config.get('PRICING_TIER_3_ID'), 'price': config.get('PRICING_TIER_3_AMOUNT'), 'calls': '0'},
+ ]
diff --git a/mapit_mysociety_org/templates/account/_form_fields_signup.html b/mapit_mysociety_org/templates/account/_form_fields_signup.html
index 0ae5356..d7f0175 100644
--- a/mapit_mysociety_org/templates/account/_form_fields_signup.html
+++ b/mapit_mysociety_org/templates/account/_form_fields_signup.html
@@ -11,7 +11,12 @@
{% include 'account/_form_field.html' with field=form.password_confirm %}
{% endif %}
-{% include 'account/_form_field.html' with field=form.plan %}
+
+
+{% include 'account/_form_field.html' with field=form.price %}
{% include 'account/_form_field_checkbox.html' with field=form.charitable_tick %}
{% include 'account/_form_field.html' with field=form.charitable %}
diff --git a/mapit_mysociety_org/tests.py b/mapit_mysociety_org/tests.py
index 67e11be..11c99a3 100644
--- a/mapit_mysociety_org/tests.py
+++ b/mapit_mysociety_org/tests.py
@@ -2,7 +2,7 @@
import re
from io import StringIO
-from mock import patch
+from mock import patch, ANY
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
@@ -28,7 +28,7 @@ def test_signup(self, socket):
'password': 'password',
'password_confirm': 'password',
'tandcs_tick': 1,
- 'price': 'mapit-10k-v',
+ 'price': 'price_123',
'charitable_tick': 1,
'charitable': 'c',
'charity_number': '123',
@@ -59,7 +59,7 @@ def test_signup_card_error(self):
'password': 'password',
'password_confirm': 'password',
'tandcs_tick': 1,
- 'price': 'mapit-10k-v',
+ 'price': 'price_123',
'charitable_tick': 1,
'charitable': 'c',
'charity_number': '123',
@@ -71,25 +71,24 @@ def test_signup_card_error(self):
class ManagementTest(PatchedStripeMixin, PatchedRedisTestCase):
def test_add_mapit_user(self):
with patch('mapit_mysociety_org.management.commands.add_mapit_user.stripe', self.MockStripe):
- self.MockStripe.Price.list.return_value = convert_to_stripe_object({
- 'data': [
- {'id': 'mapit-0k-v',
- 'product': {'id': 'prod_GHI', 'name': 'MapIt, unlimited calls'}},
- {'id': 'mapit-10k-v',
- 'product': {'id': 'prod_ABC', 'name': 'MapIt, 10,000 calls'}},
- {'id': 'mapit-100k-v',
- 'product': {'id': 'prod_DEF', 'name': 'MapIt, 100,000 calls'}},
- ]
- }, None, None)
+ self.MockStripe.Product.list.return_value = convert_to_stripe_object([
+ {'id': 'prod_GHI', 'name': 'MapIt, unlimited calls',
+ 'default_price': {'id': 'price_789', 'metadata': {'calls': '0'}}},
+ {'id': 'prod_ABC', 'name': 'MapIt, 10,000 calls',
+ 'default_price': {'id': 'price_123', 'metadata': {'calls': '10000'}}},
+ {'id': 'prod_DEF', 'name': 'MapIt, 100,000 calls',
+ 'default_price': {'id': 'price_456', 'metadata': {'calls': '100000'}}},
+ ], None, None)
call_command(
- 'add_mapit_user', '--email', 'test@example.com', '--price', "mapit-100k-v",
+ 'add_mapit_user', '--email', 'test@example.com', '--price', "MapIt, 100,000 calls",
coupon='charitable25-6months', trial='10',
stdout=StringIO(), stderr=StringIO())
self.MockStripe.Coupon.create.assert_called_once_with(
id='charitable25-6months', duration='repeating', duration_in_months='6', percent_off='25')
self.MockStripe.Subscription.create.assert_called_once_with(
- customer='CUSTOMER-ID', items=[{"price": 'mapit-100k-v'}],
+ customer='CUSTOMER-ID', items=[{"price": 'price_456'}],
+ default_tax_rates=[ANY],
coupon='charitable25-6months', trial_period_days='10')
user = User.objects.get(email='test@example.com')
diff --git a/static/js/payment.js b/static/js/payment.js
index 8bdff27..04753ce 100644
--- a/static/js/payment.js
+++ b/static/js/payment.js
@@ -2,17 +2,13 @@
function price_cost() {
var price = document.querySelector('input[name=price]:checked'),
+ pricing = document.getElementById('js-price-information'),
ctick = document.getElementById('id_charitable_tick'),
c = document.querySelector('input[name=charitable]:checked');
price = price ? price.value : '';
ctick = ctick ? ctick.checked : '';
c = c ? c.value : '';
- var num = 20;
- if (plan === 'mapit-100k-v') {
- num = 100;
- } else if (plan === 'mapit-0k-v') {
- num = 300;
- }
+ var num = pricing.dataset[price] || 20;
if (ctick) {
if (c === 'c' || c === 'i') {
if (num === 20) {
diff --git a/subscriptions/models.py b/subscriptions/models.py
index 8c7d4af..3f3bf38 100644
--- a/subscriptions/models.py
+++ b/subscriptions/models.py
@@ -1,5 +1,3 @@
-import re
-
from django.conf import settings
from django.db import models
from django.dispatch import receiver
@@ -42,8 +40,7 @@ def redis_key_history(self):
return "{0}:history".format(self.redis_key)
def redis_update_max(self, price):
- m = re.match(r'mapit-(\d+)k', price)
- max = int(m.group(1)) * 1000
+ max = int(price.metadata['calls'])
r = redis_connection()
r.set(self.redis_key_max, max)
r.delete(self.redis_key_blocked)
diff --git a/subscriptions/tests.py b/subscriptions/tests.py
index 73d956c..818171f 100644
--- a/subscriptions/tests.py
+++ b/subscriptions/tests.py
@@ -66,9 +66,16 @@ def setUp(self):
'items': {
'data': [{
'price': {
- 'id': 'mapit-0k-v',
+ 'id': 'price_789',
'nickname': 'MapIt, unlimited calls',
'unit_amount': 25000,
+ 'metadata': {
+ 'calls': '0',
+ },
+ 'product': {
+ 'id': 'prod_ABC',
+ 'name': 'MapIt, unlimited calls',
+ },
},
}],
},
@@ -93,7 +100,7 @@ def setUp(self):
'coupon': 'charitable50',
}],
'items': [{
- 'price': 'mapit-0k-v',
+ 'price': 'price_789',
}]
}],
}, None, None)
@@ -158,7 +165,7 @@ def test_add_plan(self):
data.update({'tandcs_tick': True})
form = SubsForm(data=data)
self.assertEqual(form.errors, {"price": ["This field is required.", "You need to submit payment"]})
- data.update({'price': 'mapit-10k-v'})
+ data.update({'price': 'price_123'})
form = SubsForm(data=data)
self.assertEqual(form.errors, {"price": ["You need to submit payment"]})
data.update({'charitable_tick': True, 'charitable': 'c'})
@@ -175,7 +182,7 @@ def test_add_plan(self):
self.assertTrue(form.is_valid())
def test_update_plan(self):
- data = {'price': 'mapit-10k-v'}
+ data = {'price': 'price_123'}
form = SubsForm(stripe=True, data=data)
self.assertEqual(form.errors, {"price": ["You need to submit payment"]})
data.update({'stripeToken': 'TOKEN'})
@@ -183,7 +190,7 @@ def test_update_plan(self):
self.assertTrue(form.is_valid())
def test_update_plan_has_payment(self):
- form = SubsForm(stripe=True, has_payment_data=True, data={'price': 'mapit-0k-v'})
+ form = SubsForm(stripe=True, has_payment_data=True, data={'price': 'price_123'})
self.assertTrue(form.is_valid())
@@ -241,7 +248,7 @@ def test_view_update_page_with_plan(self):
Subscription.objects.create(user=self.user, stripe_id='SUBSCRIPTION-ID')
self.client.login(username="Test user", password="password")
response = self.client.get(reverse('subscription_update'))
- self.assertEqual(response.context['form']['price'].value(), 'mapit-0k-v')
+ self.assertEqual(response.context['form']['price'].value(), 'price_789')
self.assertEqual(bool(response.context['form']['charitable_tick'].value()), True)
self.assertEqual(response.context['form']['tandcs_tick'].value(), None)
self.assertNotContains(response, 'tandcs_tick')
@@ -250,13 +257,13 @@ def test_update_page_no_plan(self):
self.client.login(username="Test user", password="password")
response = self.client.post(reverse('subscription_update'), {
'tandcs_tick': '1',
- 'price': 'mapit-100k-v',
+ 'price': 'price_456',
'stripeToken': 'TOKEN',
}, follow=True)
self.assertRedirects(response, reverse('subscription'))
self.MockStripe.Customer.create.assert_called_once_with(email='test@example.com', source='TOKEN')
self.MockStripe.Subscription.create.assert_called_once_with(
- customer='CUSTOMER-ID', items=[{"price": 'mapit-100k-v'}], coupon=None, default_tax_rates=[ANY],
+ customer='CUSTOMER-ID', items=[{"price": 'price_456'}], coupon=None, default_tax_rates=[ANY],
expand=['latest_invoice.payment_intent'], payment_behavior='allow_incomplete',
metadata={'charitable': '', 'description': '', 'charity_number': '', 'interest_contact': 'No'})
sub = Subscription.objects.get(user=self.user)
@@ -268,7 +275,7 @@ def test_update_page_no_plan_charitable(self):
self.client.login(username="Test user", password="password")
response = self.client.post(reverse('subscription_update'), {
'tandcs_tick': '1',
- 'price': 'mapit-10k-v',
+ 'price': 'price_123',
'charitable_tick': '1',
'charitable': 'c',
'charity_number': '123',
@@ -276,7 +283,7 @@ def test_update_page_no_plan_charitable(self):
self.assertRedirects(response, reverse('subscription'))
self.MockStripe.Customer.create.assert_called_once_with(email='test@example.com')
self.MockStripe.Subscription.create.assert_called_once_with(
- customer='CUSTOMER-ID', items=[{"price": 'mapit-10k-v'}], coupon='charitable100', default_tax_rates=[ANY],
+ customer='CUSTOMER-ID', items=[{"price": 'price_123'}], coupon='charitable100', default_tax_rates=[ANY],
expand=['latest_invoice.payment_intent'], payment_behavior='allow_incomplete',
metadata={'charitable': 'c', 'description': '', 'charity_number': '123', 'interest_contact': 'No'})
sub = Subscription.objects.get(user=self.user)
@@ -288,7 +295,7 @@ def test_update_page_with_plan_add_payment(self):
# Set sub to 10k so this is an upgrade
sub = self.MockStripe.Subscription.retrieve.return_value
sub['items'].data[0].price = convert_to_stripe_object({
- 'id': 'mapit-10k-v',
+ 'id': 'price_123',
'nickname': 'MapIt',
'unit_amount': 10000
})
@@ -300,7 +307,7 @@ def test_update_page_with_plan_add_payment(self):
Subscription.objects.create(user=self.user, stripe_id='SUBSCRIPTION-ID')
self.client.login(username="Test user", password="password")
response = self.client.post(reverse('subscription_update'), {
- 'price': 'mapit-100k-v',
+ 'price': 'price_456',
'payment_method': 'PMPM',
'charitable_tick': 1,
'charitable': 'c',
@@ -313,7 +320,7 @@ def test_update_page_with_plan_add_payment(self):
proration_behavior='always_invoice',
metadata={
'charitable': 'c', 'description': '', 'charity_number': '123', 'interest_contact': 'No'
- }, items=[{"price": 'mapit-100k-v'}])
+ }, items=[{"price": 'price_456'}])
self.client.get(response['Location'])
self.MockStripe.PaymentMethod.attach.assert_called_once_with('PMPM', customer='CUSTOMER-ID')
@@ -324,7 +331,7 @@ def test_update_page_with_plan_upgrade(self):
# Set sub to 10k so this is an upgrade
sub = self.MockStripe.Subscription.retrieve.return_value
sub['items'].data[0].price = convert_to_stripe_object({
- 'id': 'mapit-10k-v',
+ 'id': 'price_123',
'nickname': 'MapIt',
'unit_amount': 1667,
})
@@ -332,7 +339,7 @@ def test_update_page_with_plan_upgrade(self):
Subscription.objects.create(user=self.user, stripe_id='SUBSCRIPTION-ID')
self.client.login(username="Test user", password="password")
response = self.client.post(reverse('subscription_update'), {
- 'price': 'mapit-100k-v',
+ 'price': 'price_456',
'charitable_tick': 1,
'charitable': 'c',
'charity_number': 123,
@@ -345,13 +352,13 @@ def test_update_page_with_plan_upgrade(self):
proration_behavior='always_invoice',
metadata={
'charitable': 'c', 'description': '', 'charity_number': '123', 'interest_contact': 'No'
- }, items=[{"price": 'mapit-100k-v'}])
+ }, items=[{"price": 'price_456'}])
# The real code refetches from stripe after the redirect
# We need to reset the plan and the discount
sub = self.MockStripe.Subscription.retrieve.return_value
sub['items'].data[0].price = convert_to_stripe_object({
- 'id': 'mapit-100k-v',
+ 'id': 'price_456',
'nickname': 'MapIt',
'unit_amount': 7500
})
@@ -364,7 +371,7 @@ def test_update_page_with_plan_downgrade(self):
Subscription.objects.create(user=self.user, stripe_id='SUBSCRIPTION-ID')
self.client.login(username="Test user", password="password")
response = self.client.post(reverse('subscription_update'), {
- 'price': 'mapit-10k-v',
+ 'price': 'price_123',
'charitable_tick': 1,
'charitable': 'c',
'charity_number': 123,
@@ -374,14 +381,14 @@ def test_update_page_with_plan_downgrade(self):
self.MockStripe.SubscriptionSchedule.create.assert_called_once_with(from_subscription='SUBSCRIPTION-ID')
self.MockStripe.SubscriptionSchedule.modify.assert_called_once_with(
'SCHEDULE-ID', phases=[{
- 'items': [{'price': 'mapit-0k-v'}],
+ 'items': [{'price': 'price_789'}],
'start_date': ANY,
'end_date': ANY,
'default_tax_rates': [ANY],
'proration_behavior': 'none',
'discounts': [{'coupon': 'charitable50'}],
}, {
- 'items': [{'price': 'mapit-10k-v'}],
+ 'items': [{'price': 'price_123'}],
'iterations': 1,
'metadata': {'charitable': 'c', 'charity_number': '123', 'description': '', 'interest_contact': 'No'},
'default_tax_rates': [ANY],
@@ -394,7 +401,7 @@ def test_update_page_with_plan_remove_charitable_upgrade(self):
# Set sub to 10k so this is an upgrade
sub = self.MockStripe.Subscription.retrieve.return_value
sub['items'].data[0].price = convert_to_stripe_object({
- 'id': 'mapit-10k-v',
+ 'id': 'price_123',
'nickname': 'MapIt',
'unit_amount': 10000
})
@@ -402,7 +409,7 @@ def test_update_page_with_plan_remove_charitable_upgrade(self):
Subscription.objects.create(user=self.user, stripe_id='SUBSCRIPTION-ID')
self.client.login(username="Test user", password="password")
response = self.client.post(reverse('subscription_update'), {
- 'price': 'mapit-100k-v',
+ 'price': 'price_456',
})
self.assertEqual(response.status_code, 302)
self.MockStripe.Customer.modify.assert_not_called()
@@ -412,7 +419,7 @@ def test_update_page_with_plan_remove_charitable_upgrade(self):
proration_behavior='always_invoice',
metadata={
'charitable': '', 'description': '', 'charity_number': '', 'interest_contact': 'No'
- }, items=[{"price": 'mapit-100k-v'}])
+ }, items=[{"price": 'price_456'}])
# The real code refetches from stripe after the redirect
# We need to reset the plan and the discount
@@ -430,7 +437,7 @@ def test_update_page_with_plan_remove_charitable_downgrade(self):
Subscription.objects.create(user=self.user, stripe_id='SUBSCRIPTION-ID')
self.client.login(username="Test user", password="password")
response = self.client.post(reverse('subscription_update'), {
- 'price': 'mapit-100k-v',
+ 'price': 'price_456',
})
self.assertEqual(response.status_code, 302)
self.MockStripe.Customer.modify.assert_not_called()
@@ -438,14 +445,14 @@ def test_update_page_with_plan_remove_charitable_downgrade(self):
self.MockStripe.SubscriptionSchedule.create.assert_called_once_with(from_subscription='SUBSCRIPTION-ID')
self.MockStripe.SubscriptionSchedule.modify.assert_called_once_with(
'SCHEDULE-ID', phases=[{
- 'items': [{'price': 'mapit-0k-v'}],
+ 'items': [{'price': 'price_789'}],
'start_date': ANY,
'end_date': ANY,
'default_tax_rates': [ANY],
'proration_behavior': 'none',
'discounts': [{'coupon': 'charitable50'}],
}, {
- 'items': [{'price': 'mapit-100k-v'}],
+ 'items': [{'price': 'price_456'}],
'iterations': 1,
'metadata': {'charitable': '', 'charity_number': '', 'description': '', 'interest_contact': 'No'},
'default_tax_rates': [ANY],
@@ -514,7 +521,8 @@ def test_subscription_updated(self):
'id': 'ID',
'items': {'data': [{
"price": {
- 'id': 'mapit-10k-v',
+ 'id': 'price_123',
+ 'metadata': {'calls': 10000},
}
}]}
}}
diff --git a/subscriptions/views.py b/subscriptions/views.py
index 046bf85..9068917 100644
--- a/subscriptions/views.py
+++ b/subscriptions/views.py
@@ -407,8 +407,8 @@ def stripe_mapit_sub(invoice):
# If the invoice doesn't have a subscription, ignore it
if not invoice.subscription:
return False
- stripe_sub = stripe.Subscription.retrieve(invoice.subscription)
- return stripe_sub['items'].data[0].price.id.startswith('mapit')
+ stripe_sub = stripe.Subscription.retrieve(invoice.subscription, expand=['items.data.price.product'])
+ return stripe_sub['items'].data[0].price.product.name.startswith('MapIt')
def stripe_reset_quota(subscription):
@@ -436,7 +436,7 @@ def stripe_hook(request):
elif event.type == 'customer.subscription.updated':
try:
sub = Subscription.objects.get(stripe_id=obj.id)
- sub.redis_update_max(obj['items'].data[0].price.id)
+ sub.redis_update_max(obj['items'].data[0].price)
except Subscription.DoesNotExist: # pragma: no cover
pass
elif event.type == 'invoice.payment_failed' and obj.billing_reason == 'subscription_cycle' \
@@ -459,7 +459,7 @@ def stripe_hook(request):
try:
stripe_sub = stripe.Subscription.retrieve(obj.subscription)
mapit_sub = Subscription.objects.get(stripe_id=obj.subscription)
- mapit_sub.redis_update_max(stripe_sub['items'].data[0].price.id)
+ mapit_sub.redis_update_max(stripe_sub['items'].data[0].price)
except Subscription.DoesNotExist:
pass
try: