diff --git a/setup.py b/setup.py
index 74fadcadb63509edd8c7ff2a89dfefba385e7056..06f29abe1f8ccd42d0fa74f54d17608b46d07361 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
 
 setup(
     name="stripe-checkout",
-    version="0.1",
+    version="0.2",
     author="GEANT",
     author_email="swd@geant.org",
     description="Stripe custom checkout support service",
diff --git a/stripe_checkout/stripe_checkout/management/commands/processevents.py b/stripe_checkout/stripe_checkout/management/commands/processevents.py
index 207d291c91ad38d281bd66faf21bdbead578e071..fb0a0b5f0fabc4a86a25d04346ac7718930dda28 100644
--- a/stripe_checkout/stripe_checkout/management/commands/processevents.py
+++ b/stripe_checkout/stripe_checkout/management/commands/processevents.py
@@ -3,7 +3,6 @@ from django.core.management.base import BaseCommand
 from django.db import transaction
 from stripe_checkout.stripe_checkout.models import Event, ItemKind, Order
 from stripe_checkout.stripe_checkout.visit import VisitorAPI
-from stripe_checkout.stripe_checkout.shopping_cart import get_answers
 
 SUCCESS_EVENTS = [
     "invoice.paid",
@@ -11,8 +10,6 @@ SUCCESS_EVENTS = [
     "checkout.session.completed",
 ]
 
-PAID_TAG = "PAID"
-
 RAISE_EXCEPTIONS = True
 
 
@@ -57,23 +54,15 @@ class Command(BaseCommand):
     def update_visit_paid(self, order: Order):
         api = VisitorAPI()
         visitor = api.get_visitor(order.visitor_id)
-        current_tags = visitor["tags"]
-        answers = {}
-        payload = {}
         for item in order.items.all():
             if item.kind == ItemKind.REGISTRATION_TYPE:
-                if PAID_TAG not in current_tags:
-                    payload["tags"] = current_tags + [PAID_TAG]
+                visitor.paid = True
             elif item.kind == ItemKind.EXTRA:
                 question_id = item.visit_paid_question_id
                 answer_id = item.visit_paid_answer_id
+                visitor.add_answer(question_id, answer_id)
 
-                if question_id not in answers:
-                    answers[question_id] = get_answers(visitor, question_id)
-                answers[question_id].add(answer_id)
-
-        payload["questions"] = self.prepare_answers(answers)
-        api.update_visitor(order.visitor_id, payload)
+        api.update_visitor(visitor)
 
     @staticmethod
     def prepare_answers(answers: dict):
diff --git a/stripe_checkout/stripe_checkout/management_views.py b/stripe_checkout/stripe_checkout/management_views.py
index 42a674559429e6f26416e7365d94b19f5d15d9d6..5d68fcbc4f48f02a2ea71ada5dfac62c11f355e4 100644
--- a/stripe_checkout/stripe_checkout/management_views.py
+++ b/stripe_checkout/stripe_checkout/management_views.py
@@ -1,9 +1,8 @@
 from typing import Sequence
-from django.http import HttpResponse
 from django.shortcuts import redirect, render
 from django.contrib.auth.decorators import login_required
 
-from stripe_checkout.stripe_checkout.models import ItemKind, PricedItem
+from stripe_checkout.stripe_checkout.models import ItemKind, Order, PricedItem
 from . import visit
 from django.views.decorators.http import require_GET, require_POST
 
@@ -18,6 +17,8 @@ def index(request):
 def list_visitors(request):
     api = visit.VisitorAPI()
     visitors = api.list_visitors()
+    visitors.sort(key=lambda v: v.last_name)
+
     extra_items = PricedItem.objects.enabled().filter(kind=ItemKind.EXTRA).all()
     registration_types = PricedItem.objects.filter(
         kind=ItemKind.REGISTRATION_TYPE
@@ -27,7 +28,7 @@ def list_visitors(request):
         request,
         "visitors.html",
         context={
-            "visitors": prepare_visitors(visitors),
+            "visitors": visitors,
             "extra_items": extra_items,
             "registration_types": registration_types,
         },
@@ -37,45 +38,33 @@ def list_visitors(request):
 @login_required
 @require_POST
 def update_visitor(request, visitor_id):
-    data = request.POST
-    kind = request.GET.get("kind")
-
-    all_items: Sequence[PricedItem] = (
+    all_extras: Sequence[PricedItem] = (
         PricedItem.objects.enabled().filter(kind=ItemKind.EXTRA).all()
     )
-
-    payload = {}
-    if kind == "paid":
-        pass
-
-    elif kind == "checkout":
-
-        selected_extras = {key for key, value in data.items() if value == "on"}
-        all_answers = {item.visit_checkout_question_id: [] for item in all_items}
-        for extra in selected_extras:
-            question, answer = extra.split(":")
-            all_answers[question].append(answer)
-
-        payload = {
-            "registrationType": {"id": data["registration_type"]},
-            "questions": [
-                {
-                    "id": question,
-                    "answers": [{"id": a} for a in answers],
-                }
-                for question, answers in all_answers.items()
-            ],
-        }
-    else:
-        return HttpResponse(status=400)
-
     api = visit.VisitorAPI()
-    api.update_visitor(visitor_id, payload)
+    visitor = api.get_visitor(visitor_id)
+
+    data = request.POST
+    all_answers = {}
+    for extra in all_extras:
+        for question, answer in [
+            (extra.visit_checkout_question_id, extra.visit_checkout_answer_id),
+            (extra.visit_paid_question_id, extra.visit_paid_answer_id),
+        ]:
+            all_answers.setdefault(question, set())
+            if data.get(f"{question}:{answer}"):
+                all_answers[question].add(answer)
+    for question, answers in all_answers.items():
+        visitor.set_answers(question, answers)
+    if registration_type := data.get("registration_type"):
+        visitor.set_registration_type(registration_type)
+    visitor.paid = data.get("paid")
+    api.update_visitor(visitor)
     return redirect("stripe_checkout:list-visitors")
 
 
-def prepare_visitors(visitors):
-    return sorted(
-        filter(lambda v: not v["deleted"], visitors),
-        key=lambda v: v.get("contact", {}).get("lastName", ""),
-    )
+@login_required
+@require_POST
+def delete_orders(request, visitor_id):
+    Order.objects.filter(visitor_id=visitor_id).delete()
+    return redirect("stripe_checkout:list-visitors")
diff --git a/stripe_checkout/stripe_checkout/stripe.py b/stripe_checkout/stripe_checkout/stripe.py
index 7ed65a667cc1859b4ff2a9f6daa8f15a483f9836..b81e5f39f7369f963636842a3caa958542410519 100644
--- a/stripe_checkout/stripe_checkout/stripe.py
+++ b/stripe_checkout/stripe_checkout/stripe.py
@@ -9,6 +9,8 @@ import stripe.error
 from stripe.error import StripeError  # noqa F401
 from django.conf import settings
 
+from stripe_checkout.stripe_checkout.visit import Visitor
+
 if TYPE_CHECKING:
     from stripe_checkout.stripe_checkout.shopping_cart import ShoppingCart
 
@@ -21,22 +23,17 @@ TAX_RATE_ID = "txr_1QeddlDSpyjzuj5pPwUcMwTd"
 TAX_RATE = 20  # %
 
 
-def get_or_create_customer(visitor) -> Optional[str]:
+def get_or_create_customer(visitor: Visitor) -> Optional[str]:
     """Returns a stripe customer based on email address
     :return: customer_id
     """
-    contact = visitor["contact"]
-    email = contact["email"]
-    if not email:
-        return None
-
     stripe.api_key = settings.STRIPE_API_KEY
-    result = stripe.Customer.search(query=f"email:'{email}'")["data"]
+    result = stripe.Customer.search(query=f"email:'{visitor.email}'")["data"]
     if len(result) == 0:
         customer = stripe.Customer.create(
-            name=f"{contact['firstName']} {contact['lastName']}",
-            email=email,
-            country=contact["country"],
+            name=visitor.full_name,
+            email=visitor.email,
+            country=visitor["contact"].get("country"),
         )
         return customer["id"]
     return result[0]["id"]
@@ -55,6 +52,7 @@ def create_invoice(
     invoice = stripe.Invoice.create(
         customer=customer_id,
         collection_method="send_invoice",
+        payment_settings={"payment_method_types": ["card", "customer_balance"]},
         days_until_due=30,
         custom_fields=custom_fields,
         rendering={"template": settings.STRIPE_INVOICE_TEMPLATE_ID},
@@ -66,7 +64,7 @@ def create_invoice(
             for item in shopping_cart
         ],
     )
-    stripe.Invoice.send_invoice(invoice["id"])
+    invoice = stripe.Invoice.send_invoice(invoice["id"])
     return invoice
 
 
diff --git a/stripe_checkout/stripe_checkout/templates/visitors.html b/stripe_checkout/stripe_checkout/templates/visitors.html
index 3cda17ad25741bb235b1aef8b3decda907392c70..2376d6a6d1fb4f013b858646cbc104a57799257d 100644
--- a/stripe_checkout/stripe_checkout/templates/visitors.html
+++ b/stripe_checkout/stripe_checkout/templates/visitors.html
@@ -5,21 +5,26 @@
 {% endblock title %}
 {% block body %}
     <div>
-        <h1>Checked-out items</h1>
+        <h1>Manage visitors</h1>
+        <h2 style="color: red">Warning: Be careful, you may break things here</h2>
         <table>
             <thead>
                 <tr>
                     <th>ID</th>
                     <th>Name</th>
                     <th>RegistrationType</th>
+                    <th>Paid</th>
                     {% for item in extra_items %}<th>{{ item.display_name }}</th>{% endfor %}
                 </tr>
             </thead>
             <tbody>
                 {% for visitor in visitors %}
                     <tr>
-                        <td>{{ visitor.id }}</td>
-                        <td>{{ visitor.contact.firstName }} {{visitor.contact.lastName }}</td>
+                        <td>
+                            <a href="{% url 'stripe_checkout:checkout-entrypoint' visitor.id %}"
+                               target="_blank">{{ visitor.id }}</a>
+                        </td>
+                        <td>{{ visitor.contact.firstName }} {{ visitor.contact.lastName }}</td>
                         <td>
                             <select form="{{ visitor.id }}_checkout_update_form" name=registration_type>
                                 {% for item in registration_types %}
@@ -30,6 +35,13 @@
                                 {% endfor %}
                             </select>
                         </td>
+                        <td>
+                            <input type="checkbox"
+                                   form="{{ visitor.id }}_checkout_update_form"
+                                   name="paid"
+                                   autocomplete="off"
+                                   {% if visitor.paid %}checked{% endif %}>
+                        </td>
                         {% for item in extra_items %}
                             <td>
                                 <input type="checkbox"
@@ -37,16 +49,31 @@
                                        name="{{ item.visit_checkout_question_id }}:{{ item.visit_checkout_answer_id }}"
                                        autocomplete="off"
                                        {% if visitor|has_answer:item %}checked{% endif %}>
+                                <span>(paid <span>
+                                    <input type="checkbox"
+                                           form="{{ visitor.id }}_checkout_update_form"
+                                           name="{{ item.visit_paid_question_id }}:{{ item.visit_paid_answer_id }}"
+                                           autocomplete="off"
+                                           {% if visitor|has_paid:item %}checked{% endif %}>
+                                </span>)</span>
                             </td>
                         {% endfor %}
                         <td>
                             <form id="{{ visitor.id }}_checkout_update_form"
                                   method="post"
-                                  action="{% url 'stripe_checkout:update-visitor' visitor_id=visitor.id %}?kind=checkout">
+                                  action="{% url 'stripe_checkout:update-visitor' visitor_id=visitor.id %}">
                                 {% csrf_token %}
                                 <button>Update</button>
                             </form>
                         </td>
+                        <td>
+                            <form id="{{ visitor.id }}_delete_orders"
+                                  method="post"
+                                  action="{% url 'stripe_checkout:delete-orders' visitor_id=visitor.id %}">
+                                {% csrf_token %}
+                                <button>Delete orders</button>
+                            </form>
+                        </td>
                     </tr>
                 {% endfor %}
             </tbody>
diff --git a/stripe_checkout/stripe_checkout/templatetags/visitor_helpers.py b/stripe_checkout/stripe_checkout/templatetags/visitor_helpers.py
index a1d4980c6ec60e0a092999514938e9d1c7797875..4d61d07ac34ac83542fa97caa1768afb87db6e69 100644
--- a/stripe_checkout/stripe_checkout/templatetags/visitor_helpers.py
+++ b/stripe_checkout/stripe_checkout/templatetags/visitor_helpers.py
@@ -13,3 +13,12 @@ def has_answer(visitor: dict, item: PricedItem):
         question_id=item.visit_checkout_question_id,
         answer_id=item.visit_checkout_answer_id,
     )
+
+
+@register.filter
+def has_paid(visitor: dict, item: PricedItem):
+    return shopping_cart.has_answer(
+        visitor,
+        question_id=item.visit_paid_question_id,
+        answer_id=item.visit_paid_answer_id,
+    )
diff --git a/stripe_checkout/stripe_checkout/urls.py b/stripe_checkout/stripe_checkout/urls.py
index 24b8482a4bfebf627db435684b0b3a77c0f5a257..04b5000ce914c9066f839381c1c094e67d8236c5 100644
--- a/stripe_checkout/stripe_checkout/urls.py
+++ b/stripe_checkout/stripe_checkout/urls.py
@@ -1,6 +1,6 @@
 from django.urls import include, path, re_path
 
-from .management_views import update_visitor, list_visitors, index
+from .management_views import delete_orders, update_visitor, list_visitors, index
 from .visit_views import stripe_event, checkout, checkout_success
 
 visitor_id = r"(?P<visitor_id>[a-z0-9]+)"
@@ -11,6 +11,9 @@ urlpatterns = [
         "visitors/",
         include(
             [
+                re_path(
+                    f"{visitor_id}/delete_orders/", delete_orders, name="delete-orders"
+                ),
                 re_path(f"{visitor_id}/", update_visitor, name="update-visitor"),
                 path("", list_visitors, name="list-visitors"),
             ]
diff --git a/stripe_checkout/stripe_checkout/visit.py b/stripe_checkout/stripe_checkout/visit.py
index 5d771bba49e05c25773c6be6ba9052cabc97bf77..3fbdd320983b8648ca746f8db25f47fbb04e7856 100644
--- a/stripe_checkout/stripe_checkout/visit.py
+++ b/stripe_checkout/stripe_checkout/visit.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+from typing import Optional, Union
 import requests
 from requests.auth import HTTPBasicAuth
 from django.conf import settings
@@ -21,12 +23,94 @@ class VisitorAPI:
         return response.json()
 
     def list_visitors(self):
-        return self._request("get", f"{BASE_URL}/visitors/{self.expo_id}")
+        all_visitors = self._request("get", f"{BASE_URL}/visitors/{self.expo_id}")
+        return [
+            Visitor.from_api(data)
+            for data in filter(lambda v: not v["deleted"], all_visitors)
+        ]
 
     def get_visitor(self, visitor_id: str):
-        return self._request("get", f"{BASE_URL}/visitors/{self.expo_id}/{visitor_id}")
+        return Visitor.from_api(
+            self._request("get", f"{BASE_URL}/visitors/{self.expo_id}/{visitor_id}")
+        )
 
-    def update_visitor(self, visitor_id: str, payload: dict):
+    def update_visitor(
+        self, visitor: Union[str, Visitor], payload: Optional[dict] = None
+    ):
+        if isinstance(visitor, dict) and payload is None:
+            payload = visitor
+            visitor = visitor["id"]
         return self._request(
-            "put", f"{BASE_URL}/visitors/{self.expo_id}/{visitor_id}", json=payload
+            "put", f"{BASE_URL}/visitors/{self.expo_id}/{visitor}", json=payload
         )
+
+
+class Visitor(dict):
+    PAID_TAG = "PAID"
+
+    @classmethod
+    def from_api(cls, data):
+        return cls(**data)
+
+    @property
+    def first_name(self):
+        return self["contact"].get("firstName", "")
+
+    @property
+    def last_name(self):
+        return self["contact"].get("lastName", "")
+
+    @property
+    def full_name(self):
+        return f"{self.first_name} {self.last_name}".strip()
+
+    @property
+    def email(self):
+        return self["contact"].get("email")
+
+    @property
+    def paid(self):
+        return self.PAID_TAG in self.get("tags")
+
+    @paid.setter
+    def paid(self, value: bool):
+        current_tags = set(self.get("tags", []))
+        if value:
+            self["tags"] = list(current_tags | {self.PAID_TAG})
+        else:
+            self["tags"] = list(current_tags - {self.PAID_TAG})
+
+    def set_registration_type(self, registration_type_id):
+        self["registrationType"] = {"id": registration_type_id}
+
+    def get_all_questions(self) -> dict[str, set[str]]:
+        return {
+            question["id"]: {a["id"] for a in question["answers"]}
+            for question in self.get("questions", [])
+        }
+
+    def update_questions(self, questions: dict[str, set[str]]):
+        self["questions"] = [
+            {
+                "id": question,
+                "answers": [{"id": a} for a in answers],
+            }
+            for question, answers in questions.items()
+        ]
+
+    def add_answer(self, question_id, answer_id):
+        all_questions = self.get_all_questions()
+        all_questions.setdefault(question_id, set()).add(answer_id)
+        self.update_questions(all_questions)
+
+    def remove_answer(self, question_id, answer_id):
+        all_questions = self.get_all_questions()
+        if question_id not in all_questions:
+            return
+        all_questions[question_id].remove(answer_id)
+        self.update_questions(all_questions)
+
+    def set_answers(self, question_id, answers_ids):
+        all_questions = self.get_all_questions()
+        all_questions[question_id] = set(answers_ids)
+        self.update_questions(all_questions)
diff --git a/stripe_checkout/stripe_checkout/visit_views.py b/stripe_checkout/stripe_checkout/visit_views.py
index 848caaf9825ecf929cbef27488b7047959b71be5..eb9f70059e31d0eb7a2e04c51e0a20ded168af35 100644
--- a/stripe_checkout/stripe_checkout/visit_views.py
+++ b/stripe_checkout/stripe_checkout/visit_views.py
@@ -37,8 +37,9 @@ def checkout(request, visitor_id):
             data = form.cleaned_data
             obj = create_invoice(visitor, data)
             shopping_cart.create_order(visitor, stripe_obj=obj)
-
-            return redirect("stripe_checkout:checkout-success", visitor_id=visitor_id)
+            response = redirect(obj["hosted_invoice_url"])
+            response.status_code = 303
+            return response
 
         # form is invalid
         status_code = 400
diff --git a/test/conftest.py b/test/conftest.py
index 39681e007e6b23c61ed364a059e750e940b10081..9d4833720bc8a09970a7b06f7d75d4c79a5d61cd 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -175,7 +175,13 @@ def mock_stripe(item_data):
         ),
         patch("stripe.Invoice.create", return_value={"id": "stripe-inv-id"}),
         patch("stripe.InvoiceItem.create", return_value={"id": "stripe-invitem-id"}),
-        patch("stripe.Invoice.send_invoice"),
+        patch(
+            "stripe.Invoice.send_invoice",
+            return_value={
+                "id": "stripe-invitem-id",
+                "hosted_invoice_url": "http://boguspath",
+            },
+        ),
         patch("stripe.Invoice.add_lines"),
         patch("stripe.Price.list", return_value={"data": price_list}),
         patch("stripe.Product.list", return_value={"data": product_list}),
diff --git a/test/test_visit.py b/test/test_visit.py
index 4710578b9229629b93adee6ae2278208065c188a..454629c6df9d1bd4616155756e4e8728a284c27b 100644
--- a/test/test_visit.py
+++ b/test/test_visit.py
@@ -18,7 +18,7 @@ def test_list_visitors(client, user):
 @pytest.mark.django_db
 def test_update_visitor(client, user, visitor_id):
     client.force_login(user)
-    rv = client.post(f"/visitors/{visitor_id}/?kind=paid")
+    rv = client.post(f"/visitors/{visitor_id}/")
     assert rv.status_code == 302
 
 
@@ -31,7 +31,7 @@ def test_create_invoice(client, visitor_id):
     assert not Order.objects.count()
 
     rv = client.post(f"/checkout/{visitor_id}/", data={"payment_method": "invoice"})
-    assert rv.status_code == 302
+    assert rv.status_code == 303
     assert Order.objects.count() == 1