From cec7938c9c545f685f0940698dc502280161e33a Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 1 Apr 2020 11:30:27 +0530 Subject: [PATCH 001/257] Add tabs for one time payments --- hosting/templates/hosting/invoices.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index 1a97fd1f..1c7e91b9 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -15,6 +15,11 @@
+ + From d0be07ecd577a01ff97a052305b0d08ed167778a Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 1 Apr 2020 17:07:10 +0530 Subject: [PATCH 002/257] Add custom_tag get_line_item_from_hosting_order_charge --- datacenterlight/templatetags/custom_tags.py | 31 ++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/datacenterlight/templatetags/custom_tags.py b/datacenterlight/templatetags/custom_tags.py index 0cb18e5b..7cc68f07 100644 --- a/datacenterlight/templatetags/custom_tags.py +++ b/datacenterlight/templatetags/custom_tags.py @@ -6,7 +6,7 @@ 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 hosting.models import GenericProduct, HostingOrder from utils.hosting_utils import get_ip_addresses logger = logging.getLogger(__name__) @@ -63,6 +63,35 @@ def escaped_line_break(value): return value.replace("\\n", "\n") +@register.filter('get_line_item_from_hosting_order_charge') +def get_line_item_from_hosting_order_charge(hosting_order_id, receipt_url): + """ + Returns ready-to-use "html" line item to be shown for a charge in the + invoice list page + + :param hosting_order_id: the HostingOrder id + :return: + """ + hosting_order = HostingOrder.objects.get(id = hosting_order_id) + if hosting_order.stripe_charge_id: + return mark_safe(""" + + + + + """.format( + product_name=hosting_order.generic_product.product_name.capitalize(), + created_at=hosting_order.created_at.strftime('%Y-%m-%d'), + total='%.2f' % (hosting_order.price), + receipt_url=receipt_url, + see_invoice_text=_("See Invoice") + )) + else: + return "" + + @register.filter('get_line_item_from_stripe_invoice') def get_line_item_from_stripe_invoice(invoice): """ From 70264d592df5196a8e4f5eedeaf97ecd79fb7e88 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 1 Apr 2020 17:07:44 +0530 Subject: [PATCH 003/257] Put contents for subscription in a div of its own --- hosting/templates/hosting/invoices.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index 1c7e91b9..5bfb4704 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -19,7 +19,7 @@
  • {% trans "Subscriptions" %}
  • {% trans "One-time payments" %}
  • - +
    {product_name}{created_at}{total} + {see_invoice_text} +
    @@ -71,6 +71,7 @@ {% endif %} {% endif %} + {% endblock %} From 27aa0ea5959f39bd430c51adf08806e78224bdfa Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 1 Apr 2020 17:08:18 +0530 Subject: [PATCH 004/257] Create another div for onetime charges --- hosting/templates/hosting/invoices.html | 52 +++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index 5bfb4704..3442ac62 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -73,5 +73,57 @@ {% endif %} +
    +
    + + + + + + + + + + {% for ho_id, stripe_charge_data in invs_charge %} + + {{ ho_id | get_line_item_from_hosting_order_charge: stripe_charge_data.receipt_url }} + + {% endfor %} + +
    {% trans "Product" %}{% trans "Date" %}{% trans "Amount" %}
    +{% if invs_charge.has_other_pages %} + +{% endif %} + {% endblock %} From 343a9440f0944237c462a2cd5be7835e4a92c6d5 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 1 Apr 2020 17:09:02 +0530 Subject: [PATCH 005/257] Load invs_charge context from user's charges --- hosting/views.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/hosting/views.py b/hosting/views.py index 1d16a750..29e9644a 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -13,6 +13,7 @@ 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.db.models import Q from django.http import ( Http404, HttpResponseRedirect, HttpResponse, JsonResponse ) @@ -1287,6 +1288,7 @@ class InvoiceListView(LoginRequiredMixin, TemplateView): page = self.request.GET.get('page', 1) context = super(InvoiceListView, self).get_context_data(**kwargs) invs_page = None + invs_page_charges = None if ('user_email' in self.request.GET and self.request.user.email == settings.ADMIN_EMAIL): user_email = self.request.GET['user_email'] @@ -1308,6 +1310,21 @@ class InvoiceListView(LoginRequiredMixin, TemplateView): invs_page = paginator.page(1) except EmptyPage: invs_page = paginator.page(paginator.num_pages) + hosting_orders = HostingOrder.objects.filter( + customer=cu.stripecustomer).filter( + Q(subscription_id=None) | Q(subscription_id='') + ) + stripe_chgs = [] + for ho in hosting_orders: + stripe_chgs.append({ho.id: stripe.Charge.retrieve(ho.stripe_charge_id)}) + + paginator_charges = Paginator(stripe_chgs, 10) + try: + invs_page_charges = paginator_charges.page(page) + except PageNotAnInteger: + invs_page_charges = paginator_charges.page(1) + except EmptyPage: + invs_page_charges = paginator_charges.page(paginator_charges.num_pages) else: try: invs = stripe.Invoice.list( @@ -1321,10 +1338,27 @@ class InvoiceListView(LoginRequiredMixin, TemplateView): invs_page = paginator.page(1) except EmptyPage: invs_page = paginator.page(paginator.num_pages) + hosting_orders = HostingOrder.objects.filter( + customer=self.request.user.stripecustomer).filter( + Q(subscription_id=None) | Q(subscription_id='') + ) + stripe_chgs = [] + for ho in hosting_orders: + stripe_chgs.append( + {ho.id: stripe.Charge.retrieve(ho.stripe_charge_id)}) + paginator_charges = Paginator(stripe_chgs, 10) + try: + invs_page_charges = paginator_charges.page(page) + except PageNotAnInteger: + invs_page_charges = paginator_charges.page(1) + except EmptyPage: + invs_page_charges = paginator_charges.page( + paginator_charges.num_pages) except Exception as ex: logger.error(str(ex)) invs_page = None context["invs"] = invs_page + context["invs_charge"] = invs_page_charges return context @method_decorator(decorators) From 4435eef0776681e027c5387dda6dbfbf8756cd4e Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 1 Apr 2020 17:26:12 +0530 Subject: [PATCH 006/257] Chanage tag to simple --- datacenterlight/templatetags/custom_tags.py | 2 +- hosting/templates/hosting/invoices.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/datacenterlight/templatetags/custom_tags.py b/datacenterlight/templatetags/custom_tags.py index 7cc68f07..900d94aa 100644 --- a/datacenterlight/templatetags/custom_tags.py +++ b/datacenterlight/templatetags/custom_tags.py @@ -63,7 +63,7 @@ def escaped_line_break(value): return value.replace("\\n", "\n") -@register.filter('get_line_item_from_hosting_order_charge') +@register.simple_tag def get_line_item_from_hosting_order_charge(hosting_order_id, receipt_url): """ Returns ready-to-use "html" line item to be shown for a charge in the diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index 3442ac62..6c8394f0 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -1,5 +1,5 @@ {% extends "hosting/base_short.html" %} -{% load staticfiles bootstrap3 humanize i18n custom_tags %} +{% load staticfiles bootstrap3 humanize i18n custom_tags get_line_item_from_hosting_order_charge %} {% block content %}
    @@ -86,7 +86,7 @@ {% for ho_id, stripe_charge_data in invs_charge %} - {{ ho_id | get_line_item_from_hosting_order_charge: stripe_charge_data.receipt_url }} + {{ get_line_item_from_hosting_order_charge ho_id stripe_charge_data.receipt_url }} {% endfor %} From 4869fd51df3530104be667b32d287b885db954dc Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 7 Apr 2020 18:35:25 +0530 Subject: [PATCH 007/257] Change to filter from simple --- datacenterlight/templatetags/custom_tags.py | 6 +++--- hosting/templates/hosting/invoices.html | 12 +++++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/datacenterlight/templatetags/custom_tags.py b/datacenterlight/templatetags/custom_tags.py index 900d94aa..9727d9a7 100644 --- a/datacenterlight/templatetags/custom_tags.py +++ b/datacenterlight/templatetags/custom_tags.py @@ -63,8 +63,8 @@ def escaped_line_break(value): return value.replace("\\n", "\n") -@register.simple_tag -def get_line_item_from_hosting_order_charge(hosting_order_id, receipt_url): +@register.filter('get_line_item_from_hosting_order_charge') +def get_line_item_from_hosting_order_charge(hosting_order_id): """ Returns ready-to-use "html" line item to be shown for a charge in the invoice list page @@ -85,7 +85,7 @@ def get_line_item_from_hosting_order_charge(hosting_order_id, receipt_url): product_name=hosting_order.generic_product.product_name.capitalize(), created_at=hosting_order.created_at.strftime('%Y-%m-%d'), total='%.2f' % (hosting_order.price), - receipt_url=receipt_url, + receipt_url="test", see_invoice_text=_("See Invoice") )) else: diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index 6c8394f0..0a565969 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -1,5 +1,5 @@ {% extends "hosting/base_short.html" %} -{% load staticfiles bootstrap3 humanize i18n custom_tags get_line_item_from_hosting_order_charge %} +{% load staticfiles bootstrap3 humanize i18n custom_tags %} {% block content %}
    @@ -84,9 +84,15 @@ - {% for ho_id, stripe_charge_data in invs_charge %} + {% for ho, stripe_charge_data in invs_charge %} - {{ get_line_item_from_hosting_order_charge ho_id stripe_charge_data.receipt_url }} + {ho.generic_product.product_name.capitalize()} + {ho.created_at.strftime('%Y-%m-%d')} + {ho.price} + + {% trans "See Invoice" %} + + {{ get_line_item_from_hosting_order_charge ho_id }} {% endfor %} From b01f12c9ec656a2c71a8c666687d1d862b6ca369 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 7 Apr 2020 18:41:35 +0530 Subject: [PATCH 008/257] Fix passing params to template filter --- hosting/templates/hosting/invoices.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index 0a565969..82883e98 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -92,7 +92,7 @@ {% trans "See Invoice" %} - {{ get_line_item_from_hosting_order_charge ho_id }} + {{ ho_id | get_line_item_from_hosting_order_charge }} {% endfor %} From 132a5112fd62236203847caad3d169b85657c1d5 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 7 Apr 2020 18:46:05 +0530 Subject: [PATCH 009/257] Fix getting id --- hosting/templates/hosting/invoices.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index 82883e98..6ee5bc41 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -92,7 +92,7 @@ {% trans "See Invoice" %} - {{ ho_id | get_line_item_from_hosting_order_charge }} + {{ ho.id | get_line_item_from_hosting_order_charge }} {% endfor %} From 8a3fa667a0942a75f1aef13a5ad74a38201a547f Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 7 Apr 2020 18:57:20 +0530 Subject: [PATCH 010/257] Fix bug --- hosting/templates/hosting/invoices.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index 6ee5bc41..cd18e2c6 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -92,7 +92,7 @@ {% trans "See Invoice" %} - {{ ho.id | get_line_item_from_hosting_order_charge }} + {{ ho | get_line_item_from_hosting_order_charge }} {% endfor %} From d35403311f2aee071005eb63cfa75c8fe53a8f0f Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 7 Apr 2020 19:16:55 +0530 Subject: [PATCH 011/257] Fix bug fetching variables --- hosting/templates/hosting/invoices.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index cd18e2c6..0f0479e4 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -86,9 +86,9 @@ {% for ho, stripe_charge_data in invs_charge %} - {ho.generic_product.product_name.capitalize()} - {ho.created_at.strftime('%Y-%m-%d')} - {ho.price} + {{ho.generic_product.product_name.capitalize()}} + {{ho.created_at.strftime('%Y-%m-%d')}} + {{ho.price}} {% trans "See Invoice" %} From 84c3db7e52cb097ffb85500d9a8ab4cb2be71def Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 7 Apr 2020 19:21:47 +0530 Subject: [PATCH 012/257] Pass HostingOrder instance --- hosting/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index 29e9644a..eac81d66 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1345,7 +1345,7 @@ class InvoiceListView(LoginRequiredMixin, TemplateView): stripe_chgs = [] for ho in hosting_orders: stripe_chgs.append( - {ho.id: stripe.Charge.retrieve(ho.stripe_charge_id)}) + {ho: stripe.Charge.retrieve(ho.stripe_charge_id)}) paginator_charges = Paginator(stripe_chgs, 10) try: invs_page_charges = paginator_charges.page(page) From 3aff4bb69aed5e9e49fa083dcfb286fa9017dae7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 7 Apr 2020 19:22:45 +0530 Subject: [PATCH 013/257] Fix bug --- hosting/templates/hosting/invoices.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index 0f0479e4..3386d09c 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -92,7 +92,7 @@ {% trans "See Invoice" %} - {{ ho | get_line_item_from_hosting_order_charge }} + {{ ho.id | get_line_item_from_hosting_order_charge }} {% endfor %} From 8443e03b1ffaa603540f483e117ed22efa65bcb2 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 7 Apr 2020 19:33:13 +0530 Subject: [PATCH 014/257] Add ho values --- hosting/templates/hosting/invoices.html | 4 ++-- hosting/views.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index 3386d09c..ca7ab66e 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -86,8 +86,8 @@ {% for ho, stripe_charge_data in invs_charge %} - {{ho.generic_product.product_name.capitalize()}} - {{ho.created_at.strftime('%Y-%m-%d')}} + {{ho.generic_product.product_name}} + {{ho.created_at}} {{ho.price}} {% trans "See Invoice" %} diff --git a/hosting/views.py b/hosting/views.py index eac81d66..96bff53b 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1344,6 +1344,8 @@ class InvoiceListView(LoginRequiredMixin, TemplateView): ) stripe_chgs = [] for ho in hosting_orders: + ho.generic_product.product_name = ho.generic_product.product_name.capitalize() + ho.created = ho.created.strftime('%Y-%m-%d') stripe_chgs.append( {ho: stripe.Charge.retrieve(ho.stripe_charge_id)}) paginator_charges = Paginator(stripe_chgs, 10) From a395b7a4a6c7df3dde01f5c386118bb2b5d36689 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 10 Apr 2020 18:44:45 +0530 Subject: [PATCH 015/257] Do not update ho -> doing so, crashes --- hosting/views.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index 96bff53b..eac81d66 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1344,8 +1344,6 @@ class InvoiceListView(LoginRequiredMixin, TemplateView): ) stripe_chgs = [] for ho in hosting_orders: - ho.generic_product.product_name = ho.generic_product.product_name.capitalize() - ho.created = ho.created.strftime('%Y-%m-%d') stripe_chgs.append( {ho: stripe.Charge.retrieve(ho.stripe_charge_id)}) paginator_charges = Paginator(stripe_chgs, 10) From f089892c90e0d86f5aa365d6698f590a867bf3b2 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 10 Apr 2020 20:02:12 +0530 Subject: [PATCH 016/257] Remove duplicated html + add href link to order --- datacenterlight/templatetags/custom_tags.py | 4 ++-- hosting/templates/hosting/invoices.html | 6 ------ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/datacenterlight/templatetags/custom_tags.py b/datacenterlight/templatetags/custom_tags.py index 9727d9a7..d064e285 100644 --- a/datacenterlight/templatetags/custom_tags.py +++ b/datacenterlight/templatetags/custom_tags.py @@ -2,7 +2,7 @@ import datetime import logging from django import template -from django.core.urlresolvers import resolve, reverse +from django.core.urlresolvers import resolve, reverse, reverse_lazy from django.utils.safestring import mark_safe from django.utils.translation import activate, get_language, ugettext_lazy as _ @@ -85,7 +85,7 @@ def get_line_item_from_hosting_order_charge(hosting_order_id): product_name=hosting_order.generic_product.product_name.capitalize(), created_at=hosting_order.created_at.strftime('%Y-%m-%d'), total='%.2f' % (hosting_order.price), - receipt_url="test", + receipt_url=reverse_lazy('hosting:login', hosting_order.id), see_invoice_text=_("See Invoice") )) else: diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index ca7ab66e..32ec8004 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -86,12 +86,6 @@ {% for ho, stripe_charge_data in invs_charge %} - {{ho.generic_product.product_name}} - {{ho.created_at}} - {{ho.price}} - - {% trans "See Invoice" %} - {{ ho.id | get_line_item_from_hosting_order_charge }} {% endfor %} From 7db3dc422225910c0f3b4e00ee4c27b082da6ed4 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 15 Apr 2020 17:36:40 +0530 Subject: [PATCH 017/257] Fix circular imports and correct hosting order link --- 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 d064e285..8003be0e 100644 --- a/datacenterlight/templatetags/custom_tags.py +++ b/datacenterlight/templatetags/custom_tags.py @@ -2,7 +2,7 @@ import datetime import logging from django import template -from django.core.urlresolvers import resolve, reverse, reverse_lazy +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 _ @@ -85,7 +85,9 @@ def get_line_item_from_hosting_order_charge(hosting_order_id): product_name=hosting_order.generic_product.product_name.capitalize(), created_at=hosting_order.created_at.strftime('%Y-%m-%d'), total='%.2f' % (hosting_order.price), - receipt_url=reverse_lazy('hosting:login', hosting_order.id), + receipt_url=reverse('hosting:orders', + kwargs={'pk': hosting_order.id}), + see_invoice_text=_("See Invoice") )) else: From f9906781ba0b2730cf25faef0f9527799ad0c83d Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 21 Apr 2020 00:02:21 +0530 Subject: [PATCH 018/257] Remove wrong Ireland country code in vat rates --- vat_rates.csv | 1 - 1 file changed, 1 deletion(-) diff --git a/vat_rates.csv b/vat_rates.csv index 17bdb997..72870530 100644 --- a/vat_rates.csv +++ b/vat_rates.csv @@ -321,5 +321,4 @@ IM",GBP,0.1,standard, 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 8472bdd097f594aba19e75f794020547d68e4942 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 11 Jun 2020 10:11:46 +0530 Subject: [PATCH 019/257] Add django error logger --- dynamicweb/settings/base.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 03013ea5..59ca0fd2 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -684,6 +684,17 @@ if ENABLE_LOGGING: } } loggers_dict.update(logger_item) + if not 'django' in MODULES_TO_LOG: + loggers_dict.update( + { + 'django': { + 'handlers': ['django_error'], + 'level': 'ERROR', + 'propagate': True + } + } + ) + custom_handler_item = { 'custom_file': { @@ -697,6 +708,18 @@ if ENABLE_LOGGING: 'maxBytes': 1024 * 1024 * 5, 'backupCount': 10, 'formatter': 'standard', + }, + 'django_error': { + 'level': 'ERROR', + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': + "{PROJECT_DIR}/logs/django-error.log".format( + LEVEL=LOG_LEVEL.lower(), + PROJECT_DIR=PROJECT_DIR + ), + 'maxBytes': 1024 * 1024 * 5, + 'backupCount': 10, + 'formatter': 'standard', } } handlers_dict.update(custom_handler_item) From 495e7d4022068e06f1ad969c16ca9b95c6dbfc42 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 11 Jun 2020 11:29:32 +0530 Subject: [PATCH 020/257] Fix wrong constant name settings.LDAP_MAX_UID_PATH -> settings.LDAP_MAX_UID_FILE_PATH --- utils/ldap_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/ldap_manager.py b/utils/ldap_manager.py index 8c555224..ee190732 100644 --- a/utils/ldap_manager.py +++ b/utils/ldap_manager.py @@ -266,7 +266,7 @@ class LdapManager: logger.error( "Error reading int value from {}. {}" "Returning default value {} instead".format( - settings.LDAP_MAX_UID_PATH, + settings.LDAP_MAX_UID_FILE_PATH, str(ve), settings.LDAP_DEFAULT_START_UID ) From eadbebb79659f50f4bed0f23853c72110b9e70da Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 11 Jun 2020 11:34:56 +0530 Subject: [PATCH 021/257] Update Changelog for 2.11 --- Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog b/Changelog index 1bb0ff37..b22ce6f5 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,5 @@ +2.11: 2020-06-11 + * Bugfix: Correct the wrong constant name (caused payment to go thru and showing error and VMs not instantiated) (MR!738) 2.10.8: 2020-06-10 * #8102: Refactor MAX_TIME_TO_WAIT_FOR_VM_TERMINATE to increase time to poll whether VM has been terminated or not (MR!737) 2.10.7: 2020-05-25 From a52215bb56f280d7f83d348ac8dbe742db9e08c7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 11 Jun 2020 11:42:23 +0530 Subject: [PATCH 022/257] Update Changelog --- Changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changelog b/Changelog index b22ce6f5..33bf7dc9 100644 --- a/Changelog +++ b/Changelog @@ -1,5 +1,5 @@ 2.11: 2020-06-11 - * Bugfix: Correct the wrong constant name (caused payment to go thru and showing error and VMs not instantiated) (MR!738) + * Bugfix: Correct the wrong constant name (caused payment to go thru and showing error and VMs not instantiated) 2.10.8: 2020-06-10 * #8102: Refactor MAX_TIME_TO_WAIT_FOR_VM_TERMINATE to increase time to poll whether VM has been terminated or not (MR!737) 2.10.7: 2020-05-25 From bc69cc49e5773320a22ab94551fd14754ccdb179 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 11 Jun 2020 15:20:42 +0530 Subject: [PATCH 023/257] Move opennebula specific code to celery to make it asynchronous --- datacenterlight/views.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 17a0479f..ee97a447 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1011,14 +1011,6 @@ class OrderConfirmationView(DetailView, FormView): user_hosting_key = UserHostingKey.objects.get(id=self.request.session['new_user_hosting_key_id']) user_hosting_key.user = new_user user_hosting_key.save() - - owner = new_user - manager = OpenNebulaManager( - email=owner.username, - password=owner.password - ) - keys_to_save = get_all_public_keys(new_user) - manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) else: # We assume that if the user is here, his/her StripeCustomer # object already exists From 081f81c41cf39065078b87cdc54d7e627c4edbf2 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 19 Jun 2020 13:13:26 +0530 Subject: [PATCH 024/257] Begin handling click events --- alplora/static/alplora/js/virtual_machine_detail.js | 10 ++++++++++ hosting/templates/hosting/invoices.html | 9 +++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/alplora/static/alplora/js/virtual_machine_detail.js b/alplora/static/alplora/js/virtual_machine_detail.js index 303f95ca..39e4c3bf 100755 --- a/alplora/static/alplora/js/virtual_machine_detail.js +++ b/alplora/static/alplora/js/virtual_machine_detail.js @@ -15,4 +15,14 @@ $( document ).ready(function() { $('html,body').scrollTop(scrollmem); }); + + // Toggle subscription and one-time payments div + $('#li-one-time-charges').click(function() { + console.log("li-one-time-charges clicked"); + }); + $('#li-subscriptions').click(function() { + console.log("li-one-time-charges clicked"); + }); + + }); \ No newline at end of file diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index 32ec8004..554858dc 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -16,10 +16,10 @@
    -
    +
    @@ -73,7 +73,8 @@ {% endif %} -
    + +
    From 38109e175abcd2993f0a944827e5fb7baa83b587 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 19 Jun 2020 15:25:34 +0530 Subject: [PATCH 025/257] Move js to correct place --- alplora/static/alplora/js/virtual_machine_detail.js | 10 ---------- hosting/static/hosting/js/virtual_machine_detail.js | 10 +++++++++- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/alplora/static/alplora/js/virtual_machine_detail.js b/alplora/static/alplora/js/virtual_machine_detail.js index 39e4c3bf..303f95ca 100755 --- a/alplora/static/alplora/js/virtual_machine_detail.js +++ b/alplora/static/alplora/js/virtual_machine_detail.js @@ -15,14 +15,4 @@ $( document ).ready(function() { $('html,body').scrollTop(scrollmem); }); - - // Toggle subscription and one-time payments div - $('#li-one-time-charges').click(function() { - console.log("li-one-time-charges clicked"); - }); - $('#li-subscriptions').click(function() { - console.log("li-one-time-charges clicked"); - }); - - }); \ No newline at end of file diff --git a/hosting/static/hosting/js/virtual_machine_detail.js b/hosting/static/hosting/js/virtual_machine_detail.js index 28592883..46d65995 100644 --- a/hosting/static/hosting/js/virtual_machine_detail.js +++ b/hosting/static/hosting/js/virtual_machine_detail.js @@ -135,7 +135,15 @@ $(document).ready(function() { }); $('#createvm-modal').on('hidden.bs.modal', function () { $(this).find('.modal-footer .btn').addClass('hide'); - }) + }); + + // Toggle subscription and one-time payments div + $('#li-one-time-charges').click(function() { + console.log("li-one-time-charges clicked"); + }); + $('#li-subscriptions').click(function() { + console.log("li-one-time-charges clicked"); + }); }); window.onload = function () { From 0dc9c6cdca7b4802bf8d4c5dd63dbf967129d432 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 19 Jun 2020 15:30:38 +0530 Subject: [PATCH 026/257] Fix element id --- hosting/templates/hosting/invoices.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index 554858dc..2cb6edc8 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -17,7 +17,7 @@
    From c4e7f99202b76bad27eb277e42acdafd86a2d111 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 19 Jun 2020 15:38:26 +0530 Subject: [PATCH 027/257] Add slideToggles to subscription/one-time-payments divs --- hosting/static/hosting/js/virtual_machine_detail.js | 6 +++++- hosting/templates/hosting/invoices.html | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/hosting/static/hosting/js/virtual_machine_detail.js b/hosting/static/hosting/js/virtual_machine_detail.js index 46d65995..5406f3be 100644 --- a/hosting/static/hosting/js/virtual_machine_detail.js +++ b/hosting/static/hosting/js/virtual_machine_detail.js @@ -140,9 +140,13 @@ $(document).ready(function() { // Toggle subscription and one-time payments div $('#li-one-time-charges').click(function() { console.log("li-one-time-charges clicked"); + $('#subscriptions').slideToggle(); + $('#one-time-charges').slideToggle(); }); $('#li-subscriptions').click(function() { - console.log("li-one-time-charges clicked"); + console.log("li-one-time-charges clicked"); + $('#one-time-charges').slideToggle(); + $('#subscriptions').slideToggle(); }); }); diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index 2cb6edc8..b0063e0e 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -74,7 +74,7 @@ -
    From 6b3ecfaff4d67640fa3ba86708105ffbd24188b5 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 19 Jun 2020 15:45:44 +0530 Subject: [PATCH 028/257] Use show/hide instead of toggle --- hosting/static/hosting/js/virtual_machine_detail.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/hosting/static/hosting/js/virtual_machine_detail.js b/hosting/static/hosting/js/virtual_machine_detail.js index 5406f3be..38f18e72 100644 --- a/hosting/static/hosting/js/virtual_machine_detail.js +++ b/hosting/static/hosting/js/virtual_machine_detail.js @@ -140,14 +140,17 @@ $(document).ready(function() { // Toggle subscription and one-time payments div $('#li-one-time-charges').click(function() { console.log("li-one-time-charges clicked"); - $('#subscriptions').slideToggle(); - $('#one-time-charges').slideToggle(); + $('#subscriptions').hide(); + $('#one-time-charges').show(); }); $('#li-subscriptions').click(function() { - console.log("li-one-time-charges clicked"); - $('#one-time-charges').slideToggle(); - $('#subscriptions').slideToggle(); + console.log("li-subscriptions clicked"); + $('#one-time-charges').hide(); + $('#subscriptions').show(); }); + + $('#one-time-charges').hide(); + $('#subscriptions').show(); }); window.onload = function () { From 1b29a23edef67e1c510b4fb7562eee905412fe01 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 19 Jun 2020 15:48:00 +0530 Subject: [PATCH 029/257] Remove default display none of one-time-payments div --- hosting/templates/hosting/invoices.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index b0063e0e..669e48e9 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -74,7 +74,7 @@ -
    From 25255c862aa5bf57dd6822dc4394dc3c977d33b3 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 19 Jun 2020 15:50:24 +0530 Subject: [PATCH 030/257] Remove buggy # in dom element id --- hosting/templates/hosting/invoices.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index 669e48e9..5ec6b952 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -74,7 +74,7 @@ -
    +
    From 81fd129d48f0e640ca7779886ca930aef689f826 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 19 Jun 2020 16:05:06 +0530 Subject: [PATCH 031/257] Use css to hide div than js --- hosting/static/hosting/js/virtual_machine_detail.js | 3 --- hosting/templates/hosting/invoices.html | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/hosting/static/hosting/js/virtual_machine_detail.js b/hosting/static/hosting/js/virtual_machine_detail.js index 38f18e72..4a60d32b 100644 --- a/hosting/static/hosting/js/virtual_machine_detail.js +++ b/hosting/static/hosting/js/virtual_machine_detail.js @@ -148,9 +148,6 @@ $(document).ready(function() { $('#one-time-charges').hide(); $('#subscriptions').show(); }); - - $('#one-time-charges').hide(); - $('#subscriptions').show(); }); window.onload = function () { diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index 5ec6b952..347b1ff4 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -74,7 +74,7 @@ -
    +
    From 0c39336653c12e4adb8831f3deef2612f4e59375 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 22 Jun 2020 20:04:01 +0530 Subject: [PATCH 032/257] Sort invoices by created_at desc --- hosting/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index 65e6cc70..438a0d55 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1313,7 +1313,7 @@ class InvoiceListView(LoginRequiredMixin, TemplateView): hosting_orders = HostingOrder.objects.filter( customer=cu.stripecustomer).filter( Q(subscription_id=None) | Q(subscription_id='') - ) + ).order_by('-created_at') stripe_chgs = [] for ho in hosting_orders: stripe_chgs.append({ho.id: stripe.Charge.retrieve(ho.stripe_charge_id)}) @@ -1341,7 +1341,7 @@ class InvoiceListView(LoginRequiredMixin, TemplateView): hosting_orders = HostingOrder.objects.filter( customer=self.request.user.stripecustomer).filter( Q(subscription_id=None) | Q(subscription_id='') - ) + ).order_by('-created_at') stripe_chgs = [] for ho in hosting_orders: stripe_chgs.append( From 49a9fdd842f3fb1e11e892d9929dff09cbfc3204 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 23 Jun 2020 11:32:30 +0530 Subject: [PATCH 033/257] Update Changelog for 2.12 --- Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog b/Changelog index 33bf7dc9..1b5bde41 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,5 @@ +2.12: 2020-06-23 + * 7894: Show one time payment invoices (MR!738) 2.11: 2020-06-11 * Bugfix: Correct the wrong constant name (caused payment to go thru and showing error and VMs not instantiated) 2.10.8: 2020-06-10 From 87a154bd0afc4632a91bca918dd2be8846f93287 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 1 Jul 2020 00:47:59 +0530 Subject: [PATCH 034/257] Create migration --- .../migrations/0060_update_DE_vat_covid-19.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 hosting/migrations/0060_update_DE_vat_covid-19.py diff --git a/hosting/migrations/0060_update_DE_vat_covid-19.py b/hosting/migrations/0060_update_DE_vat_covid-19.py new file mode 100644 index 00000000..a551720e --- /dev/null +++ b/hosting/migrations/0060_update_DE_vat_covid-19.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2020-06-30 19:12 + +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0059_stripetaxrate'), + ] + + operations = [ + migrations.RunSQL( + "update hosting_vatrates set stop_date = '2020-06-30' where territory_codes = 'DE' and rate = '0.19'", + "insert into hosting_vatrates (start_date, stop_date, territory_codes, currency_code, rate, rate_type, description) values ('2020-07-01',null,'DE', 'EUR', '0.16', 'standard', 'Germany (member state) standard VAT rate - COVID 19 reduced rate')", + "update hosting_stripetaxrate set description = 'VAT for DE pre-COVID-19' where description = 'VAT for DE'" + + ) + ] From 58377319b96ef1fa15680f0ad60e981644fd42aa Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 1 Jul 2020 01:18:57 +0530 Subject: [PATCH 035/257] Make migration reversible --- .../migrations/0060_update_DE_vat_covid-19.py | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/hosting/migrations/0060_update_DE_vat_covid-19.py b/hosting/migrations/0060_update_DE_vat_covid-19.py index a551720e..2fd8418e 100644 --- a/hosting/migrations/0060_update_DE_vat_covid-19.py +++ b/hosting/migrations/0060_update_DE_vat_covid-19.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # Generated by Django 1.9.4 on 2020-06-30 19:12 - from __future__ import unicode_literals from django.db import migrations @@ -14,9 +13,21 @@ class Migration(migrations.Migration): operations = [ migrations.RunSQL( - "update hosting_vatrates set stop_date = '2020-06-30' where territory_codes = 'DE' and rate = '0.19'", - "insert into hosting_vatrates (start_date, stop_date, territory_codes, currency_code, rate, rate_type, description) values ('2020-07-01',null,'DE', 'EUR', '0.16', 'standard', 'Germany (member state) standard VAT rate - COVID 19 reduced rate')", - "update hosting_stripetaxrate set description = 'VAT for DE pre-COVID-19' where description = 'VAT for DE'" + sql=["update hosting_vatrates set stop_date = '2020-06-30' where territory_codes = 'DE' and rate = '0.19'"], + reverse_sql=[ + "update hosting_vatrates set stop_date = '' where stop_date = '2020-06-30' and territory_codes = 'DE' and rate = '0.19'"], + ), + migrations.RunSQL( + sql=[ + "insert into hosting_vatrates (start_date, stop_date, territory_codes, currency_code, rate, rate_type, description) values ('2020-07-01',null,'DE', 'EUR', '0.16', 'standard', 'Germany (member state) standard VAT rate - COVID 19 reduced rate')"], + reverse_sql=[ + "delete from hosting_vatrates where description = 'Germany (member state) standard VAT rate - COVID 19 reduced rate' and start_date = '2020-07-01' and territory_codes = 'DE'" ], + ), - ) + migrations.RunSQL( + sql=[ + "update hosting_stripetaxrate set description = 'VAT for DE pre-COVID-19' where description = 'VAT for DE'"], + reverse_sql=[ + "update hosting_stripetaxrate set description = 'VAT for DE' where description = 'VAT for DE pre-COVID-19'"], + ), ] From c339c19cfd1e78e326b983183d421442ccc83ee3 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 1 Jul 2020 01:21:57 +0530 Subject: [PATCH 036/257] Fix date format bug --- hosting/migrations/0060_update_DE_vat_covid-19.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/migrations/0060_update_DE_vat_covid-19.py b/hosting/migrations/0060_update_DE_vat_covid-19.py index 2fd8418e..17c6394b 100644 --- a/hosting/migrations/0060_update_DE_vat_covid-19.py +++ b/hosting/migrations/0060_update_DE_vat_covid-19.py @@ -15,7 +15,7 @@ class Migration(migrations.Migration): migrations.RunSQL( sql=["update hosting_vatrates set stop_date = '2020-06-30' where territory_codes = 'DE' and rate = '0.19'"], reverse_sql=[ - "update hosting_vatrates set stop_date = '' where stop_date = '2020-06-30' and territory_codes = 'DE' and rate = '0.19'"], + "update hosting_vatrates set stop_date = null where stop_date = '2020-06-30' and territory_codes = 'DE' and rate = '0.19'"], ), migrations.RunSQL( sql=[ From 050309a68e62c1db3a22330388f5b09abc100be4 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 21 Jul 2020 21:54:54 +0530 Subject: [PATCH 037/257] Add exclude_vat_calculations field and add VAT only when this is set to False --- hosting/models.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/hosting/models.py b/hosting/models.py index 67c55aa2..1061cf54 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -82,15 +82,22 @@ class GenericProduct(AssignPermissionsMixin, models.Model): product_subscription_interval = models.CharField( max_length=10, default="month", help_text="Choose between `year` and `month`") + exclude_vat_calculations = models.BooleanField( + default=False, + help_text="When checked VAT calculations are excluded for this product" + ) def __str__(self): return self.product_name def get_actual_price(self, vat_rate=None): - VAT = vat_rate if vat_rate is not None else self.product_vat - return round( - float(self.product_price) + float(self.product_price) * float(VAT), 2 - ) + if self.exclude_vat_calculations: + return round(float(self.product_price), 2) + else: + VAT = vat_rate if vat_rate is not None else self.product_vat + return round( + float(self.product_price) + float(self.product_price) * float(VAT), 2 + ) class HostingOrder(AssignPermissionsMixin, models.Model): From fb59ae4055f995ca8d4cc1a394366e3273c5faa8 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 21 Jul 2020 22:03:18 +0530 Subject: [PATCH 038/257] Add migration --- ...genericproduct_exclude_vat_calculations.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 hosting/migrations/0061_genericproduct_exclude_vat_calculations.py diff --git a/hosting/migrations/0061_genericproduct_exclude_vat_calculations.py b/hosting/migrations/0061_genericproduct_exclude_vat_calculations.py new file mode 100644 index 00000000..0bef80d4 --- /dev/null +++ b/hosting/migrations/0061_genericproduct_exclude_vat_calculations.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2020-07-21 16:32 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0060_update_DE_vat_covid-19'), + ] + + operations = [ + migrations.AddField( + model_name='genericproduct', + name='exclude_vat_calculations', + field=models.BooleanField(default=False, help_text='When checked VAT calculations are excluded for this product'), + ), + ] From ad5371a1339a95b4890dc9425229c8e673fb9e28 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 21 Jul 2020 22:15:04 +0530 Subject: [PATCH 039/257] Update order confirmation template --- .../datacenterlight/order_detail.html | 95 ++++++++++--------- 1 file changed, 49 insertions(+), 46 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 02bce0ed..0921ebc5 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -106,55 +106,58 @@

    -
    -

    - {% 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}}%)

    -
    + {% if generic_payment_details.exclude_vat_calculations %} + {% else %} +
    +

    + {% 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

    -
    +
    +
    -
    -
    -
    -
    -
    -
    -
    -

    Total

    -
    -
    -

    {{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_rate}}%)

    +
    +
    +
    +
    +

    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

    +
    +
    +
    + {% endif %}

    From df301a18fc217a5cd4266c8e591f7ecf823e62ba Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 21 Jul 2020 22:23:07 +0530 Subject: [PATCH 040/257] Set vat params --- datacenterlight/views.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index ee97a447..26b9bad0 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -424,8 +424,10 @@ class PaymentOrderView(FormView): ) gp_details = { "product_name": product.product_name, - "vat_rate": user_country_vat_rate * 100, - "vat_amount": round( + "vat_rate": 0 if product.exclude_vat_calculations else + user_country_vat_rate * 100, + "vat_amount": 0 if product.exclude_vat_calculations + else round( float(product.product_price) * user_country_vat_rate, 2), "vat_country": address_form.cleaned_data["country"], @@ -444,7 +446,8 @@ class PaymentOrderView(FormView): "product_id": product.id, "product_slug": product.product_slug, "recurring_interval": - product.product_subscription_interval + product.product_subscription_interval, + "exclude_vat_calculations": product.exclude_vat_calculations } request.session["generic_payment_details"] = ( gp_details From 08bf163d214cf74db853e4c38a2393776b768451 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 21 Jul 2020 22:26:16 +0530 Subject: [PATCH 041/257] Remove multiple hl --- datacenterlight/templates/datacenterlight/order_detail.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 0921ebc5..d8eb4934 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -103,11 +103,11 @@

    {% endif %}
    -
    -
    -
    {% if generic_payment_details.exclude_vat_calculations %} {% else %} +
    +
    +

    {% trans "Price Before VAT" %} From c2e2e1828f2f1b8972ae583336a359748f5705b9 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 21 Jul 2020 23:07:25 +0530 Subject: [PATCH 042/257] Update Changelog for 2.12.1 --- Changelog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changelog b/Changelog index 1b5bde41..43d3495f 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,6 @@ +2.12.1: 2020-07-21 + * 8307: Introduce "Exclude vat calculations" for Generic Products (MR!740) + * Change DE VAT rate to 16% from 19% (MR!739) 2.12: 2020-06-23 * 7894: Show one time payment invoices (MR!738) 2.11: 2020-06-11 From 70bfef473885cdc7ca72df5b47120ab21e410169 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 11 Oct 2020 16:01:55 +0530 Subject: [PATCH 043/257] Show SCA modal when required --- .../hosting/js/virtual_machine_detail.js | 40 ++++++++++++++----- hosting/views.py | 15 +++++++ utils/stripe_utils.py | 1 + 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/hosting/static/hosting/js/virtual_machine_detail.js b/hosting/static/hosting/js/virtual_machine_detail.js index 4a60d32b..f01cc435 100644 --- a/hosting/static/hosting/js/virtual_machine_detail.js +++ b/hosting/static/hosting/js/virtual_machine_detail.js @@ -107,18 +107,36 @@ $(document).ready(function() { success: function (data) { fa_icon = $('.modal-icon > .fa'); modal_btn = $('#createvm-modal-done-btn'); - $('#createvm-modal-title').text(data.msg_title); - $('#createvm-modal-body').html(data.msg_body); - if (data.redirect) { - modal_btn.attr('href', data.redirect).removeClass('hide'); + if (data.showSCA){ + console.log("Show SCA"); + var stripe = Stripe(data.STRIPE_PUBLISHABLE_KEY); + + stripe.confirmCardPayment(pi_secret).then(function(result) { + if (result.error) { + // Display error.message in your UI. + $("#3ds_result").text("Error!"); + $("#3ds_result").addClass("text-danger"); + } else { + // The payment has succeeded. Display a success message. + $("#3ds_result").text("Thank you for payment"); + $("#3ds_result").addClass("text-success"); + } + }); + $('#3Dsecure-modal').show(); } else { - modal_btn.attr('href', ""); - } - if (data.status === true) { - fa_icon.attr('class', 'checkmark'); - } else { - fa_icon.attr('class', 'fa fa-close'); - modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide'); + $('#createvm-modal-title').text(data.msg_title); + $('#createvm-modal-body').html(data.msg_body); + if (data.redirect) { + modal_btn.attr('href', data.redirect).removeClass('hide'); + } else { + modal_btn.attr('href', ""); + } + if (data.status === true) { + fa_icon.attr('class', 'checkmark'); + } else { + fa_icon.attr('class', 'fa fa-close'); + modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide'); + } } }, error: function (xmlhttprequest, textstatus, message) { diff --git a/hosting/views.py b/hosting/views.py index 438a0d55..5660b7d8 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1195,6 +1195,21 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): tax_rates=[stripe_tax_rate.tax_rate_id] if stripe_tax_rate else [], ) stripe_subscription_obj = subscription_result.get('response_object') + latest_invoice = stripe.Invoice.retrieve(stripe_subscription_obj.latest_invoice) + ret = stripe.PaymentIntent.confirm( + latest_invoice.payment_intent + ) + if ret.status == 'requires_action': + pi = stripe.PaymentIntent.retrieve( + latest_invoice.payment_intent + ) + context = { + 'sid': stripe_subscription_obj.id, + 'payment_intent_secret': pi.client_secret, + 'STRIPE_PUBLISHABLE_KEY': settings.STRIPE_API_PUBLIC_KEY, + 'showSCA': True + } + return JsonResponse(context) # Check if the subscription was approved and is active if (stripe_subscription_obj is None or stripe_subscription_obj.status != 'active'): diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index ade06dd3..b8667a40 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -322,6 +322,7 @@ class StripeUtils(object): customer=customer, items=plans, trial_end=trial_end, coupon=coupon, default_tax_rates=tax_rates, + payment_behavior='allow_incomplete' ) return subscription_result From 877553e4429aa37bd22ebebed84fe308d03efe05 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 11 Oct 2020 16:45:23 +0530 Subject: [PATCH 044/257] Add logger message --- hosting/views.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hosting/views.py b/hosting/views.py index 5660b7d8..cc5cd057 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1078,6 +1078,12 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): billing_address_data = request.session.get('billing_address_data') vm_template_id = template.get('id', 1) stripe_api_cus_id = request.user.stripecustomer.stripe_id + logger.debug("template=%s specs=%s stripe_customer_id=%s " + "billing_address_data=%s vm_template_id=%s " + "stripe_api_cus_id=%s" % ( + template, specs, stripe_customer_id, billing_address_data, + vm_template_id, stripe_api_cus_id) + ) if 'token' in self.request.session: card_details = stripe_utils.get_cards_details_from_token( request.session['token'] From 676a3588321bf7800e21cdc19621b2ce2474d1de Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 11 Oct 2020 16:57:10 +0530 Subject: [PATCH 045/257] Add logger messages --- datacenterlight/views.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 26b9bad0..3acc75c0 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -709,11 +709,14 @@ class OrderConfirmationView(DetailView, FormView): user = request.session.get('user') stripe_api_cus_id = request.session.get('customer') stripe_utils = StripeUtils() + logger.debug("user=%s stripe_api_cus_id=%s" % (user, stripe_api_cus_id)) if 'token' in request.session: card_details = stripe_utils.get_cards_details_from_token( request.session.get('token') ) + logger.debug( + "card_details=%s stripe_api_cus_id=%s" % (card_details)) if not card_details.get('response_object'): msg = card_details.get('error') messages.add_message(self.request, messages.ERROR, msg, @@ -794,6 +797,7 @@ class OrderConfirmationView(DetailView, FormView): 'brand': user_card_detail.brand, 'card_id': user_card_detail.card_id } + logger.debug("card_details_dict=%s" % card_details_dict) else: response = { 'status': False, @@ -811,6 +815,7 @@ class OrderConfirmationView(DetailView, FormView): if ('generic_payment_type' in request.session and self.request.session['generic_payment_type'] == 'generic'): gp_details = self.request.session['generic_payment_details'] + logger.debug("gp_details=%s" % gp_details) if gp_details['recurring']: # generic recurring payment logger.debug("Commencing a generic recurring payment") @@ -849,6 +854,8 @@ class OrderConfirmationView(DetailView, FormView): if ('generic_payment_type' not in request.session or (request.session['generic_payment_details']['recurring'])): recurring_interval = 'month' + logger.debug("'generic_payment_type' not in request.session or" + "(request.session['generic_payment_details']['recurring']") 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'] @@ -897,6 +904,7 @@ class OrderConfirmationView(DetailView, FormView): app='dcl', price=amount_to_be_charged ) + logger.debug(specs) stripe_plan = stripe_utils.get_or_create_stripe_plan( amount=amount_to_be_charged, name=plan_name, From 4c7b9eaa52d99048f22471d7c4f85ed879fe877a Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 11 Oct 2020 17:11:20 +0530 Subject: [PATCH 046/257] Handle SCA in dcl flow --- datacenterlight/views.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 3acc75c0..7dde0fa6 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -953,6 +953,23 @@ class OrderConfirmationView(DetailView, FormView): tax_rates=[stripe_tax_rate.tax_rate_id] if stripe_tax_rate else [], ) stripe_subscription_obj = subscription_result.get('response_object') + logger.debug(stripe_subscription_obj) + latest_invoice = stripe.Invoice.retrieve( + stripe_subscription_obj.latest_invoice) + ret = stripe.PaymentIntent.confirm( + latest_invoice.payment_intent + ) + if ret.status == 'requires_action': + pi = stripe.PaymentIntent.retrieve( + latest_invoice.payment_intent + ) + context = { + 'sid': stripe_subscription_obj.id, + 'payment_intent_secret': pi.client_secret, + 'STRIPE_PUBLISHABLE_KEY': settings.STRIPE_API_PUBLIC_KEY, + 'showSCA': True + } + return JsonResponse(context) # Check if the subscription was approved and is active if (stripe_subscription_obj is None or stripe_subscription_obj.status != 'active'): From 81ec1125cb58ee3627a1c648e38535f209922af6 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 11 Oct 2020 17:21:15 +0530 Subject: [PATCH 047/257] Capture both actions requires_action and requires_source_action --- 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 7dde0fa6..973ce738 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -959,7 +959,7 @@ class OrderConfirmationView(DetailView, FormView): ret = stripe.PaymentIntent.confirm( latest_invoice.payment_intent ) - if ret.status == 'requires_action': + if ret.status == 'requires_source_action' or ret.status == 'requires_action': pi = stripe.PaymentIntent.retrieve( latest_invoice.payment_intent ) diff --git a/hosting/views.py b/hosting/views.py index cc5cd057..7f322e91 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1205,7 +1205,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): ret = stripe.PaymentIntent.confirm( latest_invoice.payment_intent ) - if ret.status == 'requires_action': + if ret.status == 'requires_source_action' or ret.status == 'requires_action': pi = stripe.PaymentIntent.retrieve( latest_invoice.payment_intent ) From 2973ef3b1dbde36248219b0b30ce8011b52470b0 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 11 Oct 2020 17:24:33 +0530 Subject: [PATCH 048/257] Use correct variable --- hosting/static/hosting/js/virtual_machine_detail.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/static/hosting/js/virtual_machine_detail.js b/hosting/static/hosting/js/virtual_machine_detail.js index f01cc435..db1faaf8 100644 --- a/hosting/static/hosting/js/virtual_machine_detail.js +++ b/hosting/static/hosting/js/virtual_machine_detail.js @@ -111,7 +111,7 @@ $(document).ready(function() { console.log("Show SCA"); var stripe = Stripe(data.STRIPE_PUBLISHABLE_KEY); - stripe.confirmCardPayment(pi_secret).then(function(result) { + stripe.confirmCardPayment(data.payment_intent_secret).then(function(result) { if (result.error) { // Display error.message in your UI. $("#3ds_result").text("Error!"); From 79cbfac0922066c4f92f6a9eb7a91c34772e295c Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 12 Nov 2020 12:12:46 +0530 Subject: [PATCH 049/257] Escape ssh key before storing --- hosting/forms.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hosting/forms.py b/hosting/forms.py index 947cee44..8df2bd3e 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -2,6 +2,7 @@ import datetime import logging import subprocess import tempfile +import xml from django import forms from django.conf import settings @@ -207,7 +208,7 @@ class UserHostingKeyForm(forms.ModelForm): logger.debug( "Not a correct ssh format {error}".format(error=str(cpe))) raise forms.ValidationError(KEY_ERROR_MESSAGE) - return openssh_pubkey_str + return xml.sax.saxutils.escape(openssh_pubkey_str) def clean_name(self): INVALID_NAME_MESSAGE = _("Comma not accepted in the name of the key") From 52362cd0ea413e4b8da4a4ba06615d84825d2a35 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 1 Dec 2020 17:12:29 +0530 Subject: [PATCH 050/257] In case of error, log it and return empty result --- datacenterlight/templatetags/custom_tags.py | 40 +++++++++++---------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/datacenterlight/templatetags/custom_tags.py b/datacenterlight/templatetags/custom_tags.py index 8003be0e..0015ac58 100644 --- a/datacenterlight/templatetags/custom_tags.py +++ b/datacenterlight/templatetags/custom_tags.py @@ -72,25 +72,29 @@ def get_line_item_from_hosting_order_charge(hosting_order_id): :param hosting_order_id: the HostingOrder id :return: """ - hosting_order = HostingOrder.objects.get(id = hosting_order_id) - if hosting_order.stripe_charge_id: - return mark_safe(""" -

    - - - - """.format( - product_name=hosting_order.generic_product.product_name.capitalize(), - created_at=hosting_order.created_at.strftime('%Y-%m-%d'), - total='%.2f' % (hosting_order.price), - receipt_url=reverse('hosting:orders', - kwargs={'pk': hosting_order.id}), + try: + hosting_order = HostingOrder.objects.get(id = hosting_order_id) + if hosting_order.stripe_charge_id: + return mark_safe(""" + + + + + """.format( + product_name=hosting_order.generic_product.product_name.capitalize(), + created_at=hosting_order.created_at.strftime('%Y-%m-%d'), + total='%.2f' % (hosting_order.price), + receipt_url=reverse('hosting:orders', + kwargs={'pk': hosting_order.id}), - see_invoice_text=_("See Invoice") - )) - else: + see_invoice_text=_("See Invoice") + )) + else: + return "" + except Exception as ex: + logger.error("Error %s" % str(ex)) return "" From e8b79d6951fd2af3b42842fa8cb7be3c4d427b60 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 1 Dec 2020 17:12:55 +0530 Subject: [PATCH 051/257] Return emtpty string when plan is not set --- 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 0015ac58..120cabbf 100644 --- a/datacenterlight/templatetags/custom_tags.py +++ b/datacenterlight/templatetags/custom_tags.py @@ -114,7 +114,7 @@ def get_line_item_from_stripe_invoice(invoice): plan_name = "" for line_data in invoice["lines"]["data"]: if is_first: - plan_name = line_data.plan.name + plan_name = line_data.plan.name if line_data.plan is not None else "" start_date = line_data.period.start end_date = line_data.period.end is_first = False From d980fb00003c15008a95bb3e9176e1f481926195 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 1 Dec 2020 17:13:09 +0530 Subject: [PATCH 052/257] Quote email in links --- hosting/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index 438a0d55..d03661fd 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1,6 +1,7 @@ import logging import uuid from datetime import datetime +from urllib.parse import quote from time import sleep import stripe @@ -1292,7 +1293,7 @@ class InvoiceListView(LoginRequiredMixin, TemplateView): 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 + context['user_email'] = '%s' % quote(user_email) logger.debug( "user_email = {}".format(user_email) ) From bf1aad82b8e6a3c60d5218007e894b758df4061c Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 2 Dec 2020 18:38:37 +0530 Subject: [PATCH 053/257] Update Changelog for 2.13 --- Changelog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changelog b/Changelog index 43d3495f..fdadadf1 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,6 @@ +2.13: 2020-12-02 + * 8654: Fix 500 error on invoices list for the user contact+devuanhosting.com@virus.media (MR!742) + * 8593: Escape user's ssh key in xml-rpc call to create VM (MR!741) 2.12.1: 2020-07-21 * 8307: Introduce "Exclude vat calculations" for Generic Products (MR!740) * Change DE VAT rate to 16% from 19% (MR!739) From e9801eb9c47a584ed4ea2ce318f636c62f70013c Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 3 Dec 2020 09:48:08 +0530 Subject: [PATCH 054/257] Fix wrong logging --- datacenterlight/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 973ce738..1e530ce0 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -716,7 +716,7 @@ class OrderConfirmationView(DetailView, FormView): request.session.get('token') ) logger.debug( - "card_details=%s stripe_api_cus_id=%s" % (card_details)) + "card_details=%s" % (card_details)) if not card_details.get('response_object'): msg = card_details.get('error') messages.add_message(self.request, messages.ERROR, msg, From 352c780287e8c45230764e4fb8720c8a89e0f400 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 7 Dec 2020 07:53:20 +0530 Subject: [PATCH 055/257] Work in progress --- datacenterlight/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 1e530ce0..32e62f3c 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -956,9 +956,9 @@ class OrderConfirmationView(DetailView, FormView): logger.debug(stripe_subscription_obj) latest_invoice = stripe.Invoice.retrieve( stripe_subscription_obj.latest_invoice) - ret = stripe.PaymentIntent.confirm( - latest_invoice.payment_intent - ) + # ret = stripe.PaymentIntent.confirm( + # latest_invoice.payment_intent + # ) if ret.status == 'requires_source_action' or ret.status == 'requires_action': pi = stripe.PaymentIntent.retrieve( latest_invoice.payment_intent From 17557fd4c98d36ed626914695dc0e17702769333 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 7 Dec 2020 09:52:53 +0530 Subject: [PATCH 056/257] Create refactored method handle_metadata_and_emails --- .../commands/fix_vm_after_celery_error.py | 0 datacenterlight/tasks.py | 232 ++++++++++-------- 2 files changed, 126 insertions(+), 106 deletions(-) create mode 100644 datacenterlight/management/commands/fix_vm_after_celery_error.py diff --git a/datacenterlight/management/commands/fix_vm_after_celery_error.py b/datacenterlight/management/commands/fix_vm_after_celery_error.py new file mode 100644 index 00000000..e69de29b diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index f080da90..9c98b729 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -56,11 +56,6 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): "Running create_vm_task on {}".format(current_task.request.hostname)) vm_id = None try: - final_price = ( - specs.get('total_price') if 'total_price' in specs - else specs.get('price') - ) - if 'pass' in user: on_user = user.get('username') on_pass = user.get('pass') @@ -92,107 +87,8 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): if vm_id is None: raise Exception("Could not create VM") - # Update HostingOrder with the created vm_id - hosting_order = HostingOrder.objects.filter(id=order_id).first() - error_msg = None - - try: - hosting_order.vm_id = vm_id - hosting_order.save() - logger.debug( - "Updated hosting_order {} with vm_id={}".format( - hosting_order.id, vm_id - ) - ) - except Exception as ex: - error_msg = ( - "HostingOrder with id {order_id} not found. This means that " - "the hosting order was not created and/or it is/was not " - "associated with VM with id {vm_id}. Details {details}".format( - order_id=order_id, vm_id=vm_id, details=str(ex) - ) - ) - logger.error(error_msg) - - stripe_utils = StripeUtils() - result = stripe_utils.set_subscription_metadata( - subscription_id=hosting_order.subscription_id, - metadata={"VM_ID": str(vm_id)} - ) - - if result.get('error') is not None: - emsg = "Could not update subscription metadata for {sub}".format( - sub=hosting_order.subscription_id - ) - logger.error(emsg) - if error_msg: - error_msg += ". " + emsg - else: - error_msg = emsg - - vm = VirtualMachineSerializer(manager.get_vm(vm_id)).data - - context = { - 'name': user.get('name'), - 'email': user.get('email'), - 'cores': specs.get('cpu'), - 'memory': specs.get('memory'), - 'storage': specs.get('disk_size'), - 'price': final_price, - 'template': template.get('name'), - 'vm_name': vm.get('name'), - 'vm_id': vm['vm_id'], - 'order_id': order_id - } - - if error_msg: - context['errors'] = error_msg - if 'pricing_name' in specs: - context['pricing'] = str(VMPricing.get_vm_pricing_by_name( - name=specs['pricing_name'] - )) - email_data = { - 'subject': settings.DCL_TEXT + " Order from %s" % context['email'], - 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, - 'to': ['info@ungleich.ch'], - 'body': "\n".join( - ["%s=%s" % (k, v) for (k, v) in context.items()]), - 'reply_to': [context['email']], - } - email = EmailMessage(**email_data) - email.send() - - if 'pass' in user: - lang = 'en-us' - if user.get('language') is not None: - logger.debug( - "Language is set to {}".format(user.get('language'))) - lang = user.get('language') - translation.activate(lang) - # Send notification to the user as soon as VM has been booked - context = { - 'base_url': "{0}://{1}".format(user.get('request_scheme'), - user.get('request_host')), - 'order_url': reverse('hosting:invoices'), - 'page_header': _( - 'Your New VM %(vm_name)s at Data Center Light') % { - 'vm_name': vm.get('name')}, - 'vm_name': vm.get('name') - } - email_data = { - 'subject': context.get('page_header'), - 'to': user.get('email'), - 'context': context, - 'template_name': 'new_booked_vm', - 'template_path': 'hosting/emails/', - 'from_address': settings.DCL_SUPPORT_FROM_ADDRESS, - } - email = BaseEmail(**email_data) - email.send() - - logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id)) - if vm_id > 0: - get_or_create_vm_detail(custom_user, manager, vm_id) + handle_metadata_and_emails(order_id, vm_id, manager, user, specs, + template) except Exception as e: logger.error(str(e)) try: @@ -214,3 +110,127 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): return return vm_id + + +def handle_metadata_and_emails(order_id, vm_id, manager, user, specs, + template): + """ + Handle's setting up of the metadata in Stripe and database and sending of + emails to the user after VM creation + + :param order_id: the hosting order id + :param vm_id: the id of the vm created + :param manager: the OpenNebula Manager instance + :param user: the user's dict passed to the celery task + :param specs: the specification's dict passed to the celery task + :param template: the template dict passed to the celery task + + :return: + """ + + custom_user = CustomUser.objects.get(email=user.get('email')) + final_price = ( + specs.get('total_price') if 'total_price' in specs + else specs.get('price') + ) + # Update HostingOrder with the created vm_id + hosting_order = HostingOrder.objects.filter(id=order_id).first() + error_msg = None + + try: + hosting_order.vm_id = vm_id + hosting_order.save() + logger.debug( + "Updated hosting_order {} with vm_id={}".format( + hosting_order.id, vm_id + ) + ) + except Exception as ex: + error_msg = ( + "HostingOrder with id {order_id} not found. This means that " + "the hosting order was not created and/or it is/was not " + "associated with VM with id {vm_id}. Details {details}".format( + order_id=order_id, vm_id=vm_id, details=str(ex) + ) + ) + logger.error(error_msg) + + stripe_utils = StripeUtils() + result = stripe_utils.set_subscription_metadata( + subscription_id=hosting_order.subscription_id, + metadata={"VM_ID": str(vm_id)} + ) + + if result.get('error') is not None: + emsg = "Could not update subscription metadata for {sub}".format( + sub=hosting_order.subscription_id + ) + logger.error(emsg) + if error_msg: + error_msg += ". " + emsg + else: + error_msg = emsg + + vm = VirtualMachineSerializer(manager.get_vm(vm_id)).data + + context = { + 'name': user.get('name'), + 'email': user.get('email'), + 'cores': specs.get('cpu'), + 'memory': specs.get('memory'), + 'storage': specs.get('disk_size'), + 'price': final_price, + 'template': template.get('name'), + 'vm_name': vm.get('name'), + 'vm_id': vm['vm_id'], + 'order_id': order_id + } + + if error_msg: + context['errors'] = error_msg + if 'pricing_name' in specs: + context['pricing'] = str(VMPricing.get_vm_pricing_by_name( + name=specs['pricing_name'] + )) + email_data = { + 'subject': settings.DCL_TEXT + " Order from %s" % context['email'], + 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': ['info@ungleich.ch'], + 'body': "\n".join( + ["%s=%s" % (k, v) for (k, v) in context.items()]), + 'reply_to': [context['email']], + } + email = EmailMessage(**email_data) + email.send() + + if 'pass' in user: + lang = 'en-us' + if user.get('language') is not None: + logger.debug( + "Language is set to {}".format(user.get('language'))) + lang = user.get('language') + translation.activate(lang) + # Send notification to the user as soon as VM has been booked + context = { + 'base_url': "{0}://{1}".format(user.get('request_scheme'), + user.get('request_host')), + 'order_url': reverse('hosting:invoices'), + 'page_header': _( + 'Your New VM %(vm_name)s at Data Center Light') % { + 'vm_name': vm.get('name')}, + 'vm_name': vm.get('name') + } + email_data = { + 'subject': context.get('page_header'), + 'to': user.get('email'), + 'context': context, + 'template_name': 'new_booked_vm', + 'template_path': 'hosting/emails/', + 'from_address': settings.DCL_SUPPORT_FROM_ADDRESS, + } + email = BaseEmail(**email_data) + email.send() + + logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id)) + if vm_id > 0: + get_or_create_vm_detail(custom_user, manager, vm_id) \ No newline at end of file From 082c0b00affbc3c7c49ae13727d702931a425fcf Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 7 Dec 2020 09:53:14 +0530 Subject: [PATCH 057/257] Implement fix_vm_after_celery_error --- .../commands/fix_vm_after_celery_error.py | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/datacenterlight/management/commands/fix_vm_after_celery_error.py b/datacenterlight/management/commands/fix_vm_after_celery_error.py index e69de29b..97df553e 100644 --- a/datacenterlight/management/commands/fix_vm_after_celery_error.py +++ b/datacenterlight/management/commands/fix_vm_after_celery_error.py @@ -0,0 +1,71 @@ +from django.core.management.base import BaseCommand +from datacenterlight.tasks import handle_metadata_and_emails +from opennebula_api.models import OpenNebulaManager +import logging +import json + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + help = '''Updates the DB after manual creation of VM''' + + def add_arguments(self, parser): + parser.add_argument('vm_id', type=int) + parser.add_argument('order_id', type=int) + parser.add_argument('specs', type=str) + + def handle(self, *args, **options): + vm_id = options['vm_id'] + order_id = options['order_id'] + user_str = options['user'] + specs_str = options['specs'] + template_str = options['template'] + + json_acceptable_string = user_str.replace("'", "\"") + user = json.loads(json_acceptable_string) + + json_acceptable_string = specs_str.replace("'", "\"") + specs = json.loads(json_acceptable_string) + + json_acceptable_string = template_str.replace("'", "\"") + template = json.loads(json_acceptable_string) + if vm_id <= 0: + self.stdout.write(self.style.ERROR( + 'vm_id can\'t be less than or 0. Given: %s' % vm_id)) + return + if vm_id <= 0: + self.stdout.write(self.style.ERROR( + 'order_id can\'t be less than or 0. Given: %s' % vm_id)) + return + if specs_str is None or specs_str == "": + self.stdout.write( + self.style.ERROR('specs can\'t be empty or None')) + return + + user = { + 'name': user['name'], + 'email': user['email'], + 'username': user['username'], + 'pass': user['password'], + 'request_scheme': user['request_scheme'], + 'request_host': user['request_host'], + 'language': user['language'], + } + + on_user = user.get('username') + on_pass = user.get('pass') + + # Create OpenNebulaManager + manager = OpenNebulaManager(email=on_user, password=on_pass) + handle_metadata_and_emails(order_id, vm_id, manager, user, specs, + template) + self.stdout.write( + self.style.SUCCESS( + 'Done handling metadata and emails for %s %s %s' % ( + order_id, + vm_id, + str(user) + ) + ) + ) From 81ba834b0178c2b75707487ab14ee65d9c8b3c61 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 7 Dec 2020 10:31:45 +0530 Subject: [PATCH 058/257] Add missing arguments --- .../management/commands/fix_vm_after_celery_error.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/datacenterlight/management/commands/fix_vm_after_celery_error.py b/datacenterlight/management/commands/fix_vm_after_celery_error.py index 97df553e..2f7325f4 100644 --- a/datacenterlight/management/commands/fix_vm_after_celery_error.py +++ b/datacenterlight/management/commands/fix_vm_after_celery_error.py @@ -13,7 +13,9 @@ class Command(BaseCommand): def add_arguments(self, parser): parser.add_argument('vm_id', type=int) parser.add_argument('order_id', type=int) + parser.add_argument('user', type=str) parser.add_argument('specs', type=str) + parser.add_argument('template', type=str) def handle(self, *args, **options): vm_id = options['vm_id'] From 591f5ff37b5308f21ce7001df0fc9adf712c32d2 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 7 Dec 2020 10:37:00 +0530 Subject: [PATCH 059/257] Fix key --- .../management/commands/fix_vm_after_celery_error.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/management/commands/fix_vm_after_celery_error.py b/datacenterlight/management/commands/fix_vm_after_celery_error.py index 2f7325f4..1ba37152 100644 --- a/datacenterlight/management/commands/fix_vm_after_celery_error.py +++ b/datacenterlight/management/commands/fix_vm_after_celery_error.py @@ -49,7 +49,7 @@ class Command(BaseCommand): 'name': user['name'], 'email': user['email'], 'username': user['username'], - 'pass': user['password'], + 'pass': user['pass'], 'request_scheme': user['request_scheme'], 'request_host': user['request_host'], 'language': user['language'], From bbb51b71a6f200913428e09af2a59f707135bb48 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 7 Dec 2020 10:40:55 +0530 Subject: [PATCH 060/257] Fix variable name --- .../commands/fix_vm_after_celery_error.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/datacenterlight/management/commands/fix_vm_after_celery_error.py b/datacenterlight/management/commands/fix_vm_after_celery_error.py index 1ba37152..f64963f6 100644 --- a/datacenterlight/management/commands/fix_vm_after_celery_error.py +++ b/datacenterlight/management/commands/fix_vm_after_celery_error.py @@ -25,7 +25,7 @@ class Command(BaseCommand): template_str = options['template'] json_acceptable_string = user_str.replace("'", "\"") - user = json.loads(json_acceptable_string) + user_dict = json.loads(json_acceptable_string) json_acceptable_string = specs_str.replace("'", "\"") specs = json.loads(json_acceptable_string) @@ -46,13 +46,13 @@ class Command(BaseCommand): return user = { - 'name': user['name'], - 'email': user['email'], - 'username': user['username'], - 'pass': user['pass'], - 'request_scheme': user['request_scheme'], - 'request_host': user['request_host'], - 'language': user['language'], + 'name': user_dict['name'], + 'email': user_dict['email'], + 'username': user_dict['username'], + 'pass': user_dict['pass'], + 'request_scheme': user_dict['request_scheme'], + 'request_host': user_dict['request_host'], + 'language': user_dict['language'], } on_user = user.get('username') From ff28c6e8e8c6ded30c6792207475b1112a3e5a41 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 7 Dec 2020 10:44:20 +0530 Subject: [PATCH 061/257] Add log message --- .../management/commands/fix_vm_after_celery_error.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/datacenterlight/management/commands/fix_vm_after_celery_error.py b/datacenterlight/management/commands/fix_vm_after_celery_error.py index f64963f6..94c59868 100644 --- a/datacenterlight/management/commands/fix_vm_after_celery_error.py +++ b/datacenterlight/management/commands/fix_vm_after_celery_error.py @@ -59,6 +59,9 @@ class Command(BaseCommand): on_pass = user.get('pass') # Create OpenNebulaManager + self.style.SUCCESS( + 'Connecting using %s %s' % (on_user, on_pass) + ) manager = OpenNebulaManager(email=on_user, password=on_pass) handle_metadata_and_emails(order_id, vm_id, manager, user, specs, template) From 8c374af4ffa96f880e2ccac6f5418849d3fe738d Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 7 Dec 2020 10:45:51 +0530 Subject: [PATCH 062/257] More logging --- datacenterlight/management/commands/fix_vm_after_celery_error.py | 1 + 1 file changed, 1 insertion(+) diff --git a/datacenterlight/management/commands/fix_vm_after_celery_error.py b/datacenterlight/management/commands/fix_vm_after_celery_error.py index 94c59868..b8c71f8d 100644 --- a/datacenterlight/management/commands/fix_vm_after_celery_error.py +++ b/datacenterlight/management/commands/fix_vm_after_celery_error.py @@ -62,6 +62,7 @@ class Command(BaseCommand): self.style.SUCCESS( 'Connecting using %s %s' % (on_user, on_pass) ) + print('Connecting using %s %s' % (on_user, on_pass)) manager = OpenNebulaManager(email=on_user, password=on_pass) handle_metadata_and_emails(order_id, vm_id, manager, user, specs, template) From 9d85c058da82aad3a2026e670b9ddcfb620ad3a5 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 7 Dec 2020 10:48:48 +0530 Subject: [PATCH 063/257] Fetch correct cred --- .../management/commands/fix_vm_after_celery_error.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/datacenterlight/management/commands/fix_vm_after_celery_error.py b/datacenterlight/management/commands/fix_vm_after_celery_error.py index b8c71f8d..3286db3a 100644 --- a/datacenterlight/management/commands/fix_vm_after_celery_error.py +++ b/datacenterlight/management/commands/fix_vm_after_celery_error.py @@ -1,6 +1,7 @@ from django.core.management.base import BaseCommand from datacenterlight.tasks import handle_metadata_and_emails from opennebula_api.models import OpenNebulaManager +from membership.models import CustomUser import logging import json @@ -54,16 +55,13 @@ class Command(BaseCommand): 'request_host': user_dict['request_host'], 'language': user_dict['language'], } - - on_user = user.get('username') - on_pass = user.get('pass') - + cu = CustomUser.objects.get(username=user.get('username')) # Create OpenNebulaManager self.style.SUCCESS( - 'Connecting using %s %s' % (on_user, on_pass) + 'Connecting using %s %s' % (cu.username, cu.password) ) - print('Connecting using %s %s' % (on_user, on_pass)) - manager = OpenNebulaManager(email=on_user, password=on_pass) + print('Connecting using %s %s' % (cu.username, cu.password)) + manager = OpenNebulaManager(email=cu.username, password=cu.password) handle_metadata_and_emails(order_id, vm_id, manager, user, specs, template) self.stdout.write( From 7bcca15f0b9fc4b6755774b9e78b67198e291ee7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 7 Dec 2020 10:52:06 +0530 Subject: [PATCH 064/257] Cleanup logging --- .../management/commands/fix_vm_after_celery_error.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/datacenterlight/management/commands/fix_vm_after_celery_error.py b/datacenterlight/management/commands/fix_vm_after_celery_error.py index 3286db3a..0cfdb423 100644 --- a/datacenterlight/management/commands/fix_vm_after_celery_error.py +++ b/datacenterlight/management/commands/fix_vm_after_celery_error.py @@ -57,10 +57,11 @@ class Command(BaseCommand): } cu = CustomUser.objects.get(username=user.get('username')) # Create OpenNebulaManager - self.style.SUCCESS( - 'Connecting using %s %s' % (cu.username, cu.password) + self.stdout.write( + self.style.SUCCESS( + 'Connecting using %s' % (cu.username) + ) ) - print('Connecting using %s %s' % (cu.username, cu.password)) manager = OpenNebulaManager(email=cu.username, password=cu.password) handle_metadata_and_emails(order_id, vm_id, manager, user, specs, template) From a0ab436d9abeba91d0bf3f21babdb368454b48c9 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 7 Dec 2020 11:36:34 +0530 Subject: [PATCH 065/257] Update Changelog for 2.14 --- Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog b/Changelog index fdadadf1..b54713fe 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,5 @@ +2.14: 2020-12-07 + * 8692: Create a script that fixes django db for the order after celery error (MR!743) 2.13: 2020-12-02 * 8654: Fix 500 error on invoices list for the user contact+devuanhosting.com@virus.media (MR!742) * 8593: Escape user's ssh key in xml-rpc call to create VM (MR!741) From 890a83cfa69335564964c421473cb2f5d23d72fb Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 10 Dec 2020 08:34:17 +0530 Subject: [PATCH 066/257] Add check_vm_templates management command --- .../management/commands/check_vm_templates.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 datacenterlight/management/commands/check_vm_templates.py diff --git a/datacenterlight/management/commands/check_vm_templates.py b/datacenterlight/management/commands/check_vm_templates.py new file mode 100644 index 00000000..ee86f15d --- /dev/null +++ b/datacenterlight/management/commands/check_vm_templates.py @@ -0,0 +1,62 @@ +from django.core.management.base import BaseCommand +from opennebula_api.models import OpenNebulaManager +from datacenterlight.models import VMTemplate +from membership.models import CustomUser + +from django.conf import settings +from time import sleep +import datetime +import json +import logging +import os + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + help = '''Checks all VM templates to find if they can be instantiated''' + + def add_arguments(self, parser): + parser.add_argument('user_email', type=str) + + def handle(self, *args, **options): + result_dict = {} + user_email = options['user_email'] if 'user_email' in options else "" + + if user_email: + cu = CustomUser.objects.get(email=user_email) + specs = {'cpu': 1, 'memory': 1, 'disk_size': 10} + manager = OpenNebulaManager(email=user_email, password=cu.password) + pub_keys = [settings.TEST_MANAGE_SSH_KEY_PUBKEY] + if not os.path.exists("outputs"): + os.mkdir("outputs") + for vm_template in VMTemplate.objects.all(): + vm_name = 'test-%s' % vm_template.name + vm_id = manager.create_vm( + template_id=vm_template.opennebula_vm_template_id, + specs=specs, + ssh_key='\n'.join(pub_keys), + vm_name=vm_name + ) + if vm_id > 0: + result_dict[vm_name] = "%s OK, created VM %s" % ( + '%s %s' % (vm_template.opennebula_vm_template_id, + vm_template.name), + vm_id + ) + self.stdout.write(self.style.SUCCESS(result_dict[vm_name])) + manager.delete_vm(vm_id) + else: + result_dict[vm_name] = '''Error creating VM %s, template_id + %s ''' % (vm_name, vm_template.opennebula_vm_template_id) + self.stdout.write(self.style.ERROR(result_dict[vm_name])) + sleep(1) + date_str = datetime.datetime.strftime( + datetime.datetime.now(), '%Y%m%d%H%M%S' + ) + with open("output/check_vm_templates_%s.txt" % date_str, 'w', + encoding='utf-8') as f: + f.write(json.dumps(result_dict)) + else: + self.stdout.write(self.style.ERROR("user_email not supplied")) + self.stdout.write(self.style.SUCCESS("Done")) From 785091e4ff80ab17e439165600ad0fa75ceac395 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 10 Dec 2020 08:40:40 +0530 Subject: [PATCH 067/257] Handle vm_id is None case --- datacenterlight/management/commands/check_vm_templates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/management/commands/check_vm_templates.py b/datacenterlight/management/commands/check_vm_templates.py index ee86f15d..b75e9751 100644 --- a/datacenterlight/management/commands/check_vm_templates.py +++ b/datacenterlight/management/commands/check_vm_templates.py @@ -38,7 +38,7 @@ class Command(BaseCommand): ssh_key='\n'.join(pub_keys), vm_name=vm_name ) - if vm_id > 0: + if vm_id and vm_id > 0: result_dict[vm_name] = "%s OK, created VM %s" % ( '%s %s' % (vm_template.opennebula_vm_template_id, vm_template.name), From 01d8cc1b9bfebf5b5369cf4e79933e24ac5ba0c8 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 10 Dec 2020 08:56:34 +0530 Subject: [PATCH 068/257] Use absolute paths --- .../management/commands/check_vm_templates.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/datacenterlight/management/commands/check_vm_templates.py b/datacenterlight/management/commands/check_vm_templates.py index b75e9751..6346be50 100644 --- a/datacenterlight/management/commands/check_vm_templates.py +++ b/datacenterlight/management/commands/check_vm_templates.py @@ -28,8 +28,9 @@ class Command(BaseCommand): specs = {'cpu': 1, 'memory': 1, 'disk_size': 10} manager = OpenNebulaManager(email=user_email, password=cu.password) pub_keys = [settings.TEST_MANAGE_SSH_KEY_PUBKEY] - if not os.path.exists("outputs"): - os.mkdir("outputs") + PROJECT_PATH = os.path.abspath(os.path.dirname(__name__)) + if not os.path.exists("%s/outputs" % PROJECT_PATH): + os.mkdir("%s/outputs" % PROJECT_PATH) for vm_template in VMTemplate.objects.all(): vm_name = 'test-%s' % vm_template.name vm_id = manager.create_vm( @@ -54,7 +55,9 @@ class Command(BaseCommand): date_str = datetime.datetime.strftime( datetime.datetime.now(), '%Y%m%d%H%M%S' ) - with open("output/check_vm_templates_%s.txt" % date_str, 'w', + with open("%s/outputs/check_vm_templates_%s.txt" % + (PROJECT_PATH, date_str), + 'w', encoding='utf-8') as f: f.write(json.dumps(result_dict)) else: From 22d3b1f83c465dd1bbda416c731de1b3692dfba4 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 10 Dec 2020 09:05:04 +0530 Subject: [PATCH 069/257] Add vm_type to the log info --- datacenterlight/management/commands/check_vm_templates.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/datacenterlight/management/commands/check_vm_templates.py b/datacenterlight/management/commands/check_vm_templates.py index 6346be50..6424df80 100644 --- a/datacenterlight/management/commands/check_vm_templates.py +++ b/datacenterlight/management/commands/check_vm_templates.py @@ -41,15 +41,17 @@ class Command(BaseCommand): ) if vm_id and vm_id > 0: result_dict[vm_name] = "%s OK, created VM %s" % ( - '%s %s' % (vm_template.opennebula_vm_template_id, - vm_template.name), + '%s %s %s' % (vm_template.opennebula_vm_template_id, + vm_template.name, vm_template.vm_type), vm_id ) self.stdout.write(self.style.SUCCESS(result_dict[vm_name])) manager.delete_vm(vm_id) else: result_dict[vm_name] = '''Error creating VM %s, template_id - %s ''' % (vm_name, vm_template.opennebula_vm_template_id) + %s %s''' % (vm_name, + vm_template.opennebula_vm_template_id, + vm_template.vm_type) self.stdout.write(self.style.ERROR(result_dict[vm_name])) sleep(1) date_str = datetime.datetime.strftime( From 57b6b18243306b534db1306054996c6fe1d93cc2 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 10 Dec 2020 10:04:04 +0530 Subject: [PATCH 070/257] Fix PEP warning --- datacenterlight/management/commands/check_vm_templates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/management/commands/check_vm_templates.py b/datacenterlight/management/commands/check_vm_templates.py index 6424df80..be0a1957 100644 --- a/datacenterlight/management/commands/check_vm_templates.py +++ b/datacenterlight/management/commands/check_vm_templates.py @@ -42,7 +42,7 @@ class Command(BaseCommand): if vm_id and vm_id > 0: result_dict[vm_name] = "%s OK, created VM %s" % ( '%s %s %s' % (vm_template.opennebula_vm_template_id, - vm_template.name, vm_template.vm_type), + vm_template.name, vm_template.vm_type), vm_id ) self.stdout.write(self.style.SUCCESS(result_dict[vm_name])) From 504681107af92d60d566a33eda7635aa49e7b496 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 10 Dec 2020 10:04:16 +0530 Subject: [PATCH 071/257] Remove unreachable code --- datacenterlight/management/commands/check_vm_templates.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/datacenterlight/management/commands/check_vm_templates.py b/datacenterlight/management/commands/check_vm_templates.py index be0a1957..db36fde8 100644 --- a/datacenterlight/management/commands/check_vm_templates.py +++ b/datacenterlight/management/commands/check_vm_templates.py @@ -62,6 +62,4 @@ class Command(BaseCommand): 'w', encoding='utf-8') as f: f.write(json.dumps(result_dict)) - else: - self.stdout.write(self.style.ERROR("user_email not supplied")) self.stdout.write(self.style.SUCCESS("Done")) From 0b0c932e5a115d145980e58f1888af14e7dac98e Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 18 Dec 2020 16:45:16 +0530 Subject: [PATCH 072/257] Refactor code: show_error --- datacenterlight/views.py | 95 ++++++++++++---------------------------- 1 file changed, 29 insertions(+), 66 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 32e62f3c..a3a027cc 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -719,28 +719,7 @@ class OrderConfirmationView(DetailView, FormView): "card_details=%s" % (card_details)) if not card_details.get('response_object'): msg = card_details.get('error') - messages.add_message(self.request, messages.ERROR, msg, - extra_tags='failed_payment') - response = { - 'status': False, - 'redirect': "{url}#{section}".format( - url=(reverse( - 'show_product', - kwargs={'product_slug': - request.session['generic_payment_details'] - ['product_slug']} - ) if 'generic_payment_details' in request.session else - reverse('datacenterlight:payment') - ), - section='payment_error'), - 'msg_title': str(_('Error.')), - 'msg_body': str( - _('There was a payment related error.' - ' On close of this popup, you will be' - ' redirected back to the payment page.') - ) - } - return JsonResponse(response) + return show_error(msg, self.request) card_details_response = card_details['response_object'] card_details_dict = { 'last4': card_details_response['last4'], @@ -765,30 +744,7 @@ class OrderConfirmationView(DetailView, FormView): details=acc_result['error'] ) ) - messages.add_message(self.request, messages.ERROR, msg, - extra_tags='failed_payment') - response = { - 'status': False, - 'redirect': "{url}#{section}".format( - url=(reverse( - 'show_product', - kwargs={'product_slug': - request.session - ['generic_payment_details'] - ['product_slug']} - ) if 'generic_payment_details' in - request.session else - reverse('datacenterlight:payment') - ), - section='payment_error'), - 'msg_title': str(_('Error.')), - 'msg_body': str( - _('There was a payment related error.' - ' On close of this popup, you will be redirected' - ' back to the payment page.') - ) - } - return JsonResponse(response) + return show_error(msg, self.request) elif 'card_id' in request.session: card_id = request.session.get('card_id') user_card_detail = UserCardDetail.objects.get(id=card_id) @@ -831,26 +787,7 @@ class OrderConfirmationView(DetailView, FormView): # Check if the payment was approved if not stripe_onetime_charge: msg = charge_response.get('error') - messages.add_message(self.request, messages.ERROR, msg, - extra_tags='failed_payment') - response = { - 'status': False, - 'redirect': "{url}#{section}".format( - url=(reverse('show_product', kwargs={ - 'product_slug': gp_details['product_slug']} - ) if 'generic_payment_details' in - request.session else - reverse('datacenterlight:payment') - ), - section='payment_error'), - 'msg_title': str(_('Error.')), - 'msg_body': str( - _('There was a payment related error.' - ' On close of this popup, you will be redirected' - ' back to the payment page.')) - } - return JsonResponse(response) - + return show_error(msg, self.request) if ('generic_payment_type' not in request.session or (request.session['generic_payment_details']['recurring'])): recurring_interval = 'month' @@ -1222,3 +1159,29 @@ class OrderConfirmationView(DetailView, FormView): } return JsonResponse(response) + + +def show_error(msg, request): + messages.add_message(request, messages.ERROR, msg, + extra_tags='failed_payment') + response = { + 'status': False, + 'redirect': "{url}#{section}".format( + url=(reverse( + 'show_product', + kwargs={'product_slug': + request.session['generic_payment_details'] + ['product_slug']} + ) if 'generic_payment_details' in request.session else + reverse('datacenterlight:payment') + ), + section='payment_error' + ), + 'msg_title': str(_('Error.')), + 'msg_body': str( + _('There was a payment related error.' + ' On close of this popup, you will be redirected back to' + ' the payment page.')) + } + return JsonResponse(response) + From d0d5fb01967afc2ed7aa69ec9a280c72d8945653 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 18 Dec 2020 16:47:45 +0530 Subject: [PATCH 073/257] Handle payment_intent requires SCA case --- datacenterlight/views.py | 63 +++++++++++++++------------------------- 1 file changed, 23 insertions(+), 40 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index a3a027cc..abf0814a 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -783,7 +783,6 @@ class OrderConfirmationView(DetailView, FormView): customer=stripe_api_cus_id ) stripe_onetime_charge = charge_response.get('response_object') - # Check if the payment was approved if not stripe_onetime_charge: msg = charge_response.get('error') @@ -893,52 +892,37 @@ class OrderConfirmationView(DetailView, FormView): logger.debug(stripe_subscription_obj) latest_invoice = stripe.Invoice.retrieve( stripe_subscription_obj.latest_invoice) - # ret = stripe.PaymentIntent.confirm( - # latest_invoice.payment_intent - # ) - if ret.status == 'requires_source_action' or ret.status == 'requires_action': - pi = stripe.PaymentIntent.retrieve( - latest_invoice.payment_intent - ) - context = { - 'sid': stripe_subscription_obj.id, - 'payment_intent_secret': pi.client_secret, - 'STRIPE_PUBLISHABLE_KEY': settings.STRIPE_API_PUBLIC_KEY, - 'showSCA': True - } - return JsonResponse(context) + # Check if the subscription was approved and is active if (stripe_subscription_obj is None - or stripe_subscription_obj.status != 'active'): + or (stripe_subscription_obj.status != 'active' + and stripe_subscription_obj.status != 'incomplete')): # 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 # here. # ... - msg = subscription_result.get('error') - messages.add_message(self.request, messages.ERROR, msg, - extra_tags='failed_payment') - response = { - 'status': False, - 'redirect': "{url}#{section}".format( - url=(reverse( - 'show_product', - kwargs={'product_slug': - request.session['generic_payment_details'] - ['product_slug']} - ) if 'generic_payment_details' in request.session else - reverse('datacenterlight:payment') - ), - section='payment_error' - ), - 'msg_title': str(_('Error.')), - 'msg_body': str( - _('There was a payment related error.' - ' On close of this popup, you will be redirected back to' - ' the payment page.')) - } - return JsonResponse(response) + return show_error(msg, self.request) + elif stripe_subscription_obj.status == 'incomplete': + pi = stripe.PaymentIntent.retrieve( + latest_invoice.payment_intent + ) + # TODO: requires_attention is probably wrong value to compare + if (pi.status == 'requires_attention' or + pi.status == 'requires_source_action'): + logger.debug("Display SCA authentication") + context = { + 'sid': stripe_subscription_obj.id, + 'payment_intent_secret': pi.client_secret, + 'STRIPE_PUBLISHABLE_KEY': settings.STRIPE_API_PUBLIC_KEY, + 'showSCA': True + } + return JsonResponse(context) + else: + logger.debug("Handle this case") + msg = subscription_result.get('error') + return show_error(msg, self.request) # Create user if the user is not logged in and if he is not already # registered @@ -1184,4 +1168,3 @@ def show_error(msg, request): ' the payment page.')) } return JsonResponse(response) - From a63fac1a2081378ebed59b4a4d4232d081bf223f Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 18 Dec 2020 17:16:40 +0530 Subject: [PATCH 074/257] Set data at the client side according to success or error --- datacenterlight/views.py | 36 ++++++++++++++++++- .../hosting/js/virtual_machine_detail.js | 14 +++++--- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index abf0814a..77c0444f 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -916,7 +916,41 @@ class OrderConfirmationView(DetailView, FormView): 'sid': stripe_subscription_obj.id, 'payment_intent_secret': pi.client_secret, 'STRIPE_PUBLISHABLE_KEY': settings.STRIPE_API_PUBLIC_KEY, - 'showSCA': True + 'showSCA': True, + 'success': { + 'status': True, + 'redirect': ( + reverse('hosting:virtual_machines') + if request.user.is_authenticated() + else reverse('datacenterlight:index') + ), + 'msg_title': str(_('Thank you for the order.')), + 'msg_body': str( + _('Your VM will be up and running in a few moments.' + ' We will send you a confirmation email as soon as' + ' it is ready.')) + }, + 'error': { + 'status': False, + 'redirect': "{url}#{section}".format( + url=(reverse( + 'show_product', + kwargs={'product_slug': + request.session[ + 'generic_payment_details'] + ['product_slug']} + ) if 'generic_payment_details' in request.session else + reverse('datacenterlight:payment') + ), + section='payment_error' + ), + 'msg_title': str(_('Error.')), + 'msg_body': str( + _('There was a payment related error.' + ' On close of this popup, you will be redirected back to' + ' the payment page.') + ) + } } return JsonResponse(context) else: diff --git a/hosting/static/hosting/js/virtual_machine_detail.js b/hosting/static/hosting/js/virtual_machine_detail.js index db1faaf8..dec8e680 100644 --- a/hosting/static/hosting/js/virtual_machine_detail.js +++ b/hosting/static/hosting/js/virtual_machine_detail.js @@ -110,16 +110,20 @@ $(document).ready(function() { if (data.showSCA){ console.log("Show SCA"); var stripe = Stripe(data.STRIPE_PUBLISHABLE_KEY); - stripe.confirmCardPayment(data.payment_intent_secret).then(function(result) { if (result.error) { // Display error.message in your UI. - $("#3ds_result").text("Error!"); - $("#3ds_result").addClass("text-danger"); + modal_btn.attr('href', data.error.redirect).removeClass('hide'); + fa_icon.attr('class', 'fa fa-close'); + modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide'); + $('#createvm-modal-title').text(data.error.msg_title); + $('#createvm-modal-body').html(data.error.msg_body); } else { // The payment has succeeded. Display a success message. - $("#3ds_result").text("Thank you for payment"); - $("#3ds_result").addClass("text-success"); + modal_btn.attr('href', data.success.redirect).removeClass('hide'); + fa_icon.attr('class', 'checkmark'); + $('#createvm-modal-title').text(data.success.msg_title); + $('#createvm-modal-body').html(data.success.msg_body); } }); $('#3Dsecure-modal').show(); From 3389e69af101e8f3e0c1144092443e14f064e653 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 18 Dec 2020 17:34:40 +0530 Subject: [PATCH 075/257] WIP: Begin handling of invoice.paid webhook --- webhook/views.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/webhook/views.py b/webhook/views.py index 516d1afc..ad598805 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -111,8 +111,15 @@ def handle_webhook(request): 'to': settings.DCL_ERROR_EMAILS_TO_LIST, 'body': "Response = %s" % str(tax_id_obj), } - send_plain_email_task.delay(email_data) + elif event.type == 'invoice.paid': + #https://stripe.com/docs/billing/migration/strong-customer-authentication#scenario-1-handling-fulfillment + invoice_obj = event.data.object + logger.debug("Webhook Event: invoice.paid") + logger.debug("invoice_obj %s " % str(invoice_obj)) + if invoice_obj.paid and invoice_obj.billing_reason == "subscription_create": + logger.debug("Start provisioning") + else: logger.error("Unhandled event : " + event.type) return HttpResponse(status=200) From cb7a1ed4f4fff84c11cf94a8b0b6e060be9de402 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 20 Dec 2020 02:41:30 +0530 Subject: [PATCH 076/257] Implement provisioning of VM on invoice.paid webhook --- datacenterlight/views.py | 420 ++++++++++++++++++++------------------- webhook/views.py | 31 ++- 2 files changed, 247 insertions(+), 204 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 77c0444f..e04f5234 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -710,6 +710,7 @@ class OrderConfirmationView(DetailView, FormView): stripe_api_cus_id = request.session.get('customer') stripe_utils = StripeUtils() logger.debug("user=%s stripe_api_cus_id=%s" % (user, stripe_api_cus_id)) + card_details_response = None if 'token' in request.session: card_details = stripe_utils.get_cards_details_from_token( @@ -954,212 +955,18 @@ class OrderConfirmationView(DetailView, FormView): } return JsonResponse(context) else: - logger.debug("Handle this case") + logger.debug( + "Handle this case when " + "stripe.subscription_status is incomplete but " + "pi.status is neither requires_attention nor " + "requires_source_action") msg = subscription_result.get('error') return show_error(msg, self.request) - # Create user if the user is not logged in and if he is not already - # registered - if not request.user.is_authenticated(): - try: - custom_user = CustomUser.objects.get( - email=user.get('email')) - stripe_customer = StripeCustomer.objects.filter( - user_id=custom_user.id).first() - if stripe_customer is None: - stripe_customer = StripeCustomer.objects.create( - user=custom_user, stripe_id=stripe_api_cus_id - ) - stripe_customer_id = stripe_customer.id - except CustomUser.DoesNotExist: - logger.debug( - "Customer {} does not exist.".format(user.get('email'))) - password = CustomUser.get_random_password() - base_url = "{0}://{1}".format(self.request.scheme, - self.request.get_host()) - custom_user = CustomUser.register( - user.get('name'), password, - user.get('email'), - app='dcl', base_url=base_url, send_email=True, - account_details=password - ) - logger.debug("Created user {}.".format(user.get('email'))) - stripe_customer = StripeCustomer.objects. \ - create(user=custom_user, stripe_id=stripe_api_cus_id) - stripe_customer_id = stripe_customer.id - new_user = authenticate(username=custom_user.email, - password=password) - login(request, new_user) - if 'new_user_hosting_key_id' in self.request.session: - user_hosting_key = UserHostingKey.objects.get(id=self.request.session['new_user_hosting_key_id']) - user_hosting_key.user = new_user - user_hosting_key.save() - else: - # We assume that if the user is here, his/her StripeCustomer - # object already exists - stripe_customer_id = request.user.stripecustomer.id - custom_user = request.user - - if 'token' in request.session: - ucd = UserCardDetail.get_or_create_user_card_detail( - stripe_customer=self.request.user.stripecustomer, - card_details=card_details_response - ) - UserCardDetail.save_default_card_local( - self.request.user.stripecustomer.stripe_id, - ucd.card_id - ) - else: - card_id = request.session.get('card_id') - user_card_detail = UserCardDetail.objects.get(id=card_id) - card_details_dict = { - 'last4': user_card_detail.last4, - 'brand': user_card_detail.brand, - 'card_id': user_card_detail.card_id - } - if not user_card_detail.preferred: - UserCardDetail.set_default_card( - stripe_api_cus_id=stripe_api_cus_id, - stripe_source_id=user_card_detail.card_id - ) - - # Save billing address - billing_address_data = request.session.get('billing_address_data') - logger.debug('billing_address_data is {}'.format(billing_address_data)) - billing_address_data.update({ - 'user': custom_user.id - }) - - if 'generic_payment_type' in request.session: - stripe_cus = StripeCustomer.objects.filter( - stripe_id=stripe_api_cus_id - ).first() - billing_address = BillingAddress( - cardholder_name=billing_address_data['cardholder_name'], - 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'] - ) - billing_address.save() - - order = HostingOrder.create( - price=self.request - .session['generic_payment_details']['amount'], - customer=stripe_cus, - billing_address=billing_address, - vm_pricing=VMPricing.get_default_pricing() - ) - - # Create a Hosting Bill - HostingBill.create(customer=stripe_cus, - billing_address=billing_address) - - # Create Billing Address for User if he does not have one - if not stripe_cus.user.billing_addresses.count(): - billing_address_data.update({ - 'user': stripe_cus.user.id - }) - billing_address_user_form = UserBillingAddressForm( - billing_address_data - ) - billing_address_user_form.is_valid() - billing_address_user_form.save() - - if self.request.session['generic_payment_details']['recurring']: - # Associate the given stripe subscription with the order - order.set_subscription_id( - stripe_subscription_obj.id, card_details_dict - ) - else: - # Associate the given stripe charge id with the order - order.set_stripe_charge(stripe_onetime_charge) - - # Set order status approved - order.set_approved() - order.generic_payment_description = gp_details["description"] - order.generic_product_id = gp_details["product_id"] - order.save() - # send emails - context = { - 'name': user.get('name'), - 'email': user.get('email'), - 'amount': gp_details['amount'], - 'description': gp_details['description'], - 'recurring': gp_details['recurring'], - 'product_name': gp_details['product_name'], - 'product_id': gp_details['product_id'], - 'order_id': order.id - } - - email_data = { - 'subject': (settings.DCL_TEXT + - " Payment received from %s" % context['email']), - 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, - 'to': ['info@ungleich.ch'], - 'body': "\n".join( - ["%s=%s" % (k, v) for (k, v) in context.items()]), - 'reply_to': [context['email']], - } - send_plain_email_task.delay(email_data) - recurring_text = _(" This is a monthly recurring plan.") - if gp_details['recurring_interval'] == "year": - recurring_text = _(" This is an yearly recurring plan.") - - email_data = { - 'subject': _("Confirmation of your payment"), - 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, - 'to': [user.get('email')], - 'body': _("Hi {name},\n\n" - "thank you for your order!\n" - "We have just received a payment of CHF {amount:.2f}" - " from you.{recurring}\n\n" - "Cheers,\nYour Data Center Light team".format( - name=user.get('name'), - amount=gp_details['amount'], - recurring=( - recurring_text - if gp_details['recurring'] else '' - ) - ) - ), - 'reply_to': ['info@ungleich.ch'], - } - send_plain_email_task.delay(email_data) - - response = { - 'status': True, - 'redirect': ( - reverse('hosting:invoices') - if request.user.is_authenticated() - else reverse('datacenterlight:index') - ), - 'msg_title': str(_('Thank you for the payment.')), - 'msg_body': str( - _('You will soon receive a confirmation email of the ' - 'payment. You can always contact us at ' - 'info@ungleich.ch for any question that you may have.') - ) - } - clear_all_session_vars(request) - - return JsonResponse(response) - - user = { - 'name': custom_user.name, - 'email': custom_user.email, - 'username': custom_user.username, - 'pass': custom_user.password, - 'request_scheme': request.scheme, - 'request_host': request.get_host(), - 'language': get_language(), - } - - create_vm( - billing_address_data, stripe_customer_id, specs, - stripe_subscription_obj, card_details_dict, request, - vm_template_id, template, user + do_create_vm(self.request, user, stripe_api_cus_id, + card_details_response, stripe_subscription_obj, + stripe_onetime_charge, gp_details, specs, vm_template_id, + template ) response = { @@ -1179,6 +986,213 @@ class OrderConfirmationView(DetailView, FormView): return JsonResponse(response) +def do_create_vm(request, user, stripe_api_cus_id, card_details_response, + stripe_subscription_obj, stripe_onetime_charge, gp_details, + specs, vm_template_id, template): + # Create user if the user is not logged in and if he is not already + # registered + if not request.user.is_authenticated(): + try: + custom_user = CustomUser.objects.get( + email=user.get('email')) + stripe_customer = StripeCustomer.objects.filter( + user_id=custom_user.id).first() + if stripe_customer is None: + stripe_customer = StripeCustomer.objects.create( + user=custom_user, stripe_id=stripe_api_cus_id + ) + stripe_customer_id = stripe_customer.id + except CustomUser.DoesNotExist: + logger.debug( + "Customer {} does not exist.".format(user.get('email'))) + password = CustomUser.get_random_password() + base_url = "{0}://{1}".format(request.scheme, + request.get_host()) + custom_user = CustomUser.register( + user.get('name'), password, + user.get('email'), + app='dcl', base_url=base_url, send_email=True, + account_details=password + ) + logger.debug("Created user {}.".format(user.get('email'))) + stripe_customer = StripeCustomer.objects. \ + create(user=custom_user, stripe_id=stripe_api_cus_id) + stripe_customer_id = stripe_customer.id + new_user = authenticate(username=custom_user.email, + password=password) + login(request, new_user) + if 'new_user_hosting_key_id' in request.session: + user_hosting_key = UserHostingKey.objects.get( + id=request.session['new_user_hosting_key_id']) + user_hosting_key.user = new_user + user_hosting_key.save() + else: + # We assume that if the user is here, his/her StripeCustomer + # object already exists + stripe_customer_id = request.user.stripecustomer.id + custom_user = request.user + + if 'token' in request.session: + ucd = UserCardDetail.get_or_create_user_card_detail( + stripe_customer=request.user.stripecustomer, + card_details=card_details_response + ) + UserCardDetail.save_default_card_local( + request.user.stripecustomer.stripe_id, + ucd.card_id + ) + else: + card_id = request.session.get('card_id') + user_card_detail = UserCardDetail.objects.get(id=card_id) + card_details_dict = { + 'last4': user_card_detail.last4, + 'brand': user_card_detail.brand, + 'card_id': user_card_detail.card_id + } + if not user_card_detail.preferred: + UserCardDetail.set_default_card( + stripe_api_cus_id=stripe_api_cus_id, + stripe_source_id=user_card_detail.card_id + ) + + # Save billing address + billing_address_data = request.session.get('billing_address_data') + logger.debug('billing_address_data is {}'.format(billing_address_data)) + billing_address_data.update({ + 'user': custom_user.id + }) + + if 'generic_payment_type' in request.session: + stripe_cus = StripeCustomer.objects.filter( + stripe_id=stripe_api_cus_id + ).first() + billing_address = BillingAddress( + cardholder_name=billing_address_data['cardholder_name'], + 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'] + ) + billing_address.save() + + order = HostingOrder.create( + price=request.session['generic_payment_details']['amount'], + customer=stripe_cus, + billing_address=billing_address, + vm_pricing=VMPricing.get_default_pricing() + ) + + # Create a Hosting Bill + HostingBill.create(customer=stripe_cus, + billing_address=billing_address) + + # Create Billing Address for User if he does not have one + if not stripe_cus.user.billing_addresses.count(): + billing_address_data.update({ + 'user': stripe_cus.user.id + }) + billing_address_user_form = UserBillingAddressForm( + billing_address_data + ) + billing_address_user_form.is_valid() + billing_address_user_form.save() + + if request.session['generic_payment_details']['recurring']: + # Associate the given stripe subscription with the order + order.set_subscription_id( + stripe_subscription_obj.id, card_details_dict + ) + else: + # Associate the given stripe charge id with the order + order.set_stripe_charge(stripe_onetime_charge) + + # Set order status approved + order.set_approved() + order.generic_payment_description = gp_details["description"] + order.generic_product_id = gp_details["product_id"] + order.save() + # send emails + context = { + 'name': user.get('name'), + 'email': user.get('email'), + 'amount': gp_details['amount'], + 'description': gp_details['description'], + 'recurring': gp_details['recurring'], + 'product_name': gp_details['product_name'], + 'product_id': gp_details['product_id'], + 'order_id': order.id + } + + email_data = { + 'subject': (settings.DCL_TEXT + + " Payment received from %s" % context['email']), + 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': ['info@ungleich.ch'], + 'body': "\n".join( + ["%s=%s" % (k, v) for (k, v) in context.items()]), + 'reply_to': [context['email']], + } + send_plain_email_task.delay(email_data) + recurring_text = _(" This is a monthly recurring plan.") + if gp_details['recurring_interval'] == "year": + recurring_text = _(" This is an yearly recurring plan.") + + email_data = { + 'subject': _("Confirmation of your payment"), + 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': [user.get('email')], + 'body': _("Hi {name},\n\n" + "thank you for your order!\n" + "We have just received a payment of CHF {amount:.2f}" + " from you.{recurring}\n\n" + "Cheers,\nYour Data Center Light team".format( + name=user.get('name'), + amount=gp_details['amount'], + recurring=( + recurring_text + if gp_details['recurring'] else '' + ) + ) + ), + 'reply_to': ['info@ungleich.ch'], + } + send_plain_email_task.delay(email_data) + + response = { + 'status': True, + 'redirect': ( + reverse('hosting:invoices') + if request.user.is_authenticated() + else reverse('datacenterlight:index') + ), + 'msg_title': str(_('Thank you for the payment.')), + 'msg_body': str( + _('You will soon receive a confirmation email of the ' + 'payment. You can always contact us at ' + 'info@ungleich.ch for any question that you may have.') + ) + } + clear_all_session_vars(request) + + return JsonResponse(response) + + user = { + 'name': custom_user.name, + 'email': custom_user.email, + 'username': custom_user.username, + 'pass': custom_user.password, + 'request_scheme': request.scheme, + 'request_host': request.get_host(), + 'language': get_language(), + } + + create_vm( + billing_address_data, stripe_customer_id, specs, + stripe_subscription_obj, card_details_dict, request, + vm_template_id, template, user + ) + def show_error(msg, request): messages.add_message(request, messages.ERROR, msg, extra_tags='failed_payment') diff --git a/webhook/views.py b/webhook/views.py index ad598805..0b05b6ac 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -8,7 +8,9 @@ from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST +from datacenterlight.views import do_create_vm from membership.models import StripeCustomer +from hosting.models import HostingOrder from utils.models import BillingAddress, UserBillingAddress from utils.tasks import send_plain_email_task @@ -117,8 +119,35 @@ def handle_webhook(request): invoice_obj = event.data.object logger.debug("Webhook Event: invoice.paid") logger.debug("invoice_obj %s " % str(invoice_obj)) - if invoice_obj.paid and invoice_obj.billing_reason == "subscription_create": + logger.debug("invoice_obj.paid = %s %s" % (invoice_obj.paid, type(invoice_obj.paid))) + logger.debug("invoice_obj.billing_reason = %s %s" % (invoice_obj.billing_reason, type(invoice_obj.billing_reason))) + # We should check for billing_reason == "subscription_create" but we check for "subscription_update" + # because we are using older api. See https://stripe.com/docs/upgrades?since=2015-07-13 + + # The billing_reason attribute of the invoice object now can take the + # value of subscription_create, indicating that it is the first + # invoice of a subscription. For older API versions, + # billing_reason=subscription_create is represented as + # subscription_update. + + if invoice_obj.paid and invoice_obj.billing_reason == "subscription_update": logger.debug("Start provisioning") + # get subscription id, order_id + ho = None + try: + ho = HostingOrder.objects.get(subscription_id=invoice_obj.subscription) + except Exception as ex: + logger.error(str(ex)) + if ho: + logger.debug("Create a VM for order %s" % str(ho)) + + # TODO: fix the error below + do_create_vm(request, user, stripe_api_cus_id, + card_details_response, stripe_subscription_obj, + stripe_onetime_charge, gp_details, specs, + vm_template_id, + template + ) else: logger.error("Unhandled event : " + event.type) From cda241893b865b28b4ebd3450db3b874392ae0ef Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 07:57:54 +0530 Subject: [PATCH 077/257] Fix PEP warning --- datacenterlight/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index e04f5234..7ce6a90e 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1193,6 +1193,7 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response, vm_template_id, template, user ) + def show_error(msg, request): messages.add_message(request, messages.ERROR, msg, extra_tags='failed_payment') From 3e95a389bb6bf9d843cd80c5d8b2dbb9e0e24860 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 07:58:20 +0530 Subject: [PATCH 078/257] Preparing a fix for TODO (wip) --- webhook/views.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/webhook/views.py b/webhook/views.py index 0b05b6ac..04d19b63 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -140,14 +140,21 @@ def handle_webhook(request): logger.error(str(ex)) if ho: logger.debug("Create a VM for order %s" % str(ho)) - - # TODO: fix the error below - do_create_vm(request, user, stripe_api_cus_id, - card_details_response, stripe_subscription_obj, - stripe_onetime_charge, gp_details, specs, - vm_template_id, - template - ) + # TODO: fix the error below + try: + user = {'name': ho.customer.user.name, + 'email': ho.customer.user.email} + stripe_api_cus_id = ho.customer.stripe_id + stripe_subscription_obj = stripe.Subscription.retrieve(invoice_obj.subscription) + do_create_vm(request, user, stripe_api_cus_id, + card_details_response, + stripe_subscription_obj, + stripe_onetime_charge, gp_details, specs, + vm_template_id, + template + ) + except Exception as ex: + logger.error(str(ex)) else: logger.error("Unhandled event : " + event.type) From ca7481cce06fa47e820a482d6b671b4ecebe063c Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 08:00:08 +0530 Subject: [PATCH 079/257] Avoid request.user.is_authenticated() --- datacenterlight/views.py | 72 ++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 7ce6a90e..c57890ad 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -991,46 +991,40 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response, specs, vm_template_id, template): # Create user if the user is not logged in and if he is not already # registered - if not request.user.is_authenticated(): - try: - custom_user = CustomUser.objects.get( - email=user.get('email')) - stripe_customer = StripeCustomer.objects.filter( - user_id=custom_user.id).first() - if stripe_customer is None: - stripe_customer = StripeCustomer.objects.create( - user=custom_user, stripe_id=stripe_api_cus_id - ) - stripe_customer_id = stripe_customer.id - except CustomUser.DoesNotExist: - logger.debug( - "Customer {} does not exist.".format(user.get('email'))) - password = CustomUser.get_random_password() - base_url = "{0}://{1}".format(request.scheme, - request.get_host()) - custom_user = CustomUser.register( - user.get('name'), password, - user.get('email'), - app='dcl', base_url=base_url, send_email=True, - account_details=password + try: + custom_user = CustomUser.objects.get( + email=user.get('email')) + stripe_customer = StripeCustomer.objects.filter( + user_id=custom_user.id).first() + if stripe_customer is None: + stripe_customer = StripeCustomer.objects.create( + user=custom_user, stripe_id=stripe_api_cus_id ) - logger.debug("Created user {}.".format(user.get('email'))) - stripe_customer = StripeCustomer.objects. \ - create(user=custom_user, stripe_id=stripe_api_cus_id) - stripe_customer_id = stripe_customer.id - new_user = authenticate(username=custom_user.email, - password=password) - login(request, new_user) - if 'new_user_hosting_key_id' in request.session: - user_hosting_key = UserHostingKey.objects.get( - id=request.session['new_user_hosting_key_id']) - user_hosting_key.user = new_user - user_hosting_key.save() - else: - # We assume that if the user is here, his/her StripeCustomer - # object already exists - stripe_customer_id = request.user.stripecustomer.id - custom_user = request.user + stripe_customer_id = stripe_customer.id + except CustomUser.DoesNotExist: + logger.debug( + "Customer {} does not exist.".format(user.get('email'))) + password = CustomUser.get_random_password() + base_url = "{0}://{1}".format(request.scheme, + request.get_host()) + custom_user = CustomUser.register( + user.get('name'), password, + user.get('email'), + app='dcl', base_url=base_url, send_email=True, + account_details=password + ) + logger.debug("Created user {}.".format(user.get('email'))) + stripe_customer = StripeCustomer.objects. \ + create(user=custom_user, stripe_id=stripe_api_cus_id) + stripe_customer_id = stripe_customer.id + new_user = authenticate(username=custom_user.email, + password=password) + login(request, new_user) + if 'new_user_hosting_key_id' in request.session: + user_hosting_key = UserHostingKey.objects.get( + id=request.session['new_user_hosting_key_id']) + user_hosting_key.user = new_user + user_hosting_key.save() if 'token' in request.session: ucd = UserCardDetail.get_or_create_user_card_detail( From 9e247cc556ec7ab6caf960dca3ab15fa5cac2040 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 09:08:08 +0530 Subject: [PATCH 080/257] Fix PEP warning --- datacenterlight/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index c57890ad..3cdc49c3 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -710,7 +710,7 @@ class OrderConfirmationView(DetailView, FormView): stripe_api_cus_id = request.session.get('customer') stripe_utils = StripeUtils() logger.debug("user=%s stripe_api_cus_id=%s" % (user, stripe_api_cus_id)) - card_details_response = None + card_details_response = None if 'token' in request.session: card_details = stripe_utils.get_cards_details_from_token( From 9f49c664fa63504755174bcd13ae821ffd5e0d5e Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 09:09:40 +0530 Subject: [PATCH 081/257] Make do_create_vm independent of session --- datacenterlight/views.py | 45 ++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 3cdc49c3..db192623 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -966,7 +966,7 @@ class OrderConfirmationView(DetailView, FormView): do_create_vm(self.request, user, stripe_api_cus_id, card_details_response, stripe_subscription_obj, stripe_onetime_charge, gp_details, specs, vm_template_id, - template + template, request.session.get('billing_address_data') ) response = { @@ -1005,8 +1005,8 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response, logger.debug( "Customer {} does not exist.".format(user.get('email'))) password = CustomUser.get_random_password() - base_url = "{0}://{1}".format(request.scheme, - request.get_host()) + base_url = "{0}://{1}".format(request['scheme'], + request['host']) custom_user = CustomUser.register( user.get('name'), password, user.get('email'), @@ -1020,23 +1020,14 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response, new_user = authenticate(username=custom_user.email, password=password) login(request, new_user) - if 'new_user_hosting_key_id' in request.session: + if 'new_user_hosting_key_id' in request: user_hosting_key = UserHostingKey.objects.get( - id=request.session['new_user_hosting_key_id']) + id=request['new_user_hosting_key_id']) user_hosting_key.user = new_user user_hosting_key.save() - if 'token' in request.session: - ucd = UserCardDetail.get_or_create_user_card_detail( - stripe_customer=request.user.stripecustomer, - card_details=card_details_response - ) - UserCardDetail.save_default_card_local( - request.user.stripecustomer.stripe_id, - ucd.card_id - ) - else: - card_id = request.session.get('card_id') + if 'card_id' in request.get('card'): + card_id = request.get('card')['card_id'] user_card_detail = UserCardDetail.objects.get(id=card_id) card_details_dict = { 'last4': user_card_detail.last4, @@ -1048,15 +1039,23 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response, stripe_api_cus_id=stripe_api_cus_id, stripe_source_id=user_card_detail.card_id ) + else: + ucd = UserCardDetail.get_or_create_user_card_detail( + stripe_customer=custom_user.stripecustomer, + card_details=card_details_response + ) + UserCardDetail.save_default_card_local( + custom_user.stripecustomer.stripe_id, + ucd.card_id + ) # Save billing address - billing_address_data = request.session.get('billing_address_data') logger.debug('billing_address_data is {}'.format(billing_address_data)) billing_address_data.update({ 'user': custom_user.id }) - if 'generic_payment_type' in request.session: + if 'generic_payment_type' in request: stripe_cus = StripeCustomer.objects.filter( stripe_id=stripe_api_cus_id ).first() @@ -1071,7 +1070,7 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response, billing_address.save() order = HostingOrder.create( - price=request.session['generic_payment_details']['amount'], + price=request['generic_payment_details']['amount'], customer=stripe_cus, billing_address=billing_address, vm_pricing=VMPricing.get_default_pricing() @@ -1092,7 +1091,7 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response, billing_address_user_form.is_valid() billing_address_user_form.save() - if request.session['generic_payment_details']['recurring']: + if request['generic_payment_details']['recurring']: # Associate the given stripe subscription with the order order.set_subscription_id( stripe_subscription_obj.id, card_details_dict @@ -1176,9 +1175,9 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response, 'email': custom_user.email, 'username': custom_user.username, 'pass': custom_user.password, - 'request_scheme': request.scheme, - 'request_host': request.get_host(), - 'language': get_language(), + 'request_scheme': request['scheme'], + 'request_host': request['host'], + 'language': request['language'], } create_vm( From 2a84d20f35b14d74769f308e83965027b8310dd7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 09:09:53 +0530 Subject: [PATCH 082/257] Add docstring --- datacenterlight/views.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index db192623..f26f1788 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -988,7 +988,38 @@ class OrderConfirmationView(DetailView, FormView): def do_create_vm(request, user, stripe_api_cus_id, card_details_response, stripe_subscription_obj, stripe_onetime_charge, gp_details, - specs, vm_template_id, template): + specs, vm_template_id, template, billing_address_data): + """ + :param request: a dict + { + 'scheme': 'https', + 'host': 'domain', + 'language': 'en-us', + 'new_user_hosting_key_id': 1, + 'card': { + 'card_id': 1, # if usercarddetail exists already, else + }, + 'generic_payment_type': 'generic' # represents a generic payment + 'generic_payment_details': { + 'amount': 100, + 'recurring': + } + } + :param user: a dict + { + 'name': 'John Doe', + 'email': 'john@doe.com' + } + :param stripe_api_cus_id: 'cus_xxxxxxx' the actual stripe customer id str + :param card_details_response: + :param stripe_subscription_obj: The actual Stripe's Subscription Object + :param stripe_onetime_charge: Stripe's Charge object + :param gp_details: + :param specs: + :param vm_template_id: + :param template: + :return: + """ # Create user if the user is not logged in and if he is not already # registered try: From 20c6703236ad06bc6d469b5c6dc63c18c51d1866 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 09:10:03 +0530 Subject: [PATCH 083/257] Add a todo --- datacenterlight/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index f26f1788..35f4c856 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1050,6 +1050,7 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response, stripe_customer_id = stripe_customer.id new_user = authenticate(username=custom_user.email, password=password) + # TODO do we need login here ? login(request, new_user) if 'new_user_hosting_key_id' in request: user_hosting_key = UserHostingKey.objects.get( From c4c918d591eac20493889009a06042c8eb43933a Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 09:26:01 +0530 Subject: [PATCH 084/257] Prepare params from session to pass to do_create_vm --- datacenterlight/views.py | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 35f4c856..00f8a555 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -962,8 +962,29 @@ class OrderConfirmationView(DetailView, FormView): "requires_source_action") msg = subscription_result.get('error') return show_error(msg, self.request) + new_user_hosting_key_id = None + card_id = None + generic_payment_type = None + generic_payment_details = None + if 'generic_payment_details' in request.session: + generic_payment_details = request.session['generic_payment_details'] + if 'generic_payment_type' in request.session: + generic_payment_type = request.session['generic_payment_type'] + if 'new_user_hosting_key_id' in self.request.session: + new_user_hosting_key_id = request.session['new_user_hosting_key_id'] + if 'card_id' in request.session: + card_id = request.session.get('card_id') + req = { + 'scheme': self.request.scheme, + 'host': self.request.get_host(), + 'language': get_language(), + 'new_user_hosting_key_id': new_user_hosting_key_id, + 'card_id': card_id, + 'generic_payment_type': generic_payment_type, + 'generic_payment_details': generic_payment_details + } - do_create_vm(self.request, user, stripe_api_cus_id, + do_create_vm(req, user, stripe_api_cus_id, card_details_response, stripe_subscription_obj, stripe_onetime_charge, gp_details, specs, vm_template_id, template, request.session.get('billing_address_data') @@ -996,9 +1017,7 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response, 'host': 'domain', 'language': 'en-us', 'new_user_hosting_key_id': 1, - 'card': { - 'card_id': 1, # if usercarddetail exists already, else - }, + 'card_id': 1, # if usercarddetail exists already, 'generic_payment_type': 'generic' # represents a generic payment 'generic_payment_details': { 'amount': 100, @@ -1058,8 +1077,8 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response, user_hosting_key.user = new_user user_hosting_key.save() - if 'card_id' in request.get('card'): - card_id = request.get('card')['card_id'] + if 'card_id' in request: + card_id = request.get('card_id') user_card_detail = UserCardDetail.objects.get(id=card_id) card_details_dict = { 'last4': user_card_detail.last4, From 17c8f9ca18f813652433cf4eaab9f354da4118f0 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 10:59:21 +0530 Subject: [PATCH 085/257] Handle IncompleteSubscriptions in webhook --- datacenterlight/views.py | 92 ++++++++++++++++++++++++++-------------- hosting/models.py | 19 +++++++++ webhook/views.py | 79 ++++++++++++++++++++++------------ 3 files changed, 130 insertions(+), 60 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 00f8a555..3eb77412 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1,6 +1,7 @@ import logging - +import json import stripe + from django import forms from django.conf import settings from django.contrib import messages @@ -19,9 +20,8 @@ from hosting.forms import ( ) from hosting.models import ( HostingBill, HostingOrder, UserCardDetail, GenericProduct, UserHostingKey, - StripeTaxRate) + StripeTaxRate, IncompleteSubscriptions) from membership.models import CustomUser, StripeCustomer -from opennebula_api.models import OpenNebulaManager from opennebula_api.serializers import VMTemplateSerializer from utils.forms import ( BillingAddressForm, BillingAddressFormSignup, UserBillingAddressForm, @@ -894,6 +894,53 @@ class OrderConfirmationView(DetailView, FormView): latest_invoice = stripe.Invoice.retrieve( stripe_subscription_obj.latest_invoice) + new_user_hosting_key_id = None + card_id = None + generic_payment_type = None + generic_payment_details = None + if 'generic_payment_details' in request.session: + generic_payment_details = request.session[ + 'generic_payment_details'] + if 'generic_payment_type' in request.session: + generic_payment_type = request.session['generic_payment_type'] + if 'new_user_hosting_key_id' in self.request.session: + new_user_hosting_key_id = request.session[ + 'new_user_hosting_key_id'] + if 'card_id' in request.session: + card_id = request.session.get('card_id') + req = { + 'scheme': self.request.scheme, + 'host': self.request.get_host(), + 'language': get_language(), + 'new_user_hosting_key_id': new_user_hosting_key_id, + 'card_id': card_id, + 'generic_payment_type': generic_payment_type, + 'generic_payment_details': generic_payment_details + } + + subscription_status = '' + if stripe_subscription_obj: + subscription_status = stripe_subscription_obj.status + + # Store params so that they can be retrieved later + IncompleteSubscriptions.objects.create( + subscription_status=subscription_status, + name=user.get('name'), + email=user.get('email'), + request=json.dumps(req), + stripe_api_cus_id=stripe_api_cus_id, + card_details_response=json.dumps(card_details_response), + stripe_subscription_obj=json.dumps(stripe_subscription_obj) if stripe_customer_obj else '', + stripe_onetime_charge=json.dumps(stripe_onetime_charge) if stripe_onetime_charge else '', + gp_details=json.dumps(gp_details) if gp_details else '', + specs=json.dumps(specs) if specs else '', + vm_template_id=vm_template_id if vm_template_id else 0, + template=json.dumps(template) if template else '', + billing_address_data=json.dumps( + request.session.get('billing_address_data') + ) + ) + # Check if the subscription was approved and is active if (stripe_subscription_obj is None or (stripe_subscription_obj.status != 'active' @@ -953,6 +1000,7 @@ class OrderConfirmationView(DetailView, FormView): ) } } + clear_all_session_vars(request) return JsonResponse(context) else: logger.debug( @@ -962,28 +1010,6 @@ class OrderConfirmationView(DetailView, FormView): "requires_source_action") msg = subscription_result.get('error') return show_error(msg, self.request) - new_user_hosting_key_id = None - card_id = None - generic_payment_type = None - generic_payment_details = None - if 'generic_payment_details' in request.session: - generic_payment_details = request.session['generic_payment_details'] - if 'generic_payment_type' in request.session: - generic_payment_type = request.session['generic_payment_type'] - if 'new_user_hosting_key_id' in self.request.session: - new_user_hosting_key_id = request.session['new_user_hosting_key_id'] - if 'card_id' in request.session: - card_id = request.session.get('card_id') - req = { - 'scheme': self.request.scheme, - 'host': self.request.get_host(), - 'language': get_language(), - 'new_user_hosting_key_id': new_user_hosting_key_id, - 'card_id': card_id, - 'generic_payment_type': generic_payment_type, - 'generic_payment_details': generic_payment_details - } - do_create_vm(req, user, stripe_api_cus_id, card_details_response, stripe_subscription_obj, stripe_onetime_charge, gp_details, specs, vm_template_id, @@ -1191,14 +1217,14 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response, "We have just received a payment of CHF {amount:.2f}" " from you.{recurring}\n\n" "Cheers,\nYour Data Center Light team".format( - name=user.get('name'), - amount=gp_details['amount'], - recurring=( - recurring_text - if gp_details['recurring'] else '' - ) - ) - ), + name=user.get('name'), + amount=gp_details['amount'], + recurring=( + recurring_text + if gp_details['recurring'] else '' + ) + ) + ), 'reply_to': ['info@ungleich.ch'], } send_plain_email_task.delay(email_data) diff --git a/hosting/models.py b/hosting/models.py index 1061cf54..0554f2e9 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -741,3 +741,22 @@ class StripeTaxRate(AssignPermissionsMixin, models.Model): display_name = models.CharField(max_length=100) percentage = models.FloatField(default=0) description = models.CharField(max_length=100) + + +class IncompleteSubscriptions(AssignPermissionsMixin, models.Model): + created_at = models.DateTimeField(auto_now_add=True) + completed_at = models.DateTimeField() + subscription_id = models.CharField(max_length=100) + subscription_status = models.CharField(max_length=30) + name = models.CharField(max_length=50) + email = models.EmailField() + request = models.TextField() + stripe_api_cus_id = models.CharField(max_length=30) + card_details_response = models.TextField() + stripe_subscription_obj = models.TextField() + stripe_onetime_charge = models.TextField() + gp_details = models.TextField() + specs = models.TextField() + vm_template_id = models.PositiveIntegerField(default=0) + template = models.TextField() + billing_address_data = models.TextField() \ No newline at end of file diff --git a/webhook/views.py b/webhook/views.py index 04d19b63..5f8c85b0 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -1,7 +1,8 @@ import datetime import logging - +import json import stripe + # Create your views here. from django.conf import settings from django.http import HttpResponse @@ -10,7 +11,7 @@ from django.views.decorators.http import require_POST from datacenterlight.views import do_create_vm from membership.models import StripeCustomer -from hosting.models import HostingOrder +from hosting.models import IncompleteSubscriptions from utils.models import BillingAddress, UserBillingAddress from utils.tasks import send_plain_email_task @@ -115,14 +116,16 @@ def handle_webhook(request): } send_plain_email_task.delay(email_data) elif event.type == 'invoice.paid': - #https://stripe.com/docs/billing/migration/strong-customer-authentication#scenario-1-handling-fulfillment + #More info: https://stripe.com/docs/billing/migration/strong-customer-authentication#scenario-1-handling-fulfillment invoice_obj = event.data.object logger.debug("Webhook Event: invoice.paid") logger.debug("invoice_obj %s " % str(invoice_obj)) logger.debug("invoice_obj.paid = %s %s" % (invoice_obj.paid, type(invoice_obj.paid))) logger.debug("invoice_obj.billing_reason = %s %s" % (invoice_obj.billing_reason, type(invoice_obj.billing_reason))) - # We should check for billing_reason == "subscription_create" but we check for "subscription_update" - # because we are using older api. See https://stripe.com/docs/upgrades?since=2015-07-13 + # We should check for billing_reason == "subscription_create" but we + # check for "subscription_update" + # because we are using older api. + # See https://stripe.com/docs/upgrades?since=2015-07-13 # The billing_reason attribute of the invoice object now can take the # value of subscription_create, indicating that it is the first @@ -130,32 +133,54 @@ def handle_webhook(request): # billing_reason=subscription_create is represented as # subscription_update. - if invoice_obj.paid and invoice_obj.billing_reason == "subscription_update": + if (invoice_obj.paid and + invoice_obj.billing_reason == "subscription_update"): logger.debug("Start provisioning") - # get subscription id, order_id - ho = None try: - ho = HostingOrder.objects.get(subscription_id=invoice_obj.subscription) + stripe_subscription_obj = stripe.Subscription.retrieve( + invoice_obj.subscription) + try: + incomplete_sub = IncompleteSubscriptions.objects.get( + subscription_id=invoice_obj.subscription) + logger.debug("*******") + logger.debug(incomplete_sub) + logger.debug("*******") + do_create_vm( + request=incomplete_sub.request, + user={'name': incomplete_sub.name, + 'email': incomplete_sub.email}, + stripe_api_cus_id=incomplete_sub.stripe_api_cus_id, + card_details_response=json.loads( + incomplete_sub.card_details_response), + stripe_subscription_obj=json.loads( + stripe_subscription_obj), + stripe_onetime_charge=json.loads( + incomplete_sub.stripe_onetime_charge), + gp_details=json.loads(incomplete_sub.gp_details), + specs=json.loads(incomplete_sub.specs), + vm_template_id=incomplete_sub.vm_template_id, + template=json.loads(incomplete_sub.template) + ) + except (IncompleteSubscriptions.DoesNotExist, + IncompleteSubscriptions.MultipleObjectsReturned) as ex: + logger.error(str(ex)) + # TODO Inform admin + email_data = { + 'subject': "IncompleteSubscriptions error", + 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': settings.DCL_ERROR_EMAILS_TO_LIST, + 'body': "Response = %s" % str(ex), + } + send_plain_email_task.delay(email_data) except Exception as ex: logger.error(str(ex)) - if ho: - logger.debug("Create a VM for order %s" % str(ho)) - # TODO: fix the error below - try: - user = {'name': ho.customer.user.name, - 'email': ho.customer.user.email} - stripe_api_cus_id = ho.customer.stripe_id - stripe_subscription_obj = stripe.Subscription.retrieve(invoice_obj.subscription) - do_create_vm(request, user, stripe_api_cus_id, - card_details_response, - stripe_subscription_obj, - stripe_onetime_charge, gp_details, specs, - vm_template_id, - template - ) - except Exception as ex: - logger.error(str(ex)) - + email_data = { + 'subject': "invoice.paid Webhook error", + 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': settings.DCL_ERROR_EMAILS_TO_LIST, + 'body': "Response = %s" % str(ex), + } + send_plain_email_task.delay(email_data) else: logger.error("Unhandled event : " + event.type) return HttpResponse(status=200) From 1ed42e608c2472316d123f741a033553159ec126 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 11:07:20 +0530 Subject: [PATCH 086/257] Add incompletesubscriptions migration file --- .../0062_incompletesubscriptions.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 hosting/migrations/0062_incompletesubscriptions.py diff --git a/hosting/migrations/0062_incompletesubscriptions.py b/hosting/migrations/0062_incompletesubscriptions.py new file mode 100644 index 00000000..0405e086 --- /dev/null +++ b/hosting/migrations/0062_incompletesubscriptions.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2020-12-23 05:36 +from __future__ import unicode_literals + +from django.db import migrations, models +import utils.mixins + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0061_genericproduct_exclude_vat_calculations'), + ] + + operations = [ + migrations.CreateModel( + name='IncompleteSubscriptions', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('completed_at', models.DateTimeField()), + ('subscription_id', models.CharField(max_length=100)), + ('subscription_status', models.CharField(max_length=30)), + ('name', models.CharField(max_length=50)), + ('email', models.EmailField(max_length=254)), + ('request', models.TextField()), + ('stripe_api_cus_id', models.CharField(max_length=30)), + ('card_details_response', models.TextField()), + ('stripe_subscription_obj', models.TextField()), + ('stripe_onetime_charge', models.TextField()), + ('gp_details', models.TextField()), + ('specs', models.TextField()), + ('vm_template_id', models.PositiveIntegerField(default=0)), + ('template', models.TextField()), + ('billing_address_data', models.TextField()), + ], + bases=(utils.mixins.AssignPermissionsMixin, models.Model), + ), + ] From 50d9eb1c50e6147e364adbe9e15b42e128f3ad0e Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 11:26:52 +0530 Subject: [PATCH 087/257] Fix UnboundLocalError: local variable 'stripe_onetime_charge' referenced before assignment --- datacenterlight/views.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 3eb77412..a8760290 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -706,6 +706,12 @@ class OrderConfirmationView(DetailView, FormView): return render(request, self.template_name, context) def post(self, request, *args, **kwargs): + stripe_onetime_charge = None + stripe_customer_obj = None + gp_details = None + specs = None + vm_template_id = 0 + template = None user = request.session.get('user') stripe_api_cus_id = request.session.get('customer') stripe_utils = StripeUtils() From 95a1b8fa20759a32b4a08adfd40c63e71f6c1479 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 11:52:41 +0530 Subject: [PATCH 088/257] Make complete_at allow null --- datacenterlight/views.py | 2 +- hosting/migrations/0063_auto_20201223_0612.py | 20 +++++++++++++++++++ hosting/models.py | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 hosting/migrations/0063_auto_20201223_0612.py diff --git a/datacenterlight/views.py b/datacenterlight/views.py index a8760290..f8b9a097 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1006,7 +1006,7 @@ class OrderConfirmationView(DetailView, FormView): ) } } - clear_all_session_vars(request) + #clear_all_session_vars(request) return JsonResponse(context) else: logger.debug( diff --git a/hosting/migrations/0063_auto_20201223_0612.py b/hosting/migrations/0063_auto_20201223_0612.py new file mode 100644 index 00000000..eb4ca9d4 --- /dev/null +++ b/hosting/migrations/0063_auto_20201223_0612.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2020-12-23 06:12 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0062_incompletesubscriptions'), + ] + + operations = [ + migrations.AlterField( + model_name='incompletesubscriptions', + name='completed_at', + field=models.DateTimeField(null=True), + ), + ] diff --git a/hosting/models.py b/hosting/models.py index 0554f2e9..ac44cc9e 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -745,7 +745,7 @@ class StripeTaxRate(AssignPermissionsMixin, models.Model): class IncompleteSubscriptions(AssignPermissionsMixin, models.Model): created_at = models.DateTimeField(auto_now_add=True) - completed_at = models.DateTimeField() + completed_at = models.DateTimeField(null=True) subscription_id = models.CharField(max_length=100) subscription_status = models.CharField(max_length=30) name = models.CharField(max_length=50) From b1dd9988ce65d13c6b352c3bb12eb56807d2a7e5 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 12:03:17 +0530 Subject: [PATCH 089/257] Add missing subscription_id param --- datacenterlight/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index f8b9a097..348ad860 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -930,6 +930,7 @@ class OrderConfirmationView(DetailView, FormView): # Store params so that they can be retrieved later IncompleteSubscriptions.objects.create( + subscription_id=stripe_subscription_obj.id, subscription_status=subscription_status, name=user.get('name'), email=user.get('email'), From 92bafed3b327cd2d18573994a7442cc28d7fbcbd Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 12:09:19 +0530 Subject: [PATCH 090/257] Fix getting stripe_subscription_obj --- webhook/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webhook/views.py b/webhook/views.py index 5f8c85b0..203716e4 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -153,7 +153,7 @@ def handle_webhook(request): card_details_response=json.loads( incomplete_sub.card_details_response), stripe_subscription_obj=json.loads( - stripe_subscription_obj), + incomplete_sub.stripe_subscription_obj), stripe_onetime_charge=json.loads( incomplete_sub.stripe_onetime_charge), gp_details=json.loads(incomplete_sub.gp_details), From c70753767fb577ac05fc7fa9971be8ba2bcb0c61 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 12:32:42 +0530 Subject: [PATCH 091/257] Load request object correctly and pass correct subscription object --- webhook/views.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/webhook/views.py b/webhook/views.py index 203716e4..9381239a 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -146,14 +146,13 @@ def handle_webhook(request): logger.debug(incomplete_sub) logger.debug("*******") do_create_vm( - request=incomplete_sub.request, + request=json.loads(incomplete_sub.request), user={'name': incomplete_sub.name, 'email': incomplete_sub.email}, stripe_api_cus_id=incomplete_sub.stripe_api_cus_id, card_details_response=json.loads( incomplete_sub.card_details_response), - stripe_subscription_obj=json.loads( - incomplete_sub.stripe_subscription_obj), + stripe_subscription_obj=stripe_subscription_obj, stripe_onetime_charge=json.loads( incomplete_sub.stripe_onetime_charge), gp_details=json.loads(incomplete_sub.gp_details), From a5f49cf8bebabda04ba300ad72db17d406e4a29b Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 12:41:46 +0530 Subject: [PATCH 092/257] Add debug messages --- webhook/views.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/webhook/views.py b/webhook/views.py index 9381239a..25325984 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -143,8 +143,19 @@ def handle_webhook(request): incomplete_sub = IncompleteSubscriptions.objects.get( subscription_id=invoice_obj.subscription) logger.debug("*******") - logger.debug(incomplete_sub) + logger.debug(str(incomplete_sub)) logger.debug("*******") + logger.debug("1*******") + logger.debug(json.loads(incomplete_sub.request)) + logger.debug("2*******") + logger.debug(json.loads(incomplete_sub.card_details_response)) + logger.debug("3*******") + logger.debug(json.loads(incomplete_sub.stripe_onetime_charge)) + logger.debug("4*******") + logger.debug(json.loads(incomplete_sub.gp_details)) + logger.debug("5*******") + logger.debug(json.loads(incomplete_sub.template)) + logger.debug("6*******") do_create_vm( request=json.loads(incomplete_sub.request), user={'name': incomplete_sub.name, From de6bc06eaf47b8c1dcd3354e082b66bd0e975149 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 12:50:21 +0530 Subject: [PATCH 093/257] Fix json loads for none --- webhook/views.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/webhook/views.py b/webhook/views.py index 25325984..d788c71d 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -142,17 +142,27 @@ def handle_webhook(request): try: incomplete_sub = IncompleteSubscriptions.objects.get( subscription_id=invoice_obj.subscription) + soc = "" + card_details_response = "" + gp_details = "" + if incomplete_sub.stripe_onetime_charge: + soc = json.loads(incomplete_sub.stripe_onetime_charge) + if incomplete_sub.gp_details: + gp_details = json.loads(incomplete_sub.gp_details) + if incomplete_sub.card_details_response: + card_details_response = json.loads( + incomplete_sub.card_details_response) logger.debug("*******") logger.debug(str(incomplete_sub)) logger.debug("*******") logger.debug("1*******") logger.debug(json.loads(incomplete_sub.request)) logger.debug("2*******") - logger.debug(json.loads(incomplete_sub.card_details_response)) + logger.debug(card_details_response) logger.debug("3*******") - logger.debug(json.loads(incomplete_sub.stripe_onetime_charge)) + logger.debug(json.loads(soc)) logger.debug("4*******") - logger.debug(json.loads(incomplete_sub.gp_details)) + logger.debug(json.loads(gp_details)) logger.debug("5*******") logger.debug(json.loads(incomplete_sub.template)) logger.debug("6*******") @@ -162,11 +172,10 @@ def handle_webhook(request): 'email': incomplete_sub.email}, stripe_api_cus_id=incomplete_sub.stripe_api_cus_id, card_details_response=json.loads( - incomplete_sub.card_details_response), + card_details_response), stripe_subscription_obj=stripe_subscription_obj, - stripe_onetime_charge=json.loads( - incomplete_sub.stripe_onetime_charge), - gp_details=json.loads(incomplete_sub.gp_details), + stripe_onetime_charge=json.loads(soc), + gp_details=json.loads(gp_details), specs=json.loads(incomplete_sub.specs), vm_template_id=incomplete_sub.vm_template_id, template=json.loads(incomplete_sub.template) From 2baa77a7d4323f2038835323ab8dff3bcc135f33 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 13:00:05 +0530 Subject: [PATCH 094/257] Fix json loads issue --- webhook/views.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/webhook/views.py b/webhook/views.py index d788c71d..ecd44306 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -142,9 +142,16 @@ def handle_webhook(request): try: incomplete_sub = IncompleteSubscriptions.objects.get( subscription_id=invoice_obj.subscription) + request = "" soc = "" card_details_response = "" gp_details = "" + template = "" + specs = "" + if incomplete_sub.request: + request = json.loads(incomplete_sub.request) + if incomplete_sub.specs: + specs = json.loads(incomplete_sub.specs) if incomplete_sub.stripe_onetime_charge: soc = json.loads(incomplete_sub.stripe_onetime_charge) if incomplete_sub.gp_details: @@ -152,33 +159,35 @@ def handle_webhook(request): if incomplete_sub.card_details_response: card_details_response = json.loads( incomplete_sub.card_details_response) + if incomplete_sub.template: + template = json.loads( + incomplete_sub.template) logger.debug("*******") logger.debug(str(incomplete_sub)) logger.debug("*******") logger.debug("1*******") - logger.debug(json.loads(incomplete_sub.request)) + logger.debug(request) logger.debug("2*******") logger.debug(card_details_response) logger.debug("3*******") - logger.debug(json.loads(soc)) + logger.debug(soc) logger.debug("4*******") - logger.debug(json.loads(gp_details)) + logger.debug(gp_details) logger.debug("5*******") - logger.debug(json.loads(incomplete_sub.template)) + logger.debug(template) logger.debug("6*******") do_create_vm( - request=json.loads(incomplete_sub.request), + request=request, user={'name': incomplete_sub.name, 'email': incomplete_sub.email}, stripe_api_cus_id=incomplete_sub.stripe_api_cus_id, - card_details_response=json.loads( - card_details_response), + card_details_response=card_details_response, stripe_subscription_obj=stripe_subscription_obj, - stripe_onetime_charge=json.loads(soc), - gp_details=json.loads(gp_details), - specs=json.loads(incomplete_sub.specs), + stripe_onetime_charge=soc, + gp_details=gp_details, + specs=specs, vm_template_id=incomplete_sub.vm_template_id, - template=json.loads(incomplete_sub.template) + template=template ) except (IncompleteSubscriptions.DoesNotExist, IncompleteSubscriptions.MultipleObjectsReturned) as ex: From f6f6482ce08b6c5d67603e0ac8f558786ca70bc5 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 13:07:28 +0530 Subject: [PATCH 095/257] Add missing billing_address_data --- webhook/views.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/webhook/views.py b/webhook/views.py index ecd44306..0b3c4e30 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -148,6 +148,7 @@ def handle_webhook(request): gp_details = "" template = "" specs = "" + billing_address_data = "" if incomplete_sub.request: request = json.loads(incomplete_sub.request) if incomplete_sub.specs: @@ -162,6 +163,9 @@ def handle_webhook(request): if incomplete_sub.template: template = json.loads( incomplete_sub.template) + if incomplete_sub.billing_address: + billing_address_data = json.loads( + incomplete_sub.billing_address_data) logger.debug("*******") logger.debug(str(incomplete_sub)) logger.debug("*******") @@ -187,7 +191,8 @@ def handle_webhook(request): gp_details=gp_details, specs=specs, vm_template_id=incomplete_sub.vm_template_id, - template=template + template=template, + billing_address_data=billing_address_data ) except (IncompleteSubscriptions.DoesNotExist, IncompleteSubscriptions.MultipleObjectsReturned) as ex: From 9d765fcb6e85a63279a524780ce6e5171a33306e Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 13:10:29 +0530 Subject: [PATCH 096/257] Fix wrong variable name billing_address --- webhook/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webhook/views.py b/webhook/views.py index 0b3c4e30..20ee6c45 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -163,7 +163,7 @@ def handle_webhook(request): if incomplete_sub.template: template = json.loads( incomplete_sub.template) - if incomplete_sub.billing_address: + if incomplete_sub.billing_address_data: billing_address_data = json.loads( incomplete_sub.billing_address_data) logger.debug("*******") From 812157b6c648d9be41337930e878610dc0b2965a Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 13:40:19 +0530 Subject: [PATCH 097/257] Move login code out of the refactored do_create_vm --- datacenterlight/views.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 348ad860..ee44416c 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1017,11 +1017,18 @@ class OrderConfirmationView(DetailView, FormView): "requires_source_action") msg = subscription_result.get('error') return show_error(msg, self.request) - do_create_vm(req, user, stripe_api_cus_id, + do_create_vm( + req, user, stripe_api_cus_id, card_details_response, stripe_subscription_obj, stripe_onetime_charge, gp_details, specs, vm_template_id, template, request.session.get('billing_address_data') ) + try: + custom_user = CustomUser.objects.get(email=user.get('email')) + login(self.request, custom_user) + except (CustomUser.DoesNotExist, + CustomUser.MultipleObjectsReturned) as ex: + logger.error(str(ex)) response = { 'status': True, @@ -1102,16 +1109,17 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response, stripe_customer_id = stripe_customer.id new_user = authenticate(username=custom_user.email, password=password) - # TODO do we need login here ? - login(request, new_user) + logger.debug("User %s is authenticated" % custom_user.email) if 'new_user_hosting_key_id' in request: user_hosting_key = UserHostingKey.objects.get( id=request['new_user_hosting_key_id']) user_hosting_key.user = new_user user_hosting_key.save() + logger.debug("User's key is saved" % custom_user.email) if 'card_id' in request: card_id = request.get('card_id') + logger.debug("card_id %s was in request" % card_id) user_card_detail = UserCardDetail.objects.get(id=card_id) card_details_dict = { 'last4': user_card_detail.last4, @@ -1124,6 +1132,8 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response, stripe_source_id=user_card_detail.card_id ) else: + logger.debug("card_id %s was NOT in request, using " + "card_details_response") ucd = UserCardDetail.get_or_create_user_card_detail( stripe_customer=custom_user.stripecustomer, card_details=card_details_response From a4a5acd0e75a9a0bf81222e8daa6ba5239c847a0 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 13:46:34 +0530 Subject: [PATCH 098/257] Fix string formatting issues --- datacenterlight/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index ee44416c..613fac26 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1115,7 +1115,7 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response, id=request['new_user_hosting_key_id']) user_hosting_key.user = new_user user_hosting_key.save() - logger.debug("User's key is saved" % custom_user.email) + logger.debug("User %s key is saved" % custom_user.email) if 'card_id' in request: card_id = request.get('card_id') @@ -1132,7 +1132,7 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response, stripe_source_id=user_card_detail.card_id ) else: - logger.debug("card_id %s was NOT in request, using " + logger.debug("card_id was NOT in request, using " "card_details_response") ucd = UserCardDetail.get_or_create_user_card_detail( stripe_customer=custom_user.stripecustomer, From 981e68aa4f94c372f979d00d4051b75a4058bf6c Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 13:56:08 +0530 Subject: [PATCH 099/257] Fix getting card_id and compare --- datacenterlight/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 613fac26..d6fb5303 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1117,8 +1117,8 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response, user_hosting_key.save() logger.debug("User %s key is saved" % custom_user.email) - if 'card_id' in request: - card_id = request.get('card_id') + card_id = request.get('card_id', None) + if card_id: logger.debug("card_id %s was in request" % card_id) user_card_detail = UserCardDetail.objects.get(id=card_id) card_details_dict = { From 259c5091130cb0fbfbedae050d92bcf86f2564be Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 14:02:27 +0530 Subject: [PATCH 100/257] Don't handle generic exception for the moment --- webhook/views.py | 140 +++++++++++++++++++++++------------------------ 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/webhook/views.py b/webhook/views.py index 20ee6c45..bd45ef16 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -136,84 +136,84 @@ def handle_webhook(request): if (invoice_obj.paid and invoice_obj.billing_reason == "subscription_update"): logger.debug("Start provisioning") + # try: + stripe_subscription_obj = stripe.Subscription.retrieve( + invoice_obj.subscription) try: - stripe_subscription_obj = stripe.Subscription.retrieve( - invoice_obj.subscription) - try: - incomplete_sub = IncompleteSubscriptions.objects.get( - subscription_id=invoice_obj.subscription) - request = "" - soc = "" - card_details_response = "" - gp_details = "" - template = "" - specs = "" - billing_address_data = "" - if incomplete_sub.request: - request = json.loads(incomplete_sub.request) - if incomplete_sub.specs: - specs = json.loads(incomplete_sub.specs) - if incomplete_sub.stripe_onetime_charge: - soc = json.loads(incomplete_sub.stripe_onetime_charge) - if incomplete_sub.gp_details: - gp_details = json.loads(incomplete_sub.gp_details) - if incomplete_sub.card_details_response: - card_details_response = json.loads( - incomplete_sub.card_details_response) - if incomplete_sub.template: - template = json.loads( - incomplete_sub.template) - if incomplete_sub.billing_address_data: - billing_address_data = json.loads( - incomplete_sub.billing_address_data) - logger.debug("*******") - logger.debug(str(incomplete_sub)) - logger.debug("*******") - logger.debug("1*******") - logger.debug(request) - logger.debug("2*******") - logger.debug(card_details_response) - logger.debug("3*******") - logger.debug(soc) - logger.debug("4*******") - logger.debug(gp_details) - logger.debug("5*******") - logger.debug(template) - logger.debug("6*******") - do_create_vm( - request=request, - user={'name': incomplete_sub.name, - 'email': incomplete_sub.email}, - stripe_api_cus_id=incomplete_sub.stripe_api_cus_id, - card_details_response=card_details_response, - stripe_subscription_obj=stripe_subscription_obj, - stripe_onetime_charge=soc, - gp_details=gp_details, - specs=specs, - vm_template_id=incomplete_sub.vm_template_id, - template=template, - billing_address_data=billing_address_data - ) - except (IncompleteSubscriptions.DoesNotExist, - IncompleteSubscriptions.MultipleObjectsReturned) as ex: - logger.error(str(ex)) - # TODO Inform admin - email_data = { - 'subject': "IncompleteSubscriptions error", - 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, - 'to': settings.DCL_ERROR_EMAILS_TO_LIST, - 'body': "Response = %s" % str(ex), - } - send_plain_email_task.delay(email_data) - except Exception as ex: + incomplete_sub = IncompleteSubscriptions.objects.get( + subscription_id=invoice_obj.subscription) + request = "" + soc = "" + card_details_response = "" + gp_details = "" + template = "" + specs = "" + billing_address_data = "" + if incomplete_sub.request: + request = json.loads(incomplete_sub.request) + if incomplete_sub.specs: + specs = json.loads(incomplete_sub.specs) + if incomplete_sub.stripe_onetime_charge: + soc = json.loads(incomplete_sub.stripe_onetime_charge) + if incomplete_sub.gp_details: + gp_details = json.loads(incomplete_sub.gp_details) + if incomplete_sub.card_details_response: + card_details_response = json.loads( + incomplete_sub.card_details_response) + if incomplete_sub.template: + template = json.loads( + incomplete_sub.template) + if incomplete_sub.billing_address_data: + billing_address_data = json.loads( + incomplete_sub.billing_address_data) + logger.debug("*******") + logger.debug(str(incomplete_sub)) + logger.debug("*******") + logger.debug("1*******") + logger.debug(request) + logger.debug("2*******") + logger.debug(card_details_response) + logger.debug("3*******") + logger.debug(soc) + logger.debug("4*******") + logger.debug(gp_details) + logger.debug("5*******") + logger.debug(template) + logger.debug("6*******") + do_create_vm( + request=request, + user={'name': incomplete_sub.name, + 'email': incomplete_sub.email}, + stripe_api_cus_id=incomplete_sub.stripe_api_cus_id, + card_details_response=card_details_response, + stripe_subscription_obj=stripe_subscription_obj, + stripe_onetime_charge=soc, + gp_details=gp_details, + specs=specs, + vm_template_id=incomplete_sub.vm_template_id, + template=template, + billing_address_data=billing_address_data + ) + except (IncompleteSubscriptions.DoesNotExist, + IncompleteSubscriptions.MultipleObjectsReturned) as ex: logger.error(str(ex)) + # TODO Inform admin email_data = { - 'subject': "invoice.paid Webhook error", + 'subject': "IncompleteSubscriptions error", 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, 'to': settings.DCL_ERROR_EMAILS_TO_LIST, 'body': "Response = %s" % str(ex), } send_plain_email_task.delay(email_data) + # except Exception as ex: + # logger.error(str(ex)) + # email_data = { + # 'subject': "invoice.paid Webhook error", + # 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + # 'to': settings.DCL_ERROR_EMAILS_TO_LIST, + # 'body': "Response = %s" % str(ex), + # } + # send_plain_email_task.delay(email_data) else: logger.error("Unhandled event : " + event.type) return HttpResponse(status=200) From 70c8ed6825d70613b5c48e02e7de73842f64982e Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 14:12:30 +0530 Subject: [PATCH 101/257] Add debugging messages --- datacenterlight/views.py | 11 ++- webhook/views.py | 140 +++++++++++++++++++-------------------- 2 files changed, 78 insertions(+), 73 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index d6fb5303..32650752 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1144,12 +1144,14 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response, ) # Save billing address - logger.debug('billing_address_data is {}'.format(billing_address_data)) billing_address_data.update({ 'user': custom_user.id }) + logger.debug('billing_address_data is {}'.format(billing_address_data)) - if 'generic_payment_type' in request: + generic_payment_type = request.get('generic_payment_type', None) + if generic_payment_type: + logger.debug("generic_payment_type case") stripe_cus = StripeCustomer.objects.filter( stripe_id=stripe_api_cus_id ).first() @@ -1185,12 +1187,15 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response, billing_address_user_form.is_valid() billing_address_user_form.save() - if request['generic_payment_details']['recurring']: + recurring = request['generic_payment_details'].get('recurring') + if recurring: + logger.debug("recurring case") # Associate the given stripe subscription with the order order.set_subscription_id( stripe_subscription_obj.id, card_details_dict ) else: + logger.debug("one time charge case") # Associate the given stripe charge id with the order order.set_stripe_charge(stripe_onetime_charge) diff --git a/webhook/views.py b/webhook/views.py index bd45ef16..20ee6c45 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -136,84 +136,84 @@ def handle_webhook(request): if (invoice_obj.paid and invoice_obj.billing_reason == "subscription_update"): logger.debug("Start provisioning") - # try: - stripe_subscription_obj = stripe.Subscription.retrieve( - invoice_obj.subscription) try: - incomplete_sub = IncompleteSubscriptions.objects.get( - subscription_id=invoice_obj.subscription) - request = "" - soc = "" - card_details_response = "" - gp_details = "" - template = "" - specs = "" - billing_address_data = "" - if incomplete_sub.request: - request = json.loads(incomplete_sub.request) - if incomplete_sub.specs: - specs = json.loads(incomplete_sub.specs) - if incomplete_sub.stripe_onetime_charge: - soc = json.loads(incomplete_sub.stripe_onetime_charge) - if incomplete_sub.gp_details: - gp_details = json.loads(incomplete_sub.gp_details) - if incomplete_sub.card_details_response: - card_details_response = json.loads( - incomplete_sub.card_details_response) - if incomplete_sub.template: - template = json.loads( - incomplete_sub.template) - if incomplete_sub.billing_address_data: - billing_address_data = json.loads( - incomplete_sub.billing_address_data) - logger.debug("*******") - logger.debug(str(incomplete_sub)) - logger.debug("*******") - logger.debug("1*******") - logger.debug(request) - logger.debug("2*******") - logger.debug(card_details_response) - logger.debug("3*******") - logger.debug(soc) - logger.debug("4*******") - logger.debug(gp_details) - logger.debug("5*******") - logger.debug(template) - logger.debug("6*******") - do_create_vm( - request=request, - user={'name': incomplete_sub.name, - 'email': incomplete_sub.email}, - stripe_api_cus_id=incomplete_sub.stripe_api_cus_id, - card_details_response=card_details_response, - stripe_subscription_obj=stripe_subscription_obj, - stripe_onetime_charge=soc, - gp_details=gp_details, - specs=specs, - vm_template_id=incomplete_sub.vm_template_id, - template=template, - billing_address_data=billing_address_data - ) - except (IncompleteSubscriptions.DoesNotExist, - IncompleteSubscriptions.MultipleObjectsReturned) as ex: + stripe_subscription_obj = stripe.Subscription.retrieve( + invoice_obj.subscription) + try: + incomplete_sub = IncompleteSubscriptions.objects.get( + subscription_id=invoice_obj.subscription) + request = "" + soc = "" + card_details_response = "" + gp_details = "" + template = "" + specs = "" + billing_address_data = "" + if incomplete_sub.request: + request = json.loads(incomplete_sub.request) + if incomplete_sub.specs: + specs = json.loads(incomplete_sub.specs) + if incomplete_sub.stripe_onetime_charge: + soc = json.loads(incomplete_sub.stripe_onetime_charge) + if incomplete_sub.gp_details: + gp_details = json.loads(incomplete_sub.gp_details) + if incomplete_sub.card_details_response: + card_details_response = json.loads( + incomplete_sub.card_details_response) + if incomplete_sub.template: + template = json.loads( + incomplete_sub.template) + if incomplete_sub.billing_address_data: + billing_address_data = json.loads( + incomplete_sub.billing_address_data) + logger.debug("*******") + logger.debug(str(incomplete_sub)) + logger.debug("*******") + logger.debug("1*******") + logger.debug(request) + logger.debug("2*******") + logger.debug(card_details_response) + logger.debug("3*******") + logger.debug(soc) + logger.debug("4*******") + logger.debug(gp_details) + logger.debug("5*******") + logger.debug(template) + logger.debug("6*******") + do_create_vm( + request=request, + user={'name': incomplete_sub.name, + 'email': incomplete_sub.email}, + stripe_api_cus_id=incomplete_sub.stripe_api_cus_id, + card_details_response=card_details_response, + stripe_subscription_obj=stripe_subscription_obj, + stripe_onetime_charge=soc, + gp_details=gp_details, + specs=specs, + vm_template_id=incomplete_sub.vm_template_id, + template=template, + billing_address_data=billing_address_data + ) + except (IncompleteSubscriptions.DoesNotExist, + IncompleteSubscriptions.MultipleObjectsReturned) as ex: + logger.error(str(ex)) + # TODO Inform admin + email_data = { + 'subject': "IncompleteSubscriptions error", + 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': settings.DCL_ERROR_EMAILS_TO_LIST, + 'body': "Response = %s" % str(ex), + } + send_plain_email_task.delay(email_data) + except Exception as ex: logger.error(str(ex)) - # TODO Inform admin email_data = { - 'subject': "IncompleteSubscriptions error", + 'subject': "invoice.paid Webhook error", 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, 'to': settings.DCL_ERROR_EMAILS_TO_LIST, 'body': "Response = %s" % str(ex), } send_plain_email_task.delay(email_data) - # except Exception as ex: - # logger.error(str(ex)) - # email_data = { - # 'subject': "invoice.paid Webhook error", - # 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, - # 'to': settings.DCL_ERROR_EMAILS_TO_LIST, - # 'body': "Response = %s" % str(ex), - # } - # send_plain_email_task.delay(email_data) else: logger.error("Unhandled event : " + event.type) return HttpResponse(status=200) From 4962b72d1a2c8a5fd498650fd064d4247238651f Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 14:19:22 +0530 Subject: [PATCH 102/257] Add logging message --- datacenterlight/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 97bfef4c..d0c4b0f3 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -38,6 +38,7 @@ def get_cms_integration(name): def create_vm(billing_address_data, stripe_customer_id, specs, stripe_subscription_obj, card_details_dict, request, vm_template_id, template, user): + logger.debug("In create_vm") billing_address = BillingAddress( cardholder_name=billing_address_data['cardholder_name'], street_address=billing_address_data['street_address'], From 480e38fbc9b252856347302d91627d0bbea503e7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 14:19:52 +0530 Subject: [PATCH 103/257] Attempt fix for local variable 'card_details_dict' referenced before assignment --- datacenterlight/views.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 32650752..f373d9b0 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1142,6 +1142,11 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response, custom_user.stripecustomer.stripe_id, ucd.card_id ) + card_details_dict = { + 'last4': ucd.last4, + 'brand': ucd.brand, + 'card_id': ucd.card_id + } # Save billing address billing_address_data.update({ From 0c1b7b1885caa8e47555f0e131b1aa156157e37f Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 14:33:32 +0530 Subject: [PATCH 104/257] Do clear session vars at the end --- datacenterlight/utils.py | 2 -- datacenterlight/views.py | 50 ++++++++++++++++++++-------------------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index d0c4b0f3..8a087e3e 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -103,8 +103,6 @@ def create_vm(billing_address_data, stripe_customer_id, specs, create_vm_task.delay(vm_template_id, user, specs, template, order.id) - clear_all_session_vars(request) - def clear_all_session_vars(request): if request.session is not None: diff --git a/datacenterlight/views.py b/datacenterlight/views.py index f373d9b0..6ce1562b 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -717,6 +717,30 @@ class OrderConfirmationView(DetailView, FormView): stripe_utils = StripeUtils() logger.debug("user=%s stripe_api_cus_id=%s" % (user, stripe_api_cus_id)) card_details_response = None + new_user_hosting_key_id = None + card_id = None + generic_payment_type = None + generic_payment_details = None + stripe_subscription_obj = None + if 'generic_payment_details' in request.session: + generic_payment_details = request.session[ + 'generic_payment_details'] + if 'generic_payment_type' in request.session: + generic_payment_type = request.session['generic_payment_type'] + if 'new_user_hosting_key_id' in self.request.session: + new_user_hosting_key_id = request.session[ + 'new_user_hosting_key_id'] + if 'card_id' in request.session: + card_id = request.session.get('card_id') + req = { + 'scheme': self.request.scheme, + 'host': self.request.get_host(), + 'language': get_language(), + 'new_user_hosting_key_id': new_user_hosting_key_id, + 'card_id': card_id, + 'generic_payment_type': generic_payment_type, + 'generic_payment_details': generic_payment_details + } if 'token' in request.session: card_details = stripe_utils.get_cards_details_from_token( @@ -899,31 +923,6 @@ class OrderConfirmationView(DetailView, FormView): logger.debug(stripe_subscription_obj) latest_invoice = stripe.Invoice.retrieve( stripe_subscription_obj.latest_invoice) - - new_user_hosting_key_id = None - card_id = None - generic_payment_type = None - generic_payment_details = None - if 'generic_payment_details' in request.session: - generic_payment_details = request.session[ - 'generic_payment_details'] - if 'generic_payment_type' in request.session: - generic_payment_type = request.session['generic_payment_type'] - if 'new_user_hosting_key_id' in self.request.session: - new_user_hosting_key_id = request.session[ - 'new_user_hosting_key_id'] - if 'card_id' in request.session: - card_id = request.session.get('card_id') - req = { - 'scheme': self.request.scheme, - 'host': self.request.get_host(), - 'language': get_language(), - 'new_user_hosting_key_id': new_user_hosting_key_id, - 'card_id': card_id, - 'generic_payment_type': generic_payment_type, - 'generic_payment_details': generic_payment_details - } - subscription_status = '' if stripe_subscription_obj: subscription_status = stripe_subscription_obj.status @@ -1043,6 +1042,7 @@ class OrderConfirmationView(DetailView, FormView): ' We will send you a confirmation email as soon as' ' it is ready.')) } + clear_all_session_vars(request) return JsonResponse(response) From 41e993a3d9d18f3930abeb0d1386d2bd0d7dc39d Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 16:34:26 +0530 Subject: [PATCH 105/257] Log variable value --- datacenterlight/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 6ce1562b..fac5f6fe 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -965,7 +965,7 @@ class OrderConfirmationView(DetailView, FormView): # TODO: requires_attention is probably wrong value to compare if (pi.status == 'requires_attention' or pi.status == 'requires_source_action'): - logger.debug("Display SCA authentication") + logger.debug("Display SCA authentication %s " % pi.status) context = { 'sid': stripe_subscription_obj.id, 'payment_intent_secret': pi.client_secret, From a99924b94cd904e688b140037fce58d36b4e86ee Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 17:09:27 +0530 Subject: [PATCH 106/257] Rename do_create_vm to do_provisioning; and pass real_request --- datacenterlight/views.py | 28 +++++++++++++++++----------- webhook/views.py | 7 ++++--- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index fac5f6fe..9e4e7ed7 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1016,11 +1016,12 @@ class OrderConfirmationView(DetailView, FormView): "requires_source_action") msg = subscription_result.get('error') return show_error(msg, self.request) - do_create_vm( + do_provisioning( req, user, stripe_api_cus_id, card_details_response, stripe_subscription_obj, stripe_onetime_charge, gp_details, specs, vm_template_id, - template, request.session.get('billing_address_data') + template, request.session.get('billing_address_data'), + self.request ) try: custom_user = CustomUser.objects.get(email=user.get('email')) @@ -1047,9 +1048,10 @@ class OrderConfirmationView(DetailView, FormView): return JsonResponse(response) -def do_create_vm(request, user, stripe_api_cus_id, card_details_response, - stripe_subscription_obj, stripe_onetime_charge, gp_details, - specs, vm_template_id, template, billing_address_data): +def do_provisioning(request, user, stripe_api_cus_id, card_details_response, + stripe_subscription_obj, stripe_onetime_charge, gp_details, + specs, vm_template_id, template, billing_address_data, + real_request): """ :param request: a dict { @@ -1077,6 +1079,7 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response, :param specs: :param vm_template_id: :param template: + :param real_request: :return: """ # Create user if the user is not logged in and if he is not already @@ -1255,14 +1258,14 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response, 'reply_to': ['info@ungleich.ch'], } send_plain_email_task.delay(email_data) - + redirect_url = reverse('datacenterlight:index') + if real_request: + clear_all_session_vars(real_request) + if real_request.user.is_authenticated(): + redirect_url = reverse('hosting:invoices') response = { 'status': True, - 'redirect': ( - reverse('hosting:invoices') - if request.user.is_authenticated() - else reverse('datacenterlight:index') - ), + 'redirect': redirect_url, 'msg_title': str(_('Thank you for the payment.')), 'msg_body': str( _('You will soon receive a confirmation email of the ' @@ -1290,6 +1293,9 @@ def do_create_vm(request, user, stripe_api_cus_id, card_details_response, vm_template_id, template, user ) + if real_request: + clear_all_session_vars(real_request) + def show_error(msg, request): messages.add_message(request, messages.ERROR, msg, diff --git a/webhook/views.py b/webhook/views.py index 20ee6c45..c61bf3f0 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -9,7 +9,7 @@ from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST -from datacenterlight.views import do_create_vm +from datacenterlight.views import do_provisioning from membership.models import StripeCustomer from hosting.models import IncompleteSubscriptions @@ -180,7 +180,7 @@ def handle_webhook(request): logger.debug("5*******") logger.debug(template) logger.debug("6*******") - do_create_vm( + do_provisioning( request=request, user={'name': incomplete_sub.name, 'email': incomplete_sub.email}, @@ -192,7 +192,8 @@ def handle_webhook(request): specs=specs, vm_template_id=incomplete_sub.vm_template_id, template=template, - billing_address_data=billing_address_data + billing_address_data=billing_address_data, + real_request=None ) except (IncompleteSubscriptions.DoesNotExist, IncompleteSubscriptions.MultipleObjectsReturned) as ex: From a9778076d6efee7a221dbf4ee7194048781db770 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 17:10:06 +0530 Subject: [PATCH 107/257] Clean session variables using real_request --- datacenterlight/views.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 9e4e7ed7..0c366f9b 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1043,7 +1043,6 @@ class OrderConfirmationView(DetailView, FormView): ' We will send you a confirmation email as soon as' ' it is ready.')) } - clear_all_session_vars(request) return JsonResponse(response) @@ -1064,7 +1063,7 @@ def do_provisioning(request, user, stripe_api_cus_id, card_details_response, 'generic_payment_details': { 'amount': 100, 'recurring': - } + }, } :param user: a dict { @@ -1273,7 +1272,6 @@ def do_provisioning(request, user, stripe_api_cus_id, card_details_response, 'info@ungleich.ch for any question that you may have.') ) } - clear_all_session_vars(request) return JsonResponse(response) From 78b819116524fa0a717dec8e2db45f18bf1bdd6e Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 17:10:23 +0530 Subject: [PATCH 108/257] Implement invoice.payment_failed case --- webhook/views.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/webhook/views.py b/webhook/views.py index c61bf3f0..5627c648 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -215,6 +215,14 @@ def handle_webhook(request): 'body': "Response = %s" % str(ex), } send_plain_email_task.delay(email_data) + elif event.type == 'invoice.payment_failed': + invoice_obj = event.data.object + logger.debug("Webhook Event: invoice.payment_failed") + logger.debug("invoice_obj %s " % str(invoice_obj)) + if (invoice_obj.payment_failed and + invoice_obj.billing_reason == "subscription_update"): + logger.debug("Payment failed, inform the users") + else: logger.error("Unhandled event : " + event.type) return HttpResponse(status=200) From 799194152ee6ab29f2e7177ad5514dfd2e3858cb Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 17:16:19 +0530 Subject: [PATCH 109/257] Remove todo --- webhook/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/webhook/views.py b/webhook/views.py index 5627c648..b0eac5df 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -198,7 +198,6 @@ def handle_webhook(request): except (IncompleteSubscriptions.DoesNotExist, IncompleteSubscriptions.MultipleObjectsReturned) as ex: logger.error(str(ex)) - # TODO Inform admin email_data = { 'subject': "IncompleteSubscriptions error", 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, From d447f8d9e69943bf2147bcbd85f7aaa1a71e3152 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 17:26:40 +0530 Subject: [PATCH 110/257] Add logging message --- datacenterlight/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 0c366f9b..d6ac7b5f 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): + logger.debug("Session: %s" % str(request.session)) request.session.pop('vat_validation_status') if (('type' in request.GET and request.GET['type'] == 'generic') or 'product_slug' in kwargs): @@ -461,6 +462,7 @@ class PaymentOrderView(FormView): token = address_form.cleaned_data.get('token') if token is '': card_id = address_form.cleaned_data.get('card') + logger.debug("token is empty and card_id is %s" % card_id) try: user_card_detail = UserCardDetail.objects.get(id=card_id) if not request.user.has_perm( @@ -487,6 +489,7 @@ class PaymentOrderView(FormView): request.session['card_id'] = user_card_detail.id else: request.session['token'] = token + logger.debug("token is %s" % token) if request.user.is_authenticated(): this_user = { 'email': request.user.email, From 377d12b5a57bf81e7a3b665294a120607a16def5 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 17:47:37 +0530 Subject: [PATCH 111/257] Create IncompleteSubscriptions only for SCA case --- datacenterlight/views.py | 41 ++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index d6ac7b5f..ee5dfd00 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -930,26 +930,6 @@ class OrderConfirmationView(DetailView, FormView): if stripe_subscription_obj: subscription_status = stripe_subscription_obj.status - # Store params so that they can be retrieved later - IncompleteSubscriptions.objects.create( - subscription_id=stripe_subscription_obj.id, - subscription_status=subscription_status, - name=user.get('name'), - email=user.get('email'), - request=json.dumps(req), - stripe_api_cus_id=stripe_api_cus_id, - card_details_response=json.dumps(card_details_response), - stripe_subscription_obj=json.dumps(stripe_subscription_obj) if stripe_customer_obj else '', - stripe_onetime_charge=json.dumps(stripe_onetime_charge) if stripe_onetime_charge else '', - gp_details=json.dumps(gp_details) if gp_details else '', - specs=json.dumps(specs) if specs else '', - vm_template_id=vm_template_id if vm_template_id else 0, - template=json.dumps(template) if template else '', - billing_address_data=json.dumps( - request.session.get('billing_address_data') - ) - ) - # Check if the subscription was approved and is active if (stripe_subscription_obj is None or (stripe_subscription_obj.status != 'active' @@ -962,6 +942,27 @@ class OrderConfirmationView(DetailView, FormView): msg = subscription_result.get('error') return show_error(msg, self.request) elif stripe_subscription_obj.status == 'incomplete': + # Store params so that they can be retrieved later + IncompleteSubscriptions.objects.create( + subscription_id=stripe_subscription_obj.id, + subscription_status=subscription_status, + name=user.get('name'), + email=user.get('email'), + request=json.dumps(req), + stripe_api_cus_id=stripe_api_cus_id, + card_details_response=json.dumps(card_details_response), + stripe_subscription_obj=json.dumps( + stripe_subscription_obj) if stripe_customer_obj else '', + stripe_onetime_charge=json.dumps( + stripe_onetime_charge) if stripe_onetime_charge else '', + gp_details=json.dumps(gp_details) if gp_details else '', + specs=json.dumps(specs) if specs else '', + vm_template_id=vm_template_id if vm_template_id else 0, + template=json.dumps(template) if template else '', + billing_address_data=json.dumps( + request.session.get('billing_address_data') + ) + ) pi = stripe.PaymentIntent.retrieve( latest_invoice.payment_intent ) From c0aeac4dc7eac59c653670d6b4bf8206c71a99ef Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 17:47:50 +0530 Subject: [PATCH 112/257] Add some logger messages --- datacenterlight/views.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index ee5dfd00..be387986 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1205,6 +1205,7 @@ def do_provisioning(request, user, stripe_api_cus_id, card_details_response, order.set_subscription_id( stripe_subscription_obj.id, card_details_dict ) + logger.debug("recurring case, set order subscription id done") else: logger.debug("one time charge case") # Associate the given stripe charge id with the order @@ -1215,6 +1216,7 @@ def do_provisioning(request, user, stripe_api_cus_id, card_details_response, order.generic_payment_description = gp_details["description"] order.generic_product_id = gp_details["product_id"] order.save() + logger.debug("Order saved") # send emails context = { 'name': user.get('name'), @@ -1262,10 +1264,12 @@ def do_provisioning(request, user, stripe_api_cus_id, card_details_response, } send_plain_email_task.delay(email_data) redirect_url = reverse('datacenterlight:index') + logger.debug("Sent user/admin emails") if real_request: clear_all_session_vars(real_request) if real_request.user.is_authenticated(): redirect_url = reverse('hosting:invoices') + logger.debug("redirect_url = %s " % redirect_url) response = { 'status': True, 'redirect': redirect_url, From c28bd9091abde5dca94b976f7b63cfb72a117626 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 17:53:19 +0530 Subject: [PATCH 113/257] Add more logger --- datacenterlight/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index be387986..aea7dcbd 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1280,7 +1280,8 @@ def do_provisioning(request, user, stripe_api_cus_id, card_details_response, 'info@ungleich.ch for any question that you may have.') ) } - + logger.debug("after response") + logger.debug(str(response)) return JsonResponse(response) user = { From a03e2dc00625dc80775d04b10d76b40daa10b413 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 18:10:17 +0530 Subject: [PATCH 114/257] Return provisioning if set in do_provisioning --- datacenterlight/views.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index aea7dcbd..17690c78 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1020,7 +1020,7 @@ class OrderConfirmationView(DetailView, FormView): "requires_source_action") msg = subscription_result.get('error') return show_error(msg, self.request) - do_provisioning( + provisioning_response = do_provisioning( req, user, stripe_api_cus_id, card_details_response, stripe_subscription_obj, stripe_onetime_charge, gp_details, specs, vm_template_id, @@ -1034,6 +1034,10 @@ class OrderConfirmationView(DetailView, FormView): CustomUser.MultipleObjectsReturned) as ex: logger.error(str(ex)) + if (provisioning_response and + type(provisioning_response) == JsonResponse): + return provisioning_response + response = { 'status': True, 'redirect': ( From c8519058c49a20924f74f3ed50e45018db3c78cb Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 18:31:45 +0530 Subject: [PATCH 115/257] Fix new_user_hosting_key_id --- datacenterlight/views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 17690c78..d0a9419c 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1120,9 +1120,10 @@ def do_provisioning(request, user, stripe_api_cus_id, card_details_response, new_user = authenticate(username=custom_user.email, password=password) logger.debug("User %s is authenticated" % custom_user.email) - if 'new_user_hosting_key_id' in request: + new_user_hosting_key_id = request.get('new_user_hosting_key_id', None) + if new_user_hosting_key_id: user_hosting_key = UserHostingKey.objects.get( - id=request['new_user_hosting_key_id']) + id=new_user_hosting_key_id) user_hosting_key.user = new_user user_hosting_key.save() logger.debug("User %s key is saved" % custom_user.email) From 080a45f39c4a3812ebd61cf38a9a78dceda94778 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 20:38:42 +0530 Subject: [PATCH 116/257] Pop up stale card_id/token or billing_address_data in the paymentorder page --- datacenterlight/views.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index d0a9419c..388d78b2 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -309,8 +309,13 @@ class PaymentOrderView(FormView): @cache_control(no_cache=True, must_revalidate=True, no_store=True) def get(self, request, *args, **kwargs): - logger.debug("Session: %s" % str(request.session)) request.session.pop('vat_validation_status') + request.session.pop('card_id') + request.session.pop('token') + request.session.pop('billing_address_data') + logger.debug("Session: %s" % str(request.session)) + for key, value in request.session.items(): + logger.debug("Session: %s %s" % (key, value)) if (('type' in request.GET and request.GET['type'] == 'generic') or 'product_slug' in kwargs): request.session['generic_payment_type'] = 'generic' From 6a7373523e2f0d71e11916b81081cb972e0aa6cd Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 20:39:41 +0530 Subject: [PATCH 117/257] Set default card before making payments --- datacenterlight/views.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 388d78b2..a59cb325 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -792,6 +792,10 @@ class OrderConfirmationView(DetailView, FormView): 'brand': user_card_detail.brand, 'card_id': user_card_detail.card_id } + UserCardDetail.set_default_card( + stripe_api_cus_id=stripe_api_cus_id, + stripe_source_id=user_card_detail.card_id + ) logger.debug("card_details_dict=%s" % card_details_dict) else: response = { @@ -1142,11 +1146,11 @@ def do_provisioning(request, user, stripe_api_cus_id, card_details_response, 'brand': user_card_detail.brand, 'card_id': user_card_detail.card_id } - if not user_card_detail.preferred: - UserCardDetail.set_default_card( - stripe_api_cus_id=stripe_api_cus_id, - stripe_source_id=user_card_detail.card_id - ) + #if not user_card_detail.preferred: + UserCardDetail.set_default_card( + stripe_api_cus_id=stripe_api_cus_id, + stripe_source_id=user_card_detail.card_id + ) else: logger.debug("card_id was NOT in request, using " "card_details_response") From 968eaaf6a40c273d1c7c93128a1e5b53602dd067 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 20:40:20 +0530 Subject: [PATCH 118/257] For generic payments, take users to invoice page after purchase --- datacenterlight/views.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index a59cb325..3f0d87d6 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -987,15 +987,18 @@ class OrderConfirmationView(DetailView, FormView): 'success': { 'status': True, 'redirect': ( - reverse('hosting:virtual_machines') + reverse('hosting:invoices') if request.user.is_authenticated() else reverse('datacenterlight:index') ), 'msg_title': str(_('Thank you for the order.')), 'msg_body': str( - _('Your VM will be up and running in a few moments.' - ' We will send you a confirmation email as soon as' - ' it is ready.')) + _('Your product will be provisioned as soon as' + ' we receive a payment confirmation from ' + 'Stripe. We will send you a confirmation ' + 'email. You can always contact us at ' + 'support@datacenterlight.ch') + ) }, 'error': { 'status': False, From eefabe45b62a9cb0e496e0d7c2e3c62c748be21f Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 23 Dec 2020 20:41:31 +0530 Subject: [PATCH 119/257] Login new users only for non-SCA generic subscription payments only --- datacenterlight/views.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 3f0d87d6..988be8d0 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1039,16 +1039,13 @@ class OrderConfirmationView(DetailView, FormView): template, request.session.get('billing_address_data'), self.request ) - try: - custom_user = CustomUser.objects.get(email=user.get('email')) - login(self.request, custom_user) - except (CustomUser.DoesNotExist, - CustomUser.MultipleObjectsReturned) as ex: - logger.error(str(ex)) if (provisioning_response and - type(provisioning_response) == JsonResponse): - return provisioning_response + type(provisioning_response['response']) == JsonResponse): + new_user = provisioning_response.get('user', None) + if new_user: + login(self.request, new_user) + return provisioning_response['response'] response = { 'status': True, @@ -1103,6 +1100,7 @@ def do_provisioning(request, user, stripe_api_cus_id, card_details_response, """ # Create user if the user is not logged in and if he is not already # registered + new_user = None try: custom_user = CustomUser.objects.get( email=user.get('email')) @@ -1299,7 +1297,7 @@ def do_provisioning(request, user, stripe_api_cus_id, card_details_response, } logger.debug("after response") logger.debug(str(response)) - return JsonResponse(response) + return {'response': JsonResponse(response), 'user': new_user} user = { 'name': custom_user.name, From 39c8e35eca86ec4db33aed04af9ec1b093255151 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 24 Dec 2020 06:25:22 +0530 Subject: [PATCH 120/257] Add logger messages --- utils/stripe_utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index b8667a40..e25e736f 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -34,6 +34,7 @@ def handleStripeError(f): logger.error(str(e)) return response except stripe.error.RateLimitError as e: + logger.error(str(e)) response.update( {'error': "Too many requests made to the API too quickly"}) return response @@ -317,13 +318,16 @@ class StripeUtils(object): ] :return: The subscription StripeObject """ - + logger.debug("Subscribing %s to plan %s : coupon = %s" % ( + customer, str(plans), str(coupon) + )) subscription_result = self.stripe.Subscription.create( customer=customer, items=plans, trial_end=trial_end, coupon=coupon, default_tax_rates=tax_rates, payment_behavior='allow_incomplete' ) + logger.debug("Done subscribing") return subscription_result @handleStripeError From 98628596f02509497c6c2742ea2a8662c58fb120 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 24 Dec 2020 06:25:37 +0530 Subject: [PATCH 121/257] Add params to docstring --- utils/stripe_utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index e25e736f..8455cbb5 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -305,6 +305,8 @@ class StripeUtils(object): """ Subscribes the given customer to the list of given plans + :param tax_rates: + :param coupon: :param customer: The stripe customer identifier :param plans: A list of stripe plans. :param trial_end: An integer representing when the Stripe subscription From 82359064cd1f8b96055d2c5e3dde0f455e7dea4c Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 24 Dec 2020 06:47:33 +0530 Subject: [PATCH 122/257] Handle creation of correctly --- utils/stripe_utils.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index 8455cbb5..65b47228 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -70,7 +70,7 @@ class StripeUtils(object): CURRENCY = 'chf' INTERVAL = 'month' SUCCEEDED_STATUS = 'succeeded' - STRIPE_PLAN_ALREADY_EXISTS = 'Plan already exists' + RESOURCE_ALREADY_EXISTS_ERROR_CODE = 'resource_already_exists' STRIPE_NO_SUCH_PLAN = 'No such plan' PLAN_EXISTS_ERROR_MSG = 'Plan {} exists already.\nCreating a local StripePlan now.' PLAN_DOES_NOT_EXIST_ERROR_MSG = 'Plan {} does not exist.' @@ -268,11 +268,17 @@ class StripeUtils(object): stripe_plan_db_obj = StripePlan.objects.create( stripe_plan_id=stripe_plan_id) except stripe.error.InvalidRequestError as e: - if self.STRIPE_PLAN_ALREADY_EXISTS in str(e): + logger.error(str(e)) + logger.error("error_code = " % e.error.code) + if self.RESOURCE_ALREADY_EXISTS_ERROR_CODE in e.error.code: logger.debug( self.PLAN_EXISTS_ERROR_MSG.format(stripe_plan_id)) - stripe_plan_db_obj = StripePlan.objects.create( + stripe_plan_db_obj, c = StripePlan.objects.get_or_create( stripe_plan_id=stripe_plan_id) + if c: + logger.debug("Created stripe plan %s" % stripe_plan_id) + else: + logger.debug("Plan %s exists already" % stripe_plan_id) return stripe_plan_db_obj @handleStripeError From 624cc45c12090aef90fa9da5f8c21f9f4cc072bc Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 24 Dec 2020 06:51:29 +0530 Subject: [PATCH 123/257] Log error dict --- 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 65b47228..d32bd4e5 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -269,7 +269,7 @@ class StripeUtils(object): stripe_plan_id=stripe_plan_id) except stripe.error.InvalidRequestError as e: logger.error(str(e)) - logger.error("error_code = " % e.error.code) + logger.error("error_code = " % e.__dict__) if self.RESOURCE_ALREADY_EXISTS_ERROR_CODE in e.error.code: logger.debug( self.PLAN_EXISTS_ERROR_MSG.format(stripe_plan_id)) From acba77976d4f4d5ec4d0673a63d0b46a711c813b Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 24 Dec 2020 06:55:19 +0530 Subject: [PATCH 124/257] Add missing formatting identifier --- 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 d32bd4e5..f5df8aa8 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -269,7 +269,7 @@ class StripeUtils(object): stripe_plan_id=stripe_plan_id) except stripe.error.InvalidRequestError as e: logger.error(str(e)) - logger.error("error_code = " % e.__dict__) + logger.error("error_code = %s" % str(e.__dict__)) if self.RESOURCE_ALREADY_EXISTS_ERROR_CODE in e.error.code: logger.debug( self.PLAN_EXISTS_ERROR_MSG.format(stripe_plan_id)) From 1c4f29777555dd8ab6f32021a765e0302104cbc2 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 24 Dec 2020 19:34:06 +0530 Subject: [PATCH 125/257] Begin migrating to PaymentIntent --- .../datacenterlight/landing_payment.html | 1 + datacenterlight/views.py | 16 ++++++++++++++++ hosting/static/hosting/js/payment.js | 2 +- utils/stripe_utils.py | 13 +++++++++++++ webhook/views.py | 5 ++++- 5 files changed, 35 insertions(+), 2 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/landing_payment.html b/datacenterlight/templates/datacenterlight/landing_payment.html index 66a0e63f..f112f0d9 100644 --- a/datacenterlight/templates/datacenterlight/landing_payment.html +++ b/datacenterlight/templates/datacenterlight/landing_payment.html @@ -187,6 +187,7 @@ window.enter_your_card_text = '{%trans "Enter your credit card number" %}'; (function () { + window.paymentIntentSecret = "{{payment_intent_secret}}"; window.stripeKey = "{{stripe_key}}"; window.current_lan = "{{LANGUAGE_CODE}}"; })(); diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 988be8d0..3c92506b 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -285,12 +285,28 @@ class PaymentOrderView(FormView): product = GenericProduct.objects.get( id=self.request.session['product_id'] ) + # TODO get the correct price of the product from order + # confirmation + stripe_utils = StripeUtils() + payment_intent_response = stripe_utils.get_payment_intent( + float(product.get_actual_price()) + ) + if not payment_intent_response.get('response_object'): + logger.error("Could not create payment_intent %s" % + str(payment_intent_response)) + else: + logger.debug("*******") + logger.debug( + "payment_intent_obj = %s" % + str(payment_intent_response.get('response_object'))) + logger.debug("*******") context.update({'generic_payment_form': ProductPaymentForm( prefix='generic_payment_form', initial={'product_name': product.product_name, 'amount': float(product.get_actual_price()), 'recurring': product.product_is_subscription, 'description': product.product_description, + 'payment_intent_secret': 'secret_here' }, product_id=product.id ), }) diff --git a/hosting/static/hosting/js/payment.js b/hosting/static/hosting/js/payment.js index fa89f218..be94a866 100644 --- a/hosting/static/hosting/js/payment.js +++ b/hosting/static/hosting/js/payment.js @@ -197,7 +197,7 @@ $(document).ready(function () { } else { var process_text = "Processing"; if (typeof window.processing_text !== 'undefined') { - process_text = window.processing_text + process_text = window.processing_text; } $form_new.find('[type=submit]').html(process_text + ' '); diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index f5df8aa8..bf731508 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -493,6 +493,19 @@ class StripeUtils(object): ) return tax_id_obj + @handleStripeError + def get_payment_intent(self, amount): + """ + Adds VM metadata to a subscription + :param amount: the amount of payment_intent + :return: + """ + payment_intent_obj = stripe.PaymentIntent.create( + amount=amount, + currency='chf' + ) + return payment_intent_obj + def compare_vat_numbers(self, vat1, vat2): _vat1 = vat1.replace(" ", "").replace(".", "").replace("-","") _vat2 = vat2.replace(" ", "").replace(".", "").replace("-","") diff --git a/webhook/views.py b/webhook/views.py index b0eac5df..416d07b7 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -221,7 +221,10 @@ def handle_webhook(request): if (invoice_obj.payment_failed and invoice_obj.billing_reason == "subscription_update"): logger.debug("Payment failed, inform the users") - + elif event.type == 'payment_intent.succeeded': + payment_intent_obj = event.data.object + logger.debug("Webhook Event: payment_intent.succeeded") + logger.debug("payment_intent_obj %s " % str(payment_intent_obj)) else: logger.error("Unhandled event : " + event.type) return HttpResponse(status=200) From ec1da8fbdfabcf0b448b1ddca6ebb88c276417bd Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 24 Dec 2020 19:40:39 +0530 Subject: [PATCH 126/257] Use cents price for Stripe --- datacenterlight/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 3c92506b..cd1de47d 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -289,7 +289,7 @@ class PaymentOrderView(FormView): # confirmation stripe_utils = StripeUtils() payment_intent_response = stripe_utils.get_payment_intent( - float(product.get_actual_price()) + int(product.get_actual_price() * 100) ) if not payment_intent_response.get('response_object'): logger.error("Could not create payment_intent %s" % From e9c596de66fa3720eafc25fff703f6d2b336d983 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 24 Dec 2020 20:03:46 +0530 Subject: [PATCH 127/257] Test PaymentIntent for payment of generic onetime products --- datacenterlight/views.py | 23 +++++++++++------------ hosting/static/hosting/js/payment.js | 23 ++++++++++++++++++++++- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index cd1de47d..bc586ec2 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -291,23 +291,22 @@ class PaymentOrderView(FormView): payment_intent_response = stripe_utils.get_payment_intent( int(product.get_actual_price() * 100) ) - if not payment_intent_response.get('response_object'): + payment_intent = payment_intent_response.get('response_object') + if not payment_intent: logger.error("Could not create payment_intent %s" % str(payment_intent_response)) else: - logger.debug("*******") - logger.debug( - "payment_intent_obj = %s" % - str(payment_intent_response.get('response_object'))) - logger.debug("*******") + logger.debug("payment_intent_obj = %s" % + str(payment_intent)) context.update({'generic_payment_form': ProductPaymentForm( prefix='generic_payment_form', - initial={'product_name': product.product_name, - 'amount': float(product.get_actual_price()), - 'recurring': product.product_is_subscription, - 'description': product.product_description, - 'payment_intent_secret': 'secret_here' - }, + initial={ + 'product_name': product.product_name, + 'amount': float(product.get_actual_price()), + 'recurring': product.product_is_subscription, + 'description': product.product_description, + 'payment_intent_secret': payment_intent.client_secret + }, product_id=product.id ), }) else: diff --git a/hosting/static/hosting/js/payment.js b/hosting/static/hosting/js/payment.js index be94a866..933e15df 100644 --- a/hosting/static/hosting/js/payment.js +++ b/hosting/static/hosting/js/payment.js @@ -178,7 +178,28 @@ $(document).ready(function () { } var $form_new = $('#payment-form-new'); - $form_new.submit(payWithStripe_new); + $form_new.submit(payWithPaymentIntent); + function payWithPaymentIntent(e) { + e.preventDefault(); + + stripe.confirmCardPayment( + window.paymentIntentSecret, + { + payment_method: {card: cardNumberElement} + } + ).then(function(result) { + if (result.error) { + // Display error.message in your UI. + var errorElement = document.getElementById('card-errors'); + errorElement.textContent = result.error.message; + } else { + // The payment has succeeded + // Display a success message + alert("Thanks for the order. Your product will be provisioned " + + "as soon as we receive the payment. Thank you."); + } + }); + } function payWithStripe_new(e) { e.preventDefault(); From 35cc9d422925826007a18fa6f60085f7bf7782f2 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 24 Dec 2020 20:07:31 +0530 Subject: [PATCH 128/257] Log client secret --- datacenterlight/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index bc586ec2..5d12a878 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -296,8 +296,8 @@ class PaymentOrderView(FormView): logger.error("Could not create payment_intent %s" % str(payment_intent_response)) else: - logger.debug("payment_intent_obj = %s" % - str(payment_intent)) + logger.debug("payment_intent.client_secret = %s" % + str(payment_intent.client_secret)) context.update({'generic_payment_form': ProductPaymentForm( prefix='generic_payment_form', initial={ From c3286a68a522eb3719a04bbb85491c1620571099 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 10:04:21 +0530 Subject: [PATCH 129/257] Use payment method instead of token and PaymentIntent all over --- .../datacenterlight/landing_payment.html | 1 - .../datacenterlight/order_detail.html | 13 ++ datacenterlight/views.py | 108 ++++++++----- hosting/static/hosting/js/payment.js | 150 +++++++++++------- .../hosting/js/virtual_machine_detail.js | 31 +++- hosting/views.py | 15 +- membership/models.py | 2 +- utils/stripe_utils.py | 46 ++++-- 8 files changed, 245 insertions(+), 121 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/landing_payment.html b/datacenterlight/templates/datacenterlight/landing_payment.html index f112f0d9..66a0e63f 100644 --- a/datacenterlight/templates/datacenterlight/landing_payment.html +++ b/datacenterlight/templates/datacenterlight/landing_payment.html @@ -187,7 +187,6 @@ window.enter_your_card_text = '{%trans "Enter your credit card number" %}'; (function () { - window.paymentIntentSecret = "{{payment_intent_secret}}"; window.stripeKey = "{{stripe_key}}"; window.current_lan = "{{LANGUAGE_CODE}}"; })(); diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index d8eb4934..1914acb8 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -2,6 +2,14 @@ {% load staticfiles bootstrap3 i18n custom_tags humanize %} {% block content %} +
    {% if messages %}
    @@ -321,5 +329,10 @@ {%endblock%} diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 5d12a878..f50cf422 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -285,28 +285,13 @@ class PaymentOrderView(FormView): product = GenericProduct.objects.get( id=self.request.session['product_id'] ) - # TODO get the correct price of the product from order - # confirmation - stripe_utils = StripeUtils() - payment_intent_response = stripe_utils.get_payment_intent( - int(product.get_actual_price() * 100) - ) - payment_intent = payment_intent_response.get('response_object') - if not payment_intent: - logger.error("Could not create payment_intent %s" % - str(payment_intent_response)) - else: - logger.debug("payment_intent.client_secret = %s" % - str(payment_intent.client_secret)) context.update({'generic_payment_form': ProductPaymentForm( prefix='generic_payment_form', - initial={ - 'product_name': product.product_name, - 'amount': float(product.get_actual_price()), - 'recurring': product.product_is_subscription, - 'description': product.product_description, - 'payment_intent_secret': payment_intent.client_secret - }, + initial={'product_name': product.product_name, + 'amount': float(product.get_actual_price()), + 'recurring': product.product_is_subscription, + 'description': product.product_description, + }, product_id=product.id ), }) else: @@ -479,8 +464,9 @@ class PaymentOrderView(FormView): context['generic_payment_form'] = generic_payment_form context['billing_address_form'] = address_form return self.render_to_response(context) - token = address_form.cleaned_data.get('token') - if token is '': + id_payment_method = self.request.POST.get('id_payment_method', + None) + if id_payment_method is None: card_id = address_form.cleaned_data.get('card') logger.debug("token is empty and card_id is %s" % card_id) try: @@ -508,15 +494,16 @@ class PaymentOrderView(FormView): ) request.session['card_id'] = user_card_detail.id else: - request.session['token'] = token - logger.debug("token is %s" % token) + request.session["id_payment_method"] = id_payment_method + logger.debug("id_payment_method is %s" % id_payment_method) if request.user.is_authenticated(): this_user = { 'email': request.user.email, 'name': request.user.name } customer = StripeCustomer.get_or_create( - email=this_user.get('email'), token=token + email=this_user.get('email'), + id_payment_method=id_payment_method ) else: user_email = address_form.cleaned_data.get('email') @@ -539,7 +526,7 @@ class PaymentOrderView(FormView): ) customer = StripeCustomer.create_stripe_api_customer( email=user_email, - token=token, + token=id_payment_method, customer_name=user_name) except CustomUser.DoesNotExist: logger.debug( @@ -550,7 +537,7 @@ class PaymentOrderView(FormView): ) customer = StripeCustomer.create_stripe_api_customer( email=user_email, - token=token, + token=id_payment_method, customer_name=user_name) billing_address = address_form.save() @@ -622,11 +609,11 @@ class OrderConfirmationView(DetailView, FormView): if (('specs' not in request.session or 'user' not in request.session) and 'generic_payment_type' not in request.session): return HttpResponseRedirect(reverse('datacenterlight:index')) - if 'token' in self.request.session: - token = self.request.session['token'] + if 'id_payment_method' in self.request.session: + payment_method = self.request.session['id_payment_method'] stripe_utils = StripeUtils() - card_details = stripe_utils.get_cards_details_from_token( - token + card_details = stripe_utils.get_cards_details_from_payment_method( + payment_method ) if not card_details.get('response_object'): return HttpResponseRedirect(reverse('hosting:payment')) @@ -635,6 +622,7 @@ class OrderConfirmationView(DetailView, FormView): context['cc_brand'] = card_details_response['brand'] context['cc_exp_year'] = card_details_response['exp_year'] context['cc_exp_month'] = '{:02d}'.format(card_details_response['exp_month']) + context['id_payment_method'] = payment_method else: card_id = self.request.session.get('card_id') card_detail = UserCardDetail.objects.get(id=card_id) @@ -718,6 +706,27 @@ class OrderConfirmationView(DetailView, FormView): 'form': UserHostingKeyForm(request=self.request), 'keys': get_all_public_keys(self.request.user) }) + + # Obtain PaymentIntent so that we can initiate and charge/subscribe + # the customer + stripe_utils = StripeUtils() + payment_intent_response = stripe_utils.get_payment_intent( + int(request.session['generic_payment_details']['amount'] * + 100), + customer=request.session['customer'] + ) + payment_intent = payment_intent_response.get( + 'response_object') + if not payment_intent: + logger.error("Could not create payment_intent %s" % + str(payment_intent_response)) + else: + logger.debug("payment_intent.client_secret = %s" % + str(payment_intent.client_secret)) + context.update({ + 'payment_intent_secret': payment_intent.client_secret + }) + context.update({ 'site_url': reverse('datacenterlight:index'), 'page_header_text': _('Confirm Order'), @@ -725,6 +734,8 @@ class OrderConfirmationView(DetailView, FormView): request.session.get('billing_address_data') ), 'cms_integration': get_cms_integration('default'), + 'error_msg': get_error_response_dict("Error", request), + 'stripe_key': settings.STRIPE_API_PUBLIC_KEY, }) return render(request, self.template_name, context) @@ -765,9 +776,9 @@ class OrderConfirmationView(DetailView, FormView): 'generic_payment_details': generic_payment_details } - if 'token' in request.session: - card_details = stripe_utils.get_cards_details_from_token( - request.session.get('token') + if 'id_payment_method' in request.session: + card_details = stripe_utils.get_cards_details_from_payment_method( + request.session.get('id_payment_method') ) logger.debug( "card_details=%s" % (card_details)) @@ -788,7 +799,7 @@ class OrderConfirmationView(DetailView, FormView): ) if not ucd: acc_result = stripe_utils.associate_customer_card( - stripe_api_cus_id, request.session['token'], + stripe_api_cus_id, request.session['id_payment_method'], set_as_default=True ) if acc_result['response_object'] is None: @@ -799,6 +810,21 @@ class OrderConfirmationView(DetailView, FormView): ) ) return show_error(msg, self.request) + else: + # Associate PaymentMethod with the stripe customer + # and set it as the default source + acc_result = stripe_utils.associate_customer_card( + stripe_api_cus_id, request.session['id_payment_method'], + set_as_default=True + ) + if acc_result['response_object'] is None: + msg = _( + 'An error occurred while associating the card.' + ' Details: {details}'.format( + details=acc_result['error'] + ) + ) + return show_error(msg, self.request) elif 'card_id' in request.session: card_id = request.session.get('card_id') user_card_detail = UserCardDetail.objects.get(id=card_id) @@ -1334,9 +1360,7 @@ def do_provisioning(request, user, stripe_api_cus_id, card_details_response, clear_all_session_vars(real_request) -def show_error(msg, request): - messages.add_message(request, messages.ERROR, msg, - extra_tags='failed_payment') +def get_error_response_dict(msg, request): response = { 'status': False, 'redirect': "{url}#{section}".format( @@ -1356,4 +1380,10 @@ def show_error(msg, request): ' On close of this popup, you will be redirected back to' ' the payment page.')) } - return JsonResponse(response) + return response + + +def show_error(msg, request): + messages.add_message(request, messages.ERROR, msg, + extra_tags='failed_payment') + return JsonResponse(get_error_response_dict(msg,request)) diff --git a/hosting/static/hosting/js/payment.js b/hosting/static/hosting/js/payment.js index 933e15df..a2e2717a 100644 --- a/hosting/static/hosting/js/payment.js +++ b/hosting/static/hosting/js/payment.js @@ -84,68 +84,72 @@ $(document).ready(function () { var hasCreditcard = window.hasCreditcard || false; if (!hasCreditcard && window.stripeKey) { var stripe = Stripe(window.stripeKey); - var element_style = { - fonts: [{ - family: 'lato-light', - src: 'url(https://cdn.jsdelivr.net/font-lato/2.0/Lato/Lato-Light.woff) format("woff2")' - }, { - family: 'lato-regular', - src: 'url(https://cdn.jsdelivr.net/font-lato/2.0/Lato/Lato-Regular.woff) format("woff2")' - } - ], - locale: window.current_lan - }; - var elements = stripe.elements(element_style); - var credit_card_text_style = { - base: { - iconColor: '#666EE8', - color: '#31325F', - lineHeight: '25px', - fontWeight: 300, - fontFamily: "'lato-light', sans-serif", - fontSize: '14px', - '::placeholder': { - color: '#777' + if (window.pm_id) { + + } else { + var element_style = { + fonts: [{ + family: 'lato-light', + src: 'url(https://cdn.jsdelivr.net/font-lato/2.0/Lato/Lato-Light.woff) format("woff2")' + }, { + family: 'lato-regular', + src: 'url(https://cdn.jsdelivr.net/font-lato/2.0/Lato/Lato-Regular.woff) format("woff2")' } - }, - invalid: { - iconColor: '#eb4d5c', - color: '#eb4d5c', - lineHeight: '25px', - fontWeight: 300, - fontFamily: "'lato-regular', sans-serif", - fontSize: '14px', - '::placeholder': { + ], + locale: window.current_lan + }; + var elements = stripe.elements(element_style); + var credit_card_text_style = { + base: { + iconColor: '#666EE8', + color: '#31325F', + lineHeight: '25px', + fontWeight: 300, + fontFamily: "'lato-light', sans-serif", + fontSize: '14px', + '::placeholder': { + color: '#777' + } + }, + invalid: { + iconColor: '#eb4d5c', color: '#eb4d5c', - fontWeight: 400 + lineHeight: '25px', + fontWeight: 300, + fontFamily: "'lato-regular', sans-serif", + fontSize: '14px', + '::placeholder': { + color: '#eb4d5c', + fontWeight: 400 + } } - } - }; + }; - var enter_ccard_text = "Enter your credit card number"; - if (typeof window.enter_your_card_text !== 'undefined') { - enter_ccard_text = window.enter_your_card_text; + var enter_ccard_text = "Enter your credit card number"; + if (typeof window.enter_your_card_text !== 'undefined') { + enter_ccard_text = window.enter_your_card_text; + } + var cardNumberElement = elements.create('cardNumber', { + style: credit_card_text_style, + placeholder: enter_ccard_text + }); + cardNumberElement.mount('#card-number-element'); + + var cardExpiryElement = elements.create('cardExpiry', { + style: credit_card_text_style + }); + cardExpiryElement.mount('#card-expiry-element'); + + var cardCvcElement = elements.create('cardCvc', { + style: credit_card_text_style + }); + cardCvcElement.mount('#card-cvc-element'); + cardNumberElement.on('change', function (event) { + if (event.brand) { + setBrandIcon(event.brand); + } + }); } - var cardNumberElement = elements.create('cardNumber', { - style: credit_card_text_style, - placeholder: enter_ccard_text - }); - cardNumberElement.mount('#card-number-element'); - - var cardExpiryElement = elements.create('cardExpiry', { - style: credit_card_text_style - }); - cardExpiryElement.mount('#card-expiry-element'); - - var cardCvcElement = elements.create('cardCvc', { - style: credit_card_text_style - }); - cardCvcElement.mount('#card-cvc-element'); - cardNumberElement.on('change', function (event) { - if (event.brand) { - setBrandIcon(event.brand); - } - }); } var submit_form_btn = $('#payment_button_with_creditcard'); @@ -163,7 +167,7 @@ $(document).ready(function () { if (parts.length === 2) return parts.pop().split(";").shift(); } - function submitBillingForm() { + function submitBillingForm(pmId) { var billing_form = $('#billing-form'); var recurring_input = $('#id_generic_payment_form-recurring'); billing_form.append(''); @@ -174,20 +178,46 @@ $(document).ready(function () { billing_form.append(''); } billing_form.append(''); + billing_form.append(''); billing_form.submit(); } var $form_new = $('#payment-form-new'); $form_new.submit(payWithPaymentIntent); + window.result = ""; + window.card = ""; function payWithPaymentIntent(e) { e.preventDefault(); - stripe.confirmCardPayment( + function stripePMHandler(paymentMethod) { + // Insert the token ID into the form so it gets submitted to the server + console.log(paymentMethod); + $('#id_payment_method').val(paymentMethod.id); + submitBillingForm(paymentMethod.id); + } + stripe.createPaymentMethod({ + type: 'card', + card: cardNumberElement, + }) + .then(function(result) { + // Handle result.error or result.paymentMethod + window.result = result; + if(result.error) { + var errorElement = document.getElementById('card-errors'); + errorElement.textContent = result.error.message; + } else { + console.log("created paymentMethod " + result.paymentMethod.id); + stripePMHandler(result.paymentMethod); + } + }); + window.card = cardNumberElement; + /* stripe.confirmCardPayment( window.paymentIntentSecret, { payment_method: {card: cardNumberElement} } ).then(function(result) { + window.result = result; if (result.error) { // Display error.message in your UI. var errorElement = document.getElementById('card-errors'); @@ -198,7 +228,7 @@ $(document).ready(function () { alert("Thanks for the order. Your product will be provisioned " + "as soon as we receive the payment. Thank you."); } - }); + }); */ } function payWithStripe_new(e) { e.preventDefault(); diff --git a/hosting/static/hosting/js/virtual_machine_detail.js b/hosting/static/hosting/js/virtual_machine_detail.js index dec8e680..e1bfd3a8 100644 --- a/hosting/static/hosting/js/virtual_machine_detail.js +++ b/hosting/static/hosting/js/virtual_machine_detail.js @@ -92,6 +92,35 @@ $(document).ready(function() { }); var create_vm_form = $('#virtual_machine_create_form'); + create_vm_form.submit(placeOrderPaymentIntent); + + function placeOrderPaymentIntent(e) { + e.preventDefault(); + var stripe = Stripe(window.stripeKey); + stripe.confirmCardPayment( + window.paymentIntentSecret, + { + payment_method: window.pm_id + } + ).then(function(result) { + window.result = result; + if (result.error) { + // Display error.message in your UI. + var errorElement = document.getElementById('card-errors'); + errorElement.textContent = result.error.message; + } else { + // The payment has succeeded + // Display a success message + alert("Thanks for the order. Your product will be provisioned " + + "as soon as we receive the payment. Thank you."); + modal_btn.attr('href', err).removeClass('hide'); + fa_icon.attr('class', 'checkmark'); + $('#createvm-modal-title').text(data.success.msg_title); + $('#createvm-modal-body').html(data.success.msg_body); + } + }); + } + /* create_vm_form.submit(function () { $('#btn-create-vm').prop('disabled', true); $.ajax({ @@ -154,7 +183,7 @@ $(document).ready(function() { } }); return false; - }); + });*/ $('#createvm-modal').on('hidden.bs.modal', function () { $(this).find('.modal-footer .btn').addClass('hide'); }); diff --git a/hosting/views.py b/hosting/views.py index cc038d12..f18a22b7 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -694,16 +694,17 @@ class SettingsView(LoginRequiredMixin, FormView): msg = _("Billing address updated successfully") messages.add_message(request, messages.SUCCESS, msg) else: - token = form.cleaned_data.get('token') + # TODO : Test this flow + id_payment_method = form.cleaned_data.get('id_payment_method') stripe_utils = StripeUtils() - card_details = stripe_utils.get_cards_details_from_token( - token + card_details = stripe_utils.get_cards_details_from_payment_method( + id_payment_method ) if not card_details.get('response_object'): form.add_error("__all__", card_details.get('error')) return self.render_to_response(self.get_context_data()) stripe_customer = StripeCustomer.get_or_create( - email=request.user.email, token=token + email=request.user.email, id_payment_method=id_payment_method ) card = card_details['response_object'] if UserCardDetail.get_user_card_details(stripe_customer, card): @@ -711,7 +712,7 @@ class SettingsView(LoginRequiredMixin, FormView): messages.add_message(request, messages.ERROR, msg) else: acc_result = stripe_utils.associate_customer_card( - request.user.stripecustomer.stripe_id, token + request.user.stripecustomer.stripe_id, id_payment_method ) if acc_result['response_object'] is None: msg = _( @@ -1085,7 +1086,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): template, specs, stripe_customer_id, billing_address_data, vm_template_id, stripe_api_cus_id) ) - if 'token' in self.request.session: + if 'id_payment_method' in self.request.session: card_details = stripe_utils.get_cards_details_from_token( request.session['token'] ) @@ -1102,7 +1103,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): ) if not ucd: acc_result = stripe_utils.associate_customer_card( - stripe_api_cus_id, request.session['token'], + stripe_api_cus_id, request.session['id_payment_method'], set_as_default=True ) if acc_result['response_object'] is None: diff --git a/membership/models.py b/membership/models.py index 703b4800..079b60e0 100644 --- a/membership/models.py +++ b/membership/models.py @@ -296,7 +296,7 @@ class StripeCustomer(models.Model): return None @classmethod - def get_or_create(cls, email=None, token=None): + def get_or_create(cls, email=None, token=None, id_payment_method=None): """ Check if there is a registered stripe customer with that email or create a new one diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index bf731508..a4cc2c6a 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -83,12 +83,15 @@ class StripeUtils(object): customer.save() @handleStripeError - def associate_customer_card(self, stripe_customer_id, token, + def associate_customer_card(self, stripe_customer_id, id_payment_method, set_as_default=False): customer = stripe.Customer.retrieve(stripe_customer_id) - card = customer.sources.create(source=token) + stripe.PaymentMethod.attach( + id_payment_method, + customer=stripe_customer_id, + ) if set_as_default: - customer.default_source = card.id + customer.invoice_settings.default_payment_method = id_payment_method customer.save() return True @@ -100,6 +103,7 @@ class StripeUtils(object): @handleStripeError def update_customer_card(self, customer_id, token): + # TODO replace token with payment intent customer = stripe.Customer.retrieve(customer_id) current_card_token = customer.default_source customer.sources.retrieve(current_card_token).delete() @@ -188,6 +192,24 @@ class StripeUtils(object): } return card_details + @handleStripeError + def get_cards_details_from_payment_method(self, payment_method_id): + payment_method = stripe.PaymentMethod.retrieve(payment_method_id) + # payment_method does not always seem to have a card with id + # if that is the case, fallback to payment_method_id for card_id + card_id = payment_method_id + if hasattr(payment_method.card, 'id'): + card_id = payment_method.card.id + card_details = { + 'last4': payment_method.card.last4, + 'brand': payment_method.card.brand, + 'exp_month': payment_method.card.exp_month, + 'exp_year': payment_method.card.exp_year, + 'fingerprint': payment_method.card.fingerprint, + 'card_id': card_id + } + return card_details + def check_customer(self, stripe_cus_api_id, user, token): try: customer = stripe.Customer.retrieve(stripe_cus_api_id) @@ -207,11 +229,11 @@ class StripeUtils(object): return customer @handleStripeError - def create_customer(self, token, email, name=None): + def create_customer(self, id_payment_method, email, name=None): if name is None or name.strip() == "": name = email customer = self.stripe.Customer.create( - source=token, + payment_method=id_payment_method, description=name, email=email ) @@ -494,19 +516,19 @@ class StripeUtils(object): return tax_id_obj @handleStripeError - def get_payment_intent(self, amount): - """ - Adds VM metadata to a subscription - :param amount: the amount of payment_intent - :return: + def get_payment_intent(self, amount, customer): + """ Create a stripe PaymentIntent of the given amount and return it + :param amount: the amount of payment_intent + :return: """ payment_intent_obj = stripe.PaymentIntent.create( amount=amount, - currency='chf' + currency='chf', + customer=customer ) return payment_intent_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 + return True if _vat1 == _vat2 else False From 42c9ec6f2839e3c1a96fc2a5a18b2b9498baa30d Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 10:32:25 +0530 Subject: [PATCH 130/257] Handle js success/error messages --- .../datacenterlight/order_detail.html | 3 ++ datacenterlight/views.py | 8 +++++ .../hosting/js/virtual_machine_detail.js | 31 ++++++++++--------- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 1914acb8..b96d5123 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -333,6 +333,9 @@ var error_url = '{{ error_msg.redirect }}'; var error_msg = '{{ error_msg.msg_body }}'; var error_title = '{{ error_msg.msg_title }}'; + var success_msg = '{{ success_msg.msg_body }}'; + var success_title = '{{ success_msg.msg_title }}'; + var success_url = '{{ success_msg.redirect }}'; window.stripeKey = "{{stripe_key}}"; {%endblock%} diff --git a/datacenterlight/views.py b/datacenterlight/views.py index f50cf422..43cf7eaa 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -735,6 +735,14 @@ class OrderConfirmationView(DetailView, FormView): ), 'cms_integration': get_cms_integration('default'), 'error_msg': get_error_response_dict("Error", request), + 'success_msg': { + 'msg_title': _("Thank you !"), + 'msg_body': _("Your product will be provisioned as soon as " + "we receive the payment."), + 'redirect': reverse('hosting:invoices') if + request.user.is_authenticated() else + reverse('datacenterlight:index') + }, 'stripe_key': settings.STRIPE_API_PUBLIC_KEY, }) return render(request, self.template_name, context) diff --git a/hosting/static/hosting/js/virtual_machine_detail.js b/hosting/static/hosting/js/virtual_machine_detail.js index e1bfd3a8..5e13519c 100644 --- a/hosting/static/hosting/js/virtual_machine_detail.js +++ b/hosting/static/hosting/js/virtual_machine_detail.js @@ -104,20 +104,23 @@ $(document).ready(function() { } ).then(function(result) { window.result = result; - if (result.error) { - // Display error.message in your UI. - var errorElement = document.getElementById('card-errors'); - errorElement.textContent = result.error.message; - } else { - // The payment has succeeded - // Display a success message - alert("Thanks for the order. Your product will be provisioned " + - "as soon as we receive the payment. Thank you."); - modal_btn.attr('href', err).removeClass('hide'); - fa_icon.attr('class', 'checkmark'); - $('#createvm-modal-title').text(data.success.msg_title); - $('#createvm-modal-body').html(data.success.msg_body); - } + fa_icon = $('.modal-icon > .fa'); + modal_btn = $('#createvm-modal-done-btn'); + if (result.error) { + // Display error.message in your UI. + modal_btn.attr('href', error_url).removeClass('hide'); + fa_icon.attr('class', 'fa fa-close'); + modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide'); + $('#createvm-modal-title').text(error_title); + $('#createvm-modal-body').html(result.error.message + " " + error_msg); + } else { + // The payment has succeeded + // Display a success message + modal_btn.attr('href', success_url).removeClass('hide'); + fa_icon.attr('class', 'checkmark'); + $('#createvm-modal-title').text(success_title); + $('#createvm-modal-body').html(success_msg); + } }); } /* From ba92c8e416222a7fbdee0d9849dd225399861471 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 11:05:32 +0530 Subject: [PATCH 131/257] Do not pop billing address data from session in case of a payment failure Instead pop id_payment_method --- datacenterlight/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 43cf7eaa..1e8b1472 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1,7 +1,7 @@ -import logging import json -import stripe +import logging +import stripe from django import forms from django.conf import settings from django.contrib import messages @@ -312,7 +312,7 @@ class PaymentOrderView(FormView): request.session.pop('vat_validation_status') request.session.pop('card_id') request.session.pop('token') - request.session.pop('billing_address_data') + request.session.pop('id_payment_method') logger.debug("Session: %s" % str(request.session)) for key, value in request.session.items(): logger.debug("Session: %s %s" % (key, value)) From 41de724904ef7089255dbcf9690ed82339c5b9d9 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 15:24:47 +0530 Subject: [PATCH 132/257] Handle PaymentMethod type in set_default_card --- hosting/models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/hosting/models.py b/hosting/models.py index ac44cc9e..fe0d824c 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -673,7 +673,11 @@ class UserCardDetail(AssignPermissionsMixin, models.Model): stripe_utils = StripeUtils() cus_response = stripe_utils.get_customer(stripe_api_cus_id) cu = cus_response['response_object'] - cu.default_source = stripe_source_id + if stripe_source_id.startswith("pm"): + # card is a payment method + cu.invoice_settings.default_payment_method = stripe_source_id + else: + cu.default_source = stripe_source_id cu.save() UserCardDetail.save_default_card_local( stripe_api_cus_id, stripe_source_id From f628046417a02ab6214a06346e405fccb7659d9a Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 15:25:14 +0530 Subject: [PATCH 133/257] Add IncompletePaymentIntents model --- hosting/models.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/hosting/models.py b/hosting/models.py index fe0d824c..e95c5bb6 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -1,4 +1,3 @@ -import decimal import json import logging import os @@ -747,6 +746,19 @@ class StripeTaxRate(AssignPermissionsMixin, models.Model): description = models.CharField(max_length=100) +class IncompletePaymentIntents(AssignPermissionsMixin, models.Model): + completed_at = models.DateTimeField(null=True) + created_at = models.DateTimeField(auto_now_add=True) + payment_intent_id = models.CharField(max_length=100) + request = models.TextField() + stripe_api_cus_id = models.CharField(max_length=30) + card_details_response = models.TextField() + stripe_subscription_id = models.TextField() + stripe_charge_id = models.TextField() + gp_details = models.TextField() + billing_address_data = models.TextField() + + class IncompleteSubscriptions(AssignPermissionsMixin, models.Model): created_at = models.DateTimeField(auto_now_add=True) completed_at = models.DateTimeField(null=True) From 98b5d03d0b16f07d654ec81ef811b8bccaa6b10f Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 15:40:46 +0530 Subject: [PATCH 134/257] Refactor code for do_provisioning_generic --- datacenterlight/views.py | 269 +++++++++++++++++++++++++++++++++------ 1 file changed, 228 insertions(+), 41 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 1e8b1472..058fa96b 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -20,7 +20,7 @@ from hosting.forms import ( ) from hosting.models import ( HostingBill, HostingOrder, UserCardDetail, GenericProduct, UserHostingKey, - StripeTaxRate, IncompleteSubscriptions) + StripeTaxRate, IncompleteSubscriptions, IncompletePaymentIntents) from membership.models import CustomUser, StripeCustomer from opennebula_api.serializers import VMTemplateSerializer from utils.forms import ( @@ -745,6 +745,16 @@ class OrderConfirmationView(DetailView, FormView): }, 'stripe_key': settings.STRIPE_API_PUBLIC_KEY, }) + IncompletePaymentIntents.objects.create( + request=create_incomplete_intent_request(self.request), + payment_intent_id=payment_intent.id, + stripe_api_cus_id=request.session['customer'], + card_details_response=card_details_response, + stripe_subscription_id=None, + stripe_charge_id=None, + gp_details=request.session["generic_payment_details"], + billing_address_data=request.session["billing_address_data"] + ) return render(request, self.template_name, context) def post(self, request, *args, **kwargs): @@ -1113,46 +1123,36 @@ class OrderConfirmationView(DetailView, FormView): return JsonResponse(response) -def do_provisioning(request, user, stripe_api_cus_id, card_details_response, - stripe_subscription_obj, stripe_onetime_charge, gp_details, - specs, vm_template_id, template, billing_address_data, - real_request): +def create_incomplete_intent_request(request): """ - :param request: a dict - { - 'scheme': 'https', - 'host': 'domain', - 'language': 'en-us', - 'new_user_hosting_key_id': 1, - 'card_id': 1, # if usercarddetail exists already, - 'generic_payment_type': 'generic' # represents a generic payment - 'generic_payment_details': { - 'amount': 100, - 'recurring': - }, - } - :param user: a dict - { - 'name': 'John Doe', - 'email': 'john@doe.com' - } - :param stripe_api_cus_id: 'cus_xxxxxxx' the actual stripe customer id str - :param card_details_response: - :param stripe_subscription_obj: The actual Stripe's Subscription Object - :param stripe_onetime_charge: Stripe's Charge object - :param gp_details: - :param specs: - :param vm_template_id: - :param template: - :param real_request: + Persist session variables so that they could be pick up + in the webhook for processing. + :param request: :return: """ - # Create user if the user is not logged in and if he is not already - # registered + req = { + 'scheme': request.scheme, + 'host': request.get_host(), + 'language': get_language(), + 'new_user_hosting_key_id': request.session.get( + 'new_user_hosting_key_id', None), + 'card_id': request.session.get('card_id', None), + 'generic_payment_type': request.session.get( + 'generic_payment_type', None), + 'generic_payment_details': request.session.get( + 'generic_payment_details', None), + 'user': request.session.get('user', None) + } + return json.dumps(req) + + +def get_or_create_custom_user(request, stripe_api_cus_id): new_user = None + name = request.get('user').get('name') + email = request.get('user').get('email') + try: - custom_user = CustomUser.objects.get( - email=user.get('email')) + custom_user = CustomUser.objects.get(email=email) stripe_customer = StripeCustomer.objects.filter( user_id=custom_user.id).first() if stripe_customer is None: @@ -1162,17 +1162,17 @@ def do_provisioning(request, user, stripe_api_cus_id, card_details_response, stripe_customer_id = stripe_customer.id except CustomUser.DoesNotExist: logger.debug( - "Customer {} does not exist.".format(user.get('email'))) + "Customer {} does not exist.".format(email)) password = CustomUser.get_random_password() base_url = "{0}://{1}".format(request['scheme'], request['host']) custom_user = CustomUser.register( - user.get('name'), password, - user.get('email'), + name, password, + email, app='dcl', base_url=base_url, send_email=True, account_details=password ) - logger.debug("Created user {}.".format(user.get('email'))) + logger.debug("Created user {}.".format(email)) stripe_customer = StripeCustomer.objects. \ create(user=custom_user, stripe_id=stripe_api_cus_id) stripe_customer_id = stripe_customer.id @@ -1186,8 +1186,11 @@ def do_provisioning(request, user, stripe_api_cus_id, card_details_response, user_hosting_key.user = new_user user_hosting_key.save() logger.debug("User %s key is saved" % custom_user.email) + return custom_user, new_user - card_id = request.get('card_id', None) + +def set_user_card(card_id, stripe_api_cus_id, custom_user, + card_details_response): if card_id: logger.debug("card_id %s was in request" % card_id) user_card_detail = UserCardDetail.objects.get(id=card_id) @@ -1217,6 +1220,190 @@ def do_provisioning(request, user, stripe_api_cus_id, card_details_response, 'brand': ucd.brand, 'card_id': ucd.card_id } + return card_details_dict + + +def do_provisioning_generic( + request, stripe_api_cus_id, card_details_response, + stripe_subscription_id, stripe_charge_id, gp_details, + billing_address_data): + user = request.get('user', None) + logger.debug("generic_payment_type case") + custom_user, new_user = get_or_create_custom_user( + request, stripe_api_cus_id) + + card_id = request.get('card_id', None) + + card_details_dict = set_user_card(card_id, stripe_api_cus_id, custom_user, + card_details_response) + + # Save billing address + billing_address_data.update({ + 'user': custom_user.id + }) + logger.debug('billing_address_data is {}'.format(billing_address_data)) + + stripe_cus = StripeCustomer.objects.filter( + stripe_id=stripe_api_cus_id + ).first() + billing_address = BillingAddress( + cardholder_name=billing_address_data['cardholder_name'], + 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'] + ) + billing_address.save() + + order = HostingOrder.create( + price=request['generic_payment_details']['amount'], + customer=stripe_cus, + billing_address=billing_address, + vm_pricing=VMPricing.get_default_pricing() + ) + + # Create a Hosting Bill + HostingBill.create(customer=stripe_cus, + billing_address=billing_address) + + # Create Billing Address for User if he does not have one + if not stripe_cus.user.billing_addresses.count(): + billing_address_data.update({ + 'user': stripe_cus.user.id + }) + billing_address_user_form = UserBillingAddressForm( + billing_address_data + ) + billing_address_user_form.is_valid() + billing_address_user_form.save() + + recurring = request['generic_payment_details'].get('recurring') + if recurring: + logger.debug("recurring case") + # Associate the given stripe subscription with the order + order.set_subscription_id( + stripe_subscription_id, card_details_dict + ) + logger.debug("recurring case, set order subscription id done") + else: + logger.debug("one time charge case") + # Associate the given stripe charge id with the order + stripe_onetime_charge = stripe.Charge.retrieve(stripe_charge_id) + order.set_stripe_charge(stripe_onetime_charge) + + # Set order status approved + order.set_approved() + order.generic_payment_description = gp_details["description"] + order.generic_product_id = gp_details["product_id"] + order.save() + logger.debug("Order saved") + # send emails + context = { + 'name': user.get('name'), + 'email': user.get('email'), + 'amount': gp_details['amount'], + 'description': gp_details['description'], + 'recurring': gp_details['recurring'], + 'product_name': gp_details['product_name'], + 'product_id': gp_details['product_id'], + 'order_id': order.id + } + + email_data = { + 'subject': (settings.DCL_TEXT + + " Payment received from %s" % context['email']), + 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': ['info@ungleich.ch'], + 'body': "\n".join( + ["%s=%s" % (k, v) for (k, v) in context.items()]), + 'reply_to': [context['email']], + } + send_plain_email_task.delay(email_data) + recurring_text = _(" This is a monthly recurring plan.") + if gp_details['recurring_interval'] == "year": + recurring_text = _(" This is an yearly recurring plan.") + + email_data = { + 'subject': _("Confirmation of your payment"), + 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': [user.get('email')], + 'body': _("Hi {name},\n\n" + "thank you for your order!\n" + "We have just received a payment of CHF {amount:.2f}" + " from you.{recurring}\n\n" + "Cheers,\nYour Data Center Light team".format( + name=user.get('name'), + amount=gp_details['amount'], + recurring=( + recurring_text + if gp_details['recurring'] else '' + ) + ) + ), + 'reply_to': ['info@ungleich.ch'], + } + send_plain_email_task.delay(email_data) + redirect_url = reverse('datacenterlight:index') + logger.debug("Sent user/admin emails") + logger.debug("redirect_url = %s " % redirect_url) + response = { + 'status': True, + 'redirect': redirect_url, + 'msg_title': str(_('Thank you for the payment.')), + 'msg_body': str( + _('You will soon receive a confirmation email of the ' + 'payment. You can always contact us at ' + 'info@ungleich.ch for any question that you may have.') + ) + } + logger.debug("after response") + logger.debug(str(response)) + return {'response': JsonResponse(response), 'user': new_user} + + +def do_provisioning(request, stripe_api_cus_id, card_details_response, + stripe_subscription_obj, stripe_onetime_charge, gp_details, + specs, vm_template_id, template, billing_address_data, + real_request): + """ + :param request: a dict + { + 'scheme': 'https', + 'host': 'domain', + 'language': 'en-us', + 'new_user_hosting_key_id': 1, + 'card_id': 1, # if usercarddetail exists already, + 'generic_payment_type': 'generic' # represents a generic payment + 'generic_payment_details': { + 'amount': 100, + 'recurring': + }, + 'user': { + 'name': 'John Doe', + 'email': 'john@doe.com' + } + } + :param stripe_api_cus_id: 'cus_xxxxxxx' the actual stripe customer id str + :param card_details_response: + :param stripe_subscription_obj: The actual Stripe's Subscription Object + :param stripe_onetime_charge: Stripe's Charge object + :param gp_details: + :param specs: + :param vm_template_id: + :param template: + :param real_request: + :return: + """ + # Create user if the user is not logged in and if he is not already + # registered + custom_user, new_user = get_or_create_custom_user( + request, stripe_api_cus_id) + + card_id = request.get('card_id', None) + + card_details_dict = set_user_card(card_id, stripe_api_cus_id, custom_user, + card_details_response) # Save billing address billing_address_data.update({ From 9077eb0cf2b11f901e1d4dc03a39feb6deee283d Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 15:41:43 +0530 Subject: [PATCH 135/257] Implement webhook --- webhook/views.py | 61 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/webhook/views.py b/webhook/views.py index 416d07b7..970f0d33 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -9,9 +9,9 @@ from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST -from datacenterlight.views import do_provisioning +from datacenterlight.views import do_provisioning, do_provisioning_generic from membership.models import StripeCustomer -from hosting.models import IncompleteSubscriptions +from hosting.models import IncompleteSubscriptions, IncompletePaymentIntents from utils.models import BillingAddress, UserBillingAddress from utils.tasks import send_plain_email_task @@ -135,6 +135,8 @@ def handle_webhook(request): if (invoice_obj.paid and invoice_obj.billing_reason == "subscription_update"): + logger.debug("""invoice_obj.paid and + invoice_obj.billing_reason == subscription_update""") logger.debug("Start provisioning") try: stripe_subscription_obj = stripe.Subscription.retrieve( @@ -182,8 +184,6 @@ def handle_webhook(request): logger.debug("6*******") do_provisioning( request=request, - user={'name': incomplete_sub.name, - 'email': incomplete_sub.email}, stripe_api_cus_id=incomplete_sub.stripe_api_cus_id, card_details_response=card_details_response, stripe_subscription_obj=stripe_subscription_obj, @@ -225,6 +225,59 @@ def handle_webhook(request): payment_intent_obj = event.data.object logger.debug("Webhook Event: payment_intent.succeeded") logger.debug("payment_intent_obj %s " % str(payment_intent_obj)) + + try: + incomplete_pm = IncompletePaymentIntents.objects.get( + payment_intent_obj.id) + request = "" + soc = "" + card_details_response = "" + gp_details = "" + template = "" + billing_address_data = "" + if incomplete_pm.request: + request = json.loads(incomplete_pm.request) + if incomplete_pm.stripe_onetime_charge: + soc = json.loads(incomplete_pm.stripe_onetime_charge) + if incomplete_pm.gp_details: + gp_details = json.loads(incomplete_pm.gp_details) + if incomplete_pm.card_details_response: + card_details_response = json.loads( + incomplete_pm.card_details_response) + if incomplete_pm.billing_address_data: + billing_address_data = json.loads( + incomplete_pm.billing_address_data) + logger.debug("1*******") + logger.debug(request) + logger.debug("2*******") + logger.debug(card_details_response) + logger.debug("3*******") + logger.debug(soc) + logger.debug("4*******") + logger.debug(gp_details) + logger.debug("5*******") + logger.debug(template) + logger.debug("6*******") + logger.debug(billing_address_data) + do_provisioning_generic( + request=request, + stripe_api_cus_id=incomplete_pm.stripe_api_cus_id, + card_details_response=card_details_response, + stripe_subscription_id=None, + stripe_charge_id=soc, + gp_details=gp_details, + billing_address_data=billing_address_data + ) + except (IncompletePaymentIntents.DoesNotExist, + IncompletePaymentIntents.MultipleObjectsReturned) as ex: + logger.error(str(ex)) + email_data = { + 'subject': "IncompletePaymentIntents error", + 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': settings.DCL_ERROR_EMAILS_TO_LIST, + 'body': "Response = %s" % str(ex), + } + send_plain_email_task.delay(email_data) else: logger.error("Unhandled event : " + event.type) return HttpResponse(status=200) From 9ae4b969688d650c0e14d63fb1f5d6cdd0719a99 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 15:43:46 +0530 Subject: [PATCH 136/257] Add hosting/migrations/0064_incompletepaymentintents.py --- .../0064_incompletepaymentintents.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 hosting/migrations/0064_incompletepaymentintents.py diff --git a/hosting/migrations/0064_incompletepaymentintents.py b/hosting/migrations/0064_incompletepaymentintents.py new file mode 100644 index 00000000..868e053e --- /dev/null +++ b/hosting/migrations/0064_incompletepaymentintents.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2020-12-31 10:13 +from __future__ import unicode_literals + +from django.db import migrations, models +import utils.mixins + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0063_auto_20201223_0612'), + ] + + operations = [ + migrations.CreateModel( + name='IncompletePaymentIntents', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('completed_at', models.DateTimeField(null=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('payment_intent_id', models.CharField(max_length=100)), + ('request', models.TextField()), + ('stripe_api_cus_id', models.CharField(max_length=30)), + ('card_details_response', models.TextField()), + ('stripe_subscription_id', models.TextField()), + ('stripe_charge_id', models.TextField()), + ('gp_details', models.TextField()), + ('billing_address_data', models.TextField()), + ], + bases=(utils.mixins.AssignPermissionsMixin, models.Model), + ), + ] From b2f0a456790b7158ca3eed4a442225a9cc616fce Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 15:56:08 +0530 Subject: [PATCH 137/257] Add logger message --- datacenterlight/views.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 058fa96b..afe5d0ae 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -745,6 +745,9 @@ class OrderConfirmationView(DetailView, FormView): }, 'stripe_key': settings.STRIPE_API_PUBLIC_KEY, }) + logger.debug("Request %s" % create_incomplete_intent_request( + self.request)) + logger.debug("%s" % str(payment_intent)) IncompletePaymentIntents.objects.create( request=create_incomplete_intent_request(self.request), payment_intent_id=payment_intent.id, @@ -755,6 +758,7 @@ class OrderConfirmationView(DetailView, FormView): gp_details=request.session["generic_payment_details"], billing_address_data=request.session["billing_address_data"] ) + logger.debug("IncompletePaymentIntent done") return render(request, self.template_name, context) def post(self, request, *args, **kwargs): From 87b85c43b46c01685dc9dc4c9f50a5684d4b3ca0 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 15:59:09 +0530 Subject: [PATCH 138/257] More logger --- datacenterlight/views.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index afe5d0ae..fe98645a 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -748,6 +748,10 @@ class OrderConfirmationView(DetailView, FormView): logger.debug("Request %s" % create_incomplete_intent_request( self.request)) logger.debug("%s" % str(payment_intent)) + logger.debug("customer %s" % request.session['customer']) + logger.debug("card_details_response %s" % card_details_response) + logger.debug("request.session[generic_payment_details] %s" % request.session["generic_payment_details"]) + logger.debug("request.session[billing_address_data] %s" % request.session["billing_address_data"]) IncompletePaymentIntents.objects.create( request=create_incomplete_intent_request(self.request), payment_intent_id=payment_intent.id, From 7db0594778b07141db63e1f0a7f0780446c9d0cb Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 16:12:22 +0530 Subject: [PATCH 139/257] Update IncompletePaymentIntents to allow null subscription id and charge id --- hosting/migrations/0065_auto_20201231_1041.py | 25 +++++++++++++++++++ hosting/models.py | 4 +-- 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 hosting/migrations/0065_auto_20201231_1041.py diff --git a/hosting/migrations/0065_auto_20201231_1041.py b/hosting/migrations/0065_auto_20201231_1041.py new file mode 100644 index 00000000..936ccab1 --- /dev/null +++ b/hosting/migrations/0065_auto_20201231_1041.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2020-12-31 10:41 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0064_incompletepaymentintents'), + ] + + operations = [ + migrations.AlterField( + model_name='incompletepaymentintents', + name='stripe_charge_id', + field=models.CharField(max_length=100, null=True), + ), + migrations.AlterField( + model_name='incompletepaymentintents', + name='stripe_subscription_id', + field=models.CharField(max_length=100, null=True), + ), + ] diff --git a/hosting/models.py b/hosting/models.py index e95c5bb6..c14d18bf 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -753,8 +753,8 @@ class IncompletePaymentIntents(AssignPermissionsMixin, models.Model): request = models.TextField() stripe_api_cus_id = models.CharField(max_length=30) card_details_response = models.TextField() - stripe_subscription_id = models.TextField() - stripe_charge_id = models.TextField() + stripe_subscription_id = models.CharField(max_length=100, null=True) + stripe_charge_id = models.CharField(max_length=100, null=True) gp_details = models.TextField() billing_address_data = models.TextField() From 12c9140b3a4001101a8ef989980b30098b2ba3a8 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 16:23:44 +0530 Subject: [PATCH 140/257] Fix using correct payment intent id --- webhook/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webhook/views.py b/webhook/views.py index 970f0d33..07bf34ad 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -228,7 +228,7 @@ def handle_webhook(request): try: incomplete_pm = IncompletePaymentIntents.objects.get( - payment_intent_obj.id) + payment_intent_id=payment_intent_obj.id) request = "" soc = "" card_details_response = "" From 52d1fb6a0eb402a00c3536446dbf4c7f713516de Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 16:27:12 +0530 Subject: [PATCH 141/257] Add logger + return 200 on success of webhook --- datacenterlight/views.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index fe98645a..5b67d87c 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -8,7 +8,8 @@ from django.contrib import messages from django.contrib.auth import login, authenticate from django.core.exceptions import ValidationError from django.core.urlresolvers import reverse -from django.http import HttpResponseRedirect, JsonResponse, Http404 +from django.http import HttpResponseRedirect, JsonResponse, Http404, \ + HttpResponse from django.shortcuts import render from django.utils.translation import get_language, ugettext_lazy as _ from django.views.decorators.cache import cache_control @@ -1239,12 +1240,15 @@ def do_provisioning_generic( logger.debug("generic_payment_type case") custom_user, new_user = get_or_create_custom_user( request, stripe_api_cus_id) + logger.debug("%s %s" % (custom_user.email, custom_user.id)) card_id = request.get('card_id', None) card_details_dict = set_user_card(card_id, stripe_api_cus_id, custom_user, card_details_response) + logger.debug("After card details dict %s" % str(card_details_dict)) + # Save billing address billing_address_data.update({ 'user': custom_user.id @@ -1367,7 +1371,7 @@ def do_provisioning_generic( } logger.debug("after response") logger.debug(str(response)) - return {'response': JsonResponse(response), 'user': new_user} + return HttpResponse(status=200) def do_provisioning(request, stripe_api_cus_id, card_details_response, From f48a5cfe71d40cfa9120258daaf34f3eca126856 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 16:29:18 +0530 Subject: [PATCH 142/257] Set completed_at value --- webhook/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/webhook/views.py b/webhook/views.py index 07bf34ad..27b12b05 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -268,6 +268,7 @@ def handle_webhook(request): gp_details=gp_details, billing_address_data=billing_address_data ) + incomplete_pm.completed_at = datetime.datetime.now() except (IncompletePaymentIntents.DoesNotExist, IncompletePaymentIntents.MultipleObjectsReturned) as ex: logger.error(str(ex)) From 85757e01c92cd52537e32ad184c5f467aaa683ba Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 16:37:25 +0530 Subject: [PATCH 143/257] Save charge id --- webhook/views.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/webhook/views.py b/webhook/views.py index 27b12b05..3c5adfef 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -269,6 +269,15 @@ def handle_webhook(request): billing_address_data=billing_address_data ) incomplete_pm.completed_at = datetime.datetime.now() + charges = "" + if len(payment_intent_obj.charges.data) > 0: + for d in payment_intent_obj.charges.data: + if charges == "": + charges = "%s" % d.id + else: + charges = "%s,%s" % (charges, d.id) + incomplete_pm.stripe_charge_id=charges + incomplete_pm.save() except (IncompletePaymentIntents.DoesNotExist, IncompletePaymentIntents.MultipleObjectsReturned) as ex: logger.error(str(ex)) From 8e4b3ce96b0667be8e3e83d617ddea7d044774de Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 16:39:25 +0530 Subject: [PATCH 144/257] Store charge id from payement intent result --- webhook/views.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/webhook/views.py b/webhook/views.py index 3c5adfef..bdcf1654 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -259,15 +259,6 @@ def handle_webhook(request): logger.debug(template) logger.debug("6*******") logger.debug(billing_address_data) - do_provisioning_generic( - request=request, - stripe_api_cus_id=incomplete_pm.stripe_api_cus_id, - card_details_response=card_details_response, - stripe_subscription_id=None, - stripe_charge_id=soc, - gp_details=gp_details, - billing_address_data=billing_address_data - ) incomplete_pm.completed_at = datetime.datetime.now() charges = "" if len(payment_intent_obj.charges.data) > 0: @@ -276,7 +267,17 @@ def handle_webhook(request): charges = "%s" % d.id else: charges = "%s,%s" % (charges, d.id) + logger.debug("Charge ids = %s" % charges) incomplete_pm.stripe_charge_id=charges + do_provisioning_generic( + request=request, + stripe_api_cus_id=incomplete_pm.stripe_api_cus_id, + card_details_response=card_details_response, + stripe_subscription_id=None, + stripe_charge_id=charges, + gp_details=gp_details, + billing_address_data=billing_address_data + ) incomplete_pm.save() except (IncompletePaymentIntents.DoesNotExist, IncompletePaymentIntents.MultipleObjectsReturned) as ex: From 8827bd15bac9b6c9d282004ea8501d3675314791 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 16:47:45 +0530 Subject: [PATCH 145/257] More loggers --- webhook/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/webhook/views.py b/webhook/views.py index bdcf1654..8dc1d909 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -225,8 +225,9 @@ def handle_webhook(request): payment_intent_obj = event.data.object logger.debug("Webhook Event: payment_intent.succeeded") logger.debug("payment_intent_obj %s " % str(payment_intent_obj)) - try: + logger.debug("Looking for IncompletePaymentIntents %s " % + payment_intent_obj.id) incomplete_pm = IncompletePaymentIntents.objects.get( payment_intent_id=payment_intent_obj.id) request = "" @@ -282,6 +283,7 @@ def handle_webhook(request): except (IncompletePaymentIntents.DoesNotExist, IncompletePaymentIntents.MultipleObjectsReturned) as ex: logger.error(str(ex)) + logger.debug(str(ex)) email_data = { 'subject': "IncompletePaymentIntents error", 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, From 37f82a48d581a9f949a51316d14b983ed24de35d Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 16:55:05 +0530 Subject: [PATCH 146/257] More loggers --- webhook/views.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/webhook/views.py b/webhook/views.py index 8dc1d909..420e718c 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -230,6 +230,7 @@ def handle_webhook(request): payment_intent_obj.id) incomplete_pm = IncompletePaymentIntents.objects.get( payment_intent_id=payment_intent_obj.id) + logger.debug("incomplete_pm = %s" % str(incomplete_pm)) request = "" soc = "" card_details_response = "" @@ -238,16 +239,21 @@ def handle_webhook(request): billing_address_data = "" if incomplete_pm.request: request = json.loads(incomplete_pm.request) + logger.debug("request = %s" % str(request)) if incomplete_pm.stripe_onetime_charge: soc = json.loads(incomplete_pm.stripe_onetime_charge) + logger.debug("stripe_onetime_charge = %s" % str(soc)) if incomplete_pm.gp_details: gp_details = json.loads(incomplete_pm.gp_details) + logger.debug("gp_details = %s" % str(gp_details)) if incomplete_pm.card_details_response: card_details_response = json.loads( incomplete_pm.card_details_response) + logger.debug("card_details_response = %s" % str(card_details_response)) if incomplete_pm.billing_address_data: billing_address_data = json.loads( incomplete_pm.billing_address_data) + logger.debug("billing_address_data = %s" % str(billing_address_data)) logger.debug("1*******") logger.debug(request) logger.debug("2*******") From ff7b20b0dc1fbb8adcbcce59c3d70d88ff2cbccc Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 16:59:22 +0530 Subject: [PATCH 147/257] Stripe id is not a dict --- webhook/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webhook/views.py b/webhook/views.py index 420e718c..7e297944 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -241,7 +241,7 @@ def handle_webhook(request): request = json.loads(incomplete_pm.request) logger.debug("request = %s" % str(request)) if incomplete_pm.stripe_onetime_charge: - soc = json.loads(incomplete_pm.stripe_onetime_charge) + soc = incomplete_pm.stripe_onetime_charge logger.debug("stripe_onetime_charge = %s" % str(soc)) if incomplete_pm.gp_details: gp_details = json.loads(incomplete_pm.gp_details) From 7309b8416ce934ce7be9439f62be35cf7b6bdef9 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 17:02:03 +0530 Subject: [PATCH 148/257] Handle any exception --- webhook/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webhook/views.py b/webhook/views.py index 7e297944..318b9b35 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -287,7 +287,8 @@ def handle_webhook(request): ) incomplete_pm.save() except (IncompletePaymentIntents.DoesNotExist, - IncompletePaymentIntents.MultipleObjectsReturned) as ex: + IncompletePaymentIntents.MultipleObjectsReturned, + Exception) as ex: logger.error(str(ex)) logger.debug(str(ex)) email_data = { From 33f741424d6a19f51b43ff2e0a2e6bc23d6f15b0 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 17:05:01 +0530 Subject: [PATCH 149/257] See inner values of incomplete_pm --- webhook/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webhook/views.py b/webhook/views.py index 318b9b35..f53e7af7 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -230,7 +230,7 @@ def handle_webhook(request): payment_intent_obj.id) incomplete_pm = IncompletePaymentIntents.objects.get( payment_intent_id=payment_intent_obj.id) - logger.debug("incomplete_pm = %s" % str(incomplete_pm)) + logger.debug("incomplete_pm = %s" % str(incomplete_pm.__dict__)) request = "" soc = "" card_details_response = "" From 213b9a068e0308f06fc6fe8b9d58ef412501ee79 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 17:07:51 +0530 Subject: [PATCH 150/257] Fix attribute name --- webhook/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webhook/views.py b/webhook/views.py index f53e7af7..5760b0dd 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -240,8 +240,8 @@ def handle_webhook(request): if incomplete_pm.request: request = json.loads(incomplete_pm.request) logger.debug("request = %s" % str(request)) - if incomplete_pm.stripe_onetime_charge: - soc = incomplete_pm.stripe_onetime_charge + if incomplete_pm.stripe_charge_id: + soc = incomplete_pm.stripe_charge_id logger.debug("stripe_onetime_charge = %s" % str(soc)) if incomplete_pm.gp_details: gp_details = json.loads(incomplete_pm.gp_details) From 2f98294eab6a1c52013f2f8cd7071581d03a4f7e Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 17:12:17 +0530 Subject: [PATCH 151/257] Store dicts as json in db --- datacenterlight/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 5b67d87c..44e871a5 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -757,11 +757,11 @@ class OrderConfirmationView(DetailView, FormView): request=create_incomplete_intent_request(self.request), payment_intent_id=payment_intent.id, stripe_api_cus_id=request.session['customer'], - card_details_response=card_details_response, + card_details_response=json.dumps(card_details_response), stripe_subscription_id=None, stripe_charge_id=None, - gp_details=request.session["generic_payment_details"], - billing_address_data=request.session["billing_address_data"] + gp_details=json.dumps(request.session["generic_payment_details"]), + billing_address_data=json.dumps(request.session["billing_address_data"]) ) logger.debug("IncompletePaymentIntent done") return render(request, self.template_name, context) From 13f5f576b50776548a93533579fb6a92914e1bc5 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 17:23:43 +0530 Subject: [PATCH 152/257] Fix getting cc details from payment_methods --- hosting/models.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/hosting/models.py b/hosting/models.py index c14d18bf..48238afe 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -169,8 +169,12 @@ class HostingOrder(AssignPermissionsMixin, models.Model): def set_stripe_charge(self, stripe_charge): self.stripe_charge_id = stripe_charge.id - self.last4 = stripe_charge.source.last4 - self.cc_brand = stripe_charge.source.brand + if stripe_charge.source is None: + self.last4 = stripe_charge.payment_method_details.card.last4 + self.cc_brand = stripe_charge.payment_method_details.card.brand + else: + self.last4 = stripe_charge.source.last4 + self.cc_brand = stripe_charge.source.brand self.save() def set_subscription_id(self, subscription_id, cc_details): From a823efd8e28376f9651c10746929616af46601c4 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 20:01:52 +0530 Subject: [PATCH 153/257] Add get_available_payment_methods --- datacenterlight/views.py | 6 ++++-- utils/stripe_utils.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 44e871a5..3b05bd73 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -263,9 +263,11 @@ class PaymentOrderView(FormView): stripe_customer = user.stripecustomer else: stripe_customer = None - cards_list = UserCardDetail.get_all_cards_list( - stripe_customer=stripe_customer + stripe_utils = StripeUtils() + cards_list_request = stripe_utils.get_available_payment_methods( + stripe_customer ) + cards_list = cards_list_request.get('response_object') context.update({'cards_list': cards_list}) else: billing_address_form = BillingAddressFormSignup( diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index a4cc2c6a..e7e8d9af 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -528,6 +528,34 @@ class StripeUtils(object): ) return payment_intent_obj + @handleStripeError + def get_available_payment_methods(self, customer): + """ Retrieves all payment methods of the given customer + :param customer: StripeCustomer object + :return: a list of available payment methods + """ + return_list = [] + if customer is None: + return return_list + cu = stripe.Customer.retrieve(customer.stripe_id) + pms = stripe.PaymentMethod.list( + customer=customer.stripe_id, + type="card", + ) + default_source = None + if cu.default_source: + default_source = cu.default_source + else: + default_source = cu.invoice_settings.default_payment_method + for pm in pms.data: + return_list.append({ + 'last4': pm.card.last4, 'brand': pm.card.brand, 'id': pm.id, + 'exp_year': pm.card.exp_year, + 'exp_month': '{:02d}'.format(pm.card.exp_month), + 'preferred': pm.id == default_source + }) + return return_list + def compare_vat_numbers(self, vat1, vat2): _vat1 = vat1.replace(" ", "").replace(".", "").replace("-","") _vat2 = vat2.replace(" ", "").replace(".", "").replace("-","") From b36afcb82818c7e0a54c4ffaa5a9af34cc7f7184 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 20:35:31 +0530 Subject: [PATCH 154/257] Simplify code for logged in one-time payments with SCA --- datacenterlight/views.py | 35 +++++------------------------------ 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 3b05bd73..94d67b27 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -469,36 +469,11 @@ class PaymentOrderView(FormView): return self.render_to_response(context) id_payment_method = self.request.POST.get('id_payment_method', None) - if id_payment_method is None: - card_id = address_form.cleaned_data.get('card') - logger.debug("token is empty and card_id is %s" % card_id) - try: - user_card_detail = UserCardDetail.objects.get(id=card_id) - if not request.user.has_perm( - 'view_usercarddetail', user_card_detail - ): - raise UserCardDetail.DoesNotExist( - _("{user} does not have permission to access the " - "card").format(user=request.user.email) - ) - except UserCardDetail.DoesNotExist as e: - ex = str(e) - logger.error("Card Id: {card_id}, Exception: {ex}".format( - card_id=card_id, ex=ex - ) - ) - msg = _("An error occurred. Details: {}".format(ex)) - messages.add_message( - self.request, messages.ERROR, msg, - extra_tags='make_charge_error' - ) - return HttpResponseRedirect( - reverse('datacenterlight:payment') + '#payment_error' - ) - request.session['card_id'] = user_card_detail.id - else: - request.session["id_payment_method"] = id_payment_method - logger.debug("id_payment_method is %s" % id_payment_method) + if id_payment_method == 'undefined': + # Probably user chose one of the previously saved cards + id_payment_method = address_form.cleaned_data.get('card') + request.session["id_payment_method"] = id_payment_method + logger.debug("id_payment_method is %s" % id_payment_method) if request.user.is_authenticated(): this_user = { 'email': request.user.email, From d2ebd3c473d9daa4670cf1f55a74e6e3868ea9f1 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 20:53:02 +0530 Subject: [PATCH 155/257] Do card association --- datacenterlight/views.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 94d67b27..fcc423bd 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1127,7 +1127,8 @@ def create_incomplete_intent_request(request): 'generic_payment_type', None), 'generic_payment_details': request.session.get( 'generic_payment_details', None), - 'user': request.session.get('user', None) + 'user': request.session.get('user', None), + 'id_payment_method': request.session.get('id_payment_method', None), } return json.dumps(req) @@ -1213,6 +1214,15 @@ def do_provisioning_generic( request, stripe_api_cus_id, card_details_response, stripe_subscription_id, stripe_charge_id, gp_details, billing_address_data): + stripe_utils = StripeUtils() + acc_result = stripe_utils.associate_customer_card( + stripe_api_cus_id, request['id_payment_method'], + set_as_default=True + ) + logger.debug("Card %s associate result %s" % ( + request['id_payment_method'], + acc_result.get('response_object') + )) user = request.get('user', None) logger.debug("generic_payment_type case") custom_user, new_user = get_or_create_custom_user( From 8c72b56f6caf86d6ed67fc12c50f1d8adbdaa278 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 21:03:24 +0530 Subject: [PATCH 156/257] Use setup_future_usage='off_session' --- utils/stripe_utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index e7e8d9af..d04a24cb 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -524,7 +524,8 @@ class StripeUtils(object): payment_intent_obj = stripe.PaymentIntent.create( amount=amount, currency='chf', - customer=customer + customer=customer, + setup_future_usage='off_session' ) return payment_intent_obj From 7b71ba55f206c31e020bc9c71678b822bc8df38c Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 22:43:50 +0530 Subject: [PATCH 157/257] Rename token to id_payment_method --- datacenterlight/views.py | 4 ++-- membership/models.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index fcc423bd..cf920579 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -504,7 +504,7 @@ class PaymentOrderView(FormView): ) customer = StripeCustomer.create_stripe_api_customer( email=user_email, - token=id_payment_method, + id_payment_method=id_payment_method, customer_name=user_name) except CustomUser.DoesNotExist: logger.debug( @@ -515,7 +515,7 @@ class PaymentOrderView(FormView): ) customer = StripeCustomer.create_stripe_api_customer( email=user_email, - token=id_payment_method, + id_payment_method=id_payment_method, customer_name=user_name) billing_address = address_form.save() diff --git a/membership/models.py b/membership/models.py index 079b60e0..8de99468 100644 --- a/membership/models.py +++ b/membership/models.py @@ -277,7 +277,7 @@ class StripeCustomer(models.Model): return "%s - %s" % (self.stripe_id, self.user.email) @classmethod - def create_stripe_api_customer(cls, email=None, token=None, + def create_stripe_api_customer(cls, email=None, id_payment_method=None, customer_name=None): """ This method creates a Stripe API customer with the given From 6e6a57b3043bca51c39635768896e59c66e4dd37 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 22:45:02 +0530 Subject: [PATCH 158/257] Refactor price to charge => amount_to_charge This is a common variable between the generic onetime and subscription --- datacenterlight/views.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index cf920579..3b83507b 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -584,6 +584,9 @@ class OrderConfirmationView(DetailView, FormView): @cache_control(no_cache=True, must_revalidate=True, no_store=True) def get(self, request, *args, **kwargs): context = {} + # this is amount to be charge/subscribed before VAT and discount + # and expressed in chf. To convert to cents, multiply by 100 + amount_to_charge = 0 if (('specs' not in request.session or 'user' not in request.session) and 'generic_payment_type' not in request.session): return HttpResponseRedirect(reverse('datacenterlight:index')) @@ -621,6 +624,7 @@ class OrderConfirmationView(DetailView, FormView): 'generic_payment_details': request.session['generic_payment_details'], }) + amount_to_charge = request.session['generic_payment_details']['amount'] else: vm_specs = request.session.get('specs') user_vat_country = ( @@ -636,7 +640,7 @@ class OrderConfirmationView(DetailView, FormView): ) vm_specs["price"] = price vm_specs["price_after_discount"] = price - discount["amount"] - + amount_to_charge = price vat_number = request.session.get('billing_address_data').get("vat_number") billing_address = BillingAddress.objects.get(id=request.session["billing_address_id"]) if vat_number: @@ -689,8 +693,7 @@ class OrderConfirmationView(DetailView, FormView): # the customer stripe_utils = StripeUtils() payment_intent_response = stripe_utils.get_payment_intent( - int(request.session['generic_payment_details']['amount'] * - 100), + int(amount_to_charge * 100), customer=request.session['customer'] ) payment_intent = payment_intent_response.get( From 9faf8978186bf24839540ba7df8c0e9be4beba4f Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 31 Dec 2020 22:46:15 +0530 Subject: [PATCH 159/257] Formatting and documentation --- datacenterlight/views.py | 12 ++++++++---- membership/models.py | 3 ++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 3b83507b..e3974e3e 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -602,9 +602,11 @@ class OrderConfirmationView(DetailView, FormView): context['cc_last4'] = card_details_response['last4'] context['cc_brand'] = card_details_response['brand'] context['cc_exp_year'] = card_details_response['exp_year'] - context['cc_exp_month'] = '{:02d}'.format(card_details_response['exp_month']) + context['cc_exp_month'] = '{:02d}'.format( + card_details_response['exp_month']) context['id_payment_method'] = payment_method else: + # TODO check when we go through this case (to me, it seems useless) card_id = self.request.session.get('card_id') card_detail = UserCardDetail.objects.get(id=card_id) context['cc_last4'] = card_detail.last4 @@ -619,7 +621,9 @@ class OrderConfirmationView(DetailView, FormView): 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'] + request.session['generic_payment_details']['amount'] = ( + request.session['generic_payment_details']['amount_before_vat'] + ) context.update({ 'generic_payment_details': request.session['generic_payment_details'], @@ -642,7 +646,8 @@ class OrderConfirmationView(DetailView, FormView): vm_specs["price_after_discount"] = price - discount["amount"] amount_to_charge = price vat_number = request.session.get('billing_address_data').get("vat_number") - billing_address = BillingAddress.objects.get(id=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'], @@ -656,7 +661,6 @@ class OrderConfirmationView(DetailView, FormView): return HttpResponseRedirect( reverse('datacenterlight:payment') + '#vat_error' ) - request.session["vat_validation_status"] = validate_result["status"] if user_vat_country.lower() == "ch": diff --git a/membership/models.py b/membership/models.py index 8de99468..81054fb9 100644 --- a/membership/models.py +++ b/membership/models.py @@ -288,7 +288,8 @@ class StripeCustomer(models.Model): stripe user. """ stripe_utils = StripeUtils() - stripe_data = stripe_utils.create_customer(token, email, customer_name) + stripe_data = stripe_utils.create_customer( + id_payment_method, email, customer_name) if stripe_data.get('response_object'): stripe_cus_id = stripe_data.get('response_object').get('id') return stripe_cus_id From a32a5af5a34e68f0f9f665639ff404a74905f700 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 1 Jan 2021 00:07:23 +0530 Subject: [PATCH 160/257] Add code to differentiate between subscription and non-subscription in js --- datacenterlight/templates/datacenterlight/order_detail.html | 1 + datacenterlight/views.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index b96d5123..d6c87266 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -337,5 +337,6 @@ var success_title = '{{ success_msg.msg_title }}'; var success_url = '{{ success_msg.redirect }}'; window.stripeKey = "{{stripe_key}}"; + window.isSubscription = ("{{is_subscription}}" === 'true'); {%endblock%} diff --git a/datacenterlight/views.py b/datacenterlight/views.py index e3974e3e..a4458cc2 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -729,6 +729,10 @@ class OrderConfirmationView(DetailView, FormView): reverse('datacenterlight:index') }, 'stripe_key': settings.STRIPE_API_PUBLIC_KEY, + 'is_subscription': 'true' if ( + 'generic_payment_type' not in request.session or + (request.session['generic_payment_details']['recurring']) + ) else 'false' }) logger.debug("Request %s" % create_incomplete_intent_request( self.request)) From 9524e03762d2331b0eb387a367364f1856035d47 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 1 Jan 2021 00:08:33 +0530 Subject: [PATCH 161/257] Pass user param with request dict --- datacenterlight/views.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index a4458cc2..566668fb 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -788,7 +788,8 @@ class OrderConfirmationView(DetailView, FormView): 'new_user_hosting_key_id': new_user_hosting_key_id, 'card_id': card_id, 'generic_payment_type': generic_payment_type, - 'generic_payment_details': generic_payment_details + 'generic_payment_details': generic_payment_details, + 'user': user } if 'id_payment_method' in request.session: @@ -1089,7 +1090,7 @@ class OrderConfirmationView(DetailView, FormView): msg = subscription_result.get('error') return show_error(msg, self.request) provisioning_response = do_provisioning( - req, user, stripe_api_cus_id, + req, stripe_api_cus_id, card_details_response, stripe_subscription_obj, stripe_onetime_charge, gp_details, specs, vm_template_id, template, request.session.get('billing_address_data'), @@ -1405,6 +1406,9 @@ def do_provisioning(request, stripe_api_cus_id, card_details_response, :param real_request: :return: """ + + user = request.get('user', None) + # Create user if the user is not logged in and if he is not already # registered custom_user, new_user = get_or_create_custom_user( From d8a674da3d2b1174288074e4d7a6cbf4aed5af15 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 1 Jan 2021 00:08:58 +0530 Subject: [PATCH 162/257] Remove unwanted code + doc --- datacenterlight/views.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 566668fb..32fe4138 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -587,6 +587,7 @@ class OrderConfirmationView(DetailView, FormView): # this is amount to be charge/subscribed before VAT and discount # and expressed in chf. To convert to cents, multiply by 100 amount_to_charge = 0 + vm_specs = None if (('specs' not in request.session or 'user' not in request.session) and 'generic_payment_type' not in request.session): return HttpResponseRedirect(reverse('datacenterlight:index')) @@ -875,18 +876,6 @@ class OrderConfirmationView(DetailView, FormView): if gp_details['recurring']: # generic recurring payment logger.debug("Commencing a generic recurring payment") - else: - # generic one time payment - logger.debug("Commencing a one time payment") - charge_response = stripe_utils.make_charge( - amount=gp_details['amount'], - customer=stripe_api_cus_id - ) - stripe_onetime_charge = charge_response.get('response_object') - # Check if the payment was approved - if not stripe_onetime_charge: - msg = charge_response.get('error') - return show_error(msg, self.request) if ('generic_payment_type' not in request.session or (request.session['generic_payment_details']['recurring'])): recurring_interval = 'month' @@ -1079,7 +1068,6 @@ class OrderConfirmationView(DetailView, FormView): ) } } - #clear_all_session_vars(request) return JsonResponse(context) else: logger.debug( @@ -1089,6 +1077,10 @@ class OrderConfirmationView(DetailView, FormView): "requires_source_action") msg = subscription_result.get('error') return show_error(msg, self.request) + # the code below is executed for + # a) subscription case + # b) the subscription object is active itself, without requiring + # SCA provisioning_response = do_provisioning( req, stripe_api_cus_id, card_details_response, stripe_subscription_obj, From 2c3d00f03f946a347fd4e56e63ae31300500c78e Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 1 Jan 2021 00:09:20 +0530 Subject: [PATCH 163/257] Pass correct stripe_customer_id --- datacenterlight/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 32fe4138..9cce2e97 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1552,7 +1552,7 @@ def do_provisioning(request, stripe_api_cus_id, card_details_response, } create_vm( - billing_address_data, stripe_customer_id, specs, + billing_address_data, custom_user.stripecustomer.id, specs, stripe_subscription_obj, card_details_dict, request, vm_template_id, template, user ) From ba3c5ddd1dda0003cda4a8813f274e80ca3ffb68 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 1 Jan 2021 00:09:47 +0530 Subject: [PATCH 164/257] Use different js code for one-time and subscriptions --- .../hosting/js/virtual_machine_detail.js | 169 +++++++++--------- 1 file changed, 85 insertions(+), 84 deletions(-) diff --git a/hosting/static/hosting/js/virtual_machine_detail.js b/hosting/static/hosting/js/virtual_machine_detail.js index 5e13519c..72770182 100644 --- a/hosting/static/hosting/js/virtual_machine_detail.js +++ b/hosting/static/hosting/js/virtual_machine_detail.js @@ -92,101 +92,102 @@ $(document).ready(function() { }); var create_vm_form = $('#virtual_machine_create_form'); - create_vm_form.submit(placeOrderPaymentIntent); - - function placeOrderPaymentIntent(e) { - e.preventDefault(); - var stripe = Stripe(window.stripeKey); - stripe.confirmCardPayment( - window.paymentIntentSecret, - { - payment_method: window.pm_id - } - ).then(function(result) { - window.result = result; - fa_icon = $('.modal-icon > .fa'); - modal_btn = $('#createvm-modal-done-btn'); - if (result.error) { - // Display error.message in your UI. - modal_btn.attr('href', error_url).removeClass('hide'); - fa_icon.attr('class', 'fa fa-close'); - modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide'); - $('#createvm-modal-title').text(error_title); - $('#createvm-modal-body').html(result.error.message + " " + error_msg); - } else { - // The payment has succeeded - // Display a success message - modal_btn.attr('href', success_url).removeClass('hide'); - fa_icon.attr('class', 'checkmark'); - $('#createvm-modal-title').text(success_title); - $('#createvm-modal-body').html(success_msg); - } - }); - } - /* - create_vm_form.submit(function () { - $('#btn-create-vm').prop('disabled', true); - $.ajax({ - url: create_vm_form.attr('action'), - type: 'POST', - data: create_vm_form.serialize(), - init: function(){ - ok_btn = $('#createvm-modal-done-btn'); - close_btn = $('#createvm-modal-close-btn'); - ok_btn.addClass('btn btn-success btn-ok btn-wide hide'); - close_btn.addClass('btn btn-danger btn-ok btn-wide hide'); - }, - success: function (data) { - fa_icon = $('.modal-icon > .fa'); - modal_btn = $('#createvm-modal-done-btn'); - if (data.showSCA){ - console.log("Show SCA"); - var stripe = Stripe(data.STRIPE_PUBLISHABLE_KEY); - stripe.confirmCardPayment(data.payment_intent_secret).then(function(result) { - if (result.error) { - // Display error.message in your UI. - modal_btn.attr('href', data.error.redirect).removeClass('hide'); + if (window.isSubscription) { + create_vm_form.submit(function () { + $('#btn-create-vm').prop('disabled', true); + $.ajax({ + url: create_vm_form.attr('action'), + type: 'POST', + data: create_vm_form.serialize(), + init: function () { + ok_btn = $('#createvm-modal-done-btn'); + close_btn = $('#createvm-modal-close-btn'); + ok_btn.addClass('btn btn-success btn-ok btn-wide hide'); + close_btn.addClass('btn btn-danger btn-ok btn-wide hide'); + }, + success: function (data) { + fa_icon = $('.modal-icon > .fa'); + modal_btn = $('#createvm-modal-done-btn'); + if (data.showSCA) { + console.log("Show SCA"); + var stripe = Stripe(data.STRIPE_PUBLISHABLE_KEY); + stripe.confirmCardPayment(data.payment_intent_secret).then(function (result) { + if (result.error) { + // Display error.message in your UI. + modal_btn.attr('href', data.error.redirect).removeClass('hide'); + fa_icon.attr('class', 'fa fa-close'); + modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide'); + $('#createvm-modal-title').text(data.error.msg_title); + $('#createvm-modal-body').html(data.error.msg_body); + } else { + // The payment has succeeded. Display a success message. + modal_btn.attr('href', data.success.redirect).removeClass('hide'); + fa_icon.attr('class', 'checkmark'); + $('#createvm-modal-title').text(data.success.msg_title); + $('#createvm-modal-body').html(data.success.msg_body); + } + }); + $('#3Dsecure-modal').show(); + } else { + $('#createvm-modal-title').text(data.msg_title); + $('#createvm-modal-body').html(data.msg_body); + if (data.redirect) { + modal_btn.attr('href', data.redirect).removeClass('hide'); + } else { + modal_btn.attr('href', ""); + } + if (data.status === true) { + fa_icon.attr('class', 'checkmark'); + } else { fa_icon.attr('class', 'fa fa-close'); modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide'); - $('#createvm-modal-title').text(data.error.msg_title); - $('#createvm-modal-body').html(data.error.msg_body); - } else { - // The payment has succeeded. Display a success message. - modal_btn.attr('href', data.success.redirect).removeClass('hide'); - fa_icon.attr('class', 'checkmark'); - $('#createvm-modal-title').text(data.success.msg_title); - $('#createvm-modal-body').html(data.success.msg_body); } - }); - $('#3Dsecure-modal').show(); - } else { - $('#createvm-modal-title').text(data.msg_title); - $('#createvm-modal-body').html(data.msg_body); - if (data.redirect) { - modal_btn.attr('href', data.redirect).removeClass('hide'); - } else { - modal_btn.attr('href', ""); } - if (data.status === true) { - fa_icon.attr('class', 'checkmark'); - } else { - fa_icon.attr('class', 'fa fa-close'); - modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide'); - } - } - }, - error: function (xmlhttprequest, textstatus, message) { + }, + error: function (xmlhttprequest, textstatus, message) { fa_icon = $('.modal-icon > .fa'); fa_icon.attr('class', 'fa fa-close'); - if (typeof(create_vm_error_message) !== 'undefined') { + if (typeof (create_vm_error_message) !== 'undefined') { $('#createvm-modal-body').text(create_vm_error_message); } $('#btn-create-vm').prop('disabled', false); $('#createvm-modal-close-btn').removeClass('hide'); - } + } + }); + return false; }); - return false; - });*/ + } else { + create_vm_form.submit(placeOrderPaymentIntent); + function placeOrderPaymentIntent(e) { + e.preventDefault(); + var stripe = Stripe(window.stripeKey); + stripe.confirmCardPayment( + window.paymentIntentSecret, + { + payment_method: window.pm_id + } + ).then(function(result) { + window.result = result; + fa_icon = $('.modal-icon > .fa'); + modal_btn = $('#createvm-modal-done-btn'); + if (result.error) { + // Display error.message in your UI. + modal_btn.attr('href', error_url).removeClass('hide'); + fa_icon.attr('class', 'fa fa-close'); + modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide'); + $('#createvm-modal-title').text(error_title); + $('#createvm-modal-body').html(result.error.message + " " + error_msg); + } else { + // The payment has succeeded + // Display a success message + modal_btn.attr('href', success_url).removeClass('hide'); + fa_icon.attr('class', 'checkmark'); + $('#createvm-modal-title').text(success_title); + $('#createvm-modal-body').html(success_msg); + } + }); + } + } $('#createvm-modal').on('hidden.bs.modal', function () { $(this).find('.modal-footer .btn').addClass('hide'); }); From e024a3a7a6096d8c2864178f71b6766feeffab39 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 1 Jan 2021 00:34:35 +0530 Subject: [PATCH 165/257] Do not create paymentintent for subscription --- datacenterlight/views.py | 46 ++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 9cce2e97..464a9b34 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -694,24 +694,31 @@ class OrderConfirmationView(DetailView, FormView): 'keys': get_all_public_keys(self.request.user) }) - # Obtain PaymentIntent so that we can initiate and charge/subscribe - # the customer - stripe_utils = StripeUtils() - payment_intent_response = stripe_utils.get_payment_intent( - int(amount_to_charge * 100), - customer=request.session['customer'] - ) - payment_intent = payment_intent_response.get( - 'response_object') - if not payment_intent: - logger.error("Could not create payment_intent %s" % - str(payment_intent_response)) + is_subscription = False + if ('generic_payment_type' not in request.session or + (request.session['generic_payment_details']['recurring'])): + # Obtain PaymentIntent so that we can initiate and charge + # the customer + is_subscription = True + logger.debug("CASE: Subscription") else: - logger.debug("payment_intent.client_secret = %s" % - str(payment_intent.client_secret)) - context.update({ - 'payment_intent_secret': payment_intent.client_secret - }) + logger.debug("CASE: One time payment") + stripe_utils = StripeUtils() + payment_intent_response = stripe_utils.get_payment_intent( + int(amount_to_charge * 100), + customer=request.session['customer'] + ) + payment_intent = payment_intent_response.get( + 'response_object') + if not payment_intent: + logger.error("Could not create payment_intent %s" % + str(payment_intent_response)) + else: + logger.debug("payment_intent.client_secret = %s" % + str(payment_intent.client_secret)) + context.update({ + 'payment_intent_secret': payment_intent.client_secret + }) context.update({ 'site_url': reverse('datacenterlight:index'), @@ -730,10 +737,7 @@ class OrderConfirmationView(DetailView, FormView): reverse('datacenterlight:index') }, 'stripe_key': settings.STRIPE_API_PUBLIC_KEY, - 'is_subscription': 'true' if ( - 'generic_payment_type' not in request.session or - (request.session['generic_payment_details']['recurring']) - ) else 'false' + 'is_subscription': str(is_subscription).lower() }) logger.debug("Request %s" % create_incomplete_intent_request( self.request)) From 04003757dc2ce3fefb0e20279d2312632c4ae883 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 1 Jan 2021 00:43:28 +0530 Subject: [PATCH 166/257] Move log block to correct case --- datacenterlight/views.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 464a9b34..3a295089 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -719,6 +719,24 @@ class OrderConfirmationView(DetailView, FormView): context.update({ 'payment_intent_secret': payment_intent.client_secret }) + logger.debug("Request %s" % create_incomplete_intent_request( + self.request)) + logger.debug("%s" % str(payment_intent)) + logger.debug("customer %s" % request.session['customer']) + logger.debug("card_details_response %s" % card_details_response) + logger.debug("request.session[generic_payment_details] %s" % request.session["generic_payment_details"]) + logger.debug("request.session[billing_address_data] %s" % request.session["billing_address_data"]) + IncompletePaymentIntents.objects.create( + request=create_incomplete_intent_request(self.request), + payment_intent_id=payment_intent.id, + stripe_api_cus_id=request.session['customer'], + card_details_response=json.dumps(card_details_response), + stripe_subscription_id=None, + stripe_charge_id=None, + gp_details=json.dumps(request.session["generic_payment_details"]), + billing_address_data=json.dumps(request.session["billing_address_data"]) + ) + logger.debug("IncompletePaymentIntent done") context.update({ 'site_url': reverse('datacenterlight:index'), @@ -739,24 +757,6 @@ class OrderConfirmationView(DetailView, FormView): 'stripe_key': settings.STRIPE_API_PUBLIC_KEY, 'is_subscription': str(is_subscription).lower() }) - logger.debug("Request %s" % create_incomplete_intent_request( - self.request)) - logger.debug("%s" % str(payment_intent)) - logger.debug("customer %s" % request.session['customer']) - logger.debug("card_details_response %s" % card_details_response) - logger.debug("request.session[generic_payment_details] %s" % request.session["generic_payment_details"]) - logger.debug("request.session[billing_address_data] %s" % request.session["billing_address_data"]) - IncompletePaymentIntents.objects.create( - request=create_incomplete_intent_request(self.request), - payment_intent_id=payment_intent.id, - stripe_api_cus_id=request.session['customer'], - card_details_response=json.dumps(card_details_response), - stripe_subscription_id=None, - stripe_charge_id=None, - gp_details=json.dumps(request.session["generic_payment_details"]), - billing_address_data=json.dumps(request.session["billing_address_data"]) - ) - logger.debug("IncompletePaymentIntent done") return render(request, self.template_name, context) def post(self, request, *args, **kwargs): From 9b84461a297592d56041d776fbc361b534a3b798 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 1 Jan 2021 00:58:11 +0530 Subject: [PATCH 167/257] Add logger messages --- datacenterlight/views.py | 1 + webhook/views.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 3a295089..226985cf 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1403,6 +1403,7 @@ def do_provisioning(request, stripe_api_cus_id, card_details_response, :return: """ + logger.debug("do_provisioning") user = request.get('user', None) # Create user if the user is not logged in and if he is not already diff --git a/webhook/views.py b/webhook/views.py index 5760b0dd..2d2ce371 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -139,6 +139,8 @@ def handle_webhook(request): invoice_obj.billing_reason == subscription_update""") logger.debug("Start provisioning") try: + logger.debug("Looking for subscription %s" % + invoice_obj.subscription) stripe_subscription_obj = stripe.Subscription.retrieve( invoice_obj.subscription) try: From e7462289f6b2e532a8e9915ccf4913848cec9c94 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 1 Jan 2021 01:03:05 +0530 Subject: [PATCH 168/257] Just log IncompleteSubscription does not exist error Do not compare with db and send admin error message --- webhook/views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/webhook/views.py b/webhook/views.py index 2d2ce371..34e668ee 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -197,8 +197,9 @@ def handle_webhook(request): billing_address_data=billing_address_data, real_request=None ) - except (IncompleteSubscriptions.DoesNotExist, - IncompleteSubscriptions.MultipleObjectsReturned) as ex: + except IncompleteSubscriptions.DoesNotExist as ex: + logger.error(str(ex)) + except IncompleteSubscriptions.MultipleObjectsReturned as ex: logger.error(str(ex)) email_data = { 'subject': "IncompleteSubscriptions error", From 36505db5a2b205ac5e4727996071990d79e58235 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 1 Jan 2021 01:35:59 +0530 Subject: [PATCH 169/257] set default_payment_method --- datacenterlight/views.py | 1 + hosting/views.py | 1 + utils/stripe_utils.py | 7 +++++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 226985cf..9f31952e 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -980,6 +980,7 @@ class OrderConfirmationView(DetailView, FormView): discount['stripe_coupon_id'] else ""), tax_rates=[stripe_tax_rate.tax_rate_id] if stripe_tax_rate else [], + default_payment_method=request.session['id_payment_method'] ) stripe_subscription_obj = subscription_result.get('response_object') logger.debug(stripe_subscription_obj) diff --git a/hosting/views.py b/hosting/views.py index f18a22b7..441e33b5 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1201,6 +1201,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): discount['stripe_coupon_id'] else ""), tax_rates=[stripe_tax_rate.tax_rate_id] if stripe_tax_rate else [], + default_payment_method=request.session['id_payment_method'] ) stripe_subscription_obj = subscription_result.get('response_object') latest_invoice = stripe.Invoice.retrieve(stripe_subscription_obj.latest_invoice) diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index d04a24cb..7ef306bf 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -329,10 +329,12 @@ class StripeUtils(object): @handleStripeError def subscribe_customer_to_plan(self, customer, plans, trial_end=None, - coupon="", tax_rates=list()): + coupon="", tax_rates=list(), + default_payment_method=""): """ Subscribes the given customer to the list of given plans + :param default_payment_method: :param tax_rates: :param coupon: :param customer: The stripe customer identifier @@ -355,7 +357,8 @@ class StripeUtils(object): customer=customer, items=plans, trial_end=trial_end, coupon=coupon, default_tax_rates=tax_rates, - payment_behavior='allow_incomplete' + payment_behavior='allow_incomplete', + default_payment_method=default_payment_method ) logger.debug("Done subscribing") return subscription_result From 31c5336e1829171267bdcf24c9715d7f020526c7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 1 Jan 2021 01:59:41 +0530 Subject: [PATCH 170/257] Show cards directly from Stripe and dissociate using PaymentMethod --- hosting/views.py | 6 ++++-- utils/stripe_utils.py | 10 ++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index 441e33b5..df497a64 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -565,9 +565,11 @@ class SettingsView(LoginRequiredMixin, FormView): stripe_customer = None if hasattr(user, 'stripecustomer'): stripe_customer = user.stripecustomer - cards_list = UserCardDetail.get_all_cards_list( - stripe_customer=stripe_customer + stripe_utils = StripeUtils() + cards_list_request = stripe_utils.get_available_payment_methods( + stripe_customer ) + cards_list = cards_list_request.get('response_object') context.update({ 'cards_list': cards_list, 'stripe_key': settings.STRIPE_API_PUBLIC_KEY diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index 7ef306bf..09ffc17b 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -98,8 +98,14 @@ class StripeUtils(object): @handleStripeError def dissociate_customer_card(self, stripe_customer_id, card_id): customer = stripe.Customer.retrieve(stripe_customer_id) - card = customer.sources.retrieve(card_id) - card.delete() + if card_id.startswith("pm"): + logger.debug("PaymentMethod %s detached %s" % (card_id, + stripe_customer_id)) + customer.PaymentMethod.detach(card_id) + else: + logger.debug("card %s detached %s" % (card_id, stripe_customer_id)) + card = customer.sources.retrieve(card_id) + card.delete() @handleStripeError def update_customer_card(self, customer_id, token): From 7cd485bc6d77d1fedce8a87d6aca408619d8cd9e Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 1 Jan 2021 02:37:52 +0530 Subject: [PATCH 171/257] Fix issues on settings/card save/delete methods --- hosting/urls.py | 2 +- hosting/views.py | 37 +++++++++---------------------------- utils/stripe_utils.py | 4 +++- 3 files changed, 13 insertions(+), 30 deletions(-) diff --git a/hosting/urls.py b/hosting/urls.py index 5b2b87b0..e34d27d6 100644 --- a/hosting/urls.py +++ b/hosting/urls.py @@ -51,7 +51,7 @@ urlpatterns = [ name='choice_ssh_keys'), url(r'delete_ssh_key/(?P\d+)/?$', SSHKeyDeleteView.as_view(), name='delete_ssh_key'), - url(r'delete_card/(?P\d+)/?$', SettingsView.as_view(), + url(r'delete_card/(?P[\w\-]+)/$', SettingsView.as_view(), name='delete_card'), url(r'create_ssh_key/?$', SSHKeyCreateView.as_view(), name='create_ssh_key'), diff --git a/hosting/views.py b/hosting/views.py index df497a64..913a9643 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -595,33 +595,14 @@ class SettingsView(LoginRequiredMixin, FormView): messages.add_message(request, messages.SUCCESS, msg) return HttpResponseRedirect(reverse_lazy('hosting:settings')) if 'delete_card' in request.POST: - try: - card = UserCardDetail.objects.get(pk=self.kwargs.get('pk')) - if (request.user.has_perm(self.permission_required[0], card) - and - request.user - .stripecustomer - .usercarddetail_set - .count() > 1): - if card.card_id is not None: - stripe_utils = StripeUtils() - stripe_utils.dissociate_customer_card( - request.user.stripecustomer.stripe_id, - card.card_id - ) - if card.preferred: - UserCardDetail.set_default_card_from_stripe( - request.user.stripecustomer.stripe_id - ) - card.delete() - msg = _("Card deassociation successful") - messages.add_message(request, messages.SUCCESS, msg) - else: - msg = _("You are not permitted to do this operation") - messages.add_message(request, messages.ERROR, msg) - except UserCardDetail.DoesNotExist: - msg = _("The selected card does not exist") - messages.add_message(request, messages.ERROR, msg) + card = self.kwargs.get('pk') + stripe_utils = StripeUtils() + stripe_utils.dissociate_customer_card( + request.user.stripecustomer.stripe_id, + card + ) + msg = _("Card deassociation successful") + messages.add_message(request, messages.SUCCESS, msg) return HttpResponseRedirect(reverse_lazy('hosting:settings')) form = self.get_form() if form.is_valid(): @@ -697,7 +678,7 @@ class SettingsView(LoginRequiredMixin, FormView): messages.add_message(request, messages.SUCCESS, msg) else: # TODO : Test this flow - id_payment_method = form.cleaned_data.get('id_payment_method') + id_payment_method = request.POST.get('id_payment_method', None) stripe_utils = StripeUtils() card_details = stripe_utils.get_cards_details_from_payment_method( id_payment_method diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index 09ffc17b..0bfb2412 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -101,7 +101,9 @@ class StripeUtils(object): if card_id.startswith("pm"): logger.debug("PaymentMethod %s detached %s" % (card_id, stripe_customer_id)) - customer.PaymentMethod.detach(card_id) + pm = stripe.PaymentMethod.retrieve(card_id) + stripe.PaymentMethod.detach(card_id) + pm.delete() else: logger.debug("card %s detached %s" % (card_id, stripe_customer_id)) card = customer.sources.retrieve(card_id) From 8deed169ca6c5d223f28bd63dce5e0e9ba474951 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 1 Jan 2021 11:12:56 +0530 Subject: [PATCH 172/257] Fix redirect url Take user to virtual_machines page if the user ordered a VM --- datacenterlight/views.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 9f31952e..ea39cd9c 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1027,6 +1027,14 @@ class OrderConfirmationView(DetailView, FormView): latest_invoice.payment_intent ) # TODO: requires_attention is probably wrong value to compare + if request.user.is_authenticated(): + if 'generic_payment_details' in request.session: + redirect_url = reverse('hosting:invoices') + else: + redirect_url = reverse('hosting:virtual_machines') + else: + redirect_url = reverse('datacenterlight:index') + if (pi.status == 'requires_attention' or pi.status == 'requires_source_action'): logger.debug("Display SCA authentication %s " % pi.status) @@ -1037,11 +1045,7 @@ class OrderConfirmationView(DetailView, FormView): 'showSCA': True, 'success': { 'status': True, - 'redirect': ( - reverse('hosting:invoices') - if request.user.is_authenticated() - else reverse('datacenterlight:index') - ), + 'redirect': redirect_url, 'msg_title': str(_('Thank you for the order.')), 'msg_body': str( _('Your product will be provisioned as soon as' From 1d7c3f424cbd9ecc9c060279739e589654122d13 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 1 Jan 2021 11:17:07 +0530 Subject: [PATCH 173/257] Don't send admin email if IncompletePaymentIntent lookup doesn't contain a value --- webhook/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webhook/views.py b/webhook/views.py index 34e668ee..0a96d0b6 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -289,11 +289,11 @@ def handle_webhook(request): billing_address_data=billing_address_data ) incomplete_pm.save() - except (IncompletePaymentIntents.DoesNotExist, - IncompletePaymentIntents.MultipleObjectsReturned, + except IncompletePaymentIntents.DoesNotExist as ex: + logger.error(str(ex)) + except (IncompletePaymentIntents.MultipleObjectsReturned, Exception) as ex: logger.error(str(ex)) - logger.debug(str(ex)) email_data = { 'subject': "IncompletePaymentIntents error", 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, From 44ebb7191604383e4fbfc162d8ed595a06ad0fe9 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 2 Jan 2021 09:02:37 +0530 Subject: [PATCH 174/257] Also clear id_payment_method from session --- datacenterlight/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 8a087e3e..6a0e45ca 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -111,7 +111,8 @@ 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', 'billing_address_id']: + 'vat_validation_status', 'billing_address_id', + 'id_payment_method']: if session_var in request.session: del request.session[session_var] From 6c968fdbb8cb93a34ebffb8a2cb0fced620ac9ce Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 2 Jan 2021 09:02:58 +0530 Subject: [PATCH 175/257] Redirect to dcl payment We no longer seem to use hosting payment --- datacenterlight/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index ea39cd9c..5103f859 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -598,7 +598,7 @@ class OrderConfirmationView(DetailView, FormView): payment_method ) if not card_details.get('response_object'): - return HttpResponseRedirect(reverse('hosting:payment')) + return HttpResponseRedirect(reverse('datacenterlight:payment')) card_details_response = card_details['response_object'] context['cc_last4'] = card_details_response['last4'] context['cc_brand'] = card_details_response['brand'] From ec13a71866d73b6d8229efc9aa09148a59f1c9a5 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 7 Jan 2021 16:29:34 +0530 Subject: [PATCH 176/257] Reformat code --- datacenterlight/views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 5103f859..6f765da8 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -8,8 +8,9 @@ from django.contrib import messages from django.contrib.auth import login, authenticate from django.core.exceptions import ValidationError from django.core.urlresolvers import reverse -from django.http import HttpResponseRedirect, JsonResponse, Http404, \ - HttpResponse +from django.http import ( + HttpResponseRedirect, JsonResponse, Http404, HttpResponse +) from django.shortcuts import render from django.utils.translation import get_language, ugettext_lazy as _ from django.views.decorators.cache import cache_control From 1e67bef4f54cec9910fb1ab8c54ee571d8a786e1 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 7 Jan 2021 16:30:33 +0530 Subject: [PATCH 177/257] Remove unwanted code/comments --- datacenterlight/views.py | 10 +++++++--- hosting/static/hosting/js/payment.js | 18 ------------------ hosting/views.py | 1 - utils/stripe_utils.py | 1 - 4 files changed, 7 insertions(+), 23 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 6f765da8..3d3543c5 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -471,7 +471,6 @@ class PaymentOrderView(FormView): id_payment_method = self.request.POST.get('id_payment_method', None) if id_payment_method == 'undefined': - # Probably user chose one of the previously saved cards id_payment_method = address_form.cleaned_data.get('card') request.session["id_payment_method"] = id_payment_method logger.debug("id_payment_method is %s" % id_payment_method) @@ -1125,8 +1124,9 @@ class OrderConfirmationView(DetailView, FormView): def create_incomplete_intent_request(request): """ - Persist session variables so that they could be pick up - in the webhook for processing. + Creates a dictionary of all session variables so that they could be + picked up in the webhook for processing. + :param request: :return: """ @@ -1233,6 +1233,10 @@ def do_provisioning_generic( stripe_api_cus_id, request['id_payment_method'], set_as_default=True ) + """ + Identical to do_provisioning(), except for the fact that this + is specific to handling provisioning of the generic products + """ logger.debug("Card %s associate result %s" % ( request['id_payment_method'], acc_result.get('response_object') diff --git a/hosting/static/hosting/js/payment.js b/hosting/static/hosting/js/payment.js index a2e2717a..3c4d67da 100644 --- a/hosting/static/hosting/js/payment.js +++ b/hosting/static/hosting/js/payment.js @@ -211,24 +211,6 @@ $(document).ready(function () { } }); window.card = cardNumberElement; - /* stripe.confirmCardPayment( - window.paymentIntentSecret, - { - payment_method: {card: cardNumberElement} - } - ).then(function(result) { - window.result = result; - if (result.error) { - // Display error.message in your UI. - var errorElement = document.getElementById('card-errors'); - errorElement.textContent = result.error.message; - } else { - // The payment has succeeded - // Display a success message - alert("Thanks for the order. Your product will be provisioned " + - "as soon as we receive the payment. Thank you."); - } - }); */ } function payWithStripe_new(e) { e.preventDefault(); diff --git a/hosting/views.py b/hosting/views.py index 913a9643..011da997 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -677,7 +677,6 @@ class SettingsView(LoginRequiredMixin, FormView): msg = _("Billing address updated successfully") messages.add_message(request, messages.SUCCESS, msg) else: - # TODO : Test this flow id_payment_method = request.POST.get('id_payment_method', None) stripe_utils = StripeUtils() card_details = stripe_utils.get_cards_details_from_payment_method( diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index 0bfb2412..875a174e 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -111,7 +111,6 @@ class StripeUtils(object): @handleStripeError def update_customer_card(self, customer_id, token): - # TODO replace token with payment intent customer = stripe.Customer.retrieve(customer_id) current_card_token = customer.default_source customer.sources.retrieve(current_card_token).delete() From c58302d90e1f01beaf085b8aaf82e34b5f2edf19 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 7 Jan 2021 16:30:48 +0530 Subject: [PATCH 178/257] Log error messages --- datacenterlight/views.py | 4 ++++ opennebula_api/models.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 3d3543c5..e149bfc4 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -593,6 +593,7 @@ class OrderConfirmationView(DetailView, FormView): return HttpResponseRedirect(reverse('datacenterlight:index')) if 'id_payment_method' in self.request.session: payment_method = self.request.session['id_payment_method'] + logger.debug("id_payment_method: %s" % payment_method) stripe_utils = StripeUtils() card_details = stripe_utils.get_cards_details_from_payment_method( payment_method @@ -609,6 +610,7 @@ class OrderConfirmationView(DetailView, FormView): else: # TODO check when we go through this case (to me, it seems useless) card_id = self.request.session.get('card_id') + logger.debug("NO id_payment_method, using card: %s" % card_id) card_detail = UserCardDetail.objects.get(id=card_id) context['cc_last4'] = card_detail.last4 context['cc_brand'] = card_detail.brand @@ -1577,6 +1579,7 @@ def do_provisioning(request, stripe_api_cus_id, card_details_response, def get_error_response_dict(msg, request): + logger.error(msg) response = { 'status': False, 'redirect': "{url}#{section}".format( @@ -1600,6 +1603,7 @@ def get_error_response_dict(msg, request): def show_error(msg, request): + logger.error(msg) messages.add_message(request, messages.ERROR, msg, extra_tags='failed_payment') return JsonResponse(get_error_response_dict(msg,request)) diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 92d5c568..2f76f423 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -154,6 +154,8 @@ class OpenNebulaManager(): protocol=settings.OPENNEBULA_PROTOCOL) ) raise ConnectionRefusedError + except Exception as ex: + logger.error(str(ex)) def _get_user_pool(self): try: From 21f762d6b8fa23eb3e985b01ab1dea412d87dd9d Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 7 Jan 2021 16:39:00 +0530 Subject: [PATCH 179/257] Update Changelog for 3.0 --- Changelog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changelog b/Changelog index b54713fe..8102c8b5 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,6 @@ +3.0: 2021-01-07 + * 8393: Implement SCA for stripe payments (MR!745) + * 8691: Implment check_vm_templates management command (MR!744) 2.14: 2020-12-07 * 8692: Create a script that fixes django db for the order after celery error (MR!743) 2.13: 2020-12-02 From 640807eb6258b27ba9661f3c2daf0e8d2d0d85a3 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 12 Jan 2021 13:39:11 +0530 Subject: [PATCH 180/257] Don't get card from local db --- hosting/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index 011da997..8b9f618c 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -580,7 +580,6 @@ class SettingsView(LoginRequiredMixin, FormView): def post(self, request, *args, **kwargs): if 'card' in request.POST and request.POST['card'] is not '': card_id = escape(request.POST['card']) - user_card_detail = UserCardDetail.objects.get(id=card_id) UserCardDetail.set_default_card( stripe_api_cus_id=request.user.stripecustomer.stripe_id, stripe_source_id=user_card_detail.card_id From 3b874901bce72f5b646350b3e939083a4138ba72 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 12 Jan 2021 13:40:03 +0530 Subject: [PATCH 181/257] Update using correct card details --- hosting/views.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index 8b9f618c..81cb5de1 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -582,13 +582,23 @@ class SettingsView(LoginRequiredMixin, FormView): card_id = escape(request.POST['card']) UserCardDetail.set_default_card( stripe_api_cus_id=request.user.stripecustomer.stripe_id, - stripe_source_id=user_card_detail.card_id + stripe_source_id=card_id ) + stripe_utils = StripeUtils() + card_details = stripe_utils.get_cards_details_from_payment_method( + card_id + ) + if not card_details.get('response_object'): + logger.debug("Could not find card %s in stripe" % card_id) + messages.add_message(request, messages.ERROR, + _("Could not set a default card.")) + return HttpResponseRedirect(reverse_lazy('hosting:settings')) + card_details_response = card_details['response_object'] msg = _( ("Your {brand} card ending in {last4} set as " "default card").format( - brand=user_card_detail.brand, - last4=user_card_detail.last4 + brand=card_details_response['brand'], + last4=card_details_response['last4'] ) ) messages.add_message(request, messages.SUCCESS, msg) From af09b343c0051912ca948f1ab79b0d7d60e05c01 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 12 Jan 2021 13:56:58 +0530 Subject: [PATCH 182/257] Update Changelog for 3.1 --- Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog b/Changelog index 8102c8b5..957a3018 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,5 @@ +3.1: 2021-01-11 + * 8781: Fix error is setting a default card (MR!746) 3.0: 2021-01-07 * 8393: Implement SCA for stripe payments (MR!745) * 8691: Implment check_vm_templates management command (MR!744) From a5c83dd58931354299f78e16558539fd0f3adcef Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 7 Feb 2021 15:55:47 +0530 Subject: [PATCH 183/257] Update order confirmation text to better prepared for payment dispute --- hosting/locale/de/LC_MESSAGES/django.po | 86 +++++++++++++++------ hosting/templates/hosting/order_detail.html | 2 +- 2 files changed, 65 insertions(+), 23 deletions(-) diff --git a/hosting/locale/de/LC_MESSAGES/django.po b/hosting/locale/de/LC_MESSAGES/django.po index 08bcdd7a..3767ea0f 100644 --- a/hosting/locale/de/LC_MESSAGES/django.po +++ b/hosting/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-11-15 16:40+0000\n" +"POT-Creation-Date: 2021-02-07 10:19+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -211,6 +211,9 @@ msgstr "Bezahlbares VM Hosting in der Schweiz" msgid "My Dashboard" msgstr "Mein Dashboard" +msgid "Welcome" +msgstr "" + msgid "My VMs" msgstr "Meine VMs" @@ -364,6 +367,11 @@ msgstr "Abgelehnt" msgid "Billed to" msgstr "Rechnungsadresse" +#, fuzzy +#| msgid "Card Number" +msgid "VAT Number" +msgstr "Kreditkartennummer" + msgid "Payment method" msgstr "Bezahlmethode" @@ -391,6 +399,9 @@ msgstr "Festplattenkapazität" msgid "Subtotal" msgstr "Zwischensumme" +msgid "VAT for" +msgstr "" + msgid "VAT" msgstr "Mehrwertsteuer" @@ -424,18 +435,22 @@ msgstr "ZURÜCK ZUR LISTE" msgid "Some problem encountered. Please try again later." msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal." +#, fuzzy +#| msgid "Description" +msgid "Subscriptions" +msgstr "Beschreibung" + +#, fuzzy +#| msgid "One time payment" +msgid "One-time payments" +msgstr "Einmalzahlung" + msgid "VM ID" msgstr "" msgid "IP Address" msgstr "IP-Adresse" -msgid "See Invoice" -msgstr "Siehe Rechnung" - -msgid "Page" -msgstr "Seite" - msgid "Log in" msgstr "Anmelden" @@ -480,11 +495,13 @@ msgstr "Bestellungsübersicht" #, python-format msgid "" -"By clicking \"Place order\" this plan will charge your credit card account " -"with %(vm_price)s CHF/month" +"By clicking \"Place order\" you agree to our Terms of Service and " +"this plan will charge your credit card account with %(vm_price)s CHF/month." msgstr "" -"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(vm_price)s CHF " -"pro Monat belastet" +"Indem Du auf "Bestellung aufgeben" klickst, erklärst Du dich mit unseren" +" Nutzungsbedingungen einverstanden und Dein Kreditkartenkonto wird mit %(vm_price)s CHF/Monat belastet." msgid "Place order" msgstr "Bestellen" @@ -504,6 +521,12 @@ msgstr "Schliessen" msgid "Order Nr." msgstr "Bestellung Nr." +msgid "See Invoice" +msgstr "Siehe Rechnung" + +msgid "Page" +msgstr "Seite" + msgid "Your Order" msgstr "Deine Bestellung" @@ -572,6 +595,19 @@ msgstr "Absenden" msgid "Password reset" msgstr "Passwort zurücksetzen" +#, fuzzy +#| msgid "Key name" +msgid "My Username" +msgstr "Key-Name" + +msgid "Your VAT number has been verified" +msgstr "" + +msgid "" +"Your VAT number is under validation. VAT will be adjusted, once the " +"validation is complete." +msgstr "" + msgid "UPDATE" msgstr "AKTUALISIEREN" @@ -773,21 +809,15 @@ msgstr "Dein Passwort konnte nicht zurückgesetzt werden." msgid "The reset password link is no longer valid." msgstr "Der Link zum Zurücksetzen Deines Passwortes ist nicht mehr gültig." +msgid "Could not set a default card." +msgstr "" + msgid "Card deassociation successful" msgstr "Die Verbindung mit der Karte wurde erfolgreich aufgehoben" -msgid "You are not permitted to do this operation" -msgstr "Du hast keine Erlaubnis um diese Operation durchzuführen" - -msgid "The selected card does not exist" -msgstr "Die ausgewählte Karte existiert nicht" - msgid "Billing address updated successfully" msgstr "Die Rechnungsadresse wurde erfolgreich aktualisiert" -msgid "You seem to have already added this card" -msgstr "Es scheint, als hättest du diese Karte bereits hinzugefügt" - #, python-brace-format msgid "An error occurred while associating the card. Details: {details}" msgstr "" @@ -852,7 +882,8 @@ msgstr "Ungültige Speicher-Grösse" #, python-brace-format msgid "Incorrect pricing name. Please contact support{support_email}" -msgstr "Ungültige Preisbezeichnung. Bitte kontaktiere den Support{support_email}" +msgstr "" +"Ungültige Preisbezeichnung. Bitte kontaktiere den Support{support_email}" msgid "" "We could not find the requested VM. Please " @@ -871,7 +902,9 @@ msgstr "Fehler beenden VM" msgid "" "VM terminate action timed out. Please contact support@datacenterlight.ch for " "further information." -msgstr "VM beendet wegen Zeitüberschreitung. Bitte kontaktiere support@datacenterlight.ch für weitere Informationen." +msgstr "" +"VM beendet wegen Zeitüberschreitung. Bitte kontaktiere " +"support@datacenterlight.ch für weitere Informationen." #, python-format msgid "Virtual Machine %(vm_name)s Cancelled" @@ -882,6 +915,15 @@ msgstr "" "Es gab einen Fehler bei der Bearbeitung Deine Anfrage. Bitte versuche es " "noch einmal." +#~ msgid "You are not permitted to do this operation" +#~ msgstr "Du hast keine Erlaubnis um diese Operation durchzuführen" + +#~ msgid "The selected card does not exist" +#~ msgstr "Die ausgewählte Karte existiert nicht" + +#~ msgid "You seem to have already added this card" +#~ msgstr "Es scheint, als hättest du diese Karte bereits hinzugefügt" + #, python-format #~ msgid "This key exists already with the name \"%(name)s\"" #~ msgstr "Der SSH-Key mit dem Name \"%(name)s\" existiert bereits" diff --git a/hosting/templates/hosting/order_detail.html b/hosting/templates/hosting/order_detail.html index 9256271a..dee453d5 100644 --- a/hosting/templates/hosting/order_detail.html +++ b/hosting/templates/hosting/order_detail.html @@ -218,7 +218,7 @@ {% csrf_token %}
    -
    {% blocktrans with vm_price=vm.total_price|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{ vm_price }} CHF/month{% endblocktrans %}.
    +
    {% blocktrans with vm_price=vm.total_price|floatformat:2|intcomma %}By clicking "Place order" you agree to our Terms of Service and this plan will charge your credit card account with {{ vm_price }} CHF/month.{% endblocktrans %}.
    From 63821813d4545e1839c2d1ceeaaf5d44b2208eae Mon Sep 17 00:00:00 2001 From: PCoder Date: Sun, 7 Feb 2021 18:05:48 +0530 Subject: [PATCH 188/257] Fix translations --- datacenterlight/locale/de/LC_MESSAGES/django.po | 1 + 1 file changed, 1 insertion(+) diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po index 7f266a3e..cd7fab99 100644 --- a/datacenterlight/locale/de/LC_MESSAGES/django.po +++ b/datacenterlight/locale/de/LC_MESSAGES/django.po @@ -466,6 +466,7 @@ msgid "" "of Service and this plan will charge your credit card account with " "%(total_price)s CHF/month" msgstr "" +"\n" "Indem Du auf \"Bestellung aufgeben\" klickst, erklärst Du dich mit unseren Nutzungsbedingungen einverstanden und Dein Kreditkartenkonto wird mit %(total_price)s CHF/Monat belastet." From 1d48dfb93b89677be70de4431964ee7184018ab7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 29 Mar 2021 07:23:58 +0530 Subject: [PATCH 189/257] Filter invoices by paid status --- hosting/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index ac5f81de..f55c8383 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1315,7 +1315,8 @@ class InvoiceListView(LoginRequiredMixin, TemplateView): logger.debug("User does not exist") cu = self.request.user invs = stripe.Invoice.list(customer=cu.stripecustomer.stripe_id, - count=100) + count=100, + status='paid') paginator = Paginator(invs.data, 10) try: invs_page = paginator.page(page) From d26f2b0f69724c971ad0952509af4ab23363fe5e Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 30 Aug 2021 18:29:42 +0530 Subject: [PATCH 190/257] Normalize/convert ascii/ignore unicode characters for homeDirectory --- utils/ldap_manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/utils/ldap_manager.py b/utils/ldap_manager.py index ee190732..fadcdbbe 100644 --- a/utils/ldap_manager.py +++ b/utils/ldap_manager.py @@ -3,6 +3,7 @@ import hashlib import random import ldap3 import logging +import unidecode from django.conf import settings @@ -101,7 +102,7 @@ class LdapManager: "uidNumber": [str(uidNumber)], "gidNumber": [str(settings.LDAP_CUSTOMER_GROUP_ID)], "loginShell": ["/bin/bash"], - "homeDirectory": ["/home/{}".format(user).encode("utf-8")], + "homeDirectory": ["/home/{}".format(unicodedata.normalize('NFKD', user).encode('ascii','ignore'))], "mail": email.encode("utf-8"), "userPassword": [self._ssha_password( password.encode("utf-8") From 47d5c63e3b82bcbf1d49f9b597266fb96ef50774 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 30 Aug 2021 18:38:58 +0530 Subject: [PATCH 191/257] Fix bad import --- utils/ldap_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/ldap_manager.py b/utils/ldap_manager.py index fadcdbbe..d40e931f 100644 --- a/utils/ldap_manager.py +++ b/utils/ldap_manager.py @@ -3,7 +3,7 @@ import hashlib import random import ldap3 import logging -import unidecode +import unicodedata from django.conf import settings From 5ce283318a51656e6a8e7ab342ed51ece66cd82f Mon Sep 17 00:00:00 2001 From: amal Date: Mon, 27 Sep 2021 09:21:24 +0200 Subject: [PATCH 192/257] Fix poland country code in eu_countries --- datacenterlight/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 6a0e45ca..4e8094c0 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -20,7 +20,7 @@ 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', + 'lv', 'lu', 'mt', 'nl', 'pl', 'pt', 'ro','sk', 'si', 'es', 'se', 'gb'] From c0333212aa7b8522b86cfaf97aed551bf3ca5d9b Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Fri, 17 Dec 2021 22:02:20 +0100 Subject: [PATCH 193/257] Begin updating for dockerisation --- Dockerfile | 17 +++++++++++++++++ requirements.txt | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..89158a93 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM python:3.10.0-alpine3.15 + +WORKDIR /usr/src/app + +RUN apk add --update --no-cache \ + git \ + build-base \ + openldap-dev\ + python3-dev\ + && rm -rf /var/cache/apk/* + +# FIX https://github.com/python-ldap/python-ldap/issues/432 +RUN echo 'INPUT ( libldap.so )' > /usr/lib/libldap_r.so + +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt +COPY ./ . diff --git a/requirements.txt b/requirements.txt index e7769a7e..73cdf987 100644 --- a/requirements.txt +++ b/requirements.txt @@ -83,7 +83,7 @@ stripe==2.41.0 wheel==0.29.0 django-admin-honeypot==1.0.0 coverage==4.3.4 -git+https://github.com/ungleich/python-oca.git#egg=python-oca +git+https://github.com/ungleich/python-oca.git#egg=oca djangorestframework==3.6.3 flake8==3.3.0 python-memcached==1.58 From 8179ca4d227e513f2d433cfa10b7f1ec838600c4 Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Fri, 17 Dec 2021 22:21:59 +0100 Subject: [PATCH 194/257] Add support for docker build + docker release --- .dockerignore | 1 + Dockerfile | 5 +++-- Makefile | 6 ++++++ release.sh | 21 +++++++++++++++++++++ requirements.txt | 2 +- 5 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 .dockerignore create mode 100755 release.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..6b8710a7 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +.git diff --git a/Dockerfile b/Dockerfile index 89158a93..50b81cbb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,8 +5,9 @@ WORKDIR /usr/src/app RUN apk add --update --no-cache \ git \ build-base \ - openldap-dev\ - python3-dev\ + openldap-dev \ + python3-dev \ + libpq-dev \ && rm -rf /var/cache/apk/* # FIX https://github.com/python-ldap/python-ldap/issues/432 diff --git a/Makefile b/Makefile index 67c0c15b..68ff014e 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,12 @@ help: @echo ' make rsync_upload ' @echo ' make install_debian_packages ' +buildimage: + docker build -t dynamicweb:$$(git describe) . + +releaseimage: buildimage + ./release.sh + collectstatic: $(PY?) $(BASEDIR)/manage.py collectstatic diff --git a/release.sh b/release.sh new file mode 100755 index 00000000..535cc7d4 --- /dev/null +++ b/release.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# Nico Schottelius, 2021-12-17 + +current=$(git describe --dirty) +last_tag=$(git describe --tags --abbrev=0) +registry=harbor.ungleich.svc.p10.k8s.ooo/ungleich-public +image_url=$registry/dynamicweb:${current} + +if echo $current | grep -q -e 'dirty$'; then + echo Refusing to release a dirty tree build + exit 1 +fi + +if [ "$current" != "$last_tag" ]; then + echo "Last tag ($last_tag) is not current version ($current)" + echo "Only release proper versions" + exit 1 +fi + +docker tag dynamicweb:${current} ${image_url} +docker push ${image_url} diff --git a/requirements.txt b/requirements.txt index 73cdf987..8d04a189 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,7 +25,7 @@ django-compressor==2.0 django-debug-toolbar==1.4 python-dotenv==0.10.3 django-extensions==1.6.7 -django-filer==1.2.0 +django-filer==2.1.2 django-filter==0.13.0 django-formtools==1.0 django-guardian==1.4.4 From 138fd519b781fe0d851cfe335d557616747278ec Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 5 Feb 2022 08:30:58 +0530 Subject: [PATCH 195/257] Change admin email to dcl-orders for vm purchase --- datacenterlight/views.py | 4 ++-- hosting/views.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index e149bfc4..5bf68e0a 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1333,7 +1333,7 @@ def do_provisioning_generic( 'subject': (settings.DCL_TEXT + " Payment received from %s" % context['email']), 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, - 'to': ['info@ungleich.ch'], + 'to': ['dcl-orders@ungleich.ch'], 'body': "\n".join( ["%s=%s" % (k, v) for (k, v) in context.items()]), 'reply_to': [context['email']], @@ -1507,7 +1507,7 @@ def do_provisioning(request, stripe_api_cus_id, card_details_response, 'subject': (settings.DCL_TEXT + " Payment received from %s" % context['email']), 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, - 'to': ['info@ungleich.ch'], + 'to': ['dcl-orders@ungleich.ch'], 'body': "\n".join( ["%s=%s" % (k, v) for (k, v) in context.items()]), 'reply_to': [context['email']], diff --git a/hosting/views.py b/hosting/views.py index f55c8383..e2f6e13b 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1911,7 +1911,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': ['dcl-orders@ungleich.ch'], 'body': "\n".join( ["%s=%s" % (k, v) for (k, v) in admin_email_body.items()]), } From a21b4d6e3f59759fb4c21efb362f51ffe3b4a223 Mon Sep 17 00:00:00 2001 From: "app@dynamicweb-production" Date: Wed, 23 Mar 2022 10:46:12 +0100 Subject: [PATCH 196/257] Change to dcl-orders for new vm --- datacenterlight/tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index 9c98b729..899b506f 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -195,7 +195,7 @@ def handle_metadata_and_emails(order_id, vm_id, manager, user, specs, email_data = { 'subject': settings.DCL_TEXT + " Order from %s" % context['email'], 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, - 'to': ['info@ungleich.ch'], + 'to': ['dcl-orders@ungleich.ch'], 'body': "\n".join( ["%s=%s" % (k, v) for (k, v) in context.items()]), 'reply_to': [context['email']], @@ -233,4 +233,4 @@ def handle_metadata_and_emails(order_id, vm_id, manager, user, specs, logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id)) if vm_id > 0: - get_or_create_vm_detail(custom_user, manager, vm_id) \ No newline at end of file + get_or_create_vm_detail(custom_user, manager, vm_id) From 66f3989c23f742763f8bf12303542b8dd72dc17f Mon Sep 17 00:00:00 2001 From: Tomislav R Date: Mon, 16 May 2022 11:08:14 +0200 Subject: [PATCH 197/257] WIP removing ipv6onlyhosting.{net,ch} references --- digitalglarus/locale/de/LC_MESSAGES/django.po | 1165 +++++++---------- dynamicweb/settings/base.py | 2 - dynamicweb/settings/prod.py | 2 - templates/gdpr/gdpr_banner.html | 2 - 4 files changed, 508 insertions(+), 663 deletions(-) diff --git a/digitalglarus/locale/de/LC_MESSAGES/django.po b/digitalglarus/locale/de/LC_MESSAGES/django.po index ec96f5dc..d5a50d71 100644 --- a/digitalglarus/locale/de/LC_MESSAGES/django.po +++ b/digitalglarus/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-01-31 23:53+0000\n" +"POT-Creation-Date: 2022-05-16 08:56+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,39 +18,6 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -msgid "Posted on" -msgstr "Veröffentlicht am" - -msgid "Articles by" -msgstr "Artikel vom" - -msgid "Archive" -msgstr "Archiv" - -msgid "Tag" -msgstr "" - -msgid "Category" -msgstr "Kategorie" - -msgid "No article found." -msgstr "Keine Artikel gefunden" - -msgid "Back" -msgstr "Zurück" - -msgid "Newer Posts" -msgstr "Neuere Einträge" - -msgid "Older Posts" -msgstr "Ältere Einträge" - -msgid "read more" -msgstr "weiterlesen" - -msgid "by" -msgstr "von" - msgid "Digital Glarus Gallery" msgstr "" @@ -69,9 +36,27 @@ msgstr "Postleitzahl" msgid "Country" msgstr "Land" +msgid "VAT Number" +msgstr "" + msgid "You haven been logged out" msgstr "Sie wurden abgemeldet" +msgid "Posted on" +msgstr "Veröffentlicht am" + +msgid "read more" +msgstr "weiterlesen" + +msgid "by" +msgstr "von" + +msgid "No article found." +msgstr "Keine Artikel gefunden" + +msgid "Back" +msgstr "Zurück" + msgid "previous" msgstr "vorherige" @@ -164,640 +149,506 @@ msgstr "befurworter/?$" msgid "Message Successfully Sent" msgstr "" -msgid "US English" -msgstr "" +#~ msgid "Articles by" +#~ msgstr "Artikel vom" -msgid "German" -msgstr "" +#~ msgid "Archive" +#~ msgstr "Archiv" -msgid "Content" -msgstr "" +#~ msgid "Category" +#~ msgstr "Kategorie" -msgid "default" -msgstr "" +#~ msgid "Newer Posts" +#~ msgstr "Neuere Einträge" -msgid "2 Column" -msgstr "" - -msgid "3 Column" -msgstr "" - -msgid "DG.About" -msgstr "" +#~ msgid "Older Posts" +#~ msgstr "Ältere Einträge" #, fuzzy -#| msgid "contact/?$" -msgid "DG.Contact" -msgstr "kontakt/?$" +#~| msgid "contact/?$" +#~ msgid "DG.Contact" +#~ msgstr "kontakt/?$" -msgid "DG.Home" -msgstr "" +#~ msgid "Amount" +#~ msgstr "Betrag" -msgid "DG.CoWork" -msgstr "" +#~ msgid "" +#~ "This site uses cookies for analytics. By continuing to\n" +#~ " browse this site, you agree to use this." +#~ msgstr "" +#~ "Diese Website verwendet Cookies für Analysezwecke. Mit dem weiterem " +#~ "Besuch dieser Website erklärst Du Dich damit einverstanden, diese zu " +#~ "nutzen." -msgid "DG.OneColumn" -msgstr "" +#~ msgid "Learn more" +#~ msgstr "Lerne mehr" -msgid "Blog" -msgstr "" +#~ msgid "Privacy policy" +#~ msgstr "Datenschutz-Bestimmungen" -msgid "Data Center Light" -msgstr "" - -msgid "Glasfaser" -msgstr "" - -msgid "ungleich" -msgstr "" - -msgid "English" -msgstr "" - -msgid "Deutsch" -msgstr "" - -msgid "Datacenterlight Navbar" -msgstr "" - -msgid "Datacenterlight Footer" -msgstr "" - -msgid "Datacenterlight Calculator" -msgstr "" - -msgid "Amount" -msgstr "Betrag" - -msgid "My Donations" -msgstr "" - -msgid "Set your new password" -msgstr "" - -msgid "Reset" -msgstr "" - -msgid "Already have an account ?" -msgstr "" - -msgid "Log in" -msgstr "" - -msgid "Invoice" -msgstr "" - -msgid "Donation #" -msgstr "" - -msgid "Billing Address:" -msgstr "" - -msgid "Date:" -msgstr "" - -msgid "Payment Method:" -msgstr "" - -msgid "Donation summary" -msgstr "" - -msgid "Donation" -msgstr "" - -msgid "Total" -msgstr "" - -msgid "Finish Configuration" -msgstr "" - -msgid "" -"Thanks for you donation, you can cancel your monthly donation at any time " -"going to profile > subscription " -msgstr "" - -msgid "View Donations" -msgstr "" - -msgid "Cancel Donation" -msgstr "" - -msgid "Donate" -msgstr "" - -msgid "Donations Made" -msgstr "" - -msgid "Date" -msgstr "" - -msgid "View Detail" -msgstr "" - -msgid "Reanude Donation" -msgstr "" - -#, python-format -msgid "" -"You're receiving this email because you requested a password reset for your " -"user account at %(site_name)s." -msgstr "" - -msgid "Please go to the following page and choose a new password:" -msgstr "" - -msgid "Thanks for using our site!" -msgstr "" - -#, python-format -msgid "The %(site_name)s team" -msgstr "" - -msgid "Log in " -msgstr "" - -msgid "Login" -msgstr "" - -msgid "Don't have an account yet ? " -msgstr "" - -msgid "Sign up" -msgstr "" - -msgid "Forgot your password ? " -msgstr "" - -msgid "Reset your password" -msgstr "" - -msgid "DG.Detail" -msgstr "" - -msgid "" -"This site uses cookies for analytics. By continuing to\n" -" browse this site, you agree to use this." -msgstr "" -"Diese Website verwendet Cookies für Analysezwecke. Mit dem weiterem Besuch " -"dieser Website erklärst Du Dich damit einverstanden, diese zu nutzen." - -msgid "Learn more" -msgstr "Lerne mehr" - -msgid "OK" -msgstr "" - -msgid "Privacy policy" -msgstr "Datenschutz-Bestimmungen" - -msgid "" -"\n" -" 1. Responsibility and scope
    \n" -"
    \n" -" Responsible in regards to EU General Data Protection\n" -" Regulation (\"GDPR\") and other applicable data " -"protection\n" -" laws:
    \n" -"
    \n" -" ungleich glarus ag
    \n" -" Bahnhofstrasse 1
    \n" -" 8783 Linthal (CH)
    \n" -" Tel.: +41 55 505 6266
    \n" -" E-Mail: info@ungleich.ch
    \n" -"
    \n" -" This privacy statement applies to the web content of\n" -" ungleich glarus ag, available at the
    \n" -" following domains:
    \n" -"
    \n" -" ungleich.ch
    \n" -" datacenterlight.ch
    \n" -" devuanhosting.com
    \n" -" devuanhosting.ch
    \n" -" digitalglarus.ch
    \n" -" hack4lgarus.ch
    \n" -" ipv6onlyhosting.com
    \n" -" ipv6onlyhosting.ch
    \n" -" ipv6onlyhosting.net
    \n" -" django-hosting.ch
    \n" -" rails-hosting.ch
    \n" -" node-hosting.ch
    \n" -" blog.ungleich.ch
    \n" -"
    \n" -" The Data Privacy Officer:
    \n" -"
    \n" -" Sanghee Kim
    \n" -" ungleich glarus ag
    \n" -" Bahnhofstrasse 1
    \n" -" 8783 Linthal (CH)
    \n" -" E-Mail: sanghee.kim@ungleich.ch
    \n" -"
    \n" -"
    \n" -" 2. General
    \n" -"
    \n" -" User data is personal data which is necessary to " -"establish\n" -" or change the contractual relations\n" -" between ungleich glarus ag (\"us\") and you. This " -"includes\n" -" among other things for example the\n" -" name, the address, the date of birth, the e-mail address " -"or\n" -" the IP address. We save and use\n" -" your personal data only for the processing of your " -"orders\n" -" or for getting in contact with you.
    \n" -"
    \n" -"
    \n" -" 3. Processing of personal data
    \n" -"
    \n" -" When you call one of our websites we collect your IP\n" -" address. When you register an account with us your " -"contact\n" -" data will be stored. When you order products on our\n" -" platform we also collect among others your address and " -"your\n" -" payment details.
    \n" -"
    \n" -" I. Registration data
    \n" -"
    \n" -" In the process of registration we need to collect some " -"of\n" -" your personal data. For example we collect your name, " -"your\n" -" address, your telephone number, your e-mail address and\n" -" your payment details to process orders. We do not " -"collect\n" -" your payment details such as credit card number, " -"expiration\n" -" date or the cvv code when you pay with credit card. You\n" -" expose those data directly to the respective payment\n" -" processor. Payments with credit card are processed by\n" -" Stripe Payments Europe Ltd. (\"Stripe\").
    \n" -"
    \n" -"
    \n" -" II. Server log files
    \n" -"
    \n" -" When you call one of our websites we automatically save\n" -" traffic data. Normally the IP address, the type and " -"version\n" -" of your browser, the time and the website that lead to " -"our\n" -" website (\"Referer\") will be saved. Your IP address is\n" -" collected anonymously so that we cannot match it with " -"your\n" -" person. The collection of those data is necessary to\n" -" provide our websites and services, according to Art. 6 " -"par.\n" -" 1 lit. f GDPR.
    \n" -"
    \n" -"
    \n" -" 4. Storage durations
    \n" -"
    \n" -" Your data will be deleted immediately, as soon as it " -"isn't\n" -" relevant anymore for any contract between you and us. " -"In\n" -" some cases it could be necessary to store your data\n" -" further, to comply with our contractual or other legal\n" -" obligatons.
    \n" -"
    \n" -"
    \n" -" 5. Disclosure of your personal data
    \n" -"
    \n" -" We don't disclose your personal data to third parties,\n" -" except:
    \n" -"
    \n" -" a) You grant us your explicit permission according to " -"Art.\n" -" 6 par. 1 S. 1 lit. a GDPR.
    \n" -"
    \n" -" b) The disclosure of your data is legal and it is " -"necessary\n" -" to comply with our contractual or other legal " -"obligations,\n" -" according to Art. 6 par. 1 S. 1 lit b GDPR.
    \n" -"
    \n" -" c) There is a law or legal obligation to disclose your " -"data\n" -" or the disclosure is necessary for the performance of a\n" -" task carried out in the public interest or in the " -"exercise\n" -" of an official authority according to Art. 6 par. 1 S. " -"1\n" -" lit. c GDPR.
    \n" -"
    \n" -" d) The disclosure of your data is necessary for the\n" -" purposes of the legitimate interests pursued by us or by " -"a\n" -" third party, except where such interests are overridden " -"by\n" -" the interests or your fundamental right according to " -"Art.\n" -" 6. par. 1 S. 1 lit. f GDPR.
    \n" -"
    \n" -"
    \n" -" 6. Cookies, Google Analytics, Twitter
    \n" -"
    \n" -" We use Cookies. When you visit one of our websites,\n" -" information is stored on your terminal device in the " -"form\n" -" of a \"cookie\". Cookies are small text files that are " -"stored\n" -" on your terminal device by your browser. You can opt-out " -"of\n" -" the storage of cookies in the settings of your\n" -" browser-software, but however, in this case you might " -"not\n" -" be able to fully use all functions and services on our\n" -" website.
    \n" -"
    \n" -" We use Google Analytics, a service provided by Google " -"Inc.,\n" -" 1600 Amphitheatre Parkway, Mountainview, CA 94043, USA\n" -" (\"Google\"). Google uses Cookies. Google will use " -"these\n" -" cookies on our behalf to analyse how you use our " -"websites\n" -" and to generate reports about the activities on our\n" -" websites. The information collected by those cookies " -"will\n" -" be transmitted to a Google server in the United States " -"of\n" -" America. We have Google's IP anonymization activated, " -"so\n" -" your IP address will be shortened before transmitting " -"to\n" -" Google. Only in exceptional cases the full IP address " -"will\n" -" be transmitted and then shortened afterwards.
    \n" -"
    \n" -" You may opt out from thecollection of these cookies by\n" -" downloading and installing a browser plugin available " -"at\n" -" the following link:
    \n" -"
    \n" -" http://tools.google.com/dlpage/gaoptout
    \n" -"
    \n" -" You may find further information about the processing " -"of\n" -" your personal data at the following links:
    \n" -"
    \n" -" https://policies.google.com/technologies/ads
    \n" -"
    \n" -" https://www.privacyshield.gov/" -"participant?id=a2zt000000001L5AAI&status=Active
    \n" -"
    \n" -" https://adssettings.google.com/authenticated
    \n" -"
    \n" -" The collected personal data will be deleted or " -"anonymized\n" -" after 14 months according to Art. 6 par. 1 lit. f\n" -" GDPR.
    \n" -"
    \n" -" Our websites use several functions from the social " -"network\n" -" Twitter, a service provided by Twitter Inc., 1355 " -"Market\n" -" Street, Suite 900, San Francisco, CA 94103, USA. We may\n" -" embed content of Twitter in our websites, such as " -"photos,\n" -" videos, texts or buttons. If you are registered with\n" -" Twitter, they may combine your use of these " -"functionalities\n" -" on our websites with your Twitter account. You may find\n" -" further information at https://twitter.com/de/privacy." -"
    \n" -"
    \n" -"
    \n" -" 7. Your rights concerning your personal\n" -" data
    \n" -"
    \n" -" Under applicable GDPR you may have the right to:
    \n" -"
    \n" -" a) Obtain confirmation as to whether or not personal " -"data\n" -" concerning you are being processed, and where that is " -"the\n" -" case, access to the personal data, according to Art. 15\n" -" GDPR.
    \n" -"
    \n" -" b) Obtain the rectification of false or inaccurate data\n" -" concerning you, according to Art. 15, Art. 16 GDPR.
    \n" -"
    \n" -" c) Obtain the deletion of your personal data, according " -"to\n" -" Art. 17 GDPR.
    \n" -"
    \n" -" d) Obtain the restriction of processing your personal " -"data,\n" -" according to Art. 18 GDPR.
    \n" -"
    \n" -" e) Obtain a digital copy of your personal data processed " -"by\n" -" us, according to Art. 20 GDPR.
    \n" -"
    \n" -" f) Revoke a granted permission regarding your personal " -"data\n" -" at any time, according to Art. 7 par. 3 GDPR.
    \n" -"
    \n" -" g) Complain at a data protection authority, according " -"to\n" -" Art. 77 GDPR.
    \n" -"
    \n" -"
    \n" -"
    \n" -" 8. Hyperlinks / Disclaimer
    \n" -"
    \n" -" This privacy policy applies only to our websites and not " -"to\n" -" other websites or applications operated by third " -"parties.\n" -" We may provide links to other websites but we are not\n" -" responsible for the privacy practices of such other\n" -" websites.
    \n" -"
    \n" -"
    \n" -" " -msgstr "" -"\n" -"1. Verantwortung und Geltungsbereich

    Verantwortlich im Sinne der EU-Datenschutzgrundverordnung („DSGVO“) und " -"sonstiger datenschutzrechtlicher Bestimmungen ist:

    ungleich glarus " -"ag
    Bahnhofstrasse 1
    8783 Linthal (CH)
    Tel.: +41 55 505 6266
    E-Mail: info@ungleich.ch

    Diese Datenschutzerklärung gilt für das " -"Internetangebot der ungleich glarus ag, welches unter den nachfolgenden " -"Domains erreichbar ist:

    ungleich.ch
    datacenterlight.ch
    devuanhosting.com
    devuanhosting.ch
    digitalglarus.ch
    hack4lgarus." -"ch
    ipv6onlyhosting.com
    ipv6onlyhosting.ch
    ipv6onlyhosting.net
    django-hosting.ch
    rails-hosting.ch
    node-hosting.ch
    blog." -"ungleich.ch

    Der Datenschutzbeauftragte des Verantwortlichen ist:

    Sanghee Kim
    ungleich glarus ag
    Bahnhofstrasse 1
    8783 " -"Linthal (CH)
    E-Mail: sanghee." -"kim@ungleich.ch


    2. Grundsätzliches

    Bei personenbezogenen Daten handelt es sich um alle Informationen, die " -"sich auf eine identifizierte oder identifizierbare natürliche Person " -"beziehen. Hierzu gehört zum Beispiel dein Name, deine Anschrift, dein " -"Geburtsdatum, deine E-Mail-Adresse oder deine IP-Adresse. Wir speichern und " -"verwenden deine personenbezogenen Daten ausschließlich für die Bearbeitung " -"deiner Aufträge und für die Kontaktaufnahme mit dir.


    3. Verarbeitung deiner personenbezogenen Daten

    Bei jedem Aufruf einer unserer Internetseiten wird u. a. deine IP-Adresse " -"gespeichert. Wenn du dich bei uns registrierst, werden deine Kontaktdaten " -"gespeichert. Wenn du Aufträge an uns richtest oder Produkte bei uns " -"bestellst, werden darüber hinaus auch deine Anschrift und deine
    Zahlungsdaten gespeichert.

    I. Registrierungsdaten

    Im Rahmen der Registrierung müssen wir einige " -"personenbezogene Daten von dir erheben und verarbeiten. Beispielsweise " -"benötigen wir deinen Namen, deine Anschrift, deine Telefonnummer, deine E-" -"Mail-Adresse und deine Zahlungsdaten um deine Aufträge zu verarbeiten.
    Bei der Zahlung mittels Kreditkarte erfassen und speichern wir keine " -"Zahlungsverkehrsinformationen wie Kreditkartennummern, das Ablaufdatum oder " -"die Prüfziffer. Diese gibst du ausschließlich direkt dem entsprechenden " -"Zahlungsdienstleister bekannt. Bei der Bezahlung mit Kreditkarte erfolgt die " -"Abwicklung der Bezahlung durch einen externen Dienstleister.
    Für die " -"Kreditkartenzahlung werden deine Angaben direkt vom Diensteanbieter Stripe " -"Payments Europe Ltd. („Stripe“) verarbeitet.

    II. " -"Serverlogdateien

    Bei jedem Aufruf einer unserer " -"Internetseiten speichern wir automatisch bestimmte Daten. Dazu gehören u. a. " -"deine IP-Adresse, Typ und Version des verwendeten Browsers, Uhrzeit, Datum " -"und die Webseite von der du auf unsere Seite gelangst (sog. „Referer“). " -"Deine IP-Adresse wird bei uns nur anonymisiert gespeichert, sodass ein " -"Personenbezug nicht mehr herstellbar ist. Die Erfassung der Daten zur " -"Bereitstellung unserer Internetseiten und die Speicherung der Daten in den " -"Server Logfiles ist für den Betrieb der Internetseite zwingend erforderlich " -"und dient damit der Wahrung eines berechtigten Interesses unseres " -"Unternehmens. Die Rechtsgrundlage für diese Verarbeitung ist Art. 6. Abs. 1 " -"lit. f DSGVO.


    4. Speicherdauer

    Sofern die verarbeiteten personenbezogenen Daten für die Durchführung eines " -"ggf.
    geschlossenen Vertrages nicht mehr erforderlich sind, werden sie " -"umgehend gelöscht. Jedoch kann es auch nach Abschluss des Vertrags " -"erforderlich sein, personenbezogene Daten von dir zu speichern, um unseren " -"vertraglichen oder gesetzlichen Verpflichtungen nachzukommen.


    5. Weitergabe von personenbezogenen Daten

    Wir " -"geben deine personenbezogenen Daten grundsätzlich nicht an Dritte weiter, es " -"sei denn:

    a) Du erteilst uns hierzu deine ausdrückliche " -"Einwilligung nach Art. 6 Abs. 1 S. 1 lit. a DSGVO.

    b) Die " -"Weitergabe der Daten ist gesetzlich zulässig und nach Art. 6. Abs. 1 S. 1 " -"lit b DSGVO zur Erfüllung unserer vertraglichen Pflichten mit Ihnen " -"erforderlich.

    c) Für die Weitergabe der Daten besteht nach Art. 6. " -"Abs. 1 S. 1 lit. c DSGVO eine gesetzliche Verpflichtung.

    d) Die " -"Weitergabe der Daten ist nach Art. 6. Abs. 1 S. 1 lit. f DSGVO zur Wahrung " -"unserer berechtigten Interessen, sowie zur Geltendmachung, Ausübung oder " -"


    6. Cookies, Google Analytics und Twitter

    Wir " -"setzen auf unseren Internetseiten sog. „Cookies“ ein. Cookies sind kleine " -"Textdateien, die im Rahmen deines Besuchs einer unserer Internetseiten von " -"uns an den Browser deines Endgeräts gesendet und dort gespeichert werden. " -"Einige Funktionen unseres Angebots können ohne den Einsatz bestimmter " -"Cookies grundsätzlich aus technischen Gründen nicht angeboten werden. Du " -"kannst deinen Browser allerdings so konfigurieren, dass er nur noch " -"bestimmte oder auch gar keine Cookies mehr akzeptiert. Es kann jedoch " -"passieren, dass du dadurch möglicherweise nicht mehr alle Funktionen unserer " -"Internetseiten nutzen kannst.
    Andere Cookies ermöglichen es uns hingegen " -"verschiedene Analysen deines Verhaltens oder etwa eine Verbindung zu deinen " -"sozialen Netzwerken aufzubauen. Mithilfe dieser Cookies können wir " -"beispielsweise unser Internetangebot für dich nutzerfreundlicher und " -"effektiver gestalten. Daraus folgt, dass wir auf Grundlage unserer " -"berechtigten Unternehmensinteressen (Optimierung und wirtschaftlicher " -"Betrieb unseres Internetangebots) den Werbeanalysedienst „Google Analytics“ " -"der Google Inc., 1600 Amphitheatre Parkway, Mountainview, CA 94043, " -"Vereinigte Staaten von Amerika („Google“) einsetzen. Google verwendet " -"Cookies. Die durch das Cookie erzeugten Informationen werden an einen Server " -"von Google in den Vereinigten Staaten von Amerika übertragen und dort " -"erhoben und gespeichert. Google nutzt dies Informationen in unserem Auftrag " -"dafür, die Nutzung unseres Internetangebots auszuwerten und Berichte über " -"die Aktivitäten auf unserem Onlineangebot uns gegenüber zu erbringen.
    Dabei werden aus den verarbeiteten Daten anonymisierte bzw. pseudonyme " -"Nutzungsprofile einiger Nutzer erstellt. Diese Nutzungsprofile sind nicht " -"auf eine IP-Adresse oder einen spezifischen Nutzer zurückzuführen. Darüber " -"hinaus setzen wir Google Analytics nur mit IP-Anonymisierung ein. Das heißt, " -"dass die IP-Adresse von Google gekürzt wird. Nur in wenigen Ausnahmefällen " -"wird die vollständige IP-Adresse an Google übertragen und dort gekürzt. Du " -"kannst der Verarbeitung von deinen erfassten Daten durch die Installation " -"dieses Browserplugins widersprechen: http://tools.google.com/dlpage/gaoptout?hl=de

    Weitere Informationen zur Nutzung der Daten durch Google und " -"weiterführende Widerspruchsmöglichkeiten erhältst du in der " -"Datenschutzerklärung von Google:

    https://policies.google.com/technologies/ads

    und auf den folgenden Webseiten:

    https://" -"www.privacyshield.gov/participant?id=a2zt000000001L5AAI&status=Active

    https://" -"adssettings.google.com/authenticated

    Die personenbezogenen " -"Daten werden nach 14 Monaten gelöscht oder anonymisiert.
    Rechtliche " -"Grundlage hierfür ist Art. 6 Abs. 1 lit. f DSGVO.

    Innerhalb unseres " -"Onlineangebots können Funktionen und Inhalte des Dienstes Twitter " -"eingebunden werden. Twitter ist ein Dienst der Twitter Inc., 1355 Market " -"Street, Suite 900, San Francisco, CA 94103, Vereinigte Staaten von Amerika. " -"Hierzu können beispielsweise Inhalte wie Bilder, Videos oder Texte und " -"Schaltflächen gehören. Sofern du Mitglied der Plattform Twitter bist, kann " -"Twitter den Aufruf der Funktionalitäten deinem dortigen Profil zuordnen.
    Weitere Informationen zum Datenschutz bei Twitter, findest du in der " -"Datenschutzerklärung von
    Twitter unter https://twitter.com/de/privacy.



    7. " -"Rechte des Betroffenen

    Aus der DSGVO ergeben sich für dich " -"als Betroffener die folgenden Rechte:

    a) Du kannst gem. Art. 15 " -"DSGVO Auskunft über deine von uns verarbeiteten personenbezogenen Daten " -"verlangen. Du kannst insbesondere Auskunft über den Zweck der Verarbeitung, " -"die Kategorien der erhobenen personenbezogenen Daten, die Kategorien von " -"Dritten, gegenüber denen deine Daten offengelegt werden und die " -"voraussichtliche Speicherdauer verlangen.

    b) Du hast gem. Art. 15 " -"u. Art. 16 DSGVO ein Recht auf Berichtigung, Vervollständigung, Löschung und " -"die Bestimmung von Einschränkungen hinsichtlich der Verarbeitung deiner " -"personenbezogenen Daten.

    c) Gemäß Art. 17 DSGVO kannst du von uns " -"die Löschung deiner bei uns gespeicherten personenbezogenen Daten verlangen, " -"soweit die Verarbeitung nicht zur Erfüllung unserer rechtlichen " -"Verpflichtungen, aus Gründen des öffentlichen Interesses oder zur " -"Geltendmachung, Ausübung oder Verteidigung von Rechtsansprüchen erforderlich " -"ist.

    d) Gemäß Art. 18 DSGVO kannst du von uns die Einschränkung der " -"Verarbeitung deiner personenbezogenen Daten verlangen, soweit die " -"Richtigkeit der Daten von dir bestritten wird, die Verarbeitung " -"grundsätzlich unrechtmäßig ist oder die Löschung deiner
    personenbezogenen Daten durch uns ablehnen, weil du diese zur " -"Geltendmachung, Ausübung oder Verteidigung von Rechtsansprüchen benötigst." -"

    e) Gemäß Art. 20 DSGVO kannst du von uns verlangen, dass deine " -"personenbezogenen Daten, die du uns zur Verfügung gestellt hast, in einem " -"strukturierten, maschinenlesbaren und gängigen Format zu erhalten oder du " -"kannst die Übermittlung an eine andere Stelle verlangen.

    f) Gemäß " -"Art. 7 Abs. 3 DSGVO kannst du deine erteilte Einwilligung gegenüber uns " -"jederzeit widerrufen. Dies hat zur Folge, dass wir die auf dieser " -"Einwilligung beruhende Datenverarbeitung für die Zukunft nicht mehr " -"fortführen dürfen.

    g) Gemäß Art. 77 DSGVO steht dir das Recht zu, " -"dich bei einer Aufsichtsbehörde zu beschweren.


    8. " -"Hyperlinks und Haftungsausschluss

    Auf unseren " -"Internetseiten befinden sich sog. „Hyperlinks“, also Verbindungen bzw.
    Weiterleitungen zu den Internetseiten Dritter. Bei Aktivierung dieser " -"Hyperlinks durch Anklicken, wirst du von unserer Internetseite direkt auf " -"die Internetseite eines Dritten weitergeleitet. Wir können keine " -"Verantwortung für den vertraulichen Umgang deiner Daten auf diesen Webseiten " -"Dritter übernehmen, da wir keinen Einfluss darauf haben, dass diese " -"Unternehmen die Datenschutzbestimmungen der DSGVO einhalten. Wir weisen dich " -"außerdem darauf hin, dass es aufgrund der Struktur des Internets möglich " -"ist, dass die Regelungen des Datenschutzes bzw. der DSGVO von anderen, " -"dritten Personen oder Institutionen die nicht innerhalb unseres " -"Verantwortungsbereichs liegen nicht beachtet werden, oder dass sich spontan, " -"kurzfristig schwerwiegende Sicherheitslücken in verbreiteten Standards und " -"technischen Protokollen auftun, die die Datenintegrität gefährden könnten." -"

    \n" -" " - -msgid "Page Header" -msgstr "" - -#, python-format -msgid "No %(verbose_name)s found matching the query" -msgstr "" +#~ msgid "" +#~ "\n" +#~ " 1. Responsibility and scope
    \n" +#~ "
    \n" +#~ " Responsible in regards to EU General Data Protection\n" +#~ " Regulation (\"GDPR\") and other applicable data " +#~ "protection\n" +#~ " laws:
    \n" +#~ "
    \n" +#~ " ungleich glarus ag
    \n" +#~ " Bahnhofstrasse 1
    \n" +#~ " 8783 Linthal (CH)
    \n" +#~ " Tel.: +41 55 505 6266
    \n" +#~ " E-Mail: info@ungleich.ch
    \n" +#~ "
    \n" +#~ " This privacy statement applies to the web content of\n" +#~ " ungleich glarus ag, available at the
    \n" +#~ " following domains:
    \n" +#~ "
    \n" +#~ " ungleich.ch
    \n" +#~ " datacenterlight.ch
    \n" +#~ " devuanhosting.com
    \n" +#~ " devuanhosting.ch
    \n" +#~ " digitalglarus.ch
    \n" +#~ " hack4lgarus.ch
    \n" +#~ " ipv6onlyhosting.com
    \n" +#~ " ipv6onlyhosting.ch
    \n" +#~ " ipv6onlyhosting.net
    \n" +#~ " django-hosting.ch
    \n" +#~ " rails-hosting.ch
    \n" +#~ " node-hosting.ch
    \n" +#~ " blog.ungleich.ch
    \n" +#~ "
    \n" +#~ " The Data Privacy Officer:
    \n" +#~ "
    \n" +#~ " Sanghee Kim
    \n" +#~ " ungleich glarus ag
    \n" +#~ " Bahnhofstrasse 1
    \n" +#~ " 8783 Linthal (CH)
    \n" +#~ " E-Mail: sanghee.kim@ungleich.ch
    \n" +#~ "
    \n" +#~ "
    \n" +#~ " 2. General
    \n" +#~ "
    \n" +#~ " User data is personal data which is necessary to " +#~ "establish\n" +#~ " or change the contractual relations\n" +#~ " between ungleich glarus ag (\"us\") and you. This " +#~ "includes\n" +#~ " among other things for example the\n" +#~ " name, the address, the date of birth, the e-mail " +#~ "address or\n" +#~ " the IP address. We save and use\n" +#~ " your personal data only for the processing of your " +#~ "orders\n" +#~ " or for getting in contact with you.
    \n" +#~ "
    \n" +#~ "
    \n" +#~ " 3. Processing of personal data
    \n" +#~ "
    \n" +#~ " When you call one of our websites we collect your IP\n" +#~ " address. When you register an account with us your " +#~ "contact\n" +#~ " data will be stored. When you order products on our\n" +#~ " platform we also collect among others your address " +#~ "and your\n" +#~ " payment details.
    \n" +#~ "
    \n" +#~ " I. Registration data
    \n" +#~ "
    \n" +#~ " In the process of registration we need to collect " +#~ "some of\n" +#~ " your personal data. For example we collect your name, " +#~ "your\n" +#~ " address, your telephone number, your e-mail address " +#~ "and\n" +#~ " your payment details to process orders. We do not " +#~ "collect\n" +#~ " your payment details such as credit card number, " +#~ "expiration\n" +#~ " date or the cvv code when you pay with credit card. " +#~ "You\n" +#~ " expose those data directly to the respective payment\n" +#~ " processor. Payments with credit card are processed " +#~ "by\n" +#~ " Stripe Payments Europe Ltd. (\"Stripe\").
    \n" +#~ "
    \n" +#~ "
    \n" +#~ " II. Server log files
    \n" +#~ "
    \n" +#~ " When you call one of our websites we automatically " +#~ "save\n" +#~ " traffic data. Normally the IP address, the type and " +#~ "version\n" +#~ " of your browser, the time and the website that lead " +#~ "to our\n" +#~ " website (\"Referer\") will be saved. Your IP address " +#~ "is\n" +#~ " collected anonymously so that we cannot match it with " +#~ "your\n" +#~ " person. The collection of those data is necessary to\n" +#~ " provide our websites and services, according to Art. " +#~ "6 par.\n" +#~ " 1 lit. f GDPR.
    \n" +#~ "
    \n" +#~ "
    \n" +#~ " 4. Storage durations
    \n" +#~ "
    \n" +#~ " Your data will be deleted immediately, as soon as it " +#~ "isn't\n" +#~ " relevant anymore for any contract between you and us. " +#~ "In\n" +#~ " some cases it could be necessary to store your data\n" +#~ " further, to comply with our contractual or other " +#~ "legal\n" +#~ " obligatons.
    \n" +#~ "
    \n" +#~ "
    \n" +#~ " 5. Disclosure of your personal data
    \n" +#~ "
    \n" +#~ " We don't disclose your personal data to third " +#~ "parties,\n" +#~ " except:
    \n" +#~ "
    \n" +#~ " a) You grant us your explicit permission according to " +#~ "Art.\n" +#~ " 6 par. 1 S. 1 lit. a GDPR.
    \n" +#~ "
    \n" +#~ " b) The disclosure of your data is legal and it is " +#~ "necessary\n" +#~ " to comply with our contractual or other legal " +#~ "obligations,\n" +#~ " according to Art. 6 par. 1 S. 1 lit b GDPR.
    \n" +#~ "
    \n" +#~ " c) There is a law or legal obligation to disclose " +#~ "your data\n" +#~ " or the disclosure is necessary for the performance of " +#~ "a\n" +#~ " task carried out in the public interest or in the " +#~ "exercise\n" +#~ " of an official authority according to Art. 6 par. 1 " +#~ "S. 1\n" +#~ " lit. c GDPR.
    \n" +#~ "
    \n" +#~ " d) The disclosure of your data is necessary for the\n" +#~ " purposes of the legitimate interests pursued by us or " +#~ "by a\n" +#~ " third party, except where such interests are " +#~ "overridden by\n" +#~ " the interests or your fundamental right according to " +#~ "Art.\n" +#~ " 6. par. 1 S. 1 lit. f GDPR.
    \n" +#~ "
    \n" +#~ "
    \n" +#~ " 6. Cookies, Google Analytics, Twitter
    \n" +#~ "
    \n" +#~ " We use Cookies. When you visit one of our websites,\n" +#~ " information is stored on your terminal device in the " +#~ "form\n" +#~ " of a \"cookie\". Cookies are small text files that " +#~ "are stored\n" +#~ " on your terminal device by your browser. You can opt-" +#~ "out of\n" +#~ " the storage of cookies in the settings of your\n" +#~ " browser-software, but however, in this case you might " +#~ "not\n" +#~ " be able to fully use all functions and services on " +#~ "our\n" +#~ " website.
    \n" +#~ "
    \n" +#~ " We use Google Analytics, a service provided by Google " +#~ "Inc.,\n" +#~ " 1600 Amphitheatre Parkway, Mountainview, CA 94043, " +#~ "USA\n" +#~ " (\"Google\"). Google uses Cookies. Google will use " +#~ "these\n" +#~ " cookies on our behalf to analyse how you use our " +#~ "websites\n" +#~ " and to generate reports about the activities on our\n" +#~ " websites. The information collected by those cookies " +#~ "will\n" +#~ " be transmitted to a Google server in the United " +#~ "States of\n" +#~ " America. We have Google's IP anonymization activated, " +#~ "so\n" +#~ " your IP address will be shortened before transmitting " +#~ "to\n" +#~ " Google. Only in exceptional cases the full IP address " +#~ "will\n" +#~ " be transmitted and then shortened afterwards.
    \n" +#~ "
    \n" +#~ " You may opt out from thecollection of these cookies " +#~ "by\n" +#~ " downloading and installing a browser plugin available " +#~ "at\n" +#~ " the following link:
    \n" +#~ "
    \n" +#~ " http://tools.google.com/dlpage/gaoptout
    \n" +#~ "
    \n" +#~ " You may find further information about the processing " +#~ "of\n" +#~ " your personal data at the following links:
    \n" +#~ "
    \n" +#~ " https://policies.google.com/technologies/ads
    \n" +#~ "
    \n" +#~ " https://www.privacyshield.gov/" +#~ "participant?id=a2zt000000001L5AAI&status=Active
    \n" +#~ "
    \n" +#~ " https://adssettings.google.com/authenticated
    \n" +#~ "
    \n" +#~ " The collected personal data will be deleted or " +#~ "anonymized\n" +#~ " after 14 months according to Art. 6 par. 1 lit. f\n" +#~ " GDPR.
    \n" +#~ "
    \n" +#~ " Our websites use several functions from the social " +#~ "network\n" +#~ " Twitter, a service provided by Twitter Inc., 1355 " +#~ "Market\n" +#~ " Street, Suite 900, San Francisco, CA 94103, USA. We " +#~ "may\n" +#~ " embed content of Twitter in our websites, such as " +#~ "photos,\n" +#~ " videos, texts or buttons. If you are registered with\n" +#~ " Twitter, they may combine your use of these " +#~ "functionalities\n" +#~ " on our websites with your Twitter account. You may " +#~ "find\n" +#~ " further information at https://twitter.com/de/privacy." +#~ "
    \n" +#~ "
    \n" +#~ "
    \n" +#~ " 7. Your rights concerning your personal\n" +#~ " data
    \n" +#~ "
    \n" +#~ " Under applicable GDPR you may have the right to:
    \n" +#~ "
    \n" +#~ " a) Obtain confirmation as to whether or not personal " +#~ "data\n" +#~ " concerning you are being processed, and where that is " +#~ "the\n" +#~ " case, access to the personal data, according to Art. " +#~ "15\n" +#~ " GDPR.
    \n" +#~ "
    \n" +#~ " b) Obtain the rectification of false or inaccurate " +#~ "data\n" +#~ " concerning you, according to Art. 15, Art. 16 GDPR." +#~ "
    \n" +#~ "
    \n" +#~ " c) Obtain the deletion of your personal data, " +#~ "according to\n" +#~ " Art. 17 GDPR.
    \n" +#~ "
    \n" +#~ " d) Obtain the restriction of processing your personal " +#~ "data,\n" +#~ " according to Art. 18 GDPR.
    \n" +#~ "
    \n" +#~ " e) Obtain a digital copy of your personal data " +#~ "processed by\n" +#~ " us, according to Art. 20 GDPR.
    \n" +#~ "
    \n" +#~ " f) Revoke a granted permission regarding your " +#~ "personal data\n" +#~ " at any time, according to Art. 7 par. 3 GDPR.
    \n" +#~ "
    \n" +#~ " g) Complain at a data protection authority, according " +#~ "to\n" +#~ " Art. 77 GDPR.
    \n" +#~ "
    \n" +#~ "
    \n" +#~ "
    \n" +#~ " 8. Hyperlinks / Disclaimer
    \n" +#~ "
    \n" +#~ " This privacy policy applies only to our websites and " +#~ "not to\n" +#~ " other websites or applications operated by third " +#~ "parties.\n" +#~ " We may provide links to other websites but we are " +#~ "not\n" +#~ " responsible for the privacy practices of such other\n" +#~ " websites.
    \n" +#~ "
    \n" +#~ "
    \n" +#~ " " +#~ msgstr "" +#~ "\n" +#~ "1. Verantwortung und Geltungsbereich

    Verantwortlich im Sinne der EU-Datenschutzgrundverordnung („DSGVO“) und " +#~ "sonstiger datenschutzrechtlicher Bestimmungen ist:

    ungleich " +#~ "glarus ag
    Bahnhofstrasse 1
    8783 Linthal (CH)
    Tel.: +41 55 505 " +#~ "6266
    E-Mail: info@ungleich.ch

    Diese Datenschutzerklärung " +#~ "gilt für das Internetangebot der ungleich glarus ag, welches unter den " +#~ "nachfolgenden Domains erreichbar ist:

    ungleich.ch
    datacenterlight.ch
    devuanhosting.com
    devuanhosting.ch
    digitalglarus.ch
    hack4lgarus.ch
    ipv6onlyhosting.com
    ipv6onlyhosting.ch
    ipv6onlyhosting.net
    django-hosting.ch
    rails-hosting.ch
    node-hosting.ch
    blog.ungleich.ch

    Der " +#~ "Datenschutzbeauftragte des Verantwortlichen ist:

    Sanghee Kim
    ungleich glarus ag
    Bahnhofstrasse 1
    8783 Linthal (CH)
    E-" +#~ "Mail: sanghee.kim@ungleich.ch


    2. Grundsätzliches

    Bei " +#~ "personenbezogenen Daten handelt es sich um alle Informationen, die sich " +#~ "auf eine identifizierte oder identifizierbare natürliche Person beziehen. " +#~ "Hierzu gehört zum Beispiel dein Name, deine Anschrift, dein Geburtsdatum, " +#~ "deine E-Mail-Adresse oder deine IP-Adresse. Wir speichern und verwenden " +#~ "deine personenbezogenen Daten ausschließlich für die Bearbeitung deiner " +#~ "Aufträge und für die Kontaktaufnahme mit dir.


    3. " +#~ "Verarbeitung deiner personenbezogenen Daten

    Bei jedem " +#~ "Aufruf einer unserer Internetseiten wird u. a. deine IP-Adresse " +#~ "gespeichert. Wenn du dich bei uns registrierst, werden deine Kontaktdaten " +#~ "gespeichert. Wenn du Aufträge an uns richtest oder Produkte bei uns " +#~ "bestellst, werden darüber hinaus auch deine Anschrift und deine
    Zahlungsdaten gespeichert.

    I. Registrierungsdaten

    Im Rahmen der Registrierung müssen wir einige " +#~ "personenbezogene Daten von dir erheben und verarbeiten. Beispielsweise " +#~ "benötigen wir deinen Namen, deine Anschrift, deine Telefonnummer, deine E-" +#~ "Mail-Adresse und deine Zahlungsdaten um deine Aufträge zu verarbeiten. " +#~ "
    Bei der Zahlung mittels Kreditkarte erfassen und speichern wir keine " +#~ "Zahlungsverkehrsinformationen wie Kreditkartennummern, das Ablaufdatum " +#~ "oder die Prüfziffer. Diese gibst du ausschließlich direkt dem " +#~ "entsprechenden Zahlungsdienstleister bekannt. Bei der Bezahlung mit " +#~ "Kreditkarte erfolgt die Abwicklung der Bezahlung durch einen externen " +#~ "Dienstleister.
    Für die Kreditkartenzahlung werden deine Angaben " +#~ "direkt vom Diensteanbieter Stripe Payments Europe Ltd. („Stripe“) " +#~ "verarbeitet.

    II. Serverlogdateien

    Bei " +#~ "jedem Aufruf einer unserer Internetseiten speichern wir automatisch " +#~ "bestimmte Daten. Dazu gehören u. a. deine IP-Adresse, Typ und Version des " +#~ "verwendeten Browsers, Uhrzeit, Datum und die Webseite von der du auf " +#~ "unsere Seite gelangst (sog. „Referer“). Deine IP-Adresse wird bei uns nur " +#~ "anonymisiert gespeichert, sodass ein Personenbezug nicht mehr herstellbar " +#~ "ist. Die Erfassung der Daten zur Bereitstellung unserer Internetseiten " +#~ "und die Speicherung der Daten in den Server Logfiles ist für den Betrieb " +#~ "der Internetseite zwingend erforderlich und dient damit der Wahrung eines " +#~ "berechtigten Interesses unseres Unternehmens. Die Rechtsgrundlage für " +#~ "diese Verarbeitung ist Art. 6. Abs. 1 lit. f DSGVO.


    4. Speicherdauer

    Sofern die verarbeiteten " +#~ "personenbezogenen Daten für die Durchführung eines ggf.
    geschlossenen " +#~ "Vertrages nicht mehr erforderlich sind, werden sie umgehend gelöscht. " +#~ "Jedoch kann es auch nach Abschluss des Vertrags erforderlich sein, " +#~ "personenbezogene Daten von dir zu speichern, um unseren vertraglichen " +#~ "oder gesetzlichen Verpflichtungen nachzukommen.


    5. " +#~ "Weitergabe von personenbezogenen Daten

    Wir geben deine " +#~ "personenbezogenen Daten grundsätzlich nicht an Dritte weiter, es sei denn:" +#~ "

    a) Du erteilst uns hierzu deine ausdrückliche Einwilligung nach " +#~ "Art. 6 Abs. 1 S. 1 lit. a DSGVO.

    b) Die Weitergabe der Daten ist " +#~ "gesetzlich zulässig und nach Art. 6. Abs. 1 S. 1 lit b DSGVO zur " +#~ "Erfüllung unserer vertraglichen Pflichten mit Ihnen erforderlich.

    c) Für die Weitergabe der Daten besteht nach Art. 6. Abs. 1 S. 1 lit. c " +#~ "DSGVO eine gesetzliche Verpflichtung.

    d) Die Weitergabe der " +#~ "Daten ist nach Art. 6. Abs. 1 S. 1 lit. f DSGVO zur Wahrung unserer " +#~ "berechtigten Interessen, sowie zur Geltendmachung, Ausübung oder " +#~ "


    6. Cookies, Google Analytics und Twitter

    Wir " +#~ "setzen auf unseren Internetseiten sog. „Cookies“ ein. Cookies sind kleine " +#~ "Textdateien, die im Rahmen deines Besuchs einer unserer Internetseiten " +#~ "von uns an den Browser deines Endgeräts gesendet und dort gespeichert " +#~ "werden. Einige Funktionen unseres Angebots können ohne den Einsatz " +#~ "bestimmter Cookies grundsätzlich aus technischen Gründen nicht angeboten " +#~ "werden. Du kannst deinen Browser allerdings so konfigurieren, dass er nur " +#~ "noch bestimmte oder auch gar keine Cookies mehr akzeptiert. Es kann " +#~ "jedoch passieren, dass du dadurch möglicherweise nicht mehr alle " +#~ "Funktionen unserer Internetseiten nutzen kannst.
    Andere Cookies " +#~ "ermöglichen es uns hingegen verschiedene Analysen deines Verhaltens oder " +#~ "etwa eine Verbindung zu deinen sozialen Netzwerken aufzubauen. Mithilfe " +#~ "dieser Cookies können wir beispielsweise unser Internetangebot für dich " +#~ "nutzerfreundlicher und effektiver gestalten. Daraus folgt, dass wir auf " +#~ "Grundlage unserer berechtigten Unternehmensinteressen (Optimierung und " +#~ "wirtschaftlicher Betrieb unseres Internetangebots) den Werbeanalysedienst " +#~ "„Google Analytics“ der Google Inc., 1600 Amphitheatre Parkway, " +#~ "Mountainview, CA 94043, Vereinigte Staaten von Amerika („Google“) " +#~ "einsetzen. Google verwendet Cookies. Die durch das Cookie erzeugten " +#~ "Informationen werden an einen Server von Google in den Vereinigten " +#~ "Staaten von Amerika übertragen und dort erhoben und gespeichert. Google " +#~ "nutzt dies Informationen in unserem Auftrag dafür, die Nutzung unseres " +#~ "Internetangebots auszuwerten und Berichte über die Aktivitäten auf " +#~ "unserem Onlineangebot uns gegenüber zu erbringen.
    Dabei werden aus " +#~ "den verarbeiteten Daten anonymisierte bzw. pseudonyme Nutzungsprofile " +#~ "einiger Nutzer erstellt. Diese Nutzungsprofile sind nicht auf eine IP-" +#~ "Adresse oder einen spezifischen Nutzer zurückzuführen. Darüber hinaus " +#~ "setzen wir Google Analytics nur mit IP-Anonymisierung ein. Das heißt, " +#~ "dass die IP-Adresse von Google gekürzt wird. Nur in wenigen " +#~ "Ausnahmefällen wird die vollständige IP-Adresse an Google übertragen und " +#~ "dort gekürzt. Du kannst der Verarbeitung von deinen erfassten Daten durch " +#~ "die Installation dieses Browserplugins widersprechen:
    http://tools.google.com/dlpage/" +#~ "gaoptout?hl=de

    Weitere Informationen zur Nutzung der Daten " +#~ "durch Google und weiterführende Widerspruchsmöglichkeiten erhältst du in " +#~ "der Datenschutzerklärung von Google:

    https://policies.google.com/technologies/" +#~ "ads

    und auf den folgenden Webseiten:

    https://www.privacyshield.gov/" +#~ "participant?id=a2zt000000001L5AAI&status=Active

    https://adssettings." +#~ "google.com/authenticated

    Die personenbezogenen Daten werden " +#~ "nach 14 Monaten gelöscht oder anonymisiert.
    Rechtliche Grundlage " +#~ "hierfür ist Art. 6 Abs. 1 lit. f DSGVO.

    Innerhalb unseres " +#~ "Onlineangebots können Funktionen und Inhalte des Dienstes Twitter " +#~ "eingebunden werden. Twitter ist ein Dienst der Twitter Inc., 1355 Market " +#~ "Street, Suite 900, San Francisco, CA 94103, Vereinigte Staaten von " +#~ "Amerika. Hierzu können beispielsweise Inhalte wie Bilder, Videos oder " +#~ "Texte und Schaltflächen gehören. Sofern du Mitglied der Plattform Twitter " +#~ "bist, kann Twitter den Aufruf der Funktionalitäten deinem dortigen Profil " +#~ "zuordnen.
    Weitere Informationen zum Datenschutz bei Twitter, findest " +#~ "du in der Datenschutzerklärung von
    Twitter unter https://twitter.com/de/privacy.



    7. Rechte des Betroffenen

    Aus der DSGVO " +#~ "ergeben sich für dich als Betroffener die folgenden Rechte:

    a) " +#~ "Du kannst gem. Art. 15 DSGVO Auskunft über deine von uns verarbeiteten " +#~ "personenbezogenen Daten verlangen. Du kannst insbesondere Auskunft über " +#~ "den Zweck der Verarbeitung, die Kategorien der erhobenen " +#~ "personenbezogenen Daten, die Kategorien von Dritten, gegenüber denen " +#~ "deine Daten offengelegt werden und die voraussichtliche Speicherdauer " +#~ "verlangen.

    b) Du hast gem. Art. 15 u. Art. 16 DSGVO ein Recht " +#~ "auf Berichtigung, Vervollständigung, Löschung und die Bestimmung von " +#~ "Einschränkungen hinsichtlich der Verarbeitung deiner personenbezogenen " +#~ "Daten.

    c) Gemäß Art. 17 DSGVO kannst du von uns die Löschung " +#~ "deiner bei uns gespeicherten personenbezogenen Daten verlangen, soweit " +#~ "die Verarbeitung nicht zur Erfüllung unserer rechtlichen Verpflichtungen, " +#~ "aus Gründen des öffentlichen Interesses oder zur Geltendmachung, Ausübung " +#~ "oder Verteidigung von Rechtsansprüchen erforderlich ist.

    d) " +#~ "Gemäß Art. 18 DSGVO kannst du von uns die Einschränkung der Verarbeitung " +#~ "deiner personenbezogenen Daten verlangen, soweit die Richtigkeit der " +#~ "Daten von dir bestritten wird, die Verarbeitung grundsätzlich " +#~ "unrechtmäßig ist oder die Löschung deiner
    personenbezogenen Daten " +#~ "durch uns ablehnen, weil du diese zur Geltendmachung, Ausübung oder " +#~ "Verteidigung von Rechtsansprüchen benötigst.

    e) Gemäß Art. 20 " +#~ "DSGVO kannst du von uns verlangen, dass deine personenbezogenen Daten, " +#~ "die du uns zur Verfügung gestellt hast, in einem strukturierten, " +#~ "maschinenlesbaren und gängigen Format zu erhalten oder du kannst die " +#~ "Übermittlung an eine andere Stelle verlangen.

    f) Gemäß Art. 7 " +#~ "Abs. 3 DSGVO kannst du deine erteilte Einwilligung gegenüber uns " +#~ "jederzeit widerrufen. Dies hat zur Folge, dass wir die auf dieser " +#~ "Einwilligung beruhende Datenverarbeitung für die Zukunft nicht mehr " +#~ "fortführen dürfen.

    g) Gemäß Art. 77 DSGVO steht dir das Recht " +#~ "zu, dich bei einer Aufsichtsbehörde zu beschweren.


    8. Hyperlinks und Haftungsausschluss

    Auf " +#~ "unseren Internetseiten befinden sich sog. „Hyperlinks“, also Verbindungen " +#~ "bzw.
    Weiterleitungen zu den Internetseiten Dritter. Bei Aktivierung " +#~ "dieser Hyperlinks durch Anklicken, wirst du von unserer Internetseite " +#~ "direkt auf die Internetseite eines Dritten weitergeleitet. Wir können " +#~ "keine Verantwortung für den vertraulichen Umgang deiner Daten auf diesen " +#~ "Webseiten Dritter übernehmen, da wir keinen Einfluss darauf haben, dass " +#~ "diese Unternehmen die Datenschutzbestimmungen der DSGVO einhalten. Wir " +#~ "weisen dich außerdem darauf hin, dass es aufgrund der Struktur des " +#~ "Internets möglich ist, dass die Regelungen des Datenschutzes bzw. der " +#~ "DSGVO von anderen, dritten Personen oder Institutionen die nicht " +#~ "innerhalb unseres Verantwortungsbereichs liegen nicht beachtet werden, " +#~ "oder dass sich spontan, kurzfristig schwerwiegende Sicherheitslücken in " +#~ "verbreiteten Standards und technischen Protokollen auftun, die die " +#~ "Datenintegrität gefährden könnten.

    \n" +#~ " " #~ msgid "Accept" #~ msgstr "Annehmen" diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 03013ea5..62fe2897 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -631,8 +631,6 @@ GOOGLE_ANALYTICS_PROPERTY_IDS = { 'datacenterlight.ch': 'UA-62285904-8', 'devuanhosting.ch': 'UA-62285904-9', 'devuanhosting.com': 'UA-62285904-9', - 'ipv6onlyhosting.ch': 'UA-62285904-10', - 'ipv6onlyhosting.net': 'UA-62285904-10', 'ipv6onlyhosting.com': 'UA-62285904-10', 'comic.ungleich.ch': 'UA-62285904-13', '127.0.0.1:8000': 'localhost', diff --git a/dynamicweb/settings/prod.py b/dynamicweb/settings/prod.py index 445748ad..0590ca27 100644 --- a/dynamicweb/settings/prod.py +++ b/dynamicweb/settings/prod.py @@ -28,9 +28,7 @@ ALLOWED_HOSTS = [ ".devuanhosting.ch", ".devuanhosting.com", ".digitalezukunft.ch", - ".ipv6onlyhosting.ch", ".ipv6onlyhosting.com", - ".ipv6onlyhosting.net", ".digitalglarus.ch", ".hack4glarus.ch", ".xn--nglarus-n2a.ch" diff --git a/templates/gdpr/gdpr_banner.html b/templates/gdpr/gdpr_banner.html index 7e9f5c7f..f927f8ee 100644 --- a/templates/gdpr/gdpr_banner.html +++ b/templates/gdpr/gdpr_banner.html @@ -134,8 +134,6 @@ digitalglarus.ch
    hack4lgarus.ch
    ipv6onlyhosting.com
    - ipv6onlyhosting.ch
    - ipv6onlyhosting.net
    django-hosting.ch
    rails-hosting.ch
    node-hosting.ch
    From 338ff38bbbfd11b491fd1f6cef6ad8656e678c80 Mon Sep 17 00:00:00 2001 From: Tomislav R Date: Mon, 16 May 2022 11:37:30 +0200 Subject: [PATCH 198/257] Actual po --- digitalglarus/locale/de/LC_MESSAGES/django.po | 1164 ++++++++++------- 1 file changed, 656 insertions(+), 508 deletions(-) diff --git a/digitalglarus/locale/de/LC_MESSAGES/django.po b/digitalglarus/locale/de/LC_MESSAGES/django.po index d5a50d71..ef7a46b5 100644 --- a/digitalglarus/locale/de/LC_MESSAGES/django.po +++ b/digitalglarus/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: 2022-05-16 08:56+0000\n" +"POT-Creation-Date: 2019-01-31 23:53+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,6 +18,39 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +msgid "Posted on" +msgstr "Veröffentlicht am" + +msgid "Articles by" +msgstr "Artikel vom" + +msgid "Archive" +msgstr "Archiv" + +msgid "Tag" +msgstr "" + +msgid "Category" +msgstr "Kategorie" + +msgid "No article found." +msgstr "Keine Artikel gefunden" + +msgid "Back" +msgstr "Zurück" + +msgid "Newer Posts" +msgstr "Neuere Einträge" + +msgid "Older Posts" +msgstr "Ältere Einträge" + +msgid "read more" +msgstr "weiterlesen" + +msgid "by" +msgstr "von" + msgid "Digital Glarus Gallery" msgstr "" @@ -36,27 +69,9 @@ msgstr "Postleitzahl" msgid "Country" msgstr "Land" -msgid "VAT Number" -msgstr "" - msgid "You haven been logged out" msgstr "Sie wurden abgemeldet" -msgid "Posted on" -msgstr "Veröffentlicht am" - -msgid "read more" -msgstr "weiterlesen" - -msgid "by" -msgstr "von" - -msgid "No article found." -msgstr "Keine Artikel gefunden" - -msgid "Back" -msgstr "Zurück" - msgid "previous" msgstr "vorherige" @@ -149,506 +164,638 @@ msgstr "befurworter/?$" msgid "Message Successfully Sent" msgstr "" -#~ msgid "Articles by" -#~ msgstr "Artikel vom" +msgid "US English" +msgstr "" -#~ msgid "Archive" -#~ msgstr "Archiv" +msgid "German" +msgstr "" -#~ msgid "Category" -#~ msgstr "Kategorie" +msgid "Content" +msgstr "" -#~ msgid "Newer Posts" -#~ msgstr "Neuere Einträge" +msgid "default" +msgstr "" -#~ msgid "Older Posts" -#~ msgstr "Ältere Einträge" +msgid "2 Column" +msgstr "" + +msgid "3 Column" +msgstr "" + +msgid "DG.About" +msgstr "" #, fuzzy -#~| msgid "contact/?$" -#~ msgid "DG.Contact" -#~ msgstr "kontakt/?$" +#| msgid "contact/?$" +msgid "DG.Contact" +msgstr "kontakt/?$" -#~ msgid "Amount" -#~ msgstr "Betrag" +msgid "DG.Home" +msgstr "" -#~ msgid "" -#~ "This site uses cookies for analytics. By continuing to\n" -#~ " browse this site, you agree to use this." -#~ msgstr "" -#~ "Diese Website verwendet Cookies für Analysezwecke. Mit dem weiterem " -#~ "Besuch dieser Website erklärst Du Dich damit einverstanden, diese zu " -#~ "nutzen." +msgid "DG.CoWork" +msgstr "" -#~ msgid "Learn more" -#~ msgstr "Lerne mehr" +msgid "DG.OneColumn" +msgstr "" -#~ msgid "Privacy policy" -#~ msgstr "Datenschutz-Bestimmungen" +msgid "Blog" +msgstr "" -#~ msgid "" -#~ "\n" -#~ " 1. Responsibility and scope
    \n" -#~ "
    \n" -#~ " Responsible in regards to EU General Data Protection\n" -#~ " Regulation (\"GDPR\") and other applicable data " -#~ "protection\n" -#~ " laws:
    \n" -#~ "
    \n" -#~ " ungleich glarus ag
    \n" -#~ " Bahnhofstrasse 1
    \n" -#~ " 8783 Linthal (CH)
    \n" -#~ " Tel.: +41 55 505 6266
    \n" -#~ " E-Mail: info@ungleich.ch
    \n" -#~ "
    \n" -#~ " This privacy statement applies to the web content of\n" -#~ " ungleich glarus ag, available at the
    \n" -#~ " following domains:
    \n" -#~ "
    \n" -#~ " ungleich.ch
    \n" -#~ " datacenterlight.ch
    \n" -#~ " devuanhosting.com
    \n" -#~ " devuanhosting.ch
    \n" -#~ " digitalglarus.ch
    \n" -#~ " hack4lgarus.ch
    \n" -#~ " ipv6onlyhosting.com
    \n" -#~ " ipv6onlyhosting.ch
    \n" -#~ " ipv6onlyhosting.net
    \n" -#~ " django-hosting.ch
    \n" -#~ " rails-hosting.ch
    \n" -#~ " node-hosting.ch
    \n" -#~ " blog.ungleich.ch
    \n" -#~ "
    \n" -#~ " The Data Privacy Officer:
    \n" -#~ "
    \n" -#~ " Sanghee Kim
    \n" -#~ " ungleich glarus ag
    \n" -#~ " Bahnhofstrasse 1
    \n" -#~ " 8783 Linthal (CH)
    \n" -#~ " E-Mail: sanghee.kim@ungleich.ch
    \n" -#~ "
    \n" -#~ "
    \n" -#~ " 2. General
    \n" -#~ "
    \n" -#~ " User data is personal data which is necessary to " -#~ "establish\n" -#~ " or change the contractual relations\n" -#~ " between ungleich glarus ag (\"us\") and you. This " -#~ "includes\n" -#~ " among other things for example the\n" -#~ " name, the address, the date of birth, the e-mail " -#~ "address or\n" -#~ " the IP address. We save and use\n" -#~ " your personal data only for the processing of your " -#~ "orders\n" -#~ " or for getting in contact with you.
    \n" -#~ "
    \n" -#~ "
    \n" -#~ " 3. Processing of personal data
    \n" -#~ "
    \n" -#~ " When you call one of our websites we collect your IP\n" -#~ " address. When you register an account with us your " -#~ "contact\n" -#~ " data will be stored. When you order products on our\n" -#~ " platform we also collect among others your address " -#~ "and your\n" -#~ " payment details.
    \n" -#~ "
    \n" -#~ " I. Registration data
    \n" -#~ "
    \n" -#~ " In the process of registration we need to collect " -#~ "some of\n" -#~ " your personal data. For example we collect your name, " -#~ "your\n" -#~ " address, your telephone number, your e-mail address " -#~ "and\n" -#~ " your payment details to process orders. We do not " -#~ "collect\n" -#~ " your payment details such as credit card number, " -#~ "expiration\n" -#~ " date or the cvv code when you pay with credit card. " -#~ "You\n" -#~ " expose those data directly to the respective payment\n" -#~ " processor. Payments with credit card are processed " -#~ "by\n" -#~ " Stripe Payments Europe Ltd. (\"Stripe\").
    \n" -#~ "
    \n" -#~ "
    \n" -#~ " II. Server log files
    \n" -#~ "
    \n" -#~ " When you call one of our websites we automatically " -#~ "save\n" -#~ " traffic data. Normally the IP address, the type and " -#~ "version\n" -#~ " of your browser, the time and the website that lead " -#~ "to our\n" -#~ " website (\"Referer\") will be saved. Your IP address " -#~ "is\n" -#~ " collected anonymously so that we cannot match it with " -#~ "your\n" -#~ " person. The collection of those data is necessary to\n" -#~ " provide our websites and services, according to Art. " -#~ "6 par.\n" -#~ " 1 lit. f GDPR.
    \n" -#~ "
    \n" -#~ "
    \n" -#~ " 4. Storage durations
    \n" -#~ "
    \n" -#~ " Your data will be deleted immediately, as soon as it " -#~ "isn't\n" -#~ " relevant anymore for any contract between you and us. " -#~ "In\n" -#~ " some cases it could be necessary to store your data\n" -#~ " further, to comply with our contractual or other " -#~ "legal\n" -#~ " obligatons.
    \n" -#~ "
    \n" -#~ "
    \n" -#~ " 5. Disclosure of your personal data
    \n" -#~ "
    \n" -#~ " We don't disclose your personal data to third " -#~ "parties,\n" -#~ " except:
    \n" -#~ "
    \n" -#~ " a) You grant us your explicit permission according to " -#~ "Art.\n" -#~ " 6 par. 1 S. 1 lit. a GDPR.
    \n" -#~ "
    \n" -#~ " b) The disclosure of your data is legal and it is " -#~ "necessary\n" -#~ " to comply with our contractual or other legal " -#~ "obligations,\n" -#~ " according to Art. 6 par. 1 S. 1 lit b GDPR.
    \n" -#~ "
    \n" -#~ " c) There is a law or legal obligation to disclose " -#~ "your data\n" -#~ " or the disclosure is necessary for the performance of " -#~ "a\n" -#~ " task carried out in the public interest or in the " -#~ "exercise\n" -#~ " of an official authority according to Art. 6 par. 1 " -#~ "S. 1\n" -#~ " lit. c GDPR.
    \n" -#~ "
    \n" -#~ " d) The disclosure of your data is necessary for the\n" -#~ " purposes of the legitimate interests pursued by us or " -#~ "by a\n" -#~ " third party, except where such interests are " -#~ "overridden by\n" -#~ " the interests or your fundamental right according to " -#~ "Art.\n" -#~ " 6. par. 1 S. 1 lit. f GDPR.
    \n" -#~ "
    \n" -#~ "
    \n" -#~ " 6. Cookies, Google Analytics, Twitter
    \n" -#~ "
    \n" -#~ " We use Cookies. When you visit one of our websites,\n" -#~ " information is stored on your terminal device in the " -#~ "form\n" -#~ " of a \"cookie\". Cookies are small text files that " -#~ "are stored\n" -#~ " on your terminal device by your browser. You can opt-" -#~ "out of\n" -#~ " the storage of cookies in the settings of your\n" -#~ " browser-software, but however, in this case you might " -#~ "not\n" -#~ " be able to fully use all functions and services on " -#~ "our\n" -#~ " website.
    \n" -#~ "
    \n" -#~ " We use Google Analytics, a service provided by Google " -#~ "Inc.,\n" -#~ " 1600 Amphitheatre Parkway, Mountainview, CA 94043, " -#~ "USA\n" -#~ " (\"Google\"). Google uses Cookies. Google will use " -#~ "these\n" -#~ " cookies on our behalf to analyse how you use our " -#~ "websites\n" -#~ " and to generate reports about the activities on our\n" -#~ " websites. The information collected by those cookies " -#~ "will\n" -#~ " be transmitted to a Google server in the United " -#~ "States of\n" -#~ " America. We have Google's IP anonymization activated, " -#~ "so\n" -#~ " your IP address will be shortened before transmitting " -#~ "to\n" -#~ " Google. Only in exceptional cases the full IP address " -#~ "will\n" -#~ " be transmitted and then shortened afterwards.
    \n" -#~ "
    \n" -#~ " You may opt out from thecollection of these cookies " -#~ "by\n" -#~ " downloading and installing a browser plugin available " -#~ "at\n" -#~ " the following link:
    \n" -#~ "
    \n" -#~ " http://tools.google.com/dlpage/gaoptout
    \n" -#~ "
    \n" -#~ " You may find further information about the processing " -#~ "of\n" -#~ " your personal data at the following links:
    \n" -#~ "
    \n" -#~ " https://policies.google.com/technologies/ads
    \n" -#~ "
    \n" -#~ " https://www.privacyshield.gov/" -#~ "participant?id=a2zt000000001L5AAI&status=Active
    \n" -#~ "
    \n" -#~ " https://adssettings.google.com/authenticated
    \n" -#~ "
    \n" -#~ " The collected personal data will be deleted or " -#~ "anonymized\n" -#~ " after 14 months according to Art. 6 par. 1 lit. f\n" -#~ " GDPR.
    \n" -#~ "
    \n" -#~ " Our websites use several functions from the social " -#~ "network\n" -#~ " Twitter, a service provided by Twitter Inc., 1355 " -#~ "Market\n" -#~ " Street, Suite 900, San Francisco, CA 94103, USA. We " -#~ "may\n" -#~ " embed content of Twitter in our websites, such as " -#~ "photos,\n" -#~ " videos, texts or buttons. If you are registered with\n" -#~ " Twitter, they may combine your use of these " -#~ "functionalities\n" -#~ " on our websites with your Twitter account. You may " -#~ "find\n" -#~ " further information at https://twitter.com/de/privacy." -#~ "
    \n" -#~ "
    \n" -#~ "
    \n" -#~ " 7. Your rights concerning your personal\n" -#~ " data
    \n" -#~ "
    \n" -#~ " Under applicable GDPR you may have the right to:
    \n" -#~ "
    \n" -#~ " a) Obtain confirmation as to whether or not personal " -#~ "data\n" -#~ " concerning you are being processed, and where that is " -#~ "the\n" -#~ " case, access to the personal data, according to Art. " -#~ "15\n" -#~ " GDPR.
    \n" -#~ "
    \n" -#~ " b) Obtain the rectification of false or inaccurate " -#~ "data\n" -#~ " concerning you, according to Art. 15, Art. 16 GDPR." -#~ "
    \n" -#~ "
    \n" -#~ " c) Obtain the deletion of your personal data, " -#~ "according to\n" -#~ " Art. 17 GDPR.
    \n" -#~ "
    \n" -#~ " d) Obtain the restriction of processing your personal " -#~ "data,\n" -#~ " according to Art. 18 GDPR.
    \n" -#~ "
    \n" -#~ " e) Obtain a digital copy of your personal data " -#~ "processed by\n" -#~ " us, according to Art. 20 GDPR.
    \n" -#~ "
    \n" -#~ " f) Revoke a granted permission regarding your " -#~ "personal data\n" -#~ " at any time, according to Art. 7 par. 3 GDPR.
    \n" -#~ "
    \n" -#~ " g) Complain at a data protection authority, according " -#~ "to\n" -#~ " Art. 77 GDPR.
    \n" -#~ "
    \n" -#~ "
    \n" -#~ "
    \n" -#~ " 8. Hyperlinks / Disclaimer
    \n" -#~ "
    \n" -#~ " This privacy policy applies only to our websites and " -#~ "not to\n" -#~ " other websites or applications operated by third " -#~ "parties.\n" -#~ " We may provide links to other websites but we are " -#~ "not\n" -#~ " responsible for the privacy practices of such other\n" -#~ " websites.
    \n" -#~ "
    \n" -#~ "
    \n" -#~ " " -#~ msgstr "" -#~ "\n" -#~ "1. Verantwortung und Geltungsbereich

    Verantwortlich im Sinne der EU-Datenschutzgrundverordnung („DSGVO“) und " -#~ "sonstiger datenschutzrechtlicher Bestimmungen ist:

    ungleich " -#~ "glarus ag
    Bahnhofstrasse 1
    8783 Linthal (CH)
    Tel.: +41 55 505 " -#~ "6266
    E-Mail: info@ungleich.ch

    Diese Datenschutzerklärung " -#~ "gilt für das Internetangebot der ungleich glarus ag, welches unter den " -#~ "nachfolgenden Domains erreichbar ist:

    ungleich.ch
    datacenterlight.ch
    devuanhosting.com
    devuanhosting.ch
    digitalglarus.ch
    hack4lgarus.ch
    ipv6onlyhosting.com
    ipv6onlyhosting.ch
    ipv6onlyhosting.net
    django-hosting.ch
    rails-hosting.ch
    node-hosting.ch
    blog.ungleich.ch

    Der " -#~ "Datenschutzbeauftragte des Verantwortlichen ist:

    Sanghee Kim
    ungleich glarus ag
    Bahnhofstrasse 1
    8783 Linthal (CH)
    E-" -#~ "Mail: sanghee.kim@ungleich.ch


    2. Grundsätzliches

    Bei " -#~ "personenbezogenen Daten handelt es sich um alle Informationen, die sich " -#~ "auf eine identifizierte oder identifizierbare natürliche Person beziehen. " -#~ "Hierzu gehört zum Beispiel dein Name, deine Anschrift, dein Geburtsdatum, " -#~ "deine E-Mail-Adresse oder deine IP-Adresse. Wir speichern und verwenden " -#~ "deine personenbezogenen Daten ausschließlich für die Bearbeitung deiner " -#~ "Aufträge und für die Kontaktaufnahme mit dir.


    3. " -#~ "Verarbeitung deiner personenbezogenen Daten

    Bei jedem " -#~ "Aufruf einer unserer Internetseiten wird u. a. deine IP-Adresse " -#~ "gespeichert. Wenn du dich bei uns registrierst, werden deine Kontaktdaten " -#~ "gespeichert. Wenn du Aufträge an uns richtest oder Produkte bei uns " -#~ "bestellst, werden darüber hinaus auch deine Anschrift und deine
    Zahlungsdaten gespeichert.

    I. Registrierungsdaten

    Im Rahmen der Registrierung müssen wir einige " -#~ "personenbezogene Daten von dir erheben und verarbeiten. Beispielsweise " -#~ "benötigen wir deinen Namen, deine Anschrift, deine Telefonnummer, deine E-" -#~ "Mail-Adresse und deine Zahlungsdaten um deine Aufträge zu verarbeiten. " -#~ "
    Bei der Zahlung mittels Kreditkarte erfassen und speichern wir keine " -#~ "Zahlungsverkehrsinformationen wie Kreditkartennummern, das Ablaufdatum " -#~ "oder die Prüfziffer. Diese gibst du ausschließlich direkt dem " -#~ "entsprechenden Zahlungsdienstleister bekannt. Bei der Bezahlung mit " -#~ "Kreditkarte erfolgt die Abwicklung der Bezahlung durch einen externen " -#~ "Dienstleister.
    Für die Kreditkartenzahlung werden deine Angaben " -#~ "direkt vom Diensteanbieter Stripe Payments Europe Ltd. („Stripe“) " -#~ "verarbeitet.

    II. Serverlogdateien

    Bei " -#~ "jedem Aufruf einer unserer Internetseiten speichern wir automatisch " -#~ "bestimmte Daten. Dazu gehören u. a. deine IP-Adresse, Typ und Version des " -#~ "verwendeten Browsers, Uhrzeit, Datum und die Webseite von der du auf " -#~ "unsere Seite gelangst (sog. „Referer“). Deine IP-Adresse wird bei uns nur " -#~ "anonymisiert gespeichert, sodass ein Personenbezug nicht mehr herstellbar " -#~ "ist. Die Erfassung der Daten zur Bereitstellung unserer Internetseiten " -#~ "und die Speicherung der Daten in den Server Logfiles ist für den Betrieb " -#~ "der Internetseite zwingend erforderlich und dient damit der Wahrung eines " -#~ "berechtigten Interesses unseres Unternehmens. Die Rechtsgrundlage für " -#~ "diese Verarbeitung ist Art. 6. Abs. 1 lit. f DSGVO.


    4. Speicherdauer

    Sofern die verarbeiteten " -#~ "personenbezogenen Daten für die Durchführung eines ggf.
    geschlossenen " -#~ "Vertrages nicht mehr erforderlich sind, werden sie umgehend gelöscht. " -#~ "Jedoch kann es auch nach Abschluss des Vertrags erforderlich sein, " -#~ "personenbezogene Daten von dir zu speichern, um unseren vertraglichen " -#~ "oder gesetzlichen Verpflichtungen nachzukommen.


    5. " -#~ "Weitergabe von personenbezogenen Daten

    Wir geben deine " -#~ "personenbezogenen Daten grundsätzlich nicht an Dritte weiter, es sei denn:" -#~ "

    a) Du erteilst uns hierzu deine ausdrückliche Einwilligung nach " -#~ "Art. 6 Abs. 1 S. 1 lit. a DSGVO.

    b) Die Weitergabe der Daten ist " -#~ "gesetzlich zulässig und nach Art. 6. Abs. 1 S. 1 lit b DSGVO zur " -#~ "Erfüllung unserer vertraglichen Pflichten mit Ihnen erforderlich.

    c) Für die Weitergabe der Daten besteht nach Art. 6. Abs. 1 S. 1 lit. c " -#~ "DSGVO eine gesetzliche Verpflichtung.

    d) Die Weitergabe der " -#~ "Daten ist nach Art. 6. Abs. 1 S. 1 lit. f DSGVO zur Wahrung unserer " -#~ "berechtigten Interessen, sowie zur Geltendmachung, Ausübung oder " -#~ "


    6. Cookies, Google Analytics und Twitter

    Wir " -#~ "setzen auf unseren Internetseiten sog. „Cookies“ ein. Cookies sind kleine " -#~ "Textdateien, die im Rahmen deines Besuchs einer unserer Internetseiten " -#~ "von uns an den Browser deines Endgeräts gesendet und dort gespeichert " -#~ "werden. Einige Funktionen unseres Angebots können ohne den Einsatz " -#~ "bestimmter Cookies grundsätzlich aus technischen Gründen nicht angeboten " -#~ "werden. Du kannst deinen Browser allerdings so konfigurieren, dass er nur " -#~ "noch bestimmte oder auch gar keine Cookies mehr akzeptiert. Es kann " -#~ "jedoch passieren, dass du dadurch möglicherweise nicht mehr alle " -#~ "Funktionen unserer Internetseiten nutzen kannst.
    Andere Cookies " -#~ "ermöglichen es uns hingegen verschiedene Analysen deines Verhaltens oder " -#~ "etwa eine Verbindung zu deinen sozialen Netzwerken aufzubauen. Mithilfe " -#~ "dieser Cookies können wir beispielsweise unser Internetangebot für dich " -#~ "nutzerfreundlicher und effektiver gestalten. Daraus folgt, dass wir auf " -#~ "Grundlage unserer berechtigten Unternehmensinteressen (Optimierung und " -#~ "wirtschaftlicher Betrieb unseres Internetangebots) den Werbeanalysedienst " -#~ "„Google Analytics“ der Google Inc., 1600 Amphitheatre Parkway, " -#~ "Mountainview, CA 94043, Vereinigte Staaten von Amerika („Google“) " -#~ "einsetzen. Google verwendet Cookies. Die durch das Cookie erzeugten " -#~ "Informationen werden an einen Server von Google in den Vereinigten " -#~ "Staaten von Amerika übertragen und dort erhoben und gespeichert. Google " -#~ "nutzt dies Informationen in unserem Auftrag dafür, die Nutzung unseres " -#~ "Internetangebots auszuwerten und Berichte über die Aktivitäten auf " -#~ "unserem Onlineangebot uns gegenüber zu erbringen.
    Dabei werden aus " -#~ "den verarbeiteten Daten anonymisierte bzw. pseudonyme Nutzungsprofile " -#~ "einiger Nutzer erstellt. Diese Nutzungsprofile sind nicht auf eine IP-" -#~ "Adresse oder einen spezifischen Nutzer zurückzuführen. Darüber hinaus " -#~ "setzen wir Google Analytics nur mit IP-Anonymisierung ein. Das heißt, " -#~ "dass die IP-Adresse von Google gekürzt wird. Nur in wenigen " -#~ "Ausnahmefällen wird die vollständige IP-Adresse an Google übertragen und " -#~ "dort gekürzt. Du kannst der Verarbeitung von deinen erfassten Daten durch " -#~ "die Installation dieses Browserplugins widersprechen:
    http://tools.google.com/dlpage/" -#~ "gaoptout?hl=de

    Weitere Informationen zur Nutzung der Daten " -#~ "durch Google und weiterführende Widerspruchsmöglichkeiten erhältst du in " -#~ "der Datenschutzerklärung von Google:

    https://policies.google.com/technologies/" -#~ "ads

    und auf den folgenden Webseiten:

    https://www.privacyshield.gov/" -#~ "participant?id=a2zt000000001L5AAI&status=Active

    https://adssettings." -#~ "google.com/authenticated

    Die personenbezogenen Daten werden " -#~ "nach 14 Monaten gelöscht oder anonymisiert.
    Rechtliche Grundlage " -#~ "hierfür ist Art. 6 Abs. 1 lit. f DSGVO.

    Innerhalb unseres " -#~ "Onlineangebots können Funktionen und Inhalte des Dienstes Twitter " -#~ "eingebunden werden. Twitter ist ein Dienst der Twitter Inc., 1355 Market " -#~ "Street, Suite 900, San Francisco, CA 94103, Vereinigte Staaten von " -#~ "Amerika. Hierzu können beispielsweise Inhalte wie Bilder, Videos oder " -#~ "Texte und Schaltflächen gehören. Sofern du Mitglied der Plattform Twitter " -#~ "bist, kann Twitter den Aufruf der Funktionalitäten deinem dortigen Profil " -#~ "zuordnen.
    Weitere Informationen zum Datenschutz bei Twitter, findest " -#~ "du in der Datenschutzerklärung von
    Twitter unter https://twitter.com/de/privacy.



    7. Rechte des Betroffenen

    Aus der DSGVO " -#~ "ergeben sich für dich als Betroffener die folgenden Rechte:

    a) " -#~ "Du kannst gem. Art. 15 DSGVO Auskunft über deine von uns verarbeiteten " -#~ "personenbezogenen Daten verlangen. Du kannst insbesondere Auskunft über " -#~ "den Zweck der Verarbeitung, die Kategorien der erhobenen " -#~ "personenbezogenen Daten, die Kategorien von Dritten, gegenüber denen " -#~ "deine Daten offengelegt werden und die voraussichtliche Speicherdauer " -#~ "verlangen.

    b) Du hast gem. Art. 15 u. Art. 16 DSGVO ein Recht " -#~ "auf Berichtigung, Vervollständigung, Löschung und die Bestimmung von " -#~ "Einschränkungen hinsichtlich der Verarbeitung deiner personenbezogenen " -#~ "Daten.

    c) Gemäß Art. 17 DSGVO kannst du von uns die Löschung " -#~ "deiner bei uns gespeicherten personenbezogenen Daten verlangen, soweit " -#~ "die Verarbeitung nicht zur Erfüllung unserer rechtlichen Verpflichtungen, " -#~ "aus Gründen des öffentlichen Interesses oder zur Geltendmachung, Ausübung " -#~ "oder Verteidigung von Rechtsansprüchen erforderlich ist.

    d) " -#~ "Gemäß Art. 18 DSGVO kannst du von uns die Einschränkung der Verarbeitung " -#~ "deiner personenbezogenen Daten verlangen, soweit die Richtigkeit der " -#~ "Daten von dir bestritten wird, die Verarbeitung grundsätzlich " -#~ "unrechtmäßig ist oder die Löschung deiner
    personenbezogenen Daten " -#~ "durch uns ablehnen, weil du diese zur Geltendmachung, Ausübung oder " -#~ "Verteidigung von Rechtsansprüchen benötigst.

    e) Gemäß Art. 20 " -#~ "DSGVO kannst du von uns verlangen, dass deine personenbezogenen Daten, " -#~ "die du uns zur Verfügung gestellt hast, in einem strukturierten, " -#~ "maschinenlesbaren und gängigen Format zu erhalten oder du kannst die " -#~ "Übermittlung an eine andere Stelle verlangen.

    f) Gemäß Art. 7 " -#~ "Abs. 3 DSGVO kannst du deine erteilte Einwilligung gegenüber uns " -#~ "jederzeit widerrufen. Dies hat zur Folge, dass wir die auf dieser " -#~ "Einwilligung beruhende Datenverarbeitung für die Zukunft nicht mehr " -#~ "fortführen dürfen.

    g) Gemäß Art. 77 DSGVO steht dir das Recht " -#~ "zu, dich bei einer Aufsichtsbehörde zu beschweren.


    8. Hyperlinks und Haftungsausschluss

    Auf " -#~ "unseren Internetseiten befinden sich sog. „Hyperlinks“, also Verbindungen " -#~ "bzw.
    Weiterleitungen zu den Internetseiten Dritter. Bei Aktivierung " -#~ "dieser Hyperlinks durch Anklicken, wirst du von unserer Internetseite " -#~ "direkt auf die Internetseite eines Dritten weitergeleitet. Wir können " -#~ "keine Verantwortung für den vertraulichen Umgang deiner Daten auf diesen " -#~ "Webseiten Dritter übernehmen, da wir keinen Einfluss darauf haben, dass " -#~ "diese Unternehmen die Datenschutzbestimmungen der DSGVO einhalten. Wir " -#~ "weisen dich außerdem darauf hin, dass es aufgrund der Struktur des " -#~ "Internets möglich ist, dass die Regelungen des Datenschutzes bzw. der " -#~ "DSGVO von anderen, dritten Personen oder Institutionen die nicht " -#~ "innerhalb unseres Verantwortungsbereichs liegen nicht beachtet werden, " -#~ "oder dass sich spontan, kurzfristig schwerwiegende Sicherheitslücken in " -#~ "verbreiteten Standards und technischen Protokollen auftun, die die " -#~ "Datenintegrität gefährden könnten.

    \n" -#~ " " +msgid "Data Center Light" +msgstr "" + +msgid "Glasfaser" +msgstr "" + +msgid "ungleich" +msgstr "" + +msgid "English" +msgstr "" + +msgid "Deutsch" +msgstr "" + +msgid "Datacenterlight Navbar" +msgstr "" + +msgid "Datacenterlight Footer" +msgstr "" + +msgid "Datacenterlight Calculator" +msgstr "" + +msgid "Amount" +msgstr "Betrag" + +msgid "My Donations" +msgstr "" + +msgid "Set your new password" +msgstr "" + +msgid "Reset" +msgstr "" + +msgid "Already have an account ?" +msgstr "" + +msgid "Log in" +msgstr "" + +msgid "Invoice" +msgstr "" + +msgid "Donation #" +msgstr "" + +msgid "Billing Address:" +msgstr "" + +msgid "Date:" +msgstr "" + +msgid "Payment Method:" +msgstr "" + +msgid "Donation summary" +msgstr "" + +msgid "Donation" +msgstr "" + +msgid "Total" +msgstr "" + +msgid "Finish Configuration" +msgstr "" + +msgid "" +"Thanks for you donation, you can cancel your monthly donation at any time " +"going to profile > subscription " +msgstr "" + +msgid "View Donations" +msgstr "" + +msgid "Cancel Donation" +msgstr "" + +msgid "Donate" +msgstr "" + +msgid "Donations Made" +msgstr "" + +msgid "Date" +msgstr "" + +msgid "View Detail" +msgstr "" + +msgid "Reanude Donation" +msgstr "" + +#, python-format +msgid "" +"You're receiving this email because you requested a password reset for your " +"user account at %(site_name)s." +msgstr "" + +msgid "Please go to the following page and choose a new password:" +msgstr "" + +msgid "Thanks for using our site!" +msgstr "" + +#, python-format +msgid "The %(site_name)s team" +msgstr "" + +msgid "Log in " +msgstr "" + +msgid "Login" +msgstr "" + +msgid "Don't have an account yet ? " +msgstr "" + +msgid "Sign up" +msgstr "" + +msgid "Forgot your password ? " +msgstr "" + +msgid "Reset your password" +msgstr "" + +msgid "DG.Detail" +msgstr "" + +msgid "" +"This site uses cookies for analytics. By continuing to\n" +" browse this site, you agree to use this." +msgstr "" +"Diese Website verwendet Cookies für Analysezwecke. Mit dem weiterem Besuch " +"dieser Website erklärst Du Dich damit einverstanden, diese zu nutzen." + +msgid "Learn more" +msgstr "Lerne mehr" + +msgid "OK" +msgstr "" + +msgid "Privacy policy" +msgstr "Datenschutz-Bestimmungen" + +msgid "" +"\n" +" 1. Responsibility and scope
    \n" +"
    \n" +" Responsible in regards to EU General Data Protection\n" +" Regulation (\"GDPR\") and other applicable data " +"protection\n" +" laws:
    \n" +"
    \n" +" ungleich glarus ag
    \n" +" Bahnhofstrasse 1
    \n" +" 8783 Linthal (CH)
    \n" +" Tel.: +41 55 505 6266
    \n" +" E-Mail: info@ungleich.ch
    \n" +"
    \n" +" This privacy statement applies to the web content of\n" +" ungleich glarus ag, available at the
    \n" +" following domains:
    \n" +"
    \n" +" ungleich.ch
    \n" +" datacenterlight.ch
    \n" +" devuanhosting.com
    \n" +" devuanhosting.ch
    \n" +" digitalglarus.ch
    \n" +" hack4lgarus.ch
    \n" +" ipv6onlyhosting.com
    \n" +" django-hosting.ch
    \n" +" rails-hosting.ch
    \n" +" node-hosting.ch
    \n" +" blog.ungleich.ch
    \n" +"
    \n" +" The Data Privacy Officer:
    \n" +"
    \n" +" Sanghee Kim
    \n" +" ungleich glarus ag
    \n" +" Bahnhofstrasse 1
    \n" +" 8783 Linthal (CH)
    \n" +" E-Mail: sanghee.kim@ungleich.ch
    \n" +"
    \n" +"
    \n" +" 2. General
    \n" +"
    \n" +" User data is personal data which is necessary to " +"establish\n" +" or change the contractual relations\n" +" between ungleich glarus ag (\"us\") and you. This " +"includes\n" +" among other things for example the\n" +" name, the address, the date of birth, the e-mail address " +"or\n" +" the IP address. We save and use\n" +" your personal data only for the processing of your " +"orders\n" +" or for getting in contact with you.
    \n" +"
    \n" +"
    \n" +" 3. Processing of personal data
    \n" +"
    \n" +" When you call one of our websites we collect your IP\n" +" address. When you register an account with us your " +"contact\n" +" data will be stored. When you order products on our\n" +" platform we also collect among others your address and " +"your\n" +" payment details.
    \n" +"
    \n" +" I. Registration data
    \n" +"
    \n" +" In the process of registration we need to collect some " +"of\n" +" your personal data. For example we collect your name, " +"your\n" +" address, your telephone number, your e-mail address and\n" +" your payment details to process orders. We do not " +"collect\n" +" your payment details such as credit card number, " +"expiration\n" +" date or the cvv code when you pay with credit card. You\n" +" expose those data directly to the respective payment\n" +" processor. Payments with credit card are processed by\n" +" Stripe Payments Europe Ltd. (\"Stripe\").
    \n" +"
    \n" +"
    \n" +" II. Server log files
    \n" +"
    \n" +" When you call one of our websites we automatically save\n" +" traffic data. Normally the IP address, the type and " +"version\n" +" of your browser, the time and the website that lead to " +"our\n" +" website (\"Referer\") will be saved. Your IP address is\n" +" collected anonymously so that we cannot match it with " +"your\n" +" person. The collection of those data is necessary to\n" +" provide our websites and services, according to Art. 6 " +"par.\n" +" 1 lit. f GDPR.
    \n" +"
    \n" +"
    \n" +" 4. Storage durations
    \n" +"
    \n" +" Your data will be deleted immediately, as soon as it " +"isn't\n" +" relevant anymore for any contract between you and us. " +"In\n" +" some cases it could be necessary to store your data\n" +" further, to comply with our contractual or other legal\n" +" obligatons.
    \n" +"
    \n" +"
    \n" +" 5. Disclosure of your personal data
    \n" +"
    \n" +" We don't disclose your personal data to third parties,\n" +" except:
    \n" +"
    \n" +" a) You grant us your explicit permission according to " +"Art.\n" +" 6 par. 1 S. 1 lit. a GDPR.
    \n" +"
    \n" +" b) The disclosure of your data is legal and it is " +"necessary\n" +" to comply with our contractual or other legal " +"obligations,\n" +" according to Art. 6 par. 1 S. 1 lit b GDPR.
    \n" +"
    \n" +" c) There is a law or legal obligation to disclose your " +"data\n" +" or the disclosure is necessary for the performance of a\n" +" task carried out in the public interest or in the " +"exercise\n" +" of an official authority according to Art. 6 par. 1 S. " +"1\n" +" lit. c GDPR.
    \n" +"
    \n" +" d) The disclosure of your data is necessary for the\n" +" purposes of the legitimate interests pursued by us or by " +"a\n" +" third party, except where such interests are overridden " +"by\n" +" the interests or your fundamental right according to " +"Art.\n" +" 6. par. 1 S. 1 lit. f GDPR.
    \n" +"
    \n" +"
    \n" +" 6. Cookies, Google Analytics, Twitter
    \n" +"
    \n" +" We use Cookies. When you visit one of our websites,\n" +" information is stored on your terminal device in the " +"form\n" +" of a \"cookie\". Cookies are small text files that are " +"stored\n" +" on your terminal device by your browser. You can opt-out " +"of\n" +" the storage of cookies in the settings of your\n" +" browser-software, but however, in this case you might " +"not\n" +" be able to fully use all functions and services on our\n" +" website.
    \n" +"
    \n" +" We use Google Analytics, a service provided by Google " +"Inc.,\n" +" 1600 Amphitheatre Parkway, Mountainview, CA 94043, USA\n" +" (\"Google\"). Google uses Cookies. Google will use " +"these\n" +" cookies on our behalf to analyse how you use our " +"websites\n" +" and to generate reports about the activities on our\n" +" websites. The information collected by those cookies " +"will\n" +" be transmitted to a Google server in the United States " +"of\n" +" America. We have Google's IP anonymization activated, " +"so\n" +" your IP address will be shortened before transmitting " +"to\n" +" Google. Only in exceptional cases the full IP address " +"will\n" +" be transmitted and then shortened afterwards.
    \n" +"
    \n" +" You may opt out from thecollection of these cookies by\n" +" downloading and installing a browser plugin available " +"at\n" +" the following link:
    \n" +"
    \n" +" http://tools.google.com/dlpage/gaoptout
    \n" +"
    \n" +" You may find further information about the processing " +"of\n" +" your personal data at the following links:
    \n" +"
    \n" +" https://policies.google.com/technologies/ads
    \n" +"
    \n" +" https://www.privacyshield.gov/" +"participant?id=a2zt000000001L5AAI&status=Active
    \n" +"
    \n" +" https://adssettings.google.com/authenticated
    \n" +"
    \n" +" The collected personal data will be deleted or " +"anonymized\n" +" after 14 months according to Art. 6 par. 1 lit. f\n" +" GDPR.
    \n" +"
    \n" +" Our websites use several functions from the social " +"network\n" +" Twitter, a service provided by Twitter Inc., 1355 " +"Market\n" +" Street, Suite 900, San Francisco, CA 94103, USA. We may\n" +" embed content of Twitter in our websites, such as " +"photos,\n" +" videos, texts or buttons. If you are registered with\n" +" Twitter, they may combine your use of these " +"functionalities\n" +" on our websites with your Twitter account. You may find\n" +" further information at https://twitter.com/de/privacy." +"
    \n" +"
    \n" +"
    \n" +" 7. Your rights concerning your personal\n" +" data
    \n" +"
    \n" +" Under applicable GDPR you may have the right to:
    \n" +"
    \n" +" a) Obtain confirmation as to whether or not personal " +"data\n" +" concerning you are being processed, and where that is " +"the\n" +" case, access to the personal data, according to Art. 15\n" +" GDPR.
    \n" +"
    \n" +" b) Obtain the rectification of false or inaccurate data\n" +" concerning you, according to Art. 15, Art. 16 GDPR.
    \n" +"
    \n" +" c) Obtain the deletion of your personal data, according " +"to\n" +" Art. 17 GDPR.
    \n" +"
    \n" +" d) Obtain the restriction of processing your personal " +"data,\n" +" according to Art. 18 GDPR.
    \n" +"
    \n" +" e) Obtain a digital copy of your personal data processed " +"by\n" +" us, according to Art. 20 GDPR.
    \n" +"
    \n" +" f) Revoke a granted permission regarding your personal " +"data\n" +" at any time, according to Art. 7 par. 3 GDPR.
    \n" +"
    \n" +" g) Complain at a data protection authority, according " +"to\n" +" Art. 77 GDPR.
    \n" +"
    \n" +"
    \n" +"
    \n" +" 8. Hyperlinks / Disclaimer
    \n" +"
    \n" +" This privacy policy applies only to our websites and not " +"to\n" +" other websites or applications operated by third " +"parties.\n" +" We may provide links to other websites but we are not\n" +" responsible for the privacy practices of such other\n" +" websites.
    \n" +"
    \n" +"
    \n" +" " +msgstr "" +"\n" +"1. Verantwortung und Geltungsbereich

    Verantwortlich im Sinne der EU-Datenschutzgrundverordnung („DSGVO“) und " +"sonstiger datenschutzrechtlicher Bestimmungen ist:

    ungleich glarus " +"ag
    Bahnhofstrasse 1
    8783 Linthal (CH)
    Tel.: +41 55 505 6266
    E-Mail: info@ungleich.ch

    Diese Datenschutzerklärung gilt für das " +"Internetangebot der ungleich glarus ag, welches unter den nachfolgenden " +"Domains erreichbar ist:

    ungleich.ch
    datacenterlight.ch
    devuanhosting.com
    devuanhosting.ch
    digitalglarus.ch
    hack4lgarus." +"ch
    ipv6onlyhosting.com
    django-hosting.ch
    rails-hosting.ch" +"
    node-hosting.ch
    blog." +"ungleich.ch

    Der Datenschutzbeauftragte des Verantwortlichen ist:

    Sanghee Kim
    ungleich glarus ag
    Bahnhofstrasse 1
    8783 " +"Linthal (CH)
    E-Mail: sanghee." +"kim@ungleich.ch


    2. Grundsätzliches

    Bei personenbezogenen Daten handelt es sich um alle Informationen, die " +"sich auf eine identifizierte oder identifizierbare natürliche Person " +"beziehen. Hierzu gehört zum Beispiel dein Name, deine Anschrift, dein " +"Geburtsdatum, deine E-Mail-Adresse oder deine IP-Adresse. Wir speichern und " +"verwenden deine personenbezogenen Daten ausschließlich für die Bearbeitung " +"deiner Aufträge und für die Kontaktaufnahme mit dir.


    3. Verarbeitung deiner personenbezogenen Daten

    Bei jedem Aufruf einer unserer Internetseiten wird u. a. deine IP-Adresse " +"gespeichert. Wenn du dich bei uns registrierst, werden deine Kontaktdaten " +"gespeichert. Wenn du Aufträge an uns richtest oder Produkte bei uns " +"bestellst, werden darüber hinaus auch deine Anschrift und deine
    Zahlungsdaten gespeichert.

    I. Registrierungsdaten

    Im Rahmen der Registrierung müssen wir einige " +"personenbezogene Daten von dir erheben und verarbeiten. Beispielsweise " +"benötigen wir deinen Namen, deine Anschrift, deine Telefonnummer, deine E-" +"Mail-Adresse und deine Zahlungsdaten um deine Aufträge zu verarbeiten.
    Bei der Zahlung mittels Kreditkarte erfassen und speichern wir keine " +"Zahlungsverkehrsinformationen wie Kreditkartennummern, das Ablaufdatum oder " +"die Prüfziffer. Diese gibst du ausschließlich direkt dem entsprechenden " +"Zahlungsdienstleister bekannt. Bei der Bezahlung mit Kreditkarte erfolgt die " +"Abwicklung der Bezahlung durch einen externen Dienstleister.
    Für die " +"Kreditkartenzahlung werden deine Angaben direkt vom Diensteanbieter Stripe " +"Payments Europe Ltd. („Stripe“) verarbeitet.

    II. " +"Serverlogdateien

    Bei jedem Aufruf einer unserer " +"Internetseiten speichern wir automatisch bestimmte Daten. Dazu gehören u. a. " +"deine IP-Adresse, Typ und Version des verwendeten Browsers, Uhrzeit, Datum " +"und die Webseite von der du auf unsere Seite gelangst (sog. „Referer“). " +"Deine IP-Adresse wird bei uns nur anonymisiert gespeichert, sodass ein " +"Personenbezug nicht mehr herstellbar ist. Die Erfassung der Daten zur " +"Bereitstellung unserer Internetseiten und die Speicherung der Daten in den " +"Server Logfiles ist für den Betrieb der Internetseite zwingend erforderlich " +"und dient damit der Wahrung eines berechtigten Interesses unseres " +"Unternehmens. Die Rechtsgrundlage für diese Verarbeitung ist Art. 6. Abs. 1 " +"lit. f DSGVO.


    4. Speicherdauer

    Sofern die verarbeiteten personenbezogenen Daten für die Durchführung eines " +"ggf.
    geschlossenen Vertrages nicht mehr erforderlich sind, werden sie " +"umgehend gelöscht. Jedoch kann es auch nach Abschluss des Vertrags " +"erforderlich sein, personenbezogene Daten von dir zu speichern, um unseren " +"vertraglichen oder gesetzlichen Verpflichtungen nachzukommen.


    5. Weitergabe von personenbezogenen Daten

    Wir " +"geben deine personenbezogenen Daten grundsätzlich nicht an Dritte weiter, es " +"sei denn:

    a) Du erteilst uns hierzu deine ausdrückliche " +"Einwilligung nach Art. 6 Abs. 1 S. 1 lit. a DSGVO.

    b) Die " +"Weitergabe der Daten ist gesetzlich zulässig und nach Art. 6. Abs. 1 S. 1 " +"lit b DSGVO zur Erfüllung unserer vertraglichen Pflichten mit Ihnen " +"erforderlich.

    c) Für die Weitergabe der Daten besteht nach Art. 6. " +"Abs. 1 S. 1 lit. c DSGVO eine gesetzliche Verpflichtung.

    d) Die " +"Weitergabe der Daten ist nach Art. 6. Abs. 1 S. 1 lit. f DSGVO zur Wahrung " +"unserer berechtigten Interessen, sowie zur Geltendmachung, Ausübung oder " +"


    6. Cookies, Google Analytics und Twitter

    Wir " +"setzen auf unseren Internetseiten sog. „Cookies“ ein. Cookies sind kleine " +"Textdateien, die im Rahmen deines Besuchs einer unserer Internetseiten von " +"uns an den Browser deines Endgeräts gesendet und dort gespeichert werden. " +"Einige Funktionen unseres Angebots können ohne den Einsatz bestimmter " +"Cookies grundsätzlich aus technischen Gründen nicht angeboten werden. Du " +"kannst deinen Browser allerdings so konfigurieren, dass er nur noch " +"bestimmte oder auch gar keine Cookies mehr akzeptiert. Es kann jedoch " +"passieren, dass du dadurch möglicherweise nicht mehr alle Funktionen unserer " +"Internetseiten nutzen kannst.
    Andere Cookies ermöglichen es uns hingegen " +"verschiedene Analysen deines Verhaltens oder etwa eine Verbindung zu deinen " +"sozialen Netzwerken aufzubauen. Mithilfe dieser Cookies können wir " +"beispielsweise unser Internetangebot für dich nutzerfreundlicher und " +"effektiver gestalten. Daraus folgt, dass wir auf Grundlage unserer " +"berechtigten Unternehmensinteressen (Optimierung und wirtschaftlicher " +"Betrieb unseres Internetangebots) den Werbeanalysedienst „Google Analytics“ " +"der Google Inc., 1600 Amphitheatre Parkway, Mountainview, CA 94043, " +"Vereinigte Staaten von Amerika („Google“) einsetzen. Google verwendet " +"Cookies. Die durch das Cookie erzeugten Informationen werden an einen Server " +"von Google in den Vereinigten Staaten von Amerika übertragen und dort " +"erhoben und gespeichert. Google nutzt dies Informationen in unserem Auftrag " +"dafür, die Nutzung unseres Internetangebots auszuwerten und Berichte über " +"die Aktivitäten auf unserem Onlineangebot uns gegenüber zu erbringen.
    Dabei werden aus den verarbeiteten Daten anonymisierte bzw. pseudonyme " +"Nutzungsprofile einiger Nutzer erstellt. Diese Nutzungsprofile sind nicht " +"auf eine IP-Adresse oder einen spezifischen Nutzer zurückzuführen. Darüber " +"hinaus setzen wir Google Analytics nur mit IP-Anonymisierung ein. Das heißt, " +"dass die IP-Adresse von Google gekürzt wird. Nur in wenigen Ausnahmefällen " +"wird die vollständige IP-Adresse an Google übertragen und dort gekürzt. Du " +"kannst der Verarbeitung von deinen erfassten Daten durch die Installation " +"dieses Browserplugins widersprechen: http://tools.google.com/dlpage/gaoptout?hl=de

    Weitere Informationen zur Nutzung der Daten durch Google und " +"weiterführende Widerspruchsmöglichkeiten erhältst du in der " +"Datenschutzerklärung von Google:

    https://policies.google.com/technologies/ads

    und auf den folgenden Webseiten:

    https://" +"www.privacyshield.gov/participant?id=a2zt000000001L5AAI&status=Active

    https://" +"adssettings.google.com/authenticated

    Die personenbezogenen " +"Daten werden nach 14 Monaten gelöscht oder anonymisiert.
    Rechtliche " +"Grundlage hierfür ist Art. 6 Abs. 1 lit. f DSGVO.

    Innerhalb unseres " +"Onlineangebots können Funktionen und Inhalte des Dienstes Twitter " +"eingebunden werden. Twitter ist ein Dienst der Twitter Inc., 1355 Market " +"Street, Suite 900, San Francisco, CA 94103, Vereinigte Staaten von Amerika. " +"Hierzu können beispielsweise Inhalte wie Bilder, Videos oder Texte und " +"Schaltflächen gehören. Sofern du Mitglied der Plattform Twitter bist, kann " +"Twitter den Aufruf der Funktionalitäten deinem dortigen Profil zuordnen.
    Weitere Informationen zum Datenschutz bei Twitter, findest du in der " +"Datenschutzerklärung von
    Twitter unter https://twitter.com/de/privacy.



    7. " +"Rechte des Betroffenen

    Aus der DSGVO ergeben sich für dich " +"als Betroffener die folgenden Rechte:

    a) Du kannst gem. Art. 15 " +"DSGVO Auskunft über deine von uns verarbeiteten personenbezogenen Daten " +"verlangen. Du kannst insbesondere Auskunft über den Zweck der Verarbeitung, " +"die Kategorien der erhobenen personenbezogenen Daten, die Kategorien von " +"Dritten, gegenüber denen deine Daten offengelegt werden und die " +"voraussichtliche Speicherdauer verlangen.

    b) Du hast gem. Art. 15 " +"u. Art. 16 DSGVO ein Recht auf Berichtigung, Vervollständigung, Löschung und " +"die Bestimmung von Einschränkungen hinsichtlich der Verarbeitung deiner " +"personenbezogenen Daten.

    c) Gemäß Art. 17 DSGVO kannst du von uns " +"die Löschung deiner bei uns gespeicherten personenbezogenen Daten verlangen, " +"soweit die Verarbeitung nicht zur Erfüllung unserer rechtlichen " +"Verpflichtungen, aus Gründen des öffentlichen Interesses oder zur " +"Geltendmachung, Ausübung oder Verteidigung von Rechtsansprüchen erforderlich " +"ist.

    d) Gemäß Art. 18 DSGVO kannst du von uns die Einschränkung der " +"Verarbeitung deiner personenbezogenen Daten verlangen, soweit die " +"Richtigkeit der Daten von dir bestritten wird, die Verarbeitung " +"grundsätzlich unrechtmäßig ist oder die Löschung deiner
    personenbezogenen Daten durch uns ablehnen, weil du diese zur " +"Geltendmachung, Ausübung oder Verteidigung von Rechtsansprüchen benötigst." +"

    e) Gemäß Art. 20 DSGVO kannst du von uns verlangen, dass deine " +"personenbezogenen Daten, die du uns zur Verfügung gestellt hast, in einem " +"strukturierten, maschinenlesbaren und gängigen Format zu erhalten oder du " +"kannst die Übermittlung an eine andere Stelle verlangen.

    f) Gemäß " +"Art. 7 Abs. 3 DSGVO kannst du deine erteilte Einwilligung gegenüber uns " +"jederzeit widerrufen. Dies hat zur Folge, dass wir die auf dieser " +"Einwilligung beruhende Datenverarbeitung für die Zukunft nicht mehr " +"fortführen dürfen.

    g) Gemäß Art. 77 DSGVO steht dir das Recht zu, " +"dich bei einer Aufsichtsbehörde zu beschweren.


    8. " +"Hyperlinks und Haftungsausschluss

    Auf unseren " +"Internetseiten befinden sich sog. „Hyperlinks“, also Verbindungen bzw.
    Weiterleitungen zu den Internetseiten Dritter. Bei Aktivierung dieser " +"Hyperlinks durch Anklicken, wirst du von unserer Internetseite direkt auf " +"die Internetseite eines Dritten weitergeleitet. Wir können keine " +"Verantwortung für den vertraulichen Umgang deiner Daten auf diesen Webseiten " +"Dritter übernehmen, da wir keinen Einfluss darauf haben, dass diese " +"Unternehmen die Datenschutzbestimmungen der DSGVO einhalten. Wir weisen dich " +"außerdem darauf hin, dass es aufgrund der Struktur des Internets möglich " +"ist, dass die Regelungen des Datenschutzes bzw. der DSGVO von anderen, " +"dritten Personen oder Institutionen die nicht innerhalb unseres " +"Verantwortungsbereichs liegen nicht beachtet werden, oder dass sich spontan, " +"kurzfristig schwerwiegende Sicherheitslücken in verbreiteten Standards und " +"technischen Protokollen auftun, die die Datenintegrität gefährden könnten." +"

    \n" +" " + +msgid "Page Header" +msgstr "" + +#, python-format +msgid "No %(verbose_name)s found matching the query" +msgstr "" #~ msgid "Accept" #~ msgstr "Annehmen" @@ -689,3 +836,4 @@ msgstr "" #~ msgid "index/?$" #~ msgstr "index/?$" + From 95c07fbed13d8d650c00816d8f0faa1f17228c8b Mon Sep 17 00:00:00 2001 From: "app@dynamicweb-production" Date: Wed, 23 Mar 2022 10:46:12 +0100 Subject: [PATCH 199/257] Change to dcl-orders for new vm and removing ipv6onlyhosting.{net,ch} references --- datacenterlight/tasks.py | 4 ++-- digitalglarus/locale/de/LC_MESSAGES/django.po | 7 +++---- dynamicweb/settings/base.py | 2 -- dynamicweb/settings/prod.py | 2 -- templates/gdpr/gdpr_banner.html | 2 -- 5 files changed, 5 insertions(+), 12 deletions(-) diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index 9c98b729..899b506f 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -195,7 +195,7 @@ def handle_metadata_and_emails(order_id, vm_id, manager, user, specs, email_data = { 'subject': settings.DCL_TEXT + " Order from %s" % context['email'], 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, - 'to': ['info@ungleich.ch'], + 'to': ['dcl-orders@ungleich.ch'], 'body': "\n".join( ["%s=%s" % (k, v) for (k, v) in context.items()]), 'reply_to': [context['email']], @@ -233,4 +233,4 @@ def handle_metadata_and_emails(order_id, vm_id, manager, user, specs, logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id)) if vm_id > 0: - get_or_create_vm_detail(custom_user, manager, vm_id) \ No newline at end of file + get_or_create_vm_detail(custom_user, manager, vm_id) diff --git a/digitalglarus/locale/de/LC_MESSAGES/django.po b/digitalglarus/locale/de/LC_MESSAGES/django.po index ec96f5dc..ef7a46b5 100644 --- a/digitalglarus/locale/de/LC_MESSAGES/django.po +++ b/digitalglarus/locale/de/LC_MESSAGES/django.po @@ -376,8 +376,6 @@ msgid "" " digitalglarus.ch
    \n" " hack4lgarus.ch
    \n" " ipv6onlyhosting.com
    \n" -" ipv6onlyhosting.ch
    \n" -" ipv6onlyhosting.net
    \n" " django-hosting.ch
    \n" " rails-hosting.ch
    \n" " node-hosting.ch
    \n" @@ -636,8 +634,8 @@ msgstr "" "Internetangebot der ungleich glarus ag, welches unter den nachfolgenden " "Domains erreichbar ist:

    ungleich.ch
    datacenterlight.ch
    devuanhosting.com
    devuanhosting.ch
    digitalglarus.ch
    hack4lgarus." -"ch
    ipv6onlyhosting.com
    ipv6onlyhosting.ch
    ipv6onlyhosting.net
    django-hosting.ch
    rails-hosting.ch
    node-hosting.ch
    blog." +"ch
    ipv6onlyhosting.com
    django-hosting.ch
    rails-hosting.ch" +"
    node-hosting.ch
    blog." "ungleich.ch

    Der Datenschutzbeauftragte des Verantwortlichen ist:

    Sanghee Kim
    ungleich glarus ag
    Bahnhofstrasse 1
    8783 " "Linthal (CH)
    E-Mail: sanghee." @@ -838,3 +836,4 @@ msgstr "" #~ msgid "index/?$" #~ msgstr "index/?$" + diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 03013ea5..62fe2897 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -631,8 +631,6 @@ GOOGLE_ANALYTICS_PROPERTY_IDS = { 'datacenterlight.ch': 'UA-62285904-8', 'devuanhosting.ch': 'UA-62285904-9', 'devuanhosting.com': 'UA-62285904-9', - 'ipv6onlyhosting.ch': 'UA-62285904-10', - 'ipv6onlyhosting.net': 'UA-62285904-10', 'ipv6onlyhosting.com': 'UA-62285904-10', 'comic.ungleich.ch': 'UA-62285904-13', '127.0.0.1:8000': 'localhost', diff --git a/dynamicweb/settings/prod.py b/dynamicweb/settings/prod.py index 445748ad..0590ca27 100644 --- a/dynamicweb/settings/prod.py +++ b/dynamicweb/settings/prod.py @@ -28,9 +28,7 @@ ALLOWED_HOSTS = [ ".devuanhosting.ch", ".devuanhosting.com", ".digitalezukunft.ch", - ".ipv6onlyhosting.ch", ".ipv6onlyhosting.com", - ".ipv6onlyhosting.net", ".digitalglarus.ch", ".hack4glarus.ch", ".xn--nglarus-n2a.ch" diff --git a/templates/gdpr/gdpr_banner.html b/templates/gdpr/gdpr_banner.html index 7e9f5c7f..f927f8ee 100644 --- a/templates/gdpr/gdpr_banner.html +++ b/templates/gdpr/gdpr_banner.html @@ -134,8 +134,6 @@ digitalglarus.ch
    hack4lgarus.ch
    ipv6onlyhosting.com
    - ipv6onlyhosting.ch
    - ipv6onlyhosting.net
    django-hosting.ch
    rails-hosting.ch
    node-hosting.ch
    From 5f1534c152c77a300baa0ba95834502b93fefcb0 Mon Sep 17 00:00:00 2001 From: "app@dynamicweb-production" Date: Fri, 14 Apr 2023 05:05:35 +0200 Subject: [PATCH 200/257] Replace jQuery dependent code with vanilla js --- .../ungleich/section_products.html | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/ungleich_page/templates/ungleich_page/ungleich/section_products.html b/ungleich_page/templates/ungleich_page/ungleich/section_products.html index 9cdc94c8..e3d0dc73 100644 --- a/ungleich_page/templates/ungleich_page/ungleich/section_products.html +++ b/ungleich_page/templates/ungleich_page/ungleich/section_products.html @@ -19,13 +19,15 @@ \ No newline at end of file + From 26d9a77ebf8ba7526c0163ea6591f09534755c42 Mon Sep 17 00:00:00 2001 From: "app@dynamicweb-production" Date: Fri, 14 Apr 2023 05:07:37 +0200 Subject: [PATCH 201/257] Update changelog --- Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog b/Changelog index 44c4dee4..9e420c87 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,5 @@ +3.4: 2022-04-14 + * 11566: Fix for ungleich.ch product section alignment 3.2: 2021-02-07 * 8816: Update order confirmation text to better prepared for payment dispute * supportticket#22990: Fix: can't add a deleted card From 33aeaf7ddba91ec3edc96637a956382b70596c6d Mon Sep 17 00:00:00 2001 From: "app@dynamicweb-production" Date: Thu, 27 Jul 2023 10:32:46 +0200 Subject: [PATCH 202/257] Increase max_digits for generic product price and vat --- hosting/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hosting/models.py b/hosting/models.py index 48238afe..b51d8616 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -75,8 +75,8 @@ class GenericProduct(AssignPermissionsMixin, models.Model): ) product_description = models.CharField(max_length=500, default="") created_at = models.DateTimeField(auto_now_add=True) - product_price = models.DecimalField(max_digits=6, decimal_places=2) - product_vat = models.DecimalField(max_digits=6, decimal_places=4, default=0) + product_price = models.DecimalField(max_digits=10, decimal_places=2) + product_vat = models.DecimalField(max_digits=10, decimal_places=4, default=0) product_is_subscription = models.BooleanField(default=True) product_subscription_interval = models.CharField( max_length=10, default="month", From c69affc9a13dec2045d2bdea335232ff15f80137 Mon Sep 17 00:00:00 2001 From: "app@dynamicweb-production" Date: Thu, 27 Jul 2023 10:33:40 +0200 Subject: [PATCH 203/257] Add migration --- hosting/migrations/0066_auto_20230727_0812.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 hosting/migrations/0066_auto_20230727_0812.py diff --git a/hosting/migrations/0066_auto_20230727_0812.py b/hosting/migrations/0066_auto_20230727_0812.py new file mode 100644 index 00000000..795b1785 --- /dev/null +++ b/hosting/migrations/0066_auto_20230727_0812.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2023-07-27 08:12 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0065_auto_20201231_1041'), + ] + + operations = [ + migrations.AlterField( + model_name='genericproduct', + name='product_price', + field=models.DecimalField(decimal_places=2, max_digits=10), + ), + migrations.AlterField( + model_name='genericproduct', + name='product_vat', + field=models.DecimalField(decimal_places=4, default=0, max_digits=10), + ), + ] From dc6f5dcc5be6b06739fe634e0820366d46db14fc Mon Sep 17 00:00:00 2001 From: "app@dynamicweb-production" Date: Thu, 16 Nov 2023 19:12:59 +0100 Subject: [PATCH 204/257] add recaptcha coe --- dynamicweb/settings/base.py | 3 +++ utils/forms.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 62fe2897..308c9029 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -56,6 +56,9 @@ dotenv.load_dotenv("{0}/.env".format(PROJECT_DIR)) from multisite import SiteID +RECAPTCHA_PUBLIC_KEY = env('RECAPTCHA_PUBLIC_KEY') +RECAPTCHA_PRIVATE_KEY = env('RECAPTCHA_PRIVATE_KEY') + UNGLEICH_BLOG_SITE_ID = int_env("UNGLEICH_BLOG_SITE_ID") SITE_ID = SiteID(default=(UNGLEICH_BLOG_SITE_ID if UNGLEICH_BLOG_SITE_ID > 0 else 1)) diff --git a/utils/forms.py b/utils/forms.py index f35c90f4..92b442af 100644 --- a/utils/forms.py +++ b/utils/forms.py @@ -4,6 +4,8 @@ from django.core.mail import EmailMultiAlternatives from django.template.loader import render_to_string from django.utils.translation import ugettext_lazy as _ +from captcha.fields import ReCaptchaField + from membership.models import CustomUser from .models import ContactMessage, BillingAddress, UserBillingAddress @@ -188,6 +190,7 @@ class UserBillingAddressForm(forms.ModelForm): class ContactUsForm(forms.ModelForm): error_css_class = 'autofocus' + captcha = ReCaptchaField() class Meta: model = ContactMessage From 9f74c0286e30d2ac68af477d81eb7183426d83f7 Mon Sep 17 00:00:00 2001 From: "app@dynamicweb-production" Date: Thu, 16 Nov 2023 19:24:59 +0100 Subject: [PATCH 205/257] Add installed app --- dynamicweb/settings/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 308c9029..f03042dc 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -128,6 +128,7 @@ INSTALLED_APPS = ( 'djangocms_file', 'djangocms_picture', 'djangocms_video', + 'django_recaptcha', # 'djangocms_flash', # 'djangocms_googlemap', # 'djangocms_inherit', From c5f8660c5532481fa43b4f2ff23542752548abd0 Mon Sep 17 00:00:00 2001 From: "app@dynamicweb-production" Date: Fri, 17 Nov 2023 03:29:10 +0100 Subject: [PATCH 206/257] Disable all saving of contact us form and send emails --- alplora/views.py | 6 +++--- digitalglarus/views.py | 6 +++--- ungleich_page/views.py | 6 +++--- utils/forms.py | 19 ++++++++++--------- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/alplora/views.py b/alplora/views.py index 0a10b4e0..9fcbbc2f 100644 --- a/alplora/views.py +++ b/alplora/views.py @@ -31,9 +31,9 @@ class ContactView(FormView): return context def form_valid(self, form): - form.save() - form.send_email(email_to='info@alplora.ch') - messages.add_message(self.request, messages.SUCCESS, self.success_message) + #form.save() + #form.send_email(email_to='info@alplora.ch') + #messages.add_message(self.request, messages.SUCCESS, self.success_message) return render(self.request, 'alplora/contact_success.html', {}) diff --git a/digitalglarus/views.py b/digitalglarus/views.py index 299327e6..ba1a59f7 100644 --- a/digitalglarus/views.py +++ b/digitalglarus/views.py @@ -835,9 +835,9 @@ class ContactView(FormView): success_message = _('Message Successfully Sent') def form_valid(self, form): - form.save() - form.send_email() - messages.add_message(self.request, messages.SUCCESS, self.success_message) + #form.save() + #form.send_email() + #messages.add_message(self.request, messages.SUCCESS, self.success_message) return super(ContactView, self).form_valid(form) diff --git a/ungleich_page/views.py b/ungleich_page/views.py index e5a99d8d..372afb86 100644 --- a/ungleich_page/views.py +++ b/ungleich_page/views.py @@ -25,9 +25,9 @@ class ContactView(FormView): success_message = _('Message Successfully Sent') def form_valid(self, form): - form.save() - form.send_email() - messages.add_message(self.request, messages.SUCCESS, self.success_message) + #form.save() + #form.send_email() + #messages.add_message(self.request, messages.SUCCESS, self.success_message) return super(ContactView, self).form_valid(form) def get_context_data(self, **kwargs): diff --git a/utils/forms.py b/utils/forms.py index 92b442af..3cc57578 100644 --- a/utils/forms.py +++ b/utils/forms.py @@ -4,7 +4,7 @@ from django.core.mail import EmailMultiAlternatives from django.template.loader import render_to_string from django.utils.translation import ugettext_lazy as _ -from captcha.fields import ReCaptchaField +from django_recaptcha.fields import ReCaptchaField from membership.models import CustomUser from .models import ContactMessage, BillingAddress, UserBillingAddress @@ -209,11 +209,12 @@ class ContactUsForm(forms.ModelForm): } def send_email(self, email_to='info@digitalglarus.ch'): - text_content = render_to_string( - 'emails/contact.txt', {'data': self.cleaned_data}) - html_content = render_to_string( - 'emails/contact.html', {'data': self.cleaned_data}) - email = EmailMultiAlternatives('Subject', text_content) - email.attach_alternative(html_content, "text/html") - email.to = [email_to] - email.send() + pass + #text_content = render_to_string( + # 'emails/contact.txt', {'data': self.cleaned_data}) + #html_content = render_to_string( + # 'emails/contact.html', {'data': self.cleaned_data}) + #email = EmailMultiAlternatives('Subject', text_content) + #email.attach_alternative(html_content, "text/html") + #email.to = [email_to] + #email.send() From 3c3c614b6b1d0c5993460dd1e21b6f05fb0e2d3c Mon Sep 17 00:00:00 2001 From: "app@dynamicweb-production" Date: Fri, 17 Nov 2023 03:39:12 +0100 Subject: [PATCH 207/257] Debug --- alplora/views.py | 1 + digitalglarus/views.py | 1 + ungleich_page/views.py | 1 + 3 files changed, 3 insertions(+) diff --git a/alplora/views.py b/alplora/views.py index 9fcbbc2f..d628cffc 100644 --- a/alplora/views.py +++ b/alplora/views.py @@ -31,6 +31,7 @@ class ContactView(FormView): return context def form_valid(self, form): + print("alplora contactusform") #form.save() #form.send_email(email_to='info@alplora.ch') #messages.add_message(self.request, messages.SUCCESS, self.success_message) diff --git a/digitalglarus/views.py b/digitalglarus/views.py index ba1a59f7..bf3ed2c6 100644 --- a/digitalglarus/views.py +++ b/digitalglarus/views.py @@ -835,6 +835,7 @@ class ContactView(FormView): success_message = _('Message Successfully Sent') def form_valid(self, form): + print("digital glarus contactusform") #form.save() #form.send_email() #messages.add_message(self.request, messages.SUCCESS, self.success_message) diff --git a/ungleich_page/views.py b/ungleich_page/views.py index 372afb86..01d1138d 100644 --- a/ungleich_page/views.py +++ b/ungleich_page/views.py @@ -25,6 +25,7 @@ class ContactView(FormView): success_message = _('Message Successfully Sent') def form_valid(self, form): + print("ungleich_page contactusform") #form.save() #form.send_email() #messages.add_message(self.request, messages.SUCCESS, self.success_message) From 325d1ffcb94add403cc7c9b60d354d9a16b6ef5e Mon Sep 17 00:00:00 2001 From: "app@dynamicweb-production" Date: Fri, 17 Nov 2023 03:43:49 +0100 Subject: [PATCH 208/257] Ignore more contact f --- datacenterlight/views.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 5bf68e0a..5c333a63 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -63,23 +63,23 @@ class ContactUsView(FormView): ) def form_valid(self, form): - form.save() - from_emails = { - 'glasfaser': 'glasfaser@ungleich.ch' - } - from_page = self.request.POST.get('from_page') - email_data = { - 'subject': "{dcl_text} Message from {sender}".format( - dcl_text=settings.DCL_TEXT, - sender=form.cleaned_data.get('email') - ), - 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, - 'to': [from_emails.get(from_page, 'support@ungleich.ch')], - 'body': "\n".join( - ["%s=%s" % (k, v) for (k, v) in form.cleaned_data.items()]), - 'reply_to': [form.cleaned_data.get('email')], - } - send_plain_email_task.delay(email_data) + #form.save() + #from_emails = { + # 'glasfaser': 'glasfaser@ungleich.ch' + #} + #from_page = self.request.POST.get('from_page') + #email_data = { + # 'subject': "{dcl_text} Message from {sender}".format( + # dcl_text=settings.DCL_TEXT, + # sender=form.cleaned_data.get('email') + # ), + # 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + # 'to': [from_emails.get(from_page, 'support@ungleich.ch')], + # 'body': "\n".join( + # ["%s=%s" % (k, v) for (k, v) in form.cleaned_data.items()]), + # 'reply_to': [form.cleaned_data.get('email')], + #} + #send_plain_email_task.delay(email_data) if self.request.is_ajax(): return self.render_to_response( self.get_context_data(success=True, contact_form=form)) From a492c3de1bbccab4553ab268a7e8156d1cc51329 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 6 Dec 2023 15:34:23 +0530 Subject: [PATCH 209/257] Add management command --- .../commands/change_ch_vatrate_2023.py | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 hosting/management/commands/change_ch_vatrate_2023.py diff --git a/hosting/management/commands/change_ch_vatrate_2023.py b/hosting/management/commands/change_ch_vatrate_2023.py new file mode 100644 index 00000000..568ba78c --- /dev/null +++ b/hosting/management/commands/change_ch_vatrate_2023.py @@ -0,0 +1,93 @@ +from django.core.management.base import BaseCommand +import datetime +import csv +import stripe +from hosting.models import VATRates +from utils.hosting_utils import get_vat_rate_for_country +from django.conf import settings + +stripe.api_key = settings.STRIPE_API_PRIVATE_KEY + + +class Command(BaseCommand): + help = '''CH vat rate changes on 2024-01-01 from 7.7% to 8.1%. This commands makes the necessary changes''' + + def handle(self, *args, **options): + MAKE_MODIFS=False + try: + country_to_change = 'CH' + currency_to_change = 'CHF' + new_rate = 0.081 + user_country_vat_rate = get_vat_rate_for_country(country_to_change) + logger.debug("Existing VATRate for %s %s " % (country_to_change, user_country_vat_rate)) + vat_rate = VATRates.objects.get( + territory_codes=country_to_change, start_date__isnull=False, stop_date=None + ) + logger.debug("VAT rate for %s is %s" % (country, vat_rate.rate)) + logger.debug("vat_rate object = %s" % vat_rate) + logger.debug("Create end date for the VATRate %s" % vat_rate.id) + if MAKE_MODIFS: + vat_rate.stop_date = datetime.date(2023, 12, 31) + vat_rate.save() + print("Creating a new VATRate for CH") + obj, created = VATRates.objects.get_or_create( + start_date=datetime.date(2024, 1, 1), + stop_date=None, + territory_codes=country_to_change, + currency_code=currency_to_change, + rate=new_rate, + rate_type="standard", + description="Switzerland standard VAT (added manually on %s)" % datetime.datetime.now() + ) + if created: + logger.debug("Created new VAT Rate for %s with the new rate %s" % (country_to_change, new_rate)) + logger.debug(obj) + else: + logger.debug("VAT Rate for %s already exists with the rate %s" % (country_to_change, new_rate)) + + logger.debug("Getting all subscriptions of %s that need a VAT Rate change") + subscriptions = stripe.Subscription.list(limit=100) # Increase the limit to 100 per page (maximum) + ch_subs = [] + + while subscriptions: + for subscription in subscriptions: + if len(subscription.default_tax_rates) > 0 and subscription.default_tax_rates[0].jurisdiction and subscription.default_tax_rates[0].jurisdiction.lower() == 'ch': + ch_subs.append(subscription) + elif len(subscription.default_tax_rates) > 0: + print("subscription %s belongs to %s" % (subscription.id, subscription.default_tax_rates[0].jurisdiction)) + else: + print("subscription %s does not have a tax rate" % subscription.id) + if subscriptions.has_more: + print("FETCHING MORE") + subscriptions = stripe.Subscription.list(limit=100, starting_after=subscriptions.data[-1]) + else: + break + logger.debug("There are %s ch subscription that need VAT rate update" % len(ch_subs)) + + if MAKE_MODIFS: + print("Making modifications now") + tax_rate_obj = stripe.TaxRate.create( + display_name="VAT", + description="VAT for %s" % country_to_change, + jurisdiction=country_to_change, + percentage=new_rate, + 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 + ) + + for ch_sub in ch_subs: + ch_sub.default_tax_rates = [stripe_tax_rate.tax_rate_id] + ch_sub.save() + logger.debug("Default tax rate updated for %s" % ch_sub.id) + else: + print("Not making any modifications because MAKE_MODIFS=False") + + except Exception as e: + print(" *** Error occurred. Details {}".format(str(e))) From 5530b48d0dedff5dfc5901fbf6e815fb4fdb6a75 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 25 Dec 2023 11:23:01 +0530 Subject: [PATCH 210/257] Save subscriptions to be changed in a csv --- .../commands/change_ch_vatrate_2023.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/hosting/management/commands/change_ch_vatrate_2023.py b/hosting/management/commands/change_ch_vatrate_2023.py index 568ba78c..2740be9c 100644 --- a/hosting/management/commands/change_ch_vatrate_2023.py +++ b/hosting/management/commands/change_ch_vatrate_2023.py @@ -64,6 +64,44 @@ class Command(BaseCommand): break logger.debug("There are %s ch subscription that need VAT rate update" % len(ch_subs)) + # CSV column headers + csv_headers = [ + "customer_name", + "subscription_id", + "subscription_name", + "amount", + "vat_rate" + ] + # CSV file name + csv_filename = "ch_subscriptions_change_2024.csv" + # Write subscription data to CSV file + with open(csv_filename, mode='w', newline='') as csv_file: + writer = csv.DictWriter(csv_file, fieldnames=csv_headers) + writer.writeheader() + + for subscription in subscriptions: + subscription_id = subscription["id"] + customer_name = subscription.get("customer", "") + items = subscription.get("items", {}).get("data", []) + + for item in items: + subscription_name = item.get("plan", {}).get("id", "") + amount = item.get("plan", {}).get("amount", "") + vat_rates = item.get("tax_rates", []) + + # Convert amount to a proper format (e.g., cents to dollars) + amount_in_chf = amount / 100 # Adjust this conversion as needed + + # Writing to CSV + writer.writerow({ + "customer_name": customer_name, + "subscription_id": subscription_id, + "subscription_name": subscription_name, + "amount": amount_in_chf, + "vat_rate": ", ".join(vat_rates) # Fill in VAT rate if available + }) + + if MAKE_MODIFS: print("Making modifications now") tax_rate_obj = stripe.TaxRate.create( From ea4ff961c2eb149bb3f469a6690d939881a8e017 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 25 Dec 2023 11:43:29 +0530 Subject: [PATCH 211/257] add missing logging --- hosting/management/commands/change_ch_vatrate_2023.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hosting/management/commands/change_ch_vatrate_2023.py b/hosting/management/commands/change_ch_vatrate_2023.py index 2740be9c..d6e2ef05 100644 --- a/hosting/management/commands/change_ch_vatrate_2023.py +++ b/hosting/management/commands/change_ch_vatrate_2023.py @@ -1,6 +1,7 @@ from django.core.management.base import BaseCommand import datetime import csv +import logging import stripe from hosting.models import VATRates from utils.hosting_utils import get_vat_rate_for_country @@ -8,6 +9,7 @@ from django.conf import settings stripe.api_key = settings.STRIPE_API_PRIVATE_KEY +logger = logging.getLogger(__name__) class Command(BaseCommand): help = '''CH vat rate changes on 2024-01-01 from 7.7% to 8.1%. This commands makes the necessary changes''' From a0ade926fb71955435b5c42eae26b76095d55025 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 25 Dec 2023 11:44:52 +0530 Subject: [PATCH 212/257] Fix variable name typo --- hosting/management/commands/change_ch_vatrate_2023.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/management/commands/change_ch_vatrate_2023.py b/hosting/management/commands/change_ch_vatrate_2023.py index d6e2ef05..43cb1d92 100644 --- a/hosting/management/commands/change_ch_vatrate_2023.py +++ b/hosting/management/commands/change_ch_vatrate_2023.py @@ -25,7 +25,7 @@ class Command(BaseCommand): vat_rate = VATRates.objects.get( territory_codes=country_to_change, start_date__isnull=False, stop_date=None ) - logger.debug("VAT rate for %s is %s" % (country, vat_rate.rate)) + logger.debug("VAT rate for %s is %s" % (country_to_change, vat_rate.rate)) logger.debug("vat_rate object = %s" % vat_rate) logger.debug("Create end date for the VATRate %s" % vat_rate.id) if MAKE_MODIFS: From 92e525467968bbf7f9d7156f5ca6c541d89a0565 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 25 Dec 2023 12:14:48 +0530 Subject: [PATCH 213/257] Fix customer name and vat rate --- .../management/commands/change_ch_vatrate_2023.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/hosting/management/commands/change_ch_vatrate_2023.py b/hosting/management/commands/change_ch_vatrate_2023.py index 43cb1d92..cbf77060 100644 --- a/hosting/management/commands/change_ch_vatrate_2023.py +++ b/hosting/management/commands/change_ch_vatrate_2023.py @@ -6,6 +6,7 @@ import stripe from hosting.models import VATRates from utils.hosting_utils import get_vat_rate_for_country from django.conf import settings +from membership.models import CustomUser, StripeCustomer stripe.api_key = settings.STRIPE_API_PRIVATE_KEY @@ -81,15 +82,17 @@ class Command(BaseCommand): writer = csv.DictWriter(csv_file, fieldnames=csv_headers) writer.writeheader() - for subscription in subscriptions: + for subscription in ch_subs: subscription_id = subscription["id"] - customer_name = subscription.get("customer", "") + stripe_customer_id = subscription.get("customer", "") + vat_rate = subscription.get("tax_percent", "") + c_user = CustomUser.objects.get( + id=StripeCustomer.objects.filter(stripe_id=stripe_customer_id)[0].id) + customer_name = "%s - %s - %s" % (c_user.name, c_user.email, stripe_customer_id) items = subscription.get("items", {}).get("data", []) - for item in items: subscription_name = item.get("plan", {}).get("id", "") amount = item.get("plan", {}).get("amount", "") - vat_rates = item.get("tax_rates", []) # Convert amount to a proper format (e.g., cents to dollars) amount_in_chf = amount / 100 # Adjust this conversion as needed @@ -100,7 +103,7 @@ class Command(BaseCommand): "subscription_id": subscription_id, "subscription_name": subscription_name, "amount": amount_in_chf, - "vat_rate": ", ".join(vat_rates) # Fill in VAT rate if available + "vat_rate": vat_rate # Fill in VAT rate if available }) From f31838dbe5189c434e7c35e831185b044ccc7eee Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 25 Dec 2023 12:22:34 +0530 Subject: [PATCH 214/257] Separate fields --- hosting/management/commands/change_ch_vatrate_2023.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hosting/management/commands/change_ch_vatrate_2023.py b/hosting/management/commands/change_ch_vatrate_2023.py index cbf77060..45ec70d9 100644 --- a/hosting/management/commands/change_ch_vatrate_2023.py +++ b/hosting/management/commands/change_ch_vatrate_2023.py @@ -88,7 +88,8 @@ class Command(BaseCommand): vat_rate = subscription.get("tax_percent", "") c_user = CustomUser.objects.get( id=StripeCustomer.objects.filter(stripe_id=stripe_customer_id)[0].id) - customer_name = "%s - %s - %s" % (c_user.name, c_user.email, stripe_customer_id) + customer_name = c_user.name.encode('utf-8') + customer_email = c.user_email items = subscription.get("items", {}).get("data", []) for item in items: subscription_name = item.get("plan", {}).get("id", "") @@ -100,6 +101,8 @@ class Command(BaseCommand): # Writing to CSV writer.writerow({ "customer_name": customer_name, + "customer_email": customer_email, + "stripe_customer_id": stripe_customer_id, "subscription_id": subscription_id, "subscription_name": subscription_name, "amount": amount_in_chf, From 10c167e76bdf3e5e76e4fe2b68498750e195ac18 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 25 Dec 2023 12:23:52 +0530 Subject: [PATCH 215/257] Fix typo --- hosting/management/commands/change_ch_vatrate_2023.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/management/commands/change_ch_vatrate_2023.py b/hosting/management/commands/change_ch_vatrate_2023.py index 45ec70d9..f979ad71 100644 --- a/hosting/management/commands/change_ch_vatrate_2023.py +++ b/hosting/management/commands/change_ch_vatrate_2023.py @@ -89,7 +89,7 @@ class Command(BaseCommand): c_user = CustomUser.objects.get( id=StripeCustomer.objects.filter(stripe_id=stripe_customer_id)[0].id) customer_name = c_user.name.encode('utf-8') - customer_email = c.user_email + customer_email = c_user.user_email items = subscription.get("items", {}).get("data", []) for item in items: subscription_name = item.get("plan", {}).get("id", "") From 3c4881154898438a740be13397ba84563850696d Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 25 Dec 2023 12:25:07 +0530 Subject: [PATCH 216/257] Fix typo --- hosting/management/commands/change_ch_vatrate_2023.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/management/commands/change_ch_vatrate_2023.py b/hosting/management/commands/change_ch_vatrate_2023.py index f979ad71..f96b36d2 100644 --- a/hosting/management/commands/change_ch_vatrate_2023.py +++ b/hosting/management/commands/change_ch_vatrate_2023.py @@ -89,7 +89,7 @@ class Command(BaseCommand): c_user = CustomUser.objects.get( id=StripeCustomer.objects.filter(stripe_id=stripe_customer_id)[0].id) customer_name = c_user.name.encode('utf-8') - customer_email = c_user.user_email + customer_email = c_user.email items = subscription.get("items", {}).get("data", []) for item in items: subscription_name = item.get("plan", {}).get("id", "") From 36b091700e643a4efe79af0e61514bc6aa4455e3 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 25 Dec 2023 12:28:03 +0530 Subject: [PATCH 217/257] Fix headers --- hosting/management/commands/change_ch_vatrate_2023.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hosting/management/commands/change_ch_vatrate_2023.py b/hosting/management/commands/change_ch_vatrate_2023.py index f96b36d2..93920904 100644 --- a/hosting/management/commands/change_ch_vatrate_2023.py +++ b/hosting/management/commands/change_ch_vatrate_2023.py @@ -70,6 +70,8 @@ class Command(BaseCommand): # CSV column headers csv_headers = [ "customer_name", + "customer_email", + "stripe_customer_id", "subscription_id", "subscription_name", "amount", From 94a81fc976243a4847fda83a50fa56b0f6d31c1c Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 27 Dec 2023 19:49:50 +0530 Subject: [PATCH 218/257] Fix obtaining correct CustomUSer --- hosting/management/commands/change_ch_vatrate_2023.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/management/commands/change_ch_vatrate_2023.py b/hosting/management/commands/change_ch_vatrate_2023.py index 93920904..e397a208 100644 --- a/hosting/management/commands/change_ch_vatrate_2023.py +++ b/hosting/management/commands/change_ch_vatrate_2023.py @@ -89,7 +89,7 @@ class Command(BaseCommand): stripe_customer_id = subscription.get("customer", "") vat_rate = subscription.get("tax_percent", "") c_user = CustomUser.objects.get( - id=StripeCustomer.objects.filter(stripe_id=stripe_customer_id)[0].id) + id=StripeCustomer.objects.filter(stripe_id=stripe_customer_id)[0].user.id) customer_name = c_user.name.encode('utf-8') customer_email = c_user.email items = subscription.get("items", {}).get("data", []) From 4d3da3387ae7d53fa3e1383ef0bce784d6b6d00f Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 27 Dec 2023 20:36:59 +0530 Subject: [PATCH 219/257] Print case where CustomUser is not found in the datbase from Stripe customer id --- .../commands/change_ch_vatrate_2023.py | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/hosting/management/commands/change_ch_vatrate_2023.py b/hosting/management/commands/change_ch_vatrate_2023.py index e397a208..46ca2312 100644 --- a/hosting/management/commands/change_ch_vatrate_2023.py +++ b/hosting/management/commands/change_ch_vatrate_2023.py @@ -90,26 +90,29 @@ class Command(BaseCommand): vat_rate = subscription.get("tax_percent", "") c_user = CustomUser.objects.get( id=StripeCustomer.objects.filter(stripe_id=stripe_customer_id)[0].user.id) - customer_name = c_user.name.encode('utf-8') - customer_email = c_user.email - items = subscription.get("items", {}).get("data", []) - for item in items: - subscription_name = item.get("plan", {}).get("id", "") - amount = item.get("plan", {}).get("amount", "") + if c_user: + customer_name = c_user.name.encode('utf-8') + customer_email = c_user.email + items = subscription.get("items", {}).get("data", []) + for item in items: + subscription_name = item.get("plan", {}).get("id", "") + amount = item.get("plan", {}).get("amount", "") - # Convert amount to a proper format (e.g., cents to dollars) - amount_in_chf = amount / 100 # Adjust this conversion as needed + # Convert amount to a proper format (e.g., cents to dollars) + amount_in_chf = amount / 100 # Adjust this conversion as needed - # Writing to CSV - writer.writerow({ - "customer_name": customer_name, - "customer_email": customer_email, - "stripe_customer_id": stripe_customer_id, - "subscription_id": subscription_id, - "subscription_name": subscription_name, - "amount": amount_in_chf, - "vat_rate": vat_rate # Fill in VAT rate if available - }) + # Writing to CSV + writer.writerow({ + "customer_name": customer_name, + "customer_email": customer_email, + "stripe_customer_id": stripe_customer_id, + "subscription_id": subscription_id, + "subscription_name": subscription_name, + "amount": amount_in_chf, + "vat_rate": vat_rate # Fill in VAT rate if available + }) + else: + print("No customuser for %s %s" % (stripe_customer_id, subscription_id)) if MAKE_MODIFS: From 4e891ed0bbcd8602402521a939cfc0797bb39f28 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 29 Dec 2023 08:50:58 +0530 Subject: [PATCH 220/257] Update countries list --- utils/fields.py | 88 ++++++++++++++++++++++++++++--------------------- 1 file changed, 50 insertions(+), 38 deletions(-) diff --git a/utils/fields.py b/utils/fields.py index 48a606cc..ca1115de 100644 --- a/utils/fields.py +++ b/utils/fields.py @@ -1,7 +1,8 @@ from django.utils.translation import ugettext as _ from django.db import models -# http://xml.coverpages.org/country3166.html +# Old: http://xml.coverpages.org/country3166.html +# 2023-12-29: Updated list of countries from https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes COUNTRIES = ( ('AD', _('Andorra')), ('AE', _('United Arab Emirates')), @@ -10,7 +11,6 @@ COUNTRIES = ( ('AI', _('Anguilla')), ('AL', _('Albania')), ('AM', _('Armenia')), - ('AN', _('Netherlands Antilles')), ('AO', _('Angola')), ('AQ', _('Antarctica')), ('AR', _('Argentina')), @@ -18,6 +18,7 @@ COUNTRIES = ( ('AT', _('Austria')), ('AU', _('Australia')), ('AW', _('Aruba')), + ('AX', _('Aland Islands')), ('AZ', _('Azerbaijan')), ('BA', _('Bosnia and Herzegovina')), ('BB', _('Barbados')), @@ -28,11 +29,13 @@ COUNTRIES = ( ('BH', _('Bahrain')), ('BI', _('Burundi')), ('BJ', _('Benin')), + ('BL', _('St. Barts')), ('BM', _('Bermuda')), - ('BN', _('Brunei Darussalam')), + ('BN', _('Brunei')), ('BO', _('Bolivia')), + ('BQ', _('Caribbean Netherlands')), ('BR', _('Brazil')), - ('BS', _('Bahama')), + ('BS', _('Bahamas')), ('BT', _('Bhutan')), ('BV', _('Bouvet Island')), ('BW', _('Botswana')), @@ -40,11 +43,12 @@ COUNTRIES = ( ('BZ', _('Belize')), ('CA', _('Canada')), ('CC', _('Cocos (Keeling) Islands')), + ('CD', _('Congo - Kinshasa')), ('CF', _('Central African Republic')), - ('CG', _('Congo')), + ('CG', _('Congo - Brazzaville')), ('CH', _('Switzerland')), ('CI', _('Ivory Coast')), - ('CK', _('Cook Iislands')), + ('CK', _('Cook Islands')), ('CL', _('Chile')), ('CM', _('Cameroon')), ('CN', _('China')), @@ -52,9 +56,10 @@ COUNTRIES = ( ('CR', _('Costa Rica')), ('CU', _('Cuba')), ('CV', _('Cape Verde')), + ('CW', _('Curacao')), ('CX', _('Christmas Island')), ('CY', _('Cyprus')), - ('CZ', _('Czech Republic')), + ('CZ', _('Czechia')), ('DE', _('Germany')), ('DJ', _('Djibouti')), ('DK', _('Denmark')), @@ -70,16 +75,16 @@ COUNTRIES = ( ('ET', _('Ethiopia')), ('FI', _('Finland')), ('FJ', _('Fiji')), - ('FK', _('Falkland Islands (Malvinas)')), + ('FK', _('Falkland Islands')), ('FM', _('Micronesia')), ('FO', _('Faroe Islands')), ('FR', _('France')), - ('FX', _('France, Metropolitan')), ('GA', _('Gabon')), - ('GB', _('United Kingdom (Great Britain)')), + ('GB', _('United Kingdom')), ('GD', _('Grenada')), ('GE', _('Georgia')), ('GF', _('French Guiana')), + ('GG', _('Guernsey')), ('GH', _('Ghana')), ('GI', _('Gibraltar')), ('GL', _('Greenland')), @@ -93,7 +98,7 @@ COUNTRIES = ( ('GU', _('Guam')), ('GW', _('Guinea-Bissau')), ('GY', _('Guyana')), - ('HK', _('Hong Kong')), + ('HK', _('Hong Kong SAR China')), ('HM', _('Heard & McDonald Islands')), ('HN', _('Honduras')), ('HR', _('Croatia')), @@ -102,12 +107,14 @@ COUNTRIES = ( ('ID', _('Indonesia')), ('IE', _('Ireland')), ('IL', _('Israel')), + ('IM', _('Isle of Man')), ('IN', _('India')), ('IO', _('British Indian Ocean Territory')), ('IQ', _('Iraq')), - ('IR', _('Islamic Republic of Iran')), + ('IR', _('Iran')), ('IS', _('Iceland')), ('IT', _('Italy')), + ('JE', _('Jersey')), ('JM', _('Jamaica')), ('JO', _('Jordan')), ('JP', _('Japan')), @@ -117,14 +124,14 @@ COUNTRIES = ( ('KI', _('Kiribati')), ('KM', _('Comoros')), ('KN', _('St. Kitts and Nevis')), - ('KP', _('Korea, Democratic People\'s Republic of')), - ('KR', _('Korea, Republic of')), + ('KP', _('North Korea')), + ('KR', _('South Korea')), ('KW', _('Kuwait')), ('KY', _('Cayman Islands')), ('KZ', _('Kazakhstan')), - ('LA', _('Lao People\'s Democratic Republic')), + ('LA', _('Laos')), ('LB', _('Lebanon')), - ('LC', _('Saint Lucia')), + ('LC', _('St. Lucia')), ('LI', _('Liechtenstein')), ('LK', _('Sri Lanka')), ('LR', _('Liberia')), @@ -132,20 +139,23 @@ COUNTRIES = ( ('LT', _('Lithuania')), ('LU', _('Luxembourg')), ('LV', _('Latvia')), - ('LY', _('Libyan Arab Jamahiriya')), + ('LY', _('Libya')), ('MA', _('Morocco')), ('MC', _('Monaco')), - ('MD', _('Moldova, Republic of')), + ('MD', _('Moldova')), + ('ME', _('Montenegro')), + ('MF', _('St. Martin')), ('MG', _('Madagascar')), ('MH', _('Marshall Islands')), + ('MK', _('North Macedonia')), ('ML', _('Mali')), + ('MM', _('Myanmar (Burma)')), ('MN', _('Mongolia')), - ('MM', _('Myanmar')), - ('MO', _('Macau')), + ('MO', _('Macao SAR China')), ('MP', _('Northern Mariana Islands')), ('MQ', _('Martinique')), ('MR', _('Mauritania')), - ('MS', _('Monserrat')), + ('MS', _('Montserrat')), ('MT', _('Malta')), ('MU', _('Mauritius')), ('MV', _('Maldives')), @@ -174,15 +184,17 @@ COUNTRIES = ( ('PK', _('Pakistan')), ('PL', _('Poland')), ('PM', _('St. Pierre & Miquelon')), - ('PN', _('Pitcairn')), + ('PN', _('Pitcairn Islands')), ('PR', _('Puerto Rico')), + ('PS', _('Palestinian Territories')), ('PT', _('Portugal')), ('PW', _('Palau')), ('PY', _('Paraguay')), ('QA', _('Qatar')), ('RE', _('Reunion')), ('RO', _('Romania')), - ('RU', _('Russian Federation')), + ('RS', _('Serbia')), + ('RU', _('Russia')), ('RW', _('Rwanda')), ('SA', _('Saudi Arabia')), ('SB', _('Solomon Islands')), @@ -192,17 +204,19 @@ COUNTRIES = ( ('SG', _('Singapore')), ('SH', _('St. Helena')), ('SI', _('Slovenia')), - ('SJ', _('Svalbard & Jan Mayen Islands')), + ('SJ', _('Svalbard and Jan Mayen')), ('SK', _('Slovakia')), ('SL', _('Sierra Leone')), ('SM', _('San Marino')), ('SN', _('Senegal')), ('SO', _('Somalia')), ('SR', _('Suriname')), + ('SS', _('South Sudan')), ('ST', _('Sao Tome & Principe')), ('SV', _('El Salvador')), - ('SY', _('Syrian Arab Republic')), - ('SZ', _('Swaziland')), + ('SX', _('Sint Maarten')), + ('SY', _('Syria')), + ('SZ', _('Eswatini')), ('TC', _('Turks & Caicos Islands')), ('TD', _('Chad')), ('TF', _('French Southern Territories')), @@ -210,36 +224,34 @@ COUNTRIES = ( ('TH', _('Thailand')), ('TJ', _('Tajikistan')), ('TK', _('Tokelau')), + ('TL', _('Timor-Leste')), ('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')), + ('TW', _('Taiwan')), + ('TZ', _('Tanzania')), ('UA', _('Ukraine')), ('UG', _('Uganda')), - ('UM', _('United States Minor Outlying Islands')), - ('US', _('United States of America')), + ('UM', _('U.S. Outlying Islands')), + ('US', _('United States')), ('UY', _('Uruguay')), ('UZ', _('Uzbekistan')), - ('VA', _('Vatican City State (Holy See)')), - ('VC', _('St. Vincent & the Grenadines')), + ('VA', _('Vatican City')), + ('VC', _('St. Vincent & Grenadines')), ('VE', _('Venezuela')), ('VG', _('British Virgin Islands')), - ('VI', _('United States Virgin Islands')), - ('VN', _('Viet Nam')), + ('VI', _('U.S. Virgin Islands')), + ('VN', _('Vietnam')), ('VU', _('Vanuatu')), - ('WF', _('Wallis & Futuna Islands')), + ('WF', _('Wallis & Futuna')), ('WS', _('Samoa')), ('YE', _('Yemen')), ('YT', _('Mayotte')), - ('YU', _('Yugoslavia')), ('ZA', _('South Africa')), ('ZM', _('Zambia')), - ('ZR', _('Zaire')), ('ZW', _('Zimbabwe')), ) From 83d1e54137673f694a7b5044f695e9904a272e2a Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 26 Jan 2024 09:11:57 +0530 Subject: [PATCH 221/257] Fix error when user accesses vm details and no invoices --- hosting/templates/hosting/virtual_machine_detail.html | 2 +- hosting/views.py | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/hosting/templates/hosting/virtual_machine_detail.html b/hosting/templates/hosting/virtual_machine_detail.html index 24b2c6ad..6b41af95 100644 --- a/hosting/templates/hosting/virtual_machine_detail.html +++ b/hosting/templates/hosting/virtual_machine_detail.html @@ -46,7 +46,7 @@
    diff --git a/hosting/views.py b/hosting/views.py index e2f6e13b..6b93fdb4 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1694,7 +1694,11 @@ class VirtualMachineView(LoginRequiredMixin, View): login_url = reverse_lazy('hosting:login') def get_object(self): - owner = self.request.user + username = self.request.GET.get('username') + if self.request.user.is_admin and username: + owner = CustomUser.objects.get(username=username) + else: + owner = self.request.user vm = None manager = OpenNebulaManager( email=owner.username, @@ -1750,7 +1754,10 @@ class VirtualMachineView(LoginRequiredMixin, View): subscription=hosting_order.subscription_id, count=1 ) - inv_url = stripe_obj.data[0].hosted_invoice_url + if stripe_obj.data: + inv_url = stripe_obj.data[0].hosted_invoice_url + else: + inv_url = '' elif hosting_order.stripe_charge_id: stripe_obj = stripe.Charge.retrieve( hosting_order.stripe_charge_id From 96af882e6ddf3160c77d719d14ec062a6cec4c7e Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 9 Feb 2024 21:03:17 +0530 Subject: [PATCH 222/257] Fix mistake is obtaining the correct hosting order id --- hosting/templates/hosting/invoices.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index 347b1ff4..69ed4577 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -87,7 +87,7 @@
    {% for ho, stripe_charge_data in invs_charge %} - {{ ho.id | get_line_item_from_hosting_order_charge }} + {{ ho | get_line_item_from_hosting_order_charge }} {% endfor %} From 7afa9d2f4c10115cfce7b07b5a095ccaf9f52c0d Mon Sep 17 00:00:00 2001 From: "app@dynamicweb-production" Date: Fri, 9 Feb 2024 16:37:34 +0100 Subject: [PATCH 223/257] Show settings view for admin --- hosting/views.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index 6b93fdb4..3ac8c023 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -554,14 +554,23 @@ 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. """ + username = self.request.GET.get('username') + if self.request.user.is_admin and username: + user = CustomUser.objects.get(username=username) + else: + user = self.request.user return form_class( - instance=self.request.user.billing_addresses.first(), + instance=user.billing_addresses.first(), **self.get_form_kwargs()) def get_context_data(self, **kwargs): context = super(SettingsView, self).get_context_data(**kwargs) # Get user - user = self.request.user + username = self.request.GET.get('username') + if self.request.user.is_admin and username: + user = CustomUser.objects.get(username=username) + else: + user = self.request.user stripe_customer = None if hasattr(user, 'stripecustomer'): stripe_customer = user.stripecustomer From 41461538a3c8480fb5650026ef3ce1fbead0f637 Mon Sep 17 00:00:00 2001 From: "app@dynamicweb-production" Date: Fri, 9 Feb 2024 16:38:10 +0100 Subject: [PATCH 224/257] Changes on prod --- .../commands/change_ch_vatrate_2023.py | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/hosting/management/commands/change_ch_vatrate_2023.py b/hosting/management/commands/change_ch_vatrate_2023.py index 46ca2312..612b246e 100644 --- a/hosting/management/commands/change_ch_vatrate_2023.py +++ b/hosting/management/commands/change_ch_vatrate_2023.py @@ -29,25 +29,25 @@ class Command(BaseCommand): logger.debug("VAT rate for %s is %s" % (country_to_change, vat_rate.rate)) logger.debug("vat_rate object = %s" % vat_rate) logger.debug("Create end date for the VATRate %s" % vat_rate.id) - if MAKE_MODIFS: - vat_rate.stop_date = datetime.date(2023, 12, 31) - vat_rate.save() - print("Creating a new VATRate for CH") - obj, created = VATRates.objects.get_or_create( - start_date=datetime.date(2024, 1, 1), - stop_date=None, - territory_codes=country_to_change, - currency_code=currency_to_change, - rate=new_rate, - rate_type="standard", - description="Switzerland standard VAT (added manually on %s)" % datetime.datetime.now() - ) - if created: - logger.debug("Created new VAT Rate for %s with the new rate %s" % (country_to_change, new_rate)) - logger.debug(obj) - else: - logger.debug("VAT Rate for %s already exists with the rate %s" % (country_to_change, new_rate)) - + # if MAKE_MODIFS: + # vat_rate.stop_date = datetime.date(2023, 12, 31) + # vat_rate.save() + # print("Creating a new VATRate for CH") + # obj, created = VATRates.objects.get_or_create( + # start_date=datetime.date(2024, 1, 1), + # stop_date=None, + # territory_codes=country_to_change, + # currency_code=currency_to_change, + # rate=new_rate, + # rate_type="standard", + # description="Switzerland standard VAT (added manually on %s)" % datetime.datetime.now() + # ) + # if created: + # logger.debug("Created new VAT Rate for %s with the new rate %s" % (country_to_change, new_rate)) + # logger.debug(obj) + # else: + # logger.debug("VAT Rate for %s already exists with the rate %s" % (country_to_change, new_rate)) + # logger.debug("Getting all subscriptions of %s that need a VAT Rate change") subscriptions = stripe.Subscription.list(limit=100) # Increase the limit to 100 per page (maximum) ch_subs = [] From 10d2c25556ca6138107267a063cdf1ee17994cef Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 16 Feb 2024 16:23:02 +0530 Subject: [PATCH 225/257] Get product name from id it starts with generic otherwise use as is --- 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 120cabbf..5d0c01b3 100644 --- a/datacenterlight/templatetags/custom_tags.py +++ b/datacenterlight/templatetags/custom_tags.py @@ -144,7 +144,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(get_product_name(plan_name)), + mark_safe(get_product_name(plan_name)) if plan_name.startswith("generic-") else 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 6a374f7fa0010ef62def8bbed09151b6b69ad60b Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 16 Feb 2024 16:23:19 +0530 Subject: [PATCH 226/257] Rearrange --- datacenterlight/templatetags/custom_tags.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/datacenterlight/templatetags/custom_tags.py b/datacenterlight/templatetags/custom_tags.py index 5d0c01b3..4fcf05a2 100644 --- a/datacenterlight/templatetags/custom_tags.py +++ b/datacenterlight/templatetags/custom_tags.py @@ -160,8 +160,7 @@ def get_product_name(plan_name): product_name = "" if plan_name and plan_name.startswith("generic-"): first_index_hyphen = plan_name.index("-") + 1 - product_id = plan_name[first_index_hyphen: - (plan_name[first_index_hyphen:].index("-")) + first_index_hyphen] + 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 From fdb790bac72473a39a33c4755a413d557169e3d8 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 16 Feb 2024 16:24:37 +0530 Subject: [PATCH 227/257] Use the actual plan name instead of the generic plan id --- datacenterlight/views.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 5c333a63..941f6fae 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -42,6 +42,8 @@ from .utils import ( get_cms_integration, create_vm, clear_all_session_vars, validate_vat_number ) +from datacenterlight.templatetags.custom_tags import get_product_name + logger = logging.getLogger(__name__) @@ -900,15 +902,21 @@ class OrderConfirmationView(DetailView, FormView): 2 ) ) - plan_name = "generic-{0}-{1:.2f}".format( + stripe_plan_id = "generic-{0}-{1:.2f}".format( request.session['generic_payment_details']['product_id'], amount_to_be_charged ) - stripe_plan_id = plan_name + try: + product = GenericProduct.objects.get(id=request.session['generic_payment_details']['product_id']) + plan_name = product.product_name + except Exception as ex: + logger.debug("Error {}" % str(ex)) + plan_name = get_product_name(stripe_plan_id) recurring_interval = request.session['generic_payment_details']['recurring_interval'] if recurring_interval == "year": - plan_name = "{}-yearly".format(plan_name) - stripe_plan_id = plan_name + stripe_plan_id = "{}-yearly".format(stripe_plan_id) + plan_name = "{} (yearly)".format(plan_name) + logger.debug("Plan name = {}, Stripe Plan id = {}".format(plan_name, stripe_plan_id)) else: template = request.session.get('template') specs = request.session.get('specs') From 169dc6b1ee536fea1475828f753260d4dc6eb439 Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Sat, 17 Feb 2024 18:27:44 +0900 Subject: [PATCH 228/257] Add initial script for image building --- build-image.sh | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100755 build-image.sh diff --git a/build-image.sh b/build-image.sh new file mode 100755 index 00000000..acfb789a --- /dev/null +++ b/build-image.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +if [ $# -lt 1 ]; then + echo "$0 imageversion [push]" + echo "If push is specified, also push to our harbor" + exit 1 +fi + +tagprefix=harbor.k8s.ungleich.ch/ungleich-public/dynamicweb +version=$1; shift + +tag=${tagprefix}:${version} + +docker build -t "${tag}" . From bd4d81c286d833d97a15750d3145c53344133eb0 Mon Sep 17 00:00:00 2001 From: "app@dynamicweb-production" Date: Sat, 17 Feb 2024 11:20:12 +0100 Subject: [PATCH 229/257] Show virtual machine plan page for admin --- hosting/views.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index 3ac8c023..ce59b0d9 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1544,7 +1544,12 @@ class VirtualMachinesPlanListView(LoginRequiredMixin, ListView): ordering = '-id' def get_queryset(self): - owner = self.request.user + username = self.request.GET.get('username') + if self.request.user.is_admin and username: + user = CustomUser.objects.get(username=username) + else: + user = self.request.user + owner = user manager = OpenNebulaManager(email=owner.username, password=owner.password) try: From 4eb470df1632ff055eac6b2a5f1f02b4df618d2e Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Sat, 17 Feb 2024 20:48:17 +0900 Subject: [PATCH 230/257] [docker] switch to python 3.5 --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 50b81cbb..d12c54ff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,5 @@ -FROM python:3.10.0-alpine3.15 +# FROM python:3.10.0-alpine3.15 +FROM python:3.5-alpine3.12 WORKDIR /usr/src/app From 0d7f10776c3c0ec0b4b1aaa82a07bddf45169071 Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Sat, 17 Feb 2024 20:49:31 +0900 Subject: [PATCH 231/257] [docker] push if build is ok --- build-image.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build-image.sh b/build-image.sh index acfb789a..0ac898ed 100755 --- a/build-image.sh +++ b/build-image.sh @@ -2,6 +2,7 @@ if [ $# -lt 1 ]; then echo "$0 imageversion [push]" + echo "Version could be: $(git describe --always)" echo "If push is specified, also push to our harbor" exit 1 fi @@ -11,4 +12,7 @@ version=$1; shift tag=${tagprefix}:${version} +set -ex + docker build -t "${tag}" . +docker push "${tag}" From f178be8395a95e5023756e2824efbadac2b44216 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 19 Feb 2024 20:42:34 +0530 Subject: [PATCH 232/257] Update .dockerignore: ignore .env --- .dockerignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.dockerignore b/.dockerignore index 6b8710a7..a715c9d7 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,2 @@ .git +.env From 893c8168469382426d59b93606baf2e1e6dc4520 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 19 Feb 2024 20:43:33 +0530 Subject: [PATCH 233/257] Add apks required for the build on alpine 3.12 --- Dockerfile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d12c54ff..bee11218 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,12 @@ RUN apk add --update --no-cache \ build-base \ openldap-dev \ python3-dev \ - libpq-dev \ + postgresql-dev \ + jpeg-dev \ + libxml2-dev \ + libxslt-dev \ + libmemcached-dev \ + zlib-dev \ && rm -rf /var/cache/apk/* # FIX https://github.com/python-ldap/python-ldap/issues/432 From b6ff2b62c1da90a8a4c5bfebe10f6edb573d57fa Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 19 Feb 2024 20:44:17 +0530 Subject: [PATCH 234/257] Add comment for alpine 3.14 requirement --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index bee11218..7b5440af 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,8 @@ RUN apk add --update --no-cache \ zlib-dev \ && rm -rf /var/cache/apk/* +## For alpine 3.15 replace postgresql-dev with libpq-dev + # FIX https://github.com/python-ldap/python-ldap/issues/432 RUN echo 'INPUT ( libldap.so )' > /usr/lib/libldap_r.so From 8d13629c8b775c63cc8dd8fda7412c36fd09aefa Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 19 Feb 2024 20:49:29 +0530 Subject: [PATCH 235/257] Use proper library path for Pillow --- Dockerfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7b5440af..395ebcc9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,5 +22,8 @@ RUN apk add --update --no-cache \ RUN echo 'INPUT ( libldap.so )' > /usr/lib/libldap_r.so COPY requirements.txt ./ -RUN pip install --no-cache-dir -r requirements.txt + +# Pillow seems to need LIBRARY_PATH set as follows: (see: https://github.com/python-pillow/Pillow/issues/1763#issuecomment-222383534) +RUN LIBRARY_PATH=/lib:/usr/lib /bin/sh -c "pip install --no-cache-dir -r requirements.txt" + COPY ./ . From 9e98125b13e400d0c083589b252527872fa58a56 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 19 Feb 2024 20:50:10 +0530 Subject: [PATCH 236/257] Use the same django-filer version being used on production --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 8d04a189..73cdf987 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,7 +25,7 @@ django-compressor==2.0 django-debug-toolbar==1.4 python-dotenv==0.10.3 django-extensions==1.6.7 -django-filer==2.1.2 +django-filer==1.2.0 django-filter==0.13.0 django-formtools==1.0 django-guardian==1.4.4 From 26f3a1881decdcc98c0ae9add4a627af0833a4b7 Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Tue, 20 Feb 2024 16:25:13 +0900 Subject: [PATCH 237/257] make build image only push if push is specified --- build-image.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/build-image.sh b/build-image.sh index 0ac898ed..64a67fd6 100755 --- a/build-image.sh +++ b/build-image.sh @@ -15,4 +15,9 @@ tag=${tagprefix}:${version} set -ex docker build -t "${tag}" . -docker push "${tag}" + +push=$1; shift + +if [ "$push" ]; then + docker push "${tag}" +fi From 8b06c2c6e9b599ce887f8e26f451878511ba3557 Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Tue, 20 Feb 2024 17:18:29 +0900 Subject: [PATCH 238/257] docker: add entrypoint and add support for dynamic config --- Dockerfile | 3 +++ dynamicweb/settings/base.py | 6 ++++++ entrypoint.sh | 19 +++++++++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 entrypoint.sh diff --git a/Dockerfile b/Dockerfile index 395ebcc9..4c1a9a66 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,3 +27,6 @@ COPY requirements.txt ./ RUN LIBRARY_PATH=/lib:/usr/lib /bin/sh -c "pip install --no-cache-dir -r requirements.txt" COPY ./ . +COPY entrypoint.sh / + +ENTRYPOINT ["/entrypoint.sh" ] diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index f03042dc..b7705e17 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -777,3 +777,9 @@ if DEBUG: from .local import * # flake8: noqa else: from .prod import * # flake8: noqa + +# Try to load dynamic configuration, if it exists +try: + from .dynamic import * # flake8: noqa +except ImportError: + pass diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 00000000..e207a457 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +set -uex + +cd /usr/src/app/ +cat > dynamicweb/settings/dynamic.py < Date: Tue, 20 Feb 2024 17:19:05 +0900 Subject: [PATCH 239/257] entrypoint: make executable --- entrypoint.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 entrypoint.sh diff --git a/entrypoint.sh b/entrypoint.sh old mode 100644 new mode 100755 From c4f6780a0e0c4ac91b6636e2725dc0b581a6c361 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 22 Mar 2024 16:54:54 +0530 Subject: [PATCH 240/257] Add management command fix_generic_stripe_plan_product_names.py --- .../fix_generic_stripe_plan_product_names.py | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 datacenterlight/management/commands/fix_generic_stripe_plan_product_names.py diff --git a/datacenterlight/management/commands/fix_generic_stripe_plan_product_names.py b/datacenterlight/management/commands/fix_generic_stripe_plan_product_names.py new file mode 100644 index 00000000..2773009d --- /dev/null +++ b/datacenterlight/management/commands/fix_generic_stripe_plan_product_names.py @@ -0,0 +1,54 @@ +from django.core.management.base import BaseCommand +from datacenterlight.tasks import handle_metadata_and_emails +from datacenterlight.models import StripePlan +from opennebula_api.models import OpenNebulaManager +from membership.models import CustomUser +from hosting.models import GenericProduct +import logging +import json +import sys +import stripe + +logger = logging.getLogger(__name__) + + +class Command(BaseCommand): + help = '''Stripe plans created before version 3.4 saved the plan name like generic-{subscription_id}-amount. This + command aims at replacing this with the actual product name + ''' + + def handle(self, *args, **options): + cnt = 0 + self.stdout.write( + self.style.SUCCESS( + 'In Fix generic stripe plan product names' + ) + ) + plans_to_change = StripePlan.objects.filter(stripe_plan_id__startswith='generic') + for plan in plans_to_change: + response = input("Press 'y' to continue: ") + + # Check if the user entered 'y' + if response.lower() == 'y': + plan_name = plan.stripe_plan_id + first_index_hyphen = plan_name.index("-") + 1 + product_id = plan_name[ + first_index_hyphen:(plan_name[first_index_hyphen:].index("-")) + first_index_hyphen] + gp = GenericProduct.objects.get(id=product_id) + if gp: + cnt += 1 + # update stripe + sp = stripe.Plan.retrieve(plan_name) + pr = stripe.Product.retrieve(sp.product) + pr.name = gp.product_name + pr.save() + # update local + spl = StripePlan.objects.get(stripe_plan_id=plan_name) + spl.stripe_plan_name = gp.product_name + spl.save() + print("%s. %s => %s" % (cnt, plan_name, gp.product_name)) + else: + print("Invalid input. Please try again.") + sys.exit() + + print("Done") \ No newline at end of file From 92cfe71bbe0012b4fcab5a37c26b1f000e60641e Mon Sep 17 00:00:00 2001 From: "app@dynamicweb-production" Date: Wed, 8 May 2024 09:57:56 +0200 Subject: [PATCH 241/257] Fix invoices list not showing one time charges --- hosting/templates/hosting/invoices.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index 69ed4577..347b1ff4 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -87,7 +87,7 @@ {% for ho, stripe_charge_data in invs_charge %} - {{ ho | get_line_item_from_hosting_order_charge }} + {{ ho.id | get_line_item_from_hosting_order_charge }} {% endfor %} From 4f8bd3b45fda38ef5391ddb6a37424f02259ffa5 Mon Sep 17 00:00:00 2001 From: "app@dynamicweb-production" Date: Thu, 20 Jun 2024 13:58:19 +0200 Subject: [PATCH 242/257] Fix stripe_chgs object; make it consistent --- hosting/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hosting/views.py b/hosting/views.py index ce59b0d9..637a112b 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1339,7 +1339,7 @@ class InvoiceListView(LoginRequiredMixin, TemplateView): ).order_by('-created_at') stripe_chgs = [] for ho in hosting_orders: - stripe_chgs.append({ho.id: stripe.Charge.retrieve(ho.stripe_charge_id)}) + stripe_chgs.append({ho: stripe.Charge.retrieve(ho.stripe_charge_id)}) paginator_charges = Paginator(stripe_chgs, 10) try: From 3736f522ddee781c086d0f2bc9f22b470fe78e4a Mon Sep 17 00:00:00 2001 From: "app@dynamicweb-production" Date: Thu, 20 Jun 2024 14:00:50 +0200 Subject: [PATCH 243/257] Debug messages --- datacenterlight/templatetags/custom_tags.py | 1 + datacenterlight/views.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/datacenterlight/templatetags/custom_tags.py b/datacenterlight/templatetags/custom_tags.py index 4fcf05a2..fc39eec7 100644 --- a/datacenterlight/templatetags/custom_tags.py +++ b/datacenterlight/templatetags/custom_tags.py @@ -73,6 +73,7 @@ def get_line_item_from_hosting_order_charge(hosting_order_id): :return: """ try: + print("Hositng order id = %s" % hosting_order_id) hosting_order = HostingOrder.objects.get(id = hosting_order_id) if hosting_order.stripe_charge_id: return mark_safe(""" diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 941f6fae..1c770b3a 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -910,7 +910,7 @@ class OrderConfirmationView(DetailView, FormView): product = GenericProduct.objects.get(id=request.session['generic_payment_details']['product_id']) plan_name = product.product_name except Exception as ex: - logger.debug("Error {}" % str(ex)) + logger.debug("Errori {}" % str(ex)) plan_name = get_product_name(stripe_plan_id) recurring_interval = request.session['generic_payment_details']['recurring_interval'] if recurring_interval == "year": @@ -1009,6 +1009,8 @@ class OrderConfirmationView(DetailView, FormView): # due to some reason. So, we would want to dissociate this card # here. # ... + logger.debug("In 1 ***") + logger.debug("stripe_subscription_obj == %s" % stripe_subscription_obj) msg = subscription_result.get('error') return show_error(msg, self.request) elif stripe_subscription_obj.status == 'incomplete': @@ -1222,6 +1224,7 @@ def set_user_card(card_id, stripe_api_cus_id, custom_user, stripe_customer=custom_user.stripecustomer, card_details=card_details_response ) + logger.debug("ucd = %s" % ucd) UserCardDetail.save_default_card_local( custom_user.stripecustomer.stripe_id, ucd.card_id @@ -1231,6 +1234,7 @@ def set_user_card(card_id, stripe_api_cus_id, custom_user, 'brand': ucd.brand, 'card_id': ucd.card_id } + logger.debug("card_detail_dict = %s" % card_details_dict) return card_details_dict @@ -1435,6 +1439,7 @@ def do_provisioning(request, stripe_api_cus_id, card_details_response, card_details_dict = set_user_card(card_id, stripe_api_cus_id, custom_user, card_details_response) + logger.debug("after set_user_card %s" % card_details_dict) # Save billing address billing_address_data.update({ @@ -1457,6 +1462,7 @@ def do_provisioning(request, stripe_api_cus_id, card_details_response, vat_number=billing_address_data['vat_number'] ) billing_address.save() + logger.debug("billing_address saved") order = HostingOrder.create( price=request['generic_payment_details']['amount'], @@ -1464,6 +1470,7 @@ def do_provisioning(request, stripe_api_cus_id, card_details_response, billing_address=billing_address, vm_pricing=VMPricing.get_default_pricing() ) + logger.debug("hosting order created") # Create a Hosting Bill HostingBill.create(customer=stripe_cus, @@ -1520,7 +1527,9 @@ def do_provisioning(request, stripe_api_cus_id, card_details_response, ["%s=%s" % (k, v) for (k, v) in context.items()]), 'reply_to': [context['email']], } + logger.debug("Sending email") send_plain_email_task.delay(email_data) + logger.debug("After Sending email") recurring_text = _(" This is a monthly recurring plan.") if gp_details['recurring_interval'] == "year": recurring_text = _(" This is an yearly recurring plan.") @@ -1544,6 +1553,7 @@ def do_provisioning(request, stripe_api_cus_id, card_details_response, ), 'reply_to': ['info@ungleich.ch'], } + logger.debug("Before Sending customer email") send_plain_email_task.delay(email_data) redirect_url = reverse('datacenterlight:index') logger.debug("Sent user/admin emails") From 94a9ac31473d470840f08bc837c2ed086bfbe636 Mon Sep 17 00:00:00 2001 From: "app@dynamicweb-production" Date: Thu, 26 Sep 2024 11:13:56 +0200 Subject: [PATCH 244/257] Add change_fi_vatrate_2024.py management command --- .../commands/change_fi_vatrate_2024.py | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 hosting/management/commands/change_fi_vatrate_2024.py diff --git a/hosting/management/commands/change_fi_vatrate_2024.py b/hosting/management/commands/change_fi_vatrate_2024.py new file mode 100644 index 00000000..6947b6eb --- /dev/null +++ b/hosting/management/commands/change_fi_vatrate_2024.py @@ -0,0 +1,143 @@ +from django.core.management.base import BaseCommand +import datetime +import csv +import logging +import stripe +from hosting.models import VATRates, StripeTaxRate +from utils.hosting_utils import get_vat_rate_for_country +from django.conf import settings +from membership.models import CustomUser, StripeCustomer + +stripe.api_key = settings.STRIPE_API_PRIVATE_KEY + +logger = logging.getLogger(__name__) + +class Command(BaseCommand): + help = '''FI vat rate changes on 2024-09-01 from 24% to 25.5%. This commands makes the necessary changes''' + + def handle(self, *args, **options): + MAKE_MODIFS=False + try: + country_to_change = 'FI' + currency_to_change = 'EUR' + new_rate = 25.5 + user_country_vat_rate = get_vat_rate_for_country(country_to_change) + logger.debug("Existing VATRate for %s %s " % (country_to_change, user_country_vat_rate)) + vat_rate = VATRates.objects.get( + territory_codes=country_to_change, start_date__isnull=False, stop_date=None + ) + logger.debug("VAT rate for %s is %s" % (country_to_change, vat_rate.rate)) + logger.debug("vat_rate object = %s" % vat_rate) + logger.debug("Create end date for the VATRate %s" % vat_rate.id) + #if MAKE_MODIFS: + # vat_rate.stop_date = datetime.date(2024, 8, 31) + # vat_rate.save() + # print("Creating a new VATRate for FI") + # obj, created = VATRates.objects.get_or_create( + # start_date=datetime.date(2024, 9, 1), + # stop_date=None, + # territory_codes=country_to_change, + # currency_code=currency_to_change, + # rate=new_rate * 0.01, + # rate_type="standard", + # description="FINLAND standard VAT (added manually on %s)" % datetime.datetime.now() + # ) + # if created: + # logger.debug("Created new VAT Rate for %s with the new rate %s" % (country_to_change, new_rate)) + # logger.debug(obj) + # else: + # logger.debug("VAT Rate for %s already exists with the rate %s" % (country_to_change, new_rate)) + logger.debug("Getting all subscriptions of %s that need a VAT Rate change") + subscriptions = stripe.Subscription.list(limit=100) # Increase the limit to 100 per page (maximum) + fi_subs = [] + + while subscriptions: + for subscription in subscriptions: + if len(subscription.default_tax_rates) > 0 and subscription.default_tax_rates[0].jurisdiction and subscription.default_tax_rates[0].jurisdiction.lower() == 'fi': + fi_subs.append(subscription) + elif len(subscription.default_tax_rates) > 0: + print("subscription %s belongs to %s" % (subscription.id, subscription.default_tax_rates[0].jurisdiction)) + else: + print("subscription %s does not have a tax rate" % subscription.id) + if subscriptions.has_more: + print("FETCHING MORE") + subscriptions = stripe.Subscription.list(limit=100, starting_after=subscriptions.data[-1]) + else: + break + logger.debug("There are %s FI subscription that need VAT rate update" % len(fi_subs)) + + # CSV column headers + csv_headers = [ + "customer_name", + "customer_email", + "stripe_customer_id", + "subscription_id", + "subscription_name", + "amount", + "vat_rate" + ] + # CSV file name + csv_filename = "fi_subscriptions_change_2024.csv" + # Write subscription data to CSV file + with open(csv_filename, mode='w', newline='') as csv_file: + writer = csv.DictWriter(csv_file, fieldnames=csv_headers) + writer.writeheader() + + for subscription in fi_subs: + subscription_id = subscription["id"] + stripe_customer_id = subscription.get("customer", "") + vat_rate = subscription.get("tax_percent", "") + c_user = CustomUser.objects.get( + id=StripeCustomer.objects.filter(stripe_id=stripe_customer_id)[0].user.id) + if c_user: + customer_name = c_user.name.encode('utf-8') + customer_email = c_user.email + items = subscription.get("items", {}).get("data", []) + for item in items: + subscription_name = item.get("plan", {}).get("id", "") + amount = item.get("plan", {}).get("amount", "") + + # Convert amount to a proper format (e.g., cents to dollars) + amount_in_chf = amount / 100 # Adjust this conversion as needed + + # Writing to CSV + writer.writerow({ + "customer_name": customer_name, + "customer_email": customer_email, + "stripe_customer_id": stripe_customer_id, + "subscription_id": subscription_id, + "subscription_name": subscription_name, + "amount": amount_in_chf, + "vat_rate": vat_rate # Fill in VAT rate if available + }) + else: + print("No customuser for %s %s" % (stripe_customer_id, subscription_id)) + + + if MAKE_MODIFS: + print("Making modifications now") + tax_rate_obj = stripe.TaxRate.create( + display_name="VAT", + description="VAT for %s" % country_to_change, + jurisdiction=country_to_change, + percentage=new_rate, + 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 + ) + + for fi_sub in fi_subs: + fi_sub.default_tax_rates = [stripe_tax_rate.tax_rate_id] + fi_sub.save() + logger.debug("Default tax rate updated for %s" % fi_sub.id) + else: + print("Not making any modifications because MAKE_MODIFS=False") + + except Exception as e: + print(" *** Error occurred. Details {}".format(str(e))) From 86836f3d1eadf2988a491258a966678b7ef5e2d4 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 27 Mar 2025 15:39:24 +0530 Subject: [PATCH 245/257] Check those VMs that are displayed to the public --- datacenterlight/management/commands/check_vm_templates.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/datacenterlight/management/commands/check_vm_templates.py b/datacenterlight/management/commands/check_vm_templates.py index db36fde8..c550cd5d 100644 --- a/datacenterlight/management/commands/check_vm_templates.py +++ b/datacenterlight/management/commands/check_vm_templates.py @@ -1,6 +1,7 @@ from django.core.management.base import BaseCommand from opennebula_api.models import OpenNebulaManager from datacenterlight.models import VMTemplate +from datacenterlight.cms_models import DCLCalculatorPluginModel from membership.models import CustomUser from django.conf import settings @@ -31,7 +32,11 @@ class Command(BaseCommand): PROJECT_PATH = os.path.abspath(os.path.dirname(__name__)) if not os.path.exists("%s/outputs" % PROJECT_PATH): os.mkdir("%s/outputs" % PROJECT_PATH) - for vm_template in VMTemplate.objects.all(): + public_templates_plugin = DCLCalculatorPluginModel.objects.filter(cmsplugin_ptr_id=23356).first() + ipv6_templates_plugin = DCLCalculatorPluginModel.objects.filter(cmsplugin_ptr_id=21943).first() + templates = public_templates_plugin.vm_templates_to_show + ipv6_templates_plugin.vm_templates_to_show + vm_templates = VMTemplate.objects.filter(opennebula_vm_template_id__in=templates) + for vm_template in vm_templates: vm_name = 'test-%s' % vm_template.name vm_id = manager.create_vm( template_id=vm_template.opennebula_vm_template_id, From 8e9315b12de1670837722e2e5163f679381b26a8 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 27 Mar 2025 16:13:22 +0530 Subject: [PATCH 246/257] Fix separate messages for errors and inform admin about errors --- .../management/commands/check_vm_templates.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/datacenterlight/management/commands/check_vm_templates.py b/datacenterlight/management/commands/check_vm_templates.py index c550cd5d..365ca1f6 100644 --- a/datacenterlight/management/commands/check_vm_templates.py +++ b/datacenterlight/management/commands/check_vm_templates.py @@ -3,7 +3,7 @@ from opennebula_api.models import OpenNebulaManager from datacenterlight.models import VMTemplate from datacenterlight.cms_models import DCLCalculatorPluginModel from membership.models import CustomUser - +from utils.tasks import send_plain_email_task from django.conf import settings from time import sleep import datetime @@ -22,6 +22,7 @@ class Command(BaseCommand): def handle(self, *args, **options): result_dict = {} + error_dict = {} user_email = options['user_email'] if 'user_email' in options else "" if user_email: @@ -37,7 +38,7 @@ class Command(BaseCommand): templates = public_templates_plugin.vm_templates_to_show + ipv6_templates_plugin.vm_templates_to_show vm_templates = VMTemplate.objects.filter(opennebula_vm_template_id__in=templates) for vm_template in vm_templates: - vm_name = 'test-%s' % vm_template.name + vm_name = 'test-%s-%s' % (vm_template.vm_type, vm_template.name) vm_id = manager.create_vm( template_id=vm_template.opennebula_vm_template_id, specs=specs, @@ -57,6 +58,7 @@ class Command(BaseCommand): %s %s''' % (vm_name, vm_template.opennebula_vm_template_id, vm_template.vm_type) + error_dict[vm_name] = result_dict[vm_name] self.stdout.write(self.style.ERROR(result_dict[vm_name])) sleep(1) date_str = datetime.datetime.strftime( @@ -67,4 +69,11 @@ class Command(BaseCommand): 'w', encoding='utf-8') as f: f.write(json.dumps(result_dict)) + email_data = { + 'subject': 'Check VM Templates ERROR', + 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': [settings.ADMIN_EMAIL], + 'body': json.dumps(error_dict), + } + send_plain_email_task.delay(email_data) self.stdout.write(self.style.SUCCESS("Done")) From ca099d8c83a5d92651a22855b88b9ca84f6b9004 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 27 Mar 2025 16:21:33 +0530 Subject: [PATCH 247/257] Send error message only if there is an error --- .../management/commands/check_vm_templates.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/datacenterlight/management/commands/check_vm_templates.py b/datacenterlight/management/commands/check_vm_templates.py index 365ca1f6..d30e5b0d 100644 --- a/datacenterlight/management/commands/check_vm_templates.py +++ b/datacenterlight/management/commands/check_vm_templates.py @@ -69,11 +69,12 @@ class Command(BaseCommand): 'w', encoding='utf-8') as f: f.write(json.dumps(result_dict)) - email_data = { - 'subject': 'Check VM Templates ERROR', - 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, - 'to': [settings.ADMIN_EMAIL], - 'body': json.dumps(error_dict), - } - send_plain_email_task.delay(email_data) + if error_dict: + email_data = { + 'subject': 'Check VM Templates ERROR', + 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': [settings.ADMIN_EMAIL], + 'body': json.dumps(error_dict), + } + send_plain_email_task.delay(email_data) self.stdout.write(self.style.SUCCESS("Done")) From fc1dc37840619cf5999aa00ab9582f0c067b4aa2 Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 29 Mar 2025 12:55:17 +0530 Subject: [PATCH 248/257] Update .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 2d923e99..a41d813d 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,6 @@ secret-key !.gitkeep *.orig .vscode/settings.json +2024-* +2025-* +2023-* From 60ddfa78b066849630857e7595fd121a93079810 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 16 May 2025 16:05:50 +0530 Subject: [PATCH 249/257] Handle getting the correct user_card_detail Fixes Traceback (most recent call last): File "", line 2, in File "/home/app/pyvenv/lib/python3.5/site-packages/django/db/models/manager.py", line 122, in manager_method return getattr(self.get_queryset(), name)(*args, **kwargs) File "/home/app/pyvenv/lib/python3.5/site-packages/django/db/models/query.py", line 391, in get (self.model._meta.object_name, num) hosting.models.MultipleObjectsReturned: get() returned more than one UserCardDetail -- it returned 2! --- datacenterlight/views.py | 6 +-- hosting/models.py | 88 +++++++++++++++++++++++++++++++++++++++- hosting/views.py | 6 +-- 3 files changed, 93 insertions(+), 7 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 1c770b3a..709d1c44 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -613,7 +613,7 @@ class OrderConfirmationView(DetailView, FormView): # TODO check when we go through this case (to me, it seems useless) card_id = self.request.session.get('card_id') logger.debug("NO id_payment_method, using card: %s" % card_id) - card_detail = UserCardDetail.objects.get(id=card_id) + card_detail = UserCardDetail.get_ucd_from_card_id(card_id=card_id) context['cc_last4'] = card_detail.last4 context['cc_brand'] = card_detail.brand context['cc_exp_year'] = card_detail.exp_year @@ -852,7 +852,7 @@ class OrderConfirmationView(DetailView, FormView): return show_error(msg, self.request) elif 'card_id' in request.session: card_id = request.session.get('card_id') - user_card_detail = UserCardDetail.objects.get(id=card_id) + user_card_detail = UserCardDetail.get_ucd_from_card_id(card_id=card_id) card_details_dict = { 'last4': user_card_detail.last4, 'brand': user_card_detail.brand, @@ -1206,7 +1206,7 @@ def set_user_card(card_id, stripe_api_cus_id, custom_user, card_details_response): if card_id: logger.debug("card_id %s was in request" % card_id) - user_card_detail = UserCardDetail.objects.get(id=card_id) + user_card_detail = UserCardDetail.get_ucd_from_card_id(card_id=card_id) card_details_dict = { 'last4': user_card_detail.last4, 'brand': user_card_detail.brand, diff --git a/hosting/models.py b/hosting/models.py index b51d8616..5cc3d0fd 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -700,7 +700,7 @@ class UserCardDetail(AssignPermissionsMixin, models.Model): @staticmethod def save_default_card_local(stripe_api_cus_id, card_id): stripe_cust = StripeCustomer.objects.get(stripe_id=stripe_api_cus_id) - user_card_detail = UserCardDetail.objects.get( + user_card_detail = UserCardDetail.get_ucd_from_stripe_cust_n_card_id( stripe_customer=stripe_cust, card_id=card_id ) for card in stripe_cust.usercarddetail_set.all(): @@ -709,6 +709,92 @@ class UserCardDetail(AssignPermissionsMixin, models.Model): user_card_detail.preferred = True user_card_detail.save() + @staticmethod + def get_ucd_from_card_id(card_id): + try: + user_card_details = UserCardDetail.objects.filter( + card_id=card_id + ).order_by('-id') + + if user_card_details.count() > 1: + # Log a warning about the duplicate entries + logger.warning( + f"Multiple UserCardDetail objects found for card_id={card_id}. " + f"Found {user_card_details.count()} objects. Using the latest one." + ) + # Use the first object found + user_card_detail = user_card_details.first() + elif user_card_details.count() == 1: + # Exactly one object found, proceed as intended + user_card_detail = user_card_details.first() + else: + # No object found for the given customer and card_id. + # Depending on expected behavior, you might want to raise an error or handle this case. + # If the original get() call happened here, it would raise DoesNotExist. + logger.error( + f"No UserCardDetail found for card_id={card_id}." + ) + raise UserCardDetail.DoesNotExist(f"No UserCardDetail found for card {card_id}") + if user_card_details.count() > 1: + # Log a warning about the duplicate entries + logger.warning( + f"Multiple UserCardDetail objects found for card_id={card_id}. " + f"Found {user_card_details.count()} objects. Using the first one." + ) + # Use the first object found + user_card_detail = user_card_details.first() + elif user_card_details.count() == 1: + # Exactly one object found, proceed as intended + user_card_detail = user_card_details.first() + else: + # No object found for the given customer and card_id. + # Depending on expected behavior, you might want to raise an error or handle this case. + # If the original get() call happened here, it would raise DoesNotExist. + logger.error( + f"No UserCardDetail found for card_id={card_id}." + ) + raise UserCardDetail.DoesNotExist(f"No UserCardDetail found for card {card_id}") + except Exception as e: + # Catch other potential exceptions during the filter/get process if necessary + logger.error(f"An unexpected error occurred while fetching UserCardDetail: {e}") + raise + return user_card_detail + + + @staticmethod + def get_ucd_from_stripe_cust_n_card_id(stripe_cust, card_id): + try: + user_card_details = UserCardDetail.objects.filter( + stripe_customer=stripe_cust, card_id=card_id + ).order_by('-id') + + if user_card_details.count() > 1: + # Log a warning about the duplicate entries + logger.warning( + f"Multiple UserCardDetail objects found for stripe_customer_id={stripe_cust.id} and card_id={card_id}. " + f"Found {user_card_details.count()} objects. Using the first one." + ) + # Use the first object found + user_card_detail = user_card_details.first() + elif user_card_details.count() == 1: + # Exactly one object found, proceed as intended + user_card_detail = user_card_details.first() + else: + # No object found for the given customer and card_id. + # Depending on expected behavior, you might want to raise an error or handle this case. + # If the original get() call happened here, it would raise DoesNotExist. + logger.error( + f"No UserCardDetail found for stripe_customer_id={stripe_cust.id} and card_id={card_id}." + ) + raise UserCardDetail.DoesNotExist(f"No UserCardDetail found for customer {stripe_cust.id}, card {card_id}") + + except Exception as e: + # Catch other potential exceptions during the filter/get process if necessary + logger.error(f"An unexpected error occurred while fetching UserCardDetail: {e}") + raise + return user_card_detail + + @staticmethod def get_user_card_details(stripe_customer, card_details): """ diff --git a/hosting/views.py b/hosting/views.py index 637a112b..c2470707 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -810,7 +810,7 @@ class PaymentVMView(LoginRequiredMixin, FormView): card_id = form.cleaned_data.get('card') customer = owner.stripecustomer try: - user_card_detail = UserCardDetail.objects.get(id=card_id) + user_card_detail = UserCardDetail.get_ucd_from_card_id(card_id=card_id) if not request.user.has_perm( 'view_usercarddetail', user_card_detail ): @@ -1014,7 +1014,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): context['cc_exp_month'] = card_details_response['exp_month'] else: card_id = self.request.session.get('card_id') - card_detail = UserCardDetail.objects.get(id=card_id) + card_detail = UserCardDetail.get_ucd_from_card_id(card_id=card_id) context['cc_last4'] = card_detail.last4 context['cc_brand'] = card_detail.brand context['cc_exp_year'] = card_detail.exp_year @@ -1128,7 +1128,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): return JsonResponse(response) else: card_id = request.session.get('card_id') - user_card_detail = UserCardDetail.objects.get(id=card_id) + user_card_detail = UserCardDetail.get_ucd_from_card_id(card_id=card_id) card_details_dict = { 'last4': user_card_detail.last4, 'brand': user_card_detail.brand, From b6239159b2765594534a2568406848ce7ac53c67 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 21 May 2025 11:17:15 +0530 Subject: [PATCH 250/257] Remove f-strings to make things compatible --- hosting/models.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/hosting/models.py b/hosting/models.py index 5cc3d0fd..f9bdc751 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -719,8 +719,10 @@ class UserCardDetail(AssignPermissionsMixin, models.Model): if user_card_details.count() > 1: # Log a warning about the duplicate entries logger.warning( - f"Multiple UserCardDetail objects found for card_id={card_id}. " - f"Found {user_card_details.count()} objects. Using the latest one." + "Multiple UserCardDetail objects found for card_id={}. " + "Found {} objects. Using the latest one.".format( + card_id, user_card_details.count() + ) ) # Use the first object found user_card_detail = user_card_details.first() @@ -732,14 +734,16 @@ class UserCardDetail(AssignPermissionsMixin, models.Model): # Depending on expected behavior, you might want to raise an error or handle this case. # If the original get() call happened here, it would raise DoesNotExist. logger.error( - f"No UserCardDetail found for card_id={card_id}." + "No UserCardDetail found for card_id={}.".format(card_id) ) - raise UserCardDetail.DoesNotExist(f"No UserCardDetail found for card {card_id}") + raise UserCardDetail.DoesNotExist("No UserCardDetail found for card {}".format(card_id)) if user_card_details.count() > 1: # Log a warning about the duplicate entries logger.warning( - f"Multiple UserCardDetail objects found for card_id={card_id}. " - f"Found {user_card_details.count()} objects. Using the first one." + "Multiple UserCardDetail objects found for card_id={}. " + "Found {} objects. Using the first one.".format( + card_id, user_card_details.count() + ) ) # Use the first object found user_card_detail = user_card_details.first() @@ -751,16 +755,15 @@ class UserCardDetail(AssignPermissionsMixin, models.Model): # Depending on expected behavior, you might want to raise an error or handle this case. # If the original get() call happened here, it would raise DoesNotExist. logger.error( - f"No UserCardDetail found for card_id={card_id}." + "No UserCardDetail found for card_id={}.".format(card_id) ) - raise UserCardDetail.DoesNotExist(f"No UserCardDetail found for card {card_id}") + raise UserCardDetail.DoesNotExist("No UserCardDetail found for card {}".format(card_id)) except Exception as e: # Catch other potential exceptions during the filter/get process if necessary - logger.error(f"An unexpected error occurred while fetching UserCardDetail: {e}") + logger.error("An unexpected error occurred while fetching UserCardDetail: {}".format(e)) raise return user_card_detail - @staticmethod def get_ucd_from_stripe_cust_n_card_id(stripe_cust, card_id): try: From 8ffa2d9c3c597fc7da02e13d3bc05c456bfd4066 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 21 May 2025 11:27:22 +0530 Subject: [PATCH 251/257] Replace more f-strings --- hosting/models.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/hosting/models.py b/hosting/models.py index f9bdc751..4c039fef 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -774,8 +774,10 @@ class UserCardDetail(AssignPermissionsMixin, models.Model): if user_card_details.count() > 1: # Log a warning about the duplicate entries logger.warning( - f"Multiple UserCardDetail objects found for stripe_customer_id={stripe_cust.id} and card_id={card_id}. " - f"Found {user_card_details.count()} objects. Using the first one." + "Multiple UserCardDetail objects found for stripe_customer_id={} and card_id={}. " + "Found {} objects. Using the first one.".format( + stripe_cust.id, card_id, user_card_details.count() + ) ) # Use the first object found user_card_detail = user_card_details.first() @@ -787,13 +789,19 @@ class UserCardDetail(AssignPermissionsMixin, models.Model): # Depending on expected behavior, you might want to raise an error or handle this case. # If the original get() call happened here, it would raise DoesNotExist. logger.error( - f"No UserCardDetail found for stripe_customer_id={stripe_cust.id} and card_id={card_id}." + "No UserCardDetail found for stripe_customer_id={} and card_id={}.".format( + stripe_cust.id, card_id + ) + ) + raise UserCardDetail.DoesNotExist( + "No UserCardDetail found for customer {}, card {}".format( + stripe_cust.id, card_id + ) ) - raise UserCardDetail.DoesNotExist(f"No UserCardDetail found for customer {stripe_cust.id}, card {card_id}") except Exception as e: # Catch other potential exceptions during the filter/get process if necessary - logger.error(f"An unexpected error occurred while fetching UserCardDetail: {e}") + logger.error("An unexpected error occurred while fetching UserCardDetail: {}".format(e)) raise return user_card_detail From 2ec89b615734c2ef8af68c57ab7cf320be5c21f4 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 21 May 2025 15:23:13 +0530 Subject: [PATCH 252/257] Print provisioning parameters --- datacenterlight/views.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 709d1c44..9a4bdd51 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1428,6 +1428,30 @@ def do_provisioning(request, stripe_api_cus_id, card_details_response, """ logger.debug("do_provisioning") + + try: + params = { + 'request': request, + 'stripe_api_cus_id': stripe_api_cus_id, + 'card_details_response': card_details_response, + 'stripe_subscription_obj': stripe_subscription_obj, + 'stripe_onetime_charge': stripe_onetime_charge, + 'gp_details': gp_details, + 'specs': specs, + 'vm_template_id': vm_template_id, + 'template': template, + 'billing_address_data': billing_address_data, + 'real_request': real_request + } + print("Input Parameters:") + for key, value in params.items(): + try: + print("{}: {}".format(key, value)) + except Exception as e: + print("{}: [Error printing value: {}]".format(key, str(e))) + except Exception as e: + print("Error printing parameters: {}".format(str(e))) + user = request.get('user', None) # Create user if the user is not logged in and if he is not already From 4c0a1fd07f768318899265d0627deba321017009 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 27 May 2025 10:08:09 +0530 Subject: [PATCH 253/257] Debug --- datacenterlight/views.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 9a4bdd51..9ce2efd9 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -201,6 +201,12 @@ class IndexView(CreateView): ssd_size=storage, pricing_name=vm_pricing_name ) + if request.user.id == 51: + print("User is test") + price = 0 + vat = 0 + vat_percent = 0 + discount = 0 specs = { 'cpu': cores, 'memory': memory, @@ -588,6 +594,9 @@ class OrderConfirmationView(DetailView, FormView): context = {} # this is amount to be charge/subscribed before VAT and discount # and expressed in chf. To convert to cents, multiply by 100 + print("******************************") + print("User = {}, ID = {}".format(request.user, request.user.id)) + print("******************************") amount_to_charge = 0 vm_specs = None if (('specs' not in request.session or 'user' not in request.session) @@ -1621,7 +1630,7 @@ def do_provisioning(request, stripe_api_cus_id, card_details_response, def get_error_response_dict(msg, request): - logger.error(msg) + logger.debug("Init get_error_response_dict {}".format(msg)) response = { 'status': False, 'redirect': "{url}#{section}".format( From 4e978f62eaa2f3c90520a4f8b16a4b7ae9d37834 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 27 May 2025 10:47:06 +0530 Subject: [PATCH 254/257] More debug --- datacenterlight/views.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 9ce2efd9..1e486242 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -656,6 +656,12 @@ class OrderConfirmationView(DetailView, FormView): pricing_name=vm_specs['pricing_name'], vat_rate=user_country_vat_rate * 100 ) + if request.user.id == 51: + print("User is test") + price = 0 + vat = 0 + vat_percent = 0 + discount = {"amount": 0} vm_specs["price"] = price vm_specs["price_after_discount"] = price - discount["amount"] amount_to_charge = price From 018c3b5bba95c2c4ad435c62aa839dc39d985e90 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 27 May 2025 10:58:33 +0530 Subject: [PATCH 255/257] More debugging --- datacenterlight/views.py | 6 ++++-- hosting/models.py | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 1e486242..331e0fc9 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1219,6 +1219,7 @@ def get_or_create_custom_user(request, stripe_api_cus_id): def set_user_card(card_id, stripe_api_cus_id, custom_user, card_details_response): + logger.debug(":: set_user_card") if card_id: logger.debug("card_id %s was in request" % card_id) user_card_detail = UserCardDetail.get_ucd_from_card_id(card_id=card_id) @@ -1233,17 +1234,18 @@ def set_user_card(card_id, stripe_api_cus_id, custom_user, stripe_source_id=user_card_detail.card_id ) else: - logger.debug("card_id was NOT in request, using " + logger.debug(" card_id was NOT in request, using " "card_details_response") ucd = UserCardDetail.get_or_create_user_card_detail( stripe_customer=custom_user.stripecustomer, card_details=card_details_response ) - logger.debug("ucd = %s" % ucd) + logger.debug(" ucd = %s" % ucd) UserCardDetail.save_default_card_local( custom_user.stripecustomer.stripe_id, ucd.card_id ) + logger.debug(" after save_default_card_local") card_details_dict = { 'last4': ucd.last4, 'brand': ucd.brand, diff --git a/hosting/models.py b/hosting/models.py index 4c039fef..f17d8a57 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -699,15 +699,19 @@ class UserCardDetail(AssignPermissionsMixin, models.Model): @staticmethod def save_default_card_local(stripe_api_cus_id, card_id): + print("save_default_card_local {}, {}".format(stripe_api_cus_id, card_id)) stripe_cust = StripeCustomer.objects.get(stripe_id=stripe_api_cus_id) + print(" stripe_cust={}".format(stripe_cust)) user_card_detail = UserCardDetail.get_ucd_from_stripe_cust_n_card_id( stripe_customer=stripe_cust, card_id=card_id ) + print(" user_card_detail={}".format(user_card_detail)) for card in stripe_cust.usercarddetail_set.all(): card.preferred = False card.save() user_card_detail.preferred = True user_card_detail.save() + print(" save_default_card_local DONE") @staticmethod def get_ucd_from_card_id(card_id): From f8ba89c1934285bcb109b63afb2ce1ff23301d0b Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 27 May 2025 11:47:48 +0530 Subject: [PATCH 256/257] Fix params passing to method --- hosting/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hosting/models.py b/hosting/models.py index f17d8a57..12edba65 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -703,9 +703,9 @@ class UserCardDetail(AssignPermissionsMixin, models.Model): stripe_cust = StripeCustomer.objects.get(stripe_id=stripe_api_cus_id) print(" stripe_cust={}".format(stripe_cust)) user_card_detail = UserCardDetail.get_ucd_from_stripe_cust_n_card_id( - stripe_customer=stripe_cust, card_id=card_id + stripe_cust, card_id ) - print(" user_card_detail={}".format(user_card_detail)) + print(" user_card_detail={}".format(user_card_detail.__dict__)) for card in stripe_cust.usercarddetail_set.all(): card.preferred = False card.save() From 9cdf4593cfef31479dda480d86850e27208ea0f1 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 27 May 2025 12:02:15 +0530 Subject: [PATCH 257/257] Remove hardcode --- datacenterlight/views.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 331e0fc9..c708ff6a 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -203,10 +203,6 @@ class IndexView(CreateView): ) if request.user.id == 51: print("User is test") - price = 0 - vat = 0 - vat_percent = 0 - discount = 0 specs = { 'cpu': cores, 'memory': memory, @@ -658,10 +654,6 @@ class OrderConfirmationView(DetailView, FormView): ) if request.user.id == 51: print("User is test") - price = 0 - vat = 0 - vat_percent = 0 - discount = {"amount": 0} vm_specs["price"] = price vm_specs["price_after_discount"] = price - discount["amount"] amount_to_charge = price
    {product_name}{created_at}{total} - {see_invoice_text} - {product_name}{created_at}{total} + {see_invoice_text} +