Thirteen surfaces, one set of rows.
The landing page shows five of these as tabs on one sheet, which is the argument rather than the layout: the door list, the baskets, the settlement and the reports are the same rows counted different ways, so they cannot disagree. Here is what each surface does, and, where it matters, where it stops.
It also sells things in boxes. An event, a menu and a shop run on the same tables, because the difference between a ticket and a shipment turns out to be almost entirely presentation: one asks an age at the door, one asks for an address, and one leads with a photograph. Everything below is true of all three unless it says otherwise.
Events and tiers
An event is a catalogue with a basket behind it. Tiers are products in that catalogue with their own price, release size and on-sale window, which is why an early bird selling out needs no different kind of object from a table costing two thousand euros.
Three states, not a checkbox. Off means the link answers 404. Draft means anybody with the link can look and nobody can buy, because a page has to be looked at before it is published, and looking at a page means loading it. Live means selling. Refusal is doubled up: a hand-written request to buy from a draft gets the same answer the widget does.
| Tier | Released | Sold | Price | Status |
|---|---|---|---|---|
| Early bird | 300 | 300 / 300 | €25.00 | Sold out |
| Advance | 400 | 392 / 400 | €32.00 | On sale |
| Final release | 200 | 186 / 200 | €38.00 | 14 left |
| Late release | 100 | — | €44.00 | Opens 20:00 |
| Guest list | 60 | 48 / 60 | €0.00 | No fee |
Extras, sizes and tables
"Large", "no bun", "extra cheese", "+1 drink with the ticket" are four names for one shape: a small decision about a single line, which may move the price and which the kitchen or the door has to be told about. So a choice belongs to the line, not to the product. Two burgers in one basket, one with cheese and one without, are two different things at two different prices.
A menu repeats itself, so a group of choices belongs to the sale and is attached to as many products as want it. Retyping "choose a size" per product is how a menu ends up with three different prices for a large.
| Group | Reads as | Drawn as | Stock |
|---|---|---|---|
| Table size | Pick exactly one | Radio | — |
| Cloakroom | Up to one | Radio, clearable | 400 |
| Add-ons | As many as you like | Checkboxes | — |
| Welcome drinks | Pick two | Checkboxes | 200 |
The checkout
Everything else in this system is ordinary create-read-update-delete. Checkout is the one compound operation, and it either happens completely or not at all: the basket is locked, an expired hold is refused, the terms are read, stock is taken, the coupon is redeemed, tax is worked out on the post-discount share, the order code is minted and the ledger rows are posted, all in a single transaction.
A client may propose a quantity. It may never propose an amount. Prices, costs and tax rates are copied onto the line at the moment it is held, so a price change tomorrow cannot rewrite what somebody agreed to today.
| Time | What happened | In it | Worth |
|---|---|---|---|
| 10:54:40 | opened | — | €0.00 |
| 10:54:41 | item added, 3 × Advance | 3 | €179.70 |
| 10:55:28 | payment started | 3 | €179.70 |
| 10:56:04 | payment failed, card declined | 3 | €179.70 |
| 10:56:26 | — next basket — | — | — |
| 10:56:32 | bought | 2 | €119.80 |
Tickets and the door
There is no ticket table. A line with a quantity of four is four tickets, and each of them gets a code and a square of its own, so a party can forward one each and arrive separately, and each forwarded mail names the chair its holder sits in.
The last characters of a ticket code are a signature, not a checksum. Without them the numbering makes every ticket on an order derivable from any one of them: a photographed square would hand somebody the rest of the party's codes, and the first of them to reach the door would be let in on another person's ticket.
One order, four tickets, three arrivals
Order
K7M2-9PQR
- 1K7M2-9PQR-1H4TC3In 20.14
- 2K7M2-9PQR-2Q9MC4In 20.14
- 3K7M2-9PQR-3H4TC5In 21.06
- 4K7M2-9PQR-4X2PC6Expected
The order code heads the mail and is the only reference anybody quotes. The highlighted three characters are the signature, and they are what stops a photographed square from handing over the rest of the party.
The confirmation, and the code on it
There is no separate ticket mail. The confirmation is what somebody holds up in a doorway, so it is ordered by what a person standing in a queue needs first: the code and its square, then when and where, then what they bought and what it cost. A receipt is read top to bottom once. A ticket is opened again under a torch with somebody waiting behind you.
The code is eight characters from an alphabet with the confusable ones taken out. No zero against O, no one against I or L, because this gets read aloud across a counter and typed by somebody standing up. Hyphens and case are stripped on the way in, so a sloppily typed code still finds the order.
The thirty an order code may use
The five that are deliberately missing
Those five are the whole point. Zero and O are the same character to somebody reading a phone screen out to a colleague on a door. One, I and L are the same character to somebody typing it. Dropping them costs a little entropy and removes an entire category of support call.
Baskets, counted as journeys
A basket is a session, and a session is not a story. Somebody who fills a basket, wanders off and comes back two days later to buy is, as three unrelated rows, two abandonments and a sale: three numbers, all of them misleading. Chained together it is one customer who took three visits to decide, which is a completely different fact about the business.
An abandonment later paid for is not a loss, and somebody who put the same two tickets down twice cost you one sale rather than two. So what was lost is the largest attempt across the whole story, never the sum of the tries. Otherwise the most persistent shoppers read as the most expensive ones.
Where 1,867 baskets stopped, and what each step cost
Opened a basket
1,867
Held stock · 263 never added a line
1,604
Reached payment · 424 stopped here, holding €6,180
1,180
Bought
1,102
The widest drop is the only row worth an argument, so it is the only one drawn in the brand colour. Counts and money arrive together on purpose: "you lost 22.7% of baskets between the cart and the card" is a statistic, and "holding €6,180" is a decision. Every bar is measured against the top of the funnel rather than against the row above it, so what you see is the shape of the loss instead of four rows that always look the same.
Getting last year in
A dropped spreadsheet, read by a model, applied by a human. Nothing is created until somebody presses Approve, and what Approve creates is an inactive sale, so there is a second deliberate step before anything is on sale. Two decisions stand between a messy export and a live event, and both of them are yours.
Any value the model could not point at arrives blank and marked, never filled in with something plausible. Every proposed value shows the source cell beside it. A price nobody can source is a refund waiting to happen, so the screen would rather ask than guess.
| Field | Proposed | Read from |
|---|---|---|
| Name | Kollektiv Nacht | Sheet 1, B4 |
| Doors | 14 Mar, 20:00 | Sheet 1, C4 |
| Capacity | 1,000 | Sheet 1, E4 |
| Advance price | Blank, ask | Nothing said a price |
| Venue | Maassilo | Matched in the directory |
| What it creates | Inactive sale | A second step before selling |
Money
The charge is created on your own account with your own payment provider, the money lands in your balance, and our fee comes out of it. Your business is the name on the buyer's statement, which is what somebody redirected to "the merchant's payment page" expects to see. We hold one secret key and it is the platform's; there is no credential of yours anywhere in this system.
Coming back from the payment page is not proof of payment. That address is a plain link anybody can type, so nothing is settled on arrival. It also covers the shopper who pays and closes the tab: they still get their order and their receipt, because none of it depended on the browser coming back.
| State | What it means | The basket |
|---|---|---|
| Open | Created; they have not finished | Held |
| Completed | Paid, and settled behind it | Ordered |
| Expired | Closed unpaid. Nobody was declined | Given its own clock back |
| Cancelled | They came back and cancelled. The only one they chose | Left alone |
| Failed | Paid, and settlement refused it. Refunded in full | Untouched |
Settlement, and the ledger under it
One immutable row per line of money that moved. Everything that reports a figure reads it, and nothing else does. A refund is a negative row, so every report is a plain sum and no query has to remember to subtract. That was the bug it was built to fix: refunds used to be written as positive rows of a different kind, and a sale refunded to zero still reported its full takings.
Append-only, enforced rather than agreed. It is the one table here that cannot be edited or soft-deleted, and that is deliberately what lets the others stay editable. A correction is a compensating row.
| Line | Sold | Sales | Coupons | Net sales |
|---|---|---|---|---|
| Early bird | 300 | €7,500.00 | €0.00 | €7,500.00 |
| Advance | 392 | €12,544.00 | €384.00 | €12,160.00 |
| Final release | 186 | €7,068.00 | €0.00 | €7,068.00 |
| Tables and extras | 5 | €9,640.00 | €0.00 | €9,640.00 |
| Your payment provider, on 612 card payments | — | — | — | −€698.52 |
| Settles to your own balance | 931 | €36,752.00 | €384.00 | €35,669.48 |
Reports, and the box you ask them from
A closed set of reports, and one box that picks between them. The model reads the question and chooses which report to run on which sale over how many days, then puts the resulting figures into a sentence having been shown the figures and nothing else. Every number in that sentence is then checked back against them, and a sentence carrying one that is not there is thrown away. You get the figures reported plainly instead, and the card says so.
Findings have no model in them at all. Patterns worth acting on are format strings over named queries, so two people running them on the same data get the same words, and each detector has a floor below which it says nothing rather than guessing.
| Report | Answers |
|---|---|
| Takings | What a sale has taken, per currency, net of refunds |
| Methods | How the money arrived, and what share each took |
| Funnel | Where baskets stop: opened, held stock, reached payment, bought |
| Audience · Origins | Who bought, and the countries and cities they gave |
| Losses · Lost sales | What is being given up on, what was in it, and whether it is getting worse |
| Pace · When · Products | How long people took, when they bought, which tiers get put back |
| Pulse · Live | Today against yesterday, and what is sitting in a basket this second |
| Advice | Patterns worth acting on, with the evidence behind each |
The shop you drop on your own site
Two lines of HTML put the checkout on your page. It is a custom element behind a shadow root, and the boundary is the point: your stylesheet cannot reach in and break the checkout, and the checkout cannot reach out and restyle your site. Colours come through as ordinary custom properties, so you theme it with a stylesheet rather than by stringifying colours into HTML.
Every size decision asks the space it was given, not the window. That is what makes the same build correct in a 380px sidebar on a 27-inch monitor and full screen on a phone. One component answering the same question twice, rather than two builds and a redirect that guesses from a user agent.
| Given | What it draws |
|---|---|
| under ~48rem | One column, sticky action bar, basket as a sheet |
| ~48rem and up | Two columns, basket rail always visible, no action bar |
| ~56rem and up | Products in two columns as well |
| No site of your own | A hosted page, art-directed above the shop and deliberately ordinary at it, because nobody enters a card number under stencil caps |
Running it as your own platform
A tenant is a whitelabel environment and an operational division at once: a franchise, an overseas subsidiary, a venue group. It owns its name, logo and base theme, the identity its mail comes from, and its own staff. Every scoped row in the system is stamped with which tenant it belongs to, by the database rather than by the code that happened to write it.
Being the wrong kind of account and asking for a row you cannot see are two different refusals, on purpose. The first says so; the second answers as though the row does not exist, because a permission error would confirm that it does.
| Role | Reaches |
|---|---|
| Platform owner | Every tenant, and the tenants themselves |
| Tenant admin | One tenant, everything inside it |
| Organiser | One tenant, their own rows inside it |
| Door staff | One tenant, and only the sales they are assigned to |
| Shopper | Their own orders, including everything they bought before signing in |
Seat maps Pro
A venue is one document that stores generators rather than seats: a block says "38 rows of 26, 50cm apart, bowed by 60cm, lettered from the front". A 90,000-seat bowl is a couple of thousand of those, and a few hundred kilobytes, against a seat table that would dwarf every order ever placed and exist only to be read back in one lump and drawn.
What a seat costs and whether it is free are never in the plan. Both are facts about one night rather than about the room: availability changes minute to minute, and a price differs between a matinee and a Saturday. A plan is reused across a season, so a price written into it would be a season-long lie that only surfaces in front of a shopper.
| Asking | Writes |
|---|---|
| Where do these people sit | The order's seats |
| Which seats is this ticket sold for | The product's seat list |
| Which of these chairs have gone | Nothing at all |
| Where would you like to sit | The basket's holds, the seam still being wired |
See it against a season of your own numbers.
Thirty minutes. Send last year's export and we will send back your repeat buyers, what was recoverable and what it was worth, and how many of those people you could lawfully contact.