diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 6b8710a7..00000000 --- a/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -.git diff --git a/Changelog b/Changelog index 44c4dee4..fc85728d 100644 --- a/Changelog +++ b/Changelog @@ -1,96 +1,3 @@ -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 - * Bugfix: Handle VM templates deleted in OpenNebula but VM instances still existing (MR!736) - Notes for deployment: - When deploying define a UPDATED_TEMPLATES string represented dictionary value in .env -``` - # Represents Template Ids that were - # deleted and the new template Id to look for the template - # definition - UPDATED_TEMPLATES="{1: 100}" -``` -2.10.6: 2020-03-25 - * Bugfix: Handle Nonetype for discount's name (MR!735) -2.10.5: 2020-03-17 - * Introduce base price for VMs and let admins add stripe_coupon_id (MR!730) - Notes for deployment: - 1. Add env variable `VM_BASE_PRICE` - 2. Migrate datacenterlight app. This introduces the stripe_coupon_code field in the VMPricing. - 3. Create a coupon in stripe with the desired value and note down the stripe's coupon id - 4. Update the discount amount and set the corresponding coupon id in the admin -2.10.3b: 2020-03-05 - * #7773: Use username for communicating with opennebula all the time -2.10.2b: 2020-02-25 - * #7764: Fix uid represented as bytestring - * #7769: [hosting] ssh private key download feature does not work well on Firefox -2.10.1: 2020-02-02: - * Changes the pricing structure of generic products into the pre vat and with vat (like that for VM) - * Shows product name (if exists) in the invoices list if it belongs to a generic product - * Small bugfixes (right alignment of price in the invoice list, show prices with 2 decimal places etc) -2.10: 2020-02-01 - * Feature: Introduce new design to show VAT exclusive/VAT inclusive pricing together - * Feature: Separate VAT and discount in Stripe - * Feature: Show Stripe invoices until we have a better way of showing them elegantly - * Bugfix: Fix bug where VAT is set to 0 because user set a valid VAT number before but later chose not to use any VAT number -2.9.5: 2020-01-20 - * Feature: Show invoices directly from stripe (MR!727) -2.9.4: 2020-01-10 - * Bugfix: Buying VPN generic item caused 500 error -2.9.3: 2020-01-05 - * Feature: Add StripeTaxRate model to save tax rates created in Stripe - * Bugfix: Add vat rates for CY, IL and LI manually -2.9.2: 2020-01-02 - * Bugfix: Improve admin email for terminate vm (include subscription details and subscription amount) (MR!726) -2.9.1: 2019-12-31 - * Bugfix: Error handling tax_id updated webhook -2.9: 2019-12-31 - * Feature: Enable saving user's VAT Number and validate it (MR!725) - Notes for deployment: - 1. Migrate db for utils app - ./manage.py migrate utils - - 2. Uninstall old version and install a more recent version of stripe - - ``` - source venv/bin/activate - ./manage.py shell - pip uninstall stripe - pip install stripe==2.41.0 - ``` - - 3. Create tax id updated webhook - ``` - ./manage.py webhook --create \ - --webhook_endpoint https://datacenterlight.ch/en-us/webhooks/ \ - --events_csv customer.tax_id.updated - ``` - - 4. From the secret obtained in 3, setup an environment variable - ``` - WEBHOOK_SECRET='whsec......' - ``` - 5. Deploy 2.8.2: 2019-12-24 * Bugfix: [dcl calculator plugin] Set the POST action url explicitly 2.8.1: 2019-12-24 diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 50b81cbb..00000000 --- a/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -FROM python:3.10.0-alpine3.15 - -WORKDIR /usr/src/app - -RUN apk add --update --no-cache \ - git \ - build-base \ - openldap-dev \ - python3-dev \ - libpq-dev \ - && rm -rf /var/cache/apk/* - -# FIX https://github.com/python-ldap/python-ldap/issues/432 -RUN echo 'INPUT ( libldap.so )' > /usr/lib/libldap_r.so - -COPY requirements.txt ./ -RUN pip install --no-cache-dir -r requirements.txt -COPY ./ . diff --git a/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/datacenterlight/cms_plugins.py b/datacenterlight/cms_plugins.py index 52b4f19f..c3ec974f 100644 --- a/datacenterlight/cms_plugins.py +++ b/datacenterlight/cms_plugins.py @@ -1,6 +1,5 @@ from cms.plugin_base import CMSPluginBase from cms.plugin_pool import plugin_pool -from django.conf import settings from .cms_models import ( DCLBannerItemPluginModel, DCLBannerListPluginModel, DCLContactPluginModel, @@ -101,7 +100,6 @@ class DCLCalculatorPlugin(CMSPluginBase): vm_type=instance.vm_type ).order_by('name') context['instance'] = instance - context['vm_base_price'] = settings.VM_BASE_PRICE context['min_ram'] = 0.5 if instance.enable_512mb_ram else 1 return context diff --git a/datacenterlight/locale/de/LC_MESSAGES/django.po b/datacenterlight/locale/de/LC_MESSAGES/django.po index cd7fab99..ebb78a1c 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: 2019-12-09 12:13+0000\n" "PO-Revision-Date: 2018-03-30 23:22+0000\n" "Last-Translator: b'Anonymous User '\n" "Language-Team: LANGUAGE \n" @@ -144,8 +144,9 @@ msgid "" "the heart of Switzerland." msgstr "Bei uns findest Du die günstiges VMs aus der Schweiz." -msgid "Try now, order a VM. VM price starts from only 11.5 CHF per month." -msgstr "Unser Angebot beginnt bei 11.5 CHF pro Monat. Probier's jetzt aus!" + +msgid "Try now, order a VM. VM price starts from only 10.5 CHF per month." +msgstr "Unser Angebot beginnt bei 10.5 CHF pro Monat. Probier's jetzt aus!" msgid "ORDER VM" msgstr "VM BESTELLEN" @@ -406,19 +407,6 @@ msgstr "Datum" msgid "Billed to" msgstr "Rechnungsadresse" -msgid "VAT Number" -msgstr "MwSt-Nummer" - -msgid "Your VAT number has been verified" -msgstr "Deine MwSt-Nummer wurde überprüft" - -msgid "" -"Your VAT number is under validation. VAT will be adjusted, once the " -"validation is complete." -msgstr "" -"Deine MwSt-Nummer wird derzeit validiert. Die MwSt. wird angepasst, sobald " -"die Validierung abgeschlossen ist." - msgid "Payment method" msgstr "Bezahlmethode" @@ -431,63 +419,64 @@ msgstr "Bestellungsübersicht" msgid "Product" msgstr "Produkt" +msgid "Price" +msgstr "Preise" + +msgid "VAT for" +msgstr "MwSt für" + +msgid "Total Amount" +msgstr "Gesamtsumme" + +msgid "Amount" +msgstr "Betrag" + msgid "Description" msgstr "Beschreibung" msgid "Recurring" msgstr "Wiederholend" -msgid "Price Before VAT" -msgstr "Preis ohne MwSt." +msgid "Subtotal" +msgstr "Zwischensumme" -msgid "Pre VAT" -msgstr "Exkl. MwSt." - -msgid "VAT for" -msgstr "MwSt für" - -msgid "Your Price in Total" -msgstr "Dein Gesamtpreis" +#, fuzzy, python-format +#| msgid "" +#| "By clicking \"Place order\" this plan will charge your credit card " +#| "account with %(total_price)s CHF/month" +msgid "" +"By clicking \"Place order\" this plan will charge your credit card account " +"with %(total_price)s CHF/year" +msgstr "" +"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(total_price)s " +"CHF pro Jahr 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/year" +"By clicking \"Place order\" this plan will charge your credit card account " +"with %(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/Jahr 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 "" -"\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 %(vm_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." - -#, 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" -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" @@ -586,9 +575,6 @@ msgstr "Unser Angebot beginnt bei 15 CHF pro Monat. Probier's jetzt aus!" msgid "Actions speak louder than words. Let's do it, try our VM now." msgstr "Taten sagen mehr als Worte – Teste jetzt unsere VM!" -msgid "See Invoice" -msgstr "Siehe Rechnung" - msgid "Invalid number of cores" msgstr "Ungültige Anzahle CPU-Kerne" @@ -606,22 +592,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 +612,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,43 +655,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 "Price" -#~ msgstr "Preise" - -#~ msgid "Total Amount" -#~ msgstr "Gesamtsumme" - -#~ msgid "Amount" -#~ msgstr "Betrag" - -#~ msgid "Subtotal" -#~ msgstr "Zwischensumme" +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 "VAT" #~ msgstr "Mehrwertsteuer" @@ -735,6 +676,9 @@ msgstr "" #~ "ausgelöst, nachdem Du die Bestellung auf der nächsten Seite bestätigt " #~ "hast." +#~ msgid "Card Number" +#~ msgstr "Kreditkartennummer" + #~ msgid "" #~ "You are not making any payment yet. After placing your order, you will be " #~ "taken to the Submit Payment Page." @@ -786,6 +730,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 db36fde8..00000000 --- a/datacenterlight/management/commands/check_vm_templates.py +++ /dev/null @@ -1,65 +0,0 @@ -from django.core.management.base import BaseCommand -from opennebula_api.models import OpenNebulaManager -from datacenterlight.models import VMTemplate -from membership.models import CustomUser - -from django.conf import settings -from time import sleep -import datetime -import json -import logging -import os - -logger = logging.getLogger(__name__) - - -class Command(BaseCommand): - help = '''Checks all VM templates to find if they can be instantiated''' - - def add_arguments(self, parser): - parser.add_argument('user_email', type=str) - - def handle(self, *args, **options): - result_dict = {} - user_email = options['user_email'] if 'user_email' in options else "" - - if user_email: - cu = CustomUser.objects.get(email=user_email) - specs = {'cpu': 1, 'memory': 1, 'disk_size': 10} - manager = OpenNebulaManager(email=user_email, password=cu.password) - pub_keys = [settings.TEST_MANAGE_SSH_KEY_PUBKEY] - PROJECT_PATH = os.path.abspath(os.path.dirname(__name__)) - if not os.path.exists("%s/outputs" % PROJECT_PATH): - os.mkdir("%s/outputs" % PROJECT_PATH) - for vm_template in VMTemplate.objects.all(): - vm_name = 'test-%s' % vm_template.name - vm_id = manager.create_vm( - 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) - 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)) - self.stdout.write(self.style.SUCCESS("Done")) 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/migrations/0031_vmpricing_stripe_coupon_id.py b/datacenterlight/migrations/0031_vmpricing_stripe_coupon_id.py deleted file mode 100644 index d2e45871..00000000 --- a/datacenterlight/migrations/0031_vmpricing_stripe_coupon_id.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.4 on 2020-02-04 03:16 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('datacenterlight', '0030_dclnavbarpluginmodel_show_non_transparent_navbar_always'), - ] - - operations = [ - migrations.AddField( - model_name='vmpricing', - name='stripe_coupon_id', - field=models.CharField(blank=True, max_length=255, null=True), - ), - ] diff --git a/datacenterlight/models.py b/datacenterlight/models.py index 64d785a2..6410254b 100644 --- a/datacenterlight/models.py +++ b/datacenterlight/models.py @@ -54,7 +54,6 @@ class VMPricing(models.Model): discount_amount = models.DecimalField( max_digits=6, decimal_places=2, default=0 ) - stripe_coupon_id = models.CharField(max_length=255, null=True, blank=True) def __str__(self): display_str = self.name + ' => ' + ' - '.join([ diff --git a/datacenterlight/static/datacenterlight/css/hosting.css b/datacenterlight/static/datacenterlight/css/hosting.css index bcf266cc..0f16ab77 100644 --- a/datacenterlight/static/datacenterlight/css/hosting.css +++ b/datacenterlight/static/datacenterlight/css/hosting.css @@ -532,7 +532,6 @@ .order-detail-container .total-price { font-size: 18px; - line-height: 20px; } @media (max-width: 767px) { diff --git a/datacenterlight/static/datacenterlight/js/main.js b/datacenterlight/static/datacenterlight/js/main.js index c6869cda..8fea438a 100644 --- a/datacenterlight/static/datacenterlight/js/main.js +++ b/datacenterlight/static/datacenterlight/js/main.js @@ -225,8 +225,8 @@ } var total = (cardPricing['cpu'].value * window.coresUnitPrice) + (cardPricing['ram'].value * window.ramUnitPrice) + - (cardPricing['storage'].value * window.ssdUnitPrice) + - window.vmBasePrice - window.discountAmount; + (cardPricing['storage'].value * window.ssdUnitPrice) - + window.discountAmount; total = parseFloat(total.toFixed(2)); $("#total").text(total); } diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index 899b506f..8b4626e8 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -56,8 +56,13 @@ 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_user = user.get('email') on_pass = user.get('pass') logger.debug("Using user {user} to create VM".format(user=on_user)) vm_name = None @@ -87,8 +92,108 @@ 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:orders', + kwargs={'pk': order_id}), + '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 +215,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/cms/calculator.html b/datacenterlight/templates/datacenterlight/cms/calculator.html index 20a6664a..7b123a72 100644 --- a/datacenterlight/templates/datacenterlight/cms/calculator.html +++ b/datacenterlight/templates/datacenterlight/cms/calculator.html @@ -1,5 +1,5 @@
- {% include "datacenterlight/includes/_calculator_form.html" with vm_pricing=instance.pricing vm_base_price=vm_base_price %} + {% include "datacenterlight/includes/_calculator_form.html" with vm_pricing=instance.pricing %}
\ No newline at end of file diff --git a/datacenterlight/templates/datacenterlight/emails/invoice_failed.html b/datacenterlight/templates/datacenterlight/emails/invoice_failed.html new file mode 100644 index 00000000..7f973c88 --- /dev/null +++ b/datacenterlight/templates/datacenterlight/emails/invoice_failed.html @@ -0,0 +1,44 @@ +{% load static i18n %} + + + + + + + {% trans "Data Center Light VM payment failed" %} + + + + + + + + + + + + + + + + + + +
+ +
+

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

+
+

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

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

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

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

+
+

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

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

Please ensure that your credit card is active and that you have sufficient credit.{% endblocktrans %} + +{% blocktrans %}We will reattempt with your active payment source in the next {number_of_remaining_hours} hours. If this is not resolved by then, the VM and your subscription will be terminated and the VM can not be recovered back.{% endblocktrans %} + +{% blocktrans %}Please reply to this email or write to us at support@datacenterlight.ch if you have any queries.{% endblocktrans %} + +{% trans "Your Data Center Light Team" %} \ No newline at end of file diff --git a/datacenterlight/templates/datacenterlight/emails/welcome_user.html b/datacenterlight/templates/datacenterlight/emails/welcome_user.html index 2044b2ee..25185618 100644 --- a/datacenterlight/templates/datacenterlight/emails/welcome_user.html +++ b/datacenterlight/templates/datacenterlight/emails/welcome_user.html @@ -28,7 +28,7 @@ {% blocktrans %}Thanks for joining us! We provide the most affordable virtual machines from the heart of Switzerland.{% endblocktrans %}

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

diff --git a/datacenterlight/templates/datacenterlight/emails/welcome_user.txt b/datacenterlight/templates/datacenterlight/emails/welcome_user.txt index 06e8aa33..772e51a5 100644 --- a/datacenterlight/templates/datacenterlight/emails/welcome_user.txt +++ b/datacenterlight/templates/datacenterlight/emails/welcome_user.txt @@ -3,7 +3,7 @@ {% trans "Welcome to Data Center Light!" %} {% blocktrans %}Thanks for joining us! We provide the most affordable virtual machines from the heart of Switzerland.{% endblocktrans %} -{% blocktrans %}Try now, order a VM. VM price starts from only 11.5 CHF per month.{% endblocktrans %} +{% blocktrans %}Try now, order a VM. VM price starts from only 10.5 CHF per month.{% endblocktrans %} {{ base_url }}{% url 'hosting:create_virtual_machine' %} diff --git a/datacenterlight/templates/datacenterlight/includes/_calculator_form.html b/datacenterlight/templates/datacenterlight/includes/_calculator_form.html index 2c2b51dd..f64a9500 100644 --- a/datacenterlight/templates/datacenterlight/includes/_calculator_form.html +++ b/datacenterlight/templates/datacenterlight/includes/_calculator_form.html @@ -9,7 +9,6 @@ window.ssdUnitPrice = {{vm_pricing.ssd_unit_price|default:0}}; window.hddUnitPrice = {{vm_pricing.hdd_unit_price|default:0}}; window.discountAmount = {{vm_pricing.discount_amount|default:0}}; - window.vmBasePrice = {{vm_base_price|default:0}}; window.minRam = {{min_ram}}; window.minRamErr = '{% blocktrans with min_ram=min_ram %}Please enter a value in range {{min_ram}} - 200.{% endblocktrans %}'; diff --git a/datacenterlight/templates/datacenterlight/landing_payment.html b/datacenterlight/templates/datacenterlight/landing_payment.html index 66a0e63f..4e71eab9 100644 --- a/datacenterlight/templates/datacenterlight/landing_payment.html +++ b/datacenterlight/templates/datacenterlight/landing_payment.html @@ -13,15 +13,6 @@
-
- {% for message in messages %} - {% if 'vat_error' in message.tags %} -
    -
  • An error occurred while validating VAT number: {{ message|safe }}

  • -
- {% endif %} - {% endfor %} -
diff --git a/datacenterlight/templates/datacenterlight/order_detail.html b/datacenterlight/templates/datacenterlight/order_detail.html index 1f7a3cda..bd2edcb2 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 %}
@@ -40,16 +32,6 @@ {{billing_address.cardholder_name}}
{{billing_address.street_address}}, {{billing_address.postal_code}}
{{billing_address.city}}, {{billing_address.country}} - {% if billing_address.vat_number %} -
{% trans "VAT Number" %} {{billing_address.vat_number}} - {% if vm.vat_validation_status != "ch_vat" and vm.vat_validation_status != "not_needed" %} - {% if vm.vat_validation_status == "verified" %} - - {% else %} - - {% endif %} - {% endif %} - {% endif %} {% endwith %}

@@ -66,121 +48,53 @@

{% trans "Order summary" %}

- {% if generic_payment_details %} -
-

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

- {% if generic_payment_details.description %} -

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

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

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

- {% endif %} -
- {% if generic_payment_details.exclude_vat_calculations %} - {% else %} -
-
-
-
+
+
+ {% if generic_payment_details.vat_rate > 0 %}

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

+

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

+

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

+ {% else %} +

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

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

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

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

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

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

-
-
-

{% 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 %} -
-
-
- {% trans "Your Price in Total" %} - {{generic_payment_details.amount|floatformat:2|intcomma}} CHF -
-
{% else %}

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

-
+

{% trans "Cores" %}: {{vm.cpu|floatformat}} @@ -197,75 +111,38 @@


-
-

- {% trans "Price Before VAT" %} - {{vm.price|floatformat:2|intcomma}} CHF + {% if vm.vat > 0 or vm.discount.amount > 0 %} +

+
+ {% if vm.vat > 0 %} +

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

+

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

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

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

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

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

-
-
-
-
-
-
-

-
-
-

{% trans "Pre VAT" %}

-
-
-

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

-
-
-
-
-

Price

-
-
-

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

-
-
-

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

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

{{vm.discount.name}}

-
-
-

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

-
-
-

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

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

Total

-
-
-

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

-
-
-

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

-
-
-
-
-
-
-
- {% trans "Your Price in Total" %} - {{vm.total_price|floatformat:2|intcomma}} CHF -
{% endif %}
@@ -278,16 +155,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 +206,5 @@ -{%endblock%} +{%endblock%} \ No newline at end of file diff --git a/datacenterlight/templatetags/custom_tags.py b/datacenterlight/templatetags/custom_tags.py index 120cabbf..a2b20bcb 100644 --- a/datacenterlight/templatetags/custom_tags.py +++ b/datacenterlight/templatetags/custom_tags.py @@ -1,15 +1,6 @@ -import datetime -import logging - from django import template from django.core.urlresolvers import resolve, reverse -from django.utils.safestring import mark_safe -from django.utils.translation import activate, get_language, ugettext_lazy as _ - -from hosting.models import GenericProduct, HostingOrder -from utils.hosting_utils import get_ip_addresses - -logger = logging.getLogger(__name__) +from django.utils.translation import activate, get_language register = template.Library() @@ -61,116 +52,3 @@ def escaped_line_break(value): :return: """ 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: - 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): - """ - Returns ready-to-use "html" line item to be shown for an invoice in the - invoice list page - - :param invoice: the stripe Invoice object - :return: - """ - start_date = 0 - end_date = 0 - is_first = True - vm_id = -1 - 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 "" - start_date = line_data.period.start - end_date = line_data.period.end - is_first = False - if hasattr(line_data.metadata, "VM_ID"): - vm_id = line_data.metadata.VM_ID - else: - if line_data.period.start < start_date: - start_date = line_data.period.start - if line_data.period.end > end_date: - end_date = line_data.period.end - if hasattr(line_data.metadata, "VM_ID"): - vm_id = line_data.metadata.VM_ID - - try: - vm_id = int(vm_id) - except ValueError as ve: - print(str(ve)) - if invoice["lines"]["data"]: - return mark_safe(""" - {vm_id} - {ip_addresses} - {period} - {total} - - {see_invoice_text} - - """.format( - vm_id=vm_id if vm_id > 0 else "", - ip_addresses=mark_safe(get_ip_addresses(vm_id)) if vm_id > 0 else - 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'))), - total='%.2f' % (invoice.total/100), - stripe_invoice_url=invoice.hosted_invoice_url, - see_invoice_text=_("See Invoice") - )) - else: - return "" - - -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] - try: - product = GenericProduct.objects.get(id=product_id) - product_name = product.product_name - except GenericProduct.DoesNotExist as dne: - logger.error("Generic product id=%s does not exist" % product_id) - product_name = plan_name - except GenericProduct.MultipleObjectsReturned as mor: - logger.error("Multiple products with id=%s exist" % product_id) - product_name = "Unknown" - else: - logger.debug("Product name for plan %s does not exist" % plan_name) - return product_name diff --git a/datacenterlight/tests.py b/datacenterlight/tests.py index ca1bb930..74798cc3 100644 --- a/datacenterlight/tests.py +++ b/datacenterlight/tests.py @@ -1,6 +1,7 @@ # from django.test import TestCase - +import datetime from time import sleep +from unittest import skipIf import stripe from celery.result import AsyncResult @@ -8,7 +9,6 @@ from django.conf import settings from django.core.management import call_command from django.test import TestCase, override_settings from model_mommy import mommy -from unittest import skipIf from datacenterlight.models import VMTemplate from datacenterlight.tasks import create_vm_task @@ -119,11 +119,14 @@ class CeleryTaskTestCase(TestCase): subscription_result = self.stripe_utils.subscribe_customer_to_plan( stripe_customer.stripe_id, [{"plan": stripe_plan.get( - 'response_object').stripe_plan_id}]) + 'response_object').stripe_plan_id}], + int(datetime.datetime.now().timestamp()) + 300 if settings.ADD_TRIAL_PERIOD_TO_SUBSCRIPTION else None) stripe_subscription_obj = subscription_result.get('response_object') # Check if the subscription was approved and is active - if stripe_subscription_obj is None \ - or stripe_subscription_obj.status != 'active': + if (stripe_subscription_obj is None or + (stripe_subscription_obj.status != 'active' and + stripe_subscription_obj.status != 'trialing') + ): msg = subscription_result.get('error') raise Exception("Creating subscription failed: {}".format(msg)) diff --git a/datacenterlight/utils.py b/datacenterlight/utils.py index 4e8094c0..11d2b82e 100644 --- a/datacenterlight/utils.py +++ b/datacenterlight/utils.py @@ -1,9 +1,7 @@ -import datetime import logging import pyotp import requests -import stripe from django.conf import settings from django.contrib.sites.models import Site @@ -11,19 +9,12 @@ from datacenterlight.tasks import create_vm_task from hosting.models import HostingOrder, HostingBill, OrderDetail from membership.models import StripeCustomer from utils.forms import UserBillingAddressForm -from utils.models import BillingAddress, UserBillingAddress -from utils.stripe_utils import StripeUtils +from utils.models import BillingAddress from .cms_models import CMSIntegration from .models import VMPricing, VMTemplate logger = logging.getLogger(__name__) -eu_countries = ['at', 'be', 'bg', 'ch', 'cy', 'cz', 'hr', 'dk', - 'ee', 'fi', 'fr', 'mc', 'de', 'gr', 'hu', 'ie', 'it', - 'lv', 'lu', 'mt', 'nl', 'pl', 'pt', 'ro','sk', 'si', 'es', - 'se', 'gb'] - - def get_cms_integration(name): current_site = Site.objects.get_current() try: @@ -38,14 +29,12 @@ 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'], city=billing_address_data['city'], postal_code=billing_address_data['postal_code'], - country=billing_address_data['country'], - vat_number=billing_address_data['vat_number'], + country=billing_address_data['country'] ) billing_address.save() customer = StripeCustomer.objects.filter(id=stripe_customer_id).first() @@ -103,6 +92,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: @@ -110,9 +101,7 @@ def clear_all_session_vars(request): 'billing_address_data', 'card_id', 'token', 'customer', 'generic_payment_type', 'generic_payment_details', 'product_id', - 'order_confirm_url', 'new_user_hosting_key_id', - 'vat_validation_status', 'billing_address_id', - 'id_payment_method']: + 'order_confirm_url', 'new_user_hosting_key_id']: if session_var in request.session: del request.session[session_var] @@ -134,172 +123,3 @@ def check_otp(name, realm, token): data=data ) return response.status_code - - -def validate_vat_number(stripe_customer_id, billing_address_id, - is_user_ba=False): - if is_user_ba: - try: - billing_address = UserBillingAddress.objects.get( - id=billing_address_id) - except UserBillingAddress.DoesNotExist as dne: - billing_address = None - logger.debug( - "UserBillingAddress does not exist for %s" % billing_address_id) - except UserBillingAddress.MultipleObjectsReturned as mor: - logger.debug( - "Multiple UserBillingAddress exist for %s" % billing_address_id) - billing_address = UserBillingAddress.objects.filter( - id=billing_address_id).order_by('-id').first() - else: - try: - billing_address = BillingAddress.objects.get(id=billing_address_id) - except BillingAddress.DoesNotExist as dne: - billing_address = None - logger.debug("BillingAddress does not exist for %s" % billing_address_id) - except BillingAddress.MultipleObjectsReturned as mor: - logger.debug("Multiple BillingAddress exist for %s" % billing_address_id) - billing_address = BillingAddress.objects.filter(id=billing_address_id).order_by('-id').first() - if billing_address is not None: - logger.debug("BillingAddress found: %s %s type=%s" % ( - billing_address_id, str(billing_address), type(billing_address))) - if billing_address.country.lower().strip() not in eu_countries: - return { - "validated_on": "", - "status": "not_needed" - } - if billing_address.vat_number_validated_on: - logger.debug("billing_address verified on %s" % - billing_address.vat_number_validated_on) - return { - "validated_on": billing_address.vat_number_validated_on, - "status": "verified" - } - else: - logger.debug("billing_address not yet verified, " - "Checking if we already have a tax id") - if billing_address.stripe_tax_id: - logger.debug("We have a tax id %s" % billing_address.stripe_tax_id) - tax_id_obj = stripe.Customer.retrieve_tax_id( - stripe_customer_id, - billing_address.stripe_tax_id, - ) - if tax_id_obj.verification.status == "verified": - logger.debug("Latest status on Stripe=%s. Updating" % - tax_id_obj.verification.status) - # update billing address - billing_address.vat_number_validated_on = datetime.datetime.now() - billing_address.vat_validation_status = tax_id_obj.verification.status - billing_address.save() - return { - "status": "verified", - "validated_on": billing_address.vat_number_validated_on - } - else: - billing_address.vat_validation_status = tax_id_obj.verification.status - billing_address.save() - logger.debug( - "Latest status on Stripe=%s" % str(tax_id_obj) - ) - return { - "status": tax_id_obj.verification.status if tax_id_obj - else "unknown", - "validated_on": "" - } - else: - logger.debug("Creating a tax id") - logger.debug("Billing address = %s" % str(billing_address)) - tax_id_obj = create_tax_id( - stripe_customer_id, billing_address_id, - "ch_vat" if billing_address.country.lower() == "ch" else "eu_vat", - is_user_ba=is_user_ba - ) - logger.debug("tax_id_obj = %s" % str(tax_id_obj)) - else: - logger.debug("invalid billing address") - return { - "status": "invalid billing address", - "validated_on": "" - } - - if 'response_object' in tax_id_obj: - return tax_id_obj - - return { - "status": tax_id_obj.verification.status, - "validated_on": datetime.datetime.now() if tax_id_obj.verification.status == "verified" else "" - } - - -def create_tax_id(stripe_customer_id, billing_address_id, type, - is_user_ba=False): - if is_user_ba: - try: - billing_address = UserBillingAddress.objects.get( - id=billing_address_id) - except UserBillingAddress.DoesNotExist as dne: - billing_address = None - logger.debug( - "UserBillingAddress does not exist for %s" % billing_address_id) - except UserBillingAddress.MultipleObjectsReturned as mor: - logger.debug( - "Multiple UserBillingAddress exist for %s" % billing_address_id) - billing_address = UserBillingAddress.objects.filter( - id=billing_address_id).order_by('-id').first() - else: - try: - billing_address = BillingAddress.objects.get(id=billing_address_id) - except BillingAddress.DoesNotExist as dne: - billing_address = None - logger.debug("BillingAddress does not exist for %s" % billing_address_id) - except BillingAddress.MultipleObjectsReturned as mor: - logger.debug("Multiple BillingAddress exist for %s" % billing_address_id) - billing_address = BillingAddress.objects.filter(billing_address_id).order_by('-id').first() - - tax_id_obj = None - if billing_address: - stripe_utils = StripeUtils() - tax_id_response = stripe_utils.get_or_create_tax_id_for_user( - stripe_customer_id, - vat_number=billing_address.vat_number, - type=type, - country=billing_address.country - ) - - tax_id_obj = tax_id_response.get('response_object') - - if not tax_id_obj: - logger.debug("Received none in tax_id_obj") - return { - 'paid': False, - 'response_object': None, - 'error': "No such address found" if 'error' not in tax_id_response else - tax_id_response["error"] - } - - try: - stripe_customer = StripeCustomer.objects.get(stripe_id=stripe_customer_id) - billing_address_set = set() - logger.debug("Updating billing address") - for ho in stripe_customer.hostingorder_set.all(): - if ho.billing_address.vat_number == billing_address.vat_number: - billing_address_set.add(ho.billing_address) - for b_address in billing_address_set: - b_address.stripe_tax_id = tax_id_obj.id - b_address.vat_validation_status = tax_id_obj.verification.status - b_address.save() - logger.debug("Updated billing_address %s" % str(b_address)) - - ub_addresses = stripe_customer.user.billing_addresses.filter( - vat_number=billing_address.vat_number) - for ub_address in ub_addresses: - ub_address.stripe_tax_id = tax_id_obj.id - ub_address.vat_validation_status = tax_id_obj.verification.status - ub_address.save() - logger.debug("Updated user_billing_address %s" % str(ub_address)) - except StripeCustomer.DoesNotExist as dne: - logger.debug("StripeCustomer %s does not exist" % stripe_customer_id) - billing_address.stripe_tax_id = tax_id_obj.id - billing_address.vat_validation_status = tax_id_obj.verification.status - billing_address.save() - return tax_id_obj diff --git a/datacenterlight/views.py b/datacenterlight/views.py index 5bf68e0a..53b5a6df 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -1,16 +1,13 @@ -import json +import datetime import logging -import stripe from django import forms from django.conf import settings 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 @@ -21,9 +18,10 @@ from hosting.forms import ( UserHostingKeyForm ) from hosting.models import ( - HostingBill, HostingOrder, UserCardDetail, GenericProduct, UserHostingKey, - StripeTaxRate, IncompleteSubscriptions, IncompletePaymentIntents) + HostingBill, HostingOrder, UserCardDetail, GenericProduct, UserHostingKey +) 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, @@ -38,9 +36,7 @@ from utils.tasks import send_plain_email_task from .cms_models import DCLCalculatorPluginModel from .forms import ContactForm from .models import VMTemplate, VMPricing -from .utils import ( - get_cms_integration, create_vm, clear_all_session_vars, validate_vat_number -) +from .utils import get_cms_integration, create_vm, clear_all_session_vars logger = logging.getLogger(__name__) @@ -257,18 +253,16 @@ class PaymentOrderView(FormView): ) else: billing_address_form = BillingAddressForm( - instance=self.request.user.billing_addresses.order_by('-id').first() + instance=self.request.user.billing_addresses.first() ) user = self.request.user if hasattr(user, 'stripecustomer'): 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( @@ -313,13 +307,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' @@ -434,10 +421,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"], @@ -456,8 +441,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 @@ -468,20 +452,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') @@ -504,7 +510,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( @@ -515,11 +521,9 @@ 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() - request.session["billing_address_id"] = billing_address.id request.session['billing_address_data'] = address_form.cleaned_data request.session['user'] = this_user # Get or create stripe customer @@ -537,23 +541,6 @@ class PaymentOrderView(FormView): else: request.session['customer'] = customer - vat_number = address_form.cleaned_data.get('vat_number').strip() - if vat_number: - validate_result = validate_vat_number( - stripe_customer_id=request.session['customer'], - billing_address_id=billing_address.id - ) - - if 'error' in validate_result and validate_result['error']: - messages.add_message( - request, messages.ERROR, validate_result["error"], - extra_tags='vat_error' - ) - return HttpResponseRedirect( - reverse('datacenterlight:payment') + '#vat_error' - ) - request.session["vat_validation_status"] = validate_result["status"] - # For generic payment we take the user directly to confirmation if ('generic_payment_type' in request.session and self.request.session['generic_payment_type'] == 'generic'): @@ -584,33 +571,24 @@ class OrderConfirmationView(DetailView, FormView): @cache_control(no_cache=True, must_revalidate=True, no_store=True) def get(self, request, *args, **kwargs): context = {} - # this is amount to be charge/subscribed before VAT and discount - # and expressed in chf. To convert to cents, multiply by 100 - amount_to_charge = 0 - 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.objects.get(id=card_id) context['cc_last4'] = card_detail.last4 context['cc_brand'] = card_detail.brand @@ -619,19 +597,10 @@ class OrderConfirmationView(DetailView, FormView): if ('generic_payment_type' in request.session and self.request.session['generic_payment_type'] == 'generic'): - if "vat_validation_status" in request.session and ( - request.session["vat_validation_status"] == "verified" or - request.session["vat_validation_status"] == "not_needed"): - request.session['generic_payment_details']['vat_rate'] = 0 - request.session['generic_payment_details']['vat_amount'] = 0 - 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 = ( @@ -646,48 +615,11 @@ class OrderConfirmationView(DetailView, FormView): vat_rate=user_country_vat_rate * 100 ) vm_specs["price"] = price - vm_specs["price_after_discount"] = price - discount["amount"] - amount_to_charge = price - vat_number = request.session.get('billing_address_data').get("vat_number") - billing_address = BillingAddress.objects.get( - id=request.session["billing_address_id"]) - if vat_number: - validate_result = validate_vat_number( - stripe_customer_id=request.session['customer'], - billing_address_id=billing_address.id - ) - if 'error' in validate_result and validate_result['error']: - messages.add_message( - request, messages.ERROR, validate_result["error"], - extra_tags='vat_error' - ) - return HttpResponseRedirect( - reverse('datacenterlight:payment') + '#vat_error' - ) - request.session["vat_validation_status"] = validate_result["status"] - - if user_vat_country.lower() == "ch": - vm_specs["vat"] = vat - vm_specs["vat_percent"] = vat_percent - vm_specs["vat_validation_status"] = "ch_vat" - elif ("vat_validation_status" in request.session and - (request.session["vat_validation_status"] == "verified" or - request.session["vat_validation_status"] == "not_needed")): - vm_specs["vat_percent"] = 0 - vm_specs["vat"] = 0 - vm_specs["vat_validation_status"] = request.session["vat_validation_status"] - else: - vm_specs["vat"] = vat - vm_specs["vat_percent"] = vat_percent - vm_specs["vat_validation_status"] = request.session["vat_validation_status"] if "vat_validation_status" in request.session else "" + vm_specs["vat"] = vat + vm_specs["vat_percent"] = vat_percent vm_specs["vat_country"] = user_vat_country - vm_specs["price_with_vat"] = round(price * (1 + vm_specs["vat_percent"] * 0.01), 2) - vm_specs["price_after_discount"] = round(price - discount['amount'], 2) - vm_specs["price_after_discount_with_vat"] = round((price - discount['amount']) * (1 + vm_specs["vat_percent"] * 0.01), 2) - discount["amount_with_vat"] = round(vm_specs["price_with_vat"] - vm_specs["price_after_discount_with_vat"], 2) - vm_specs["total_price"] = vm_specs["price_after_discount_with_vat"] vm_specs["discount"] = discount - logger.debug(vm_specs) + vm_specs["total_price"] = round(price + vat - discount['amount'], 2) request.session['specs'] = vm_specs context.update({ @@ -695,51 +627,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'), @@ -747,67 +634,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'], @@ -822,7 +684,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: @@ -832,22 +694,30 @@ 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.objects.get(id=card_id) @@ -856,11 +726,6 @@ class OrderConfirmationView(DetailView, FormView): 'brand': user_card_detail.brand, 'card_id': user_card_detail.card_id } - UserCardDetail.set_default_card( - stripe_api_cus_id=stripe_api_cus_id, - stripe_source_id=user_card_detail.card_id - ) - logger.debug("card_details_dict=%s" % card_details_dict) else: response = { 'status': False, @@ -878,25 +743,48 @@ 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'] - if 'discount' in request.session['generic_payment_details']: - discount = request.session['generic_payment_details']['discount'] - else: - discount = {'name': '', 'amount': 0, 'coupon_id': ''} amount_to_be_charged = ( round( - request.session['generic_payment_details']['amount_before_vat'], + request.session['generic_payment_details']['amount'], 2 ) ) @@ -917,10 +805,7 @@ class OrderConfirmationView(DetailView, FormView): cpu = specs.get('cpu') memory = specs.get('memory') disk_size = specs.get('disk_size') - amount_to_be_charged = specs.get('price') - vat_percent = specs.get('vat_percent') - vat_country = specs.get('vat_country') - discount = specs.get('discount') + amount_to_be_charged = specs.get('total_price') plan_name = StripeUtils.get_stripe_plan_name( cpu=cpu, memory=memory, @@ -935,178 +820,264 @@ 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, stripe_plan_id=stripe_plan_id, interval=recurring_interval ) - # Create StripeTaxRate if applicable to the user - logger.debug("vat_percent = %s, vat_country = %s" % - (vat_percent, vat_country) - ) - stripe_tax_rate = None - if vat_percent > 0: - try: - stripe_tax_rate = StripeTaxRate.objects.get( - description="VAT for %s" % vat_country - ) - print("Stripe Tax Rate exists") - except StripeTaxRate.DoesNotExist as dne: - print("StripeTaxRate does not exist") - tax_rate_obj = stripe.TaxRate.create( - display_name="VAT", - description="VAT for %s" % vat_country, - jurisdiction=vat_country, - percentage=vat_percent, - inclusive=False, - ) - stripe_tax_rate = StripeTaxRate.objects.create( - display_name=tax_rate_obj.display_name, - description=tax_rate_obj.description, - jurisdiction=tax_rate_obj.jurisdiction, - percentage=tax_rate_obj.percentage, - inclusive=False, - tax_rate_id=tax_rate_obj.id - ) - logger.debug("Created StripeTaxRate %s" % - stripe_tax_rate.tax_rate_id) subscription_result = stripe_utils.subscribe_customer_to_plan( stripe_api_cus_id, - [{"plan": stripe_plan.get('response_object').stripe_plan_id}], - coupon=(discount['stripe_coupon_id'] - if 'name' in discount and - discount['name'] is not None and - 'ipv6' in discount['name'].lower() and - discount['stripe_coupon_id'] - else ""), - tax_rates=[stripe_tax_rate.tax_rate_id] if stripe_tax_rate else [], - default_payment_method=request.session['id_payment_method'] + [{"plan": stripe_plan.get( + 'response_object').stripe_plan_id}], + int(datetime.datetime.now().timestamp()) + 300 if settings.ADD_TRIAL_PERIOD_TO_SUBSCRIPTION else None ) 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')): + if (stripe_subscription_obj is None or + (stripe_subscription_obj.status != 'active' and + stripe_subscription_obj.status != 'trialing') + ): # At this point, we have created a Stripe API card and # associated it with the customer; but the transaction failed # due to some reason. So, we would want to dissociate this card # here. # ... + 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.email, + 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'] + ) + 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:orders') + 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, + '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': ( @@ -1122,488 +1093,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): - if card_id: - logger.debug("card_id %s was in request" % card_id) - user_card_detail = UserCardDetail.objects.get(id=card_id) - card_details_dict = { - '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 - ) - UserCardDetail.save_default_card_local( - custom_user.stripecustomer.stripe_id, - ucd.card_id - ) - card_details_dict = { - 'last4': ucd.last4, - 'brand': ucd.brand, - 'card_id': ucd.card_id - } - return card_details_dict - - -def do_provisioning_generic( - request, stripe_api_cus_id, card_details_response, - stripe_subscription_id, stripe_charge_id, gp_details, - billing_address_data): - 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") - 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) - - # 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() - - 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_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']], - } - 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") - 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.error(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/forms.py b/digitalglarus/forms.py index 200099e3..a0e685f8 100644 --- a/digitalglarus/forms.py +++ b/digitalglarus/forms.py @@ -35,7 +35,6 @@ class MembershipBillingForm(BillingAddressForm): 'city': _('City'), 'postal_code': _('Postal Code'), 'country': _('Country'), - 'vat_number': _('VAT Number'), } 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/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 62fe2897..9c89a8ff 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -631,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', @@ -649,6 +651,7 @@ CELERY_MAX_RETRIES = int_env('CELERY_MAX_RETRIES', 5) DCL_ERROR_EMAILS_TO = env('DCL_ERROR_EMAILS_TO') ADMIN_EMAIL = env('ADMIN_EMAIL') +WEBHOOK_EMAIL_TO = env('WEBHOOK_EMAIL_TO') DCL_ERROR_EMAILS_TO_LIST = [] if DCL_ERROR_EMAILS_TO is not None: @@ -703,7 +706,7 @@ if ENABLE_LOGGING: 'disable_existing_loggers': False, 'formatters': { 'standard': { - 'format': '%(asctime)s,%(msecs)d %(levelname)-8s [%(filename)s:%(lineno)d] %(message)s', + 'format': '%(asctime)s %(levelname)s %(name)s: %(message)s' } }, 'handlers': handlers_dict, @@ -719,7 +722,7 @@ X_FRAME_OPTIONS = ('SAMEORIGIN' if X_FRAME_OPTIONS_ALLOW_FROM_URI is None else X_FRAME_OPTIONS_ALLOW_FROM_URI.strip() )) -WEBHOOK_SECRET = env('WEBHOOK_SECRET') +INVOICE_WEBHOOK_SECRET = env('INVOICE_WEBHOOK_SECRET') DEBUG = bool_env('DEBUG') ADD_TRIAL_PERIOD_TO_SUBSCRIPTION = bool_env('ADD_TRIAL_PERIOD_TO_SUBSCRIPTION') @@ -759,15 +762,6 @@ OTP_VERIFY_ENDPOINT = env('OTP_VERIFY_ENDPOINT') FIRST_VM_ID_AFTER_EU_VAT = int_env('FIRST_VM_ID_AFTER_EU_VAT') PRE_EU_VAT_RATE = float(env('PRE_EU_VAT_RATE')) -VM_BASE_PRICE = float(env('VM_BASE_PRICE')) - -UPDATED_TEMPLATES_STR = env('UPDATED_TEMPLATES') -UPDATED_TEMPLATES_DICT = {} -if UPDATED_TEMPLATES_STR: - UPDATED_TEMPLATES_DICT = eval(UPDATED_TEMPLATES_STR) - -MAX_TIME_TO_WAIT_FOR_VM_TERMINATE = int_env( - 'MAX_TIME_TO_WAIT_FOR_VM_TERMINATE', 15) if DEBUG: from .local import * # flake8: noqa 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/dynamicweb/urls.py b/dynamicweb/urls.py index 113a5cda..e07ca6bc 100644 --- a/dynamicweb/urls.py +++ b/dynamicweb/urls.py @@ -63,7 +63,7 @@ urlpatterns += i18n_patterns( name='blog_list_view'), url(r'^cms/', include('cms.urls')), url(r'^blog/', include('djangocms_blog.urls', namespace='djangocms_blog')), - url(r'^webhooks/', webhook_views.handle_webhook), + url(r'^webhooks/invoices/', webhook_views.handle_invoice_webhook), url(r'^$', RedirectView.as_view(url='/cms') if REDIRECT_TO_CMS else LandingView.as_view()), url(r'^', include('ungleich_page.urls', namespace='ungleich_page')), diff --git a/hosting/forms.py b/hosting/forms.py index 8df2bd3e..947cee44 100644 --- a/hosting/forms.py +++ b/hosting/forms.py @@ -2,7 +2,6 @@ import datetime import logging import subprocess import tempfile -import xml from django import forms from django.conf import settings @@ -208,7 +207,7 @@ class UserHostingKeyForm(forms.ModelForm): logger.debug( "Not a correct ssh format {error}".format(error=str(cpe))) raise forms.ValidationError(KEY_ERROR_MESSAGE) - return xml.sax.saxutils.escape(openssh_pubkey_str) + return openssh_pubkey_str def clean_name(self): INVALID_NAME_MESSAGE = _("Comma not accepted in the name of the key") diff --git a/hosting/locale/de/LC_MESSAGES/django.po b/hosting/locale/de/LC_MESSAGES/django.po index 11c4a2a5..08bcdd7a 100644 --- a/hosting/locale/de/LC_MESSAGES/django.po +++ b/hosting/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-02-07 10:19+0000\n" +"POT-Creation-Date: 2019-11-15 16:40+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \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/migrations/0053_auto_20190415_1952.py b/hosting/migrations/0053_auto_20190415_1952.py new file mode 100644 index 00000000..9a8a9c3d --- /dev/null +++ b/hosting/migrations/0053_auto_20190415_1952.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-04-15 19:52 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0052_hostingbilllineitem'), + ] + + operations = [ + migrations.AlterField( + model_name='monthlyhostingbill', + name='order', + field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='hosting.HostingOrder'), + ), + ] diff --git a/hosting/migrations/0056_merge.py b/hosting/migrations/0056_merge.py new file mode 100644 index 00000000..af8acc73 --- /dev/null +++ b/hosting/migrations/0056_merge.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.4 on 2019-07-18 03:26 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('hosting', '0055_auto_20190701_1614'), + ('hosting', '0053_auto_20190415_1952'), + ] + + operations = [ + ] diff --git a/hosting/migrations/0059_stripetaxrate.py b/hosting/migrations/0059_stripetaxrate.py deleted file mode 100644 index af024991..00000000 --- a/hosting/migrations/0059_stripetaxrate.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.4 on 2020-01-05 04:29 -from __future__ import unicode_literals - -from django.db import migrations, models -import utils.mixins - - -class Migration(migrations.Migration): - - dependencies = [ - ('hosting', '0058_genericproduct_product_subscription_interval'), - ] - - operations = [ - migrations.CreateModel( - name='StripeTaxRate', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('tax_rate_id', models.CharField(max_length=100, unique=True)), - ('jurisdiction', models.CharField(max_length=10)), - ('inclusive', models.BooleanField(default=False)), - ('display_name', models.CharField(max_length=100)), - ('percentage', models.FloatField(default=0)), - ('description', models.CharField(max_length=100)), - ], - bases=(utils.mixins.AssignPermissionsMixin, models.Model), - ), - ] 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/models.py b/hosting/models.py index 48238afe..b858c4d2 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -81,22 +81,15 @@ class GenericProduct(AssignPermissionsMixin, models.Model): product_subscription_interval = models.CharField( max_length=10, default="month", help_text="Choose between `year` and `month`") - exclude_vat_calculations = models.BooleanField( - default=False, - help_text="When checked VAT calculations are excluded for this product" - ) def __str__(self): return self.product_name def get_actual_price(self, vat_rate=None): - 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 +162,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): @@ -264,7 +253,10 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model): Corresponds to Invoice object of Stripe """ customer = models.ForeignKey(StripeCustomer) - order = models.ForeignKey(HostingOrder) + order = models.ForeignKey( + HostingOrder, null=True, blank=True, default=None, + on_delete=models.SET_NULL + ) created = models.DateTimeField(help_text="When the invoice was created") receipt_number = models.CharField( help_text="The receipt number that is generated on Stripe", @@ -552,6 +544,19 @@ class HostingBillLineItem(AssignPermissionsMixin, models.Model): ) return item_detail + def get_vm_id(self): + """ + If VM_ID is set in the metadata extract and return it as integer + other return None + + :return: + """ + if "VM_ID" in self.metadata: + data = json.loads(self.metadata) + return int(data["VM_ID"]) + else: + return None + class VMDetail(models.Model): user = models.ForeignKey(CustomUser) @@ -676,11 +681,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 @@ -741,42 +742,44 @@ class VATRates(AssignPermissionsMixin, models.Model): description = models.TextField(blank=True, default='') -class StripeTaxRate(AssignPermissionsMixin, models.Model): - tax_rate_id = models.CharField(max_length=100, unique=True) - jurisdiction = models.CharField(max_length=10) - inclusive = models.BooleanField(default=False) - display_name = models.CharField(max_length=100) - percentage = models.FloatField(default=0) - description = models.CharField(max_length=100) - - -class IncompletePaymentIntents(AssignPermissionsMixin, models.Model): - completed_at = models.DateTimeField(null=True) +class FailedInvoice(AssignPermissionsMixin, models.Model): + permissions = ('view_failedinvoice',) + stripe_customer = models.ForeignKey(StripeCustomer) + order = models.ForeignKey( + HostingOrder, null=True, blank=True, default=None, + on_delete=models.SET_NULL + ) created_at = models.DateTimeField(auto_now_add=True) - 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() + number_of_attempts = models.IntegerField( + default=0, + help_text="The number of attempts for repayment") + invoice_id = models.CharField( + unique=True, + max_length=127, + help_text= "The ID of the invoice that failed") + result = models.IntegerField( + help_text="Whether the service was interrupted or another payment " + "succeeded" + ) + service_interrupted_at = models.DateTimeField( + help_text="The datetime if/when service was interrupted" + ) + + class Meta: + permissions = ( + ('view_failedinvoice', 'View Failed Invoice'), + ) + + @classmethod + def create(cls, stripe_customer=None, order=None, invoice_id=None, + number_of_attempts=0): + instance = cls.objects.create( + stripe_customer=stripe_customer, + order=order, + number_of_attempts=number_of_attempts, + invoice_id=invoice_id + ) + instance.assign_permissions(stripe_customer.user) + return instance -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/css/orders.css b/hosting/static/hosting/css/orders.css index 2deab999..6819a94b 100644 --- a/hosting/static/hosting/css/orders.css +++ b/hosting/static/hosting/css/orders.css @@ -2,10 +2,4 @@ .orders-container .table > tbody > tr > td { vertical-align: middle; -} - -@media screen and (min-width:767px){ - .dcl-text-right { - padding-right: 20px; - } } \ No newline at end of file diff --git a/hosting/static/hosting/js/initial.js b/hosting/static/hosting/js/initial.js index 36cf6d07..6b6d744d 100644 --- a/hosting/static/hosting/js/initial.js +++ b/hosting/static/hosting/js/initial.js @@ -266,8 +266,8 @@ $( document ).ready(function() { } var total = (cardPricing['cpu'].value * window.coresUnitPrice) + (cardPricing['ram'].value * window.ramUnitPrice) + - (cardPricing['storage'].value * window.ssdUnitPrice) + - window.vmBasePrice - window.discountAmount; + (cardPricing['storage'].value * window.ssdUnitPrice) - + window.discountAmount; total = parseFloat(total.toFixed(2)); $("#total").text(total); } 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/dashboard.html b/hosting/templates/hosting/dashboard.html index 212c5885..bda6eb11 100644 --- a/hosting/templates/hosting/dashboard.html +++ b/hosting/templates/hosting/dashboard.html @@ -29,7 +29,7 @@
- +

{% trans "My Bills" %}

diff --git a/hosting/templates/hosting/includes/_messages.html b/hosting/templates/hosting/includes/_messages.html index a32ce177..12540558 100644 --- a/hosting/templates/hosting/includes/_messages.html +++ b/hosting/templates/hosting/includes/_messages.html @@ -1,11 +1,7 @@ {% if messages %}
    {% for message in messages %} - {% if message.tags and 'error' in message.tags %} -

    {{ message|safe }}

    - {% elif message.tags %} -
    {{ message|safe }}
    - {% endif %} +
    {{ message|safe }}
    {% endfor %}
{% endif %} \ No newline at end of file diff --git a/hosting/templates/hosting/invoice_detail.html b/hosting/templates/hosting/invoice_detail.html index e5714b82..67fa06e4 100644 --- a/hosting/templates/hosting/invoice_detail.html +++ b/hosting/templates/hosting/invoice_detail.html @@ -70,9 +70,6 @@ {{invoice.order.billing_address.postal_code}}
{{invoice.order.billing_address.city}}, {{invoice.order.billing_address.country}} - {% if invoice.order.billing_address.vat_number %} -
{% trans "VAT Number" %} {{invoice.order.billing_address.vat_number}} - {% endif %} {% endif %}

diff --git a/hosting/templates/hosting/invoices.html b/hosting/templates/hosting/invoices.html index 347b1ff4..5f7be4b4 100644 --- a/hosting/templates/hosting/invoices.html +++ b/hosting/templates/hosting/invoices.html @@ -15,116 +15,47 @@
-
-
- + - {% for inv_data in invs %} + {% for invoice in invoices %} - {{ inv_data | get_line_item_from_stripe_invoice }} + + + {% with period|get_value_from_dict:invoice.invoice_number as period_to_show %} + + {% endwith %} + + {% endfor %}
{% trans "VM ID" %}{% trans "IP Address" %}/{% trans "Product" %}{% trans "IP Address" %} {% trans "Period" %} {% trans "Amount" %}
{{ invoice.order.vm_id }}{{ ips|get_value_from_dict:invoice.invoice_number|join:"
" }}
{{ period_to_show.period_start | date:'Y-m-d' }} — {{ period_to_show.period_end | date:'Y-m-d' }}{{ invoice.total_in_chf|floatformat:2|intcomma }} + {% trans 'See Invoice' %} +
-{% if invs.has_other_pages %} -
    - {% if invs.has_previous %} - {% if user_email %} -
  • «
  • - {% else %} -
  • «
  • - {% endif %} - {% else %} -
  • «
  • - {% endif %} - {% for i in invs.paginator.page_range %} - {% if invs.number == i %} -
  • {{ i }} (current)
  • - {% else %} - {% if user_email %} -
  • {{ i }}
  • - {% else %} -
  • {{ i }}
  • - {% endif %} - {% endif %} - {% endfor %} - {% if invs.has_next %} - {% if user_email %} -
  • »
  • - {% else %} -
  • »
  • - {% endif %} - {% else %} -
  • »
  • - {% endif %} -
-{% endif %} -
- - {% endblock %} diff --git a/hosting/templates/hosting/order_detail.html b/hosting/templates/hosting/order_detail.html index dee453d5..b725a645 100644 --- a/hosting/templates/hosting/order_detail.html +++ b/hosting/templates/hosting/order_detail.html @@ -62,17 +62,11 @@ {{user.name}}
{{order.billing_address.street_address}}, {{order.billing_address.postal_code}}
{{order.billing_address.city}}, {{order.billing_address.country}} - {% if order.billing_address.vat_number %} -
{% trans "VAT Number" %} {{order.billing_address.vat_number}} - {% endif %} {% else %} {% with request.session.billing_address_data as billing_address %} {{billing_address.cardholder_name}}
{{billing_address.street_address}}, {{billing_address.postal_code}}
{{billing_address.city}}, {{billing_address.country}} - {% if billing_address.vat_number %} -
{% trans "VAT Number" %} {{billing_address.vat_number}} - {% endif %} {% endwith %} {% endif %}

@@ -218,7 +212,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/templates/hosting/user_keys.html b/hosting/templates/hosting/user_keys.html index 8009b23d..6810efdf 100644 --- a/hosting/templates/hosting/user_keys.html +++ b/hosting/templates/hosting/user_keys.html @@ -81,10 +81,12 @@ {% if user_key.private_key %} - +
+ +
{% endif %} diff --git a/hosting/templates/hosting/virtual_machine_detail.html b/hosting/templates/hosting/virtual_machine_detail.html index 24b2c6ad..3281d9c3 100644 --- a/hosting/templates/hosting/virtual_machine_detail.html +++ b/hosting/templates/hosting/virtual_machine_detail.html @@ -46,7 +46,7 @@
{% trans "Current Pricing" %}
{{order.price|floatformat:2|intcomma}} CHF/{% if order.generic_product %}{% trans order.generic_product.product_subscription_interval %}{% else %}{% trans "Month" %}{% endif %}
- {% trans "See Invoice" %} + {% trans "See Invoice" %}
diff --git a/hosting/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 e2f6e13b..54cdc481 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -1,10 +1,8 @@ import logging import uuid from datetime import datetime -from urllib.parse import quote from time import sleep -import stripe from django import forms from django.conf import settings from django.contrib import messages @@ -12,9 +10,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.tokens import default_token_generator from django.core.exceptions import ValidationError from django.core.files.base import ContentFile -from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.core.urlresolvers import reverse_lazy, reverse -from django.db.models import Q from django.http import ( Http404, HttpResponseRedirect, HttpResponse, JsonResponse ) @@ -41,10 +37,8 @@ from stored_messages.settings import stored_messages_settings from datacenterlight.cms_models import DCLCalculatorPluginModel from datacenterlight.models import VMTemplate, VMPricing -from datacenterlight.utils import ( - create_vm, get_cms_integration, check_otp, validate_vat_number -) -from hosting.models import UserCardDetail, StripeTaxRate +from datacenterlight.utils import create_vm, get_cms_integration, check_otp +from hosting.models import UserCardDetail from membership.models import CustomUser, StripeCustomer from opennebula_api.models import OpenNebulaManager from opennebula_api.serializers import ( @@ -60,10 +54,11 @@ from utils.hosting_utils import ( get_vm_price_with_vat, get_vm_price_for_given_vat, HostingUtils, get_vat_rate_for_country ) -from utils.ldap_manager import LdapManager from utils.mailer import BaseEmail from utils.stripe_utils import StripeUtils from utils.tasks import send_plain_email_task +from utils.ldap_manager import LdapManager + from utils.views import ( PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin, ResendActivationLinkViewMixin @@ -75,7 +70,7 @@ from .forms import ( from .mixins import ProcessVMSelectionMixin, HostingContextMixin from .models import ( HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail, - GenericProduct, MonthlyHostingBill + GenericProduct, MonthlyHostingBill, HostingBillLineItem ) logger = logging.getLogger(__name__) @@ -388,7 +383,7 @@ class PasswordResetConfirmView(HostingContextMixin, user = CustomUser.objects.get(pk=uid) opennebula_client = OpenNebulaManager( - email=user.username, + email=user.email, password=user.password, ) @@ -480,7 +475,7 @@ class SSHKeyDeleteView(LoginRequiredMixin, DeleteView): def delete(self, request, *args, **kwargs): owner = self.request.user manager = OpenNebulaManager( - email=owner.username, + email=owner.email, password=owner.password ) pk = self.kwargs.get('pk') @@ -534,7 +529,7 @@ class SSHKeyChoiceView(LoginRequiredMixin, View): ssh_key.private_key.save(filename, content) owner = self.request.user manager = OpenNebulaManager( - email=owner.username, + email=owner.email, password=owner.password ) keys = get_all_public_keys(request.user) @@ -565,11 +560,9 @@ class SettingsView(LoginRequiredMixin, FormView): 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 @@ -580,155 +573,108 @@ 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(): if 'billing-form' in request.POST: - current_billing_address = self.request.user.billing_addresses.last() billing_address_data = form.cleaned_data billing_address_data.update({ 'user': self.request.user.id }) billing_address_user_form = UserBillingAddressForm( - instance=self.request.user.billing_addresses.order_by('-id').first(), + instance=self.request.user.billing_addresses.first(), data=billing_address_data) - billing_address = billing_address_user_form.save() - billing_address.stripe_tax_id = '' - billing_address.vat_number_validated_on = None - billing_address.vat_validation_status = '' - billing_address.save() - vat_number = billing_address_user_form.cleaned_data.get( - 'vat_number').strip() - logger.debug("Vat number = %s" % vat_number) - if vat_number: - try: - stripe_customer = request.user.stripecustomer - except StripeCustomer.DoesNotExist as dne: - logger.debug( - "User %s does not have a stripecustomer. " - "Creating one." % request.user.email) - stripe_customer = StripeCustomer.get_or_create( - email=request.user.email, - token=None) - request.user.stripecustomer = stripe_customer - request.user.save() - validate_result = validate_vat_number( - stripe_customer_id=request.user.stripecustomer.stripe_id, - billing_address_id=billing_address.id, - is_user_ba=True - ) - logger.debug("validate_result = %s" % str(validate_result)) - if 'error' in validate_result and validate_result['error']: - messages.add_message( - request, messages.ERROR, - "VAT Number validation error: %s" % validate_result["error"], - extra_tags='error' - ) - billing_address = current_billing_address - if billing_address: - billing_address.save() - email_data = { - 'subject': "%s updated VAT number to %s but failed" % - (request.user.email, vat_number), - 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, - 'to': settings.DCL_ERROR_EMAILS_TO_LIST, - 'body': "\n".join( - ["%s=%s" % (k, v) for (k, v) in - validate_result.items()]), - } - else: - email_data = { - 'subject': "%s updated VAT number to %s" % ( - request.user.email, vat_number - ), - 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, - 'to': settings.DCL_ERROR_EMAILS_TO_LIST, - 'body': "\n".join( - ["%s=%s" % (k, v) for (k, v) in - validate_result.items()]), - } - msg = _("Billing address updated successfully") - messages.add_message(request, messages.SUCCESS, msg) - send_plain_email_task.delay(email_data) - else: - msg = _("Billing address updated successfully") - messages.add_message(request, messages.SUCCESS, msg) + billing_address_user_form.save() + 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 @@ -741,7 +687,7 @@ class PaymentVMView(LoginRequiredMixin, FormView): form_class = BillingAddressForm def get_form_kwargs(self): - current_billing_address = self.request.user.billing_addresses.last() + current_billing_address = self.request.user.billing_addresses.first() form_kwargs = super(PaymentVMView, self).get_form_kwargs() if not current_billing_address: return form_kwargs @@ -753,7 +699,6 @@ class PaymentVMView(LoginRequiredMixin, FormView): 'city': current_billing_address.city, 'postal_code': current_billing_address.postal_code, 'country': current_billing_address.country, - 'vat_number': current_billing_address.vat_number } }) return form_kwargs @@ -944,7 +889,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): except VMDetail.DoesNotExist: try: manager = OpenNebulaManager( - email=owner.username, password=owner.password + email=owner.email, password=owner.password ) vm = manager.get_vm(obj.vm_id) context['vm'] = VirtualMachineSerializer(vm).data @@ -1069,13 +1014,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'] ) @@ -1092,7 +1031,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: @@ -1133,8 +1072,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): cpu = specs.get('cpu') memory = specs.get('memory') disk_size = specs.get('disk_size') - amount_to_be_charged = specs.get('price') - discount = specs.get('discount') + amount_to_be_charged = specs.get('total_price') plan_name = StripeUtils.get_stripe_plan_name( cpu=cpu, memory=memory, @@ -1153,64 +1091,17 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): amount=amount_to_be_charged, name=plan_name, stripe_plan_id=stripe_plan_id) - # Create StripeTaxRate if applicable to the user - stripe_tax_rate = None - if specs["vat_percent"] > 0: - try: - stripe_tax_rate = StripeTaxRate.objects.get( - description="VAT for %s" % specs["vat_country"] - ) - print("Stripe Tax Rate exists") - except StripeTaxRate.DoesNotExist as dne: - print("StripeTaxRate does not exist") - tax_rate_obj = stripe.TaxRate.create( - display_name="VAT", - description="VAT for %s" % specs["vat_country"], - jurisdiction=specs["vat_country"], - percentage=specs["vat_percent"] * 100, - inclusive=False, - ) - stripe_tax_rate = StripeTaxRate.objects.create( - display_name=tax_rate_obj.display_name, - description=tax_rate_obj.description, - jurisdiction=tax_rate_obj.jurisdiction, - percentage=tax_rate_obj.percentage, - inclusive=False, - tax_rate_id=tax_rate_obj.id - ) - logger.debug("Created StripeTaxRate %s" % - stripe_tax_rate.tax_rate_id) subscription_result = stripe_utils.subscribe_customer_to_plan( stripe_api_cus_id, - [{"plan": stripe_plan.get('response_object').stripe_plan_id}], - coupon=(discount['stripe_coupon_id'] - if 'name' in discount and - discount['name'] is not None and - 'ipv6' in discount['name'].lower() and - discount['stripe_coupon_id'] - else ""), - tax_rates=[stripe_tax_rate.tax_rate_id] if stripe_tax_rate else [], - default_payment_method=request.session['id_payment_method'] - ) + [{"plan": stripe_plan.get( + 'response_object').stripe_plan_id}], + int(datetime.now().timestamp()) + 300 if settings.ADD_TRIAL_PERIOD_TO_SUBSCRIPTION else None) 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'): + (stripe_subscription_obj.status != 'active' and + stripe_subscription_obj.status != 'trialing') + ): # At this point, we have created a Stripe API card and # associated it with the customer; but the transaction failed # due to some reason. So, we would want to dissociate this card @@ -1246,7 +1137,6 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView): user = { 'name': self.request.user.name, 'email': self.request.user.email, - 'username': self.request.user.username, 'pass': self.request.user.password, 'request_scheme': request.scheme, 'request_host': request.get_host(), @@ -1290,7 +1180,7 @@ class OrdersHostingListView(LoginRequiredMixin, ListView): return super(OrdersHostingListView, self).get(request, *args, **kwargs) -class InvoiceListView(LoginRequiredMixin, TemplateView): +class InvoiceListView(LoginRequiredMixin, ListView): template_name = "hosting/invoices.html" login_url = reverse_lazy('hosting:login') context_object_name = "invoices" @@ -1298,14 +1188,10 @@ class InvoiceListView(LoginRequiredMixin, TemplateView): ordering = '-created' def get_context_data(self, **kwargs): - page = self.request.GET.get('page', 1) context = super(InvoiceListView, self).get_context_data(**kwargs) - invs_page = None - 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) logger.debug( "user_email = {}".format(user_email) ) @@ -1314,67 +1200,54 @@ class InvoiceListView(LoginRequiredMixin, TemplateView): except CustomUser.DoesNotExist as dne: logger.debug("User does not exist") cu = self.request.user - invs = stripe.Invoice.list(customer=cu.stripecustomer.stripe_id, - count=100, - status='paid') - paginator = Paginator(invs.data, 10) - try: - invs_page = paginator.page(page) - except PageNotAnInteger: - invs_page = paginator.page(1) - except EmptyPage: - invs_page = paginator.page(paginator.num_pages) - hosting_orders = HostingOrder.objects.filter( - customer=cu.stripecustomer).filter( - Q(subscription_id=None) | Q(subscription_id='') - ).order_by('-created_at') - stripe_chgs = [] - for ho in hosting_orders: - stripe_chgs.append({ho.id: stripe.Charge.retrieve(ho.stripe_charge_id)}) - - 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) + mhbs = MonthlyHostingBill.objects.filter(customer__user=cu) else: + mhbs = MonthlyHostingBill.objects.filter( + customer__user=self.request.user + ) + ips_dict = {} + line_item_period_dict = {} + for mhb in mhbs: try: - invs = stripe.Invoice.list( - customer=self.request.user.stripecustomer.stripe_id, - count=100 - ) - paginator = Paginator(invs.data, 10) - try: - invs_page = paginator.page(page) - except PageNotAnInteger: - invs_page = paginator.page(1) - except EmptyPage: - invs_page = paginator.page(paginator.num_pages) - 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 + vm_detail = VMDetail.objects.get(vm_id=mhb.order.vm_id) + ips_dict[mhb.invoice_number] = [vm_detail.ipv6, vm_detail.ipv4] + all_line_items = HostingBillLineItem.objects.filter(monthly_hosting_bill=mhb) + for line_item in all_line_items: + if line_item.get_item_detail_str() != "": + line_item_period_dict[mhb.invoice_number] = { + "period_start": line_item.period_start, + "period_end": line_item.period_end + } + break + except VMDetail.DoesNotExist as dne: + ips_dict[mhb.invoice_number] = ['--'] + logger.debug("VMDetail for {} doesn't exist".format( + mhb.order.vm_id + )) + context['ips'] = ips_dict + context['period'] = line_item_period_dict return context + def get_queryset(self): + user = self.request.user + if ('user_email' in self.request.GET + and self.request.user.email == settings.ADMIN_EMAIL): + user_email = self.request.GET['user_email'] + logger.debug( + "user_email = {}".format(user_email) + ) + try: + cu = CustomUser.objects.get(email=user_email) + except CustomUser.DoesNotExist as dne: + logger.debug("User does not exist") + cu = self.request.user + self.queryset = MonthlyHostingBill.objects.filter(customer__user=cu) + else: + self.queryset = MonthlyHostingBill.objects.filter( + customer__user=self.request.user + ) + return super(InvoiceListView, self).get_queryset() + @method_decorator(decorators) def get(self, request, *args, **kwargs): return super(InvoiceListView, self).get(request, *args, **kwargs) @@ -1389,6 +1262,7 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView): def get_object(self, queryset=None): invoice_id = self.kwargs.get('invoice_id') + logger.debug("Getting invoice for %s" % invoice_id) try: invoice_obj = MonthlyHostingBill.objects.get( invoice_number=invoice_id @@ -1417,47 +1291,22 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView): if obj is not None: vm_id = obj.get_vm_id() - try: - # Try to get vm details from database - vm_detail = VMDetail.objects.get(vm_id=vm_id) - context['vm'] = vm_detail.__dict__ - context['vm']['name'] = '{}-{}'.format( - context['vm']['configuration'], context['vm']['vm_id']) - user_vat_country = obj.order.billing_address.country - user_country_vat_rate = get_vat_rate_for_country( - user_vat_country) - price, vat, vat_percent, discount = get_vm_price_for_given_vat( - cpu=context['vm']['cores'], - ssd_size=context['vm']['disk_size'], - memory=context['vm']['memory'], - pricing_name=(obj.order.vm_pricing.name - if obj.order.vm_pricing else 'default'), - vat_rate=( - user_country_vat_rate * 100 - if obj.order.vm_id >= settings.FIRST_VM_ID_AFTER_EU_VAT - else settings.PRE_EU_VAT_RATE - ) - ) - context['vm']["after_eu_vat_intro"] = ( - True if obj.order.vm_id >= settings.FIRST_VM_ID_AFTER_EU_VAT - else False - ) - context['vm']["price"] = price - context['vm']["vat"] = vat - context['vm']["vat_percent"] = vat_percent - context['vm']["vat_country"] = user_vat_country - context['vm']["discount"] = discount - context['vm']["total_price"] = round( - price + vat - discount['amount'], 2) - except VMDetail.DoesNotExist: - # fallback to get it from the infrastructure + if vm_id is None: + # We did not find it in the metadata, fallback to order + if obj.order is not None: + vm_id = obj.order.vm_id + logger.debug("VM ID from order is %s" % vm_id) + else: + logger.debug("VM order is None. So, we don't have VM_ID") + else: + logger.debug("VM ID was set in metadata") + if vm_id > 0: try: - manager = OpenNebulaManager( - email=self.request.user.username, - password=self.request.user.password - ) - vm = manager.get_vm(vm_id) - context['vm'] = VirtualMachineSerializer(vm).data + # Try to get vm details from database + vm_detail = VMDetail.objects.get(vm_id=vm_id) + context['vm'] = vm_detail.__dict__ + context['vm']['name'] = '{}-{}'.format( + context['vm']['configuration'], context['vm']['vm_id']) user_vat_country = obj.order.billing_address.country user_country_vat_rate = get_vat_rate_for_country( user_vat_country) @@ -1484,21 +1333,58 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView): context['vm']["discount"] = discount context['vm']["total_price"] = round( price + vat - discount['amount'], 2) - except TypeError: - logger.error("Type error. Probably we " - "came from a generic product. " - "Invoice ID %s" % obj.invoice_id) - except WrongIdError: - logger.error("WrongIdError while accessing " - "invoice {}".format(obj.invoice_id)) - messages.error( - self.request, - _('The VM you are looking for is unavailable at the ' - 'moment. Please contact Data Center Light support.') - ) - self.kwargs['error'] = 'WrongIdError' - context['error'] = 'WrongIdError' - return context + except VMDetail.DoesNotExist: + # fallback to get it from the infrastructure + try: + manager = OpenNebulaManager( + email=self.request.user.email, + password=self.request.user.password + ) + vm = manager.get_vm(vm_id) + context['vm'] = VirtualMachineSerializer(vm).data + user_vat_country = obj.order.billing_address.country + user_country_vat_rate = get_vat_rate_for_country( + user_vat_country) + price, vat, vat_percent, discount = get_vm_price_for_given_vat( + cpu=context['vm']['cores'], + ssd_size=context['vm']['disk_size'], + memory=context['vm']['memory'], + pricing_name=(obj.order.vm_pricing.name + if obj.order.vm_pricing else 'default'), + vat_rate=( + user_country_vat_rate * 100 + if obj.order.vm_id >= settings.FIRST_VM_ID_AFTER_EU_VAT + else settings.PRE_EU_VAT_RATE + ) + ) + context['vm']["after_eu_vat_intro"] = ( + True if obj.order.vm_id >= settings.FIRST_VM_ID_AFTER_EU_VAT + else False + ) + context['vm']["price"] = price + context['vm']["vat"] = vat + context['vm']["vat_percent"] = vat_percent + context['vm']["vat_country"] = user_vat_country + context['vm']["discount"] = discount + context['vm']["total_price"] = round( + price + vat - discount['amount'], 2) + except TypeError: + logger.error("Type error. Probably we " + "came from a generic product. " + "Invoice ID %s" % obj.invoice_id) + except WrongIdError: + logger.error("WrongIdError while accessing " + "invoice {}".format(obj.invoice_id)) + messages.error( + self.request, + _('The VM you are looking for is unavailable at the ' + 'moment. Please contact Data Center Light support.') + ) + self.kwargs['error'] = 'WrongIdError' + context['error'] = 'WrongIdError' + return context + else: + logger.debug("No VM_ID. So, no details available.") # add context params from monthly hosting bill context['period_start'] = obj.get_period_start() @@ -1536,7 +1422,7 @@ class VirtualMachinesPlanListView(LoginRequiredMixin, ListView): def get_queryset(self): owner = self.request.user - manager = OpenNebulaManager(email=owner.username, + manager = OpenNebulaManager(email=owner.email, password=owner.password) try: queryset = manager.get_vms() @@ -1697,7 +1583,7 @@ class VirtualMachineView(LoginRequiredMixin, View): owner = self.request.user vm = None manager = OpenNebulaManager( - email=owner.username, + email=owner.email, password=owner.password ) vm_id = self.kwargs.get('pk') @@ -1741,28 +1627,26 @@ class VirtualMachineView(LoginRequiredMixin, View): context = None try: serializer = VirtualMachineSerializer(vm) - hosting_order = HostingOrder.objects.get( - vm_id=serializer.data['vm_id'] - ) - inv_url = None - if hosting_order.subscription_id: - stripe_obj = stripe.Invoice.list( - subscription=hosting_order.subscription_id, - count=1 - ) - 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 - ) - inv_url = stripe_obj.receipt_url context = { 'virtual_machine': serializer.data, - 'order': hosting_order, + 'order': HostingOrder.objects.get( + vm_id=serializer.data['vm_id'] + ), 'keys': UserHostingKey.objects.filter(user=request.user), - 'inv_url': inv_url + 'has_invoices': False } - + try: + bills = [] + if hasattr(self.request.user, 'stripecustomer'): + bills = MonthlyHostingBill.objects.filter( + customer=self.request.user.stripecustomer + ) + if len(bills) > 0: + context['has_invoices'] = True + except MonthlyHostingBill.DoesNotExist as dne: + logger.error("{}'s monthly hosting bill not imported ?".format( + self.request.user.email + )) except Exception as ex: logger.debug("Exception generated {}".format(str(ex))) messages.error(self.request, @@ -1781,7 +1665,7 @@ class VirtualMachineView(LoginRequiredMixin, View): vm = self.get_object() manager = OpenNebulaManager( - email=owner.username, + email=owner.email, password=owner.password ) try: @@ -1794,7 +1678,6 @@ class VirtualMachineView(LoginRequiredMixin, View): # Cancel Stripe subscription stripe_utils = StripeUtils() hosting_order = None - stripe_subscription_obj = None try: hosting_order = HostingOrder.objects.get( vm_id=vm.id @@ -1809,7 +1692,7 @@ class VirtualMachineView(LoginRequiredMixin, View): error_msg = result.get('error') logger.error( 'Error canceling subscription for {user} and vm id ' - '{vm_id}'.format(user=owner.username, vm_id=vm.id) + '{vm_id}'.format(user=owner.email, vm_id=vm.id) ) logger.error(error_msg) admin_email_body['stripe_error_msg'] = error_msg @@ -1831,7 +1714,7 @@ class VirtualMachineView(LoginRequiredMixin, View): ) response['text'] = str(_('Error terminating VM')) + str(vm.id) else: - for t in range(settings.MAX_TIME_TO_WAIT_FOR_VM_TERMINATE): + for t in range(15): try: manager.get_vm(vm.id) except WrongIdError: @@ -1854,10 +1737,6 @@ class VirtualMachineView(LoginRequiredMixin, View): ) break else: - logger.debug( - 'Sleeping 2 seconds for terminate action on VM %s' % - vm.id - ) sleep(2) if not response['status']: response['text'] = str(_("VM terminate action timed out. " @@ -1885,33 +1764,19 @@ class VirtualMachineView(LoginRequiredMixin, View): email.send() admin_email_body.update(response) admin_email_body["customer_email"] = owner.email - admin_email_body["customer_username"] = owner.username admin_email_body["VM_ID"] = vm.id admin_email_body["VM_created_at"] = (str(hosting_order.created_at) if hosting_order is not None else "unknown") - content = "" - total_amount = 0 - if stripe_subscription_obj: - for line_item in stripe_subscription_obj["items"]["data"]: - total_amount += (line_item["quantity"] * - line_item.plan["amount"]) - content += " %s => %s x %s => %s\n" % ( - line_item.plan["name"], line_item["quantity"], - line_item.plan["amount"]/100, - (line_item["quantity"] * line_item.plan["amount"])/100 - ) - admin_email_body["subscription_amount"] = total_amount/100 - admin_email_body["subscription_detail"] = content - admin_msg_sub = "VM and Subscription for VM {} and user: {}, {}".format( + admin_msg_sub = "VM and Subscription for VM {} and user: {}".format( vm.id, - owner.email, owner.username + owner.email ) email_to_admin_data = { '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()]), } @@ -1951,7 +1816,7 @@ class HostingBillDetailView(PermissionRequiredMixin, LoginRequiredMixin, context = super(DetailView, self).get_context_data(**kwargs) owner = self.request.user - manager = OpenNebulaManager(email=owner.username, + manager = OpenNebulaManager(email=owner.email, password=owner.password) # Get vms queryset = manager.get_vms() diff --git a/membership/models.py b/membership/models.py index 81054fb9..80aaf408 100644 --- a/membership/models.py +++ b/membership/models.py @@ -233,17 +233,8 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): ldap_manager.create_user(self.username, password=password, firstname=first_name, lastname=last_name, email=self.email) - else: - # User exists already in LDAP, but with a dummy credential - # We are here implies that the user has successfully - # authenticated against Django db, and a corresponding user - # exists in LDAP. - # We just update the LDAP credentials once again, assuming it - # was set to a dummy value while migrating users from Django to - # LDAP - ldap_manager.change_password(self.username, password) - self.in_ldap = True - self.save() + self.in_ldap = True + self.save() def __str__(self): # __unicode__ on Python 2 return self.email @@ -277,7 +268,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 +279,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 +287,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..19e3e4f7 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: @@ -429,12 +427,8 @@ class OpenNebulaManager(): template_id = int(template_id) try: template_pool = self._get_template_pool() - if template_id in settings.UPDATED_TEMPLATES_DICT.keys(): - template_id = settings.UPDATED_TEMPLATES_DICT[template_id] return template_pool.get_by_id(template_id) - except Exception as ex: - logger.debug("Template Id we are looking for : %s" % template_id) - logger.error(str(ex)) + except: raise ConnectionRefusedError def create_template(self, name, cores, memory, disk_size, core_price, diff --git a/opennebula_api/serializers.py b/opennebula_api/serializers.py index 34cdde7c..c7418aa5 100644 --- a/opennebula_api/serializers.py +++ b/opennebula_api/serializers.py @@ -86,7 +86,7 @@ class VirtualMachineSerializer(serializers.Serializer): } try: - manager = OpenNebulaManager(email=owner.username, + manager = OpenNebulaManager(email=owner.email, password=owner.password, ) opennebula_id = manager.create_vm(template_id=template_id, diff --git a/opennebula_api/views.py b/opennebula_api/views.py index 318fa32e..9bf03a74 100644 --- a/opennebula_api/views.py +++ b/opennebula_api/views.py @@ -19,7 +19,7 @@ class VmCreateView(generics.ListCreateAPIView): def get_queryset(self): owner = self.request.user - manager = OpenNebulaManager(email=owner.username, + manager = OpenNebulaManager(email=owner.email, password=owner.password) # We may have ConnectionRefusedError if we don't have a # connection to OpenNebula. For now, we raise ServiceUnavailable @@ -42,7 +42,7 @@ class VmDetailsView(generics.RetrieveUpdateDestroyAPIView): def get_queryset(self): owner = self.request.user - manager = OpenNebulaManager(email=owner.username, + manager = OpenNebulaManager(email=owner.email, password=owner.password) # We may have ConnectionRefusedError if we don't have a # connection to OpenNebula. For now, we raise ServiceUnavailable @@ -54,7 +54,7 @@ class VmDetailsView(generics.RetrieveUpdateDestroyAPIView): def get_object(self): owner = self.request.user - manager = OpenNebulaManager(email=owner.username, + manager = OpenNebulaManager(email=owner.email, password=owner.password) # We may have ConnectionRefusedError if we don't have a # connection to OpenNebula. For now, we raise ServiceUnavailable @@ -66,7 +66,7 @@ class VmDetailsView(generics.RetrieveUpdateDestroyAPIView): def perform_destroy(self, instance): owner = self.request.user - manager = OpenNebulaManager(email=owner.username, + manager = OpenNebulaManager(email=owner.email, password=owner.password) # We may have ConnectionRefusedError if we don't have a # connection to OpenNebula. For now, we raise ServiceUnavailable 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 8d04a189..874fc1a4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,7 +25,7 @@ django-compressor==2.0 django-debug-toolbar==1.4 python-dotenv==0.10.3 django-extensions==1.6.7 -django-filer==2.1.2 +django-filer==1.2.0 django-filter==0.13.0 django-formtools==1.0 django-guardian==1.4.4 @@ -79,11 +79,11 @@ requests==2.10.0 rjsmin==1.0.12 six==1.10.0 sqlparse==0.1.19 -stripe==2.41.0 +stripe==2.24.1 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/utils/forms.py b/utils/forms.py index f35c90f4..fdc67d26 100644 --- a/utils/forms.py +++ b/utils/forms.py @@ -124,14 +124,13 @@ class BillingAddressForm(forms.ModelForm): class Meta: model = BillingAddress fields = ['cardholder_name', 'street_address', - 'city', 'postal_code', 'country', 'vat_number'] + 'city', 'postal_code', 'country'] labels = { 'cardholder_name': _('Cardholder Name'), 'street_address': _('Street Address'), 'city': _('City'), 'postal_code': _('Postal Code'), 'Country': _('Country'), - 'VAT Number': _('VAT Number') } @@ -143,7 +142,7 @@ class BillingAddressFormSignup(BillingAddressForm): class Meta: model = BillingAddress fields = ['name', 'email', 'cardholder_name', 'street_address', - 'city', 'postal_code', 'country', 'vat_number'] + 'city', 'postal_code', 'country'] labels = { 'name': 'Name', 'email': _('Email'), @@ -152,7 +151,6 @@ class BillingAddressFormSignup(BillingAddressForm): 'city': _('City'), 'postal_code': _('Postal Code'), 'Country': _('Country'), - 'vat_number': _('VAT Number') } def clean_email(self): @@ -175,14 +173,13 @@ class UserBillingAddressForm(forms.ModelForm): class Meta: model = UserBillingAddress fields = ['cardholder_name', 'street_address', - 'city', 'postal_code', 'country', 'user', 'vat_number'] + 'city', 'postal_code', 'country', 'user'] labels = { 'cardholder_name': _('Cardholder Name'), 'street_address': _('Street Building'), 'city': _('City'), 'postal_code': _('Postal Code'), 'Country': _('Country'), - 'vat_number': _('VAT Number'), } diff --git a/utils/hosting_utils.py b/utils/hosting_utils.py index b9e2eb8a..73e2c035 100644 --- a/utils/hosting_utils.py +++ b/utils/hosting_utils.py @@ -1,10 +1,7 @@ import decimal import logging -import math import subprocess -from django.conf import settings - from oca.pool import WrongIdError from datacenterlight.models import VMPricing @@ -81,8 +78,7 @@ def get_vm_price(cpu, memory, disk_size, hdd_size=0, pricing_name='default'): price = ((decimal.Decimal(cpu) * pricing.cores_unit_price) + (decimal.Decimal(memory) * pricing.ram_unit_price) + (decimal.Decimal(disk_size) * pricing.ssd_unit_price) + - (decimal.Decimal(hdd_size) * pricing.hdd_unit_price) + - decimal.Decimal(settings.VM_BASE_PRICE)) + (decimal.Decimal(hdd_size) * pricing.hdd_unit_price)) cents = decimal.Decimal('.01') price = price.quantize(cents, decimal.ROUND_HALF_UP) return round(float(price), 2) @@ -105,25 +101,18 @@ def get_vm_price_for_given_vat(cpu, memory, ssd_size, hdd_size=0, (decimal.Decimal(cpu) * pricing.cores_unit_price) + (decimal.Decimal(memory) * pricing.ram_unit_price) + (decimal.Decimal(ssd_size) * pricing.ssd_unit_price) + - (decimal.Decimal(hdd_size) * pricing.hdd_unit_price) + - decimal.Decimal(settings.VM_BASE_PRICE) + (decimal.Decimal(hdd_size) * pricing.hdd_unit_price) ) - discount_name = pricing.discount_name - discount_amount = round(float(pricing.discount_amount), 2) vat = price * decimal.Decimal(vat_rate) * decimal.Decimal(0.01) vat_percent = vat_rate cents = decimal.Decimal('.01') price = price.quantize(cents, decimal.ROUND_HALF_UP) vat = vat.quantize(cents, decimal.ROUND_HALF_UP) - discount_amount_with_vat = decimal.Decimal(discount_amount) * (1 + decimal.Decimal(vat_rate) * decimal.Decimal(0.01)) - discount_amount_with_vat = discount_amount_with_vat.quantize(cents, decimal.ROUND_HALF_UP) discount = { - 'name': discount_name, - 'amount': discount_amount, - 'amount_with_vat': round(float(discount_amount_with_vat), 2), - 'stripe_coupon_id': pricing.stripe_coupon_id + 'name': pricing.discount_name, + 'amount': round(float(pricing.discount_amount), 2) } return (round(float(price), 2), round(float(vat), 2), round(float(vat_percent), 2), discount) @@ -159,8 +148,7 @@ def get_vm_price_with_vat(cpu, memory, ssd_size, hdd_size=0, (decimal.Decimal(cpu) * pricing.cores_unit_price) + (decimal.Decimal(memory) * pricing.ram_unit_price) + (decimal.Decimal(ssd_size) * pricing.ssd_unit_price) + - (decimal.Decimal(hdd_size) * pricing.hdd_unit_price) + - decimal.Decimal(settings.VM_BASE_PRICE) + (decimal.Decimal(hdd_size) * pricing.hdd_unit_price) ) if pricing.vat_inclusive: vat = decimal.Decimal(0) @@ -174,8 +162,7 @@ def get_vm_price_with_vat(cpu, memory, ssd_size, hdd_size=0, vat = vat.quantize(cents, decimal.ROUND_HALF_UP) discount = { 'name': pricing.discount_name, - 'amount': round(float(pricing.discount_amount), 2), - 'stripe_coupon_id': pricing.stripe_coupon_id + 'amount': round(float(pricing.discount_amount), 2) } return (round(float(price), 2), round(float(vat), 2), round(float(vat_percent), 2), discount) @@ -212,16 +199,6 @@ def get_vat_rate_for_country(country): return 0 -def get_ip_addresses(vm_id): - try: - vm_detail = VMDetail.objects.get(vm_id=vm_id) - return "%s
%s" % (vm_detail.ipv6, vm_detail.ipv4) - except VMDetail.DoesNotExist as dne: - logger.error(str(dne)) - logger.error("VMDetail for %s does not exist" % vm_id) - return "--" - - class HostingUtils: @staticmethod def clear_items_from_list(from_list, items_list): diff --git a/utils/ldap_manager.py b/utils/ldap_manager.py index d40e931f..fd039ad5 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 @@ -88,7 +87,7 @@ class LdapManager: logger.debug("{uid} does not exist. Using it".format(uid=uidNumber)) self._set_max_uid(uidNumber) try: - uid = user + uid = user.encode("utf-8") conn.add("uid={uid},{customer_dn}".format( uid=uid, customer_dn=settings.LDAP_CUSTOMER_DN ), @@ -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/migrations/0007_auto_20191226_0610.py b/utils/migrations/0007_auto_20191226_0610.py deleted file mode 100644 index c57c6431..00000000 --- a/utils/migrations/0007_auto_20191226_0610.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.4 on 2019-12-26 06:10 -from __future__ import unicode_literals - -from django.db import migrations, models -import utils.fields - - -class Migration(migrations.Migration): - - dependencies = [ - ('utils', '0006_auto_20170810_1742'), - ] - - operations = [ - migrations.AddField( - model_name='billingaddress', - name='stripe_tax_id', - field=models.CharField(blank=True, default='', max_length=100), - ), - migrations.AddField( - model_name='billingaddress', - name='vat_number', - field=models.CharField(blank=True, default='', max_length=100), - ), - migrations.AddField( - model_name='billingaddress', - name='vat_number_validated_on', - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AddField( - model_name='userbillingaddress', - name='stripe_tax_id', - field=models.CharField(blank=True, default='', max_length=100), - ), - migrations.AddField( - model_name='userbillingaddress', - name='vat_number', - field=models.CharField(blank=True, default='', max_length=100), - ), - migrations.AddField( - model_name='userbillingaddress', - name='vat_number_validated_on', - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AlterField( - model_name='billingaddress', - name='country', - field=utils.fields.CountryField(choices=[('AD', 'Andorra'), ('AE', 'United Arab Emirates'), ('AF', 'Afghanistan'), ('AG', 'Antigua & Barbuda'), ('AI', 'Anguilla'), ('AL', 'Albania'), ('AM', 'Armenia'), ('AN', 'Netherlands Antilles'), ('AO', 'Angola'), ('AQ', 'Antarctica'), ('AR', 'Argentina'), ('AS', 'American Samoa'), ('AT', 'Austria'), ('AU', 'Australia'), ('AW', 'Aruba'), ('AZ', 'Azerbaijan'), ('BA', 'Bosnia and Herzegovina'), ('BB', 'Barbados'), ('BD', 'Bangladesh'), ('BE', 'Belgium'), ('BF', 'Burkina Faso'), ('BG', 'Bulgaria'), ('BH', 'Bahrain'), ('BI', 'Burundi'), ('BJ', 'Benin'), ('BM', 'Bermuda'), ('BN', 'Brunei Darussalam'), ('BO', 'Bolivia'), ('BR', 'Brazil'), ('BS', 'Bahama'), ('BT', 'Bhutan'), ('BV', 'Bouvet Island'), ('BW', 'Botswana'), ('BY', 'Belarus'), ('BZ', 'Belize'), ('CA', 'Canada'), ('CC', 'Cocos (Keeling) Islands'), ('CF', 'Central African Republic'), ('CG', 'Congo'), ('CH', 'Switzerland'), ('CI', 'Ivory Coast'), ('CK', 'Cook Iislands'), ('CL', 'Chile'), ('CM', 'Cameroon'), ('CN', 'China'), ('CO', 'Colombia'), ('CR', 'Costa Rica'), ('CU', 'Cuba'), ('CV', 'Cape Verde'), ('CX', 'Christmas Island'), ('CY', 'Cyprus'), ('CZ', 'Czech Republic'), ('DE', 'Germany'), ('DJ', 'Djibouti'), ('DK', 'Denmark'), ('DM', 'Dominica'), ('DO', 'Dominican Republic'), ('DZ', 'Algeria'), ('EC', 'Ecuador'), ('EE', 'Estonia'), ('EG', 'Egypt'), ('EH', 'Western Sahara'), ('ER', 'Eritrea'), ('ES', 'Spain'), ('ET', 'Ethiopia'), ('FI', 'Finland'), ('FJ', 'Fiji'), ('FK', 'Falkland Islands (Malvinas)'), ('FM', 'Micronesia'), ('FO', 'Faroe Islands'), ('FR', 'France'), ('FX', 'France, Metropolitan'), ('GA', 'Gabon'), ('GB', 'United Kingdom (Great Britain)'), ('GD', 'Grenada'), ('GE', 'Georgia'), ('GF', 'French Guiana'), ('GH', 'Ghana'), ('GI', 'Gibraltar'), ('GL', 'Greenland'), ('GM', 'Gambia'), ('GN', 'Guinea'), ('GP', 'Guadeloupe'), ('GQ', 'Equatorial Guinea'), ('GR', 'Greece'), ('GS', 'South Georgia and the South Sandwich Islands'), ('GT', 'Guatemala'), ('GU', 'Guam'), ('GW', 'Guinea-Bissau'), ('GY', 'Guyana'), ('HK', 'Hong Kong'), ('HM', 'Heard & McDonald Islands'), ('HN', 'Honduras'), ('HR', 'Croatia'), ('HT', 'Haiti'), ('HU', 'Hungary'), ('ID', 'Indonesia'), ('IE', 'Ireland'), ('IL', 'Israel'), ('IN', 'India'), ('IO', 'British Indian Ocean Territory'), ('IQ', 'Iraq'), ('IR', 'Islamic Republic of Iran'), ('IS', 'Iceland'), ('IT', 'Italy'), ('JM', 'Jamaica'), ('JO', 'Jordan'), ('JP', 'Japan'), ('KE', 'Kenya'), ('KG', 'Kyrgyzstan'), ('KH', 'Cambodia'), ('KI', 'Kiribati'), ('KM', 'Comoros'), ('KN', 'St. Kitts and Nevis'), ('KP', "Korea, Democratic People's Republic of"), ('KR', 'Korea, Republic of'), ('KW', 'Kuwait'), ('KY', 'Cayman Islands'), ('KZ', 'Kazakhstan'), ('LA', "Lao People's Democratic Republic"), ('LB', 'Lebanon'), ('LC', 'Saint Lucia'), ('LI', 'Liechtenstein'), ('LK', 'Sri Lanka'), ('LR', 'Liberia'), ('LS', 'Lesotho'), ('LT', 'Lithuania'), ('LU', 'Luxembourg'), ('LV', 'Latvia'), ('LY', 'Libyan Arab Jamahiriya'), ('MA', 'Morocco'), ('MC', 'Monaco'), ('MD', 'Moldova, Republic of'), ('MG', 'Madagascar'), ('MH', 'Marshall Islands'), ('ML', 'Mali'), ('MN', 'Mongolia'), ('MM', 'Myanmar'), ('MO', 'Macau'), ('MP', 'Northern Mariana Islands'), ('MQ', 'Martinique'), ('MR', 'Mauritania'), ('MS', 'Monserrat'), ('MT', 'Malta'), ('MU', 'Mauritius'), ('MV', 'Maldives'), ('MW', 'Malawi'), ('MX', 'Mexico'), ('MY', 'Malaysia'), ('MZ', 'Mozambique'), ('NA', 'Namibia'), ('NC', 'New Caledonia'), ('NE', 'Niger'), ('NF', 'Norfolk Island'), ('NG', 'Nigeria'), ('NI', 'Nicaragua'), ('NL', 'Netherlands'), ('NO', 'Norway'), ('NP', 'Nepal'), ('NR', 'Nauru'), ('NU', 'Niue'), ('NZ', 'New Zealand'), ('OM', 'Oman'), ('PA', 'Panama'), ('PE', 'Peru'), ('PF', 'French Polynesia'), ('PG', 'Papua New Guinea'), ('PH', 'Philippines'), ('PK', 'Pakistan'), ('PL', 'Poland'), ('PM', 'St. Pierre & Miquelon'), ('PN', 'Pitcairn'), ('PR', 'Puerto Rico'), ('PT', 'Portugal'), ('PW', 'Palau'), ('PY', 'Paraguay'), ('QA', 'Qatar'), ('RE', 'Reunion'), ('RO', 'Romania'), ('RU', 'Russian Federation'), ('RW', 'Rwanda'), ('SA', 'Saudi Arabia'), ('SB', 'Solomon Islands'), ('SC', 'Seychelles'), ('SD', 'Sudan'), ('SE', 'Sweden'), ('SG', 'Singapore'), ('SH', 'St. Helena'), ('SI', 'Slovenia'), ('SJ', 'Svalbard & Jan Mayen Islands'), ('SK', 'Slovakia'), ('SL', 'Sierra Leone'), ('SM', 'San Marino'), ('SN', 'Senegal'), ('SO', 'Somalia'), ('SR', 'Suriname'), ('ST', 'Sao Tome & Principe'), ('SV', 'El Salvador'), ('SY', 'Syrian Arab Republic'), ('SZ', 'Swaziland'), ('TC', 'Turks & Caicos Islands'), ('TD', 'Chad'), ('TF', 'French Southern Territories'), ('TG', 'Togo'), ('TH', 'Thailand'), ('TJ', 'Tajikistan'), ('TK', 'Tokelau'), ('TM', 'Turkmenistan'), ('TN', 'Tunisia'), ('TO', 'Tonga'), ('TP', 'East Timor'), ('TR', 'Turkey'), ('TT', 'Trinidad & Tobago'), ('TV', 'Tuvalu'), ('TW', 'Taiwan, Province of China'), ('TZ', 'Tanzania, United Republic of'), ('UA', 'Ukraine'), ('UG', 'Uganda'), ('UM', 'United States Minor Outlying Islands'), ('US', 'United States of America'), ('UY', 'Uruguay'), ('UZ', 'Uzbekistan'), ('VA', 'Vatican City State (Holy See)'), ('VC', 'St. Vincent & the Grenadines'), ('VE', 'Venezuela'), ('VG', 'British Virgin Islands'), ('VI', 'United States Virgin Islands'), ('VN', 'Viet Nam'), ('VU', 'Vanuatu'), ('WF', 'Wallis & Futuna Islands'), ('WS', 'Samoa'), ('YE', 'Yemen'), ('YT', 'Mayotte'), ('YU', 'Yugoslavia'), ('ZA', 'South Africa'), ('ZM', 'Zambia'), ('ZR', 'Zaire'), ('ZW', 'Zimbabwe')], default='CH', max_length=2), - ), - migrations.AlterField( - model_name='userbillingaddress', - name='country', - field=utils.fields.CountryField(choices=[('AD', 'Andorra'), ('AE', 'United Arab Emirates'), ('AF', 'Afghanistan'), ('AG', 'Antigua & Barbuda'), ('AI', 'Anguilla'), ('AL', 'Albania'), ('AM', 'Armenia'), ('AN', 'Netherlands Antilles'), ('AO', 'Angola'), ('AQ', 'Antarctica'), ('AR', 'Argentina'), ('AS', 'American Samoa'), ('AT', 'Austria'), ('AU', 'Australia'), ('AW', 'Aruba'), ('AZ', 'Azerbaijan'), ('BA', 'Bosnia and Herzegovina'), ('BB', 'Barbados'), ('BD', 'Bangladesh'), ('BE', 'Belgium'), ('BF', 'Burkina Faso'), ('BG', 'Bulgaria'), ('BH', 'Bahrain'), ('BI', 'Burundi'), ('BJ', 'Benin'), ('BM', 'Bermuda'), ('BN', 'Brunei Darussalam'), ('BO', 'Bolivia'), ('BR', 'Brazil'), ('BS', 'Bahama'), ('BT', 'Bhutan'), ('BV', 'Bouvet Island'), ('BW', 'Botswana'), ('BY', 'Belarus'), ('BZ', 'Belize'), ('CA', 'Canada'), ('CC', 'Cocos (Keeling) Islands'), ('CF', 'Central African Republic'), ('CG', 'Congo'), ('CH', 'Switzerland'), ('CI', 'Ivory Coast'), ('CK', 'Cook Iislands'), ('CL', 'Chile'), ('CM', 'Cameroon'), ('CN', 'China'), ('CO', 'Colombia'), ('CR', 'Costa Rica'), ('CU', 'Cuba'), ('CV', 'Cape Verde'), ('CX', 'Christmas Island'), ('CY', 'Cyprus'), ('CZ', 'Czech Republic'), ('DE', 'Germany'), ('DJ', 'Djibouti'), ('DK', 'Denmark'), ('DM', 'Dominica'), ('DO', 'Dominican Republic'), ('DZ', 'Algeria'), ('EC', 'Ecuador'), ('EE', 'Estonia'), ('EG', 'Egypt'), ('EH', 'Western Sahara'), ('ER', 'Eritrea'), ('ES', 'Spain'), ('ET', 'Ethiopia'), ('FI', 'Finland'), ('FJ', 'Fiji'), ('FK', 'Falkland Islands (Malvinas)'), ('FM', 'Micronesia'), ('FO', 'Faroe Islands'), ('FR', 'France'), ('FX', 'France, Metropolitan'), ('GA', 'Gabon'), ('GB', 'United Kingdom (Great Britain)'), ('GD', 'Grenada'), ('GE', 'Georgia'), ('GF', 'French Guiana'), ('GH', 'Ghana'), ('GI', 'Gibraltar'), ('GL', 'Greenland'), ('GM', 'Gambia'), ('GN', 'Guinea'), ('GP', 'Guadeloupe'), ('GQ', 'Equatorial Guinea'), ('GR', 'Greece'), ('GS', 'South Georgia and the South Sandwich Islands'), ('GT', 'Guatemala'), ('GU', 'Guam'), ('GW', 'Guinea-Bissau'), ('GY', 'Guyana'), ('HK', 'Hong Kong'), ('HM', 'Heard & McDonald Islands'), ('HN', 'Honduras'), ('HR', 'Croatia'), ('HT', 'Haiti'), ('HU', 'Hungary'), ('ID', 'Indonesia'), ('IE', 'Ireland'), ('IL', 'Israel'), ('IN', 'India'), ('IO', 'British Indian Ocean Territory'), ('IQ', 'Iraq'), ('IR', 'Islamic Republic of Iran'), ('IS', 'Iceland'), ('IT', 'Italy'), ('JM', 'Jamaica'), ('JO', 'Jordan'), ('JP', 'Japan'), ('KE', 'Kenya'), ('KG', 'Kyrgyzstan'), ('KH', 'Cambodia'), ('KI', 'Kiribati'), ('KM', 'Comoros'), ('KN', 'St. Kitts and Nevis'), ('KP', "Korea, Democratic People's Republic of"), ('KR', 'Korea, Republic of'), ('KW', 'Kuwait'), ('KY', 'Cayman Islands'), ('KZ', 'Kazakhstan'), ('LA', "Lao People's Democratic Republic"), ('LB', 'Lebanon'), ('LC', 'Saint Lucia'), ('LI', 'Liechtenstein'), ('LK', 'Sri Lanka'), ('LR', 'Liberia'), ('LS', 'Lesotho'), ('LT', 'Lithuania'), ('LU', 'Luxembourg'), ('LV', 'Latvia'), ('LY', 'Libyan Arab Jamahiriya'), ('MA', 'Morocco'), ('MC', 'Monaco'), ('MD', 'Moldova, Republic of'), ('MG', 'Madagascar'), ('MH', 'Marshall Islands'), ('ML', 'Mali'), ('MN', 'Mongolia'), ('MM', 'Myanmar'), ('MO', 'Macau'), ('MP', 'Northern Mariana Islands'), ('MQ', 'Martinique'), ('MR', 'Mauritania'), ('MS', 'Monserrat'), ('MT', 'Malta'), ('MU', 'Mauritius'), ('MV', 'Maldives'), ('MW', 'Malawi'), ('MX', 'Mexico'), ('MY', 'Malaysia'), ('MZ', 'Mozambique'), ('NA', 'Namibia'), ('NC', 'New Caledonia'), ('NE', 'Niger'), ('NF', 'Norfolk Island'), ('NG', 'Nigeria'), ('NI', 'Nicaragua'), ('NL', 'Netherlands'), ('NO', 'Norway'), ('NP', 'Nepal'), ('NR', 'Nauru'), ('NU', 'Niue'), ('NZ', 'New Zealand'), ('OM', 'Oman'), ('PA', 'Panama'), ('PE', 'Peru'), ('PF', 'French Polynesia'), ('PG', 'Papua New Guinea'), ('PH', 'Philippines'), ('PK', 'Pakistan'), ('PL', 'Poland'), ('PM', 'St. Pierre & Miquelon'), ('PN', 'Pitcairn'), ('PR', 'Puerto Rico'), ('PT', 'Portugal'), ('PW', 'Palau'), ('PY', 'Paraguay'), ('QA', 'Qatar'), ('RE', 'Reunion'), ('RO', 'Romania'), ('RU', 'Russian Federation'), ('RW', 'Rwanda'), ('SA', 'Saudi Arabia'), ('SB', 'Solomon Islands'), ('SC', 'Seychelles'), ('SD', 'Sudan'), ('SE', 'Sweden'), ('SG', 'Singapore'), ('SH', 'St. Helena'), ('SI', 'Slovenia'), ('SJ', 'Svalbard & Jan Mayen Islands'), ('SK', 'Slovakia'), ('SL', 'Sierra Leone'), ('SM', 'San Marino'), ('SN', 'Senegal'), ('SO', 'Somalia'), ('SR', 'Suriname'), ('ST', 'Sao Tome & Principe'), ('SV', 'El Salvador'), ('SY', 'Syrian Arab Republic'), ('SZ', 'Swaziland'), ('TC', 'Turks & Caicos Islands'), ('TD', 'Chad'), ('TF', 'French Southern Territories'), ('TG', 'Togo'), ('TH', 'Thailand'), ('TJ', 'Tajikistan'), ('TK', 'Tokelau'), ('TM', 'Turkmenistan'), ('TN', 'Tunisia'), ('TO', 'Tonga'), ('TP', 'East Timor'), ('TR', 'Turkey'), ('TT', 'Trinidad & Tobago'), ('TV', 'Tuvalu'), ('TW', 'Taiwan, Province of China'), ('TZ', 'Tanzania, United Republic of'), ('UA', 'Ukraine'), ('UG', 'Uganda'), ('UM', 'United States Minor Outlying Islands'), ('US', 'United States of America'), ('UY', 'Uruguay'), ('UZ', 'Uzbekistan'), ('VA', 'Vatican City State (Holy See)'), ('VC', 'St. Vincent & the Grenadines'), ('VE', 'Venezuela'), ('VG', 'British Virgin Islands'), ('VI', 'United States Virgin Islands'), ('VN', 'Viet Nam'), ('VU', 'Vanuatu'), ('WF', 'Wallis & Futuna Islands'), ('WS', 'Samoa'), ('YE', 'Yemen'), ('YT', 'Mayotte'), ('YU', 'Yugoslavia'), ('ZA', 'South Africa'), ('ZM', 'Zambia'), ('ZR', 'Zaire'), ('ZW', 'Zimbabwe')], default='CH', max_length=2), - ), - ] diff --git a/utils/migrations/0008_auto_20191226_1402.py b/utils/migrations/0008_auto_20191226_1402.py deleted file mode 100644 index 2c409c0f..00000000 --- a/utils/migrations/0008_auto_20191226_1402.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.9.4 on 2019-12-26 14:02 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('utils', '0007_auto_20191226_0610'), - ] - - operations = [ - migrations.AddField( - model_name='billingaddress', - name='vat_validation_status', - field=models.CharField(blank=True, default='', max_length=25), - ), - migrations.AddField( - model_name='userbillingaddress', - name='vat_validation_status', - field=models.CharField(blank=True, default='', max_length=25), - ), - ] diff --git a/utils/models.py b/utils/models.py index 9e643707..8cd529e0 100644 --- a/utils/models.py +++ b/utils/models.py @@ -13,11 +13,6 @@ class BaseBillingAddress(models.Model): city = models.CharField(max_length=50) postal_code = models.CharField(max_length=50) country = CountryField() - vat_number = models.CharField(max_length=100, default="", blank=True) - stripe_tax_id = models.CharField(max_length=100, default="", blank=True) - vat_number_validated_on = models.DateTimeField(blank=True, null=True) - vat_validation_status = models.CharField(max_length=25, default="", - blank=True) class Meta: abstract = True @@ -25,18 +20,10 @@ class BaseBillingAddress(models.Model): class BillingAddress(BaseBillingAddress): def __str__(self): - if self.vat_number: - return "%s, %s, %s, %s, %s, %s %s %s %s" % ( - self.cardholder_name, self.street_address, self.city, - self.postal_code, self.country, self.vat_number, - self.stripe_tax_id, self.vat_number_validated_on, - self.vat_validation_status - ) - else: - return "%s, %s, %s, %s, %s" % ( - self.cardholder_name, self.street_address, self.city, - self.postal_code, self.country - ) + return "%s, %s, %s, %s, %s" % ( + self.cardholder_name, self.street_address, self.city, + self.postal_code, self.country + ) class UserBillingAddress(BaseBillingAddress): @@ -44,18 +31,10 @@ class UserBillingAddress(BaseBillingAddress): current = models.BooleanField(default=True) def __str__(self): - if self.vat_number: - return "%s, %s, %s, %s, %s, %s %s %s %s" % ( - self.cardholder_name, self.street_address, self.city, - self.postal_code, self.country, self.vat_number, - self.stripe_tax_id, self.vat_number_validated_on, - self.vat_validation_status - ) - else: - return "%s, %s, %s, %s, %s" % ( - self.cardholder_name, self.street_address, self.city, - self.postal_code, self.country - ) + return "%s, %s, %s, %s, %s" % ( + self.cardholder_name, self.street_address, self.city, + self.postal_code, self.country + ) def to_dict(self): return { @@ -64,7 +43,6 @@ class UserBillingAddress(BaseBillingAddress): 'City': self.city, 'Postal Code': self.postal_code, 'Country': self.country, - 'VAT Number': self.vat_number } diff --git a/utils/stripe_utils.py b/utils/stripe_utils.py index 875a174e..e2bdb983 100644 --- a/utils/stripe_utils.py +++ b/utils/stripe_utils.py @@ -1,9 +1,7 @@ import logging import re - import stripe from django.conf import settings - from datacenterlight.models import StripePlan stripe.api_key = settings.STRIPE_API_PRIVATE_KEY @@ -34,33 +32,32 @@ 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 except stripe.error.InvalidRequestError as e: logger.error(str(e)) - response.update({'error': str(e._message)}) + response.update({'error': "Invalid parameters"}) return response except stripe.error.AuthenticationError as e: # Authentication with Stripe's API failed # (maybe you changed API keys recently) logger.error(str(e)) - response.update({'error': str(e)}) + response.update({'error': common_message}) return response except stripe.error.APIConnectionError as e: logger.error(str(e)) - response.update({'error': str(e)}) + response.update({'error': common_message}) return response except stripe.error.StripeError as e: # maybe send email logger.error(str(e)) - response.update({'error': str(e)}) + response.update({'error': common_message}) return response except Exception as e: # maybe send email logger.error(str(e)) - response.update({'error': str(e)}) + response.update({'error': common_message}) return response return handleProblems @@ -70,7 +67,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 +80,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 +185,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 +204,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 +265,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 @@ -335,15 +297,10 @@ class StripeUtils(object): return return_value @handleStripeError - def subscribe_customer_to_plan(self, customer, plans, trial_end=None, - coupon="", tax_rates=list(), - default_payment_method=""): + def subscribe_customer_to_plan(self, customer, plans, trial_end=None): """ 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 +314,10 @@ 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 + customer=customer, items=plans, trial_end=trial_end ) - logger.debug("Done subscribing") return subscription_result @handleStripeError @@ -398,7 +348,7 @@ class StripeUtils(object): @staticmethod def get_stripe_plan_id(cpu, ram, ssd, version, app='dcl', hdd=None, - price=None, excl_vat=True): + price=None): """ Returns the Stripe plan id string of the form `dcl-v1-cpu-2-ram-5gb-ssd-10gb` based on the input parameters @@ -425,16 +375,13 @@ class StripeUtils(object): plan=dcl_plan_string ) if price is not None: - stripe_plan_id_string = '{}-{}chf'.format( + stripe_plan_id_string_with_price = '{}-{}chf'.format( stripe_plan_id_string, round(price, 2) ) - if excl_vat: - stripe_plan_id_string = '{}-{}'.format( - stripe_plan_id_string, - "excl_vat" - ) - return stripe_plan_id_string + return stripe_plan_id_string_with_price + else: + return stripe_plan_id_string @staticmethod def get_vm_config_from_stripe_id(stripe_id): @@ -463,27 +410,18 @@ class StripeUtils(object): @staticmethod - def get_stripe_plan_name(cpu, memory, disk_size, price, excl_vat=True): + def get_stripe_plan_name(cpu, memory, disk_size, price): """ Returns the Stripe plan name :return: """ - if excl_vat: - return "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD, " \ - "{price} CHF Excl. VAT".format( - cpu=cpu, - memory=memory, - disk_size=disk_size, - price=round(price, 2) - ) - else: - return "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD, " \ - "{price} CHF".format( - cpu=cpu, - memory=memory, - disk_size=disk_size, - price=round(price, 2) - ) + return "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD, " \ + "{price} CHF".format( + cpu=cpu, + memory=memory, + disk_size=disk_size, + price=round(price, 2) + ) @handleStripeError def set_subscription_meta_data(self, subscription_id, meta_data): @@ -496,78 +434,3 @@ class StripeUtils(object): subscription = stripe.Subscription.retrieve(subscription_id) subscription.metadata = meta_data subscription.save() - - @handleStripeError - def get_or_create_tax_id_for_user(self, stripe_customer_id, vat_number, - type="eu_vat", country=""): - tax_ids_list = stripe.Customer.list_tax_ids( - stripe_customer_id, - limit=100, - ) - for tax_id_obj in tax_ids_list.data: - if self.compare_vat_numbers(tax_id_obj.value, vat_number): - logger.debug("tax id obj exists already") - return tax_id_obj - else: - logger.debug( - "{val1} is not equal to {val2} or {con1} not same as " - "{con2}".format(val1=tax_id_obj.value, val2=vat_number, - con1=tax_id_obj.country.lower(), - con2=country.lower().strip())) - logger.debug( - "tax id obj does not exist for {val}. Creating a new one".format( - val=vat_number - )) - tax_id_obj = stripe.Customer.create_tax_id( - stripe_customer_id, - type=type, - value=vat_number, - ) - 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 diff --git a/utils/tests.py b/utils/tests.py index 8abbbb1d..fb499a94 100644 --- a/utils/tests.py +++ b/utils/tests.py @@ -1,5 +1,7 @@ +import datetime import uuid from time import sleep +from unittest import skipIf from unittest.mock import patch import stripe @@ -8,7 +10,6 @@ from django.conf import settings from django.http.request import HttpRequest from django.test import Client from django.test import TestCase, override_settings -from unittest import skipIf from model_mommy import mommy from datacenterlight.models import StripePlan @@ -231,7 +232,8 @@ class StripePlanTestCase(TestStripeCustomerDescription): result = self.stripe_utils.subscribe_customer_to_plan( stripe_customer.stripe_id, [{"plan": stripe_plan.get( - 'response_object').stripe_plan_id}]) + 'response_object').stripe_plan_id}], + int(datetime.datetime.now().timestamp()) + 300 if settings.ADD_TRIAL_PERIOD_TO_SUBSCRIPTION else None) self.assertIsInstance(result.get('response_object'), stripe.Subscription) self.assertIsNone(result.get('error')) @@ -247,7 +249,8 @@ class StripePlanTestCase(TestStripeCustomerDescription): result = self.stripe_utils.subscribe_customer_to_plan( stripe_customer.stripe_id, [{"plan": stripe_plan.get( - 'response_object').stripe_plan_id}]) + 'response_object').stripe_plan_id}], + int(datetime.datetime.now().timestamp()) + 300 if settings.ADD_TRIAL_PERIOD_TO_SUBSCRIPTION else None) self.assertIsNone(result.get('response_object'), None) self.assertIsNotNone(result.get('error')) diff --git a/utils/views.py b/utils/views.py index f30a349a..05d0fdc2 100644 --- a/utils/views.py +++ b/utils/views.py @@ -228,7 +228,7 @@ class SSHKeyCreateView(FormView): if self.request.user.is_authenticated(): owner = self.request.user manager = OpenNebulaManager( - email=owner.username, + email=owner.email, password=owner.password ) keys_to_save = get_all_public_keys(self.request.user) diff --git a/vat_rates.csv b/vat_rates.csv index 72870530..74422f48 100644 --- a/vat_rates.csv +++ b/vat_rates.csv @@ -319,6 +319,3 @@ IM",GBP,0.1,standard, 2019-12-17,,AD,EUR,0.045,standard,Andorra standard VAT (added manually) 2019-12-17,,TK,EUR,0.18,standard,Turkey standard VAT (added manually) 2019-12-17,,IS,EUR,0.24,standard,Iceland standard VAT (added manually) -2019-12-17,,FX,EUR,0.20,standard,France metropolitan standard VAT (added manually) -2020-01-04,,CY,EUR,0.19,standard,Cyprus standard VAT (added manually) -2019-01-04,,LI,EUR,0.077,standard,Liechtenstein standard VAT (added manually) diff --git a/webhook/admin.py b/webhook/admin.py deleted file mode 100644 index 8c38f3f3..00000000 --- a/webhook/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/webhook/apps.py b/webhook/apps.py deleted file mode 100644 index 1473609d..00000000 --- a/webhook/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class WebhookConfig(AppConfig): - name = 'webhook' diff --git a/webhook/management/commands/webhook.py b/webhook/management/commands/webhook.py index 4ceb2755..3648fb85 100644 --- a/webhook/management/commands/webhook.py +++ b/webhook/management/commands/webhook.py @@ -1,6 +1,6 @@ import logging - import stripe + from django.core.management.base import BaseCommand logger = logging.getLogger(__name__) diff --git a/webhook/migrations/__init__.py b/webhook/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/webhook/tests.py b/webhook/tests.py deleted file mode 100644 index 7ce503c2..00000000 --- a/webhook/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/webhook/views.py b/webhook/views.py index 0a96d0b6..d20b266c 100644 --- a/webhook/views.py +++ b/webhook/views.py @@ -1,48 +1,25 @@ -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 hosting.models import ( + MonthlyHostingBill, HostingBillLineItem, FailedInvoice +) from membership.models import StripeCustomer -from hosting.models import IncompleteSubscriptions, IncompletePaymentIntents - -from utils.models import BillingAddress, UserBillingAddress +from utils.mailer import BaseEmail from utils.tasks import send_plain_email_task logger = logging.getLogger(__name__) -# Things to do for webhooks feature -# 1. Uninstall old version and install a more recent version of stripe -# ``` -# source venv/bin/activate -# ./manage.py shell -# pip uninstall stripe -# pip install stripe==2.24.1 -# ``` -# 2. Create tax id updated webhook -# ``` -# ./manage.py webhook --create \ -# --webhook_endpoint https://datacenterlight.ch/en-us/webhooks/ \ -# --events_csv customer.tax_id.updated -# ``` -# -# 3. From the secret obtained in 2, setup an environment variable -# ``` -# WEBHOOK_SECRET='whsec......' -# ``` - - @require_POST @csrf_exempt -def handle_webhook(request): +def handle_invoice_webhook(request): payload = request.body event = None @@ -54,7 +31,7 @@ def handle_webhook(request): # Invalid payload return HttpResponse(status=400) event = stripe.Webhook.construct_event( - payload, sig_header, settings.WEBHOOK_SECRET + payload, sig_header, settings.INVOICE_WEBHOOK_SECRET ) except ValueError as e: # Invalid payload @@ -68,241 +45,118 @@ def handle_webhook(request): return handle_error(err_msg, err_body) # Do something with event - logger.debug("Passed signature verification") + logger.debug("Passed invoice signature verification") - if event.type == "customer.tax_id.updated": - logger.debug("Webhook Event: customer.tax_id.updated") - tax_id_obj = event.data.object - logger.debug("Tax_id %s is %s" % (tax_id_obj.id, - tax_id_obj.verification.status)) - stripe_customer = None - try: - stripe_customer = StripeCustomer.objects.get(stripe_id=tax_id_obj.customer) - except StripeCustomer.DoesNotExist as dne: - logger.debug( - "StripeCustomer %s does not exist" % tax_id_obj.customer) - if tax_id_obj.verification.status == "verified": - b_addresses = BillingAddress.objects.filter(stripe_tax_id=tax_id_obj.id) - for b_address in b_addresses: - b_address.vat_validation_status = tax_id_obj.verification.status - b_address.vat_number_validated_on = datetime.datetime.now() - b_address.save() + # Get the user from the invoice + invoice = event.data.object + logger.debug("Checking whether StripeCustomer %s exists" % invoice.customer) + try: + stripe_customer = StripeCustomer.objects.get(stripe_id=invoice.customer) + except StripeCustomer.DoesNotExist as dne: + # StripeCustomer does not exist + err_msg = "FAILURE handle_invoice_webhook: StripeCustomer %s doesn't exist" % invoice.customer + err_body = "Details %s" % str(dne) + return handle_error(err_msg, err_body) - ub_addresses = UserBillingAddress.objects.filter(stripe_tax_id=tax_id_obj.id) - for ub_address in ub_addresses: - ub_address.vat_validation_status = tax_id_obj.verification.status - ub_address.vat_number_validated_on = datetime.datetime.now() - ub_address.save() - email_data = { - 'subject': "The VAT %s associated with %s was verified" % - (tax_id_obj.value, stripe_customer.user.email if stripe_customer else "unknown"), - 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, - 'to': settings.DCL_ERROR_EMAILS_TO_LIST, - 'body': "The following objects were modified:\n".join( - '\n'.join([str(b_address) for b_address in b_addresses]) - ).join( - '\n'.join([str(ub_address) for ub_address in ub_addresses]) - ), - } - else: - logger.debug("Tax_id %s is %s" % (tax_id_obj.id, - tax_id_obj.verification.status)) - email_data = { - 'subject': "The VAT %s associated with %s was %s" % - (tax_id_obj.value, stripe_customer.user.email if stripe_customer else "unknown", tax_id_obj.verification.status), - 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, - 'to': settings.DCL_ERROR_EMAILS_TO_LIST, - 'body': "Response = %s" % str(tax_id_obj), - } - send_plain_email_task.delay(email_data) - 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 + if event.type == "invoice.payment_succeeded": + logger.debug("Invoice payment succeeded") - # 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. + # Create a new invoice for the user + invoice_dict = { + 'created': invoice.created, + 'receipt_number': invoice.receipt_number, + 'invoice_number': invoice.number, + 'paid_at': invoice.status_transitions.paid_at if invoice.paid else 0, + 'period_start': invoice.period_start, + 'period_end': invoice.period_end, + 'billing_reason': invoice.billing_reason, + 'discount': invoice.discount.coupon.amount_off if invoice.discount else 0, + 'total': invoice.total, + # to see how many line items we have in this invoice and + # then later check if we have more than 1 + 'lines_data_count': len( + invoice.lines.data) if invoice.lines.data is not None else 0, + 'invoice_id': invoice.id, + 'lines_meta_data_csv': ','.join( + [line.metadata.VM_ID if hasattr(line.metadata, 'VM_ID') else '' + for line in invoice.lines.data] + ), + 'subscription_ids_csv': ','.join( + [line.id if line.type == 'subscription' else '' for line in + invoice.lines.data] + ), + 'line_items': invoice.lines.data, + 'customer': stripe_customer + } + mhb = MonthlyHostingBill.create(invoice_dict) + mbli = HostingBillLineItem.objects.filter(monthly_hosting_bill=mhb).first() + vm_id = mbli.get_vm_id() - 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) + if vm_id is None: + vm_id = mhb.order.vm_id + + # Send an email to admin + admin_msg_sub = "Invoice payment success for user {} and VM {}".format( + stripe_customer.user.email, + vm_id if vm_id is not None else "Unknown" + ) + email_to_admin_data = { + 'subject': admin_msg_sub, + 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': settings.WEBHOOK_EMAIL_TO.split(","), + 'body': "\n".join( + ["%s=%s" % (k, v) for (k, v) in invoice_dict.items()]), + } + logger.debug("Sending msg %s to %s" % (admin_msg_sub, + settings.WEBHOOK_EMAIL_TO)) + send_plain_email_task.delay(email_to_admin_data) + + elif event.type == "invoice.payment_failed": + # Create a failed invoice, so that we have a trace of which invoices + # need a followup + FailedInvoice.create( + stripe_customer, number_of_attempts = 1, invoice_id=invoice.id + ) + VM_ID = invoice.lines.data[0].metadata["VM_ID"] + admin_msg_sub = "Invoice payment FAILED for user {} and {}".format( + stripe_customer.user.email, + VM_ID + ) + logger.error(admin_msg_sub) + # send email to admin + email_to_admin_data = { + 'subject': admin_msg_sub, + 'from_email': settings.DCL_SUPPORT_FROM_ADDRESS, + 'to': settings.WEBHOOK_EMAIL_TO.split(","), + 'body': "\n".join( + ["%s=%s" % (k, v) for (k, v) in invoice.__dict__.items()]), + } + # send email to user + context = { + 'base_url': "{0}://{1}".format(request.scheme, + request.get_host()), + 'dcl_text': settings.DCL_TEXT, + 'VM_ID': VM_ID, + 'number_of_remaining_hours': 48 # for the first failure we wait 48 hours + } + email_data = { + 'subject': 'IMPORTANT: The payment for VM {VM_ID} at {dcl_text} failed'.format( + dcl_text=settings.DCL_TEXT, + VM_ID=invoice.lines.data.metadata + ), + 'to': stripe_customer.user.email, + 'context': context, + 'template_name': 'invoice_failed', + 'template_path': 'datacenterlight/emails/', + 'from_address': settings.DCL_SUPPORT_FROM_ADDRESS + } + email = BaseEmail(**email_data) + email.send() + + send_plain_email_task.delay(email_to_admin_data) else: logger.error("Unhandled event : " + event.type) + return HttpResponse(status=200)