diff --git a/aleksis/apps/tezor/forms.py b/aleksis/apps/tezor/forms.py
new file mode 100644
index 0000000000000000000000000000000000000000..6f35f89f3c3d69baa88bc6c0794e2723b3de3926
--- /dev/null
+++ b/aleksis/apps/tezor/forms.py
@@ -0,0 +1,26 @@
+from material import Layout, Row
+
+from aleksis.core.mixins import ExtensibleForm
+
+from .models.base import Client
+from .models.invoice import InvoiceGroup
+
+class EditClientForm(ExtensibleForm):
+    """Form to create or edit clients."""
+
+    layout = Layout("name")
+
+    class Meta:
+        model = Client
+        exclude = []
+
+
+class EditInvoiceGroupForm(ExtensibleForm):
+
+    layout = Layout(
+        Row("name", "template_name")
+    )
+
+    class Meta:
+        model = InvoiceGroup
+        exclude = ["client"]
diff --git a/aleksis/apps/tezor/menus.py b/aleksis/apps/tezor/menus.py
new file mode 100644
index 0000000000000000000000000000000000000000..49c9644d489921913cd07a84aeaf217008bfa661
--- /dev/null
+++ b/aleksis/apps/tezor/menus.py
@@ -0,0 +1,29 @@
+from django.utils.translation import gettext_lazy as _
+
+MENUS = {
+    "NAV_MENU_CORE": [
+        {
+            "name": _("Payments"),
+            "url": "#",
+            "root": True,
+            "icon": "price_check",
+            "validators": [
+                "menu_generator.validators.is_authenticated",
+                "aleksis.core.util.core_helpers.has_person",
+            ],
+            "submenu": [
+                {
+                    "name": _("Clients"),
+                    "url": "clients",
+                    "icon": "account_balance",
+                    "validators": [
+                        (
+                            "aleksis.core.util.predicates.permission_validator",
+                            "tezor.can_view_clients",
+                        )
+                    ],
+                },
+            ],
+        }
+    ]
+}
diff --git a/aleksis/apps/tezor/models/base.py b/aleksis/apps/tezor/models/base.py
index 7c1815365e65a9801aad6c19ce567514f37472e3..459f9b22b924289f33a4f192e28b2794973184e0 100644
--- a/aleksis/apps/tezor/models/base.py
+++ b/aleksis/apps/tezor/models/base.py
@@ -11,3 +11,6 @@ class Client(ExtensibleModel):
         constraints = [
             models.UniqueConstraint(fields=["name", "site"], name="uniq_client_per_site")
         ]
+
+    def __str__(self) -> str:
+        return self.name
diff --git a/aleksis/apps/tezor/models/invoice.py b/aleksis/apps/tezor/models/invoice.py
index 448f470b9293fe62909ed3d6d727ce3b3c119ee7..fd6be73de84a1e3113009a4d38351adc9d7db4c2 100644
--- a/aleksis/apps/tezor/models/invoice.py
+++ b/aleksis/apps/tezor/models/invoice.py
@@ -21,6 +21,10 @@ class InvoiceGroup(ExtensibleModel):
 
     template_name = models.CharField(verbose_name=_("Template to render invoices with as PDF"), blank=True, max_length=255)
 
+    def __str__(self) -> str:
+        return self.name
+
+
     class Meta:
         constraints = [
             models.UniqueConstraint(fields=["client", "name"], name="group_uniq_per_client")
diff --git a/aleksis/apps/tezor/tables.py b/aleksis/apps/tezor/tables.py
index 3cf48132ba0ceab99588815c898defa8b69ce2ee..d9001895cd8d4232f3ff24b25ea847b2747077db 100644
--- a/aleksis/apps/tezor/tables.py
+++ b/aleksis/apps/tezor/tables.py
@@ -1,6 +1,7 @@
 from django.utils.translation import gettext_lazy as _
 
 import django_tables2 as tables
+from django_tables2.utils import A
 
 
 class PurchasedItemsTable(tables.Table):
@@ -32,3 +33,73 @@ class TotalsTable(tables.Table):
     class Meta:
         show_header = False
         orderable = False
+
+
+class ClientsTable(tables.Table):
+    class Meta:
+        attrs = {"class": "responsive-table highlight"}
+
+    name = tables.Column()
+
+    view = tables.LinkColumn(
+        "client_by_pk",
+        args=[A("id")],
+        verbose_name=_("View"),
+        text=_("View"),
+    )
+    edit = tables.LinkColumn(
+        "edit_client_by_pk",
+        args=[A("id")],
+        verbose_name=_("Edit"),
+        text=_("Edit"),
+    )
+    delete = tables.LinkColumn(
+        "delete_client_by_pk",
+        args=[A("id")],
+        verbose_name=_("Delete"),
+        text=_("Delete"),
+    )
+
+class InvoiceGroupsTable(tables.Table):
+
+    name = tables.Column()
+    template_name = tables.Column()
+    view = tables.LinkColumn(
+        "invoice_group_by_pk",
+        args=[A("id")],
+        verbose_name=_("View"),
+        text=_("View"),
+    )
+    edit = tables.LinkColumn(
+        "edit_invoice_group_by_pk",
+        args=[A("id")],
+        verbose_name=_("Edit"),
+        text=_("Edit"),
+    )
+    delete = tables.LinkColumn(
+        "delete_invoice_group_by_pk",
+        args=[A("id")],
+        verbose_name=_("Delete"),
+        text=_("Delete"),
+    )
+
+class InvoicesTable(tables.Table):
+
+    transaction_id = tables.Column()
+    status = tables.Column()
+    created = tables.DateColumn()
+    billing_first_name = tables.Column()
+    billing_last_name = tables.Column()
+    total = tables.Column()
+    view = tables.LinkColumn(
+        "invoice_by_pk",
+        args=[A("id")],
+        verbose_name=_("View"),
+        text=_("View"),
+    )
+    print = tables.LinkColumn(
+        "get_invoice_by_pk",
+        args=[A("id")],
+        verbose_name=_("Print"),
+        text=_("Print"),
+    )
diff --git a/aleksis/apps/tezor/templates/tezor/client/create.html b/aleksis/apps/tezor/templates/tezor/client/create.html
new file mode 100644
index 0000000000000000000000000000000000000000..634500f5ff3e0352f07a46cc073f19ad382c14e4
--- /dev/null
+++ b/aleksis/apps/tezor/templates/tezor/client/create.html
@@ -0,0 +1,19 @@
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% block page_title %}{% blocktrans %}Create client{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}Create client{% endblocktrans %}{% endblock %}
+
+{% block extra_head %}
+    {{ form.media.css }}
+{% endblock %}
+
+{% block content %}
+  <form method="post">
+    {% csrf_token %}
+    {% form form=form %}{% endform %}
+    {% include "core/partials/save_button.html" %}
+  </form>
+
+  {{ form.media.js }}
+{% endblock %}
diff --git a/aleksis/apps/tezor/templates/tezor/client/edit.html b/aleksis/apps/tezor/templates/tezor/client/edit.html
new file mode 100644
index 0000000000000000000000000000000000000000..3c193950bc685dc5224f9f2248fbbd60a8639827
--- /dev/null
+++ b/aleksis/apps/tezor/templates/tezor/client/edit.html
@@ -0,0 +1,18 @@
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% block page_title %}{% blocktrans %}Edit client{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}Edit client{% endblocktrans %}{% endblock %}
+
+{% block extra_head %}
+    {{ form.media.css }}
+{% endblock %}
+
+{% block content %}
+  <form method="post">
+    {% csrf_token %}
+    {% form form=form %}{% endform %}
+    {% include "core/partials/save_button.html" %}
+  </form>
+  {{ form.media.js }}
+{% endblock %}
diff --git a/aleksis/apps/tezor/templates/tezor/client/full.html b/aleksis/apps/tezor/templates/tezor/client/full.html
new file mode 100644
index 0000000000000000000000000000000000000000..19c4ab002a7c1ab3fc4ded90ee286c3d27083260
--- /dev/null
+++ b/aleksis/apps/tezor/templates/tezor/client/full.html
@@ -0,0 +1,14 @@
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% load render_table from django_tables2 %}
+
+{% block page_title %}{% blocktrans %}Client{% endblocktrans %} {{ client }}{% endblock %}
+{% block browser_title %}{% blocktrans %}Client{% endblocktrans %} {{ client }}{% endblock %}
+
+{% block content %}
+
+    <a class="btn colour-primary waves-effect waves-light" href="{% url 'create_invoice_group' client.id %}">{% trans "Add invoice group" %}</a>
+    {% render_table invoice_groups_table %}
+
+{% endblock %}
diff --git a/aleksis/apps/tezor/templates/tezor/client/list.html b/aleksis/apps/tezor/templates/tezor/client/list.html
new file mode 100644
index 0000000000000000000000000000000000000000..e8b32263958ec250023248fcb08b75022ca05c79
--- /dev/null
+++ b/aleksis/apps/tezor/templates/tezor/client/list.html
@@ -0,0 +1,14 @@
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% load render_table from django_tables2 %}
+
+{% block page_title %}{% blocktrans %}Clients{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}Clients{% endblocktrans %}{% endblock %}
+
+{% block content %}
+
+    <a class="btn colour-primary waves-effect waves-light" href="{% url 'create_client' %}">{% trans "Create client" %}</a>
+    {% render_table table %}
+
+{% endblock %}
diff --git a/aleksis/apps/tezor/templates/tezor/empty.html b/aleksis/apps/tezor/templates/tezor/empty.html
deleted file mode 100644
index 2fb4415f4772cd40a8eb40e290d50f912c4f219f..0000000000000000000000000000000000000000
--- a/aleksis/apps/tezor/templates/tezor/empty.html
+++ /dev/null
@@ -1,8 +0,0 @@
-{% extends 'core/base.html' %}
-{% load i18n %}
-
-{% block content %}
-  <p class="flow-text">
-   {% blocktrans %}Tezor (account and payment system){% endblocktrans %}
-  </p>
-{% endblock %}
diff --git a/aleksis/apps/tezor/templates/tezor/invoice/full.html b/aleksis/apps/tezor/templates/tezor/invoice/full.html
new file mode 100644
index 0000000000000000000000000000000000000000..add38a90fa88f33578f98b3282ae1f315d05a031
--- /dev/null
+++ b/aleksis/apps/tezor/templates/tezor/invoice/full.html
@@ -0,0 +1,16 @@
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% load render_table from django_tables2 %}
+
+{% block browser_title %}{{ object.transaction_id }}{% endblock %}
+
+{% block content %}
+
+    <h1>{% trans "Invoice" %} {{ object.transaction_id }} — {{ object.created.date }}</h1>
+    <a class="btn colour-primary waves-effect waves-light" href="{% url 'invoice_group_by_pk' object.group.pk %}">{% trans "Back" %}</a>
+
+    {% render_table object.purchased_items_table %}
+    {% render_table object.totals_table %}
+
+{% endblock %}
diff --git a/aleksis/apps/tezor/templates/tezor/invoice_group/create.html b/aleksis/apps/tezor/templates/tezor/invoice_group/create.html
new file mode 100644
index 0000000000000000000000000000000000000000..fa24a828cf0dd07141cab8f8bcd9ca5a50d0343d
--- /dev/null
+++ b/aleksis/apps/tezor/templates/tezor/invoice_group/create.html
@@ -0,0 +1,19 @@
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% block page_title %}{% blocktrans %}Create invoice group{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}Create invoice group{% endblocktrans %}{% endblock %}
+
+{% block extra_head %}
+    {{ form.media.css }}
+{% endblock %}
+
+{% block content %}
+  <form method="post">
+    {% csrf_token %}
+    {% form form=form %}{% endform %}
+    {% include "core/partials/save_button.html" %}
+  </form>
+
+  {{ form.media.js }}
+{% endblock %}
diff --git a/aleksis/apps/tezor/templates/tezor/invoice_group/edit.html b/aleksis/apps/tezor/templates/tezor/invoice_group/edit.html
new file mode 100644
index 0000000000000000000000000000000000000000..284a3ec0197f0e31d972e1c0c14e589685bd73ab
--- /dev/null
+++ b/aleksis/apps/tezor/templates/tezor/invoice_group/edit.html
@@ -0,0 +1,18 @@
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% block page_title %}{% blocktrans %}Edit invoice group{% endblocktrans %}{% endblock %}
+{% block browser_title %}{% blocktrans %}Edit invoice group{% endblocktrans %}{% endblock %}
+
+{% block extra_head %}
+    {{ form.media.css }}
+{% endblock %}
+
+{% block content %}
+  <form method="post">
+    {% csrf_token %}
+    {% form form=form %}{% endform %}
+    {% include "core/partials/save_button.html" %}
+  </form>
+  {{ form.media.js }}
+{% endblock %}
diff --git a/aleksis/apps/tezor/templates/tezor/invoice_group/full.html b/aleksis/apps/tezor/templates/tezor/invoice_group/full.html
new file mode 100644
index 0000000000000000000000000000000000000000..6fecbff61a76f4176bc8e979d731b11a8c403a73
--- /dev/null
+++ b/aleksis/apps/tezor/templates/tezor/invoice_group/full.html
@@ -0,0 +1,14 @@
+{% extends "core/base.html" %}
+{% load material_form i18n %}
+
+{% load render_table from django_tables2 %}
+
+{% block page_title %}{{ object }}{% endblock %}
+{% block browser_title %}{{ object }}{% endblock %}
+
+{% block content %}
+
+    <a class="btn colour-primary waves-effect waves-light" href="{% url 'client_by_pk' object.client.pk %}">{% trans "Back" %}</a>
+    {% render_table invoices_table %}
+
+{% endblock %}
diff --git a/aleksis/apps/tezor/urls.py b/aleksis/apps/tezor/urls.py
index 571809cf71d6d08182a261b2aa26cde2155b6844..627d64f756e6bd336acfe3be02a244a15ae069de 100644
--- a/aleksis/apps/tezor/urls.py
+++ b/aleksis/apps/tezor/urls.py
@@ -4,5 +4,55 @@ from . import views
 
 urlpatterns = [
     path("payments/", include("payments.urls")),
-    path("invoice/<int:pk>/print", views.GetInvoicePDF.as_view(), name="get_invoice_by_pk")
+    path("invoice/<int:pk>/print", views.GetInvoicePDF.as_view(), name="get_invoice_by_pk"),
+    path(
+        "clients/list",
+        views.ClientListView.as_view(),
+        name="clients",
+    ),
+    path(
+        "clients/create",
+        views.ClientCreateView.as_view(),
+        name="create_client",
+    ),
+    path(
+        "clients/<int:pk>/edit",
+        views.ClientEditView.as_view(),
+        name="edit_client_by_pk",
+    ),
+    path(
+        "clients/<int:pk>/delete",
+        views.ClientDeleteView.as_view(),
+        name="delete_client_by_pk",
+    ),
+    path(
+        "clients/<int:pk>/",
+        views.ClientDetailView.as_view(),
+        name="client_by_pk",
+    ),
+    path(
+        "client/<int:pk>/invoice_groups/create",
+        views.InvoiceGroupCreateView.as_view(),
+        name="create_invoice_group",
+    ),
+    path(
+        "invoice_groups/<int:pk>/edit",
+        views.InvoiceGroupEditView.as_view(),
+        name="edit_invoice_group_by_pk",
+    ),
+    path(
+        "invoice_groups/<int:pk>/",
+        views.InvoiceGroupDetailView.as_view(),
+        name="invoice_group_by_pk",
+    ),
+    path(
+        "invoice_groups/<int:pk>/delete",
+        views.InvoiceGroupDeleteView.as_view(),
+        name="delete_invoice_group_by_pk",
+    ),
+    path(
+        "invoice/<int:pk>/",
+        views.InvoiceDetailView.as_view(),
+        name="invoice_by_pk",
+    ),
 ]
diff --git a/aleksis/apps/tezor/views.py b/aleksis/apps/tezor/views.py
index 22fe10830ab05dd5688ae8bf2a498a4e406ec45b..27939605b54e85cf57ce36abaad31d147e8132da 100644
--- a/aleksis/apps/tezor/views.py
+++ b/aleksis/apps/tezor/views.py
@@ -1,11 +1,22 @@
 from django.views.generic import View
 from django.shortcuts import render
+from django.views.decorators.cache import never_cache
+from django.utils.decorators import method_decorator
+from django.views.generic import FormView, TemplateView
+from django.utils.translation import ugettext as _
+from django.urls import reverse, reverse_lazy
+from django.views.generic.detail import DetailView
 
 from rules.contrib.views import PermissionRequiredMixin
+from django_tables2.views import SingleTableView, RequestConfig
 
 from aleksis.core.views import RenderPDFView
+from aleksis.core.mixins import AdvancedCreateView, AdvancedDeleteView, AdvancedEditView
 
-from .models.invoice import Invoice
+from .tables import ClientsTable, InvoiceGroupsTable, InvoicesTable
+from .forms import EditClientForm, EditInvoiceGroupForm
+from .models.base import Client
+from .models.invoice import Invoice, InvoiceGroup
 
 
 class GetInvoicePDF(PermissionRequiredMixin, RenderPDFView):
@@ -20,3 +31,125 @@ class GetInvoicePDF(PermissionRequiredMixin, RenderPDFView):
 
         print(invoice.group.__dict__)
         return context
+
+class ClientListView(PermissionRequiredMixin, SingleTableView):
+    """Table of all clients."""
+
+    model = Client
+    table_class = ClientsTable
+    permission_required = "tezor.view_clients"
+    template_name = "tezor/client/list.html"
+
+
+@method_decorator(never_cache, name="dispatch")
+class ClientCreateView(PermissionRequiredMixin, AdvancedCreateView):
+    """Create view for clients."""
+
+    model = Client
+    form_class = EditClientForm
+    permission_required = "tezor.add_clients"
+    template_name = "tezor/client/create.html"
+    success_url = reverse_lazy("clients")
+    success_message = _("The client has been created.")
+
+
+@method_decorator(never_cache, name="dispatch")
+class ClientEditView(PermissionRequiredMixin, AdvancedEditView):
+    """Edit view for clients."""
+
+    model = Client
+    form_class = EditClientForm
+    permission_required = "tezor.edit_clients"
+    template_name = "tezor/client/edit.html"
+    success_url = reverse_lazy("clients")
+    success_message = _("The client has been saved.")
+
+
+class ClientDeleteView(PermissionRequiredMixin, AdvancedDeleteView):
+    """Delete view for client."""
+
+    model = Client
+    permission_required = "tezor.delete_client"
+    template_name = "core/pages/delete.html"
+    success_url = reverse_lazy("clients")
+    success_message = _("The client has been deleted.")
+
+
+class ClientDetailView(PermissionRequiredMixin, DetailView):
+
+    model = Client
+    permission_required = "tezor.view_client"
+    template_name = "tezor/client/full.html"
+
+    def get_context_data(self, object):
+        context = super().get_context_data()
+
+        invoice_groups = object.invoice_groups.all()
+        invoice_groups_table = InvoiceGroupsTable(invoice_groups)
+        RequestConfig(self.request).configure(invoice_groups_table)
+        context["invoice_groups_table"] = invoice_groups_table
+
+        return context
+
+class InvoiceGroupDetailView(PermissionRequiredMixin, DetailView):
+
+    model = InvoiceGroup
+    permission_required = "tezor.view_invoice_group"
+    template_name = "tezor/invoice_group/full.html"
+
+    def get_context_data(self, object):
+        context = super().get_context_data()
+
+        invoices = object.invoices.all()
+        invoices_table = InvoicesTable(invoices)
+        RequestConfig(self.request).configure(invoices_table)
+        context["invoices_table"] = invoices_table
+
+        return context
+
+
+@method_decorator(never_cache, name="dispatch")
+class InvoiceGroupCreateView(PermissionRequiredMixin, AdvancedCreateView):
+    """Create view for invoice_groups."""
+
+    model = InvoiceGroup
+    form_class = EditInvoiceGroupForm
+    permission_required = "tezor.add_invoice_groups"
+    template_name = "tezor/invoice_group/create.html"
+    success_url = reverse_lazy("clients")
+    success_message = _("The invoice_group has been created.")
+
+
+    def form_valid(self, form):
+        client = Client.objects.get(id=self.kwargs["pk"])
+        InvoiceGroup.objects.create(client=client, name=form.cleaned_data["name"], template_name=form.cleaned_data["template_name"])
+
+        return super().form_valid(form)
+
+@method_decorator(never_cache, name="dispatch")
+class InvoiceGroupEditView(PermissionRequiredMixin, AdvancedEditView):
+    """Edit view for invoice_groups."""
+
+    model = InvoiceGroup
+    form_class = EditInvoiceGroupForm
+    permission_required = "tezor.edit_invoice_groups"
+    template_name = "tezor/invoice_group/edit.html"
+    success_url = reverse_lazy("invoice_groups")
+    success_message = _("The invoice_group has been saved.")
+
+
+class InvoiceGroupDeleteView(PermissionRequiredMixin, AdvancedDeleteView):
+    """Delete view for invoice_group."""
+
+    model = InvoiceGroup
+    permission_required = "tezor.delete_invoice_group"
+    template_name = "core/pages/delete.html"
+    success_url = reverse_lazy("invoice_groups")
+    success_message = _("The invoice_group has been deleted.")
+
+
+class InvoiceDetailView(PermissionRequiredMixin, DetailView):
+
+    model = Invoice
+    permission_required = "tezor.view_invoice"
+    template_name = "tezor/invoice/full.html"