NextCloud Integration

This commit is contained in:
amalelshihaby 2021-09-10 09:58:42 +02:00
commit 5f4b10cb82
113 changed files with 18992 additions and 235 deletions

8
uncloud_pay/forms.py Normal file
View file

@ -0,0 +1,8 @@
from uncloud.forms import MainModelForm
from .models import BillingAddress
class BillingAddressForm(MainModelForm):
class Meta:
model = BillingAddress
fields = ['full_name', 'street',
'city', 'postal_code', 'country', 'vat_number', 'active', 'owner']

View file

@ -13,8 +13,6 @@ from django.db import models
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
from django.utils import timezone
from django_q.tasks import schedule
from django_q.models import Schedule
# Verify whether or not to use them here
from django.core.exceptions import ObjectDoesNotExist, ValidationError
@ -722,7 +720,7 @@ class Order(models.Model):
if self.recurring_period.duration_seconds > 0:
delta = until_when - self.starting_date
num_times = ceil(delta.total_seconds() / self.recurring_period.duration_seconds)
next_date = self.starting_date + datetime.timedelta(seconds=num_times * self.recurring_period.duration_seconds)
@ -762,12 +760,6 @@ class Order(models.Model):
self.ending_date = timezone.now()
self.should_be_billed = False
self.save()
if hasattr(self, 'instance_id'):
last_bill_record = BillRecord.objects.filter(order=self).order_by('id').last()
schedule('matrixhosting.tasks.delete_instance',
self.instance_id,
schedule_type=Schedule.ONCE,
next_run=last_bill_record.ending_date or (timezone.now() + datetime.timedelta(hours=1)))
def count_used(self, when=None):
"""

View file

@ -12,7 +12,7 @@ def get_spendings_for_user(user):
@transaction.atomic
def get_balance_for_user(user):
return get_deposit_payments_for_user(user) - get_spendings_for_user(user)
return float(get_deposit_payments_for_user(user) - get_spendings_for_user(user))
@transaction.atomic
def has_enough_balance(user, due_amount):

View file

@ -0,0 +1,114 @@
body {
font-family: Avenir;
background: white;
font-weight: 500;
line-height: 1.1em;
font-size: 16px;
margin: auto;
}
p {
display: block;
-webkit-margin-before: 14px;
-webkit-margin-after: 14px;
-webkit-margin-start: 0px;
-webkit-margin-end: 0px;
}
.bold {
font-weight: bold;
}
.d1 {
line-height:1.1em;
width: 60%;
float: left;
}
.d2 {
line-height:1.5em;
padding-top: 15px;
font-style: normal;
width: 40%;
float: left;
}
.d4 {
line-height:1.5em;
width:40%;
float: left;
}
.b1 {
width: 45%;
float: left;
}
.b2 {
width: 55%;
float: left;
text-align: right;
left: 0;
}
.d5 {
width: 100%;
}
.d6 {
width: 68%;
float: left;
font-size: 13px;
}
.d7 {
width: 32%;
float: left;
}
.wf {
width: 100%;
}
hr {
border: 0;
clear:both;
display: inline-block;
width: 100%;
background-color:gray;
height: 1px;
}
.tl {
text-align: left;
margin-left: 5px;
}
.tr {
text-align: right;
margin-right: 5px;
float: right;
}
.tc {
text-align: center;
}
.pc p {
display: block;
-webkit-margin-before: 3px;
-webkit-margin-after: 5px;
-webkit-margin-start: 0px;
-webkit-margin-end: 0px;
}
.th {
border-top: 1px solid gray;
border-bottom: 1px solid gray;
}
.ts {
font-size: 14px;
}
.icon {
width: 16px;
height: 14px;
vertical-align: middle;
margin-right: 2px;
}
.footer {
margin-top: 70px;
font-size: 14px;
}
.footer p {
display: block;
-webkit-margin-before: 5px;
-webkit-margin-after: 5px;
-webkit-margin-start: 0px;
-webkit-margin-end: 0px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View file

@ -0,0 +1,33 @@
{% load i18n %}
<form action="" id="payment-form-new" method="POST">
<input type="hidden" name="token"/>
<input type="hidden" name="id_card" id="id_card" value=""/>
<div class="group">
<div class="credit-card-goup">
<div class="card-element card-number-element form-group">
<label>{%trans "Card Number" %}</label>
<div id="card-number-element" class="field my-input form-control"></div>
</div>
<div class="row">
<div class="card-element card-expiry-element col-md-6 form-group">
<label>{%trans "Expiry Date" %}</label>
<div id="card-expiry-element" class="field my-input form-control"></div>
</div>
<div class="card-element card-cvc-element col-md-6">
<label>{%trans "CVC" %}</label>
<div id="card-cvc-element" class="field my-input form-control"></div>
</div>
</div>
<div class="card-element brand text-6">
<i class="fab fa-credit-card" id="brand-icon"></i>
</div>
</div>
</div>
<div id="card-errors"></div>
<div class="text-right">
<button class="btn btn-vm-contact btn-primary btn-wide" type="submit" name="payment-form">{%trans "Checkout" %}</button>
</div>
<div style="display:none;">
<p class="payment-errors"></p>
</div>
</form>

View file

@ -0,0 +1,78 @@
{% load static i18n %}
<!DOCTYPE html>
<html lang="en">
<head>
<style>
body {
font-family: Avenir;
font-weight: 500;
line-height: 1.1em;
font-size: 16px;
}
.icon {
width: 16px;
height: 14px;
vertical-align: middle;
margin-right: 2px;
}
.footer {
margin-top: 70px;
font-size: 14px;
}
.footer p {
display: block;
-webkit-margin-before: 5px;
-webkit-margin-after: 5px;
-webkit-margin-start: 0px;
-webkit-margin-end: 0px;
}
.d6 {
width: 68%;
float: left;
font-size: 13px;
}
.wf {
width: 100%;
}
</style>
</head>
<body>
<div class="footer wf custom_footer">
<br/>
<div class="d6">
<p>
<img class="icon" src="{{ base_url }}{% static 'uncloud_pay/images/call.png' %}"/>
<span>+41 55 505 6266</span>
</p>
<p>
<img class="icon" src="{{ base_url }}{% static 'uncloud_pay/images/msg.png' %}"/>
<span>buchhaltung-ag@ungleich.ch</span>
</p>
<p>
<img class="icon" src="{{ base_url }}{% static 'uncloud_pay/images/home.png' %}"/>
<span>https://www.ungleich.ch</span>
</p>
<p>
<img class="icon" src="{{ base_url }}{% static 'uncloud_pay/images/twitter.png' %}"/>
@ungleich
</p>
</div>
<div class="d7">
<div>
<p>Glarner Kantonalbank</p>
<p>
<span class="bold">IBAN: CH 4300 7730 0055 5931 177</span>
</p>
<p>
<span class="bold">BIC: GLKBCH22</span>
</p>
</div>
<p style="font-size: 13px; white-space: nowrap !important">Mwst-Nummer: CHE-156.970.649 MWST</span></p>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,129 @@
{% load static i18n %}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{%trans "Invoice: " %} {{bill.id}}</title>
<style>
@font-face {
font-family: 'Avenir';
src: url("{{ base_url }}{% static 'uncloud_pay/webfonts/Avenir-Book.ttf' %}");
}
</style>
<link href="{{ base_url }}{% static 'uncloud_pay/css/invoice.css' %}" rel="stylesheet">
</head>
<body>
<div class="invoice-container">
<div class="header" style="line-height:1.1em;">
<div class="d1">
<div class="logo">
<img class="" src="{{ base_url }}{% static 'uncloud_pay/images/company-small.jpg' %}" width="50%"/>
</div>
</div>
<div class="d2" name="company_address">
<span class="bold">ungleich glarus ag</span>
<address style="font-style: normal;">
Bahnhofstrasse 1<br>
8783 Linthal<br>
Switzerland
</address>
</div>
<div class="first-page">
<div class="d1">
<b>
<span style="font-size: 16px">{{bill.billing_address.full_name}}</span>
</b>
<br/>
<span>{{bill.billing_address.owner.email}}</span>
<address style="font-style: normal;">
{{bill.billing_address.street}}<br>
{{bill.billing_address.city}}<br>
{{bill.billing_address.get_country_display}}
</address>
</div>
</div>
<div class="d4" style="margin-top:15px;">
<div class="bold">
<div class="b1">
<span>{%trans "Bill Number:" %}</span>
</div>
<div class="b2">
<span>#{{bill.id}}</span>
</div>
</div>
<div>
<div class="b1">
<span>{%trans "Date of invoice:" %}</span>
</div>
<div class="b2">
<span>{{bill.starting_date|date:"Y-m-d"}}</span>
</div>
</div>
<div>
<div class="b1">
<span>{%trans "Due Date:" %}</span>
</div>
<div class="b2">
<span>{{bill.due_date|date:"Y-m-d"}}</span>
</div>
</div>
</div>
<div style="clear: both;">
</div>
<div style="clear: both;">
<table class="wf" style="margin-top:20px; margin-bottom:10px important;border-top: 1px solid gray;">
<thead >
<tr class="" style="padding-top: 10px; padding-bottom:10px;background-color: #f5f5f5;">
<th class="tl bold" style="padding: 5px 0px 5px 0px !important;">{%trans "Product" %}</th>
<th class="tc bold" style="padding: 5px 0px 5px 0px !important;">{%trans "Quantity" %}</th>
<th class="bold" style="text-align: right; padding: 5px 0px 5px 0px !important;">{%trans "Amount in " %} <span>CHF</span></th>
</tr>
</thead>
{% for record in bill.bill_records.all %}
<tr class="ts" style="line-height:1.8em !important;">
<td class="tl">
<span>{{record.description}}</span>
</td>
<td class="tc">
<span>{{record.quantity}}</span>
</td>
<td style="text-align: right;">
<span>{{record.subtotal}}</span>
</td>
</tr>
{% endfor %}
</table>
<div class="page">
<div class="wf th">
<p class="ts">
<span class="tl">SubTotal</span>
<span class="tr">{{bill.subtotal}}</span>
</span>
</p>
<p class="ts">
<span class="tl">{{bill.billing_address.get_country_display}} VAT {{vat_rate}}%</span>
<span class="tr">{{tax_amount}}
</span>
</p>
</div>
<div class="wf pc" style="background-color: #f5f5f5;padding-top:5px;padding-bottom:5px;">
<p class="bold" style="padding-top:5px;">
<span class="tl" style="font-size: 16px">{%trans "Total CHF" %}</span>
<span class="tr" style="font-size: 16px">{{bill.sum}}</span>
</p>
</div>
</div>
</div>
</div>
</body>
</html>

View file

@ -6,4 +6,9 @@ app_name = 'uncloud_pay'
urlpatterns = [
path('cards/activate', CardActivateView.as_view(), name='card_activate'),
path('billing/', PaymentsView.as_view(), name='billing'),
path('billing/cards', CardsView.as_view(), name='cards'),
path('billing/bills', BillsView.as_view(), name='bills'),
path('order/success/', OrderSuccessView.as_view(), name='order_success'),
path('order/invoice/<int:bill_id>/download/', InvoiceDownloadView.as_view(), name='invoice_download'),
]

View file

@ -1,6 +1,8 @@
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.base import TemplateView, View
from django.views.generic import DetailView
from django.shortcuts import render
from django.views.decorators.cache import cache_control
from django.db import transaction
from django.contrib.auth import get_user_model
from rest_framework import viewsets, mixins, permissions, status, views
@ -13,8 +15,10 @@ from vat_validator import validate_vat, vies
from vat_validator.countries import EU_COUNTRY_CODES
from hardcopy import bytestring_to_pdf
from django.core.files.temp import NamedTemporaryFile
from django.http import FileResponse
from django.views.generic.list import ListView
from django.http import FileResponse, HttpResponseRedirect
from django.template.loader import render_to_string
from wkhtmltopdf.views import PDFTemplateResponse
from copy import deepcopy
import json
@ -88,6 +92,119 @@ class RegisterCard(TemplateView):
context['stripe_pk'] = uncloud_stripe.public_api_key
return context
class OrderSuccessView(DetailView):
template_name = "uncloud_pay/order_success.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):
if ('order' not in request.session or ('bill_id' not in request.session)):
return HttpResponseRedirect(reverse('nextcloud:index'))
context = {
'order': self.request.session.get('order'),
'bill_id': self.request.session['bill_id'],
'balance': get_balance_for_user(self.request.user)
}
return render(request, self.template_name, context)
class InvoiceDownloadView(View):
template = 'uncloud_pay/invoice.html'
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
def get_context_data(self, **kwargs):
context = {'base_url': f'{self.request.scheme}://{self.request.get_host()}'}
return context
def get(self, request, bill_id):
cmd_options = settings.REPORT_FORMAT
context = self.get_context_data()
bill = Bill.objects.get(owner=self.request.user, id=bill_id)
if bill:
context['bill'] = bill
context['vat_rate'] = str(round(bill.vat_rate * 100, 2))
context['tax_amount'] = round(bill.vat_rate * bill.subtotal, 2)
return PDFTemplateResponse(request=request,
template=self.template,
filename = f"bill-{bill_id}.pdf",
cmd_options= cmd_options,
footer_template= 'uncloud_pay/includes/invoice_footer.html',
context= context)
class PaymentsView(ListView):
template_name = "uncloud_pay/payments.html"
model = Payment
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
def get_context_data(self, **kwargs):
context = super(PaymentsView, self).get_context_data(**kwargs)
context.update({
'balance': get_balance_for_user(self.request.user),
'type': self.request.GET.get('type')
})
return context
def get_queryset(self):
if self.request.GET.get('type'):
return Payment.objects.filter(owner=self.request.user, type=self.request.GET.get('type')).order_by('-timestamp')
return Payment.objects.filter(owner=self.request.user).order_by('-timestamp')
class CardsView(ListView):
template_name = "uncloud_pay/cards.html"
model = StripeCreditCard
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
def get_context_data(self, **kwargs):
context = super(CardsView, self).get_context_data(**kwargs)
customer_id = uncloud_stripe.get_customer_id_for(self.request.user)
setup_intent = uncloud_stripe.create_setup_intent(customer_id)
context = super().get_context_data(**kwargs)
context.update({
'balance': get_balance_for_user(self.request.user),
'client_secret': setup_intent.client_secret,
'username': self.request.user.username,
'stripe_pk':uncloud_stripe.public_api_key,
'min_amount': settings.MIN_PER_TRANSACTION
})
return context
def get_queryset(self):
uncloud_stripe.sync_cards_for_user(self.request.user)
return StripeCreditCard.objects.filter(owner=self.request.user).order_by('-active')
class BillsView(ListView):
template_name = "uncloud_pay/bills.html"
model = Bill
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
def get_context_data(self, **kwargs):
context = super(BillsView, self).get_context_data(**kwargs)
context = super().get_context_data(**kwargs)
context.update({
'balance': get_balance_for_user(self.request.user),
})
return context
def get_queryset(self):
return Bill.objects.filter(owner=self.request.user).order_by('-creation_date')
class CreditCardViewSet(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,