Fakturering i systemet — och ingen betalleverantör

ALVA säljs inte över disk. Aktiveringssekvensen på webbplatsen har hela
tiden sagt det: ansökan granskas, faktura utfärdas, betalning
registreras, organisationen aktiveras. Ändå laddade sidan Stripe.

Stripe. `@stripe/stripe-js` injicerar sitt skript redan vid import, inte
när någon ska betala, så ALVA hämtade en betalleverantör vid varje
sidvisning trots att produkten aldrig tar emot en betalning. Bakom
artefaktens CSP blockerades anropet och syntes som ett fel i konsolen på
ett system som inte har en kassa. Importen går nu mot `/pure`, som är
samma modul utan sidoeffekten. Butikens kassa fungerar som förut, men
bara när någon faktiskt öppnar den. Externa begäranden på ALVA-ytan:
noll.

Faktureringen. Beloppet måste komma någonstans ifrån, och det enda
hållbara stället är organisationens faktiska tillstånd: aktiva konton,
licensperiod, påslagna moduler. En summa någon skriver in kan säga emot
verkligheten — en härledd kan inte det. Samma resonemang som
sammanfattningen. Varje rad bär sitt underlag, så en granskare ser varför
summan blev den den blev utan att fråga någon.

Beloppen räknas i öre, momsen på nettot och inte per rad, och en utfärdad
faktura ändras aldrig: en felaktig faktura krediteras med en post som
pekar tillbaka och kräver ett skäl. Samma regel som för händelseloggen.

Prislistan ligger på ett ställe och är märkt som platshållare — den ska
sättas per marknad. Mekanismen är oberoende av siffrorna.

Ingen betalningshantering, inga kortuppgifter, ingen extern tjänst.
Betalning registreras av en administratör när pengarna kommit in;
systemet påstår aldrig att något är betalt utan att en människa sagt det.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
This commit is contained in:
Claude
2026-08-06 09:38:52 +00:00
parent 9533a90b7b
commit b457543ede
8 changed files with 586 additions and 2 deletions
+181
View File
@@ -0,0 +1,181 @@
// ALVA-PROC-0001 · Fakturor.
//
// Fakturan skapas i systemet, inte hos en betalleverantör. Det finns
// ingen kassa, ingen kortinmatning och ingen prenumeration att säga upp:
// ansökan granskas, faktura utfärdas, betalning registreras av en
// människa, organisationen aktiveras.
//
// Vyn visar därför två saker och inget mer: vad organisationen betalar
// för, och varför beloppet blev det det blev. Varje rad bär sitt
// underlag — antalet aktiva användare är inte en uppskattning utan de
// konton som faktiskt kan logga in.
import { useMemo } from "react";
import {
PRISLISTA,
fakturera,
formateraBelopp,
} from "../../../../services/gemensam/fakturering.mjs";
import { Block, Demonstration, Etikett, FARG, Rubrik, Statusmärke, Tabell } from "@/alva/komponenter";
import { Ram } from "./Ram";
interface Faktura {
beteckning: string;
organisation: string;
period: { fran: string; till: string };
utfardad: string;
forfaller: string;
valuta: string;
rader: { benamning: string; underlag: string; antal: number; enhet: string; apris: number; belopp: number }[];
netto: number;
moms: number;
momssats: number;
totalt: number;
status: string;
betalningssatt: string;
}
// Exempelorganisationen. Siffrorna nedan är indata, inte resultat —
// fakturan under dem räknas fram av samma modul som servern använder.
const ORGANISATION = {
namn: "Verkstad Nord AB",
moduler: ["forsakringsrapportering"],
};
const AKTIVA_ANVANDARE = 12;
const PERIOD = { fran: "2026-01-01", till: "2026-12-31" };
const STATUSAV: Record<string, "passed" | "pending" | "not_applicable"> = {
betald: "passed",
utfardad: "pending",
krediterad: "not_applicable",
};
export default function Fakturor() {
const faktura = useMemo(
() =>
fakturera({
nummer: 1,
org: ORGANISATION,
aktiva: AKTIVA_ANVANDARE,
period: PERIOD,
utfardad: "2026-01-15",
}) as Faktura,
[],
);
return (
<Ram portal>
<div className="mx-auto max-w-[1040px] px-6 py-12">
<Etikett>Commercial</Etikett>
<div className="mt-2 mb-2">
<Rubrik niva={1}>Invoices</Rubrik>
</div>
<p className="mb-8 max-w-[680px] text-[14px] leading-[22px]" style={{ color: FARG.steel }}>
ALVA is invoiced, not purchased online. No card details are handled and no payment provider is
involved. An invoice is derived from the organization&rsquo;s own state its licence period, its
active accounts, its enabled modules so the amount cannot disagree with what the platform
actually holds.
</p>
<Demonstration>
The organization below is an example, and the price list is a placeholder to be set per market.
The invoice itself is not: it is computed by the same module the server uses, from the inputs
shown.
</Demonstration>
<Block rubrik="Invoice" beteckning={faktura.beteckning}>
<div className="grid gap-6 sm:grid-cols-2">
<dl className="text-[13px] leading-[22px]">
{[
["Organization", faktura.organisation],
["Period", `${faktura.period.fran} ${faktura.period.till}`],
["Issued", faktura.utfardad],
["Due", faktura.forfaller],
].map(([etikett, varde]) => (
<div key={etikett} className="flex justify-between border-b py-2" style={{ borderColor: FARG.lightSteel }}>
<dt style={{ color: FARG.steel }}>{etikett}</dt>
<dd className="font-mono">{varde}</dd>
</div>
))}
</dl>
<div>
<Etikett>Status</Etikett>
<div className="mt-2">
<Statusmärke status={STATUSAV[faktura.status] ?? "pending"} />
</div>
<p className="mt-6 text-[12px] leading-[18px]" style={{ color: FARG.steel }}>
{faktura.betalningssatt} Payment is registered by an administrator when the funds arrive
the platform never records an invoice as paid on its own.
</p>
</div>
</div>
</Block>
<Block rubrik="Lines" beteckning="ALVA-PROC-0001">
<Tabell
kolumner={["Item", "Basis", "Quantity", "Unit price", "Amount"]}
rader={faktura.rader.map((r) => [
r.benamning,
<span key="u" className="text-[12px]" style={{ color: FARG.steel }}>
{r.underlag}
</span>,
<span key="a" className="font-mono">
{r.antal} {r.enhet}
</span>,
<span key="p" className="font-mono">
{formateraBelopp(r.apris, faktura.valuta)}
</span>,
<span key="b" className="font-mono">
{formateraBelopp(r.belopp, faktura.valuta)}
</span>,
])}
/>
<dl className="mt-6 ml-auto max-w-[320px] text-[13px] leading-[22px]">
{[
["Net", faktura.netto],
[`VAT ${Math.round(faktura.momssats * 100)} %`, faktura.moms],
].map(([etikett, belopp]) => (
<div key={etikett as string} className="flex justify-between border-b py-2" style={{ borderColor: FARG.lightSteel }}>
<dt style={{ color: FARG.steel }}>{etikett}</dt>
<dd className="font-mono">{formateraBelopp(belopp as number, faktura.valuta)}</dd>
</div>
))}
<div className="flex justify-between border-b-2 py-2" style={{ borderColor: FARG.graphite }}>
<dt className="font-semibold">Total</dt>
<dd className="font-mono font-semibold">{formateraBelopp(faktura.totalt, faktura.valuta)}</dd>
</div>
</dl>
</Block>
<Block rubrik="Price list" beteckning="ALVA-SPEC-030">
<p className="mb-4 text-[13px] leading-[20px]" style={{ color: FARG.steel }}>
One list, one place. Change a figure here and every invoice issued afterwards follows
invoices already issued do not, because an issued invoice is never edited. A correction is a
credit note that points back at it, and it requires a stated reason.
</p>
<Tabell
kolumner={["Component", "Basis", "Price"]}
rader={[
["Platform license", "Per organization, annually", formateraBelopp(PRISLISTA.plattform_ar)],
["User licenses", "Per active user, monthly", formateraBelopp(PRISLISTA.anvandare_manad)],
...Object.values(PRISLISTA.moduler).map((m) => [
(m as { namn: string }).namn,
"Enterprise module, annually",
formateraBelopp((m as { ar: number }).ar),
]),
].map((rad) => [
rad[0],
<span key="b" className="text-[12px]" style={{ color: FARG.steel }}>
{rad[1]}
</span>,
<span key="p" className="font-mono">
{rad[2]}
</span>,
])}
/>
</Block>
</div>
</Ram>
);
}
+1
View File
@@ -20,6 +20,7 @@ const PORTAL = [
{ till: "/alva/portal/analys", text: "Analysis" },
{ till: "/alva/portal/kunskapskallor", text: "Knowledge sources" },
{ till: "/alva/portal/integration", text: "Integration" },
{ till: "/alva/portal/fakturor", text: "Invoices" },
{ till: "/felsokning", text: "Diagnostics" },
];