Pēteris Caune
6 years ago
27 changed files with 1418 additions and 779 deletions
-
20hc/payments/migrations/0003_subscription_address_id.py
-
150hc/payments/models.py
-
69hc/payments/tests/test_address.py
-
16hc/payments/tests/test_billing_history.py
-
38hc/payments/tests/test_cancel_plan.py
-
2hc/payments/tests/test_get_client_token.py
-
56hc/payments/tests/test_invoice.py
-
94hc/payments/tests/test_payment_method.py
-
6hc/payments/tests/test_pdf_invoice.py
-
96hc/payments/tests/test_set_plan.py
-
30hc/payments/urls.py
-
216hc/payments/views.py
-
4static/css/pricing.css
-
21static/css/profile.css
-
46static/js/billing.js
-
51static/js/pricing.js
-
3templates/accounts/badges.html
-
428templates/accounts/billing.html
-
3templates/accounts/notifications.html
-
3templates/accounts/profile.html
-
41templates/payments/address.html
-
8templates/payments/address_plain.html
-
98templates/payments/billing.html
-
42templates/payments/billing_history.html
-
250templates/payments/countries.html
-
14templates/payments/payment_method.html
-
392templates/payments/pricing.html
@ -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="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") |
@ -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) |
@ -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") |
@ -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) |
@ -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 %} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue