import json import uuid import logging import stripe 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.mixins import LoginRequiredMixin from django.contrib.auth.views import LogoutView from django.core.files.base import ContentFile from django.http import HttpResponseRedirect, Http404, JsonResponse from django.urls import reverse_lazy, reverse from django.utils.decorators import method_decorator from django.utils.http import urlsafe_base64_decode from django.utils.safestring import mark_safe from django.views import View from django.views.generic import CreateView, TemplateView, FormView, DetailView from django.utils.translation import gettext_lazy as _, get_language from django.views.decorators.cache import never_cache, cache_control from django.contrib import messages from django.shortcuts import render from dynamicweb2.ldap_manager import LdapManager from dynamicweb2.stripe_utils import StripeUtils from hosting.forms import HostingUserLoginForm, HostingUserSignupForm, ResendActivationEmailForm, \ PasswordResetRequestForm, UserHostingKeyForm, BillingAddressForm, BillingAddressFormSignup, ProductPaymentForm, \ GenericPaymentForm from hosting.mailer import BaseEmail from hosting.mixins import LoginViewMixin, ResendActivationLinkViewMixin, PasswordResetViewMixin, \ PasswordResetConfirmViewMixin 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] logger = logging.getLogger(__name__) class LoginView(LoginViewMixin): template_name = "hosting/login.html" form_class = HostingUserLoginForm success_url = reverse_lazy('hosting:dashboard') class SignupView(CreateView): template_name = 'hosting/signup.html' form_class = HostingUserSignupForm model = CustomUser success_url = reverse_lazy('hosting:dashboard') def get_success_url(self): next_url = self.request.session.get( 'next', self.success_url) return next_url def form_valid(self, form): name = form.cleaned_data.get('name') email = form.cleaned_data.get('email') password = form.cleaned_data.get('password') this_base_url = "{0}://{1}".format(self.request.scheme, self.request.get_host()) CustomUser.register(name, password, email, app='dcl', base_url=this_base_url, send_email=settings.SEND_EMAIL) return HttpResponseRedirect(reverse_lazy('hosting:signup-validate')) @method_decorator(decorators) def get(self, request, *args, **kwargs): if self.request.user.is_authenticated: return HttpResponseRedirect(self.get_success_url()) return super(SignupView, self).get(request, *args, **kwargs) class SignupValidateView(TemplateView): template_name = "hosting/signup_validate.html" def get_context_data(self, **kwargs): context = super(SignupValidateView, self).get_context_data(**kwargs) login_url = '' + str(_('login')) + '' home_url = 'Data Center Light' message = '{signup_success_message} {lurl} \
{go_back} {hurl}.'.format( signup_success_message=_( 'Thank you for signing up. We have sent an email to you. ' 'Please follow the instructions in it to activate your ' 'account. Once activated, you can login using'), go_back=_('Go back to'), lurl=login_url, hurl=home_url ) context['message'] = mark_safe(message) context['section_title'] = _('Sign up') return context class SignupValidatedView(SignupValidateView): template_name = "hosting/signup_validate.html" def get_context_data(self, **kwargs): context = super(SignupValidateView, self).get_context_data(**kwargs) validated = CustomUser.validate_url(self.kwargs['validate_slug']) login_url = '' + str(_('login')) + '' section_title = _('Account activation') user = CustomUser.objects.filter( validation_slug=self.kwargs['validate_slug']).first() if validated: message = ('{account_activation_string}
' ' {login_string} {lurl}.').format( account_activation_string=_( "Your account has been activated."), login_string=_("You can now"), lurl=login_url) email_data = { 'subject': _('Welcome to Data Center Light!'), 'to': user.email, 'context': { 'base_url': "{0}://{1}".format( self.request.scheme, self.request.get_host() ) }, 'template_name': 'welcome_user', 'template_path': 'datacenterlight/emails/', 'from_address': settings.DCL_SUPPORT_FROM_ADDRESS, } email = BaseEmail(**email_data) email.send() else: home_url = 'Data Center Light' message = '{sorry_message}
{go_back_to} {hurl}'.format( sorry_message=_("Sorry. Your request is invalid."), go_back_to=_('Go back to'), hurl=home_url ) context['message'] = mark_safe(message) context['section_title'] = section_title return context @method_decorator(decorators) def get(self, request, *args, **kwargs): if self.request.user.is_authenticated: return HttpResponseRedirect(reverse_lazy('hosting:dashboard')) return super(SignupValidatedView, self).get(request, *args, **kwargs) class ResendActivationEmailView(ResendActivationLinkViewMixin): template_name = 'hosting/resend_activation_link.html' form_class = ResendActivationEmailForm success_url = reverse_lazy('hosting:login') email_template_path = 'datacenterlight/emails/' email_template_name = 'user_activation' class PasswordResetView(PasswordResetViewMixin): site = 'dcl' template_name = 'hosting/reset_password.html' form_class = PasswordResetRequestForm success_url = reverse_lazy('hosting:login') template_email_path = 'hosting/emails/' class PasswordResetConfirmView(PasswordResetConfirmViewMixin): template_name = 'hosting/confirm_reset_password.html' success_url = reverse_lazy('hosting:login') def post(self, request, uidb64=None, token=None, *arg, **kwargs): try: uid = urlsafe_base64_decode(uidb64) user = CustomUser.objects.get(pk=uid) # opennebula_client = OpenNebulaManager( # email=user.username, # password=user.password, # ) except (TypeError, ValueError, OverflowError, CustomUser.DoesNotExist): user = None opennebula_client = None form = self.form_class(request.POST) if user is not None and default_token_generator.check_token(user, token): if form.is_valid(): ldap_manager = LdapManager() new_password = form.cleaned_data['new_password2'] # Make sure the user have an ldap account already user.create_ldap_account(new_password) # We are changing password in ldap before changing in database because # ldap have more chances of failure than local database if ldap_manager.change_password(user.username, new_password): user.set_password(new_password) user.save() messages.success(request, _('Password has been reset.')) # Change opennebula password opennebula_client.change_user_password(user.password) return self.form_valid(form) messages.error( request, _('Password reset has not been successful.')) form.add_error(None, _('Password reset has not been successful.')) return self.form_invalid(form) else: error_msg = _('The reset password link is no longer valid.') messages.error(request, _(error_msg)) form.add_error(None, error_msg) return self.form_invalid(form) class SSHKeyCreateView(FormView): form_class = UserHostingKeyForm model = UserHostingKey template_name = 'hosting/user_key.html' login_url = reverse_lazy('hosting:login') context_object_name = "virtual_machine" success_url = reverse_lazy('hosting:ssh_keys') def get_form_kwargs(self): kwargs = super(SSHKeyCreateView, self).get_form_kwargs() kwargs.update({'request': self.request}) return kwargs def form_valid(self, form): form.save() if settings.DCL_SSH_KEY_NAME_PREFIX in form.instance.name: content = ContentFile(form.cleaned_data.get('private_key')) filename = form.cleaned_data.get( 'name') + '_' + str(uuid.uuid4())[:8] + '_private.pem' form.instance.private_key.save(filename, content) context = self.get_context_data() next_url = self.request.session.get( 'next', reverse_lazy('hosting:create_virtual_machine') ) if 'next' in self.request.session: context.update({ 'next_url': next_url }) del (self.request.session['next']) if form.cleaned_data.get('private_key'): context.update({ 'private_key': form.cleaned_data.get('private_key'), 'key_name': form.cleaned_data.get('name'), 'form': UserHostingKeyForm(request=self.request), }) if self.request.user.is_authenticated: owner = self.request.user # manager = OpenNebulaManager( # email=owner.username, # password=owner.password # ) # keys_to_save = get_all_public_keys(self.request.user) # manager.save_key_in_opennebula_user('\n'.join(keys_to_save)) else: self.request.session["new_user_hosting_key_id"] = form.instance.id return HttpResponseRedirect(self.success_url) def post(self, request, *args, **kwargs): form = self.get_form() required = 'add_ssh' in self.request.POST form.fields['name'].required = required form.fields['public_key'].required = required if form.is_valid(): return self.form_valid(form) else: return self.form_invalid(form) class AskSSHKeyView(SSHKeyCreateView): form_class = UserHostingKeyForm template_name = "hosting/add_ssh_key.html" success_url = reverse_lazy('hosting:order_confirmation') context_object_name = "dcl_vm_buy_add_ssh_key" # @cache_control(no_cache=True, must_revalidate=True, no_store=True) def get(self, request, *args, **kwargs): context = { 'site_url': reverse_lazy('hosting:login'), # 'cms_integration': get_cms_integration('default'), 'form': UserHostingKeyForm(request=self.request), # 'keys': get_all_public_keys(self.request.user) } return render(request, self.template_name, context) def post(self, request, *args, **kwargs): self.success_url = self.request.session.get("order_confirm_url") return super(AskSSHKeyView, self).post(self, request, *args, **kwargs) class IndexView(View): template_name = "hosting/index.html" def get_context_data(self, **kwargs): # TODO refactor below templates = [] data = None context = { 'hosting': "nodejs", 'hosting_long': "NodeJS", 'domain': "node-hosting.ch", 'google_analytics': "UA-62285904-7", 'email': "info@node-hosting.ch", 'vm_types': data # 'vm_types': VirtualMachineType.get_serialized_vm_types(), } return context @method_decorator(decorators) def get(self, request, *args, **kwargs): context = self.get_context_data() return render(request, self.template_name, context) class DashboardView(LoginRequiredMixin, View): template_name = "hosting/dashboard.html" login_url = reverse_lazy('hosting:login') def get_context_data(self, **kwargs): context = {} return context @method_decorator(decorators) def get(self, request, *args, **kwargs): context = self.get_context_data() context['has_invoices'] = False bills = [] # try: # if hasattr(self.request.user, 'stripecustomer'): # bills = MonthlyHostingBill.objects.filter( # customer=self.request.user.stripecustomer # ) # if len(bills) > 0: # context['has_invoices'] = True # except MonthlyHostingBill.DoesNotExist as dne: # logger.error("{}'s monthly hosting bill not imported ?".format( # self.request.user.email # )) return render(request, self.template_name, context) class CustomLogoutView(LogoutView): 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)