From 68538ac981e162f1ded834dc9b4e89a2729abe89 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 13 Apr 2019 22:20:09 +0200 Subject: [PATCH 001/433] Update stripe version (supports stripe.Webhook) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fe70299b..00dd0ad2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -78,7 +78,7 @@ requests==2.10.0 rjsmin==1.0.12 six==1.10.0 sqlparse==0.1.19 -stripe==1.33.0 +stripe==2.24.1 wheel==0.29.0 django-admin-honeypot==1.0.0 coverage==4.3.4 From 9b32290964c9e176bf0054e60653e5244522f4e0 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 13 Apr 2019 23:52:41 +0200 Subject: [PATCH 002/433] Add webhook app and create_webhook management command --- dynamicweb/settings/base.py | 3 ++ webhook/__init__.py | 0 webhook/management/commands/create_webhook.py | 49 +++++++++++++++++++ webhook/models.py | 3 ++ webhook/views.py | 36 ++++++++++++++ 5 files changed, 91 insertions(+) create mode 100644 webhook/__init__.py create mode 100644 webhook/management/commands/create_webhook.py create mode 100644 webhook/models.py create mode 100644 webhook/views.py diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index a770018f..45018f13 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -153,6 +153,7 @@ INSTALLED_APPS = ( 'rest_framework', 'opennebula_api', 'django_celery_results', + 'webhook', ) MIDDLEWARE_CLASSES = ( @@ -719,6 +720,8 @@ X_FRAME_OPTIONS = ('SAMEORIGIN' if X_FRAME_OPTIONS_ALLOW_FROM_URI is None else X_FRAME_OPTIONS_ALLOW_FROM_URI.strip() )) +INVOICE_WEBHOOK_SECRET = env('INVOICE_WEBHOOK_SECRET') + DEBUG = bool_env('DEBUG') if DEBUG: diff --git a/webhook/__init__.py b/webhook/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/webhook/management/commands/create_webhook.py b/webhook/management/commands/create_webhook.py new file mode 100644 index 00000000..23960297 --- /dev/null +++ b/webhook/management/commands/create_webhook.py @@ -0,0 +1,49 @@ +import logging +import stripe + +from django.core.management.base import BaseCommand + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + help = '''creates webhook with the supplied arguments and returns the + webhook secret + ''' + + def add_arguments(self, parser): + parser.add_argument( + '--webhook_endpoint', + help="The url of the webhook endpoint that accepts the events " + "from stripe", + dest="webhook_endpoint" + ) + parser.add_argument('--events_csv', dest="events_csv") + + def handle(self, *args, **options): + wep_exists = False + try: + we_list = stripe.WebhookEndpoint.list(limit=100) + for wep in we_list.data: + if set(wep.enabled_events) == set(options['events_csv'].split(",")): + if wep.url == options['webhook_endpoint']: + logger.debug("We have this webhook already") + wep_exists = True + break + if wep_exists is False: + logger.debug( + "No webhook exists for {} at {}. Creatting a new endpoint " + "now".format( + options['webhook_endpoint'], options['events_csv'] + ) + ) + wep = stripe.WebhookEndpoint.create( + url=options['webhook_endpoint'], + enabled_events=options['events_csv'].split(",") + ) + self.stdout.write( + self.style.SUCCESS('Creation successful. ' + 'webhook_secret = %s' % wep.secret) + ) + except Exception as e: + print(" *** Error occurred. Details {}".format(str(e))) diff --git a/webhook/models.py b/webhook/models.py new file mode 100644 index 00000000..d49766e4 --- /dev/null +++ b/webhook/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. \ No newline at end of file diff --git a/webhook/views.py b/webhook/views.py new file mode 100644 index 00000000..19587e69 --- /dev/null +++ b/webhook/views.py @@ -0,0 +1,36 @@ +import logging +import stripe + +# Create your views here. +from django.conf import settings +from django.http import HttpResponse +from django.views.decorators.csrf import csrf_exempt +from django.views.decorators.http import require_POST + +logger = logging.getLogger(__name__) + + +@require_POST +@csrf_exempt +def handle_invoice_webhook(request): + payload = request.body + sig_header = request.META['HTTP_STRIPE_SIGNATURE'] + event = None + + try: + event = stripe.Webhook.construct_event( + payload, sig_header, settings.INVOICE_WEBHOOK_SECRET + ) + except ValueError as e: + logger.error("Invalid payload details = " + str(e)) + # Invalid payload + return HttpResponse(status=400) + except stripe.error.SignatureVerificationError as e: + logger.error("SignatureVerificationError details = " + str(e)) + # Invalid signature + return HttpResponse(status=400) + + # Do something with event + logger.debug("Passed invoice signature verification") + + return HttpResponse(status=200) \ No newline at end of file From 8a59c2da1e04fb216e8ab4993bf5f3a17bd368b0 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 14 Apr 2019 00:58:13 +0200 Subject: [PATCH 003/433] Implement handling invoice.payment_succeeded and invoice.payment_failed webhooks --- dynamicweb/settings/base.py | 1 + hosting/models.py | 13 +++++++ webhook/views.py | 71 +++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 45018f13..85cf1a86 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -650,6 +650,7 @@ CELERY_MAX_RETRIES = int_env('CELERY_MAX_RETRIES', 5) DCL_ERROR_EMAILS_TO = env('DCL_ERROR_EMAILS_TO') ADMIN_EMAIL = env('ADMIN_EMAIL') +WEBHOOK_EMAIL_TO = env('WEBHOOK_EMAIL_TO') DCL_ERROR_EMAILS_TO_LIST = [] if DCL_ERROR_EMAILS_TO is not None: diff --git a/hosting/models.py b/hosting/models.py index 550cf27f..ed6329b8 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -435,6 +435,19 @@ class HostingBillLineItem(AssignPermissionsMixin, models.Model): ('view_hostingbilllineitem', 'View Monthly Hosting Bill Line Item'), ) + def get_vm_id(self): + """ + If VM_ID is set in the metadata extract and return it as integer + other return -1 + + :return: + """ + if "VM_ID" in self.metadata: + data = json.loads(self.metadata) + return int(data["VM_ID"]) + else: + return None + class VMDetail(models.Model): user = models.ForeignKey(CustomUser) diff --git a/webhook/views.py b/webhook/views.py index 19587e69..cf1b0950 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -7,6 +7,9 @@ from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST +from membership.models import StripeCustomer +from hosting.models import MonthlyHostingBill, HostingBillLineItem +from utils.tasks import send_plain_email_task logger = logging.getLogger(__name__) @@ -33,4 +36,72 @@ def handle_invoice_webhook(request): # Do something with event logger.debug("Passed invoice signature verification") + # Get the user from the invoice + invoice = event.data.object + stripe_customer = StripeCustomer.objects.get(stripe_id=invoice.customer) + + if event.type == "invoice.payment_succeeded": + logger.debug("Invoice payment succeeded") + + # Create a new invoice for the user + invoice_dict = { + 'created': invoice.created, + 'receipt_number': invoice.receipt_number, + 'invoice_number': invoice.number, + 'paid_at': invoice.status_transitions.paid_at if invoice.paid else 0, + 'period_start': invoice.period_start, + 'period_end': invoice.period_end, + 'billing_reason': invoice.billing_reason, + 'discount': invoice.discount.coupon.amount_off if invoice.discount else 0, + 'total': invoice.total, + # to see how many line items we have in this invoice and + # then later check if we have more than 1 + 'lines_data_count': len( + invoice.lines.data) if invoice.lines.data is not None else 0, + 'invoice_id': invoice.id, + 'lines_meta_data_csv': ','.join( + [line.metadata.VM_ID if hasattr(line.metadata, 'VM_ID') else '' + for line in invoice.lines.data] + ), + 'subscription_ids_csv': ','.join( + [line.id if line.type == 'subscription' else '' for line in + invoice.lines.data] + ), + 'line_items': invoice.lines.data, + 'customer': stripe_customer + } + mhb = MonthlyHostingBill.create(invoice_dict) + mbli = HostingBillLineItem.objects.filter(monthly_hosting_bill=mhb).first() + vm_id = mbli.get_vm_id() + + # Send an email to admin + admin_msg_sub = "Invoice payment success for user {} and VM {}".format( + stripe_customer.user.email, + vm_id if vm_id is not None else "Unknown" + ) + email_to_admin_data = { + 'subject': admin_msg_sub, + 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': settings.WEBHOOK_EMAIL_TO.split(","), + 'body': "\n".join( + ["%s=%s" % (k, v) for (k, v) in invoice_dict.items()]), + } + send_plain_email_task.delay(email_to_admin_data) + + elif event.type == "invoice.payment_failed": + logger.error("Invoice payment failed") + + admin_msg_sub = "Invoice payment FAILED for user {} and ".format( + stripe_customer.user.email, + invoice.lines.data.metadata + ) + email_to_admin_data = { + 'subject': admin_msg_sub, + 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': settings.WEBHOOK_EMAIL_TO.split(","), + 'body': "\n".join( + ["%s=%s" % (k, v) for (k, v) in invoice.__dict__.items()]), + } + send_plain_email_task.delay(email_to_admin_data) + return HttpResponse(status=200) \ No newline at end of file From 0ed3c84461777750756368b32c4e4b85ed0a4113 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 14 Apr 2019 00:59:25 +0200 Subject: [PATCH 004/433] Log unhandled case --- webhook/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webhook/views.py b/webhook/views.py index cf1b0950..842f55b5 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -103,5 +103,7 @@ def handle_invoice_webhook(request): ["%s=%s" % (k, v) for (k, v) in invoice.__dict__.items()]), } send_plain_email_task.delay(email_to_admin_data) + else: + logger.error("Unhandled event : " + event.type) return HttpResponse(status=200) \ No newline at end of file From d71bf8747069fad50a6cd233e52a28eed14375d3 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 14 Apr 2019 01:09:53 +0200 Subject: [PATCH 005/433] Add webhooks url --- dynamicweb/urls.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dynamicweb/urls.py b/dynamicweb/urls.py index 37bb69a4..e07ca6bc 100644 --- a/dynamicweb/urls.py +++ b/dynamicweb/urls.py @@ -11,6 +11,7 @@ from hosting.views import ( RailsHostingView, DjangoHostingView, NodeJSHostingView ) from datacenterlight.views import PaymentOrderView +from webhook import views as webhook_views from membership import urls as membership_urls from ungleich_page.views import LandingView from django.views.generic import RedirectView @@ -62,6 +63,7 @@ urlpatterns += i18n_patterns( name='blog_list_view'), url(r'^cms/', include('cms.urls')), url(r'^blog/', include('djangocms_blog.urls', namespace='djangocms_blog')), + url(r'^webhooks/invoices/', webhook_views.handle_invoice_webhook), url(r'^$', RedirectView.as_view(url='/cms') if REDIRECT_TO_CMS else LandingView.as_view()), url(r'^', include('ungleich_page.urls', namespace='ungleich_page')), From 3b84d6f646bd6dc7f81340c5db063608fc3a146e Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 14 Apr 2019 01:36:03 +0200 Subject: [PATCH 006/433] Handle presence of HTTP_STRIPE_SIGNATURE header in META --- webhook/views.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/webhook/views.py b/webhook/views.py index 842f55b5..84e53724 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -17,10 +17,15 @@ logger = logging.getLogger(__name__) @csrf_exempt def handle_invoice_webhook(request): payload = request.body - sig_header = request.META['HTTP_STRIPE_SIGNATURE'] event = None try: + if 'HTTP_STRIPE_SIGNATURE' in request.META: + sig_header = request.META['HTTP_STRIPE_SIGNATURE'] + else: + logger.error("No HTTP_STRIPE_SIGNATURE header") + # Invalid payload + return HttpResponse(status=400) event = stripe.Webhook.construct_event( payload, sig_header, settings.INVOICE_WEBHOOK_SECRET ) From dc4ad93de811bd37b013b06fee92c0b07d7071ee Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 14 Apr 2019 01:59:29 +0200 Subject: [PATCH 007/433] Add list/delete functionality to webhook --- webhook/management/commands/create_webhook.py | 49 ----------- webhook/management/commands/webhook.py | 81 +++++++++++++++++++ 2 files changed, 81 insertions(+), 49 deletions(-) delete mode 100644 webhook/management/commands/create_webhook.py create mode 100644 webhook/management/commands/webhook.py diff --git a/webhook/management/commands/create_webhook.py b/webhook/management/commands/create_webhook.py deleted file mode 100644 index 23960297..00000000 --- a/webhook/management/commands/create_webhook.py +++ /dev/null @@ -1,49 +0,0 @@ -import logging -import stripe - -from django.core.management.base import BaseCommand - -logger = logging.getLogger(__name__) - - -class Command(BaseCommand): - help = '''creates webhook with the supplied arguments and returns the - webhook secret - ''' - - def add_arguments(self, parser): - parser.add_argument( - '--webhook_endpoint', - help="The url of the webhook endpoint that accepts the events " - "from stripe", - dest="webhook_endpoint" - ) - parser.add_argument('--events_csv', dest="events_csv") - - def handle(self, *args, **options): - wep_exists = False - try: - we_list = stripe.WebhookEndpoint.list(limit=100) - for wep in we_list.data: - if set(wep.enabled_events) == set(options['events_csv'].split(",")): - if wep.url == options['webhook_endpoint']: - logger.debug("We have this webhook already") - wep_exists = True - break - if wep_exists is False: - logger.debug( - "No webhook exists for {} at {}. Creatting a new endpoint " - "now".format( - options['webhook_endpoint'], options['events_csv'] - ) - ) - wep = stripe.WebhookEndpoint.create( - url=options['webhook_endpoint'], - enabled_events=options['events_csv'].split(",") - ) - self.stdout.write( - self.style.SUCCESS('Creation successful. ' - 'webhook_secret = %s' % wep.secret) - ) - except Exception as e: - print(" *** Error occurred. Details {}".format(str(e))) diff --git a/webhook/management/commands/webhook.py b/webhook/management/commands/webhook.py new file mode 100644 index 00000000..b5fd3184 --- /dev/null +++ b/webhook/management/commands/webhook.py @@ -0,0 +1,81 @@ +import logging +import stripe + +from django.core.management.base import BaseCommand + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + help = '''creates webhook with the supplied arguments and returns the + webhook secret + ''' + + def add_arguments(self, parser): + parser.add_argument( + '--webhook_endpoint', + help="The url of the webhook endpoint that accepts the events " + "from stripe", + dest="webhook_endpoint", + required=False + ) + parser.add_argument('--events_csv', dest="events_csv", required=False) + parser.add_argument('--webhook_id', dest="webhook_id", required=False) + parser.add_argument('--create', dest='create', action='store_true') + parser.add_argument('--list', dest='list', action='store_true') + parser.add_argument('--delete', dest='delete', action='store_true') + + def handle(self, *args, **options): + wep_exists = False + if options['list']: + logger.debug("Listing webhooks") + we_list = stripe.WebhookEndpoint.list(limit=100) + for wep in we_list.data: + msg = wep.id + " -- " + ",".join(wep.enabled_events) + logger.debug(msg) + self.stdout.write( + self.style.SUCCESS(msg) + ) + elif options['delete']: + logger.debug("Deleting webhook") + if ['webhook_id'] in options: + stripe.Webhook.delete(options['webhook_id']) + msg = "Deleted " + options['webhook_id'] + logger.debug(msg) + self.stdout.write( + self.style.SUCCESS(msg) + ) + else: + msg = "Supply webhook_id to delete a webhook" + logger.debug(msg) + self.stdout.write( + self.style.SUCCESS(msg) + ) + exit(0) + elif options['create']: + logger.debug("Creating webhook") + try: + we_list = stripe.WebhookEndpoint.list(limit=100) + for wep in we_list.data: + if set(wep.enabled_events) == set(options['events_csv'].split(",")): + if wep.url == options['webhook_endpoint']: + logger.debug("We have this webhook already") + wep_exists = True + break + if wep_exists is False: + logger.debug( + "No webhook exists for {} at {}. Creatting a new endpoint " + "now".format( + options['webhook_endpoint'], options['events_csv'] + ) + ) + wep = stripe.WebhookEndpoint.create( + url=options['webhook_endpoint'], + enabled_events=options['events_csv'].split(",") + ) + self.stdout.write( + self.style.SUCCESS('Creation successful. ' + 'webhook_secret = %s' % wep.secret) + ) + except Exception as e: + print(" *** Error occurred. Details {}".format(str(e))) From 5cb1c136cf57130b9914aa68cb7ff680fecb5e3c Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 14 Apr 2019 02:03:06 +0200 Subject: [PATCH 008/433] Fex checking webhook_id in options --- webhook/management/commands/webhook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webhook/management/commands/webhook.py b/webhook/management/commands/webhook.py index b5fd3184..8e17d5e1 100644 --- a/webhook/management/commands/webhook.py +++ b/webhook/management/commands/webhook.py @@ -38,7 +38,7 @@ class Command(BaseCommand): ) elif options['delete']: logger.debug("Deleting webhook") - if ['webhook_id'] in options: + if 'webhook_id' in options: stripe.Webhook.delete(options['webhook_id']) msg = "Deleted " + options['webhook_id'] logger.debug(msg) From e7196af1f96732e1eb6b87f3503c0fcc1b398997 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 14 Apr 2019 02:04:12 +0200 Subject: [PATCH 009/433] Fix a typo --- webhook/management/commands/webhook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webhook/management/commands/webhook.py b/webhook/management/commands/webhook.py index 8e17d5e1..3648fb85 100644 --- a/webhook/management/commands/webhook.py +++ b/webhook/management/commands/webhook.py @@ -39,7 +39,7 @@ class Command(BaseCommand): elif options['delete']: logger.debug("Deleting webhook") if 'webhook_id' in options: - stripe.Webhook.delete(options['webhook_id']) + stripe.WebhookEndpoint.delete(options['webhook_id']) msg = "Deleted " + options['webhook_id'] logger.debug(msg) self.stdout.write( From c081f9e73a8ede6655e8dc0b4a8ea47e069658ae Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 14 Apr 2019 11:17:48 +0200 Subject: [PATCH 010/433] Handle error better --- webhook/views.py | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/webhook/views.py b/webhook/views.py index 84e53724..8b8a65b0 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -30,20 +30,29 @@ def handle_invoice_webhook(request): payload, sig_header, settings.INVOICE_WEBHOOK_SECRET ) except ValueError as e: - logger.error("Invalid payload details = " + str(e)) # Invalid payload - return HttpResponse(status=400) + err_msg = "FAILURE handle_invoice_webhook: Invalid payload details" + err_body = "Details %s" % str(e) + return handle_error(err_msg, err_body) except stripe.error.SignatureVerificationError as e: - logger.error("SignatureVerificationError details = " + str(e)) # Invalid signature - return HttpResponse(status=400) + err_msg = "FAILURE handle_invoice_webhook: SignatureVerificationError" + err_body = "Details %s" % str(e) + return handle_error(err_msg, err_body) # Do something with event logger.debug("Passed invoice signature verification") # Get the user from the invoice invoice = event.data.object - stripe_customer = StripeCustomer.objects.get(stripe_id=invoice.customer) + logger.debug("Checking whether StripeCustomer %s exists" % invoice.customer) + try: + stripe_customer = StripeCustomer.objects.get(stripe_id=invoice.customer) + except StripeCustomer.DoesNotExist as dne: + # StripeCustomer does not exist + err_msg = "FAILURE handle_invoice_webhook: StripeCustomer %s doesn't exist" % invoice.customer + err_body = "Details %s" % str(dne) + return handle_error(err_msg, err_body) if event.type == "invoice.payment_succeeded": logger.debug("Invoice payment succeeded") @@ -111,4 +120,16 @@ def handle_invoice_webhook(request): else: logger.error("Unhandled event : " + event.type) - return HttpResponse(status=200) \ No newline at end of file + return HttpResponse(status=200) + + +def handle_error(error_msg, error_body): + logger.error("%s -- %s" % (error_msg, error_body)) + email_to_admin_data = { + 'subject': error_msg, + 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': settings.WEBHOOK_EMAIL_TO.split(","), + 'body': error_body, + } + send_plain_email_task.delay(email_to_admin_data) + return HttpResponse(status=400) \ No newline at end of file From eb360c74063ba973593b36a056c1e89af10e0b77 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 15 Apr 2019 21:53:35 +0200 Subject: [PATCH 011/433] Make HostingOrder not mandatory in MonthlyHostingBill --- hosting/migrations/0053_auto_20190415_1952.py | 21 +++++++++++++++++++ hosting/models.py | 17 +++++++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 hosting/migrations/0053_auto_20190415_1952.py diff --git a/hosting/migrations/0053_auto_20190415_1952.py b/hosting/migrations/0053_auto_20190415_1952.py new file mode 100644 index 00000000..9a8a9c3d --- /dev/null +++ b/hosting/migrations/0053_auto_20190415_1952.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-04-15 19:52 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0052_hostingbilllineitem'), + ] + + operations = [ + migrations.AlterField( + model_name='monthlyhostingbill', + name='order', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='hosting.HostingOrder'), + ), + ] diff --git a/hosting/models.py b/hosting/models.py index ed6329b8..12ff687c 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -240,7 +240,10 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model): Corresponds to Invoice object of Stripe """ customer = models.ForeignKey(StripeCustomer) - order = models.ForeignKey(HostingOrder) + order = models.ForeignKey( + HostingOrder, null=True, blank=True, default=None, + on_delete=models.SET_NULL + ) created = models.DateTimeField(help_text="When the invoice was created") receipt_number = models.CharField( help_text="The receipt number that is generated on Stripe", @@ -274,9 +277,15 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model): if len(args['subscription_ids_csv']) > 0: sub_ids = [sub_id.strip() for sub_id in args['subscription_ids_csv'].split(",")] if len(sub_ids) == 1: - args['order'] = HostingOrder.objects.get( - subscription_id=sub_ids[0] - ) + try: + args['order'] = HostingOrder.objects.get( + subscription_id=sub_ids[0] + ) + except HostingOrder.DoesNotExist as dne: + logger.error("Hosting order for {} doesn't exist".format( + sub_ids[0] + )) + args['order'] = None else: logger.debug( "More than one subscriptions" From 3b26b94fd375f8acf091c3f335d2be30f454b7ab Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 15 Apr 2019 22:27:11 +0200 Subject: [PATCH 012/433] Add logger message --- webhook/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webhook/views.py b/webhook/views.py index 8b8a65b0..8a2bb712 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -100,6 +100,8 @@ def handle_invoice_webhook(request): 'body': "\n".join( ["%s=%s" % (k, v) for (k, v) in invoice_dict.items()]), } + logger.debug("Sending msg %s to %s" % (admin_msg_sub, + settings.WEBHOOK_EMAIL_TO)) send_plain_email_task.delay(email_to_admin_data) elif event.type == "invoice.payment_failed": From 3c215638f5464c85d0127cb3f87361a0ff0f83aa Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 15 Apr 2019 22:57:02 +0200 Subject: [PATCH 013/433] Send error mails to admin only --- webhook/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webhook/views.py b/webhook/views.py index 8a2bb712..0542448a 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -130,7 +130,7 @@ def handle_error(error_msg, error_body): email_to_admin_data = { 'subject': error_msg, 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, - 'to': settings.WEBHOOK_EMAIL_TO.split(","), + 'to': [settings.ADMIN_EMAIL], 'body': error_body, } send_plain_email_task.delay(email_to_admin_data) From d64b6329abf37e4124dd80ae62bfff16ae2ed19b Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 15 Apr 2019 23:17:38 +0200 Subject: [PATCH 014/433] Fallback to obtain VM_ID from order if not in metadata --- hosting/views.py | 93 +++++++++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 40 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index fe13ff21..da139f0b 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1234,6 +1234,7 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView): def get_object(self, queryset=None): invoice_id = self.kwargs.get('invoice_id') + logger.debug("Getting invoice for %s" % invoice_id) try: invoice_obj = MonthlyHostingBill.objects.get( invoice_number=invoice_id @@ -1262,33 +1263,22 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView): if obj is not None: vm_id = obj.get_vm_id() - try: - # Try to get vm details from database - vm_detail = VMDetail.objects.get(vm_id=vm_id) - context['vm'] = vm_detail.__dict__ - context['vm']['name'] = '{}-{}'.format( - context['vm']['configuration'], context['vm']['vm_id']) - price, vat, vat_percent, discount = get_vm_price_with_vat( - cpu=context['vm']['cores'], - ssd_size=context['vm']['disk_size'], - memory=context['vm']['memory'], - pricing_name=(obj.order.vm_pricing.name - if obj.order.vm_pricing else 'default') - ) - context['vm']['vat'] = vat - context['vm']['price'] = price - context['vm']['discount'] = discount - context['vm']['vat_percent'] = vat_percent - context['vm']['total_price'] = price + vat - discount['amount'] - except VMDetail.DoesNotExist: - # fallback to get it from the infrastructure + if vm_id is None: + # We did not find it in the metadata, fallback to order + if obj.order is not None: + vm_id = obj.order.vm_id + logger.debug("VM ID from order is %s" % vm_id) + else: + logger.debug("VM order is None. So, we don't have VM_ID") + else: + logger.debug("VM ID was set in metadata") + if vm_id > 0: try: - manager = OpenNebulaManager( - email=self.request.email, - password=self.request.password - ) - vm = manager.get_vm(vm_id) - context['vm'] = VirtualMachineSerializer(vm).data + # Try to get vm details from database + vm_detail = VMDetail.objects.get(vm_id=vm_id) + context['vm'] = vm_detail.__dict__ + context['vm']['name'] = '{}-{}'.format( + context['vm']['configuration'], context['vm']['vm_id']) price, vat, vat_percent, discount = get_vm_price_with_vat( cpu=context['vm']['cores'], ssd_size=context['vm']['disk_size'], @@ -1300,20 +1290,43 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView): context['vm']['price'] = price context['vm']['discount'] = discount context['vm']['vat_percent'] = vat_percent - context['vm']['total_price'] = ( - price + vat - discount['amount'] - ) - except WrongIdError: - logger.error("WrongIdError while accessing " - "invoice {}".format(obj.invoice_id)) - messages.error( - self.request, - _('The VM you are looking for is unavailable at the ' - 'moment. Please contact Data Center Light support.') - ) - self.kwargs['error'] = 'WrongIdError' - context['error'] = 'WrongIdError' - return context + context['vm']['total_price'] = price + vat - discount['amount'] + except VMDetail.DoesNotExist: + # fallback to get it from the infrastructure + try: + manager = OpenNebulaManager( + email=self.request.email, + password=self.request.password + ) + vm = manager.get_vm(vm_id) + context['vm'] = VirtualMachineSerializer(vm).data + price, vat, vat_percent, discount = get_vm_price_with_vat( + cpu=context['vm']['cores'], + ssd_size=context['vm']['disk_size'], + memory=context['vm']['memory'], + pricing_name=(obj.order.vm_pricing.name + if obj.order.vm_pricing else 'default') + ) + context['vm']['vat'] = vat + context['vm']['price'] = price + context['vm']['discount'] = discount + context['vm']['vat_percent'] = vat_percent + context['vm']['total_price'] = ( + price + vat - discount['amount'] + ) + except WrongIdError: + logger.error("WrongIdError while accessing " + "invoice {}".format(obj.invoice_id)) + messages.error( + self.request, + _('The VM you are looking for is unavailable at the ' + 'moment. Please contact Data Center Light support.') + ) + self.kwargs['error'] = 'WrongIdError' + context['error'] = 'WrongIdError' + return context + else: + logger.debug("No VM_ID. So, no details available.") # add context params from monthly hosting bill context['period_start'] = obj.get_period_start() From 22accdd0d07ffac0ac0a16383a167198b16d6e3b Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 16 Apr 2019 00:02:44 +0200 Subject: [PATCH 015/433] Fallback to vm_id from order if its not set in metadata --- webhook/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/webhook/views.py b/webhook/views.py index 0542448a..aabbf9c4 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -88,6 +88,9 @@ def handle_invoice_webhook(request): mbli = HostingBillLineItem.objects.filter(monthly_hosting_bill=mhb).first() vm_id = mbli.get_vm_id() + if vm_id is None: + vm_id = mhb.order.vm_id + # Send an email to admin admin_msg_sub = "Invoice payment success for user {} and VM {}".format( stripe_customer.user.email, From 6f08a0e7da728afc264b2fc92614c8f0f7f8a8a4 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 16 Apr 2019 00:15:37 +0200 Subject: [PATCH 016/433] Redirect users to invoice instead of orders --- .../templates/hosting/virtual_machine_detail.html | 2 +- hosting/views.py | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/hosting/templates/hosting/virtual_machine_detail.html b/hosting/templates/hosting/virtual_machine_detail.html index ce02036f..52fff9d6 100644 --- a/hosting/templates/hosting/virtual_machine_detail.html +++ b/hosting/templates/hosting/virtual_machine_detail.html @@ -46,7 +46,7 @@
{% trans "Current Pricing" %}
{{order.price|floatformat:2|intcomma}} CHF/{% trans "Month" %}
- {% trans "See Invoice" %} + {% trans "See Invoice" %}
diff --git a/hosting/views.py b/hosting/views.py index da139f0b..fa2f177e 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1570,8 +1570,21 @@ class VirtualMachineView(LoginRequiredMixin, View): 'virtual_machine': serializer.data, 'order': HostingOrder.objects.get( vm_id=serializer.data['vm_id'] - ) + ), + 'has_invoices': False } + try: + bills = [] + if hasattr(self.request.user, 'stripecustomer'): + bills = MonthlyHostingBill.objects.filter( + customer=self.request.user.stripecustomer + ) + if len(bills) > 0: + context['has_invoices'] = True + except MonthlyHostingBill.DoesNotExist as dne: + logger.error("{}'s monthly hosting bill not imported ?".format( + self.request.user.email + )) except Exception as ex: logger.debug("Exception generated {}".format(str(ex))) messages.error(self.request, From 0d4287d36ffab8506cd0b136b232976258833a07 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 12 Jun 2019 06:07:16 +0200 Subject: [PATCH 017/433] Add missing param --- webhook/views.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/webhook/views.py b/webhook/views.py index aabbf9c4..971d1d98 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -1,15 +1,16 @@ import logging -import stripe +import stripe # Create your views here. from django.conf import settings from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST -from membership.models import StripeCustomer from hosting.models import MonthlyHostingBill, HostingBillLineItem +from membership.models import StripeCustomer from utils.tasks import send_plain_email_task + logger = logging.getLogger(__name__) @@ -110,7 +111,7 @@ def handle_invoice_webhook(request): elif event.type == "invoice.payment_failed": logger.error("Invoice payment failed") - admin_msg_sub = "Invoice payment FAILED for user {} and ".format( + admin_msg_sub = "Invoice payment FAILED for user {} and {}".format( stripe_customer.user.email, invoice.lines.data.metadata ) From ecdc0c32fb2c03afa295cbd447aadee367b9a223 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 12 Jun 2019 07:29:19 +0200 Subject: [PATCH 018/433] Improve logger message --- webhook/views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/webhook/views.py b/webhook/views.py index 971d1d98..f768e55d 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -109,12 +109,11 @@ def handle_invoice_webhook(request): send_plain_email_task.delay(email_to_admin_data) elif event.type == "invoice.payment_failed": - logger.error("Invoice payment failed") - admin_msg_sub = "Invoice payment FAILED for user {} and {}".format( stripe_customer.user.email, invoice.lines.data.metadata ) + logger.error(admin_msg_sub) email_to_admin_data = { 'subject': admin_msg_sub, 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, From 68ff2c8520871ab8570ecf903622ca76759f5056 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 12 Jun 2019 07:29:49 +0200 Subject: [PATCH 019/433] Add model for failed invoices -- FailedInvoice --- hosting/models.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/hosting/models.py b/hosting/models.py index 3df69c99..e1bcdd63 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -1,11 +1,11 @@ import json import logging import os -import pytz +from datetime import datetime +import pytz from Crypto.PublicKey import RSA from dateutil.relativedelta import relativedelta -from datetime import datetime from django.db import models from django.utils import timezone from django.utils.functional import cached_property @@ -718,3 +718,29 @@ class UserCardDetail(AssignPermissionsMixin, models.Model): return ucd except UserCardDetail.DoesNotExist: return None + + +class FailedInvoice(AssignPermissionsMixin, models.Model): + permissions = ('view_failedinvoice',) + stripe_customer = models.ForeignKey(StripeCustomer) + order = models.ForeignKey( + HostingOrder, null=True, blank=True, default=None, + on_delete=models.SET_NULL + ) + created_at = models.DateTimeField(auto_now_add=True) + number_of_attempts = models.IntegerField( + default=0, + help_text="The number of attempts for repayment") + invoice_id = models.CharField( + unique=True, + max_length=127, + help_text= "The ID of the invoice that failed") + result = models.IntegerField( + help_text="Whether the service was interrupted or another payment " + "succeeded" + ) + service_interrupted_at = models.DateTimeField( + help_text="The datetime if/when service was interrupted" + ) + + From 78f4e1767e8283addb905b303831b177eaa62f9a Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 13 Jun 2019 05:18:47 +0200 Subject: [PATCH 020/433] Add invoice_failed email templates --- .../emails/invoice_failed.html | 44 +++++++++++++++++++ .../datacenterlight/emails/invoice_failed.txt | 11 +++++ 2 files changed, 55 insertions(+) create mode 100644 datacenterlight/templates/datacenterlight/emails/invoice_failed.html create mode 100644 datacenterlight/templates/datacenterlight/emails/invoice_failed.txt diff --git a/datacenterlight/templates/datacenterlight/emails/invoice_failed.html b/datacenterlight/templates/datacenterlight/emails/invoice_failed.html new file mode 100644 index 00000000..7f973c88 --- /dev/null +++ b/datacenterlight/templates/datacenterlight/emails/invoice_failed.html @@ -0,0 +1,44 @@ +{% load static i18n %} + + + + + + + {% trans "Data Center Light VM payment failed" %} + + + + + + + + + + + + + + + + + + +
+ +
+

{% trans "Data Center Light VM payment failed" %}

+
+

+{% blocktrans %}Your invoice payment for the VM {{VM_ID}} failed.

Please ensure that your credit card is active and that you have sufficient credit.{% endblocktrans %}

+ +{% blocktrans %}We will reattempt with your active payment source in the next {number_of_remaining_hours} hours. If this is not resolved by then, the VM and your subscription will be terminated and the VM can not be recovered back.{% endblocktrans %}

+ +{% blocktrans %}Please reply to this email or write to us at support@datacenterlight.ch if you have any queries.{% endblocktrans %} +

+
+

{% trans "Your Data Center Light Team" %}

+
+ + + diff --git a/datacenterlight/templates/datacenterlight/emails/invoice_failed.txt b/datacenterlight/templates/datacenterlight/emails/invoice_failed.txt new file mode 100644 index 00000000..f1b5b1d6 --- /dev/null +++ b/datacenterlight/templates/datacenterlight/emails/invoice_failed.txt @@ -0,0 +1,11 @@ +{% load i18n %} + +{% trans "Data Center Light VM payment failed" %} + +{% blocktrans %}Your invoice payment for the VM {{VM_ID}} failed.

Please ensure that your credit card is active and that you have sufficient credit.{% endblocktrans %} + +{% blocktrans %}We will reattempt with your active payment source in the next {number_of_remaining_hours} hours. If this is not resolved by then, the VM and your subscription will be terminated and the VM can not be recovered back.{% endblocktrans %} + +{% blocktrans %}Please reply to this email or write to us at support@datacenterlight.ch if you have any queries.{% endblocktrans %} + +{% trans "Your Data Center Light Team" %} \ No newline at end of file From 690b80a61674697763b0c07f377be66ff30852f8 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 13 Jun 2019 05:19:34 +0200 Subject: [PATCH 021/433] Add FailedInvoice create method --- hosting/models.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/hosting/models.py b/hosting/models.py index e1bcdd63..aec95dfc 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -743,4 +743,18 @@ class FailedInvoice(AssignPermissionsMixin, models.Model): help_text="The datetime if/when service was interrupted" ) + class Meta: + permissions = ( + ('view_failedinvoice', 'View User Card'), + ) + + @classmethod + def create(cls, stripe_customer=None, order=None, invoice_id=None): + instance = cls.objects.create( + stripe_customer=stripe_customer, order=order, number_of_attempts=0, + invoice_id=invoice_id + ) + instance.assign_permissions(stripe_customer.user) + return instance + From ad846fabec075fccf4781e4aa8772d4d805f6308 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 13 Jun 2019 05:20:00 +0200 Subject: [PATCH 022/433] Send invoice failed notification to user also --- webhook/views.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/webhook/views.py b/webhook/views.py index f768e55d..7eed6141 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -9,6 +9,7 @@ from django.views.decorators.http import require_POST from hosting.models import MonthlyHostingBill, HostingBillLineItem from membership.models import StripeCustomer +from utils.mailer import BaseEmail from utils.tasks import send_plain_email_task logger = logging.getLogger(__name__) @@ -114,6 +115,7 @@ def handle_invoice_webhook(request): invoice.lines.data.metadata ) logger.error(admin_msg_sub) + # send email to admin email_to_admin_data = { 'subject': admin_msg_sub, 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, @@ -121,6 +123,26 @@ def handle_invoice_webhook(request): 'body': "\n".join( ["%s=%s" % (k, v) for (k, v) in invoice.__dict__.items()]), } + # send email to user + context = { + 'base_url': "{0}://{1}".format(request.scheme, + request.get_host()), + 'dcl_text': settings.DCL_TEXT, + } + email_data = { + 'subject': 'IMPORTANT: The payment for VM {VM_ID} at {dcl_text} failed'.format( + dcl_text=settings.DCL_TEXT, + VM_ID=invoice.lines.data.metadata + ), + 'to': stripe_customer.user.email, + 'context': context, + 'template_name': 'invoice_failed', + 'template_path': 'datacenterlight/emails/', + 'from_address': settings.DCL_SUPPORT_FROM_ADDRESS + } + email = BaseEmail(**email_data) + email.send() + send_plain_email_task.delay(email_to_admin_data) else: logger.error("Unhandled event : " + event.type) From e981bf1542ea73a132236e44db45fd4a089ebd0e Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 13 Jun 2019 09:54:19 +0200 Subject: [PATCH 023/433] Pass number_of_attempts as parameter also when creating FailedInvoice --- hosting/models.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/hosting/models.py b/hosting/models.py index aec95dfc..c7a3dcde 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -749,9 +749,12 @@ class FailedInvoice(AssignPermissionsMixin, models.Model): ) @classmethod - def create(cls, stripe_customer=None, order=None, invoice_id=None): + def create(cls, stripe_customer=None, order=None, invoice_id=None, + number_of_attempts=0): instance = cls.objects.create( - stripe_customer=stripe_customer, order=order, number_of_attempts=0, + stripe_customer=stripe_customer, + order=order, + number_of_attempts=number_of_attempts, invoice_id=invoice_id ) instance.assign_permissions(stripe_customer.user) From 655316305bda064307a46436865ace0f1fcefb55 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 13 Jun 2019 09:55:36 +0200 Subject: [PATCH 024/433] Create FailedInvoice when invoice.payment_failed webhook is fired Also set the context parameters for sending emails --- webhook/views.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/webhook/views.py b/webhook/views.py index 7eed6141..d20b266c 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -7,7 +7,9 @@ from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST -from hosting.models import MonthlyHostingBill, HostingBillLineItem +from hosting.models import ( + MonthlyHostingBill, HostingBillLineItem, FailedInvoice +) from membership.models import StripeCustomer from utils.mailer import BaseEmail from utils.tasks import send_plain_email_task @@ -110,9 +112,15 @@ def handle_invoice_webhook(request): send_plain_email_task.delay(email_to_admin_data) elif event.type == "invoice.payment_failed": + # Create a failed invoice, so that we have a trace of which invoices + # need a followup + FailedInvoice.create( + stripe_customer, number_of_attempts = 1, invoice_id=invoice.id + ) + VM_ID = invoice.lines.data[0].metadata["VM_ID"] admin_msg_sub = "Invoice payment FAILED for user {} and {}".format( stripe_customer.user.email, - invoice.lines.data.metadata + VM_ID ) logger.error(admin_msg_sub) # send email to admin @@ -128,6 +136,8 @@ def handle_invoice_webhook(request): 'base_url': "{0}://{1}".format(request.scheme, request.get_host()), 'dcl_text': settings.DCL_TEXT, + 'VM_ID': VM_ID, + 'number_of_remaining_hours': 48 # for the first failure we wait 48 hours } email_data = { 'subject': 'IMPORTANT: The payment for VM {VM_ID} at {dcl_text} failed'.format( From 82a03ece747c4b230c459910e3ae38cb7599f6a4 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 18 Jul 2019 08:46:38 +0530 Subject: [PATCH 025/433] Update comment --- hosting/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hosting/models.py b/hosting/models.py index bb7c5cc1..50e79cd0 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -537,7 +537,7 @@ class HostingBillLineItem(AssignPermissionsMixin, models.Model): def get_vm_id(self): """ If VM_ID is set in the metadata extract and return it as integer - other return -1 + other return None :return: """ @@ -747,7 +747,7 @@ class FailedInvoice(AssignPermissionsMixin, models.Model): class Meta: permissions = ( - ('view_failedinvoice', 'View User Card'), + ('view_failedinvoice', 'View Failed Invoice'), ) @classmethod From 9b798b43762d1cfb0ed932309fe4c4a5d026eef4 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 18 Jul 2019 08:56:54 +0530 Subject: [PATCH 026/433] Add merge migration 0056_merge.py --- hosting/migrations/0056_merge.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 hosting/migrations/0056_merge.py diff --git a/hosting/migrations/0056_merge.py b/hosting/migrations/0056_merge.py new file mode 100644 index 00000000..af8acc73 --- /dev/null +++ b/hosting/migrations/0056_merge.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-07-18 03:26 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0055_auto_20190701_1614'), + ('hosting', '0053_auto_20190415_1952'), + ] + + operations = [ + ] From 9b3e292598c40d81f3d5421f16915b42f3275b48 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 18 Jul 2019 09:35:57 +0530 Subject: [PATCH 027/433] Add a 5 min trial period to subscriptions for test purposes iff ADD_TRIAL_PERIOD_TO_SUBSCRIPTION is set to true --- datacenterlight/tests.py | 7 ++++--- datacenterlight/views.py | 5 ++++- dynamicweb/settings/base.py | 1 + hosting/views.py | 3 ++- utils/tests.py | 9 ++++++--- 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/datacenterlight/tests.py b/datacenterlight/tests.py index ca1bb930..68f4e15d 100644 --- a/datacenterlight/tests.py +++ b/datacenterlight/tests.py @@ -1,6 +1,7 @@ # from django.test import TestCase - +import datetime from time import sleep +from unittest import skipIf import stripe from celery.result import AsyncResult @@ -8,7 +9,6 @@ from django.conf import settings from django.core.management import call_command from django.test import TestCase, override_settings from model_mommy import mommy -from unittest import skipIf from datacenterlight.models import VMTemplate from datacenterlight.tasks import create_vm_task @@ -119,7 +119,8 @@ class CeleryTaskTestCase(TestCase): subscription_result = self.stripe_utils.subscribe_customer_to_plan( stripe_customer.stripe_id, [{"plan": stripe_plan.get( - 'response_object').stripe_plan_id}]) + 'response_object').stripe_plan_id}], + int(datetime.datetime.now().timestamp()) + 300 if settings.ADD_5MIN_TRIAL_TO_SUBSCRIPTION else None) stripe_subscription_obj = subscription_result.get('response_object') # Check if the subscription was approved and is active if stripe_subscription_obj is None \ diff --git a/datacenterlight/views.py b/datacenterlight/views.py index ae649623..4dd328ad 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1,3 +1,4 @@ +import datetime import logging from django import forms @@ -785,7 +786,9 @@ class OrderConfirmationView(DetailView, FormView): subscription_result = stripe_utils.subscribe_customer_to_plan( stripe_api_cus_id, [{"plan": stripe_plan.get( - 'response_object').stripe_plan_id}]) + 'response_object').stripe_plan_id}], + int(datetime.datetime.now().timestamp()) + 300 if settings.ADD_5MIN_TRIAL_TO_SUBSCRIPTION else None + ) stripe_subscription_obj = subscription_result.get('response_object') # Check if the subscription was approved and is active if (stripe_subscription_obj is None diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index a1779d5f..8af6ddf6 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -724,6 +724,7 @@ X_FRAME_OPTIONS = ('SAMEORIGIN' if X_FRAME_OPTIONS_ALLOW_FROM_URI is None else INVOICE_WEBHOOK_SECRET = env('INVOICE_WEBHOOK_SECRET') DEBUG = bool_env('DEBUG') +ADD_TRIAL_PERIOD_TO_SUBSCRIPTION = bool_env('ADD_TRIAL_PERIOD_TO_SUBSCRIPTION') READ_VM_REALM = env('READ_VM_REALM') AUTH_NAME = env('AUTH_NAME') diff --git a/hosting/views.py b/hosting/views.py index e0f5ebd3..ce17376b 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1034,7 +1034,8 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): subscription_result = stripe_utils.subscribe_customer_to_plan( stripe_api_cus_id, [{"plan": stripe_plan.get( - 'response_object').stripe_plan_id}]) + 'response_object').stripe_plan_id}], + int(datetime.datetime.now().timestamp()) + 300 if settings.ADD_5MIN_TRIAL_TO_SUBSCRIPTION else None) stripe_subscription_obj = subscription_result.get('response_object') # Check if the subscription was approved and is active if (stripe_subscription_obj is None or diff --git a/utils/tests.py b/utils/tests.py index 8abbbb1d..c67041d1 100644 --- a/utils/tests.py +++ b/utils/tests.py @@ -1,5 +1,7 @@ +import datetime import uuid from time import sleep +from unittest import skipIf from unittest.mock import patch import stripe @@ -8,7 +10,6 @@ from django.conf import settings from django.http.request import HttpRequest from django.test import Client from django.test import TestCase, override_settings -from unittest import skipIf from model_mommy import mommy from datacenterlight.models import StripePlan @@ -231,7 +232,8 @@ class StripePlanTestCase(TestStripeCustomerDescription): result = self.stripe_utils.subscribe_customer_to_plan( stripe_customer.stripe_id, [{"plan": stripe_plan.get( - 'response_object').stripe_plan_id}]) + 'response_object').stripe_plan_id}], + int(datetime.datetime.now().timestamp()) + 300 if settings.ADD_5MIN_TRIAL_TO_SUBSCRIPTION else None) self.assertIsInstance(result.get('response_object'), stripe.Subscription) self.assertIsNone(result.get('error')) @@ -247,7 +249,8 @@ class StripePlanTestCase(TestStripeCustomerDescription): result = self.stripe_utils.subscribe_customer_to_plan( stripe_customer.stripe_id, [{"plan": stripe_plan.get( - 'response_object').stripe_plan_id}]) + 'response_object').stripe_plan_id}], + int(datetime.datetime.now().timestamp()) + 300 if settings.ADD_5MIN_TRIAL_TO_SUBSCRIPTION else None) self.assertIsNone(result.get('response_object'), None) self.assertIsNotNone(result.get('error')) From 494d68bb47017c6336911ceb54daffcd7c4111f9 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 18 Jul 2019 09:51:03 +0530 Subject: [PATCH 028/433] Use correct variable name ADD_TRIAL_PERIOD_TO_SUBSCRIPTION --- datacenterlight/tests.py | 2 +- datacenterlight/views.py | 2 +- hosting/views.py | 2 +- utils/tests.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/datacenterlight/tests.py b/datacenterlight/tests.py index 68f4e15d..964a9d5f 100644 --- a/datacenterlight/tests.py +++ b/datacenterlight/tests.py @@ -120,7 +120,7 @@ class CeleryTaskTestCase(TestCase): stripe_customer.stripe_id, [{"plan": stripe_plan.get( 'response_object').stripe_plan_id}], - int(datetime.datetime.now().timestamp()) + 300 if settings.ADD_5MIN_TRIAL_TO_SUBSCRIPTION else None) + int(datetime.datetime.now().timestamp()) + 300 if settings.ADD_TRIAL_PERIOD_TO_SUBSCRIPTION else None) stripe_subscription_obj = subscription_result.get('response_object') # Check if the subscription was approved and is active if stripe_subscription_obj is None \ diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 4dd328ad..ffdd4831 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -787,7 +787,7 @@ class OrderConfirmationView(DetailView, FormView): stripe_api_cus_id, [{"plan": stripe_plan.get( 'response_object').stripe_plan_id}], - int(datetime.datetime.now().timestamp()) + 300 if settings.ADD_5MIN_TRIAL_TO_SUBSCRIPTION else None + int(datetime.datetime.now().timestamp()) + 300 if settings.ADD_TRIAL_PERIOD_TO_SUBSCRIPTION else None ) stripe_subscription_obj = subscription_result.get('response_object') # Check if the subscription was approved and is active diff --git a/hosting/views.py b/hosting/views.py index ce17376b..e6763f32 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1035,7 +1035,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): stripe_api_cus_id, [{"plan": stripe_plan.get( 'response_object').stripe_plan_id}], - int(datetime.datetime.now().timestamp()) + 300 if settings.ADD_5MIN_TRIAL_TO_SUBSCRIPTION else None) + int(datetime.datetime.now().timestamp()) + 300 if settings.ADD_TRIAL_PERIOD_TO_SUBSCRIPTION else None) stripe_subscription_obj = subscription_result.get('response_object') # Check if the subscription was approved and is active if (stripe_subscription_obj is None or diff --git a/utils/tests.py b/utils/tests.py index c67041d1..fb499a94 100644 --- a/utils/tests.py +++ b/utils/tests.py @@ -233,7 +233,7 @@ class StripePlanTestCase(TestStripeCustomerDescription): stripe_customer.stripe_id, [{"plan": stripe_plan.get( 'response_object').stripe_plan_id}], - int(datetime.datetime.now().timestamp()) + 300 if settings.ADD_5MIN_TRIAL_TO_SUBSCRIPTION else None) + int(datetime.datetime.now().timestamp()) + 300 if settings.ADD_TRIAL_PERIOD_TO_SUBSCRIPTION else None) self.assertIsInstance(result.get('response_object'), stripe.Subscription) self.assertIsNone(result.get('error')) @@ -250,7 +250,7 @@ class StripePlanTestCase(TestStripeCustomerDescription): stripe_customer.stripe_id, [{"plan": stripe_plan.get( 'response_object').stripe_plan_id}], - int(datetime.datetime.now().timestamp()) + 300 if settings.ADD_5MIN_TRIAL_TO_SUBSCRIPTION else None) + int(datetime.datetime.now().timestamp()) + 300 if settings.ADD_TRIAL_PERIOD_TO_SUBSCRIPTION else None) self.assertIsNone(result.get('response_object'), None) self.assertIsNotNone(result.get('error')) From ee90ee562489899ef852c6d0861b6fd0523f9c75 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 18 Jul 2019 09:57:45 +0530 Subject: [PATCH 029/433] Fix bug: correct the way to get timestamp from datetime --- hosting/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index e6763f32..ff45b0db 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1035,7 +1035,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): stripe_api_cus_id, [{"plan": stripe_plan.get( 'response_object').stripe_plan_id}], - int(datetime.datetime.now().timestamp()) + 300 if settings.ADD_TRIAL_PERIOD_TO_SUBSCRIPTION else None) + int(datetime.now().timestamp()) + 300 if settings.ADD_TRIAL_PERIOD_TO_SUBSCRIPTION else None) stripe_subscription_obj = subscription_result.get('response_object') # Check if the subscription was approved and is active if (stripe_subscription_obj is None or From ceff7964e915d7c9e6264e7364b5bf3d8696d16a Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 18 Jul 2019 10:13:03 +0530 Subject: [PATCH 030/433] Accept subscription status = trialing because that is what we will be if we are using the trial period --- datacenterlight/tests.py | 6 ++++-- datacenterlight/views.py | 6 ++++-- hosting/views.py | 4 +++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/datacenterlight/tests.py b/datacenterlight/tests.py index 964a9d5f..74798cc3 100644 --- a/datacenterlight/tests.py +++ b/datacenterlight/tests.py @@ -123,8 +123,10 @@ class CeleryTaskTestCase(TestCase): int(datetime.datetime.now().timestamp()) + 300 if settings.ADD_TRIAL_PERIOD_TO_SUBSCRIPTION else None) stripe_subscription_obj = subscription_result.get('response_object') # Check if the subscription was approved and is active - if stripe_subscription_obj is None \ - or stripe_subscription_obj.status != 'active': + if (stripe_subscription_obj is None or + (stripe_subscription_obj.status != 'active' and + stripe_subscription_obj.status != 'trialing') + ): msg = subscription_result.get('error') raise Exception("Creating subscription failed: {}".format(msg)) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index ffdd4831..b2fd483c 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -791,8 +791,10 @@ class OrderConfirmationView(DetailView, FormView): ) stripe_subscription_obj = subscription_result.get('response_object') # Check if the subscription was approved and is active - if (stripe_subscription_obj is None - or stripe_subscription_obj.status != 'active'): + if (stripe_subscription_obj is None or + (stripe_subscription_obj.status != 'active' and + stripe_subscription_obj.status != 'trialing') + ): # At this point, we have created a Stripe API card and # associated it with the customer; but the transaction failed # due to some reason. So, we would want to dissociate this card diff --git a/hosting/views.py b/hosting/views.py index ff45b0db..729c402f 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1039,7 +1039,9 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): stripe_subscription_obj = subscription_result.get('response_object') # Check if the subscription was approved and is active if (stripe_subscription_obj is None or - stripe_subscription_obj.status != 'active'): + (stripe_subscription_obj.status != 'active' and + stripe_subscription_obj.status != 'trialing') + ): # At this point, we have created a Stripe API card and # associated it with the customer; but the transaction failed # due to some reason. So, we would want to dissociate this card From 490ceec47d3db8198909e24ee1f9e6f1c376670d Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 17 Dec 2019 21:34:17 +0530 Subject: [PATCH 031/433] Add france metropolitan vat rate --- vat_rates.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/vat_rates.csv b/vat_rates.csv index 74422f48..38655f38 100644 --- a/vat_rates.csv +++ b/vat_rates.csv @@ -319,3 +319,4 @@ IM",GBP,0.1,standard, 2019-12-17,,AD,EUR,0.045,standard,Andorra standard VAT (added manually) 2019-12-17,,TK,EUR,0.18,standard,Turkey standard VAT (added manually) 2019-12-17,,IS,EUR,0.24,standard,Iceland standard VAT (added manually) +2019-12-17,,FX,EUR,0.20,standard,France metropolitan standard VAT (added manually) From 33120d14f3391bf18d8979c104a0ea16031f23ce Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 17 Dec 2019 21:35:02 +0530 Subject: [PATCH 032/433] Modify billing address form and signup forms to include vat_number --- utils/forms.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/utils/forms.py b/utils/forms.py index fdc67d26..b7c8c6b1 100644 --- a/utils/forms.py +++ b/utils/forms.py @@ -120,17 +120,19 @@ class EditCreditCardForm(forms.Form): class BillingAddressForm(forms.ModelForm): token = forms.CharField(widget=forms.HiddenInput(), required=False) card = forms.CharField(widget=forms.HiddenInput(), required=False) + vat_number = forms.CharField(max_length=100, default="") class Meta: model = BillingAddress fields = ['cardholder_name', 'street_address', - 'city', 'postal_code', 'country'] + 'city', 'postal_code', 'country', 'vat_number'] labels = { 'cardholder_name': _('Cardholder Name'), 'street_address': _('Street Address'), 'city': _('City'), 'postal_code': _('Postal Code'), 'Country': _('Country'), + 'VAT Number': _('VAT Number') } @@ -142,7 +144,7 @@ class BillingAddressFormSignup(BillingAddressForm): class Meta: model = BillingAddress fields = ['name', 'email', 'cardholder_name', 'street_address', - 'city', 'postal_code', 'country'] + 'city', 'postal_code', 'country', 'vat_number'] labels = { 'name': 'Name', 'email': _('Email'), @@ -151,6 +153,7 @@ class BillingAddressFormSignup(BillingAddressForm): 'city': _('City'), 'postal_code': _('Postal Code'), 'Country': _('Country'), + 'vat_number': _('VAT Number') } def clean_email(self): From e11882685fd2760809151da06d13e582fd8a77f6 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 17 Dec 2019 22:07:57 +0530 Subject: [PATCH 033/433] Add vat_number field to CustomUser --- membership/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/membership/models.py b/membership/models.py index df5a5326..28b5951d 100644 --- a/membership/models.py +++ b/membership/models.py @@ -68,6 +68,7 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): site = models.ForeignKey(Site, default=1) name = models.CharField(max_length=50) email = models.EmailField(unique=True) + vat_number = models.CharField(max_length=100, default="") validated = models.IntegerField(choices=VALIDATED_CHOICES, default=0) # By default, we initialize the validation_slug with appropriate value From 3efd6087e25e7a729647c25c3c557980baafa126 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 17 Dec 2019 22:08:55 +0530 Subject: [PATCH 034/433] Add vat_number migration file --- .../migrations/0011_customuser_vat_number.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 membership/migrations/0011_customuser_vat_number.py diff --git a/membership/migrations/0011_customuser_vat_number.py b/membership/migrations/0011_customuser_vat_number.py new file mode 100644 index 00000000..f814d84f --- /dev/null +++ b/membership/migrations/0011_customuser_vat_number.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-12-17 16:37 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('membership', '0010_customuser_import_stripe_bill_remark'), + ] + + operations = [ + migrations.AddField( + model_name='customuser', + name='vat_number', + field=models.CharField(default='', max_length=100), + ), + ] From cdaf498487c6978e1a7e2a0280248101e3ba8c96 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 17 Dec 2019 22:09:34 +0530 Subject: [PATCH 035/433] Remove default for CharField (introduced unwantedly) --- utils/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/forms.py b/utils/forms.py index b7c8c6b1..9855e367 100644 --- a/utils/forms.py +++ b/utils/forms.py @@ -120,7 +120,7 @@ class EditCreditCardForm(forms.Form): class BillingAddressForm(forms.ModelForm): token = forms.CharField(widget=forms.HiddenInput(), required=False) card = forms.CharField(widget=forms.HiddenInput(), required=False) - vat_number = forms.CharField(max_length=100, default="") + vat_number = forms.CharField(max_length=100) class Meta: model = BillingAddress From 0f3acf5db435ec0b2dc4d9ea5fa096185db5dfe0 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 17 Dec 2019 22:51:54 +0530 Subject: [PATCH 036/433] Change order_detail and invoice_detail templates to show VAT number --- datacenterlight/templates/datacenterlight/order_detail.html | 4 ++++ hosting/templates/hosting/invoice_detail.html | 3 +++ hosting/templates/hosting/order_detail.html | 3 +++ 3 files changed, 10 insertions(+) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index bd2edcb2..87ff3bd3 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -32,7 +32,11 @@ {{billing_address.cardholder_name}}
{{billing_address.street_address}}, {{billing_address.postal_code}}
{{billing_address.city}}, {{billing_address.country}} + {% if request.user.vat_number %} +
{% trans "VAT Number" %} {{request.user.vat_number}} + {% endif %} {% endwith %} +

diff --git a/hosting/templates/hosting/invoice_detail.html b/hosting/templates/hosting/invoice_detail.html index 67fa06e4..3463e505 100644 --- a/hosting/templates/hosting/invoice_detail.html +++ b/hosting/templates/hosting/invoice_detail.html @@ -70,6 +70,9 @@ {{invoice.order.billing_address.postal_code}}
{{invoice.order.billing_address.city}}, {{invoice.order.billing_address.country}} + {% if invoice.customer.user.vat_number %} +
{% trans "VAT Number" %} {{invoice.customer.user.vat_number}} + {% endif %} {% endif %}

diff --git a/hosting/templates/hosting/order_detail.html b/hosting/templates/hosting/order_detail.html index b725a645..0ce72fa3 100644 --- a/hosting/templates/hosting/order_detail.html +++ b/hosting/templates/hosting/order_detail.html @@ -67,6 +67,9 @@ {{billing_address.cardholder_name}}
{{billing_address.street_address}}, {{billing_address.postal_code}}
{{billing_address.city}}, {{billing_address.country}} + {% if user.vat_number %} +
{% trans "VAT Number" %} {{user.vat_number}} + {% endif %} {% endwith %} {% endif %}

From 5e97d70a5e6d6403304e0599ca8917fa6bad8750 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 17 Dec 2019 22:52:22 +0530 Subject: [PATCH 037/433] Save VAT number --- datacenterlight/views.py | 5 +++++ hosting/views.py | 3 +++ 2 files changed, 8 insertions(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 8ed0b794..d93fcc2d 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -942,6 +942,11 @@ class OrderConfirmationView(DetailView, FormView): 'user': custom_user.id }) + # Customer is created, we save his VAT Number + custom_user.vat_number = request.session.get( + 'billing_address_data').get("vat_number") + custom_user.save() + if 'generic_payment_type' in request.session: stripe_cus = StripeCustomer.objects.filter( stripe_id=stripe_api_cus_id diff --git a/hosting/views.py b/hosting/views.py index 21ede03e..b81c5ea3 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -616,6 +616,9 @@ class SettingsView(LoginRequiredMixin, FormView): instance=self.request.user.billing_addresses.first(), data=billing_address_data) billing_address_user_form.save() + self.request.user.vat_number = billing_address_data.get( + "vat_number") + self.request.user.save() msg = _("Billing address updated successfully") messages.add_message(request, messages.SUCCESS, msg) else: From 568d8744768dd9f3e7989b04c1c4f43750801e57 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 17 Dec 2019 23:32:49 +0530 Subject: [PATCH 038/433] Add initial value for the vat_number field in the settings --- hosting/views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index b81c5ea3..c2a6cb0a 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -538,9 +538,12 @@ class SettingsView(LoginRequiredMixin, FormView): Check if the user already saved contact details. If so, then show the form populated with those details, to let user change them. """ - return form_class( + + bill_form = form_class( instance=self.request.user.billing_addresses.first(), **self.get_form_kwargs()) + bill_form.fields['vat_number'].initial = self.request.user.vat_number + return bill_form def get_context_data(self, **kwargs): context = super(SettingsView, self).get_context_data(**kwargs) From 6ea486b5276e14a5d4c166a0e1c1fda3319d2394 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 17 Dec 2019 23:48:05 +0530 Subject: [PATCH 039/433] Initialize vat number in payment forms --- datacenterlight/views.py | 3 +++ hosting/views.py | 1 + 2 files changed, 4 insertions(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index d93fcc2d..76abd9b3 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -254,6 +254,9 @@ class PaymentOrderView(FormView): billing_address_form = BillingAddressForm( instance=self.request.user.billing_addresses.first() ) + billing_address_form.fields['vat_number'].initial = ( + self.request.user.vat_number + ) user = self.request.user if hasattr(user, 'stripecustomer'): stripe_customer = user.stripecustomer diff --git a/hosting/views.py b/hosting/views.py index c2a6cb0a..8acd0d28 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -694,6 +694,7 @@ class PaymentVMView(LoginRequiredMixin, FormView): 'city': current_billing_address.city, 'postal_code': current_billing_address.postal_code, 'country': current_billing_address.country, + 'vat_number': self.request.user.vat_number } }) return form_kwargs From 24edf05e7ac8d8c51e5d3313b4bd0bab92be842a Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 17 Dec 2019 23:57:15 +0530 Subject: [PATCH 040/433] Save vat_number after payment is submitted --- datacenterlight/views.py | 3 +++ hosting/views.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 76abd9b3..51466d93 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -491,6 +491,9 @@ class PaymentOrderView(FormView): customer = StripeCustomer.get_or_create( email=this_user.get('email'), token=token ) + request.user.vat_number = address_form.cleaned_data.get( + "vat_number") + request.user.save() else: user_email = address_form.cleaned_data.get('email') user_name = address_form.cleaned_data.get('name') diff --git a/hosting/views.py b/hosting/views.py index 8acd0d28..0c7077e5 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -779,6 +779,8 @@ class PaymentVMView(LoginRequiredMixin, FormView): reverse('hosting:payment') + '#payment_error') request.session['token'] = token request.session['billing_address_data'] = billing_address_data + owner.vat_number = billing_address_data.get("vat_number") + owner.save() self.request.session['order_confirm_url'] = "{url}?{query_params}".format( url=reverse('hosting:order-confirmation'), query_params='page=payment') From f566aa8a2e3c91e88d43b1e343b15007b3817533 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 21 Dec 2019 08:43:34 +0530 Subject: [PATCH 041/433] Make VAT number a part of billing address --- .../templates/datacenterlight/order_detail.html | 7 +++---- datacenterlight/views.py | 12 +----------- hosting/templates/hosting/invoice_detail.html | 4 ++-- hosting/templates/hosting/order_detail.html | 4 ++-- hosting/views.py | 12 ++---------- membership/models.py | 2 -- utils/forms.py | 1 - utils/models.py | 1 + 8 files changed, 11 insertions(+), 32 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 87ff3bd3..b7e28e62 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -32,11 +32,10 @@ {{billing_address.cardholder_name}}
{{billing_address.street_address}}, {{billing_address.postal_code}}
{{billing_address.city}}, {{billing_address.country}} - {% if request.user.vat_number %} -
{% trans "VAT Number" %} {{request.user.vat_number}} + {% if billing_address.vat_number %} +
{% trans "VAT Number" %} {{billing_address.vat_number}} {% endif %} {% endwith %} -

@@ -211,4 +210,4 @@ {% trans "Some problem encountered. Please try again later." as err_msg %} var create_vm_error_message = '{{err_msg|safe}}'; -{%endblock%} \ No newline at end of file +{%endblock%} diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 301049aa..078f64ac 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -254,9 +254,6 @@ class PaymentOrderView(FormView): billing_address_form = BillingAddressForm( instance=self.request.user.billing_addresses.first() ) - billing_address_form.fields['vat_number'].initial = ( - self.request.user.vat_number - ) user = self.request.user if hasattr(user, 'stripecustomer'): stripe_customer = user.stripecustomer @@ -491,9 +488,6 @@ class PaymentOrderView(FormView): customer = StripeCustomer.get_or_create( email=this_user.get('email'), token=token ) - request.user.vat_number = address_form.cleaned_data.get( - "vat_number") - request.user.save() else: user_email = address_form.cleaned_data.get('email') user_name = address_form.cleaned_data.get('name') @@ -949,11 +943,6 @@ class OrderConfirmationView(DetailView, FormView): 'user': custom_user.id }) - # Customer is created, we save his VAT Number - custom_user.vat_number = request.session.get( - 'billing_address_data').get("vat_number") - custom_user.save() - if 'generic_payment_type' in request.session: stripe_cus = StripeCustomer.objects.filter( stripe_id=stripe_api_cus_id @@ -964,6 +953,7 @@ class OrderConfirmationView(DetailView, FormView): city=billing_address_data['city'], postal_code=billing_address_data['postal_code'], country=billing_address_data['country'] + vat_number=billing_address_data['vat_number'] ) billing_address.save() diff --git a/hosting/templates/hosting/invoice_detail.html b/hosting/templates/hosting/invoice_detail.html index 3463e505..e5714b82 100644 --- a/hosting/templates/hosting/invoice_detail.html +++ b/hosting/templates/hosting/invoice_detail.html @@ -70,8 +70,8 @@ {{invoice.order.billing_address.postal_code}}
{{invoice.order.billing_address.city}}, {{invoice.order.billing_address.country}} - {% if invoice.customer.user.vat_number %} -
{% trans "VAT Number" %} {{invoice.customer.user.vat_number}} + {% if invoice.order.billing_address.vat_number %} +
{% trans "VAT Number" %} {{invoice.order.billing_address.vat_number}} {% endif %} {% endif %}

diff --git a/hosting/templates/hosting/order_detail.html b/hosting/templates/hosting/order_detail.html index 0ce72fa3..2ad17276 100644 --- a/hosting/templates/hosting/order_detail.html +++ b/hosting/templates/hosting/order_detail.html @@ -67,8 +67,8 @@ {{billing_address.cardholder_name}}
{{billing_address.street_address}}, {{billing_address.postal_code}}
{{billing_address.city}}, {{billing_address.country}} - {% if user.vat_number %} -
{% trans "VAT Number" %} {{user.vat_number}} + {% if billing_address.vat_number %} +
{% trans "VAT Number" %} {{billing_address.vat_number}} {% endif %} {% endwith %} {% endif %} diff --git a/hosting/views.py b/hosting/views.py index 39b43ad8..3f89496b 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -549,12 +549,9 @@ class SettingsView(LoginRequiredMixin, FormView): Check if the user already saved contact details. If so, then show the form populated with those details, to let user change them. """ - - bill_form = form_class( + return form_class( instance=self.request.user.billing_addresses.first(), **self.get_form_kwargs()) - bill_form.fields['vat_number'].initial = self.request.user.vat_number - return bill_form def get_context_data(self, **kwargs): context = super(SettingsView, self).get_context_data(**kwargs) @@ -630,9 +627,6 @@ class SettingsView(LoginRequiredMixin, FormView): instance=self.request.user.billing_addresses.first(), data=billing_address_data) billing_address_user_form.save() - self.request.user.vat_number = billing_address_data.get( - "vat_number") - self.request.user.save() msg = _("Billing address updated successfully") messages.add_message(request, messages.SUCCESS, msg) else: @@ -705,7 +699,7 @@ class PaymentVMView(LoginRequiredMixin, FormView): 'city': current_billing_address.city, 'postal_code': current_billing_address.postal_code, 'country': current_billing_address.country, - 'vat_number': self.request.user.vat_number + 'vat_number': current_billing_address.vat_number } }) return form_kwargs @@ -790,8 +784,6 @@ class PaymentVMView(LoginRequiredMixin, FormView): reverse('hosting:payment') + '#payment_error') request.session['token'] = token request.session['billing_address_data'] = billing_address_data - owner.vat_number = billing_address_data.get("vat_number") - owner.save() self.request.session['order_confirm_url'] = "{url}?{query_params}".format( url=reverse('hosting:order-confirmation'), query_params='page=payment') diff --git a/membership/models.py b/membership/models.py index 34ef73cc..80aaf408 100644 --- a/membership/models.py +++ b/membership/models.py @@ -117,8 +117,6 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): name = models.CharField(max_length=50, validators=[validate_name]) email = models.EmailField(unique=True) username = models.CharField(max_length=60, unique=True, null=True) - vat_number = models.CharField(max_length=100, default="") - validated = models.IntegerField(choices=VALIDATED_CHOICES, default=0) in_ldap = models.BooleanField(default=False) # By default, we initialize the validation_slug with appropriate value diff --git a/utils/forms.py b/utils/forms.py index 9855e367..71a675bc 100644 --- a/utils/forms.py +++ b/utils/forms.py @@ -120,7 +120,6 @@ class EditCreditCardForm(forms.Form): class BillingAddressForm(forms.ModelForm): token = forms.CharField(widget=forms.HiddenInput(), required=False) card = forms.CharField(widget=forms.HiddenInput(), required=False) - vat_number = forms.CharField(max_length=100) class Meta: model = BillingAddress diff --git a/utils/models.py b/utils/models.py index 8cd529e0..e70dea5c 100644 --- a/utils/models.py +++ b/utils/models.py @@ -13,6 +13,7 @@ class BaseBillingAddress(models.Model): city = models.CharField(max_length=50) postal_code = models.CharField(max_length=50) country = CountryField() + vat_number = models.CharField(max_length=100) class Meta: abstract = True From b919b6cfbdca142da5a5fd01ad7e99e7646d4842 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 21 Dec 2019 08:51:11 +0530 Subject: [PATCH 042/433] Fix wrong indentation --- datacenterlight/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 078f64ac..0763e259 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -952,8 +952,8 @@ class OrderConfirmationView(DetailView, FormView): street_address=billing_address_data['street_address'], city=billing_address_data['city'], postal_code=billing_address_data['postal_code'], - country=billing_address_data['country'] - vat_number=billing_address_data['vat_number'] + country=billing_address_data['country'], + vat_number=billing_address_data['vat_number'] ) billing_address.save() From 202a514b1b00e90eda63b51df614585ea07aa201 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 21 Dec 2019 08:51:43 +0530 Subject: [PATCH 043/433] Set empty default value for vat_number --- utils/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/models.py b/utils/models.py index e70dea5c..3bf5020f 100644 --- a/utils/models.py +++ b/utils/models.py @@ -13,7 +13,7 @@ class BaseBillingAddress(models.Model): city = models.CharField(max_length=50) postal_code = models.CharField(max_length=50) country = CountryField() - vat_number = models.CharField(max_length=100) + vat_number = models.CharField(max_length=100, default="") class Meta: abstract = True From 2d66ae6783eb208898d47d43d627310b2272302c Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 21 Dec 2019 08:52:06 +0530 Subject: [PATCH 044/433] Improve BillingAddress string representation Include VAT number if available --- utils/models.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/utils/models.py b/utils/models.py index 3bf5020f..b6c0ef88 100644 --- a/utils/models.py +++ b/utils/models.py @@ -21,10 +21,16 @@ class BaseBillingAddress(models.Model): class BillingAddress(BaseBillingAddress): def __str__(self): - return "%s, %s, %s, %s, %s" % ( - self.cardholder_name, self.street_address, self.city, - self.postal_code, self.country - ) + if self.vat_number: + return "%s, %s, %s, %s, %s, %s" % ( + self.cardholder_name, self.street_address, self.city, + self.postal_code, self.country, self.vat_number + ) + else: + return "%s, %s, %s, %s, %s" % ( + self.cardholder_name, self.street_address, self.city, + self.postal_code, self.country + ) class UserBillingAddress(BaseBillingAddress): From e3078f3ea9c14d0c98bb9b9a0a9f668d8a000c2f Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 21 Dec 2019 08:52:43 +0530 Subject: [PATCH 045/433] Add migration --- utils/migrations/0007_auto_20191221_0319.py | 36 +++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 utils/migrations/0007_auto_20191221_0319.py diff --git a/utils/migrations/0007_auto_20191221_0319.py b/utils/migrations/0007_auto_20191221_0319.py new file mode 100644 index 00000000..b9fe9b6d --- /dev/null +++ b/utils/migrations/0007_auto_20191221_0319.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-12-21 03:19 +from __future__ import unicode_literals + +from django.db import migrations, models +import utils.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('utils', '0006_auto_20170810_1742'), + ] + + operations = [ + migrations.AddField( + model_name='billingaddress', + name='vat_number', + field=models.CharField(default='', max_length=100), + ), + migrations.AddField( + model_name='userbillingaddress', + name='vat_number', + field=models.CharField(default='', max_length=100), + ), + migrations.AlterField( + model_name='billingaddress', + name='country', + field=utils.fields.CountryField(choices=[('AD', 'Andorra'), ('AE', 'United Arab Emirates'), ('AF', 'Afghanistan'), ('AG', 'Antigua & Barbuda'), ('AI', 'Anguilla'), ('AL', 'Albania'), ('AM', 'Armenia'), ('AN', 'Netherlands Antilles'), ('AO', 'Angola'), ('AQ', 'Antarctica'), ('AR', 'Argentina'), ('AS', 'American Samoa'), ('AT', 'Austria'), ('AU', 'Australia'), ('AW', 'Aruba'), ('AZ', 'Azerbaijan'), ('BA', 'Bosnia and Herzegovina'), ('BB', 'Barbados'), ('BD', 'Bangladesh'), ('BE', 'Belgium'), ('BF', 'Burkina Faso'), ('BG', 'Bulgaria'), ('BH', 'Bahrain'), ('BI', 'Burundi'), ('BJ', 'Benin'), ('BM', 'Bermuda'), ('BN', 'Brunei Darussalam'), ('BO', 'Bolivia'), ('BR', 'Brazil'), ('BS', 'Bahama'), ('BT', 'Bhutan'), ('BV', 'Bouvet Island'), ('BW', 'Botswana'), ('BY', 'Belarus'), ('BZ', 'Belize'), ('CA', 'Canada'), ('CC', 'Cocos (Keeling) Islands'), ('CF', 'Central African Republic'), ('CG', 'Congo'), ('CH', 'Switzerland'), ('CI', 'Ivory Coast'), ('CK', 'Cook Iislands'), ('CL', 'Chile'), ('CM', 'Cameroon'), ('CN', 'China'), ('CO', 'Colombia'), ('CR', 'Costa Rica'), ('CU', 'Cuba'), ('CV', 'Cape Verde'), ('CX', 'Christmas Island'), ('CY', 'Cyprus'), ('CZ', 'Czech Republic'), ('DE', 'Germany'), ('DJ', 'Djibouti'), ('DK', 'Denmark'), ('DM', 'Dominica'), ('DO', 'Dominican Republic'), ('DZ', 'Algeria'), ('EC', 'Ecuador'), ('EE', 'Estonia'), ('EG', 'Egypt'), ('EH', 'Western Sahara'), ('ER', 'Eritrea'), ('ES', 'Spain'), ('ET', 'Ethiopia'), ('FI', 'Finland'), ('FJ', 'Fiji'), ('FK', 'Falkland Islands (Malvinas)'), ('FM', 'Micronesia'), ('FO', 'Faroe Islands'), ('FR', 'France'), ('FX', 'France, Metropolitan'), ('GA', 'Gabon'), ('GB', 'United Kingdom (Great Britain)'), ('GD', 'Grenada'), ('GE', 'Georgia'), ('GF', 'French Guiana'), ('GH', 'Ghana'), ('GI', 'Gibraltar'), ('GL', 'Greenland'), ('GM', 'Gambia'), ('GN', 'Guinea'), ('GP', 'Guadeloupe'), ('GQ', 'Equatorial Guinea'), ('GR', 'Greece'), ('GS', 'South Georgia and the South Sandwich Islands'), ('GT', 'Guatemala'), ('GU', 'Guam'), ('GW', 'Guinea-Bissau'), ('GY', 'Guyana'), ('HK', 'Hong Kong'), ('HM', 'Heard & McDonald Islands'), ('HN', 'Honduras'), ('HR', 'Croatia'), ('HT', 'Haiti'), ('HU', 'Hungary'), ('ID', 'Indonesia'), ('IE', 'Ireland'), ('IL', 'Israel'), ('IN', 'India'), ('IO', 'British Indian Ocean Territory'), ('IQ', 'Iraq'), ('IR', 'Islamic Republic of Iran'), ('IS', 'Iceland'), ('IT', 'Italy'), ('JM', 'Jamaica'), ('JO', 'Jordan'), ('JP', 'Japan'), ('KE', 'Kenya'), ('KG', 'Kyrgyzstan'), ('KH', 'Cambodia'), ('KI', 'Kiribati'), ('KM', 'Comoros'), ('KN', 'St. Kitts and Nevis'), ('KP', "Korea, Democratic People's Republic of"), ('KR', 'Korea, Republic of'), ('KW', 'Kuwait'), ('KY', 'Cayman Islands'), ('KZ', 'Kazakhstan'), ('LA', "Lao People's Democratic Republic"), ('LB', 'Lebanon'), ('LC', 'Saint Lucia'), ('LI', 'Liechtenstein'), ('LK', 'Sri Lanka'), ('LR', 'Liberia'), ('LS', 'Lesotho'), ('LT', 'Lithuania'), ('LU', 'Luxembourg'), ('LV', 'Latvia'), ('LY', 'Libyan Arab Jamahiriya'), ('MA', 'Morocco'), ('MC', 'Monaco'), ('MD', 'Moldova, Republic of'), ('MG', 'Madagascar'), ('MH', 'Marshall Islands'), ('ML', 'Mali'), ('MN', 'Mongolia'), ('MM', 'Myanmar'), ('MO', 'Macau'), ('MP', 'Northern Mariana Islands'), ('MQ', 'Martinique'), ('MR', 'Mauritania'), ('MS', 'Monserrat'), ('MT', 'Malta'), ('MU', 'Mauritius'), ('MV', 'Maldives'), ('MW', 'Malawi'), ('MX', 'Mexico'), ('MY', 'Malaysia'), ('MZ', 'Mozambique'), ('NA', 'Namibia'), ('NC', 'New Caledonia'), ('NE', 'Niger'), ('NF', 'Norfolk Island'), ('NG', 'Nigeria'), ('NI', 'Nicaragua'), ('NL', 'Netherlands'), ('NO', 'Norway'), ('NP', 'Nepal'), ('NR', 'Nauru'), ('NU', 'Niue'), ('NZ', 'New Zealand'), ('OM', 'Oman'), ('PA', 'Panama'), ('PE', 'Peru'), ('PF', 'French Polynesia'), ('PG', 'Papua New Guinea'), ('PH', 'Philippines'), ('PK', 'Pakistan'), ('PL', 'Poland'), ('PM', 'St. Pierre & Miquelon'), ('PN', 'Pitcairn'), ('PR', 'Puerto Rico'), ('PT', 'Portugal'), ('PW', 'Palau'), ('PY', 'Paraguay'), ('QA', 'Qatar'), ('RE', 'Reunion'), ('RO', 'Romania'), ('RU', 'Russian Federation'), ('RW', 'Rwanda'), ('SA', 'Saudi Arabia'), ('SB', 'Solomon Islands'), ('SC', 'Seychelles'), ('SD', 'Sudan'), ('SE', 'Sweden'), ('SG', 'Singapore'), ('SH', 'St. Helena'), ('SI', 'Slovenia'), ('SJ', 'Svalbard & Jan Mayen Islands'), ('SK', 'Slovakia'), ('SL', 'Sierra Leone'), ('SM', 'San Marino'), ('SN', 'Senegal'), ('SO', 'Somalia'), ('SR', 'Suriname'), ('ST', 'Sao Tome & Principe'), ('SV', 'El Salvador'), ('SY', 'Syrian Arab Republic'), ('SZ', 'Swaziland'), ('TC', 'Turks & Caicos Islands'), ('TD', 'Chad'), ('TF', 'French Southern Territories'), ('TG', 'Togo'), ('TH', 'Thailand'), ('TJ', 'Tajikistan'), ('TK', 'Tokelau'), ('TM', 'Turkmenistan'), ('TN', 'Tunisia'), ('TO', 'Tonga'), ('TP', 'East Timor'), ('TR', 'Turkey'), ('TT', 'Trinidad & Tobago'), ('TV', 'Tuvalu'), ('TW', 'Taiwan, Province of China'), ('TZ', 'Tanzania, United Republic of'), ('UA', 'Ukraine'), ('UG', 'Uganda'), ('UM', 'United States Minor Outlying Islands'), ('US', 'United States of America'), ('UY', 'Uruguay'), ('UZ', 'Uzbekistan'), ('VA', 'Vatican City State (Holy See)'), ('VC', 'St. Vincent & the Grenadines'), ('VE', 'Venezuela'), ('VG', 'British Virgin Islands'), ('VI', 'United States Virgin Islands'), ('VN', 'Viet Nam'), ('VU', 'Vanuatu'), ('WF', 'Wallis & Futuna Islands'), ('WS', 'Samoa'), ('YE', 'Yemen'), ('YT', 'Mayotte'), ('YU', 'Yugoslavia'), ('ZA', 'South Africa'), ('ZM', 'Zambia'), ('ZR', 'Zaire'), ('ZW', 'Zimbabwe')], default='CH', max_length=2), + ), + migrations.AlterField( + model_name='userbillingaddress', + name='country', + field=utils.fields.CountryField(choices=[('AD', 'Andorra'), ('AE', 'United Arab Emirates'), ('AF', 'Afghanistan'), ('AG', 'Antigua & Barbuda'), ('AI', 'Anguilla'), ('AL', 'Albania'), ('AM', 'Armenia'), ('AN', 'Netherlands Antilles'), ('AO', 'Angola'), ('AQ', 'Antarctica'), ('AR', 'Argentina'), ('AS', 'American Samoa'), ('AT', 'Austria'), ('AU', 'Australia'), ('AW', 'Aruba'), ('AZ', 'Azerbaijan'), ('BA', 'Bosnia and Herzegovina'), ('BB', 'Barbados'), ('BD', 'Bangladesh'), ('BE', 'Belgium'), ('BF', 'Burkina Faso'), ('BG', 'Bulgaria'), ('BH', 'Bahrain'), ('BI', 'Burundi'), ('BJ', 'Benin'), ('BM', 'Bermuda'), ('BN', 'Brunei Darussalam'), ('BO', 'Bolivia'), ('BR', 'Brazil'), ('BS', 'Bahama'), ('BT', 'Bhutan'), ('BV', 'Bouvet Island'), ('BW', 'Botswana'), ('BY', 'Belarus'), ('BZ', 'Belize'), ('CA', 'Canada'), ('CC', 'Cocos (Keeling) Islands'), ('CF', 'Central African Republic'), ('CG', 'Congo'), ('CH', 'Switzerland'), ('CI', 'Ivory Coast'), ('CK', 'Cook Iislands'), ('CL', 'Chile'), ('CM', 'Cameroon'), ('CN', 'China'), ('CO', 'Colombia'), ('CR', 'Costa Rica'), ('CU', 'Cuba'), ('CV', 'Cape Verde'), ('CX', 'Christmas Island'), ('CY', 'Cyprus'), ('CZ', 'Czech Republic'), ('DE', 'Germany'), ('DJ', 'Djibouti'), ('DK', 'Denmark'), ('DM', 'Dominica'), ('DO', 'Dominican Republic'), ('DZ', 'Algeria'), ('EC', 'Ecuador'), ('EE', 'Estonia'), ('EG', 'Egypt'), ('EH', 'Western Sahara'), ('ER', 'Eritrea'), ('ES', 'Spain'), ('ET', 'Ethiopia'), ('FI', 'Finland'), ('FJ', 'Fiji'), ('FK', 'Falkland Islands (Malvinas)'), ('FM', 'Micronesia'), ('FO', 'Faroe Islands'), ('FR', 'France'), ('FX', 'France, Metropolitan'), ('GA', 'Gabon'), ('GB', 'United Kingdom (Great Britain)'), ('GD', 'Grenada'), ('GE', 'Georgia'), ('GF', 'French Guiana'), ('GH', 'Ghana'), ('GI', 'Gibraltar'), ('GL', 'Greenland'), ('GM', 'Gambia'), ('GN', 'Guinea'), ('GP', 'Guadeloupe'), ('GQ', 'Equatorial Guinea'), ('GR', 'Greece'), ('GS', 'South Georgia and the South Sandwich Islands'), ('GT', 'Guatemala'), ('GU', 'Guam'), ('GW', 'Guinea-Bissau'), ('GY', 'Guyana'), ('HK', 'Hong Kong'), ('HM', 'Heard & McDonald Islands'), ('HN', 'Honduras'), ('HR', 'Croatia'), ('HT', 'Haiti'), ('HU', 'Hungary'), ('ID', 'Indonesia'), ('IE', 'Ireland'), ('IL', 'Israel'), ('IN', 'India'), ('IO', 'British Indian Ocean Territory'), ('IQ', 'Iraq'), ('IR', 'Islamic Republic of Iran'), ('IS', 'Iceland'), ('IT', 'Italy'), ('JM', 'Jamaica'), ('JO', 'Jordan'), ('JP', 'Japan'), ('KE', 'Kenya'), ('KG', 'Kyrgyzstan'), ('KH', 'Cambodia'), ('KI', 'Kiribati'), ('KM', 'Comoros'), ('KN', 'St. Kitts and Nevis'), ('KP', "Korea, Democratic People's Republic of"), ('KR', 'Korea, Republic of'), ('KW', 'Kuwait'), ('KY', 'Cayman Islands'), ('KZ', 'Kazakhstan'), ('LA', "Lao People's Democratic Republic"), ('LB', 'Lebanon'), ('LC', 'Saint Lucia'), ('LI', 'Liechtenstein'), ('LK', 'Sri Lanka'), ('LR', 'Liberia'), ('LS', 'Lesotho'), ('LT', 'Lithuania'), ('LU', 'Luxembourg'), ('LV', 'Latvia'), ('LY', 'Libyan Arab Jamahiriya'), ('MA', 'Morocco'), ('MC', 'Monaco'), ('MD', 'Moldova, Republic of'), ('MG', 'Madagascar'), ('MH', 'Marshall Islands'), ('ML', 'Mali'), ('MN', 'Mongolia'), ('MM', 'Myanmar'), ('MO', 'Macau'), ('MP', 'Northern Mariana Islands'), ('MQ', 'Martinique'), ('MR', 'Mauritania'), ('MS', 'Monserrat'), ('MT', 'Malta'), ('MU', 'Mauritius'), ('MV', 'Maldives'), ('MW', 'Malawi'), ('MX', 'Mexico'), ('MY', 'Malaysia'), ('MZ', 'Mozambique'), ('NA', 'Namibia'), ('NC', 'New Caledonia'), ('NE', 'Niger'), ('NF', 'Norfolk Island'), ('NG', 'Nigeria'), ('NI', 'Nicaragua'), ('NL', 'Netherlands'), ('NO', 'Norway'), ('NP', 'Nepal'), ('NR', 'Nauru'), ('NU', 'Niue'), ('NZ', 'New Zealand'), ('OM', 'Oman'), ('PA', 'Panama'), ('PE', 'Peru'), ('PF', 'French Polynesia'), ('PG', 'Papua New Guinea'), ('PH', 'Philippines'), ('PK', 'Pakistan'), ('PL', 'Poland'), ('PM', 'St. Pierre & Miquelon'), ('PN', 'Pitcairn'), ('PR', 'Puerto Rico'), ('PT', 'Portugal'), ('PW', 'Palau'), ('PY', 'Paraguay'), ('QA', 'Qatar'), ('RE', 'Reunion'), ('RO', 'Romania'), ('RU', 'Russian Federation'), ('RW', 'Rwanda'), ('SA', 'Saudi Arabia'), ('SB', 'Solomon Islands'), ('SC', 'Seychelles'), ('SD', 'Sudan'), ('SE', 'Sweden'), ('SG', 'Singapore'), ('SH', 'St. Helena'), ('SI', 'Slovenia'), ('SJ', 'Svalbard & Jan Mayen Islands'), ('SK', 'Slovakia'), ('SL', 'Sierra Leone'), ('SM', 'San Marino'), ('SN', 'Senegal'), ('SO', 'Somalia'), ('SR', 'Suriname'), ('ST', 'Sao Tome & Principe'), ('SV', 'El Salvador'), ('SY', 'Syrian Arab Republic'), ('SZ', 'Swaziland'), ('TC', 'Turks & Caicos Islands'), ('TD', 'Chad'), ('TF', 'French Southern Territories'), ('TG', 'Togo'), ('TH', 'Thailand'), ('TJ', 'Tajikistan'), ('TK', 'Tokelau'), ('TM', 'Turkmenistan'), ('TN', 'Tunisia'), ('TO', 'Tonga'), ('TP', 'East Timor'), ('TR', 'Turkey'), ('TT', 'Trinidad & Tobago'), ('TV', 'Tuvalu'), ('TW', 'Taiwan, Province of China'), ('TZ', 'Tanzania, United Republic of'), ('UA', 'Ukraine'), ('UG', 'Uganda'), ('UM', 'United States Minor Outlying Islands'), ('US', 'United States of America'), ('UY', 'Uruguay'), ('UZ', 'Uzbekistan'), ('VA', 'Vatican City State (Holy See)'), ('VC', 'St. Vincent & the Grenadines'), ('VE', 'Venezuela'), ('VG', 'British Virgin Islands'), ('VI', 'United States Virgin Islands'), ('VN', 'Viet Nam'), ('VU', 'Vanuatu'), ('WF', 'Wallis & Futuna Islands'), ('WS', 'Samoa'), ('YE', 'Yemen'), ('YT', 'Mayotte'), ('YU', 'Yugoslavia'), ('ZA', 'South Africa'), ('ZM', 'Zambia'), ('ZR', 'Zaire'), ('ZW', 'Zimbabwe')], default='CH', max_length=2), + ), + ] From 32cfdea68c9c744068d23247c75c68b820d05dc5 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 21 Dec 2019 10:05:58 +0530 Subject: [PATCH 046/433] Add missing vat_number field to user billing address --- utils/forms.py | 1 + utils/models.py | 15 +++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/utils/forms.py b/utils/forms.py index 71a675bc..6e8f4606 100644 --- a/utils/forms.py +++ b/utils/forms.py @@ -182,6 +182,7 @@ class UserBillingAddressForm(forms.ModelForm): 'city': _('City'), 'postal_code': _('Postal Code'), 'Country': _('Country'), + 'vat_number': _('VAT Number'), } diff --git a/utils/models.py b/utils/models.py index b6c0ef88..0084ddd4 100644 --- a/utils/models.py +++ b/utils/models.py @@ -38,10 +38,16 @@ class UserBillingAddress(BaseBillingAddress): current = models.BooleanField(default=True) def __str__(self): - return "%s, %s, %s, %s, %s" % ( - self.cardholder_name, self.street_address, self.city, - self.postal_code, self.country - ) + if self.vat_number: + return "%s, %s, %s, %s, %s, %s" % ( + self.cardholder_name, self.street_address, self.city, + self.postal_code, self.country, self.vat_number + ) + else: + return "%s, %s, %s, %s, %s" % ( + self.cardholder_name, self.street_address, self.city, + self.postal_code, self.country + ) def to_dict(self): return { @@ -50,6 +56,7 @@ class UserBillingAddress(BaseBillingAddress): 'City': self.city, 'Postal Code': self.postal_code, 'Country': self.country, + 'VAT Number': self.vat_number } From 8409acf02dfe06c3499a319888c368cf8ac37d3a Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 21 Dec 2019 10:10:32 +0530 Subject: [PATCH 047/433] Add missing vat_number field --- utils/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/forms.py b/utils/forms.py index 6e8f4606..f35c90f4 100644 --- a/utils/forms.py +++ b/utils/forms.py @@ -175,7 +175,7 @@ class UserBillingAddressForm(forms.ModelForm): class Meta: model = UserBillingAddress fields = ['cardholder_name', 'street_address', - 'city', 'postal_code', 'country', 'user'] + 'city', 'postal_code', 'country', 'user', 'vat_number'] labels = { 'cardholder_name': _('Cardholder Name'), 'street_address': _('Street Building'), From e48ae6a39d0101a2b3ccc740bc419cf35e8e03fb Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 08:31:40 +0530 Subject: [PATCH 048/433] Update migration to make vat_number non mandatory --- ...007_auto_20191221_0319.py => 0007_auto_20191225_0300.py} | 6 +++--- utils/models.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename utils/migrations/{0007_auto_20191221_0319.py => 0007_auto_20191225_0300.py} (98%) diff --git a/utils/migrations/0007_auto_20191221_0319.py b/utils/migrations/0007_auto_20191225_0300.py similarity index 98% rename from utils/migrations/0007_auto_20191221_0319.py rename to utils/migrations/0007_auto_20191225_0300.py index b9fe9b6d..72f5c836 100644 --- a/utils/migrations/0007_auto_20191221_0319.py +++ b/utils/migrations/0007_auto_20191225_0300.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.9.4 on 2019-12-21 03:19 +# Generated by Django 1.9.4 on 2019-12-25 03:00 from __future__ import unicode_literals from django.db import migrations, models @@ -16,12 +16,12 @@ class Migration(migrations.Migration): migrations.AddField( model_name='billingaddress', name='vat_number', - field=models.CharField(default='', max_length=100), + field=models.CharField(blank=True, default='', max_length=100), ), migrations.AddField( model_name='userbillingaddress', name='vat_number', - field=models.CharField(default='', max_length=100), + field=models.CharField(blank=True, default='', max_length=100), ), migrations.AlterField( model_name='billingaddress', diff --git a/utils/models.py b/utils/models.py index 0084ddd4..a10aad92 100644 --- a/utils/models.py +++ b/utils/models.py @@ -13,7 +13,7 @@ class BaseBillingAddress(models.Model): city = models.CharField(max_length=50) postal_code = models.CharField(max_length=50) country = CountryField() - vat_number = models.CharField(max_length=100, default="") + vat_number = models.CharField(max_length=100, default="", blank=True) class Meta: abstract = True From 8ebd12c420a50946a511767cb046e5eda6d171e9 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 10:32:37 +0530 Subject: [PATCH 049/433] Add missing vat_number while saving billing address --- datacenterlight/utils.py | 3 ++- digitalglarus/forms.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 11d2b82e..08dd73aa 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -34,7 +34,8 @@ def create_vm(billing_address_data, stripe_customer_id, specs, street_address=billing_address_data['street_address'], city=billing_address_data['city'], postal_code=billing_address_data['postal_code'], - country=billing_address_data['country'] + country=billing_address_data['country'], + vat_number=billing_address_data['vat_number'], ) billing_address.save() customer = StripeCustomer.objects.filter(id=stripe_customer_id).first() diff --git a/digitalglarus/forms.py b/digitalglarus/forms.py index a0e685f8..200099e3 100644 --- a/digitalglarus/forms.py +++ b/digitalglarus/forms.py @@ -35,6 +35,7 @@ class MembershipBillingForm(BillingAddressForm): 'city': _('City'), 'postal_code': _('Postal Code'), 'country': _('Country'), + 'vat_number': _('VAT Number'), } From 92570ada7fabbca225184250594dad4b2f201f12 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 10:37:45 +0530 Subject: [PATCH 050/433] Add VAT number to order detail (hosting) --- hosting/templates/hosting/order_detail.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hosting/templates/hosting/order_detail.html b/hosting/templates/hosting/order_detail.html index 2ad17276..9256271a 100644 --- a/hosting/templates/hosting/order_detail.html +++ b/hosting/templates/hosting/order_detail.html @@ -62,6 +62,9 @@ {{user.name}}
{{order.billing_address.street_address}}, {{order.billing_address.postal_code}}
{{order.billing_address.city}}, {{order.billing_address.country}} + {% if order.billing_address.vat_number %} +
{% trans "VAT Number" %} {{order.billing_address.vat_number}} + {% endif %} {% else %} {% with request.session.billing_address_data as billing_address %} {{billing_address.cardholder_name}}
From c6db34efdd30a2ad793ade660536cccdcf991d3e Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 11:00:04 +0530 Subject: [PATCH 051/433] Add DateTimeField vat_number_validated_on --- utils/migrations/0008_auto_20191225_0529.py | 25 +++++++++++++++++++++ utils/models.py | 1 + 2 files changed, 26 insertions(+) create mode 100644 utils/migrations/0008_auto_20191225_0529.py diff --git a/utils/migrations/0008_auto_20191225_0529.py b/utils/migrations/0008_auto_20191225_0529.py new file mode 100644 index 00000000..20a6183c --- /dev/null +++ b/utils/migrations/0008_auto_20191225_0529.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-12-25 05:29 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('utils', '0007_auto_20191225_0300'), + ] + + operations = [ + migrations.AddField( + model_name='billingaddress', + name='vat_number_validated_on', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='userbillingaddress', + name='vat_number_validated_on', + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/utils/models.py b/utils/models.py index a10aad92..b8e0110c 100644 --- a/utils/models.py +++ b/utils/models.py @@ -14,6 +14,7 @@ class BaseBillingAddress(models.Model): postal_code = models.CharField(max_length=50) country = CountryField() vat_number = models.CharField(max_length=100, default="", blank=True) + vat_number_validated_on = models.DateTimeField(blank=True, null=True) class Meta: abstract = True From 50043f8283acd0b27aa320b27469215a2ae3d22d Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 12:03:58 +0530 Subject: [PATCH 052/433] Add webhook management command Handles managing stripe webhooks --- webhook/management/commands/webhook.py | 81 ++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 webhook/management/commands/webhook.py diff --git a/webhook/management/commands/webhook.py b/webhook/management/commands/webhook.py new file mode 100644 index 00000000..4ceb2755 --- /dev/null +++ b/webhook/management/commands/webhook.py @@ -0,0 +1,81 @@ +import logging + +import stripe +from django.core.management.base import BaseCommand + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + help = '''creates webhook with the supplied arguments and returns the + webhook secret + ''' + + def add_arguments(self, parser): + parser.add_argument( + '--webhook_endpoint', + help="The url of the webhook endpoint that accepts the events " + "from stripe", + dest="webhook_endpoint", + required=False + ) + parser.add_argument('--events_csv', dest="events_csv", required=False) + parser.add_argument('--webhook_id', dest="webhook_id", required=False) + parser.add_argument('--create', dest='create', action='store_true') + parser.add_argument('--list', dest='list', action='store_true') + parser.add_argument('--delete', dest='delete', action='store_true') + + def handle(self, *args, **options): + wep_exists = False + if options['list']: + logger.debug("Listing webhooks") + we_list = stripe.WebhookEndpoint.list(limit=100) + for wep in we_list.data: + msg = wep.id + " -- " + ",".join(wep.enabled_events) + logger.debug(msg) + self.stdout.write( + self.style.SUCCESS(msg) + ) + elif options['delete']: + logger.debug("Deleting webhook") + if 'webhook_id' in options: + stripe.WebhookEndpoint.delete(options['webhook_id']) + msg = "Deleted " + options['webhook_id'] + logger.debug(msg) + self.stdout.write( + self.style.SUCCESS(msg) + ) + else: + msg = "Supply webhook_id to delete a webhook" + logger.debug(msg) + self.stdout.write( + self.style.SUCCESS(msg) + ) + exit(0) + elif options['create']: + logger.debug("Creating webhook") + try: + we_list = stripe.WebhookEndpoint.list(limit=100) + for wep in we_list.data: + if set(wep.enabled_events) == set(options['events_csv'].split(",")): + if wep.url == options['webhook_endpoint']: + logger.debug("We have this webhook already") + wep_exists = True + break + if wep_exists is False: + logger.debug( + "No webhook exists for {} at {}. Creatting a new endpoint " + "now".format( + options['webhook_endpoint'], options['events_csv'] + ) + ) + wep = stripe.WebhookEndpoint.create( + url=options['webhook_endpoint'], + enabled_events=options['events_csv'].split(",") + ) + self.stdout.write( + self.style.SUCCESS('Creation successful. ' + 'webhook_secret = %s' % wep.secret) + ) + except Exception as e: + print(" *** Error occurred. Details {}".format(str(e))) From cb065d36dff49ed7a8dcfbd798c29ac189dd846f Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 12:07:09 +0530 Subject: [PATCH 053/433] Add webhook handling url --- dynamicweb/urls.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dynamicweb/urls.py b/dynamicweb/urls.py index 37bb69a4..113a5cda 100644 --- a/dynamicweb/urls.py +++ b/dynamicweb/urls.py @@ -11,6 +11,7 @@ from hosting.views import ( RailsHostingView, DjangoHostingView, NodeJSHostingView ) from datacenterlight.views import PaymentOrderView +from webhook import views as webhook_views from membership import urls as membership_urls from ungleich_page.views import LandingView from django.views.generic import RedirectView @@ -62,6 +63,7 @@ urlpatterns += i18n_patterns( name='blog_list_view'), url(r'^cms/', include('cms.urls')), url(r'^blog/', include('djangocms_blog.urls', namespace='djangocms_blog')), + url(r'^webhooks/', webhook_views.handle_webhook), url(r'^$', RedirectView.as_view(url='/cms') if REDIRECT_TO_CMS else LandingView.as_view()), url(r'^', include('ungleich_page.urls', namespace='ungleich_page')), From f6832c090e35d360cdba4e9b33eb429961d9fedb Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 12:07:42 +0530 Subject: [PATCH 054/433] Add webhooks/views.py --- webhook/views.py | 77 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 webhook/views.py diff --git a/webhook/views.py b/webhook/views.py new file mode 100644 index 00000000..38c18d51 --- /dev/null +++ b/webhook/views.py @@ -0,0 +1,77 @@ +import datetime +import logging + +import stripe +# Create your views here. +from django.conf import settings +from django.http import HttpResponse +from django.views.decorators.csrf import csrf_exempt +from django.views.decorators.http import require_POST + +from utils.models import BillingAddress, UserBillingAddress +from utils.tasks import send_plain_email_task + +logger = logging.getLogger(__name__) + + +@require_POST +@csrf_exempt +def handle_webhook(request): + payload = request.body + event = None + + try: + if 'HTTP_STRIPE_SIGNATURE' in request.META: + sig_header = request.META['HTTP_STRIPE_SIGNATURE'] + else: + logger.error("No HTTP_STRIPE_SIGNATURE header") + # Invalid payload + return HttpResponse(status=400) + event = stripe.Webhook.construct_event( + payload, sig_header, settings.WEBHOOK_SECRET + ) + except ValueError as e: + # Invalid payload + err_msg = "FAILURE handle_invoice_webhook: Invalid payload details" + err_body = "Details %s" % str(e) + return handle_error(err_msg, err_body) + except stripe.error.SignatureVerificationError as e: + # Invalid signature + err_msg = "FAILURE handle_invoice_webhook: SignatureVerificationError" + err_body = "Details %s" % str(e) + return handle_error(err_msg, err_body) + + # Do something with event + logger.debug("Passed signature verification") + + if event.type == "customer.tax_id.updated": + logger.debug("Webhook Event: customer.tax_id.updated") + tax_id_obj = event.data.object + logger.debug("Tax_id %s is %s" % tax_id_obj.id, + tax_id_obj.verification.status) + if tax_id_obj.verification.status == "verified": + b_addresses = BillingAddress.objects.filter(vat_number=tax_id_obj.value) + for b_address in b_addresses: + b_address.vat_number_validated_on = datetime.datetime.now() + + ub_addresses = UserBillingAddress.objects.filter(vat_number=tax_id_obj.value) + for ub_address in ub_addresses: + ub_address.vat_number_validated_on = datetime.datetime.now() + else: + logger.debug("Tax_id %s is %s" % tax_id_obj.id, + tax_id_obj.verification.status) + else: + logger.error("Unhandled event : " + event.type) + return HttpResponse(status=200) + + +def handle_error(error_msg, error_body): + logger.error("%s -- %s" % (error_msg, error_body)) + email_to_admin_data = { + 'subject': error_msg, + 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': [settings.ADMIN_EMAIL], + 'body': error_body, + } + send_plain_email_task.delay(email_to_admin_data) + return HttpResponse(status=400) \ No newline at end of file From 55979f37016aee7043f513dfdc0d4bda302db3ae Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 12:09:04 +0530 Subject: [PATCH 055/433] Add webhook to installed apps + introduce some webhook constants --- dynamicweb/settings/base.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index bdbb8f8f..e026245e 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -153,6 +153,7 @@ INSTALLED_APPS = ( 'rest_framework', 'opennebula_api', 'django_celery_results', + 'webhook', ) MIDDLEWARE_CLASSES = ( @@ -720,7 +721,10 @@ X_FRAME_OPTIONS = ('SAMEORIGIN' if X_FRAME_OPTIONS_ALLOW_FROM_URI is None else X_FRAME_OPTIONS_ALLOW_FROM_URI.strip() )) +WEBHOOK_SECRET = env('WEBHOOK_SECRET') + DEBUG = bool_env('DEBUG') +ADD_TRIAL_PERIOD_TO_SUBSCRIPTION = bool_env('ADD_TRIAL_PERIOD_TO_SUBSCRIPTION') # LDAP setup From 772c7557e3f7d39da00053cf35f82d01cc082895 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 12:09:43 +0530 Subject: [PATCH 056/433] Update Stripe version to 2.24.1 (supports webhook) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5fb2ec67..874fc1a4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -79,7 +79,7 @@ requests==2.10.0 rjsmin==1.0.12 six==1.10.0 sqlparse==0.1.19 -stripe==1.33.0 +stripe==2.24.1 wheel==0.29.0 django-admin-honeypot==1.0.0 coverage==4.3.4 From 127c83059f4802158d82b07b134c3ca1a73ee207 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 12:20:26 +0530 Subject: [PATCH 057/433] Add stub files from webhook app --- webhook/__init__.py | 0 webhook/admin.py | 3 +++ webhook/apps.py | 5 +++++ webhook/migrations/__init__.py | 0 webhook/models.py | 3 +++ webhook/tests.py | 3 +++ 6 files changed, 14 insertions(+) create mode 100644 webhook/__init__.py create mode 100644 webhook/admin.py create mode 100644 webhook/apps.py create mode 100644 webhook/migrations/__init__.py create mode 100644 webhook/models.py create mode 100644 webhook/tests.py diff --git a/webhook/__init__.py b/webhook/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/webhook/admin.py b/webhook/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/webhook/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/webhook/apps.py b/webhook/apps.py new file mode 100644 index 00000000..1473609d --- /dev/null +++ b/webhook/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class WebhookConfig(AppConfig): + name = 'webhook' diff --git a/webhook/migrations/__init__.py b/webhook/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/webhook/models.py b/webhook/models.py new file mode 100644 index 00000000..d49766e4 --- /dev/null +++ b/webhook/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. \ No newline at end of file diff --git a/webhook/tests.py b/webhook/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/webhook/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. From 80f01aec07c1f7e27bf75bb7c147f1b699c73d85 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 12:36:47 +0530 Subject: [PATCH 058/433] Add steps to be followed for tax_id updated webhook --- webhook/views.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/webhook/views.py b/webhook/views.py index 38c18d51..17718b13 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -14,6 +14,27 @@ from utils.tasks import send_plain_email_task logger = logging.getLogger(__name__) +# Things to do for webhooks feature +# 1. Uninstall old version and install a more recent version of stripe +# ``` +# source venv/bin/activate +# ./manage.py shell +# pip uninstall stripe +# pip install stripe==2.24.1 +# ``` +# 2. Create tax id updated webhook +# ``` +# ./manage.py webhook --create \ +# --webhook_endpoint https://datacenterlight.ch/en-us/webhooks/ \ +# --events_csv customer.tax_id.updated +# ``` +# +# 3. From the secret obtained in 2, setup an environment variable +# ``` +# WEBHOOK_SECRET='whsec......' +# ``` + + @require_POST @csrf_exempt def handle_webhook(request): From 89962542121fc44139af503ae930907c0e576680 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 18:44:43 +0530 Subject: [PATCH 059/433] Save stripe tax id --- utils/migrations/0009_auto_20191225_1311.py | 25 +++++++++++++++++++++ utils/models.py | 1 + 2 files changed, 26 insertions(+) create mode 100644 utils/migrations/0009_auto_20191225_1311.py diff --git a/utils/migrations/0009_auto_20191225_1311.py b/utils/migrations/0009_auto_20191225_1311.py new file mode 100644 index 00000000..866a5d12 --- /dev/null +++ b/utils/migrations/0009_auto_20191225_1311.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-12-25 13:11 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('utils', '0008_auto_20191225_0529'), + ] + + operations = [ + migrations.AddField( + model_name='billingaddress', + name='stripe_tax_id', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='userbillingaddress', + name='stripe_tax_id', + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/utils/models.py b/utils/models.py index b8e0110c..c1a5e7b4 100644 --- a/utils/models.py +++ b/utils/models.py @@ -14,6 +14,7 @@ class BaseBillingAddress(models.Model): postal_code = models.CharField(max_length=50) country = CountryField() vat_number = models.CharField(max_length=100, default="", blank=True) + stripe_tax_id = models.DateTimeField(blank=True, null=True) vat_number_validated_on = models.DateTimeField(blank=True, null=True) class Meta: From 1400d27afa8351b0047cc37cc39c380e9d0e1c01 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 20:11:15 +0530 Subject: [PATCH 060/433] Handle tax_id creation --- .../datacenterlight/order_detail.html | 7 +- datacenterlight/utils.py | 69 ++++++++++++++++++- datacenterlight/views.py | 31 +++++++-- 3 files changed, 101 insertions(+), 6 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index b7e28e62..5f61ccf3 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -33,7 +33,12 @@ {{billing_address.street_address}}, {{billing_address.postal_code}}
{{billing_address.city}}, {{billing_address.country}} {% if billing_address.vat_number %} -
{% trans "VAT Number" %} {{billing_address.vat_number}} +
{% trans "VAT Number" %} {{billing_address.vat_number}}  + {% if vm.vat_validation_status == "verified" %} + + {% else %} + + {% endif %} {% endif %} {% endwith %}

diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 08dd73aa..13d8386c 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -1,7 +1,9 @@ +import datetime import logging import pyotp import requests +import stripe from django.conf import settings from django.contrib.sites.models import Site @@ -9,12 +11,13 @@ from datacenterlight.tasks import create_vm_task from hosting.models import HostingOrder, HostingBill, OrderDetail from membership.models import StripeCustomer from utils.forms import UserBillingAddressForm -from utils.models import BillingAddress +from utils.models import BillingAddress, UserBillingAddress from .cms_models import CMSIntegration from .models import VMPricing, VMTemplate logger = logging.getLogger(__name__) + def get_cms_integration(name): current_site = Site.objects.get_current() try: @@ -124,3 +127,67 @@ def check_otp(name, realm, token): data=data ) return response.status_code + + +def validate_vat_number(self, stripe_customer_id, vat_number): + try: + billing_address = BillingAddress.objects.get(vat_number=vat_number) + except BillingAddress.DoesNotExist as dne: + billing_address = None + logger.debug("BillingAddress does not exist for %s" % vat_number) + except BillingAddress.MultipleObjectsReturned as mor: + logger.debug("Multiple BillingAddress exist for %s" % vat_number) + billing_address = BillingAddress.objects.last(vat_number=vat_number) + if billing_address is not None: + if billing_address.vat_number_validated_on: + return { + "validated_on": billing_address.vat_number_validated_on, + "status": "verified" + } + else: + if billing_address.stripe_tax_id: + tax_id_obj = stripe.Customer.retrieve_tax_id( + stripe_customer_id, + billing_address.stripe_tax_id, + ) + if tax_id_obj.verification.status == "verified": + return { + "status": "verified", + "validated_on": billing_address.vat_number_validated_on + } + else: + return { + "status": tax_id_obj.verification.status, + "validated_on": "" + } + else: + tax_id_obj = create_tax_id(stripe_customer_id, vat_number) + else: + tax_id_obj = create_tax_id(stripe_customer_id, vat_number) + + return { + "status": tax_id_obj.verification.status, + "validated_on": datetime.datetime.now() if tax_id_obj.verification.status == "verified" else "" + } + + +def create_tax_id(stripe_customer_id, vat_number): + tax_id_obj = stripe.Customer.create_tax_id( + stripe_customer_id, + type="eu_vat", + value=vat_number, + ) + b_addresses = BillingAddress.objects.filter( + stripe_customer_id=stripe_customer_id, + vat_number=vat_number + ) + for b_address in b_addresses: + b_address.stripe_tax_id = tax_id_obj.id + + ub_addresses = UserBillingAddress.objects.filter( + stripe_customer_id=stripe_customer_id, + vat_number=vat_number + ) + for ub_address in ub_addresses: + ub_address.stripe_tax_id = tax_id_obj.id + return tax_id_obj diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 0763e259..512989ac 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -35,7 +35,9 @@ from utils.tasks import send_plain_email_task from .cms_models import DCLCalculatorPluginModel from .forms import ContactForm from .models import VMTemplate, VMPricing -from .utils import get_cms_integration, create_vm, clear_all_session_vars +from .utils import ( + get_cms_integration, create_vm, clear_all_session_vars, validate_vat_number +) logger = logging.getLogger(__name__) @@ -540,6 +542,14 @@ class PaymentOrderView(FormView): else: request.session['customer'] = customer + validate_result = validate_vat_number( + stripe_customer_id=request.session['customer'], + vat_number=address_form.cleaned_data.get('vat_number') + ) + + request.session["vat_validation_status"] = validate_result["status"] + request.session["vat_validated_on"] = validate_result["validated_on"] + # For generic payment we take the user directly to confirmation if ('generic_payment_type' in request.session and self.request.session['generic_payment_type'] == 'generic'): @@ -596,6 +606,10 @@ class OrderConfirmationView(DetailView, FormView): if ('generic_payment_type' in request.session and self.request.session['generic_payment_type'] == 'generic'): + if request.session["vat_validation_status"] == "verified": + request.session['generic_payment_details']['vat_rate'] = 0 + request.session['generic_payment_details']['vat_amount'] = 0 + request.session['generic_payment_details']['amount'] = request.session['generic_payment_details']['amount_before_vat'] context.update({ 'generic_payment_details': request.session['generic_payment_details'], @@ -614,11 +628,20 @@ class OrderConfirmationView(DetailView, FormView): vat_rate=user_country_vat_rate * 100 ) vm_specs["price"] = price - vm_specs["vat"] = vat - vm_specs["vat_percent"] = vat_percent + + if request.session["vat_validation_status"] == "verified": + vm_specs["vat_percent"] = 0 + vm_specs["vat"] = 0 + vm_specs["total_price"] = price + vm_specs["vat_validation_status"] = "verified" + else: + vm_specs["vat"] = vat + vm_specs["vat_percent"] = vat_percent + vm_specs["total_price"] = round(price + vat - discount['amount'], 2) + vm_specs["vat_validation_status"] = request.session["vat_validation_status"] + vm_specs["vat_country"] = user_vat_country vm_specs["discount"] = discount - vm_specs["total_price"] = round(price + vat - discount['amount'], 2) request.session['specs'] = vm_specs context.update({ From 1b243822a9dfaeef51a8d5bc6bdbef8508de6a85 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 20:20:43 +0530 Subject: [PATCH 061/433] Bugfix: remove unnecessary self param --- datacenterlight/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 13d8386c..cdbe1913 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -129,7 +129,7 @@ def check_otp(name, realm, token): return response.status_code -def validate_vat_number(self, stripe_customer_id, vat_number): +def validate_vat_number(stripe_customer_id, vat_number): try: billing_address = BillingAddress.objects.get(vat_number=vat_number) except BillingAddress.DoesNotExist as dne: From 8cc766b62f4f23ac7896493a6377ff40e72e3971 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 20:45:38 +0530 Subject: [PATCH 062/433] Update stripe to 2.41.0 to support tax id --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 874fc1a4..e7769a7e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -79,7 +79,7 @@ requests==2.10.0 rjsmin==1.0.12 six==1.10.0 sqlparse==0.1.19 -stripe==2.24.1 +stripe==2.41.0 wheel==0.29.0 django-admin-honeypot==1.0.0 coverage==4.3.4 From 785f99311d7d54ad50d5670d6ce676fb414b5753 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 21:02:43 +0530 Subject: [PATCH 063/433] Handle invalid VAT number case --- datacenterlight/utils.py | 14 ++++++++++++-- datacenterlight/views.py | 10 ++++++++++ utils/stripe_utils.py | 9 +++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index cdbe1913..58d9de61 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -12,6 +12,7 @@ from hosting.models import HostingOrder, HostingBill, OrderDetail from membership.models import StripeCustomer from utils.forms import UserBillingAddressForm from utils.models import BillingAddress, UserBillingAddress +from utils.stripe_utils import StripeUtils from .cms_models import CMSIntegration from .models import VMPricing, VMTemplate @@ -165,6 +166,9 @@ def validate_vat_number(stripe_customer_id, vat_number): else: tax_id_obj = create_tax_id(stripe_customer_id, vat_number) + if 'response_object' in tax_id_obj: + return tax_id_obj + return { "status": tax_id_obj.verification.status, "validated_on": datetime.datetime.now() if tax_id_obj.verification.status == "verified" else "" @@ -172,11 +176,17 @@ def validate_vat_number(stripe_customer_id, vat_number): def create_tax_id(stripe_customer_id, vat_number): - tax_id_obj = stripe.Customer.create_tax_id( + stripe_utils = StripeUtils() + tax_id_response = stripe_utils.create_tax_id_for_user( stripe_customer_id, - type="eu_vat", value=vat_number, ) + + tax_id_obj = tax_id_response.get('response_object') + + if not tax_id_obj: + return tax_id_response + b_addresses = BillingAddress.objects.filter( stripe_customer_id=stripe_customer_id, vat_number=vat_number diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 512989ac..36b8e505 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -547,6 +547,16 @@ class PaymentOrderView(FormView): vat_number=address_form.cleaned_data.get('vat_number') ) + if 'response_object' in validate_result: + address_form.add_error( + "__all__", validate_result["error"] + ) + return self.render_to_response( + self.get_context_data( + billing_address_form=address_form + ) + ) + request.session["vat_validation_status"] = validate_result["status"] request.session["vat_validated_on"] = validate_result["validated_on"] diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index e2bdb983..90437056 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -434,3 +434,12 @@ class StripeUtils(object): subscription = stripe.Subscription.retrieve(subscription_id) subscription.metadata = meta_data subscription.save() + + @handleStripeError + def create_tax_id_for_user(self, stripe_customer_id, vat_number): + tax_id_obj = stripe.Customer.create_tax_id( + stripe_customer_id, + type="eu_vat", + value=vat_number, + ) + return tax_id_obj From de0fe77779f208bc313cc6c8676cb1759fb880e9 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 22:22:08 +0530 Subject: [PATCH 064/433] Bugfix: pass correct param --- datacenterlight/utils.py | 2 +- datacenterlight/views.py | 9 +-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 58d9de61..f65f3588 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -179,7 +179,7 @@ def create_tax_id(stripe_customer_id, vat_number): stripe_utils = StripeUtils() tax_id_response = stripe_utils.create_tax_id_for_user( stripe_customer_id, - value=vat_number, + vat_number=vat_number, ) tax_id_obj = tax_id_response.get('response_object') diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 36b8e505..7110e7a8 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -548,14 +548,7 @@ class PaymentOrderView(FormView): ) if 'response_object' in validate_result: - address_form.add_error( - "__all__", validate_result["error"] - ) - return self.render_to_response( - self.get_context_data( - billing_address_form=address_form - ) - ) + raise forms.ValidationError(validate_result["error"]) request.session["vat_validation_status"] = validate_result["status"] request.session["vat_validated_on"] = validate_result["validated_on"] From 110459b38d238d721ee1b1dcfd77f838b9d84e5c Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 22:27:14 +0530 Subject: [PATCH 065/433] Show vat error in payment page --- .../templates/datacenterlight/landing_payment.html | 9 +++++++++ datacenterlight/views.py | 8 +++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/datacenterlight/templates/datacenterlight/landing_payment.html b/datacenterlight/templates/datacenterlight/landing_payment.html index 4e71eab9..66a0e63f 100644 --- a/datacenterlight/templates/datacenterlight/landing_payment.html +++ b/datacenterlight/templates/datacenterlight/landing_payment.html @@ -13,6 +13,15 @@
+
+ {% for message in messages %} + {% if 'vat_error' in message.tags %} +
    +
  • An error occurred while validating VAT number: {{ message|safe }}

  • +
+ {% endif %} + {% endfor %} +
diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 7110e7a8..3274c480 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -548,7 +548,13 @@ class PaymentOrderView(FormView): ) if 'response_object' in validate_result: - raise forms.ValidationError(validate_result["error"]) + messages.add_message( + request, messages.ERROR, validate_result["error"], + extra_tags='vat_error' + ) + return HttpResponseRedirect( + reverse('datacenterlight:payment') + '#vat_error' + ) request.session["vat_validation_status"] = validate_result["status"] request.session["vat_validated_on"] = validate_result["validated_on"] From 752b61a852ddb5da83680c7594699a6ae8f4212f Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 22:33:56 +0530 Subject: [PATCH 066/433] Correct the way to get the first object --- datacenterlight/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index f65f3588..5272ef20 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -138,7 +138,7 @@ def validate_vat_number(stripe_customer_id, vat_number): logger.debug("BillingAddress does not exist for %s" % vat_number) except BillingAddress.MultipleObjectsReturned as mor: logger.debug("Multiple BillingAddress exist for %s" % vat_number) - billing_address = BillingAddress.objects.last(vat_number=vat_number) + billing_address = BillingAddress.objects.all(vat_number=vat_number).order_by('-id').first() if billing_address is not None: if billing_address.vat_number_validated_on: return { From 3b654f1c49382e1582bb06fe2855229f4723d8c8 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 22:35:41 +0530 Subject: [PATCH 067/433] Correct++ --- datacenterlight/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 5272ef20..dff35523 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -138,7 +138,7 @@ def validate_vat_number(stripe_customer_id, vat_number): logger.debug("BillingAddress does not exist for %s" % vat_number) except BillingAddress.MultipleObjectsReturned as mor: logger.debug("Multiple BillingAddress exist for %s" % vat_number) - billing_address = BillingAddress.objects.all(vat_number=vat_number).order_by('-id').first() + billing_address = BillingAddress.objects.filter(vat_number=vat_number).order_by('-id').first() if billing_address is not None: if billing_address.vat_number_validated_on: return { From b15ece7088c48f490b5526733d50a363aaa5a88b Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 22:40:57 +0530 Subject: [PATCH 068/433] Validate VAT number only if it is set --- datacenterlight/views.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 3274c480..e2fa8d0f 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -542,22 +542,24 @@ class PaymentOrderView(FormView): else: request.session['customer'] = customer - validate_result = validate_vat_number( - stripe_customer_id=request.session['customer'], - vat_number=address_form.cleaned_data.get('vat_number') - ) - - if 'response_object' in validate_result: - messages.add_message( - request, messages.ERROR, validate_result["error"], - extra_tags='vat_error' - ) - return HttpResponseRedirect( - reverse('datacenterlight:payment') + '#vat_error' + vat_number = address_form.cleaned_data.get('vat_number').strip() + if vat_number: + validate_result = validate_vat_number( + stripe_customer_id=request.session['customer'], + vat_number=address_form.cleaned_data.get('vat_number') ) - request.session["vat_validation_status"] = validate_result["status"] - request.session["vat_validated_on"] = validate_result["validated_on"] + if 'response_object' in validate_result: + messages.add_message( + request, messages.ERROR, validate_result["error"], + extra_tags='vat_error' + ) + return HttpResponseRedirect( + reverse('datacenterlight:payment') + '#vat_error' + ) + + request.session["vat_validation_status"] = validate_result["status"] + request.session["vat_validated_on"] = validate_result["validated_on"] # For generic payment we take the user directly to confirmation if ('generic_payment_type' in request.session and From 4491a52bd9c42ab50988f249b5775dbf61916b7b Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 22:45:55 +0530 Subject: [PATCH 069/433] Check if vat_validation_status is in session --- datacenterlight/views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index e2fa8d0f..ccec2142 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -640,7 +640,8 @@ class OrderConfirmationView(DetailView, FormView): ) vm_specs["price"] = price - if request.session["vat_validation_status"] == "verified": + if ("vat_validation_status" in request.session and + request.session["vat_validation_status"] == "verified"): vm_specs["vat_percent"] = 0 vm_specs["vat"] = 0 vm_specs["total_price"] = price @@ -649,7 +650,7 @@ class OrderConfirmationView(DetailView, FormView): vm_specs["vat"] = vat vm_specs["vat_percent"] = vat_percent vm_specs["total_price"] = round(price + vat - discount['amount'], 2) - vm_specs["vat_validation_status"] = request.session["vat_validation_status"] + vm_specs["vat_validation_status"] = request.session["vat_validation_status"] if "vat_validation_status" in request.session else "" vm_specs["vat_country"] = user_vat_country vm_specs["discount"] = discount From 908c2e055c4a962a2f5b6acd75b8cfdd036371c3 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 22:48:57 +0530 Subject: [PATCH 070/433] Also clear vat_validation_status from session --- datacenterlight/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index dff35523..7d6494f6 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -106,7 +106,8 @@ def clear_all_session_vars(request): 'billing_address_data', 'card_id', 'token', 'customer', 'generic_payment_type', 'generic_payment_details', 'product_id', - 'order_confirm_url', 'new_user_hosting_key_id']: + 'order_confirm_url', 'new_user_hosting_key_id', + 'vat_validation_status']: if session_var in request.session: del request.session[session_var] From 4a40438edc6209795113271509267836e0a96faa Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 23:10:18 +0530 Subject: [PATCH 071/433] Also handle ch_vat --- datacenterlight/utils.py | 13 +++++++++---- datacenterlight/views.py | 3 ++- utils/stripe_utils.py | 4 ++-- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 7d6494f6..096f835c 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -131,7 +131,7 @@ def check_otp(name, realm, token): return response.status_code -def validate_vat_number(stripe_customer_id, vat_number): +def validate_vat_number(stripe_customer_id, vat_number, country): try: billing_address = BillingAddress.objects.get(vat_number=vat_number) except BillingAddress.DoesNotExist as dne: @@ -163,9 +163,13 @@ def validate_vat_number(stripe_customer_id, vat_number): "validated_on": "" } else: - tax_id_obj = create_tax_id(stripe_customer_id, vat_number) + tax_id_obj = create_tax_id( + stripe_customer_id, vat_number, + "ch_vat" if country.lower() == "ch" else "eu_vat") else: - tax_id_obj = create_tax_id(stripe_customer_id, vat_number) + tax_id_obj = create_tax_id( + stripe_customer_id, vat_number, + "ch_vat" if country.lower() == "ch" else "eu_vat") if 'response_object' in tax_id_obj: return tax_id_obj @@ -176,11 +180,12 @@ def validate_vat_number(stripe_customer_id, vat_number): } -def create_tax_id(stripe_customer_id, vat_number): +def create_tax_id(stripe_customer_id, vat_number, type): stripe_utils = StripeUtils() tax_id_response = stripe_utils.create_tax_id_for_user( stripe_customer_id, vat_number=vat_number, + type=type ) tax_id_obj = tax_id_response.get('response_object') diff --git a/datacenterlight/views.py b/datacenterlight/views.py index ccec2142..f3f07e43 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -546,7 +546,8 @@ class PaymentOrderView(FormView): if vat_number: validate_result = validate_vat_number( stripe_customer_id=request.session['customer'], - vat_number=address_form.cleaned_data.get('vat_number') + vat_number=address_form.cleaned_data.get('vat_number'), + country=address_form.cleaned_data.get("country").strip() ) if 'response_object' in validate_result: diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index 90437056..2cadefd2 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -436,10 +436,10 @@ class StripeUtils(object): subscription.save() @handleStripeError - def create_tax_id_for_user(self, stripe_customer_id, vat_number): + def create_tax_id_for_user(self, stripe_customer_id, vat_number, type="eu_vat"): tax_id_obj = stripe.Customer.create_tax_id( stripe_customer_id, - type="eu_vat", + type=type, value=vat_number, ) return tax_id_obj From 0b8315ca7600fdf2fc430ab6649cdd26b8897ec4 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Dec 2019 23:20:48 +0530 Subject: [PATCH 072/433] Remove wrong filter --- datacenterlight/utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 096f835c..6b1ac112 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -194,14 +194,12 @@ def create_tax_id(stripe_customer_id, vat_number, type): return tax_id_response b_addresses = BillingAddress.objects.filter( - stripe_customer_id=stripe_customer_id, vat_number=vat_number ) for b_address in b_addresses: b_address.stripe_tax_id = tax_id_obj.id ub_addresses = UserBillingAddress.objects.filter( - stripe_customer_id=stripe_customer_id, vat_number=vat_number ) for ub_address in ub_addresses: From ca128dd8c43ef16f9a189e7cad57031e45220442 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 08:06:27 +0530 Subject: [PATCH 073/433] Fix str formatting --- webhook/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/webhook/views.py b/webhook/views.py index 17718b13..85aaf218 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -68,8 +68,8 @@ def handle_webhook(request): if event.type == "customer.tax_id.updated": logger.debug("Webhook Event: customer.tax_id.updated") tax_id_obj = event.data.object - logger.debug("Tax_id %s is %s" % tax_id_obj.id, - tax_id_obj.verification.status) + logger.debug("Tax_id %s is %s" % (tax_id_obj.id, + tax_id_obj.verification.status)) if tax_id_obj.verification.status == "verified": b_addresses = BillingAddress.objects.filter(vat_number=tax_id_obj.value) for b_address in b_addresses: @@ -79,8 +79,8 @@ def handle_webhook(request): for ub_address in ub_addresses: ub_address.vat_number_validated_on = datetime.datetime.now() else: - logger.debug("Tax_id %s is %s" % tax_id_obj.id, - tax_id_obj.verification.status) + logger.debug("Tax_id %s is %s" % (tax_id_obj.id, + tax_id_obj.verification.status)) else: logger.error("Unhandled event : " + event.type) return HttpResponse(status=200) From 52201c0aa14558880f8ecc32ed395f75d0fec50f Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 10:49:45 +0530 Subject: [PATCH 074/433] Clear billing_address_id session var also --- datacenterlight/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 6b1ac112..6d927339 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -107,7 +107,7 @@ def clear_all_session_vars(request): 'token', 'customer', 'generic_payment_type', 'generic_payment_details', 'product_id', 'order_confirm_url', 'new_user_hosting_key_id', - 'vat_validation_status']: + 'vat_validation_status', 'billing_address_id']: if session_var in request.session: del request.session[session_var] From 0d208d2bd922d2818e7865cd1b4511b1611eaa2a Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 10:50:53 +0530 Subject: [PATCH 075/433] Take billing_address_id in validate_vat_number --- datacenterlight/utils.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 6d927339..fec5d957 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -131,15 +131,15 @@ def check_otp(name, realm, token): return response.status_code -def validate_vat_number(stripe_customer_id, vat_number, country): +def validate_vat_number(stripe_customer_id, billing_address_id): try: - billing_address = BillingAddress.objects.get(vat_number=vat_number) + billing_address = BillingAddress.objects.get(billing_address_id) except BillingAddress.DoesNotExist as dne: billing_address = None - logger.debug("BillingAddress does not exist for %s" % vat_number) + logger.debug("BillingAddress does not exist for %s" % billing_address_id) except BillingAddress.MultipleObjectsReturned as mor: - logger.debug("Multiple BillingAddress exist for %s" % vat_number) - billing_address = BillingAddress.objects.filter(vat_number=vat_number).order_by('-id').first() + logger.debug("Multiple BillingAddress exist for %s" % billing_address_id) + billing_address = BillingAddress.objects.filter(billing_address_id).order_by('-id').first() if billing_address is not None: if billing_address.vat_number_validated_on: return { @@ -153,6 +153,9 @@ def validate_vat_number(stripe_customer_id, vat_number, country): billing_address.stripe_tax_id, ) if tax_id_obj.verification.status == "verified": + # update billing address + billing_address.vat_number_validated_on = datetime.datetime.now() + billing_address.save() return { "status": "verified", "validated_on": billing_address.vat_number_validated_on @@ -164,12 +167,13 @@ def validate_vat_number(stripe_customer_id, vat_number, country): } else: tax_id_obj = create_tax_id( - stripe_customer_id, vat_number, - "ch_vat" if country.lower() == "ch" else "eu_vat") + stripe_customer_id, billing_address.vat_number, + "ch_vat" if billing_address.country.lower() == "ch" else "eu_vat") else: - tax_id_obj = create_tax_id( - stripe_customer_id, vat_number, - "ch_vat" if country.lower() == "ch" else "eu_vat") + return { + "status": "invalid billing address", + "validated_on": "" + } if 'response_object' in tax_id_obj: return tax_id_obj From 825e716625196eab7ff7f181639d3cbbb07fa0a6 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 10:51:50 +0530 Subject: [PATCH 076/433] Update tax_id fields of billing_addresses which belong to supplied user only --- datacenterlight/utils.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index fec5d957..0b41926d 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -197,15 +197,16 @@ def create_tax_id(stripe_customer_id, vat_number, type): if not tax_id_obj: return tax_id_response - b_addresses = BillingAddress.objects.filter( - vat_number=vat_number - ) - for b_address in b_addresses: + stripe_customer = StripeCustomer.objects.get(stripe_customer_id) + billing_address_set = set() + for ho in stripe_customer.hostingorder_set.all(): + if ho.billing_address.vat_number==vat_number: + billing_address_set.add(ho.billing_address) + for b_address in billing_address_set: b_address.stripe_tax_id = tax_id_obj.id - ub_addresses = UserBillingAddress.objects.filter( - vat_number=vat_number - ) + ub_addresses = stripe_customer.user.billing_addresses.filter( + vat_number=vat_number) for ub_address in ub_addresses: ub_address.stripe_tax_id = tax_id_obj.id return tax_id_obj From c3939023968b340093e9937703be33a18a90617a Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 10:54:26 +0530 Subject: [PATCH 077/433] Save address form in payment post itself --- datacenterlight/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index f3f07e43..3227992d 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -525,6 +525,8 @@ class PaymentOrderView(FormView): token=token, customer_name=user_name) + billing_address = address_form.save() + request.session["billing_address_id"] = billing_address.id request.session['billing_address_data'] = address_form.cleaned_data request.session['user'] = this_user # Get or create stripe customer @@ -546,11 +548,10 @@ class PaymentOrderView(FormView): if vat_number: validate_result = validate_vat_number( stripe_customer_id=request.session['customer'], - vat_number=address_form.cleaned_data.get('vat_number'), - country=address_form.cleaned_data.get("country").strip() + billing_address_id=billing_address.id ) - if 'response_object' in validate_result: + if 'error' in validate_result and validate_result['error']: messages.add_message( request, messages.ERROR, validate_result["error"], extra_tags='vat_error' @@ -558,7 +559,6 @@ class PaymentOrderView(FormView): return HttpResponseRedirect( reverse('datacenterlight:payment') + '#vat_error' ) - request.session["vat_validation_status"] = validate_result["status"] request.session["vat_validated_on"] = validate_result["validated_on"] From 0e40ca6044207b4cc5ff354e0bb75e8048409e6a Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 10:55:01 +0530 Subject: [PATCH 078/433] Also validate vat in order confirmation get --- datacenterlight/views.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 3227992d..360f99bb 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -641,6 +641,26 @@ class OrderConfirmationView(DetailView, FormView): ) vm_specs["price"] = price + vat_number = request.session.get('billing_address_data').get("vat_number") + billing_address = BillingAddress.objects.get( + request.session["billing_address_id"]) + if vat_number: + validate_result = validate_vat_number( + stripe_customer_id=request.session['customer'], + billing_address_id=billing_address.id + ) + if 'error' in validate_result and validate_result['error']: + messages.add_message( + request, messages.ERROR, validate_result["error"], + extra_tags='vat_error' + ) + return HttpResponseRedirect( + reverse('datacenterlight:payment') + '#vat_error' + ) + + request.session["vat_validation_status"] = validate_result["status"] + request.session["vat_validated_on"] = validate_result["validated_on"] + if ("vat_validation_status" in request.session and request.session["vat_validation_status"] == "verified"): vm_specs["vat_percent"] = 0 From 4560c8bf837f0f1233e6a2c6f8ec5f5806ff8d44 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 11:00:24 +0530 Subject: [PATCH 079/433] Bugfix: Pass parameter name --- datacenterlight/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 0b41926d..3585577c 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -133,7 +133,7 @@ def check_otp(name, realm, token): def validate_vat_number(stripe_customer_id, billing_address_id): try: - billing_address = BillingAddress.objects.get(billing_address_id) + billing_address = BillingAddress.objects.get(id=billing_address_id) except BillingAddress.DoesNotExist as dne: billing_address = None logger.debug("BillingAddress does not exist for %s" % billing_address_id) From 6fd0659c88741397c4c57799b35994721b4d5cca Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 11:02:58 +0530 Subject: [PATCH 080/433] Another missing param --- datacenterlight/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 3585577c..25267095 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -197,7 +197,7 @@ def create_tax_id(stripe_customer_id, vat_number, type): if not tax_id_obj: return tax_id_response - stripe_customer = StripeCustomer.objects.get(stripe_customer_id) + stripe_customer = StripeCustomer.objects.get(stripe_id=stripe_customer_id) billing_address_set = set() for ho in stripe_customer.hostingorder_set.all(): if ho.billing_address.vat_number==vat_number: From 74d1bbb6d3099bdcf222de6282014e195dd1d560 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 11:09:14 +0530 Subject: [PATCH 081/433] Add missing customer save call --- datacenterlight/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 360f99bb..aef0e38a 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -524,6 +524,7 @@ class PaymentOrderView(FormView): email=user_email, token=token, customer_name=user_name) + customer.save() billing_address = address_form.save() request.session["billing_address_id"] = billing_address.id From 9310f72cf99290281d4230a00d202dcd9f737151 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 11:30:01 +0530 Subject: [PATCH 082/433] Save tax id in billing address even if no StripeCustomer --- datacenterlight/utils.py | 41 ++++++++++++++++++++++++++-------------- datacenterlight/views.py | 1 - 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 25267095..27179b26 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -167,7 +167,7 @@ def validate_vat_number(stripe_customer_id, billing_address_id): } else: tax_id_obj = create_tax_id( - stripe_customer_id, billing_address.vat_number, + stripe_customer_id, billing_address_id, "ch_vat" if billing_address.country.lower() == "ch" else "eu_vat") else: return { @@ -184,11 +184,19 @@ def validate_vat_number(stripe_customer_id, billing_address_id): } -def create_tax_id(stripe_customer_id, vat_number, type): +def create_tax_id(stripe_customer_id, billing_address_id, type): + try: + billing_address = BillingAddress.objects.get(id=billing_address_id) + except BillingAddress.DoesNotExist as dne: + billing_address = None + logger.debug("BillingAddress does not exist for %s" % billing_address_id) + except BillingAddress.MultipleObjectsReturned as mor: + logger.debug("Multiple BillingAddress exist for %s" % billing_address_id) + billing_address = BillingAddress.objects.filter(billing_address_id).order_by('-id').first() stripe_utils = StripeUtils() tax_id_response = stripe_utils.create_tax_id_for_user( stripe_customer_id, - vat_number=vat_number, + vat_number=billing_address.vat_number, type=type ) @@ -197,16 +205,21 @@ def create_tax_id(stripe_customer_id, vat_number, type): if not tax_id_obj: return tax_id_response - stripe_customer = StripeCustomer.objects.get(stripe_id=stripe_customer_id) - billing_address_set = set() - for ho in stripe_customer.hostingorder_set.all(): - if ho.billing_address.vat_number==vat_number: - billing_address_set.add(ho.billing_address) - for b_address in billing_address_set: - b_address.stripe_tax_id = tax_id_obj.id + try: + stripe_customer = StripeCustomer.objects.get(stripe_id=stripe_customer_id) + billing_address_set = set() + for ho in stripe_customer.hostingorder_set.all(): + if ho.billing_address.vat_number == billing_address.vat_number: + billing_address_set.add(ho.billing_address) + for b_address in billing_address_set: + b_address.stripe_tax_id = tax_id_obj.id - ub_addresses = stripe_customer.user.billing_addresses.filter( - vat_number=vat_number) - for ub_address in ub_addresses: - ub_address.stripe_tax_id = tax_id_obj.id + ub_addresses = stripe_customer.user.billing_addresses.filter( + vat_number=billing_address.vat_number) + for ub_address in ub_addresses: + ub_address.stripe_tax_id = tax_id_obj.id + except StripeCustomer.DoesNotExist as dne: + logger.debug("StripeCustomer %s does not exist" % stripe_customer_id) + billing_address.stripe_tax_id = tax_id_obj.id + billing_address.save() return tax_id_obj diff --git a/datacenterlight/views.py b/datacenterlight/views.py index aef0e38a..360f99bb 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -524,7 +524,6 @@ class PaymentOrderView(FormView): email=user_email, token=token, customer_name=user_name) - customer.save() billing_address = address_form.save() request.session["billing_address_id"] = billing_address.id From 833dc9bdcb835666adc5ffc9dcd815700c41a824 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 11:41:29 +0530 Subject: [PATCH 083/433] Change stripe_tax_id field to char + regenerate migrations --- ...225_0300.py => 0007_auto_20191226_0610.py} | 22 +++++++++++++++- utils/migrations/0008_auto_20191225_0529.py | 25 ------------------- utils/migrations/0009_auto_20191225_1311.py | 25 ------------------- utils/models.py | 2 +- 4 files changed, 22 insertions(+), 52 deletions(-) rename utils/migrations/{0007_auto_20191225_0300.py => 0007_auto_20191226_0610.py} (93%) delete mode 100644 utils/migrations/0008_auto_20191225_0529.py delete mode 100644 utils/migrations/0009_auto_20191225_1311.py diff --git a/utils/migrations/0007_auto_20191225_0300.py b/utils/migrations/0007_auto_20191226_0610.py similarity index 93% rename from utils/migrations/0007_auto_20191225_0300.py rename to utils/migrations/0007_auto_20191226_0610.py index 72f5c836..c57c6431 100644 --- a/utils/migrations/0007_auto_20191225_0300.py +++ b/utils/migrations/0007_auto_20191226_0610.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.9.4 on 2019-12-25 03:00 +# Generated by Django 1.9.4 on 2019-12-26 06:10 from __future__ import unicode_literals from django.db import migrations, models @@ -13,16 +13,36 @@ class Migration(migrations.Migration): ] operations = [ + migrations.AddField( + model_name='billingaddress', + name='stripe_tax_id', + field=models.CharField(blank=True, default='', max_length=100), + ), migrations.AddField( model_name='billingaddress', name='vat_number', field=models.CharField(blank=True, default='', max_length=100), ), + migrations.AddField( + model_name='billingaddress', + name='vat_number_validated_on', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AddField( + model_name='userbillingaddress', + name='stripe_tax_id', + field=models.CharField(blank=True, default='', max_length=100), + ), migrations.AddField( model_name='userbillingaddress', name='vat_number', field=models.CharField(blank=True, default='', max_length=100), ), + migrations.AddField( + model_name='userbillingaddress', + name='vat_number_validated_on', + field=models.DateTimeField(blank=True, null=True), + ), migrations.AlterField( model_name='billingaddress', name='country', diff --git a/utils/migrations/0008_auto_20191225_0529.py b/utils/migrations/0008_auto_20191225_0529.py deleted file mode 100644 index 20a6183c..00000000 --- a/utils/migrations/0008_auto_20191225_0529.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.4 on 2019-12-25 05:29 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('utils', '0007_auto_20191225_0300'), - ] - - operations = [ - migrations.AddField( - model_name='billingaddress', - name='vat_number_validated_on', - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AddField( - model_name='userbillingaddress', - name='vat_number_validated_on', - field=models.DateTimeField(blank=True, null=True), - ), - ] diff --git a/utils/migrations/0009_auto_20191225_1311.py b/utils/migrations/0009_auto_20191225_1311.py deleted file mode 100644 index 866a5d12..00000000 --- a/utils/migrations/0009_auto_20191225_1311.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.4 on 2019-12-25 13:11 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('utils', '0008_auto_20191225_0529'), - ] - - operations = [ - migrations.AddField( - model_name='billingaddress', - name='stripe_tax_id', - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AddField( - model_name='userbillingaddress', - name='stripe_tax_id', - field=models.DateTimeField(blank=True, null=True), - ), - ] diff --git a/utils/models.py b/utils/models.py index c1a5e7b4..89475752 100644 --- a/utils/models.py +++ b/utils/models.py @@ -14,7 +14,7 @@ class BaseBillingAddress(models.Model): postal_code = models.CharField(max_length=50) country = CountryField() vat_number = models.CharField(max_length=100, default="", blank=True) - stripe_tax_id = models.DateTimeField(blank=True, null=True) + stripe_tax_id = models.CharField(max_length=100, default="", blank=True) vat_number_validated_on = models.DateTimeField(blank=True, null=True) class Meta: From 242dbb247920a37153e5b18bfc72b12d98c32a3c Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 11:50:15 +0530 Subject: [PATCH 084/433] Bugfix: pass param --- datacenterlight/views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 360f99bb..21657bca 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -642,8 +642,7 @@ class OrderConfirmationView(DetailView, FormView): vm_specs["price"] = price vat_number = request.session.get('billing_address_data').get("vat_number") - billing_address = BillingAddress.objects.get( - request.session["billing_address_id"]) + billing_address = BillingAddress.objects.get(id=request.session["billing_address_id"]) if vat_number: validate_result = validate_vat_number( stripe_customer_id=request.session['customer'], From fe6ade38ebf23236d327fe38eecd9531922cd445 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 11:54:54 +0530 Subject: [PATCH 085/433] Update vat_number validated field based on tax_id + save --- webhook/views.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/webhook/views.py b/webhook/views.py index 85aaf218..7e9175e6 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -71,13 +71,15 @@ def handle_webhook(request): logger.debug("Tax_id %s is %s" % (tax_id_obj.id, tax_id_obj.verification.status)) if tax_id_obj.verification.status == "verified": - b_addresses = BillingAddress.objects.filter(vat_number=tax_id_obj.value) + b_addresses = BillingAddress.objects.filter(stripe_tax_id=tax_id_obj.id) for b_address in b_addresses: b_address.vat_number_validated_on = datetime.datetime.now() + b_address.save() - ub_addresses = UserBillingAddress.objects.filter(vat_number=tax_id_obj.value) + ub_addresses = UserBillingAddress.objects.filter(stripe_tax_id=tax_id_obj.id) for ub_address in ub_addresses: ub_address.vat_number_validated_on = datetime.datetime.now() + ub_address.save() else: logger.debug("Tax_id %s is %s" % (tax_id_obj.id, tax_id_obj.verification.status)) From 7d9ab322c9d92ed757863d36590548a516859ae9 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 12:38:00 +0530 Subject: [PATCH 086/433] Remove vat_validate_on session var --- datacenterlight/views.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 21657bca..de68f418 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -560,7 +560,6 @@ class PaymentOrderView(FormView): reverse('datacenterlight:payment') + '#vat_error' ) request.session["vat_validation_status"] = validate_result["status"] - request.session["vat_validated_on"] = validate_result["validated_on"] # For generic payment we take the user directly to confirmation if ('generic_payment_type' in request.session and @@ -658,7 +657,6 @@ class OrderConfirmationView(DetailView, FormView): ) request.session["vat_validation_status"] = validate_result["status"] - request.session["vat_validated_on"] = validate_result["validated_on"] if ("vat_validation_status" in request.session and request.session["vat_validation_status"] == "verified"): From 99e70d95c4a17bb938423c29dc3524a699c74f14 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 13:31:15 +0530 Subject: [PATCH 087/433] VAT number validation in settings --- datacenterlight/utils.py | 33 ++++++++++++++++++------- hosting/templates/hosting/settings.html | 7 ++++++ hosting/views.py | 27 +++++++++++++++++--- 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 27179b26..6ae05d39 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -131,15 +131,30 @@ def check_otp(name, realm, token): return response.status_code -def validate_vat_number(stripe_customer_id, billing_address_id): - try: - billing_address = BillingAddress.objects.get(id=billing_address_id) - except BillingAddress.DoesNotExist as dne: - billing_address = None - logger.debug("BillingAddress does not exist for %s" % billing_address_id) - except BillingAddress.MultipleObjectsReturned as mor: - logger.debug("Multiple BillingAddress exist for %s" % billing_address_id) - billing_address = BillingAddress.objects.filter(billing_address_id).order_by('-id').first() +def validate_vat_number(stripe_customer_id, billing_address_id, + is_user_ba=False): + if is_user_ba: + try: + billing_address = UserBillingAddress.objects.get( + id=billing_address_id) + except UserBillingAddress.DoesNotExist as dne: + billing_address = None + logger.debug( + "UserBillingAddress does not exist for %s" % billing_address_id) + except UserBillingAddress.MultipleObjectsReturned as mor: + logger.debug( + "Multiple UserBillingAddress exist for %s" % billing_address_id) + billing_address = UserBillingAddress.objects.filter( + id=billing_address_id).order_by('-id').first() + else: + try: + billing_address = BillingAddress.objects.get(id=billing_address_id) + except BillingAddress.DoesNotExist as dne: + billing_address = None + logger.debug("BillingAddress does not exist for %s" % billing_address_id) + except BillingAddress.MultipleObjectsReturned as mor: + logger.debug("Multiple BillingAddress exist for %s" % billing_address_id) + billing_address = BillingAddress.objects.filter(id=billing_address_id).order_by('-id').first() if billing_address is not None: if billing_address.vat_number_validated_on: return { diff --git a/hosting/templates/hosting/settings.html b/hosting/templates/hosting/settings.html index 3ca0eee6..915bdaa0 100644 --- a/hosting/templates/hosting/settings.html +++ b/hosting/templates/hosting/settings.html @@ -26,6 +26,13 @@ {% for field in form %} {% bootstrap_field field show_label=False type='fields' bound_css_class='' %} {% endfor %} + {% if form.instance.vat_number %} + {% if form.instance.vat_validation_status == "verified" %} + + {% else %} + + {% endif %} + {% endif %}
diff --git a/hosting/views.py b/hosting/views.py index 3f89496b..943c173a 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -37,7 +37,9 @@ from stored_messages.settings import stored_messages_settings from datacenterlight.cms_models import DCLCalculatorPluginModel from datacenterlight.models import VMTemplate, VMPricing -from datacenterlight.utils import create_vm, get_cms_integration, check_otp +from datacenterlight.utils import ( + create_vm, get_cms_integration, check_otp, validate_vat_number +) from hosting.models import UserCardDetail from membership.models import CustomUser, StripeCustomer from opennebula_api.models import OpenNebulaManager @@ -626,9 +628,26 @@ class SettingsView(LoginRequiredMixin, FormView): billing_address_user_form = UserBillingAddressForm( instance=self.request.user.billing_addresses.first(), data=billing_address_data) - billing_address_user_form.save() - msg = _("Billing address updated successfully") - messages.add_message(request, messages.SUCCESS, msg) + billing_address = billing_address_user_form.save() + vat_number = billing_address_user_form.cleaned_data.get( + 'vat_number').strip() + if vat_number: + validate_result = validate_vat_number( + stripe_customer_id=request.user.stripecustomer.stripe_id, + billing_address_id=billing_address.id, + is_user_ba=True + ) + if 'error' in validate_result and validate_result['error']: + messages.add_message( + request, messages.ERROR, validate_result["error"], + extra_tags='vat_error' + ) + else: + msg = _("Billing address updated successfully") + messages.add_message(request, messages.SUCCESS, msg) + else: + msg = _("Billing address updated successfully") + messages.add_message(request, messages.SUCCESS, msg) else: token = form.cleaned_data.get('token') stripe_utils = StripeUtils() From 262bf3e2f7f22a74368e7be0a1c3bc873c49be76 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 13:49:18 +0530 Subject: [PATCH 088/433] Force VAT validation on each save --- hosting/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hosting/views.py b/hosting/views.py index 943c173a..6520a006 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -629,6 +629,9 @@ class SettingsView(LoginRequiredMixin, FormView): instance=self.request.user.billing_addresses.first(), data=billing_address_data) billing_address = billing_address_user_form.save() + billing_address.stripe_tax_id = '' + billing_address.vat_validated_on = None + billing_address.save() vat_number = billing_address_user_form.cleaned_data.get( 'vat_number').strip() if vat_number: From 7eff6fc92c6b3dd3223ad24cdb5a6c73d0ac1b4d Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 13:52:14 +0530 Subject: [PATCH 089/433] Use correct field --- hosting/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index 6520a006..e88e0f2a 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -630,7 +630,7 @@ class SettingsView(LoginRequiredMixin, FormView): data=billing_address_data) billing_address = billing_address_user_form.save() billing_address.stripe_tax_id = '' - billing_address.vat_validated_on = None + billing_address.vat_number_validated_on = None billing_address.save() vat_number = billing_address_user_form.cleaned_data.get( 'vat_number').strip() From b284ed70a663193dce09a8324d06d537eb7a8c37 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 13:56:31 +0530 Subject: [PATCH 090/433] Show error elegantly --- hosting/templates/hosting/includes/_messages.html | 2 +- hosting/views.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/hosting/templates/hosting/includes/_messages.html b/hosting/templates/hosting/includes/_messages.html index 12540558..e9a3e681 100644 --- a/hosting/templates/hosting/includes/_messages.html +++ b/hosting/templates/hosting/includes/_messages.html @@ -1,7 +1,7 @@ {% if messages %}
    {% for message in messages %} -
    {{ message|safe }}
    +
    {{ message|safe }}
    {% endfor %}
{% endif %} \ No newline at end of file diff --git a/hosting/views.py b/hosting/views.py index e88e0f2a..aeca6ada 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -642,7 +642,8 @@ class SettingsView(LoginRequiredMixin, FormView): ) if 'error' in validate_result and validate_result['error']: messages.add_message( - request, messages.ERROR, validate_result["error"], + request, messages.ERROR, + "VAT Number validation error: %s" % validate_result["error"], extra_tags='vat_error' ) else: From 2038d719f097240417db439944f7c9d00edcde64 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 13:58:50 +0530 Subject: [PATCH 091/433] Show status icon for pending and verified only --- hosting/templates/hosting/settings.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/templates/hosting/settings.html b/hosting/templates/hosting/settings.html index 915bdaa0..0d27f291 100644 --- a/hosting/templates/hosting/settings.html +++ b/hosting/templates/hosting/settings.html @@ -29,7 +29,7 @@ {% if form.instance.vat_number %} {% if form.instance.vat_validation_status == "verified" %} - {% else %} + {% else if form.instance.vat_validation_status == "pending" %} {% endif %} {% endif %} From c3b22992ea0470396fe273786b4030e743b1309f Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 14:00:27 +0530 Subject: [PATCH 092/433] Fix wrong elif syntax --- hosting/templates/hosting/settings.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/templates/hosting/settings.html b/hosting/templates/hosting/settings.html index 0d27f291..0452f49e 100644 --- a/hosting/templates/hosting/settings.html +++ b/hosting/templates/hosting/settings.html @@ -29,7 +29,7 @@ {% if form.instance.vat_number %} {% if form.instance.vat_validation_status == "verified" %} - {% else if form.instance.vat_validation_status == "pending" %} + {% elif form.instance.vat_validation_status == "pending" %} {% endif %} {% endif %} From ec5bfb18b3b2696f8150f11a3a1bbe49b13f7d95 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 14:05:56 +0530 Subject: [PATCH 093/433] Replace parenthesis is template --- hosting/templates/hosting/includes/_messages.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/templates/hosting/includes/_messages.html b/hosting/templates/hosting/includes/_messages.html index e9a3e681..b261a512 100644 --- a/hosting/templates/hosting/includes/_messages.html +++ b/hosting/templates/hosting/includes/_messages.html @@ -1,7 +1,7 @@ {% if messages %}
    {% for message in messages %} -
    {{ message|safe }}
    +
    {{ message|safe }}
    {% endfor %}
{% endif %} \ No newline at end of file From 364f5599e637cac1160c627303adfae51b556399 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 14:08:53 +0530 Subject: [PATCH 094/433] Correct the way we show vat error --- hosting/templates/hosting/includes/_messages.html | 2 +- hosting/views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hosting/templates/hosting/includes/_messages.html b/hosting/templates/hosting/includes/_messages.html index b261a512..12540558 100644 --- a/hosting/templates/hosting/includes/_messages.html +++ b/hosting/templates/hosting/includes/_messages.html @@ -1,7 +1,7 @@ {% if messages %}
    {% for message in messages %} -
    {{ message|safe }}
    +
    {{ message|safe }}
    {% endfor %}
{% endif %} \ No newline at end of file diff --git a/hosting/views.py b/hosting/views.py index aeca6ada..17c3fb5e 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -644,7 +644,7 @@ class SettingsView(LoginRequiredMixin, FormView): messages.add_message( request, messages.ERROR, "VAT Number validation error: %s" % validate_result["error"], - extra_tags='vat_error' + extra_tags='error' ) else: msg = _("Billing address updated successfully") From 9aff248d3181668670c1c769cbd14f5ba35dc95e Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 14:19:07 +0530 Subject: [PATCH 095/433] Use correct billingaddress --- datacenterlight/utils.py | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 6ae05d39..4b36870a 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -199,15 +199,30 @@ def validate_vat_number(stripe_customer_id, billing_address_id, } -def create_tax_id(stripe_customer_id, billing_address_id, type): - try: - billing_address = BillingAddress.objects.get(id=billing_address_id) - except BillingAddress.DoesNotExist as dne: - billing_address = None - logger.debug("BillingAddress does not exist for %s" % billing_address_id) - except BillingAddress.MultipleObjectsReturned as mor: - logger.debug("Multiple BillingAddress exist for %s" % billing_address_id) - billing_address = BillingAddress.objects.filter(billing_address_id).order_by('-id').first() +def create_tax_id(stripe_customer_id, billing_address_id, type, + is_user_ba=False): + if is_user_ba: + try: + billing_address = UserBillingAddress.objects.get( + id=billing_address_id) + except UserBillingAddress.DoesNotExist as dne: + billing_address = None + logger.debug( + "UserBillingAddress does not exist for %s" % billing_address_id) + except UserBillingAddress.MultipleObjectsReturned as mor: + logger.debug( + "Multiple UserBillingAddress exist for %s" % billing_address_id) + billing_address = UserBillingAddress.objects.filter( + id=billing_address_id).order_by('-id').first() + else: + try: + billing_address = BillingAddress.objects.get(id=billing_address_id) + except BillingAddress.DoesNotExist as dne: + billing_address = None + logger.debug("BillingAddress does not exist for %s" % billing_address_id) + except BillingAddress.MultipleObjectsReturned as mor: + logger.debug("Multiple BillingAddress exist for %s" % billing_address_id) + billing_address = BillingAddress.objects.filter(billing_address_id).order_by('-id').first() stripe_utils = StripeUtils() tax_id_response = stripe_utils.create_tax_id_for_user( stripe_customer_id, From d8c03a4364407ba50966b8bf7d83d494dd84bfff Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 14:21:18 +0530 Subject: [PATCH 096/433] Add missing param --- datacenterlight/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 4b36870a..f23d91bc 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -183,7 +183,9 @@ def validate_vat_number(stripe_customer_id, billing_address_id, else: tax_id_obj = create_tax_id( stripe_customer_id, billing_address_id, - "ch_vat" if billing_address.country.lower() == "ch" else "eu_vat") + "ch_vat" if billing_address.country.lower() == "ch" else "eu_vat", + is_user_ba=is_user_ba + ) else: return { "status": "invalid billing address", From ad52338653f84ad73118189f40996a0853e015ee Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 14:27:16 +0530 Subject: [PATCH 097/433] Save billing addresses --- datacenterlight/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index f23d91bc..af49d1ba 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -245,11 +245,13 @@ def create_tax_id(stripe_customer_id, billing_address_id, type, billing_address_set.add(ho.billing_address) for b_address in billing_address_set: b_address.stripe_tax_id = tax_id_obj.id + b_address.save() ub_addresses = stripe_customer.user.billing_addresses.filter( vat_number=billing_address.vat_number) for ub_address in ub_addresses: ub_address.stripe_tax_id = tax_id_obj.id + ub_address.save() except StripeCustomer.DoesNotExist as dne: logger.debug("StripeCustomer %s does not exist" % stripe_customer_id) billing_address.stripe_tax_id = tax_id_obj.id From 5ab0bf6993102a66942a8b756203e4663ff3327a Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 14:46:16 +0530 Subject: [PATCH 098/433] Retrieve tax id if exists before creating a new one --- utils/stripe_utils.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index 2cadefd2..72f04352 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -437,9 +437,13 @@ class StripeUtils(object): @handleStripeError def create_tax_id_for_user(self, stripe_customer_id, vat_number, type="eu_vat"): - tax_id_obj = stripe.Customer.create_tax_id( - stripe_customer_id, - type=type, - value=vat_number, + tax_id_obj = stripe.Customer.retrieve( + stripe_customer_id,type=type, value=vat_number ) + if tax_id_obj["response_object"]["error"]: + tax_id_obj = stripe.Customer.create_tax_id( + stripe_customer_id, + type=type, + value=vat_number, + ) return tax_id_obj From 6d0a7f7049b97b82ef08adb287359e689f02d7c7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 18:06:22 +0530 Subject: [PATCH 099/433] Improve stripe_customer_id --- datacenterlight/utils.py | 2 +- utils/stripe_utils.py | 23 ++++++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index af49d1ba..4ebdc4ab 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -226,7 +226,7 @@ def create_tax_id(stripe_customer_id, billing_address_id, type, logger.debug("Multiple BillingAddress exist for %s" % billing_address_id) billing_address = BillingAddress.objects.filter(billing_address_id).order_by('-id').first() stripe_utils = StripeUtils() - tax_id_response = stripe_utils.create_tax_id_for_user( + tax_id_response = stripe_utils.get_or_create_tax_id_for_user( stripe_customer_id, vat_number=billing_address.vat_number, type=type diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index 72f04352..20b5c19a 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -436,14 +436,19 @@ class StripeUtils(object): subscription.save() @handleStripeError - def create_tax_id_for_user(self, stripe_customer_id, vat_number, type="eu_vat"): - tax_id_obj = stripe.Customer.retrieve( - stripe_customer_id,type=type, value=vat_number + def get_or_create_tax_id_for_user(self, stripe_customer_id, vat_number, type="eu_vat"): + tax_ids_list = stripe.Customer.list_tax_ids( + stripe_customer_id, + limit=100, + ) + for tax_id_obj in tax_ids_list.data: + if tax_id_obj.value == vat_number: + logger.debug("tax id obj exists already") + return tax_id_obj + logger.debug("tax id obj does not exist. Creating a new one") + tax_id_obj = stripe.Customer.create_tax_id( + stripe_customer_id, + type=type, + value=vat_number, ) - if tax_id_obj["response_object"]["error"]: - tax_id_obj = stripe.Customer.create_tax_id( - stripe_customer_id, - type=type, - value=vat_number, - ) return tax_id_obj From 32d9f06c18dbea2bf789b8e4f2615f4613c9a032 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 18:22:08 +0530 Subject: [PATCH 100/433] Add logger messages --- datacenterlight/utils.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 4ebdc4ab..7181e38f 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -156,18 +156,27 @@ def validate_vat_number(stripe_customer_id, billing_address_id, logger.debug("Multiple BillingAddress exist for %s" % billing_address_id) billing_address = BillingAddress.objects.filter(id=billing_address_id).order_by('-id').first() if billing_address is not None: + logger.debug("BillingAddress found: %s %s type=%s" % ( + billing_address_id, str(billing_address), type(billing_address))) if billing_address.vat_number_validated_on: + logger.debug("billing_address verified on %s" % + billing_address.vat_number_validated_on) return { "validated_on": billing_address.vat_number_validated_on, "status": "verified" } else: + logger.debug("billing_address not yet verified, " + "Checking if we already have a tax id") if billing_address.stripe_tax_id: + logger.debug("We have a tax id %s" % billing_address.stripe_tax_id) tax_id_obj = stripe.Customer.retrieve_tax_id( stripe_customer_id, billing_address.stripe_tax_id, ) if tax_id_obj.verification.status == "verified": + logger.debug("Latest status on Stripe=%s. Updating" % + tax_id_obj.verification.status) # update billing address billing_address.vat_number_validated_on = datetime.datetime.now() billing_address.save() @@ -176,17 +185,24 @@ def validate_vat_number(stripe_customer_id, billing_address_id, "validated_on": billing_address.vat_number_validated_on } else: + logger.debug( + "Latest status on Stripe=%s" % str(tax_id_obj) + ) return { - "status": tax_id_obj.verification.status, + "status": tax_id_obj.verification.status if tax_id_obj + else "unknown", "validated_on": "" } else: + logger.debug("Creating a tax id") tax_id_obj = create_tax_id( stripe_customer_id, billing_address_id, "ch_vat" if billing_address.country.lower() == "ch" else "eu_vat", is_user_ba=is_user_ba ) + logger.debug("Created tax_id %s" % tax_id_obj['response_object'].id) else: + logger.debug("invalid billing address") return { "status": "invalid billing address", "validated_on": "" From 064ea5be2f3a366048817f55497aeed844652fd5 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 18:26:30 +0530 Subject: [PATCH 101/433] More logging --- datacenterlight/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 7181e38f..1124e0e0 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -200,6 +200,7 @@ def validate_vat_number(stripe_customer_id, billing_address_id, "ch_vat" if billing_address.country.lower() == "ch" else "eu_vat", is_user_ba=is_user_ba ) + logger.debug("tax_id_obj = %s" % str(tax_id_obj)) logger.debug("Created tax_id %s" % tax_id_obj['response_object'].id) else: logger.debug("invalid billing address") From c6147c887c97a64e7ace1865496afd0c908a9bad Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 18:34:26 +0530 Subject: [PATCH 102/433] More logging + improve ba string repr --- datacenterlight/utils.py | 6 +++++- utils/models.py | 10 ++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 1124e0e0..8d12be3f 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -201,7 +201,7 @@ def validate_vat_number(stripe_customer_id, billing_address_id, is_user_ba=is_user_ba ) logger.debug("tax_id_obj = %s" % str(tax_id_obj)) - logger.debug("Created tax_id %s" % tax_id_obj['response_object'].id) + logger.debug("Created tax_id %s" % tax_id_obj.id) else: logger.debug("invalid billing address") return { @@ -252,23 +252,27 @@ def create_tax_id(stripe_customer_id, billing_address_id, type, tax_id_obj = tax_id_response.get('response_object') if not tax_id_obj: + logger.debug("Received none in tax_id_obj") return tax_id_response try: stripe_customer = StripeCustomer.objects.get(stripe_id=stripe_customer_id) billing_address_set = set() + logger.debug("Updating billing address") for ho in stripe_customer.hostingorder_set.all(): if ho.billing_address.vat_number == billing_address.vat_number: billing_address_set.add(ho.billing_address) for b_address in billing_address_set: b_address.stripe_tax_id = tax_id_obj.id b_address.save() + logger.debug("Updated billing_address %s" % str(b_address)) ub_addresses = stripe_customer.user.billing_addresses.filter( vat_number=billing_address.vat_number) for ub_address in ub_addresses: ub_address.stripe_tax_id = tax_id_obj.id ub_address.save() + logger.debug("Updated user_billing_address %s" % str(ub_address)) except StripeCustomer.DoesNotExist as dne: logger.debug("StripeCustomer %s does not exist" % stripe_customer_id) billing_address.stripe_tax_id = tax_id_obj.id diff --git a/utils/models.py b/utils/models.py index 89475752..e2cabaad 100644 --- a/utils/models.py +++ b/utils/models.py @@ -24,9 +24,10 @@ class BaseBillingAddress(models.Model): class BillingAddress(BaseBillingAddress): def __str__(self): if self.vat_number: - return "%s, %s, %s, %s, %s, %s" % ( + return "%s, %s, %s, %s, %s, %s %s %s" % ( self.cardholder_name, self.street_address, self.city, - self.postal_code, self.country, self.vat_number + self.postal_code, self.country, self.vat_number, + self.stripe_tax_id, self.vat_number_validated_on ) else: return "%s, %s, %s, %s, %s" % ( @@ -41,9 +42,10 @@ class UserBillingAddress(BaseBillingAddress): def __str__(self): if self.vat_number: - return "%s, %s, %s, %s, %s, %s" % ( + return "%s, %s, %s, %s, %s, %s %s %s" % ( self.cardholder_name, self.street_address, self.city, - self.postal_code, self.country, self.vat_number + self.postal_code, self.country, self.vat_number, + self.stripe_tax_id, self.vat_number_validated_on ) else: return "%s, %s, %s, %s, %s" % ( From f2d738ae62d7b6d593b9fbfa055bedb70744a151 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 18:40:06 +0530 Subject: [PATCH 103/433] Improve logging format --- dynamicweb/settings/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index e026245e..c959c237 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -705,7 +705,7 @@ if ENABLE_LOGGING: 'disable_existing_loggers': False, 'formatters': { 'standard': { - 'format': '%(asctime)s %(levelname)s %(name)s: %(message)s' + 'format': '%(asctime)s,%(msecs)d %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s', } }, 'handlers': handlers_dict, From 9e87fa76c32778e3984d35f63849e05c8267e4e3 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 18:47:40 +0530 Subject: [PATCH 104/433] More logging --- hosting/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hosting/views.py b/hosting/views.py index 17c3fb5e..4e0cb900 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -634,12 +634,14 @@ class SettingsView(LoginRequiredMixin, FormView): billing_address.save() vat_number = billing_address_user_form.cleaned_data.get( 'vat_number').strip() + logger.debug("Vat number = %s" % vat_number) if vat_number: validate_result = validate_vat_number( stripe_customer_id=request.user.stripecustomer.stripe_id, billing_address_id=billing_address.id, is_user_ba=True ) + logger.debug("validate_result = %s" % str(validate_result)) if 'error' in validate_result and validate_result['error']: messages.add_message( request, messages.ERROR, From cbf2f05d7054852cc59ce475555a6b9d161dc430 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 18:54:17 +0530 Subject: [PATCH 105/433] Use the latest billing address as the default one --- datacenterlight/views.py | 2 +- hosting/views.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index de68f418..bd940c5f 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -254,7 +254,7 @@ class PaymentOrderView(FormView): ) else: billing_address_form = BillingAddressForm( - instance=self.request.user.billing_addresses.first() + instance=self.request.user.billing_addresses.order_by('-id').first() ) user = self.request.user if hasattr(user, 'stripecustomer'): diff --git a/hosting/views.py b/hosting/views.py index 4e0cb900..869f5d1c 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -626,7 +626,7 @@ class SettingsView(LoginRequiredMixin, FormView): 'user': self.request.user.id }) billing_address_user_form = UserBillingAddressForm( - instance=self.request.user.billing_addresses.first(), + instance=self.request.user.billing_addresses.order_by('-id').first(), data=billing_address_data) billing_address = billing_address_user_form.save() billing_address.stripe_tax_id = '' From 7eed04ec73f6326357d6de11a7d08cadcdcfc595 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 19:16:26 +0530 Subject: [PATCH 106/433] Remove one log too many --- datacenterlight/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 8d12be3f..aa679e5d 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -201,7 +201,6 @@ def validate_vat_number(stripe_customer_id, billing_address_id, is_user_ba=is_user_ba ) logger.debug("tax_id_obj = %s" % str(tax_id_obj)) - logger.debug("Created tax_id %s" % tax_id_obj.id) else: logger.debug("invalid billing address") return { From 69996f536bf174381dbb40daa2ec6d7166c61a3a Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 19:35:50 +0530 Subject: [PATCH 107/433] Add vat_validation_status --- utils/migrations/0008_auto_20191226_1402.py | 25 +++++++++++++++++++++ utils/models.py | 2 ++ webhook/views.py | 2 ++ 3 files changed, 29 insertions(+) create mode 100644 utils/migrations/0008_auto_20191226_1402.py diff --git a/utils/migrations/0008_auto_20191226_1402.py b/utils/migrations/0008_auto_20191226_1402.py new file mode 100644 index 00000000..2c409c0f --- /dev/null +++ b/utils/migrations/0008_auto_20191226_1402.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-12-26 14:02 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('utils', '0007_auto_20191226_0610'), + ] + + operations = [ + migrations.AddField( + model_name='billingaddress', + name='vat_validation_status', + field=models.CharField(blank=True, default='', max_length=25), + ), + migrations.AddField( + model_name='userbillingaddress', + name='vat_validation_status', + field=models.CharField(blank=True, default='', max_length=25), + ), + ] diff --git a/utils/models.py b/utils/models.py index e2cabaad..f179fffa 100644 --- a/utils/models.py +++ b/utils/models.py @@ -16,6 +16,8 @@ class BaseBillingAddress(models.Model): vat_number = models.CharField(max_length=100, default="", blank=True) stripe_tax_id = models.CharField(max_length=100, default="", blank=True) vat_number_validated_on = models.DateTimeField(blank=True, null=True) + vat_validation_status = models.CharField(max_length=25, default="", + blank=True) class Meta: abstract = True diff --git a/webhook/views.py b/webhook/views.py index 7e9175e6..482985ba 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -73,11 +73,13 @@ def handle_webhook(request): if tax_id_obj.verification.status == "verified": b_addresses = BillingAddress.objects.filter(stripe_tax_id=tax_id_obj.id) for b_address in b_addresses: + b_address.vat_validation_status = tax_id_obj.verification.status b_address.vat_number_validated_on = datetime.datetime.now() b_address.save() ub_addresses = UserBillingAddress.objects.filter(stripe_tax_id=tax_id_obj.id) for ub_address in ub_addresses: + ub_address.vat_validation_status = tax_id_obj.verification.status ub_address.vat_number_validated_on = datetime.datetime.now() ub_address.save() else: From a5c7865811f341aec78af48b5f5749d239a7fcca Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 19:37:57 +0530 Subject: [PATCH 108/433] Save validation status --- datacenterlight/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index aa679e5d..1fb546b4 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -179,12 +179,15 @@ def validate_vat_number(stripe_customer_id, billing_address_id, tax_id_obj.verification.status) # update billing address billing_address.vat_number_validated_on = datetime.datetime.now() + billing_address.vat_validation_status = tax_id_obj.verification.status billing_address.save() return { "status": "verified", "validated_on": billing_address.vat_number_validated_on } else: + billing_address.vat_validation_status = tax_id_obj.verification.status + billing_address.save() logger.debug( "Latest status on Stripe=%s" % str(tax_id_obj) ) From fcdabd8dc30371fe45a8ceb46d41e298dd9a25cc Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 19:55:28 +0530 Subject: [PATCH 109/433] Set vat validation status in more places --- datacenterlight/utils.py | 3 +++ utils/models.py | 10 ++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 1fb546b4..a082df7f 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -266,6 +266,7 @@ def create_tax_id(stripe_customer_id, billing_address_id, type, billing_address_set.add(ho.billing_address) for b_address in billing_address_set: b_address.stripe_tax_id = tax_id_obj.id + b_address.vat_validation_status = tax_id_obj.verification.status b_address.save() logger.debug("Updated billing_address %s" % str(b_address)) @@ -273,10 +274,12 @@ def create_tax_id(stripe_customer_id, billing_address_id, type, vat_number=billing_address.vat_number) for ub_address in ub_addresses: ub_address.stripe_tax_id = tax_id_obj.id + ub_address.vat_validation_status = tax_id_obj.verification.status ub_address.save() logger.debug("Updated user_billing_address %s" % str(ub_address)) except StripeCustomer.DoesNotExist as dne: logger.debug("StripeCustomer %s does not exist" % stripe_customer_id) billing_address.stripe_tax_id = tax_id_obj.id + billing_address.vat_validation_status = tax_id_obj.verification.status billing_address.save() return tax_id_obj diff --git a/utils/models.py b/utils/models.py index f179fffa..9e643707 100644 --- a/utils/models.py +++ b/utils/models.py @@ -26,10 +26,11 @@ class BaseBillingAddress(models.Model): class BillingAddress(BaseBillingAddress): def __str__(self): if self.vat_number: - return "%s, %s, %s, %s, %s, %s %s %s" % ( + return "%s, %s, %s, %s, %s, %s %s %s %s" % ( self.cardholder_name, self.street_address, self.city, self.postal_code, self.country, self.vat_number, - self.stripe_tax_id, self.vat_number_validated_on + self.stripe_tax_id, self.vat_number_validated_on, + self.vat_validation_status ) else: return "%s, %s, %s, %s, %s" % ( @@ -44,10 +45,11 @@ class UserBillingAddress(BaseBillingAddress): def __str__(self): if self.vat_number: - return "%s, %s, %s, %s, %s, %s %s %s" % ( + return "%s, %s, %s, %s, %s, %s %s %s %s" % ( self.cardholder_name, self.street_address, self.city, self.postal_code, self.country, self.vat_number, - self.stripe_tax_id, self.vat_number_validated_on + self.stripe_tax_id, self.vat_number_validated_on, + self.vat_validation_status ) else: return "%s, %s, %s, %s, %s" % ( From d62986c91f472368e9ad9a34bfdbaa00e11ef0c6 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 20:05:55 +0530 Subject: [PATCH 110/433] Bugfix: use correct total price after discount --- datacenterlight/views.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index bd940c5f..8d371c97 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -662,14 +662,13 @@ class OrderConfirmationView(DetailView, FormView): request.session["vat_validation_status"] == "verified"): vm_specs["vat_percent"] = 0 vm_specs["vat"] = 0 - vm_specs["total_price"] = price vm_specs["vat_validation_status"] = "verified" else: vm_specs["vat"] = vat vm_specs["vat_percent"] = vat_percent - vm_specs["total_price"] = round(price + vat - discount['amount'], 2) vm_specs["vat_validation_status"] = request.session["vat_validation_status"] if "vat_validation_status" in request.session else "" - + vm_specs["total_price"] = round(price + vat - discount['amount'], + 2) vm_specs["vat_country"] = user_vat_country vm_specs["discount"] = discount request.session['specs'] = vm_specs From 12975565a51de8eb61a0954c13f37500a52f76ad Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 20:09:28 +0530 Subject: [PATCH 111/433] More bugfix --- datacenterlight/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 8d371c97..10b3ebbf 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -667,7 +667,7 @@ class OrderConfirmationView(DetailView, FormView): vm_specs["vat"] = vat vm_specs["vat_percent"] = vat_percent vm_specs["vat_validation_status"] = request.session["vat_validation_status"] if "vat_validation_status" in request.session else "" - vm_specs["total_price"] = round(price + vat - discount['amount'], + vm_specs["total_price"] = round(price + vm_specs["vat"] - discount['amount'], 2) vm_specs["vat_country"] = user_vat_country vm_specs["discount"] = discount From 74921fcd4ab207e4f6959bcbf5d2a401ecc39d43 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 20:21:01 +0530 Subject: [PATCH 112/433] Send the same error message as forwarded by Stripe --- utils/stripe_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index 20b5c19a..4030317e 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -37,7 +37,7 @@ def handleStripeError(f): return response except stripe.error.InvalidRequestError as e: logger.error(str(e)) - response.update({'error': "Invalid parameters"}) + response.update({'error': str(e)}) return response except stripe.error.AuthenticationError as e: # Authentication with Stripe's API failed From c142d743d1677cb40bc419c4a492ac545fd4199e Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 20:25:07 +0530 Subject: [PATCH 113/433] Show only error message --- utils/stripe_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index 4030317e..d4b71f8c 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -37,7 +37,7 @@ def handleStripeError(f): return response except stripe.error.InvalidRequestError as e: logger.error(str(e)) - response.update({'error': str(e)}) + response.update({'error': str(e._message)}) return response except stripe.error.AuthenticationError as e: # Authentication with Stripe's API failed From 2a760639f6b565c81c278955af4918bb23f41414 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 20:27:15 +0530 Subject: [PATCH 114/433] Set validation status to empty on error --- hosting/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/hosting/views.py b/hosting/views.py index 869f5d1c..6467c243 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -631,6 +631,7 @@ class SettingsView(LoginRequiredMixin, FormView): billing_address = billing_address_user_form.save() billing_address.stripe_tax_id = '' billing_address.vat_number_validated_on = None + billing_address.vat_validation_status = '' billing_address.save() vat_number = billing_address_user_form.cleaned_data.get( 'vat_number').strip() From 3202c83c68a14a6c8ddfd88b147257d892148b4d Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 20:32:01 +0530 Subject: [PATCH 115/433] Show error messages in red --- hosting/templates/hosting/includes/_messages.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/hosting/templates/hosting/includes/_messages.html b/hosting/templates/hosting/includes/_messages.html index 12540558..ceea8190 100644 --- a/hosting/templates/hosting/includes/_messages.html +++ b/hosting/templates/hosting/includes/_messages.html @@ -1,7 +1,11 @@ {% if messages %}
    {% for message in messages %} -
    {{ message|safe }}
    + {% if message.tags and message.tags == 'error' %} +

    {{ message|safe }}

    + {% else %} +
    {{ message|safe }}
    + {% endif %} {% endfor %}
{% endif %} \ No newline at end of file From 398a2559650d4d59b90c918976524901882bfce7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 20:36:54 +0530 Subject: [PATCH 116/433] Change condition so as to show error messages in red --- hosting/templates/hosting/includes/_messages.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hosting/templates/hosting/includes/_messages.html b/hosting/templates/hosting/includes/_messages.html index ceea8190..b5f78b98 100644 --- a/hosting/templates/hosting/includes/_messages.html +++ b/hosting/templates/hosting/includes/_messages.html @@ -1,10 +1,11 @@ {% if messages %}
    {% for message in messages %} - {% if message.tags and message.tags == 'error' %} + {% if message.tags and 'error' in message.tags %}

    {{ message|safe }}

    - {% else %} + {% elif message.tags %}
    {{ message|safe }}
    + {{message.tags}} {% endif %} {% endfor %}
From 7397be98a5e01e7fa2b9894147f9811f203bd07b Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 20:40:50 +0530 Subject: [PATCH 117/433] Remove message tags printed mistakenly --- hosting/templates/hosting/includes/_messages.html | 1 - 1 file changed, 1 deletion(-) diff --git a/hosting/templates/hosting/includes/_messages.html b/hosting/templates/hosting/includes/_messages.html index b5f78b98..a32ce177 100644 --- a/hosting/templates/hosting/includes/_messages.html +++ b/hosting/templates/hosting/includes/_messages.html @@ -5,7 +5,6 @@

{{ message|safe }}

{% elif message.tags %}
{{ message|safe }}
- {{message.tags}} {% endif %} {% endfor %} From b567b013622ccf560a309692624ed7a5c0dbdd9d Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Dec 2019 20:51:11 +0530 Subject: [PATCH 118/433] For CH we don't care whether VAT is validated or not --- datacenterlight/views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 10b3ebbf..5b98651c 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -658,7 +658,10 @@ class OrderConfirmationView(DetailView, FormView): request.session["vat_validation_status"] = validate_result["status"] - if ("vat_validation_status" in request.session and + if user_vat_country.lower() == "ch": + vm_specs["vat"] = vat + vm_specs["vat_percent"] = vat_percent + elif ("vat_validation_status" in request.session and request.session["vat_validation_status"] == "verified"): vm_specs["vat_percent"] = 0 vm_specs["vat"] = 0 From 7949ab274e63cb343d0c0bf7bb4cbc59ebbeec1b Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 31 Dec 2019 15:43:43 +0530 Subject: [PATCH 119/433] Impove logging --- utils/stripe_utils.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index d4b71f8c..9d9bb77f 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -445,7 +445,13 @@ class StripeUtils(object): if tax_id_obj.value == vat_number: logger.debug("tax id obj exists already") return tax_id_obj - logger.debug("tax id obj does not exist. Creating a new one") + else: + logger.debug("{val1} is not equal to {val2}".format( + val1=tax_id_obj.value, val2=vat_number + )) + logger.debug("tax id obj does not exist for {val}. Creating a new one".format( + val=vat_number + )) tax_id_obj = stripe.Customer.create_tax_id( stripe_customer_id, type=type, From aba3092207efb73dd5e7cac34f374f154734ba32 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 31 Dec 2019 15:57:50 +0530 Subject: [PATCH 120/433] Ignore spaces, hyphens and dots in vat number comparison --- utils/stripe_utils.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index 9d9bb77f..50de6979 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -442,7 +442,7 @@ class StripeUtils(object): limit=100, ) for tax_id_obj in tax_ids_list.data: - if tax_id_obj.value == vat_number: + if self.compare_vat_numbers(tax_id_obj.value, vat_number): logger.debug("tax id obj exists already") return tax_id_obj else: @@ -458,3 +458,8 @@ class StripeUtils(object): value=vat_number, ) return tax_id_obj + + def compare_vat_numbers(self, vat1, vat2): + _vat1 = vat1.replace(" ", "").replace(".", "").replace("-","") + _vat2 = vat2.replace(" ", "").replace(".", "").replace("-","") + return True if _vat1 == _vat2 else False \ No newline at end of file From 3ca7e89f4fe8d06eb87e2bbd4ab80a8d411a0efb Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 31 Dec 2019 17:28:11 +0530 Subject: [PATCH 121/433] Show VAT for eu countries only --- .../templates/datacenterlight/order_detail.html | 14 ++++++++------ datacenterlight/utils.py | 10 ++++++++++ datacenterlight/views.py | 11 +++++++---- hosting/templates/hosting/settings.html | 11 +++++++---- 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 5f61ccf3..db3f73a8 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -33,12 +33,14 @@ {{billing_address.street_address}}, {{billing_address.postal_code}}
{{billing_address.city}}, {{billing_address.country}} {% if billing_address.vat_number %} -
{% trans "VAT Number" %} {{billing_address.vat_number}}  - {% if vm.vat_validation_status == "verified" %} - - {% else %} - - {% endif %} +
{% trans "VAT Number" %} {{billing_address.vat_number}} + {% if vm.vat_validation_status != "ch_vat" and vm.vat_validation_status != "not_needed" %} + {% if vm.vat_validation_status == "verified" %} + + {% else %} + + {% endif %} + {% endif %} {% endif %} {% endwith %}

diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index a082df7f..da9ebdc1 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -18,6 +18,11 @@ from .models import VMPricing, VMTemplate logger = logging.getLogger(__name__) +eu_countries = ['at', 'be', 'bg', 'ch', 'cy', 'cz', 'hr', 'dk', + 'ee', 'fi', 'fr', 'mc', 'de', 'gr', 'hu', 'ie', 'it', + 'lv', 'lu', 'mt', 'nl', 'po', 'pt', 'ro','sk', 'si', 'es', + 'se', 'gb'] + def get_cms_integration(name): current_site = Site.objects.get_current() @@ -158,6 +163,11 @@ def validate_vat_number(stripe_customer_id, billing_address_id, if billing_address is not None: logger.debug("BillingAddress found: %s %s type=%s" % ( billing_address_id, str(billing_address), type(billing_address))) + if billing_address.country.lower().strip() not in eu_countries: + return { + "validated_on": "", + "status": "not_needed" + } if billing_address.vat_number_validated_on: logger.debug("billing_address verified on %s" % billing_address.vat_number_validated_on) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 5b98651c..1d832e39 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -562,7 +562,7 @@ class PaymentOrderView(FormView): request.session["vat_validation_status"] = validate_result["status"] # For generic payment we take the user directly to confirmation - if ('generic_payment_type' in request.session and + if ('generic_p`ayment_type' in request.session and self.request.session['generic_payment_type'] == 'generic'): return HttpResponseRedirect( reverse('datacenterlight:order_confirmation')) @@ -617,7 +617,8 @@ class OrderConfirmationView(DetailView, FormView): if ('generic_payment_type' in request.session and self.request.session['generic_payment_type'] == 'generic'): - if request.session["vat_validation_status"] == "verified": + if (request.session["vat_validation_status"] == "verified" or + request.session["vat_validation_status"] == "not_needed"): request.session['generic_payment_details']['vat_rate'] = 0 request.session['generic_payment_details']['vat_amount'] = 0 request.session['generic_payment_details']['amount'] = request.session['generic_payment_details']['amount_before_vat'] @@ -661,11 +662,13 @@ class OrderConfirmationView(DetailView, FormView): if user_vat_country.lower() == "ch": vm_specs["vat"] = vat vm_specs["vat_percent"] = vat_percent + vm_specs["vat_validation_status"] = "ch_vat" elif ("vat_validation_status" in request.session and - request.session["vat_validation_status"] == "verified"): + (request.session["vat_validation_status"] == "verified" or + request.session["vat_validation_status"] == "not_needed")): vm_specs["vat_percent"] = 0 vm_specs["vat"] = 0 - vm_specs["vat_validation_status"] = "verified" + vm_specs["vat_validation_status"] = request.session["vat_validation_status"] else: vm_specs["vat"] = vat vm_specs["vat_percent"] = vat_percent diff --git a/hosting/templates/hosting/settings.html b/hosting/templates/hosting/settings.html index 0452f49e..5a0b7990 100644 --- a/hosting/templates/hosting/settings.html +++ b/hosting/templates/hosting/settings.html @@ -27,10 +27,13 @@ {% bootstrap_field field show_label=False type='fields' bound_css_class='' %} {% endfor %} {% if form.instance.vat_number %} - {% if form.instance.vat_validation_status == "verified" %} - - {% elif form.instance.vat_validation_status == "pending" %} - + {% if form.instance.vat_validation_status != "ch_vat" and form.instance.vat_validation_status != "not_needed" %} + + {% if form.instance.vat_validation_status == "verified" %} + + {% elif form.instance.vat_validation_status == "pending" %} + + {% endif %} {% endif %} {% endif %}
From 9078e461963ec471349c9da88c21787adb31cef7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 31 Dec 2019 18:08:09 +0530 Subject: [PATCH 122/433] Add log --- datacenterlight/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index da9ebdc1..087db3a3 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -208,6 +208,7 @@ def validate_vat_number(stripe_customer_id, billing_address_id, } else: logger.debug("Creating a tax id") + logger.debug("Billing address = %s" % str(billing_address)) tax_id_obj = create_tax_id( stripe_customer_id, billing_address_id, "ch_vat" if billing_address.country.lower() == "ch" else "eu_vat", From 2378410f2dc2383c2afc3bdd47057044ce651bb0 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 31 Dec 2019 18:22:57 +0530 Subject: [PATCH 123/433] Compare country for creating new tax id --- datacenterlight/utils.py | 24 ++++++++++++++++-------- utils/stripe_utils.py | 22 ++++++++++++++-------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 087db3a3..de37f95c 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -255,18 +255,26 @@ def create_tax_id(stripe_customer_id, billing_address_id, type, except BillingAddress.MultipleObjectsReturned as mor: logger.debug("Multiple BillingAddress exist for %s" % billing_address_id) billing_address = BillingAddress.objects.filter(billing_address_id).order_by('-id').first() - stripe_utils = StripeUtils() - tax_id_response = stripe_utils.get_or_create_tax_id_for_user( - stripe_customer_id, - vat_number=billing_address.vat_number, - type=type - ) - tax_id_obj = tax_id_response.get('response_object') + tax_id_obj = None + if billing_address: + stripe_utils = StripeUtils() + tax_id_response = stripe_utils.get_or_create_tax_id_for_user( + stripe_customer_id, + vat_number=billing_address.vat_number, + type=type, + country=billing_address.country + ) + + tax_id_obj = tax_id_response.get('response_object') if not tax_id_obj: logger.debug("Received none in tax_id_obj") - return tax_id_response + return { + 'paid': False, + 'response_object': None, + 'error': "No such address found" + } try: stripe_customer = StripeCustomer.objects.get(stripe_id=stripe_customer_id) diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index 50de6979..f8d4c46d 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -436,22 +436,28 @@ class StripeUtils(object): subscription.save() @handleStripeError - def get_or_create_tax_id_for_user(self, stripe_customer_id, vat_number, type="eu_vat"): + def get_or_create_tax_id_for_user(self, stripe_customer_id, vat_number, + type="eu_vat", country=""): tax_ids_list = stripe.Customer.list_tax_ids( stripe_customer_id, limit=100, ) for tax_id_obj in tax_ids_list.data: - if self.compare_vat_numbers(tax_id_obj.value, vat_number): + if (self.compare_vat_numbers(tax_id_obj.value, vat_number) and + tax_id_obj.country.lower().strip() == + country.lower().strip()): logger.debug("tax id obj exists already") return tax_id_obj else: - logger.debug("{val1} is not equal to {val2}".format( - val1=tax_id_obj.value, val2=vat_number - )) - logger.debug("tax id obj does not exist for {val}. Creating a new one".format( - val=vat_number - )) + logger.debug( + "{val1} is not equal to {val2} or {con1} not same as " + "{con2}".format(val1=tax_id_obj.value, val2=vat_number, + con1=tax_id_obj.country.lower(), + con2=country.lower().strip())) + logger.debug( + "tax id obj does not exist for {val}. Creating a new one".format( + val=vat_number + )) tax_id_obj = stripe.Customer.create_tax_id( stripe_customer_id, type=type, From ca5724f10f1adb820300b5dba11a029068153e22 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 31 Dec 2019 18:36:38 +0530 Subject: [PATCH 124/433] Fix error message --- datacenterlight/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index de37f95c..dd30070d 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -273,7 +273,8 @@ def create_tax_id(stripe_customer_id, billing_address_id, type, return { 'paid': False, 'response_object': None, - 'error': "No such address found" + 'error': "No such address found" if 'error' not in tax_id_obj else + tax_id_obj["error"] } try: From 7423a80670206ac6dc1932d8a26c91b6f1969a2d Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 31 Dec 2019 22:50:46 +0530 Subject: [PATCH 125/433] Use correct variable --- datacenterlight/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index dd30070d..97bfef4c 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -273,8 +273,8 @@ def create_tax_id(stripe_customer_id, billing_address_id, type, return { 'paid': False, 'response_object': None, - 'error': "No such address found" if 'error' not in tax_id_obj else - tax_id_obj["error"] + 'error': "No such address found" if 'error' not in tax_id_response else + tax_id_response["error"] } try: From 6ac6db8212cd3fa0b3de3bbcfc7c23c03b8ff3e1 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 31 Dec 2019 22:52:24 +0530 Subject: [PATCH 126/433] Get the last user billing address as the default address --- hosting/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/hosting/views.py b/hosting/views.py index 6467c243..bca31df0 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -621,6 +621,7 @@ class SettingsView(LoginRequiredMixin, FormView): form = self.get_form() if form.is_valid(): if 'billing-form' in request.POST: + current_billing_address = self.request.user.billing_addresses.last() billing_address_data = form.cleaned_data billing_address_data.update({ 'user': self.request.user.id From 0695d689030b67d7181f4ad69903b97f517e8514 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 31 Dec 2019 22:52:49 +0530 Subject: [PATCH 127/433] Create StripeCustomer if not already created --- hosting/views.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/hosting/views.py b/hosting/views.py index bca31df0..41a7967a 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -638,6 +638,17 @@ class SettingsView(LoginRequiredMixin, FormView): 'vat_number').strip() logger.debug("Vat number = %s" % vat_number) if vat_number: + try: + stripe_customer = request.user.stripecustomer + except StripeCustomer.DoesNotExist as dne: + logger.debug( + "User %s does not have a stripecustomer. " + "Creating one." % request.user.email) + stripe_customer = StripeCustomer.get_or_create( + email=request.user.email, + token=None) + request.user.stripecustomer = stripe_customer + request.user.save() validate_result = validate_vat_number( stripe_customer_id=request.user.stripecustomer.stripe_id, billing_address_id=billing_address.id, From 8f2bd568db7ff8e0e07029044447ae36a4ee51fc Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 31 Dec 2019 22:53:32 +0530 Subject: [PATCH 128/433] Restore billing address if VAT number is not valid --- hosting/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index 41a7967a..cdddec64 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -661,6 +661,8 @@ class SettingsView(LoginRequiredMixin, FormView): "VAT Number validation error: %s" % validate_result["error"], extra_tags='error' ) + billing_address = current_billing_address + billing_address.save() else: msg = _("Billing address updated successfully") messages.add_message(request, messages.SUCCESS, msg) @@ -725,7 +727,7 @@ class PaymentVMView(LoginRequiredMixin, FormView): form_class = BillingAddressForm def get_form_kwargs(self): - current_billing_address = self.request.user.billing_addresses.first() + current_billing_address = self.request.user.billing_addresses.last() form_kwargs = super(PaymentVMView, self).get_form_kwargs() if not current_billing_address: return form_kwargs From 6674e70dedad392e2cfb861b538251287eb1747c Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 31 Dec 2019 22:54:12 +0530 Subject: [PATCH 129/433] Compare vat numbers only --- utils/stripe_utils.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index f8d4c46d..cdb5182b 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -443,9 +443,7 @@ class StripeUtils(object): limit=100, ) for tax_id_obj in tax_ids_list.data: - if (self.compare_vat_numbers(tax_id_obj.value, vat_number) and - tax_id_obj.country.lower().strip() == - country.lower().strip()): + if self.compare_vat_numbers(tax_id_obj.value, vat_number): logger.debug("tax id obj exists already") return tax_id_obj else: From d4bfcbef47a02a38bf5a6efb19634c5939a2c2ea Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 31 Dec 2019 22:55:53 +0530 Subject: [PATCH 130/433] Use message as returned by stripe --- utils/stripe_utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index cdb5182b..de16fe4b 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -43,21 +43,21 @@ def handleStripeError(f): # Authentication with Stripe's API failed # (maybe you changed API keys recently) logger.error(str(e)) - response.update({'error': common_message}) + response.update({'error': str(e)}) return response except stripe.error.APIConnectionError as e: logger.error(str(e)) - response.update({'error': common_message}) + response.update({'error': str(e)}) return response except stripe.error.StripeError as e: # maybe send email logger.error(str(e)) - response.update({'error': common_message}) + response.update({'error': str(e)}) return response except Exception as e: # maybe send email logger.error(str(e)) - response.update({'error': common_message}) + response.update({'error': str(e)}) return response return handleProblems From efaf75615ba18b5dfbcebde4857015186d8cca04 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 1 Jan 2020 00:01:44 +0530 Subject: [PATCH 131/433] Send email to admin on VAT number update --- hosting/views.py | 21 +++++++++++++++++++++ webhook/views.py | 24 ++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/hosting/views.py b/hosting/views.py index cdddec64..9d975df1 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -40,6 +40,7 @@ from datacenterlight.models import VMTemplate, VMPricing from datacenterlight.utils import ( 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 membership.models import CustomUser, StripeCustomer from opennebula_api.models import OpenNebulaManager @@ -663,9 +664,29 @@ class SettingsView(LoginRequiredMixin, FormView): ) billing_address = current_billing_address billing_address.save() + email_data = { + 'subject': "%s updated VAT number to %s but failed" % + (request.user.email, vat_number), + 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': settings.DCL_ERROR_EMAILS_TO_LIST, + 'body': "\n".join( + ["%s=%s" % (k, v) for (k, v) in + validate_result.items()]), + } else: + email_data = { + 'subject': "%s updated VAT number to %s" % ( + request.user.email, vat_number + ), + 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': settings.DCL_ERROR_EMAILS_TO_LIST, + 'body': "\n".join( + ["%s=%s" % (k, v) for (k, v) in + validate_result.items()]), + } msg = _("Billing address updated successfully") messages.add_message(request, messages.SUCCESS, msg) + send_plain_email_task.delay(email_data) else: msg = _("Billing address updated successfully") messages.add_message(request, messages.SUCCESS, msg) diff --git a/webhook/views.py b/webhook/views.py index 482985ba..090ac5ae 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -8,6 +8,8 @@ from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST +from membership.models import StripeCustomer + from utils.models import BillingAddress, UserBillingAddress from utils.tasks import send_plain_email_task @@ -70,6 +72,12 @@ def handle_webhook(request): tax_id_obj = event.data.object logger.debug("Tax_id %s is %s" % (tax_id_obj.id, tax_id_obj.verification.status)) + stripe_customer = None + try: + stripe_customer = StripeCustomer.objects.get(tax_id_obj.customer) + except StripeCustomer.DoesNotExist as dne: + logger.debug( + "StripeCustomer %s does not exist" % tax_id_obj.customer) if tax_id_obj.verification.status == "verified": b_addresses = BillingAddress.objects.filter(stripe_tax_id=tax_id_obj.id) for b_address in b_addresses: @@ -82,9 +90,25 @@ def handle_webhook(request): ub_address.vat_validation_status = tax_id_obj.verification.status ub_address.vat_number_validated_on = datetime.datetime.now() ub_address.save() + email_data = { + 'subject': "The VAT %s associated with %s was verified" % + (tax_id_obj.value, stripe_customer.user.email if stripe_customer else "unknown"), + 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': settings.DCL_ERROR_EMAILS_TO_LIST, + 'body': "The following objects were modified:\n".join(b_addresses).join(ub_addresses), + } else: logger.debug("Tax_id %s is %s" % (tax_id_obj.id, tax_id_obj.verification.status)) + email_data = { + 'subject': "The VAT %s associated with %s was %s" % + (tax_id_obj.value, stripe_customer.user.email if stripe_customer else "unknown", tax_id_obj.verification.status), + 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': settings.DCL_ERROR_EMAILS_TO_LIST, + 'body': "Response = %s" % str(tax_id_obj), + } + + send_plain_email_task.delay(email_data) else: logger.error("Unhandled event : " + event.type) return HttpResponse(status=200) From 0bca5113ca38da971415396e00f521d5215e4fc6 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 1 Jan 2020 00:10:56 +0530 Subject: [PATCH 132/433] Fix unwanted char --- datacenterlight/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 1d832e39..c6929fd8 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -562,7 +562,7 @@ class PaymentOrderView(FormView): request.session["vat_validation_status"] = validate_result["status"] # For generic payment we take the user directly to confirmation - if ('generic_p`ayment_type' in request.session and + if ('generic_payment_type' in request.session and self.request.session['generic_payment_type'] == 'generic'): return HttpResponseRedirect( reverse('datacenterlight:order_confirmation')) From 18b04a70e6ee38cfe92ebea2192d691ff9ddc99c Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 1 Jan 2020 00:33:57 +0530 Subject: [PATCH 133/433] Update Changelog for 2.9 --- Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog b/Changelog index fc85728d..f64d101a 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,5 @@ +2.9: 2019-12-31 + * Feature: Enable saving user's VAT Number and validate it (MR!725) 2.8.2: 2019-12-24 * Bugfix: [dcl calculator plugin] Set the POST action url explicitly 2.8.1: 2019-12-24 From 7130df9fd446fe58346c10a1154b8ec46037eb72 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 1 Jan 2020 00:36:22 +0530 Subject: [PATCH 134/433] Update Changelog (add notes for deployment) --- Changelog | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Changelog b/Changelog index f64d101a..fb3e3dfe 100644 --- a/Changelog +++ b/Changelog @@ -1,5 +1,32 @@ 2.9: 2019-12-31 * Feature: Enable saving user's VAT Number and validate it (MR!725) + Notes for deployment: + Notes for deployment: + + 1. Migrate db for utils app + ./manage.py migrate utils + + 2. Uninstall old version and install a more recent version of stripe + + ``` + source venv/bin/activate + ./manage.py shell + pip uninstall stripe + pip install stripe==2.41.0 + ``` + + 3. Create tax id updated webhook + ``` + ./manage.py webhook --create \ + --webhook_endpoint https://datacenterlight.ch/en-us/webhooks/ \ + --events_csv customer.tax_id.updated + ``` + + 4. From the secret obtained in 3, setup an environment variable + ``` + WEBHOOK_SECRET='whsec......' + ``` + 5. Deploy 2.8.2: 2019-12-24 * Bugfix: [dcl calculator plugin] Set the POST action url explicitly 2.8.1: 2019-12-24 From 2acb1fb4180dfb1885b58ae5b50863ccac4110a1 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 1 Jan 2020 00:37:36 +0530 Subject: [PATCH 135/433] Remove duplicate --- Changelog | 2 -- 1 file changed, 2 deletions(-) diff --git a/Changelog b/Changelog index fb3e3dfe..5950118f 100644 --- a/Changelog +++ b/Changelog @@ -1,8 +1,6 @@ 2.9: 2019-12-31 * Feature: Enable saving user's VAT Number and validate it (MR!725) Notes for deployment: - Notes for deployment: - 1. Migrate db for utils app ./manage.py migrate utils From 251676ead8f481a275487434fe30d2cdbc355575 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 1 Jan 2020 00:52:07 +0530 Subject: [PATCH 136/433] Fix getting StripeCustomer from stripe_id --- webhook/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webhook/views.py b/webhook/views.py index 090ac5ae..fb6b9b1f 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -74,7 +74,7 @@ def handle_webhook(request): tax_id_obj.verification.status)) stripe_customer = None try: - stripe_customer = StripeCustomer.objects.get(tax_id_obj.customer) + stripe_customer = StripeCustomer.objects.get(stripe_id=tax_id_obj.customer) except StripeCustomer.DoesNotExist as dne: logger.debug( "StripeCustomer %s does not exist" % tax_id_obj.customer) From 0d27bac3a80033b606106cdd28b050e0eaaa5dc4 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 1 Jan 2020 00:54:03 +0530 Subject: [PATCH 137/433] Update Changelog for 2.9.1 --- Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog b/Changelog index 5950118f..85b20b09 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,5 @@ +2.9.1: 2019-12-31 + * Bugfix: Error handling tax_id updated webhook 2.9: 2019-12-31 * Feature: Enable saving user's VAT Number and validate it (MR!725) Notes for deployment: From f4e84f62a466858bd0ab3fc382891e921dca32d0 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 1 Jan 2020 01:28:35 +0530 Subject: [PATCH 138/433] Save billing address only if billing_address exists --- hosting/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index 9d975df1..24da0ba9 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -663,7 +663,8 @@ class SettingsView(LoginRequiredMixin, FormView): extra_tags='error' ) billing_address = current_billing_address - billing_address.save() + if billing_address: + billing_address.save() email_data = { 'subject': "%s updated VAT number to %s but failed" % (request.user.email, vat_number), From 690952156d769f36ed4d3dd35bdc10092abe9235 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 1 Jan 2020 02:04:55 +0530 Subject: [PATCH 139/433] Convert to str before joining --- webhook/views.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/webhook/views.py b/webhook/views.py index fb6b9b1f..516d1afc 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -95,7 +95,11 @@ def handle_webhook(request): (tax_id_obj.value, stripe_customer.user.email if stripe_customer else "unknown"), 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, 'to': settings.DCL_ERROR_EMAILS_TO_LIST, - 'body': "The following objects were modified:\n".join(b_addresses).join(ub_addresses), + 'body': "The following objects were modified:\n".join( + '\n'.join([str(b_address) for b_address in b_addresses]) + ).join( + '\n'.join([str(ub_address) for ub_address in ub_addresses]) + ), } else: logger.debug("Tax_id %s is %s" % (tax_id_obj.id, From 5f81bc909177f18c5c9972512ba0a8a47a0e0ac3 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 2 Jan 2020 12:07:52 +0530 Subject: [PATCH 140/433] Improve admin email for VM terminate --- hosting/views.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/hosting/views.py b/hosting/views.py index 24da0ba9..bd72af94 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1712,6 +1712,7 @@ class VirtualMachineView(LoginRequiredMixin, View): # Cancel Stripe subscription stripe_utils = StripeUtils() hosting_order = None + stripe_subscription_obj = None try: hosting_order = HostingOrder.objects.get( vm_id=vm.id @@ -1802,6 +1803,19 @@ class VirtualMachineView(LoginRequiredMixin, View): admin_email_body["VM_created_at"] = (str(hosting_order.created_at) if hosting_order is not None else "unknown") + content = "" + total_amount = 0 + if stripe_subscription_obj: + for line_item in stripe_subscription_obj["items"]["data"]: + total_amount += (line_item["quantity"] * + line_item.plan["amount"]) + content += " %s => %s x %s => %s\n" % ( + line_item.plan["name"], line_item["quantity"], + line_item.plan["amount"]/100, + (line_item["quantity"] * line_item.plan["amount"])/100 + ) + admin_email_body["subscription_amount"] = total_amount/100 + admin_email_body["subscription_detail"] = content admin_msg_sub = "VM and Subscription for VM {} and user: {}".format( vm.id, owner.email From b4a3c5e277b21143b93ce6d82b6a6f6218e7e052 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 2 Jan 2020 12:17:57 +0530 Subject: [PATCH 141/433] Send VM deleted message to error mail list --- hosting/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index bd72af94..4c8ba5a7 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1824,7 +1824,7 @@ class VirtualMachineView(LoginRequiredMixin, View): 'subject': ("Deleted " if response['status'] else "ERROR deleting ") + admin_msg_sub, 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, - 'to': ['info@ungleich.ch'], + 'to': [settings.DCL_ERROR_EMAILS_TO_LIST], 'body': "\n".join( ["%s=%s" % (k, v) for (k, v) in admin_email_body.items()]), } From 7d7bd60a7feeb5c75c07f25b92a173270eadd624 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 2 Jan 2020 12:27:20 +0530 Subject: [PATCH 142/433] Error emails list is already a list --- hosting/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index 4c8ba5a7..7947953b 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1824,7 +1824,7 @@ class VirtualMachineView(LoginRequiredMixin, View): 'subject': ("Deleted " if response['status'] else "ERROR deleting ") + admin_msg_sub, 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, - 'to': [settings.DCL_ERROR_EMAILS_TO_LIST], + 'to': settings.DCL_ERROR_EMAILS_TO_LIST, 'body': "\n".join( ["%s=%s" % (k, v) for (k, v) in admin_email_body.items()]), } From ceb7f9b0e65d20d4d6bce50afe72c1474764b04c Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 2 Jan 2020 12:34:42 +0530 Subject: [PATCH 143/433] Revert back to email --- hosting/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index 7947953b..bd72af94 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1824,7 +1824,7 @@ class VirtualMachineView(LoginRequiredMixin, View): 'subject': ("Deleted " if response['status'] else "ERROR deleting ") + admin_msg_sub, 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, - 'to': settings.DCL_ERROR_EMAILS_TO_LIST, + 'to': ['info@ungleich.ch'], 'body': "\n".join( ["%s=%s" % (k, v) for (k, v) in admin_email_body.items()]), } From 235904d7845312570a6f2b3fd58b1f3d6bc8981e Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 2 Jan 2020 12:58:34 +0530 Subject: [PATCH 144/433] Update Changelog for 2.9.2 --- Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog b/Changelog index 85b20b09..27e9630a 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,5 @@ +2.9.2: 2020-01-02 + * Bugfix: Improve admin email for terminate vm (include subscription details and subscription amount) (MR!726) 2.9.1: 2019-12-31 * Bugfix: Error handling tax_id updated webhook 2.9: 2019-12-31 From 96e50ddc8a9e0c566866c85bb078af13aeb23be9 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 4 Jan 2020 22:47:22 +0530 Subject: [PATCH 145/433] Add vat rates for CY, IL and LI manually --- vat_rates.csv | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vat_rates.csv b/vat_rates.csv index 38655f38..17bdb997 100644 --- a/vat_rates.csv +++ b/vat_rates.csv @@ -320,3 +320,6 @@ IM",GBP,0.1,standard, 2019-12-17,,TK,EUR,0.18,standard,Turkey standard VAT (added manually) 2019-12-17,,IS,EUR,0.24,standard,Iceland standard VAT (added manually) 2019-12-17,,FX,EUR,0.20,standard,France metropolitan standard VAT (added manually) +2020-01-04,,CY,EUR,0.19,standard,Cyprus standard VAT (added manually) +2019-01-04,,IL,EUR,0.23,standard,Ireland standard VAT (added manually) +2019-01-04,,LI,EUR,0.077,standard,Liechtenstein standard VAT (added manually) From 5468d5436c00f2dfd71b63a6a8ee35ee62225f66 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 5 Jan 2020 09:57:54 +0530 Subject: [PATCH 146/433] Add StripeTaxRate model --- hosting/models.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/hosting/models.py b/hosting/models.py index 0e6caa50..67c55aa2 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -724,4 +724,13 @@ class VATRates(AssignPermissionsMixin, models.Model): currency_code = models.CharField(max_length=10) rate = models.FloatField() rate_type = models.TextField(blank=True, default='') - description = models.TextField(blank=True, default='') \ No newline at end of file + description = models.TextField(blank=True, default='') + + +class StripeTaxRate(AssignPermissionsMixin, models.Model): + tax_rate_id = models.CharField(max_length=100, unique=True) + jurisdiction = models.CharField(max_length=10) + inclusive = models.BooleanField(default=False) + display_name = models.CharField(max_length=100) + percentage = models.FloatField(default=0) + description = models.CharField(max_length=100) From 4b8b0b0540e02cf0fa10ce9a03050f10171ec2d7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 5 Jan 2020 09:59:23 +0530 Subject: [PATCH 147/433] Add stripe tax rate migration --- hosting/migrations/0059_stripetaxrate.py | 29 ++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 hosting/migrations/0059_stripetaxrate.py diff --git a/hosting/migrations/0059_stripetaxrate.py b/hosting/migrations/0059_stripetaxrate.py new file mode 100644 index 00000000..af024991 --- /dev/null +++ b/hosting/migrations/0059_stripetaxrate.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2020-01-05 04:29 +from __future__ import unicode_literals + +from django.db import migrations, models +import utils.mixins + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0058_genericproduct_product_subscription_interval'), + ] + + operations = [ + migrations.CreateModel( + name='StripeTaxRate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('tax_rate_id', models.CharField(max_length=100, unique=True)), + ('jurisdiction', models.CharField(max_length=10)), + ('inclusive', models.BooleanField(default=False)), + ('display_name', models.CharField(max_length=100)), + ('percentage', models.FloatField(default=0)), + ('description', models.CharField(max_length=100)), + ], + bases=(utils.mixins.AssignPermissionsMixin, models.Model), + ), + ] From f762cbe58cc5942d6cc409c932ed60eba48c8843 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 5 Jan 2020 10:00:57 +0530 Subject: [PATCH 148/433] Update Changelog --- Changelog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changelog b/Changelog index 27e9630a..020b3e80 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,6 @@ +Next: + * Feature: Add StripeTaxRate model to save tax rates created in Stripe + * Bugfix: Add vat rates for CY, IL and LI manually 2.9.2: 2020-01-02 * Bugfix: Improve admin email for terminate vm (include subscription details and subscription amount) (MR!726) 2.9.1: 2019-12-31 From 5fe1c21b57f6904d131db2015eb3311abd2eb5c6 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 5 Jan 2020 12:56:17 +0530 Subject: [PATCH 149/433] Update Changelog for 2.9.3 --- Changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changelog b/Changelog index 020b3e80..1cd2eb05 100644 --- a/Changelog +++ b/Changelog @@ -1,4 +1,4 @@ -Next: +2.9.3: 2020-01-05 * Feature: Add StripeTaxRate model to save tax rates created in Stripe * Bugfix: Add vat rates for CY, IL and LI manually 2.9.2: 2020-01-02 From 9212c02cd7b3b7a8aa37591dedfd4f97a41afb2e Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 10 Jan 2020 19:47:49 +0530 Subject: [PATCH 150/433] Check vat_validation_status exists in dict before using it --- datacenterlight/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index c6929fd8..d2581fd9 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -617,7 +617,8 @@ class OrderConfirmationView(DetailView, FormView): if ('generic_payment_type' in request.session and self.request.session['generic_payment_type'] == 'generic'): - if (request.session["vat_validation_status"] == "verified" or + if "vat_validation_status" in request.session and ( + request.session["vat_validation_status"] == "verified" or request.session["vat_validation_status"] == "not_needed"): request.session['generic_payment_details']['vat_rate'] = 0 request.session['generic_payment_details']['vat_amount'] = 0 From fd4f61bc5c5fa4af97fe9477e250039e074b20c4 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 10 Jan 2020 20:16:16 +0530 Subject: [PATCH 151/433] Update Changelog for 2.9.4 --- Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog b/Changelog index 1cd2eb05..5c3b0551 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,5 @@ +2.9.4: 2020-01-10 + * Bugfix: Buying VPN generic item caused 500 error 2.9.3: 2020-01-05 * Feature: Add StripeTaxRate model to save tax rates created in Stripe * Bugfix: Add vat rates for CY, IL and LI manually From a00a9f6ff054f0e6f31070cdf9b5ff54e4f342e1 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 20 Jan 2020 12:07:32 +0530 Subject: [PATCH 152/433] Show invoices directly from stripe --- datacenterlight/templatetags/custom_tags.py | 62 ++++++++++++++- hosting/templates/hosting/dashboard.html | 2 +- hosting/templates/hosting/invoices.html | 60 ++++++++------- hosting/views.py | 83 ++++++++------------- utils/hosting_utils.py | 9 +++ 5 files changed, 139 insertions(+), 77 deletions(-) diff --git a/datacenterlight/templatetags/custom_tags.py b/datacenterlight/templatetags/custom_tags.py index a2b20bcb..4cc50caa 100644 --- a/datacenterlight/templatetags/custom_tags.py +++ b/datacenterlight/templatetags/custom_tags.py @@ -1,6 +1,11 @@ +import datetime + from django import template 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() @@ -52,3 +57,58 @@ def escaped_line_break(value): :return: """ 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(""" + {vm_id} + {ip_addresses} + {period} + {total} + + {see_invoice_text} + + """.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 "" diff --git a/hosting/templates/hosting/dashboard.html b/hosting/templates/hosting/dashboard.html index bda6eb11..212c5885 100644 --- a/hosting/templates/hosting/dashboard.html +++ b/hosting/templates/hosting/dashboard.html @@ -29,7 +29,7 @@
- +

{% trans "My Bills" %}

diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index 5f7be4b4..f48802d1 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -26,36 +26,46 @@ - {% for invoice in invoices %} + {% for inv_data in invs %} - {{ invoice.order.vm_id }} - {{ ips|get_value_from_dict:invoice.invoice_number|join:"
" }} - {% with period|get_value_from_dict:invoice.invoice_number as period_to_show %} - {{ period_to_show.period_start | date:'Y-m-d' }} — {{ period_to_show.period_end | date:'Y-m-d' }} - {% endwith %} - {{ invoice.total_in_chf|floatformat:2|intcomma }} - -
{% trans 'See Invoice' %} - + {{ inv_data | get_line_item_from_stripe_invoice }} {% endfor %} - - {% if is_paginated %} - +{% if invs.has_other_pages %} +
    + {% if invs.has_previous %} + {% if user_email %} +
  • «
  • + {% else %} +
  • «
  • + {% endif %} + {% else %} +
  • «
  • {% endif %} + {% for i in invs.paginator.page_range %} + {% if invs.number == i %} +
  • {{ i }} (current)
  • + {% else %} + {% if user_email %} +
  • {{ i }}
  • + {% else %} +
  • {{ i }}
  • + {% endif %} + {% endif %} + {% endfor %} + {% if invs.has_next %} + {% if user_email %} +
  • »
  • + {% else %} +
  • »
  • + {% endif %} + {% else %} +
  • »
  • + {% endif %} +
+{% endif %} +
{% endblock %} diff --git a/hosting/views.py b/hosting/views.py index bd72af94..c5d505bf 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -3,6 +3,7 @@ import uuid from datetime import datetime from time import sleep +import stripe from django import forms from django.conf import settings 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.core.exceptions import ValidationError 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.http import ( Http404, HttpResponseRedirect, HttpResponse, JsonResponse @@ -40,7 +42,6 @@ from datacenterlight.models import VMTemplate, VMPricing from datacenterlight.utils import ( 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 membership.models import CustomUser, StripeCustomer 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_vat_rate_for_country ) +from utils.ldap_manager import LdapManager from utils.mailer import BaseEmail from utils.stripe_utils import StripeUtils from utils.tasks import send_plain_email_task -from utils.ldap_manager import LdapManager - from utils.views import ( PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin, ResendActivationLinkViewMixin @@ -73,7 +73,7 @@ from .forms import ( from .mixins import ProcessVMSelectionMixin, HostingContextMixin from .models import ( HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail, - GenericProduct, MonthlyHostingBill, HostingBillLineItem + GenericProduct, MonthlyHostingBill ) logger = logging.getLogger(__name__) @@ -1240,7 +1240,7 @@ class OrdersHostingListView(LoginRequiredMixin, ListView): return super(OrdersHostingListView, self).get(request, *args, **kwargs) -class InvoiceListView(LoginRequiredMixin, ListView): +class InvoiceListView(LoginRequiredMixin, TemplateView): template_name = "hosting/invoices.html" login_url = reverse_lazy('hosting:login') context_object_name = "invoices" @@ -1248,10 +1248,13 @@ class InvoiceListView(LoginRequiredMixin, ListView): ordering = '-created' def get_context_data(self, **kwargs): + page = self.request.GET.get('page', 1) context = super(InvoiceListView, self).get_context_data(**kwargs) + invs_page = None if ('user_email' in self.request.GET and self.request.user.email == settings.ADMIN_EMAIL): user_email = self.request.GET['user_email'] + context['user_email'] = user_email logger.debug( "user_email = {}".format(user_email) ) @@ -1260,54 +1263,34 @@ class InvoiceListView(LoginRequiredMixin, ListView): except CustomUser.DoesNotExist as dne: logger.debug("User does not exist") cu = self.request.user - mhbs = MonthlyHostingBill.objects.filter(customer__user=cu) - else: - mhbs = MonthlyHostingBill.objects.filter( - customer__user=self.request.user - ) - ips_dict = {} - line_item_period_dict = {} - for mhb in mhbs: + invs = stripe.Invoice.list(customer=cu.stripecustomer.stripe_id, + count=100) + paginator = Paginator(invs.data, 10) try: - vm_detail = VMDetail.objects.get(vm_id=mhb.order.vm_id) - ips_dict[mhb.invoice_number] = [vm_detail.ipv6, vm_detail.ipv4] - all_line_items = HostingBillLineItem.objects.filter(monthly_hosting_bill=mhb) - for line_item in all_line_items: - if line_item.get_item_detail_str() != "": - line_item_period_dict[mhb.invoice_number] = { - "period_start": line_item.period_start, - "period_end": line_item.period_end - } - break - except VMDetail.DoesNotExist as dne: - ips_dict[mhb.invoice_number] = ['--'] - logger.debug("VMDetail for {} doesn't exist".format( - mhb.order.vm_id - )) - context['ips'] = ips_dict - context['period'] = line_item_period_dict + invs_page = paginator.page(page) + except PageNotAnInteger: + invs_page = paginator.page(1) + except EmptyPage: + invs_page = paginator.page(paginator.num_pages) + else: + try: + invs = stripe.Invoice.list( + customer=self.request.user.stripecustomer.stripe_id, + count=100 + ) + paginator = Paginator(invs.data, 10) + try: + invs_page = paginator.page(page) + except PageNotAnInteger: + invs_page = paginator.page(1) + 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 - 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) def get(self, request, *args, **kwargs): return super(InvoiceListView, self).get(request, *args, **kwargs) diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index 73e2c035..3674185c 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -199,6 +199,15 @@ def get_vat_rate_for_country(country): return 0 +def get_ip_addresses(vm_id): + try: + vm_detail = VMDetail.objects.get(vm_id=vm_id) + return "%s
%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: @staticmethod def clear_items_from_list(from_list, items_list): From 0b2a305f57ece9368eabfdc036ca0a438e68f280 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 20 Jan 2020 12:09:49 +0530 Subject: [PATCH 153/433] Fix pep warning --- utils/hosting_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index 3674185c..f47905a5 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -208,6 +208,7 @@ def get_ip_addresses(vm_id): logger.error("VMDetail for %s does not exist" % vm_id) return "--" + class HostingUtils: @staticmethod def clear_items_from_list(from_list, items_list): From a82c4d556c75097c1d1da1017660acff0d47a208 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 20 Jan 2020 13:25:49 +0530 Subject: [PATCH 154/433] Update Changelog for 2.9.5 --- Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog b/Changelog index 5c3b0551..566f5960 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,5 @@ +2.9.5: 2020-01-20 + * Feature: Show invoices directly from stripe (MR!727) 2.9.4: 2020-01-10 * Bugfix: Buying VPN generic item caused 500 error 2.9.3: 2020-01-05 From 399f9ed6c9c34b93f2af7feb44dbc995b448dec9 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 23 Jan 2020 16:37:35 +0530 Subject: [PATCH 155/433] Adjust hosting VM buy flow --- hosting/views.py | 38 ++++++++++++++++++++++++++++++++++---- utils/stripe_utils.py | 32 ++++++++++++++++++++++---------- 2 files changed, 56 insertions(+), 14 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index c5d505bf..43b6f5a7 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -42,7 +42,7 @@ from datacenterlight.models import VMTemplate, VMPricing from datacenterlight.utils import ( create_vm, get_cms_integration, check_otp, validate_vat_number ) -from hosting.models import UserCardDetail +from hosting.models import UserCardDetail, StripeTaxRate from membership.models import CustomUser, StripeCustomer from opennebula_api.models import OpenNebulaManager from opennebula_api.serializers import ( @@ -1135,7 +1135,8 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): cpu = specs.get('cpu') memory = specs.get('memory') disk_size = specs.get('disk_size') - amount_to_be_charged = specs.get('total_price') + amount_to_be_charged = specs.get('price') + discount = specs.get('discount') plan_name = StripeUtils.get_stripe_plan_name( cpu=cpu, memory=memory, @@ -1154,10 +1155,39 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): amount=amount_to_be_charged, name=plan_name, stripe_plan_id=stripe_plan_id) + # Create StripeTaxRate if applicable to the user + stripe_tax_rate = None + if specs["vat_percent"] > 0: + try: + stripe_tax_rate = StripeTaxRate.objects.get( + description="VAT for %s" % specs["vat_country"] + ) + print("Stripe Tax Rate exists") + except StripeTaxRate.DoesNotExist as dne: + print("StripeTaxRate does not exist") + tax_rate_obj = stripe.TaxRate.create( + display_name="VAT", + description="VAT for %s" % specs["vat_country"], + jurisdiction=specs["vat_country"], + percentage=specs["vat_percent"] * 100, + inclusive=False, + ) + stripe_tax_rate = StripeTaxRate.objects.create( + display_name=tax_rate_obj.display_name, + description=tax_rate_obj.description, + jurisdiction=tax_rate_obj.jurisdiction, + percentage=tax_rate_obj.percentage, + inclusive=False, + tax_rate_id=tax_rate_obj.id + ) + logger.debug("Created StripeTaxRate %s" % + stripe_tax_rate.tax_rate_id) subscription_result = stripe_utils.subscribe_customer_to_plan( stripe_api_cus_id, - [{"plan": stripe_plan.get( - 'response_object').stripe_plan_id}]) + [{"plan": stripe_plan.get('response_object').stripe_plan_id}], + coupon='ipv6-discount-8chf' if 'name' in discount and 'ipv6' in discount['name'].lower() else "", + tax_rate=[stripe_tax_rate.tax_rate_id] if stripe_tax_rate else [], + ) stripe_subscription_obj = subscription_result.get('response_object') # Check if the subscription was approved and is active if (stripe_subscription_obj is None or diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index de16fe4b..45904f14 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -297,7 +297,8 @@ class StripeUtils(object): return return_value @handleStripeError - def subscribe_customer_to_plan(self, customer, plans, trial_end=None): + def subscribe_customer_to_plan(self, customer, plans, trial_end=None, + coupon="", tax_rates=list()): """ Subscribes the given customer to the list of given plans @@ -316,7 +317,9 @@ class StripeUtils(object): """ subscription_result = self.stripe.Subscription.create( - customer=customer, items=plans, trial_end=trial_end + customer=customer, items=plans, trial_end=trial_end, + coupon=coupon, + default_tax_rates=tax_rates, ) return subscription_result @@ -410,18 +413,27 @@ class StripeUtils(object): @staticmethod - def get_stripe_plan_name(cpu, memory, disk_size, price): + def get_stripe_plan_name(cpu, memory, disk_size, price, excl_vat=True): """ Returns the Stripe plan name :return: """ - return "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD, " \ - "{price} CHF".format( - cpu=cpu, - memory=memory, - disk_size=disk_size, - price=round(price, 2) - ) + if excl_vat: + return "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD, " \ + "{price} CHF Excl. VAT".format( + cpu=cpu, + memory=memory, + disk_size=disk_size, + price=round(price, 2) + ) + else: + return "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD, " \ + "{price} CHF".format( + cpu=cpu, + memory=memory, + disk_size=disk_size, + price=round(price, 2) + ) @handleStripeError def set_subscription_meta_data(self, subscription_id, meta_data): From ec00785068dc38f9b946cc0e1f371b3f298a96b5 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 24 Jan 2020 14:10:56 +0530 Subject: [PATCH 156/433] Update get_stripe_plan_id to make it compatible with excl_vat --- utils/stripe_utils.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index 45904f14..ade06dd3 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -1,7 +1,9 @@ import logging import re + import stripe from django.conf import settings + from datacenterlight.models import StripePlan stripe.api_key = settings.STRIPE_API_PRIVATE_KEY @@ -351,7 +353,7 @@ class StripeUtils(object): @staticmethod def get_stripe_plan_id(cpu, ram, ssd, version, app='dcl', hdd=None, - price=None): + price=None, excl_vat=True): """ Returns the Stripe plan id string of the form `dcl-v1-cpu-2-ram-5gb-ssd-10gb` based on the input parameters @@ -378,13 +380,16 @@ class StripeUtils(object): plan=dcl_plan_string ) if price is not None: - stripe_plan_id_string_with_price = '{}-{}chf'.format( + stripe_plan_id_string = '{}-{}chf'.format( stripe_plan_id_string, round(price, 2) ) - return stripe_plan_id_string_with_price - else: - return stripe_plan_id_string + if excl_vat: + stripe_plan_id_string = '{}-{}'.format( + stripe_plan_id_string, + "excl_vat" + ) + return stripe_plan_id_string @staticmethod def get_vm_config_from_stripe_id(stripe_id): From e01b27835ea64f2001834fde56c46ba8726d065d Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 24 Jan 2020 14:11:55 +0530 Subject: [PATCH 157/433] Make subscription exclusive of VAT + Add VAT separately to subscription --- datacenterlight/views.py | 58 +++++++++++++++++++++++++++++++++++----- hosting/views.py | 2 +- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index d2581fd9..92c65681 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1,5 +1,6 @@ import logging +import stripe from django import forms from django.conf import settings from django.contrib import messages @@ -17,8 +18,8 @@ from hosting.forms import ( UserHostingKeyForm ) from hosting.models import ( - HostingBill, HostingOrder, UserCardDetail, GenericProduct, UserHostingKey -) + HostingBill, HostingOrder, UserCardDetail, GenericProduct, UserHostingKey, + StripeTaxRate) from membership.models import CustomUser, StripeCustomer from opennebula_api.models import OpenNebulaManager from opennebula_api.serializers import VMTemplateSerializer @@ -840,9 +841,15 @@ class OrderConfirmationView(DetailView, FormView): (request.session['generic_payment_details']['recurring'])): recurring_interval = 'month' if 'generic_payment_details' in request.session: + vat_percent = request.session['generic_payment_details']['vat_rate'] + vat_country = request.session['generic_payment_details']['vat_country'] + if 'discount' in request.session['generic_payment_details']: + discount = request.session['generic_payment_details']['discount'] + else: + discount = {'name': '', 'amount': 0, 'coupon_id': ''} amount_to_be_charged = ( round( - request.session['generic_payment_details']['amount'], + request.session['generic_payment_details']['amount_before_vat'], 2 ) ) @@ -863,7 +870,10 @@ class OrderConfirmationView(DetailView, FormView): cpu = specs.get('cpu') memory = specs.get('memory') disk_size = specs.get('disk_size') - amount_to_be_charged = specs.get('total_price') + amount_to_be_charged = specs.get('price') + vat_percent = specs.get('vat_percent') + vat_country = specs.get('vat_country') + discount = specs.get('discount') plan_name = StripeUtils.get_stripe_plan_name( cpu=cpu, memory=memory, @@ -884,10 +894,46 @@ class OrderConfirmationView(DetailView, FormView): stripe_plan_id=stripe_plan_id, interval=recurring_interval ) + # Create StripeTaxRate if applicable to the user + logger.debug("vat_percent = %s, vat_country = %s" % + (vat_percent, vat_country) + ) + stripe_tax_rate = None + if vat_percent > 0: + try: + stripe_tax_rate = StripeTaxRate.objects.get( + description="VAT for %s" % vat_country + ) + print("Stripe Tax Rate exists") + except StripeTaxRate.DoesNotExist as dne: + print("StripeTaxRate does not exist") + tax_rate_obj = stripe.TaxRate.create( + display_name="VAT", + description="VAT for %s" % vat_country, + jurisdiction=vat_country, + percentage=vat_percent, + inclusive=False, + ) + stripe_tax_rate = StripeTaxRate.objects.create( + display_name=tax_rate_obj.display_name, + description=tax_rate_obj.description, + jurisdiction=tax_rate_obj.jurisdiction, + percentage=tax_rate_obj.percentage, + inclusive=False, + tax_rate_id=tax_rate_obj.id + ) + logger.debug("Created StripeTaxRate %s" % + stripe_tax_rate.tax_rate_id) subscription_result = stripe_utils.subscribe_customer_to_plan( stripe_api_cus_id, - [{"plan": stripe_plan.get( - 'response_object').stripe_plan_id}]) + [{"plan": stripe_plan.get('response_object').stripe_plan_id}], + coupon='ipv6-discount-8chf' if ( + 'name' in discount and + discount['name'] is not None and + 'ipv6' in discount['name'].lower() + ) else "", + tax_rates=[stripe_tax_rate.tax_rate_id] if stripe_tax_rate else [], + ) stripe_subscription_obj = subscription_result.get('response_object') # Check if the subscription was approved and is active if (stripe_subscription_obj is None diff --git a/hosting/views.py b/hosting/views.py index 43b6f5a7..672868d8 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1186,7 +1186,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): stripe_api_cus_id, [{"plan": stripe_plan.get('response_object').stripe_plan_id}], coupon='ipv6-discount-8chf' if 'name' in discount and 'ipv6' in discount['name'].lower() else "", - tax_rate=[stripe_tax_rate.tax_rate_id] if stripe_tax_rate else [], + tax_rates=[stripe_tax_rate.tax_rate_id] if stripe_tax_rate else [], ) stripe_subscription_obj = subscription_result.get('response_object') # Check if the subscription was approved and is active From 38550ea75c5de1b3de12a6979813793a1900bedb Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 24 Jan 2020 15:00:16 +0530 Subject: [PATCH 158/433] Rearrange the way we show VAT and discount calculations --- .../datacenterlight/order_detail.html | 20 +++++++++++-------- utils/hosting_utils.py | 8 +++++--- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index db3f73a8..80e35914 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -126,21 +126,25 @@
{% if vm.vat > 0 %}

- {% trans "Subtotal" %} + {% trans "Price" %} {{vm.price|floatformat:2|intcomma}} CHF

+ {% if vm.discount.amount > 0 %} +

+ {%trans "Discount" as discount_name %} + {{ vm.discount.name|default:discount_name }} + - {{ vm.discount.amount }} CHF +

+ {% endif %} +

+ {% trans "Subtotal" %} + {{vm.price - vm.discount.amount|floatformat:2|intcomma}} CHF +

{% trans "VAT for" %} {{vm.vat_country}} ({{vm.vat_percent}}%) : {{vm.vat|floatformat:2|intcomma}} CHF

{% endif %} - {% if vm.discount.amount > 0 %} -

- {%trans "Discount" as discount_name %} - {{ vm.discount.name|default:discount_name }} - - {{ vm.discount.amount }} CHF -

- {% endif %}
diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index f47905a5..2705143c 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -104,15 +104,17 @@ def get_vm_price_for_given_vat(cpu, memory, ssd_size, hdd_size=0, (decimal.Decimal(hdd_size) * pricing.hdd_unit_price) ) - vat = price * decimal.Decimal(vat_rate) * decimal.Decimal(0.01) + discount_name = pricing.discount_name + discount_amount = round(float(pricing.discount_amount), 2) + vat = (price - discount_amount) * decimal.Decimal(vat_rate) * decimal.Decimal(0.01) vat_percent = vat_rate cents = decimal.Decimal('.01') price = price.quantize(cents, decimal.ROUND_HALF_UP) vat = vat.quantize(cents, decimal.ROUND_HALF_UP) discount = { - 'name': pricing.discount_name, - 'amount': round(float(pricing.discount_amount), 2) + 'name': discount_name, + 'amount': discount_amount } return (round(float(price), 2), round(float(vat), 2), round(float(vat_percent), 2), discount) From 112f3e17a9ce7d2f998546e7690261c2a000225b Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 24 Jan 2020 15:03:07 +0530 Subject: [PATCH 159/433] Convert to decimal for type consistency --- utils/hosting_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index 2705143c..3312474d 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -106,7 +106,7 @@ def get_vm_price_for_given_vat(cpu, memory, ssd_size, hdd_size=0, discount_name = pricing.discount_name discount_amount = round(float(pricing.discount_amount), 2) - vat = (price - discount_amount) * decimal.Decimal(vat_rate) * decimal.Decimal(0.01) + vat = (price - decimal.Decimal(discount_amount)) * decimal.Decimal(vat_rate) * decimal.Decimal(0.01) vat_percent = vat_rate cents = decimal.Decimal('.01') From 156930ab26418f260967cf5cf60f250cb27f81ba Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 24 Jan 2020 15:24:46 +0530 Subject: [PATCH 160/433] Add price_after_discount explicitly for showing in template --- datacenterlight/templates/datacenterlight/order_detail.html | 2 +- datacenterlight/views.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 80e35914..c2b97556 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -138,7 +138,7 @@ {% endif %}

{% trans "Subtotal" %} - {{vm.price - vm.discount.amount|floatformat:2|intcomma}} CHF + {{vm.price_after_discount}} CHF

{% trans "VAT for" %} {{vm.vat_country}} ({{vm.vat_percent}}%) : diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 92c65681..8c1fe504 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -642,6 +642,7 @@ class OrderConfirmationView(DetailView, FormView): vat_rate=user_country_vat_rate * 100 ) vm_specs["price"] = price + vm_specs["price_after_discount"] = price - discount vat_number = request.session.get('billing_address_data').get("vat_number") billing_address = BillingAddress.objects.get(id=request.session["billing_address_id"]) From d1fd57b7308b60ab8e82ba56bfcd44823ce0d724 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 24 Jan 2020 15:26:41 +0530 Subject: [PATCH 161/433] Correct the way we get amount from discount --- datacenterlight/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 8c1fe504..b2dec576 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -642,7 +642,7 @@ class OrderConfirmationView(DetailView, FormView): vat_rate=user_country_vat_rate * 100 ) vm_specs["price"] = price - vm_specs["price_after_discount"] = price - discount + vm_specs["price_after_discount"] = price - discount["amount"] vat_number = request.session.get('billing_address_data').get("vat_number") billing_address = BillingAddress.objects.get(id=request.session["billing_address_id"]) From cde6c51d4b22a3b8ada0a4362c8d17daff0847d7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 26 Jan 2020 10:06:31 +0530 Subject: [PATCH 162/433] Reset vat_validation_status when on payment page --- datacenterlight/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index b2dec576..0d62cf1e 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -309,6 +309,7 @@ class PaymentOrderView(FormView): @cache_control(no_cache=True, must_revalidate=True, no_store=True) def get(self, request, *args, **kwargs): + request.session.pop('vat_validation_status') if (('type' in request.GET and request.GET['type'] == 'generic') or 'product_slug' in kwargs): request.session['generic_payment_type'] = 'generic' From 24740438f7f05eff23561454928a3eb06faa3044 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 29 Jan 2020 16:02:26 +0530 Subject: [PATCH 163/433] get_vm_price_for_given_vat: Also return discount amount with vat --- utils/hosting_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index 3312474d..61f849ad 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -114,7 +114,8 @@ def get_vm_price_for_given_vat(cpu, memory, ssd_size, hdd_size=0, vat = vat.quantize(cents, decimal.ROUND_HALF_UP) discount = { 'name': discount_name, - 'amount': discount_amount + 'amount': discount_amount, + 'amount_with_vat': round(discount_amount * vat_rate, 2) } return (round(float(price), 2), round(float(vat), 2), round(float(vat_percent), 2), discount) From 970834cc38e77a2d3e3a1c9275e5cb677a157fc3 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 29 Jan 2020 16:03:13 +0530 Subject: [PATCH 164/433] Add parameters needed for displaying prices after discount, and vat --- datacenterlight/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 0d62cf1e..f1911cbf 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -681,6 +681,9 @@ class OrderConfirmationView(DetailView, FormView): 2) vm_specs["vat_country"] = user_vat_country vm_specs["discount"] = discount + vm_specs["price_with_vat"] = price + vat + vm_specs["price_after_discount"] = round(price - discount['amount'], 2) + vm_specs["price_after_discount_with_vat"] = round((price - discount['amount']) + vm_specs["vat"], 2) request.session['specs'] = vm_specs context.update({ From b8eca59e0dbfdae39daf2b6f0420735eda832116 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 29 Jan 2020 16:04:03 +0530 Subject: [PATCH 165/433] Fix design for showing prices excl and incl vat and discount --- .../datacenterlight/order_detail.html | 127 +++++++++++++----- 1 file changed, 94 insertions(+), 33 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index c2b97556..f62fd063 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -121,40 +121,101 @@


- {% if vm.vat > 0 or vm.discount.amount > 0 %} -
-
- {% if vm.vat > 0 %} -

- {% trans "Price" %} - {{vm.price|floatformat:2|intcomma}} CHF -

- {% if vm.discount.amount > 0 %} -

- {%trans "Discount" as discount_name %} - {{ vm.discount.name|default:discount_name }} - - {{ vm.discount.amount }} CHF -

- {% endif %} -

- {% trans "Subtotal" %} - {{vm.price_after_discount}} CHF -

-

- {% trans "VAT for" %} {{vm.vat_country}} ({{vm.vat_percent}}%) : - {{vm.vat|floatformat:2|intcomma}} CHF -

- {% endif %} -
-
-
-
-
- {% endif %}
-

- {% trans "Total" %} - {{vm.total_price|floatformat:2|intcomma}} CHF +

+ {% trans "Price Before VAT" %} + {{vm.price|intcomma}} CHF +

+
+
+
+
+
+ +
+
+

+
+
+

Pre VAT

+
+
+

VAT for {{vm.vat_country}} ({{vm.vat_percent}}%)

+
+
+
+
+

Price

+
+
+

{{vm.price|intcomma}} CHF

+
+
+

{{vm.price_with_vat|intcomma}} CHF

+
+
+ {% if vm.discount.amount > 0 %} +
+
+

{{vm.discount.name}}

+
+
+

-{{vm.discount.amount|intcomma}} CHF

+
+
+

-{{vm.discount.amount_with_vat|intcomma}} CHF

+
+
+ {% endif %} +
+
+
+
+
+
+
+

Total

+
+
+

{{vm.price_after_discount|intcomma}} CHF

+
+
+

{{vm.price_after_discount_with_vat|intcomma}} CHF

+
+
+
+
+
+
+
+

+ {% trans "Your Price in Total" %} + {{vm.total_price|floatformat:2|intcomma}} CHF

From 1f79ccd490ac9734d7f40508ec88da2effb545a9 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 29 Jan 2020 16:15:28 +0530 Subject: [PATCH 166/433] Convert discount amount into CHF --- utils/hosting_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index 61f849ad..85829d05 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -115,7 +115,7 @@ def get_vm_price_for_given_vat(cpu, memory, ssd_size, hdd_size=0, discount = { 'name': discount_name, 'amount': discount_amount, - 'amount_with_vat': round(discount_amount * vat_rate, 2) + 'amount_with_vat': round(discount_amount * vat_rate * 0.01, 2) } return (round(float(price), 2), round(float(vat), 2), round(float(vat_percent), 2), discount) From 48cc2b49394c97433ed2d435490b38edc908b1f2 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 29 Jan 2020 16:19:48 +0530 Subject: [PATCH 167/433] Fix discount amount calculation after VAT --- utils/hosting_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index 85829d05..e20e9b16 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -115,7 +115,7 @@ def get_vm_price_for_given_vat(cpu, memory, ssd_size, hdd_size=0, discount = { 'name': discount_name, 'amount': discount_amount, - 'amount_with_vat': round(discount_amount * vat_rate * 0.01, 2) + 'amount_with_vat': round(discount_amount * (1 + vat_rate * 0.01), 2) } return (round(float(price), 2), round(float(vat), 2), round(float(vat_percent), 2), discount) From 6132638faa2e438196ce12f20a6f2fbdfa563da2 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 29 Jan 2020 16:32:07 +0530 Subject: [PATCH 168/433] Use correct vat --- datacenterlight/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index f1911cbf..f8f7f928 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -681,7 +681,7 @@ class OrderConfirmationView(DetailView, FormView): 2) vm_specs["vat_country"] = user_vat_country vm_specs["discount"] = discount - vm_specs["price_with_vat"] = price + vat + vm_specs["price_with_vat"] = price + vm_specs["vat"] vm_specs["price_after_discount"] = round(price - discount['amount'], 2) vm_specs["price_after_discount_with_vat"] = round((price - discount['amount']) + vm_specs["vat"], 2) request.session['specs'] = vm_specs From a81fdc8ec1a344f92a665a795e5088237b9781f9 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 29 Jan 2020 16:45:07 +0530 Subject: [PATCH 169/433] Revert back to original vat calculation --- utils/hosting_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index e20e9b16..da2540d6 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -106,7 +106,7 @@ def get_vm_price_for_given_vat(cpu, memory, ssd_size, hdd_size=0, discount_name = pricing.discount_name discount_amount = round(float(pricing.discount_amount), 2) - vat = (price - decimal.Decimal(discount_amount)) * decimal.Decimal(vat_rate) * decimal.Decimal(0.01) + vat = price * decimal.Decimal(vat_rate) * decimal.Decimal(0.01) vat_percent = vat_rate cents = decimal.Decimal('.01') From ad606c2c554a854e1499b7ec86b04d851f956e91 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 29 Jan 2020 16:50:05 +0530 Subject: [PATCH 170/433] Correct calculation of total price --- datacenterlight/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index f8f7f928..6dec71d2 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -677,13 +677,13 @@ class OrderConfirmationView(DetailView, FormView): vm_specs["vat"] = vat vm_specs["vat_percent"] = vat_percent vm_specs["vat_validation_status"] = request.session["vat_validation_status"] if "vat_validation_status" in request.session else "" - vm_specs["total_price"] = round(price + vm_specs["vat"] - discount['amount'], + vm_specs["total_price"] = round(price + vm_specs["vat"] - discount['amount_with_vat'], 2) vm_specs["vat_country"] = user_vat_country vm_specs["discount"] = discount vm_specs["price_with_vat"] = price + vm_specs["vat"] vm_specs["price_after_discount"] = round(price - discount['amount'], 2) - vm_specs["price_after_discount_with_vat"] = round((price - discount['amount']) + vm_specs["vat"], 2) + vm_specs["price_after_discount_with_vat"] = round((price - discount['amount_with_vat']) + vm_specs["vat"], 2) request.session['specs'] = vm_specs context.update({ From 8ee4081f6011af7c4506c42ec7c3b5b41c611a1e Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 29 Jan 2020 16:58:47 +0530 Subject: [PATCH 171/433] Fix round up calculations --- utils/hosting_utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index da2540d6..5d898b0a 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -112,10 +112,12 @@ def get_vm_price_for_given_vat(cpu, memory, ssd_size, hdd_size=0, cents = decimal.Decimal('.01') price = price.quantize(cents, decimal.ROUND_HALF_UP) vat = vat.quantize(cents, decimal.ROUND_HALF_UP) + discount_amount_with_vat = decimal.Decimal(discount_amount) * (1 + decimal.Decimal(vat_rate) * decimal.Decimal(0.01)) + discount_amount_with_vat = discount_amount_with_vat.quantize(cents, decimal.ROUND_HALF_UP) discount = { 'name': discount_name, 'amount': discount_amount, - 'amount_with_vat': round(discount_amount * (1 + vat_rate * 0.01), 2) + 'amount_with_vat': round(float(discount_amount_with_vat), 2) } return (round(float(price), 2), round(float(vat), 2), round(float(vat_percent), 2), discount) From 4128aeb64da8a964e9d0794fc0b9d095740fdd00 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 29 Jan 2020 17:21:59 +0530 Subject: [PATCH 172/433] Add line height for final price --- datacenterlight/static/datacenterlight/css/hosting.css | 1 + 1 file changed, 1 insertion(+) diff --git a/datacenterlight/static/datacenterlight/css/hosting.css b/datacenterlight/static/datacenterlight/css/hosting.css index 0f16ab77..bcf266cc 100644 --- a/datacenterlight/static/datacenterlight/css/hosting.css +++ b/datacenterlight/static/datacenterlight/css/hosting.css @@ -532,6 +532,7 @@ .order-detail-container .total-price { font-size: 18px; + line-height: 20px; } @media (max-width: 767px) { From f546c5cb4f032b7c071b50aee328c6b224cd27b8 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 29 Jan 2020 17:38:44 +0530 Subject: [PATCH 173/433] Increase content space in order detail desktop view --- .../templates/datacenterlight/order_detail.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index f62fd063..fbe6ef16 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -104,7 +104,7 @@ {{ request.session.template.name }}

-
+

{% trans "Cores" %}: {{vm.cpu|floatformat}} @@ -121,7 +121,7 @@


-
+

{% trans "Price Before VAT" %} {{vm.price|intcomma}} CHF @@ -130,7 +130,7 @@


-
+
-
+

{% trans "Cores" %}: {{vm.cpu|floatformat}} @@ -121,7 +132,7 @@


-
+

{% trans "Price Before VAT" %} {{vm.price|intcomma}} CHF @@ -130,64 +141,38 @@


-
- +
-
+

-
-

Pre VAT

+
+

{% trans "Pre VAT" %}

-
-

VAT for {{vm.vat_country}} ({{vm.vat_percent}}%)

+
+

{% trans "VAT for" %} {{vm.vat_country}} ({{vm.vat_percent}}%)

-
+

Price

-
+

{{vm.price|intcomma}} CHF

-
+

{{vm.price_with_vat|intcomma}} CHF

{% if vm.discount.amount > 0 %}
-
+

{{vm.discount.name}}

-
+

-{{vm.discount.amount|intcomma}} CHF

-
+

-{{vm.discount.amount_with_vat|intcomma}} CHF

@@ -196,15 +181,15 @@

-
+
-
+

Total

-
+

{{vm.price_after_discount|intcomma}} CHF

-
+

{{vm.price_after_discount_with_vat|intcomma}} CHF

@@ -212,11 +197,9 @@

-
-

+

{% trans "Your Price in Total" %} {{vm.total_price|floatformat:2|intcomma}} CHF -

{% endif %} From 3141dc279326b38cd0025f080b95ec558ea3ebd8 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 30 Jan 2020 11:52:29 +0530 Subject: [PATCH 175/433] Round price_with_vat --- datacenterlight/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 6dec71d2..2f411194 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -681,7 +681,7 @@ class OrderConfirmationView(DetailView, FormView): 2) vm_specs["vat_country"] = user_vat_country vm_specs["discount"] = discount - vm_specs["price_with_vat"] = price + vm_specs["vat"] + vm_specs["price_with_vat"] = round(price + vm_specs["vat"], 2) vm_specs["price_after_discount"] = round(price - discount['amount'], 2) vm_specs["price_after_discount_with_vat"] = round((price - discount['amount_with_vat']) + vm_specs["vat"], 2) request.session['specs'] = vm_specs From 7a9b315e2ed0de42eac0f4dc1199e3ace02abe92 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 30 Jan 2020 12:33:47 +0530 Subject: [PATCH 176/433] Add media query for device width less than 368px --- .../templates/datacenterlight/order_detail.html | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 3236ffe0..525a8a95 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -113,6 +113,15 @@ } } + @media screen and (max-width:367px){ + .cmf-ord-heading { + font-size: 13px; + } + .order-detail-container .order-details { + font-size: 12px; + } + } +
From f4393426d376e73ab13bb3b0cb8602cf5ef731a8 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 08:53:24 +0530 Subject: [PATCH 177/433] Fix proper VAT values --- datacenterlight/views.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 2f411194..8da3e5c5 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -680,10 +680,11 @@ class OrderConfirmationView(DetailView, FormView): vm_specs["total_price"] = round(price + vm_specs["vat"] - discount['amount_with_vat'], 2) vm_specs["vat_country"] = user_vat_country - vm_specs["discount"] = discount - vm_specs["price_with_vat"] = round(price + vm_specs["vat"], 2) + vm_specs["price_with_vat"] = round(price * (1 + vm_specs["vat_percent"]), 2) vm_specs["price_after_discount"] = round(price - discount['amount'], 2) - vm_specs["price_after_discount_with_vat"] = round((price - discount['amount_with_vat']) + vm_specs["vat"], 2) + vm_specs["price_after_discount_with_vat"] = round((price - discount['amount']) * (1 + vm_specs["vat_percent"]), 2) + discount["amount_with_vat"] = vm_specs["price_with_vat"] - vm_specs["price_after_discount_with_vat"] + vm_specs["discount"] = discount request.session['specs'] = vm_specs context.update({ From b1acd3f25b716851ea4f0fd01a60af70f5756c9a Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 09:02:17 +0530 Subject: [PATCH 178/433] Convert VAT percent to CHF before calculations --- datacenterlight/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 8da3e5c5..943c3565 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -680,10 +680,10 @@ class OrderConfirmationView(DetailView, FormView): vm_specs["total_price"] = round(price + vm_specs["vat"] - discount['amount_with_vat'], 2) vm_specs["vat_country"] = user_vat_country - vm_specs["price_with_vat"] = round(price * (1 + vm_specs["vat_percent"]), 2) + vm_specs["price_with_vat"] = round(price * (1 + vm_specs["vat_percent"] * 0.01), 2) vm_specs["price_after_discount"] = round(price - discount['amount'], 2) - vm_specs["price_after_discount_with_vat"] = round((price - discount['amount']) * (1 + vm_specs["vat_percent"]), 2) - discount["amount_with_vat"] = vm_specs["price_with_vat"] - vm_specs["price_after_discount_with_vat"] + vm_specs["price_after_discount_with_vat"] = round((price - discount['amount']) * (1 + vm_specs["vat_percent"] * 0.01), 2) + discount["amount_with_vat"] = round(vm_specs["price_with_vat"] - vm_specs["price_after_discount_with_vat"], 2) vm_specs["discount"] = discount request.session['specs'] = vm_specs From 838163bd5953cdfde64f89bedd42db22bc5f9c56 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 09:15:12 +0530 Subject: [PATCH 179/433] Make total price consistent --- datacenterlight/views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 943c3565..37635904 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -677,13 +677,12 @@ class OrderConfirmationView(DetailView, FormView): vm_specs["vat"] = vat vm_specs["vat_percent"] = vat_percent vm_specs["vat_validation_status"] = request.session["vat_validation_status"] if "vat_validation_status" in request.session else "" - vm_specs["total_price"] = round(price + vm_specs["vat"] - discount['amount_with_vat'], - 2) vm_specs["vat_country"] = user_vat_country vm_specs["price_with_vat"] = round(price * (1 + vm_specs["vat_percent"] * 0.01), 2) vm_specs["price_after_discount"] = round(price - discount['amount'], 2) vm_specs["price_after_discount_with_vat"] = round((price - discount['amount']) * (1 + vm_specs["vat_percent"] * 0.01), 2) discount["amount_with_vat"] = round(vm_specs["price_with_vat"] - vm_specs["price_after_discount_with_vat"], 2) + vm_specs["total_price"] = round(vm_specs["price_after_discount_with_vat"]) vm_specs["discount"] = discount request.session['specs'] = vm_specs From e6f00abd71c59dc788c88adb5e17d181dd3a443c Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 09:17:24 +0530 Subject: [PATCH 180/433] Do not round --- datacenterlight/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 37635904..4307fcc3 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -682,7 +682,7 @@ class OrderConfirmationView(DetailView, FormView): vm_specs["price_after_discount"] = round(price - discount['amount'], 2) vm_specs["price_after_discount_with_vat"] = round((price - discount['amount']) * (1 + vm_specs["vat_percent"] * 0.01), 2) discount["amount_with_vat"] = round(vm_specs["price_with_vat"] - vm_specs["price_after_discount_with_vat"], 2) - vm_specs["total_price"] = round(vm_specs["price_after_discount_with_vat"]) + vm_specs["total_price"] = vm_specs["price_after_discount_with_vat"] vm_specs["discount"] = discount request.session['specs'] = vm_specs From 918d2b17e1d476f2311fc9d6f6af5960c2272f57 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 12:13:56 +0530 Subject: [PATCH 181/433] Change invoice url --- .../hosting/virtual_machine_detail.html | 2 +- hosting/views.py | 22 +++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/hosting/templates/hosting/virtual_machine_detail.html b/hosting/templates/hosting/virtual_machine_detail.html index 5873a2aa..36c04fed 100644 --- a/hosting/templates/hosting/virtual_machine_detail.html +++ b/hosting/templates/hosting/virtual_machine_detail.html @@ -46,7 +46,7 @@
{% trans "Current Pricing" %}
{{order.price|floatformat:2|intcomma}} CHF/{% if order.generic_product %}{% trans order.generic_product.product_subscription_interval %}{% else %}{% trans "Month" %}{% endif %}
- {% trans "See Invoice" %} + {% trans "See Invoice" %}
diff --git a/hosting/views.py b/hosting/views.py index 672868d8..b7bf07c8 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1687,13 +1687,27 @@ class VirtualMachineView(LoginRequiredMixin, View): context = None try: serializer = VirtualMachineSerializer(vm) + hosting_order = HostingOrder.objects.get( + vm_id=serializer.data['vm_id'] + ) + inv_url = None + if hosting_order.subscription_id: + stripe_obj = stripe.Invoice.list( + hosting_order.subscription_id + ) + inv_url = stripe_obj[0].data.hosted_invoice_url + elif hosting_order.stripe_charge_id: + stripe_obj = stripe.Charge.retrieve( + hosting_order.stripe_charge_id + ) + inv_url = stripe_obj.receipt_url context = { 'virtual_machine': serializer.data, - 'order': HostingOrder.objects.get( - vm_id=serializer.data['vm_id'] - ), - 'keys': UserHostingKey.objects.filter(user=request.user) + 'order': hosting_order, + 'keys': UserHostingKey.objects.filter(user=request.user), + 'inv_url': inv_url } + except Exception as ex: logger.debug("Exception generated {}".format(str(ex))) messages.error(self.request, From d8482c52f9d7d487ad1dcab7fa462686020db482 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 12:20:13 +0530 Subject: [PATCH 182/433] Get invoice correctly --- hosting/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index b7bf07c8..1f30ba11 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1693,7 +1693,7 @@ class VirtualMachineView(LoginRequiredMixin, View): inv_url = None if hosting_order.subscription_id: stripe_obj = stripe.Invoice.list( - hosting_order.subscription_id + subscription=hosting_order.subscription_id ) inv_url = stripe_obj[0].data.hosted_invoice_url elif hosting_order.stripe_charge_id: From 9d21181073748d60b1f85759ce44e03a07324215 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 12:23:47 +0530 Subject: [PATCH 183/433] Get stripe invoice obj correctly --- hosting/views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index 1f30ba11..09e1131a 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1693,9 +1693,10 @@ class VirtualMachineView(LoginRequiredMixin, View): inv_url = None if hosting_order.subscription_id: stripe_obj = stripe.Invoice.list( - subscription=hosting_order.subscription_id + subscription=hosting_order.subscription_id, + count=0 ) - inv_url = stripe_obj[0].data.hosted_invoice_url + inv_url = stripe_obj.data[0].hosted_invoice_url elif hosting_order.stripe_charge_id: stripe_obj = stripe.Charge.retrieve( hosting_order.stripe_charge_id From b645f9894bbc0b67ac800353ed5f266fd438412a Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 12:25:51 +0530 Subject: [PATCH 184/433] Open invoice in a new window --- hosting/templates/hosting/virtual_machine_detail.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/templates/hosting/virtual_machine_detail.html b/hosting/templates/hosting/virtual_machine_detail.html index 36c04fed..24b2c6ad 100644 --- a/hosting/templates/hosting/virtual_machine_detail.html +++ b/hosting/templates/hosting/virtual_machine_detail.html @@ -46,7 +46,7 @@
{% trans "Current Pricing" %}
{{order.price|floatformat:2|intcomma}} CHF/{% if order.generic_product %}{% trans order.generic_product.product_subscription_interval %}{% else %}{% trans "Month" %}{% endif %}
- {% trans "See Invoice" %} + {% trans "See Invoice" %}
From 23b25002aeb713604c8b47e9814e5a8282bdc8d9 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 13:00:04 +0530 Subject: [PATCH 185/433] Take users to invoice url instead of orders Orders need a VAT alignment fix --- datacenterlight/tasks.py | 3 +- .../datacenterlight/order_detail.html | 36 +++++++++---------- datacenterlight/views.py | 2 +- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index 8b4626e8..55be8099 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -173,8 +173,7 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): context = { 'base_url': "{0}://{1}".format(user.get('request_scheme'), user.get('request_host')), - 'order_url': reverse('hosting:orders', - kwargs={'pk': order_id}), + 'order_url': reverse('hosting:invoices'), 'page_header': _( 'Your New VM %(vm_name)s at Data Center Light') % { 'vm_name': vm.get('name')}, diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 525a8a95..6f9d43f2 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -103,26 +103,26 @@ {% trans "Product" %}:  {{ request.session.template.name }}

- +

diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 4307fcc3..385cf808 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1154,7 +1154,7 @@ class OrderConfirmationView(DetailView, FormView): response = { 'status': True, 'redirect': ( - reverse('hosting:orders') + reverse('hosting:invoices') if request.user.is_authenticated() else reverse('datacenterlight:index') ), From 2058c660c0ea364bfb329255bc2ac5601b81204a Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 13:15:03 +0530 Subject: [PATCH 186/433] Retrieve only one invoice --- hosting/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index 09e1131a..729d115b 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1694,7 +1694,7 @@ class VirtualMachineView(LoginRequiredMixin, View): if hosting_order.subscription_id: stripe_obj = stripe.Invoice.list( subscription=hosting_order.subscription_id, - count=0 + count=1 ) inv_url = stripe_obj.data[0].hosted_invoice_url elif hosting_order.stripe_charge_id: From c43afb7c590dccf0ce9e7b8b283d418f3869440b Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 13:47:29 +0530 Subject: [PATCH 187/433] Use round_up method --- datacenterlight/views.py | 10 +++++----- utils/hosting_utils.py | 6 ++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 385cf808..d6bfd27d 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -29,7 +29,7 @@ from utils.forms import ( ) from utils.hosting_utils import ( get_vm_price_with_vat, get_all_public_keys, get_vat_rate_for_country, - get_vm_price_for_given_vat + get_vm_price_for_given_vat, round_up ) from utils.stripe_utils import StripeUtils from utils.tasks import send_plain_email_task @@ -678,10 +678,10 @@ class OrderConfirmationView(DetailView, FormView): vm_specs["vat_percent"] = vat_percent vm_specs["vat_validation_status"] = request.session["vat_validation_status"] if "vat_validation_status" in request.session else "" vm_specs["vat_country"] = user_vat_country - vm_specs["price_with_vat"] = round(price * (1 + vm_specs["vat_percent"] * 0.01), 2) - vm_specs["price_after_discount"] = round(price - discount['amount'], 2) - vm_specs["price_after_discount_with_vat"] = round((price - discount['amount']) * (1 + vm_specs["vat_percent"] * 0.01), 2) - discount["amount_with_vat"] = round(vm_specs["price_with_vat"] - vm_specs["price_after_discount_with_vat"], 2) + vm_specs["price_with_vat"] = round_up(price * (1 + vm_specs["vat_percent"] * 0.01), 2) + vm_specs["price_after_discount"] = round_up(price - discount['amount'], 2) + vm_specs["price_after_discount_with_vat"] = round_up((price - discount['amount']) * (1 + vm_specs["vat_percent"] * 0.01), 2) + discount["amount_with_vat"] = round_up(vm_specs["price_with_vat"] - vm_specs["price_after_discount_with_vat"], 2) vm_specs["total_price"] = vm_specs["price_after_discount_with_vat"] vm_specs["discount"] = discount request.session['specs'] = vm_specs diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index 5d898b0a..7bff9a89 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -1,5 +1,6 @@ import decimal import logging +import math import subprocess from oca.pool import WrongIdError @@ -214,6 +215,11 @@ def get_ip_addresses(vm_id): return "--" +def round_up(n, decimals=0): + multiplier = 10 ** decimals + return math.ceil(n * multiplier) / multiplier + + class HostingUtils: @staticmethod def clear_items_from_list(from_list, items_list): From b103cff0a6742bea0a178914985ae7f29882e1db Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 13:54:29 +0530 Subject: [PATCH 188/433] Dont round_up discount (is obtained from subtraction) --- datacenterlight/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index d6bfd27d..185b3e29 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -681,9 +681,10 @@ class OrderConfirmationView(DetailView, FormView): vm_specs["price_with_vat"] = round_up(price * (1 + vm_specs["vat_percent"] * 0.01), 2) vm_specs["price_after_discount"] = round_up(price - discount['amount'], 2) vm_specs["price_after_discount_with_vat"] = round_up((price - discount['amount']) * (1 + vm_specs["vat_percent"] * 0.01), 2) - discount["amount_with_vat"] = round_up(vm_specs["price_with_vat"] - vm_specs["price_after_discount_with_vat"], 2) + discount["amount_with_vat"] = round(vm_specs["price_with_vat"] - vm_specs["price_after_discount_with_vat"], 2) vm_specs["total_price"] = vm_specs["price_after_discount_with_vat"] vm_specs["discount"] = discount + logger.debug(vm_specs) request.session['specs'] = vm_specs context.update({ From 8fe689d9933ae6c24c29df12b9b52b284ada2db0 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 15:23:26 +0530 Subject: [PATCH 189/433] Update some DE translations --- .../locale/de/LC_MESSAGES/django.po | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po index ebb78a1c..5ba12558 100644 --- a/datacenterlight/locale/de/LC_MESSAGES/django.po +++ b/datacenterlight/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-12-09 12:13+0000\n" +"POT-Creation-Date: 2020-02-01 09:42+0000\n" "PO-Revision-Date: 2018-03-30 23:22+0000\n" "Last-Translator: b'Anonymous User '\n" "Language-Team: LANGUAGE \n" @@ -144,7 +144,6 @@ msgid "" "the heart of Switzerland." msgstr "Bei uns findest Du die günstiges VMs aus der Schweiz." - msgid "Try now, order a VM. VM price starts from only 10.5 CHF per month." msgstr "Unser Angebot beginnt bei 10.5 CHF pro Monat. Probier's jetzt aus!" @@ -407,6 +406,17 @@ msgstr "Datum" msgid "Billed to" msgstr "Rechnungsadresse" +msgid "VAT Number" +msgstr "Mehrwertsteuernummer" + +msgid "Your VAT number has been verified" +msgstr "Deine Mehrwertsteuernummer wurde überprüft" + +msgid "" +"Your VAT number is under validation. VAT will be adjusted, once the " +"validation is complete." +msgstr "" + msgid "Payment method" msgstr "Bezahlmethode" @@ -437,8 +447,14 @@ msgstr "Beschreibung" msgid "Recurring" msgstr "Wiederholend" -msgid "Subtotal" -msgstr "Zwischensumme" +msgid "Price Before VAT" +msgstr "" + +msgid "Pre VAT" +msgstr "" + +msgid "Your Price in Total" +msgstr "" #, fuzzy, python-format #| msgid "" @@ -575,6 +591,9 @@ msgstr "Unser Angebot beginnt bei 15 CHF pro Monat. Probier's jetzt aus!" msgid "Actions speak louder than words. Let's do it, try our VM now." msgstr "Taten sagen mehr als Worte – Teste jetzt unsere VM!" +msgid "See Invoice" +msgstr "" + msgid "Invalid number of cores" msgstr "Ungültige Anzahle CPU-Kerne" @@ -665,6 +684,9 @@ msgstr "" "Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du " "auf sie zugreifen kannst." +#~ msgid "Subtotal" +#~ msgstr "Zwischensumme" + #~ msgid "VAT" #~ msgstr "Mehrwertsteuer" @@ -676,9 +698,6 @@ msgstr "" #~ "ausgelöst, nachdem Du die Bestellung auf der nächsten Seite bestätigt " #~ "hast." -#~ msgid "Card Number" -#~ msgstr "Kreditkartennummer" - #~ msgid "" #~ "You are not making any payment yet. After placing your order, you will be " #~ "taken to the Submit Payment Page." From 88a39ef85a6f9250900a28b0f1156a2fe51cc242 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 15:23:42 +0530 Subject: [PATCH 190/433] Update Changelog for 2.10 --- Changelog | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Changelog b/Changelog index 566f5960..2c2d73d7 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,8 @@ +2.10: 2020-02-01 + * Feature: Introduce new design to show VAT exclusive/VAT inclusive pricing together + * Feature: Separate VAT and discount in Stripe + * Feature: Show Stripe invoices until we have a better way of showing them elegantly + * Bugfix: Fix bug where VAT is set to 0 because user set a valid VAT number before but later chose not to use any VAT number 2.9.5: 2020-01-20 * Feature: Show invoices directly from stripe (MR!727) 2.9.4: 2020-01-10 From 44dee625b4f0f5099a7ffd8d0dc43520ffe4069c Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 1 Feb 2020 20:24:55 +0530 Subject: [PATCH 191/433] Add some DE translations --- datacenterlight/locale/de/LC_MESSAGES/django.po | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po index 5ba12558..2c9d0587 100644 --- a/datacenterlight/locale/de/LC_MESSAGES/django.po +++ b/datacenterlight/locale/de/LC_MESSAGES/django.po @@ -407,10 +407,10 @@ msgid "Billed to" msgstr "Rechnungsadresse" msgid "VAT Number" -msgstr "Mehrwertsteuernummer" +msgstr "MwSt-Nummer" msgid "Your VAT number has been verified" -msgstr "Deine Mehrwertsteuernummer wurde überprüft" +msgstr "Deine MwSt-Nummer wurde überprüft" msgid "" "Your VAT number is under validation. VAT will be adjusted, once the " @@ -451,7 +451,7 @@ msgid "Price Before VAT" msgstr "" msgid "Pre VAT" -msgstr "" +msgstr "Exkl. MwSt." msgid "Your Price in Total" msgstr "" From c765698a0f96618ee283fa309fa34b82f4e63d9e Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 2 Feb 2020 10:16:45 +0530 Subject: [PATCH 192/433] Add two columns for pricing --- .../datacenterlight/order_detail.html | 133 +++++++++++------- 1 file changed, 86 insertions(+), 47 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 6f9d43f2..80c5f459 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -58,52 +58,7 @@


{% trans "Order summary" %}

- {% if generic_payment_details %} -

- {% trans "Product" %}:  - {{ generic_payment_details.product_name }} -

-
-
- {% if generic_payment_details.vat_rate > 0 %} -

- {% trans "Price" %}: - CHF {{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} -

-

- {% trans "VAT for" %} {{generic_payment_details.vat_country}} ({{generic_payment_details.vat_rate}}%) : - CHF {{generic_payment_details.vat_amount|floatformat:2|intcomma}} -

-

- {% trans "Total Amount" %} : - CHF {{generic_payment_details.amount|floatformat:2|intcomma}} -

- {% else %} -

- {% trans "Amount" %}: - CHF {{generic_payment_details.amount|floatformat:2|intcomma}} -

- {% endif %} - {% if generic_payment_details.description %} -

- {% trans "Description" %}: - {{generic_payment_details.description}} -

- {% endif %} - {% if generic_payment_details.recurring %} -

- {% trans "Recurring" %}: - Yes -

- {% endif %} -
-
- {% else %} -

- {% trans "Product" %}:  - {{ request.session.template.name }} -

- + + {% if generic_payment_details %} +

+ {% trans "Product" %}:  + {{ generic_payment_details.product_name }} +

+ {% if generic_payment_details.description %} +

+ {% trans "Description" %}: + {{generic_payment_details.description}} +

+ {% endif %} + {% if generic_payment_details.recurring %} +

+ {% trans "Recurring" %}: + Yes +

+ {% endif %} +
+
+
+
+
+
+

+ {% trans "Price Before VAT" %} + {{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} CHF +

+
+
+
+
+
+
+
+

+
+
+

{% trans "Pre VAT" %}

+
+
+

{% trans "VAT for" %} {{generic_payment_details.vat_country}} ({{generic_payment_details.vat_percent}}%)

+
+
+
+
+

Price

+
+
+

{{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} CHF

+
+
+

{{generic_payment_details.amount|floatformat:2|intcomma}} CHF

+
+
+
+
+
+
+
+
+
+

Total

+
+
+

{{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} CHF

+
+
+

{{generic_payment_details.amount|floatformat:2|intcomma}} CHF

+
+
+
+
+
+
+
+ {% trans "Your Price in Total" %} + {{generic_payment_details.amount|floatformat:2|intcomma}} CHF +
+
+ {% else %} +

+ {% trans "Product" %}:  + {{ request.session.template.name }} +

From ffde015c3124eb87713ee61ba45644054fd7e14f Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 2 Feb 2020 10:26:13 +0530 Subject: [PATCH 193/433] Fix divs --- .../datacenterlight/order_detail.html | 113 +++++++++--------- 1 file changed, 55 insertions(+), 58 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 80c5f459..3e2ecf04 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -95,68 +95,65 @@ Yes

{% endif %} -
-
-
-
+
+
+
+
+

+ {% trans "Price Before VAT" %} + {{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} CHF +

+
+
+
+
+
+
+
+

+
+
+

{% trans "Pre VAT" %}

+
+
+

{% trans "VAT for" %} {{generic_payment_details.vat_country}} ({{generic_payment_details.vat_rate}}%)

+
-
-

- {% trans "Price Before VAT" %} - {{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} CHF -

+
+
+

Price

+
+
+

{{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} CHF

+
+
+

{{generic_payment_details.amount|floatformat:2|intcomma}} CHF

+
-
-
-
-
-
-
-

-
-
-

{% trans "Pre VAT" %}

-
-
-

{% trans "VAT for" %} {{generic_payment_details.vat_country}} ({{generic_payment_details.vat_percent}}%)

-
-
-
-
-

Price

-
-
-

{{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} CHF

-
-
-

{{generic_payment_details.amount|floatformat:2|intcomma}} CHF

-
-
-
-
-
-
-
-
-
-

Total

-
-
-

{{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} CHF

-
-
-

{{generic_payment_details.amount|floatformat:2|intcomma}} CHF

-
-
-
-
-
-
-
- {% trans "Your Price in Total" %} - {{generic_payment_details.amount|floatformat:2|intcomma}} CHF +
+
+
+
+
+
+
+

Total

+
+
+

{{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} CHF

+
+
+

{{generic_payment_details.amount|floatformat:2|intcomma}} CHF

+
+
+
+
+
+ {% trans "Your Price in Total" %} + {{generic_payment_details.amount|floatformat:2|intcomma}} CHF +
{% else %}

{% trans "Product" %}:  From 9f86f445695a5efc86048a41f37ccb5c60001b0a Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 2 Feb 2020 10:29:33 +0530 Subject: [PATCH 194/433] Add missing divs --- datacenterlight/templates/datacenterlight/order_detail.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 3e2ecf04..078f5efd 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -79,6 +79,8 @@ {% if generic_payment_details %} +

+

{% trans "Product" %}:  {{ generic_payment_details.product_name }} @@ -95,6 +97,7 @@ Yes

{% endif %} +

@@ -154,6 +157,7 @@ {% trans "Your Price in Total" %} {{generic_payment_details.amount|floatformat:2|intcomma}} CHF
+
{% else %}

{% trans "Product" %}:  From fc8c4579fb42197e29da7377b76f4a0b421f38c2 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 2 Feb 2020 10:41:51 +0530 Subject: [PATCH 195/433] Show prices to two decimals --- .../templates/datacenterlight/order_detail.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 078f5efd..b8cd7a4b 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -184,7 +184,7 @@

{% trans "Price Before VAT" %} - {{vm.price|intcomma}} CHF + {{vm.price|floatformat:2|intcomma}} CHF

@@ -207,10 +207,10 @@

Price

-

{{vm.price|intcomma}} CHF

+

{{vm.price|floatformat:2|intcomma}} CHF

-

{{vm.price_with_vat|intcomma}} CHF

+

{{vm.price_with_vat|floatformat:2|intcomma}} CHF

{% if vm.discount.amount > 0 %} @@ -219,10 +219,10 @@

{{vm.discount.name}}

-

-{{vm.discount.amount|intcomma}} CHF

+

-{{vm.discount.amount|floatformat:2|intcomma}} CHF

-

-{{vm.discount.amount_with_vat|intcomma}} CHF

+

-{{vm.discount.amount_with_vat|floatformat:2|intcomma}} CHF

{% endif %} @@ -236,10 +236,10 @@

Total

-

{{vm.price_after_discount|intcomma}} CHF

+

{{vm.price_after_discount|floatformat:2|intcomma}} CHF

-

{{vm.price_after_discount_with_vat|intcomma}} CHF

+

{{vm.price_after_discount_with_vat|floatformat:2|intcomma}} CHF

From c0683d9f538769791e3b2fd6623b9ca24a706a61 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 2 Feb 2020 10:57:14 +0530 Subject: [PATCH 196/433] Show prices to two decimal places --- datacenterlight/templatetags/custom_tags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/templatetags/custom_tags.py b/datacenterlight/templatetags/custom_tags.py index 4cc50caa..0574d29f 100644 --- a/datacenterlight/templatetags/custom_tags.py +++ b/datacenterlight/templatetags/custom_tags.py @@ -106,7 +106,7 @@ def get_line_item_from_stripe_invoice(invoice): 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, + total='%.2f' % (invoice.total/100), stripe_invoice_url=invoice.hosted_invoice_url, see_invoice_text=_("See Invoice") )) From e094930d6eec56e523e182e299746601d2e0f046 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 2 Feb 2020 12:39:19 +0530 Subject: [PATCH 197/433] Show product name in invoices list if product is not a VM --- datacenterlight/templatetags/custom_tags.py | 29 +++++++++++++++++++-- hosting/templates/hosting/invoices.html | 2 +- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/datacenterlight/templatetags/custom_tags.py b/datacenterlight/templatetags/custom_tags.py index 0574d29f..cb0fb6fa 100644 --- a/datacenterlight/templatetags/custom_tags.py +++ b/datacenterlight/templatetags/custom_tags.py @@ -1,12 +1,16 @@ import datetime +import logging from django import template from django.core.urlresolvers import resolve, reverse from django.utils.safestring import mark_safe from django.utils.translation import activate, get_language, ugettext_lazy as _ +from hosting.models import GenericProduct from utils.hosting_utils import get_ip_addresses +logger = logging.getLogger(__name__) + register = template.Library() @@ -72,8 +76,10 @@ def get_line_item_from_stripe_invoice(invoice): end_date = 0 is_first = True vm_id = -1 + plan_name = "" for line_data in invoice["lines"]["data"]: if is_first: + plan_name = line_data.plan.name start_date = line_data.period.start end_date = line_data.period.end is_first = False @@ -102,8 +108,9 @@ def get_line_item_from_stripe_invoice(invoice): """.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" % ( + ip_addresses=mark_safe(get_ip_addresses(vm_id)) if vm_id > 0 else + mark_safe(plan_name), + period=mark_safe("%s — %s" % ( datetime.datetime.fromtimestamp(start_date).strftime('%Y-%m-%d'), datetime.datetime.fromtimestamp(end_date).strftime('%Y-%m-%d'))), total='%.2f' % (invoice.total/100), @@ -112,3 +119,21 @@ def get_line_item_from_stripe_invoice(invoice): )) else: return "" + + +def get_product_name(plan_name): + product_name = "" + if plan_name and plan_name.startswith("generic-"): + product_id = plan_name[plan_name.index("-") + 1:plan_name.rindex("-")] + try: + product = GenericProduct.objects.get(id=product_id) + product_name = product.product_name + except GenericProduct.DoesNotExist as dne: + logger.error("Generic product id=%s does not exist" % product_id) + product_name = "Unknown" + except GenericProduct.MultipleObjectsReturned as mor: + logger.error("Multiple products with id=%s exist" % product_id) + product_name = "Unknown" + else: + logger.debug("Product name for plan %s does not exist" % plan_name) + return product_name diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index f48802d1..1a97fd1f 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -19,7 +19,7 @@ {% trans "VM ID" %} - {% trans "IP Address" %} + {% trans "IP Address" %}/{% trans "Product" %} {% trans "Period" %} {% trans "Amount" %} From 42a4a77c0278cf2c4336a605f0d9b1715d9ba304 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 2 Feb 2020 12:42:19 +0530 Subject: [PATCH 198/433] Show product name --- datacenterlight/templatetags/custom_tags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/templatetags/custom_tags.py b/datacenterlight/templatetags/custom_tags.py index cb0fb6fa..a2d9bec6 100644 --- a/datacenterlight/templatetags/custom_tags.py +++ b/datacenterlight/templatetags/custom_tags.py @@ -109,7 +109,7 @@ def get_line_item_from_stripe_invoice(invoice): """.format( vm_id=vm_id if vm_id > 0 else "", ip_addresses=mark_safe(get_ip_addresses(vm_id)) if vm_id > 0 else - mark_safe(plan_name), + mark_safe(get_product_name(plan_name)), period=mark_safe("%s — %s" % ( datetime.datetime.fromtimestamp(start_date).strftime('%Y-%m-%d'), datetime.datetime.fromtimestamp(end_date).strftime('%Y-%m-%d'))), From 70a3620598cf06f904c01276470096670b1c069f Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 2 Feb 2020 13:09:27 +0530 Subject: [PATCH 199/433] Fix getting product from plan_name --- datacenterlight/templatetags/custom_tags.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/datacenterlight/templatetags/custom_tags.py b/datacenterlight/templatetags/custom_tags.py index a2d9bec6..d86ba27d 100644 --- a/datacenterlight/templatetags/custom_tags.py +++ b/datacenterlight/templatetags/custom_tags.py @@ -124,13 +124,15 @@ def get_line_item_from_stripe_invoice(invoice): def get_product_name(plan_name): product_name = "" if plan_name and plan_name.startswith("generic-"): - product_id = plan_name[plan_name.index("-") + 1:plan_name.rindex("-")] + first_index_hyphen = plan_name.index("-") + 1 + product_id = plan_name[first_index_hyphen: + (plan_name[first_index_hyphen:].index("-")) + first_index_hyphen] try: product = GenericProduct.objects.get(id=product_id) product_name = product.product_name except GenericProduct.DoesNotExist as dne: logger.error("Generic product id=%s does not exist" % product_id) - product_name = "Unknown" + product_name = plan_name except GenericProduct.MultipleObjectsReturned as mor: logger.error("Multiple products with id=%s exist" % product_id) product_name = "Unknown" From a5d393ad20042770853dcb4c4765dac876b60a4b Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 2 Feb 2020 13:21:01 +0530 Subject: [PATCH 200/433] Right align prices --- datacenterlight/templatetags/custom_tags.py | 2 +- hosting/static/hosting/css/orders.css | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/datacenterlight/templatetags/custom_tags.py b/datacenterlight/templatetags/custom_tags.py index d86ba27d..0cb18e5b 100644 --- a/datacenterlight/templatetags/custom_tags.py +++ b/datacenterlight/templatetags/custom_tags.py @@ -102,7 +102,7 @@ def get_line_item_from_stripe_invoice(invoice): {vm_id} {ip_addresses} {period} - {total} + {total} {see_invoice_text} diff --git a/hosting/static/hosting/css/orders.css b/hosting/static/hosting/css/orders.css index 6819a94b..2deab999 100644 --- a/hosting/static/hosting/css/orders.css +++ b/hosting/static/hosting/css/orders.css @@ -2,4 +2,10 @@ .orders-container .table > tbody > tr > td { vertical-align: middle; +} + +@media screen and (min-width:767px){ + .dcl-text-right { + padding-right: 20px; + } } \ No newline at end of file From f0f8af23674f1be9b0bd353b507a7e33df249a78 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 2 Feb 2020 21:39:51 +0530 Subject: [PATCH 201/433] Change col-sm-8 to col-sm-9 to spread content further on desktop view --- .../datacenterlight/order_detail.html | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index b8cd7a4b..2897e3bf 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -80,7 +80,7 @@ {% if generic_payment_details %}
-
+

{% trans "Product" %}:  {{ generic_payment_details.product_name }} @@ -101,7 +101,7 @@


-
+

{% trans "Price Before VAT" %} {{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} CHF @@ -110,7 +110,7 @@


-
+

@@ -137,7 +137,7 @@

-
+

Total

@@ -153,7 +153,7 @@

-
+
{% trans "Your Price in Total" %} {{generic_payment_details.amount|floatformat:2|intcomma}} CHF
@@ -164,7 +164,7 @@ {{ request.session.template.name }}

-
+

{% trans "Cores" %}: {{vm.cpu|floatformat}} @@ -181,7 +181,7 @@


-
+

{% trans "Price Before VAT" %} {{vm.price|floatformat:2|intcomma}} CHF @@ -190,7 +190,7 @@


-
+

@@ -230,7 +230,7 @@

-
+

Total

@@ -246,7 +246,7 @@

-
+
{% trans "Your Price in Total" %} {{vm.total_price|floatformat:2|intcomma}} CHF
From 1630dc195b96f839d249f2b2d64a97786a20e7aa Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 2 Feb 2020 21:52:48 +0530 Subject: [PATCH 202/433] Reduce header font size --- datacenterlight/templates/datacenterlight/order_detail.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 2897e3bf..5060f1cc 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -61,7 +61,7 @@