CluPilotCloud/app
nexxo 8f1630ba91 Bill a booked module every month, and put it on the invoice
A module was never an item on the Stripe subscription, so it was charged in
the month it was booked and never again. And the renewal document was written
from our own contract snapshot, so it would not have said so even if the money
had been taken. Two halves of one fault, closed together.

Modules are items now. stripe:sync-catalogue mirrors a Product and a Price per
module on both terms — a Stripe Price carries its own interval, and a booking
frozen at an older figure needs its own Price, so they are keyed by module,
amount, currency and interval in stripe_addon_prices. Booking adds an item with
always_invoice: prorated by days AND charged there and then, which is the
invoice the owner wants for a module booked mid-term. A second storage pack is
a quantity on one item, not a second item. Cancelling takes the item off with
no proration — no credit, and the next cycle is simply smaller — while the
module itself is kept until the period end on subscription_addons.cancels_at
and ended by clupilot:end-cancelled-addons.

Documents are built from Stripe's own invoice lines. Stripe is what charged the
customer; a document assembled from our figures would state a sum that is not
the one taken. So every paid Stripe invoice gets one document with one number,
idempotent on stripe_invoice_id — a cycle carrying package and every module
together, a mid-period booking carrying just its prorated line, an upgrade
carrying its proration. Only the wording is ours: each line is named from the
catalogue rather than printed as a Price id, and a line nobody can name stays on
the document under Stripe's own description and is logged. The checkout's own
invoice is still the one exception; that purchase already has a document.

Stripe being away never undoes a booking that has already reached the machine.
The failure is parked on the contract in stripe_addon_sync and swept by the
same clupilot:sync-stripe-subscriptions that retries a plan change, which finds
its work from the bookings themselves rather than from the marker. A granted
contract has no Stripe subscription and is not touched at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 20:22:02 +02:00
..
Actions Bill a booked module every month, and put it on the invoice 2026-07-29 20:22:02 +02:00
Console Bill a booked module every month, and put it on the invoice 2026-07-29 20:22:02 +02:00
Exceptions Refuse a portal login for an address that already belongs to an operator 2026-07-28 14:42:16 +02:00
Http Let the customer buy it themselves, and tab the integrations page 2026-07-29 19:42:28 +02:00
Jobs Let the export have as many destinations as somebody wants 2026-07-29 02:43:15 +02:00
Listeners Recognise the devices an account signs in from, and warn about a new one 2026-07-28 23:28:34 +02:00
Livewire Write an invoice for work that came off no price list 2026-07-29 20:12:26 +02:00
Mail Give people a way back in, and put the URL in English 2026-07-29 16:50:29 +02:00
Models Bill a booked module every month, and put it on the invoice 2026-07-29 20:22:02 +02:00
Notifications Add the SSH identity to the vault, and give deployment config a console page 2026-07-29 00:52:44 +02:00
Observers Apply a bought plan change instead of only pricing it 2026-07-29 17:18:55 +02:00
Policies Move the console's identity out of the customer table 2026-07-28 10:31:43 +02:00
Providers Close the domain page to packages that may not have one 2026-07-29 16:20:21 +02:00
Provisioning Deliver the storage a customer actually buys 2026-07-29 19:13:10 +02:00
Services Bill a booked module every month, and put it on the invoice 2026-07-29 20:22:02 +02:00
Support Quote what a person pays, and tab the settings page 2026-07-29 19:18:10 +02:00