Live Product

One chain from the joining form to the books

A membership organisation is administratively heavier than its size suggests. It admits members and collects a kontingent from them. It runs activities and lets people book facilities. It sells things. It keeps minutes, answers to an auditor, and has to produce books at the end of the year. Medlemsplatformen is the platform that runs all of it: one backend and one database serving many organisations, each able to present its own public site.

The interesting work was not building the modules. It was the joins between them. Each of these processes traditionally keeps its own truth — a spreadsheet of members, a bank statement, a booking calendar on a noticeboard, a shoebox of receipts, and one volunteer treasurer who knows how the pieces relate. Nothing reconciles, because nothing is derived from anything else.

Joining them into one chain forces a question at every handover: what carries across, what is derived rather than re-entered, and who is allowed to say yes. Most of the design in this system is answers to that question, and most of the hard cases are at the seams rather than inside any one module.

It is built for Danish practice — kontingent, vedtægter, a board that decides who joins, a bookkeeping obligation that runs five years — and the money rules are shaped by the fact that the people operating it are usually volunteers.

Multi-tenant: one backend and database, many organisations, each on its own public domainMembership, kontingent, activities, bookings, webshop, governance and bookkeeping on one chain of recordA member's price has exactly one source, enforced by a build-time guardNo money moves before a person approves, and value is granted by an admin rather than taken by a memberTransactions from the operating modules post into a double-entry ledger with voucher traceabilityBookkeeping is not registered with Erhvervsstyrelsen; that is stated, not implied
Scroll to see how it works

The Problem

Every process kept its own truth, and none of them agreed

None of these frictions is dramatic alone. Together they mean an organisation cannot answer simple questions about itself, and the answers it does give depend on who you ask.

Start with the simplest question a member can ask: what do I pay? In the system as it first grew, that answer could come from eight different places — organisation settings, environment configuration, a global pricing catalogue row, a member segment, a trial group, an amount stamped on the subscription when they signed up, an amount stamped on the signup form, and a hardcoded age cutoff that silently decided who counted as a youth member. Each was a plausible answer, so every screen that displayed a price picked a different one.

The same fragmentation runs through the rest. A booking calendar does not know whether the person booking is a paid-up member. Activity income arrives as a line on a bank statement with no link to the activity. The treasurer reconciles by hand, from memory and receipts, and the books are assembled once a year from evidence that was never designed to be assembled.

And the dashboard disagrees with itself. A badge counting one set of pending members while the queue it links to listed a disjoint set meant an admin could be told one person was waiting and land on an empty page — a small thing that teaches people not to trust the system.

  • One question, eight competing answers, and no way to tell which was authoritative
  • Modules that cannot see each other's state, so eligibility is checked by hand
  • Income that reaches the books without carrying what it was income for
  • Reconciliation done annually, from evidence never designed for it
  • Counters and the pages they link to derived independently, and disagreeing
  • An operating model that depends on one volunteer's working knowledge

The Solution

One source per fact, and a person at every point where value is granted

Joining processes into a chain is mostly a matter of deciding what derives from what. Two rules did most of the work, and the second one is the governance decision.

The first rule: a member's price lives on their membership plan and nowhere else. The plan is the membership type — Standard, Premium, Ungdom, Æresmedlem, a trial that graduates onto another plan — and the price is not attached somewhere alongside it, it is the plan. All eight competing sources were removed, and a source-level test now fails the build if any of them reappears, which is how this would actually come back: through a merge, quietly, months later.

The second rule: value is granted by an administrator, never taken by a member. A card presented at signup is authorised, not charged. The organisation decides who joins it, so the signup waits in a queue where a board member reviews the applicant and, if they are on the wrong plan, changes it before any money moves. The amount is then resolved from the plan they are on at approval — never from the figure quoted at signup, which is exactly the number that may have just been changed.

That ordering is the whole design. Charge first and adjust afterwards, and you owe a refund and an apology. Charge on approval, and nobody is billed the wrong amount in the first place. It also produces the harder case honestly: someone who picked no plan has no card on file, so when the board places them on a paid plan the system bills them and leaves them inactive until they pay. Activating them anyway would be giving away a paid membership; that is a decision an organisation should make deliberately, not a default the software picks.

  • The plan is the price; a build-time guard stops any other source returning
  • Cards are authorised at signup and charged only on approval
  • The amount comes from the plan at approval, not the one quoted at signup
  • A member with no card is billed, never activated for free
  • An exempt member is no payment at all, not a zero-kroner one
  • One shared definition of "waiting on an admin", so counters and queues cannot disagree

Key Flow

How a joining form becomes a member, a payment, and a line in the books

Every step below is implemented. The chain matters more than any step in it: each handover states what carries across and who is allowed to advance it.

Step 01

The form has two endings

A single public signup form branches on one question: did the applicant pick a plan? Every plan an applicant may pick is one that eventually charges, so picking one leads honestly to a payment screen. Reduced rates such as youth and honorary membership are published without a button, because the vedtægter usually require the rates to be public while the organisation grants them. Submitting an empty choice is therefore not an oversight — it is how you ask to be placed.

Step 02

The card is authorised, not charged

An applicant who picked a plan is sent to the payment provider, which returns a subscriber the system can bill later. Nothing is taken. The signup, the user, the member record and the subscription are created in one commit, so a half-finished applicant cannot exist.

Step 03

The organisation decides

Both endings arrive in the same approval queue, and only there — a person waiting on a decision appears on exactly one screen, so nobody is asked "approve or reject?" in one place and "which plan?" in another. The reviewer sees whether a trial has already been spent and whether this is a returning member, before choosing a plan rather than after, when a refusal would arrive as an error.

Step 04

The amount is resolved at approval

On approval the charge is computed from the plan the member is on at that moment. If a card is on file it is charged; if not, a bill and a payment link go out and the applicant stays inactive until they settle it. An exempt plan produces no payment at all rather than a zero-kroner one, because a zero handed to a payment provider is a transaction that has to be explained later.

Step 05

Payment settles the obligation that was raised

Paying the link settles the same pending payment the approval created, rather than raising a second one and leaving the first open for ever. Because approval and payment can happen in either order depending on which ending the applicant took, the approval is stamped separately from the card acceptance and from activation — three distinct facts, so the payment webhook can tell an approved member from an unapproved one and never sends a paid member back into the queue.

Step 06

The money reaches the books carrying what it was for

A payment records the plan it was paid under at the time the money moved. A later plan change decides what a member pays next, never what they already paid — without that stamp, moving somebody from Premium to Standard would silently re-attribute every kroner they had ever paid, and the organisation's own record of what it earned would rewrite itself. From there, transactions from membership, the webshop and activities post into a double-entry ledger against a fiscal year, with vouchers, VAT codes, bank reconciliation and an append-only audit log.

Artifact Preview

The money rules, and what stops each one quietly becoming false

These are the claims the system makes about money. Each is stated so it can be argued with, and each names the test that fails if it stops holding — which is the difference between a rule and an intention.

Membership money invariants — excerptRedacted excerpt

The rule

What it prevents

How it is held

A member's price comes from their plan, with no fallback anywhere

Eight screens showing four different prices for the same person

A source-level test that fails the build if a removed price source reappears

Nothing is charged before an administrator approves

Billing someone the organisation has not admitted

A named test on the approval path

The charge comes from the plan chosen at approval, not the one quoted at signup

Charging the figure the reviewer just corrected

A named test that changes the plan, then approves

A member with no card is never charged and never activated free

Giving away a paid membership, or inventing a charge with no mandate

A named test that stubs the charge function to throw

A signup cannot be approved twice

A double-clicked approval charging the card twice

A named test on repeat approval

A spent trial period is refused, not granted again

Churning a free membership by cancelling and rejoining

The trial is recorded against the person and outlives any subscription; a second request raises rather than grants

An applicant who never finished owes nothing

Inventing a debt out of an abandoned browser tab

A named test on the abandoned-checkout state

Moving a member does not rewrite what they already paid

A plan change re-attributing historic income behind the treasurer's back

The plan is stamped onto the payment at the moment the money moves

Test names, table names and thresholds are omitted here; they are named in the repository's own lifecycle document. Two further rules on exempt members and bill settlement are covered in the flow above. The rules and their guards are how the system runs today.

Artifact Preview

Where a person stands, and which screen is responsible for them

A joined-up chain creates a failure mode a set of disconnected modules does not have: the same person appearing on two screens with two different jobs. Assigning each state to exactly one owner is what keeps the counters honest.

Membership state ownership — excerptRedacted excerpt

Where the person stands

Approval queue

Member overview

Finance

Waiting on an administrator

Only here

Excluded

Excluded

Approved and billed, not yet paid

Excluded

Awaiting payment, with plan and amount

Counted as outstanding

Abandoned the checkout

Excluded

Never activated, showing the plan they asked for

Owes nothing

Paid

Excluded

Ordinary member, with plan and renewal date

Revenue, attributed to their plan

The abandoned-checkout row is deliberately kept on the member overview rather than dropped: nobody is waiting on that person, so no other screen would show them, and an organisation still has them on its books. Counting them as outstanding would invent a debt; hiding them would lose a person.

Proof Layer

What was built, and what it is tested against

This section reports properties of the system rather than improvement percentages. Each can be checked against the running platform and its repository.

Context

Operational domain
Membership organisations running joining and kontingent, activities, facility bookings, a webshop, governance and bookkeeping
Primary users
Volunteer boards and treasurers, an administrator per organisation, and members acting through a public site or emailed links

Scope

Workflow coverage
Signup and approval, kontingent collection and renewal, activities and check-in, bookings with waitlists, webshop with reserve-or-charge fulfilment, invoicing, double-entry bookkeeping, board minutes and agreements, questionnaires, incident handling and a mentoring programme
Tenancy
One backend and one database serving many organisations, each able to serve its own public domain from shared APIs, with authentication once and tenant switching for people who belong to several

Constraints

Authority requirement
Value is granted by an administrator and never taken by a member; no charge precedes an approval, and a spent trial is refused rather than reissued
Correctness requirement
One source per fact, enforced at build time for price; a signup and its member, user and subscription are created in one commit or not at all

Artifacts Delivered

Product deliverables
Administrative interface per organisation, public tenant sites and signup pages, a mobile client, a double-entry ledger with vouchers and VAT codes, and scheduled jobs for recurring collection and card-expiry reminders
Assurance deliverables
2,605 backend tests passing at 78.16% statement and branch coverage, measured 2026-07-31; ten money rules each named against a guarding test; a documentation status table regenerated by reading the code

Outcome Signals

What the organisation gets
One answer to what a member pays, a queue that agrees with the badge that counts it, kontingent collected without volunteer chasing, and books assembled from transactions that carry what they were for
What is provable afterwards
What each member was charged and under which plan, when an administrator approved them, which vouchers support each journal entry, and an append-only finance audit log across the fiscal year

Outcome signals are anonymized measurements from a defined pilot period. Ranges are used to preserve client confidentiality. The measurement period, baseline, and scope are stated in the classification and evidence note on this page.

How to read this case

Classification and evidence method

A product we build and run in production today, not a one-off engagement.

No controlled before-and-after study was run, so this case states no improvement percentages. What it reports instead is verifiable: what the system does, what it refuses to do, and what it does not cover. This case reports properties of the system rather than improvement percentages, because no controlled before-and-after study of an adopting organisation exists. The verifiable facts are the system's own: 2,605 backend tests passing at 78.16% statement and branch coverage, measured 2026-07-31; ten stated money rules each named against the test that fails if it stops being true; and a shipped-versus-designed status table maintained by reading the code rather than the plan documents.

Case type
Live Product
Number basis
Not measured — capability stated instead

Published · Updated

What Changed Structurally

The design lives at the joins, not inside the modules

Any competent team can build a member registry, a booking calendar or a ledger. What decides whether the result is trustworthy is what happens between them — and those decisions are governance decisions wearing engineering clothes.

Three of them carried most of the weight. The first was collapsing eight price sources to one, which is unglamorous and was the single largest improvement: it turned an unanswerable question into a lookup, and it made every downstream number — a member's bill, an outstanding balance, an income line — derive from the same fact rather than race to define it.

The second was deciding that authorisation and charging are different events, separated by a human decision. That single ordering removes an entire category of remediation work: refunds, apologies, corrected invoices, and the awkward conversation where a volunteer treasurer explains to a member why they were charged the wrong kontingent. It costs one extra step in the flow and a stamp in the database to tell the two orderings apart.

The third was stamping provenance onto payments so that history cannot be rewritten by a present-tense change. A payment that knows only its subscription inherits whatever plan that subscription names today, which means the organisation's record of its own past income is a function of its current configuration. Recording the plan at the moment the money moves makes a plan change mean what people expect it to mean: it decides what a member pays next.

There is a fourth, and it is a refusal. A kontingent buys a membership, not access. What someone paid is deliberately not wired to what they may do in the system, because a lapsed renewal would then silently withdraw permissions — which is a way to lock a board out of its own tools on a failed card. Roles are granted by an administrator, independently of payment. The two are related in the organisation's rules and unrelated in the software, on purpose.

  • One fact, one source, enforced where it would otherwise regress
  • Authorisation and charging separated by a human decision
  • Provenance stamped at the moment of the transaction, not derived later
  • Payment deliberately not wired to permission

Limits

What this platform does not do

Two of these matter commercially, and stating them is what makes the rest of the page worth trusting.

The bookkeeping is a working double-entry ledger — accounts, journal entries and lines, vouchers, VAT codes, fiscal years, bank transactions, an audit log, and services supporting the five-year retention obligation. It is not a registered bookkeeping system. Registration with Erhvervsstyrelsen, along with NemHandel and Peppol e-invoicing and the current SAF-T version, is designed and not shipped, and the plan for it still carries open decisions requiring review by an accountant and a lawyer. An organisation adopting the module today keeps that obligation where it already sits.

Retrieval over an organisation's own content is not built. The platform has a working retrieval feature over referee rulebooks, and an AI writing assistant that rewrites selected text, but the general tenant-content knowledge base described in the internal scope document has no implementing code, and the member-facing AI that would depend on it therefore does not either.

Some module boundaries are still partial in named ways rather than vaguely: recurring booking series are designed while one-off bookings ship; the AI editor assist covers selected text but not block or page generation; notification emails remain English-only despite the scaffolding for per-recipient language. Each of these is tracked as a named gap rather than an implied capability.

One limit is about people rather than code. A trial period is recorded against a person, so cancelling and rejoining cannot produce a second one — but somebody signing up again under a different email address gets a fresh record and therefore a fresh trial. Closing that needs identity matching across name, birthdate or phone, which is a product decision and a GDPR conversation rather than a bug fix. Today the defence is that an administrator sees every signup before approving it, which is a real control and a weaker one than a constraint in the database.

  • The ledger works; it is not registered with Erhvervsstyrelsen
  • E-invoicing and the current SAF-T version are designed, not shipped
  • No general retrieval over an organisation's own content, and no member-facing AI built on it
  • Recurring booking series, block-level AI generation and localised notifications are named gaps
  • Trial reuse under a new email address is open, and defended by human review rather than by the data model

Outcome

The organisation can answer questions about itself

The gain is not that any single task is faster. It is that the chain holds, so a question asked at one end can be answered from the other.

What does this member pay, and since when? Who is waiting on the board, and does the badge that says so agree with the page it links to? What did we earn last year, under which plans, and which voucher supports each entry? Before the consolidation these were reconstructions. They are now lookups, because each is derived from a single recorded fact rather than assembled from several competing ones.

The operating consequence is that the work stops depending on one volunteer's memory. A treasurer who leaves takes no unique knowledge of how the kontingent is computed, because the plan computes it. A board reviewing a joiner sees the trial history and the plan options on the screen where the decision is made. And the points where a person must still decide are explicit and few, rather than scattered through the process as unwritten habit.

  • One authoritative answer to what a member pays
  • Counters and the queues behind them derived from one definition
  • Kontingent collected on a schedule, with a clear list of who is missing
  • Income arriving in the books carrying the plan it was earned under
  • Human decisions concentrated at the points where value is granted
Explore the Garage CRM case: Garage CRM
Related Systems Delivery
Rental OperationsEncoded Process

Garage CRM

The same pattern in a smaller domain: encode the operating rules first, then let assistance act on them, with every business record change gated behind a person.

Explore the Garage CRM case

explore further

Related capabilities

  • Systems Architecture

    Designing architectural foundations that allow complex organizations to operate reliably and evolve safely.

  • Platform Engineering

    Reliable and maintainable platform foundations that support development and operations at scale.

Further reading

  • The AI code trust gap: adoption is settled, ownership is not

    Around 90% of developers use AI daily, more distrust its accuracy than trust it, and its security pass rate has not moved in a year. Read together, the 2025-2026 evidence says the constraint has shifted from writing software to owning it, and that is a specification and accountability problem rather than a tooling one.

Interested in how this approach could work for your organization?

Get in touch
core purpose. techTechnology consulting with purpose.