Compare commits
2 commits
master
...
bugfix/lda
Author | SHA1 | Date | |
---|---|---|---|
|
ddd90e650b | ||
|
e46729ee04 |
74 changed files with 1070 additions and 3732 deletions
101
Changelog
101
Changelog
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 "
|
||||||
|
|
|
@ -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"))
|
|
|
@ -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)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
|
@ -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)'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -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),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -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([
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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) {
|
||||||
|
|
|
@ -77,7 +77,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
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");
|
||||||
|
@ -90,7 +89,6 @@
|
||||||
$(".dropdown-menu > li > a").css("color", "#fff");
|
$(".dropdown-menu > li > a").css("color", "#fff");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
_navScroll();
|
_navScroll();
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,52 +92,6 @@ 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,
|
|
||||||
template)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(str(e))
|
|
||||||
try:
|
|
||||||
retry_task(self)
|
|
||||||
except MaxRetriesExceededError:
|
|
||||||
msg_text = 'Finished {} retries for create_vm_task'.format(
|
|
||||||
self.request.retries)
|
|
||||||
logger.error(msg_text)
|
|
||||||
# Try sending email and stop
|
|
||||||
email_data = {
|
|
||||||
'subject': '{} CELERY TASK ERROR: {}'.format(settings.DCL_TEXT,
|
|
||||||
msg_text),
|
|
||||||
'from_email': current_task.request.hostname,
|
|
||||||
'to': settings.DCL_ERROR_EMAILS_TO_LIST,
|
|
||||||
'body': ',\n'.join(str(i) for i in self.request.args)
|
|
||||||
}
|
|
||||||
email = EmailMessage(**email_data)
|
|
||||||
email.send()
|
|
||||||
return
|
|
||||||
|
|
||||||
return vm_id
|
|
||||||
|
|
||||||
|
|
||||||
def handle_metadata_and_emails(order_id, vm_id, manager, user, specs,
|
|
||||||
template):
|
|
||||||
"""
|
|
||||||
Handle's setting up of the metadata in Stripe and database and sending of
|
|
||||||
emails to the user after VM creation
|
|
||||||
|
|
||||||
:param order_id: the hosting order id
|
|
||||||
:param vm_id: the id of the vm created
|
|
||||||
:param manager: the OpenNebula Manager instance
|
|
||||||
:param user: the user's dict passed to the celery task
|
|
||||||
:param specs: the specification's dict passed to the celery task
|
|
||||||
:param template: the template dict passed to the celery task
|
|
||||||
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
|
|
||||||
custom_user = CustomUser.objects.get(email=user.get('email'))
|
|
||||||
final_price = (
|
|
||||||
specs.get('total_price') if 'total_price' in specs
|
|
||||||
else specs.get('price')
|
|
||||||
)
|
|
||||||
# Update HostingOrder with the created vm_id
|
# Update HostingOrder with the created vm_id
|
||||||
hosting_order = HostingOrder.objects.filter(id=order_id).first()
|
hosting_order = HostingOrder.objects.filter(id=order_id).first()
|
||||||
error_msg = None
|
error_msg = None
|
||||||
|
@ -214,7 +173,8 @@ def handle_metadata_and_emails(order_id, vm_id, manager, user, specs,
|
||||||
context = {
|
context = {
|
||||||
'base_url': "{0}://{1}".format(user.get('request_scheme'),
|
'base_url': "{0}://{1}".format(user.get('request_scheme'),
|
||||||
user.get('request_host')),
|
user.get('request_host')),
|
||||||
'order_url': reverse('hosting:invoices'),
|
'order_url': reverse('hosting:orders',
|
||||||
|
kwargs={'pk': order_id}),
|
||||||
'page_header': _(
|
'page_header': _(
|
||||||
'Your New VM %(vm_name)s at Data Center Light') % {
|
'Your New VM %(vm_name)s at Data Center Light') % {
|
||||||
'vm_name': vm.get('name')},
|
'vm_name': vm.get('name')},
|
||||||
|
@ -234,3 +194,24 @@ def handle_metadata_and_emails(order_id, vm_id, manager, user, specs,
|
||||||
logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id))
|
logger.debug("New VM ID is {vm_id}".format(vm_id=vm_id))
|
||||||
if vm_id > 0:
|
if vm_id > 0:
|
||||||
get_or_create_vm_detail(custom_user, manager, vm_id)
|
get_or_create_vm_detail(custom_user, manager, vm_id)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(str(e))
|
||||||
|
try:
|
||||||
|
retry_task(self)
|
||||||
|
except MaxRetriesExceededError:
|
||||||
|
msg_text = 'Finished {} retries for create_vm_task'.format(
|
||||||
|
self.request.retries)
|
||||||
|
logger.error(msg_text)
|
||||||
|
# Try sending email and stop
|
||||||
|
email_data = {
|
||||||
|
'subject': '{} CELERY TASK ERROR: {}'.format(settings.DCL_TEXT,
|
||||||
|
msg_text),
|
||||||
|
'from_email': current_task.request.hostname,
|
||||||
|
'to': settings.DCL_ERROR_EMAILS_TO_LIST,
|
||||||
|
'body': ',\n'.join(str(i) for i in self.request.args)
|
||||||
|
}
|
||||||
|
email = EmailMessage(**email_data)
|
||||||
|
email.send()
|
||||||
|
return
|
||||||
|
|
||||||
|
return vm_id
|
||||||
|
|
|
@ -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>
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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' %}
|
||||||
|
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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,113 +48,45 @@
|
||||||
<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>
|
<strong>{% trans "Product" %}:</strong>
|
||||||
{{ generic_payment_details.product_name }}
|
{{ generic_payment_details.product_name }}
|
||||||
</p>
|
</p>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
{% if generic_payment_details.vat_rate > 0 %}
|
||||||
|
<p>
|
||||||
|
<span>{% trans "Price" %}: </span>
|
||||||
|
<strong class="pull-right">CHF {{generic_payment_details.amount_before_vat|floatformat:2|intcomma}}</strong>
|
||||||
|
</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 %}
|
{% if generic_payment_details.description %}
|
||||||
<p>
|
<p>
|
||||||
<strong>{% trans "Description" %}: </strong>
|
<span>{% trans "Description" %}: </span>
|
||||||
<span class="pull-right">{{generic_payment_details.description}}</span>
|
<strong class="pull-right">{{generic_payment_details.description}}</strong>
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if generic_payment_details.recurring %}
|
{% if generic_payment_details.recurring %}
|
||||||
<p>
|
<p>
|
||||||
<strong>{% trans "Recurring" %}: </strong>
|
<span>{% trans "Recurring" %}: </span>
|
||||||
<span class="pull-right">Yes</span>
|
<strong class="pull-right">Yes</strong>
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</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>
|
|
||||||
<strong class="text-uppercase">{% trans "Price Before VAT" %}</strong>
|
|
||||||
<strong class="pull-right">{{generic_payment_details.amount_before_vat|floatformat:2|intcomma}} CHF</strong>
|
|
||||||
</p>
|
|
||||||
</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 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>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>
|
<p>
|
||||||
|
@ -180,7 +94,7 @@
|
||||||
{{ 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,74 +111,37 @@
|
||||||
<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 %}
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<div class="subtotal-price">
|
||||||
|
{% if vm.vat > 0 %}
|
||||||
<p>
|
<p>
|
||||||
<strong class="text-uppercase">{% trans "Price Before VAT" %}</strong>
|
<strong class="text-lg">{% trans "Subtotal" %} </strong>
|
||||||
<strong class="pull-right">{{vm.price|floatformat:2|intcomma}} CHF</strong>
|
<strong class="pull-right">{{vm.price|floatformat:2|intcomma}} CHF</strong>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
<p>
|
||||||
<div class="col-sm-12">
|
<small>{% trans "VAT for" %} {{vm.vat_country}} ({{vm.vat_percent}}%) : </small>
|
||||||
<hr class="thin-hr">
|
<strong class="pull-right">{{vm.vat|floatformat:2|intcomma}} CHF</strong>
|
||||||
</div>
|
</p>
|
||||||
<div class="col-sm-9">
|
{% endif %}
|
||||||
<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 %}
|
{% if vm.discount.amount > 0 %}
|
||||||
<div class="row">
|
<p class="text-primary">
|
||||||
<div class="col-md-4 col-sm-4 col-xs-4">
|
{%trans "Discount" as discount_name %}
|
||||||
<p><span>{{vm.discount.name}}</span></p>
|
<strong>{{ vm.discount.name|default:discount_name }} </strong>
|
||||||
</div>
|
<strong class="pull-right">- {{ vm.discount.amount }} CHF</strong>
|
||||||
<div class="col-md-3 col-sm-3 col-xs-4">
|
</p>
|
||||||
<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 %}
|
{% 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><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>
|
||||||
<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">
|
{% endif %}
|
||||||
<strong class="text-uppercase align-center">{% trans "Your Price in Total" %}</strong>
|
<div class="col-sm-6">
|
||||||
<strong class="total-price pull-right">{{vm.total_price|floatformat:2|intcomma}} CHF</strong>
|
<p class="total-price">
|
||||||
|
<strong>{% trans "Total" %} </strong>
|
||||||
|
<strong class="pull-right">{{vm.total_price|floatformat:2|intcomma}} CHF</strong>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -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%}
|
|
@ -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 — %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
|
|
||||||
|
|
|
@ -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
|
@ -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'),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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')),
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -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'"],
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -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'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -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),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -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),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -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),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -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),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import decimal
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
@ -81,18 +82,11 @@ 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:
|
|
||||||
return round(float(self.product_price), 2)
|
|
||||||
else:
|
|
||||||
VAT = vat_rate if vat_rate is not None else self.product_vat
|
VAT = vat_rate if vat_rate is not None else self.product_vat
|
||||||
return round(
|
return round(
|
||||||
float(self.product_price) + float(self.product_price) * float(VAT), 2
|
float(self.product_price) + float(self.product_price) * float(VAT), 2
|
||||||
|
@ -169,10 +163,6 @@ 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.payment_method_details.card.last4
|
|
||||||
self.cc_brand = stripe_charge.payment_method_details.card.brand
|
|
||||||
else:
|
|
||||||
self.last4 = stripe_charge.source.last4
|
self.last4 = stripe_charge.source.last4
|
||||||
self.cc_brand = stripe_charge.source.brand
|
self.cc_brand = stripe_charge.source.brand
|
||||||
self.save()
|
self.save()
|
||||||
|
@ -676,10 +666,6 @@ 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"):
|
|
||||||
# card is a payment method
|
|
||||||
cu.invoice_settings.default_payment_method = stripe_source_id
|
|
||||||
else:
|
|
||||||
cu.default_source = stripe_source_id
|
cu.default_source = stripe_source_id
|
||||||
cu.save()
|
cu.save()
|
||||||
UserCardDetail.save_default_card_local(
|
UserCardDetail.save_default_card_local(
|
||||||
|
@ -739,44 +725,3 @@ class VATRates(AssignPermissionsMixin, models.Model):
|
||||||
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()
|
|
|
@ -3,9 +3,3 @@
|
||||||
.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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,9 +84,6 @@ $(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) {
|
|
||||||
|
|
||||||
} else {
|
|
||||||
var element_style = {
|
var element_style = {
|
||||||
fonts: [{
|
fonts: [{
|
||||||
family: 'lato-light',
|
family: 'lato-light',
|
||||||
|
@ -150,7 +147,6 @@ $(document).ready(function () {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var submit_form_btn = $('#payment_button_with_creditcard');
|
var submit_form_btn = $('#payment_button_with_creditcard');
|
||||||
submit_form_btn.on('click', submit_payment);
|
submit_form_btn.on('click', submit_payment);
|
||||||
|
@ -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>');
|
||||||
|
|
|
@ -92,7 +92,6 @@ $(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({
|
||||||
|
@ -108,27 +107,6 @@ $(document).ready(function() {
|
||||||
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');
|
||||||
if (data.showSCA) {
|
|
||||||
console.log("Show SCA");
|
|
||||||
var stripe = Stripe(data.STRIPE_PUBLISHABLE_KEY);
|
|
||||||
stripe.confirmCardPayment(data.payment_intent_secret).then(function (result) {
|
|
||||||
if (result.error) {
|
|
||||||
// Display error.message in your UI.
|
|
||||||
modal_btn.attr('href', data.error.redirect).removeClass('hide');
|
|
||||||
fa_icon.attr('class', 'fa fa-close');
|
|
||||||
modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide');
|
|
||||||
$('#createvm-modal-title').text(data.error.msg_title);
|
|
||||||
$('#createvm-modal-body').html(data.error.msg_body);
|
|
||||||
} else {
|
|
||||||
// The payment has succeeded. Display a success message.
|
|
||||||
modal_btn.attr('href', data.success.redirect).removeClass('hide');
|
|
||||||
fa_icon.attr('class', 'checkmark');
|
|
||||||
$('#createvm-modal-title').text(data.success.msg_title);
|
|
||||||
$('#createvm-modal-body').html(data.success.msg_body);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$('#3Dsecure-modal').show();
|
|
||||||
} else {
|
|
||||||
$('#createvm-modal-title').text(data.msg_title);
|
$('#createvm-modal-title').text(data.msg_title);
|
||||||
$('#createvm-modal-body').html(data.msg_body);
|
$('#createvm-modal-body').html(data.msg_body);
|
||||||
if (data.redirect) {
|
if (data.redirect) {
|
||||||
|
@ -142,7 +120,6 @@ $(document).ready(function() {
|
||||||
fa_icon.attr('class', 'fa fa-close');
|
fa_icon.attr('class', 'fa fa-close');
|
||||||
modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide');
|
modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
error: function (xmlhttprequest, textstatus, message) {
|
error: function (xmlhttprequest, textstatus, message) {
|
||||||
fa_icon = $('.modal-icon > .fa');
|
fa_icon = $('.modal-icon > .fa');
|
||||||
|
@ -156,53 +133,9 @@ $(document).ready(function() {
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
create_vm_form.submit(placeOrderPaymentIntent);
|
|
||||||
function placeOrderPaymentIntent(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
var stripe = Stripe(window.stripeKey);
|
|
||||||
stripe.confirmCardPayment(
|
|
||||||
window.paymentIntentSecret,
|
|
||||||
{
|
|
||||||
payment_method: window.pm_id
|
|
||||||
}
|
|
||||||
).then(function(result) {
|
|
||||||
window.result = result;
|
|
||||||
fa_icon = $('.modal-icon > .fa');
|
|
||||||
modal_btn = $('#createvm-modal-done-btn');
|
|
||||||
if (result.error) {
|
|
||||||
// Display error.message in your UI.
|
|
||||||
modal_btn.attr('href', error_url).removeClass('hide');
|
|
||||||
fa_icon.attr('class', 'fa fa-close');
|
|
||||||
modal_btn.attr('class', '').addClass('btn btn-danger btn-ok btn-wide');
|
|
||||||
$('#createvm-modal-title').text(error_title);
|
|
||||||
$('#createvm-modal-body').html(result.error.message + " " + error_msg);
|
|
||||||
} else {
|
|
||||||
// The payment has succeeded
|
|
||||||
// Display a success message
|
|
||||||
modal_btn.attr('href', success_url).removeClass('hide');
|
|
||||||
fa_icon.attr('class', 'checkmark');
|
|
||||||
$('#createvm-modal-title').text(success_title);
|
|
||||||
$('#createvm-modal-body').html(success_msg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$('#createvm-modal').on('hidden.bs.modal', function () {
|
$('#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 () {
|
||||||
|
|
|
@ -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' %}">
|
||||||
|
|
|
@ -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 %}
|
|
@ -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>
|
||||||
|
|
|
@ -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' }} — {{ 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}}">«</a></li>
|
|
||||||
{% else %}
|
|
||||||
<li><a href="?page={{ invs.previous_page_number }}">«</a></li>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<li class="disabled"><span>«</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}}">»</a></li>
|
|
||||||
{% else %}
|
|
||||||
<li><a href="?page={{ invs.next_page_number }}">»</a></li>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<li class="disabled"><span>»</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>
|
|
||||||
<th>{% trans "Date" %}</th>
|
|
||||||
<th>{% trans "Amount" %}</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for ho, stripe_charge_data in invs_charge %}
|
|
||||||
<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}}">«</a></li>
|
|
||||||
{% else %}
|
|
||||||
<li><a href="?page={{ invs_charge.previous_page_number }}">«</a></li>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
<span class="page-current">
|
||||||
<li class="disabled"><span>«</span></li>
|
{% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}.
|
||||||
{% endif %}
|
</span>
|
||||||
{% for i in invs_charge.paginator.page_range %}
|
{% if page_obj.has_next %}
|
||||||
{% if invs_charge.number == i %}
|
<a href="{{request.path}}?page={{ page_obj.next_page_number }}">{% trans "next" %}</a>
|
||||||
<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}}">»</a></li>
|
|
||||||
{% else %}
|
|
||||||
<li><a href="?page={{ invs_charge.next_page_number }}">»</a></li>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<li class="disabled"><span>»</span></li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
384
hosting/views.py
384
hosting/views.py
|
@ -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,128 +573,81 @@ 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:
|
||||||
|
card = UserCardDetail.objects.get(pk=self.kwargs.get('pk'))
|
||||||
|
if (request.user.has_perm(self.permission_required[0], card)
|
||||||
|
and
|
||||||
|
request.user
|
||||||
|
.stripecustomer
|
||||||
|
.usercarddetail_set
|
||||||
|
.count() > 1):
|
||||||
|
if card.card_id is not None:
|
||||||
stripe_utils = StripeUtils()
|
stripe_utils = StripeUtils()
|
||||||
stripe_utils.dissociate_customer_card(
|
stripe_utils.dissociate_customer_card(
|
||||||
request.user.stripecustomer.stripe_id,
|
request.user.stripecustomer.stripe_id,
|
||||||
card
|
card.card_id
|
||||||
)
|
)
|
||||||
|
if card.preferred:
|
||||||
|
UserCardDetail.set_default_card_from_stripe(
|
||||||
|
request.user.stripecustomer.stripe_id
|
||||||
|
)
|
||||||
|
card.delete()
|
||||||
msg = _("Card deassociation successful")
|
msg = _("Card deassociation successful")
|
||||||
messages.add_message(request, messages.SUCCESS, msg)
|
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 = ''
|
|
||||||
billing_address.vat_number_validated_on = None
|
|
||||||
billing_address.vat_validation_status = ''
|
|
||||||
billing_address.save()
|
|
||||||
vat_number = billing_address_user_form.cleaned_data.get(
|
|
||||||
'vat_number').strip()
|
|
||||||
logger.debug("Vat number = %s" % vat_number)
|
|
||||||
if vat_number:
|
|
||||||
try:
|
|
||||||
stripe_customer = request.user.stripecustomer
|
|
||||||
except StripeCustomer.DoesNotExist as dne:
|
|
||||||
logger.debug(
|
|
||||||
"User %s does not have a stripecustomer. "
|
|
||||||
"Creating one." % request.user.email)
|
|
||||||
stripe_customer = StripeCustomer.get_or_create(
|
|
||||||
email=request.user.email,
|
|
||||||
token=None)
|
|
||||||
request.user.stripecustomer = stripe_customer
|
|
||||||
request.user.save()
|
|
||||||
validate_result = validate_vat_number(
|
|
||||||
stripe_customer_id=request.user.stripecustomer.stripe_id,
|
|
||||||
billing_address_id=billing_address.id,
|
|
||||||
is_user_ba=True
|
|
||||||
)
|
|
||||||
logger.debug("validate_result = %s" % str(validate_result))
|
|
||||||
if 'error' in validate_result and validate_result['error']:
|
|
||||||
messages.add_message(
|
|
||||||
request, messages.ERROR,
|
|
||||||
"VAT Number validation error: %s" % validate_result["error"],
|
|
||||||
extra_tags='error'
|
|
||||||
)
|
|
||||||
billing_address = current_billing_address
|
|
||||||
if billing_address:
|
|
||||||
billing_address.save()
|
|
||||||
email_data = {
|
|
||||||
'subject': "%s updated VAT number to %s but failed" %
|
|
||||||
(request.user.email, vat_number),
|
|
||||||
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
|
|
||||||
'to': settings.DCL_ERROR_EMAILS_TO_LIST,
|
|
||||||
'body': "\n".join(
|
|
||||||
["%s=%s" % (k, v) for (k, v) in
|
|
||||||
validate_result.items()]),
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
email_data = {
|
|
||||||
'subject': "%s updated VAT number to %s" % (
|
|
||||||
request.user.email, vat_number
|
|
||||||
),
|
|
||||||
'from_email': settings.DCL_SUPPORT_FROM_ADDRESS,
|
|
||||||
'to': settings.DCL_ERROR_EMAILS_TO_LIST,
|
|
||||||
'body': "\n".join(
|
|
||||||
["%s=%s" % (k, v) for (k, v) in
|
|
||||||
validate_result.items()]),
|
|
||||||
}
|
|
||||||
msg = _("Billing address updated successfully")
|
|
||||||
messages.add_message(request, messages.SUCCESS, msg)
|
|
||||||
send_plain_email_task.delay(email_data)
|
|
||||||
else:
|
|
||||||
msg = _("Billing address updated successfully")
|
msg = _("Billing address updated successfully")
|
||||||
messages.add_message(request, messages.SUCCESS, msg)
|
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']
|
||||||
|
if UserCardDetail.get_user_card_details(stripe_customer, card):
|
||||||
|
msg = _('You seem to have already added this card')
|
||||||
|
messages.add_message(request, messages.ERROR, msg)
|
||||||
|
else:
|
||||||
acc_result = stripe_utils.associate_customer_card(
|
acc_result = stripe_utils.associate_customer_card(
|
||||||
request.user.stripecustomer.stripe_id,
|
request.user.stripecustomer.stripe_id, token
|
||||||
id_payment_method,
|
|
||||||
set_as_default=True
|
|
||||||
)
|
)
|
||||||
if acc_result['response_object'] is None:
|
if acc_result['response_object'] is None:
|
||||||
msg = _(
|
msg = _(
|
||||||
|
@ -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:
|
||||||
try:
|
mhbs = MonthlyHostingBill.objects.filter(
|
||||||
invs = stripe.Invoice.list(
|
customer__user=self.request.user
|
||||||
customer=self.request.user.stripecustomer.stripe_id,
|
|
||||||
count=100
|
|
||||||
)
|
)
|
||||||
paginator = Paginator(invs.data, 10)
|
ips_dict = {}
|
||||||
|
line_item_period_dict = {}
|
||||||
|
for mhb in mhbs:
|
||||||
try:
|
try:
|
||||||
invs_page = paginator.page(page)
|
vm_detail = VMDetail.objects.get(vm_id=mhb.order.vm_id)
|
||||||
except PageNotAnInteger:
|
ips_dict[mhb.invoice_number] = [vm_detail.ipv6, vm_detail.ipv4]
|
||||||
invs_page = paginator.page(1)
|
all_line_items = HostingBillLineItem.objects.filter(monthly_hosting_bill=mhb)
|
||||||
except EmptyPage:
|
for line_item in all_line_items:
|
||||||
invs_page = paginator.page(paginator.num_pages)
|
if line_item.get_item_detail_str() != "":
|
||||||
hosting_orders = HostingOrder.objects.filter(
|
line_item_period_dict[mhb.invoice_number] = {
|
||||||
customer=self.request.user.stripecustomer).filter(
|
"period_start": line_item.period_start,
|
||||||
Q(subscription_id=None) | Q(subscription_id='')
|
"period_end": line_item.period_end
|
||||||
).order_by('-created_at')
|
}
|
||||||
stripe_chgs = []
|
break
|
||||||
for ho in hosting_orders:
|
except VMDetail.DoesNotExist as dne:
|
||||||
stripe_chgs.append(
|
ips_dict[mhb.invoice_number] = ['--']
|
||||||
{ho: stripe.Charge.retrieve(ho.stripe_charge_id)})
|
logger.debug("VMDetail for {} doesn't exist".format(
|
||||||
paginator_charges = Paginator(stripe_chgs, 10)
|
mhb.order.vm_id
|
||||||
try:
|
))
|
||||||
invs_page_charges = paginator_charges.page(page)
|
context['ips'] = ips_dict
|
||||||
except PageNotAnInteger:
|
context['period'] = line_item_period_dict
|
||||||
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()
|
||||||
|
|
|
@ -233,15 +233,6 @@ 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:
|
|
||||||
# User exists already in LDAP, but with a dummy credential
|
|
||||||
# We are here implies that the user has successfully
|
|
||||||
# authenticated against Django db, and a corresponding user
|
|
||||||
# exists in LDAP.
|
|
||||||
# We just update the LDAP credentials once again, assuming it
|
|
||||||
# was set to a dummy value while migrating users from Django to
|
|
||||||
# LDAP
|
|
||||||
ldap_manager.change_password(self.username, password)
|
|
||||||
self.in_ldap = True
|
self.in_ldap = True
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
if user.check_password(password):
|
||||||
return user
|
return user
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_user(self, user_id):
|
||||||
|
try:
|
||||||
|
return CustomUser.objects.get(pk=user_id)
|
||||||
|
except CustomUser.DoesNotExist:
|
||||||
|
return None
|
||||||
|
|
|
@ -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'),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
@ -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),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -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,14 +20,6 @@ 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, %s %s %s %s" % (
|
|
||||||
self.cardholder_name, self.street_address, self.city,
|
|
||||||
self.postal_code, self.country, self.vat_number,
|
|
||||||
self.stripe_tax_id, self.vat_number_validated_on,
|
|
||||||
self.vat_validation_status
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return "%s, %s, %s, %s, %s" % (
|
return "%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
|
||||||
|
@ -44,14 +31,6 @@ 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, %s %s %s %s" % (
|
|
||||||
self.cardholder_name, self.street_address, self.city,
|
|
||||||
self.postal_code, self.country, self.vat_number,
|
|
||||||
self.stripe_tax_id, self.vat_number_validated_on,
|
|
||||||
self.vat_validation_status
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return "%s, %s, %s, %s, %s" % (
|
return "%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
|
||||||
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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,29 +80,18 @@ 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"):
|
|
||||||
logger.debug("PaymentMethod %s detached %s" % (card_id,
|
|
||||||
stripe_customer_id))
|
|
||||||
pm = stripe.PaymentMethod.retrieve(card_id)
|
|
||||||
stripe.PaymentMethod.detach(card_id)
|
|
||||||
pm.delete()
|
|
||||||
else:
|
|
||||||
logger.debug("card %s detached %s" % (card_id, stripe_customer_id))
|
|
||||||
card = customer.sources.retrieve(card_id)
|
card = customer.sources.retrieve(card_id)
|
||||||
card.delete()
|
card.delete()
|
||||||
|
|
||||||
|
@ -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,15 +375,12 @@ 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,
|
|
||||||
"excl_vat"
|
|
||||||
)
|
|
||||||
return stripe_plan_id_string
|
return stripe_plan_id_string
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -463,20 +410,11 @@ 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, " \
|
|
||||||
"{price} CHF Excl. VAT".format(
|
|
||||||
cpu=cpu,
|
|
||||||
memory=memory,
|
|
||||||
disk_size=disk_size,
|
|
||||||
price=round(price, 2)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD, " \
|
return "{cpu} Cores, {memory} GB RAM, {disk_size} GB SSD, " \
|
||||||
"{price} CHF".format(
|
"{price} CHF".format(
|
||||||
cpu=cpu,
|
cpu=cpu,
|
||||||
|
@ -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
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,3 +0,0 @@
|
||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
# Register your models here.
|
|
|
@ -1,5 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class WebhookConfig(AppConfig):
|
|
||||||
name = 'webhook'
|
|
|
@ -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)))
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
# Create your models here.
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
318
webhook/views.py
318
webhook/views.py
|
@ -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)
|
|
Loading…
Reference in a new issue