diff --git a/hc/payments/migrations/0003_subscription_address_id.py b/hc/payments/migrations/0003_subscription_address_id.py new file mode 100644 index 00000000..1df31f9d --- /dev/null +++ b/hc/payments/migrations/0003_subscription_address_id.py @@ -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), + ), + ] diff --git a/hc/payments/models.py b/hc/payments/models.py index ad55a9dd..35e8104c 100644 --- a/hc/payments/models.py +++ b/hc/payments/models.py @@ -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 diff --git a/hc/payments/tests/test_address.py b/hc/payments/tests/test_address.py new file mode 100644 index 00000000..2901860c --- /dev/null +++ b/hc/payments/tests/test_address.py @@ -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") diff --git a/hc/payments/tests/test_billing.py b/hc/payments/tests/test_billing_history.py similarity index 56% rename from hc/payments/tests/test_billing.py rename to hc/payments/tests/test_billing_history.py index 906ffb82..e6c7a1ec 100644 --- a/hc/payments/tests/test_billing.py +++ b/hc/payments/tests/test_billing_history.py @@ -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") diff --git a/hc/payments/tests/test_cancel_plan.py b/hc/payments/tests/test_cancel_plan.py deleted file mode 100644 index 5bff942b..00000000 --- a/hc/payments/tests/test_cancel_plan.py +++ /dev/null @@ -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) diff --git a/hc/payments/tests/test_get_client_token.py b/hc/payments/tests/test_get_client_token.py index 34491e78..3bd11d03 100644 --- a/hc/payments/tests/test_get_client_token.py +++ b/hc/payments/tests/test_get_client_token.py @@ -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") diff --git a/hc/payments/tests/test_invoice.py b/hc/payments/tests/test_invoice.py deleted file mode 100644 index cb0e3dae..00000000 --- a/hc/payments/tests/test_invoice.py +++ /dev/null @@ -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") diff --git a/hc/payments/tests/test_payment_method.py b/hc/payments/tests/test_payment_method.py new file mode 100644 index 00000000..8720746a --- /dev/null +++ b/hc/payments/tests/test_payment_method.py @@ -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) diff --git a/hc/payments/tests/test_pdf_invoice.py b/hc/payments/tests/test_pdf_invoice.py index 866f4996..85bc13f2 100644 --- a/hc/payments/tests/test_pdf_invoice.py +++ b/hc/payments/tests/test_pdf_invoice.py @@ -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() diff --git a/hc/payments/tests/test_create_plan.py b/hc/payments/tests/test_set_plan.py similarity index 57% rename from hc/payments/tests/test_create_plan.py rename to hc/payments/tests/test_set_plan.py index fb255a7e..4bdb0967 100644 --- a/hc/payments/tests/test_create_plan.py +++ b/hc/payments/tests/test_set_plan.py @@ -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") diff --git a/hc/payments/urls.py b/hc/payments/urls.py index e86f2d50..7256e6b2 100644 --- a/hc/payments/urls.py +++ b/hc/payments/urls.py @@ -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, diff --git a/hc/payments/views.py b/hc/payments/views.py index 69bfebbf..fba84efb 100644 --- a/hc/payments/views.py +++ b/hc/payments/views.py @@ -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 diff --git a/static/css/pricing.css b/static/css/pricing.css index 33069c25..e4425c42 100644 --- a/static/css/pricing.css +++ b/static/css/pricing.css @@ -64,7 +64,3 @@ margin: 10px 0; color: #AAA; } - -#payment-method-modal .modal-header { - text-align: center; -} \ No newline at end of file diff --git a/static/css/profile.css b/static/css/profile.css index 98dd4a07..bfdbc61a 100644 --- a/static/css/profile.css +++ b/static/css/profile.css @@ -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; +} \ No newline at end of file diff --git a/static/js/billing.js b/static/js/billing.js new file mode 100644 index 00000000..299826fc --- /dev/null +++ b/static/js/billing.js @@ -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()); + }); + +}); \ No newline at end of file diff --git a/static/js/pricing.js b/static/js/pricing.js index 9edfedad..c56aadd3 100644 --- a/static/js/pricing.js +++ b/static/js/pricing.js @@ -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"); + } + }); }); \ No newline at end of file diff --git a/templates/accounts/badges.html b/templates/accounts/badges.html index f9ba9340..c504f433 100644 --- a/templates/accounts/badges.html +++ b/templates/accounts/badges.html @@ -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> diff --git a/templates/accounts/billing.html b/templates/accounts/billing.html new file mode 100644 index 00000000..096e0c1e --- /dev/null +++ b/templates/accounts/billing.html @@ -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 %} diff --git a/templates/accounts/notifications.html b/templates/accounts/notifications.html index f6c8f58a..b1c3979c 100644 --- a/templates/accounts/notifications.html +++ b/templates/accounts/notifications.html @@ -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> diff --git a/templates/accounts/profile.html b/templates/accounts/profile.html index d79be8d7..68b48867 100644 --- a/templates/accounts/profile.html +++ b/templates/accounts/profile.html @@ -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> diff --git a/templates/payments/address.html b/templates/payments/address.html new file mode 100644 index 00000000..b9394be7 --- /dev/null +++ b/templates/payments/address.html @@ -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:"" }}"> diff --git a/templates/payments/address_plain.html b/templates/payments/address_plain.html new file mode 100644 index 00000000..631de3d7 --- /dev/null +++ b/templates/payments/address_plain.html @@ -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 %} \ No newline at end of file diff --git a/templates/payments/billing.html b/templates/payments/billing.html deleted file mode 100644 index 79411cab..00000000 --- a/templates/payments/billing.html +++ /dev/null @@ -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 %} diff --git a/templates/payments/billing_history.html b/templates/payments/billing_history.html new file mode 100644 index 00000000..2ae78c4d --- /dev/null +++ b/templates/payments/billing_history.html @@ -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 %} \ No newline at end of file diff --git a/templates/payments/countries.html b/templates/payments/countries.html new file mode 100644 index 00000000..f084393c --- /dev/null +++ b/templates/payments/countries.html @@ -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> diff --git a/templates/payments/payment_method.html b/templates/payments/payment_method.html new file mode 100644 index 00000000..1f59de24 --- /dev/null +++ b/templates/payments/payment_method.html @@ -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 %} \ No newline at end of file diff --git a/templates/payments/pricing.html b/templates/payments/pricing.html index 46b1f74e..ef8e036a 100644 --- a/templates/payments/pricing.html +++ b/templates/payments/pricing.html @@ -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>