Merge branch 'master' into feature/6561/invoices-webhook
This commit is contained in:
commit
6873d901c9
25 changed files with 892 additions and 182 deletions
34
Changelog
34
Changelog
|
@ -1,3 +1,37 @@
|
|||
2.6.10: 2019-11-16
|
||||
* translation: Add DE translations for features in 2.6.{8,9} by moep (MR!719)
|
||||
2.6.9: 2019-11-15
|
||||
* feature: Allow creating yearly subscriptions for Generic Products (MR!718)
|
||||
Notes for deployment:
|
||||
- do a db migrate for new column added to Generic Product model
|
||||
./manage.py migrate hosting
|
||||
2.6.8: 2019-11-15
|
||||
* feature: [EU VAT] Add EU VAT feature for generic products (MR!717)
|
||||
Notes for deployment:
|
||||
- do a db migrate a to create VATRates table
|
||||
./manage.py migrate hosting
|
||||
- load vat_rates.csv
|
||||
./manage.py import_vat_rates vat_rates.csv
|
||||
2.6.7: 2019-11-04
|
||||
* bugfix: [admin] Improve dumpuser: show proper dates + bugfix
|
||||
* bugfix: [admin] Improve fetch_stripe_bills:
|
||||
- fix wrong assigment of string to num_invoice_created
|
||||
variable,
|
||||
- return None (do not handle the case) if we don't have an
|
||||
order
|
||||
* bugfix: [admin] Improve deleteuser: do not delete order, bill and vm_detail
|
||||
2.6.6: 2019-11-04
|
||||
* feature: [admin] Add dumpuser management command that dumps a user's data in json (MR!716)
|
||||
2.6.5: 2019-09-24
|
||||
* #7169: [hosting] Fix server error while vm terminate takes longer than 30 seconds
|
||||
* #7170: [hosting] Improve admin email body contents for hosting vm terminate error case
|
||||
2.6.4: 2019-09-15
|
||||
* #7147: [OpenBSD vm] Add an explanatory text for username puffy on OpenBSD (MR!714)
|
||||
2.6.3: 2019-08-28
|
||||
* #7032: [hosting] Bugfix: Reentering the same SSH key used before does allow user to proceed further; complains key exists (MR!712)
|
||||
* #7070: [check_vm/api] Bugfix: Provide oneadmin credentials to check whether a user is the owner of a VM (MR!713)
|
||||
2.6.2: 2019-08-22
|
||||
* #7068: [django/node/rails] Remove public- prefix from OS template names (MR!711)
|
||||
2.6.1: 2019-07-09
|
||||
* #6941: [hosting dashboard] Show the card's expiry year & month too in the list of added cards (MR!710)
|
||||
2.6: 2019-07-03
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-07-03 11:18+0000\n"
|
||||
"POT-Creation-Date: 2019-11-15 17:33+0000\n"
|
||||
"PO-Revision-Date: 2018-03-30 23:22+0000\n"
|
||||
"Last-Translator: b'Anonymous User <coder.purple+25@gmail.com>'\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -20,7 +20,7 @@ msgstr ""
|
|||
"X-Translated-Using: django-rosetta 0.8.1\n"
|
||||
|
||||
msgid "CMS Favicon"
|
||||
msgstr ""
|
||||
msgstr "CMS Favicon"
|
||||
|
||||
#, python-format
|
||||
msgid "Your New VM %(vm_name)s at Data Center Light"
|
||||
|
@ -52,7 +52,7 @@ msgid "Login"
|
|||
msgstr "Anmelden"
|
||||
|
||||
msgid "Dashboard"
|
||||
msgstr ""
|
||||
msgstr "Dashboard"
|
||||
|
||||
msgid "Thank you for contacting us."
|
||||
msgstr "Nachricht gesendet."
|
||||
|
@ -64,7 +64,7 @@ msgid "Get in touch with us!"
|
|||
msgstr "Sende uns eine Nachricht."
|
||||
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
msgstr "Name"
|
||||
|
||||
msgid "Please enter your name."
|
||||
msgstr "Bitte gib Deinen Namen ein."
|
||||
|
@ -108,7 +108,7 @@ msgid "Your account details are as follows"
|
|||
msgstr "Deine Account Details sind unten aufgelistet"
|
||||
|
||||
msgid "Username"
|
||||
msgstr "Username"
|
||||
msgstr "Benutzername"
|
||||
|
||||
msgid "Your email address"
|
||||
msgstr "Deine E-Mail-Adresse"
|
||||
|
@ -155,7 +155,7 @@ msgid "Please enter a value in range %(min_ram)s - 200."
|
|||
msgstr "Bitte gib einen Wert von %(min_ram)s bis 200 ein."
|
||||
|
||||
msgid "VM hosting"
|
||||
msgstr ""
|
||||
msgstr "VM Hosting"
|
||||
|
||||
msgid "month"
|
||||
msgstr "Monat"
|
||||
|
@ -207,14 +207,14 @@ msgstr ""
|
|||
|
||||
msgid "Only wants you to pay for what you actually need."
|
||||
msgstr ""
|
||||
"Möchte, dass du nur bezahlst, was du auch wirklich brauchst: Wähle deine "
|
||||
"Du möchtest nur das bezahlen, was du auch wirklich brauchst: Wähle deine "
|
||||
"Ressourcen individuell aus!
"
|
||||
|
||||
msgid ""
|
||||
"Is creative, using a modern and alternative design for a data center in "
|
||||
"order to make it more sustainable and affordable at the same time."
|
||||
msgstr ""
|
||||
"Ist kreativ, indem es sich ein modernes und alternatives Layout zu Nutze "
|
||||
"Es ist kreativ, da es sich ein modernes und alternatives Layout zu Nutze"
|
||||
"macht um Nachhaltigkeit zu fördern und somit erschwingliche Preise bieten zu "
|
||||
"können.
"
|
||||
|
||||
|
@ -222,9 +222,9 @@ msgid ""
|
|||
"Cuts down the costs for you by using FOSS (Free Open Source Software) "
|
||||
"exclusively, wherefore we can save money from paying licenses."
|
||||
msgstr ""
|
||||
"Sorgt dafür, dass unnötige Kosten erspart werden, indem es ausschliesslich "
|
||||
"mit FOSS (Free Open Source Software) arbeitet und wir daher auf "
|
||||
"Lizenzgebühren verzichten können.
"
|
||||
"Um unnötige Kosten zu sparen werden, wird ausschliesslich Software auf"
|
||||
"Basis von FOSS (Free Open Source Software) eingesetzt und dadurch können auf "
|
||||
"Lizenzgebühren verzichtet werden.
"
|
||||
|
||||
msgid "Scale out"
|
||||
msgstr "Skalierung"
|
||||
|
@ -311,7 +311,7 @@ msgid "Billing Address"
|
|||
msgstr "Rechnungsadresse"
|
||||
|
||||
msgid "Make a payment"
|
||||
msgstr ""
|
||||
msgstr "Tätige eine Bezahlung"
|
||||
|
||||
msgid "Your Order"
|
||||
msgstr "Deine Bestellung"
|
||||
|
@ -375,6 +375,9 @@ msgstr "Letzten"
|
|||
msgid "Type"
|
||||
msgstr "Typ"
|
||||
|
||||
msgid "Expiry"
|
||||
msgstr "Ablaufdatum"
|
||||
|
||||
msgid "SELECT"
|
||||
msgstr "AUSWÄHLEN"
|
||||
|
||||
|
@ -415,14 +418,23 @@ msgstr "Bestellungsübersicht"
|
|||
msgid "Product"
|
||||
msgstr "Produkt"
|
||||
|
||||
msgid "Price"
|
||||
msgstr "Preise"
|
||||
|
||||
msgid "VAT for"
|
||||
msgstr "MwSt für"
|
||||
|
||||
msgid "Total Amount"
|
||||
msgstr "Gesamtsumme"
|
||||
|
||||
msgid "Amount"
|
||||
msgstr ""
|
||||
msgstr "Betrag"
|
||||
|
||||
msgid "Description"
|
||||
msgstr ""
|
||||
msgstr "Beschreibung"
|
||||
|
||||
msgid "Recurring"
|
||||
msgstr ""
|
||||
msgstr "Wiederholend"
|
||||
|
||||
msgid "Subtotal"
|
||||
msgstr "Zwischensumme"
|
||||
|
@ -434,12 +446,20 @@ msgstr "Mehrwertsteuer"
|
|||
#| msgid ""
|
||||
#| "By clicking \"Place order\" this plan will charge your credit card "
|
||||
#| "account with %(total_price)s CHF/month"
|
||||
msgid ""
|
||||
"By clicking \"Place order\" this plan will charge your credit card account "
|
||||
"with %(total_price)s CHF/year"
|
||||
msgstr ""
|
||||
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(total_price)s "
|
||||
"CHF pro Jahr belastet"
|
||||
|
||||
|
||||
msgid ""
|
||||
"By clicking \"Place order\" this plan will charge your credit card account "
|
||||
"with %(total_price)s CHF/month"
|
||||
msgstr ""
|
||||
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit "
|
||||
"%(vm_total_price)s CHF pro Monat belastet"
|
||||
"Wenn Du \"bestellen\" auswählst, wird Deine Kreditkarte mit %(total_price)s "
|
||||
"CHF pro Monat belastet"
|
||||
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
|
@ -470,10 +490,10 @@ msgid "Hold tight, we are processing your request"
|
|||
msgstr "Bitte warten - wir verarbeiten Deine Anfrage gerade"
|
||||
|
||||
msgid "OK"
|
||||
msgstr ""
|
||||
msgstr "Ok"
|
||||
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
msgstr "Schliessen"
|
||||
|
||||
msgid "Some problem encountered. Please try again later."
|
||||
msgstr "Ein Problem ist aufgetreten. Bitte versuche es später noch einmal."
|
||||
|
@ -485,7 +505,7 @@ msgid "Tech Stack"
|
|||
msgstr "Tech Stack"
|
||||
|
||||
msgid "We are seriously open source."
|
||||
msgstr "Wir sind vollends opensource."
|
||||
msgstr "Wir sind vollends Open Source."
|
||||
|
||||
msgid ""
|
||||
" Our full software stack is open source – We don't use anything that isn't "
|
||||
|
@ -555,13 +575,13 @@ msgid "Starting from only 15CHF per month. Try now."
|
|||
msgstr "Unser Angebot beginnt bei 15 CHF pro Monat. Probier's jetzt aus!"
|
||||
|
||||
msgid "Actions speak louder than words. Let's do it, try our VM now."
|
||||
msgstr "Tagen sagen mehr als Worte – Teste jetzt unsere VM!"
|
||||
msgstr "Taten sagen mehr als Worte – Teste jetzt unsere VM!"
|
||||
|
||||
msgid "Invalid number of cores"
|
||||
msgstr "Ungültige Anzahle CPU-Kerne"
|
||||
|
||||
msgid "Invalid calculator properties"
|
||||
msgstr ""
|
||||
msgstr "Ungültige Berechnungseigenschaften"
|
||||
|
||||
msgid "Invalid RAM size"
|
||||
msgstr "Ungültige RAM-Grösse"
|
||||
|
@ -571,7 +591,7 @@ msgstr "Ungültige Speicher-Grösse"
|
|||
|
||||
#, python-brace-format
|
||||
msgid "Incorrect pricing name. Please contact support{support_email}"
|
||||
msgstr ""
|
||||
msgstr "Ungültige Preisbezeichnung. Bitte kontaktiere den Support{support_email}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{user} does not have permission to access the card"
|
||||
|
@ -598,11 +618,14 @@ msgid "An error occurred while associating the card. Details: {details}"
|
|||
msgstr ""
|
||||
"Beim Verbinden der Karte ist ein Fehler aufgetreten. Details: {details}"
|
||||
|
||||
msgid "Confirmation of your payment"
|
||||
msgstr ""
|
||||
|
||||
msgid " This is a monthly recurring plan."
|
||||
msgstr ""
|
||||
msgstr "Dies ist ein monatlich wiederkehrender Plan."
|
||||
|
||||
msgid " This is an yearly recurring plan."
|
||||
msgstr "Dies ist ein jährlich wiederkehrender Plan."
|
||||
|
||||
msgid "Confirmation of your payment"
|
||||
msgstr "Bestätigung deiner Zahlung"
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
|
@ -613,7 +636,8 @@ msgid ""
|
|||
"\n"
|
||||
"Cheers,\n"
|
||||
"Your Data Center Light team"
|
||||
msgstr ""
|
||||
msgstr "Hallo {name},\n" "\n" "vielen Dank für deine Bestellung!\n" "Wir haben deine Bezahlung in Höhe von {amount:.2f} CHF erhalten. {recurring}\n" "\n" "Grüsse\n"
|
||||
"Dein Data Center Light Team"
|
||||
|
||||
msgid "Thank you for the payment."
|
||||
msgstr "Danke für Deine Bestellung."
|
||||
|
@ -621,7 +645,7 @@ msgstr "Danke für Deine Bestellung."
|
|||
msgid ""
|
||||
"You will soon receive a confirmation email of the payment. You can always "
|
||||
"contact us at info@ungleich.ch for any question that you may have."
|
||||
msgstr ""
|
||||
msgstr "Du wirst bald eine Bestätigungs-E-Mail über die Zahlung erhalten. Du kannst jederzeit unter info@ungleich.ch kontaktieren."
|
||||
|
||||
msgid "Thank you for the order."
|
||||
msgstr "Danke für Deine Bestellung."
|
||||
|
@ -644,9 +668,6 @@ msgstr ""
|
|||
#~ msgid "Card Number"
|
||||
#~ msgstr "Kreditkartennummer"
|
||||
|
||||
#~ msgid "Expiry Date"
|
||||
#~ msgstr "Ablaufdatum"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "You are not making any payment yet. After placing your order, you will be "
|
||||
#~ "taken to the Submit Payment Page."
|
||||
|
@ -655,9 +676,6 @@ msgstr ""
|
|||
#~ "ausgelöst, nachdem Du die Bestellung auf der nächsten Seite bestätigt "
|
||||
#~ "hast."
|
||||
|
||||
#~ msgid "Pricing"
|
||||
#~ msgstr "Preise"
|
||||
|
||||
#~ msgid "Order VM"
|
||||
#~ msgstr "VM bestellen"
|
||||
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
import logging
|
||||
import oca
|
||||
import sys
|
||||
import stripe
|
||||
import uuid
|
||||
|
||||
import oca
|
||||
import stripe
|
||||
from django.core.management.base import BaseCommand
|
||||
from membership.models import CustomUser, DeletedUser
|
||||
|
||||
from hosting.models import (
|
||||
HostingOrder, HostingBill, VMDetail, UserCardDetail, UserHostingKey
|
||||
UserCardDetail, UserHostingKey
|
||||
)
|
||||
from membership.models import CustomUser, DeletedUser
|
||||
from opennebula_api.models import OpenNebulaManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -79,86 +82,6 @@ class Command(BaseCommand):
|
|||
else:
|
||||
logger.error("Error while deleting the StripeCustomer")
|
||||
|
||||
hosting_orders = HostingOrder.objects.filter(
|
||||
customer=stripe_customer.id
|
||||
)
|
||||
|
||||
vm_ids = []
|
||||
for order in hosting_orders:
|
||||
vm_ids.append(order.vm_id)
|
||||
|
||||
# Delete Billing Address
|
||||
if order.billing_address is not None:
|
||||
logger.debug(
|
||||
"Billing Address {} associated with {} deleted"
|
||||
"".format(order.billing_address.id, email)
|
||||
)
|
||||
order.billing_address.delete()
|
||||
else:
|
||||
logger.error(
|
||||
"Error while deleting the billing_address")
|
||||
|
||||
# Delete Order Detail
|
||||
if order.order_detail is not None:
|
||||
logger.debug(
|
||||
"Order Detail {} associated with {} deleted"
|
||||
"".format(order.order_detail.id, email)
|
||||
)
|
||||
order.order_detail.delete()
|
||||
else:
|
||||
logger.error(
|
||||
"Error while deleting the order_detail. None")
|
||||
|
||||
# Delete order
|
||||
if order is not None:
|
||||
logger.debug(
|
||||
"Order {} associated with {} deleted"
|
||||
"".format(order.id, email)
|
||||
)
|
||||
order.delete()
|
||||
else:
|
||||
logger.error(
|
||||
"Error while deleting the Order")
|
||||
|
||||
hosting_bills = HostingBill.objects.filter(
|
||||
customer=stripe_customer.id
|
||||
)
|
||||
|
||||
# delete hosting bills
|
||||
for bill in hosting_bills:
|
||||
if bill.billing_address is not None:
|
||||
logger.debug(
|
||||
"HostingBills billing address {} associated with {} deleted"
|
||||
"".format(bill.billing_address.id, email)
|
||||
)
|
||||
bill.billing_address.delete()
|
||||
else:
|
||||
logger.error(
|
||||
"Error while deleting the HostingBill's Billing address")
|
||||
|
||||
if bill is not None:
|
||||
logger.debug(
|
||||
"HostingBill {} associated with {} deleted"
|
||||
"".format(bill.id, email)
|
||||
)
|
||||
bill.delete()
|
||||
else:
|
||||
logger.error(
|
||||
"Error while deleting the HostingBill")
|
||||
|
||||
# delete VMDetail
|
||||
for vm_id in vm_ids:
|
||||
vm_detail = VMDetail.objects.get(vm_id=vm_id)
|
||||
if vm_detail is not None:
|
||||
logger.debug(
|
||||
"vm_detail {} associated with {} deleted"
|
||||
"".format(vm_detail.id, email)
|
||||
)
|
||||
vm_detail.delete()
|
||||
else:
|
||||
logger.error(
|
||||
"Error while deleting the vm_detail")
|
||||
|
||||
# delete UserCardDetail
|
||||
ucds = UserCardDetail.objects.filter(
|
||||
stripe_customer=stripe_customer
|
||||
|
@ -190,8 +113,10 @@ class Command(BaseCommand):
|
|||
user_id = cus_user.id
|
||||
)
|
||||
|
||||
# delete CustomUser
|
||||
cus_user.delete()
|
||||
# reset CustomUser
|
||||
cus_user.email = str(uuid.uuid4())
|
||||
cus_user.validated = 0
|
||||
cus_user.save()
|
||||
|
||||
# remove user from OpenNebula
|
||||
manager = OpenNebulaManager()
|
||||
|
|
134
datacenterlight/management/commands/dumpuser.py
Normal file
134
datacenterlight/management/commands/dumpuser.py
Normal file
|
@ -0,0 +1,134 @@
|
|||
import json
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from membership.models import CustomUser
|
||||
from hosting.models import (
|
||||
HostingOrder, VMDetail, UserCardDetail, UserHostingKey
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '''Dumps the data of a customer into a json file'''
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('customer_email', nargs='+', type=str)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
try:
|
||||
for email in options['customer_email']:
|
||||
try:
|
||||
cus_user = CustomUser.objects.get(email=email)
|
||||
except CustomUser.DoesNotExist as dne:
|
||||
logger.error("CustomUser with email {} does "
|
||||
"not exist".format(email))
|
||||
sys.exit(1)
|
||||
|
||||
hosting_orders = HostingOrder.objects.filter(
|
||||
customer=cus_user.stripecustomer.id
|
||||
)
|
||||
|
||||
vm_ids = []
|
||||
orders_dict = {}
|
||||
for order in hosting_orders:
|
||||
order_dict = {}
|
||||
vm_ids.append(order.vm_id)
|
||||
order_dict["VM_ID"] = order.vm_id
|
||||
order_dict["Order Nr."] = order.id
|
||||
order_dict["Created On"] = str(order.created_at)
|
||||
order_dict["Price"] = order.price
|
||||
order_dict["Payment card details"] = {
|
||||
"last4": order.last4,
|
||||
"brand": order.cc_brand
|
||||
}
|
||||
if order.subscription_id is not None and order.stripe_charge_id is None:
|
||||
order_dict["Order type"] = "Monthly susbcription"
|
||||
else:
|
||||
order_dict["Order type"] = "One time payment"
|
||||
|
||||
# billing address
|
||||
if order.billing_address is not None:
|
||||
order_dict["Billing Address"] = {
|
||||
"Street": order.billing_address.street_address,
|
||||
"City": order.billing_address.city,
|
||||
"Country": order.billing_address.country,
|
||||
"Postal code": order.billing_address.postal_code,
|
||||
"Card holder name": order.billing_address.cardholder_name
|
||||
}
|
||||
else:
|
||||
logger.error(
|
||||
"did not find billing_address")
|
||||
|
||||
# Order Detail
|
||||
if order.order_detail is not None:
|
||||
order_dict["Specifications"] = {
|
||||
"RAM": "{} GB".format(order.order_detail.memory),
|
||||
"Cores": order.order_detail.cores,
|
||||
"Disk space (SSD)": "{} GB".format(
|
||||
order.order_detail.ssd_size)
|
||||
}
|
||||
else:
|
||||
logger.error(
|
||||
"Did not find order_detail. None")
|
||||
|
||||
vm_detail = VMDetail.objects.get(vm_id=order.vm_id)
|
||||
if vm_detail is not None:
|
||||
order_dict["VM Details"] = {
|
||||
"VM_ID": order.vm_id,
|
||||
"IPv4": vm_detail.ipv4,
|
||||
"IPv6": vm_detail.ipv6,
|
||||
"OS": vm_detail.configuration,
|
||||
}
|
||||
order_dict["Terminated on"] = str(vm_detail.terminated_at)
|
||||
|
||||
orders_dict[order.vm_id] = order_dict
|
||||
|
||||
|
||||
# UserCardDetail
|
||||
cards = {}
|
||||
ucds = UserCardDetail.objects.filter(
|
||||
stripe_customer=cus_user.stripecustomer
|
||||
)
|
||||
for ucd in ucds:
|
||||
card = {}
|
||||
if ucd is not None:
|
||||
card["Last 4"] = ucd.last4
|
||||
card["Brand"] = ucd.brand
|
||||
card["Expiry month"] = ucd.exp_month
|
||||
card["Expiry year"] = ucd.exp_year
|
||||
card["Preferred"] = ucd.preferred
|
||||
cards[ucd.id] = card
|
||||
else:
|
||||
logger.error(
|
||||
"Error while deleting the User Card Detail")
|
||||
|
||||
# UserHostingKey
|
||||
keys = {}
|
||||
uhks = UserHostingKey.objects.filter(
|
||||
user=cus_user
|
||||
)
|
||||
for uhk in uhks:
|
||||
key = {
|
||||
"Public key": uhk.public_key,
|
||||
"Name": uhk.name,
|
||||
"Created on": str(uhk.created_at)
|
||||
}
|
||||
if uhk.private_key:
|
||||
key["Private key"] = uhk.private_key
|
||||
keys[uhk.name] = key
|
||||
output_dict = {
|
||||
"User details": {
|
||||
"Name": cus_user.name,
|
||||
"Email": cus_user.email,
|
||||
"Activated": "yes" if cus_user.validated == 1 else "no",
|
||||
"Last login": str(cus_user.last_login)
|
||||
},
|
||||
"Orders": orders_dict,
|
||||
"Payment cards": cards,
|
||||
"SSH Keys": keys
|
||||
}
|
||||
print(json.dumps(output_dict, indent=4))
|
||||
except Exception as e:
|
||||
print(" *** Error occurred. Details {}".format(str(e)))
|
|
@ -55,10 +55,25 @@
|
|||
</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 %}
|
||||
<p>
|
||||
<span>{% trans "Description" %}: </span>
|
||||
|
@ -139,7 +154,11 @@
|
|||
<div class="col-sm-8">
|
||||
{% if generic_payment_details %}
|
||||
{% if generic_payment_details.recurring %}
|
||||
<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>
|
||||
{% 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" this plan will charge your credit card account with {{total_price}} CHF/year{% endblocktrans %}.</div>
|
||||
{% else %}
|
||||
<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>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<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 %}
|
||||
|
|
|
@ -27,7 +27,9 @@ from utils.forms import (
|
|||
BillingAddressForm, BillingAddressFormSignup, UserBillingAddressForm,
|
||||
BillingAddress
|
||||
)
|
||||
from utils.hosting_utils import get_vm_price_with_vat, get_all_public_keys
|
||||
from utils.hosting_utils import (
|
||||
get_vm_price_with_vat, get_all_public_keys, get_vat_rate_for_country
|
||||
)
|
||||
from utils.stripe_utils import StripeUtils
|
||||
from utils.tasks import send_plain_email_task
|
||||
from .cms_models import DCLCalculatorPluginModel
|
||||
|
@ -413,10 +415,21 @@ class PaymentOrderView(FormView):
|
|||
product = generic_payment_form.cleaned_data.get(
|
||||
'product_name'
|
||||
)
|
||||
user_country_vat_rate = get_vat_rate_for_country(
|
||||
address_form.cleaned_data["country"]
|
||||
)
|
||||
gp_details = {
|
||||
"product_name": product.product_name,
|
||||
"amount": generic_payment_form.cleaned_data.get(
|
||||
'amount'
|
||||
"vat_rate": user_country_vat_rate * 100,
|
||||
"vat_amount": round(
|
||||
float(product.product_price) *
|
||||
user_country_vat_rate, 2),
|
||||
"vat_country": address_form.cleaned_data["country"],
|
||||
"amount_before_vat": round(
|
||||
float(product.product_price), 2),
|
||||
"amount": product.get_actual_price(
|
||||
vat_rate=get_vat_rate_for_country(
|
||||
address_form.cleaned_data["country"])
|
||||
),
|
||||
"recurring": generic_payment_form.cleaned_data.get(
|
||||
'recurring'
|
||||
|
@ -425,7 +438,9 @@ class PaymentOrderView(FormView):
|
|||
'description'
|
||||
),
|
||||
"product_id": product.id,
|
||||
"product_slug": product.product_slug
|
||||
"product_slug": product.product_slug,
|
||||
"recurring_interval":
|
||||
product.product_subscription_interval
|
||||
}
|
||||
request.session["generic_payment_details"] = (
|
||||
gp_details
|
||||
|
@ -744,6 +759,7 @@ class OrderConfirmationView(DetailView, FormView):
|
|||
|
||||
if ('generic_payment_type' not in request.session or
|
||||
(request.session['generic_payment_details']['recurring'])):
|
||||
recurring_interval = 'month'
|
||||
if 'generic_payment_details' in request.session:
|
||||
amount_to_be_charged = (
|
||||
round(
|
||||
|
@ -756,6 +772,10 @@ class OrderConfirmationView(DetailView, FormView):
|
|||
amount_to_be_charged
|
||||
)
|
||||
stripe_plan_id = plan_name
|
||||
recurring_interval = request.session['generic_payment_details']['recurring_interval']
|
||||
if recurring_interval == "year":
|
||||
plan_name = "{}-yearly".format(plan_name)
|
||||
stripe_plan_id = plan_name
|
||||
else:
|
||||
template = request.session.get('template')
|
||||
specs = request.session.get('specs')
|
||||
|
@ -782,7 +802,9 @@ class OrderConfirmationView(DetailView, FormView):
|
|||
stripe_plan = stripe_utils.get_or_create_stripe_plan(
|
||||
amount=amount_to_be_charged,
|
||||
name=plan_name,
|
||||
stripe_plan_id=stripe_plan_id)
|
||||
stripe_plan_id=stripe_plan_id,
|
||||
interval=recurring_interval
|
||||
)
|
||||
subscription_result = stripe_utils.subscribe_customer_to_plan(
|
||||
stripe_api_cus_id,
|
||||
[{"plan": stripe_plan.get(
|
||||
|
@ -977,6 +999,9 @@ class OrderConfirmationView(DetailView, FormView):
|
|||
'reply_to': [context['email']],
|
||||
}
|
||||
send_plain_email_task.delay(email_data)
|
||||
recurring_text = _(" This is a monthly recurring plan.")
|
||||
if gp_details['recurring_interval'] == "year":
|
||||
recurring_text = _(" This is an yearly recurring plan.")
|
||||
|
||||
email_data = {
|
||||
'subject': _("Confirmation of your payment"),
|
||||
|
@ -990,7 +1015,7 @@ class OrderConfirmationView(DetailView, FormView):
|
|||
name=user.get('name'),
|
||||
amount=gp_details['amount'],
|
||||
recurring=(
|
||||
_(' This is a monthly recurring plan.')
|
||||
recurring_text
|
||||
if gp_details['recurring'] else ''
|
||||
)
|
||||
)
|
||||
|
|
|
@ -9,7 +9,6 @@ from django.contrib.auth import authenticate
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from membership.models import CustomUser
|
||||
from utils.hosting_utils import get_all_public_keys
|
||||
from .models import UserHostingKey, GenericProduct
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -110,9 +109,14 @@ class ProductPaymentForm(GenericPaymentForm):
|
|||
)
|
||||
)
|
||||
if self.product.product_is_subscription:
|
||||
payment_type = "month"
|
||||
if self.product.product_subscription_interval == "month":
|
||||
payment_type = _('Monthly subscription')
|
||||
elif self.product.product_subscription_interval == "year":
|
||||
payment_type = _('Yearly subscription')
|
||||
self.fields['amount'].label = "{amt} ({payment_type})".format(
|
||||
amt=_('Amount in CHF'),
|
||||
payment_type=_('Monthly subscription')
|
||||
payment_type=payment_type
|
||||
)
|
||||
else:
|
||||
self.fields['amount'].label = "{amt} ({payment_type})".format(
|
||||
|
@ -193,15 +197,6 @@ class UserHostingKeyForm(forms.ModelForm):
|
|||
KEY_ERROR_MESSAGE = _("Please input a proper SSH key")
|
||||
openssh_pubkey_str = self.data.get('public_key').strip()
|
||||
|
||||
if openssh_pubkey_str in get_all_public_keys(self.request.user):
|
||||
key_name = UserHostingKey.objects.filter(
|
||||
user_id=self.request.user.id,
|
||||
public_key=openssh_pubkey_str).first().name
|
||||
KEY_EXISTS_MESSAGE = _(
|
||||
"This key exists already with the name \"%(name)s\"") % {
|
||||
'name': key_name}
|
||||
raise forms.ValidationError(KEY_EXISTS_MESSAGE)
|
||||
|
||||
with tempfile.NamedTemporaryFile(delete=True) as tmp_public_key_file:
|
||||
tmp_public_key_file.write(openssh_pubkey_str.encode('utf-8'))
|
||||
tmp_public_key_file.flush()
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-07-09 15:21+0000\n"
|
||||
"POT-Creation-Date: 2019-11-15 16:40+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -28,28 +28,31 @@ msgid "User does not exist"
|
|||
msgstr "Der Benutzer existiert nicht"
|
||||
|
||||
msgid "Choose a product"
|
||||
msgstr ""
|
||||
msgstr "Wähle ein Produkt"
|
||||
|
||||
msgid "Amount in CHF"
|
||||
msgstr "Betrag"
|
||||
|
||||
msgid "Recurring monthly"
|
||||
msgstr ""
|
||||
msgstr "monatlich wiederkehrend"
|
||||
|
||||
msgid "Amount field does not match"
|
||||
msgstr ""
|
||||
msgstr "Betragsfeld stimmt nicht überein"
|
||||
|
||||
msgid "Recurring field does not match"
|
||||
msgstr ""
|
||||
msgstr "Betragsfeld stimmt nicht überein"
|
||||
|
||||
msgid "Product name"
|
||||
msgstr "Produkt"
|
||||
|
||||
msgid "Monthly subscription"
|
||||
msgstr ""
|
||||
msgstr "Monatliches Abonnement"
|
||||
|
||||
msgid "Yearly subscription"
|
||||
msgstr "Jährliches Abonnement"
|
||||
|
||||
msgid "One time payment"
|
||||
msgstr ""
|
||||
msgstr "Einmalzahlung"
|
||||
|
||||
msgid "Confirm Password"
|
||||
msgstr "Passwort Bestätigung"
|
||||
|
@ -72,12 +75,8 @@ msgstr "Key-Name"
|
|||
msgid "Please input a proper SSH key"
|
||||
msgstr "Bitte verwende einen gültigen SSH-Key"
|
||||
|
||||
#, python-format
|
||||
msgid "This key exists already with the name \"%(name)s\""
|
||||
msgstr "Der SSH-Key mit dem Name \"%(name)s\" existiert bereits"
|
||||
|
||||
msgid "Comma not accepted in the name of the key"
|
||||
msgstr ""
|
||||
msgstr "Komma im Namen des Keys wird nicht akzeptiert"
|
||||
|
||||
msgid "All Rights Reserved"
|
||||
msgstr "Alle Rechte vorbehalten"
|
||||
|
@ -242,6 +241,10 @@ msgstr ""
|
|||
msgid "You can view your VM detail by clicking the button below."
|
||||
msgstr "Um die Rechnung zu sehen, klicke auf den Button unten."
|
||||
|
||||
msgid "You can log in to your VM by the username <strong>puffy</strong>."
|
||||
msgstr ""
|
||||
"Du kannst Dich auf Deiner VM mit dem user <strong>puffy</strong> einloggen."
|
||||
|
||||
msgid "View Detail"
|
||||
msgstr "Details anzeigen"
|
||||
|
||||
|
@ -255,6 +258,9 @@ msgstr "Deine Bestellung von %(vm_name)s wurde entgegengenommen."
|
|||
msgid "You can view your VM detail by following the link below."
|
||||
msgstr "Um die Rechnung zu sehen, klicke auf den Link unten."
|
||||
|
||||
msgid "You can log in to your VM by the username puffy."
|
||||
msgstr "Du kannst Dich auf Deiner VM mit dem user puffy einloggen."
|
||||
|
||||
msgid "Password Reset"
|
||||
msgstr "Passwort zurücksetzen"
|
||||
|
||||
|
@ -398,13 +404,19 @@ msgid "Amount"
|
|||
msgstr "Betrag"
|
||||
|
||||
msgid "Description"
|
||||
msgstr ""
|
||||
msgstr "Beschreibung"
|
||||
|
||||
msgid "Recurring"
|
||||
msgstr ""
|
||||
msgstr "wiederkehrend"
|
||||
|
||||
msgid "of"
|
||||
msgstr "von"
|
||||
|
||||
msgid "each year"
|
||||
msgstr "jedes Jahr"
|
||||
|
||||
msgid "of every month"
|
||||
msgstr ""
|
||||
msgstr "jeden Monat"
|
||||
|
||||
msgid "BACK TO LIST"
|
||||
msgstr "ZURÜCK ZUR LISTE"
|
||||
|
@ -416,16 +428,13 @@ msgid "VM ID"
|
|||
msgstr ""
|
||||
|
||||
msgid "IP Address"
|
||||
msgstr ""
|
||||
msgstr "IP-Adresse"
|
||||
|
||||
msgid "See Invoice"
|
||||
msgstr "Siehe Rechnung"
|
||||
|
||||
msgid "Page"
|
||||
msgstr ""
|
||||
|
||||
msgid "of"
|
||||
msgstr ""
|
||||
msgstr "Seite"
|
||||
|
||||
msgid "Log in"
|
||||
msgstr "Anmelden"
|
||||
|
@ -487,7 +496,7 @@ msgid "Hold tight, we are processing your request"
|
|||
msgstr "Bitte warten - wir bearbeiten Deine Anfrage gerade"
|
||||
|
||||
msgid "OK"
|
||||
msgstr ""
|
||||
msgstr "Ok"
|
||||
|
||||
msgid "Close"
|
||||
msgstr "Schliessen"
|
||||
|
@ -843,7 +852,7 @@ msgstr "Ungültige Speicher-Grösse"
|
|||
|
||||
#, python-brace-format
|
||||
msgid "Incorrect pricing name. Please contact support{support_email}"
|
||||
msgstr ""
|
||||
msgstr "Ungültige Preisbezeichnung. Bitte kontaktiere den Support{support_email}"
|
||||
|
||||
msgid ""
|
||||
"We could not find the requested VM. Please "
|
||||
|
@ -862,7 +871,7 @@ msgstr "Fehler beenden VM"
|
|||
msgid ""
|
||||
"VM terminate action timed out. Please contact support@datacenterlight.ch for "
|
||||
"further information."
|
||||
msgstr ""
|
||||
msgstr "VM beendet wegen Zeitüberschreitung. Bitte kontaktiere support@datacenterlight.ch für weitere Informationen."
|
||||
|
||||
#, python-format
|
||||
msgid "Virtual Machine %(vm_name)s Cancelled"
|
||||
|
@ -873,6 +882,10 @@ msgstr ""
|
|||
"Es gab einen Fehler bei der Bearbeitung Deine Anfrage. Bitte versuche es "
|
||||
"noch einmal."
|
||||
|
||||
#, python-format
|
||||
#~ msgid "This key exists already with the name \"%(name)s\""
|
||||
#~ msgstr "Der SSH-Key mit dem Name \"%(name)s\" existiert bereits"
|
||||
|
||||
#~ msgid "Add your public SSH key"
|
||||
#~ msgstr "Füge deinen öffentlichen SSH-Key hinzu"
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import datetime
|
||||
import logging
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
@ -19,6 +20,10 @@ class Command(BaseCommand):
|
|||
def handle(self, *args, **options):
|
||||
try:
|
||||
for email in options['customer_email']:
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
"---------------------------------------------")
|
||||
)
|
||||
stripe_utils = StripeUtils()
|
||||
user = CustomUser.objects.get(email=email)
|
||||
if hasattr(user, 'stripecustomer'):
|
||||
|
@ -39,7 +44,9 @@ class Command(BaseCommand):
|
|||
)
|
||||
if all_invoices_response['error'] is not None:
|
||||
self.stdout.write(self.style.ERROR(all_invoices_response['error']))
|
||||
exit(1)
|
||||
user.import_stripe_bill_remark += "{}: {},".format(datetime.datetime.now(), all_invoices_response['error'])
|
||||
user.save()
|
||||
continue
|
||||
all_invoices = all_invoices_response['response_object']
|
||||
self.stdout.write(self.style.SUCCESS("Obtained {} invoices".format(len(all_invoices) if all_invoices is not None else 0)))
|
||||
num_invoice_created = 0
|
||||
|
@ -50,12 +57,25 @@ class Command(BaseCommand):
|
|||
logger.debug("Invoice %s exists already. Not importing." % invoice['invoice_id'])
|
||||
except MonthlyHostingBill.DoesNotExist as dne:
|
||||
logger.debug("Invoice id %s does not exist" % invoice['invoice_id'])
|
||||
num_invoice_created += 1 if MonthlyHostingBill.create(invoice) is not None else logger.error("Did not import invoice for %s" % str(invoice))
|
||||
|
||||
if MonthlyHostingBill.create(invoice) is not None:
|
||||
num_invoice_created += 1
|
||||
else:
|
||||
user.import_stripe_bill_remark += "{}: Import failed - {},".format(
|
||||
datetime.datetime.now(),
|
||||
invoice['invoice_id'])
|
||||
user.save()
|
||||
logger.error("Did not import invoice for %s"
|
||||
"" % str(invoice))
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS("Number of invoices imported = %s" % num_invoice_created)
|
||||
)
|
||||
else:
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
'Customer email %s does not have a stripe customer.' % email))
|
||||
user.import_stripe_bill_remark += "{}: No stripecustomer,".format(
|
||||
datetime.datetime.now()
|
||||
)
|
||||
user.save()
|
||||
except Exception as e:
|
||||
print(" *** Error occurred. Details {}".format(str(e)))
|
||||
|
|
44
hosting/management/commands/import_vat_rates.py
Normal file
44
hosting/management/commands/import_vat_rates.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
import csv
|
||||
from hosting.models import VATRates
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '''Imports VAT Rates. Assume vat rates of format https://github.com/kdeldycke/vat-rates/blob/master/vat_rates.csv'''
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('csv_file', nargs='+', type=str)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
try:
|
||||
for c_file in options['csv_file']:
|
||||
print("c_file = %s" % c_file)
|
||||
with open(c_file, mode='r') as csv_file:
|
||||
csv_reader = csv.DictReader(csv_file)
|
||||
line_count = 0
|
||||
for row in csv_reader:
|
||||
if line_count == 0:
|
||||
line_count += 1
|
||||
obj, created = VATRates.objects.get_or_create(
|
||||
start_date=row["start_date"],
|
||||
stop_date=row["stop_date"] if row["stop_date"] is not "" else None,
|
||||
territory_codes=row["territory_codes"],
|
||||
currency_code=row["currency_code"],
|
||||
rate=row["rate"],
|
||||
rate_type=row["rate_type"],
|
||||
description=row["description"]
|
||||
)
|
||||
if created:
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
'%s. %s - %s - %s - %s' % (
|
||||
line_count,
|
||||
obj.start_date,
|
||||
obj.stop_date,
|
||||
obj.territory_codes,
|
||||
obj.rate
|
||||
)
|
||||
))
|
||||
line_count+=1
|
||||
|
||||
except Exception as e:
|
||||
print(" *** Error occurred. Details {}".format(str(e)))
|
20
hosting/migrations/0056_auto_20191026_0454.py
Normal file
20
hosting/migrations/0056_auto_20191026_0454.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2019-10-26 04:54
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('hosting', '0055_auto_20190701_1614'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='hostingbilllineitem',
|
||||
name='amount',
|
||||
field=models.IntegerField(),
|
||||
),
|
||||
]
|
30
hosting/migrations/0057_vatrates.py
Normal file
30
hosting/migrations/0057_vatrates.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2019-11-15 05:16
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import utils.mixins
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('hosting', '0056_auto_20191026_0454'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='VATRates',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('start_date', models.DateField(blank=True, null=True)),
|
||||
('stop_date', models.DateField(blank=True, null=True)),
|
||||
('territory_codes', models.TextField(blank=True, default='')),
|
||||
('currency_code', models.CharField(max_length=10)),
|
||||
('rate', models.FloatField()),
|
||||
('rate_type', models.TextField(blank=True, default='')),
|
||||
('description', models.TextField(blank=True, default='')),
|
||||
],
|
||||
bases=(utils.mixins.AssignPermissionsMixin, models.Model),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2019-11-15 14:57
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('hosting', '0057_vatrates'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='genericproduct',
|
||||
name='product_subscription_interval',
|
||||
field=models.CharField(default='month', help_text='Choose between `year` and `month`', max_length=10),
|
||||
),
|
||||
]
|
|
@ -78,13 +78,17 @@ class GenericProduct(AssignPermissionsMixin, models.Model):
|
|||
product_price = models.DecimalField(max_digits=6, decimal_places=2)
|
||||
product_vat = models.DecimalField(max_digits=6, decimal_places=4, default=0)
|
||||
product_is_subscription = models.BooleanField(default=True)
|
||||
product_subscription_interval = models.CharField(
|
||||
max_length=10, default="month",
|
||||
help_text="Choose between `year` and `month`")
|
||||
|
||||
def __str__(self):
|
||||
return self.product_name
|
||||
|
||||
def get_actual_price(self):
|
||||
def get_actual_price(self, vat_rate=None):
|
||||
VAT = vat_rate if vat_rate is not None else self.product_vat
|
||||
return round(
|
||||
self.product_price + (self.product_price * self.product_vat), 2
|
||||
float(self.product_price) + float(self.product_price) * float(VAT), 2
|
||||
)
|
||||
|
||||
|
||||
|
@ -322,7 +326,10 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
|
|||
logger.debug("Neither subscription id nor vm_id available")
|
||||
logger.debug("Can't import invoice")
|
||||
return None
|
||||
|
||||
if args['order'] is None:
|
||||
logger.error(
|
||||
"Order is None for {}".format(args['invoice_id']))
|
||||
return None
|
||||
instance = cls.objects.create(
|
||||
created=datetime.utcfromtimestamp(
|
||||
args['created']).replace(tzinfo=pytz.utc),
|
||||
|
@ -340,7 +347,10 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model):
|
|||
args['period_start']).replace(tzinfo=pytz.utc),
|
||||
period_end=datetime.utcfromtimestamp(
|
||||
args['period_end']).replace(tzinfo=pytz.utc),
|
||||
billing_reason=args['billing_reason'],
|
||||
billing_reason=(
|
||||
args['billing_reason']
|
||||
if args['billing_reason'] is not None else ''
|
||||
),
|
||||
discount=args['discount'],
|
||||
total=args['total'],
|
||||
lines_data_count=args['lines_data_count'],
|
||||
|
@ -469,7 +479,7 @@ class HostingBillLineItem(AssignPermissionsMixin, models.Model):
|
|||
on_delete=models.CASCADE)
|
||||
stripe_plan = models.ForeignKey(StripePlan, null=True,
|
||||
on_delete=models.CASCADE)
|
||||
amount = models.PositiveSmallIntegerField()
|
||||
amount = models.IntegerField()
|
||||
description = models.CharField(max_length=255)
|
||||
discountable = models.BooleanField()
|
||||
metadata = models.CharField(max_length=128)
|
||||
|
@ -722,6 +732,16 @@ class UserCardDetail(AssignPermissionsMixin, models.Model):
|
|||
return None
|
||||
|
||||
|
||||
class VATRates(AssignPermissionsMixin, models.Model):
|
||||
start_date = models.DateField(blank=True, null=True)
|
||||
stop_date = models.DateField(blank=True, null=True)
|
||||
territory_codes = models.TextField(blank=True, default='')
|
||||
currency_code = models.CharField(max_length=10)
|
||||
rate = models.FloatField()
|
||||
rate_type = models.TextField(blank=True, default='')
|
||||
description = models.TextField(blank=True, default='')
|
||||
|
||||
|
||||
class FailedInvoice(AssignPermissionsMixin, models.Model):
|
||||
permissions = ('view_failedinvoice',)
|
||||
stripe_customer = models.ForeignKey(StripeCustomer)
|
||||
|
|
|
@ -33,6 +33,11 @@
|
|||
<p style="line-height: 1.75; font-family: Lato, Arial, sans-serif; font-weight: 300; margin: 0;">
|
||||
{% blocktrans %}You can view your VM detail by clicking the button below.{% endblocktrans %}
|
||||
</p>
|
||||
{% if 'OpenBSD' in vm_name %}
|
||||
<p style="line-height: 1.75; font-family: Lato, Arial, sans-serif; font-weight: 300; margin: 0;">
|
||||
{% blocktrans %}You can log in to your VM by the username <strong>puffy</strong>.{% endblocktrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
{% blocktrans %}You have ordered a new virtual machine!{% endblocktrans %}
|
||||
{% blocktrans %}Your order of {{vm_name}} has been charged.{% endblocktrans %}
|
||||
{% blocktrans %}You can view your VM detail by following the link below.{% endblocktrans %}
|
||||
{% if 'OpenBSD' in vm_name %}
|
||||
{% blocktrans %}You can log in to your VM by the username puffy.{% endblocktrans %}
|
||||
{% endif %}
|
||||
|
||||
{{ base_url }}{{ order_url }}
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
<label for="configuration">Configuration: </label>
|
||||
<select class="form-control" name="vm_template_id" id="{{vm.hosting_company}}-configuration" data-vm-type="{{vm.hosting_company}}">
|
||||
{% for template in templates %}
|
||||
<option value="{{template.id}}">{{ template.name }}</option>
|
||||
<option value="{{template.id}}">{{ template.name|cut:'public-' }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</li>
|
||||
|
|
|
@ -93,10 +93,10 @@
|
|||
<table>
|
||||
<tr><th style="width: 35%">Product</th><th style="width: 20%">Period</th><th style="text-align: center; width: 10%">Qty</th><th align="center" style="width: 10%; text-align: center;">Unit Price</th><th style="width: 10%; text-align: right;">Total</th></tr>
|
||||
{% for line_item in line_items %}
|
||||
<tr class="border_bottom"><td>{% if line_item.description|length > 0 %}{{line_item.description}}{% elif line_item.stripe_plan.stripe_plan_name|length > 0 %}{{line_item.stripe_plan.stripe_plan_name}}{% else %}{{line_item.get_item_detail_str|safe}}{% endif %}</td><td>{{ line_item.period_start | date:'Y-m-d' }} — {{ line_item.period_end | date:'Y-m-d' }}</td><td align="center">{{line_item.quantity}}</td><td align="center">{{line_item.unit_amount_in_chf}}</td><td align="right">{{line_item.amount_in_chf}}</td></tr>
|
||||
<tr class="border_bottom"><td>{% if line_item.description|length > 0 %}{{line_item.description}}{% elif line_item.stripe_plan.stripe_plan_name|length > 0 %}{{line_item.stripe_plan.stripe_plan_name}}{% else %}{{line_item.get_item_detail_str|safe}}{% endif %}</td><td>{{ line_item.period_start | date:'Y-m-d' }} — {{ line_item.period_end | date:'Y-m-d' }}</td><td align="center">{{line_item.quantity}}</td><td align="center">{{line_item.unit_amount_in_chf}}</td><td align="right">{{line_item.amount_in_chf|floatformat:2}}</td></tr>
|
||||
|
||||
{% endfor %}
|
||||
<tr class="grand-total-padding"><td colspan="4">Grand Total</td><td align="right">{{total_in_chf}}</td></tr>
|
||||
<tr class="grand-total-padding"><td colspan="4">Grand Total</td><td align="right">{{total_in_chf|floatformat:2}}</td></tr>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>
|
||||
|
@ -195,8 +195,13 @@
|
|||
{% if invoice.order.subscription_id %}
|
||||
<p>
|
||||
<span>{% trans "Recurring" %}: </span>
|
||||
<strong class="pull-right">{{invoice.order.created_at|date:'d'|ordinal}}
|
||||
{% if invoice.order.generic_product.product_subscription_interval == 'year' %}
|
||||
<strong class="pull-right">{{invoice.order.created_at|date:'d'|ordinal}} {% trans "of" %} {{invoice.order.created_at|date:'b'|title}}
|
||||
{% trans "each year" %}</strong>
|
||||
{% else %}
|
||||
<strong class="pull-right">{{invoice.order.created_at|date:'d'|ordinal}}
|
||||
{% trans "of every month" %}</strong>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -186,7 +186,13 @@
|
|||
{% if order.subscription_id %}
|
||||
<p>
|
||||
<span>{% trans "Recurring" %}: </span>
|
||||
<strong class="pull-right">{{order.created_at|date:'d'|ordinal}} {% trans "of every month" %}</strong>
|
||||
{% if order.generic_product.product_subscription_interval == 'year' %}
|
||||
<strong class="pull-right">{{order.created_at|date:'d'|ordinal}} {% trans "of" %} {{order.created_at|date:'b'|title}}
|
||||
{% trans "each year" %}</strong>
|
||||
{% else %}
|
||||
<strong class="pull-right">{{order.created_at|date:'d'|ordinal}}
|
||||
{% trans "of every month" %}</strong>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -1282,6 +1282,10 @@ class InvoiceDetailView(LoginRequiredMixin, DetailView):
|
|||
context['vm']['total_price'] = (
|
||||
price + vat - discount['amount']
|
||||
)
|
||||
except TypeError:
|
||||
logger.error("Type error. Probably we "
|
||||
"came from a generic product. "
|
||||
"Invoice ID %s" % obj.invoice_id)
|
||||
except WrongIdError:
|
||||
logger.error("WrongIdError while accessing "
|
||||
"invoice {}".format(obj.invoice_id))
|
||||
|
@ -1587,6 +1591,7 @@ class VirtualMachineView(LoginRequiredMixin, View):
|
|||
|
||||
# Cancel Stripe subscription
|
||||
stripe_utils = StripeUtils()
|
||||
hosting_order = None
|
||||
try:
|
||||
hosting_order = HostingOrder.objects.get(
|
||||
vm_id=vm.id
|
||||
|
@ -1648,9 +1653,10 @@ class VirtualMachineView(LoginRequiredMixin, View):
|
|||
else:
|
||||
sleep(2)
|
||||
if not response['status']:
|
||||
response['text'] = _("VM terminate action timed out. Please "
|
||||
"contact support@datacenterlight.ch for "
|
||||
"further information.")
|
||||
response['text'] = str(_("VM terminate action timed out. "
|
||||
"Please contact "
|
||||
"support@datacenterlight.ch for "
|
||||
"further information."))
|
||||
context = {
|
||||
'vm_name': vm_name,
|
||||
'base_url': "{0}://{1}".format(
|
||||
|
@ -1671,6 +1677,11 @@ class VirtualMachineView(LoginRequiredMixin, View):
|
|||
email = BaseEmail(**email_data)
|
||||
email.send()
|
||||
admin_email_body.update(response)
|
||||
admin_email_body["customer_email"] = owner.email
|
||||
admin_email_body["VM_ID"] = vm.id
|
||||
admin_email_body["VM_created_at"] = (str(hosting_order.created_at) if
|
||||
hosting_order is not None
|
||||
else "unknown")
|
||||
admin_msg_sub = "VM and Subscription for VM {} and user: {}".format(
|
||||
vm.id,
|
||||
owner.email
|
||||
|
@ -1760,7 +1771,8 @@ class CheckUserVM(APIView):
|
|||
response = check_otp(user, realm, token)
|
||||
if response != 200:
|
||||
return Response('Invalid token', 403)
|
||||
manager = OpenNebulaManager()
|
||||
manager = OpenNebulaManager(settings.OPENNEBULA_USERNAME,
|
||||
settings.OPENNEBULA_PASSWORD)
|
||||
# not the best way to lookup vms by ip
|
||||
# TODO: make this optimal
|
||||
vms = manager.get_vms()
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.4 on 2019-11-28 07:19
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('membership', '0009_deleteduser'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='customuser',
|
||||
name='import_stripe_bill_remark',
|
||||
field=models.TextField(default='', help_text='Indicates any issues while importing stripe bills'),
|
||||
),
|
||||
]
|
|
@ -82,6 +82,10 @@ class CustomUser(AbstractBaseUser, PermissionsMixin):
|
|||
help_text=_(
|
||||
'Designates whether the user can log into this admin site.'),
|
||||
)
|
||||
import_stripe_bill_remark = models.TextField(
|
||||
default="",
|
||||
help_text="Indicates any issues while importing stripe bills"
|
||||
)
|
||||
|
||||
objects = MyUserManager()
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import subprocess
|
|||
from oca.pool import WrongIdError
|
||||
|
||||
from datacenterlight.models import VMPricing
|
||||
from hosting.models import UserHostingKey, VMDetail
|
||||
from hosting.models import UserHostingKey, VMDetail, VATRates
|
||||
from opennebula_api.serializers import VirtualMachineSerializer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -18,7 +18,7 @@ def get_all_public_keys(customer):
|
|||
:return: A list of public keys
|
||||
"""
|
||||
return UserHostingKey.objects.filter(user_id=customer.id).values_list(
|
||||
"public_key", flat=True)
|
||||
"public_key", flat=True).distinct()
|
||||
|
||||
|
||||
def get_or_create_vm_detail(user, manager, vm_id):
|
||||
|
@ -150,6 +150,20 @@ def ping_ok(host_ipv6):
|
|||
return True
|
||||
|
||||
|
||||
def get_vat_rate_for_country(country):
|
||||
vat_rate = None
|
||||
try:
|
||||
vat_rate = VATRates.objects.get(
|
||||
territory_codes=country, start_date__isnull=False, stop_date=None
|
||||
)
|
||||
logger.debug("VAT rate for %s is %s" % (country, vat_rate.rate))
|
||||
return vat_rate.rate
|
||||
except VATRates.DoesNotExist as dne:
|
||||
logger.debug(str(dne))
|
||||
logger.debug("Did not find VAT rate for %s, returning 0" % country)
|
||||
return 0
|
||||
|
||||
|
||||
class HostingUtils:
|
||||
@staticmethod
|
||||
def clear_items_from_list(from_list, items_list):
|
||||
|
|
|
@ -226,7 +226,8 @@ class StripeUtils(object):
|
|||
return charge
|
||||
|
||||
@handleStripeError
|
||||
def get_or_create_stripe_plan(self, amount, name, stripe_plan_id):
|
||||
def get_or_create_stripe_plan(self, amount, name, stripe_plan_id,
|
||||
interval=""):
|
||||
"""
|
||||
This function checks if a StripePlan with the given
|
||||
stripe_plan_id already exists. If it exists then the function
|
||||
|
@ -238,6 +239,10 @@ class StripeUtils(object):
|
|||
:param stripe_plan_id: The id of the Stripe plan to be
|
||||
created. Use get_stripe_plan_id_string function to
|
||||
obtain the name of the plan to be created
|
||||
:param interval: str representing the interval of the Plan
|
||||
Specifies billing frequency. Either day, week, month or year.
|
||||
Ref: https://stripe.com/docs/api/plans/create#create_plan-interval
|
||||
The default is month
|
||||
:return: The StripePlan object if it exists else creates a
|
||||
Plan object in Stripe and a local StripePlan and
|
||||
returns it. Returns None in case of Stripe error
|
||||
|
@ -245,6 +250,7 @@ class StripeUtils(object):
|
|||
_amount = float(amount)
|
||||
amount = int(_amount * 100) # stripe amount unit, in cents
|
||||
stripe_plan_db_obj = None
|
||||
plan_interval = interval if interval is not "" else self.INTERVAL
|
||||
try:
|
||||
stripe_plan_db_obj = StripePlan.objects.get(
|
||||
stripe_plan_id=stripe_plan_id)
|
||||
|
@ -252,7 +258,7 @@ class StripeUtils(object):
|
|||
try:
|
||||
self.stripe.Plan.create(
|
||||
amount=amount,
|
||||
interval=self.INTERVAL,
|
||||
interval=plan_interval,
|
||||
name=name,
|
||||
currency=self.CURRENCY,
|
||||
id=stripe_plan_id)
|
||||
|
|
318
vat_rates.csv
Normal file
318
vat_rates.csv
Normal file
|
@ -0,0 +1,318 @@
|
|||
start_date,stop_date,territory_codes,currency_code,rate,rate_type,description
|
||||
2011-01-04,,AI,XCD,0,standard,Anguilla (British overseas territory) is exempted of VAT.
|
||||
1984-01-01,,AT,EUR,0.2,standard,Austria (member state) standard VAT rate.
|
||||
1976-01-01,1984-01-01,AT,EUR,0.18,standard,
|
||||
1973-01-01,1976-01-01,AT,EUR,0.16,standard,
|
||||
1984-01-01,,"AT-6691
|
||||
DE-87491",EUR,0.19,standard,Jungholz (Austrian town) special VAT rate.
|
||||
1984-01-01,,"AT-6991
|
||||
AT-6992
|
||||
AT-6993
|
||||
DE-87567
|
||||
DE-87568
|
||||
DE-87569",EUR,0.19,standard,Mittelberg (Austrian town) special VAT rate.
|
||||
1996-01-01,,BE,EUR,0.21,standard,Belgium (member state) standard VAT rate.
|
||||
1994-01-01,1996-01-01,BE,EUR,0.205,standard,
|
||||
1992-04-01,1994-01-01,BE,EUR,0.195,standard,
|
||||
1983-01-01,1992-04-01,BE,EUR,0.19,standard,
|
||||
1981-07-01,1983-01-01,BE,EUR,0.17,standard,
|
||||
1978-07-01,1981-07-01,BE,EUR,0.16,standard,
|
||||
1971-07-01,1978-07-01,BE,EUR,0.18,standard,
|
||||
1999-01-01,,BG,BGN,0.2,standard,Bulgaria (member state) standard VAT rate.
|
||||
1996-07-01,1999-01-01,BG,BGN,0.22,standard,
|
||||
1994-04-01,1996-07-01,BG,BGN,0.18,standard,
|
||||
2011-01-04,,BM,BMD,0,standard,Bermuda (British overseas territory) is exempted of VAT.
|
||||
2014-01-13,,"CY
|
||||
GB-BFPO 57
|
||||
GB-BFPO 58
|
||||
GB-BFPO 59
|
||||
UK-BFPO 57
|
||||
UK-BFPO 58
|
||||
UK-BFPO 59",EUR,0.19,standard,"Cyprus (member state) standard VAT rate.
|
||||
Akrotiri and Dhekelia (British overseas territory) is subjected to Cyprus' standard VAT rate."
|
||||
2013-01-14,2014-01-13,CY,EUR,0.18,standard,
|
||||
2012-03-01,2013-01-14,CY,EUR,0.17,standard,
|
||||
2003-01-01,2012-03-01,CY,EUR,0.15,standard,
|
||||
2002-07-01,2003-01-01,CY,EUR,0.13,standard,
|
||||
2000-07-01,2002-07-01,CY,EUR,0.1,standard,
|
||||
1993-10-01,2000-07-01,CY,EUR,0.08,standard,
|
||||
1992-07-01,1993-10-01,CY,EUR,0.05,standard,
|
||||
2013-01-01,,CZ,CZK,0.21,standard,Czech Republic (member state) standard VAT rate.
|
||||
2010-01-01,2013-01-01,CZ,CZK,0.2,standard,
|
||||
2004-05-01,2010-01-01,CZ,CZK,0.19,standard,
|
||||
1995-01-01,2004-05-01,CZ,CZK,0.22,standard,
|
||||
1993-01-01,1995-01-01,CZ,CZK,0.23,standard,
|
||||
2007-01-01,,DE,EUR,0.19,standard,Germany (member state) standard VAT rate.
|
||||
1998-04-01,2007-01-01,DE,EUR,0.16,standard,
|
||||
1993-01-01,1998-04-01,DE,EUR,0.15,standard,
|
||||
1983-07-01,1993-01-01,DE,EUR,0.14,standard,
|
||||
1979-07-01,1983-07-01,DE,EUR,0.13,standard,
|
||||
1978-01-01,1979-07-01,DE,EUR,0.12,standard,
|
||||
1968-07-01,1978-01-01,DE,EUR,0.11,standard,
|
||||
1968-01-01,1968-07-01,DE,EUR,0.1,standard,
|
||||
2007-01-01,,DE-27498,EUR,0,standard,Heligoland (German island) is exempted of VAT.
|
||||
2007-01-01,,"DE-78266
|
||||
CH-8238",EUR,0,standard,Busingen am Hochrhein (German territory) is exempted of VAT.
|
||||
1992-01-01,,DK,DKK,0.25,standard,Denmark (member state) standard VAT rate.
|
||||
1980-06-30,1992-01-01,DK,DKK,0.22,standard,
|
||||
1978-10-30,1980-06-30,DK,DKK,0.2025,standard,
|
||||
1977-10-03,1978-10-30,DK,DKK,0.18,standard,
|
||||
1970-06-29,1977-10-03,DK,DKK,0.15,standard,
|
||||
1968-04-01,1970-06-29,DK,DKK,0.125,standard,
|
||||
1967-07-03,1968-04-01,DK,DKK,0.1,standard,
|
||||
2009-07-01,,EE,EUR,0.2,standard,Estonia (member state) standard VAT rate.
|
||||
1993-01-01,2009-07-01,EE,EUR,0.18,standard,
|
||||
1991-01-01,1993-01-01,EE,EUR,0.1,standard,
|
||||
2016-06-01,,"GR
|
||||
EL",EUR,0.24,standard,Greece (member state) standard VAT rate.
|
||||
2010-07-01,2016-06-01,"GR
|
||||
EL",EUR,0.23,standard,
|
||||
2010-03-15,2010-07-01,"GR
|
||||
EL",EUR,0.21,standard,
|
||||
2005-04-01,2010-03-15,"GR
|
||||
EL",EUR,0.19,standard,
|
||||
1990-04-28,2005-04-01,"GR
|
||||
EL",EUR,0.18,standard,
|
||||
1988-01-01,1990-04-28,"GR
|
||||
EL",EUR,0.16,standard,
|
||||
1987-01-01,1988-01-01,"GR
|
||||
EL",EUR,0.18,standard,
|
||||
2012-09-01,,ES,EUR,0.21,standard,Spain (member state) standard VAT rate.
|
||||
2010-07-01,2012-09-01,ES,EUR,0.18,standard,
|
||||
1995-01-01,2010-07-01,ES,EUR,0.16,standard,
|
||||
1992-08-01,1995-01-01,ES,EUR,0.15,standard,
|
||||
1992-01-01,1992-08-01,ES,EUR,0.13,standard,
|
||||
1986-01-01,1992-01-01,ES,EUR,0.12,standard,
|
||||
2012-09-01,,"ES-CN
|
||||
ES-GC
|
||||
ES-TF
|
||||
IC",EUR,0,standard,Canary Islands (Spanish autonomous community) is exempted of VAT.
|
||||
2012-09-01,,"ES-ML
|
||||
ES-CE
|
||||
EA",EUR,0,standard,Ceuta and Melilla (Spanish autonomous cities) is exempted of VAT.
|
||||
2013-01-01,,FI,EUR,0.24,standard,Finland (member state) standard VAT rate.
|
||||
2010-07-01,2013-01-01,FI,EUR,0.23,standard,
|
||||
1994-06-01,2010-07-01,FI,EUR,0.22,standard,
|
||||
2013-01-01,,"FI-01
|
||||
AX",EUR,0,standard,Aland Islands (Finish autonomous region) is exempted of VAT.
|
||||
2011-01-04,,FK,FKP,0,standard,Falkland Islands (British overseas territory) is exempted of VAT.
|
||||
1992-01-01,,FO,DKK,0,standard,Faroe Islands (Danish autonomous country) is exempted of VAT.
|
||||
2014-01-01,,"FR
|
||||
MC",EUR,0.2,standard,"France (member state) standard VAT rate.
|
||||
Monaco (sovereign city-state) is member of the EU VAT area and subjected to France's standard VAT rate."
|
||||
2000-04-01,2014-01-01,"FR
|
||||
MC",EUR,0.196,standard,
|
||||
1995-08-01,2000-04-01,"FR
|
||||
MC",EUR,0.206,standard,
|
||||
1982-07-01,1995-08-01,"FR
|
||||
MC",EUR,0.186,standard,
|
||||
1977-01-01,1982-07-01,"FR
|
||||
MC",EUR,0.176,standard,
|
||||
1973-01-01,1977-01-01,"FR
|
||||
MC",EUR,0.2,standard,
|
||||
1970-01-01,1973-01-01,"FR
|
||||
MC",EUR,0.23,standard,
|
||||
1968-12-01,1970-01-01,"FR
|
||||
MC",EUR,0.19,standard,
|
||||
1968-01-01,1968-12-01,"FR
|
||||
MC",EUR,0.1666,standard,
|
||||
2014-01-01,,"FR-BL
|
||||
BL",EUR,0,standard,Saint Barthelemy (French overseas collectivity) is exempted of VAT.
|
||||
2014-01-01,,"FR-GF
|
||||
GF",EUR,0,standard,Guiana (French overseas department) is exempted of VAT.
|
||||
2014-01-01,,"FR-GP
|
||||
GP",EUR,0.085,standard,Guadeloupe (French overseas department) special VAT rate.
|
||||
2014-01-01,,"FR-MF
|
||||
MF",EUR,0,standard,Saint Martin (French overseas collectivity) is subjected to France's standard VAT rate.
|
||||
2014-01-01,,"FR-MQ
|
||||
MQ",EUR,0.085,standard,Martinique (French overseas department) special VAT rate.
|
||||
2014-01-01,,"FR-NC
|
||||
NC",XPF,0,standard,New Caledonia (French special collectivity) is exempted of VAT.
|
||||
2014-01-01,,"FR-PF
|
||||
PF",XPF,0,standard,French Polynesia (French overseas collectivity) is exempted of VAT.
|
||||
2014-01-01,,"FR-PM
|
||||
PM",EUR,0,standard,Saint Pierre and Miquelon (French overseas collectivity) is exempted of VAT.
|
||||
2014-01-01,,"FR-RE
|
||||
RE",EUR,0.085,standard,Reunion (French overseas department) special VAT rate.
|
||||
2014-01-01,,"FR-TF
|
||||
TF",EUR,0,standard,French Southern and Antarctic Lands (French overseas territory) is exempted of VAT.
|
||||
2014-01-01,,"FR-WF
|
||||
WF",XPF,0,standard,Wallis and Futuna (French overseas collectivity) is exempted of VAT.
|
||||
2014-01-01,,"FR-YT
|
||||
YT",EUR,0,standard,Mayotte (French overseas department) is exempted of VAT.
|
||||
2011-01-04,,GG,GBP,0,standard,Guernsey (British Crown dependency) is exempted of VAT.
|
||||
2011-01-04,,GI,GIP,0,standard,Gibraltar (British overseas territory) is exempted of VAT.
|
||||
1992-01-01,,GL,DKK,0,standard,Greenland (Danish autonomous country) is exempted of VAT.
|
||||
2010-07-01,2016-06-01,"GR-34007
|
||||
EL-34007",EUR,0.16,standard,Skyros (Greek island) special VAT rate.
|
||||
2010-07-01,2016-06-01,"GR-37002
|
||||
GR-37003
|
||||
GR-37005
|
||||
EL-37002
|
||||
EL-37003
|
||||
EL-37005",EUR,0.16,standard,Northern Sporades (Greek islands) special VAT rate.
|
||||
2010-07-01,2016-06-01,"GR-64004
|
||||
EL-64004",EUR,0.16,standard,Thasos (Greek island) special VAT rate.
|
||||
2010-07-01,2016-06-01,"GR-68002
|
||||
EL-68002",EUR,0.16,standard,Samothrace (Greek island) special VAT rate.
|
||||
2010-07-01,,"GR-69
|
||||
EL-69",EUR,0,standard,Mount Athos (Greek self-governed part) is exempted of VAT.
|
||||
2010-07-01,2016-06-01,"GR-81
|
||||
EL-81",EUR,0.16,standard,Dodecanese (Greek department) special VAT rate.
|
||||
2010-07-01,2016-06-01,"GR-82
|
||||
EL-82",EUR,0.16,standard,Cyclades (Greek department) special VAT rate.
|
||||
2010-07-01,2016-06-01,"GR-83
|
||||
EL-83",EUR,0.16,standard,Lesbos (Greek department) special VAT rate.
|
||||
2010-07-01,2016-06-01,"GR-84
|
||||
EL-84",EUR,0.16,standard,Samos (Greek department) special VAT rate.
|
||||
2010-07-01,2016-06-01,"GR-85
|
||||
EL-85",EUR,0.16,standard,Chios (Greek department) special VAT rate.
|
||||
2011-01-04,,GS,GBP,0,standard,South Georgia and the South Sandwich Islands (British overseas territory) is exempted of VAT.
|
||||
2012-03-01,,HR,HRK,0.25,standard,Croatia (member state) standard VAT rate.
|
||||
2009-08-01,2012-03-01,HR,HRK,0.23,standard,
|
||||
1998-08-01,2009-08-01,HR,HRK,0.22,standard,
|
||||
2012-01-01,,HU,HUF,0.27,standard,Hungary (member state) standard VAT rate.
|
||||
2009-07-01,2012-01-01,HU,HUF,0.25,standard,
|
||||
2006-01-01,2009-07-01,HU,HUF,0.2,standard,
|
||||
1988-01-01,2006-01-01,HU,HUF,0.25,standard,
|
||||
2012-01-01,,IE,EUR,0.23,standard,Republic of Ireland (member state) standard VAT rate.
|
||||
2010-01-01,2012-01-01,IE,EUR,0.21,standard,
|
||||
2008-12-01,2010-01-01,IE,EUR,0.215,standard,
|
||||
2002-03-01,2008-12-01,IE,EUR,0.21,standard,
|
||||
2001-01-01,2002-03-01,IE,EUR,0.2,standard,
|
||||
1991-03-01,2001-01-01,IE,EUR,0.21,standard,
|
||||
1990-03-01,1991-03-01,IE,EUR,0.23,standard,
|
||||
1986-03-01,1990-03-01,IE,EUR,0.25,standard,
|
||||
1983-05-01,1986-03-01,IE,EUR,0.23,standard,
|
||||
1983-03-01,1983-05-01,IE,EUR,0.35,standard,
|
||||
1982-05-01,1983-03-01,IE,EUR,0.3,standard,
|
||||
1980-05-01,1982-05-01,IE,EUR,0.25,standard,
|
||||
1976-03-01,1980-05-01,IE,EUR,0.2,standard,
|
||||
1973-09-03,1976-03-01,IE,EUR,0.195,standard,
|
||||
1972-11-01,1973-09-03,IE,EUR,0.1637,standard,
|
||||
2011-01-04,,IO,GBP,0,standard,British Indian Ocean Territory (British overseas territory) is exempted of VAT.
|
||||
2013-10-01,,IT,EUR,0.22,standard,Italy (member state) standard VAT rate.
|
||||
2011-09-17,2013-10-01,IT,EUR,0.21,standard,
|
||||
1997-10-01,2011-09-17,IT,EUR,0.2,standard,
|
||||
1988-08-01,1997-10-01,IT,EUR,0.19,standard,
|
||||
1982-08-05,1988-08-01,IT,EUR,0.18,standard,
|
||||
1981-01-01,1982-08-05,IT,EUR,0.15,standard,
|
||||
1980-11-01,1981-01-01,IT,EUR,0.14,standard,
|
||||
1980-07-03,1980-11-01,IT,EUR,0.15,standard,
|
||||
1977-02-08,1980-07-03,IT,EUR,0.14,standard,
|
||||
1973-01-01,1977-02-08,IT,EUR,0.12,standard,
|
||||
2013-10-01,,"IT-22060
|
||||
CH-6911",CHF,0,standard,Campione (Italian town) is exempted of VAT.
|
||||
2013-10-01,,IT-23030,EUR,0,standard,Livigno (Italian town) is exempted of VAT.
|
||||
2011-01-04,,JE,GBP,0,standard,Jersey (British Crown dependency) is exempted of VAT.
|
||||
2011-01-04,,KY,KYD,0,standard,Cayman Islands (British overseas territory) is exempted of VAT.
|
||||
2009-09-01,,LT,EUR,0.21,standard,Lithuania (member state) standard VAT rate.
|
||||
2009-01-01,2009-09-01,LT,EUR,0.19,standard,
|
||||
1994-05-01,2009-01-01,LT,EUR,0.18,standard,
|
||||
2015-01-01,,LU,EUR,0.17,standard,Luxembourg (member state) standard VAT rate.
|
||||
1992-01-01,2015-01-01,LU,EUR,0.15,standard,
|
||||
1983-07-01,1992-01-01,LU,EUR,0.12,standard,
|
||||
1971-01-01,1983-07-01,LU,EUR,0.1,standard,
|
||||
1970-01-01,1971-01-01,LU,EUR,0.8,standard,
|
||||
2012-07-01,,LV,EUR,0.21,standard,Latvia (member state) standard VAT rate.
|
||||
2011-01-01,2012-07-01,LV,EUR,0.22,standard,
|
||||
2009-01-01,2011-01-01,LV,EUR,0.21,standard,
|
||||
1995-05-01,2009-01-01,LV,EUR,0.18,standard,
|
||||
2011-01-04,,MS,XCD,0,standard,Montserrat (British overseas territory) is exempted of VAT.
|
||||
2004-01-01,,MT,EUR,0.18,standard,Malta (member state) standard VAT rate.
|
||||
1995-01-01,2004-01-01,MT,EUR,0.15,standard,
|
||||
2012-10-01,,NL,EUR,0.21,standard,Netherlands (member state) standard VAT rate.
|
||||
2001-01-01,2012-10-01,NL,EUR,0.19,standard,
|
||||
1992-10-01,2001-01-01,NL,EUR,0.175,standard,
|
||||
1989-01-01,1992-10-01,NL,EUR,0.185,standard,
|
||||
1986-10-01,1989-01-01,NL,EUR,0.2,standard,
|
||||
1984-01-01,1986-10-01,NL,EUR,0.19,standard,
|
||||
1976-01-01,1984-01-01,NL,EUR,0.18,standard,
|
||||
1973-01-01,1976-01-01,NL,EUR,0.16,standard,
|
||||
1971-01-01,1973-01-01,NL,EUR,0.14,standard,
|
||||
1969-01-01,1971-01-01,NL,EUR,0.12,standard,
|
||||
2012-10-01,,"NL-AW
|
||||
AW",AWG,0,standard,Aruba (Dutch country) are exempted of VAT.
|
||||
2012-10-01,,"NL-CW
|
||||
NL-SX
|
||||
CW
|
||||
SX",ANG,0,standard,Curacao and Sint Maarten (Dutch countries) are exempted of VAT.
|
||||
2012-10-01,,"NL-BQ1
|
||||
NL-BQ2
|
||||
NL-BQ3
|
||||
BQ
|
||||
BQ-BO
|
||||
BQ-SA
|
||||
BQ-SE",USD,0,standard,"Bonaire, Saba and Sint Eustatius (Dutch special municipalities) are exempted of VAT."
|
||||
2011-01-01,,PL,PLN,0.23,standard,Poland (member state) standard VAT rate.
|
||||
1993-01-08,2011-01-01,PL,PLN,0.22,standard,
|
||||
2011-01-04,,PN,NZD,0,standard,Pitcairn Islands (British overseas territory) is exempted of VAT.
|
||||
2011-01-01,,PT,EUR,0.23,standard,Portugal (member state) standard VAT rate.
|
||||
2010-07-01,2011-01-01,PT,EUR,0.21,standard,
|
||||
2008-07-01,2010-07-01,PT,EUR,0.2,standard,
|
||||
2005-07-01,2008-07-01,PT,EUR,0.21,standard,
|
||||
2002-06-05,2005-07-01,PT,EUR,0.19,standard,
|
||||
1995-01-01,2002-06-05,PT,EUR,0.17,standard,
|
||||
1992-03-24,1995-01-01,PT,EUR,0.16,standard,
|
||||
1988-02-01,1992-03-24,PT,EUR,0.17,standard,
|
||||
1986-01-01,1988-02-01,PT,EUR,0.16,standard,
|
||||
2011-01-01,,PT-20,EUR,0.18,standard,Azores (Portuguese autonomous region) special VAT rate.
|
||||
2011-01-01,,PT-30,EUR,0.22,standard,Madeira (Portuguese autonomous region) special VAT rate.
|
||||
2017-01-01,,RO,RON,0.19,standard,Romania (member state) standard VAT rate.
|
||||
2016-01-01,2017-01-01,RO,RON,0.2,standard,Romania (member state) standard VAT rate.
|
||||
2010-07-01,2016-01-01,RO,RON,0.24,standard,
|
||||
2000-01-01,2010-07-01,RO,RON,0.19,standard,
|
||||
1998-02-01,2000-01-01,RO,RON,0.22,standard,
|
||||
1993-07-01,1998-02-01,RO,RON,0.18,standard,
|
||||
1990-07-01,,SE,SEK,0.25,standard,Sweden (member state) standard VAT rate.
|
||||
1983-01-01,1990-07-01,SE,SEK,0.2346,standard,
|
||||
1981-11-16,1983-01-01,SE,SEK,0.2151,standard,
|
||||
1980-09-08,1981-11-16,SE,SEK,0.2346,standard,
|
||||
1977-06-01,1980-09-08,SE,SEK,0.2063,standard,
|
||||
1971-01-01,1977-06-01,SE,SEK,0.1765,standard,
|
||||
1969-01-01,1971-01-01,SE,SEK,0.1111,standard,
|
||||
2011-01-04,,"AC
|
||||
SH
|
||||
SH-AC
|
||||
SH-HL",SHP,0,standard,Ascension and Saint Helena (British overseas territory) is exempted of VAT.
|
||||
2011-01-04,,"TA
|
||||
SH-TA",GBP,0,standard,Tristan da Cunha (British oversea territory) is exempted of VAT.
|
||||
2013-07-01,,SI,EUR,0.22,standard,Slovenia (member state) standard VAT rate.
|
||||
2002-01-01,2013-07-01,SI,EUR,0.2,standard,
|
||||
1999-07-01,2002-01-01,SI,EUR,0.19,standard,
|
||||
2011-01-01,,SK,EUR,0.2,standard,Slovakia (member state) standard VAT rate.
|
||||
2004-01-01,2011-01-01,SK,EUR,0.19,standard,
|
||||
2003-01-01,2004-01-01,SK,EUR,0.2,standard,
|
||||
1996-01-01,2003-01-01,SK,EUR,0.23,standard,
|
||||
1993-08-01,1996-01-01,SK,EUR,0.25,standard,
|
||||
1993-01-01,1993-08-01,SK,EUR,0.23,standard,
|
||||
2011-01-04,,TC,USD,0,standard,Turks and Caicos Islands (British overseas territory) is exempted of VAT.
|
||||
2011-01-04,,"GB
|
||||
UK
|
||||
IM",GBP,0.2,standard,"United Kingdom (member state) standard VAT rate.
|
||||
Isle of Man (British self-governing dependency) is member of the EU VAT area and subjected to UK's standard VAT rate."
|
||||
2010-01-01,2011-01-04,"GB
|
||||
UK
|
||||
IM",GBP,0.175,standard,
|
||||
2008-12-01,2010-01-01,"GB
|
||||
UK
|
||||
IM",GBP,0.15,standard,
|
||||
1991-04-01,2008-12-01,"GB
|
||||
UK
|
||||
IM",GBP,0.175,standard,
|
||||
1979-06-18,1991-04-01,"GB
|
||||
UK
|
||||
IM",GBP,0.15,standard,
|
||||
1974-07-29,1979-06-18,"GB
|
||||
UK
|
||||
IM",GBP,0.08,standard,
|
||||
1973-04-01,1974-07-29,"GB
|
||||
UK
|
||||
IM",GBP,0.1,standard,
|
||||
2011-01-04,,VG,USD,0,standard,British Virgin Islands (British overseas territory) is exempted of VAT.
|
||||
2014-01-01,,CP,EUR,0,standard,Clipperton Island (French overseas possession) is exempted of VAT.
|
||||
2019-11-15,,CH,CHF,0.077,standard,Switzerland standard VAT (added manually)
|
||||
2019-11-15,,MC,EUR,0.196,standard,Monaco standard VAT (added manually)
|
||||
2019-11-15,,FR,EUR,0.2,standard,France standard VAT (added manually)
|
||||
2019-11-15,,GR,EUR,0.24,standard,Greece standard VAT (added manually)
|
||||
2019-11-15,,GB,EUR,0.2,standard,UK standard VAT (added manually)
|
|
Loading…
Add table
Reference in a new issue