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 %} | ||||||
|     {% if is_paginated %} |   <ul class="pagination"> | ||||||
|         <div class="pagination"> |     {% if invs.has_previous %} | ||||||
|             <span class="page-links"> |       {% if user_email %} | ||||||
|                 {% if page_obj.has_previous %} |         <li><a href="?page={{ invs.previous_page_number }}&user_email={{user_email}}">«</a></li> | ||||||
|                     <a href="{{request.path}}?page={{ page_obj.previous_page_number }}">{% trans "previous" %}</a> |       {% else %} | ||||||
|                 {% endif %} |         <li><a href="?page={{ invs.previous_page_number }}">«</a></li> | ||||||
|                 <span class="page-current"> |       {% endif %} | ||||||
|                     {% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}. |     {% else %} | ||||||
|                 </span> |       <li class="disabled"><span>«</span></li> | ||||||
|                 {% if page_obj.has_next %} |  | ||||||
|                     <a href="{{request.path}}?page={{ page_obj.next_page_number }}">{% trans "next" %}</a> |  | ||||||
|                 {% endif %} |  | ||||||
|             </span> |  | ||||||
|         </div> |  | ||||||
|     {% endif %} |     {% 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 %} | ||||||
|  | 
 | ||||||
| </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…
	
	Add table
		Add a link
		
	
		Reference in a new issue