diff --git a/prices-live.json b/prices-live.json
new file mode 100644
index 0000000000000000000000000000000000000000..111c7b5a90f39152f76bd964e9bfae4ab6919f6f
--- /dev/null
+++ b/prices-live.json
@@ -0,0 +1,122 @@
+[
+   {
+      "fields" : {
+         "display_name" : "TNC25 Regular Pass",
+         "kind" : "REGISTRATION_TYPE",
+         "name" : "regular_pass",
+         "stripe_product_id" : "prod_RZw4xezI1PkDWK",
+         "visit_checkout_answer_id" : "",
+         "visit_checkout_question_id" : "",
+         "visit_paid_answer_id" : "",
+         "visit_paid_question_id" : "",
+         "visit_registration_id" : "1zdvtaxrc"
+      },
+      "model" : "stripe_checkout.priceditem",
+      "pk" : 1
+   },
+   {
+      "fields" : {
+         "display_name" : "TNC25 Speaker Pass",
+         "kind" : "REGISTRATION_TYPE",
+         "name" : "speaker_pass",
+         "stripe_product_id" : "prod_RZw4iAk7qjAeaT",
+         "visit_checkout_answer_id" : "",
+         "visit_checkout_question_id" : "",
+         "visit_paid_answer_id" : "",
+         "visit_paid_question_id" : "",
+         "visit_registration_id" : "1zdvtaxrf"
+      },
+      "model" : "stripe_checkout.priceditem",
+      "pk" : 2
+   },
+   {
+      "fields" : {
+         "display_name" : "TNC25 Sponsor Pass",
+         "kind" : "REGISTRATION_TYPE",
+         "name" : "sponsor_pass",
+         "stripe_product_id" : "prod_RZw44phJtblEZv",
+         "visit_checkout_answer_id" : "",
+         "visit_checkout_question_id" : "",
+         "visit_paid_answer_id" : "",
+         "visit_paid_question_id" : "",
+         "visit_registration_id" : "1zegwg39v"
+      },
+      "model" : "stripe_checkout.priceditem",
+      "pk" : 4
+   },
+   {
+      "fields" : {
+         "display_name" : "Side meeting (Friday)",
+         "kind" : "EXTRA",
+         "name" : "side_meeting_pass_friday",
+         "stripe_product_id" : "prod_RZw4eSzXLC1A2N",
+         "visit_checkout_answer_id" : "1zeb3k6yy",
+         "visit_checkout_question_id" : "1zeb2vc8p",
+         "visit_paid_answer_id" : "1zen12x8z",
+         "visit_paid_question_id" : "1zen0dy45",
+         "visit_registration_id" : ""
+      },
+      "model" : "stripe_checkout.priceditem",
+      "pk" : 5
+   },
+   {
+      "fields" : {
+         "display_name" : "Side meeting (Monday)",
+         "kind" : "EXTRA",
+         "name" : "side_meeting_pass_monday",
+         "stripe_product_id" : "prod_RPrt4MzT2m98d2",
+         "visit_checkout_answer_id" : "1zeb3k6yx",
+         "visit_checkout_question_id" : "1zeb2vc8p",
+         "visit_paid_answer_id" : "1zen12x8y",
+         "visit_paid_question_id" : "1zen0dy45",
+         "visit_registration_id" : ""
+      },
+      "model" : "stripe_checkout.priceditem",
+      "pk" : 6
+   },
+   {
+      "fields" : {
+         "display_name" : "Social Pass (Tuesday)",
+         "kind" : "EXTRA",
+         "name" : "social_pass_tuesday",
+         "stripe_product_id" : "price_1QX2JCDSpyjzuj5pMEiQwFuO",
+         "visit_checkout_answer_id" : "",
+         "visit_checkout_question_id" : "",
+         "visit_paid_answer_id" : "",
+         "visit_paid_question_id" : "",
+         "visit_registration_id" : ""
+      },
+      "model" : "stripe_checkout.priceditem",
+      "pk" : 7
+   },
+   {
+      "fields" : {
+         "display_name" : "Social Pass (Wednesday)",
+         "kind" : "EXTRA",
+         "name" : "social_pass_wednesday",
+         "stripe_product_id" : "price_1QX2KNDSpyjzuj5pDNrkoH0x",
+         "visit_checkout_answer_id" : "",
+         "visit_checkout_question_id" : "",
+         "visit_paid_answer_id" : "",
+         "visit_paid_question_id" : "",
+         "visit_registration_id" : ""
+      },
+      "model" : "stripe_checkout.priceditem",
+      "pk" : 8
+   },
+   {
+      "fields" : {
+         "display_name" : "Social Pass (Combined)",
+         "kind" : "EXTRA",
+         "name" : "social_pass_all",
+         "stripe_product_id" : "price_1QX2MJDSpyjzuj5pFUbQUyV4",
+         "visit_checkout_answer_id" : "",
+         "visit_checkout_question_id" : "",
+         "visit_paid_answer_id" : "",
+         "visit_paid_question_id" : "",
+         "visit_registration_id" : ""
+      },
+      "model" : "stripe_checkout.priceditem",
+      "pk" : 9
+   }
+]
diff --git a/prices.json b/prices-test.json
similarity index 100%
rename from prices.json
rename to prices-test.json
diff --git a/requirements.txt b/requirements.txt
index ca5218b4fbaa21b27fd49630bcb92ac9fe72e11e..4826df6874c9c38f4ad1cd354eceb9446a2d5552 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-django<5.1
+django~=4.2.18
 dj_database_url==2.3.0 # pin for security
 jsonschema
 stripe
diff --git a/setup.py b/setup.py
index 6ac984a2af5a53e5b45af7a94162ae0531861d02..61aa704f4bd11d4bd4959b064f34b1771324be53 100644
--- a/setup.py
+++ b/setup.py
@@ -2,14 +2,14 @@ from setuptools import setup, find_packages
 
 setup(
     name="stripe-checkout",
-    version="0.5",
+    version="0.6",
     author="GEANT",
     author_email="swd@geant.org",
     description="Stripe custom checkout support service",
     url=("TBD"),
     packages=find_packages(),
     install_requires=[
-        "django<5.1",
+        "django~=4.2.18",
         "dj_database_url==2.3.0",  # pin for security
         "jsonschema",
         "stripe",
diff --git a/stripe_checkout/stripe_checkout/management/commands/processevents.py b/stripe_checkout/stripe_checkout/management/commands/processevents.py
index fb0a0b5f0fabc4a86a25d04346ac7718930dda28..d9be070e716d8576e4d63d4358fd823a25aec5c2 100644
--- a/stripe_checkout/stripe_checkout/management/commands/processevents.py
+++ b/stripe_checkout/stripe_checkout/management/commands/processevents.py
@@ -4,10 +4,11 @@ from django.db import transaction
 from stripe_checkout.stripe_checkout.models import Event, ItemKind, Order
 from stripe_checkout.stripe_checkout.visit import VisitorAPI
 
-SUCCESS_EVENTS = [
-    "invoice.paid",
-    "payment_intent.succeeded",
-    "checkout.session.completed",
+PAYMENT_INTENT_SUCCEEDED = "payment_intent.succeeded"
+PAYMENT_INTENT_CANCELED = "payment_intent.canceled"
+VALID_EVENTS = [
+    PAYMENT_INTENT_SUCCEEDED,
+    PAYMENT_INTENT_CANCELED,
 ]
 
 RAISE_EXCEPTIONS = True
@@ -37,7 +38,9 @@ class Command(BaseCommand):
                     self.stdout.write(msg)
 
     def _handle_event(self, event: Event) -> bool:
-        if event.type not in SUCCESS_EVENTS:
+        if event.type not in VALID_EVENTS:
+            return False
+        if not event.object:
             return False
         order = (
             Order.objects.filter(stripe_id=event.stripe_id)
@@ -46,12 +49,15 @@ class Command(BaseCommand):
         )
         if order is None:
             return False
-        self.update_visit_paid(order)
-        order.paid = True
+
+        if event.type == PAYMENT_INTENT_SUCCEEDED:
+            self._handle_pi_succeeded(event.object, order)
+        if event.type == PAYMENT_INTENT_CANCELED:
+            self._handle_pi_canceled(event.object, order)
         order.save()
         return True
 
-    def update_visit_paid(self, order: Order):
+    def _handle_pi_succeeded(self, payment_intent: dict, order: Order):
         api = VisitorAPI()
         visitor = api.get_visitor(order.visitor_id)
         for item in order.items.all():
@@ -63,13 +69,10 @@ class Command(BaseCommand):
                 visitor.add_answer(question_id, answer_id)
 
         api.update_visitor(visitor)
+        order.paid = True
 
-    @staticmethod
-    def prepare_answers(answers: dict):
-        return [
-            {
-                "id": question,
-                "answers": [{"id": a} for a in answers],
-            }
-            for question, answers in answers.items()
-        ]
+    def _handle_pi_canceled(self, invoice: dict, order: Order):
+        api = VisitorAPI()
+        visitor = api.get_visitor(order.visitor_id)
+        visitor.canceled = True
+        api.update_visitor(visitor)
diff --git a/stripe_checkout/stripe_checkout/migrations/0002_event_alter_priceditem_stripe_id_order.py b/stripe_checkout/stripe_checkout/migrations/0002_event_alter_priceditem_stripe_id_order.py
index 59cdb1075f277d27d9c256f303d4f93686ac0d42..94a59bab6232814328d2eed49f9494a12b52ecf1 100644
--- a/stripe_checkout/stripe_checkout/migrations/0002_event_alter_priceditem_stripe_id_order.py
+++ b/stripe_checkout/stripe_checkout/migrations/0002_event_alter_priceditem_stripe_id_order.py
@@ -23,7 +23,7 @@ class Migration(migrations.Migration):
                     ),
                 ),
                 ("payload", models.JSONField()),
-                ("handled", models.BooleanField(db_default=False, default=False)),
+                ("handled", models.BooleanField(default=False)),
             ],
         ),
         migrations.AlterField(
@@ -45,7 +45,7 @@ class Migration(migrations.Migration):
                 ),
                 ("visitor_id", models.CharField(max_length=40)),
                 ("stripe_id", models.CharField(max_length=255)),
-                ("paid", models.BooleanField(db_default=False, default=False)),
+                ("paid", models.BooleanField(default=False)),
                 (
                     "items",
                     models.ManyToManyField(
diff --git a/stripe_checkout/stripe_checkout/models.py b/stripe_checkout/stripe_checkout/models.py
index b81a4d5947198e6be4a4d8a1d1de1a5f22182874..9fdaf242c8a175f82b63a5ee5cfe76a751e5afe6 100644
--- a/stripe_checkout/stripe_checkout/models.py
+++ b/stripe_checkout/stripe_checkout/models.py
@@ -23,7 +23,14 @@ class PricedItem(models.Model):
 
     name = models.CharField(max_length=255, blank=False, unique=True)
     display_name = models.CharField(max_length=255, blank=False)
-    kind = models.CharField(max_length=40, choices=ItemKind, blank=False)
+    kind = models.CharField(
+        max_length=40,
+        choices=[
+            ("REGISTRATION_TYPE", "Registration Type"),
+            ("EXTRA", "Extra"),
+        ],
+        blank=False,
+    )
     stripe_product_id = models.CharField(max_length=40, blank=False, unique=True)
 
     # The following items is required for ItemKind.REGISTRATION_TYPE
@@ -65,7 +72,7 @@ class Order(models.Model):
     visitor_id = models.CharField(max_length=40, blank=False)
     stripe_id = models.CharField(max_length=255, blank=False, unique=True)
     items = models.ManyToManyField(to=PricedItem, related_name="order")
-    paid = models.BooleanField(default=False, db_default=False)
+    paid = models.BooleanField(default=False)
     exchange_rate = models.ForeignKey(
         ExchangeRate,
         on_delete=models.SET_NULL,
@@ -77,7 +84,7 @@ class Order(models.Model):
 
 class Event(models.Model):
     payload = models.JSONField(blank=False)
-    handled = models.BooleanField(default=False, db_default=False)
+    handled = models.BooleanField(default=False)
 
     @property
     def type(self):
@@ -88,6 +95,10 @@ class Event(models.Model):
         if (created := self.payload.get("created")) is not None:
             return datetime.datetime.fromtimestamp(created)
 
+    @property
+    def object(self):
+        return self.payload.get("data", {}).get("object", {})
+
     @property
     def stripe_id(self):
-        return self.payload.get("data", {}).get("object", {}).get("id")
+        return self.object.get("id")
diff --git a/stripe_checkout/stripe_checkout/shopping_cart.py b/stripe_checkout/stripe_checkout/shopping_cart.py
index 5ddc8430fca47b0f488e69a89601e8df3f7cf8d7..84b3753dbba76930586381a547eb5bcd88efac52 100644
--- a/stripe_checkout/stripe_checkout/shopping_cart.py
+++ b/stripe_checkout/stripe_checkout/shopping_cart.py
@@ -24,8 +24,11 @@ class ShoppingCart:
         return cls(items=to_pay)
 
     def create_order(self, visitor: dict, stripe_obj: dict):
+        payment_intent = stripe_obj["payment_intent"]
+        assert payment_intent is not None
+
         order = models.Order.objects.create(
-            visitor_id=visitor["id"], stripe_id=stripe_obj["id"]
+            visitor_id=visitor["id"], stripe_id=payment_intent
         )
         order.items.set(sc_item.item for sc_item in self)
         return order
diff --git a/stripe_checkout/stripe_checkout/stripe.py b/stripe_checkout/stripe_checkout/stripe.py
index 9a8fcc386baf4ed9339c4097ea3ea51bdcba6300..5cbe462e3c99494e4764beabfa5eccb94c8fd0c3 100644
--- a/stripe_checkout/stripe_checkout/stripe.py
+++ b/stripe_checkout/stripe_checkout/stripe.py
@@ -34,12 +34,35 @@ def get_or_create_customer(visitor: Visitor) -> Optional[str]:
         customer = stripe.Customer.create(
             name=visitor.full_name,
             email=visitor.email,
-            country=visitor["contact"].get("country"),
+            address=parse_address(visitor.billing_address),
         )
         return customer["id"]
+    customer_id = result[0]["id"]
+    stripe.Customer.modify(
+        customer_id,
+        name=visitor.full_name,
+        address=parse_address(visitor.billing_address),
+    )
+
     return result[0]["id"]
 
 
+def parse_address(visit_address: dict):
+    address_line = visit_address.get("address", "")
+    if house_number := visit_address.get("houseNumber", ""):
+        address_line += " " + house_number
+    if house_number_suffix := visit_address.get("houseNumberSuffix", ""):
+        address_line += " " + house_number_suffix
+
+    return {
+        "line1": address_line,
+        "postal_code": visit_address.get("postalCode", ""),
+        "city": visit_address.get("city", ""),
+        "state": visit_address.get("state", ""),
+        "country": visit_address.get("country", ""),
+    }
+
+
 def create_invoice(
     shopping_cart: ShoppingCart,
     customer_id,
@@ -57,7 +80,7 @@ def create_invoice(
         rate = gbp_exchange_rate.rate
         vat = shopping_cart.vat * rate
         custom_fields.append(
-            {"name": "GBP VAT Rate", "value": f"GBP {vat:.2f} ({rate:.4f})"}
+            {"name": "GBP VAT", "value": f"GBP {vat:.2f} ({rate:.4f})"}
         )
 
     invoice = stripe.Invoice.create(
diff --git a/stripe_checkout/stripe_checkout/visit.py b/stripe_checkout/stripe_checkout/visit.py
index 3fbdd320983b8648ca746f8db25f47fbb04e7856..b53950e54164b2bd68f01bfde35f398fbcfa9528 100644
--- a/stripe_checkout/stripe_checkout/visit.py
+++ b/stripe_checkout/stripe_checkout/visit.py
@@ -1,5 +1,6 @@
 from __future__ import annotations
 from typing import Optional, Union
+from django.http import Http404
 import requests
 from requests.auth import HTTPBasicAuth
 from django.conf import settings
@@ -30,10 +31,18 @@ class VisitorAPI:
         ]
 
     def get_visitor(self, visitor_id: str):
-        return Visitor.from_api(
-            self._request("get", f"{BASE_URL}/visitors/{self.expo_id}/{visitor_id}")
+        result = self._request(
+            "get", f"{BASE_URL}/visitors/{self.expo_id}/{visitor_id}"
         )
 
+        # Visit has at least one special, fake, visitor ids that instead of returning
+        # 404, falls back to return the whole visitor list. We need to catch it and
+        # properly raise
+        if not isinstance(result, dict):
+            raise Http404()
+
+        return Visitor.from_api(result)
+
     def update_visitor(
         self, visitor: Union[str, Visitor], payload: Optional[dict] = None
     ):
@@ -47,9 +56,12 @@ class VisitorAPI:
 
 class Visitor(dict):
     PAID_TAG = "PAID"
+    CANCELED_TAG = "CANCELLED"  # double L since we're british
 
     @classmethod
     def from_api(cls, data):
+        if isinstance(data, Visitor):
+            return data
         return cls(**data)
 
     @property
@@ -68,17 +80,35 @@ class Visitor(dict):
     def email(self):
         return self["contact"].get("email")
 
+    @property
+    def billing_address(self):
+        for addr in self["contact"]["addresses"]:
+            if addr.get("type") == "billing":
+                return addr
+        return {}
+
     @property
     def paid(self):
         return self.PAID_TAG in self.get("tags")
 
     @paid.setter
     def paid(self, value: bool):
+        self.set_tag(self.PAID_TAG, value)
+
+    @property
+    def canceled(self):
+        return self.CANCELED_TAG in self.get("tags")
+
+    @canceled.setter
+    def canceled(self, value: bool):
+        self.set_tag(self.CANCELED_TAG, value)
+
+    def set_tag(self, tag: str, active: bool):
         current_tags = set(self.get("tags", []))
-        if value:
-            self["tags"] = list(current_tags | {self.PAID_TAG})
+        if active:
+            self["tags"] = list(current_tags | {tag})
         else:
-            self["tags"] = list(current_tags - {self.PAID_TAG})
+            self["tags"] = list(current_tags - {tag})
 
     def set_registration_type(self, registration_type_id):
         self["registrationType"] = {"id": registration_type_id}
diff --git a/test/conftest.py b/test/conftest.py
index c49d56d1ecb0c096758c1e829868017de31534c9..740bfd5d45d666bf34fbc3cf3be591657cf20ba9 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -1,6 +1,5 @@
 import itertools
 import json
-import pathlib
 import re
 from unittest.mock import patch
 
@@ -10,18 +9,14 @@ from django.conf import settings
 from django.contrib.auth import get_user_model
 from django.test import Client
 from django.utils import timezone
+
 from stripe_checkout import config
 from stripe_checkout.stripe_checkout.models import ExchangeRate, ItemKind, PricedItem
-
-VISIT_RESPONSES_DIR = pathlib.Path(__file__).parent / "visit-responses"
+from stripe_checkout.stripe_checkout.visit import Visitor
 
 User = get_user_model()
 
 
-def _load_test_data(filename):
-    return json.loads((VISIT_RESPONSES_DIR / filename).read_text())
-
-
 @pytest.fixture
 def uses_db(request):
     result = any(m.name == "django_db" for m in request.node.own_markers)
@@ -70,9 +65,11 @@ def item_data():
 
 @pytest.fixture(autouse=True)
 def setup_django(uses_db, item_data, config_file):
+    config.load_config(config_file, settings)
+
     if not uses_db:
         return
-    config.load_config(config_file, settings)
+
     for item in item_data:
         PricedItem.objects.create(**item)
 
@@ -94,12 +91,18 @@ def create_visitor(mock_visit):
                 "firstName": "Some",
                 "lastName": "Visitor",
                 "email": f"{visitor_id}@example.com",
-                "country": "NL",
+                "addresses": [
+                    {
+                        "type": "billing",
+                        "country": "NL",
+                    }
+                ],
             },
             "tags": [],
         }
-        mock_visit.append(payload)
-        return payload
+        visitor = Visitor.from_api(payload)
+        mock_visit.append(visitor)
+        return visitor
 
     return _create_visitor
 
@@ -173,13 +176,16 @@ def mock_stripe(item_data):
         patch(
             "stripe.Customer.search", return_value={"data": [{"id": "stripe-cus-id"}]}
         ),
+        patch("stripe.Customer.create", return_value={"id": "stripe-cus-id"}),
+        patch("stripe.Customer.modify", return_value={"id": "stripe-cus-id"}),
         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",
             return_value={
-                "id": "stripe-invitem-id",
+                "id": "inv_stripe-inv-id",
                 "hosted_invoice_url": "http://boguspath",
+                "payment_intent": "pi_some_payment_intent",
             },
         ),
         patch("stripe.Invoice.add_lines"),
@@ -225,9 +231,9 @@ def config_file(stripe_api_key, visit_api_key, visit_expo_id, tmp_path):
     file.write_text(
         json.dumps(
             {
-                "STRIPE_API_KEY": stripe_api_key,
                 "VISIT_API_KEY": visit_api_key,
                 "VISIT_EXPO_ID": visit_expo_id,
+                "STRIPE_API_KEY": stripe_api_key,
                 "STRIPE_SIGNING_SECRET": "stripe-signing-secret",
                 "STRIPE_INVOICE_TEMPLATE_ID": "stripe-invoice-template-id",
                 "STRIPE_TAX_RATE_ID": "stripe-tax-rate-id",
@@ -238,7 +244,7 @@ def config_file(stripe_api_key, visit_api_key, visit_expo_id, tmp_path):
 
 
 @pytest.fixture
-def client(setup_django, mock_visit, mock_stripe):
+def client(mock_visit, mock_stripe):
     return Client()
 
 
diff --git a/test/test_processevent.py b/test/test_processevent.py
index 84f0d4f0d551335876bda545ea6141073bdd06c3..b95ae79daac92d248f2d29bb2fd72d51b9874b83 100644
--- a/test/test_processevent.py
+++ b/test/test_processevent.py
@@ -6,6 +6,14 @@ from stripe_checkout.stripe_checkout.models import Event, Order, PricedItem
 from stripe_checkout.stripe_checkout.shopping_cart import ShoppingCart
 
 
+def a_payment_intent():
+    return {"id": "pi_12345"}
+
+
+def an_invoice():
+    return {"id": "stripe-invoice", "payment_intent": a_payment_intent()["id"]}
+
+
 @pytest.fixture
 def order(default_visitor):
     item = PricedItem.objects.get(name="side-meeting-friday")
@@ -16,8 +24,7 @@ def order(default_visitor):
         }
     ]
     cart = ShoppingCart.from_visitor(default_visitor)
-    stripe_obj = {"id": "stripe-invoice"}
-    order = cart.create_order(default_visitor, stripe_obj=stripe_obj)
+    order = cart.create_order(default_visitor, stripe_obj=an_invoice())
     order.save()
     return order
 
@@ -32,12 +39,19 @@ def test_ignores_order_without_callback_event(order):
 
 @responses.activate
 @pytest.mark.django_db
-def test_processes_order_with_callback_event(order, default_visitor):
+@pytest.mark.parametrize(
+    "event, paid",
+    [
+        ("payment_intent.succeeded", True),
+        ("payment_intent.canceled", False),
+    ],
+)
+def test_processes_order_with_callback_event(event, paid, order, default_visitor):
     assert not order.paid
     event = Event.objects.create(
         payload={
-            "type": "invoice.paid",
-            "data": {"object": {"id": "stripe-invoice"}},
+            "type": event,
+            "data": {"object": a_payment_intent()},
         }
     )
     assert not event.handled
@@ -47,18 +61,18 @@ def test_processes_order_with_callback_event(order, default_visitor):
     event = Event.objects.first()
     assert event.handled
     order = Order.objects.get(pk=order.pk)
-    assert order.paid
+    assert order.paid == paid
 
 
 @responses.activate
 @pytest.mark.django_db
-def test_updates_visitor(order, default_visitor):
+def test_updates_visitor_paid(order, default_visitor):
     assert "PAID" not in default_visitor["tags"]
     assert len(default_visitor["questions"]) == 1
     Event.objects.create(
         payload={
-            "type": "invoice.paid",
-            "data": {"object": {"id": "stripe-invoice"}},
+            "type": "payment_intent.succeeded",
+            "data": {"object": a_payment_intent()},
         }
     )
     call_command("processevents")
@@ -69,3 +83,21 @@ def test_updates_visitor(order, default_visitor):
         "id": "side_meeting_paid",
         "answers": [{"id": "friday-paid"}],
     }
+
+
+@responses.activate
+@pytest.mark.django_db
+def test_updates_visitor_canceled(order, default_visitor):
+    assert "CANCELLED" not in default_visitor["tags"]
+    assert "PAID" not in default_visitor["tags"]
+
+    Event.objects.create(
+        payload={
+            "type": "payment_intent.canceled",
+            "data": {"object": a_payment_intent()},
+        }
+    )
+    call_command("processevents")
+
+    assert "PAID" not in default_visitor["tags"]
+    assert "CANCELLED" in default_visitor["tags"]
diff --git a/test/test_shopping_cart.py b/test/test_shopping_cart.py
index 622e83515da34f77d5d4d11aff3acda1b4d8edbb..f253cc9fc5208bcaa0155d4ba857fe5aa4d5fcd4 100644
--- a/test/test_shopping_cart.py
+++ b/test/test_shopping_cart.py
@@ -27,13 +27,13 @@ def test_create_order_with_items(default_visitor):
         }
     ]
     cart = ShoppingCart.from_visitor(default_visitor)
-    stripe_obj = {"id": "stripe-invoice"}
+    stripe_obj = {"id": "stripe-invoice", "payment_intent": "pi_12345"}
     cart.create_order(default_visitor, stripe_obj=stripe_obj)
     all_orders = Order.objects.all()
     assert len(all_orders) == 1
     order = all_orders[0]
     assert order.visitor_id == default_visitor["id"]
-    assert order.stripe_id == stripe_obj["id"]
+    assert order.stripe_id == stripe_obj["payment_intent"]
     assert len(order.items.all()) == 2
     assert not order.paid
 
diff --git a/test/test_stripe.py b/test/test_stripe.py
new file mode 100644
index 0000000000000000000000000000000000000000..694ea2d64651d0baeea036a70da4fd0aa4e08df4
--- /dev/null
+++ b/test/test_stripe.py
@@ -0,0 +1,61 @@
+import pytest
+import stripe
+from stripe_checkout.stripe_checkout.stripe import get_or_create_customer
+
+
+@pytest.mark.parametrize(
+    "visit_addresses, stripe_address",
+    [
+        ([], {}),
+        ([{"country": "NL", "type": "postal"}], {}),
+        (
+            [
+                {"country": "NL", "type": "postal"},
+                {"country": "BE", "type": "billing"},
+            ],
+            {"country": "BE"},
+        ),
+        (
+            [
+                {
+                    "address": "line 1\nline 2",
+                    "city": "cityasdf",
+                    "country": "NL",
+                    "houseNumber": "1234",
+                    "houseNumberSuffix": "A",
+                    "postalCode": "1235AF",
+                    "state": "state-asdf",
+                    "type": "billing",
+                }
+            ],
+            {
+                "line1": "line 1\nline 2 1234 A",
+                "postal_code": "1235AF",
+                "city": "cityasdf",
+                "state": "state-asdf",
+                "country": "NL",
+            },
+        ),
+    ],
+)
+def test_create_customer_with_billing_address(
+    visit_addresses, stripe_address, default_visitor, mock_stripe
+):
+
+    default_visitor["contact"]["addresses"] = visit_addresses
+    stripe.Customer.search.return_value = {"data": []}
+    get_or_create_customer(default_visitor)
+    assert stripe.Customer.create.call_count == 1
+
+    full_result = stripe.Customer.create.call_args.kwargs["address"]
+    non_empty = {key: val for key, val in full_result.items() if val}
+    assert non_empty == stripe_address
+
+
+def test_updates_visitor_with_address(default_visitor, mock_stripe):
+    default_visitor["contact"]["addresses"] = [{"country": "BE", "type": "billing"}]
+    get_or_create_customer(default_visitor)
+    assert stripe.Customer.modify.call_count == 1
+    new_address = stripe.Customer.modify.call_args.kwargs["address"]
+    non_empty = {key: val for key, val in new_address.items() if val}
+    assert non_empty == {"country": "BE"}
diff --git a/test/test_visit.py b/test/test_visit.py
index 535a93d969f115157e260432a486b8d9cd7ee308..4f0206da7f274c7a447156ba14a4514c76bf1ca4 100644
--- a/test/test_visit.py
+++ b/test/test_visit.py
@@ -26,14 +26,16 @@ def test_update_visitor(client, user, visitor_id):
 @responses.activate
 @pytest.mark.django_db
 def test_create_invoice(client, visitor_id):
-    """
-    test that the create-intent endpoint returns a redirect url
-    """
     assert not Order.objects.count()
 
     rv = client.post(f"/checkout/{visitor_id}/", data={"payment_method": "invoice"})
     assert rv.status_code == 303
-    assert Order.objects.count() == 1
+
+    orders = Order.objects.all()
+    assert len(orders) == 1
+
+    order = orders[0]
+    assert order.stripe_id.startswith("pi_")
 
 
 @responses.activate
@@ -42,7 +44,7 @@ def test_exchange_rate(client, default_exchange_rate, visitor_id):
     client.post(f"/checkout/{visitor_id}/", data={"payment_method": "invoice"})
     call_args = stripe.Invoice.create.call_args[1]
     assert call_args["custom_fields"][0] == {
-        "name": "GBP VAT Rate",
+        "name": "GBP VAT",
         "value": "GBP 1.60 (0.8000)",
     }
 
diff --git a/test/test_visit_client.py b/test/test_visit_client.py
index 1504bca383abc3aed157f4c4ce734caa69ebf65a..1e00118e0e9aacb66fa8ab460841b348b8d3ac0b 100644
--- a/test/test_visit_client.py
+++ b/test/test_visit_client.py
@@ -1,4 +1,6 @@
 import base64
+import re
+from django.http import Http404
 import pytest
 import responses
 
@@ -13,8 +15,17 @@ def api(mock_visit, visit_expo_id, visit_api_key):
 @responses.activate
 def test_sends_authorization_header(api, visit_api_key):
     api.list_visitors()
-    header = (
-        "Basic "
-        + base64.b64encode(f"{visit_api_key}:".encode()).decode()
-    )
+    header = "Basic " + base64.b64encode(f"{visit_api_key}:".encode()).decode()
     assert responses.calls[0].request.headers["Authorization"] == header
+
+
+@responses.activate
+def test_special_fake_visitor_id_that_return_list(api):
+    responses.reset()
+    responses.add(
+        responses.GET,
+        re.compile(r"https://api.visitcloud.com/create/v2/visitors/[^/]+/[^/]+$"),
+        json=[{"id": "12345"}],
+    )
+    with pytest.raises(Http404):
+        api.get_visitor("fakevisitor")