4 Commits (a3cbaaef61b3b80b2d57328de1eec357a95be22e)
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
bf95f9da8a |
Zweiter Sicherungsort ebenfalls aus dem Verkauf nehmen, eigener Schalter
tests / pest (push) Waiting to run
Details
tests / assets (push) Waiting to run
Details
tests / release (push) Blocked by required conditions
Details
extra_backups verspricht einen zweiten, getrennten Sicherungsort, den es nicht gibt (nur die erste, tägliche Sicherung läuft). Gesperrt nach dem Muster von collabora_pro, aber mit eigener Konstante (EXTRA_BACKUPS_ON_SALE statt OFFICE_ON_SALE) und eigenem Abweisungssatz, weil beide Module aus unterschiedlichen Gründen fehlen und zu unterschiedlichen Zeitpunkten zurückkommen. Keine Bestandsbuchung betroffen (subscription_addons enthielt auf dieser Maschine keine Zeile dazu). OpenWork-Eintrag von SOLD auf PLANNED umgestellt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
d3ac839b1e |
Office (collabora_pro) auch über die Abrechnungsseite sperren
tests / pest (push) Waiting to run
Details
tests / assets (push) Waiting to run
Details
tests / release (push) Blocked by required conditions
Details
Der Vortag nahm Office nur von der Preistafel — ein eingeloggter Team- oder Business-Kunde hätte collabora_pro technisch weiterhin über Billing::purchase() buchen können, weil AddonCatalogue den Preis unverändert kannte und die Abrechnungsseite nie danach fragte. Der Schalter (OFFICE_ON_SALE) wandert deshalb vom LandingController in AddonCatalogue, der einzigen Stelle, die Preistafel UND Abrechnungsseite kennen; eine neue Methode saleRefusal() weist die Buchung an beiden Enden ab (Kauf-Aufruf und Karten-Anzeige), ohne BookAddon/GrantAddon anzufassen — Bestandsfälle und Geschenke des Betreibers bleiben möglich. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
32a8bd9a88 |
Refuse the sale instead of reaching Stripe without a key
Task 5: Billing::purchase() now checks SecretVault for a stripe.secret before touching anything else — no customer, no plan lookup, no Order row — and the customer reads a plain sentence instead of a 500 page. HttpStripeClient's own secret() throws the same StripeNotConfigured exception rather than sending a request with an empty bearer token and letting Stripe's 401 stand in for the real cause. isConfigured() was fixed alongside it: it used to call secret() too, which would have turned every "is Stripe set up?" check (the checkout controller, the catalogue sync commands) into an uncaught exception the moment a key went missing — the opposite of what this task is for. It now reads the vault directly. The new guard is unconditional, including for the cart-only purchase types (storage, addon, plan changes) that never call Stripe themselves — a cart order nobody can ever pay for is pointless to create. That broke every existing Billing/Cart/Downgrade test that calls purchase() without a stored key; each affected fixture now deposits one via the new withStripeSecret() Pest helper. A few of those tests (CustomDomainAccessTest, DowngradeTest) were passing already but for the wrong reason — the new guard, not the check they were named for — since both only assert an order was NOT created; they get the same fixture fix so they still prove what they claim. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|
|
|
8f2252e81b |
Book a downgrade for a date that cannot move, and sell a module once
Two ways of charging a customer for something they did not get. A downgrade was due when `subscriptions.current_period_end` said so, and Stripe pushes that column forward on every renewal. So a downgrade booked in March was deferred to the end of April by April's renewal, and to the end of May by May's — each time by a whole term, and each time the customer was billed again for the package they had asked to leave. The due date was being re-derived from a moving target on every scheduler tick. It is decided once now, at the moment the customer decides, and stamped onto the contract: `pending_plan` and `pending_effective_at`, the two columns that have been in the schema since the first migration and were written by nothing at all. The shop stamps them, PlanChange reads them instead of the period end when a move is already booked, and clupilot:apply-due-plan-changes finds contracts by the stamped date rather than orders by a date it recomputes. The order stays what it always was — the customer's own record of the request, what the cart shows them and what they remove to change their mind — so it is still consumed with the change, and removing it unbooks the change. What it no longer does is decide when. A second downgrade replaces the first rather than queueing behind it; an upgrade clears one, or it would come due months later and undo the bigger package; a contract cancelled before the date takes the booking with it; and a customer with no contract can no longer place a downgrade at all, which was accepted before and could never be carried out. A booked change is now visible where the contract is read: on the plan card in the portal, because the cart entry disappears when it is paid for and the booking does not, and on the customer row in the console, because that is where an operator answers "what is this customer on". The second defect is the same money in one step: BookAddon guarded duplicates per ORDER — the unique index on (order_id, addon_key) — so two orders for the same module on one contract both went through. A stale tab or a double click bought priority support twice. But storage is deliberately sold in packs, and AddonCatalogue sums the quantities for exactly that reason, so "refuse the second" is only right for some of them. Which is which is declared beside each price in config/provisioning.php (`sold_as`), because it is a commercial decision about each module and not something to infer from a key: storage is a quantity; off-site backups are on or off, support is prioritised or it is not, Collabora Pro is one licence, and a machine answers to one own domain. An undeclared module counts as an entitlement — the cheaper of the two mistakes — and a test refuses to let one ship undeclared. Enforced in the action, where the portal, the console's grant screen and a webhook all pass, with the sentence the customer is shown rather than a developer's. A retried webhook for the SAME order still gets its one booking back; that is one purchase arriving twice, not two purchases. The portal stops offering what would be refused — booked, or already waiting in the cart — and goes on offering the pack. What is missing is on the Stripe side and is not invented here: nothing moves the subscription's item onto the new Price, so the renewal after a plan change still bills the old plan at Stripe. StripeClient only creates and archives catalogue objects, and swapping a price also needs the subscription ITEM id, which nothing stores. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |