forked from uncloud/uncloud
302 lines
13 KiB
Python
302 lines
13 KiB
Python
|
import logging
|
||
|
import json
|
||
|
|
||
|
from django.shortcuts import redirect, render
|
||
|
from django.contrib import messages
|
||
|
from django.utils.translation import get_language, ugettext_lazy as _
|
||
|
from django.contrib.auth.decorators import login_required
|
||
|
from django.views.decorators.cache import cache_control
|
||
|
from django.utils.decorators import method_decorator
|
||
|
from django.views import View
|
||
|
from django.views.generic import FormView, DetailView
|
||
|
from django.views.generic.list import ListView
|
||
|
from matrixhosting.forms import RequestHostedVMForm, BillingAddressForm
|
||
|
from django.urls import reverse
|
||
|
from django.conf import settings
|
||
|
from django.http import (
|
||
|
HttpResponseRedirect, JsonResponse
|
||
|
)
|
||
|
from rest_framework import viewsets, permissions
|
||
|
|
||
|
from uncloud_pay.models import PricingPlan
|
||
|
from uncloud_pay.utils import get_order_total_with_vat
|
||
|
from uncloud_pay.models import *
|
||
|
from uncloud_pay.utils import validate_vat_number
|
||
|
from uncloud_pay.selectors import get_billing_address_for_user
|
||
|
import uncloud_pay.stripe as uncloud_stripe
|
||
|
from .models import VMInstance
|
||
|
from .serializers import *
|
||
|
|
||
|
logger = logging.getLogger(__name__)
|
||
|
|
||
|
class PricingView(View):
|
||
|
def get(self, request, **args):
|
||
|
subtotal, subtotal_after_discount, price_after_discount_with_vat, vat, vat_percent, discount = get_order_total_with_vat(
|
||
|
request.GET.get('cores'),
|
||
|
request.GET.get('memory'),
|
||
|
request.GET.get('storage'),
|
||
|
pricing_name = args['name']
|
||
|
)
|
||
|
return JsonResponse({'subtotal': subtotal})
|
||
|
|
||
|
class IndexView(FormView):
|
||
|
template_name = "matrixhosting/index.html"
|
||
|
form_class = RequestHostedVMForm
|
||
|
success_url = "/matrixhosting#requestform"
|
||
|
success_message = "Thank you, we will contact you as soon as possible"
|
||
|
|
||
|
|
||
|
def get_context_data(self, **kwargs):
|
||
|
context = super().get_context_data(**kwargs)
|
||
|
context['matrix_vm_pricing'] = PricingPlan.get_default_pricing()
|
||
|
return context
|
||
|
|
||
|
def form_valid(self, form):
|
||
|
self.request.session['order'] = form.cleaned_data
|
||
|
subtotal, subtotal_with_discount, total, vat, vat_percent, discount = get_order_total_with_vat(
|
||
|
form.cleaned_data['cores'],
|
||
|
form.cleaned_data['memory'],
|
||
|
form.cleaned_data['storage'],
|
||
|
form.cleaned_data['pricing_name'],
|
||
|
False
|
||
|
)
|
||
|
self.request.session['pricing'] = {'name': form.cleaned_data['pricing_name'],
|
||
|
'subtotal': subtotal, 'vat': vat,
|
||
|
'vat_percent': vat_percent, 'discount': discount}
|
||
|
return HttpResponseRedirect(reverse('matrix:payment'))
|
||
|
|
||
|
|
||
|
class OrderPaymentView(FormView):
|
||
|
template_name = 'matrixhosting/payment.html'
|
||
|
success_url = 'matrix:order_confirmation'
|
||
|
form_class = BillingAddressForm
|
||
|
|
||
|
@method_decorator(login_required)
|
||
|
def dispatch(self, *args, **kwargs):
|
||
|
return super().dispatch(*args, **kwargs)
|
||
|
|
||
|
def get_context_data(self, **kwargs):
|
||
|
context = super(OrderPaymentView, self).get_context_data(**kwargs)
|
||
|
if 'billing_address_data' in self.request.session:
|
||
|
billing_address_form = BillingAddressForm(
|
||
|
initial=self.request.session['billing_address_data']
|
||
|
)
|
||
|
else:
|
||
|
old_active = get_billing_address_for_user(self.request.user)
|
||
|
billing_address_form = BillingAddressForm(
|
||
|
instance=old_active
|
||
|
) if old_active else BillingAddressForm(
|
||
|
initial={'active': True, 'owner': self.request.user.id}
|
||
|
)
|
||
|
customer_id = uncloud_stripe.get_customer_id_for(self.request.user)
|
||
|
cards = uncloud_stripe.get_customer_cards(customer_id)
|
||
|
context.update({
|
||
|
'matrix_vm_pricing': PricingPlan.get_by_name(self.request.session.get('pricing', {'name': 'unknown'})['name']),
|
||
|
'billing_address_form': billing_address_form,
|
||
|
'cards': cards,
|
||
|
'stripe_key': settings.STRIPE_PUBLIC_KEY
|
||
|
})
|
||
|
|
||
|
return context
|
||
|
|
||
|
@cache_control(no_cache=True, must_revalidate=True, no_store=True)
|
||
|
def get(self, request, *args, **kwargs):
|
||
|
for k in ['vat_validation_status', 'token', 'id_payment_method']:
|
||
|
if request.session.get(k):
|
||
|
request.session.pop(k)
|
||
|
if 'order' not in request.session:
|
||
|
return HttpResponseRedirect(reverse('matrix:index'))
|
||
|
return self.render_to_response(self.get_context_data())
|
||
|
|
||
|
def form_valid(self, address_form):
|
||
|
id_payment_method = self.request.POST.get('id_payment_method', None)
|
||
|
self.request.session["id_payment_method"] = id_payment_method
|
||
|
this_user = {
|
||
|
'email': self.request.user.email,
|
||
|
'username': self.request.user.username
|
||
|
}
|
||
|
customer_id = uncloud_stripe.get_customer_id_for(self.request.user)
|
||
|
uncloud_stripe.attach_payment_method(id_payment_method, customer_id)
|
||
|
address = get_billing_address_for_user(self.request.user)
|
||
|
if address:
|
||
|
form = BillingAddressForm(self.request.POST, instance=address)
|
||
|
else:
|
||
|
form = BillingAddressForm(self.request.POST)
|
||
|
if form.is_valid:
|
||
|
billing_address_ins = form.save()
|
||
|
self.request.session["billing_address_id"] = billing_address_ins.id
|
||
|
self.request.session['billing_address_data'] = address_form.cleaned_data
|
||
|
self.request.session['billing_address_data']['owner'] = self.request.user.id
|
||
|
self.request.session['user'] = this_user
|
||
|
self.request.session['customer'] = customer_id
|
||
|
|
||
|
vat_number = address_form.cleaned_data.get('vat_number').strip()
|
||
|
if vat_number:
|
||
|
validate_result = validate_vat_number(
|
||
|
stripe_customer_id=customer_id,
|
||
|
billing_address_id=billing_address_ins.id
|
||
|
)
|
||
|
|
||
|
if 'error' in validate_result and validate_result['error']:
|
||
|
messages.add_message(
|
||
|
self.request, messages.ERROR, validate_result["error"],
|
||
|
extra_tags='vat_error'
|
||
|
)
|
||
|
return HttpResponseRedirect(
|
||
|
reverse('matrix:payment') + '#vat_error'
|
||
|
)
|
||
|
self.request.session["vat_validation_status"] = validate_result["status"]
|
||
|
|
||
|
return HttpResponseRedirect(reverse('matrix:order_details'))
|
||
|
|
||
|
|
||
|
class OrderDetailsView(DetailView):
|
||
|
template_name = "matrixhosting/order_detail.html"
|
||
|
context_object_name = "order"
|
||
|
model = Order
|
||
|
|
||
|
@method_decorator(login_required)
|
||
|
def dispatch(self, *args, **kwargs):
|
||
|
return super().dispatch(*args, **kwargs)
|
||
|
|
||
|
@cache_control(no_cache=True, must_revalidate=True, no_store=True)
|
||
|
def get(self, request, *args, **kwargs):
|
||
|
context = {}
|
||
|
if ('order' not in request.session or 'user' not in request.session):
|
||
|
return HttpResponseRedirect(reverse('matrix:index'))
|
||
|
if 'id_payment_method' in self.request.session:
|
||
|
card = uncloud_stripe.get_card_from_payment(self.request.user, self.request.session['id_payment_method'])
|
||
|
if not card:
|
||
|
return HttpResponseRedirect(reverse('matrix:payment'))
|
||
|
context['card'] = card
|
||
|
elif 'id_payment_method' not in self.request.session or 'vat_validation_status' not in self.request.session:
|
||
|
return HttpResponseRedirect(reverse('matrix:payment'))
|
||
|
specs = request.session.get('order')
|
||
|
pricing = request.session.get('pricing')
|
||
|
billing_address = BillingAddress.objects.get(id=request.session.get('billing_address_id'))
|
||
|
vat_rate = VATRate.get_vat_rate(billing_address)
|
||
|
vat_validation_status = "verified" if billing_address.vat_number_validated_on and billing_address.vat_number_verified else False
|
||
|
subtotal, subtotal_after_discount, price_after_discount_with_vat, vat, vat_percent, discount = get_order_total_with_vat(
|
||
|
specs['cores'], specs['memory'], specs['storage'], request.session['pricing']['name'],
|
||
|
vat_rate=vat_rate * 100, vat_validation_status = vat_validation_status
|
||
|
)
|
||
|
pricing = {
|
||
|
"subtotal": subtotal, "discount": discount, "vat": vat, "vat_percent": vat_percent,
|
||
|
"vat_country": billing_address.country.lower(),
|
||
|
"subtotal_after_discount": subtotal_after_discount,
|
||
|
"price_after_discount_with_vat": price_after_discount_with_vat
|
||
|
|
||
|
}
|
||
|
pricing["price_with_vat"] = round(subtotal * (1 + pricing["vat_percent"] * 0.01), 2)
|
||
|
discount["amount_with_vat"] = round(pricing["price_with_vat"] - pricing["price_after_discount_with_vat"], 2)
|
||
|
pricing["total_price"] = pricing["price_after_discount_with_vat"]
|
||
|
self.request.session['total_price'] = pricing["price_after_discount_with_vat"]
|
||
|
payment_intent_response = uncloud_stripe.get_payment_intent(request.user, pricing["price_after_discount_with_vat"])
|
||
|
context.update({
|
||
|
'payment_intent_secret': payment_intent_response.client_secret,
|
||
|
'order': specs,
|
||
|
'pricing': pricing,
|
||
|
'stripe_key': settings.STRIPE_PUBLIC_KEY,
|
||
|
})
|
||
|
return render(request, self.template_name, context)
|
||
|
|
||
|
def post(self, request, *args, **kwargs):
|
||
|
customer = StripeCustomer.objects.get(owner=self.request.user)
|
||
|
billing_address = BillingAddress.objects.get(id=request.session.get('billing_address_id'))
|
||
|
if 'id_payment_method' in request.session:
|
||
|
card = uncloud_stripe.get_card_from_payment(self.request.user, self.request.session['id_payment_method'])
|
||
|
if not card:
|
||
|
return show_error("There was a payment related error.", self.request)
|
||
|
else:
|
||
|
return show_error("There was a payment related error.", self.request)
|
||
|
|
||
|
order = finalize_order(request, customer,
|
||
|
billing_address,
|
||
|
self.request.session['total_price'],
|
||
|
PricingPlan.get_by_name(self.request.session['pricing']['name']),
|
||
|
request.session.get('order'))
|
||
|
if order:
|
||
|
bill = Bill.create_next_bill_for_user_address(billing_address)
|
||
|
payment= Payment.objects.create(owner=request.user, amount=self.request.session['total_price'], source='stripe')
|
||
|
if payment:
|
||
|
#Close the bill as the payment has been added
|
||
|
bill.close()
|
||
|
response = {
|
||
|
'status': True,
|
||
|
'redirect': (reverse('matrix:dashboard')),
|
||
|
'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)
|
||
|
|
||
|
|
||
|
def finalize_order(request, customer, billing_address,
|
||
|
one_time_price, pricing_plan,
|
||
|
specs):
|
||
|
product = Product.objects.first()
|
||
|
recurring_period_product = ProductToRecurringPeriod.objects.filter(product=product, is_default=True).first()
|
||
|
order = Order.objects.create(
|
||
|
owner=request.user,
|
||
|
customer=customer,
|
||
|
billing_address=billing_address,
|
||
|
one_time_price=one_time_price,
|
||
|
pricing_plan=pricing_plan,
|
||
|
recurring_period= recurring_period_product.recurring_period,
|
||
|
product = product,
|
||
|
config=json.dumps(specs)
|
||
|
)
|
||
|
return order
|
||
|
|
||
|
|
||
|
class Dashboard(ListView):
|
||
|
template_name = "matrixhosting/dashboard.html"
|
||
|
model = Order
|
||
|
|
||
|
@method_decorator(login_required)
|
||
|
def dispatch(self, *args, **kwargs):
|
||
|
return super().dispatch(*args, **kwargs)
|
||
|
|
||
|
def get_queryset(self):
|
||
|
return Order.objects.filter(owner=self.request.user)
|
||
|
|
||
|
def post(self, request, *args, **kwargs):
|
||
|
order = Order.objects.get(id=request.POST.get('order_id', 0))
|
||
|
order.cancel()
|
||
|
return JsonResponse({'message': 'Successfully Cancelled'})
|
||
|
|
||
|
|
||
|
def get_error_response_dict(request):
|
||
|
response = {
|
||
|
'status': False,
|
||
|
'redirect': "{url}#{section}".format(
|
||
|
url=(reverse('matrix: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 response
|
||
|
|
||
|
|
||
|
def show_error(msg, request):
|
||
|
messages.add_message(request, messages.ERROR, msg,
|
||
|
extra_tags='failed_payment')
|
||
|
return JsonResponse(get_error_response_dict(request))
|
||
|
|
||
|
|
||
|
class MachineViewSet(viewsets.ReadOnlyModelViewSet):
|
||
|
serializer_class = VMInstanceSerializer
|
||
|
permission_classes = [permissions.IsAuthenticated]
|
||
|
|
||
|
def get_queryset(self):
|
||
|
return VMInstance.objects.filter(owner=self.request.user)
|
||
|
|
||
|
|