Compare commits

..

2 Commits

Author SHA1 Message Date
PCoder ddd90e650b Add clean_name method for BillingAddressFormSignup 2019-12-18 22:54:10 +05:30
PCoder e46729ee04 Add name validators to all signup forms 2019-12-18 22:40:33 +05:30
74 changed files with 1070 additions and 3732 deletions

101
Changelog
View File

@ -1,102 +1,5 @@
3.2: 2021-02-07 Next:
* 8816: Update order confirmation text to better prepared for payment dispute * ldap_migration
* 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
* [dcl cms navbar plugin]: Provide an option to show non transparent navar always
2.8: 2019-12-20
* ldap_migration: Migrate django users to Ldap
Notes for deployment: Notes for deployment:
``` ```
1. Git Pull 1. Git Pull

View File

@ -184,11 +184,6 @@ class DCLNavbarPluginModel(CMSPlugin):
default=True, default=True,
help_text='Uncheck this if you do not want to show login/dashboard.' help_text='Uncheck this if you do not want to show login/dashboard.'
) )
show_non_transparent_navbar_always = models.BooleanField(
default=False,
help_text='Check this if you want to show non transparent navbar only.'
'(Useful when we want to setup a simple page)'
)
def get_logo_dark(self): def get_logo_dark(self):
# used only if atleast one logo exists # used only if atleast one logo exists

View File

@ -1,6 +1,5 @@
from cms.plugin_base import CMSPluginBase from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool from cms.plugin_pool import plugin_pool
from django.conf import settings
from .cms_models import ( from .cms_models import (
DCLBannerItemPluginModel, DCLBannerListPluginModel, DCLContactPluginModel, DCLBannerItemPluginModel, DCLBannerListPluginModel, DCLContactPluginModel,
@ -101,7 +100,6 @@ class DCLCalculatorPlugin(CMSPluginBase):
vm_type=instance.vm_type vm_type=instance.vm_type
).order_by('name') ).order_by('name')
context['instance'] = instance context['instance'] = instance
context['vm_base_price'] = settings.VM_BASE_PRICE
context['min_ram'] = 0.5 if instance.enable_512mb_ram else 1 context['min_ram'] = 0.5 if instance.enable_512mb_ram else 1
return context return context

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: 2018-03-30 23:22+0000\n"
"Last-Translator: b'Anonymous User <coder.purple+25@gmail.com>'\n" "Last-Translator: b'Anonymous User <coder.purple+25@gmail.com>'\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -144,8 +144,9 @@ msgid ""
"the heart of Switzerland." "the heart of Switzerland."
msgstr "Bei uns findest Du die günstiges VMs aus der Schweiz." 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" msgid "ORDER VM"
msgstr "VM BESTELLEN" msgstr "VM BESTELLEN"
@ -406,19 +407,6 @@ msgstr "Datum"
msgid "Billed to" msgid "Billed to"
msgstr "Rechnungsadresse" 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" msgid "Payment method"
msgstr "Bezahlmethode" msgstr "Bezahlmethode"
@ -431,63 +419,64 @@ msgstr "Bestellungsübersicht"
msgid "Product" msgid "Product"
msgstr "Produkt" msgstr "Produkt"
msgid "Price"
msgstr "Preise"
msgid "VAT for"
msgstr "MwSt für"
msgid "Total Amount"
msgstr "Gesamtsumme"
msgid "Amount"
msgstr "Betrag"
msgid "Description" msgid "Description"
msgstr "Beschreibung" msgstr "Beschreibung"
msgid "Recurring" msgid "Recurring"
msgstr "Wiederholend" msgstr "Wiederholend"
msgid "Price Before VAT" msgid "Subtotal"
msgstr "Preis ohne MwSt." msgstr "Zwischensumme"
msgid "Pre VAT" #, fuzzy, python-format
msgstr "Exkl. MwSt." #| msgid ""
#| "By clicking \"Place order\" this plan will charge your credit card "
msgid "VAT for" #| "account with %(total_price)s CHF/month"
msgstr "MwSt für" msgid ""
"By clicking \"Place order\" this plan will charge your credit card account "
msgid "Your Price in Total" "with %(total_price)s CHF/year"
msgstr "Dein Gesamtpreis" msgstr ""
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(total_price)s "
"CHF pro Jahr belastet"
#, python-format #, python-format
msgid "" msgid ""
" By clicking \"Place order\" you agree to our <a href=\"https://" "By clicking \"Place order\" this plan will charge your credit card account "
"datacenterlight.ch/en-us/cms/terms-of-service/\">Terms of Service</a> and " "with %(total_price)s CHF/month"
"this plan will charge your credit card account with %(total_price)s CHF/year"
msgstr "" msgstr ""
"Indem Du auf \"Bestellung aufgeben\" klickst, erklärst Du dich mit unseren <a href=\"https://" "Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(total_price)s "
"datacenterlight.ch/en-us/cms/terms-of-service/\">Nutzungsbedingungen</a> einverstanden und Dein Kreditkartenkonto wird mit %(total_price)s CHF/Jahr belastet." "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 #, python-format
msgid "" msgid ""
"\n" "By clicking \"Place order\" this plan will charge your credit card account "
" By clicking \"Place order\" you agree to " "with %(vm_total_price)s CHF/month"
"our <a href=\"https://datacenterlight.ch/en-us/cms/terms-of-service/\">Terms "
"of Service</a> and this plan will charge your credit card account with "
"%(total_price)s CHF/month"
msgstr "" msgstr ""
"\n" "Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit "
"Indem Du auf \"Bestellung aufgeben\" klickst, erklärst Du dich mit unseren <a href=\"https://" "%(vm_total_price)s CHF pro Monat belastet"
"datacenterlight.ch/en-us/cms/terms-of-service/\">Nutzungsbedingungen</a> einverstanden und Dein Kreditkartenkonto wird mit %(total_price)s CHF/Monat belastet."
#, python-format
msgid ""
"By clicking \"Place order\" you agree to our <a href=\"https://"
"datacenterlight.ch/en-us/cms/terms-of-service/\">Terms of Service</a> 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 <a href=\"https://"
"datacenterlight.ch/de/cms/terms-of-service/\">Nutzungsbedingungen</a> einverstanden und Dein Kreditkartenkonto wird mit %(total_price)s CHF belastet."
#, python-format
msgid ""
"By clicking \"Place order\" you agree to our <a href=\"https://"
"datacenterlight.ch/en-us/cms/terms-of-service/\">Terms of Service</a> 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 <a href=\"https://"
"datacenterlight.ch/de/cms/terms-of-service/\">Nutzungsbedingungen</a> einverstanden und Dein Kreditkartenkonto wird mit %(vm_total_price)s CHF/Monat belastet"
msgid "Place order" msgid "Place order"
msgstr "Bestellen" 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." msgid "Actions speak louder than words. Let's do it, try our VM now."
msgstr "Taten sagen mehr als Worte Teste jetzt unsere VM!" msgstr "Taten sagen mehr als Worte Teste jetzt unsere VM!"
msgid "See Invoice"
msgstr "Siehe Rechnung"
msgid "Invalid number of cores" msgid "Invalid number of cores"
msgstr "Ungültige Anzahle CPU-Kerne" msgstr "Ungültige Anzahle CPU-Kerne"
@ -606,22 +592,16 @@ msgid "Incorrect pricing name. Please contact support{support_email}"
msgstr "" msgstr ""
"Ungültige Preisbezeichnung. Bitte kontaktiere den Support{support_email}" "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" msgid "Confirm Order"
msgstr "Bestellung Bestätigen" 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." msgid "Error."
msgstr "" msgstr ""
@ -632,21 +612,10 @@ msgstr ""
"Es ist ein Fehler bei der Zahlung betreten. Du wirst nach dem Schliessen vom " "Es ist ein Fehler bei der Zahlung betreten. Du wirst nach dem Schliessen vom "
"Popup zur Bezahlseite weitergeleitet." "Popup zur Bezahlseite weitergeleitet."
msgid "Thank you for the order." #, python-brace-format
msgstr "Danke für Deine Bestellung." msgid "An error occurred while associating the card. Details: {details}"
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"
msgstr "" msgstr ""
"Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: {details}"
msgid ""
"Your VM will be up and running in a few moments. We will send you a "
"confirmation email as soon as it is ready."
msgstr ""
"Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du "
"auf sie zugreifen kannst."
msgid " This is a monthly recurring plan." msgid " This is a monthly recurring plan."
msgstr "Dies ist ein monatlich wiederkehrender 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 " "Du wirst bald eine Bestätigungs-E-Mail über die Zahlung erhalten. Du kannst "
"jederzeit unter info@ungleich.ch kontaktieren." "jederzeit unter info@ungleich.ch kontaktieren."
#, python-format msgid "Thank you for the order."
#~ msgid "" msgstr "Danke für Deine Bestellung."
#~ "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"
#, fuzzy, python-format msgid ""
#~| msgid "" "Your VM will be up and running in a few moments. We will send you a "
#~| "By clicking \"Place order\" this payment will charge your credit card " "confirmation email as soon as it is ready."
#~| "account with a one time amount of %(total_price)s CHF" msgstr ""
#~ msgid "" "Deine VM ist gleich bereit. Wir senden Dir eine Bestätigungsemail, sobald Du "
#~ "By clicking \"Place order\" this payment will charge your credit card " "auf sie zugreifen kannst."
#~ "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 "VAT" #~ msgid "VAT"
#~ msgstr "Mehrwertsteuer" #~ msgstr "Mehrwertsteuer"
@ -735,6 +676,9 @@ msgstr ""
#~ "ausgelöst, nachdem Du die Bestellung auf der nächsten Seite bestätigt " #~ "ausgelöst, nachdem Du die Bestellung auf der nächsten Seite bestätigt "
#~ "hast." #~ "hast."
#~ msgid "Card Number"
#~ msgstr "Kreditkartennummer"
#~ msgid "" #~ msgid ""
#~ "You are not making any payment yet. After placing your order, you will be " #~ "You are not making any payment yet. After placing your order, you will be "
#~ "taken to the Submit Payment Page." #~ "taken to the Submit Payment Page."
@ -786,6 +730,9 @@ msgstr ""
#~ "Wir werden dann sobald als möglich Ihren Beta-Zugang erstellen und Sie " #~ "Wir werden dann sobald als möglich Ihren Beta-Zugang erstellen und Sie "
#~ "daraufhin kontaktieren.Bis dahin bitten wir Sie um etwas Geduld." #~ "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" #~ msgid "Thank you for order! Our team will contact you via email"
#~ msgstr "" #~ msgstr ""
#~ "Vielen Dank für die Bestellung. Unser Team setzt sich sobald wie möglich " #~ "Vielen Dank für die Bestellung. Unser Team setzt sich sobald wie möglich "

View File

@ -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"))

View File

@ -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)
)
)
)

View File

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2019-12-24 03:34
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('datacenterlight', '0029_auto_20190420_1022'),
]
operations = [
migrations.AddField(
model_name='dclnavbarpluginmodel',
name='show_non_transparent_navbar_always',
field=models.BooleanField(default=False, help_text='Check this if you want to show non transparent navbar only.(Useful when we want to setup a simple page)'),
),
]

View File

@ -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),
),
]

View File

@ -54,7 +54,6 @@ class VMPricing(models.Model):
discount_amount = models.DecimalField( discount_amount = models.DecimalField(
max_digits=6, decimal_places=2, default=0 max_digits=6, decimal_places=2, default=0
) )
stripe_coupon_id = models.CharField(max_length=255, null=True, blank=True)
def __str__(self): def __str__(self):
display_str = self.name + ' => ' + ' - '.join([ display_str = self.name + ' => ' + ' - '.join([

View File

@ -191,9 +191,3 @@ footer .dcl-link-separator::before {
font-weight: bold; font-weight: bold;
font-size: 14px; font-size: 14px;
} }
@media(max-width:767px) {
.vspace-top {
margin-top: 35px;
}
}

View File

@ -532,7 +532,6 @@
.order-detail-container .total-price { .order-detail-container .total-price {
font-size: 18px; font-size: 18px;
line-height: 20px;
} }
@media (max-width: 767px) { @media (max-width: 767px) {

View File

@ -77,18 +77,16 @@
} }
function _navScroll() { function _navScroll() {
if (!window.non_transparent_navbar_always) { if ($(window).scrollTop() > 10) {
if ($(window).scrollTop() > 10) { $(".navbar").removeClass("navbar-transparent");
$(".navbar").removeClass("navbar-transparent"); $(".navbar-default .btn-link").css("color", "#777");
$(".navbar-default .btn-link").css("color", "#777"); $(".dropdown-menu").removeClass("navbar-transparent");
$(".dropdown-menu").removeClass("navbar-transparent"); $(".dropdown-menu > li > a").css("color", "#777");
$(".dropdown-menu > li > a").css("color", "#777"); } else {
} else { $(".navbar").addClass("navbar-transparent");
$(".navbar").addClass("navbar-transparent"); $(".navbar-default .btn-link").css("color", "#fff");
$(".navbar-default .btn-link").css("color", "#fff"); $(".dropdown-menu").addClass("navbar-transparent");
$(".dropdown-menu").addClass("navbar-transparent"); $(".dropdown-menu > li > a").css("color", "#fff");
$(".dropdown-menu > li > a").css("color", "#fff");
}
} }
} }
@ -225,8 +223,8 @@
} }
var total = (cardPricing['cpu'].value * window.coresUnitPrice) + var total = (cardPricing['cpu'].value * window.coresUnitPrice) +
(cardPricing['ram'].value * window.ramUnitPrice) + (cardPricing['ram'].value * window.ramUnitPrice) +
(cardPricing['storage'].value * window.ssdUnitPrice) + (cardPricing['storage'].value * window.ssdUnitPrice) -
window.vmBasePrice - window.discountAmount; window.discountAmount;
total = parseFloat(total.toFixed(2)); total = parseFloat(total.toFixed(2));
$("#total").text(total); $("#total").text(total);
} }

View File

@ -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)) "Running create_vm_task on {}".format(current_task.request.hostname))
vm_id = None vm_id = None
try: try:
final_price = (
specs.get('total_price') if 'total_price' in specs
else specs.get('price')
)
if 'pass' in user: if 'pass' in user:
on_user = user.get('username') on_user = user.get('email')
on_pass = user.get('pass') on_pass = user.get('pass')
logger.debug("Using user {user} to create VM".format(user=on_user)) logger.debug("Using user {user} to create VM".format(user=on_user))
vm_name = None 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: if vm_id is None:
raise Exception("Could not create VM") raise Exception("Could not create VM")
handle_metadata_and_emails(order_id, vm_id, manager, user, specs, # Update HostingOrder with the created vm_id
template) 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: except Exception as e:
logger.error(str(e)) logger.error(str(e))
try: try:
@ -110,127 +215,3 @@ def create_vm_task(self, vm_template_id, user, specs, template, order_id):
return return
return vm_id return vm_id
def handle_metadata_and_emails(order_id, vm_id, manager, user, specs,
template):
"""
Handle's setting up of the metadata in Stripe and database and sending of
emails to the user after VM creation
:param order_id: the hosting order id
:param vm_id: the id of the vm created
:param manager: the OpenNebula Manager instance
:param user: the user's dict passed to the celery task
:param specs: the specification's dict passed to the celery task
:param template: the template dict passed to the celery task
:return:
"""
custom_user = CustomUser.objects.get(email=user.get('email'))
final_price = (
specs.get('total_price') if 'total_price' in specs
else specs.get('price')
)
# Update HostingOrder with the created vm_id
hosting_order = HostingOrder.objects.filter(id=order_id).first()
error_msg = None
try:
hosting_order.vm_id = vm_id
hosting_order.save()
logger.debug(
"Updated hosting_order {} with vm_id={}".format(
hosting_order.id, vm_id
)
)
except Exception as ex:
error_msg = (
"HostingOrder with id {order_id} not found. This means that "
"the hosting order was not created and/or it is/was not "
"associated with VM with id {vm_id}. Details {details}".format(
order_id=order_id, vm_id=vm_id, details=str(ex)
)
)
logger.error(error_msg)
stripe_utils = StripeUtils()
result = stripe_utils.set_subscription_metadata(
subscription_id=hosting_order.subscription_id,
metadata={"VM_ID": str(vm_id)}
)
if result.get('error') is not None:
emsg = "Could not update subscription metadata for {sub}".format(
sub=hosting_order.subscription_id
)
logger.error(emsg)
if error_msg:
error_msg += ". " + emsg
else:
error_msg = emsg
vm = VirtualMachineSerializer(manager.get_vm(vm_id)).data
context = {
'name': user.get('name'),
'email': user.get('email'),
'cores': specs.get('cpu'),
'memory': specs.get('memory'),
'storage': specs.get('disk_size'),
'price': final_price,
'template': template.get('name'),
'vm_name': vm.get('name'),
'vm_id': vm['vm_id'],
'order_id': order_id
}
if error_msg:
context['errors'] = error_msg
if 'pricing_name' in specs:
context['pricing'] = str(VMPricing.get_vm_pricing_by_name(
name=specs['pricing_name']
))
email_data = {
'subject': settings.DCL_TEXT + " Order from %s" % context['email'],
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
'to': ['info@ungleich.ch'],
'body': "\n".join(
["%s=%s" % (k, v) for (k, v) in context.items()]),
'reply_to': [context['email']],
}
email = EmailMessage(**email_data)
email.send()
if 'pass' in user:
lang = 'en-us'
if user.get('language') is not None:
logger.debug(
"Language is set to {}".format(user.get('language')))
lang = user.get('language')
translation.activate(lang)
# Send notification to the user as soon as VM has been booked
context = {
'base_url': "{0}://{1}".format(user.get('request_scheme'),
user.get('request_host')),
'order_url': reverse('hosting:invoices'),
'page_header': _(
'Your New VM %(vm_name)s at Data Center Light') % {
'vm_name': vm.get('name')},
'vm_name': vm.get('name')
}
email_data = {
'subject': context.get('page_header'),
'to': user.get('email'),
'context': context,
'template_name': 'new_booked_vm',
'template_path': 'hosting/emails/',
'from_address': settings.DCL_SUPPORT_FROM_ADDRESS,
}
email = BaseEmail(**email_data)
email.send()
logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id))
if vm_id > 0:
get_or_create_vm_detail(custom_user, manager, vm_id)

View File

@ -1,5 +1,5 @@
<div class="price-calc-section"> <div class="price-calc-section">
<div class="card"> <div class="card">
{% 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 %}
</div> </div>
</div> </div>

View File

@ -1,9 +1,7 @@
{% load static i18n custom_tags cms_tags %} {% load static i18n custom_tags cms_tags %}
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
{% if instance.show_non_transparent_navbar_always %}
<script>window.non_transparent_navbar_always=true;</script> <nav class="navbar navbar-default navbar-fixed-top topnav navbar-transparent">
{% endif %}
<nav class="navbar navbar-default navbar-fixed-top topnav {% if instance.show_non_transparent_navbar_always != True %}navbar-transparent{% endif %}">
<!-- Brand and toggle get grouped for better mobile display --> <!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header"> <div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#dcl-topnav"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#dcl-topnav">

View File

@ -28,7 +28,7 @@
{% blocktrans %}Thanks for joining us! We provide the most affordable virtual machines from the heart of Switzerland.{% endblocktrans %} {% blocktrans %}Thanks for joining us! We provide the most affordable virtual machines from the heart of Switzerland.{% endblocktrans %}
</p> </p>
<p style="line-height: 1.75; font-family: Lato, Arial, sans-serif; font-weight: 300; margin: 0;"> <p style="line-height: 1.75; font-family: Lato, Arial, sans-serif; font-weight: 300; margin: 0;">
{% 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 %}
</p> </p>
</td> </td>
</tr> </tr>

View File

@ -3,7 +3,7 @@
{% trans "Welcome to Data Center Light!" %} {% 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 %}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' %} {{ base_url }}{% url 'hosting:create_virtual_machine' %}

View File

@ -9,13 +9,12 @@
window.ssdUnitPrice = {{vm_pricing.ssd_unit_price|default:0}}; window.ssdUnitPrice = {{vm_pricing.ssd_unit_price|default:0}};
window.hddUnitPrice = {{vm_pricing.hdd_unit_price|default:0}}; window.hddUnitPrice = {{vm_pricing.hdd_unit_price|default:0}};
window.discountAmount = {{vm_pricing.discount_amount|default:0}}; window.discountAmount = {{vm_pricing.discount_amount|default:0}};
window.vmBasePrice = {{vm_base_price|default:0}};
window.minRam = {{min_ram}}; window.minRam = {{min_ram}};
window.minRamErr = '{% blocktrans with min_ram=min_ram %}Please enter a value in range {{min_ram}} - 200.{% endblocktrans %}'; window.minRamErr = '{% blocktrans with min_ram=min_ram %}Please enter a value in range {{min_ram}} - 200.{% endblocktrans %}';
</script> </script>
{% endif %} {% endif %}
<form id="order_form" method="POST" action="{% url 'datacenterlight:index' %}" data-toggle="validator" role="form"> <form id="order_form" method="POST" action="{{calculator_form_url}}" data-toggle="validator" role="form">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="pid" value="{{instance.id}}"> <input type="hidden" name="pid" value="{{instance.id}}">
<div class="title"> <div class="title">
@ -103,4 +102,4 @@
</div> </div>
<input type="hidden" name="pricing_name" value="{% if vm_pricing.name %}{{vm_pricing.name}}{% else %}unknown{% endif%}"></input> <input type="hidden" name="pricing_name" value="{% if vm_pricing.name %}{{vm_pricing.name}}{% else %}unknown{% endif%}"></input>
<input type="submit" class="btn btn-primary disabled" value="{% trans 'Continue' %}"></input> <input type="submit" class="btn btn-primary disabled" value="{% trans 'Continue' %}"></input>
</form> </form>

View File

@ -1,6 +1,7 @@
{% load staticfiles i18n custom_tags %} {% load staticfiles i18n custom_tags %}
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
<nav class="navbar navbar-default navbar-fixed-top topnav {% if instance.show_non_transparent_navbar_always is False %}navbar-transparent{% endif %}">
<nav class="navbar navbar-default navbar-fixed-top topnav navbar-transparent">
<!-- Brand and toggle get grouped for better mobile display --> <!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header"> <div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1"> <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">

View File

@ -13,15 +13,6 @@
<!-- Credit card form --> <!-- Credit card form -->
<div class="dcl-order-container"> <div class="dcl-order-container">
<div class="payment-container"> <div class="payment-container">
<div id='payment_error'>
{% for message in messages %}
{% if 'vat_error' in message.tags %}
<ul class="list-unstyled">
<li><p class="card-warning-content card-warning-error">An error occurred while validating VAT number: {{ message|safe }}</p></li>
</ul>
{% endif %}
{% endfor %}
</div>
<div class="dcl-payment-grid"> <div class="dcl-payment-grid">
<div class="dcl-payment-box"> <div class="dcl-payment-box">
<div class="dcl-payment-section"> <div class="dcl-payment-section">

View File

@ -2,14 +2,6 @@
{% load staticfiles bootstrap3 i18n custom_tags humanize %} {% load staticfiles bootstrap3 i18n custom_tags humanize %}
{% block content %} {% block content %}
<script>
{% if payment_intent_secret %}
console.log("payment_intent_secret");
window.paymentIntentSecret = "{{payment_intent_secret}}";
{% else %}
console.log("No payment_intent_secret");
{% endif %}
</script>
<div id="order-detail{{order.pk}}" class="order-detail-container"> <div id="order-detail{{order.pk}}" class="order-detail-container">
{% if messages %} {% if messages %}
<div class="alert alert-warning"> <div class="alert alert-warning">
@ -40,16 +32,6 @@
{{billing_address.cardholder_name}}<br> {{billing_address.cardholder_name}}<br>
{{billing_address.street_address}}, {{billing_address.postal_code}}<br> {{billing_address.street_address}}, {{billing_address.postal_code}}<br>
{{billing_address.city}}, {{billing_address.country}} {{billing_address.city}}, {{billing_address.country}}
{% if billing_address.vat_number %}
<br/>{% 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" %}
<span class="fa fa-fw fa-check-circle" aria-hidden="true" title='{% trans "Your VAT number has been verified" %}'></span>
{% else %}
<span class="fa fa-fw fa-info-circle" aria-hidden="true" title='{% trans "Your VAT number is under validation. VAT will be adjusted, once the validation is complete." %}'></span>
{% endif %}
{% endif %}
{% endif %}
{% endwith %} {% endwith %}
</p> </p>
</address> </address>
@ -66,121 +48,53 @@
<hr> <hr>
<div> <div>
<h4>{% trans "Order summary" %}</h4> <h4>{% trans "Order summary" %}</h4>
<style>
@media screen and (max-width:400px){
.header-no-left-padding {
padding-left: 0 !important;
}
}
@media screen and (max-width:767px){
.cmf-ord-heading {
font-size: 11px;
}
.order-detail-container .order-details {
font-size: 13px;
}
}
@media screen and (max-width:367px){
.cmf-ord-heading {
font-size: 11px;
}
.order-detail-container .order-details {
font-size: 12px;
}
}
</style>
{% if generic_payment_details %} {% if generic_payment_details %}
<div class="row">
<div class="col-sm-9">
<p> <p>
<strong>{% trans "Product" %}:</strong>&nbsp; <strong>{% trans "Product" %}:</strong>&nbsp;
{{ generic_payment_details.product_name }} {{ generic_payment_details.product_name }}
</p> </p>
{% if generic_payment_details.description %} <div class="row">
<p> <div class="col-sm-6">
<strong>{% trans "Description" %}: </strong> {% if generic_payment_details.vat_rate > 0 %}
<span class="pull-right">{{generic_payment_details.description}}</span>
</p>
{% endif %}
{% if generic_payment_details.recurring %}
<p>
<strong>{% trans "Recurring" %}: </strong>
<span class="pull-right">Yes</span>
</p>
{% endif %}
</div>
{% if generic_payment_details.exclude_vat_calculations %}
{% else %}
<div class="col-sm-12">
<hr class="thin-hr">
</div>
<div class="col-sm-9">
<p> <p>
<strong class="text-uppercase">{% trans "Price Before VAT" %}</strong> <span>{% trans "Price" %}: </span>
<strong class="pull-right">{{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} CHF</strong> <strong class="pull-right">CHF {{generic_payment_details.amount_before_vat|floatformat:2|intcomma}}</strong>
</p> </p>
<p>
<span>{% trans "VAT for" %} {{generic_payment_details.vat_country}} ({{generic_payment_details.vat_rate}}%) : </span>
<strong class="pull-right">CHF {{generic_payment_details.vat_amount|floatformat:2|intcomma}}</strong>
</p>
<p>
<span>{% trans "Total Amount" %} : </span>
<strong class="pull-right">CHF {{generic_payment_details.amount|floatformat:2|intcomma}}</strong>
</p>
{% else %}
<p>
<span>{% trans "Amount" %}: </span>
<strong class="pull-right">CHF {{generic_payment_details.amount|floatformat:2|intcomma}}</strong>
</p>
{% endif %}
{% if generic_payment_details.description %}
<p>
<span>{% trans "Description" %}: </span>
<strong class="pull-right">{{generic_payment_details.description}}</strong>
</p>
{% endif %}
{% if generic_payment_details.recurring %}
<p>
<span>{% trans "Recurring" %}: </span>
<strong class="pull-right">Yes</strong>
</p>
{% endif %}
</div> </div>
<div class="col-sm-12">
<hr class="thin-hr">
</div>
<div class="col-sm-9">
<div class="row">
<div class="col-md-4 col-sm-4 col-xs-4">
<p><span></span></p>
</div>
<div class="col-md-3 col-sm-3 col-xs-4">
<p class="text-right"><strong class="cmf-ord-heading">{% trans "Pre VAT" %}</strong></p>
</div>
<div class="col-md-5 col-sm-5 col-xs-4 header-no-left-padding">
<p class="text-right"><strong class="cmf-ord-heading">{% trans "VAT for" %} {{generic_payment_details.vat_country}} ({{generic_payment_details.vat_rate}}%)</strong></p>
</div>
</div>
<div class="row">
<div class="col-md-4 col-sm-4 col-xs-4">
<p><span>Price</span></p>
</div>
<div class="col-md-3 col-sm-3 col-xs-4">
<p><span class="pull-right" >{{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} CHF</span></p>
</div>
<div class="col-md-5 col-sm-5 col-xs-4">
<p><span class="pull-right">{{generic_payment_details.amount|floatformat:2|intcomma}} CHF</span></p>
</div>
</div>
</div>
<div class="col-sm-12">
<hr class="thin-hr">
</div>
<div class="col-sm-9">
<div class="row">
<div class="col-md-4 col-sm-4 col-xs-4">
<p><strong>Total</strong></p>
</div>
<div class="col-md-3 col-sm-3 col-xs-4">
<p><strong class="pull-right">{{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} CHF</strong></p>
</div>
<div class="col-md-5 col-sm-5 col-xs-4">
<p><strong class="pull-right">{{generic_payment_details.amount|floatformat:2|intcomma}} CHF</strong></p>
</div>
</div>
</div>
{% endif %}
<div class="col-sm-12">
<hr class="thin-hr">
</div> </div>
<div class="col-sm-9">
<strong class="text-uppercase align-center">{% trans "Your Price in Total" %}</strong>
<strong class="total-price pull-right">{{generic_payment_details.amount|floatformat:2|intcomma}} CHF</strong>
</div>
</div>
{% else %} {% else %}
<p> <p>
<strong>{% trans "Product" %}:</strong>&nbsp; <strong>{% trans "Product" %}:</strong>&nbsp;
{{ request.session.template.name }} {{ request.session.template.name }}
</p> </p>
<div class="row"> <div class="row">
<div class="col-sm-9"> <div class="col-sm-6">
<p> <p>
<span>{% trans "Cores" %}: </span> <span>{% trans "Cores" %}: </span>
<strong class="pull-right">{{vm.cpu|floatformat}}</strong> <strong class="pull-right">{{vm.cpu|floatformat}}</strong>
@ -197,75 +111,38 @@
<div class="col-sm-12"> <div class="col-sm-12">
<hr class="thin-hr"> <hr class="thin-hr">
</div> </div>
<div class="col-sm-9"> {% if vm.vat > 0 or vm.discount.amount > 0 %}
<p> <div class="col-sm-6">
<strong class="text-uppercase">{% trans "Price Before VAT" %}</strong> <div class="subtotal-price">
<strong class="pull-right">{{vm.price|floatformat:2|intcomma}} CHF</strong> {% if vm.vat > 0 %}
<p>
<strong class="text-lg">{% trans "Subtotal" %} </strong>
<strong class="pull-right">{{vm.price|floatformat:2|intcomma}} CHF</strong>
</p>
<p>
<small>{% trans "VAT for" %} {{vm.vat_country}} ({{vm.vat_percent}}%) : </small>
<strong class="pull-right">{{vm.vat|floatformat:2|intcomma}} CHF</strong>
</p>
{% endif %}
{% if vm.discount.amount > 0 %}
<p class="text-primary">
{%trans "Discount" as discount_name %}
<strong>{{ vm.discount.name|default:discount_name }} </strong>
<strong class="pull-right">- {{ vm.discount.amount }} CHF</strong>
</p>
{% endif %}
</div>
</div>
<div class="col-sm-12">
<hr class="thin-hr">
</div>
{% endif %}
<div class="col-sm-6">
<p class="total-price">
<strong>{% trans "Total" %} </strong>
<strong class="pull-right">{{vm.total_price|floatformat:2|intcomma}} CHF</strong>
</p> </p>
</div> </div>
<div class="col-sm-12">
<hr class="thin-hr">
</div>
<div class="col-sm-9">
<div class="row">
<div class="col-md-4 col-sm-4 col-xs-4">
<p><span></span></p>
</div>
<div class="col-md-3 col-sm-3 col-xs-4">
<p class="text-right"><strong class="cmf-ord-heading">{% trans "Pre VAT" %}</strong></p>
</div>
<div class="col-md-5 col-sm-5 col-xs-4 header-no-left-padding">
<p class="text-right"><strong class="cmf-ord-heading">{% trans "VAT for" %} {{vm.vat_country}} ({{vm.vat_percent}}%)</strong></p>
</div>
</div>
<div class="row">
<div class="col-md-4 col-sm-4 col-xs-4">
<p><span>Price</span></p>
</div>
<div class="col-md-3 col-sm-3 col-xs-4">
<p><span class="pull-right" >{{vm.price|floatformat:2|intcomma}} CHF</span></p>
</div>
<div class="col-md-5 col-sm-5 col-xs-4">
<p><span class="pull-right">{{vm.price_with_vat|floatformat:2|intcomma}} CHF</span></p>
</div>
</div>
{% if vm.discount.amount > 0 %}
<div class="row">
<div class="col-md-4 col-sm-4 col-xs-4">
<p><span>{{vm.discount.name}}</span></p>
</div>
<div class="col-md-3 col-sm-3 col-xs-4">
<p><span class="pull-right">-{{vm.discount.amount|floatformat:2|intcomma}} CHF</span></p>
</div>
<div class="col-md-5 col-sm-5 col-xs-4">
<p><span class="pull-right">-{{vm.discount.amount_with_vat|floatformat:2|intcomma}} CHF</span></p>
</div>
</div>
{% endif %}
</div>
<div class="col-sm-12">
<hr class="thin-hr">
</div>
<div class="col-sm-9">
<div class="row">
<div class="col-md-4 col-sm-4 col-xs-4">
<p><strong>Total</strong></p>
</div>
<div class="col-md-3 col-sm-3 col-xs-4">
<p><strong class="pull-right">{{vm.price_after_discount|floatformat:2|intcomma}} CHF</strong></p>
</div>
<div class="col-md-5 col-sm-5 col-xs-4">
<p><strong class="pull-right">{{vm.price_after_discount_with_vat|floatformat:2|intcomma}} CHF</strong></p>
</div>
</div>
</div>
<div class="col-sm-12">
<hr class="thin-hr">
</div>
<div class="col-sm-9">
<strong class="text-uppercase align-center">{% trans "Your Price in Total" %}</strong>
<strong class="total-price pull-right">{{vm.total_price|floatformat:2|intcomma}} CHF</strong>
</div>
</div> </div>
{% endif %} {% endif %}
</div> </div>
@ -278,16 +155,15 @@
{% if generic_payment_details %} {% if generic_payment_details %}
{% if generic_payment_details.recurring %} {% if generic_payment_details.recurring %}
{% if generic_payment_details.recurring_interval == 'year' %} {% if generic_payment_details.recurring_interval == 'year' %}
<div class="dcl-place-order-text">{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %} By clicking "Place order" you agree to our <a href="https://datacenterlight.ch/en-us/cms/terms-of-service/">Terms of Service</a> and this plan will charge your credit card account with {{ total_price }} CHF/year{% endblocktrans %}.</div> <div class="dcl-place-order-text">{% 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 %}.</div>
{% else %} {% else %}
<div class="dcl-place-order-text">{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %} <div class="dcl-place-order-text">{% 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 %}.</div>
By clicking "Place order" you agree to our <a href="https://datacenterlight.ch/en-us/cms/terms-of-service/">Terms of Service</a> and this plan will charge your credit card account with {{ total_price }} CHF/month{% endblocktrans %}.</div>
{% endif %} {% endif %}
{% else %} {% else %}
<div class="dcl-place-order-text">{% blocktrans with total_price=generic_payment_details.amount|floatformat:2|intcomma %}By clicking "Place order" you agree to our <a href="https://datacenterlight.ch/en-us/cms/terms-of-service/">Terms of Service</a> and this plan will charge your credit card account with {{ total_price }} CHF{% endblocktrans %}.</div> <div class="dcl-place-order-text">{% 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 %}.</div>
{% endif %} {% endif %}
{% else %} {% else %}
<div class="dcl-place-order-text">{% blocktrans with vm_total_price=vm.total_price|floatformat:2|intcomma %}By clicking "Place order" you agree to our <a href="https://datacenterlight.ch/en-us/cms/terms-of-service/">Terms of Service</a> and this plan will charge your credit card account with {{ vm_total_price }} CHF/month{% endblocktrans %}.</div> <div class="dcl-place-order-text">{% 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 %}.</div>
{% endif %} {% endif %}
</div> </div>
<div class="col-sm-4 order-confirm-btn text-right"> <div class="col-sm-4 order-confirm-btn text-right">
@ -330,14 +206,5 @@
<script type="text/javascript"> <script type="text/javascript">
{% trans "Some problem encountered. Please try again later." as err_msg %} {% trans "Some problem encountered. Please try again later." as err_msg %}
var create_vm_error_message = '{{err_msg|safe}}'; var create_vm_error_message = '{{err_msg|safe}}';
var pm_id = '{{id_payment_method}}';
var error_url = '{{ error_msg.redirect }}';
var error_msg = '{{ error_msg.msg_body }}';
var error_title = '{{ error_msg.msg_title }}';
var success_msg = '{{ success_msg.msg_body }}';
var success_title = '{{ success_msg.msg_title }}';
var success_url = '{{ success_msg.redirect }}';
window.stripeKey = "{{stripe_key}}";
window.isSubscription = ("{{is_subscription}}" === 'true');
</script> </script>
{%endblock%} {%endblock%}

View File

@ -1,15 +1,6 @@
import datetime
import logging
from django import template from django import template
from django.core.urlresolvers import resolve, reverse from django.core.urlresolvers import resolve, reverse
from django.utils.safestring import mark_safe from django.utils.translation import activate, get_language
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__)
register = template.Library() register = template.Library()
@ -61,116 +52,3 @@ def escaped_line_break(value):
:return: :return:
""" """
return value.replace("\\n", "\n") 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("""
<td class="xs-td-inline">{product_name}</td>
<td class="xs-td-inline">{created_at}</td>
<td class="xs-td-inline">{total}</td>
<td class="text-right last-td">
<a class="btn btn-order-detail" href="{receipt_url}" target="_blank">{see_invoice_text}</a>
</td>
""".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("""
<td class="xs-td-inline">{vm_id}</td>
<td class="xs-td-inline">{ip_addresses}</td>
<td class="xs-td-inline">{period}</td>
<td class="xs-td-inline text-right dcl-text-right-padding">{total}</td>
<td class="text-right last-td">
<a class="btn btn-order-detail" href="{stripe_invoice_url}" target="_blank">{see_invoice_text}</a>
</td>
""".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 &mdash; %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

View File

@ -1,9 +1,7 @@
import datetime
import logging import logging
import pyotp import pyotp
import requests import requests
import stripe
from django.conf import settings from django.conf import settings
from django.contrib.sites.models import Site 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 hosting.models import HostingOrder, HostingBill, OrderDetail
from membership.models import StripeCustomer from membership.models import StripeCustomer
from utils.forms import UserBillingAddressForm from utils.forms import UserBillingAddressForm
from utils.models import BillingAddress, UserBillingAddress from utils.models import BillingAddress
from utils.stripe_utils import StripeUtils
from .cms_models import CMSIntegration from .cms_models import CMSIntegration
from .models import VMPricing, VMTemplate from .models import VMPricing, VMTemplate
logger = logging.getLogger(__name__) 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): def get_cms_integration(name):
current_site = Site.objects.get_current() current_site = Site.objects.get_current()
try: try:
@ -38,14 +29,12 @@ def get_cms_integration(name):
def create_vm(billing_address_data, stripe_customer_id, specs, def create_vm(billing_address_data, stripe_customer_id, specs,
stripe_subscription_obj, card_details_dict, request, stripe_subscription_obj, card_details_dict, request,
vm_template_id, template, user): vm_template_id, template, user):
logger.debug("In create_vm")
billing_address = BillingAddress( billing_address = BillingAddress(
cardholder_name=billing_address_data['cardholder_name'], cardholder_name=billing_address_data['cardholder_name'],
street_address=billing_address_data['street_address'], street_address=billing_address_data['street_address'],
city=billing_address_data['city'], city=billing_address_data['city'],
postal_code=billing_address_data['postal_code'], postal_code=billing_address_data['postal_code'],
country=billing_address_data['country'], country=billing_address_data['country']
vat_number=billing_address_data['vat_number'],
) )
billing_address.save() billing_address.save()
customer = StripeCustomer.objects.filter(id=stripe_customer_id).first() 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) create_vm_task.delay(vm_template_id, user, specs, template, order.id)
clear_all_session_vars(request)
def clear_all_session_vars(request): def clear_all_session_vars(request):
if request.session is not None: if request.session is not None:
@ -110,9 +101,7 @@ def clear_all_session_vars(request):
'billing_address_data', 'card_id', 'billing_address_data', 'card_id',
'token', 'customer', 'generic_payment_type', 'token', 'customer', 'generic_payment_type',
'generic_payment_details', 'product_id', 'generic_payment_details', 'product_id',
'order_confirm_url', 'new_user_hosting_key_id', 'order_confirm_url', 'new_user_hosting_key_id']:
'vat_validation_status', 'billing_address_id',
'id_payment_method']:
if session_var in request.session: if session_var in request.session:
del request.session[session_var] del request.session[session_var]
@ -134,172 +123,3 @@ def check_otp(name, realm, token):
data=data data=data
) )
return response.status_code 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

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@ from utils.forms import LoginFormMixin, SignupFormMixin, BillingAddressForm
from .models import MembershipType, MembershipOrder from .models import MembershipType, MembershipOrder
from .models import Booking, BookingOrder from .models import Booking, BookingOrder
from membership.models import validate_name
class LoginForm(LoginFormMixin): class LoginForm(LoginFormMixin):
@ -19,7 +20,8 @@ class SignupForm(SignupFormMixin):
confirm_password = forms.CharField(widget=forms.PasswordInput()) confirm_password = forms.CharField(widget=forms.PasswordInput())
password = forms.CharField(widget=forms.PasswordInput()) password = forms.CharField(widget=forms.PasswordInput())
name = forms.CharField(label='name', name = forms.CharField(label='name',
widget=forms.TextInput(attrs={'placeholder': 'Full name'})) widget=forms.TextInput(attrs={'placeholder': 'Full name'}),
validators=[validate_name])
class MembershipBillingForm(BillingAddressForm): class MembershipBillingForm(BillingAddressForm):
@ -35,7 +37,6 @@ class MembershipBillingForm(BillingAddressForm):
'city': _('City'), 'city': _('City'),
'postal_code': _('Postal Code'), 'postal_code': _('Postal Code'),
'country': _('Country'), 'country': _('Country'),
'vat_number': _('VAT Number'),
} }

View File

@ -153,7 +153,6 @@ INSTALLED_APPS = (
'rest_framework', 'rest_framework',
'opennebula_api', 'opennebula_api',
'django_celery_results', 'django_celery_results',
'webhook',
) )
MIDDLEWARE_CLASSES = ( MIDDLEWARE_CLASSES = (
@ -705,7 +704,7 @@ if ENABLE_LOGGING:
'disable_existing_loggers': False, 'disable_existing_loggers': False,
'formatters': { 'formatters': {
'standard': { '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, 'handlers': handlers_dict,
@ -721,10 +720,7 @@ X_FRAME_OPTIONS = ('SAMEORIGIN' if X_FRAME_OPTIONS_ALLOW_FROM_URI is None else
X_FRAME_OPTIONS_ALLOW_FROM_URI.strip() X_FRAME_OPTIONS_ALLOW_FROM_URI.strip()
)) ))
WEBHOOK_SECRET = env('WEBHOOK_SECRET')
DEBUG = bool_env('DEBUG') DEBUG = bool_env('DEBUG')
ADD_TRIAL_PERIOD_TO_SUBSCRIPTION = bool_env('ADD_TRIAL_PERIOD_TO_SUBSCRIPTION')
# LDAP setup # LDAP setup
@ -761,15 +757,6 @@ OTP_VERIFY_ENDPOINT = env('OTP_VERIFY_ENDPOINT')
FIRST_VM_ID_AFTER_EU_VAT = int_env('FIRST_VM_ID_AFTER_EU_VAT') FIRST_VM_ID_AFTER_EU_VAT = int_env('FIRST_VM_ID_AFTER_EU_VAT')
PRE_EU_VAT_RATE = float(env('PRE_EU_VAT_RATE')) 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: if DEBUG:
from .local import * # flake8: noqa from .local import * # flake8: noqa

View File

@ -11,7 +11,6 @@ from hosting.views import (
RailsHostingView, DjangoHostingView, NodeJSHostingView RailsHostingView, DjangoHostingView, NodeJSHostingView
) )
from datacenterlight.views import PaymentOrderView from datacenterlight.views import PaymentOrderView
from webhook import views as webhook_views
from membership import urls as membership_urls from membership import urls as membership_urls
from ungleich_page.views import LandingView from ungleich_page.views import LandingView
from django.views.generic import RedirectView from django.views.generic import RedirectView
@ -63,7 +62,6 @@ urlpatterns += i18n_patterns(
name='blog_list_view'), name='blog_list_view'),
url(r'^cms/', include('cms.urls')), url(r'^cms/', include('cms.urls')),
url(r'^blog/', include('djangocms_blog.urls', namespace='djangocms_blog')), url(r'^blog/', include('djangocms_blog.urls', namespace='djangocms_blog')),
url(r'^webhooks/', webhook_views.handle_webhook),
url(r'^$', RedirectView.as_view(url='/cms') if REDIRECT_TO_CMS url(r'^$', RedirectView.as_view(url='/cms') if REDIRECT_TO_CMS
else LandingView.as_view()), else LandingView.as_view()),
url(r'^', include('ungleich_page.urls', namespace='ungleich_page')), url(r'^', include('ungleich_page.urls', namespace='ungleich_page')),

View File

@ -2,14 +2,14 @@ import datetime
import logging import logging
import subprocess import subprocess
import tempfile import tempfile
import xml
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from membership.models import CustomUser from membership.models import CustomUser, validate_name
from .models import UserHostingKey, GenericProduct from .models import UserHostingKey, GenericProduct
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -165,6 +165,14 @@ class HostingUserSignupForm(forms.ModelForm):
raise forms.ValidationError("Passwords don't match") raise forms.ValidationError("Passwords don't match")
return confirm_password return confirm_password
def clean_name(self):
name = self.cleaned_data.get('name')
try:
validate_name(name)
except ValidationError as ve:
raise forms.ValidationError(_("Improper Name"))
return name
class UserHostingKeyForm(forms.ModelForm): class UserHostingKeyForm(forms.ModelForm):
private_key = forms.CharField(widget=forms.HiddenInput(), required=False) private_key = forms.CharField(widget=forms.HiddenInput(), required=False)
@ -208,7 +216,7 @@ class UserHostingKeyForm(forms.ModelForm):
logger.debug( logger.debug(
"Not a correct ssh format {error}".format(error=str(cpe))) "Not a correct ssh format {error}".format(error=str(cpe)))
raise forms.ValidationError(KEY_ERROR_MESSAGE) raise forms.ValidationError(KEY_ERROR_MESSAGE)
return xml.sax.saxutils.escape(openssh_pubkey_str) return openssh_pubkey_str
def clean_name(self): def clean_name(self):
INVALID_NAME_MESSAGE = _("Comma not accepted in the name of the key") INVALID_NAME_MESSAGE = _("Comma not accepted in the name of the key")

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -211,9 +211,6 @@ msgstr "Bezahlbares VM Hosting in der Schweiz"
msgid "My Dashboard" msgid "My Dashboard"
msgstr "Mein Dashboard" msgstr "Mein Dashboard"
msgid "Welcome"
msgstr ""
msgid "My VMs" msgid "My VMs"
msgstr "Meine VMs" msgstr "Meine VMs"
@ -367,11 +364,6 @@ msgstr "Abgelehnt"
msgid "Billed to" msgid "Billed to"
msgstr "Rechnungsadresse" msgstr "Rechnungsadresse"
#, fuzzy
#| msgid "Card Number"
msgid "VAT Number"
msgstr "Kreditkartennummer"
msgid "Payment method" msgid "Payment method"
msgstr "Bezahlmethode" msgstr "Bezahlmethode"
@ -399,9 +391,6 @@ msgstr "Festplattenkapazität"
msgid "Subtotal" msgid "Subtotal"
msgstr "Zwischensumme" msgstr "Zwischensumme"
msgid "VAT for"
msgstr ""
msgid "VAT" msgid "VAT"
msgstr "Mehrwertsteuer" msgstr "Mehrwertsteuer"
@ -435,22 +424,18 @@ msgstr "ZURÜCK ZUR LISTE"
msgid "Some problem encountered. Please try again later." msgid "Some problem encountered. Please try again later."
msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal." 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" msgid "VM ID"
msgstr "" msgstr ""
msgid "IP Address" msgid "IP Address"
msgstr "IP-Adresse" msgstr "IP-Adresse"
msgid "See Invoice"
msgstr "Siehe Rechnung"
msgid "Page"
msgstr "Seite"
msgid "Log in" msgid "Log in"
msgstr "Anmelden" msgstr "Anmelden"
@ -495,13 +480,11 @@ msgstr "Bestellungsübersicht"
#, python-format #, python-format
msgid "" msgid ""
"By clicking \"Place order\" you agree to our <a href=\"https://" "By clicking \"Place order\" this plan will charge your credit card account "
"datacenterlight.ch/en-us/cms/terms-of-service/\">Terms of Service</a> and " "with %(vm_price)s CHF/month"
"this plan will charge your credit card account with %(vm_price)s CHF/month."
msgstr "" msgstr ""
"Indem Du auf \"Bestellung aufgeben\" klickst, erklärst Du dich mit unseren" "Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(vm_price)s CHF "
" <a href=\"https://" "pro Monat belastet"
"datacenterlight.ch/de/cms/terms-of-service/\">Nutzungsbedingungen</a> einverstanden und Dein Kreditkartenkonto wird mit %(vm_price)s CHF/Monat belastet."
msgid "Place order" msgid "Place order"
msgstr "Bestellen" msgstr "Bestellen"
@ -521,12 +504,6 @@ msgstr "Schliessen"
msgid "Order Nr." msgid "Order Nr."
msgstr "Bestellung Nr." msgstr "Bestellung Nr."
msgid "See Invoice"
msgstr "Siehe Rechnung"
msgid "Page"
msgstr "Seite"
msgid "Your Order" msgid "Your Order"
msgstr "Deine Bestellung" msgstr "Deine Bestellung"
@ -595,19 +572,6 @@ msgstr "Absenden"
msgid "Password reset" msgid "Password reset"
msgstr "Passwort zurücksetzen" 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" msgid "UPDATE"
msgstr "AKTUALISIEREN" msgstr "AKTUALISIEREN"
@ -809,15 +773,21 @@ msgstr "Dein Passwort konnte nicht zurückgesetzt werden."
msgid "The reset password link is no longer valid." msgid "The reset password link is no longer valid."
msgstr "Der Link zum Zurücksetzen Deines Passwortes ist nicht mehr gültig." 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" msgid "Card deassociation successful"
msgstr "Die Verbindung mit der Karte wurde erfolgreich aufgehoben" 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" msgid "Billing address updated successfully"
msgstr "Die Rechnungsadresse wurde erfolgreich aktualisiert" 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 #, python-brace-format
msgid "An error occurred while associating the card. Details: {details}" msgid "An error occurred while associating the card. Details: {details}"
msgstr "" msgstr ""
@ -882,8 +852,7 @@ msgstr "Ungültige Speicher-Grösse"
#, python-brace-format #, python-brace-format
msgid "Incorrect pricing name. Please contact support{support_email}" msgid "Incorrect pricing name. Please contact support{support_email}"
msgstr "" msgstr "Ungültige Preisbezeichnung. Bitte kontaktiere den Support{support_email}"
"Ungültige Preisbezeichnung. Bitte kontaktiere den Support{support_email}"
msgid "" msgid ""
"We could not find the requested VM. Please " "We could not find the requested VM. Please "
@ -902,9 +871,7 @@ msgstr "Fehler beenden VM"
msgid "" msgid ""
"VM terminate action timed out. Please contact support@datacenterlight.ch for " "VM terminate action timed out. Please contact support@datacenterlight.ch for "
"further information." "further information."
msgstr "" msgstr "VM beendet wegen Zeitüberschreitung. Bitte kontaktiere support@datacenterlight.ch für weitere Informationen."
"VM beendet wegen Zeitüberschreitung. Bitte kontaktiere "
"support@datacenterlight.ch für weitere Informationen."
#, python-format #, python-format
msgid "Virtual Machine %(vm_name)s Cancelled" msgid "Virtual Machine %(vm_name)s Cancelled"
@ -915,15 +882,6 @@ msgstr ""
"Es gab einen Fehler bei der Bearbeitung Deine Anfrage. Bitte versuche es " "Es gab einen Fehler bei der Bearbeitung Deine Anfrage. Bitte versuche es "
"noch einmal." "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 #, python-format
#~ msgid "This key exists already with the name \"%(name)s\"" #~ msgid "This key exists already with the name \"%(name)s\""
#~ msgstr "Der SSH-Key mit dem Name \"%(name)s\" existiert bereits" #~ msgstr "Der SSH-Key mit dem Name \"%(name)s\" existiert bereits"

View File

@ -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),
),
]

View File

@ -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'"],
),
]

View File

@ -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'),
),
]

View File

@ -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),
),
]

View File

@ -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),
),
]

View File

@ -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),
),
]

View File

@ -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),
),
]

View File

@ -1,3 +1,4 @@
import decimal
import json import json
import logging import logging
import os import os
@ -81,22 +82,15 @@ class GenericProduct(AssignPermissionsMixin, models.Model):
product_subscription_interval = models.CharField( product_subscription_interval = models.CharField(
max_length=10, default="month", max_length=10, default="month",
help_text="Choose between `year` and `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): def __str__(self):
return self.product_name return self.product_name
def get_actual_price(self, vat_rate=None): def get_actual_price(self, vat_rate=None):
if self.exclude_vat_calculations: VAT = vat_rate if vat_rate is not None else self.product_vat
return round(float(self.product_price), 2) return round(
else: 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): class HostingOrder(AssignPermissionsMixin, models.Model):
@ -169,12 +163,8 @@ class HostingOrder(AssignPermissionsMixin, models.Model):
def set_stripe_charge(self, stripe_charge): def set_stripe_charge(self, stripe_charge):
self.stripe_charge_id = stripe_charge.id self.stripe_charge_id = stripe_charge.id
if stripe_charge.source is None: self.last4 = stripe_charge.source.last4
self.last4 = stripe_charge.payment_method_details.card.last4 self.cc_brand = stripe_charge.source.brand
self.cc_brand = stripe_charge.payment_method_details.card.brand
else:
self.last4 = stripe_charge.source.last4
self.cc_brand = stripe_charge.source.brand
self.save() self.save()
def set_subscription_id(self, subscription_id, cc_details): def set_subscription_id(self, subscription_id, cc_details):
@ -676,11 +666,7 @@ class UserCardDetail(AssignPermissionsMixin, models.Model):
stripe_utils = StripeUtils() stripe_utils = StripeUtils()
cus_response = stripe_utils.get_customer(stripe_api_cus_id) cus_response = stripe_utils.get_customer(stripe_api_cus_id)
cu = cus_response['response_object'] cu = cus_response['response_object']
if stripe_source_id.startswith("pm"): cu.default_source = stripe_source_id
# card is a payment method
cu.invoice_settings.default_payment_method = stripe_source_id
else:
cu.default_source = stripe_source_id
cu.save() cu.save()
UserCardDetail.save_default_card_local( UserCardDetail.save_default_card_local(
stripe_api_cus_id, stripe_source_id stripe_api_cus_id, stripe_source_id
@ -738,45 +724,4 @@ class VATRates(AssignPermissionsMixin, models.Model):
currency_code = models.CharField(max_length=10) currency_code = models.CharField(max_length=10)
rate = models.FloatField() rate = models.FloatField()
rate_type = models.TextField(blank=True, default='') rate_type = models.TextField(blank=True, default='')
description = models.TextField(blank=True, default='') 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)
created_at = models.DateTimeField(auto_now_add=True)
payment_intent_id = models.CharField(max_length=100)
request = models.TextField()
stripe_api_cus_id = models.CharField(max_length=30)
card_details_response = models.TextField()
stripe_subscription_id = models.CharField(max_length=100, null=True)
stripe_charge_id = models.CharField(max_length=100, null=True)
gp_details = models.TextField()
billing_address_data = models.TextField()
class IncompleteSubscriptions(AssignPermissionsMixin, models.Model):
created_at = models.DateTimeField(auto_now_add=True)
completed_at = models.DateTimeField(null=True)
subscription_id = models.CharField(max_length=100)
subscription_status = models.CharField(max_length=30)
name = models.CharField(max_length=50)
email = models.EmailField()
request = models.TextField()
stripe_api_cus_id = models.CharField(max_length=30)
card_details_response = models.TextField()
stripe_subscription_obj = models.TextField()
stripe_onetime_charge = models.TextField()
gp_details = models.TextField()
specs = models.TextField()
vm_template_id = models.PositiveIntegerField(default=0)
template = models.TextField()
billing_address_data = models.TextField()

View File

@ -2,10 +2,4 @@
.orders-container .table > tbody > tr > td { .orders-container .table > tbody > tr > td {
vertical-align: middle; vertical-align: middle;
}
@media screen and (min-width:767px){
.dcl-text-right {
padding-right: 20px;
}
} }

View File

@ -266,8 +266,8 @@ $( document ).ready(function() {
} }
var total = (cardPricing['cpu'].value * window.coresUnitPrice) + var total = (cardPricing['cpu'].value * window.coresUnitPrice) +
(cardPricing['ram'].value * window.ramUnitPrice) + (cardPricing['ram'].value * window.ramUnitPrice) +
(cardPricing['storage'].value * window.ssdUnitPrice) + (cardPricing['storage'].value * window.ssdUnitPrice) -
window.vmBasePrice - window.discountAmount; window.discountAmount;
total = parseFloat(total.toFixed(2)); total = parseFloat(total.toFixed(2));
$("#total").text(total); $("#total").text(total);
} }

View File

@ -84,72 +84,68 @@ $(document).ready(function () {
var hasCreditcard = window.hasCreditcard || false; var hasCreditcard = window.hasCreditcard || false;
if (!hasCreditcard && window.stripeKey) { if (!hasCreditcard && window.stripeKey) {
var stripe = Stripe(window.stripeKey); var stripe = Stripe(window.stripeKey);
if (window.pm_id) { var element_style = {
fonts: [{
} else { family: 'lato-light',
var element_style = { src: 'url(https://cdn.jsdelivr.net/font-lato/2.0/Lato/Lato-Light.woff) format("woff2")'
fonts: [{ }, {
family: 'lato-light', family: 'lato-regular',
src: 'url(https://cdn.jsdelivr.net/font-lato/2.0/Lato/Lato-Light.woff) format("woff2")' src: 'url(https://cdn.jsdelivr.net/font-lato/2.0/Lato/Lato-Regular.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 cardNumberElement = elements.create('cardNumber', { ],
style: credit_card_text_style, locale: window.current_lan
placeholder: enter_ccard_text };
}); var elements = stripe.elements(element_style);
cardNumberElement.mount('#card-number-element'); var credit_card_text_style = {
base: {
var cardExpiryElement = elements.create('cardExpiry', { iconColor: '#666EE8',
style: credit_card_text_style color: '#31325F',
}); lineHeight: '25px',
cardExpiryElement.mount('#card-expiry-element'); fontWeight: 300,
fontFamily: "'lato-light', sans-serif",
var cardCvcElement = elements.create('cardCvc', { fontSize: '14px',
style: credit_card_text_style '::placeholder': {
}); color: '#777'
cardCvcElement.mount('#card-cvc-element');
cardNumberElement.on('change', function (event) {
if (event.brand) {
setBrandIcon(event.brand);
} }
}); },
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'); var submit_form_btn = $('#payment_button_with_creditcard');
@ -167,7 +163,7 @@ $(document).ready(function () {
if (parts.length === 2) return parts.pop().split(";").shift(); if (parts.length === 2) return parts.pop().split(";").shift();
} }
function submitBillingForm(pmId) { function submitBillingForm() {
var billing_form = $('#billing-form'); var billing_form = $('#billing-form');
var recurring_input = $('#id_generic_payment_form-recurring'); var recurring_input = $('#id_generic_payment_form-recurring');
billing_form.append('<input type="hidden" name="generic_payment_form-product_name" value="' + $('#id_generic_payment_form-product_name').val() + '" />'); billing_form.append('<input type="hidden" name="generic_payment_form-product_name" value="' + $('#id_generic_payment_form-product_name').val() + '" />');
@ -178,40 +174,11 @@ $(document).ready(function () {
billing_form.append('<input type="hidden" name="generic_payment_form-recurring" value="' + (recurring_input.prop('checked') ? 'on' : '') + '" />'); billing_form.append('<input type="hidden" name="generic_payment_form-recurring" value="' + (recurring_input.prop('checked') ? 'on' : '') + '" />');
} }
billing_form.append('<input type="hidden" name="generic_payment_form-description" value="' + $('#id_generic_payment_form-description').val() + '" />'); billing_form.append('<input type="hidden" name="generic_payment_form-description" value="' + $('#id_generic_payment_form-description').val() + '" />');
billing_form.append('<input type="hidden" name="id_payment_method" value="' + pmId + '" />');
billing_form.submit(); billing_form.submit();
} }
var $form_new = $('#payment-form-new'); var $form_new = $('#payment-form-new');
$form_new.submit(payWithPaymentIntent); $form_new.submit(payWithStripe_new);
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;
}
function payWithStripe_new(e) { function payWithStripe_new(e) {
e.preventDefault(); e.preventDefault();
@ -230,7 +197,7 @@ $(document).ready(function () {
} else { } else {
var process_text = "Processing"; var process_text = "Processing";
if (typeof window.processing_text !== 'undefined') { if (typeof window.processing_text !== 'undefined') {
process_text = window.processing_text; process_text = window.processing_text
} }
$form_new.find('[type=submit]').html(process_text + ' <i class="fa fa-spinner fa-pulse"></i>'); $form_new.find('[type=submit]').html(process_text + ' <i class="fa fa-spinner fa-pulse"></i>');

View File

@ -92,117 +92,50 @@ $(document).ready(function() {
}); });
var create_vm_form = $('#virtual_machine_create_form'); var create_vm_form = $('#virtual_machine_create_form');
if (window.isSubscription) { create_vm_form.submit(function () {
create_vm_form.submit(function () { $('#btn-create-vm').prop('disabled', true);
$('#btn-create-vm').prop('disabled', true); $.ajax({
$.ajax({ url: create_vm_form.attr('action'),
url: create_vm_form.attr('action'), type: 'POST',
type: 'POST', data: create_vm_form.serialize(),
data: create_vm_form.serialize(), init: function(){
init: function () { ok_btn = $('#createvm-modal-done-btn');
ok_btn = $('#createvm-modal-done-btn'); close_btn = $('#createvm-modal-close-btn');
close_btn = $('#createvm-modal-close-btn'); ok_btn.addClass('btn btn-success btn-ok btn-wide hide');
ok_btn.addClass('btn btn-success btn-ok btn-wide hide'); close_btn.addClass('btn btn-danger btn-ok btn-wide hide');
close_btn.addClass('btn btn-danger btn-ok btn-wide hide'); },
}, success: function (data) {
success: function (data) { fa_icon = $('.modal-icon > .fa');
fa_icon = $('.modal-icon > .fa'); modal_btn = $('#createvm-modal-done-btn');
modal_btn = $('#createvm-modal-done-btn'); $('#createvm-modal-title').text(data.msg_title);
if (data.showSCA) { $('#createvm-modal-body').html(data.msg_body);
console.log("Show SCA"); if (data.redirect) {
var stripe = Stripe(data.STRIPE_PUBLISHABLE_KEY); modal_btn.attr('href', data.redirect).removeClass('hide');
stripe.confirmCardPayment(data.payment_intent_secret).then(function (result) { } else {
if (result.error) { modal_btn.attr('href', "");
// Display error.message in your UI. }
modal_btn.attr('href', data.error.redirect).removeClass('hide'); if (data.status === true) {
fa_icon.attr('class', 'fa fa-close'); fa_icon.attr('class', 'checkmark');
modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide'); } else {
$('#createvm-modal-title').text(data.error.msg_title); fa_icon.attr('class', 'fa fa-close');
$('#createvm-modal-body').html(data.error.msg_body); modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide');
} else { }
// The payment has succeeded. Display a success message. },
modal_btn.attr('href', data.success.redirect).removeClass('hide'); error: function (xmlhttprequest, textstatus, message) {
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) {
fa_icon = $('.modal-icon > .fa'); fa_icon = $('.modal-icon > .fa');
fa_icon.attr('class', 'fa fa-close'); 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); $('#createvm-modal-body').text(create_vm_error_message);
} }
$('#btn-create-vm').prop('disabled', false); $('#btn-create-vm').prop('disabled', false);
$('#createvm-modal-close-btn').removeClass('hide'); $('#createvm-modal-close-btn').removeClass('hide');
} }
});
return false;
}); });
} else { return false;
create_vm_form.submit(placeOrderPaymentIntent); });
function placeOrderPaymentIntent(e) {
e.preventDefault();
var stripe = Stripe(window.stripeKey);
stripe.confirmCardPayment(
window.paymentIntentSecret,
{
payment_method: window.pm_id
}
).then(function(result) {
window.result = result;
fa_icon = $('.modal-icon > .fa');
modal_btn = $('#createvm-modal-done-btn');
if (result.error) {
// Display error.message in your UI.
modal_btn.attr('href', error_url).removeClass('hide');
fa_icon.attr('class', 'fa fa-close');
modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide');
$('#createvm-modal-title').text(error_title);
$('#createvm-modal-body').html(result.error.message + " " + error_msg);
} else {
// The payment has succeeded
// Display a success message
modal_btn.attr('href', success_url).removeClass('hide');
fa_icon.attr('class', 'checkmark');
$('#createvm-modal-title').text(success_title);
$('#createvm-modal-body').html(success_msg);
}
});
}
}
$('#createvm-modal').on('hidden.bs.modal', function () { $('#createvm-modal').on('hidden.bs.modal', function () {
$(this).find('.modal-footer .btn').addClass('hide'); $(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 () { window.onload = function () {

View File

@ -29,7 +29,7 @@
<img class="svg-img" src="{% static 'hosting/img/key.svg' %}"> <img class="svg-img" src="{% static 'hosting/img/key.svg' %}">
</div> </div>
</a> </a>
<a href="{% url 'hosting:invoices' %}" class="hosting-dashboard-item"> <a href="{% if has_invoices %}{% url 'hosting:invoices' %}{% else %}{% url 'hosting:orders' %}{% endif %}" class="hosting-dashboard-item">
<h2>{% trans "My Bills" %}</h2> <h2>{% trans "My Bills" %}</h2>
<div class="hosting-dashboard-image"> <div class="hosting-dashboard-image">
<img class="svg-img" src="{% static 'hosting/img/billing.svg' %}"> <img class="svg-img" src="{% static 'hosting/img/billing.svg' %}">

View File

@ -1,11 +1,7 @@
{% if messages %} {% if messages %}
<ul class="list-unstyled msg-list"> <ul class="list-unstyled msg-list">
{% for message in messages %} {% for message in messages %}
{% if message.tags and 'error' in message.tags %} <div class="alert {% if message.tags and message.tags == 'error' %} alert-danger {% else %} alert-{{message.tags}} {% endif %}">{{ message|safe }}</div>
<p class="card-warning-content card-warning-error">{{ message|safe }}</p>
{% elif message.tags %}
<div class="alert alert-{{message.tags}}">{{ message|safe }}</div>
{% endif %}
{% endfor %} {% endfor %}
</ul> </ul>
{% endif %} {% endif %}

View File

@ -70,9 +70,6 @@
{{invoice.order.billing_address.postal_code}}<br> {{invoice.order.billing_address.postal_code}}<br>
{{invoice.order.billing_address.city}}, {{invoice.order.billing_address.city}},
{{invoice.order.billing_address.country}} {{invoice.order.billing_address.country}}
{% if invoice.order.billing_address.vat_number %}
<br/>{% trans "VAT Number" %} {{invoice.order.billing_address.vat_number}}
{% endif %}
{% endif %} {% endif %}
</p> </p>
</address> </address>

View File

@ -15,116 +15,47 @@
<div class="dashboard-subtitle"></div> <div class="dashboard-subtitle"></div>
</div> </div>
<ul class="nav nav-tabs">
<li class="active" id="li-subscriptions"><a href="#">{% trans "Subscriptions" %}</a></li>
<li id="li-one-time-charges"><a href="#">{% trans "One-time payments" %}</a></li>
</ul>
<div class="subscriptions" id="subscriptions">
<table class="table table-switch"> <table class="table table-switch">
<thead> <thead>
<tr> <tr>
<th>{% trans "VM ID" %}</th> <th>{% trans "VM ID" %}</th>
<th>{% trans "IP Address" %}/{% trans "Product" %}</th> <th>{% trans "IP Address" %}</th>
<th>{% trans "Period" %}</th> <th>{% trans "Period" %}</th>
<th>{% trans "Amount" %}</th> <th>{% trans "Amount" %}</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for inv_data in invs %} {% for invoice in invoices %}
<tr> <tr>
{{ inv_data | get_line_item_from_stripe_invoice }} <td class="xs-td-inline" data-header="{% trans 'VM ID' %}">{{ invoice.order.vm_id }}</td>
<td class="xs-td-inline" data-header="{% trans 'IP Address' %}">{{ ips|get_value_from_dict:invoice.invoice_number|join:"<br/>" }}</td>
{% with period|get_value_from_dict:invoice.invoice_number as period_to_show %}
<td class="xs-td-inline" data-header="{% trans 'Period' %}">{{ period_to_show.period_start | date:'Y-m-d' }} &mdash; {{ period_to_show.period_end | date:'Y-m-d' }}</td>
{% endwith %}
<td class="xs-td-inline" data-header="{% trans 'Amount' %}">{{ invoice.total_in_chf|floatformat:2|intcomma }}</td>
<td class="text-right last-td">
<a class="btn btn-order-detail" href="{% url 'hosting:invoices' invoice.invoice_number %}">{% trans 'See Invoice' %}</a>
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% if invs.has_other_pages %}
<ul class="pagination">
{% if invs.has_previous %}
{% if user_email %}
<li><a href="?page={{ invs.previous_page_number }}&user_email={{user_email}}">&laquo;</a></li>
{% else %}
<li><a href="?page={{ invs.previous_page_number }}">&laquo;</a></li>
{% endif %}
{% else %}
<li class="disabled"><span>&laquo;</span></li>
{% endif %}
{% for i in invs.paginator.page_range %}
{% if invs.number == i %}
<li class="active"><span>{{ i }} <span class="sr-only">(current)</span></span></li>
{% else %}
{% if user_email %}
<li><a href="?page={{ i }}&user_email={{user_email}}">{{ i }}</a></li>
{% else %}
<li><a href="?page={{ i }}">{{ i }}</a></li>
{% endif %}
{% endif %}
{% endfor %}
{% if invs.has_next %}
{% if user_email %}
<li><a href="?page={{ invs.next_page_number }}&user_email={{user_email}}">&raquo;</a></li>
{% else %}
<li><a href="?page={{ invs.next_page_number }}">&raquo;</a></li>
{% endif %}
{% else %}
<li class="disabled"><span>&raquo;</span></li>
{% endif %}
</ul>
{% endif %}
</div>
{% if is_paginated %}
<div id="one-time-charges" class="one-time-charges" style="display: none;"> <div class="pagination">
<table class="table table-switch"> <span class="page-links">
<thead> {% if page_obj.has_previous %}
<tr> <a href="{{request.path}}?page={{ page_obj.previous_page_number }}">{% trans "previous" %}</a>
<th>{% trans "Product" %}</th> {% endif %}
<th>{% trans "Date" %}</th> <span class="page-current">
<th>{% trans "Amount" %}</th> {% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}.
<th></th> </span>
</tr> {% if page_obj.has_next %}
</thead> <a href="{{request.path}}?page={{ page_obj.next_page_number }}">{% trans "next" %}</a>
<tbody> {% endif %}
{% for ho, stripe_charge_data in invs_charge %} </span>
<tr>
{{ ho.id | get_line_item_from_hosting_order_charge }}
</tr>
{% endfor %}
</tbody>
</table>
{% if invs_charge.has_other_pages %}
<ul class="pagination">
{% if invs_charge.has_previous %}
{% if user_email %}
<li><a href="?page={{ invs_charge.previous_page_number }}&user_email={{user_email}}">&laquo;</a></li>
{% else %}
<li><a href="?page={{ invs_charge.previous_page_number }}">&laquo;</a></li>
{% endif %}
{% else %}
<li class="disabled"><span>&laquo;</span></li>
{% endif %}
{% for i in invs_charge.paginator.page_range %}
{% if invs_charge.number == i %}
<li class="active"><span>{{ i }} <span class="sr-only">(current)</span></span></li>
{% else %}
{% if user_email %}
<li><a href="?page={{ i }}&user_email={{user_email}}">{{ i }}</a></li>
{% else %}
<li><a href="?page={{ i }}">{{ i }}</a></li>
{% endif %}
{% endif %}
{% endfor %}
{% if invs_charge.has_next %}
{% if user_email %}
<li><a href="?page={{ invs_charge.next_page_number }}&user_email={{user_email}}">&raquo;</a></li>
{% else %}
<li><a href="?page={{ invs_charge.next_page_number }}">&raquo;</a></li>
{% endif %}
{% else %}
<li class="disabled"><span>&raquo;</span></li>
{% endif %}
</ul>
{% endif %}
</div> </div>
{% endif %}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -62,17 +62,11 @@
{{user.name}}<br> {{user.name}}<br>
{{order.billing_address.street_address}}, {{order.billing_address.postal_code}}<br> {{order.billing_address.street_address}}, {{order.billing_address.postal_code}}<br>
{{order.billing_address.city}}, {{order.billing_address.country}} {{order.billing_address.city}}, {{order.billing_address.country}}
{% if order.billing_address.vat_number %}
<br/>{% trans "VAT Number" %} {{order.billing_address.vat_number}}
{% endif %}
{% else %} {% else %}
{% with request.session.billing_address_data as billing_address %} {% with request.session.billing_address_data as billing_address %}
{{billing_address.cardholder_name}}<br> {{billing_address.cardholder_name}}<br>
{{billing_address.street_address}}, {{billing_address.postal_code}}<br> {{billing_address.street_address}}, {{billing_address.postal_code}}<br>
{{billing_address.city}}, {{billing_address.country}} {{billing_address.city}}, {{billing_address.country}}
{% if billing_address.vat_number %}
<br/>{% trans "VAT Number" %} {{billing_address.vat_number}}
{% endif %}
{% endwith %} {% endwith %}
{% endif %} {% endif %}
</p> </p>
@ -218,7 +212,7 @@
{% csrf_token %} {% csrf_token %}
<div class="row"> <div class="row">
<div class="col-sm-8"> <div class="col-sm-8">
<div class="dcl-place-order-text">{% blocktrans with vm_price=vm.total_price|floatformat:2|intcomma %}By clicking "Place order" you agree to our <a href="https://datacenterlight.ch/en-us/cms/terms-of-service/">Terms of Service</a> and this plan will charge your credit card account with {{ vm_price }} CHF/month.{% endblocktrans %}.</div> <div class="dcl-place-order-text">{% 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 %}.</div>
</div> </div>
<div class="col-sm-4 order-confirm-btn text-right"> <div class="col-sm-4 order-confirm-btn text-right">
<button class="btn choice-btn" id="btn-create-vm" data-href="{% url 'hosting:order-confirmation' %}" data-toggle="modal" data-target="#createvm-modal"> <button class="btn choice-btn" id="btn-create-vm" data-href="{% url 'hosting:order-confirmation' %}" data-toggle="modal" data-target="#createvm-modal">

View File

@ -26,16 +26,6 @@
{% for field in form %} {% for field in form %}
{% bootstrap_field field show_label=False type='fields' bound_css_class='' %} {% bootstrap_field field show_label=False type='fields' bound_css_class='' %}
{% endfor %} {% endfor %}
{% if form.instance.vat_number %}
{% if form.instance.vat_validation_status != "ch_vat" and form.instance.vat_validation_status != "not_needed" %}
{% if form.instance.vat_validation_status == "verified" %}
<span class="fa fa-fw fa-check-circle" aria-hidden="true" title='{% trans "Your VAT number has been verified" %}'></span>
{% elif form.instance.vat_validation_status == "pending" %}
<span class="fa fa-fw fa-info-circle" aria-hidden="true" title='{% trans "Your VAT number is under validation. VAT will be adjusted, once the validation is complete." %}'></span>
{% endif %}
{% endif %}
{% endif %}
<div class="form-group text-right"> <div class="form-group text-right">
<button type="submit" class="btn btn-vm-contact btn-wide" name="billing-form">{% trans "UPDATE" %}</button> <button type="submit" class="btn btn-vm-contact btn-wide" name="billing-form">{% trans "UPDATE" %}</button>
</div> </div>

View File

@ -81,10 +81,12 @@
</td> </td>
<td> <td>
{% if user_key.private_key %} {% if user_key.private_key %}
<a class="btn btn-default" style='color: #717274;' href="{{ user_key.private_key.url }}" download="{{user_key.private_key.name}}"> <form action="{{ user_key.private_key.url }}">
<button style="color: #717274" type="submit" class="btn btn-default">
<span class="pc-only">{% trans "Download" %}</span> <span class="pc-only">{% trans "Download" %}</span>
<span class="mob-only"><i class="fa fa-download"></i></span> <span class="mob-only"><i class="fa fa-download"></i></span>
</a> </button>
</form>
{% endif %} {% endif %}
</td> </td>
</tr> </tr>

View File

@ -46,7 +46,7 @@
<div class="vm-vmid"> <div class="vm-vmid">
<div class="vm-item-subtitle">{% trans "Current Pricing" %}</div> <div class="vm-item-subtitle">{% trans "Current Pricing" %}</div>
<div class="vm-item-lg">{{order.price|floatformat:2|intcomma}} CHF/{% if order.generic_product %}{% trans order.generic_product.product_subscription_interval %}{% else %}{% trans "Month" %}{% endif %}</div> <div class="vm-item-lg">{{order.price|floatformat:2|intcomma}} CHF/{% if order.generic_product %}{% trans order.generic_product.product_subscription_interval %}{% else %}{% trans "Month" %}{% endif %}</div>
<a class="btn btn-vm-invoice" href="{{inv_url}}" target="_blank">{% trans "See Invoice" %}</a> <a class="btn btn-vm-invoice" href="{% url 'hosting:orders' order.pk %}">{% trans "See Invoice" %}</a>
</div> </div>
</div> </div>
<div class="vm-detail-item"> <div class="vm-detail-item">

View File

@ -51,7 +51,7 @@ urlpatterns = [
name='choice_ssh_keys'), name='choice_ssh_keys'),
url(r'delete_ssh_key/(?P<pk>\d+)/?$', SSHKeyDeleteView.as_view(), url(r'delete_ssh_key/(?P<pk>\d+)/?$', SSHKeyDeleteView.as_view(),
name='delete_ssh_key'), name='delete_ssh_key'),
url(r'delete_card/(?P<pk>[\w\-]+)/$', SettingsView.as_view(), url(r'delete_card/(?P<pk>\d+)/?$', SettingsView.as_view(),
name='delete_card'), name='delete_card'),
url(r'create_ssh_key/?$', SSHKeyCreateView.as_view(), url(r'create_ssh_key/?$', SSHKeyCreateView.as_view(),
name='create_ssh_key'), name='create_ssh_key'),

View File

@ -1,10 +1,8 @@
import logging import logging
import uuid import uuid
from datetime import datetime from datetime import datetime
from urllib.parse import quote
from time import sleep from time import sleep
import stripe
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.contrib import messages 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.contrib.auth.tokens import default_token_generator
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.files.base import ContentFile 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.core.urlresolvers import reverse_lazy, reverse
from django.db.models import Q
from django.http import ( from django.http import (
Http404, HttpResponseRedirect, HttpResponse, JsonResponse Http404, HttpResponseRedirect, HttpResponse, JsonResponse
) )
@ -41,10 +37,8 @@ from stored_messages.settings import stored_messages_settings
from datacenterlight.cms_models import DCLCalculatorPluginModel from datacenterlight.cms_models import DCLCalculatorPluginModel
from datacenterlight.models import VMTemplate, VMPricing from datacenterlight.models import VMTemplate, VMPricing
from datacenterlight.utils import ( from datacenterlight.utils import create_vm, get_cms_integration, check_otp
create_vm, get_cms_integration, check_otp, validate_vat_number from hosting.models import UserCardDetail
)
from hosting.models import UserCardDetail, StripeTaxRate
from membership.models import CustomUser, StripeCustomer from membership.models import CustomUser, StripeCustomer
from opennebula_api.models import OpenNebulaManager from opennebula_api.models import OpenNebulaManager
from opennebula_api.serializers import ( 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_vm_price_with_vat, get_vm_price_for_given_vat, HostingUtils,
get_vat_rate_for_country get_vat_rate_for_country
) )
from utils.ldap_manager import LdapManager
from utils.mailer import BaseEmail from utils.mailer import BaseEmail
from utils.stripe_utils import StripeUtils from utils.stripe_utils import StripeUtils
from utils.tasks import send_plain_email_task from utils.tasks import send_plain_email_task
from utils.ldap_manager import LdapManager
from utils.views import ( from utils.views import (
PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin, PasswordResetViewMixin, PasswordResetConfirmViewMixin, LoginViewMixin,
ResendActivationLinkViewMixin ResendActivationLinkViewMixin
@ -75,7 +70,7 @@ from .forms import (
from .mixins import ProcessVMSelectionMixin, HostingContextMixin from .mixins import ProcessVMSelectionMixin, HostingContextMixin
from .models import ( from .models import (
HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail, HostingOrder, HostingBill, HostingPlan, UserHostingKey, VMDetail,
GenericProduct, MonthlyHostingBill GenericProduct, MonthlyHostingBill, HostingBillLineItem
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -388,7 +383,7 @@ class PasswordResetConfirmView(HostingContextMixin,
user = CustomUser.objects.get(pk=uid) user = CustomUser.objects.get(pk=uid)
opennebula_client = OpenNebulaManager( opennebula_client = OpenNebulaManager(
email=user.username, email=user.email,
password=user.password, password=user.password,
) )
@ -480,7 +475,7 @@ class SSHKeyDeleteView(LoginRequiredMixin, DeleteView):
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
owner = self.request.user owner = self.request.user
manager = OpenNebulaManager( manager = OpenNebulaManager(
email=owner.username, email=owner.email,
password=owner.password password=owner.password
) )
pk = self.kwargs.get('pk') pk = self.kwargs.get('pk')
@ -534,7 +529,7 @@ class SSHKeyChoiceView(LoginRequiredMixin, View):
ssh_key.private_key.save(filename, content) ssh_key.private_key.save(filename, content)
owner = self.request.user owner = self.request.user
manager = OpenNebulaManager( manager = OpenNebulaManager(
email=owner.username, email=owner.email,
password=owner.password password=owner.password
) )
keys = get_all_public_keys(request.user) keys = get_all_public_keys(request.user)
@ -565,11 +560,9 @@ class SettingsView(LoginRequiredMixin, FormView):
stripe_customer = None stripe_customer = None
if hasattr(user, 'stripecustomer'): if hasattr(user, 'stripecustomer'):
stripe_customer = user.stripecustomer stripe_customer = user.stripecustomer
stripe_utils = StripeUtils() cards_list = UserCardDetail.get_all_cards_list(
cards_list_request = stripe_utils.get_available_payment_methods( stripe_customer=stripe_customer
stripe_customer
) )
cards_list = cards_list_request.get('response_object')
context.update({ context.update({
'cards_list': cards_list, 'cards_list': cards_list,
'stripe_key': settings.STRIPE_API_PUBLIC_KEY 'stripe_key': settings.STRIPE_API_PUBLIC_KEY
@ -580,155 +573,108 @@ class SettingsView(LoginRequiredMixin, FormView):
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
if 'card' in request.POST and request.POST['card'] is not '': if 'card' in request.POST and request.POST['card'] is not '':
card_id = escape(request.POST['card']) card_id = escape(request.POST['card'])
user_card_detail = UserCardDetail.objects.get(id=card_id)
UserCardDetail.set_default_card( UserCardDetail.set_default_card(
stripe_api_cus_id=request.user.stripecustomer.stripe_id, 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 = _( msg = _(
("Your {brand} card ending in {last4} set as " ("Your {brand} card ending in {last4} set as "
"default card").format( "default card").format(
brand=card_details_response['brand'], brand=user_card_detail.brand,
last4=card_details_response['last4'] last4=user_card_detail.last4
) )
) )
messages.add_message(request, messages.SUCCESS, msg) messages.add_message(request, messages.SUCCESS, msg)
return HttpResponseRedirect(reverse_lazy('hosting:settings')) return HttpResponseRedirect(reverse_lazy('hosting:settings'))
if 'delete_card' in request.POST: if 'delete_card' in request.POST:
card = self.kwargs.get('pk') try:
stripe_utils = StripeUtils() card = UserCardDetail.objects.get(pk=self.kwargs.get('pk'))
stripe_utils.dissociate_customer_card( if (request.user.has_perm(self.permission_required[0], card)
request.user.stripecustomer.stripe_id, and
card request.user
) .stripecustomer
msg = _("Card deassociation successful") .usercarddetail_set
messages.add_message(request, messages.SUCCESS, msg) .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')) return HttpResponseRedirect(reverse_lazy('hosting:settings'))
form = self.get_form() form = self.get_form()
if form.is_valid(): if form.is_valid():
if 'billing-form' in request.POST: if 'billing-form' in request.POST:
current_billing_address = self.request.user.billing_addresses.last()
billing_address_data = form.cleaned_data billing_address_data = form.cleaned_data
billing_address_data.update({ billing_address_data.update({
'user': self.request.user.id 'user': self.request.user.id
}) })
billing_address_user_form = UserBillingAddressForm( 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) data=billing_address_data)
billing_address = billing_address_user_form.save() billing_address_user_form.save()
billing_address.stripe_tax_id = '' msg = _("Billing address updated successfully")
billing_address.vat_number_validated_on = None messages.add_message(request, messages.SUCCESS, msg)
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)
else: else:
id_payment_method = request.POST.get('id_payment_method', None) token = form.cleaned_data.get('token')
stripe_utils = StripeUtils() stripe_utils = StripeUtils()
card_details = stripe_utils.get_cards_details_from_payment_method( card_details = stripe_utils.get_cards_details_from_token(
id_payment_method token
) )
if not card_details.get('response_object'): if not card_details.get('response_object'):
form.add_error("__all__", card_details.get('error')) form.add_error("__all__", card_details.get('error'))
return self.render_to_response(self.get_context_data()) return self.render_to_response(self.get_context_data())
stripe_customer = StripeCustomer.get_or_create( 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'] card = card_details['response_object']
acc_result = stripe_utils.associate_customer_card( if UserCardDetail.get_user_card_details(stripe_customer, card):
request.user.stripecustomer.stripe_id, msg = _('You seem to have already added this card')
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']
)
)
messages.add_message(request, messages.ERROR, msg) messages.add_message(request, messages.ERROR, msg)
return self.render_to_response(self.get_context_data()) else:
preferred = False acc_result = stripe_utils.associate_customer_card(
if stripe_customer.usercarddetail_set.count() == 0: request.user.stripecustomer.stripe_id, token
preferred = True )
UserCardDetail.create( if acc_result['response_object'] is None:
stripe_customer=stripe_customer, msg = _(
last4=card['last4'], 'An error occurred while associating the card.'
brand=card['brand'], ' Details: {details}'.format(
fingerprint=card['fingerprint'], details=acc_result['error']
exp_month=card['exp_month'], )
exp_year=card['exp_year'], )
card_id=card['card_id'], messages.add_message(request, messages.ERROR, msg)
preferred=preferred return self.render_to_response(self.get_context_data())
) preferred = False
msg = _( if stripe_customer.usercarddetail_set.count() == 0:
"Successfully associated the card with your account" preferred = True
) UserCardDetail.create(
messages.add_message(request, messages.SUCCESS, msg) 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()) return self.render_to_response(self.get_context_data())
else: else:
billing_address_data = form.cleaned_data billing_address_data = form.cleaned_data
@ -741,7 +687,7 @@ class PaymentVMView(LoginRequiredMixin, FormView):
form_class = BillingAddressForm form_class = BillingAddressForm
def get_form_kwargs(self): 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() form_kwargs = super(PaymentVMView, self).get_form_kwargs()
if not current_billing_address: if not current_billing_address:
return form_kwargs return form_kwargs
@ -753,7 +699,6 @@ class PaymentVMView(LoginRequiredMixin, FormView):
'city': current_billing_address.city, 'city': current_billing_address.city,
'postal_code': current_billing_address.postal_code, 'postal_code': current_billing_address.postal_code,
'country': current_billing_address.country, 'country': current_billing_address.country,
'vat_number': current_billing_address.vat_number
} }
}) })
return form_kwargs return form_kwargs
@ -944,7 +889,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView):
except VMDetail.DoesNotExist: except VMDetail.DoesNotExist:
try: try:
manager = OpenNebulaManager( manager = OpenNebulaManager(
email=owner.username, password=owner.password email=owner.email, password=owner.password
) )
vm = manager.get_vm(obj.vm_id) vm = manager.get_vm(obj.vm_id)
context['vm'] = VirtualMachineSerializer(vm).data context['vm'] = VirtualMachineSerializer(vm).data
@ -1069,13 +1014,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView):
billing_address_data = request.session.get('billing_address_data') billing_address_data = request.session.get('billing_address_data')
vm_template_id = template.get('id', 1) vm_template_id = template.get('id', 1)
stripe_api_cus_id = request.user.stripecustomer.stripe_id stripe_api_cus_id = request.user.stripecustomer.stripe_id
logger.debug("template=%s specs=%s stripe_customer_id=%s " if 'token' in self.request.session:
"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:
card_details = stripe_utils.get_cards_details_from_token( card_details = stripe_utils.get_cards_details_from_token(
request.session['token'] request.session['token']
) )
@ -1092,7 +1031,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView):
) )
if not ucd: if not ucd:
acc_result = stripe_utils.associate_customer_card( 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 set_as_default=True
) )
if acc_result['response_object'] is None: if acc_result['response_object'] is None:
@ -1133,8 +1072,7 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView):
cpu = specs.get('cpu') cpu = specs.get('cpu')
memory = specs.get('memory') memory = specs.get('memory')
disk_size = specs.get('disk_size') disk_size = specs.get('disk_size')
amount_to_be_charged = specs.get('price') amount_to_be_charged = specs.get('total_price')
discount = specs.get('discount')
plan_name = StripeUtils.get_stripe_plan_name( plan_name = StripeUtils.get_stripe_plan_name(
cpu=cpu, cpu=cpu,
memory=memory, memory=memory,
@ -1153,61 +1091,11 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView):
amount=amount_to_be_charged, amount=amount_to_be_charged,
name=plan_name, name=plan_name,
stripe_plan_id=stripe_plan_id) 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( subscription_result = stripe_utils.subscribe_customer_to_plan(
stripe_api_cus_id, stripe_api_cus_id,
[{"plan": stripe_plan.get('response_object').stripe_plan_id}], [{"plan": stripe_plan.get(
coupon=(discount['stripe_coupon_id'] 'response_object').stripe_plan_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']
)
stripe_subscription_obj = subscription_result.get('response_object') 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 # Check if the subscription was approved and is active
if (stripe_subscription_obj is None or if (stripe_subscription_obj is None or
stripe_subscription_obj.status != 'active'): stripe_subscription_obj.status != 'active'):
@ -1246,7 +1134,6 @@ class OrdersHostingDetailView(LoginRequiredMixin, DetailView, FormView):
user = { user = {
'name': self.request.user.name, 'name': self.request.user.name,
'email': self.request.user.email, 'email': self.request.user.email,
'username': self.request.user.username,
'pass': self.request.user.password, 'pass': self.request.user.password,
'request_scheme': request.scheme, 'request_scheme': request.scheme,
'request_host': request.get_host(), 'request_host': request.get_host(),
@ -1290,7 +1177,7 @@ class OrdersHostingListView(LoginRequiredMixin, ListView):
return super(OrdersHostingListView, self).get(request, *args, **kwargs) return super(OrdersHostingListView, self).get(request, *args, **kwargs)
class InvoiceListView(LoginRequiredMixin, TemplateView): class InvoiceListView(LoginRequiredMixin, ListView):
template_name = "hosting/invoices.html" template_name = "hosting/invoices.html"
login_url = reverse_lazy('hosting:login') login_url = reverse_lazy('hosting:login')
context_object_name = "invoices" context_object_name = "invoices"
@ -1298,14 +1185,10 @@ class InvoiceListView(LoginRequiredMixin, TemplateView):
ordering = '-created' ordering = '-created'
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
page = self.request.GET.get('page', 1)
context = super(InvoiceListView, self).get_context_data(**kwargs) context = super(InvoiceListView, self).get_context_data(**kwargs)
invs_page = None
invs_page_charges = None
if ('user_email' in self.request.GET if ('user_email' in self.request.GET
and self.request.user.email == settings.ADMIN_EMAIL): and self.request.user.email == settings.ADMIN_EMAIL):
user_email = self.request.GET['user_email'] user_email = self.request.GET['user_email']
context['user_email'] = '%s' % quote(user_email)
logger.debug( logger.debug(
"user_email = {}".format(user_email) "user_email = {}".format(user_email)
) )
@ -1314,67 +1197,54 @@ class InvoiceListView(LoginRequiredMixin, TemplateView):
except CustomUser.DoesNotExist as dne: except CustomUser.DoesNotExist as dne:
logger.debug("User does not exist") logger.debug("User does not exist")
cu = self.request.user cu = self.request.user
invs = stripe.Invoice.list(customer=cu.stripecustomer.stripe_id, mhbs = MonthlyHostingBill.objects.filter(customer__user=cu)
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)
else: else:
mhbs = MonthlyHostingBill.objects.filter(
customer__user=self.request.user
)
ips_dict = {}
line_item_period_dict = {}
for mhb in mhbs:
try: try:
invs = stripe.Invoice.list( vm_detail = VMDetail.objects.get(vm_id=mhb.order.vm_id)
customer=self.request.user.stripecustomer.stripe_id, ips_dict[mhb.invoice_number] = [vm_detail.ipv6, vm_detail.ipv4]
count=100 all_line_items = HostingBillLineItem.objects.filter(monthly_hosting_bill=mhb)
) for line_item in all_line_items:
paginator = Paginator(invs.data, 10) if line_item.get_item_detail_str() != "":
try: line_item_period_dict[mhb.invoice_number] = {
invs_page = paginator.page(page) "period_start": line_item.period_start,
except PageNotAnInteger: "period_end": line_item.period_end
invs_page = paginator.page(1) }
except EmptyPage: break
invs_page = paginator.page(paginator.num_pages) except VMDetail.DoesNotExist as dne:
hosting_orders = HostingOrder.objects.filter( ips_dict[mhb.invoice_number] = ['--']
customer=self.request.user.stripecustomer).filter( logger.debug("VMDetail for {} doesn't exist".format(
Q(subscription_id=None) | Q(subscription_id='') mhb.order.vm_id
).order_by('-created_at') ))
stripe_chgs = [] context['ips'] = ips_dict
for ho in hosting_orders: context['period'] = line_item_period_dict
stripe_chgs.append(
{ho: stripe.Charge.retrieve(ho.stripe_charge_id)})
paginator_charges = Paginator(stripe_chgs, 10)
try:
invs_page_charges = paginator_charges.page(page)
except PageNotAnInteger:
invs_page_charges = paginator_charges.page(1)
except EmptyPage:
invs_page_charges = paginator_charges.page(
paginator_charges.num_pages)
except Exception as ex:
logger.error(str(ex))
invs_page = None
context["invs"] = invs_page
context["invs_charge"] = invs_page_charges
return context 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) @method_decorator(decorators)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
return super(InvoiceListView, self).get(request, *args, **kwargs) return super(InvoiceListView, self).get(request, *args, **kwargs)
@ -1453,7 +1323,7 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView):
# fallback to get it from the infrastructure # fallback to get it from the infrastructure
try: try:
manager = OpenNebulaManager( manager = OpenNebulaManager(
email=self.request.user.username, email=self.request.user.email,
password=self.request.user.password password=self.request.user.password
) )
vm = manager.get_vm(vm_id) vm = manager.get_vm(vm_id)
@ -1536,7 +1406,7 @@ class VirtualMachinesPlanListView(LoginRequiredMixin, ListView):
def get_queryset(self): def get_queryset(self):
owner = self.request.user owner = self.request.user
manager = OpenNebulaManager(email=owner.username, manager = OpenNebulaManager(email=owner.email,
password=owner.password) password=owner.password)
try: try:
queryset = manager.get_vms() queryset = manager.get_vms()
@ -1697,7 +1567,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
owner = self.request.user owner = self.request.user
vm = None vm = None
manager = OpenNebulaManager( manager = OpenNebulaManager(
email=owner.username, email=owner.email,
password=owner.password password=owner.password
) )
vm_id = self.kwargs.get('pk') vm_id = self.kwargs.get('pk')
@ -1741,28 +1611,13 @@ class VirtualMachineView(LoginRequiredMixin, View):
context = None context = None
try: try:
serializer = VirtualMachineSerializer(vm) 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 = { context = {
'virtual_machine': serializer.data, 'virtual_machine': serializer.data,
'order': hosting_order, 'order': HostingOrder.objects.get(
'keys': UserHostingKey.objects.filter(user=request.user), vm_id=serializer.data['vm_id']
'inv_url': inv_url ),
'keys': UserHostingKey.objects.filter(user=request.user)
} }
except Exception as ex: except Exception as ex:
logger.debug("Exception generated {}".format(str(ex))) logger.debug("Exception generated {}".format(str(ex)))
messages.error(self.request, messages.error(self.request,
@ -1781,7 +1636,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
vm = self.get_object() vm = self.get_object()
manager = OpenNebulaManager( manager = OpenNebulaManager(
email=owner.username, email=owner.email,
password=owner.password password=owner.password
) )
try: try:
@ -1794,7 +1649,6 @@ class VirtualMachineView(LoginRequiredMixin, View):
# Cancel Stripe subscription # Cancel Stripe subscription
stripe_utils = StripeUtils() stripe_utils = StripeUtils()
hosting_order = None hosting_order = None
stripe_subscription_obj = None
try: try:
hosting_order = HostingOrder.objects.get( hosting_order = HostingOrder.objects.get(
vm_id=vm.id vm_id=vm.id
@ -1809,7 +1663,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
error_msg = result.get('error') error_msg = result.get('error')
logger.error( logger.error(
'Error canceling subscription for {user} and vm id ' '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) logger.error(error_msg)
admin_email_body['stripe_error_msg'] = error_msg admin_email_body['stripe_error_msg'] = error_msg
@ -1831,7 +1685,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
) )
response['text'] = str(_('Error terminating VM')) + str(vm.id) response['text'] = str(_('Error terminating VM')) + str(vm.id)
else: else:
for t in range(settings.MAX_TIME_TO_WAIT_FOR_VM_TERMINATE): for t in range(15):
try: try:
manager.get_vm(vm.id) manager.get_vm(vm.id)
except WrongIdError: except WrongIdError:
@ -1854,10 +1708,6 @@ class VirtualMachineView(LoginRequiredMixin, View):
) )
break break
else: else:
logger.debug(
'Sleeping 2 seconds for terminate action on VM %s' %
vm.id
)
sleep(2) sleep(2)
if not response['status']: if not response['status']:
response['text'] = str(_("VM terminate action timed out. " response['text'] = str(_("VM terminate action timed out. "
@ -1885,27 +1735,13 @@ class VirtualMachineView(LoginRequiredMixin, View):
email.send() email.send()
admin_email_body.update(response) admin_email_body.update(response)
admin_email_body["customer_email"] = owner.email 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_ID"] = vm.id
admin_email_body["VM_created_at"] = (str(hosting_order.created_at) if admin_email_body["VM_created_at"] = (str(hosting_order.created_at) if
hosting_order is not None hosting_order is not None
else "unknown") else "unknown")
content = "" admin_msg_sub = "VM and Subscription for VM {} and user: {}".format(
total_amount = 0
if stripe_subscription_obj:
for line_item in stripe_subscription_obj["items"]["data"]:
total_amount += (line_item["quantity"] *
line_item.plan["amount"])
content += " %s => %s x %s => %s\n" % (
line_item.plan["name"], line_item["quantity"],
line_item.plan["amount"]/100,
(line_item["quantity"] * line_item.plan["amount"])/100
)
admin_email_body["subscription_amount"] = total_amount/100
admin_email_body["subscription_detail"] = content
admin_msg_sub = "VM and Subscription for VM {} and user: {}, {}".format(
vm.id, vm.id,
owner.email, owner.username owner.email
) )
email_to_admin_data = { email_to_admin_data = {
'subject': ("Deleted " if response['status'] 'subject': ("Deleted " if response['status']
@ -1951,7 +1787,7 @@ class HostingBillDetailView(PermissionRequiredMixin, LoginRequiredMixin,
context = super(DetailView, self).get_context_data(**kwargs) context = super(DetailView, self).get_context_data(**kwargs)
owner = self.request.user owner = self.request.user
manager = OpenNebulaManager(email=owner.username, manager = OpenNebulaManager(email=owner.email,
password=owner.password) password=owner.password)
# Get vms # Get vms
queryset = manager.get_vms() queryset = manager.get_vms()

View File

@ -233,17 +233,8 @@ class CustomUser(AbstractBaseUser, PermissionsMixin):
ldap_manager.create_user(self.username, password=password, ldap_manager.create_user(self.username, password=password,
firstname=first_name, lastname=last_name, firstname=first_name, lastname=last_name,
email=self.email) email=self.email)
else: self.in_ldap = True
# User exists already in LDAP, but with a dummy credential self.save()
# 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()
def __str__(self): # __unicode__ on Python 2 def __str__(self): # __unicode__ on Python 2
return self.email return self.email
@ -277,7 +268,7 @@ class StripeCustomer(models.Model):
return "%s - %s" % (self.stripe_id, self.user.email) return "%s - %s" % (self.stripe_id, self.user.email)
@classmethod @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): customer_name=None):
""" """
This method creates a Stripe API customer with the given This method creates a Stripe API customer with the given
@ -288,8 +279,7 @@ class StripeCustomer(models.Model):
stripe user. stripe user.
""" """
stripe_utils = StripeUtils() stripe_utils = StripeUtils()
stripe_data = stripe_utils.create_customer( stripe_data = stripe_utils.create_customer(token, email, customer_name)
id_payment_method, email, customer_name)
if stripe_data.get('response_object'): if stripe_data.get('response_object'):
stripe_cus_id = stripe_data.get('response_object').get('id') stripe_cus_id = stripe_data.get('response_object').get('id')
return stripe_cus_id return stripe_cus_id
@ -297,7 +287,7 @@ class StripeCustomer(models.Model):
return None return None
@classmethod @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 Check if there is a registered stripe customer with that email
or create a new one or create a new one

View File

@ -154,8 +154,6 @@ class OpenNebulaManager():
protocol=settings.OPENNEBULA_PROTOCOL) protocol=settings.OPENNEBULA_PROTOCOL)
) )
raise ConnectionRefusedError raise ConnectionRefusedError
except Exception as ex:
logger.error(str(ex))
def _get_user_pool(self): def _get_user_pool(self):
try: try:
@ -429,12 +427,8 @@ class OpenNebulaManager():
template_id = int(template_id) template_id = int(template_id)
try: try:
template_pool = self._get_template_pool() 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) return template_pool.get_by_id(template_id)
except Exception as ex: except:
logger.debug("Template Id we are looking for : %s" % template_id)
logger.error(str(ex))
raise ConnectionRefusedError raise ConnectionRefusedError
def create_template(self, name, cores, memory, disk_size, core_price, def create_template(self, name, cores, memory, disk_size, core_price,

View File

@ -86,7 +86,7 @@ class VirtualMachineSerializer(serializers.Serializer):
} }
try: try:
manager = OpenNebulaManager(email=owner.username, manager = OpenNebulaManager(email=owner.email,
password=owner.password, password=owner.password,
) )
opennebula_id = manager.create_vm(template_id=template_id, opennebula_id = manager.create_vm(template_id=template_id,

View File

@ -19,7 +19,7 @@ class VmCreateView(generics.ListCreateAPIView):
def get_queryset(self): def get_queryset(self):
owner = self.request.user owner = self.request.user
manager = OpenNebulaManager(email=owner.username, manager = OpenNebulaManager(email=owner.email,
password=owner.password) password=owner.password)
# We may have ConnectionRefusedError if we don't have a # We may have ConnectionRefusedError if we don't have a
# connection to OpenNebula. For now, we raise ServiceUnavailable # connection to OpenNebula. For now, we raise ServiceUnavailable
@ -42,7 +42,7 @@ class VmDetailsView(generics.RetrieveUpdateDestroyAPIView):
def get_queryset(self): def get_queryset(self):
owner = self.request.user owner = self.request.user
manager = OpenNebulaManager(email=owner.username, manager = OpenNebulaManager(email=owner.email,
password=owner.password) password=owner.password)
# We may have ConnectionRefusedError if we don't have a # We may have ConnectionRefusedError if we don't have a
# connection to OpenNebula. For now, we raise ServiceUnavailable # connection to OpenNebula. For now, we raise ServiceUnavailable
@ -54,7 +54,7 @@ class VmDetailsView(generics.RetrieveUpdateDestroyAPIView):
def get_object(self): def get_object(self):
owner = self.request.user owner = self.request.user
manager = OpenNebulaManager(email=owner.username, manager = OpenNebulaManager(email=owner.email,
password=owner.password) password=owner.password)
# We may have ConnectionRefusedError if we don't have a # We may have ConnectionRefusedError if we don't have a
# connection to OpenNebula. For now, we raise ServiceUnavailable # connection to OpenNebula. For now, we raise ServiceUnavailable
@ -66,7 +66,7 @@ class VmDetailsView(generics.RetrieveUpdateDestroyAPIView):
def perform_destroy(self, instance): def perform_destroy(self, instance):
owner = self.request.user owner = self.request.user
manager = OpenNebulaManager(email=owner.username, manager = OpenNebulaManager(email=owner.email,
password=owner.password) password=owner.password)
# We may have ConnectionRefusedError if we don't have a # We may have ConnectionRefusedError if we don't have a
# connection to OpenNebula. For now, we raise ServiceUnavailable # connection to OpenNebula. For now, we raise ServiceUnavailable

View File

@ -79,7 +79,7 @@ requests==2.10.0
rjsmin==1.0.12 rjsmin==1.0.12
six==1.10.0 six==1.10.0
sqlparse==0.1.19 sqlparse==0.1.19
stripe==2.41.0 stripe==1.33.0
wheel==0.29.0 wheel==0.29.0
django-admin-honeypot==1.0.0 django-admin-honeypot==1.0.0
coverage==4.3.4 coverage==4.3.4

View File

@ -1,13 +1,26 @@
import logging import logging
from django.contrib.auth.backends import ModelBackend from membership.models import CustomUser
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class MyLDAPBackend(ModelBackend): class MyLDAPBackend(object):
def authenticate(self, username=None, password=None, **kwargs): def authenticate(self, email, password):
user = super().authenticate(username, password, **kwargs) try:
if user: user = CustomUser.objects.get(email=email)
except CustomUser.DoesNotExist:
# User does not exists in Database
return None
else:
user.create_ldap_account(password) user.create_ldap_account(password)
return user if user.check_password(password):
return user
else:
return None
def get_user(self, user_id):
try:
return CustomUser.objects.get(pk=user_id)
except CustomUser.DoesNotExist:
return None

View File

@ -3,8 +3,9 @@ from django.contrib.auth import authenticate
from django.core.mail import EmailMultiAlternatives from django.core.mail import EmailMultiAlternatives
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from membership.models import CustomUser from membership.models import CustomUser, validate_name
from .models import ContactMessage, BillingAddress, UserBillingAddress from .models import ContactMessage, BillingAddress, UserBillingAddress
@ -30,6 +31,15 @@ class SignupFormMixin(forms.ModelForm):
raise forms.ValidationError("Passwords don't match") raise forms.ValidationError("Passwords don't match")
return confirm_password return confirm_password
def clean_name(self):
name = self.cleaned_data.get('name')
try:
validate_name(name)
except ValidationError as ve:
raise forms.ValidationError(_("Improper Name"))
return name
class LoginFormMixin(forms.Form): class LoginFormMixin(forms.Form):
email = forms.CharField(widget=forms.EmailInput()) email = forms.CharField(widget=forms.EmailInput())
@ -124,14 +134,13 @@ class BillingAddressForm(forms.ModelForm):
class Meta: class Meta:
model = BillingAddress model = BillingAddress
fields = ['cardholder_name', 'street_address', fields = ['cardholder_name', 'street_address',
'city', 'postal_code', 'country', 'vat_number'] 'city', 'postal_code', 'country']
labels = { labels = {
'cardholder_name': _('Cardholder Name'), 'cardholder_name': _('Cardholder Name'),
'street_address': _('Street Address'), 'street_address': _('Street Address'),
'city': _('City'), 'city': _('City'),
'postal_code': _('Postal Code'), 'postal_code': _('Postal Code'),
'Country': _('Country'), 'Country': _('Country'),
'VAT Number': _('VAT Number')
} }
@ -143,7 +152,7 @@ class BillingAddressFormSignup(BillingAddressForm):
class Meta: class Meta:
model = BillingAddress model = BillingAddress
fields = ['name', 'email', 'cardholder_name', 'street_address', fields = ['name', 'email', 'cardholder_name', 'street_address',
'city', 'postal_code', 'country', 'vat_number'] 'city', 'postal_code', 'country']
labels = { labels = {
'name': 'Name', 'name': 'Name',
'email': _('Email'), 'email': _('Email'),
@ -152,7 +161,6 @@ class BillingAddressFormSignup(BillingAddressForm):
'city': _('City'), 'city': _('City'),
'postal_code': _('Postal Code'), 'postal_code': _('Postal Code'),
'Country': _('Country'), 'Country': _('Country'),
'vat_number': _('VAT Number')
} }
def clean_email(self): def clean_email(self):
@ -167,6 +175,14 @@ class BillingAddressFormSignup(BillingAddressForm):
except CustomUser.DoesNotExist: except CustomUser.DoesNotExist:
return email return email
def clean_name(self):
name = self.cleaned_data.get('name')
try:
validate_name(name)
except ValidationError as ve:
raise forms.ValidationError(_("Improper Name"))
return name
class UserBillingAddressForm(forms.ModelForm): class UserBillingAddressForm(forms.ModelForm):
user = forms.ModelChoiceField(queryset=CustomUser.objects.all(), user = forms.ModelChoiceField(queryset=CustomUser.objects.all(),
@ -175,14 +191,13 @@ class UserBillingAddressForm(forms.ModelForm):
class Meta: class Meta:
model = UserBillingAddress model = UserBillingAddress
fields = ['cardholder_name', 'street_address', fields = ['cardholder_name', 'street_address',
'city', 'postal_code', 'country', 'user', 'vat_number'] 'city', 'postal_code', 'country', 'user']
labels = { labels = {
'cardholder_name': _('Cardholder Name'), 'cardholder_name': _('Cardholder Name'),
'street_address': _('Street Building'), 'street_address': _('Street Building'),
'city': _('City'), 'city': _('City'),
'postal_code': _('Postal Code'), 'postal_code': _('Postal Code'),
'Country': _('Country'), 'Country': _('Country'),
'vat_number': _('VAT Number'),
} }

View File

@ -1,10 +1,7 @@
import decimal import decimal
import logging import logging
import math
import subprocess import subprocess
from django.conf import settings
from oca.pool import WrongIdError from oca.pool import WrongIdError
from datacenterlight.models import VMPricing 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) + price = ((decimal.Decimal(cpu) * pricing.cores_unit_price) +
(decimal.Decimal(memory) * pricing.ram_unit_price) + (decimal.Decimal(memory) * pricing.ram_unit_price) +
(decimal.Decimal(disk_size) * pricing.ssd_unit_price) + (decimal.Decimal(disk_size) * pricing.ssd_unit_price) +
(decimal.Decimal(hdd_size) * pricing.hdd_unit_price) + (decimal.Decimal(hdd_size) * pricing.hdd_unit_price))
decimal.Decimal(settings.VM_BASE_PRICE))
cents = decimal.Decimal('.01') cents = decimal.Decimal('.01')
price = price.quantize(cents, decimal.ROUND_HALF_UP) price = price.quantize(cents, decimal.ROUND_HALF_UP)
return round(float(price), 2) 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(cpu) * pricing.cores_unit_price) +
(decimal.Decimal(memory) * pricing.ram_unit_price) + (decimal.Decimal(memory) * pricing.ram_unit_price) +
(decimal.Decimal(ssd_size) * pricing.ssd_unit_price) + (decimal.Decimal(ssd_size) * pricing.ssd_unit_price) +
(decimal.Decimal(hdd_size) * pricing.hdd_unit_price) + (decimal.Decimal(hdd_size) * pricing.hdd_unit_price)
decimal.Decimal(settings.VM_BASE_PRICE)
) )
discount_name = pricing.discount_name
discount_amount = round(float(pricing.discount_amount), 2)
vat = price * decimal.Decimal(vat_rate) * decimal.Decimal(0.01) vat = price * decimal.Decimal(vat_rate) * decimal.Decimal(0.01)
vat_percent = vat_rate vat_percent = vat_rate
cents = decimal.Decimal('.01') cents = decimal.Decimal('.01')
price = price.quantize(cents, decimal.ROUND_HALF_UP) price = price.quantize(cents, decimal.ROUND_HALF_UP)
vat = vat.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 = { discount = {
'name': discount_name, 'name': pricing.discount_name,
'amount': discount_amount, 'amount': round(float(pricing.discount_amount), 2)
'amount_with_vat': round(float(discount_amount_with_vat), 2),
'stripe_coupon_id': pricing.stripe_coupon_id
} }
return (round(float(price), 2), round(float(vat), 2), return (round(float(price), 2), round(float(vat), 2),
round(float(vat_percent), 2), discount) 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(cpu) * pricing.cores_unit_price) +
(decimal.Decimal(memory) * pricing.ram_unit_price) + (decimal.Decimal(memory) * pricing.ram_unit_price) +
(decimal.Decimal(ssd_size) * pricing.ssd_unit_price) + (decimal.Decimal(ssd_size) * pricing.ssd_unit_price) +
(decimal.Decimal(hdd_size) * pricing.hdd_unit_price) + (decimal.Decimal(hdd_size) * pricing.hdd_unit_price)
decimal.Decimal(settings.VM_BASE_PRICE)
) )
if pricing.vat_inclusive: if pricing.vat_inclusive:
vat = decimal.Decimal(0) 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) vat = vat.quantize(cents, decimal.ROUND_HALF_UP)
discount = { discount = {
'name': pricing.discount_name, 'name': pricing.discount_name,
'amount': round(float(pricing.discount_amount), 2), 'amount': round(float(pricing.discount_amount), 2)
'stripe_coupon_id': pricing.stripe_coupon_id
} }
return (round(float(price), 2), round(float(vat), 2), return (round(float(price), 2), round(float(vat), 2),
round(float(vat_percent), 2), discount) round(float(vat_percent), 2), discount)
@ -212,16 +199,6 @@ def get_vat_rate_for_country(country):
return 0 return 0
def get_ip_addresses(vm_id):
try:
vm_detail = VMDetail.objects.get(vm_id=vm_id)
return "%s <br/>%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: class HostingUtils:
@staticmethod @staticmethod
def clear_items_from_list(from_list, items_list): def clear_items_from_list(from_list, items_list):

View File

@ -3,7 +3,6 @@ import hashlib
import random import random
import ldap3 import ldap3
import logging import logging
import unicodedata
from django.conf import settings from django.conf import settings
@ -88,7 +87,7 @@ class LdapManager:
logger.debug("{uid} does not exist. Using it".format(uid=uidNumber)) logger.debug("{uid} does not exist. Using it".format(uid=uidNumber))
self._set_max_uid(uidNumber) self._set_max_uid(uidNumber)
try: try:
uid = user uid = user.encode("utf-8")
conn.add("uid={uid},{customer_dn}".format( conn.add("uid={uid},{customer_dn}".format(
uid=uid, customer_dn=settings.LDAP_CUSTOMER_DN uid=uid, customer_dn=settings.LDAP_CUSTOMER_DN
), ),
@ -102,7 +101,7 @@ class LdapManager:
"uidNumber": [str(uidNumber)], "uidNumber": [str(uidNumber)],
"gidNumber": [str(settings.LDAP_CUSTOMER_GROUP_ID)], "gidNumber": [str(settings.LDAP_CUSTOMER_GROUP_ID)],
"loginShell": ["/bin/bash"], "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"), "mail": email.encode("utf-8"),
"userPassword": [self._ssha_password( "userPassword": [self._ssha_password(
password.encode("utf-8") password.encode("utf-8")
@ -267,7 +266,7 @@ class LdapManager:
logger.error( logger.error(
"Error reading int value from {}. {}" "Error reading int value from {}. {}"
"Returning default value {} instead".format( "Returning default value {} instead".format(
settings.LDAP_MAX_UID_FILE_PATH, settings.LDAP_MAX_UID_PATH,
str(ve), str(ve),
settings.LDAP_DEFAULT_START_UID settings.LDAP_DEFAULT_START_UID
) )

File diff suppressed because one or more lines are too long

View File

@ -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),
),
]

View File

@ -13,11 +13,6 @@ class BaseBillingAddress(models.Model):
city = models.CharField(max_length=50) city = models.CharField(max_length=50)
postal_code = models.CharField(max_length=50) postal_code = models.CharField(max_length=50)
country = CountryField() 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: class Meta:
abstract = True abstract = True
@ -25,18 +20,10 @@ class BaseBillingAddress(models.Model):
class BillingAddress(BaseBillingAddress): class BillingAddress(BaseBillingAddress):
def __str__(self): def __str__(self):
if self.vat_number: return "%s, %s, %s, %s, %s" % (
return "%s, %s, %s, %s, %s, %s %s %s %s" % ( self.cardholder_name, self.street_address, self.city,
self.cardholder_name, self.street_address, self.city, self.postal_code, self.country
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
)
class UserBillingAddress(BaseBillingAddress): class UserBillingAddress(BaseBillingAddress):
@ -44,18 +31,10 @@ class UserBillingAddress(BaseBillingAddress):
current = models.BooleanField(default=True) current = models.BooleanField(default=True)
def __str__(self): def __str__(self):
if self.vat_number: return "%s, %s, %s, %s, %s" % (
return "%s, %s, %s, %s, %s, %s %s %s %s" % ( self.cardholder_name, self.street_address, self.city,
self.cardholder_name, self.street_address, self.city, self.postal_code, self.country
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
)
def to_dict(self): def to_dict(self):
return { return {
@ -64,7 +43,6 @@ class UserBillingAddress(BaseBillingAddress):
'City': self.city, 'City': self.city,
'Postal Code': self.postal_code, 'Postal Code': self.postal_code,
'Country': self.country, 'Country': self.country,
'VAT Number': self.vat_number
} }

View File

@ -1,9 +1,7 @@
import logging import logging
import re import re
import stripe import stripe
from django.conf import settings from django.conf import settings
from datacenterlight.models import StripePlan from datacenterlight.models import StripePlan
stripe.api_key = settings.STRIPE_API_PRIVATE_KEY stripe.api_key = settings.STRIPE_API_PRIVATE_KEY
@ -34,33 +32,32 @@ def handleStripeError(f):
logger.error(str(e)) logger.error(str(e))
return response return response
except stripe.error.RateLimitError as e: except stripe.error.RateLimitError as e:
logger.error(str(e))
response.update( response.update(
{'error': "Too many requests made to the API too quickly"}) {'error': "Too many requests made to the API too quickly"})
return response return response
except stripe.error.InvalidRequestError as e: except stripe.error.InvalidRequestError as e:
logger.error(str(e)) logger.error(str(e))
response.update({'error': str(e._message)}) response.update({'error': "Invalid parameters"})
return response return response
except stripe.error.AuthenticationError as e: except stripe.error.AuthenticationError as e:
# Authentication with Stripe's API failed # Authentication with Stripe's API failed
# (maybe you changed API keys recently) # (maybe you changed API keys recently)
logger.error(str(e)) logger.error(str(e))
response.update({'error': str(e)}) response.update({'error': common_message})
return response return response
except stripe.error.APIConnectionError as e: except stripe.error.APIConnectionError as e:
logger.error(str(e)) logger.error(str(e))
response.update({'error': str(e)}) response.update({'error': common_message})
return response return response
except stripe.error.StripeError as e: except stripe.error.StripeError as e:
# maybe send email # maybe send email
logger.error(str(e)) logger.error(str(e))
response.update({'error': str(e)}) response.update({'error': common_message})
return response return response
except Exception as e: except Exception as e:
# maybe send email # maybe send email
logger.error(str(e)) logger.error(str(e))
response.update({'error': str(e)}) response.update({'error': common_message})
return response return response
return handleProblems return handleProblems
@ -70,7 +67,7 @@ class StripeUtils(object):
CURRENCY = 'chf' CURRENCY = 'chf'
INTERVAL = 'month' INTERVAL = 'month'
SUCCEEDED_STATUS = 'succeeded' 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' STRIPE_NO_SUCH_PLAN = 'No such plan'
PLAN_EXISTS_ERROR_MSG = 'Plan {} exists already.\nCreating a local StripePlan now.' PLAN_EXISTS_ERROR_MSG = 'Plan {} exists already.\nCreating a local StripePlan now.'
PLAN_DOES_NOT_EXIST_ERROR_MSG = 'Plan {} does not exist.' PLAN_DOES_NOT_EXIST_ERROR_MSG = 'Plan {} does not exist.'
@ -83,31 +80,20 @@ class StripeUtils(object):
customer.save() customer.save()
@handleStripeError @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): set_as_default=False):
customer = stripe.Customer.retrieve(stripe_customer_id) customer = stripe.Customer.retrieve(stripe_customer_id)
stripe.PaymentMethod.attach( card = customer.sources.create(source=token)
id_payment_method,
customer=stripe_customer_id,
)
if set_as_default: if set_as_default:
customer.invoice_settings.default_payment_method = id_payment_method customer.default_source = card.id
customer.save() customer.save()
return True return True
@handleStripeError @handleStripeError
def dissociate_customer_card(self, stripe_customer_id, card_id): def dissociate_customer_card(self, stripe_customer_id, card_id):
customer = stripe.Customer.retrieve(stripe_customer_id) customer = stripe.Customer.retrieve(stripe_customer_id)
if card_id.startswith("pm"): card = customer.sources.retrieve(card_id)
logger.debug("PaymentMethod %s detached %s" % (card_id, card.delete()
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()
@handleStripeError @handleStripeError
def update_customer_card(self, customer_id, token): def update_customer_card(self, customer_id, token):
@ -199,24 +185,6 @@ class StripeUtils(object):
} }
return card_details 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): def check_customer(self, stripe_cus_api_id, user, token):
try: try:
customer = stripe.Customer.retrieve(stripe_cus_api_id) customer = stripe.Customer.retrieve(stripe_cus_api_id)
@ -236,11 +204,11 @@ class StripeUtils(object):
return customer return customer
@handleStripeError @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() == "": if name is None or name.strip() == "":
name = email name = email
customer = self.stripe.Customer.create( customer = self.stripe.Customer.create(
payment_method=id_payment_method, source=token,
description=name, description=name,
email=email email=email
) )
@ -297,17 +265,11 @@ class StripeUtils(object):
stripe_plan_db_obj = StripePlan.objects.create( stripe_plan_db_obj = StripePlan.objects.create(
stripe_plan_id=stripe_plan_id) stripe_plan_id=stripe_plan_id)
except stripe.error.InvalidRequestError as e: except stripe.error.InvalidRequestError as e:
logger.error(str(e)) if self.STRIPE_PLAN_ALREADY_EXISTS in str(e):
logger.error("error_code = %s" % str(e.__dict__))
if self.RESOURCE_ALREADY_EXISTS_ERROR_CODE in e.error.code:
logger.debug( logger.debug(
self.PLAN_EXISTS_ERROR_MSG.format(stripe_plan_id)) 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) 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 return stripe_plan_db_obj
@handleStripeError @handleStripeError
@ -335,15 +297,10 @@ class StripeUtils(object):
return return_value return return_value
@handleStripeError @handleStripeError
def subscribe_customer_to_plan(self, customer, plans, trial_end=None, def subscribe_customer_to_plan(self, customer, plans, trial_end=None):
coupon="", tax_rates=list(),
default_payment_method=""):
""" """
Subscribes the given customer to the list of given plans 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 customer: The stripe customer identifier
:param plans: A list of stripe plans. :param plans: A list of stripe plans.
:param trial_end: An integer representing when the Stripe subscription :param trial_end: An integer representing when the Stripe subscription
@ -357,17 +314,10 @@ class StripeUtils(object):
] ]
:return: The subscription StripeObject :return: The subscription StripeObject
""" """
logger.debug("Subscribing %s to plan %s : coupon = %s" % (
customer, str(plans), str(coupon)
))
subscription_result = self.stripe.Subscription.create( subscription_result = self.stripe.Subscription.create(
customer=customer, items=plans, trial_end=trial_end, customer=customer, items=plans, trial_end=trial_end
coupon=coupon,
default_tax_rates=tax_rates,
payment_behavior='allow_incomplete',
default_payment_method=default_payment_method
) )
logger.debug("Done subscribing")
return subscription_result return subscription_result
@handleStripeError @handleStripeError
@ -398,7 +348,7 @@ class StripeUtils(object):
@staticmethod @staticmethod
def get_stripe_plan_id(cpu, ram, ssd, version, app='dcl', hdd=None, 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 Returns the Stripe plan id string of the form
`dcl-v1-cpu-2-ram-5gb-ssd-10gb` based on the input parameters `dcl-v1-cpu-2-ram-5gb-ssd-10gb` based on the input parameters
@ -425,16 +375,13 @@ class StripeUtils(object):
plan=dcl_plan_string plan=dcl_plan_string
) )
if price is not None: if price is not None:
stripe_plan_id_string = '{}-{}chf'.format( stripe_plan_id_string_with_price = '{}-{}chf'.format(
stripe_plan_id_string, stripe_plan_id_string,
round(price, 2) round(price, 2)
) )
if excl_vat: return stripe_plan_id_string_with_price
stripe_plan_id_string = '{}-{}'.format( else:
stripe_plan_id_string, return stripe_plan_id_string
"excl_vat"
)
return stripe_plan_id_string
@staticmethod @staticmethod
def get_vm_config_from_stripe_id(stripe_id): def get_vm_config_from_stripe_id(stripe_id):
@ -463,27 +410,18 @@ class StripeUtils(object):
@staticmethod @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 Returns the Stripe plan name
:return: :return:
""" """
if excl_vat: return "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD, " \
return "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD, " \ "{price} CHF".format(
"{price} CHF Excl. VAT".format( cpu=cpu,
cpu=cpu, memory=memory,
memory=memory, disk_size=disk_size,
disk_size=disk_size, price=round(price, 2)
price=round(price, 2) )
)
else:
return "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD, " \
"{price} CHF".format(
cpu=cpu,
memory=memory,
disk_size=disk_size,
price=round(price, 2)
)
@handleStripeError @handleStripeError
def set_subscription_meta_data(self, subscription_id, meta_data): def set_subscription_meta_data(self, subscription_id, meta_data):
@ -496,78 +434,3 @@ class StripeUtils(object):
subscription = stripe.Subscription.retrieve(subscription_id) subscription = stripe.Subscription.retrieve(subscription_id)
subscription.metadata = meta_data subscription.metadata = meta_data
subscription.save() 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

View File

@ -228,7 +228,7 @@ class SSHKeyCreateView(FormView):
if self.request.user.is_authenticated(): if self.request.user.is_authenticated():
owner = self.request.user owner = self.request.user
manager = OpenNebulaManager( manager = OpenNebulaManager(
email=owner.username, email=owner.email,
password=owner.password password=owner.password
) )
keys_to_save = get_all_public_keys(self.request.user) keys_to_save = get_all_public_keys(self.request.user)

View File

@ -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,,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,,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,,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)

1 start_date stop_date territory_codes currency_code rate rate_type description
319
320
321

View File

View File

@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.

View File

@ -1,5 +0,0 @@
from django.apps import AppConfig
class WebhookConfig(AppConfig):
name = 'webhook'

View File

@ -1,81 +0,0 @@
import logging
import stripe
from django.core.management.base import BaseCommand
logger = logging.getLogger(__name__)
class Command(BaseCommand):
help = '''creates webhook with the supplied arguments and returns the
webhook secret
'''
def add_arguments(self, parser):
parser.add_argument(
'--webhook_endpoint',
help="The url of the webhook endpoint that accepts the events "
"from stripe",
dest="webhook_endpoint",
required=False
)
parser.add_argument('--events_csv', dest="events_csv", required=False)
parser.add_argument('--webhook_id', dest="webhook_id", required=False)
parser.add_argument('--create', dest='create', action='store_true')
parser.add_argument('--list', dest='list', action='store_true')
parser.add_argument('--delete', dest='delete', action='store_true')
def handle(self, *args, **options):
wep_exists = False
if options['list']:
logger.debug("Listing webhooks")
we_list = stripe.WebhookEndpoint.list(limit=100)
for wep in we_list.data:
msg = wep.id + " -- " + ",".join(wep.enabled_events)
logger.debug(msg)
self.stdout.write(
self.style.SUCCESS(msg)
)
elif options['delete']:
logger.debug("Deleting webhook")
if 'webhook_id' in options:
stripe.WebhookEndpoint.delete(options['webhook_id'])
msg = "Deleted " + options['webhook_id']
logger.debug(msg)
self.stdout.write(
self.style.SUCCESS(msg)
)
else:
msg = "Supply webhook_id to delete a webhook"
logger.debug(msg)
self.stdout.write(
self.style.SUCCESS(msg)
)
exit(0)
elif options['create']:
logger.debug("Creating webhook")
try:
we_list = stripe.WebhookEndpoint.list(limit=100)
for wep in we_list.data:
if set(wep.enabled_events) == set(options['events_csv'].split(",")):
if wep.url == options['webhook_endpoint']:
logger.debug("We have this webhook already")
wep_exists = True
break
if wep_exists is False:
logger.debug(
"No webhook exists for {} at {}. Creatting a new endpoint "
"now".format(
options['webhook_endpoint'], options['events_csv']
)
)
wep = stripe.WebhookEndpoint.create(
url=options['webhook_endpoint'],
enabled_events=options['events_csv'].split(",")
)
self.stdout.write(
self.style.SUCCESS('Creation successful. '
'webhook_secret = %s' % wep.secret)
)
except Exception as e:
print(" *** Error occurred. Details {}".format(str(e)))

View File

@ -1,3 +0,0 @@
from django.db import models
# Create your models here.

View File

@ -1,3 +0,0 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,318 +0,0 @@
import datetime
import logging
import json
import stripe
# Create your views here.
from django.conf import settings
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from datacenterlight.views import do_provisioning, do_provisioning_generic
from membership.models import StripeCustomer
from hosting.models import IncompleteSubscriptions, IncompletePaymentIntents
from utils.models import BillingAddress, UserBillingAddress
from utils.tasks import send_plain_email_task
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):
payload = request.body
event = None
try:
if 'HTTP_STRIPE_SIGNATURE' in request.META:
sig_header = request.META['HTTP_STRIPE_SIGNATURE']
else:
logger.error("No HTTP_STRIPE_SIGNATURE header")
# Invalid payload
return HttpResponse(status=400)
event = stripe.Webhook.construct_event(
payload, sig_header, settings.WEBHOOK_SECRET
)
except ValueError as e:
# Invalid payload
err_msg = "FAILURE handle_invoice_webhook: Invalid payload details"
err_body = "Details %s" % str(e)
return handle_error(err_msg, err_body)
except stripe.error.SignatureVerificationError as e:
# Invalid signature
err_msg = "FAILURE handle_invoice_webhook: SignatureVerificationError"
err_body = "Details %s" % str(e)
return handle_error(err_msg, err_body)
# Do something with event
logger.debug("Passed signature verification")
if event.type == "customer.tax_id.updated":
logger.debug("Webhook Event: customer.tax_id.updated")
tax_id_obj = event.data.object
logger.debug("Tax_id %s is %s" % (tax_id_obj.id,
tax_id_obj.verification.status))
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()
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
# The billing_reason attribute of the invoice object now can take the
# value of subscription_create, indicating that it is the first
# invoice of a subscription. For older API versions,
# billing_reason=subscription_create is represented as
# subscription_update.
if (invoice_obj.paid and
invoice_obj.billing_reason == "subscription_update"):
logger.debug("""invoice_obj.paid and
invoice_obj.billing_reason == subscription_update""")
logger.debug("Start provisioning")
try:
logger.debug("Looking for subscription %s" %
invoice_obj.subscription)
stripe_subscription_obj = stripe.Subscription.retrieve(
invoice_obj.subscription)
try:
incomplete_sub = IncompleteSubscriptions.objects.get(
subscription_id=invoice_obj.subscription)
request = ""
soc = ""
card_details_response = ""
gp_details = ""
template = ""
specs = ""
billing_address_data = ""
if incomplete_sub.request:
request = json.loads(incomplete_sub.request)
if incomplete_sub.specs:
specs = json.loads(incomplete_sub.specs)
if incomplete_sub.stripe_onetime_charge:
soc = json.loads(incomplete_sub.stripe_onetime_charge)
if incomplete_sub.gp_details:
gp_details = json.loads(incomplete_sub.gp_details)
if incomplete_sub.card_details_response:
card_details_response = json.loads(
incomplete_sub.card_details_response)
if incomplete_sub.template:
template = json.loads(
incomplete_sub.template)
if incomplete_sub.billing_address_data:
billing_address_data = json.loads(
incomplete_sub.billing_address_data)
logger.debug("*******")
logger.debug(str(incomplete_sub))
logger.debug("*******")
logger.debug("1*******")
logger.debug(request)
logger.debug("2*******")
logger.debug(card_details_response)
logger.debug("3*******")
logger.debug(soc)
logger.debug("4*******")
logger.debug(gp_details)
logger.debug("5*******")
logger.debug(template)
logger.debug("6*******")
do_provisioning(
request=request,
stripe_api_cus_id=incomplete_sub.stripe_api_cus_id,
card_details_response=card_details_response,
stripe_subscription_obj=stripe_subscription_obj,
stripe_onetime_charge=soc,
gp_details=gp_details,
specs=specs,
vm_template_id=incomplete_sub.vm_template_id,
template=template,
billing_address_data=billing_address_data,
real_request=None
)
except IncompleteSubscriptions.DoesNotExist as ex:
logger.error(str(ex))
except IncompleteSubscriptions.MultipleObjectsReturned as ex:
logger.error(str(ex))
email_data = {
'subject': "IncompleteSubscriptions error",
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
'to': settings.DCL_ERROR_EMAILS_TO_LIST,
'body': "Response = %s" % str(ex),
}
send_plain_email_task.delay(email_data)
except Exception as ex:
logger.error(str(ex))
email_data = {
'subject': "invoice.paid Webhook error",
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
'to': settings.DCL_ERROR_EMAILS_TO_LIST,
'body': "Response = %s" % str(ex),
}
send_plain_email_task.delay(email_data)
elif event.type == 'invoice.payment_failed':
invoice_obj = event.data.object
logger.debug("Webhook Event: invoice.payment_failed")
logger.debug("invoice_obj %s " % str(invoice_obj))
if (invoice_obj.payment_failed and
invoice_obj.billing_reason == "subscription_update"):
logger.debug("Payment failed, inform the users")
elif event.type == 'payment_intent.succeeded':
payment_intent_obj = event.data.object
logger.debug("Webhook Event: payment_intent.succeeded")
logger.debug("payment_intent_obj %s " % str(payment_intent_obj))
try:
logger.debug("Looking for IncompletePaymentIntents %s " %
payment_intent_obj.id)
incomplete_pm = IncompletePaymentIntents.objects.get(
payment_intent_id=payment_intent_obj.id)
logger.debug("incomplete_pm = %s" % str(incomplete_pm.__dict__))
request = ""
soc = ""
card_details_response = ""
gp_details = ""
template = ""
billing_address_data = ""
if incomplete_pm.request:
request = json.loads(incomplete_pm.request)
logger.debug("request = %s" % str(request))
if incomplete_pm.stripe_charge_id:
soc = incomplete_pm.stripe_charge_id
logger.debug("stripe_onetime_charge = %s" % str(soc))
if incomplete_pm.gp_details:
gp_details = json.loads(incomplete_pm.gp_details)
logger.debug("gp_details = %s" % str(gp_details))
if incomplete_pm.card_details_response:
card_details_response = json.loads(
incomplete_pm.card_details_response)
logger.debug("card_details_response = %s" % str(card_details_response))
if incomplete_pm.billing_address_data:
billing_address_data = json.loads(
incomplete_pm.billing_address_data)
logger.debug("billing_address_data = %s" % str(billing_address_data))
logger.debug("1*******")
logger.debug(request)
logger.debug("2*******")
logger.debug(card_details_response)
logger.debug("3*******")
logger.debug(soc)
logger.debug("4*******")
logger.debug(gp_details)
logger.debug("5*******")
logger.debug(template)
logger.debug("6*******")
logger.debug(billing_address_data)
incomplete_pm.completed_at = datetime.datetime.now()
charges = ""
if len(payment_intent_obj.charges.data) > 0:
for d in payment_intent_obj.charges.data:
if charges == "":
charges = "%s" % d.id
else:
charges = "%s,%s" % (charges, d.id)
logger.debug("Charge ids = %s" % charges)
incomplete_pm.stripe_charge_id=charges
do_provisioning_generic(
request=request,
stripe_api_cus_id=incomplete_pm.stripe_api_cus_id,
card_details_response=card_details_response,
stripe_subscription_id=None,
stripe_charge_id=charges,
gp_details=gp_details,
billing_address_data=billing_address_data
)
incomplete_pm.save()
except IncompletePaymentIntents.DoesNotExist as ex:
logger.error(str(ex))
except (IncompletePaymentIntents.MultipleObjectsReturned,
Exception) as ex:
logger.error(str(ex))
email_data = {
'subject': "IncompletePaymentIntents error",
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
'to': settings.DCL_ERROR_EMAILS_TO_LIST,
'body': "Response = %s" % str(ex),
}
send_plain_email_task.delay(email_data)
else:
logger.error("Unhandled event : " + event.type)
return HttpResponse(status=200)
def handle_error(error_msg, error_body):
logger.error("%s -- %s" % (error_msg, error_body))
email_to_admin_data = {
'subject': error_msg,
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
'to': [settings.ADMIN_EMAIL],
'body': error_body,
}
send_plain_email_task.delay(email_to_admin_data)
return HttpResponse(status=400)