From 00b434efb9a5f21e38501b58826624491e755b61 Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 3 Feb 2020 11:37:30 +0530 Subject: [PATCH 001/217] Read VM_BASE_PRICE from env --- dynamicweb/settings/base.py | 1 + utils/hosting_utils.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index c959c237..743d11c3 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -761,6 +761,7 @@ OTP_VERIFY_ENDPOINT = env('OTP_VERIFY_ENDPOINT') FIRST_VM_ID_AFTER_EU_VAT = int_env('FIRST_VM_ID_AFTER_EU_VAT') PRE_EU_VAT_RATE = float(env('PRE_EU_VAT_RATE')) +VM_BASE_PRICE = float(env('VM_BASE_PRICE')) if DEBUG: from .local import * # flake8: noqa diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index 7bff9a89..2c325364 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -3,6 +3,8 @@ import logging import math import subprocess +from django.conf import settings + from oca.pool import WrongIdError from datacenterlight.models import VMPricing @@ -154,7 +156,8 @@ def get_vm_price_with_vat(cpu, memory, ssd_size, hdd_size=0, (decimal.Decimal(cpu) * pricing.cores_unit_price) + (decimal.Decimal(memory) * pricing.ram_unit_price) + (decimal.Decimal(ssd_size) * pricing.ssd_unit_price) + - (decimal.Decimal(hdd_size) * pricing.hdd_unit_price) + (decimal.Decimal(hdd_size) * pricing.hdd_unit_price) + + decimal.Decimal(settings.VM_BASE_PRICE) ) if pricing.vat_inclusive: vat = decimal.Decimal(0) From e6de90e431a4486dd4c4006e46a80ba27902b2ad Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 3 Feb 2020 12:07:50 +0530 Subject: [PATCH 002/217] Set vm base price in js also --- datacenterlight/cms_plugins.py | 2 ++ datacenterlight/static/datacenterlight/js/main.js | 4 ++-- datacenterlight/templates/datacenterlight/cms/calculator.html | 2 +- .../templates/datacenterlight/includes/_calculator_form.html | 1 + hosting/static/hosting/js/initial.js | 4 ++-- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/datacenterlight/cms_plugins.py b/datacenterlight/cms_plugins.py index c3ec974f..52b4f19f 100644 --- a/datacenterlight/cms_plugins.py +++ b/datacenterlight/cms_plugins.py @@ -1,5 +1,6 @@ from cms.plugin_base import CMSPluginBase from cms.plugin_pool import plugin_pool +from django.conf import settings from .cms_models import ( DCLBannerItemPluginModel, DCLBannerListPluginModel, DCLContactPluginModel, @@ -100,6 +101,7 @@ class DCLCalculatorPlugin(CMSPluginBase): vm_type=instance.vm_type ).order_by('name') context['instance'] = instance + context['vm_base_price'] = settings.VM_BASE_PRICE context['min_ram'] = 0.5 if instance.enable_512mb_ram else 1 return context diff --git a/datacenterlight/static/datacenterlight/js/main.js b/datacenterlight/static/datacenterlight/js/main.js index 8fea438a..c6869cda 100644 --- a/datacenterlight/static/datacenterlight/js/main.js +++ b/datacenterlight/static/datacenterlight/js/main.js @@ -225,8 +225,8 @@ } var total = (cardPricing['cpu'].value * window.coresUnitPrice) + (cardPricing['ram'].value * window.ramUnitPrice) + - (cardPricing['storage'].value * window.ssdUnitPrice) - - window.discountAmount; + (cardPricing['storage'].value * window.ssdUnitPrice) + + window.vmBasePrice - window.discountAmount; total = parseFloat(total.toFixed(2)); $("#total").text(total); } diff --git a/datacenterlight/templates/datacenterlight/cms/calculator.html b/datacenterlight/templates/datacenterlight/cms/calculator.html index 7b123a72..20a6664a 100644 --- a/datacenterlight/templates/datacenterlight/cms/calculator.html +++ b/datacenterlight/templates/datacenterlight/cms/calculator.html @@ -1,5 +1,5 @@
- {% include "datacenterlight/includes/_calculator_form.html" with vm_pricing=instance.pricing %} + {% include "datacenterlight/includes/_calculator_form.html" with vm_pricing=instance.pricing vm_base_price=vm_base_price %}
\ No newline at end of file diff --git a/datacenterlight/templates/datacenterlight/includes/_calculator_form.html b/datacenterlight/templates/datacenterlight/includes/_calculator_form.html index f64a9500..2c2b51dd 100644 --- a/datacenterlight/templates/datacenterlight/includes/_calculator_form.html +++ b/datacenterlight/templates/datacenterlight/includes/_calculator_form.html @@ -9,6 +9,7 @@ window.ssdUnitPrice = {{vm_pricing.ssd_unit_price|default:0}}; window.hddUnitPrice = {{vm_pricing.hdd_unit_price|default:0}}; window.discountAmount = {{vm_pricing.discount_amount|default:0}}; + window.vmBasePrice = {{vm_base_price|default:0}}; window.minRam = {{min_ram}}; window.minRamErr = '{% blocktrans with min_ram=min_ram %}Please enter a value in range {{min_ram}} - 200.{% endblocktrans %}'; diff --git a/hosting/static/hosting/js/initial.js b/hosting/static/hosting/js/initial.js index 6b6d744d..36cf6d07 100644 --- a/hosting/static/hosting/js/initial.js +++ b/hosting/static/hosting/js/initial.js @@ -266,8 +266,8 @@ $( document ).ready(function() { } var total = (cardPricing['cpu'].value * window.coresUnitPrice) + (cardPricing['ram'].value * window.ramUnitPrice) + - (cardPricing['storage'].value * window.ssdUnitPrice) - - window.discountAmount; + (cardPricing['storage'].value * window.ssdUnitPrice) + + window.vmBasePrice - window.discountAmount; total = parseFloat(total.toFixed(2)); $("#total").text(total); } From c315030b06fde0d10e237d45f3f62fe7d785085c Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 3 Feb 2020 12:29:14 +0530 Subject: [PATCH 003/217] Add vm base price to missing places --- utils/hosting_utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index 2c325364..2ad3d335 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -81,7 +81,8 @@ def get_vm_price(cpu, memory, disk_size, hdd_size=0, pricing_name='default'): price = ((decimal.Decimal(cpu) * pricing.cores_unit_price) + (decimal.Decimal(memory) * pricing.ram_unit_price) + (decimal.Decimal(disk_size) * pricing.ssd_unit_price) + - (decimal.Decimal(hdd_size) * pricing.hdd_unit_price)) + (decimal.Decimal(hdd_size) * pricing.hdd_unit_price) + + decimal.Decimal(settings.VM_BASE_PRICE)) cents = decimal.Decimal('.01') price = price.quantize(cents, decimal.ROUND_HALF_UP) return round(float(price), 2) @@ -104,7 +105,8 @@ def get_vm_price_for_given_vat(cpu, memory, ssd_size, hdd_size=0, (decimal.Decimal(cpu) * pricing.cores_unit_price) + (decimal.Decimal(memory) * pricing.ram_unit_price) + (decimal.Decimal(ssd_size) * pricing.ssd_unit_price) + - (decimal.Decimal(hdd_size) * pricing.hdd_unit_price) + (decimal.Decimal(hdd_size) * pricing.hdd_unit_price) + + decimal.Decimal(settings.VM_BASE_PRICE) ) discount_name = pricing.discount_name From 3ca1a4521738708312910d3c7aadaf6add89c1fc Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 4 Feb 2020 08:57:54 +0530 Subject: [PATCH 004/217] Add stripe_coupon_id to VMPricing --- .../0031_vmpricing_stripe_coupon_id.py | 20 +++++++++++++++++++ datacenterlight/models.py | 1 + 2 files changed, 21 insertions(+) create mode 100644 datacenterlight/migrations/0031_vmpricing_stripe_coupon_id.py diff --git a/datacenterlight/migrations/0031_vmpricing_stripe_coupon_id.py b/datacenterlight/migrations/0031_vmpricing_stripe_coupon_id.py new file mode 100644 index 00000000..d2e45871 --- /dev/null +++ b/datacenterlight/migrations/0031_vmpricing_stripe_coupon_id.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2020-02-04 03:16 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('datacenterlight', '0030_dclnavbarpluginmodel_show_non_transparent_navbar_always'), + ] + + operations = [ + migrations.AddField( + model_name='vmpricing', + name='stripe_coupon_id', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/datacenterlight/models.py b/datacenterlight/models.py index 6410254b..64d785a2 100644 --- a/datacenterlight/models.py +++ b/datacenterlight/models.py @@ -54,6 +54,7 @@ class VMPricing(models.Model): discount_amount = models.DecimalField( max_digits=6, decimal_places=2, default=0 ) + stripe_coupon_id = models.CharField(max_length=255, null=True, blank=True) def __str__(self): display_str = self.name + ' => ' + ' - '.join([ From e322e58246dc9889e9146e51958b4c7cc46b2231 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 4 Feb 2020 09:06:10 +0530 Subject: [PATCH 005/217] Use appropriate stripe_coupon_id --- datacenterlight/views.py | 10 +++++----- hosting/views.py | 6 +++++- utils/hosting_utils.py | 6 ++++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 185b3e29..3a720cdc 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -933,11 +933,11 @@ class OrderConfirmationView(DetailView, FormView): subscription_result = stripe_utils.subscribe_customer_to_plan( stripe_api_cus_id, [{"plan": stripe_plan.get('response_object').stripe_plan_id}], - coupon='ipv6-discount-8chf' if ( - 'name' in discount and - discount['name'] is not None and - 'ipv6' in discount['name'].lower() - ) else "", + coupon=(discount['stripe_coupon_id'] + if 'name' in discount and + 'ipv6' in discount['name'].lower() and + discount['stripe_coupon_id'] + else ""), tax_rates=[stripe_tax_rate.tax_rate_id] if stripe_tax_rate else [], ) stripe_subscription_obj = subscription_result.get('response_object') diff --git a/hosting/views.py b/hosting/views.py index 729d115b..17af51fd 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1185,7 +1185,11 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): subscription_result = stripe_utils.subscribe_customer_to_plan( stripe_api_cus_id, [{"plan": stripe_plan.get('response_object').stripe_plan_id}], - coupon='ipv6-discount-8chf' if 'name' in discount and 'ipv6' in discount['name'].lower() else "", + coupon=(discount['stripe_coupon_id'] + if 'name' in discount and + 'ipv6' in discount['name'].lower() and + discount['stripe_coupon_id'] + else ""), tax_rates=[stripe_tax_rate.tax_rate_id] if stripe_tax_rate else [], ) stripe_subscription_obj = subscription_result.get('response_object') diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index 2ad3d335..aebbf717 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -122,7 +122,8 @@ def get_vm_price_for_given_vat(cpu, memory, ssd_size, hdd_size=0, discount = { 'name': discount_name, 'amount': discount_amount, - 'amount_with_vat': round(float(discount_amount_with_vat), 2) + 'amount_with_vat': round(float(discount_amount_with_vat), 2), + 'stripe_coupon_id': pricing.stripe_coupon_id } return (round(float(price), 2), round(float(vat), 2), round(float(vat_percent), 2), discount) @@ -173,7 +174,8 @@ def get_vm_price_with_vat(cpu, memory, ssd_size, hdd_size=0, vat = vat.quantize(cents, decimal.ROUND_HALF_UP) discount = { 'name': pricing.discount_name, - 'amount': round(float(pricing.discount_amount), 2) + 'amount': round(float(pricing.discount_amount), 2), + 'stripe_coupon_id': pricing.stripe_coupon_id } return (round(float(price), 2), round(float(vat), 2), round(float(vat_percent), 2), discount) From dd2eae68e6a1b989599beea3906d8dc489fdb1a6 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 4 Feb 2020 11:20:17 +0530 Subject: [PATCH 006/217] Use math round to round amount to 2 decimal places --- datacenterlight/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 3a720cdc..abd17964 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -678,9 +678,9 @@ class OrderConfirmationView(DetailView, FormView): vm_specs["vat_percent"] = vat_percent vm_specs["vat_validation_status"] = request.session["vat_validation_status"] if "vat_validation_status" in request.session else "" vm_specs["vat_country"] = user_vat_country - vm_specs["price_with_vat"] = round_up(price * (1 + vm_specs["vat_percent"] * 0.01), 2) - vm_specs["price_after_discount"] = round_up(price - discount['amount'], 2) - vm_specs["price_after_discount_with_vat"] = round_up((price - discount['amount']) * (1 + vm_specs["vat_percent"] * 0.01), 2) + vm_specs["price_with_vat"] = round(price * (1 + vm_specs["vat_percent"] * 0.01), 2) + vm_specs["price_after_discount"] = round(price - discount['amount'], 2) + vm_specs["price_after_discount_with_vat"] = round((price - discount['amount']) * (1 + vm_specs["vat_percent"] * 0.01), 2) discount["amount_with_vat"] = round(vm_specs["price_with_vat"] - vm_specs["price_after_discount_with_vat"], 2) vm_specs["total_price"] = vm_specs["price_after_discount_with_vat"] vm_specs["discount"] = discount From f45f8dd51f1349243178a21e65688f1241eda305 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 4 Feb 2020 11:27:59 +0530 Subject: [PATCH 007/217] Remove unused method round_up --- utils/hosting_utils.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index aebbf717..b9e2eb8a 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -222,11 +222,6 @@ def get_ip_addresses(vm_id): return "--" -def round_up(n, decimals=0): - multiplier = 10 ** decimals - return math.ceil(n * multiplier) / multiplier - - class HostingUtils: @staticmethod def clear_items_from_list(from_list, items_list): From c05813804489cdb5bc73c96aa9564495990d4f2d Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 4 Feb 2020 17:07:14 +0530 Subject: [PATCH 008/217] Update Changelog for 2.10.2 --- Changelog | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Changelog b/Changelog index e636bc66..df611d74 100644 --- a/Changelog +++ b/Changelog @@ -1,4 +1,11 @@ -2.10.1: 2020-02-02: +2.10.2: 2020-02-04 + * Introduce base price for VMs and let admins add stripe_coupon_id (MR!730) + Notes for deployment: + 1. Add env variable `VM_BASE_PRICE` + 2. Migrate datacenterlight app. This introduces the stripe_coupon_code field in the VMPricing. + 3. Create a coupon in stripe with the desired value and note down the stripe's coupon id + 4. Update the discount amount and set the corresponding coupon id in the admin +2.10.1: 2020-02-02 * Changes the pricing structure of generic products into the pre vat and with vat (like that for VM) * Shows product name (if exists) in the invoices list if it belongs to a generic product * Small bugfixes (right alignment of price in the invoice list, show prices with 2 decimal places etc) From ea79fafb08a07d05dc90a9aae2f7f98184b6bd9f Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 6 Mar 2020 12:17:23 +0530 Subject: [PATCH 009/217] Handle unicodes in username + limit username length --- dynamicweb/settings/base.py | 1 + membership/models.py | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index c959c237..9d5cf2bb 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -761,6 +761,7 @@ OTP_VERIFY_ENDPOINT = env('OTP_VERIFY_ENDPOINT') FIRST_VM_ID_AFTER_EU_VAT = int_env('FIRST_VM_ID_AFTER_EU_VAT') PRE_EU_VAT_RATE = float(env('PRE_EU_VAT_RATE')) +MAX_USERNAME_LENGTH = int_env('MAX_USERNAME_LENGTH', 18) if DEBUG: from .local import * # flake8: noqa diff --git a/membership/models.py b/membership/models.py index 703b4800..ab098726 100644 --- a/membership/models.py +++ b/membership/models.py @@ -77,28 +77,54 @@ def get_first_and_last_name(full_name): return first_name, last_name +def limit_username_length(username): + """ + Limit the length of username before the addition of random numbers to + 18 characters + + :param username: the username to limit + :return: + """ + if len(username) > settings.MAX_USERNAME_LENGTH: + username = username[(len(username) - settings.MAX_USERNAME_LENGTH):] + return username.strip() + + def assign_username(user): if not user.username: ldap_manager = LdapManager() # Try to come up with a username first_name, last_name = get_first_and_last_name(user.name) - user.username = unicodedata.normalize('NFKD', first_name + last_name) + user.username = unicodedata.normalize('NFKD', first_name + last_name).encode('ascii', 'ignore') user.username = "".join([char for char in user.username if char.isalnum()]).lower() + if user.username.strip() == "": + try: + # the inferred username from name is empty, hence attempt + # inferring a username from email + logger.debug("Inferred username from name is empty. So, " + "inferring from email now.") + user.username = user.email[0:user.email.index("@")] + except Exception as ex: + logger.debug("Exception %s" % str(ex)) + user.username = get_random_string( + allowed_chars='abcdefghijklmnopqrstuvwxyz' + ) exist = True + user_username = limit_username_length(user.username) while exist: # Check if it exists - exist, entries = ldap_manager.check_user_exists(user.username) + exist, entries = ldap_manager.check_user_exists(user_username) if exist: # If username exists in ldap, come up with a new user name and check it again - user.username = user.username + str(random.randint(0, 2 ** 10)) + user.username = user_username + str(random.randint(0, 2 ** 10)) else: # If username does not exists in ldap, try to save it in database try: user.save() except IntegrityError: # If username exists in database then come up with a new username - user.username = user.username + str(random.randint(0, 2 ** 10)) + user.username = user_username + str(random.randint(0, 2 ** 10)) exist = True From a995f418b29b0fda388c09dee50e3a7a5f6b2b73 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 6 Mar 2020 12:25:42 +0530 Subject: [PATCH 010/217] Remove any non alphanum character present in email for username --- membership/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/membership/models.py b/membership/models.py index ab098726..9a9dfd89 100644 --- a/membership/models.py +++ b/membership/models.py @@ -105,6 +105,9 @@ def assign_username(user): logger.debug("Inferred username from name is empty. So, " "inferring from email now.") user.username = user.email[0:user.email.index("@")] + user.username = "".join( + [char for char in user.username if char.isalnum()] + ).lower() except Exception as ex: logger.debug("Exception %s" % str(ex)) user.username = get_random_string( From 4890c4956f97bec49239d821133bcb99eb21295a Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 6 Mar 2020 15:29:42 +0530 Subject: [PATCH 011/217] Increase MAX_USERNAME_LENGTH to 64 chars --- dynamicweb/settings/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 9d5cf2bb..735cd913 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -761,7 +761,7 @@ OTP_VERIFY_ENDPOINT = env('OTP_VERIFY_ENDPOINT') FIRST_VM_ID_AFTER_EU_VAT = int_env('FIRST_VM_ID_AFTER_EU_VAT') PRE_EU_VAT_RATE = float(env('PRE_EU_VAT_RATE')) -MAX_USERNAME_LENGTH = int_env('MAX_USERNAME_LENGTH', 18) +MAX_USERNAME_LENGTH = int_env('MAX_USERNAME_LENGTH', 64) if DEBUG: from .local import * # flake8: noqa From af70824d6a14a0a945519e7c01fdebbbeab785f7 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 6 Mar 2020 15:30:07 +0530 Subject: [PATCH 012/217] Increase username charfield to 120 chars --- membership/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/membership/models.py b/membership/models.py index 9a9dfd89..e5454839 100644 --- a/membership/models.py +++ b/membership/models.py @@ -145,7 +145,7 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): site = models.ForeignKey(Site, default=1) name = models.CharField(max_length=50, validators=[validate_name]) email = models.EmailField(unique=True) - username = models.CharField(max_length=60, unique=True, null=True) + username = models.CharField(max_length=120, unique=True, null=True) validated = models.IntegerField(choices=VALIDATED_CHOICES, default=0) in_ldap = models.BooleanField(default=False) # By default, we initialize the validation_slug with appropriate value From 2f16f2440ea2695c45e497f92fd4f20f5cc7e1ae Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 6 Mar 2020 15:47:25 +0530 Subject: [PATCH 013/217] Return default value when return value is None --- dynamicweb/settings/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 735cd913..ca8996d1 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -42,7 +42,7 @@ def int_env(val, default_value=0): "details: {}").format( val, str(e))) - return return_value + return return_value if return_value is not None else default_value BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) From 4441ba37ca400201b03533b7a203d2bb669dc9d5 Mon Sep 17 00:00:00 2001 From: PCoder Date: Fri, 6 Mar 2020 15:48:02 +0530 Subject: [PATCH 014/217] Add migration: membership/migrations/0012_auto_20200306_1016.py --- .../migrations/0012_auto_20200306_1016.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 membership/migrations/0012_auto_20200306_1016.py diff --git a/membership/migrations/0012_auto_20200306_1016.py b/membership/migrations/0012_auto_20200306_1016.py new file mode 100644 index 00000000..f38f0780 --- /dev/null +++ b/membership/migrations/0012_auto_20200306_1016.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2020-03-06 10:16 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('membership', '0011_auto_20191218_1050'), + ] + + operations = [ + migrations.AlterField( + model_name='customuser', + name='username', + field=models.CharField(max_length=120, null=True, unique=True), + ), + ] From e18188603a0b28932134c9b7244eb68790ca4e1c Mon Sep 17 00:00:00 2001 From: PCoder Date: Sat, 7 Mar 2020 11:13:57 +0530 Subject: [PATCH 015/217] Decode username back to string from bytes after encode --- membership/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/membership/models.py b/membership/models.py index e5454839..b572833d 100644 --- a/membership/models.py +++ b/membership/models.py @@ -96,7 +96,9 @@ def assign_username(user): # Try to come up with a username first_name, last_name = get_first_and_last_name(user.name) - user.username = unicodedata.normalize('NFKD', first_name + last_name).encode('ascii', 'ignore') + user.username = unicodedata.normalize( + 'NFKD', first_name + last_name + ).encode('ascii', 'ignore').decode('ascii', 'ignore') user.username = "".join([char for char in user.username if char.isalnum()]).lower() if user.username.strip() == "": try: From 9d96ecefeadbfc6728383a08f50cbae30e07f0b3 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 18 Mar 2020 12:44:18 +0530 Subject: [PATCH 016/217] Remove unwanted import --- datacenterlight/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 2a55b96f..233edb20 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -29,7 +29,7 @@ from utils.forms import ( ) from utils.hosting_utils import ( get_vm_price_with_vat, get_all_public_keys, get_vat_rate_for_country, - get_vm_price_for_given_vat, round_up + get_vm_price_for_given_vat ) from utils.stripe_utils import StripeUtils from utils.tasks import send_plain_email_task From 46c33cf10723164245a32d582192f5d34b21766c Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 24 Mar 2020 12:34:11 +0530 Subject: [PATCH 017/217] Update price to 11.5 CHF per month in intro emails --- .../locale/de/LC_MESSAGES/django.po | 36 ++++++++++--------- .../datacenterlight/emails/welcome_user.html | 2 +- .../datacenterlight/emails/welcome_user.txt | 2 +- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po index 1a1a2a26..2dce7582 100644 --- a/datacenterlight/locale/de/LC_MESSAGES/django.po +++ b/datacenterlight/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-02-01 09:42+0000\n" +"POT-Creation-Date: 2020-03-24 07:02+0000\n" "PO-Revision-Date: 2018-03-30 23:22+0000\n" "Last-Translator: b'Anonymous User '\n" "Language-Team: LANGUAGE \n" @@ -144,8 +144,8 @@ msgid "" "the heart of Switzerland." msgstr "Bei uns findest Du die günstiges VMs aus der Schweiz." -msgid "Try now, order a VM. VM price starts from only 10.5 CHF per month." -msgstr "Unser Angebot beginnt bei 10.5 CHF pro Monat. Probier's jetzt aus!" +msgid "Try now, order a VM. VM price starts from only 11.5 CHF per month." +msgstr "Unser Angebot beginnt bei 11.5 CHF pro Monat. Probier's jetzt aus!" msgid "ORDER VM" msgstr "VM BESTELLEN" @@ -415,8 +415,9 @@ msgstr "Deine MwSt-Nummer wurde überprüft" msgid "" "Your VAT number is under validation. VAT will be adjusted, once the " "validation is complete." -msgstr "Deine MwSt-Nummer wird derzeit validiert. Die MwSt. wird angepasst, " -"sobald die Validierung abgeschlossen ist." +msgstr "" +"Deine MwSt-Nummer wird derzeit validiert. Die MwSt. wird angepasst, sobald " +"die Validierung abgeschlossen ist." msgid "Payment method" msgstr "Bezahlmethode" @@ -430,18 +431,6 @@ msgstr "Bestellungsübersicht" msgid "Product" msgstr "Produkt" -msgid "Price" -msgstr "Preise" - -msgid "VAT for" -msgstr "MwSt für" - -msgid "Total Amount" -msgstr "Gesamtsumme" - -msgid "Amount" -msgstr "Betrag" - msgid "Description" msgstr "Beschreibung" @@ -454,9 +443,13 @@ msgstr "Preis ohne MwSt." msgid "Pre VAT" msgstr "Exkl. MwSt." +msgid "VAT for" +msgstr "MwSt für" + msgid "Your Price in Total" msgstr "Dein Gesamtpreis" +#, python-format msgid "" "By clicking \"Place order\" this plan will charge your credit card account " "with %(total_price)s CHF/year" @@ -681,6 +674,15 @@ msgstr "" "Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du " "auf sie zugreifen kannst." +#~ msgid "Price" +#~ msgstr "Preise" + +#~ msgid "Total Amount" +#~ msgstr "Gesamtsumme" + +#~ msgid "Amount" +#~ msgstr "Betrag" + #~ msgid "Subtotal" #~ msgstr "Zwischensumme" diff --git a/datacenterlight/templates/datacenterlight/emails/welcome_user.html b/datacenterlight/templates/datacenterlight/emails/welcome_user.html index 25185618..2044b2ee 100644 --- a/datacenterlight/templates/datacenterlight/emails/welcome_user.html +++ b/datacenterlight/templates/datacenterlight/emails/welcome_user.html @@ -28,7 +28,7 @@ {% blocktrans %}Thanks for joining us! We provide the most affordable virtual machines from the heart of Switzerland.{% endblocktrans %}

- {% blocktrans %}Try now, order a VM. VM price starts from only 10.5 CHF per month.{% endblocktrans %} + {% blocktrans %}Try now, order a VM. VM price starts from only 11.5 CHF per month.{% endblocktrans %}

diff --git a/datacenterlight/templates/datacenterlight/emails/welcome_user.txt b/datacenterlight/templates/datacenterlight/emails/welcome_user.txt index 772e51a5..06e8aa33 100644 --- a/datacenterlight/templates/datacenterlight/emails/welcome_user.txt +++ b/datacenterlight/templates/datacenterlight/emails/welcome_user.txt @@ -3,7 +3,7 @@ {% trans "Welcome to Data Center Light!" %} {% blocktrans %}Thanks for joining us! We provide the most affordable virtual machines from the heart of Switzerland.{% endblocktrans %} -{% blocktrans %}Try now, order a VM. VM price starts from only 10.5 CHF per month.{% endblocktrans %} +{% blocktrans %}Try now, order a VM. VM price starts from only 11.5 CHF per month.{% endblocktrans %} {{ base_url }}{% url 'hosting:create_virtual_machine' %} From cb3ff7310099723b5a9303df1e09494484c8e116 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Mar 2020 18:47:00 +0530 Subject: [PATCH 018/217] Filter out None case for discount's name --- datacenterlight/views.py | 1 + hosting/views.py | 1 + 2 files changed, 2 insertions(+) diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 233edb20..17a0479f 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -935,6 +935,7 @@ class OrderConfirmationView(DetailView, FormView): [{"plan": stripe_plan.get('response_object').stripe_plan_id}], coupon=(discount['stripe_coupon_id'] if 'name' in discount and + discount['name'] is not None and 'ipv6' in discount['name'].lower() and discount['stripe_coupon_id'] else ""), diff --git a/hosting/views.py b/hosting/views.py index 969523b3..1d16a750 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1187,6 +1187,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): [{"plan": stripe_plan.get('response_object').stripe_plan_id}], coupon=(discount['stripe_coupon_id'] if 'name' in discount and + discount['name'] is not None and 'ipv6' in discount['name'].lower() and discount['stripe_coupon_id'] else ""), From 7072420ea5c32137a99c813b3ef88e8a0e1ccbc5 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 25 Mar 2020 18:55:49 +0530 Subject: [PATCH 019/217] Update Changelog for 2.10.6 --- Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog b/Changelog index a4840758..00d84edf 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,5 @@ +2.10.6: 2020-03-25 + * Bugfix: Handle Nonetype for discount's name (MR!735) 2.10.5: 2020-03-17 * Introduce base price for VMs and let admins add stripe_coupon_id (MR!730) Notes for deployment: From cec7938c9c545f685f0940698dc502280161e33a Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 1 Apr 2020 11:30:27 +0530 Subject: [PATCH 020/217] 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 021/217] 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 022/217] 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 023/217] 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 %} +
      + {% if invs_charge.has_previous %} + {% if user_email %} +
    • «
    • + {% else %} +
    • «
    • + {% endif %} + {% else %} +
    • «
    • + {% endif %} + {% for i in invs_charge.paginator.page_range %} + {% if invs_charge.number == i %} +
    • {{ i }} (current)
    • + {% else %} + {% if user_email %} +
    • {{ i }}
    • + {% else %} +
    • {{ i }}
    • + {% endif %} + {% endif %} + {% endfor %} + {% if invs_charge.has_next %} + {% if user_email %} +
    • »
    • + {% else %} +
    • »
    • + {% endif %} + {% else %} +
    • »
    • + {% endif %} +
    +{% endif %} + {% endblock %} From 343a9440f0944237c462a2cd5be7835e4a92c6d5 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 1 Apr 2020 17:09:02 +0530 Subject: [PATCH 024/217] 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 025/217] 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 026/217] 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 027/217] 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 028/217] 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 029/217] 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 030/217] 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 031/217] 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 032/217] 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 033/217] 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 034/217] 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 035/217] 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 036/217] 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 037/217] 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 ac1170a0f1c18414b841399a94e344fe1cad9073 Mon Sep 17 00:00:00 2001 From: PCoder Date: Tue, 21 Apr 2020 00:03:13 +0530 Subject: [PATCH 038/217] Remove wrong IL 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 c1473fa37401e931e5a4b31946157addc520af2f Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 25 May 2020 11:10:41 +0530 Subject: [PATCH 039/217] Handle updated templates --- dynamicweb/settings/base.py | 5 +++++ opennebula_api/models.py | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 743d11c3..76135165 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -763,6 +763,11 @@ PRE_EU_VAT_RATE = float(env('PRE_EU_VAT_RATE')) VM_BASE_PRICE = float(env('VM_BASE_PRICE')) +UPDATED_TEMPLATES_STR = env('UPDATED_TEMPLATES') +UPDATED_TEMPLATES_DICT = {} +if UPDATED_TEMPLATES_STR: + UPDATED_TEMPLATES_DICT = eval(UPDATED_TEMPLATES_STR) + if DEBUG: from .local import * # flake8: noqa else: diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 19e3e4f7..6d9716b4 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -427,8 +427,12 @@ class OpenNebulaManager(): template_id = int(template_id) try: template_pool = self._get_template_pool() + if template_id in settings.UPDATED_TEMPLATES.keys(): + template_id = settings.UPDATED_TEMPLATES[template_id] return template_pool.get_by_id(template_id) - except: + except Exception as ex: + logger.debug("Template Id we are looking for : %s" % template_id) + logger.error(str(ex)) raise ConnectionRefusedError def create_template(self, name, cores, memory, disk_size, core_price, From 17a8efb0b6eecacd5ccd5e00336ced5c69aea12d Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 25 May 2020 11:33:58 +0530 Subject: [PATCH 040/217] Fix variable name --- opennebula_api/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 6d9716b4..92d5c568 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -427,8 +427,8 @@ class OpenNebulaManager(): template_id = int(template_id) try: template_pool = self._get_template_pool() - if template_id in settings.UPDATED_TEMPLATES.keys(): - template_id = settings.UPDATED_TEMPLATES[template_id] + if template_id in settings.UPDATED_TEMPLATES_DICT.keys(): + template_id = settings.UPDATED_TEMPLATES_DICT[template_id] return template_pool.get_by_id(template_id) except Exception as ex: logger.debug("Template Id we are looking for : %s" % template_id) From 6131270b1df68461ee5a9f18afb4daf9eede883b Mon Sep 17 00:00:00 2001 From: PCoder Date: Mon, 25 May 2020 11:44:07 +0530 Subject: [PATCH 041/217] Update Changelog for 2.10.7 --- Changelog | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Changelog b/Changelog index 00d84edf..5068197e 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,13 @@ +2.10.7: 2020-05-25 + * Bugfix: Handle VM templates deleted in OpenNebula but VM instances still existing (MR!736) + Notes for deployment: + When deploying define a UPDATED_TEMPLATES string represented dictionary value in .env +``` + # Represents Template Ids that were + # deleted and the new template Id to look for the template + # definition + UPDATED_TEMPLATES="{1: 100}" +``` 2.10.6: 2020-03-25 * Bugfix: Handle Nonetype for discount's name (MR!735) 2.10.5: 2020-03-17 From 4bff49dab64ad364d08c0a9df13a6edd4a6e5101 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 10 Jun 2020 12:27:59 +0530 Subject: [PATCH 042/217] Refactor polling time to terminate VM --- dynamicweb/settings/base.py | 3 +++ hosting/views.py | 10 +++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 76135165..03013ea5 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -768,6 +768,9 @@ UPDATED_TEMPLATES_DICT = {} if UPDATED_TEMPLATES_STR: UPDATED_TEMPLATES_DICT = eval(UPDATED_TEMPLATES_STR) +MAX_TIME_TO_WAIT_FOR_VM_TERMINATE = int_env( + 'MAX_TIME_TO_WAIT_FOR_VM_TERMINATE', 15) + if DEBUG: from .local import * # flake8: noqa else: diff --git a/hosting/views.py b/hosting/views.py index 1d16a750..ceecfd79 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1783,11 +1783,11 @@ class VirtualMachineView(LoginRequiredMixin, View): ) response['text'] = str(_('Error terminating VM')) + str(vm.id) else: - for t in range(15): + for t in range(settings.MAX_TIME_TO_WAIT_FOR_VM_TERMINATE): try: manager.get_vm(vm.id) except WrongIdError: - logger.error( + logger.debug( "VM {} not found. So, its terminated.".format(vm.id) ) response['status'] = True @@ -1798,7 +1798,7 @@ class VirtualMachineView(LoginRequiredMixin, View): vm_detail_obj.terminated_at = datetime.utcnow() vm_detail_obj.save() except BaseException as base_exception: - logger.error( + logger.debug( "manager.get_vm({vm_id}) returned exception: " "{details}.".format( details=str(base_exception), vm_id=vm.id @@ -1806,6 +1806,10 @@ class VirtualMachineView(LoginRequiredMixin, View): ) break else: + logger.debug( + 'Sleeping 2 seconds for terminate action on VM %s' % + vm.id + ) sleep(2) if not response['status']: response['text'] = str(_("VM terminate action timed out. " From af36a493662472a51595d6629fc4c22389203cf2 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 10 Jun 2020 13:36:30 +0530 Subject: [PATCH 043/217] Revert back errors --- hosting/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hosting/views.py b/hosting/views.py index ceecfd79..1bd7c1aa 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1787,7 +1787,7 @@ class VirtualMachineView(LoginRequiredMixin, View): try: manager.get_vm(vm.id) except WrongIdError: - logger.debug( + logger.error( "VM {} not found. So, its terminated.".format(vm.id) ) response['status'] = True @@ -1798,7 +1798,7 @@ class VirtualMachineView(LoginRequiredMixin, View): vm_detail_obj.terminated_at = datetime.utcnow() vm_detail_obj.save() except BaseException as base_exception: - logger.debug( + logger.error( "manager.get_vm({vm_id}) returned exception: " "{details}.".format( details=str(base_exception), vm_id=vm.id From 81eee87fb93c9bb51f4dc20154740b021977cce8 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 10 Jun 2020 13:45:46 +0530 Subject: [PATCH 044/217] Update Changelog for 2.10.8 --- Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changelog b/Changelog index 5068197e..1bb0ff37 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,5 @@ +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 * Bugfix: Handle VM templates deleted in OpenNebula but VM instances still existing (MR!736) Notes for deployment: From 495e7d4022068e06f1ad969c16ca9b95c6dbfc42 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 11 Jun 2020 11:29:32 +0530 Subject: [PATCH 045/217] 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 046/217] 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 047/217] 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 048/217] 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 049/217] 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 050/217] 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 051/217] 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 052/217] 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 053/217] 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 054/217] 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 055/217] 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 056/217] 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 057/217] 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 058/217] 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 059/217] 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 060/217] 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 061/217] 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 062/217] 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 063/217] 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 064/217] 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 065/217] 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 066/217] 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 067/217] 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 068/217] 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 069/217] 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 070/217] 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 071/217] 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 072/217] 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 073/217] 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 074/217] 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 075/217] 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 076/217] 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 077/217] 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 078/217] 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 079/217] 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 080/217] 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 081/217] 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 082/217] 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 083/217] 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 084/217] 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 085/217] 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 086/217] 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 087/217] 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 088/217] 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 089/217] 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 090/217] 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 091/217] 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 092/217] 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 093/217] 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 094/217] 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 095/217] 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 096/217] 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 097/217] 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 098/217] 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 099/217] 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 100/217] 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 101/217] 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 102/217] 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 103/217] 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 104/217] 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 105/217] 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 106/217] 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 107/217] 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 108/217] 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 109/217] 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 110/217] 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 111/217] 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 112/217] 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 113/217] 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 114/217] 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 115/217] 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 116/217] 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 117/217] 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 118/217] 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 119/217] 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 120/217] 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 121/217] 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 122/217] 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 123/217] 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 124/217] 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 125/217] 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 126/217] 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 127/217] 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 128/217] 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 129/217] 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 130/217] 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 131/217] 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 132/217] 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 133/217] 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 134/217] 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 135/217] 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 136/217] 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 137/217] 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 138/217] 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 139/217] 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 140/217] 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 141/217] 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 142/217] 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 143/217] 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 144/217] 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 145/217] 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 146/217] 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 147/217] 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 148/217] 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 149/217] 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 150/217] 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 151/217] 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 152/217] 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 153/217] 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 154/217] 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 155/217] 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 156/217] 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 157/217] 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 158/217] 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 159/217] 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 160/217] 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 161/217] 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 162/217] 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 163/217] 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 164/217] 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 165/217] 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 166/217] 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 167/217] 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 168/217] 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 169/217] 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 170/217] 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 171/217] 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 172/217] 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 173/217] 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 174/217] 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 175/217] 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 176/217] 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 177/217] 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 178/217] 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 179/217] 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 180/217] 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 181/217] 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 182/217] 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 183/217] 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 184/217] 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 185/217] 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 186/217] 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 187/217] 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 188/217] 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 189/217] 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 190/217] 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 191/217] 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 192/217] 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 193/217] 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 194/217] 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 195/217] 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 196/217] 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 197/217] 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 198/217] 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 199/217] 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 200/217] 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 201/217] 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 202/217] 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 203/217] 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 204/217] 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 205/217] 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 206/217] 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 207/217] 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 208/217] 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 213/217] 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 214/217] 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 215/217] 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 216/217] 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 217/217] 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']
    {product_name}{created_at}{total} - {see_invoice_text} - {product_name}{created_at}{total} + {see_invoice_text} +