diff --git a/aleksis/apps/tezor/models/invoice.py b/aleksis/apps/tezor/models/invoice.py
index d73515bcc09e89d6bba788f3bd0913c7c4aa7dd1..b7c1eb5418bde62776942ccd74b0564a86b82601 100644
--- a/aleksis/apps/tezor/models/invoice.py
+++ b/aleksis/apps/tezor/models/invoice.py
@@ -46,6 +46,14 @@ class Invoice(BasePayment, PureDjangoModel):
     def get_purchased_items(self):
         return self.for_object.get_purchased_items()
 
+    def get_person(self):
+        if hasattr(self.for_object, "person"):
+            return self.for_object.person
+        elif hasattr(self.for_object, "get_person"):
+            return self.for_object.get_person()
+
+        return None
+
     class Meta:
         constraints = [
             models.UniqueConstraint(fields=["transaction_id", "group"], name="number_uniq_per_group")
diff --git a/aleksis/apps/tezor/predicates.py b/aleksis/apps/tezor/predicates.py
new file mode 100644
index 0000000000000000000000000000000000000000..4980926acb10b6d12db668e2601b0cb0443857df
--- /dev/null
+++ b/aleksis/apps/tezor/predicates.py
@@ -0,0 +1,24 @@
+from django.contrib.auth import get_user_model
+
+from rules import predicate
+
+from .models.invoice import Invoice
+
+@predicate
+def is_own_invoice(user: User, obj: Invoice):
+    """Predicate which checks if the invoice is linked to the current user."""
+    return obj.get_person() == user.person
+
+@predicate
+def has_no_payment_variant(user: User, obj: Invoice):
+    """Predicate which checks that the invoice has no payment variant."""
+    return not obj.variant
+
+@predicate
+def is_in_payment_status(status: str):
+    """Predicate which checks whether the invoice is in a specific state."""
+
+    def _predicate(user: User, obj: Invoice):
+        return obj.status == status
+
+    return _predicate
diff --git a/aleksis/apps/tezor/preferences.py b/aleksis/apps/tezor/preferences.py
index 9cf77979b91a0dd5809f0b961532537a4f965299..8a25f34519a9b34394ec9b126aa6d67651a12049 100644
--- a/aleksis/apps/tezor/preferences.py
+++ b/aleksis/apps/tezor/preferences.py
@@ -8,6 +8,18 @@ from aleksis.core.registries import site_preferences_registry
 payments = Section("payments", verbose_name=_("Payments"))
 
 
+@site_preferences_registry.register
+class EnablePledge(BooleanPreference):
+    """Allow payments to be made by anyone, not only invoice recipient."""
+
+    section = payments
+    name = "public_payments"
+    verbose_name = _("Public payments")
+    help_text = _("Allow anyone (including guests) to make payments. Basic invoice information will be visible to anyone who knows the invoice token.")
+    default = True
+    required = False
+
+
 @site_preferences_registry.register
 class SofortAPIID(StringPreference):
     """Sofort payment backend - API ID."""
diff --git a/aleksis/apps/tezor/rules.py b/aleksis/apps/tezor/rules.py
index 98aa9de4d49b81646950b1704128336f68271a57..2b6ef10ef0cd864a86cdce1c90a5f9f4cd94c017 100644
--- a/aleksis/apps/tezor/rules.py
+++ b/aleksis/apps/tezor/rules.py
@@ -1,9 +1,11 @@
 import rules
+from payments import PaymentStatus
+
+from aleksis.core.util.predicates import has_person, has_global_perm, has_any_object, has_object_perm, is_site_preference_set
 
 from .models.base import Client
 from .models.invoice import Invoice, InvoiceGroup
-
-from aleksis.core.util.predicates import has_person, has_global_perm, has_any_object, has_object_perm
+from .predicates import has_no_payment_variant, is_own_invoice, is_in_payment_status
 
 # View clients
 view_clients_predicate = has_person & (
@@ -65,8 +67,22 @@ delete_invoice_groups_predicate = has_person & (
 )
 rules.add_perm("tezor.delete_invoice_groups_rule", delete_invoice_groups_predicate)
 
+# Display invoice billing information
+display_billing_predicate = has_person & (is_own_invoice | has_global_perm("tezor.display_billing") | has_object_perm("tezor.display_billing"))
+rules.add_perm("tezor.display_billing_rule", display_billing_predicate)
+
+# Display invoice purchased items
+display_purchased_items_predicate = has_person & (is_own_invoice | has_global_perm("tezor.display_purchased_items") | has_object_perm("tezor.display_purchased_items"))
+rules.add_perm("tezor.display_purchased_items_rule", display_purchased_items_predicate)
+
+# Change payment variant
+change_payment_variant_predicate = has_person & is_in_payment_status(PaymentStatus.WAITING) & ((is_own_invoice & has_no_payment_variant) | has_global_perm("tezor.change_payment_variant") | has_object_perm("tezor.change_payment_variant"))
+rules.add_perm("tezor.change_payment_variant", change_payment_variant_predicate)
+
+# Start payment
+do_payment_predicate = has_person & (is_in_payment_status(PaymentStatus.WAITING) | is_in_payment_status(PaymentStatus.INPUT)) & ((is_own_invoice | is_site_preference_set("payments", "public_payments")) | has_global_perm("tezor.do_payment") | has_object_perm("tezor.do_payment"))
+rules.add_perm("tezor.do_payment", do_payment_predicate)
+
 # View invoice
-view_invoice_predicate = has_person & (
-    has_global_perm("tezor.view_invoice") | has_object_perm("tezor.view_invoice")
-)
+view_invoice_predicate = is_own_invoice | is_site_preference_set("payments", "public_payments") | has_global_perm("tezor.view_invoice") | has_object_perm("tezor.view_invoice")
 rules.add_perm("tezor.view_invoice_rule", view_invoice_predicate)