What happens when a module lapses
A module that can be bought can stop being paid for. What the product does at that moment is the difference between a system that feels modular and one that feels broken, so it is decided once here and every module behaves the same way.
Decided in #204, part of #199.
The policy
Hide access. Retain data. Never destroy.
Three sentences, in priority order. When they conflict, the earlier one wins.
1. Access disappears
- The module’s screens leave the dashboard. Not greyed out with a padlock — gone, with the module listed on the Modules screen as something to re-enable.
- Its API routes answer
403. A request that used to work stops working, and says why. - Its contributions stop. The dashboard revenue row, the Telegram report section, the topic in the settings list: absent, not zero. A section reading “0 orders” is a claim about the business; a section that is not there is a claim about the subscription, and only the second one is true.
2. Data stays exactly where it is
Nothing is deleted, archived, anonymised or moved. A hotel that re-subscribes finds its four hundred menu items, its categories, its photographs and its order history as it left them.
This is not sentiment. A module that empties itself on lapse is a module nobody will risk buying, because the cost of a failed card becomes the cost of rebuilding a catalogue.
3. Work in flight completes
An order placed before the lapse still reaches the kitchen. A booking made minutes ago still notifies. The subscription ended; the guest’s dinner did not, and the person who has already paid for it is not part of this dispute.
Guest-facing surfaces degrade deliberately
The rules above are about staff. A guest never learns anything about a hotel’s billing, and never meets a failure caused by it.
| Surface | On lapse |
|---|---|
| QR menu | A polite “this service is currently unavailable” page. Not a 404, not an error, not an empty menu that can be ordered from |
| Bronit TV | The welcome screen with the hotel’s wallpaper. Never a black screen |
| Anything else guest-facing | Whatever the calm version is. If in doubt, fewer features, never an error page |
The TV rule is the one to be careful about. A television in an occupied room is the most visible thing this product owns, and a black screen there is the worst possible way for a payment state to express itself — it does not read as “the hotel did not pay”, it reads as “the hotel is broken”, in front of the guest.
lapsed is not off
They grant the same access — none — and they are different situations. off is
a business that never bought the module. lapsed is a customer whose card
failed. The screens they should see are not the same: one is an offer, the other
is a renewal.
effectiveState() keeps them apart for exactly this reason, and any surface
that says something to the user should read it rather than the boolean.
Retention has a limit, and it is a decision
Data is kept for 12 months after a lapse, then eligible for deletion.
Two things make that a policy rather than a hope:
- The business is told before it happens, not after — at lapse, and again before the window closes.
- The deletion is a deliberate, logged action. There is no cron job quietly removing customer data on an anniversary.
Nothing implements deletion today, and that is the correct state: the first lapse is a year away from mattering, and a destructive job written now would run for eleven months without anyone checking it.
For whoever adds the next module
You inherit all of this. What you owe:
- No delete-on-lapse anywhere in your module.
- A guest-facing surface, if you have one, with a calm unavailable state.
- Your
ModuleContributionreturning nothing when the business does not have you — which is free, because core filters contributions by entitlement before asking.