mirror of
https://github.com/healthchecks/healthchecks.git
synced 2025-04-07 14:15:34 +00:00
Billing overhaul.
This commit is contained in:
parent
56aa1b2b1f
commit
01c3a13922
27 changed files with 1426 additions and 787 deletions
20
hc/payments/migrations/0003_subscription_address_id.py
Normal file
20
hc/payments/migrations/0003_subscription_address_id.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.6 on 2018-01-07 16:29
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('payments', '0002_subscription_plan_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='subscription',
|
||||
name='address_id',
|
||||
field=models.CharField(blank=True, max_length=2),
|
||||
),
|
||||
]
|
|
@ -10,6 +10,11 @@ else:
|
|||
braintree = None
|
||||
|
||||
|
||||
ADDRESS_KEYS = ("first_name", "last_name", "company", "street_address",
|
||||
"extended_address", "locality", "region", "postal_code",
|
||||
"country_code_alpha2")
|
||||
|
||||
|
||||
class SubscriptionManager(models.Manager):
|
||||
|
||||
def for_user(self, user):
|
||||
|
@ -23,6 +28,7 @@ class Subscription(models.Model):
|
|||
payment_method_token = models.CharField(max_length=35, blank=True)
|
||||
subscription_id = models.CharField(max_length=10, blank=True)
|
||||
plan_id = models.CharField(max_length=10, blank=True)
|
||||
address_id = models.CharField(max_length=2, blank=True)
|
||||
|
||||
objects = SubscriptionManager()
|
||||
|
||||
|
@ -46,11 +52,99 @@ class Subscription(models.Model):
|
|||
|
||||
raise NotImplementedError("Unexpected plan: %s" % self.plan_id)
|
||||
|
||||
def _get_braintree_payment_method(self):
|
||||
@property
|
||||
def payment_method(self):
|
||||
if not self.payment_method_token:
|
||||
return None
|
||||
|
||||
if not hasattr(self, "_pm"):
|
||||
self._pm = braintree.PaymentMethod.find(self.payment_method_token)
|
||||
return self._pm
|
||||
|
||||
def _get_braintree_subscription(self):
|
||||
if not hasattr(self, "_sub"):
|
||||
self._sub = braintree.Subscription.find(self.subscription_id)
|
||||
return self._sub
|
||||
|
||||
def get_client_token(self):
|
||||
return braintree.ClientToken.generate({
|
||||
"customer_id": self.customer_id
|
||||
})
|
||||
|
||||
def update_payment_method(self, nonce):
|
||||
# Create customer record if it does not exist:
|
||||
if not self.customer_id:
|
||||
result = braintree.Customer.create({
|
||||
"email": self.user.email
|
||||
})
|
||||
if not result.is_success:
|
||||
return result
|
||||
|
||||
self.customer_id = result.customer.id
|
||||
self.save()
|
||||
|
||||
# Create payment method
|
||||
result = braintree.PaymentMethod.create({
|
||||
"customer_id": self.customer_id,
|
||||
"payment_method_nonce": nonce,
|
||||
"options": {"make_default": True}
|
||||
})
|
||||
|
||||
if not result.is_success:
|
||||
return result
|
||||
|
||||
self.payment_method_token = result.payment_method.token
|
||||
self.save()
|
||||
|
||||
# Update an existing subscription to use this payment method
|
||||
if self.subscription_id:
|
||||
result = braintree.Subscription.update(self.subscription_id, {
|
||||
"payment_method_token": self.payment_method_token
|
||||
})
|
||||
|
||||
if not result.is_success:
|
||||
return result
|
||||
|
||||
def update_address(self, post_data):
|
||||
# Create customer record if it does not exist:
|
||||
if not self.customer_id:
|
||||
result = braintree.Customer.create({
|
||||
"email": self.user.email
|
||||
})
|
||||
if not result.is_success:
|
||||
return result
|
||||
|
||||
self.customer_id = result.customer.id
|
||||
self.save()
|
||||
|
||||
payload = {key: str(post_data.get(key)) for key in ADDRESS_KEYS}
|
||||
if self.address_id:
|
||||
result = braintree.Address.update(self.customer_id,
|
||||
self.address_id,
|
||||
payload)
|
||||
else:
|
||||
payload["customer_id"] = self.customer_id
|
||||
result = braintree.Address.create(payload)
|
||||
if result.is_success:
|
||||
self.address_id = result.address.id
|
||||
self.save()
|
||||
|
||||
if not result.is_success:
|
||||
return result
|
||||
|
||||
def setup(self, plan_id):
|
||||
result = braintree.Subscription.create({
|
||||
"payment_method_token": self.payment_method_token,
|
||||
"plan_id": plan_id
|
||||
})
|
||||
|
||||
if result.is_success:
|
||||
self.subscription_id = result.subscription.id
|
||||
self.plan_id = plan_id
|
||||
self.save()
|
||||
|
||||
return result
|
||||
|
||||
def cancel(self):
|
||||
if self.subscription_id:
|
||||
braintree.Subscription.cancel(self.subscription_id)
|
||||
|
@ -59,22 +153,42 @@ class Subscription(models.Model):
|
|||
self.plan_id = ""
|
||||
self.save()
|
||||
|
||||
def pm_is_credit_card(self):
|
||||
return isinstance(self._get_braintree_payment_method(),
|
||||
braintree.credit_card.CreditCard)
|
||||
def pm_is_card(self):
|
||||
pm = self.payment_method
|
||||
return isinstance(pm, braintree.credit_card.CreditCard)
|
||||
|
||||
def pm_is_paypal(self):
|
||||
return isinstance(self._get_braintree_payment_method(),
|
||||
braintree.paypal_account.PayPalAccount)
|
||||
pm = self.payment_method
|
||||
return isinstance(pm, braintree.paypal_account.PayPalAccount)
|
||||
|
||||
def card_type(self):
|
||||
o = self._get_braintree_payment_method()
|
||||
return o.card_type
|
||||
def next_billing_date(self):
|
||||
o = self._get_braintree_subscription()
|
||||
return o.next_billing_date
|
||||
|
||||
def last_4(self):
|
||||
o = self._get_braintree_payment_method()
|
||||
return o.last_4
|
||||
@property
|
||||
def address(self):
|
||||
if not hasattr(self, "_address"):
|
||||
try:
|
||||
self._address = braintree.Address.find(self.customer_id,
|
||||
self.address_id)
|
||||
except braintree.exceptions.NotFoundError:
|
||||
self._address = None
|
||||
|
||||
def paypal_email(self):
|
||||
o = self._get_braintree_payment_method()
|
||||
return o.email
|
||||
return self._address
|
||||
|
||||
@property
|
||||
def transactions(self):
|
||||
if not hasattr(self, "_tx"):
|
||||
if not self.customer_id:
|
||||
self._tx = []
|
||||
else:
|
||||
self._tx = list(braintree.Transaction.search(braintree.TransactionSearch.customer_id == self.customer_id))
|
||||
|
||||
return self._tx
|
||||
|
||||
def get_transaction(self, transaction_id):
|
||||
tx = braintree.Transaction.find(transaction_id)
|
||||
if tx.customer_details.id != self.customer_id:
|
||||
return None
|
||||
|
||||
return tx
|
||||
|
|
69
hc/payments/tests/test_address.py
Normal file
69
hc/payments/tests/test_address.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
from mock import patch
|
||||
|
||||
from hc.payments.models import Subscription
|
||||
from hc.test import BaseTestCase
|
||||
|
||||
|
||||
class AddressTestCase(BaseTestCase):
|
||||
|
||||
@patch("hc.payments.models.braintree")
|
||||
def test_it_retrieves_address(self, mock):
|
||||
mock.Address.find.return_value = {"company": "FooCo"}
|
||||
|
||||
self.sub = Subscription(user=self.alice)
|
||||
self.sub.address_id = "aa"
|
||||
self.sub.save()
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.get("/accounts/profile/billing/address/")
|
||||
self.assertContains(r, "FooCo")
|
||||
|
||||
@patch("hc.payments.models.braintree")
|
||||
def test_it_creates_address(self, mock):
|
||||
mock.Address.create.return_value.is_success = True
|
||||
mock.Address.create.return_value.address.id = "bb"
|
||||
|
||||
self.sub = Subscription(user=self.alice)
|
||||
self.sub.customer_id = "test-customer"
|
||||
self.sub.save()
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
form = {"company": "BarCo"}
|
||||
r = self.client.post("/accounts/profile/billing/address/", form)
|
||||
|
||||
self.assertRedirects(r, "/accounts/profile/billing/")
|
||||
self.sub.refresh_from_db()
|
||||
self.assertEqual(self.sub.address_id, "bb")
|
||||
|
||||
@patch("hc.payments.models.braintree")
|
||||
def test_it_updates_address(self, mock):
|
||||
mock.Address.update.return_value.is_success = True
|
||||
|
||||
self.sub = Subscription(user=self.alice)
|
||||
self.sub.customer_id = "test-customer"
|
||||
self.sub.address_id = "aa"
|
||||
self.sub.save()
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
form = {"company": "BarCo"}
|
||||
r = self.client.post("/accounts/profile/billing/address/", form)
|
||||
|
||||
self.assertRedirects(r, "/accounts/profile/billing/")
|
||||
|
||||
@patch("hc.payments.models.braintree")
|
||||
def test_it_creates_customer(self, mock):
|
||||
mock.Address.create.return_value.is_success = True
|
||||
mock.Address.create.return_value.address.id = "bb"
|
||||
|
||||
mock.Customer.create.return_value.is_success = True
|
||||
mock.Customer.create.return_value.customer.id = "test-customer-id"
|
||||
|
||||
self.sub = Subscription(user=self.alice)
|
||||
self.sub.save()
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
form = {"company": "BarCo"}
|
||||
self.client.post("/accounts/profile/billing/address/", form)
|
||||
|
||||
self.sub.refresh_from_db()
|
||||
self.assertEqual(self.sub.customer_id, "test-customer-id")
|
|
@ -4,16 +4,16 @@ from hc.payments.models import Subscription
|
|||
from hc.test import BaseTestCase
|
||||
|
||||
|
||||
class BillingTestCase(BaseTestCase):
|
||||
class BillingHistoryTestCase(BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BillingTestCase, self).setUp()
|
||||
super(BillingHistoryTestCase, self).setUp()
|
||||
self.sub = Subscription(user=self.alice)
|
||||
self.sub.subscription_id = "test-id"
|
||||
self.sub.customer_id = "test-customer-id"
|
||||
self.sub.save()
|
||||
|
||||
@patch("hc.payments.views.braintree")
|
||||
@patch("hc.payments.models.braintree")
|
||||
def test_it_works(self, mock_braintree):
|
||||
|
||||
m1 = Mock(id="abc123", amount=123)
|
||||
|
@ -21,14 +21,6 @@ class BillingTestCase(BaseTestCase):
|
|||
mock_braintree.Transaction.search.return_value = [m1, m2]
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.get("/billing/")
|
||||
r = self.client.get("/accounts/profile/billing/history/")
|
||||
self.assertContains(r, "123")
|
||||
self.assertContains(r, "def456")
|
||||
|
||||
def test_it_saves_company_details(self):
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.post("/billing/", {"bill_to": "foo\nbar"})
|
||||
|
||||
self.assertEqual(r.status_code, 302)
|
||||
self.profile.refresh_from_db()
|
||||
self.assertEqual(self.profile.bill_to, "foo\nbar")
|
|
@ -1,38 +0,0 @@
|
|||
from mock import patch
|
||||
|
||||
from hc.accounts.models import Profile
|
||||
from hc.payments.models import Subscription
|
||||
from hc.test import BaseTestCase
|
||||
|
||||
|
||||
class CancelPlanTestCase(BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(CancelPlanTestCase, self).setUp()
|
||||
self.sub = Subscription(user=self.alice)
|
||||
self.sub.subscription_id = "test-id"
|
||||
self.sub.plan_id = "P5"
|
||||
self.sub.save()
|
||||
|
||||
self.profile.ping_log_limit = 1000
|
||||
self.profile.check_limit = 500
|
||||
self.profile.sms_limit = 50
|
||||
self.profile.save()
|
||||
|
||||
@patch("hc.payments.models.braintree")
|
||||
def test_it_works(self, mock_braintree):
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.post("/pricing/cancel_plan/")
|
||||
self.assertRedirects(r, "/pricing/")
|
||||
|
||||
self.sub.refresh_from_db()
|
||||
self.assertEqual(self.sub.subscription_id, "")
|
||||
self.assertEqual(self.sub.plan_id, "")
|
||||
|
||||
# User's profile should have standard limits
|
||||
profile = Profile.objects.get(user=self.alice)
|
||||
self.assertEqual(profile.ping_log_limit, 100)
|
||||
self.assertEqual(profile.check_limit, 20)
|
||||
self.assertEqual(profile.team_limit, 2)
|
||||
self.assertEqual(profile.sms_limit, 0)
|
|
@ -6,7 +6,7 @@ from hc.test import BaseTestCase
|
|||
|
||||
class GetClientTokenTestCase(BaseTestCase):
|
||||
|
||||
@patch("hc.payments.views.braintree")
|
||||
@patch("hc.payments.models.braintree")
|
||||
def test_it_works(self, mock_braintree):
|
||||
mock_braintree.ClientToken.generate.return_value = "test-token"
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
from mock import Mock, patch
|
||||
|
||||
from hc.payments.models import Subscription
|
||||
from hc.test import BaseTestCase
|
||||
|
||||
|
||||
class InvoiceTestCase(BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(InvoiceTestCase, self).setUp()
|
||||
self.sub = Subscription(user=self.alice)
|
||||
self.sub.subscription_id = "test-id"
|
||||
self.sub.customer_id = "test-customer-id"
|
||||
self.sub.save()
|
||||
|
||||
@patch("hc.payments.views.braintree")
|
||||
def test_it_works(self, mock_braintree):
|
||||
|
||||
tx = Mock()
|
||||
tx.id = "abc123"
|
||||
tx.customer_details.id = "test-customer-id"
|
||||
tx.created_at = None
|
||||
mock_braintree.Transaction.find.return_value = tx
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.get("/invoice/abc123/")
|
||||
self.assertContains(r, "ABC123") # tx.id in uppercase
|
||||
self.assertContains(r, "alice@example.org") # bill to
|
||||
|
||||
@patch("hc.payments.views.braintree")
|
||||
def test_it_checks_customer_id(self, mock_braintree):
|
||||
|
||||
tx = Mock()
|
||||
tx.id = "abc123"
|
||||
tx.customer_details.id = "test-another-customer-id"
|
||||
tx.created_at = None
|
||||
mock_braintree.Transaction.find.return_value = tx
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.get("/invoice/abc123/")
|
||||
self.assertEqual(r.status_code, 403)
|
||||
|
||||
@patch("hc.payments.views.braintree")
|
||||
def test_it_shows_company_data(self, mock_braintree):
|
||||
self.profile.bill_to = "Alice and Partners"
|
||||
self.profile.save()
|
||||
|
||||
tx = Mock()
|
||||
tx.id = "abc123"
|
||||
tx.customer_details.id = "test-customer-id"
|
||||
tx.created_at = None
|
||||
mock_braintree.Transaction.find.return_value = tx
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.get("/invoice/abc123/")
|
||||
self.assertContains(r, "Alice and Partners")
|
94
hc/payments/tests/test_payment_method.py
Normal file
94
hc/payments/tests/test_payment_method.py
Normal file
|
@ -0,0 +1,94 @@
|
|||
from mock import patch
|
||||
|
||||
from hc.payments.models import Subscription
|
||||
from hc.test import BaseTestCase
|
||||
|
||||
|
||||
class UpdatePaymentMethodTestCase(BaseTestCase):
|
||||
|
||||
def _setup_mock(self, mock):
|
||||
""" Set up Braintree calls that the controller will use. """
|
||||
|
||||
mock.PaymentMethod.create.return_value.is_success = True
|
||||
mock.PaymentMethod.create.return_value.payment_method.token = "fake"
|
||||
|
||||
@patch("hc.payments.models.braintree")
|
||||
def test_it_retrieves_paypal(self, mock):
|
||||
self._setup_mock(mock)
|
||||
|
||||
mock.paypal_account.PayPalAccount = dict
|
||||
mock.credit_card.CreditCard = list
|
||||
mock.PaymentMethod.find.return_value = {"email": "foo@example.org"}
|
||||
|
||||
self.sub = Subscription(user=self.alice)
|
||||
self.sub.payment_method_token = "fake-token"
|
||||
self.sub.save()
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.get("/accounts/profile/billing/payment_method/")
|
||||
self.assertContains(r, "foo@example.org")
|
||||
|
||||
@patch("hc.payments.models.braintree")
|
||||
def test_it_retrieves_cc(self, mock):
|
||||
self._setup_mock(mock)
|
||||
|
||||
mock.paypal_account.PayPalAccount = list
|
||||
mock.credit_card.CreditCard = dict
|
||||
mock.PaymentMethod.find.return_value = {"masked_number": "1***2"}
|
||||
|
||||
self.sub = Subscription(user=self.alice)
|
||||
self.sub.payment_method_token = "fake-token"
|
||||
self.sub.save()
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
r = self.client.get("/accounts/profile/billing/payment_method/")
|
||||
self.assertContains(r, "1***2")
|
||||
|
||||
@patch("hc.payments.models.braintree")
|
||||
def test_it_creates_payment_method(self, mock):
|
||||
self._setup_mock(mock)
|
||||
|
||||
self.sub = Subscription(user=self.alice)
|
||||
self.sub.customer_id = "test-customer"
|
||||
self.sub.save()
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
form = {"payment_method_nonce": "test-nonce"}
|
||||
r = self.client.post("/accounts/profile/billing/payment_method/", form)
|
||||
|
||||
self.assertRedirects(r, "/accounts/profile/billing/")
|
||||
|
||||
@patch("hc.payments.models.braintree")
|
||||
def test_it_creates_customer(self, mock):
|
||||
self._setup_mock(mock)
|
||||
|
||||
mock.Customer.create.return_value.is_success = True
|
||||
mock.Customer.create.return_value.customer.id = "test-customer-id"
|
||||
|
||||
self.sub = Subscription(user=self.alice)
|
||||
self.sub.save()
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
form = {"payment_method_nonce": "test-nonce"}
|
||||
self.client.post("/accounts/profile/billing/payment_method/", form)
|
||||
|
||||
self.sub.refresh_from_db()
|
||||
self.assertEqual(self.sub.customer_id, "test-customer-id")
|
||||
|
||||
@patch("hc.payments.models.braintree")
|
||||
def test_it_updates_subscription(self, mock):
|
||||
self._setup_mock(mock)
|
||||
|
||||
self.sub = Subscription(user=self.alice)
|
||||
self.sub.customer_id = "test-customer"
|
||||
self.sub.subscription_id = "fake-id"
|
||||
self.sub.save()
|
||||
|
||||
mock.Customer.create.return_value.is_success = True
|
||||
mock.Customer.create.return_value.customer.id = "test-customer-id"
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
form = {"payment_method_nonce": "test-nonce"}
|
||||
self.client.post("/accounts/profile/billing/payment_method/", form)
|
||||
|
||||
self.assertTrue(mock.Subscription.update.called)
|
|
@ -31,7 +31,7 @@ class PdfInvoiceTestCase(BaseTestCase):
|
|||
self.tx.subscription_details.billing_period_end_date = now()
|
||||
|
||||
@skipIf(reportlab is None, "reportlab not installed")
|
||||
@patch("hc.payments.views.braintree")
|
||||
@patch("hc.payments.models.braintree")
|
||||
def test_it_works(self, mock_braintree):
|
||||
mock_braintree.Transaction.find.return_value = self.tx
|
||||
|
||||
|
@ -40,7 +40,7 @@ class PdfInvoiceTestCase(BaseTestCase):
|
|||
self.assertTrue(six.b("ABC123") in r.content)
|
||||
self.assertTrue(six.b("alice@example.org") in r.content)
|
||||
|
||||
@patch("hc.payments.views.braintree")
|
||||
@patch("hc.payments.models.braintree")
|
||||
def test_it_checks_customer_id(self, mock_braintree):
|
||||
|
||||
tx = Mock()
|
||||
|
@ -54,7 +54,7 @@ class PdfInvoiceTestCase(BaseTestCase):
|
|||
self.assertEqual(r.status_code, 403)
|
||||
|
||||
@skipIf(reportlab is None, "reportlab not installed")
|
||||
@patch("hc.payments.views.braintree")
|
||||
@patch("hc.payments.models.braintree")
|
||||
def test_it_shows_company_data(self, mock_braintree):
|
||||
self.profile.bill_to = "Alice and Partners"
|
||||
self.profile.save()
|
||||
|
|
|
@ -4,26 +4,20 @@ from hc.payments.models import Subscription
|
|||
from hc.test import BaseTestCase
|
||||
|
||||
|
||||
class CreatePlanTestCase(BaseTestCase):
|
||||
class SetPlanTestCase(BaseTestCase):
|
||||
|
||||
def _setup_mock(self, mock):
|
||||
""" Set up Braintree calls that the controller will use. """
|
||||
|
||||
mock.Customer.create.return_value.is_success = True
|
||||
mock.Customer.create.return_value.customer.id = "test-customer-id"
|
||||
|
||||
mock.PaymentMethod.create.return_value.is_success = True
|
||||
mock.PaymentMethod.create.return_value.payment_method.token = "t-token"
|
||||
|
||||
mock.Subscription.create.return_value.is_success = True
|
||||
mock.Subscription.create.return_value.subscription.id = "t-sub-id"
|
||||
|
||||
def run_create_plan(self, plan_id="P5"):
|
||||
form = {"plan_id": plan_id, "payment_method_nonce": "test-nonce"}
|
||||
def run_set_plan(self, plan_id="P5"):
|
||||
form = {"plan_id": plan_id}
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
return self.client.post("/pricing/create_plan/", form, follow=True)
|
||||
return self.client.post("/pricing/set_plan/", form, follow=True)
|
||||
|
||||
@patch("hc.payments.views.braintree")
|
||||
@patch("hc.payments.models.braintree")
|
||||
def test_it_works(self, mock):
|
||||
self._setup_mock(mock)
|
||||
|
||||
|
@ -31,13 +25,11 @@ class CreatePlanTestCase(BaseTestCase):
|
|||
self.profile.sms_sent = 1
|
||||
self.profile.save()
|
||||
|
||||
r = self.run_create_plan()
|
||||
self.assertRedirects(r, "/pricing/")
|
||||
r = self.run_set_plan()
|
||||
self.assertRedirects(r, "/accounts/profile/billing/")
|
||||
|
||||
# Subscription should be filled out:
|
||||
sub = Subscription.objects.get(user=self.alice)
|
||||
self.assertEqual(sub.customer_id, "test-customer-id")
|
||||
self.assertEqual(sub.payment_method_token, "t-token")
|
||||
self.assertEqual(sub.subscription_id, "t-sub-id")
|
||||
self.assertEqual(sub.plan_id, "P5")
|
||||
|
||||
|
@ -52,7 +44,7 @@ class CreatePlanTestCase(BaseTestCase):
|
|||
# braintree.Subscription.cancel should have not been called
|
||||
assert not mock.Subscription.cancel.called
|
||||
|
||||
@patch("hc.payments.views.braintree")
|
||||
@patch("hc.payments.models.braintree")
|
||||
def test_yearly_works(self, mock):
|
||||
self._setup_mock(mock)
|
||||
|
||||
|
@ -60,13 +52,11 @@ class CreatePlanTestCase(BaseTestCase):
|
|||
self.profile.sms_sent = 1
|
||||
self.profile.save()
|
||||
|
||||
r = self.run_create_plan("Y48")
|
||||
self.assertRedirects(r, "/pricing/")
|
||||
r = self.run_set_plan("Y48")
|
||||
self.assertRedirects(r, "/accounts/profile/billing/")
|
||||
|
||||
# Subscription should be filled out:
|
||||
sub = Subscription.objects.get(user=self.alice)
|
||||
self.assertEqual(sub.customer_id, "test-customer-id")
|
||||
self.assertEqual(sub.payment_method_token, "t-token")
|
||||
self.assertEqual(sub.subscription_id, "t-sub-id")
|
||||
self.assertEqual(sub.plan_id, "Y48")
|
||||
|
||||
|
@ -81,11 +71,41 @@ class CreatePlanTestCase(BaseTestCase):
|
|||
# braintree.Subscription.cancel should have not been called
|
||||
assert not mock.Subscription.cancel.called
|
||||
|
||||
@patch("hc.payments.models.braintree")
|
||||
def test_it_cancels(self, mock):
|
||||
self._setup_mock(mock)
|
||||
|
||||
self.sub = Subscription(user=self.alice)
|
||||
self.sub.subscription_id = "test-id"
|
||||
self.sub.plan_id = "P5"
|
||||
self.sub.save()
|
||||
|
||||
self.profile.sms_limit = 1
|
||||
self.profile.sms_sent = 1
|
||||
self.profile.save()
|
||||
|
||||
r = self.run_set_plan("")
|
||||
self.assertRedirects(r, "/accounts/profile/billing/")
|
||||
|
||||
# Subscription should be cleared
|
||||
sub = Subscription.objects.get(user=self.alice)
|
||||
self.assertEqual(sub.subscription_id, "")
|
||||
self.assertEqual(sub.plan_id, "")
|
||||
|
||||
# User's profile should have standard limits
|
||||
self.profile.refresh_from_db()
|
||||
self.assertEqual(self.profile.ping_log_limit, 100)
|
||||
self.assertEqual(self.profile.check_limit, 20)
|
||||
self.assertEqual(self.profile.team_limit, 2)
|
||||
self.assertEqual(self.profile.sms_limit, 0)
|
||||
|
||||
assert mock.Subscription.cancel.called
|
||||
|
||||
def test_bad_plan_id(self):
|
||||
r = self.run_create_plan(plan_id="this-is-wrong")
|
||||
r = self.run_set_plan(plan_id="this-is-wrong")
|
||||
self.assertEqual(r.status_code, 400)
|
||||
|
||||
@patch("hc.payments.views.braintree")
|
||||
@patch("hc.payments.models.braintree")
|
||||
def test_it_cancels_previous_subscription(self, mock):
|
||||
self._setup_mock(mock)
|
||||
|
||||
|
@ -93,39 +113,17 @@ class CreatePlanTestCase(BaseTestCase):
|
|||
sub.subscription_id = "prev-sub"
|
||||
sub.save()
|
||||
|
||||
r = self.run_create_plan()
|
||||
self.assertRedirects(r, "/pricing/")
|
||||
r = self.run_set_plan()
|
||||
self.assertRedirects(r, "/accounts/profile/billing/")
|
||||
assert mock.Subscription.cancel.called
|
||||
|
||||
@patch("hc.payments.views.braintree")
|
||||
def test_customer_creation_failure(self, mock):
|
||||
self._setup_mock(mock)
|
||||
|
||||
mock.Customer.create.return_value.is_success = False
|
||||
mock.Customer.create.return_value.message = "Test Failure"
|
||||
|
||||
r = self.run_create_plan()
|
||||
self.assertRedirects(r, "/pricing/")
|
||||
self.assertContains(r, "Test Failure")
|
||||
|
||||
@patch("hc.payments.views.braintree")
|
||||
def test_pm_creation_failure(self, mock):
|
||||
self._setup_mock(mock)
|
||||
|
||||
mock.PaymentMethod.create.return_value.is_success = False
|
||||
mock.PaymentMethod.create.return_value.message = "pm failure"
|
||||
|
||||
r = self.run_create_plan()
|
||||
self.assertRedirects(r, "/pricing/")
|
||||
self.assertContains(r, "pm failure")
|
||||
|
||||
@patch("hc.payments.views.braintree")
|
||||
@patch("hc.payments.models.braintree")
|
||||
def test_subscription_creation_failure(self, mock):
|
||||
self._setup_mock(mock)
|
||||
|
||||
mock.Subscription.create.return_value.is_success = False
|
||||
mock.Subscription.create.return_value.message = "sub failure"
|
||||
|
||||
r = self.run_create_plan()
|
||||
self.assertRedirects(r, "/pricing/")
|
||||
r = self.run_set_plan()
|
||||
self.assertRedirects(r, "/accounts/profile/billing/")
|
||||
self.assertContains(r, "sub failure")
|
|
@ -7,29 +7,29 @@ urlpatterns = [
|
|||
views.pricing,
|
||||
name="hc-pricing"),
|
||||
|
||||
url(r'^billing/$',
|
||||
url(r'^accounts/profile/billing/$',
|
||||
views.billing,
|
||||
name="hc-billing"),
|
||||
|
||||
url(r'^invoice/([\w-]+)/$',
|
||||
views.invoice,
|
||||
name="hc-invoice"),
|
||||
url(r'^accounts/profile/billing/history/$',
|
||||
views.billing_history,
|
||||
name="hc-billing-history"),
|
||||
|
||||
url(r'^accounts/profile/billing/address/$',
|
||||
views.address,
|
||||
name="hc-billing-address"),
|
||||
|
||||
url(r'^accounts/profile/billing/payment_method/$',
|
||||
views.payment_method,
|
||||
name="hc-payment-method"),
|
||||
|
||||
url(r'^invoice/pdf/([\w-]+)/$',
|
||||
views.pdf_invoice,
|
||||
name="hc-invoice-pdf"),
|
||||
|
||||
url(r'^pricing/create_plan/$',
|
||||
views.create_plan,
|
||||
name="hc-create-plan"),
|
||||
|
||||
url(r'^pricing/update_payment_method/$',
|
||||
views.update_payment_method,
|
||||
name="hc-update-payment-method"),
|
||||
|
||||
url(r'^pricing/cancel_plan/$',
|
||||
views.cancel_plan,
|
||||
name="hc-cancel-plan"),
|
||||
url(r'^pricing/set_plan/$',
|
||||
views.set_plan,
|
||||
name="hc-set-plan"),
|
||||
|
||||
url(r'^pricing/get_client_token/$',
|
||||
views.get_client_token,
|
||||
|
|
|
@ -1,31 +1,20 @@
|
|||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.http import (HttpResponseBadRequest, HttpResponseForbidden,
|
||||
JsonResponse, HttpResponse)
|
||||
from django.shortcuts import redirect, render
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.template.loader import render_to_string
|
||||
from django.views.decorators.http import require_POST
|
||||
|
||||
from hc.payments.forms import BillToForm
|
||||
from hc.api.models import Check
|
||||
from hc.payments.invoices import PdfInvoice
|
||||
from hc.payments.models import Subscription
|
||||
|
||||
if settings.USE_PAYMENTS:
|
||||
import braintree
|
||||
else:
|
||||
# hc.payments tests mock this object, so tests should
|
||||
# still be able to run:
|
||||
braintree = None
|
||||
|
||||
|
||||
@login_required
|
||||
def get_client_token(request):
|
||||
sub = Subscription.objects.for_user(request.user)
|
||||
client_token = braintree.ClientToken.generate({
|
||||
"customer_id": sub.customer_id
|
||||
})
|
||||
|
||||
return JsonResponse({"client_token": client_token})
|
||||
return JsonResponse({"client_token": sub.get_client_token()})
|
||||
|
||||
|
||||
def pricing(request):
|
||||
|
@ -33,24 +22,31 @@ def pricing(request):
|
|||
ctx = {"page": "pricing"}
|
||||
return render(request, "payments/pricing_not_owner.html", ctx)
|
||||
|
||||
sub = None
|
||||
if request.user.is_authenticated:
|
||||
# Don't use Subscription.objects.for_user method here, so a
|
||||
# subscription object is not created just by viewing a page.
|
||||
sub = Subscription.objects.filter(user_id=request.user.id).first()
|
||||
ctx = {"page": "pricing"}
|
||||
return render(request, "payments/pricing.html", ctx)
|
||||
|
||||
period = "monthly"
|
||||
if sub and sub.plan_id.startswith("Y"):
|
||||
period = "annual"
|
||||
|
||||
@login_required
|
||||
def billing(request):
|
||||
if request.team != request.profile:
|
||||
request.team = request.profile
|
||||
request.profile.current_team = request.profile
|
||||
request.profile.save()
|
||||
|
||||
# Don't use Subscription.objects.for_user method here, so a
|
||||
# subscription object is not created just by viewing a page.
|
||||
sub = Subscription.objects.filter(user_id=request.user.id).first()
|
||||
|
||||
ctx = {
|
||||
"page": "pricing",
|
||||
"page": "billing",
|
||||
"profile": request.profile,
|
||||
"sub": sub,
|
||||
"period": period,
|
||||
"first_charge": request.session.pop("first_charge", False)
|
||||
"num_checks": Check.objects.filter(user=request.user).count(),
|
||||
"team_size": request.profile.member_set.count() + 1,
|
||||
"team_max": request.profile.team_limit + 1
|
||||
}
|
||||
|
||||
return render(request, "payments/pricing.html", ctx)
|
||||
return render(request, "accounts/billing.html", ctx)
|
||||
|
||||
|
||||
def log_and_bail(request, result):
|
||||
|
@ -63,63 +59,35 @@ def log_and_bail(request, result):
|
|||
if not logged_deep_error:
|
||||
messages.error(request, result.message)
|
||||
|
||||
return redirect("hc-pricing")
|
||||
return redirect("hc-billing")
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
def create_plan(request):
|
||||
def set_plan(request):
|
||||
plan_id = request.POST["plan_id"]
|
||||
if plan_id not in ("P5", "P50", "Y48", "Y480"):
|
||||
if plan_id not in ("", "P5", "P50", "Y48", "Y480"):
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
sub = Subscription.objects.for_user(request.user)
|
||||
if sub.plan_id == plan_id:
|
||||
return redirect("hc-billing")
|
||||
|
||||
# Cancel the previous plan
|
||||
if sub.subscription_id:
|
||||
braintree.Subscription.cancel(sub.subscription_id)
|
||||
sub.subscription_id = ""
|
||||
sub.plan_id = ""
|
||||
sub.save()
|
||||
|
||||
# Create Braintree customer record
|
||||
if not sub.customer_id:
|
||||
result = braintree.Customer.create({
|
||||
"email": request.user.email
|
||||
})
|
||||
if not result.is_success:
|
||||
return log_and_bail(request, result)
|
||||
|
||||
sub.customer_id = result.customer.id
|
||||
sub.save()
|
||||
|
||||
# Create Braintree payment method
|
||||
if "payment_method_nonce" in request.POST:
|
||||
result = braintree.PaymentMethod.create({
|
||||
"customer_id": sub.customer_id,
|
||||
"payment_method_nonce": request.POST["payment_method_nonce"],
|
||||
"options": {"make_default": True}
|
||||
})
|
||||
|
||||
if not result.is_success:
|
||||
return log_and_bail(request, result)
|
||||
|
||||
sub.payment_method_token = result.payment_method.token
|
||||
sub.save()
|
||||
|
||||
# Create Braintree subscription
|
||||
result = braintree.Subscription.create({
|
||||
"payment_method_token": sub.payment_method_token,
|
||||
"plan_id": plan_id,
|
||||
})
|
||||
sub.cancel()
|
||||
if plan_id == "":
|
||||
profile = request.user.profile
|
||||
profile.ping_log_limit = 100
|
||||
profile.check_limit = 20
|
||||
profile.team_limit = 2
|
||||
profile.sms_limit = 0
|
||||
profile.save()
|
||||
return redirect("hc-billing")
|
||||
|
||||
result = sub.setup(plan_id)
|
||||
if not result.is_success:
|
||||
return log_and_bail(request, result)
|
||||
|
||||
sub.subscription_id = result.subscription.id
|
||||
sub.plan_id = plan_id
|
||||
sub.save()
|
||||
|
||||
# Update user's profile
|
||||
profile = request.user.profile
|
||||
if plan_id in ("P5", "Y48"):
|
||||
|
@ -138,100 +106,76 @@ def create_plan(request):
|
|||
profile.save()
|
||||
|
||||
request.session["first_charge"] = True
|
||||
return redirect("hc-pricing")
|
||||
return redirect("hc-billing")
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
def update_payment_method(request):
|
||||
def address(request):
|
||||
sub = Subscription.objects.for_user(request.user)
|
||||
|
||||
if not sub.customer_id or not sub.subscription_id:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
if "payment_method_nonce" not in request.POST:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
result = braintree.PaymentMethod.create({
|
||||
"customer_id": sub.customer_id,
|
||||
"payment_method_nonce": request.POST["payment_method_nonce"],
|
||||
"options": {"make_default": True}
|
||||
})
|
||||
|
||||
if not result.is_success:
|
||||
return log_and_bail(request, result)
|
||||
|
||||
payment_method_token = result.payment_method.token
|
||||
result = braintree.Subscription.update(sub.subscription_id, {
|
||||
"payment_method_token": payment_method_token
|
||||
})
|
||||
|
||||
if not result.is_success:
|
||||
return log_and_bail(request, result)
|
||||
|
||||
sub.payment_method_token = payment_method_token
|
||||
sub.save()
|
||||
|
||||
return redirect("hc-pricing")
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
def cancel_plan(request):
|
||||
sub = Subscription.objects.get(user=request.user)
|
||||
sub.cancel()
|
||||
|
||||
# Revert to default limits--
|
||||
profile = request.user.profile
|
||||
profile.ping_log_limit = 100
|
||||
profile.check_limit = 20
|
||||
profile.team_limit = 2
|
||||
profile.sms_limit = 0
|
||||
profile.save()
|
||||
|
||||
return redirect("hc-pricing")
|
||||
|
||||
|
||||
@login_required
|
||||
def billing(request):
|
||||
if request.method == "POST":
|
||||
form = BillToForm(request.POST)
|
||||
if form.is_valid():
|
||||
request.user.profile.bill_to = form.cleaned_data["bill_to"]
|
||||
request.user.profile.save()
|
||||
return redirect("hc-billing")
|
||||
error = sub.update_address(request.POST)
|
||||
if error:
|
||||
return log_and_bail(request, error)
|
||||
|
||||
sub = Subscription.objects.get(user=request.user)
|
||||
return redirect("hc-billing")
|
||||
|
||||
transactions = braintree.Transaction.search(
|
||||
braintree.TransactionSearch.customer_id == sub.customer_id)
|
||||
ctx = {"a": sub.address}
|
||||
return render(request, "payments/address.html", ctx)
|
||||
|
||||
|
||||
@login_required
|
||||
def payment_method(request):
|
||||
sub = get_object_or_404(Subscription, user=request.user)
|
||||
|
||||
if request.method == "POST":
|
||||
if "payment_method_nonce" not in request.POST:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
nonce = request.POST["payment_method_nonce"]
|
||||
error = sub.update_payment_method(nonce)
|
||||
if error:
|
||||
return log_and_bail(request, error)
|
||||
|
||||
return redirect("hc-billing")
|
||||
|
||||
ctx = {
|
||||
"sub": sub,
|
||||
"pm": sub.payment_method
|
||||
}
|
||||
return render(request, "payments/payment_method.html", ctx)
|
||||
|
||||
|
||||
@login_required
|
||||
def billing_history(request):
|
||||
try:
|
||||
sub = Subscription.objects.get(user=request.user)
|
||||
transactions = sub.transactions
|
||||
except Subscription.DoesNotExist:
|
||||
transactions = []
|
||||
|
||||
ctx = {"transactions": transactions}
|
||||
return render(request, "payments/billing.html", ctx)
|
||||
|
||||
|
||||
@login_required
|
||||
def invoice(request, transaction_id):
|
||||
sub = Subscription.objects.get(user=request.user)
|
||||
transaction = braintree.Transaction.find(transaction_id)
|
||||
if transaction.customer_details.id != sub.customer_id:
|
||||
return HttpResponseForbidden()
|
||||
|
||||
ctx = {"tx": transaction}
|
||||
return render(request, "payments/invoice.html", ctx)
|
||||
return render(request, "payments/billing_history.html", ctx)
|
||||
|
||||
|
||||
@login_required
|
||||
def pdf_invoice(request, transaction_id):
|
||||
sub = Subscription.objects.get(user=request.user)
|
||||
transaction = braintree.Transaction.find(transaction_id)
|
||||
if transaction.customer_details.id != sub.customer_id:
|
||||
transaction = sub.get_transaction(transaction_id)
|
||||
if transaction is None:
|
||||
return HttpResponseForbidden()
|
||||
|
||||
response = HttpResponse(content_type='application/pdf')
|
||||
filename = "MS-HC-%s.pdf" % transaction.id.upper()
|
||||
response['Content-Disposition'] = 'attachment; filename="%s"' % filename
|
||||
|
||||
bill_to = request.user.profile.bill_to or request.user.email
|
||||
bill_to = []
|
||||
if sub.address_id:
|
||||
ctx = {"a": sub.address}
|
||||
bill_to = render_to_string("payments/address_plain.html", ctx)
|
||||
elif request.user.profile.bill_to:
|
||||
bill_to = request.user.profile.bill_to
|
||||
else:
|
||||
bill_to = request.user.email
|
||||
|
||||
PdfInvoice(response).render(transaction, bill_to)
|
||||
return response
|
||||
|
|
|
@ -64,7 +64,3 @@
|
|||
margin: 10px 0;
|
||||
color: #AAA;
|
||||
}
|
||||
|
||||
#payment-method-modal .modal-header {
|
||||
text-align: center;
|
||||
}
|
|
@ -16,3 +16,24 @@
|
|||
padding: 8px 15px;
|
||||
}
|
||||
|
||||
span.loading {
|
||||
color: #888;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#billing-address-modal label {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#billing-address-modal .modal-body {
|
||||
margin-left: 30px;
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
.masked_number {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.billing-empty {
|
||||
color: #888;
|
||||
}
|
46
static/js/billing.js
Normal file
46
static/js/billing.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
$(function () {
|
||||
var clientTokenRequested = false;
|
||||
function requestClientToken() {
|
||||
if (!clientTokenRequested) {
|
||||
clientTokenRequested = true;
|
||||
$.getJSON("/pricing/get_client_token/", setupDropin);
|
||||
}
|
||||
}
|
||||
|
||||
function setupDropin(data) {
|
||||
braintree.dropin.create({
|
||||
authorization: data.client_token,
|
||||
container: "#dropin",
|
||||
paypal: { flow: 'vault' }
|
||||
}, function(createErr, instance) {
|
||||
$("#payment-form-submit").click(function() {
|
||||
instance.requestPaymentMethod(function (requestPaymentMethodErr, payload) {
|
||||
$("#pmm-nonce").val(payload.nonce);
|
||||
$("#payment-form").submit();
|
||||
});
|
||||
}).prop("disabled", false);
|
||||
});
|
||||
}
|
||||
|
||||
$("#update-payment-method").hover(requestClientToken);
|
||||
|
||||
$("#update-payment-method").click(function() {
|
||||
requestClientToken();
|
||||
$("#payment-form").attr("action", this.dataset.action);
|
||||
$("#payment-form-submit").text("Update Payment Method");
|
||||
$("#payment-method-modal").modal("show");
|
||||
});
|
||||
|
||||
|
||||
$("#billing-history").load( "/accounts/profile/billing/history/" );
|
||||
$("#billing-address").load( "/accounts/profile/billing/address/", function() {
|
||||
$("#billing-address input").each(function(idx, obj) {
|
||||
$("#" + obj.name).val(obj.value);
|
||||
});
|
||||
});
|
||||
|
||||
$("#payment-method").load( "/accounts/profile/billing/payment_method/", function() {
|
||||
$("#next-billing-date").text($("#nbd").val());
|
||||
});
|
||||
|
||||
});
|
|
@ -1,48 +1,13 @@
|
|||
$(function () {
|
||||
var clientTokenRequested = false;
|
||||
function requestClientToken() {
|
||||
if (!clientTokenRequested) {
|
||||
clientTokenRequested = true;
|
||||
$.getJSON("/pricing/get_client_token/", setupDropin);
|
||||
}
|
||||
}
|
||||
|
||||
function setupDropin(data) {
|
||||
braintree.dropin.create({
|
||||
authorization: data.client_token,
|
||||
container: "#dropin",
|
||||
paypal: { flow: 'vault' }
|
||||
}, function(createErr, instance) {
|
||||
$("#payment-form-submit").click(function() {
|
||||
instance.requestPaymentMethod(function (requestPaymentMethodErr, payload) {
|
||||
$("#pmm-nonce").val(payload.nonce);
|
||||
$("#payment-form").submit();
|
||||
});
|
||||
}).prop("disabled", false);
|
||||
});
|
||||
}
|
||||
|
||||
$(".btn-create-payment-method").hover(requestClientToken);
|
||||
$(".btn-update-payment-method").hover(requestClientToken);
|
||||
|
||||
$(".btn-create-payment-method").click(function() {
|
||||
requestClientToken();
|
||||
$("#plan_id").val(this.dataset.planId);
|
||||
$("#payment-form").attr("action", this.dataset.action);
|
||||
$("#payment-form-submit").text("Set Up Subscription and Pay $" + this.dataset.planPay);
|
||||
$("#payment-method-modal").modal("show");
|
||||
});
|
||||
|
||||
$(".btn-update-payment-method").click(function() {
|
||||
requestClientToken();
|
||||
$("#payment-form").attr("action", this.dataset.action);
|
||||
$("#payment-form-submit").text("Update Payment Method");
|
||||
$("#payment-method-modal").modal("show");
|
||||
});
|
||||
|
||||
$("#period-controls :input").change(function() {
|
||||
$("#monthly").toggleClass("hide", this.value != "monthly");
|
||||
$("#annual").toggleClass("hide", this.value != "annual");
|
||||
});
|
||||
if (this.value == "monthly") {
|
||||
$("#s-price").text("$5");
|
||||
$("#p-price").text("$50");
|
||||
}
|
||||
|
||||
if (this.value == "annual") {
|
||||
$("#s-price").text("$4");
|
||||
$("#p-price").text("$40");
|
||||
}
|
||||
});
|
||||
});
|
|
@ -14,6 +14,9 @@
|
|||
<div class="col-sm-2">
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li><a href="{% url 'hc-profile' %}">Account</a></li>
|
||||
{% if show_pricing %}
|
||||
<li><a href="{% url 'hc-billing' %}">Billing</a></li>
|
||||
{% endif %}
|
||||
<li><a href="{% url 'hc-notifications' %}">Email Reports</a></li>
|
||||
<li class="active"><a href="{% url 'hc-badges' %}">Badges</a></li>
|
||||
</ul>
|
||||
|
|
428
templates/accounts/billing.html
Normal file
428
templates/accounts/billing.html
Normal file
|
@ -0,0 +1,428 @@
|
|||
{% extends "base.html" %}
|
||||
{% load compress staticfiles hc_extras %}
|
||||
|
||||
{% block title %}Account Settings - {% site_name %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<h1 class="settings-title">Settings</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li><a href="{% url 'hc-profile' %}">Account</a></li>
|
||||
<li class="active"><a href="{% url 'hc-billing' %}">Billing</a></li>
|
||||
<li><a href="{% url 'hc-notifications' %}">Email Reports</a></li>
|
||||
<li><a href="{% url 'hc-badges' %}">Badges</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-9 col-md-9">
|
||||
{% if messages %}
|
||||
<div class="alert alert-danger">
|
||||
<p>
|
||||
<strong>We're sorry!</strong> There was a problem setting
|
||||
up the subscription. Response from payment gateway:</p>
|
||||
|
||||
{% for message in messages %}
|
||||
<p class="error-message">{{ message }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body settings-block">
|
||||
<h2>Billing Plan</h2>
|
||||
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td>Current Plan</td>
|
||||
<td>
|
||||
{% if sub is None or sub.plan_id == "" %}
|
||||
Free
|
||||
{% else %}
|
||||
{% if sub.plan_id == "P5" or sub.plan_id == "Y48" %}
|
||||
Standard
|
||||
{% endif %}
|
||||
{% if sub.plan_id == "P50" or sub.plan_id == "Y480" %}
|
||||
Plus
|
||||
{% endif %}
|
||||
|
||||
(${{ sub.price }}/{{ sub.period }})
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% if sub.plan_id %}
|
||||
<tr>
|
||||
<td>Next Payment</td>
|
||||
<td id="next-billing-date">
|
||||
<span class="loading">loading…</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>Checks Used</td>
|
||||
<td>
|
||||
{{ num_checks }} of
|
||||
{% if sub.plan_id %}
|
||||
unlimited
|
||||
{% else %}
|
||||
{{ profile.check_limit }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Team Size</td>
|
||||
<td>
|
||||
{{ team_size }} of
|
||||
{% if profile.team_limit == 500 %}
|
||||
unlimited
|
||||
{% else %}
|
||||
{{ team_max }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<button
|
||||
data-toggle="modal"
|
||||
data-target="#change-billing-plan-modal"
|
||||
class="btn btn-default pull-right">
|
||||
Change Billing Plan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body settings-block">
|
||||
<h2>Payment Method</h2>
|
||||
{% if sub.payment_method_token %}
|
||||
<p id="payment-method">
|
||||
<span class="loading">loading…</span>
|
||||
</p>
|
||||
{% else %}
|
||||
<p id="payment-method-missing" class="billing-empty">Not set</p>
|
||||
{% endif %}
|
||||
<button
|
||||
id="update-payment-method"
|
||||
class="btn btn-default pull-right">
|
||||
Change Payment Method</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body settings-block">
|
||||
<h2>Billing Address</h2>
|
||||
|
||||
{% if sub.address_id %}
|
||||
<div id="billing-address">
|
||||
<span class="loading">loading…</span>
|
||||
</div>
|
||||
{% else %}
|
||||
<p id="billing-address-missing" class="billing-empty">
|
||||
Not set
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<button
|
||||
data-toggle="modal"
|
||||
data-target="#billing-address-modal"
|
||||
class="btn btn-default pull-right">
|
||||
Change Billing Address
|
||||
</button>
|
||||
</div>
|
||||
{% if status == "info" %}
|
||||
<div class="panel-footer">
|
||||
Your billing address has been updated!
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body settings-block">
|
||||
<h2>Billing History</h2>
|
||||
<div id="billing-history">
|
||||
<span class="loading">loading…</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="change-billing-plan-modal" class="modal">
|
||||
<div class="modal-dialog">
|
||||
{% if sub.payment_method_token and sub.address_id %}
|
||||
<form method="post" class="form-horizontal" action="{% url 'hc-set-plan' %}">
|
||||
{% csrf_token %}
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="remove-check-title">Change Billing Plan</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<h2>Free <small>20 checks, 3 team members</small></h2>
|
||||
<label class="radio-container">
|
||||
<input
|
||||
type="radio"
|
||||
name="plan_id"
|
||||
value=""
|
||||
{% if sub.plan_id == "" %} checked {% endif %}>
|
||||
<span class="radiomark"></span>
|
||||
Enjoy free service.
|
||||
</label>
|
||||
|
||||
<h2>Standard <small>Unlimited checks, 20 team members</small></h2>
|
||||
<label class="radio-container">
|
||||
<input
|
||||
type="radio"
|
||||
name="plan_id"
|
||||
value="P5"
|
||||
{% if sub.plan_id == "P5" %} checked {% endif %}>
|
||||
<span class="radiomark"></span>
|
||||
Monthly, $5/month
|
||||
</label>
|
||||
|
||||
<label class="radio-container">
|
||||
<input
|
||||
type="radio"
|
||||
name="plan_id"
|
||||
value="Y48"
|
||||
{% if sub.plan_id == "Y48" %} checked {% endif %}>
|
||||
<span class="radiomark"></span>
|
||||
Yearly, $48/year (20% off monthly)
|
||||
</label>
|
||||
|
||||
<h2>Plus <small>Unlimited checks, unlimited team members</small></h2>
|
||||
<label class="radio-container">
|
||||
<input
|
||||
type="radio"
|
||||
name="plan_id"
|
||||
value="P50"
|
||||
{% if sub.plan_id == "P50" %} checked {% endif %}>
|
||||
<span class="radiomark"></span>
|
||||
Monthly, $50/month
|
||||
</label>
|
||||
|
||||
<label class="radio-container">
|
||||
<input
|
||||
type="radio"
|
||||
name="plan_id"
|
||||
value="Y480"
|
||||
{% if sub.plan_id == "Y480" %} checked {% endif %}>
|
||||
<span class="radiomark"></span>
|
||||
Yearly, $480/year (20% off monthly)
|
||||
</label>
|
||||
|
||||
<div class="alert alert-warning">
|
||||
<strong>No proration.</strong> We currently do not
|
||||
support proration when changing billing plans.
|
||||
Changing the plan starts a new billing cycle
|
||||
and charges your payment method.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Change Billing Plan
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4>Some details are missing…</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{% if not sub.payment_method_token %}
|
||||
<div id="no-payment-method">
|
||||
<h4>No payment method.</h4>
|
||||
<p>Please add a payment method before changing the billing
|
||||
plan.
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if not sub.address_id %}
|
||||
<div id="no-billing-address">
|
||||
<h4>No billing address.</h4>
|
||||
<p>Please add a billing address before changing
|
||||
the billing plan.
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="payment-method-modal" class="modal pm-modal">
|
||||
<div class="modal-dialog">
|
||||
<form id="payment-form" method="post" action="{% url 'hc-payment-method' %}">
|
||||
{% csrf_token %}
|
||||
<input id="pmm-nonce" type="hidden" name="payment_method_nonce" />
|
||||
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4>Payment Method</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="dropin"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="payment-form-submit" type="button" class="btn btn-primary" disabled>
|
||||
Confirm Payment Method
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="billing-address-modal" class="modal">
|
||||
<div class="modal-dialog">
|
||||
<form action="{% url 'hc-billing-address' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4>Billing Address</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group">
|
||||
<input
|
||||
id="first_name"
|
||||
name="first_name"
|
||||
type="text"
|
||||
placeholder="First Name"
|
||||
class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group">
|
||||
<input
|
||||
id="last_name"
|
||||
name="last_name"
|
||||
type="text"
|
||||
placeholder="Last Name"
|
||||
class="input-name form-control" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<div class="form-group">
|
||||
<input
|
||||
id="company"
|
||||
name="company"
|
||||
placeholder="Company (optional)"
|
||||
type="text"
|
||||
class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group">
|
||||
<input
|
||||
id="extended_address"
|
||||
name="extended_address"
|
||||
placeholder="VAT ID (optional)"
|
||||
type="text"
|
||||
class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<div class="form-group">
|
||||
<input
|
||||
id="street_address"
|
||||
name="street_address"
|
||||
placeholder="Street Address"
|
||||
type="text"
|
||||
class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group">
|
||||
<input
|
||||
id="locality"
|
||||
name="locality"
|
||||
placeholder="City"
|
||||
type="text"
|
||||
class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group">
|
||||
<input
|
||||
id="region"
|
||||
name="region"
|
||||
placeholder="State / Region"
|
||||
type="text"
|
||||
class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group">
|
||||
<input
|
||||
id="postal_code"
|
||||
name="postal_code"
|
||||
placeholder="Postal Code"
|
||||
type="text"
|
||||
class="form-control" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6">
|
||||
<div class="form-group">
|
||||
<select id="country_code_alpha2" class="form-control" name="country_code_alpha2">
|
||||
{% include "payments/countries.html" %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="https://js.braintreegateway.com/web/dropin/1.8.0/js/dropin.min.js"></script>
|
||||
{% compress js %}
|
||||
<script src="{% static 'js/jquery-2.1.4.min.js' %}"></script>
|
||||
<script src="{% static 'js/bootstrap.min.js' %}"></script>
|
||||
<script src="{% static 'js/billing.js' %}"></script>
|
||||
{% endcompress %}
|
||||
{% endblock %}
|
|
@ -14,6 +14,9 @@
|
|||
<div class="col-sm-3">
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li><a href="{% url 'hc-profile' %}">Account</a></li>
|
||||
{% if show_pricing %}
|
||||
<li><a href="{% url 'hc-billing' %}">Billing</a></li>
|
||||
{% endif %}
|
||||
<li class="active"><a href="{% url 'hc-notifications' %}">Email Reports</a></li>
|
||||
<li><a href="{% url 'hc-badges' %}">Badges</a></li>
|
||||
</ul>
|
||||
|
|
|
@ -22,6 +22,9 @@
|
|||
<div class="col-sm-3">
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
<li class="active"><a href="{% url 'hc-profile' %}">Account</a></li>
|
||||
{% if show_pricing %}
|
||||
<li><a href="{% url 'hc-billing' %}">Billing</a></li>
|
||||
{% endif %}
|
||||
<li><a href="{% url 'hc-notifications' %}">Email Reports</a></li>
|
||||
<li><a href="{% url 'hc-badges' %}">Badges</a></li>
|
||||
</ul>
|
||||
|
|
41
templates/payments/address.html
Normal file
41
templates/payments/address.html
Normal file
|
@ -0,0 +1,41 @@
|
|||
{% if a.first_name or a.last_name %}
|
||||
<p>{{ a.first_name|default:"" }} {{ a.last_name|default:"" }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if a.company %}
|
||||
<p>{{ a.company }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if a.extended_address %}
|
||||
<p>VAT: {{ a.extended_address }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if a.street_address %}
|
||||
<p>{{ a.street_address }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if a.locality %}
|
||||
<p>{{ a.locality }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if a.region %}
|
||||
<p>{{ a.region }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if a.country_name %}
|
||||
<p>{{ a.country_name }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if a.postal_code %}
|
||||
<p>{{ a.postal_code }}</p>
|
||||
{% endif %}
|
||||
|
||||
<input type="hidden" name="first_name" value="{{ a.first_name|default:"" }}">
|
||||
<input type="hidden" name="last_name" value="{{ a.last_name|default:"" }}">
|
||||
<input type="hidden" name="company" value="{{ a.company|default:"" }}">
|
||||
<input type="hidden" name="street_address" value="{{ a.street_address|default:"" }}">
|
||||
<input type="hidden" name="extended_address" value="{{ a.extended_address|default:"" }}">
|
||||
<input type="hidden" name="locality" value="{{ a.locality|default:"" }}">
|
||||
<input type="hidden" name="region" value="{{ a.region|default:"" }}">
|
||||
<input type="hidden" name="country_code_alpha2" value="{{ a.country_code_alpha2|default:"US" }}">
|
||||
<input type="hidden" name="postal_code" value="{{ a.postal_code|default:"" }}">
|
8
templates/payments/address_plain.html
Normal file
8
templates/payments/address_plain.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
{% if a.first_name or a.last_name %}{{ a.first_name|default:"" }} {{ a.last_name|default:"" }}
|
||||
{% endif %}{% if a.company %}{{ a.company }}
|
||||
{% endif %}{% if a.extended_address %}VAT: {{ a.extended_address }}
|
||||
{% endif %}{% if a.street_address %}{{ a.street_address }}
|
||||
{% endif %}{% if a.locality %}{{ a.locality }}
|
||||
{% endif %}{% if a.region %}{{ a.region }}
|
||||
{% endif %}{% if a.country_name %}{{ a.country_name }}
|
||||
{% endif %}{% if a.postal_code %}{{ a.postal_code }}{% endif %}
|
|
@ -1,98 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Billing History - healthchecks.io{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<h1>Billing History</h1>
|
||||
<div class="row">
|
||||
<div class="col-sm-9">
|
||||
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Payment Method</th>
|
||||
<th>Amount</th>
|
||||
<th>Status</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
{% for tx in transactions %}
|
||||
<tr>
|
||||
<td>{{ tx.created_at }}</td>
|
||||
<td>
|
||||
{% if tx.payment_instrument_type == "paypal_account" %}
|
||||
Paypal from {{ tx.paypal.payer_email }}
|
||||
{% endif %}
|
||||
|
||||
{% if tx.payment_instrument_type == "credit_card" %}
|
||||
{{ tx.credit_card.card_type }} ending in {{ tx.credit_card.last_4 }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if tx.currency_iso_code == "USD" %}
|
||||
${{ tx.amount }}
|
||||
{% elif tx.currency_iso_code == "EUR" %}
|
||||
€{{ tx.amount }}
|
||||
{% else %}
|
||||
{{ tx.currency_iso_code }} {{ tx.amount }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><code>{{ tx.status }}</code></td>
|
||||
<td>
|
||||
<a href="{% url 'hc-invoice-pdf' tx.id %}">PDF Invoice</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
No past transactions to display here
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor%}
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<p><strong>Bill to:</strong></p>
|
||||
<p>
|
||||
{% if request.user.profile.bill_to %}
|
||||
{{ request.user.profile.bill_to|linebreaksbr }}
|
||||
{% else %}
|
||||
{{ request.user.email }}
|
||||
{% endif %}
|
||||
</p>
|
||||
<button
|
||||
data-toggle="modal"
|
||||
data-target="#bill-to-modal"
|
||||
class="btn btn-default">
|
||||
Edit Company Details…
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="bill-to-modal" class="modal">
|
||||
<div class="modal-dialog">
|
||||
<form id="bill-to-form" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4>Company Details for Invoice</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<textarea
|
||||
name="bill_to"
|
||||
class="form-control"
|
||||
rows="5">{{ request.user.profile.bill_to|default:request.user.email }}</textarea>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
42
templates/payments/billing_history.html
Normal file
42
templates/payments/billing_history.html
Normal file
|
@ -0,0 +1,42 @@
|
|||
{% if transactions %}
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Payment Method</th>
|
||||
<th>Amount</th>
|
||||
<th>Status</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
{% for tx in transactions %}
|
||||
<tr>
|
||||
<td>{{ tx.created_at }}</td>
|
||||
<td>
|
||||
{% if tx.payment_instrument_type == "paypal_account" %}
|
||||
Paypal from {{ tx.paypal.payer_email }}
|
||||
{% endif %}
|
||||
|
||||
{% if tx.payment_instrument_type == "credit_card" %}
|
||||
{{ tx.credit_card.card_type }} ending in {{ tx.credit_card.last_4 }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if tx.currency_iso_code == "USD" %}
|
||||
${{ tx.amount }}
|
||||
{% elif tx.currency_iso_code == "EUR" %}
|
||||
€{{ tx.amount }}
|
||||
{% else %}
|
||||
{{ tx.currency_iso_code }} {{ tx.amount }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><code>{{ tx.status }}</code></td>
|
||||
<td>
|
||||
<a href="{% url 'hc-invoice-pdf' tx.id %}">PDF Invoice</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor%}
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="billing-empty">
|
||||
No past transactions to display here
|
||||
</p>
|
||||
{% endif %}
|
250
templates/payments/countries.html
Normal file
250
templates/payments/countries.html
Normal file
|
@ -0,0 +1,250 @@
|
|||
<option value="US">United States of America</option>
|
||||
<option value="AF">Afghanistan</option>
|
||||
<option value="AX">Åland</option>
|
||||
<option value="AL">Albania</option>
|
||||
<option value="DZ">Algeria</option>
|
||||
<option value="AS">American Samoa</option>
|
||||
<option value="AD">Andorra</option>
|
||||
<option value="AO">Angola</option>
|
||||
<option value="AI">Anguilla</option>
|
||||
<option value="AQ">Antarctica</option>
|
||||
<option value="AG">Antigua and Barbuda</option>
|
||||
<option value="AR">Argentina</option>
|
||||
<option value="AM">Armenia</option>
|
||||
<option value="AW">Aruba</option>
|
||||
<option value="AU">Australia</option>
|
||||
<option value="AT">Austria</option>
|
||||
<option value="AZ">Azerbaijan</option>
|
||||
<option value="BS">Bahamas</option>
|
||||
<option value="BH">Bahrain</option>
|
||||
<option value="BD">Bangladesh</option>
|
||||
<option value="BB">Barbados</option>
|
||||
<option value="BY">Belarus</option>
|
||||
<option value="BE">Belgium</option>
|
||||
<option value="BZ">Belize</option>
|
||||
<option value="BJ">Benin</option>
|
||||
<option value="BM">Bermuda</option>
|
||||
<option value="BT">Bhutan</option>
|
||||
<option value="BO">Bolivia</option>
|
||||
<option value="BQ">Bonaire, Sint Eustatius and Saba</option>
|
||||
<option value="BA">Bosnia and Herzegovina</option>
|
||||
<option value="BW">Botswana</option>
|
||||
<option value="BV">Bouvet Island</option>
|
||||
<option value="BR">Brazil</option>
|
||||
<option value="IO">British Indian Ocean Territory</option>
|
||||
<option value="BN">Brunei Darussalam</option>
|
||||
<option value="BG">Bulgaria</option>
|
||||
<option value="BF">Burkina Faso</option>
|
||||
<option value="BI">Burundi</option>
|
||||
<option value="KH">Cambodia</option>
|
||||
<option value="CM">Cameroon</option>
|
||||
<option value="CA">Canada</option>
|
||||
<option value="CV">Cape Verde</option>
|
||||
<option value="KY">Cayman Islands</option>
|
||||
<option value="CF">Central African Republic</option>
|
||||
<option value="TD">Chad</option>
|
||||
<option value="CL">Chile</option>
|
||||
<option value="CN">China</option>
|
||||
<option value="CX">Christmas Island</option>
|
||||
<option value="CC">Cocos (Keeling) Islands</option>
|
||||
<option value="CO">Colombia</option>
|
||||
<option value="KM">Comoros</option>
|
||||
<option value="CG">Congo (Brazzaville)</option>
|
||||
<option value="CD">Congo (Kinshasa)</option>
|
||||
<option value="CK">Cook Islands</option>
|
||||
<option value="CR">Costa Rica</option>
|
||||
<option value="CI">Côte d'Ivoire</option>
|
||||
<option value="HR">Croatia</option>
|
||||
<option value="CU">Cuba</option>
|
||||
<option value="CW">Curaçao</option>
|
||||
<option value="CY">Cyprus</option>
|
||||
<option value="CZ">Czech Republic</option>
|
||||
<option value="DK">Denmark</option>
|
||||
<option value="DJ">Djibouti</option>
|
||||
<option value="DM">Dominica</option>
|
||||
<option value="DO">Dominican Republic</option>
|
||||
<option value="EC">Ecuador</option>
|
||||
<option value="EG">Egypt</option>
|
||||
<option value="SV">El Salvador</option>
|
||||
<option value="GQ">Equatorial Guinea</option>
|
||||
<option value="ER">Eritrea</option>
|
||||
<option value="EE">Estonia</option>
|
||||
<option value="ET">Ethiopia</option>
|
||||
<option value="FK">Falkland Islands</option>
|
||||
<option value="FO">Faroe Islands</option>
|
||||
<option value="FJ">Fiji</option>
|
||||
<option value="FI">Finland</option>
|
||||
<option value="FR">France</option>
|
||||
<option value="GF">French Guiana</option>
|
||||
<option value="PF">French Polynesia</option>
|
||||
<option value="TF">French Southern Lands</option>
|
||||
<option value="GA">Gabon</option>
|
||||
<option value="GM">Gambia</option>
|
||||
<option value="GE">Georgia</option>
|
||||
<option value="DE">Germany</option>
|
||||
<option value="GH">Ghana</option>
|
||||
<option value="GI">Gibraltar</option>
|
||||
<option value="GR">Greece</option>
|
||||
<option value="GL">Greenland</option>
|
||||
<option value="GD">Grenada</option>
|
||||
<option value="GP">Guadeloupe</option>
|
||||
<option value="GU">Guam</option>
|
||||
<option value="GT">Guatemala</option>
|
||||
<option value="GG">Guernsey</option>
|
||||
<option value="GN">Guinea</option>
|
||||
<option value="GW">Guinea-Bissau</option>
|
||||
<option value="GY">Guyana</option>
|
||||
<option value="HT">Haiti</option>
|
||||
<option value="HM">Heard and McDonald Islands</option>
|
||||
<option value="HN">Honduras</option>
|
||||
<option value="HK">Hong Kong</option>
|
||||
<option value="HU">Hungary</option>
|
||||
<option value="IS">Iceland</option>
|
||||
<option value="IN">India</option>
|
||||
<option value="ID">Indonesia</option>
|
||||
<option value="IR">Iran</option>
|
||||
<option value="IQ">Iraq</option>
|
||||
<option value="IE">Ireland</option>
|
||||
<option value="IM">Isle of Man</option>
|
||||
<option value="IL">Israel</option>
|
||||
<option value="IT">Italy</option>
|
||||
<option value="JM">Jamaica</option>
|
||||
<option value="JP">Japan</option>
|
||||
<option value="JE">Jersey</option>
|
||||
<option value="JO">Jordan</option>
|
||||
<option value="KZ">Kazakhstan</option>
|
||||
<option value="KE">Kenya</option>
|
||||
<option value="KI">Kiribati</option>
|
||||
<option value="KP">Korea, North</option>
|
||||
<option value="KR">Korea, South</option>
|
||||
<option value="KW">Kuwait</option>
|
||||
<option value="KG">Kyrgyzstan</option>
|
||||
<option value="LA">Laos</option>
|
||||
<option value="LV">Latvia</option>
|
||||
<option value="LB">Lebanon</option>
|
||||
<option value="LS">Lesotho</option>
|
||||
<option value="LR">Liberia</option>
|
||||
<option value="LY">Libya</option>
|
||||
<option value="LI">Liechtenstein</option>
|
||||
<option value="LT">Lithuania</option>
|
||||
<option value="LU">Luxembourg</option>
|
||||
<option value="MO">Macau</option>
|
||||
<option value="MK">Macedonia</option>
|
||||
<option value="MG">Madagascar</option>
|
||||
<option value="MW">Malawi</option>
|
||||
<option value="MY">Malaysia</option>
|
||||
<option value="MV">Maldives</option>
|
||||
<option value="ML">Mali</option>
|
||||
<option value="MT">Malta</option>
|
||||
<option value="MH">Marshall Islands</option>
|
||||
<option value="MQ">Martinique</option>
|
||||
<option value="MR">Mauritania</option>
|
||||
<option value="MU">Mauritius</option>
|
||||
<option value="YT">Mayotte</option>
|
||||
<option value="MX">Mexico</option>
|
||||
<option value="FM">Micronesia</option>
|
||||
<option value="MD">Moldova</option>
|
||||
<option value="MC">Monaco</option>
|
||||
<option value="MN">Mongolia</option>
|
||||
<option value="ME">Montenegro</option>
|
||||
<option value="MS">Montserrat</option>
|
||||
<option value="MA">Morocco</option>
|
||||
<option value="MZ">Mozambique</option>
|
||||
<option value="MM">Myanmar</option>
|
||||
<option value="NA">Namibia</option>
|
||||
<option value="NR">Nauru</option>
|
||||
<option value="NP">Nepal</option>
|
||||
<option value="NL">Netherlands</option>
|
||||
<option value="AN">Netherlands Antilles</option>
|
||||
<option value="NC">New Caledonia</option>
|
||||
<option value="NZ">New Zealand</option>
|
||||
<option value="NI">Nicaragua</option>
|
||||
<option value="NE">Niger</option>
|
||||
<option value="NG">Nigeria</option>
|
||||
<option value="NU">Niue</option>
|
||||
<option value="NF">Norfolk Island</option>
|
||||
<option value="MP">Northern Mariana Islands</option>
|
||||
<option value="NO">Norway</option>
|
||||
<option value="OM">Oman</option>
|
||||
<option value="PK">Pakistan</option>
|
||||
<option value="PW">Palau</option>
|
||||
<option value="PS">Palestine</option>
|
||||
<option value="PA">Panama</option>
|
||||
<option value="PG">Papua New Guinea</option>
|
||||
<option value="PY">Paraguay</option>
|
||||
<option value="PE">Peru</option>
|
||||
<option value="PH">Philippines</option>
|
||||
<option value="PN">Pitcairn</option>
|
||||
<option value="PL">Poland</option>
|
||||
<option value="PT">Portugal</option>
|
||||
<option value="PR">Puerto Rico</option>
|
||||
<option value="QA">Qatar</option>
|
||||
<option value="RE">Reunion</option>
|
||||
<option value="RO">Romania</option>
|
||||
<option value="RU">Russian Federation</option>
|
||||
<option value="RW">Rwanda</option>
|
||||
<option value="BL">Saint Barthélemy</option>
|
||||
<option value="SH">Saint Helena</option>
|
||||
<option value="KN">Saint Kitts and Nevis</option>
|
||||
<option value="LC">Saint Lucia</option>
|
||||
<option value="MF">Saint Martin (French part)</option>
|
||||
<option value="PM">Saint Pierre and Miquelon</option>
|
||||
<option value="VC">Saint Vincent and the Grenadines</option>
|
||||
<option value="WS">Samoa</option>
|
||||
<option value="SM">San Marino</option>
|
||||
<option value="ST">Sao Tome and Principe</option>
|
||||
<option value="SA">Saudi Arabia</option>
|
||||
<option value="SN">Senegal</option>
|
||||
<option value="RS">Serbia</option>
|
||||
<option value="SC">Seychelles</option>
|
||||
<option value="SL">Sierra Leone</option>
|
||||
<option value="SG">Singapore</option>
|
||||
<option value="SX">Sint Maarten (Dutch part)</option>
|
||||
<option value="SK">Slovakia</option>
|
||||
<option value="SI">Slovenia</option>
|
||||
<option value="SB">Solomon Islands</option>
|
||||
<option value="SO">Somalia</option>
|
||||
<option value="ZA">South Africa</option>
|
||||
<option value="GS">South Georgia and South Sandwich Islands</option>
|
||||
<option value="SS">South Sudan</option>
|
||||
<option value="ES">Spain</option>
|
||||
<option value="LK">Sri Lanka</option>
|
||||
<option value="SD">Sudan</option>
|
||||
<option value="SR">Suriname</option>
|
||||
<option value="SJ">Svalbard and Jan Mayen Islands</option>
|
||||
<option value="SZ">Swaziland</option>
|
||||
<option value="SE">Sweden</option>
|
||||
<option value="CH">Switzerland</option>
|
||||
<option value="SY">Syria</option>
|
||||
<option value="TW">Taiwan</option>
|
||||
<option value="TJ">Tajikistan</option>
|
||||
<option value="TZ">Tanzania</option>
|
||||
<option value="TH">Thailand</option>
|
||||
<option value="TL">Timor-Leste</option>
|
||||
<option value="TG">Togo</option>
|
||||
<option value="TK">Tokelau</option>
|
||||
<option value="TO">Tonga</option>
|
||||
<option value="TT">Trinidad and Tobago</option>
|
||||
<option value="TN">Tunisia</option>
|
||||
<option value="TR">Turkey</option>
|
||||
<option value="TM">Turkmenistan</option>
|
||||
<option value="TC">Turks and Caicos Islands</option>
|
||||
<option value="TV">Tuvalu</option>
|
||||
<option value="UG">Uganda</option>
|
||||
<option value="UA">Ukraine</option>
|
||||
<option value="AE">United Arab Emirates</option>
|
||||
<option value="GB">United Kingdom</option>
|
||||
<option value="UM">United States Minor Outlying Islands</option>
|
||||
<option value="UY">Uruguay</option>
|
||||
<option value="UZ">Uzbekistan</option>
|
||||
<option value="VU">Vanuatu</option>
|
||||
<option value="VA">Vatican City</option>
|
||||
<option value="VE">Venezuela</option>
|
||||
<option value="VN">Vietnam</option>
|
||||
<option value="VG">Virgin Islands, British</option>
|
||||
<option value="VI">Virgin Islands, U.S.</option>
|
||||
<option value="WF">Wallis and Futuna Islands</option>
|
||||
<option value="EH">Western Sahara</option>
|
||||
<option value="YE">Yemen</option>
|
||||
<option value="ZM">Zambia</option>
|
||||
<option value="ZW">Zimbabwe</option>
|
14
templates/payments/payment_method.html
Normal file
14
templates/payments/payment_method.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
{% if sub.pm_is_card %}
|
||||
<img src="{{ pm.image_url }}" height="30" /> <span class="masked_number">{{ pm.masked_number }}</span>
|
||||
{% if pm.expired %}
|
||||
<span class="text-danger">(expired)</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if sub.pm_is_paypal %}
|
||||
<img src="{{ pm.image_url }}" height="30" /> {{ pm.email }}
|
||||
{% endif %}
|
||||
|
||||
{% if sub.subscription_id %}
|
||||
<input type="hidden" id="nbd" value="{{ sub.next_billing_date }}" />
|
||||
{% endif %}
|
|
@ -8,51 +8,32 @@
|
|||
<!-- Plans -->
|
||||
<section id="plans" {% if request.user.is_authenticated %} data- {% endif %}>
|
||||
<div class="container">
|
||||
{% if messages %}
|
||||
<div class="alert alert-danger">
|
||||
<p>
|
||||
<strong>We're sorry!</strong> There was a problem setting
|
||||
up the subscription. Response from payment gateway:</p>
|
||||
|
||||
{% for message in messages %}
|
||||
<p class="error-message">{{ message }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if sub.plan_id %}
|
||||
{% if request.user.is_authenticated %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div id="subscription-status" class="jumbotron">
|
||||
<p>
|
||||
{% if first_charge %}
|
||||
Success! You just paid ${{ sub.price }}.
|
||||
Your account is currently on the
|
||||
{% if sub.plan_id == "P5" %}
|
||||
<strong>Monthly Standard</strong>
|
||||
{% elif sub.plan_id == "P50" %}
|
||||
<strong>Monthly Plus</strong>
|
||||
{% elif sub.plan_id == "Y48" %}
|
||||
<strong>Yearly Standard</strong>
|
||||
{% elif sub.plan_id == "Y480" %}
|
||||
<strong>Yearly Plus</strong>
|
||||
{% else %}
|
||||
You are currently paying ${{ sub.price }}/{{ sub.period }}
|
||||
|
||||
{% if sub.pm_is_credit_card %}
|
||||
using {{ sub.card_type }} card
|
||||
ending with {{ sub.last_4 }}.
|
||||
{% endif %}
|
||||
|
||||
{% if sub.pm_is_paypal %}
|
||||
using PayPal account
|
||||
{{ sub.paypal_email }}.
|
||||
{% endif %}
|
||||
<strong>Free</strong>
|
||||
{% endif %}
|
||||
plan.
|
||||
|
||||
{% if sub.plan_id %}
|
||||
You are paying
|
||||
<strong>${{ sub.price }}</strong> / {{ sub.period }}.
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>Thank you for supporting healthchecks.io!</p>
|
||||
|
||||
<p>
|
||||
<a class="btn btn-default" href="{% url 'hc-billing' %}">See Billing History</a>
|
||||
{% if not first_charge %}
|
||||
<button
|
||||
class="btn btn-default btn-update-payment-method"
|
||||
data-action="{% url 'hc-update-payment-method' %}">
|
||||
Change Payment Method
|
||||
</button>
|
||||
{% endif %}
|
||||
<a class="btn btn-default" href="{% url 'hc-billing' %}">Billing Details</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -68,20 +49,19 @@
|
|||
<div class="row">
|
||||
<div class="col-sm-12" id="period-controls">
|
||||
<div class="btn-group" data-toggle="buttons">
|
||||
<label class="btn btn-default {% if period == "monthly" %} active {% endif %}">
|
||||
<label class="btn btn-default active">
|
||||
<input
|
||||
type="radio"
|
||||
name="period"
|
||||
value="monthly"
|
||||
{% if period == "monthly" %} checked {% endif %}
|
||||
checked
|
||||
autocomplete="off"> Monthly
|
||||
</label>
|
||||
<label class="btn btn-default {% if period == "annual" %} active {% endif %}">
|
||||
<label class="btn btn-default">
|
||||
<input
|
||||
type="radio"
|
||||
name="period"
|
||||
value="annual"
|
||||
{% if period == "annual" %} checked {% endif %}
|
||||
autocomplete="off"> Annual
|
||||
</label>
|
||||
</div>
|
||||
|
@ -89,255 +69,84 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="monthly" class="row {% if period != "monthly" %} hide {% endif %}">
|
||||
<!-- Free -->
|
||||
<div class="col-sm-4 text-center">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body text-center free">
|
||||
<h1>Free</h1>
|
||||
<h2>$0<span class="mo">/mo</span></h2>
|
||||
</div>
|
||||
<ul class="list-group text-center">
|
||||
<li class="list-group-item"><i class="fa fa-check"></i> 20 Checks</li>
|
||||
<li class="list-group-item">3 Team Members</li>
|
||||
<li class="list-group-item">100 log entries per check</li>
|
||||
<li class="list-group-item"> </li>
|
||||
<li class="list-group-item"> </li>
|
||||
</ul>
|
||||
<div class="panel-footer">
|
||||
{% if request.user.is_authenticated %}
|
||||
{% if sub.subscription_id %}
|
||||
<form method="post" action="{% url 'hc-cancel-plan' %}">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-lg btn-default">
|
||||
Switch To Free
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<a class="btn btn-lg btn-success disabled" href="#">Selected</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<a class="btn btn-lg btn-success" href="{% url 'hc-login' %}">Get Started</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- Free -->
|
||||
<div class="col-sm-4 text-center">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body text-center free">
|
||||
<h1>Free</h1>
|
||||
<h2>$0<span class="mo">/mo</span></h2>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /item -->
|
||||
|
||||
<!-- P5 -->
|
||||
<div class="col-sm-4 text-center">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body text-center">
|
||||
<h1>Standard</h1>
|
||||
<h2>$5<span class="mo">/mo</span></h2>
|
||||
</div>
|
||||
|
||||
<ul class="list-group text-center">
|
||||
<li class="list-group-item">Unlimited Checks</li>
|
||||
<li class="list-group-item">10 Team Members</li>
|
||||
<li class="list-group-item">1000 log entries per check</li>
|
||||
<li class="list-group-item">50 SMS alerts per month</li>
|
||||
<li class="list-group-item">Email Support</li>
|
||||
</ul>
|
||||
<div class="panel-footer">
|
||||
{% if request.user.is_authenticated %}
|
||||
{% if sub.plan_id == "P5" %}
|
||||
<button class="btn btn-lg btn-success disabled">
|
||||
Selected
|
||||
</button>
|
||||
{% else %}
|
||||
<button
|
||||
data-plan-id="P5"
|
||||
data-plan-pay="5"
|
||||
data-action="{% url 'hc-create-plan' %}"
|
||||
class="btn btn-lg btn-default btn-create-payment-method">
|
||||
{% if not sub.subscription_id %}
|
||||
Upgrade your Account
|
||||
{% else %}
|
||||
Switch to $5/mo
|
||||
{% endif %}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<a class="btn btn-lg btn-primary" href="{% url 'hc-login' %}">
|
||||
Get Started
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<ul class="list-group text-center">
|
||||
<li class="list-group-item"><i class="fa fa-check"></i> 20 Checks</li>
|
||||
<li class="list-group-item">3 Team Members</li>
|
||||
<li class="list-group-item">100 log entries per check</li>
|
||||
<li class="list-group-item"> </li>
|
||||
<li class="list-group-item"> </li>
|
||||
</ul>
|
||||
{% if not request.user.is_authenticated %}
|
||||
<div class="panel-footer">
|
||||
<a class="btn btn-lg btn-success" href="{% url 'hc-login' %}">Get Started</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- /item -->
|
||||
|
||||
<!-- P50 -->
|
||||
<div class="col-sm-4 text-center">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body text-center">
|
||||
<h1>Plus</h1>
|
||||
<h2>$50<span class="mo">/mo</span></h2>
|
||||
</div>
|
||||
|
||||
<ul class="list-group text-center">
|
||||
<li class="list-group-item">Unlimited Checks</li>
|
||||
<li class="list-group-item">Unlimited Team Members</li>
|
||||
<li class="list-group-item">1000 log entries per check</li>
|
||||
<li class="list-group-item">500 SMS alerts per month</li>
|
||||
<li class="list-group-item">Priority Email Support</li>
|
||||
</ul>
|
||||
<div class="panel-footer">
|
||||
{% if request.user.is_authenticated %}
|
||||
{% if sub.plan_id == "P50" %}
|
||||
<button class="btn btn-lg btn-success disabled">
|
||||
Selected
|
||||
</button>
|
||||
{% else %}
|
||||
<button
|
||||
data-plan-id="P50"
|
||||
data-plan-pay="50"
|
||||
data-action="{% url 'hc-create-plan' %}"
|
||||
class="btn btn-lg btn-default btn-create-payment-method">
|
||||
{% if not sub.subscription_id %}
|
||||
Upgrade your Account
|
||||
{% else %}
|
||||
Switch to $50/mo
|
||||
{% endif %}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<a class="btn btn-lg btn-primary" href="{% url 'hc-login' %}">
|
||||
Get Started
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /item -->
|
||||
</div>
|
||||
<!-- /item -->
|
||||
|
||||
<div id="annual" class="row {% if period != "annual" %} hide {% endif %}">
|
||||
<!-- Free -->
|
||||
<div class="col-sm-4 text-center">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body text-center free">
|
||||
<h1>Free</h1>
|
||||
<h2>$0<span class="mo">/mo</span></h2>
|
||||
</div>
|
||||
<ul class="list-group text-center">
|
||||
<li class="list-group-item"><i class="fa fa-check"></i> 20 Checks</li>
|
||||
<li class="list-group-item">3 Team Members</li>
|
||||
<li class="list-group-item">100 log entries per check</li>
|
||||
<li class="list-group-item"> </li>
|
||||
<li class="list-group-item"> </li>
|
||||
</ul>
|
||||
<div class="panel-footer">
|
||||
{% if request.user.is_authenticated %}
|
||||
{% if sub.subscription_id %}
|
||||
<form method="post" action="{% url 'hc-cancel-plan' %}">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-lg btn-default">
|
||||
Switch To Free
|
||||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<a class="btn btn-lg btn-success disabled" href="#">Selected</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<a class="btn btn-lg btn-success" href="{% url 'hc-login' %}">Get Started</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- P5 -->
|
||||
<div class="col-sm-4 text-center">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body text-center">
|
||||
<h1>Standard</h1>
|
||||
<h2>
|
||||
<span id="s-price">$5</span><span class="mo">/mo</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /item -->
|
||||
|
||||
<!-- Y48 -->
|
||||
<div class="col-sm-4 text-center">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body text-center">
|
||||
<h1>Standard</h1>
|
||||
<h2>$4<span class="mo">/mo</span></h2>
|
||||
</div>
|
||||
|
||||
<ul class="list-group text-center">
|
||||
<li class="list-group-item">Unlimited Checks</li>
|
||||
<li class="list-group-item">10 Team Members</li>
|
||||
<li class="list-group-item">1000 log entries per check</li>
|
||||
<li class="list-group-item">50 SMS alerts per month</li>
|
||||
<li class="list-group-item">Email Support</li>
|
||||
</ul>
|
||||
<div class="panel-footer">
|
||||
{% if request.user.is_authenticated %}
|
||||
{% if sub.plan_id == "Y48" %}
|
||||
<button class="btn btn-lg btn-success disabled">
|
||||
Selected
|
||||
</button>
|
||||
{% else %}
|
||||
<button
|
||||
data-plan-id="Y48"
|
||||
data-plan-pay="48"
|
||||
data-action="{% url 'hc-create-plan' %}"
|
||||
class="btn btn-lg btn-default btn-create-payment-method">
|
||||
{% if not sub.subscription_id %}
|
||||
Upgrade your Account
|
||||
{% else %}
|
||||
Switch to $4/mo
|
||||
{% endif %}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<a class="btn btn-lg btn-primary" href="{% url 'hc-login' %}">
|
||||
Get Started
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<ul class="list-group text-center">
|
||||
<li class="list-group-item">Unlimited Checks</li>
|
||||
<li class="list-group-item">10 Team Members</li>
|
||||
<li class="list-group-item">1000 log entries per check</li>
|
||||
<li class="list-group-item">50 SMS alerts per month</li>
|
||||
<li class="list-group-item">Email Support</li>
|
||||
</ul>
|
||||
{% if not request.user.is_authenticated %}
|
||||
<div class="panel-footer">
|
||||
<a class="btn btn-lg btn-primary" href="{% url 'hc-login' %}">
|
||||
Get Started
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- /item -->
|
||||
|
||||
<!-- Y480 -->
|
||||
<div class="col-sm-4 text-center">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body text-center">
|
||||
<h1>Plus</h1>
|
||||
<h2>$40<span class="mo">/mo</span></h2>
|
||||
</div>
|
||||
|
||||
<ul class="list-group text-center">
|
||||
<li class="list-group-item">Unlimited Checks</li>
|
||||
<li class="list-group-item">Unlimited Team Members</li>
|
||||
<li class="list-group-item">1000 log entries per check</li>
|
||||
<li class="list-group-item">500 SMS alerts per month</li>
|
||||
<li class="list-group-item">Priority Email Support</li>
|
||||
</ul>
|
||||
<div class="panel-footer">
|
||||
{% if request.user.is_authenticated %}
|
||||
{% if sub.plan_id == "Y480" %}
|
||||
<button class="btn btn-lg btn-success disabled">
|
||||
Selected
|
||||
</button>
|
||||
{% else %}
|
||||
<button
|
||||
data-plan-id="Y480"
|
||||
data-plan-pay="480"
|
||||
data-action="{% url 'hc-create-plan' %}"
|
||||
class="btn btn-lg btn-default btn-create-payment-method">
|
||||
{% if not sub.subscription_id %}
|
||||
Upgrade your Account
|
||||
{% else %}
|
||||
Switch to $40/mo
|
||||
{% endif %}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<a class="btn btn-lg btn-primary" href="{% url 'hc-login' %}">
|
||||
Get Started
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /item -->
|
||||
</div>
|
||||
<!-- /item -->
|
||||
|
||||
<!-- P50 -->
|
||||
<div class="col-sm-4 text-center">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body text-center">
|
||||
<h1>Plus</h1>
|
||||
<h2>
|
||||
<span id="p-price">$50</span><span class="mo">/mo</span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<ul class="list-group text-center">
|
||||
<li class="list-group-item">Unlimited Checks</li>
|
||||
<li class="list-group-item">Unlimited Team Members</li>
|
||||
<li class="list-group-item">1000 log entries per check</li>
|
||||
<li class="list-group-item">500 SMS alerts per month</li>
|
||||
<li class="list-group-item">Priority Email Support</li>
|
||||
</ul>
|
||||
{% if not request.user.is_authenticated %}
|
||||
<div class="panel-footer">
|
||||
<a class="btn btn-lg btn-primary" href="{% url 'hc-login' %}">
|
||||
Get Started
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<!-- /item -->
|
||||
</div>
|
||||
</section>
|
||||
<!-- /Plans -->
|
||||
|
@ -416,38 +225,9 @@
|
|||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div id="payment-method-modal" class="modal pm-modal">
|
||||
<div class="modal-dialog">
|
||||
<form id="payment-form" method="post" action="{% url 'hc-create-plan' %}">
|
||||
{% csrf_token %}
|
||||
<input id="plan_id" type="hidden" name="plan_id" value="" />
|
||||
<input id="pmm-nonce" type="hidden" name="payment_method_nonce" />
|
||||
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4>Subscription for {{ request.user.profile }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="dropin"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">
|
||||
Cancel
|
||||
</button>
|
||||
<button id="payment-form-submit" type="button" class="btn btn-primary" disabled>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="https://js.braintreegateway.com/web/dropin/1.8.0/js/dropin.min.js"></script>
|
||||
{% compress js %}
|
||||
<script src="{% static 'js/jquery-2.1.4.min.js' %}"></script>
|
||||
<script src="{% static 'js/bootstrap.min.js' %}"></script>
|
||||
|
|
Loading…
Add table
Reference in a new issue