Add generic products

This commit is contained in:
M.Ravi 2023-12-02 13:44:43 +05:30
parent 83fc4f5036
commit 45f84182df
6 changed files with 1193 additions and 11 deletions

View file

@ -168,6 +168,7 @@ LDAP_DEFAULT_START_UID = int(env('LDAP_DEFAULT_START_UID'))
# Search union over OUsss # Search union over OUsss
AUTH_LDAP_START_TLS = bool(os.environ.get('LDAP_USE_TLS', False)) AUTH_LDAP_START_TLS = bool(os.environ.get('LDAP_USE_TLS', False))
SEND_EMAIL = False
ENTIRE_SEARCH_BASE = env("ENTIRE_SEARCH_BASE") ENTIRE_SEARCH_BASE = env("ENTIRE_SEARCH_BASE")
@ -259,6 +260,7 @@ BOOTSTRAP3 = {
DCL_TEXT = env('DCL_TEXT') DCL_TEXT = env('DCL_TEXT')
DCL_SUPPORT_FROM_ADDRESS = env('DCL_SUPPORT_FROM_ADDRESS') DCL_SUPPORT_FROM_ADDRESS = env('DCL_SUPPORT_FROM_ADDRESS')
VM_BASE_PRICE = float(env('VM_BASE_PRICE'))
# from django.contrib.sites.models import Site # from django.contrib.sites.models import Site
# #

View file

@ -499,7 +499,7 @@ class StripeUtils(object):
@handleStripeError @handleStripeError
def get_or_create_tax_id_for_user(self, stripe_customer_id, vat_number, def get_or_create_tax_id_for_user(self, stripe_customer_id, vat_number,
type="eu_vat", country=""): vat_type="eu_vat", country=""):
tax_ids_list = stripe.Customer.list_tax_ids( tax_ids_list = stripe.Customer.list_tax_ids(
stripe_customer_id, stripe_customer_id,
limit=100, limit=100,
@ -520,7 +520,7 @@ class StripeUtils(object):
)) ))
tax_id_obj = stripe.Customer.create_tax_id( tax_id_obj = stripe.Customer.create_tax_id(
stripe_customer_id, stripe_customer_id,
type=type, type=vat_type,
value=vat_number, value=vat_number,
) )
return tax_id_obj return tax_id_obj

View file

@ -16,8 +16,12 @@ Including another URLconf
""" """
from django.contrib import admin from django.contrib import admin
from django.urls import re_path, path, include from django.urls import re_path, path, include
from hosting.views import PaymentOrderView
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
re_path('hosting/', include('hosting.urls')) re_path('hosting/', include('hosting.urls')),
] re_path(r'^product/(?P<product_slug>[\w-]+)/$',
PaymentOrderView.as_view(),
name='show_product'),
]

View file

@ -610,3 +610,279 @@ class UserHostingKey(models.Model):
def get_anonymous_user_instance(CustomUser): def get_anonymous_user_instance(CustomUser):
return CustomUser(email='anonymous@ungleich.ch') return CustomUser(email='anonymous@ungleich.ch')
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 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()
class UserCardDetail(AssignPermissionsMixin, models.Model):
permissions = ('view_usercarddetail',)
stripe_customer = models.ForeignKey(StripeCustomer)
last4 = models.CharField(max_length=4)
brand = models.CharField(max_length=128)
card_id = models.CharField(max_length=100, blank=True, default='')
fingerprint = models.CharField(max_length=100)
exp_month = models.IntegerField(null=False)
exp_year = models.IntegerField(null=False)
preferred = models.BooleanField(default=False)
class Meta:
permissions = (
('view_usercarddetail', 'View User Card'),
)
@classmethod
def create(cls, stripe_customer=None, last4=None, brand=None,
fingerprint=None, exp_month=None, exp_year=None, card_id=None,
preferred=False):
instance = cls.objects.create(
stripe_customer=stripe_customer, last4=last4, brand=brand,
fingerprint=fingerprint, exp_month=exp_month, exp_year=exp_year,
card_id=card_id, preferred=preferred
)
instance.assign_permissions(stripe_customer.user)
return instance
@classmethod
def get_all_cards_list(cls, stripe_customer):
"""
Get all the cards of the given customer as a list
:param stripe_customer: The StripeCustomer object
:return: A list of all cards; an empty list if the customer object is
None
"""
cards_list = []
if stripe_customer is None:
return cards_list
user_card_details = UserCardDetail.objects.filter(
stripe_customer_id=stripe_customer.id
).order_by('-preferred', 'id')
for card in user_card_details:
cards_list.append({
'last4': card.last4, 'brand': card.brand, 'id': card.id,
'exp_year': card.exp_year,
'exp_month': '{:02d}'.format(card.exp_month),
'preferred': card.preferred
})
return cards_list
@classmethod
def get_or_create_user_card_detail(cls, stripe_customer, card_details):
"""
A method that checks if a UserCardDetail object exists already
based upon the given card_details and creates it for the given
customer if it does not exist. It returns the UserCardDetail object
matching the given card_details if it exists.
:param stripe_customer: The given StripeCustomer object to whom the
card object should belong to
:param card_details: A dictionary identifying a given card
:return: UserCardDetail object
"""
try:
if ('fingerprint' in card_details and 'exp_month' in card_details
and 'exp_year' in card_details):
card_detail = UserCardDetail.objects.get(
stripe_customer=stripe_customer,
fingerprint=card_details['fingerprint'],
exp_month=card_details['exp_month'],
exp_year=card_details['exp_year']
)
else:
raise UserCardDetail.DoesNotExist()
except UserCardDetail.DoesNotExist:
preferred = False
if 'preferred' in card_details:
preferred = card_details['preferred']
card_detail = UserCardDetail.create(
stripe_customer=stripe_customer,
last4=card_details['last4'],
brand=card_details['brand'],
fingerprint=card_details['fingerprint'],
exp_month=card_details['exp_month'],
exp_year=card_details['exp_year'],
card_id=card_details['card_id'],
preferred=preferred
)
return card_detail
@staticmethod
def set_default_card(stripe_api_cus_id, stripe_source_id):
"""
Sets the given stripe source as the default source for the given
Stripe customer
:param stripe_api_cus_id: Stripe customer id
:param stripe_source_id: The Stripe source id
:return:
"""
stripe_utils = StripeUtils()
cus_response = stripe_utils.get_customer(stripe_api_cus_id)
cu = cus_response['response_object']
if stripe_source_id.startswith("pm"):
# card is a payment method
cu.invoice_settings.default_payment_method = stripe_source_id
else:
cu.default_source = stripe_source_id
cu.save()
UserCardDetail.save_default_card_local(
stripe_api_cus_id, stripe_source_id
)
@staticmethod
def set_default_card_from_stripe(stripe_api_cus_id):
stripe_utils = StripeUtils()
cus_response = stripe_utils.get_customer(stripe_api_cus_id)
cu = cus_response['response_object']
default_source = cu.default_source
if default_source is not None:
UserCardDetail.save_default_card_local(
stripe_api_cus_id, default_source
)
@staticmethod
def save_default_card_local(stripe_api_cus_id, card_id):
stripe_cust = StripeCustomer.objects.get(stripe_id=stripe_api_cus_id)
user_card_detail = UserCardDetail.objects.get(
stripe_customer=stripe_cust, card_id=card_id
)
for card in stripe_cust.usercarddetail_set.all():
card.preferred = False
card.save()
user_card_detail.preferred = True
user_card_detail.save()
@staticmethod
def get_user_card_details(stripe_customer, card_details):
"""
A utility function to check whether a StripeCustomer is already
associated with the card having given details
:param stripe_customer:
:param card_details:
:return: The UserCardDetails object if it exists, None otherwise
"""
try:
ucd = UserCardDetail.objects.get(
stripe_customer=stripe_customer,
fingerprint=card_details['fingerprint'],
exp_month=card_details['exp_month'],
exp_year=card_details['exp_year']
)
return ucd
except UserCardDetail.DoesNotExist:
return None
class VMPricing(models.Model):
name = models.CharField(max_length=255, unique=True)
vat_inclusive = models.BooleanField(default=True)
vat_percentage = models.DecimalField(
max_digits=7, decimal_places=5, blank=True, default=0
)
cores_unit_price = models.DecimalField(
max_digits=7, decimal_places=5, default=0
)
ram_unit_price = models.DecimalField(
max_digits=7, decimal_places=5, default=0
)
ssd_unit_price = models.DecimalField(
max_digits=7, decimal_places=5, default=0
)
hdd_unit_price = models.DecimalField(
max_digits=7, decimal_places=6, default=0
)
discount_name = models.CharField(max_length=255, null=True, blank=True)
discount_amount = models.DecimalField(
max_digits=6, decimal_places=2, default=0
)
stripe_coupon_id = models.CharField(max_length=255, null=True, blank=True)
def __str__(self):
display_str = self.name + ' => ' + ' - '.join([
'{}/Core'.format(self.cores_unit_price.normalize()),
'{}/GB RAM'.format(self.ram_unit_price.normalize()),
'{}/GB SSD'.format(self.ssd_unit_price.normalize()),
'{}/GB HDD'.format(self.hdd_unit_price.normalize()),
'{}% VAT'.format(self.vat_percentage.normalize())
if not self.vat_inclusive else 'VAT-Incl',
])
if self.discount_amount:
display_str = ' - '.join([
display_str,
'{} {}'.format(
self.discount_amount,
self.discount_name if self.discount_name else 'Discount'
)
])
return display_str
@classmethod
def get_vm_pricing_by_name(cls, name):
try:
pricing = VMPricing.objects.get(name=name)
except Exception as e:
logger.error(
"Error getting VMPricing with name {name}. "
"Details: {details}. Attempting to return default"
"pricing.".format(name=name, details=str(e))
)
pricing = VMPricing.get_default_pricing()
return pricing
@classmethod
def get_default_pricing(cls):
""" Returns the default pricing or None """
try:
default_pricing = VMPricing.objects.get(name='default')
except Exception as e:
logger.error(str(e))
default_pricing = None
return default_pricing

View file

@ -1,7 +1,7 @@
from django.urls import re_path from django.urls import re_path
from django.contrib.auth import views as auth_views from django.contrib.auth import views as auth_views
from .views import SSHKeyCreateView, AskSSHKeyView from .views import SSHKeyCreateView, AskSSHKeyView, PaymentOrderView, OrderConfirmationView
from .views import ( from .views import (
#PaymentVMView, #PaymentVMView,
LoginView, SignupView, SignupValidateView, SignupValidatedView, IndexView, LoginView, SignupView, SignupValidateView, SignupValidatedView, IndexView,
@ -34,4 +34,7 @@ urlpatterns = [
re_path(r'^validate/(?P<validate_slug>.*)/$', re_path(r'^validate/(?P<validate_slug>.*)/$',
SignupValidatedView.as_view(), name='validate'), SignupValidatedView.as_view(), name='validate'),
re_path(r'dashboard/?$', DashboardView.as_view(), name='dashboard'), re_path(r'dashboard/?$', DashboardView.as_view(), name='dashboard'),
re_path(r'^payment/?$', PaymentOrderView.as_view(), name='payment'),
re_path(r'^order-confirmation/?$', OrderConfirmationView.as_view(),
name='order_confirmation'),
] ]

View file

@ -1,32 +1,43 @@
import json
import uuid import uuid
import logging
import stripe
from django.conf import settings from django.conf import settings
from django.contrib.auth import authenticate, login
from django.contrib.auth.tokens import default_token_generator from django.contrib.auth.tokens import default_token_generator
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.views import LogoutView from django.contrib.auth.views import LogoutView
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect, Http404, JsonResponse
from django.urls import reverse_lazy, reverse from django.urls import reverse_lazy, reverse
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.http import urlsafe_base64_decode from django.utils.http import urlsafe_base64_decode
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.views import View from django.views import View
from django.views.generic import CreateView, TemplateView, FormView from django.views.generic import CreateView, TemplateView, FormView, DetailView
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _, get_language
from django.views.decorators.cache import never_cache, cache_control from django.views.decorators.cache import never_cache, cache_control
from django.contrib import messages from django.contrib import messages
from django.shortcuts import render from django.shortcuts import render
from dynamicweb2.ldap_manager import LdapManager from dynamicweb2.ldap_manager import LdapManager
from dynamicweb2.stripe_utils import StripeUtils
from hosting.forms import HostingUserLoginForm, HostingUserSignupForm, ResendActivationEmailForm, \ from hosting.forms import HostingUserLoginForm, HostingUserSignupForm, ResendActivationEmailForm, \
PasswordResetRequestForm, UserHostingKeyForm PasswordResetRequestForm, UserHostingKeyForm, BillingAddressForm, BillingAddressFormSignup, ProductPaymentForm, \
GenericPaymentForm
from hosting.mailer import BaseEmail from hosting.mailer import BaseEmail
from hosting.mixins import LoginViewMixin, ResendActivationLinkViewMixin, PasswordResetViewMixin, \ from hosting.mixins import LoginViewMixin, ResendActivationLinkViewMixin, PasswordResetViewMixin, \
PasswordResetConfirmViewMixin PasswordResetConfirmViewMixin
from hosting.models import CustomUser, UserHostingKey from hosting.models import CustomUser, UserHostingKey, GenericProduct, StripeCustomer, HostingOrder, UserCardDetail, \
BillingAddress, IncompletePaymentIntents, StripeTaxRate, IncompleteSubscriptions
from hosting.utils import get_vat_rate_for_country, validate_vat_number, get_vm_price_for_given_vat, \
get_all_public_keys, create_incomplete_intent_request, get_error_response_dict, show_error
decorators = [never_cache] decorators = [never_cache]
logger = logging.getLogger(__name__)
class LoginView(LoginViewMixin): class LoginView(LoginViewMixin):
template_name = "hosting/login.html" template_name = "hosting/login.html"
@ -52,7 +63,7 @@ class SignupView(CreateView):
this_base_url = "{0}://{1}".format(self.request.scheme, this_base_url = "{0}://{1}".format(self.request.scheme,
self.request.get_host()) self.request.get_host())
CustomUser.register(name, password, email, CustomUser.register(name, password, email,
app='dcl', base_url=this_base_url) app='dcl', base_url=this_base_url, send_email=settings.SEND_EMAIL)
return HttpResponseRedirect(reverse_lazy('hosting:signup-validate')) return HttpResponseRedirect(reverse_lazy('hosting:signup-validate'))
@ -351,3 +362,889 @@ class CustomLogoutView(LogoutView):
next_page = reverse_lazy('hosting:login') next_page = reverse_lazy('hosting:login')
class PaymentOrderView(FormView):
template_name = 'hositng/landing_payment.html'
def get_form_class(self):
if self.request.user.is_authenticated():
return BillingAddressForm
else:
return BillingAddressFormSignup
def get_context_data(self, **kwargs):
context = super(PaymentOrderView, self).get_context_data(**kwargs)
if 'billing_address_data' in self.request.session:
billing_address_data = self.request.session['billing_address_data']
else:
billing_address_data = {}
if self.request.user.is_authenticated():
if billing_address_data:
billing_address_form = BillingAddressForm(
initial=billing_address_data
)
else:
billing_address_form = BillingAddressForm(
instance=self.request.user.billing_addresses.order_by('-id').first()
)
user = self.request.user
if hasattr(user, 'stripecustomer'):
stripe_customer = user.stripecustomer
else:
stripe_customer = None
stripe_utils = StripeUtils()
cards_list_request = stripe_utils.get_available_payment_methods(
stripe_customer
)
cards_list = cards_list_request.get('response_object')
context.update({'cards_list': cards_list})
else:
billing_address_form = BillingAddressFormSignup(
initial=billing_address_data
)
context.update({
'stripe_key': settings.STRIPE_API_PUBLIC_KEY,
'site_url': reverse('hosting:dashboard'),
'login_form': HostingUserLoginForm(prefix='login_form'),
'billing_address_form': billing_address_form,
})
if ('generic_payment_type' in self.request.session and
self.request.session['generic_payment_type'] == 'generic'):
if 'product_id' in self.request.session:
product = GenericProduct.objects.get(
id=self.request.session['product_id']
)
context.update({'generic_payment_form': ProductPaymentForm(
prefix='generic_payment_form',
initial={'product_name': product.product_name,
'amount': float(product.get_actual_price()),
'recurring': product.product_is_subscription,
'description': product.product_description,
},
product_id=product.id
), })
else:
context.update({'generic_payment_form': GenericPaymentForm(
prefix='generic_payment_form',
), })
else:
logger.debug(f"VM creation not implemented")
return context
@cache_control(no_cache=True, must_revalidate=True, no_store=True)
def get(self, request, *args, **kwargs):
request.session.pop('vat_validation_status')
request.session.pop('card_id')
request.session.pop('token')
request.session.pop('id_payment_method')
logger.debug("Session: %s" % str(request.session))
for key, value in request.session.items():
logger.debug("Session: %s %s" % (key, value))
if (('type' in request.GET and request.GET['type'] == 'generic')
or 'product_slug' in kwargs):
request.session['generic_payment_type'] = 'generic'
if 'generic_payment_details' in request.session:
request.session.pop('generic_payment_details')
request.session.pop('product_id')
if 'product_slug' in kwargs:
logger.debug("Product slug is " + kwargs['product_slug'])
try:
product = GenericProduct.objects.get(
product_slug=kwargs['product_slug']
)
except GenericProduct.DoesNotExist as dne:
logger.error(
"Product '{}' does "
"not exist".format(kwargs['product_slug'])
)
raise Http404()
request.session['product_id'] = product.id
elif 'specs' not in request.session:
return HttpResponseRedirect(reverse('hosting:dashboard'))
return self.render_to_response(self.get_context_data())
def post(self, request, *args, **kwargs):
if 'product' in request.POST:
# query for the supplied product
product = None
try:
product = GenericProduct.objects.get(
id=request.POST['generic_payment_form-product_name']
)
except GenericProduct.DoesNotExist as dne:
logger.error(
"The requested product '{}' does not exist".format(
request.POST['generic_payment_form-product_name']
)
)
except GenericProduct.MultipleObjectsReturned as mpe:
logger.error(
"There seem to be more than one product with "
"the name {}".format(
request.POST['generic_payment_form-product_name']
)
)
product = GenericProduct.objects.all(
product_name=request.
POST['generic_payment_form-product_name']
).first()
if product is None:
return JsonResponse({})
else:
return JsonResponse({
'amount': product.get_actual_price(),
'isSubscription': product.product_is_subscription
})
if 'login_form' in request.POST:
login_form = HostingUserLoginForm(
data=request.POST, prefix='login_form'
)
if login_form.is_valid():
email = login_form.cleaned_data.get('email')
password = login_form.cleaned_data.get('password')
auth_user = authenticate(email=email, password=password)
if auth_user:
login(self.request, auth_user)
if 'product_slug' in kwargs:
return HttpResponseRedirect(
reverse('show_product',
kwargs={
'product_slug': kwargs['product_slug']}
)
)
return HttpResponseRedirect(
reverse('hosting:payment')
)
else:
context = self.get_context_data()
context['login_form'] = login_form
return self.render_to_response(context)
if request.user.is_authenticated():
address_form = BillingAddressForm(
data=request.POST,
)
else:
address_form = BillingAddressFormSignup(
data=request.POST,
)
if address_form.is_valid():
# Check if we are in a generic payment case and handle the generic
# payment details form before we go on to verify payment
if ('generic_payment_type' in request.session and
self.request.session['generic_payment_type'] == 'generic'):
if 'product_id' in request.session:
generic_payment_form = ProductPaymentForm(
data=request.POST, prefix='generic_payment_form',
product_id=request.session['product_id']
)
else:
generic_payment_form = GenericPaymentForm(
data=request.POST, prefix='generic_payment_form'
)
if generic_payment_form.is_valid():
logger.debug("Generic payment form is valid.")
if 'product_id' in request.session:
product = generic_payment_form.product
else:
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,
"vat_rate": 0 if product.exclude_vat_calculations else
user_country_vat_rate * 100,
"vat_amount": 0 if product.exclude_vat_calculations
else 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'
),
"description": generic_payment_form.cleaned_data.get(
'description'
),
"product_id": product.id,
"product_slug": product.product_slug,
"recurring_interval":
product.product_subscription_interval,
"exclude_vat_calculations": product.exclude_vat_calculations
}
request.session["generic_payment_details"] = (
gp_details
)
else:
logger.debug("Generic payment form invalid")
context = self.get_context_data()
context['generic_payment_form'] = generic_payment_form
context['billing_address_form'] = address_form
return self.render_to_response(context)
id_payment_method = self.request.POST.get('id_payment_method',
None)
if id_payment_method == 'undefined':
id_payment_method = address_form.cleaned_data.get('card')
request.session["id_payment_method"] = id_payment_method
logger.debug("id_payment_method is %s" % id_payment_method)
if request.user.is_authenticated():
this_user = {
'email': request.user.email,
'name': request.user.name
}
customer = StripeCustomer.get_or_create(
email=this_user.get('email'),
id_payment_method=id_payment_method
)
else:
user_email = address_form.cleaned_data.get('email')
user_name = address_form.cleaned_data.get('name')
this_user = {
'email': user_email,
'name': user_name
}
try:
custom_user = CustomUser.objects.get(email=user_email)
customer = StripeCustomer.objects.filter(
user_id=custom_user.id).first()
if customer is None:
logger.debug(
("User {email} is already registered with us."
"But, StripeCustomer does not exist for {email}."
"Hence, creating a new StripeCustomer.").format(
email=user_email
)
)
customer = StripeCustomer.create_stripe_api_customer(
email=user_email,
id_payment_method=id_payment_method,
customer_name=user_name)
except CustomUser.DoesNotExist:
logger.debug(
("StripeCustomer does not exist for {email}."
"Hence, creating a new StripeCustomer.").format(
email=user_email
)
)
customer = StripeCustomer.create_stripe_api_customer(
email=user_email,
id_payment_method=id_payment_method,
customer_name=user_name)
billing_address = address_form.save()
request.session["billing_address_id"] = billing_address.id
request.session['billing_address_data'] = address_form.cleaned_data
request.session['user'] = this_user
# Get or create stripe customer
if not customer:
address_form.add_error(
"__all__", "Invalid credit card"
)
return self.render_to_response(
self.get_context_data(
billing_address_form=address_form
)
)
if type(customer) is StripeCustomer:
request.session['customer'] = customer.stripe_id
else:
request.session['customer'] = customer
vat_number = address_form.cleaned_data.get('vat_number').strip()
if vat_number:
validate_result = validate_vat_number(
stripe_customer_id=request.session['customer'],
billing_address_id=billing_address.id
)
if 'error' in validate_result and validate_result['error']:
messages.add_message(
request, messages.ERROR, validate_result["error"],
extra_tags='vat_error'
)
return HttpResponseRedirect(
reverse('hosting:payment') + '#vat_error'
)
request.session["vat_validation_status"] = validate_result["status"]
# For generic payment we take the user directly to confirmation
if ('generic_payment_type' in request.session and
self.request.session['generic_payment_type'] == 'generic'):
return HttpResponseRedirect(
reverse('hosting:order_confirmation'))
else:
self.request.session['order_confirm_url'] = reverse('hosting:order_confirmation')
return HttpResponseRedirect(
reverse('hosting:add_ssh_key'))
else:
context = self.get_context_data()
context['billing_address_form'] = address_form
return self.render_to_response(context)
class OrderConfirmationView(DetailView, FormView):
form_class = UserHostingKeyForm
template_name = "hosting/order_detail.html"
payment_template_name = 'hosting/landing_payment.html'
context_object_name = "order"
model = HostingOrder
def get_form_kwargs(self):
kwargs = super(OrderConfirmationView, self).get_form_kwargs()
kwargs.update({'request': self.request})
return kwargs
@cache_control(no_cache=True, must_revalidate=True, no_store=True)
def get(self, request, *args, **kwargs):
context = {}
# this is amount to be charge/subscribed before VAT and discount
# and expressed in chf. To convert to cents, multiply by 100
amount_to_charge = 0
vm_specs = None
if (('specs' not in request.session or 'user' not in request.session)
and 'generic_payment_type' not in request.session):
return HttpResponseRedirect(reverse('hosting:dashboards'))
if 'id_payment_method' in self.request.session:
payment_method = self.request.session['id_payment_method']
logger.debug("id_payment_method: %s" % payment_method)
stripe_utils = StripeUtils()
card_details = stripe_utils.get_cards_details_from_payment_method(
payment_method
)
if not card_details.get('response_object'):
return HttpResponseRedirect(reverse('hosting:payment'))
card_details_response = card_details['response_object']
context['cc_last4'] = card_details_response['last4']
context['cc_brand'] = card_details_response['brand']
context['cc_exp_year'] = card_details_response['exp_year']
context['cc_exp_month'] = '{:02d}'.format(
card_details_response['exp_month'])
context['id_payment_method'] = payment_method
else:
# TODO check when we go through this case (to me, it seems useless)
card_id = self.request.session.get('card_id')
logger.debug("NO id_payment_method, using card: %s" % card_id)
card_detail = UserCardDetail.objects.get(id=card_id)
context['cc_last4'] = card_detail.last4
context['cc_brand'] = card_detail.brand
context['cc_exp_year'] = card_detail.exp_year
context['cc_exp_month'] = '{:02d}'.format(card_detail.exp_month)
if ('generic_payment_type' in request.session and
self.request.session['generic_payment_type'] == 'generic'):
if "vat_validation_status" in request.session and (
request.session["vat_validation_status"] == "verified" or
request.session["vat_validation_status"] == "not_needed"):
request.session['generic_payment_details']['vat_rate'] = 0
request.session['generic_payment_details']['vat_amount'] = 0
request.session['generic_payment_details']['amount'] = (
request.session['generic_payment_details']['amount_before_vat']
)
context.update({
'generic_payment_details':
request.session['generic_payment_details'],
})
amount_to_charge = request.session['generic_payment_details']['amount']
else:
vm_specs = request.session.get('specs')
user_vat_country = (
request.session.get('billing_address_data').get("country")
)
user_country_vat_rate = get_vat_rate_for_country(user_vat_country)
price, vat, vat_percent, discount = get_vm_price_for_given_vat(
cpu=vm_specs['cpu'],
memory=vm_specs['memory'],
ssd_size=vm_specs['disk_size'],
pricing_name=vm_specs['pricing_name'],
vat_rate=user_country_vat_rate * 100
)
vm_specs["price"] = price
vm_specs["price_after_discount"] = price - discount["amount"]
amount_to_charge = price
vat_number = request.session.get('billing_address_data').get("vat_number")
billing_address = BillingAddress.objects.get(
id=request.session["billing_address_id"])
if vat_number:
validate_result = validate_vat_number(
stripe_customer_id=request.session['customer'],
billing_address_id=billing_address.id
)
if 'error' in validate_result and validate_result['error']:
messages.add_message(
request, messages.ERROR, validate_result["error"],
extra_tags='vat_error'
)
return HttpResponseRedirect(
reverse('datacenterlight:payment') + '#vat_error'
)
request.session["vat_validation_status"] = validate_result["status"]
if user_vat_country.lower() == "ch":
vm_specs["vat"] = vat
vm_specs["vat_percent"] = vat_percent
vm_specs["vat_validation_status"] = "ch_vat"
elif ("vat_validation_status" in request.session and
(request.session["vat_validation_status"] == "verified" or
request.session["vat_validation_status"] == "not_needed")):
vm_specs["vat_percent"] = 0
vm_specs["vat"] = 0
vm_specs["vat_validation_status"] = request.session["vat_validation_status"]
else:
vm_specs["vat"] = vat
vm_specs["vat_percent"] = vat_percent
vm_specs["vat_validation_status"] = request.session[
"vat_validation_status"] if "vat_validation_status" in request.session else ""
vm_specs["vat_country"] = user_vat_country
vm_specs["price_with_vat"] = round(price * (1 + vm_specs["vat_percent"] * 0.01), 2)
vm_specs["price_after_discount"] = round(price - discount['amount'], 2)
vm_specs["price_after_discount_with_vat"] = round(
(price - discount['amount']) * (1 + vm_specs["vat_percent"] * 0.01), 2)
discount["amount_with_vat"] = round(vm_specs["price_with_vat"] - vm_specs["price_after_discount_with_vat"],
2)
vm_specs["total_price"] = vm_specs["price_after_discount_with_vat"]
vm_specs["discount"] = discount
logger.debug(vm_specs)
request.session['specs'] = vm_specs
context.update({
'vm': vm_specs,
'form': UserHostingKeyForm(request=self.request),
'keys': get_all_public_keys(self.request.user)
})
is_subscription = False
if ('generic_payment_type' not in request.session or
(request.session['generic_payment_details']['recurring'])):
# Obtain PaymentIntent so that we can initiate and charge
# the customer
is_subscription = True
logger.debug("CASE: Subscription")
else:
logger.debug("CASE: One time payment")
stripe_utils = StripeUtils()
payment_intent_response = stripe_utils.get_payment_intent(
int(amount_to_charge * 100),
customer=request.session['customer']
)
payment_intent = payment_intent_response.get(
'response_object')
if not payment_intent:
logger.error("Could not create payment_intent %s" %
str(payment_intent_response))
else:
logger.debug("payment_intent.client_secret = %s" %
str(payment_intent.client_secret))
context.update({
'payment_intent_secret': payment_intent.client_secret
})
logger.debug("Request %s" % create_incomplete_intent_request(
self.request))
logger.debug("%s" % str(payment_intent))
logger.debug("customer %s" % request.session['customer'])
logger.debug("card_details_response %s" % card_details_response)
logger.debug("request.session[generic_payment_details] %s" % request.session["generic_payment_details"])
logger.debug("request.session[billing_address_data] %s" % request.session["billing_address_data"])
IncompletePaymentIntents.objects.create(
request=create_incomplete_intent_request(self.request),
payment_intent_id=payment_intent.id,
stripe_api_cus_id=request.session['customer'],
card_details_response=json.dumps(card_details_response),
stripe_subscription_id=None,
stripe_charge_id=None,
gp_details=json.dumps(request.session["generic_payment_details"]),
billing_address_data=json.dumps(request.session["billing_address_data"])
)
logger.debug("IncompletePaymentIntent done")
context.update({
'site_url': reverse('datacenterlight:index'),
'page_header_text': _('Confirm Order'),
'billing_address_data': (
request.session.get('billing_address_data')
),
#'cms_integration': get_cms_integration('default'),
'error_msg': get_error_response_dict("Error", request),
'success_msg': {
'msg_title': _("Thank you !"),
'msg_body': _("Your product will be provisioned as soon as "
"we receive the payment."),
'redirect': reverse('hosting:invoices') if
request.user.is_authenticated() else
reverse('datacenterlight:index')
},
'stripe_key': settings.STRIPE_API_PUBLIC_KEY,
'is_subscription': str(is_subscription).lower()
})
return render(request, self.template_name, context)
def post(self, request, *args, **kwargs):
stripe_onetime_charge = None
stripe_customer_obj = None
gp_details = None
specs = None
vm_template_id = 0
template = None
user = request.session.get('user')
stripe_api_cus_id = request.session.get('customer')
stripe_utils = StripeUtils()
logger.debug("user=%s stripe_api_cus_id=%s" % (user, stripe_api_cus_id))
card_details_response = None
new_user_hosting_key_id = None
card_id = None
generic_payment_type = None
generic_payment_details = None
stripe_subscription_obj = None
if 'generic_payment_details' in request.session:
generic_payment_details = request.session[
'generic_payment_details']
if 'generic_payment_type' in request.session:
generic_payment_type = request.session['generic_payment_type']
if 'new_user_hosting_key_id' in self.request.session:
new_user_hosting_key_id = request.session[
'new_user_hosting_key_id']
if 'card_id' in request.session:
card_id = request.session.get('card_id')
req = {
'scheme': self.request.scheme,
'host': self.request.get_host(),
'language': get_language(),
'new_user_hosting_key_id': new_user_hosting_key_id,
'card_id': card_id,
'generic_payment_type': generic_payment_type,
'generic_payment_details': generic_payment_details,
'user': user
}
if 'id_payment_method' in request.session:
card_details = stripe_utils.get_cards_details_from_payment_method(
request.session.get('id_payment_method')
)
logger.debug(
"card_details=%s" % (card_details))
if not card_details.get('response_object'):
msg = card_details.get('error')
return show_error(msg, self.request)
card_details_response = card_details['response_object']
card_details_dict = {
'last4': card_details_response['last4'],
'brand': card_details_response['brand'],
'card_id': card_details_response['card_id']
}
stripe_customer_obj = StripeCustomer.objects.filter(
stripe_id=stripe_api_cus_id).first()
if stripe_customer_obj:
ucd = UserCardDetail.get_user_card_details(
stripe_customer_obj, card_details_response
)
if not ucd:
acc_result = stripe_utils.associate_customer_card(
stripe_api_cus_id, request.session['id_payment_method'],
set_as_default=True
)
if acc_result['response_object'] is None:
msg = _(
'An error occurred while associating the card.'
' Details: {details}'.format(
details=acc_result['error']
)
)
return show_error(msg, self.request)
else:
# Associate PaymentMethod with the stripe customer
# and set it as the default source
acc_result = stripe_utils.associate_customer_card(
stripe_api_cus_id, request.session['id_payment_method'],
set_as_default=True
)
if acc_result['response_object'] is None:
msg = _(
'An error occurred while associating the card.'
' Details: {details}'.format(
details=acc_result['error']
)
)
return show_error(msg, self.request)
elif 'card_id' in request.session:
card_id = request.session.get('card_id')
user_card_detail = UserCardDetail.objects.get(id=card_id)
card_details_dict = {
'last4': user_card_detail.last4,
'brand': user_card_detail.brand,
'card_id': user_card_detail.card_id
}
UserCardDetail.set_default_card(
stripe_api_cus_id=stripe_api_cus_id,
stripe_source_id=user_card_detail.card_id
)
logger.debug("card_details_dict=%s" % card_details_dict)
else:
response = {
'status': False,
'redirect': "{url}#{section}".format(
url=reverse('datacenterlight:payment'),
section='payment_error'),
'msg_title': str(_('Error.')),
'msg_body': str(
_('There was a payment related error.'
' On close of this popup, you will be redirected back to'
' the payment page.'))
}
return JsonResponse(response)
if ('generic_payment_type' in request.session and
self.request.session['generic_payment_type'] == 'generic'):
gp_details = self.request.session['generic_payment_details']
logger.debug("gp_details=%s" % gp_details)
if gp_details['recurring']:
# generic recurring payment
logger.debug("Commencing a generic recurring payment")
if ('generic_payment_type' not in request.session or
(request.session['generic_payment_details']['recurring'])):
recurring_interval = 'month'
logger.debug("'generic_payment_type' not in request.session or"
"(request.session['generic_payment_details']['recurring']")
if 'generic_payment_details' in request.session:
vat_percent = request.session['generic_payment_details']['vat_rate']
vat_country = request.session['generic_payment_details']['vat_country']
if 'discount' in request.session['generic_payment_details']:
discount = request.session['generic_payment_details']['discount']
else:
discount = {'name': '', 'amount': 0, 'coupon_id': ''}
amount_to_be_charged = (
round(
request.session['generic_payment_details']['amount_before_vat'],
2
)
)
plan_name = "generic-{0}-{1:.2f}".format(
request.session['generic_payment_details']['product_id'],
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')
vm_template_id = template.get('id', 1)
cpu = specs.get('cpu')
memory = specs.get('memory')
disk_size = specs.get('disk_size')
amount_to_be_charged = specs.get('price')
vat_percent = specs.get('vat_percent')
vat_country = specs.get('vat_country')
discount = specs.get('discount')
plan_name = StripeUtils.get_stripe_plan_name(
cpu=cpu,
memory=memory,
disk_size=disk_size,
price=amount_to_be_charged
)
stripe_plan_id = StripeUtils.get_stripe_plan_id(
cpu=cpu,
ram=memory,
ssd=disk_size,
version=1,
app='dcl',
price=amount_to_be_charged
)
logger.debug(specs)
stripe_plan = stripe_utils.get_or_create_stripe_plan(
amount=amount_to_be_charged,
name=plan_name,
stripe_plan_id=stripe_plan_id,
interval=recurring_interval
)
# Create StripeTaxRate if applicable to the user
logger.debug("vat_percent = %s, vat_country = %s" %
(vat_percent, vat_country)
)
stripe_tax_rate = None
if vat_percent > 0:
try:
stripe_tax_rate = StripeTaxRate.objects.get(
description="VAT for %s" % vat_country
)
print("Stripe Tax Rate exists")
except StripeTaxRate.DoesNotExist as dne:
print("StripeTaxRate does not exist")
tax_rate_obj = stripe.TaxRate.create(
display_name="VAT",
description="VAT for %s" % vat_country,
jurisdiction=vat_country,
percentage=vat_percent,
inclusive=False,
)
stripe_tax_rate = StripeTaxRate.objects.create(
display_name=tax_rate_obj.display_name,
description=tax_rate_obj.description,
jurisdiction=tax_rate_obj.jurisdiction,
percentage=tax_rate_obj.percentage,
inclusive=False,
tax_rate_id=tax_rate_obj.id
)
logger.debug("Created StripeTaxRate %s" %
stripe_tax_rate.tax_rate_id)
subscription_result = stripe_utils.subscribe_customer_to_plan(
stripe_api_cus_id,
[{"plan": stripe_plan.get('response_object').stripe_plan_id}],
coupon=(discount['stripe_coupon_id']
if 'name' in discount and
discount['name'] is not None and
'ipv6' in discount['name'].lower() and
discount['stripe_coupon_id']
else ""),
tax_rates=[stripe_tax_rate.tax_rate_id] if stripe_tax_rate else [],
default_payment_method=request.session['id_payment_method']
)
stripe_subscription_obj = subscription_result.get('response_object')
logger.debug(stripe_subscription_obj)
latest_invoice = stripe.Invoice.retrieve(
stripe_subscription_obj.latest_invoice)
subscription_status = ''
if stripe_subscription_obj:
subscription_status = stripe_subscription_obj.status
# Check if the subscription was approved and is active
if (stripe_subscription_obj is None
or (stripe_subscription_obj.status != 'active'
and stripe_subscription_obj.status != 'incomplete')):
# At this point, we have created a Stripe API card and
# associated it with the customer; but the transaction failed
# due to some reason. So, we would want to dissociate this card
# here.
# ...
msg = subscription_result.get('error')
return show_error(msg, self.request)
elif stripe_subscription_obj.status == 'incomplete':
# Store params so that they can be retrieved later
IncompleteSubscriptions.objects.create(
subscription_id=stripe_subscription_obj.id,
subscription_status=subscription_status,
name=user.get('name'),
email=user.get('email'),
request=json.dumps(req),
stripe_api_cus_id=stripe_api_cus_id,
card_details_response=json.dumps(card_details_response),
stripe_subscription_obj=json.dumps(
stripe_subscription_obj) if stripe_customer_obj else '',
stripe_onetime_charge=json.dumps(
stripe_onetime_charge) if stripe_onetime_charge else '',
gp_details=json.dumps(gp_details) if gp_details else '',
specs=json.dumps(specs) if specs else '',
vm_template_id=vm_template_id if vm_template_id else 0,
template=json.dumps(template) if template else '',
billing_address_data=json.dumps(
request.session.get('billing_address_data')
)
)
pi = stripe.PaymentIntent.retrieve(
latest_invoice.payment_intent
)
# TODO: requires_attention is probably wrong value to compare
if request.user.is_authenticated():
if 'generic_payment_details' in request.session:
redirect_url = reverse('hosting:invoices')
else:
redirect_url = reverse('hosting:virtual_machines')
else:
redirect_url = reverse('datacenterlight:index')
if (pi.status == 'requires_attention' or
pi.status == 'requires_source_action'):
logger.debug("Display SCA authentication %s " % pi.status)
context = {
'sid': stripe_subscription_obj.id,
'payment_intent_secret': pi.client_secret,
'STRIPE_PUBLISHABLE_KEY': settings.STRIPE_API_PUBLIC_KEY,
'showSCA': True,
'success': {
'status': True,
'redirect': redirect_url,
'msg_title': str(_('Thank you for the order.')),
'msg_body': str(
_('Your product will be provisioned as soon as'
' we receive a payment confirmation from '
'Stripe. We will send you a confirmation '
'email. You can always contact us at '
'support@datacenterlight.ch')
)
},
'error': {
'status': False,
'redirect': "{url}#{section}".format(
url=(reverse(
'show_product',
kwargs={'product_slug':
request.session[
'generic_payment_details']
['product_slug']}
) if 'generic_payment_details' in request.session else
reverse('datacenterlight:payment')
),
section='payment_error'
),
'msg_title': str(_('Error.')),
'msg_body': str(
_('There was a payment related error.'
' On close of this popup, you will be redirected back to'
' the payment page.')
)
}
}
return JsonResponse(context)
else:
logger.debug(
"Handle this case when "
"stripe.subscription_status is incomplete but "
"pi.status is neither requires_attention nor "
"requires_source_action")
msg = subscription_result.get('error')
return show_error(msg, self.request)
# the code below is executed for
# a) subscription case
# b) the subscription object is active itself, without requiring
# SCA
# provisioning_response = do_provisioning(
# req, stripe_api_cus_id,
# card_details_response, stripe_subscription_obj,
# stripe_onetime_charge, gp_details, specs, vm_template_id,
# template, request.session.get('billing_address_data'),
# self.request
# )
if (provisioning_response and
type(provisioning_response['response']) == JsonResponse):
new_user = provisioning_response.get('user', None)
if new_user:
login(self.request, new_user)
return provisioning_response['response']
response = {
'status': True,
'redirect': (
reverse('hosting:virtual_machines')
if request.user.is_authenticated()
else reverse('datacenterlight:index')
),
'msg_title': str(_('Thank you for the order.')),
'msg_body': str(
_('Your VM will be up and running in a few moments.'
' We will send you a confirmation email as soon as'
' it is ready.'))
}
return JsonResponse(response)