@ -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), | |||
), | |||
] |
@ -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="[email protected]", 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="[email protected]", 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="[email protected]", 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="[email protected]", 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") |
@ -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="[email protected]", 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="[email protected]", 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="[email protected]", password="password") | |||
r = self.client.get("/invoice/abc123/") | |||
self.assertContains(r, "ABC123") # tx.id in uppercase | |||
self.assertContains(r, "[email protected]") # 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="[email protected]", 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="[email protected]", password="password") | |||
r = self.client.get("/invoice/abc123/") | |||
self.assertContains(r, "Alice and Partners") |
@ -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": "[email protected]"} | |||
self.sub = Subscription(user=self.alice) | |||
self.sub.payment_method_token = "fake-token" | |||
self.sub.save() | |||
self.client.login(username="[email protected]", password="password") | |||
r = self.client.get("/accounts/profile/billing/payment_method/") | |||
self.assertContains(r, "[email protected]") | |||
@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="[email protected]", 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="[email protected]", 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="[email protected]", 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="[email protected]", 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("[email protected]") 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() | |||
@ -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); | |||
$("#period-controls :input").change(function() { | |||
if (this.value == "monthly") { | |||
$("#s-price").text("$5"); | |||
$("#p-price").text("$50"); | |||
} | |||
} | |||
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 == "annual") { | |||
$("#s-price").text("$4"); | |||
$("#p-price").text("$40"); | |||
} | |||
}); | |||
}); |
@ -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 %} |
@ -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:"" }}"> |
@ -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 %} |
@ -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 %} |
@ -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> |
@ -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 %} |