Ett kassasystem ser ut som en webbshop med färre sidor. Det är det inte. En webbshop får misslyckas — kunden försöker igen. En kassa har en människa framför sig med en kortterminal i handen och en kö bakom sig, och den får inte tveka.
Det här handlar om kassaapparat.se, och om att det svåraste inte var tekniken.
Lagen ritar produkten
I Sverige finns en gräns som avgör allt: den som säljer mot kontant betalning för mer än fyra prisbasbelopp om året måste ha ett certifierat kassaregister med typgodkänd kontrollenhet. Under gränsen — och för många ideella föreningar — gäller inte kravet.
Den gränsen är inte en detalj i kravlistan. Den är produktstrategin.
Ovanför den finns en mur av certifiering, hårdvara och myndighetskrav som kostar pengar att ta sig igenom. Under den finns föreningar och små verksamheter som behöver exakt samma funktioner — sälja, kvittera, stämma av dagen — men inget av regelverket. Det är ett ovanligt tydligt läge: en fri nivå under gränsen kostar mig nästan ingenting att erbjuda, och en betald nivå ovanför den har ett självklart existensberättigande, eftersom den löser något kunden ändå är tvungen att lösa.
Bygger man i ett reglerat område är regelverket färdplanen. Det tog mig längre tid att inse än det borde.
”Helt gratis” var charmigt och låste allt
Första versionen av landningssidan sa att allt var gratis. Det kändes generöst och det stämde — systemet hade en enda användare, en cateringverksamhet som står mig nära.
Problemet med ett offentligt löfte om gratis är att det cementeras i samma sekund det får sin första riktiga användare. Går det att ta tillbaka? Formellt ja. I praktiken blir det ett svek mot dem som kom in på det löftet.
Det var alltså sista chansen att positionera om, och beslutet blev tre nivåer i stället för en: en fri nivå med hårda kvoter — en försäljningsplats, ett begränsat antal produkter och användare, tre månaders rapporthistorik — och två betalda ovanför.
Kvoter i stället för ”obegränsat gratis” är inte snålhet. Det är det som gör att en verksamhet som borde betala inte kan bo kvar på fribiljetten, och det är den enda anledningen till att fribiljetten kan fortsätta finnas.
Priserna lämnades först tomma. Att gissa ett pris och skriva ut det är värre än att skriva att det kommer — tomrummet gav några veckor att lyssna. När de väl sattes blev de lågt, av ett enda skäl: det är betydligt lättare att höja ett pris än att sänka det.
Löften utan kod bakom sig
Och här gjorde jag felet som är värt hela det här inlägget.
Kvoterna stod på landningssidan. De fanns inte i koden.
En besökare kunde alltså läsa att gratisnivån rymmer femtio produkter, registrera sig, lägga in femhundra, och ingenting hade hänt. Marknadsföringen hade skrivit checkar som systemet inte kunde lösa in, och det är exakt så förtroende raseras — inte genom att man lovar för mycket, utan genom att löftet och verkligheten aldrig kopplades ihop.
Rättningen var att flytta ner planerna i databasen: en kolumn på företaget, en modul med kvoterna, och kontroller i de anrop som skapar resurser. Försöker man passera gränsen svarar servern med en tydlig statuskod och texten om att uppgradera — i själva gränssnittet, inte i finstilt text.
Samtidigt fick den enda befintliga användaren en flagga som undantar dem från kvoterna. Den som var med från början ska inte straffas av att villkoren ändrades.
Buggen som bara finns i fleranvändarsystem
En säkerhetsgenomgång hittade tre allvarliga fynd. Det mest lärorika handlade om identitet.
Systemet är fleranvändarbaserat: samma person kan finnas i flera företag. Det var löst genom en rad per företag — vilket är en fullt rimlig modell, ända tills man kommer till lösenord.
Ett lösenordsbyte uppdaterade nämligen en rad. De andra behöll det gamla lösenordet. Alltså: en användare som blivit av med sitt konto, eller som bytt lösenord efter en misstänkt läcka, kunde fortfarande logga in — i ett annat företag, med den gamla uppgiften. Ingen del av flödet var uppenbart fel. Felet låg i att identiteten fanns i flera exemplar medan alla trodde att den fanns i ett.
Fixen var en gemensam funktion som sätter lösenordet på samtliga rader med samma e-postadress, anropad från alla fyra ställen där ett lösenord kan ändras. Plus deterministisk sortering vid inloggning, så att det inte beror på databasens humör vilken rad som råkar väljas.
Läxan: när samma person kan finnas flera gånger i din datamodell har du en identitetsmodell, vare sig du bestämt dig för det eller inte. Bestämmer man sig inte blir svaret olika på olika ställen i koden.
Det som verkligen tog tid
Inte kassavyn. Inte lagerhanteringen. Inte kunddisplayen.
Det som tog tid var allt runtomkring som gör att någon vågar använda det: integritetspolicy, ett personuppgiftsbiträdesavtal att signera, en statussida att peka på när något strular, en hjälpsida som faktiskt svarar på frågor, och en supportadress som inte är ett svart hål.
Ingen av de sakerna syns i produkten. Alla saknas omedelbart om de inte finns — och en förening som ska hantera medlemmars betalningar kommer att fråga efter dem.
Vad jag skulle gjort annorlunda
Bestämt affärsmodellen före landningssidan. Att skriva ”gratis” innan man vet vad det ska kosta är att fatta beslutet ändå — bara utan att tänka efter först.
Aldrig publicerat en gräns som inte gick att överträda. Kvoter på en säljsida utan kod bakom är inte marknadsföring, det är en lögn med fördröjd verkan.
Läst regelverket först. Gränsen på fyra prisbasbelopp hade gett hela produktstrukturen på en eftermiddag, i stället för efter ett halvår av gissningar om vad folk kunde tänkas vilja betala för.
Att bygga systemet gick fort. Att förstå vad det skulle vara tog mycket längre tid — och den delen kan ingen agent göra åt mig.