diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index a715c9d7..00000000 --- a/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -.git -.env diff --git a/.gitignore b/.gitignore index a41d813d..2d923e99 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,3 @@ secret-key !.gitkeep *.orig .vscode/settings.json -2024-* -2025-* -2023-* diff --git a/Changelog b/Changelog index 9e420c87..1bb0ff37 100644 --- a/Changelog +++ b/Changelog @@ -1,25 +1,3 @@ -3.4: 2022-04-14 - * 11566: Fix for ungleich.ch product section alignment -3.2: 2021-02-07 - * 8816: Update order confirmation text to better prepared for payment dispute - * supportticket#22990: Fix: can't add a deleted card -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) -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) -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 - * 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 diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 4c1a9a66..00000000 --- a/Dockerfile +++ /dev/null @@ -1,32 +0,0 @@ -# FROM python:3.10.0-alpine3.15 -FROM python:3.5-alpine3.12 - -WORKDIR /usr/src/app - -RUN apk add --update --no-cache \ - git \ - build-base \ - openldap-dev \ - python3-dev \ - postgresql-dev \ - jpeg-dev \ - libxml2-dev \ - libxslt-dev \ - libmemcached-dev \ - zlib-dev \ - && rm -rf /var/cache/apk/* - -## For alpine 3.15 replace postgresql-dev with libpq-dev - -# FIX https://github.com/python-ldap/python-ldap/issues/432 -RUN echo 'INPUT ( libldap.so )' > /usr/lib/libldap_r.so - -COPY requirements.txt ./ - -# Pillow seems to need LIBRARY_PATH set as follows: (see: https://github.com/python-pillow/Pillow/issues/1763#issuecomment-222383534) -RUN LIBRARY_PATH=/lib:/usr/lib /bin/sh -c "pip install --no-cache-dir -r requirements.txt" - -COPY ./ . -COPY entrypoint.sh / - -ENTRYPOINT ["/entrypoint.sh" ] diff --git a/Makefile b/Makefile index 68ff014e..67c0c15b 100644 --- a/Makefile +++ b/Makefile @@ -14,12 +14,6 @@ help: @echo ' make rsync_upload ' @echo ' make install_debian_packages ' -buildimage: - docker build -t dynamicweb:$$(git describe) . - -releaseimage: buildimage - ./release.sh - collectstatic: $(PY?) $(BASEDIR)/manage.py collectstatic diff --git a/alplora/views.py b/alplora/views.py index d628cffc..0a10b4e0 100644 --- a/alplora/views.py +++ b/alplora/views.py @@ -31,10 +31,9 @@ class ContactView(FormView): return context def form_valid(self, form): - print("alplora contactusform") - #form.save() - #form.send_email(email_to='info@alplora.ch') - #messages.add_message(self.request, messages.SUCCESS, self.success_message) + form.save() + form.send_email(email_to='info@alplora.ch') + messages.add_message(self.request, messages.SUCCESS, self.success_message) return render(self.request, 'alplora/contact_success.html', {}) diff --git a/build-image.sh b/build-image.sh deleted file mode 100755 index 64a67fd6..00000000 --- a/build-image.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh - -if [ $# -lt 1 ]; then - echo "$0 imageversion [push]" - echo "Version could be: $(git describe --always)" - echo "If push is specified, also push to our harbor" - exit 1 -fi - -tagprefix=harbor.k8s.ungleich.ch/ungleich-public/dynamicweb -version=$1; shift - -tag=${tagprefix}:${version} - -set -ex - -docker build -t "${tag}" . - -push=$1; shift - -if [ "$push" ]; then - docker push "${tag}" -fi diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po index cd7fab99..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: 2021-02-07 11:10+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" @@ -451,43 +451,38 @@ msgstr "Dein Gesamtpreis" #, python-format msgid "" -" By clicking \"Place order\" you agree to our Terms of Service and " -"this plan will charge your credit card account with %(total_price)s CHF/year" +"By clicking \"Place order\" this plan will charge your credit card account " +"with %(total_price)s CHF/year" msgstr "" -"Indem Du auf \"Bestellung aufgeben\" klickst, erklärst Du dich mit unseren Nutzungsbedingungen einverstanden und Dein Kreditkartenkonto wird mit %(total_price)s CHF/Jahr belastet." +"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(total_price)s " +"CHF pro Jahr belastet" #, python-format msgid "" -"\n" -" By clicking \"Place order\" you agree to " -"our Terms " -"of Service and this plan will charge your credit card account with " -"%(total_price)s CHF/month" +"By clicking \"Place order\" 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." +"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(total_price)s " +"CHF pro Monat belastet" + +#, fuzzy, python-format +#| msgid "" +#| "By clicking \"Place order\" this payment will charge your credit card " +#| "account with a one time amount of %(total_price)s CHF" +msgid "" +"By clicking \"Place order\" this payment will charge your credit card " +"account with a one time amount of %(total_price)s CHF" +msgstr "" +"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit " +"%(vm_total_price)s CHF pro Monat belastet" #, python-format msgid "" -"By clicking \"Place order\" you agree to our Terms of Service and " -"this plan will charge your credit card account with %(total_price)s CHF" +"By clicking \"Place order\" this plan will charge your credit card account " +"with %(vm_total_price)s CHF/month" msgstr "" -"Indem Du auf \"Bestellung aufgeben\" klickst, erklärst Du dich mit unseren Nutzungsbedingungen einverstanden und Dein Kreditkartenkonto wird mit %(total_price)s CHF belastet." - -#, python-format -msgid "" -"By clicking \"Place order\" you agree to our Terms of Service and " -"this plan will charge your credit card account with %(vm_total_price)s CHF/" -"month" -msgstr "" -"Indem Du auf \"Bestellung aufgeben\" klickst, erklärst Du dich mit unseren Nutzungsbedingungen einverstanden und Dein Kreditkartenkonto wird mit %(vm_total_price)s CHF/Monat belastet" +"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit " +"%(vm_total_price)s CHF pro Monat belastet" msgid "Place order" msgstr "Bestellen" @@ -606,22 +601,16 @@ msgid "Incorrect pricing name. Please contact support{support_email}" msgstr "" "Ungültige Preisbezeichnung. Bitte kontaktiere den Support{support_email}" +#, python-brace-format +msgid "{user} does not have permission to access the card" +msgstr "{user} hat keine Erlaubnis auf diese Karte zuzugreifen" + +msgid "An error occurred. Details: {}" +msgstr "Ein Fehler ist aufgetreten. Details: {}" + msgid "Confirm Order" msgstr "Bestellung Bestätigen" -#, fuzzy -#| msgid "Thank you!" -msgid "Thank you !" -msgstr "Vielen Dank!" - -msgid "Your product will be provisioned as soon as we receive the payment." -msgstr "" - -#, python-brace-format -msgid "An error occurred while associating the card. Details: {details}" -msgstr "" -"Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: {details}" - msgid "Error." msgstr "" @@ -632,21 +621,10 @@ msgstr "" "Es ist ein Fehler bei der Zahlung betreten. Du wirst nach dem Schliessen vom " "Popup zur Bezahlseite weitergeleitet." -msgid "Thank you for the order." -msgstr "Danke für Deine Bestellung." - -msgid "" -"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" +#, python-brace-format +msgid "An error occurred while associating the card. Details: {details}" msgstr "" - -msgid "" -"Your VM will be up and running in a few moments. We will send you a " -"confirmation email as soon as it is ready." -msgstr "" -"Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du " -"auf sie zugreifen kannst." +"Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: {details}" msgid " This is a monthly recurring plan." msgstr "Dies ist ein monatlich wiederkehrender Plan." @@ -686,31 +664,15 @@ msgstr "" "Du wirst bald eine Bestätigungs-E-Mail über die Zahlung erhalten. Du kannst " "jederzeit unter info@ungleich.ch kontaktieren." -#, python-format -#~ msgid "" -#~ "By clicking \"Place order\" this plan will charge your credit card " -#~ "account with %(total_price)s CHF/month" -#~ msgstr "" -#~ "Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit " -#~ "%(total_price)s CHF pro Monat belastet" +msgid "Thank you for the order." +msgstr "Danke für Deine Bestellung." -#, fuzzy, python-format -#~| msgid "" -#~| "By clicking \"Place order\" this payment will charge your credit card " -#~| "account with a one time amount of %(total_price)s CHF" -#~ msgid "" -#~ "By clicking \"Place order\" this payment will charge your credit card " -#~ "account with a one time amount of %(total_price)s CHF" -#~ msgstr "" -#~ "Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit " -#~ "%(vm_total_price)s CHF pro Monat belastet" - -#, python-brace-format -#~ msgid "{user} does not have permission to access the card" -#~ msgstr "{user} hat keine Erlaubnis auf diese Karte zuzugreifen" - -#~ msgid "An error occurred. Details: {}" -#~ msgstr "Ein Fehler ist aufgetreten. Details: {}" +msgid "" +"Your VM will be up and running in a few moments. We will send you a " +"confirmation email as soon as it is ready." +msgstr "" +"Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du " +"auf sie zugreifen kannst." #~ msgid "Price" #~ msgstr "Preise" @@ -786,6 +748,9 @@ msgstr "" #~ "Wir werden dann sobald als möglich Ihren Beta-Zugang erstellen und Sie " #~ "daraufhin kontaktieren.Bis dahin bitten wir Sie um etwas Geduld." +#~ msgid "Thank you!" +#~ msgstr "Vielen Dank!" + #~ msgid "Thank you for order! Our team will contact you via email" #~ msgstr "" #~ "Vielen Dank für die Bestellung. Unser Team setzt sich sobald wie möglich " diff --git a/datacenterlight/management/commands/check_vm_templates.py b/datacenterlight/management/commands/check_vm_templates.py deleted file mode 100644 index d30e5b0d..00000000 --- a/datacenterlight/management/commands/check_vm_templates.py +++ /dev/null @@ -1,80 +0,0 @@ -from django.core.management.base import BaseCommand -from opennebula_api.models import OpenNebulaManager -from datacenterlight.models import VMTemplate -from datacenterlight.cms_models import DCLCalculatorPluginModel -from membership.models import CustomUser -from utils.tasks import send_plain_email_task -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 = {} - error_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] - PROJECT_PATH = os.path.abspath(os.path.dirname(__name__)) - if not os.path.exists("%s/outputs" % PROJECT_PATH): - os.mkdir("%s/outputs" % PROJECT_PATH) - public_templates_plugin = DCLCalculatorPluginModel.objects.filter(cmsplugin_ptr_id=23356).first() - ipv6_templates_plugin = DCLCalculatorPluginModel.objects.filter(cmsplugin_ptr_id=21943).first() - templates = public_templates_plugin.vm_templates_to_show + ipv6_templates_plugin.vm_templates_to_show - vm_templates = VMTemplate.objects.filter(opennebula_vm_template_id__in=templates) - for vm_template in vm_templates: - vm_name = 'test-%s-%s' % (vm_template.vm_type, 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 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_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 %s''' % (vm_name, - vm_template.opennebula_vm_template_id, - vm_template.vm_type) - error_dict[vm_name] = result_dict[vm_name] - self.stdout.write(self.style.ERROR(result_dict[vm_name])) - sleep(1) - date_str = datetime.datetime.strftime( - datetime.datetime.now(), '%Y%m%d%H%M%S' - ) - 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)) - if error_dict: - email_data = { - 'subject': 'Check VM Templates ERROR', - 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, - 'to': [settings.ADMIN_EMAIL], - 'body': json.dumps(error_dict), - } - send_plain_email_task.delay(email_data) - self.stdout.write(self.style.SUCCESS("Done")) diff --git a/datacenterlight/management/commands/fix_generic_stripe_plan_product_names.py b/datacenterlight/management/commands/fix_generic_stripe_plan_product_names.py deleted file mode 100644 index 2773009d..00000000 --- a/datacenterlight/management/commands/fix_generic_stripe_plan_product_names.py +++ /dev/null @@ -1,54 +0,0 @@ -from django.core.management.base import BaseCommand -from datacenterlight.tasks import handle_metadata_and_emails -from datacenterlight.models import StripePlan -from opennebula_api.models import OpenNebulaManager -from membership.models import CustomUser -from hosting.models import GenericProduct -import logging -import json -import sys -import stripe - -logger = logging.getLogger(__name__) - - -class Command(BaseCommand): - help = '''Stripe plans created before version 3.4 saved the plan name like generic-{subscription_id}-amount. This - command aims at replacing this with the actual product name - ''' - - def handle(self, *args, **options): - cnt = 0 - self.stdout.write( - self.style.SUCCESS( - 'In Fix generic stripe plan product names' - ) - ) - plans_to_change = StripePlan.objects.filter(stripe_plan_id__startswith='generic') - for plan in plans_to_change: - response = input("Press 'y' to continue: ") - - # Check if the user entered 'y' - if response.lower() == 'y': - plan_name = plan.stripe_plan_id - first_index_hyphen = plan_name.index("-") + 1 - product_id = plan_name[ - first_index_hyphen:(plan_name[first_index_hyphen:].index("-")) + first_index_hyphen] - gp = GenericProduct.objects.get(id=product_id) - if gp: - cnt += 1 - # update stripe - sp = stripe.Plan.retrieve(plan_name) - pr = stripe.Product.retrieve(sp.product) - pr.name = gp.product_name - pr.save() - # update local - spl = StripePlan.objects.get(stripe_plan_id=plan_name) - spl.stripe_plan_name = gp.product_name - spl.save() - print("%s. %s => %s" % (cnt, plan_name, gp.product_name)) - else: - print("Invalid input. Please try again.") - sys.exit() - - print("Done") \ No newline at end of file diff --git a/datacenterlight/management/commands/fix_vm_after_celery_error.py b/datacenterlight/management/commands/fix_vm_after_celery_error.py deleted file mode 100644 index 0cfdb423..00000000 --- a/datacenterlight/management/commands/fix_vm_after_celery_error.py +++ /dev/null @@ -1,76 +0,0 @@ -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 - -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('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'] - order_id = options['order_id'] - user_str = options['user'] - specs_str = options['specs'] - template_str = options['template'] - - json_acceptable_string = user_str.replace("'", "\"") - user_dict = 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_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'], - } - cu = CustomUser.objects.get(username=user.get('username')) - # Create OpenNebulaManager - self.stdout.write( - self.style.SUCCESS( - 'Connecting using %s' % (cu.username) - ) - ) - manager = OpenNebulaManager(email=cu.username, password=cu.password) - 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) - ) - ) - ) diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index 899b506f..f080da90 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -56,6 +56,11 @@ 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') @@ -87,8 +92,107 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id): if vm_id is None: raise Exception("Could not create VM") - handle_metadata_and_emails(order_id, vm_id, manager, user, specs, - template) + # 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) except Exception as e: logger.error(str(e)) try: @@ -110,127 +214,3 @@ 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': ['dcl-orders@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) diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 1f7a3cda..02bce0ed 100644 --- a/datacenterlight/templates/datacenterlight/order_detail.html +++ b/datacenterlight/templates/datacenterlight/order_detail.html @@ -2,14 +2,6 @@ {% load staticfiles bootstrap3 i18n custom_tags humanize %} {% block content %} -
{% if messages %}
@@ -111,61 +103,58 @@

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

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

+
+
+
+
+
+
+
+

+
+
+

{% trans "Pre VAT" %}

+
+
+

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

+
-
-

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

+
+
+

Price

+
+
+

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

+
+
+

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

+
-
-
+
+
+
+
+
+
+
+

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

@@ -278,16 +267,15 @@ {% if generic_payment_details %} {% if generic_payment_details.recurring %} {% if generic_payment_details.recurring_interval == 'year' %} -
{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %} By clicking "Place order" you agree to our Terms of Service and this plan will charge your credit card account with {{ total_price }} CHF/year{% endblocktrans %}.
+
{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{total_price}} CHF/year{% endblocktrans %}.
{% else %} -
{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %} - By clicking "Place order" you agree to our Terms of Service and this plan will charge your credit card account with {{ total_price }} CHF/month{% endblocktrans %}.
+
{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{total_price}} CHF/month{% endblocktrans %}.
{% endif %} {% else %} -
{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" you agree to our Terms of Service and this plan will charge your credit card account with {{ total_price }} CHF{% endblocktrans %}.
+
{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" this payment will charge your credit card account with a one time amount of {{total_price}} CHF{% endblocktrans %}.
{% endif %} {% else %} -
{% blocktrans with vm_total_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_total_price }} CHF/month{% endblocktrans %}.
+
{% blocktrans with vm_total_price=vm.total_price|floatformat:2|intcomma %}By clicking "Place order" this plan will charge your credit card account with {{vm_total_price}} CHF/month{% endblocktrans %}.
{% endif %}
@@ -330,14 +318,5 @@ {%endblock%} diff --git a/datacenterlight/templatetags/custom_tags.py b/datacenterlight/templatetags/custom_tags.py index fc39eec7..0cb18e5b 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, HostingOrder +from hosting.models import GenericProduct from utils.hosting_utils import get_ip_addresses logger = logging.getLogger(__name__) @@ -63,42 +63,6 @@ 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): - """ - 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: - """ - try: - print("Hositng order id = %s" % hosting_order_id) - hosting_order = HostingOrder.objects.get(id = hosting_order_id) - if hosting_order.stripe_charge_id: - return mark_safe(""" - {product_name} - {created_at} - {total} - - {see_invoice_text} - - """.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: - return "" - except Exception as ex: - logger.error("Error %s" % str(ex)) - return "" - - @register.filter('get_line_item_from_stripe_invoice') def get_line_item_from_stripe_invoice(invoice): """ @@ -115,7 +79,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 if line_data.plan is not None else "" + plan_name = line_data.plan.name start_date = line_data.period.start end_date = line_data.period.end is_first = False @@ -145,7 +109,7 @@ def get_line_item_from_stripe_invoice(invoice): """.format( vm_id=vm_id if vm_id > 0 else "", ip_addresses=mark_safe(get_ip_addresses(vm_id)) if vm_id > 0 else - mark_safe(get_product_name(plan_name)) if plan_name.startswith("generic-") else plan_name, + mark_safe(get_product_name(plan_name)), period=mark_safe("%s — %s" % ( datetime.datetime.fromtimestamp(start_date).strftime('%Y-%m-%d'), datetime.datetime.fromtimestamp(end_date).strftime('%Y-%m-%d'))), @@ -161,7 +125,8 @@ def get_product_name(plan_name): product_name = "" if plan_name and plan_name.startswith("generic-"): first_index_hyphen = plan_name.index("-") + 1 - product_id = plan_name[first_index_hyphen:(plan_name[first_index_hyphen:].index("-")) + first_index_hyphen] + product_id = plan_name[first_index_hyphen: + (plan_name[first_index_hyphen:].index("-")) + first_index_hyphen] try: product = GenericProduct.objects.get(id=product_id) product_name = product.product_name diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 4e8094c0..97bfef4c 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', 'pl', 'pt', 'ro','sk', 'si', 'es', + 'lv', 'lu', 'mt', 'nl', 'po', 'pt', 'ro','sk', 'si', 'es', 'se', 'gb'] @@ -38,7 +38,6 @@ 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'], @@ -103,6 +102,8 @@ 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: @@ -111,8 +112,7 @@ def clear_all_session_vars(request): 'token', 'customer', 'generic_payment_type', 'generic_payment_details', 'product_id', 'order_confirm_url', 'new_user_hosting_key_id', - 'vat_validation_status', 'billing_address_id', - 'id_payment_method']: + 'vat_validation_status', 'billing_address_id']: if session_var in request.session: del request.session[session_var] diff --git a/datacenterlight/views.py b/datacenterlight/views.py index c708ff6a..17a0479f 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1,4 +1,3 @@ -import json import logging import stripe @@ -8,9 +7,7 @@ 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 from django.shortcuts import render from django.utils.translation import get_language, ugettext_lazy as _ from django.views.decorators.cache import cache_control @@ -22,8 +19,9 @@ from hosting.forms import ( ) from hosting.models import ( HostingBill, HostingOrder, UserCardDetail, GenericProduct, UserHostingKey, - StripeTaxRate, IncompleteSubscriptions, IncompletePaymentIntents) + StripeTaxRate) 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, @@ -42,8 +40,6 @@ from .utils import ( get_cms_integration, create_vm, clear_all_session_vars, validate_vat_number ) -from datacenterlight.templatetags.custom_tags import get_product_name - logger = logging.getLogger(__name__) @@ -65,23 +61,23 @@ class ContactUsView(FormView): ) def form_valid(self, form): - #form.save() - #from_emails = { - # 'glasfaser': 'glasfaser@ungleich.ch' - #} - #from_page = self.request.POST.get('from_page') - #email_data = { - # 'subject': "{dcl_text} Message from {sender}".format( - # dcl_text=settings.DCL_TEXT, - # sender=form.cleaned_data.get('email') - # ), - # 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, - # 'to': [from_emails.get(from_page, 'support@ungleich.ch')], - # 'body': "\n".join( - # ["%s=%s" % (k, v) for (k, v) in form.cleaned_data.items()]), - # 'reply_to': [form.cleaned_data.get('email')], - #} - #send_plain_email_task.delay(email_data) + form.save() + from_emails = { + 'glasfaser': 'glasfaser@ungleich.ch' + } + from_page = self.request.POST.get('from_page') + email_data = { + 'subject': "{dcl_text} Message from {sender}".format( + dcl_text=settings.DCL_TEXT, + sender=form.cleaned_data.get('email') + ), + 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': [from_emails.get(from_page, 'support@ungleich.ch')], + 'body': "\n".join( + ["%s=%s" % (k, v) for (k, v) in form.cleaned_data.items()]), + 'reply_to': [form.cleaned_data.get('email')], + } + send_plain_email_task.delay(email_data) if self.request.is_ajax(): return self.render_to_response( self.get_context_data(success=True, contact_form=form)) @@ -201,8 +197,6 @@ class IndexView(CreateView): ssd_size=storage, pricing_name=vm_pricing_name ) - if request.user.id == 51: - print("User is test") specs = { 'cpu': cores, 'memory': memory, @@ -268,11 +262,9 @@ class PaymentOrderView(FormView): stripe_customer = user.stripecustomer else: stripe_customer = None - stripe_utils = StripeUtils() - cards_list_request = stripe_utils.get_available_payment_methods( - stripe_customer + cards_list = UserCardDetail.get_all_cards_list( + stripe_customer=stripe_customer ) - cards_list = cards_list_request.get('response_object') context.update({'cards_list': cards_list}) else: billing_address_form = BillingAddressFormSignup( @@ -318,12 +310,6 @@ class PaymentOrderView(FormView): @cache_control(no_cache=True, must_revalidate=True, no_store=True) def get(self, request, *args, **kwargs): request.session.pop('vat_validation_status') - request.session.pop('card_id') - request.session.pop('token') - 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)) if (('type' in request.GET and request.GET['type'] == 'generic') or 'product_slug' in kwargs): request.session['generic_payment_type'] = 'generic' @@ -438,10 +424,8 @@ class PaymentOrderView(FormView): ) gp_details = { "product_name": product.product_name, - "vat_rate": 0 if product.exclude_vat_calculations else - user_country_vat_rate * 100, - "vat_amount": 0 if product.exclude_vat_calculations - else round( + "vat_rate": user_country_vat_rate * 100, + "vat_amount": round( float(product.product_price) * user_country_vat_rate, 2), "vat_country": address_form.cleaned_data["country"], @@ -460,8 +444,7 @@ class PaymentOrderView(FormView): "product_id": product.id, "product_slug": product.product_slug, "recurring_interval": - product.product_subscription_interval, - "exclude_vat_calculations": product.exclude_vat_calculations + product.product_subscription_interval } request.session["generic_payment_details"] = ( gp_details @@ -472,20 +455,42 @@ class PaymentOrderView(FormView): context['generic_payment_form'] = generic_payment_form context['billing_address_form'] = address_form return self.render_to_response(context) - id_payment_method = self.request.POST.get('id_payment_method', - None) - if id_payment_method == 'undefined': - 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) + token = address_form.cleaned_data.get('token') + if token is '': + card_id = address_form.cleaned_data.get('card') + 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['token'] = token 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'), - id_payment_method=id_payment_method + email=this_user.get('email'), token=token ) else: user_email = address_form.cleaned_data.get('email') @@ -508,7 +513,7 @@ class PaymentOrderView(FormView): ) customer = StripeCustomer.create_stripe_api_customer( email=user_email, - id_payment_method=id_payment_method, + token=token, customer_name=user_name) except CustomUser.DoesNotExist: logger.debug( @@ -519,7 +524,7 @@ class PaymentOrderView(FormView): ) customer = StripeCustomer.create_stripe_api_customer( email=user_email, - id_payment_method=id_payment_method, + token=token, customer_name=user_name) billing_address = address_form.save() @@ -588,37 +593,25 @@ 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 - print("******************************") - print("User = {}, ID = {}".format(request.user, request.user.id)) - print("******************************") - amount_to_charge = 0 - vm_specs = None if (('specs' not in request.session or 'user' not in request.session) and 'generic_payment_type' not in request.session): 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) + if 'token' in self.request.session: + token = self.request.session['token'] stripe_utils = StripeUtils() - card_details = stripe_utils.get_cards_details_from_payment_method( - payment_method + card_details = stripe_utils.get_cards_details_from_token( + token ) if not card_details.get('response_object'): - return HttpResponseRedirect(reverse('datacenterlight:payment')) + return HttpResponseRedirect(reverse('hosting:payment')) card_details_response = card_details['response_object'] 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['id_payment_method'] = payment_method + context['cc_exp_month'] = '{:02d}'.format(card_details_response['exp_month']) 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.get_ucd_from_card_id(card_id=card_id) + card_detail = UserCardDetail.objects.get(id=card_id) context['cc_last4'] = card_detail.last4 context['cc_brand'] = card_detail.brand context['cc_exp_year'] = card_detail.exp_year @@ -631,14 +624,11 @@ 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'], }) - amount_to_charge = request.session['generic_payment_details']['amount'] else: vm_specs = request.session.get('specs') user_vat_country = ( @@ -652,14 +642,11 @@ class OrderConfirmationView(DetailView, FormView): pricing_name=vm_specs['pricing_name'], vat_rate=user_country_vat_rate * 100 ) - if request.user.id == 51: - print("User is test") 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"]) + 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'], @@ -673,6 +660,7 @@ 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": @@ -704,51 +692,6 @@ class OrderConfirmationView(DetailView, FormView): 'form': UserHostingKeyForm(request=self.request), 'keys': get_all_public_keys(self.request.user) }) - - 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("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 - }) - 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'), 'page_header_text': _('Confirm Order'), @@ -756,67 +699,42 @@ class OrderConfirmationView(DetailView, FormView): request.session.get('billing_address_data') ), '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, - 'is_subscription': str(is_subscription).lower() }) 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() - 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, - 'user': user - } - if 'id_payment_method' in request.session: - card_details = stripe_utils.get_cards_details_from_payment_method( - request.session.get('id_payment_method') + if 'token' in request.session: + card_details = stripe_utils.get_cards_details_from_token( + request.session.get('token') ) - logger.debug( - "card_details=%s" % (card_details)) if not card_details.get('response_object'): msg = card_details.get('error') - return show_error(msg, self.request) + 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) card_details_response = card_details['response_object'] card_details_dict = { 'last4': card_details_response['last4'], @@ -831,7 +749,7 @@ class OrderConfirmationView(DetailView, FormView): ) if not ucd: acc_result = stripe_utils.associate_customer_card( - stripe_api_cus_id, request.session['id_payment_method'], + stripe_api_cus_id, request.session['token'], set_as_default=True ) if acc_result['response_object'] is None: @@ -841,35 +759,38 @@ class OrderConfirmationView(DetailView, FormView): details=acc_result['error'] ) ) - 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) + 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) elif 'card_id' in request.session: card_id = request.session.get('card_id') - user_card_detail = UserCardDetail.get_ucd_from_card_id(card_id=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 } - 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 = { 'status': False, @@ -887,15 +808,44 @@ 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") + 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') + 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) + 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'] @@ -909,21 +859,15 @@ class OrderConfirmationView(DetailView, FormView): 2 ) ) - stripe_plan_id = "generic-{0}-{1:.2f}".format( + plan_name = "generic-{0}-{1:.2f}".format( request.session['generic_payment_details']['product_id'], amount_to_be_charged ) - try: - product = GenericProduct.objects.get(id=request.session['generic_payment_details']['product_id']) - plan_name = product.product_name - except Exception as ex: - logger.debug("Errori {}" % str(ex)) - plan_name = get_product_name(stripe_plan_id) + stripe_plan_id = plan_name recurring_interval = request.session['generic_payment_details']['recurring_interval'] if recurring_interval == "year": - stripe_plan_id = "{}-yearly".format(stripe_plan_id) - plan_name = "{} (yearly)".format(plan_name) - logger.debug("Plan name = {}, Stripe Plan id = {}".format(plan_name, stripe_plan_id)) + plan_name = "{}-yearly".format(plan_name) + stripe_plan_id = plan_name else: template = request.session.get('template') specs = request.session.get('specs') @@ -950,7 +894,6 @@ 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, @@ -997,133 +940,253 @@ 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) - latest_invoice = stripe.Invoice.retrieve( - stripe_subscription_obj.latest_invoice) - subscription_status = '' - if stripe_subscription_obj: - subscription_status = stripe_subscription_obj.status - # Check if the subscription was approved and is active if (stripe_subscription_obj is None - or (stripe_subscription_obj.status != 'active' - and stripe_subscription_obj.status != 'incomplete')): + or stripe_subscription_obj.status != 'active'): # 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. # ... - logger.debug("In 1 ***") - logger.debug("stripe_subscription_obj == %s" % stripe_subscription_obj) + msg = subscription_result.get('error') - return show_error(msg, self.request) - elif stripe_subscription_obj.status == 'incomplete': - # 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') + 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) + + # 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 ) - pi = stripe.PaymentIntent.retrieve( - 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') + 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() - if (pi.status == 'requires_attention' or - pi.status == 'requires_source_action'): - logger.debug("Display SCA authentication %s " % pi.status) - context = { - 'sid': stripe_subscription_obj.id, - 'payment_intent_secret': pi.client_secret, - 'STRIPE_PUBLISHABLE_KEY': settings.STRIPE_API_PUBLIC_KEY, - 'showSCA': True, - 'success': { - 'status': True, - 'redirect': redirect_url, - 'msg_title': str(_('Thank you for the order.')), - 'msg_body': str( - _('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, - '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: - 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) - # 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, - stripe_onetime_charge, gp_details, specs, vm_template_id, - template, request.session.get('billing_address_data'), - self.request + 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 + 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 ) - if (provisioning_response and - 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, 'redirect': ( @@ -1139,522 +1202,3 @@ class OrderConfirmationView(DetailView, FormView): } return JsonResponse(response) - - -def create_incomplete_intent_request(request): - """ - Creates a dictionary of all session variables so that they could be - picked up in the webhook for processing. - - :param request: - :return: - """ - 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), - 'id_payment_method': request.session.get('id_payment_method', 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=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(email)) - password = CustomUser.get_random_password() - base_url = "{0}://{1}".format(request['scheme'], - request['host']) - custom_user = CustomUser.register( - name, password, - email, - app='dcl', base_url=base_url, send_email=True, - account_details=password - ) - 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 - new_user = authenticate(username=custom_user.email, - password=password) - logger.debug("User %s is authenticated" % custom_user.email) - 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=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) - return custom_user, new_user - - -def set_user_card(card_id, stripe_api_cus_id, custom_user, - card_details_response): - logger.debug(":: set_user_card") - if card_id: - logger.debug("card_id %s was in request" % card_id) - user_card_detail = UserCardDetail.get_ucd_from_card_id(card_id=card_id) - 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 - ) - else: - logger.debug(" card_id was NOT in request, using " - "card_details_response") - ucd = UserCardDetail.get_or_create_user_card_detail( - stripe_customer=custom_user.stripecustomer, - card_details=card_details_response - ) - logger.debug(" ucd = %s" % ucd) - UserCardDetail.save_default_card_local( - custom_user.stripecustomer.stripe_id, - ucd.card_id - ) - logger.debug(" after save_default_card_local") - card_details_dict = { - 'last4': ucd.last4, - 'brand': ucd.brand, - 'card_id': ucd.card_id - } - logger.debug("card_detail_dict = %s" % card_details_dict) - 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): - stripe_utils = StripeUtils() - acc_result = stripe_utils.associate_customer_card( - 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') - )) - 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) - 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 - }) - 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': ['dcl-orders@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 HttpResponse(status=200) - - -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: - """ - - logger.debug("do_provisioning") - - try: - params = { - 'request': request, - 'stripe_api_cus_id': stripe_api_cus_id, - 'card_details_response': card_details_response, - 'stripe_subscription_obj': stripe_subscription_obj, - 'stripe_onetime_charge': stripe_onetime_charge, - 'gp_details': gp_details, - 'specs': specs, - 'vm_template_id': vm_template_id, - 'template': template, - 'billing_address_data': billing_address_data, - 'real_request': real_request - } - print("Input Parameters:") - for key, value in params.items(): - try: - print("{}: {}".format(key, value)) - except Exception as e: - print("{}: [Error printing value: {}]".format(key, str(e))) - except Exception as e: - print("Error printing parameters: {}".format(str(e))) - - user = request.get('user', None) - - # Create user if the user is not logged in and if he is not already - # 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) - logger.debug("after set_user_card %s" % card_details_dict) - - # Save billing address - billing_address_data.update({ - 'user': custom_user.id - }) - logger.debug('billing_address_data is {}'.format(billing_address_data)) - - 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() - 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() - logger.debug("billing_address saved") - - order = HostingOrder.create( - price=request['generic_payment_details']['amount'], - customer=stripe_cus, - billing_address=billing_address, - vm_pricing=VMPricing.get_default_pricing() - ) - logger.debug("hosting order created") - - # 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_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 - 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': ['dcl-orders@ungleich.ch'], - 'body': "\n".join( - ["%s=%s" % (k, v) for (k, v) in context.items()]), - 'reply_to': [context['email']], - } - logger.debug("Sending email") - send_plain_email_task.delay(email_data) - logger.debug("After Sending email") - recurring_text = _(" This is a monthly recurring plan.") - if gp_details['recurring_interval'] == "year": - recurring_text = _(" This is an yearly recurring plan.") - - 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'], - } - logger.debug("Before Sending customer email") - 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, - '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} - - user = { - 'name': custom_user.name, - 'email': custom_user.email, - 'username': custom_user.username, - 'pass': custom_user.password, - 'request_scheme': request['scheme'], - 'request_host': request['host'], - 'language': request['language'], - } - - create_vm( - billing_address_data, custom_user.stripecustomer.id, specs, - stripe_subscription_obj, card_details_dict, request, - vm_template_id, template, user - ) - - if real_request: - clear_all_session_vars(real_request) - - -def get_error_response_dict(msg, request): - logger.debug("Init get_error_response_dict {}".format(msg)) - 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 response - - -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/digitalglarus/locale/de/LC_MESSAGES/django.po b/digitalglarus/locale/de/LC_MESSAGES/django.po index ef7a46b5..ec96f5dc 100644 --- a/digitalglarus/locale/de/LC_MESSAGES/django.po +++ b/digitalglarus/locale/de/LC_MESSAGES/django.po @@ -376,6 +376,8 @@ msgid "" " digitalglarus.ch
\n" " hack4lgarus.ch
\n" " ipv6onlyhosting.com
\n" +" ipv6onlyhosting.ch
\n" +" ipv6onlyhosting.net
\n" " django-hosting.ch
\n" " rails-hosting.ch
\n" " node-hosting.ch
\n" @@ -634,8 +636,8 @@ msgstr "" "Internetangebot der ungleich glarus ag, welches unter den nachfolgenden " "Domains erreichbar ist:

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

Der Datenschutzbeauftragte des Verantwortlichen ist:

Sanghee Kim
ungleich glarus ag
Bahnhofstrasse 1
8783 " "Linthal (CH)
E-Mail: sanghee." @@ -836,4 +838,3 @@ msgstr "" #~ msgid "index/?$" #~ msgstr "index/?$" - diff --git a/digitalglarus/views.py b/digitalglarus/views.py index bf3ed2c6..299327e6 100644 --- a/digitalglarus/views.py +++ b/digitalglarus/views.py @@ -835,10 +835,9 @@ class ContactView(FormView): success_message = _('Message Successfully Sent') def form_valid(self, form): - print("digital glarus contactusform") - #form.save() - #form.send_email() - #messages.add_message(self.request, messages.SUCCESS, self.success_message) + form.save() + form.send_email() + messages.add_message(self.request, messages.SUCCESS, self.success_message) return super(ContactView, self).form_valid(form) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index b7705e17..59ca0fd2 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -56,9 +56,6 @@ dotenv.load_dotenv("{0}/.env".format(PROJECT_DIR)) from multisite import SiteID -RECAPTCHA_PUBLIC_KEY = env('RECAPTCHA_PUBLIC_KEY') -RECAPTCHA_PRIVATE_KEY = env('RECAPTCHA_PRIVATE_KEY') - UNGLEICH_BLOG_SITE_ID = int_env("UNGLEICH_BLOG_SITE_ID") SITE_ID = SiteID(default=(UNGLEICH_BLOG_SITE_ID if UNGLEICH_BLOG_SITE_ID > 0 else 1)) @@ -128,7 +125,6 @@ INSTALLED_APPS = ( 'djangocms_file', 'djangocms_picture', 'djangocms_video', - 'django_recaptcha', # 'djangocms_flash', # 'djangocms_googlemap', # 'djangocms_inherit', @@ -635,6 +631,8 @@ GOOGLE_ANALYTICS_PROPERTY_IDS = { 'datacenterlight.ch': 'UA-62285904-8', 'devuanhosting.ch': 'UA-62285904-9', 'devuanhosting.com': 'UA-62285904-9', + 'ipv6onlyhosting.ch': 'UA-62285904-10', + 'ipv6onlyhosting.net': 'UA-62285904-10', 'ipv6onlyhosting.com': 'UA-62285904-10', 'comic.ungleich.ch': 'UA-62285904-13', '127.0.0.1:8000': 'localhost', @@ -686,6 +684,17 @@ if ENABLE_LOGGING: } } loggers_dict.update(logger_item) + if not 'django' in MODULES_TO_LOG: + loggers_dict.update( + { + 'django': { + 'handlers': ['django_error'], + 'level': 'ERROR', + 'propagate': True + } + } + ) + custom_handler_item = { 'custom_file': { @@ -699,6 +708,18 @@ if ENABLE_LOGGING: 'maxBytes': 1024 * 1024 * 5, 'backupCount': 10, 'formatter': 'standard', + }, + 'django_error': { + 'level': 'ERROR', + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': + "{PROJECT_DIR}/logs/django-error.log".format( + LEVEL=LOG_LEVEL.lower(), + PROJECT_DIR=PROJECT_DIR + ), + 'maxBytes': 1024 * 1024 * 5, + 'backupCount': 10, + 'formatter': 'standard', } } handlers_dict.update(custom_handler_item) @@ -777,9 +798,3 @@ if DEBUG: from .local import * # flake8: noqa else: from .prod import * # flake8: noqa - -# Try to load dynamic configuration, if it exists -try: - from .dynamic import * # flake8: noqa -except ImportError: - pass diff --git a/dynamicweb/settings/prod.py b/dynamicweb/settings/prod.py index 0590ca27..445748ad 100644 --- a/dynamicweb/settings/prod.py +++ b/dynamicweb/settings/prod.py @@ -28,7 +28,9 @@ ALLOWED_HOSTS = [ ".devuanhosting.ch", ".devuanhosting.com", ".digitalezukunft.ch", + ".ipv6onlyhosting.ch", ".ipv6onlyhosting.com", + ".ipv6onlyhosting.net", ".digitalglarus.ch", ".hack4glarus.ch", ".xn--nglarus-n2a.ch" diff --git a/entrypoint.sh b/entrypoint.sh deleted file mode 100755 index e207a457..00000000 --- a/entrypoint.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -set -uex - -cd /usr/src/app/ -cat > dynamicweb/settings/dynamic.py <\n" "Language-Team: LANGUAGE \n" @@ -211,9 +211,6 @@ msgstr "Bezahlbares VM Hosting in der Schweiz" msgid "My Dashboard" msgstr "Mein Dashboard" -msgid "Welcome" -msgstr "" - msgid "My VMs" msgstr "Meine VMs" @@ -367,11 +364,6 @@ msgstr "Abgelehnt" msgid "Billed to" msgstr "Rechnungsadresse" -#, fuzzy -#| msgid "Card Number" -msgid "VAT Number" -msgstr "Kreditkartennummer" - msgid "Payment method" msgstr "Bezahlmethode" @@ -399,9 +391,6 @@ msgstr "Festplattenkapazität" msgid "Subtotal" msgstr "Zwischensumme" -msgid "VAT for" -msgstr "" - msgid "VAT" msgstr "Mehrwertsteuer" @@ -435,22 +424,18 @@ 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" @@ -495,13 +480,11 @@ msgstr "Bestellungsübersicht" #, python-format msgid "" -"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." +"By clicking \"Place order\" this plan will charge your credit card account " +"with %(vm_price)s CHF/month" msgstr "" -"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." +"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(vm_price)s CHF " +"pro Monat belastet" msgid "Place order" msgstr "Bestellen" @@ -521,12 +504,6 @@ msgstr "Schliessen" msgid "Order Nr." msgstr "Bestellung Nr." -msgid "See Invoice" -msgstr "Siehe Rechnung" - -msgid "Page" -msgstr "Seite" - msgid "Your Order" msgstr "Deine Bestellung" @@ -595,19 +572,6 @@ 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" @@ -809,15 +773,21 @@ 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 "" @@ -882,8 +852,7 @@ 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 " @@ -902,9 +871,7 @@ 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" @@ -915,15 +882,6 @@ 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/management/commands/change_ch_vatrate_2023.py b/hosting/management/commands/change_ch_vatrate_2023.py deleted file mode 100644 index 612b246e..00000000 --- a/hosting/management/commands/change_ch_vatrate_2023.py +++ /dev/null @@ -1,144 +0,0 @@ -from django.core.management.base import BaseCommand -import datetime -import csv -import logging -import stripe -from hosting.models import VATRates -from utils.hosting_utils import get_vat_rate_for_country -from django.conf import settings -from membership.models import CustomUser, StripeCustomer - -stripe.api_key = settings.STRIPE_API_PRIVATE_KEY - -logger = logging.getLogger(__name__) - -class Command(BaseCommand): - help = '''CH vat rate changes on 2024-01-01 from 7.7% to 8.1%. This commands makes the necessary changes''' - - def handle(self, *args, **options): - MAKE_MODIFS=False - try: - country_to_change = 'CH' - currency_to_change = 'CHF' - new_rate = 0.081 - user_country_vat_rate = get_vat_rate_for_country(country_to_change) - logger.debug("Existing VATRate for %s %s " % (country_to_change, user_country_vat_rate)) - vat_rate = VATRates.objects.get( - territory_codes=country_to_change, start_date__isnull=False, stop_date=None - ) - logger.debug("VAT rate for %s is %s" % (country_to_change, vat_rate.rate)) - logger.debug("vat_rate object = %s" % vat_rate) - logger.debug("Create end date for the VATRate %s" % vat_rate.id) - # if MAKE_MODIFS: - # vat_rate.stop_date = datetime.date(2023, 12, 31) - # vat_rate.save() - # print("Creating a new VATRate for CH") - # obj, created = VATRates.objects.get_or_create( - # start_date=datetime.date(2024, 1, 1), - # stop_date=None, - # territory_codes=country_to_change, - # currency_code=currency_to_change, - # rate=new_rate, - # rate_type="standard", - # description="Switzerland standard VAT (added manually on %s)" % datetime.datetime.now() - # ) - # if created: - # logger.debug("Created new VAT Rate for %s with the new rate %s" % (country_to_change, new_rate)) - # logger.debug(obj) - # else: - # logger.debug("VAT Rate for %s already exists with the rate %s" % (country_to_change, new_rate)) - # - logger.debug("Getting all subscriptions of %s that need a VAT Rate change") - subscriptions = stripe.Subscription.list(limit=100) # Increase the limit to 100 per page (maximum) - ch_subs = [] - - while subscriptions: - for subscription in subscriptions: - if len(subscription.default_tax_rates) > 0 and subscription.default_tax_rates[0].jurisdiction and subscription.default_tax_rates[0].jurisdiction.lower() == 'ch': - ch_subs.append(subscription) - elif len(subscription.default_tax_rates) > 0: - print("subscription %s belongs to %s" % (subscription.id, subscription.default_tax_rates[0].jurisdiction)) - else: - print("subscription %s does not have a tax rate" % subscription.id) - if subscriptions.has_more: - print("FETCHING MORE") - subscriptions = stripe.Subscription.list(limit=100, starting_after=subscriptions.data[-1]) - else: - break - logger.debug("There are %s ch subscription that need VAT rate update" % len(ch_subs)) - - # CSV column headers - csv_headers = [ - "customer_name", - "customer_email", - "stripe_customer_id", - "subscription_id", - "subscription_name", - "amount", - "vat_rate" - ] - # CSV file name - csv_filename = "ch_subscriptions_change_2024.csv" - # Write subscription data to CSV file - with open(csv_filename, mode='w', newline='') as csv_file: - writer = csv.DictWriter(csv_file, fieldnames=csv_headers) - writer.writeheader() - - for subscription in ch_subs: - subscription_id = subscription["id"] - stripe_customer_id = subscription.get("customer", "") - vat_rate = subscription.get("tax_percent", "") - c_user = CustomUser.objects.get( - id=StripeCustomer.objects.filter(stripe_id=stripe_customer_id)[0].user.id) - if c_user: - customer_name = c_user.name.encode('utf-8') - customer_email = c_user.email - items = subscription.get("items", {}).get("data", []) - for item in items: - subscription_name = item.get("plan", {}).get("id", "") - amount = item.get("plan", {}).get("amount", "") - - # Convert amount to a proper format (e.g., cents to dollars) - amount_in_chf = amount / 100 # Adjust this conversion as needed - - # Writing to CSV - writer.writerow({ - "customer_name": customer_name, - "customer_email": customer_email, - "stripe_customer_id": stripe_customer_id, - "subscription_id": subscription_id, - "subscription_name": subscription_name, - "amount": amount_in_chf, - "vat_rate": vat_rate # Fill in VAT rate if available - }) - else: - print("No customuser for %s %s" % (stripe_customer_id, subscription_id)) - - - if MAKE_MODIFS: - print("Making modifications now") - tax_rate_obj = stripe.TaxRate.create( - display_name="VAT", - description="VAT for %s" % country_to_change, - jurisdiction=country_to_change, - percentage=new_rate, - inclusive=False, - ) - stripe_tax_rate = StripeTaxRate.objects.create( - display_name=tax_rate_obj.display_name, - description=tax_rate_obj.description, - jurisdiction=tax_rate_obj.jurisdiction, - percentage=tax_rate_obj.percentage, - inclusive=False, - tax_rate_id=tax_rate_obj.id - ) - - for ch_sub in ch_subs: - ch_sub.default_tax_rates = [stripe_tax_rate.tax_rate_id] - ch_sub.save() - logger.debug("Default tax rate updated for %s" % ch_sub.id) - else: - print("Not making any modifications because MAKE_MODIFS=False") - - except Exception as e: - print(" *** Error occurred. Details {}".format(str(e))) diff --git a/hosting/management/commands/change_fi_vatrate_2024.py b/hosting/management/commands/change_fi_vatrate_2024.py deleted file mode 100644 index 6947b6eb..00000000 --- a/hosting/management/commands/change_fi_vatrate_2024.py +++ /dev/null @@ -1,143 +0,0 @@ -from django.core.management.base import BaseCommand -import datetime -import csv -import logging -import stripe -from hosting.models import VATRates, StripeTaxRate -from utils.hosting_utils import get_vat_rate_for_country -from django.conf import settings -from membership.models import CustomUser, StripeCustomer - -stripe.api_key = settings.STRIPE_API_PRIVATE_KEY - -logger = logging.getLogger(__name__) - -class Command(BaseCommand): - help = '''FI vat rate changes on 2024-09-01 from 24% to 25.5%. This commands makes the necessary changes''' - - def handle(self, *args, **options): - MAKE_MODIFS=False - try: - country_to_change = 'FI' - currency_to_change = 'EUR' - new_rate = 25.5 - user_country_vat_rate = get_vat_rate_for_country(country_to_change) - logger.debug("Existing VATRate for %s %s " % (country_to_change, user_country_vat_rate)) - vat_rate = VATRates.objects.get( - territory_codes=country_to_change, start_date__isnull=False, stop_date=None - ) - logger.debug("VAT rate for %s is %s" % (country_to_change, vat_rate.rate)) - logger.debug("vat_rate object = %s" % vat_rate) - logger.debug("Create end date for the VATRate %s" % vat_rate.id) - #if MAKE_MODIFS: - # vat_rate.stop_date = datetime.date(2024, 8, 31) - # vat_rate.save() - # print("Creating a new VATRate for FI") - # obj, created = VATRates.objects.get_or_create( - # start_date=datetime.date(2024, 9, 1), - # stop_date=None, - # territory_codes=country_to_change, - # currency_code=currency_to_change, - # rate=new_rate * 0.01, - # rate_type="standard", - # description="FINLAND standard VAT (added manually on %s)" % datetime.datetime.now() - # ) - # if created: - # logger.debug("Created new VAT Rate for %s with the new rate %s" % (country_to_change, new_rate)) - # logger.debug(obj) - # else: - # logger.debug("VAT Rate for %s already exists with the rate %s" % (country_to_change, new_rate)) - logger.debug("Getting all subscriptions of %s that need a VAT Rate change") - subscriptions = stripe.Subscription.list(limit=100) # Increase the limit to 100 per page (maximum) - fi_subs = [] - - while subscriptions: - for subscription in subscriptions: - if len(subscription.default_tax_rates) > 0 and subscription.default_tax_rates[0].jurisdiction and subscription.default_tax_rates[0].jurisdiction.lower() == 'fi': - fi_subs.append(subscription) - elif len(subscription.default_tax_rates) > 0: - print("subscription %s belongs to %s" % (subscription.id, subscription.default_tax_rates[0].jurisdiction)) - else: - print("subscription %s does not have a tax rate" % subscription.id) - if subscriptions.has_more: - print("FETCHING MORE") - subscriptions = stripe.Subscription.list(limit=100, starting_after=subscriptions.data[-1]) - else: - break - logger.debug("There are %s FI subscription that need VAT rate update" % len(fi_subs)) - - # CSV column headers - csv_headers = [ - "customer_name", - "customer_email", - "stripe_customer_id", - "subscription_id", - "subscription_name", - "amount", - "vat_rate" - ] - # CSV file name - csv_filename = "fi_subscriptions_change_2024.csv" - # Write subscription data to CSV file - with open(csv_filename, mode='w', newline='') as csv_file: - writer = csv.DictWriter(csv_file, fieldnames=csv_headers) - writer.writeheader() - - for subscription in fi_subs: - subscription_id = subscription["id"] - stripe_customer_id = subscription.get("customer", "") - vat_rate = subscription.get("tax_percent", "") - c_user = CustomUser.objects.get( - id=StripeCustomer.objects.filter(stripe_id=stripe_customer_id)[0].user.id) - if c_user: - customer_name = c_user.name.encode('utf-8') - customer_email = c_user.email - items = subscription.get("items", {}).get("data", []) - for item in items: - subscription_name = item.get("plan", {}).get("id", "") - amount = item.get("plan", {}).get("amount", "") - - # Convert amount to a proper format (e.g., cents to dollars) - amount_in_chf = amount / 100 # Adjust this conversion as needed - - # Writing to CSV - writer.writerow({ - "customer_name": customer_name, - "customer_email": customer_email, - "stripe_customer_id": stripe_customer_id, - "subscription_id": subscription_id, - "subscription_name": subscription_name, - "amount": amount_in_chf, - "vat_rate": vat_rate # Fill in VAT rate if available - }) - else: - print("No customuser for %s %s" % (stripe_customer_id, subscription_id)) - - - if MAKE_MODIFS: - print("Making modifications now") - tax_rate_obj = stripe.TaxRate.create( - display_name="VAT", - description="VAT for %s" % country_to_change, - jurisdiction=country_to_change, - percentage=new_rate, - inclusive=False, - ) - stripe_tax_rate = StripeTaxRate.objects.create( - display_name=tax_rate_obj.display_name, - description=tax_rate_obj.description, - jurisdiction=tax_rate_obj.jurisdiction, - percentage=tax_rate_obj.percentage, - inclusive=False, - tax_rate_id=tax_rate_obj.id - ) - - for fi_sub in fi_subs: - fi_sub.default_tax_rates = [stripe_tax_rate.tax_rate_id] - fi_sub.save() - logger.debug("Default tax rate updated for %s" % fi_sub.id) - else: - print("Not making any modifications because MAKE_MODIFS=False") - - except Exception as e: - print(" *** Error occurred. Details {}".format(str(e))) diff --git a/hosting/migrations/0060_update_DE_vat_covid-19.py b/hosting/migrations/0060_update_DE_vat_covid-19.py deleted file mode 100644 index 17c6394b..00000000 --- a/hosting/migrations/0060_update_DE_vat_covid-19.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- 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( - 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 = null 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'"], - ), - ] diff --git a/hosting/migrations/0061_genericproduct_exclude_vat_calculations.py b/hosting/migrations/0061_genericproduct_exclude_vat_calculations.py deleted file mode 100644 index 0bef80d4..00000000 --- a/hosting/migrations/0061_genericproduct_exclude_vat_calculations.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- 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'), - ), - ] diff --git a/hosting/migrations/0062_incompletesubscriptions.py b/hosting/migrations/0062_incompletesubscriptions.py deleted file mode 100644 index 0405e086..00000000 --- a/hosting/migrations/0062_incompletesubscriptions.py +++ /dev/null @@ -1,39 +0,0 @@ -# -*- 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), - ), - ] diff --git a/hosting/migrations/0063_auto_20201223_0612.py b/hosting/migrations/0063_auto_20201223_0612.py deleted file mode 100644 index eb4ca9d4..00000000 --- a/hosting/migrations/0063_auto_20201223_0612.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- 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/migrations/0064_incompletepaymentintents.py b/hosting/migrations/0064_incompletepaymentintents.py deleted file mode 100644 index 868e053e..00000000 --- a/hosting/migrations/0064_incompletepaymentintents.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- 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), - ), - ] diff --git a/hosting/migrations/0065_auto_20201231_1041.py b/hosting/migrations/0065_auto_20201231_1041.py deleted file mode 100644 index 936ccab1..00000000 --- a/hosting/migrations/0065_auto_20201231_1041.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- 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/migrations/0066_auto_20230727_0812.py b/hosting/migrations/0066_auto_20230727_0812.py deleted file mode 100644 index 795b1785..00000000 --- a/hosting/migrations/0066_auto_20230727_0812.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.4 on 2023-07-27 08:12 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('hosting', '0065_auto_20201231_1041'), - ] - - operations = [ - migrations.AlterField( - model_name='genericproduct', - name='product_price', - field=models.DecimalField(decimal_places=2, max_digits=10), - ), - migrations.AlterField( - model_name='genericproduct', - name='product_vat', - field=models.DecimalField(decimal_places=4, default=0, max_digits=10), - ), - ] diff --git a/hosting/models.py b/hosting/models.py index 12edba65..67c55aa2 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -1,3 +1,4 @@ +import decimal import json import logging import os @@ -75,28 +76,21 @@ class GenericProduct(AssignPermissionsMixin, models.Model): ) product_description = models.CharField(max_length=500, default="") created_at = models.DateTimeField(auto_now_add=True) - product_price = models.DecimalField(max_digits=10, decimal_places=2) - product_vat = models.DecimalField(max_digits=10, decimal_places=4, default=0) + product_price = models.DecimalField(max_digits=6, decimal_places=2) + product_vat = models.DecimalField(max_digits=6, decimal_places=4, default=0) product_is_subscription = models.BooleanField(default=True) 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): - 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 - ) + 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): @@ -169,12 +163,8 @@ class HostingOrder(AssignPermissionsMixin, models.Model): def set_stripe_charge(self, stripe_charge): self.stripe_charge_id = stripe_charge.id - 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.last4 = stripe_charge.source.last4 + self.cc_brand = stripe_charge.source.brand self.save() def set_subscription_id(self, subscription_id, cc_details): @@ -676,11 +666,7 @@ class UserCardDetail(AssignPermissionsMixin, models.Model): stripe_utils = StripeUtils() cus_response = stripe_utils.get_customer(stripe_api_cus_id) cu = cus_response['response_object'] - 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.default_source = stripe_source_id cu.save() UserCardDetail.save_default_card_local( stripe_api_cus_id, stripe_source_id @@ -699,116 +685,15 @@ class UserCardDetail(AssignPermissionsMixin, models.Model): @staticmethod def save_default_card_local(stripe_api_cus_id, card_id): - print("save_default_card_local {}, {}".format(stripe_api_cus_id, card_id)) stripe_cust = StripeCustomer.objects.get(stripe_id=stripe_api_cus_id) - print(" stripe_cust={}".format(stripe_cust)) - user_card_detail = UserCardDetail.get_ucd_from_stripe_cust_n_card_id( - stripe_cust, card_id + user_card_detail = UserCardDetail.objects.get( + stripe_customer=stripe_cust, card_id=card_id ) - print(" user_card_detail={}".format(user_card_detail.__dict__)) for card in stripe_cust.usercarddetail_set.all(): card.preferred = False card.save() user_card_detail.preferred = True user_card_detail.save() - print(" save_default_card_local DONE") - - @staticmethod - def get_ucd_from_card_id(card_id): - try: - user_card_details = UserCardDetail.objects.filter( - card_id=card_id - ).order_by('-id') - - if user_card_details.count() > 1: - # Log a warning about the duplicate entries - logger.warning( - "Multiple UserCardDetail objects found for card_id={}. " - "Found {} objects. Using the latest one.".format( - card_id, user_card_details.count() - ) - ) - # Use the first object found - user_card_detail = user_card_details.first() - elif user_card_details.count() == 1: - # Exactly one object found, proceed as intended - user_card_detail = user_card_details.first() - else: - # No object found for the given customer and card_id. - # Depending on expected behavior, you might want to raise an error or handle this case. - # If the original get() call happened here, it would raise DoesNotExist. - logger.error( - "No UserCardDetail found for card_id={}.".format(card_id) - ) - raise UserCardDetail.DoesNotExist("No UserCardDetail found for card {}".format(card_id)) - if user_card_details.count() > 1: - # Log a warning about the duplicate entries - logger.warning( - "Multiple UserCardDetail objects found for card_id={}. " - "Found {} objects. Using the first one.".format( - card_id, user_card_details.count() - ) - ) - # Use the first object found - user_card_detail = user_card_details.first() - elif user_card_details.count() == 1: - # Exactly one object found, proceed as intended - user_card_detail = user_card_details.first() - else: - # No object found for the given customer and card_id. - # Depending on expected behavior, you might want to raise an error or handle this case. - # If the original get() call happened here, it would raise DoesNotExist. - logger.error( - "No UserCardDetail found for card_id={}.".format(card_id) - ) - raise UserCardDetail.DoesNotExist("No UserCardDetail found for card {}".format(card_id)) - except Exception as e: - # Catch other potential exceptions during the filter/get process if necessary - logger.error("An unexpected error occurred while fetching UserCardDetail: {}".format(e)) - raise - return user_card_detail - - @staticmethod - def get_ucd_from_stripe_cust_n_card_id(stripe_cust, card_id): - try: - user_card_details = UserCardDetail.objects.filter( - stripe_customer=stripe_cust, card_id=card_id - ).order_by('-id') - - if user_card_details.count() > 1: - # Log a warning about the duplicate entries - logger.warning( - "Multiple UserCardDetail objects found for stripe_customer_id={} and card_id={}. " - "Found {} objects. Using the first one.".format( - stripe_cust.id, card_id, user_card_details.count() - ) - ) - # Use the first object found - user_card_detail = user_card_details.first() - elif user_card_details.count() == 1: - # Exactly one object found, proceed as intended - user_card_detail = user_card_details.first() - else: - # No object found for the given customer and card_id. - # Depending on expected behavior, you might want to raise an error or handle this case. - # If the original get() call happened here, it would raise DoesNotExist. - logger.error( - "No UserCardDetail found for stripe_customer_id={} and card_id={}.".format( - stripe_cust.id, card_id - ) - ) - raise UserCardDetail.DoesNotExist( - "No UserCardDetail found for customer {}, card {}".format( - stripe_cust.id, card_id - ) - ) - - except Exception as e: - # Catch other potential exceptions during the filter/get process if necessary - logger.error("An unexpected error occurred while fetching UserCardDetail: {}".format(e)) - raise - return user_card_detail - @staticmethod def get_user_card_details(stripe_customer, card_details): @@ -849,35 +734,3 @@ class StripeTaxRate(AssignPermissionsMixin, models.Model): display_name = models.CharField(max_length=100) percentage = models.FloatField(default=0) 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.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() - - -class IncompleteSubscriptions(AssignPermissionsMixin, models.Model): - created_at = models.DateTimeField(auto_now_add=True) - 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) - 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/hosting/static/hosting/js/payment.js b/hosting/static/hosting/js/payment.js index 3c4d67da..fa89f218 100644 --- a/hosting/static/hosting/js/payment.js +++ b/hosting/static/hosting/js/payment.js @@ -84,72 +84,68 @@ $(document).ready(function () { var hasCreditcard = window.hasCreditcard || false; if (!hasCreditcard && window.stripeKey) { var stripe = Stripe(window.stripeKey); - 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")' - } - ], - 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', - 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 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")' } - 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); + ], + 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', + 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 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'); @@ -167,7 +163,7 @@ $(document).ready(function () { if (parts.length === 2) return parts.pop().split(";").shift(); } - function submitBillingForm(pmId) { + function submitBillingForm() { var billing_form = $('#billing-form'); var recurring_input = $('#id_generic_payment_form-recurring'); billing_form.append(''); @@ -178,40 +174,11 @@ $(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(); - - 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; - } + $form_new.submit(payWithStripe_new); function payWithStripe_new(e) { e.preventDefault(); @@ -230,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/hosting/static/hosting/js/virtual_machine_detail.js b/hosting/static/hosting/js/virtual_machine_detail.js index 72770182..28592883 100644 --- a/hosting/static/hosting/js/virtual_machine_detail.js +++ b/hosting/static/hosting/js/virtual_machine_detail.js @@ -92,117 +92,50 @@ $(document).ready(function() { }); var create_vm_form = $('#virtual_machine_create_form'); - 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'); - } - } - }, - error: function (xmlhttprequest, textstatus, message) { + 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'); + $('#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) { 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; + } }); - } 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); - } - }); - } - } + return false; + }); $('#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"); - $('#subscriptions').hide(); - $('#one-time-charges').show(); - }); - $('#li-subscriptions').click(function() { - console.log("li-subscriptions clicked"); - $('#one-time-charges').hide(); - $('#subscriptions').show(); - }); + }) }); window.onload = function () { diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index 347b1ff4..1a97fd1f 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -15,11 +15,6 @@
- -
@@ -71,60 +66,6 @@ {% endif %} {% endif %} - - -
- - - - - - - - - - {% for ho, stripe_charge_data in invs_charge %} - - {{ ho.id | get_line_item_from_hosting_order_charge }} - - {% 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 %} diff --git a/hosting/templates/hosting/order_detail.html b/hosting/templates/hosting/order_detail.html index dee453d5..9256271a 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" you agree to our Terms of Service and 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" this plan will charge your credit card account with {{ vm_price }} CHF/month{% endblocktrans %}.
diff --git a/hosting/urls.py b/hosting/urls.py index e34d27d6..5b2b87b0 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[\w\-]+)/$', SettingsView.as_view(), + url(r'delete_card/(?P\d+)/?$', 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 c2470707..1bd7c1aa 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1,7 +1,6 @@ import logging import uuid from datetime import datetime -from urllib.parse import quote from time import sleep import stripe @@ -14,7 +13,6 @@ 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 ) @@ -554,31 +552,20 @@ class SettingsView(LoginRequiredMixin, FormView): Check if the user already saved contact details. If so, then show the form populated with those details, to let user change them. """ - username = self.request.GET.get('username') - if self.request.user.is_admin and username: - user = CustomUser.objects.get(username=username) - else: - user = self.request.user return form_class( - instance=user.billing_addresses.first(), + instance=self.request.user.billing_addresses.first(), **self.get_form_kwargs()) def get_context_data(self, **kwargs): context = super(SettingsView, self).get_context_data(**kwargs) # Get user - username = self.request.GET.get('username') - if self.request.user.is_admin and username: - user = CustomUser.objects.get(username=username) - else: - user = self.request.user + user = self.request.user stripe_customer = None if hasattr(user, 'stripecustomer'): stripe_customer = user.stripecustomer - stripe_utils = StripeUtils() - cards_list_request = stripe_utils.get_available_payment_methods( - stripe_customer + cards_list = UserCardDetail.get_all_cards_list( + stripe_customer=stripe_customer ) - cards_list = cards_list_request.get('response_object') context.update({ 'cards_list': cards_list, 'stripe_key': settings.STRIPE_API_PUBLIC_KEY @@ -589,38 +576,48 @@ 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=card_id + stripe_source_id=user_card_detail.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=card_details_response['brand'], - last4=card_details_response['last4'] + brand=user_card_detail.brand, + last4=user_card_detail.last4 ) ) messages.add_message(request, messages.SUCCESS, msg) return HttpResponseRedirect(reverse_lazy('hosting:settings')) if 'delete_card' in request.POST: - 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) + 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) return HttpResponseRedirect(reverse_lazy('hosting:settings')) form = self.get_form() if form.is_valid(): @@ -695,49 +692,51 @@ class SettingsView(LoginRequiredMixin, FormView): msg = _("Billing address updated successfully") messages.add_message(request, messages.SUCCESS, msg) else: - id_payment_method = request.POST.get('id_payment_method', None) + token = form.cleaned_data.get('token') stripe_utils = StripeUtils() - card_details = stripe_utils.get_cards_details_from_payment_method( - id_payment_method + card_details = stripe_utils.get_cards_details_from_token( + token ) 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, id_payment_method=id_payment_method + email=request.user.email, token=token ) card = card_details['response_object'] - acc_result = stripe_utils.associate_customer_card( - request.user.stripecustomer.stripe_id, - 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'] - ) - ) + if UserCardDetail.get_user_card_details(stripe_customer, card): + msg = _('You seem to have already added this card') messages.add_message(request, messages.ERROR, msg) - return self.render_to_response(self.get_context_data()) - preferred = False - if stripe_customer.usercarddetail_set.count() == 0: - preferred = True - UserCardDetail.create( - stripe_customer=stripe_customer, - last4=card['last4'], - brand=card['brand'], - fingerprint=card['fingerprint'], - exp_month=card['exp_month'], - exp_year=card['exp_year'], - card_id=card['card_id'], - preferred=preferred - ) - msg = _( - "Successfully associated the card with your account" - ) - messages.add_message(request, messages.SUCCESS, msg) + else: + acc_result = stripe_utils.associate_customer_card( + request.user.stripecustomer.stripe_id, token + ) + if acc_result['response_object'] is None: + msg = _( + 'An error occurred while associating the card.' + ' Details: {details}'.format( + details=acc_result['error'] + ) + ) + messages.add_message(request, messages.ERROR, msg) + return self.render_to_response(self.get_context_data()) + preferred = False + if stripe_customer.usercarddetail_set.count() == 0: + preferred = True + UserCardDetail.create( + stripe_customer=stripe_customer, + last4=card['last4'], + brand=card['brand'], + fingerprint=card['fingerprint'], + exp_month=card['exp_month'], + exp_year=card['exp_year'], + card_id=card['card_id'], + preferred=preferred + ) + msg = _( + "Successfully associated the card with your account" + ) + messages.add_message(request, messages.SUCCESS, msg) return self.render_to_response(self.get_context_data()) else: billing_address_data = form.cleaned_data @@ -810,7 +809,7 @@ class PaymentVMView(LoginRequiredMixin, FormView): card_id = form.cleaned_data.get('card') customer = owner.stripecustomer try: - user_card_detail = UserCardDetail.get_ucd_from_card_id(card_id=card_id) + user_card_detail = UserCardDetail.objects.get(id=card_id) if not request.user.has_perm( 'view_usercarddetail', user_card_detail ): @@ -1014,7 +1013,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): context['cc_exp_month'] = card_details_response['exp_month'] else: card_id = self.request.session.get('card_id') - card_detail = UserCardDetail.get_ucd_from_card_id(card_id=card_id) + card_detail = UserCardDetail.objects.get(id=card_id) context['cc_last4'] = card_detail.last4 context['cc_brand'] = card_detail.brand context['cc_exp_year'] = card_detail.exp_year @@ -1078,13 +1077,7 @@ 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 'id_payment_method' in self.request.session: + if 'token' in self.request.session: card_details = stripe_utils.get_cards_details_from_token( request.session['token'] ) @@ -1101,7 +1094,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): ) if not ucd: acc_result = stripe_utils.associate_customer_card( - stripe_api_cus_id, request.session['id_payment_method'], + stripe_api_cus_id, request.session['token'], set_as_default=True ) if acc_result['response_object'] is None: @@ -1128,7 +1121,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): return JsonResponse(response) else: card_id = request.session.get('card_id') - user_card_detail = UserCardDetail.get_ucd_from_card_id(card_id=card_id) + user_card_detail = UserCardDetail.objects.get(id=card_id) card_details_dict = { 'last4': user_card_detail.last4, 'brand': user_card_detail.brand, @@ -1199,24 +1192,8 @@ 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) - 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'): @@ -1310,11 +1287,10 @@ 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'] - context['user_email'] = '%s' % quote(user_email) + context['user_email'] = user_email logger.debug( "user_email = {}".format(user_email) ) @@ -1324,8 +1300,7 @@ 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, - status='paid') + count=100) paginator = Paginator(invs.data, 10) try: invs_page = paginator.page(page) @@ -1333,21 +1308,6 @@ 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='') - ).order_by('-created_at') - stripe_chgs = [] - for ho in hosting_orders: - stripe_chgs.append({ho: 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( @@ -1361,27 +1321,10 @@ 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='') - ).order_by('-created_at') - stripe_chgs = [] - for ho in hosting_orders: - stripe_chgs.append( - {ho: 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) @@ -1544,12 +1487,7 @@ class VirtualMachinesPlanListView(LoginRequiredMixin, ListView): ordering = '-id' def get_queryset(self): - username = self.request.GET.get('username') - if self.request.user.is_admin and username: - user = CustomUser.objects.get(username=username) - else: - user = self.request.user - owner = user + owner = self.request.user manager = OpenNebulaManager(email=owner.username, password=owner.password) try: @@ -1708,11 +1646,7 @@ class VirtualMachineView(LoginRequiredMixin, View): login_url = reverse_lazy('hosting:login') def get_object(self): - username = self.request.GET.get('username') - if self.request.user.is_admin and username: - owner = CustomUser.objects.get(username=username) - else: - owner = self.request.user + owner = self.request.user vm = None manager = OpenNebulaManager( email=owner.username, @@ -1768,10 +1702,7 @@ class VirtualMachineView(LoginRequiredMixin, View): subscription=hosting_order.subscription_id, count=1 ) - if stripe_obj.data: - inv_url = stripe_obj.data[0].hosted_invoice_url - else: - inv_url = '' + inv_url = stripe_obj.data[0].hosted_invoice_url elif hosting_order.stripe_charge_id: stripe_obj = stripe.Charge.retrieve( hosting_order.stripe_charge_id @@ -1932,7 +1863,7 @@ class VirtualMachineView(LoginRequiredMixin, View): 'subject': ("Deleted " if response['status'] else "ERROR deleting ") + admin_msg_sub, 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, - 'to': ['dcl-orders@ungleich.ch'], + 'to': ['info@ungleich.ch'], 'body': "\n".join( ["%s=%s" % (k, v) for (k, v) in admin_email_body.items()]), } diff --git a/membership/models.py b/membership/models.py index 81054fb9..703b4800 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, id_payment_method=None, + def create_stripe_api_customer(cls, email=None, token=None, customer_name=None): """ This method creates a Stripe API customer with the given @@ -288,8 +288,7 @@ class StripeCustomer(models.Model): stripe user. """ stripe_utils = StripeUtils() - stripe_data = stripe_utils.create_customer( - id_payment_method, email, customer_name) + stripe_data = stripe_utils.create_customer(token, email, customer_name) if stripe_data.get('response_object'): stripe_cus_id = stripe_data.get('response_object').get('id') return stripe_cus_id @@ -297,7 +296,7 @@ class StripeCustomer(models.Model): return None @classmethod - def get_or_create(cls, email=None, token=None, id_payment_method=None): + def get_or_create(cls, email=None, token=None): """ Check if there is a registered stripe customer with that email or create a new one diff --git a/opennebula_api/models.py b/opennebula_api/models.py index 2f76f423..92d5c568 100644 --- a/opennebula_api/models.py +++ b/opennebula_api/models.py @@ -154,8 +154,6 @@ class OpenNebulaManager(): protocol=settings.OPENNEBULA_PROTOCOL) ) raise ConnectionRefusedError - except Exception as ex: - logger.error(str(ex)) def _get_user_pool(self): try: diff --git a/release.sh b/release.sh deleted file mode 100755 index 535cc7d4..00000000 --- a/release.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh -# Nico Schottelius, 2021-12-17 - -current=$(git describe --dirty) -last_tag=$(git describe --tags --abbrev=0) -registry=harbor.ungleich.svc.p10.k8s.ooo/ungleich-public -image_url=$registry/dynamicweb:${current} - -if echo $current | grep -q -e 'dirty$'; then - echo Refusing to release a dirty tree build - exit 1 -fi - -if [ "$current" != "$last_tag" ]; then - echo "Last tag ($last_tag) is not current version ($current)" - echo "Only release proper versions" - exit 1 -fi - -docker tag dynamicweb:${current} ${image_url} -docker push ${image_url} diff --git a/requirements.txt b/requirements.txt index 73cdf987..e7769a7e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -83,7 +83,7 @@ stripe==2.41.0 wheel==0.29.0 django-admin-honeypot==1.0.0 coverage==4.3.4 -git+https://github.com/ungleich/python-oca.git#egg=oca +git+https://github.com/ungleich/python-oca.git#egg=python-oca djangorestframework==3.6.3 flake8==3.3.0 python-memcached==1.58 diff --git a/templates/gdpr/gdpr_banner.html b/templates/gdpr/gdpr_banner.html index f927f8ee..7e9f5c7f 100644 --- a/templates/gdpr/gdpr_banner.html +++ b/templates/gdpr/gdpr_banner.html @@ -134,6 +134,8 @@ digitalglarus.ch
hack4lgarus.ch
ipv6onlyhosting.com
+ ipv6onlyhosting.ch
+ ipv6onlyhosting.net
django-hosting.ch
rails-hosting.ch
node-hosting.ch
diff --git a/ungleich_page/templates/ungleich_page/ungleich/section_products.html b/ungleich_page/templates/ungleich_page/ungleich/section_products.html index e3d0dc73..9cdc94c8 100644 --- a/ungleich_page/templates/ungleich_page/ungleich/section_products.html +++ b/ungleich_page/templates/ungleich_page/ungleich/section_products.html @@ -19,15 +19,13 @@ + \ No newline at end of file diff --git a/ungleich_page/views.py b/ungleich_page/views.py index 01d1138d..e5a99d8d 100644 --- a/ungleich_page/views.py +++ b/ungleich_page/views.py @@ -25,10 +25,9 @@ class ContactView(FormView): success_message = _('Message Successfully Sent') def form_valid(self, form): - print("ungleich_page contactusform") - #form.save() - #form.send_email() - #messages.add_message(self.request, messages.SUCCESS, self.success_message) + form.save() + form.send_email() + messages.add_message(self.request, messages.SUCCESS, self.success_message) return super(ContactView, self).form_valid(form) def get_context_data(self, **kwargs): diff --git a/utils/fields.py b/utils/fields.py index ca1115de..48a606cc 100644 --- a/utils/fields.py +++ b/utils/fields.py @@ -1,8 +1,7 @@ from django.utils.translation import ugettext as _ from django.db import models -# Old: http://xml.coverpages.org/country3166.html -# 2023-12-29: Updated list of countries from https://en.wikipedia.org/wiki/List_of_ISO_3166_country_codes +# http://xml.coverpages.org/country3166.html COUNTRIES = ( ('AD', _('Andorra')), ('AE', _('United Arab Emirates')), @@ -11,6 +10,7 @@ COUNTRIES = ( ('AI', _('Anguilla')), ('AL', _('Albania')), ('AM', _('Armenia')), + ('AN', _('Netherlands Antilles')), ('AO', _('Angola')), ('AQ', _('Antarctica')), ('AR', _('Argentina')), @@ -18,7 +18,6 @@ COUNTRIES = ( ('AT', _('Austria')), ('AU', _('Australia')), ('AW', _('Aruba')), - ('AX', _('Aland Islands')), ('AZ', _('Azerbaijan')), ('BA', _('Bosnia and Herzegovina')), ('BB', _('Barbados')), @@ -29,13 +28,11 @@ COUNTRIES = ( ('BH', _('Bahrain')), ('BI', _('Burundi')), ('BJ', _('Benin')), - ('BL', _('St. Barts')), ('BM', _('Bermuda')), - ('BN', _('Brunei')), + ('BN', _('Brunei Darussalam')), ('BO', _('Bolivia')), - ('BQ', _('Caribbean Netherlands')), ('BR', _('Brazil')), - ('BS', _('Bahamas')), + ('BS', _('Bahama')), ('BT', _('Bhutan')), ('BV', _('Bouvet Island')), ('BW', _('Botswana')), @@ -43,12 +40,11 @@ COUNTRIES = ( ('BZ', _('Belize')), ('CA', _('Canada')), ('CC', _('Cocos (Keeling) Islands')), - ('CD', _('Congo - Kinshasa')), ('CF', _('Central African Republic')), - ('CG', _('Congo - Brazzaville')), + ('CG', _('Congo')), ('CH', _('Switzerland')), ('CI', _('Ivory Coast')), - ('CK', _('Cook Islands')), + ('CK', _('Cook Iislands')), ('CL', _('Chile')), ('CM', _('Cameroon')), ('CN', _('China')), @@ -56,10 +52,9 @@ COUNTRIES = ( ('CR', _('Costa Rica')), ('CU', _('Cuba')), ('CV', _('Cape Verde')), - ('CW', _('Curacao')), ('CX', _('Christmas Island')), ('CY', _('Cyprus')), - ('CZ', _('Czechia')), + ('CZ', _('Czech Republic')), ('DE', _('Germany')), ('DJ', _('Djibouti')), ('DK', _('Denmark')), @@ -75,16 +70,16 @@ COUNTRIES = ( ('ET', _('Ethiopia')), ('FI', _('Finland')), ('FJ', _('Fiji')), - ('FK', _('Falkland Islands')), + ('FK', _('Falkland Islands (Malvinas)')), ('FM', _('Micronesia')), ('FO', _('Faroe Islands')), ('FR', _('France')), + ('FX', _('France, Metropolitan')), ('GA', _('Gabon')), - ('GB', _('United Kingdom')), + ('GB', _('United Kingdom (Great Britain)')), ('GD', _('Grenada')), ('GE', _('Georgia')), ('GF', _('French Guiana')), - ('GG', _('Guernsey')), ('GH', _('Ghana')), ('GI', _('Gibraltar')), ('GL', _('Greenland')), @@ -98,7 +93,7 @@ COUNTRIES = ( ('GU', _('Guam')), ('GW', _('Guinea-Bissau')), ('GY', _('Guyana')), - ('HK', _('Hong Kong SAR China')), + ('HK', _('Hong Kong')), ('HM', _('Heard & McDonald Islands')), ('HN', _('Honduras')), ('HR', _('Croatia')), @@ -107,14 +102,12 @@ COUNTRIES = ( ('ID', _('Indonesia')), ('IE', _('Ireland')), ('IL', _('Israel')), - ('IM', _('Isle of Man')), ('IN', _('India')), ('IO', _('British Indian Ocean Territory')), ('IQ', _('Iraq')), - ('IR', _('Iran')), + ('IR', _('Islamic Republic of Iran')), ('IS', _('Iceland')), ('IT', _('Italy')), - ('JE', _('Jersey')), ('JM', _('Jamaica')), ('JO', _('Jordan')), ('JP', _('Japan')), @@ -124,14 +117,14 @@ COUNTRIES = ( ('KI', _('Kiribati')), ('KM', _('Comoros')), ('KN', _('St. Kitts and Nevis')), - ('KP', _('North Korea')), - ('KR', _('South Korea')), + ('KP', _('Korea, Democratic People\'s Republic of')), + ('KR', _('Korea, Republic of')), ('KW', _('Kuwait')), ('KY', _('Cayman Islands')), ('KZ', _('Kazakhstan')), - ('LA', _('Laos')), + ('LA', _('Lao People\'s Democratic Republic')), ('LB', _('Lebanon')), - ('LC', _('St. Lucia')), + ('LC', _('Saint Lucia')), ('LI', _('Liechtenstein')), ('LK', _('Sri Lanka')), ('LR', _('Liberia')), @@ -139,23 +132,20 @@ COUNTRIES = ( ('LT', _('Lithuania')), ('LU', _('Luxembourg')), ('LV', _('Latvia')), - ('LY', _('Libya')), + ('LY', _('Libyan Arab Jamahiriya')), ('MA', _('Morocco')), ('MC', _('Monaco')), - ('MD', _('Moldova')), - ('ME', _('Montenegro')), - ('MF', _('St. Martin')), + ('MD', _('Moldova, Republic of')), ('MG', _('Madagascar')), ('MH', _('Marshall Islands')), - ('MK', _('North Macedonia')), ('ML', _('Mali')), - ('MM', _('Myanmar (Burma)')), ('MN', _('Mongolia')), - ('MO', _('Macao SAR China')), + ('MM', _('Myanmar')), + ('MO', _('Macau')), ('MP', _('Northern Mariana Islands')), ('MQ', _('Martinique')), ('MR', _('Mauritania')), - ('MS', _('Montserrat')), + ('MS', _('Monserrat')), ('MT', _('Malta')), ('MU', _('Mauritius')), ('MV', _('Maldives')), @@ -184,17 +174,15 @@ COUNTRIES = ( ('PK', _('Pakistan')), ('PL', _('Poland')), ('PM', _('St. Pierre & Miquelon')), - ('PN', _('Pitcairn Islands')), + ('PN', _('Pitcairn')), ('PR', _('Puerto Rico')), - ('PS', _('Palestinian Territories')), ('PT', _('Portugal')), ('PW', _('Palau')), ('PY', _('Paraguay')), ('QA', _('Qatar')), ('RE', _('Reunion')), ('RO', _('Romania')), - ('RS', _('Serbia')), - ('RU', _('Russia')), + ('RU', _('Russian Federation')), ('RW', _('Rwanda')), ('SA', _('Saudi Arabia')), ('SB', _('Solomon Islands')), @@ -204,19 +192,17 @@ COUNTRIES = ( ('SG', _('Singapore')), ('SH', _('St. Helena')), ('SI', _('Slovenia')), - ('SJ', _('Svalbard and Jan Mayen')), + ('SJ', _('Svalbard & Jan Mayen Islands')), ('SK', _('Slovakia')), ('SL', _('Sierra Leone')), ('SM', _('San Marino')), ('SN', _('Senegal')), ('SO', _('Somalia')), ('SR', _('Suriname')), - ('SS', _('South Sudan')), ('ST', _('Sao Tome & Principe')), ('SV', _('El Salvador')), - ('SX', _('Sint Maarten')), - ('SY', _('Syria')), - ('SZ', _('Eswatini')), + ('SY', _('Syrian Arab Republic')), + ('SZ', _('Swaziland')), ('TC', _('Turks & Caicos Islands')), ('TD', _('Chad')), ('TF', _('French Southern Territories')), @@ -224,34 +210,36 @@ COUNTRIES = ( ('TH', _('Thailand')), ('TJ', _('Tajikistan')), ('TK', _('Tokelau')), - ('TL', _('Timor-Leste')), ('TM', _('Turkmenistan')), ('TN', _('Tunisia')), ('TO', _('Tonga')), + ('TP', _('East Timor')), ('TR', _('Turkey')), ('TT', _('Trinidad & Tobago')), ('TV', _('Tuvalu')), - ('TW', _('Taiwan')), - ('TZ', _('Tanzania')), + ('TW', _('Taiwan, Province of China')), + ('TZ', _('Tanzania, United Republic of')), ('UA', _('Ukraine')), ('UG', _('Uganda')), - ('UM', _('U.S. Outlying Islands')), - ('US', _('United States')), + ('UM', _('United States Minor Outlying Islands')), + ('US', _('United States of America')), ('UY', _('Uruguay')), ('UZ', _('Uzbekistan')), - ('VA', _('Vatican City')), - ('VC', _('St. Vincent & Grenadines')), + ('VA', _('Vatican City State (Holy See)')), + ('VC', _('St. Vincent & the Grenadines')), ('VE', _('Venezuela')), ('VG', _('British Virgin Islands')), - ('VI', _('U.S. Virgin Islands')), - ('VN', _('Vietnam')), + ('VI', _('United States Virgin Islands')), + ('VN', _('Viet Nam')), ('VU', _('Vanuatu')), - ('WF', _('Wallis & Futuna')), + ('WF', _('Wallis & Futuna Islands')), ('WS', _('Samoa')), ('YE', _('Yemen')), ('YT', _('Mayotte')), + ('YU', _('Yugoslavia')), ('ZA', _('South Africa')), ('ZM', _('Zambia')), + ('ZR', _('Zaire')), ('ZW', _('Zimbabwe')), ) diff --git a/utils/forms.py b/utils/forms.py index 3cc57578..f35c90f4 100644 --- a/utils/forms.py +++ b/utils/forms.py @@ -4,8 +4,6 @@ from django.core.mail import EmailMultiAlternatives from django.template.loader import render_to_string from django.utils.translation import ugettext_lazy as _ -from django_recaptcha.fields import ReCaptchaField - from membership.models import CustomUser from .models import ContactMessage, BillingAddress, UserBillingAddress @@ -190,7 +188,6 @@ class UserBillingAddressForm(forms.ModelForm): class ContactUsForm(forms.ModelForm): error_css_class = 'autofocus' - captcha = ReCaptchaField() class Meta: model = ContactMessage @@ -209,12 +206,11 @@ class ContactUsForm(forms.ModelForm): } def send_email(self, email_to='info@digitalglarus.ch'): - pass - #text_content = render_to_string( - # 'emails/contact.txt', {'data': self.cleaned_data}) - #html_content = render_to_string( - # 'emails/contact.html', {'data': self.cleaned_data}) - #email = EmailMultiAlternatives('Subject', text_content) - #email.attach_alternative(html_content, "text/html") - #email.to = [email_to] - #email.send() + text_content = render_to_string( + 'emails/contact.txt', {'data': self.cleaned_data}) + html_content = render_to_string( + 'emails/contact.html', {'data': self.cleaned_data}) + email = EmailMultiAlternatives('Subject', text_content) + email.attach_alternative(html_content, "text/html") + email.to = [email_to] + email.send() diff --git a/utils/ldap_manager.py b/utils/ldap_manager.py index d40e931f..8c555224 100644 --- a/utils/ldap_manager.py +++ b/utils/ldap_manager.py @@ -3,7 +3,6 @@ import hashlib import random import ldap3 import logging -import unicodedata from django.conf import settings @@ -102,7 +101,7 @@ class LdapManager: "uidNumber": [str(uidNumber)], "gidNumber": [str(settings.LDAP_CUSTOMER_GROUP_ID)], "loginShell": ["/bin/bash"], - "homeDirectory": ["/home/{}".format(unicodedata.normalize('NFKD', user).encode('ascii','ignore'))], + "homeDirectory": ["/home/{}".format(user).encode("utf-8")], "mail": email.encode("utf-8"), "userPassword": [self._ssha_password( password.encode("utf-8") @@ -267,7 +266,7 @@ class LdapManager: logger.error( "Error reading int value from {}. {}" "Returning default value {} instead".format( - settings.LDAP_MAX_UID_FILE_PATH, + settings.LDAP_MAX_UID_PATH, str(ve), settings.LDAP_DEFAULT_START_UID ) diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index 875a174e..ade06dd3 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -34,7 +34,6 @@ 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 @@ -70,7 +69,7 @@ class StripeUtils(object): CURRENCY = 'chf' INTERVAL = 'month' SUCCEEDED_STATUS = 'succeeded' - RESOURCE_ALREADY_EXISTS_ERROR_CODE = 'resource_already_exists' + STRIPE_PLAN_ALREADY_EXISTS = 'Plan 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.' @@ -83,31 +82,20 @@ class StripeUtils(object): customer.save() @handleStripeError - def associate_customer_card(self, stripe_customer_id, id_payment_method, + def associate_customer_card(self, stripe_customer_id, token, set_as_default=False): customer = stripe.Customer.retrieve(stripe_customer_id) - stripe.PaymentMethod.attach( - id_payment_method, - customer=stripe_customer_id, - ) + card = customer.sources.create(source=token) if set_as_default: - customer.invoice_settings.default_payment_method = id_payment_method + customer.default_source = card.id customer.save() return True @handleStripeError def dissociate_customer_card(self, stripe_customer_id, card_id): customer = stripe.Customer.retrieve(stripe_customer_id) - if card_id.startswith("pm"): - logger.debug("PaymentMethod %s detached %s" % (card_id, - stripe_customer_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) - card.delete() + card = customer.sources.retrieve(card_id) + card.delete() @handleStripeError def update_customer_card(self, customer_id, token): @@ -199,24 +187,6 @@ 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) @@ -236,11 +206,11 @@ class StripeUtils(object): return customer @handleStripeError - def create_customer(self, id_payment_method, email, name=None): + def create_customer(self, token, email, name=None): if name is None or name.strip() == "": name = email customer = self.stripe.Customer.create( - payment_method=id_payment_method, + source=token, description=name, email=email ) @@ -297,17 +267,11 @@ class StripeUtils(object): stripe_plan_db_obj = StripePlan.objects.create( stripe_plan_id=stripe_plan_id) except stripe.error.InvalidRequestError as e: - logger.error(str(e)) - logger.error("error_code = %s" % str(e.__dict__)) - if self.RESOURCE_ALREADY_EXISTS_ERROR_CODE in e.error.code: + if self.STRIPE_PLAN_ALREADY_EXISTS in str(e): logger.debug( self.PLAN_EXISTS_ERROR_MSG.format(stripe_plan_id)) - stripe_plan_db_obj, c = StripePlan.objects.get_or_create( + stripe_plan_db_obj = StripePlan.objects.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 @@ -336,14 +300,10 @@ class StripeUtils(object): @handleStripeError def subscribe_customer_to_plan(self, customer, plans, trial_end=None, - coupon="", tax_rates=list(), - default_payment_method=""): + coupon="", tax_rates=list()): """ 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 :param plans: A list of stripe plans. :param trial_end: An integer representing when the Stripe subscription @@ -357,17 +317,12 @@ 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', - default_payment_method=default_payment_method ) - logger.debug("Done subscribing") return subscription_result @handleStripeError @@ -525,49 +480,7 @@ class StripeUtils(object): ) return tax_id_obj - @handleStripeError - 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', - customer=customer, - setup_future_usage='off_session' - ) - 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("-","") - return True if _vat1 == _vat2 else False + return True if _vat1 == _vat2 else False \ No newline at end of file diff --git a/webhook/views.py b/webhook/views.py index 0a96d0b6..516d1afc 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -1,17 +1,14 @@ import datetime import logging -import json -import stripe +import stripe # Create your views here. from django.conf import settings from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST -from datacenterlight.views import do_provisioning, do_provisioning_generic from membership.models import StripeCustomer -from hosting.models import IncompleteSubscriptions, IncompletePaymentIntents from utils.models import BillingAddress, UserBillingAddress from utils.tasks import send_plain_email_task @@ -114,193 +111,8 @@ 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': - #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 - - # 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("""invoice_obj.paid and - 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: - 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_provisioning( - request=request, - 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, - real_request=None - ) - except IncompleteSubscriptions.DoesNotExist as ex: - logger.error(str(ex)) - except IncompleteSubscriptions.MultipleObjectsReturned as ex: - logger.error(str(ex)) - 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)) - 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) - 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") - 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)) - try: - logger.debug("Looking for IncompletePaymentIntents %s " % - payment_intent_obj.id) - incomplete_pm = IncompletePaymentIntents.objects.get( - payment_intent_id=payment_intent_obj.id) - logger.debug("incomplete_pm = %s" % str(incomplete_pm.__dict__)) - request = "" - soc = "" - card_details_response = "" - gp_details = "" - template = "" - billing_address_data = "" - if incomplete_pm.request: - request = json.loads(incomplete_pm.request) - logger.debug("request = %s" % str(request)) - 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) - 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*******") - 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) - 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) - 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 as ex: - logger.error(str(ex)) - except (IncompletePaymentIntents.MultipleObjectsReturned, - Exception) 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)