Show invoices directly from stripe
This commit is contained in:
parent
fd4f61bc5c
commit
a00a9f6ff0
5 changed files with 139 additions and 77 deletions
|
@ -1,6 +1,11 @@
|
||||||
|
import datetime
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.core.urlresolvers import resolve, reverse
|
from django.core.urlresolvers import resolve, reverse
|
||||||
from django.utils.translation import activate, get_language
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.utils.translation import activate, get_language, ugettext_lazy as _
|
||||||
|
|
||||||
|
from utils.hosting_utils import get_ip_addresses
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
@ -52,3 +57,58 @@ def escaped_line_break(value):
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
return value.replace("\\n", "\n")
|
return value.replace("\\n", "\n")
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter('get_line_item_from_stripe_invoice')
|
||||||
|
def get_line_item_from_stripe_invoice(invoice):
|
||||||
|
"""
|
||||||
|
Returns ready-to-use "html" line item to be shown for an invoice in the
|
||||||
|
invoice list page
|
||||||
|
|
||||||
|
:param invoice: the stripe Invoice object
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
start_date = 0
|
||||||
|
end_date = 0
|
||||||
|
is_first = True
|
||||||
|
vm_id = -1
|
||||||
|
for line_data in invoice["lines"]["data"]:
|
||||||
|
if is_first:
|
||||||
|
start_date = line_data.period.start
|
||||||
|
end_date = line_data.period.end
|
||||||
|
is_first = False
|
||||||
|
if hasattr(line_data.metadata, "VM_ID"):
|
||||||
|
vm_id = line_data.metadata.VM_ID
|
||||||
|
else:
|
||||||
|
if line_data.period.start < start_date:
|
||||||
|
start_date = line_data.period.start
|
||||||
|
if line_data.period.end > end_date:
|
||||||
|
end_date = line_data.period.end
|
||||||
|
if hasattr(line_data.metadata, "VM_ID"):
|
||||||
|
vm_id = line_data.metadata.VM_ID
|
||||||
|
|
||||||
|
try:
|
||||||
|
vm_id = int(vm_id)
|
||||||
|
except ValueError as ve:
|
||||||
|
print(str(ve))
|
||||||
|
if invoice["lines"]["data"]:
|
||||||
|
return mark_safe("""
|
||||||
|
<td class="xs-td-inline">{vm_id}</td>
|
||||||
|
<td class="xs-td-inline">{ip_addresses}</td>
|
||||||
|
<td class="xs-td-inline">{period}</td>
|
||||||
|
<td class="xs-td-inline">{total}</td>
|
||||||
|
<td class="text-right last-td">
|
||||||
|
<a class="btn btn-order-detail" href="{stripe_invoice_url}" target="_blank">{see_invoice_text}</a>
|
||||||
|
</td>
|
||||||
|
""".format(
|
||||||
|
vm_id=vm_id if vm_id > 0 else "",
|
||||||
|
ip_addresses=mark_safe(get_ip_addresses(vm_id)) if vm_id > 0 else "",
|
||||||
|
period = mark_safe("%s — %s" % (
|
||||||
|
datetime.datetime.fromtimestamp(start_date).strftime('%Y-%m-%d'),
|
||||||
|
datetime.datetime.fromtimestamp(end_date).strftime('%Y-%m-%d'))),
|
||||||
|
total=invoice.total/100,
|
||||||
|
stripe_invoice_url=invoice.hosted_invoice_url,
|
||||||
|
see_invoice_text=_("See Invoice")
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
<img class="svg-img" src="{% static 'hosting/img/key.svg' %}">
|
<img class="svg-img" src="{% static 'hosting/img/key.svg' %}">
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% if has_invoices %}{% url 'hosting:invoices' %}{% else %}{% url 'hosting:orders' %}{% endif %}" class="hosting-dashboard-item">
|
<a href="{% url 'hosting:invoices' %}" class="hosting-dashboard-item">
|
||||||
<h2>{% trans "My Bills" %}</h2>
|
<h2>{% trans "My Bills" %}</h2>
|
||||||
<div class="hosting-dashboard-image">
|
<div class="hosting-dashboard-image">
|
||||||
<img class="svg-img" src="{% static 'hosting/img/billing.svg' %}">
|
<img class="svg-img" src="{% static 'hosting/img/billing.svg' %}">
|
||||||
|
|
|
@ -26,36 +26,46 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for invoice in invoices %}
|
{% for inv_data in invs %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="xs-td-inline" data-header="{% trans 'VM ID' %}">{{ invoice.order.vm_id }}</td>
|
{{ inv_data | get_line_item_from_stripe_invoice }}
|
||||||
<td class="xs-td-inline" data-header="{% trans 'IP Address' %}">{{ ips|get_value_from_dict:invoice.invoice_number|join:"<br/>" }}</td>
|
|
||||||
{% with period|get_value_from_dict:invoice.invoice_number as period_to_show %}
|
|
||||||
<td class="xs-td-inline" data-header="{% trans 'Period' %}">{{ period_to_show.period_start | date:'Y-m-d' }} — {{ period_to_show.period_end | date:'Y-m-d' }}</td>
|
|
||||||
{% endwith %}
|
|
||||||
<td class="xs-td-inline" data-header="{% trans 'Amount' %}">{{ invoice.total_in_chf|floatformat:2|intcomma }}</td>
|
|
||||||
<td class="text-right last-td">
|
|
||||||
<a class="btn btn-order-detail" href="{% url 'hosting:invoices' invoice.invoice_number %}">{% trans 'See Invoice' %}</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
{% if invs.has_other_pages %}
|
||||||
|
<ul class="pagination">
|
||||||
|
{% if invs.has_previous %}
|
||||||
|
{% if user_email %}
|
||||||
|
<li><a href="?page={{ invs.previous_page_number }}&user_email={{user_email}}">«</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li><a href="?page={{ invs.previous_page_number }}">«</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<li class="disabled"><span>«</span></li>
|
||||||
|
{% endif %}
|
||||||
|
{% for i in invs.paginator.page_range %}
|
||||||
|
{% if invs.number == i %}
|
||||||
|
<li class="active"><span>{{ i }} <span class="sr-only">(current)</span></span></li>
|
||||||
|
{% else %}
|
||||||
|
{% if user_email %}
|
||||||
|
<li><a href="?page={{ i }}&user_email={{user_email}}">{{ i }}</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li><a href="?page={{ i }}">{{ i }}</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% if invs.has_next %}
|
||||||
|
{% if user_email %}
|
||||||
|
<li><a href="?page={{ invs.next_page_number }}&user_email={{user_email}}">»</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li><a href="?page={{ invs.next_page_number }}">»</a></li>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<li class="disabled"><span>»</span></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% if is_paginated %}
|
|
||||||
<div class="pagination">
|
|
||||||
<span class="page-links">
|
|
||||||
{% if page_obj.has_previous %}
|
|
||||||
<a href="{{request.path}}?page={{ page_obj.previous_page_number }}">{% trans "previous" %}</a>
|
|
||||||
{% endif %}
|
|
||||||
<span class="page-current">
|
|
||||||
{% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}.
|
|
||||||
</span>
|
|
||||||
{% if page_obj.has_next %}
|
|
||||||
<a href="{{request.path}}?page={{ page_obj.next_page_number }}">{% trans "next" %}</a>
|
|
||||||
{% endif %}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
|
import stripe
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
@ -10,6 +11,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.auth.tokens import default_token_generator
|
from django.contrib.auth.tokens import default_token_generator
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
|
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||||
from django.core.urlresolvers import reverse_lazy, reverse
|
from django.core.urlresolvers import reverse_lazy, reverse
|
||||||
from django.http import (
|
from django.http import (
|
||||||
Http404, HttpResponseRedirect, HttpResponse, JsonResponse
|
Http404, HttpResponseRedirect, HttpResponse, JsonResponse
|
||||||
|
@ -40,7 +42,6 @@ from datacenterlight.models import VMTemplate, VMPricing
|
||||||
from datacenterlight.utils import (
|
from datacenterlight.utils import (
|
||||||
create_vm, get_cms_integration, check_otp, validate_vat_number
|
create_vm, get_cms_integration, check_otp, validate_vat_number
|
||||||
)
|
)
|
||||||
from dynamicweb.settings.base import DCL_ERROR_EMAILS_TO_LIST
|
|
||||||
from hosting.models import UserCardDetail
|
from hosting.models import UserCardDetail
|
||||||
from membership.models import CustomUser, StripeCustomer
|
from membership.models import CustomUser, StripeCustomer
|
||||||
from opennebula_api.models import OpenNebulaManager
|
from opennebula_api.models import OpenNebulaManager
|
||||||
|
@ -57,11 +58,10 @@ from utils.hosting_utils import (
|
||||||
get_vm_price_with_vat, get_vm_price_for_given_vat, HostingUtils,
|
get_vm_price_with_vat, get_vm_price_for_given_vat, HostingUtils,
|
||||||
get_vat_rate_for_country
|
get_vat_rate_for_country
|
||||||
)
|
)
|
||||||
|
from utils.ldap_manager import LdapManager
|
||||||
from utils.mailer import BaseEmail
|
from utils.mailer import BaseEmail
|
||||||
from utils.stripe_utils import StripeUtils
|
from utils.stripe_utils import StripeUtils
|
||||||
from utils.tasks import send_plain_email_task
|
from utils.tasks import send_plain_email_task
|
||||||
from utils.ldap_manager import LdapManager
|
|
||||||
|
|
||||||
from utils.views import (
|
from utils.views import (
|
||||||
PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin,
|
PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin,
|
||||||
ResendActivationLinkViewMixin
|
ResendActivationLinkViewMixin
|
||||||
|
@ -73,7 +73,7 @@ from .forms import (
|
||||||
from .mixins import ProcessVMSelectionMixin, HostingContextMixin
|
from .mixins import ProcessVMSelectionMixin, HostingContextMixin
|
||||||
from .models import (
|
from .models import (
|
||||||
HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail,
|
HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail,
|
||||||
GenericProduct, MonthlyHostingBill, HostingBillLineItem
|
GenericProduct, MonthlyHostingBill
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -1240,7 +1240,7 @@ class OrdersHostingListView(LoginRequiredMixin, ListView):
|
||||||
return super(OrdersHostingListView, self).get(request, *args, **kwargs)
|
return super(OrdersHostingListView, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class InvoiceListView(LoginRequiredMixin, ListView):
|
class InvoiceListView(LoginRequiredMixin, TemplateView):
|
||||||
template_name = "hosting/invoices.html"
|
template_name = "hosting/invoices.html"
|
||||||
login_url = reverse_lazy('hosting:login')
|
login_url = reverse_lazy('hosting:login')
|
||||||
context_object_name = "invoices"
|
context_object_name = "invoices"
|
||||||
|
@ -1248,10 +1248,13 @@ class InvoiceListView(LoginRequiredMixin, ListView):
|
||||||
ordering = '-created'
|
ordering = '-created'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
page = self.request.GET.get('page', 1)
|
||||||
context = super(InvoiceListView, self).get_context_data(**kwargs)
|
context = super(InvoiceListView, self).get_context_data(**kwargs)
|
||||||
|
invs_page = None
|
||||||
if ('user_email' in self.request.GET
|
if ('user_email' in self.request.GET
|
||||||
and self.request.user.email == settings.ADMIN_EMAIL):
|
and self.request.user.email == settings.ADMIN_EMAIL):
|
||||||
user_email = self.request.GET['user_email']
|
user_email = self.request.GET['user_email']
|
||||||
|
context['user_email'] = user_email
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"user_email = {}".format(user_email)
|
"user_email = {}".format(user_email)
|
||||||
)
|
)
|
||||||
|
@ -1260,54 +1263,34 @@ class InvoiceListView(LoginRequiredMixin, ListView):
|
||||||
except CustomUser.DoesNotExist as dne:
|
except CustomUser.DoesNotExist as dne:
|
||||||
logger.debug("User does not exist")
|
logger.debug("User does not exist")
|
||||||
cu = self.request.user
|
cu = self.request.user
|
||||||
mhbs = MonthlyHostingBill.objects.filter(customer__user=cu)
|
invs = stripe.Invoice.list(customer=cu.stripecustomer.stripe_id,
|
||||||
else:
|
count=100)
|
||||||
mhbs = MonthlyHostingBill.objects.filter(
|
paginator = Paginator(invs.data, 10)
|
||||||
customer__user=self.request.user
|
|
||||||
)
|
|
||||||
ips_dict = {}
|
|
||||||
line_item_period_dict = {}
|
|
||||||
for mhb in mhbs:
|
|
||||||
try:
|
try:
|
||||||
vm_detail = VMDetail.objects.get(vm_id=mhb.order.vm_id)
|
invs_page = paginator.page(page)
|
||||||
ips_dict[mhb.invoice_number] = [vm_detail.ipv6, vm_detail.ipv4]
|
except PageNotAnInteger:
|
||||||
all_line_items = HostingBillLineItem.objects.filter(monthly_hosting_bill=mhb)
|
invs_page = paginator.page(1)
|
||||||
for line_item in all_line_items:
|
except EmptyPage:
|
||||||
if line_item.get_item_detail_str() != "":
|
invs_page = paginator.page(paginator.num_pages)
|
||||||
line_item_period_dict[mhb.invoice_number] = {
|
else:
|
||||||
"period_start": line_item.period_start,
|
try:
|
||||||
"period_end": line_item.period_end
|
invs = stripe.Invoice.list(
|
||||||
}
|
customer=self.request.user.stripecustomer.stripe_id,
|
||||||
break
|
count=100
|
||||||
except VMDetail.DoesNotExist as dne:
|
)
|
||||||
ips_dict[mhb.invoice_number] = ['--']
|
paginator = Paginator(invs.data, 10)
|
||||||
logger.debug("VMDetail for {} doesn't exist".format(
|
try:
|
||||||
mhb.order.vm_id
|
invs_page = paginator.page(page)
|
||||||
))
|
except PageNotAnInteger:
|
||||||
context['ips'] = ips_dict
|
invs_page = paginator.page(1)
|
||||||
context['period'] = line_item_period_dict
|
except EmptyPage:
|
||||||
|
invs_page = paginator.page(paginator.num_pages)
|
||||||
|
except Exception as ex:
|
||||||
|
logger.error(str(ex))
|
||||||
|
invs_page = None
|
||||||
|
context["invs"] = invs_page
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
user = self.request.user
|
|
||||||
if ('user_email' in self.request.GET
|
|
||||||
and self.request.user.email == settings.ADMIN_EMAIL):
|
|
||||||
user_email = self.request.GET['user_email']
|
|
||||||
logger.debug(
|
|
||||||
"user_email = {}".format(user_email)
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
cu = CustomUser.objects.get(email=user_email)
|
|
||||||
except CustomUser.DoesNotExist as dne:
|
|
||||||
logger.debug("User does not exist")
|
|
||||||
cu = self.request.user
|
|
||||||
self.queryset = MonthlyHostingBill.objects.filter(customer__user=cu)
|
|
||||||
else:
|
|
||||||
self.queryset = MonthlyHostingBill.objects.filter(
|
|
||||||
customer__user=self.request.user
|
|
||||||
)
|
|
||||||
return super(InvoiceListView, self).get_queryset()
|
|
||||||
|
|
||||||
@method_decorator(decorators)
|
@method_decorator(decorators)
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
return super(InvoiceListView, self).get(request, *args, **kwargs)
|
return super(InvoiceListView, self).get(request, *args, **kwargs)
|
||||||
|
|
|
@ -199,6 +199,15 @@ def get_vat_rate_for_country(country):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def get_ip_addresses(vm_id):
|
||||||
|
try:
|
||||||
|
vm_detail = VMDetail.objects.get(vm_id=vm_id)
|
||||||
|
return "%s <br/>%s" % (vm_detail.ipv6, vm_detail.ipv4)
|
||||||
|
except VMDetail.DoesNotExist as dne:
|
||||||
|
logger.error(str(dne))
|
||||||
|
logger.error("VMDetail for %s does not exist" % vm_id)
|
||||||
|
return "--"
|
||||||
|
|
||||||
class HostingUtils:
|
class HostingUtils:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def clear_items_from_list(from_list, items_list):
|
def clear_items_from_list(from_list, items_list):
|
||||||
|
|
Loading…
Reference in a new issue